Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/.idea
/result
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ cargo test # 115 tests; the workspace integration tests shell out t
cargo clippy --all-targets
```

## Nix

A flake is provided. It builds faff and wraps the binary so `jj` and `wezterm` are on
`PATH` at runtime (`claude` still comes from your interactive session, since faff spawns it
inside a WezTerm pane).

```sh
nix run github:Jezza/faff # run without installing
nix profile install github:Jezza/faff # install into your profile
nix develop # dev shell with the toolchain + jj + wezterm
```

To install declaratively, add the flake as an input and reference
`faff.packages.${system}.default`.

## Usage

From your repo root, inside WezTerm:
Expand Down
61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 79 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
description = "faff — a TUI for running several Claude Code agents in parallel on one repo";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = {
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {inherit system;};

# Runtime dependencies faff shells out to by bare name. jj drives the
# workspaces and wezterm drives the panes. `claude` is intentionally NOT
# here: faff spawns it via `wezterm cli spawn -- claude ...`, so it runs
# in the user's interactive shell inside the pane, not from faff's PATH.
runtimeDeps = [
pkgs.jujutsu # provides `jj`
pkgs.wezterm # provides `wezterm`
];

cargoToml = pkgs.lib.importTOML ./Cargo.toml;

faff = pkgs.rustPlatform.buildRustPackage {
pname = cargoToml.package.name;
version = cargoToml.package.version;

src = self;

cargoLock.lockFile = ./Cargo.lock;

nativeBuildInputs = [pkgs.makeWrapper];

# rusqlite's `bundled` feature compiles SQLite from C; the stdenv cc
# covers that, no extra buildInputs needed.

# The test suite (115 tests) shells out to a real `jj`, which is not
# available in the build sandbox, so skip it during the Nix build.
doCheck = false;

postInstall = ''
wrapProgram $out/bin/faff \
--prefix PATH : ${pkgs.lib.makeBinPath runtimeDeps}
'';

meta = {
description = "TUI for running several Claude Code agents in parallel on one repo, each in its own jj workspace and WezTerm pane";
homepage = "https://github.com/Jezza/faff";
mainProgram = "faff";
};
};
in {
packages.default = faff;
packages.faff = faff;

apps.default = {
type = "app";
program = "${faff}/bin/faff";
};

devShells.default = pkgs.mkShell {
inputsFrom = [faff];
packages =
runtimeDeps
++ [
pkgs.cargo
pkgs.rustc
pkgs.clippy
pkgs.rustfmt
];
};
}
);
}