diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ea0344e..77fb0c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -65,3 +65,13 @@ jobs:
done
exit 1
- run: zig build test-all -Doptimize=ReleaseSafe --summary all
+
+ nix:
+ name: nix build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: DeterminateSystems/nix-installer-action@v22
+ - run: nix flake check
+ - run: nix build -L
+ - run: nix run . -- version
diff --git a/.gitignore b/.gitignore
index dca1103..568b828 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
zig-out/
.zig-cache/
+result
+result-*
diff --git a/README.md b/README.md
index bbf9b4c..075e54b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,19 @@
-# boo
+
+
boo
-Sessions that haunt your terminal. A GNU `screen` style terminal
-multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty)
+Sessions that haunt your terminal.
+
+[Install](#install) | [Usage](#usage) | [Automation](#automation) | [Architecture](#architecture)
+
+[](https://github.com/coder/boo/actions/workflows/ci.yml)
+[](https://github.com/coder/boo/releases/latest)
+[](./LICENSE)
+[](https://discord.gg/coder)
+
+
+
+A GNU `screen` style terminal multiplexer built on
+[libghostty](https://github.com/ghostty-org/ghostty)
(`libghostty-vt`), written in Zig.
Every session's output is parsed through Ghostty's terminal emulation
@@ -37,6 +49,8 @@ exactly as a human would see it.
## Install
+### Install script
+
```sh
curl -fsSL https://raw.githubusercontent.com/coder/boo/main/install.sh | sh
```
@@ -48,6 +62,19 @@ Pre-built binaries for Linux (x86_64, aarch64; fully static) and macOS
install location (default: `/usr/local/bin` when writable, otherwise
`~/.local/bin`).
+### Nix
+
+With [flakes](https://wiki.nixos.org/wiki/Flakes) enabled:
+
+```sh
+nix run github:coder/boo # try it without installing
+nix profile add github:coder/boo # install into your profile
+```
+
+Or add `github:coder/boo` as an input to your own flake and reference
+`packages..default` from your NixOS, nix-darwin, or Home
+Manager configuration.
+
## Building
Requires [Zig](https://ziglang.org) 0.15.2.
@@ -62,6 +89,9 @@ zig build test-all # everything
The libghostty dependency is fetched and built from source
automatically (pinned in `build.zig.zon`).
+With Nix, `nix develop` opens a shell with the right Zig version, and
+`nix build` builds the package to `./result/bin/boo`.
+
## Usage
```sh
@@ -172,6 +202,27 @@ This is a young project, not a drop-in GNU screen replacement:
- No status line, monitoring, or copy mode yet.
- Sessions run with `TERM=xterm-256color`.
+## Support
+
+Feel free to [open an issue](https://github.com/coder/boo/issues/new)
+if you have questions, run into bugs, or have a feature request.
+
+[Join the Coder Discord](https://discord.gg/coder) to chat with the
+community.
+
+## Contributing
+
+Contributions are welcome:
+
+1. Fork and clone the repository.
+2. Make your change and cover it with tests.
+3. Run `zig build test-all` and
+ `zig fmt build.zig build.zig.zon src test`.
+4. Open a pull request against `main`.
+
+CI runs formatting checks, unit tests, and PTY integration tests on
+Linux and macOS, plus a Nix build.
+
## License
[MIT](LICENSE). Ghostty itself is MIT licensed.
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..9140dbc
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1780749050,
+ "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..313caa1
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,110 @@
+{
+ description = "Sessions that haunt your terminal. A GNU screen style terminal multiplexer built on libghostty.";
+
+ 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 = nixpkgs.legacyPackages.${system};
+ inherit (pkgs) lib;
+ zig = pkgs.zig_0_15;
+
+ # Single source of truth for the version is build.zig.zon.
+ version = builtins.head (
+ builtins.match ''.*\.version = "([^"]+)".*'' (builtins.readFile ./build.zig.zon)
+ );
+
+ # Zig package cache containing every dependency pinned in
+ # build.zig.zon (libghostty and its transitive dependencies).
+ # Pre-fetched as a fixed-output derivation so the sandboxed
+ # build below needs no network access. When dependencies in
+ # build.zig.zon change, update outputHash (set it to
+ # lib.fakeHash, build, and copy the hash from the error).
+ deps = pkgs.stdenvNoCC.mkDerivation {
+ pname = "boo-deps";
+ inherit version;
+ src = ./.;
+
+ nativeBuildInputs = [ zig ];
+
+ dontConfigure = true;
+ dontBuild = true;
+ dontFixup = true;
+
+ installPhase = ''
+ export ZIG_GLOBAL_CACHE_DIR="$TMPDIR/zig-cache"
+ # Dependency hosts intermittently fail; retry like CI
+ # does. Zig resumes from its cache, so completed fetches
+ # are not repeated and the output stays reproducible.
+ for i in 1 2 3 4 5; do
+ zig build --fetch=all && break
+ if [ "$i" = 5 ]; then exit 1; fi
+ echo "fetch attempt $i failed; retrying in 10s" >&2
+ sleep 10
+ done
+ mv "$ZIG_GLOBAL_CACHE_DIR/p" "$out"
+ '';
+
+ impureEnvVars = lib.fetchers.proxyImpureEnvVars;
+ SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ outputHashMode = "recursive";
+ outputHashAlgo = "sha256";
+ outputHash = "sha256-2dZHdZoAap25va9ka2SN5QqoQ2xcZITJKNzwfOGmvus=";
+ };
+
+ boo = pkgs.stdenv.mkDerivation {
+ pname = "boo";
+ inherit version;
+ src = ./.;
+
+ nativeBuildInputs = [ zig.hook ];
+
+ # zig.hook builds with --release=safe, matching the
+ # optimization mode of the published release binaries.
+ #
+ # The dependency cache is copied, not symlinked: some
+ # ghostty build steps run helper executables with a working
+ # directory inside the package cache and locate their
+ # outputs via relative paths, which resolve incorrectly
+ # through a symlink into the store.
+ postConfigure = ''
+ cp -r --no-preserve=mode ${deps} "$ZIG_GLOBAL_CACHE_DIR/p"
+ '';
+
+ # Runs `zig build test` (unit tests; no TTY required). The
+ # PTY integration tests stay in CI via `zig build test-all`.
+ doCheck = true;
+
+ meta = {
+ description = "Sessions that haunt your terminal. A GNU screen style terminal multiplexer built on libghostty";
+ homepage = "https://github.com/coder/boo";
+ license = lib.licenses.mit;
+ mainProgram = "boo";
+ platforms = lib.platforms.linux ++ lib.platforms.darwin;
+ };
+ };
+ in
+ {
+ packages = {
+ default = boo;
+ inherit boo;
+ };
+
+ devShells.default = pkgs.mkShell {
+ packages = [ zig ];
+ };
+
+ formatter = pkgs.nixfmt-tree;
+ }
+ );
+}