{"id":50402289,"url":"https://github.com/avinashjoshi/canopy","last_synced_at":"2026-05-31T00:02:56.912Z","repository":{"id":355060225,"uuid":"1223062741","full_name":"avinashjoshi/canopy","owner":"avinashjoshi","description":"TUI for managing git worktrees with paired tmux sessions and per-project setup hooks.","archived":false,"fork":false,"pushed_at":"2026-05-27T00:59:59.000Z","size":2364,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T02:20:33.030Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avinashjoshi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-28T01:18:04.000Z","updated_at":"2026-05-27T01:00:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/avinashjoshi/canopy","commit_stats":null,"previous_names":["avinashjoshi/canopy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/avinashjoshi/canopy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avinashjoshi%2Fcanopy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avinashjoshi%2Fcanopy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avinashjoshi%2Fcanopy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avinashjoshi%2Fcanopy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avinashjoshi","download_url":"https://codeload.github.com/avinashjoshi/canopy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avinashjoshi%2Fcanopy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33714034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T00:02:53.869Z","updated_at":"2026-05-31T00:02:56.906Z","avatar_url":"https://github.com/avinashjoshi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"```\n   _____\n  / ____|\n | |     __ _ _ __   ___  _ __  _   _\n | |    / _` | '_ \\ / _ \\| '_ \\| | | |\n | |___| (_| | | | | (_) | |_) | |_| |\n  \\_____\\__,_|_| |_|\\___/| .__/ \\__, |\n                         | |     __/ |\n                         |_|    |___/\n```\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/avinashjoshi/canopy.svg)](https://pkg.go.dev/github.com/avinashjoshi/canopy)\n[![Go Report Card](https://goreportcard.com/badge/github.com/avinashjoshi/canopy)](https://goreportcard.com/report/github.com/avinashjoshi/canopy)\n[![Tests](https://github.com/avinashjoshi/canopy/actions/workflows/test.yml/badge.svg)](https://github.com/avinashjoshi/canopy/actions/workflows/test.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n**TUI for managing git worktrees with paired tmux sessions, per-project setup hooks, and remote-host dispatch.**\n\n\u003e Status: v0.20, daily-driven by the author. APIs and on-disk state may still shift before v1.\n\n![canopy TUI: Workspaces tab listing workspaces across multiple projects + one remote host, each with port, memory, agent badge, and PR status](docs/images/tui-workspaces.png)\n\n`canopy new` and ten seconds later you're attached to a tmux session with `nvim`, `claude`, and a shell, all on a fresh git worktree against an isolated database with its own port. Reboot your laptop, `canopy switch \u003cname\u003e`, and you're back exactly where you left off — claude conversation included. Run `canopy new --on tower --prompt \"fix the timezone bug\"` and the same workspace lands on a beefier remote box while you keep working on your laptop.\n\n## Why canopy?\n\nAI-paired development means many parallel branches in flight at once: one agent refactoring auth, another fixing the timezone bug, plus the feature you're driving by hand. Raw `git worktree` + ad-hoc `tmux new-session` doesn't scale past three. Canopy is the missing orchestrator: per-workspace ports, per-workspace databases via `scripts.setup`, per-workspace tmux sessions with the same layout every time, agent-state badges so you can see which agent needs you, and one TUI that views every workspace across every host. See [`docs/landscape.md`](docs/landscape.md) for where canopy sits next to Conductor, tmuxinator, raw `git worktree`, and the agent CLIs it hosts.\n\n## What's new in v0.20\n\n- **Add a project from anywhere.** `canopy init` now accepts a folder path or a git URL, so you can register a project without `cd`-ing into it: `canopy init ~/code/foo` or `canopy init https://github.com/foo/bar.git`. URL form clones into your configured source-root (default `~/.canopy/sources`, override via `canopy config set source-root ~/Work`) and registers in one shot. A new TUI **Add Project** form lives on the splash and on the Global tab (`a` keybind), with `Tab` to cycle between local and registered hosts. See [`docs/getting-started.md`](docs/getting-started.md).\n- **Remote-host init.** `canopy init \u003cgit-url\u003e --on tower` dispatches the clone+init to the remote canopy via SSH (reusing v0.17's ControlMaster plumbing) and auto-registers the new project in the laptop's `hosts.json` so the next `canopy new --on tower` resolves cleanly — no more manual `canopy project add` after init.\n- **`canopy config` subcommand.** Persistent user-level settings at `~/.canopy/config.json`. First key is `source-root`. Precedence: per-call dest \u003e `$CANOPY_SOURCE_ROOT` env \u003e config file \u003e built-in default. Get/set/list/unset, with `(env)` / `(config)` / `(default)` source labels so you can debug why a value isn't taking effect. Press `,` from any TUI tab to open the settings modal.\n\n## Previous releases\n\n- **v0.17** — Remote workspaces. Register an SSH-reachable host once with `canopy host add tower cassy@tower.tail.ts.net`, then run `canopy new --on tower` from your laptop. The heavy work runs on the host; one TUI views every workspace across every machine. See [`docs/remote-workspaces.md`](docs/remote-workspaces.md). Plus fire-and-forget agents (`canopy new --prompt \"...\" --no-attach`), in-TUI `canopy upgrade`, and workspace identity that follows the branch (rename via `git branch -m` and canopy's tmux session, statusline, terminal-tab title, and TUI rows pick up the new name within 15 seconds).\n- **v0.18 / v0.19** — TUI picker for `canopy use`; remote workspace observability (live ⚡ claude badge across SSH, ⊙ attached-client indicator, confirm-attach modal for remote rows, \"⚠ stale Ns\" pill when refresh data goes cold).\n\n## Features\n\nFrom inside any project that has a `canopy.json`:\n\n```bash\ncanopy new                              # workspace with a random name (e.g. bold-falcon)\ncanopy new --name fix-bug               # explicit name\ncanopy new --prompt \"fix the bug\" --no-attach   # fire-and-forget claude\ncanopy new --pr 1214                    # check out a GitHub PR into a workspace\ncanopy new --issue 42                   # fresh branch, briefing seeded from issue body\ncanopy new --branch existing-feature    # check out an existing remote branch\ncanopy new --on tower                   # dispatch to a remote host\ncanopy init \u003curl\u003e                       # clone a git URL + init in one shot (v0.20)\ncanopy init \u003curl\u003e --on tower            # same, dispatched to a remote host (v0.20)\ncanopy main                             # tmux session anchored at the project root\ncanopy ls                               # workspaces in the current project\ncanopy ls --all                         # everything across every project + remote host\ncanopy switch \u003cname\u003e                    # attach (resurrect first if stopped)\ncanopy switch --on tower foo            # attach over mosh+tmux to a remote workspace\ncanopy rm \u003cname\u003e                        # tear down (archive script + tmux + git + branch)\ncanopy retry \u003cname\u003e                     # re-run scripts.setup on a broken workspace\ncanopy rename [\u003cname\u003e]                  # sync labels to the current branch (--pin/--unpin)\ncanopy reconcile                        # update statuses to match disk + tmux reality\n```\n\nEach workspace gets a 3-pane tmux session: `nvim` top-left, an agent (`claude` by default, with `--continue` on resurrect) top-right, and a shell full-width on the bottom. `scripts.run` (your dev server) launches on demand via `canopy run` rather than auto-starting — that way a stopped workspace resurrects to the same layout without a port collision.\n\nWorkspaces live at `~/.canopy/workspaces/\u003cproject\u003e/\u003cname\u003e` — canopy owns the storage so your source repo stays clean — and each one gets a unique TCP port via `CANOPY_PORT`.\n\n`canopy` with no args launches a Bubbletea TUI: tabbed views (current project / Global / Remote hosts), arrow-key navigation, `enter` to attach, `n` to create, `d` to delete, `i` to inspect, `U` to upgrade, `?` for help. CLI subcommands work alongside it; both call into the same `workspace.Manager` underneath.\n\nPlus operational glue:\n\n- `canopy init [path-or-url] [dest]` — onboard a project. Three shapes: `canopy init` inits the cwd (creates `canopy.json` + stub `bin/canopy-*` scripts; detects `conductor.json` and adopts its schema); `canopy init ~/code/foo` inits a folder without `cd`-ing in; `canopy init \u003cgit-url\u003e` clones + inits in one shot. Add `--on \u003chost\u003e` to dispatch the whole flow to a registered remote canopy. (v0.20)\n- `canopy config set|get|list|unset` — user-level prefs at `~/.canopy/config.json`. First key is `source-root` (where `canopy init \u003curl\u003e` clones). Env override: `CANOPY_SOURCE_ROOT`. (v0.20)\n- `canopy host add \u003cname\u003e \u003cssh-target\u003e` — register a remote canopy host (with `--interactive` for a guided form)\n- `canopy project add \u003cname\u003e \u003cpath\u003e --on \u003chost\u003e` — bind a project name to a path on a remote (auto-populated by `canopy init \u003curl\u003e --on \u003chost\u003e` in v0.20+)\n- `canopy install tmux` — write managed keybinds + statusline into `~/.tmux.conf` (idempotent, backed up)\n- `canopy upgrade` — fetch + build the latest release; `--check` for a dry run, `--dismiss` to silence pills until next release\n- `canopy use [target]` — flip the active canopy binary between `release` and any in-flight workspace's `./canopy`\n- `canopy version` — version, commit, build date, active binary, DEV vs release\n- `canopy --debug` — DEBUG-level JSON logs to `~/.canopy/log/canopy.log` (auto-rotated: 10 MB / 3 backups / 28 days / gzip)\n\n### Agent-state badges\n\nEvery workspace row in the TUI carries a small badge showing what the agent pane is doing — polled every 2 seconds across every workspace, local AND remote:\n\n| Badge | Meaning |\n|---|---|\n| `⚡` (cyan) | claude is thinking |\n| `💤` (gray) | claude is idle, ready for your next message |\n| `✋` (yellow) | claude is awaiting input (y/N or tool-permission popup blocking) |\n| `·` (subtle) | workspace has no agent pane (or pane crashed to shell) |\n\nCombined with `canopy new --prompt \"...\" --no-attach`, this turns canopy into a triage queue: spawn three claudes in parallel, do something else, glance at the TUI to see which one wants you.\n\n### Workspace health badges\n\nThe TUI's HINTS column surfaces problems before they bite. All inferred from `git` plumbing on every refresh — no extra config:\n\n| Badge | Meaning |\n|---|---|\n| `⚠ conflict` | a merge conflict against `origin/\u003cdefault\u003e` is waiting for you |\n| `⚠ rebasing` / `merging` / `pick` / `detached` | git is mid-operation; finish or abort it before doing more |\n| `↑N ↓N *N` | N commits ahead of `origin/\u003cdefault\u003e`, N behind, N dirty files |\n| `⇡N` / `⇅` | N commits unpushed to your branch's upstream, or upstream has diverged |\n| `↗ rename-suggested` | branch is still on a namegen name (`bold-falcon`) but you've made progress — commits past `origin/\u003cdefault\u003e` OR tracked-file edits (untracked noise excluded). Try `canopy rename`. |\n| PR status | open / approved / merged / closed (via `gh pr view`, polled out of band) |\n\nStuck-state badges (rebasing, merging, etc.) preempt the `↑N ↓N *N` numbers because those numbers reflect git's transient internal state during the operation.\n\n### Port allocation\n\nEvery workspace gets a unique TCP port via `CANOPY_PORT`, allocated through a Conductor-style block plan:\n\n- Each project's first workspace lands on `base_port` (default 40000).\n- Subsequent workspaces in the same project step up by `workspace_stride` (default 10): 40010, 40020, 40030, ...\n- A new project's first workspace lands `project_stride` higher than the previous project (default 1000): canopy → 40000, cravd → 41000, hey-cli → 42000.\n\nProject-to-base assignments are first-come-first-served and persisted in `state.json`, so a workspace's port is stable across reboots.\n\nDefaults are tweakable via `~/.canopy/config.json` (optional file):\n\n```json\n{\n  \"ports\": {\n    \"base\": 40000,\n    \"project_stride\": 1000,\n    \"workspace_stride\": 10\n  }\n}\n```\n\nPartial overrides are fine — any field you skip stays at the default.\n\n### tmux integration\n\nIf you live in tmux, three extra subcommands turn canopy into an always-one-keystroke-away workspace switcher and a glanceable status widget. All are inside-tmux-only.\n\n**One-shot install:**\n\n```bash\ncanopy install tmux       # writes managed block to ~/.tmux.conf (with backup)\ntmux source-file ~/.tmux.conf\n```\n\nThat's it. The installer is idempotent (refuses if already present; `--force` replaces in place), backs up `~/.tmux.conf` before any change, and writes a clearly-marked managed block so you can see what canopy added:\n\n```tmux\n# canopy:start (managed by `canopy install tmux` — edit only outside markers)\nbind g run-shell \"canopy popup\"\nbind -n C-M-c display-popup -E \"CANOPY_IN_POPUP=1 canopy\"\nset -ag status-right \" #(canopy statusline --format=current) \"\nset -g status-left-length 50\nset -g set-titles on\n# canopy:end\n```\n\n**What you get:**\n\n- **`canopy popup`** — `\u003cprefix\u003eg` opens the global TUI in a tmux floating popup. Tabs: **\u003cproject\u003e** (current project's workspaces, default if launched from inside a project), **Global** (everything), **Remote hosts** (registered SSH boxes). `Tab` cycles tabs, `/` enters fuzzy search, `Enter` switches to the selected workspace. Requires tmux 3.2+.\n- **`Ctrl+Alt+c`** — no-prefix global chord that launches the same popup. One keystroke from anywhere in your terminal.\n- **`canopy run`** — `\u003cprefix\u003er` execs `scripts.run` (e.g. `bin/dev`) from the nearest `canopy.json` in a tmux popup. Inherits `CANOPY_PORT` and friends from the workspace tmux session.\n- **`canopy statusline --format=current`** — appended to `status-right`, shows `\u003cproject\u003e / \u003cbranch\u003e \u003cglyph\u003e :\u003cport\u003e` when you're attached to a canopy workspace's tmux session, and empty otherwise. When the workspace folder name and branch have diverged (after `git branch -m`), both are shown: `\u003cproject\u003e / \u003cwsName\u003e / \u003cbranch\u003e`. When you're attached to a remote canopy via `canopy switch --on \u003chost\u003e`, a yellow `@\u003chost\u003e` pill prefixes the line so you can tell at a glance you're working on a remote machine. Errors never propagate to stdout — your status bar stays clean even if state.json is corrupt or canopy crashes.\n\n**Manual install:** paste the block above into `~/.tmux.conf` and `source-file` it.\n\n### Remote workspaces (v0.17)\n\nWorkspaces don't have to live on your laptop. Register an SSH-reachable host once:\n\n```bash\ncanopy host add tower cassy@tower.tail.ts.net\ncanopy host add tower --interactive          # guided form, runs ssh-copy-id if needed\ncanopy host ls\n```\n\nBind a project to a remote path:\n\n```bash\ncanopy project add cravd /home/cassy/Work/cravd --on tower\ncanopy project ls\n```\n\nThen dispatch from anywhere:\n\n```bash\ncanopy new --on tower                                 # uses the project path from the registry\ncanopy new --on tower --prompt \"fix the bug\"          # remote fire-and-forget; prompt travels via base64+temp file, never in `ps`\ncanopy switch --on tower fix-bug                      # attaches via mosh+tmux (UDP, suspend-tolerant)\ncanopy switch --on tower fix-bug --share              # multi-attach instead of stealing\ncanopy rm --on tower fix-bug                          # remote teardown\n```\n\nThe TUI's **Remote hosts** tab shows every registered host with a live version, last-seen timestamp, and per-host workspace count:\n\n![canopy TUI: Remote hosts tab listing two registered SSH hosts with version, workspace count, and last-seen timestamp](docs/images/tui-hosts.png)\n\nFrom there:\n\n- `enter` opens a detail drawer with SSH target, projects, last-error\n- `n` opens the in-TUI add-workspace picker (Fresh / Prompt / PR / Issue / Branch — parity with local; PR/Issue/Branch loaders SSH `gh` and `git for-each-ref` against the remote project cwd)\n- `s` drops you into an interactive `ssh` shell on the host (y/N gate first, refreshes when you `exit`)\n- `U` runs `canopy upgrade --yes` on the remote, streaming output to the TUI\n- `S` runs `canopy use release` on the remote (recovery path for hosts running a DEV binary)\n- `a` re-runs `ssh-copy-id` if a host lost key auth\n- `d` removes a host (with `F` to force-delete a remote workspace whose worktree is hanging)\n\nEnd-to-end guide with worked examples: [`docs/remote-workspaces.md`](docs/remote-workspaces.md).\n\nSample output of `canopy ls --all` with one local and one remote project:\n\n```\nTMUX  PROJECT   NAME           BRANCH                    STATUS  PORT   SESSION\n●     canopy    (main)         —                         main    40000  canopy/main\n●     canopy    polite-vale    update-readme-and-docs    ready   40010  canopy/update-readme-and-docs\n●     cravd     (main)         —                         main    41000  cravd/main\n●     cravd     pr-1214        pd/follow-up-strategies   ready   41020  cravd/pd-follow-up-strategies\n↗     tower     foo            fix-the-bug               ready   40010  canopy/fix-the-bug\n```\n\nThe `↗` glyph in the TMUX column marks rows that live on a remote host.\n\n## Install\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/avinashjoshi/canopy/main/install.sh | sh\n```\n\nThat clones canopy to `~/.canopy/src`, runs `make install` (which writes the binary to `~/.local/bin/canopy.bin` and symlinks `~/.local/bin/canopy` at it), and prints a PATH hint if `~/.local/bin` isn't on your shell's PATH.\n\nIdempotent: re-running on a machine that already has canopy installed prints \"looks like canopy is already installed, run canopy upgrade instead\" and exits 0.\n\n### Prerequisites\n\n| Tool | Version | Why |\n|---|---|---|\n| `git` | 2.x+ | worktree creation per workspace |\n| `tmux` | 3.2+ | display-popup support (canopy popup keybind needs this) |\n| `go` | 1.22+ | canopy is built from source on install |\n| `make` | any | drives the install pipeline |\n| `mosh` | 1.4+ (optional) | remote workspace attach (`canopy switch --on \u003chost\u003e`) |\n| `ssh` | OpenSSH 8.x+ | remote dispatch + ControlMaster reuse |\n\n`install.sh` enforces the required ones — if any are missing, it prints the exact install command for your OS and exits cleanly. Per-platform install lines:\n\n- **Arch / CachyOS / Omarchy:** `sudo pacman -S git tmux neovim go mosh`\n- **Debian / Ubuntu:** `sudo apt-get install git tmux neovim golang-go make mosh`\n- **macOS:** `brew install git tmux neovim go mosh`\n- **Windows:** canopy needs tmux, which doesn't run natively. Use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) and run the Debian/Ubuntu line inside the Linux shell.\n\nCanopy also expects `nvim` and `claude` ([Claude Code](https://docs.claude.com/en/docs/claude-code)) for the default tmux-pane layout — but workspaces can run anything (codex, aider, opencode), so these aren't checked at install time.\n\n### Update\n\n```bash\ncanopy upgrade\n```\n\nThat fetches the latest VERSION from `main`, compares with what you're running, prints the CHANGELOG diff, and runs `git pull --ff-only \u0026\u0026 make install` in `~/.canopy/src`. Refuses cleanly if you're on a dev binary (`canopy use release` first) or if `~/.canopy/src` is missing/corrupt (re-run install.sh).\n\nFlags:\n- `canopy upgrade --check` — compare versions without upgrading\n- `canopy upgrade --force` — run `git pull` + `make install` even when versions match\n- `canopy upgrade --yes` — skip the changelog-confirm prompt (used by in-TUI upgrade)\n- `canopy upgrade --dismiss` — silence the in-TUI upgrade pill until the next release ships\n\nCanopy also auto-checks once every 6 hours in the background. When a newer release is out, the TUI's top-bar version pill mutates from `v0.17.1.0` to `v0.17.1.0 ⇑ v0.17.2.0` (yellow arrow), and `canopy ls` ends with one dim hint line. Press `U` inside the TUI to read the changelog in a scrollable viewport and run the upgrade without leaving canopy. Press `D` to dismiss the current available version. On the **Remote hosts** tab, `U` upgrades the selected remote (same streaming UX over SSH).\n\n### Uninstall\n\n```bash\nmake -C ~/.canopy/src uninstall    # remove ~/.local/bin/canopy{,.bin}\nrm -rf ~/.canopy                    # remove the source clone, workspaces, state, and logs\n```\n\nThe second line is destructive — it nukes every workspace on disk too. Only run it when you really mean to wipe canopy entirely.\n\n### Verify\n\n```bash\ncanopy version\n```\n\nOutput looks like:\n\n```\ncanopy v0.17.1.0+abc1234\n  binary:    /home/you/.local/bin/canopy -\u003e canopy.bin\n  commit:    abc1234\n  built:     2026-05-13T12:34:56Z\n  mode:      release\n```\n\nIf you see `command not found`, `~/.local/bin` isn't on your PATH:\n\n```bash\nexport PATH=\"$HOME/.local/bin:$PATH\"\n```\n\n## Onboarding a project\n\nRun `canopy init` from your project root:\n\n```bash\ncd ~/Work/your-project\ncanopy init                       # writes canopy.json (no scripts)\ncanopy init --with-scripts        # also writes bin/canopy-{setup,run,archive} stubs\n```\n\nEdit the scripts, commit them, then run `canopy new`.\n\nIf the project already has a `conductor.json` (Conductor's config — same schema), `canopy init` detects it and copies the script paths verbatim. Your existing `bin/conductor-*` scripts keep working; `CONDUCTOR_*` env vars are exported alongside the canonical `CANOPY_*` ones so the scripts don't need changes. See [`docs/migrate-from-conductor.md`](docs/migrate-from-conductor.md).\n\n### canopy.json schema\n\n```json\n{\n  \"scripts\": {\n    \"setup\": \"bin/canopy-setup\",\n    \"run\": \"bin/dev\",\n    \"archive\": \"bin/canopy-archive\"\n  }\n}\n```\n\nThree script paths, all optional. Each script gets the same env vars when canopy invokes it:\n\n| Var | Meaning |\n|---|---|\n| `CANOPY_WORKSPACE_PATH` | absolute path to the workspace dir |\n| `CANOPY_ROOT_PATH` | absolute path to the original repo root |\n| `CANOPY_PORT` | allocated TCP port for this workspace |\n\n`setup` runs once at workspace creation; failure flips the workspace to `broken` status (recoverable via `canopy retry`). `run` is the long-running server command, launched on demand by `canopy run` (or `\u003cprefix\u003er` inside tmux). `archive` runs at workspace removal, before the worktree is deleted.\n\nFull reference, including idempotency tips: [`docs/canopy-json.md`](docs/canopy-json.md).\n\n## End-to-end walkthrough\n\n```bash\n# 1. Install\ncurl -fsSL https://raw.githubusercontent.com/avinashjoshi/canopy/main/install.sh | sh\ncanopy version\n\n# 2. Onboard a project\ncd ~/Work/your-project\ncanopy init --with-scripts\n# … edit bin/canopy-setup, bin/dev, bin/canopy-archive …\ngit add canopy.json bin/canopy-* \u0026\u0026 git commit -m \"canopy onboarding\"\n\n# 3. Install tmux keybinds\ncanopy install tmux\ntmux source-file ~/.tmux.conf\n\n# 4. Create a workspace\ncanopy new --name fix-timezone\n# … you're now attached to a 3-pane tmux session at\n#   ~/.canopy/workspaces/your-project/fix-timezone …\n\n# 5. Fire-and-forget a parallel claude\ncanopy new --prompt \"fix the timezone bug in app/models/booking.rb\" --no-attach\n\n# 6. Glance at the queue\ncanopy ls\n# Press Ctrl+Alt+c (or \u003cprefix\u003eg in tmux) to open the TUI popup instead\n\n# 7. Resurrect after reboot\ncanopy switch fix-timezone\n# Same layout, claude --continue picks up the conversation\n\n# 8. Ship the workspace\ngh pr create\ncanopy rm fix-timezone    # archive script, drop branch, free port\n\n# 9. (Optional) Add a remote host for heavy work\ncanopy host add tower cassy@tower.tail.ts.net\ncanopy project add your-project /home/cassy/Work/your-project --on tower\ncanopy new --on tower --prompt \"expensive refactor\" --no-attach\ncanopy switch --on tower expensive-refactor    # mosh+tmux attach\n```\n\n## Contributing\n\nBug reports and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code conventions, and PR flow.\n\nIf you're hacking on canopy itself, you'll have multiple worktrees in flight (one per feature). The active `canopy` on PATH is a symlink — flip it between the released binary and any in-flight feature build with one command, no rebuild on the way back.\n\n```bash\ncanopy use                       # show current target + list available\ncanopy use release               # symlink → ~/.local/bin/canopy.bin\ncanopy use feature-A             # symlink → workspace feature-A's ./canopy\ncanopy use --build feature-A     # build feature-A's ./canopy first, then switch\n```\n\n`make dev` (from inside a worktree) is the muscle-memory wrapper for \"build this and make it active\"; `make release` flips back to the released binary without a rebuild. The active binary shows up as a `DEV: \u003cworkspace\u003e` pill in the TUI top bar and a `[DEV:\u003cworkspace\u003e]` suffix in the tmux statusline.\n\nConvention: only run `make install` from main. From feature branches, `make dev` is the right tool — it doesn't touch the released `canopy.bin`, so parallel agents in other worktrees aren't affected.\n\nFull make-task list:\n\n```\nmake build          # build ./canopy in the worktree (no install, no symlink change)\nmake install        # build with ldflags, install to ~/.local/bin/canopy.bin, symlink canopy → canopy.bin\nmake dev            # build + flip ~/.local/bin/canopy at this worktree's ./canopy\nmake release        # flip ~/.local/bin/canopy back at canopy.bin (no rebuild)\nmake test           # fast unit tests\nmake test-e2e       # full E2E suite (real tmux, scratch repo, slow)\nmake lint           # golangci-lint if installed\nmake uninstall      # remove ~/.local/bin/canopy and canopy.bin\nmake clean          # remove ./canopy in the worktree\n```\n\n## Documentation\n\nUser-facing guides:\n\n- [`docs/getting-started.md`](docs/getting-started.md) — 5-minute tour: install, init, first workspace\n- [`docs/remote-workspaces.md`](docs/remote-workspaces.md) — end-to-end guide for v0.17 remote dispatch\n- [`docs/landscape.md`](docs/landscape.md) — where canopy fits next to Conductor, tmuxinator, raw `git worktree`, and the agent CLIs it hosts\n- [`docs/canopy-json.md`](docs/canopy-json.md) — schema reference + `~/.canopy/config.json` settings\n- [`docs/migrate-from-conductor.md`](docs/migrate-from-conductor.md) — step-by-step for projects with `conductor.json`\n- [`docs/troubleshooting.md`](docs/troubleshooting.md) — common problems and fixes (local and remote)\n- [`CHANGELOG.md`](CHANGELOG.md) — release notes\n\nFor contributors:\n\n- [`CONTRIBUTING.md`](CONTRIBUTING.md) — setup, code conventions, PR flow\n- [`docs/architecture.md`](docs/architecture.md) — codebase layout, dependency direction, where to add things\n- [`docs/design/v0-canopy.md`](docs/design/v0-canopy.md) — design doc with premises, state machine, error conventions\n- [`docs/reviews/v0-test-plan.md`](docs/reviews/v0-test-plan.md) — test coverage plan and critical concurrency tests\n- [`TODOS.md`](TODOS.md) — deferred work, organized by milestone\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favinashjoshi%2Fcanopy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favinashjoshi%2Fcanopy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favinashjoshi%2Fcanopy/lists"}