░▒▓▓▒░ ░▒▒▓▒░
░▒▓▓▓▓▓▓▒▓▓▓▒▒▒▓▓▓▒▓▓▓▓▓▒░
░▒▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▓▓▓▒
▒▓▓▓▓▓▓▓▓▓▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▓▓▓▓▓▓▓▓
▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
░░░ ░ ░░░ ░ ░░░ ░░░
┃┃ ███ ┃┃ █████ ┃┃
┃┃ ╱███ ┃┃ █████ ┃┃ ╲╲
┃┃ ███ ┃┃ █████ ┃┃ ╲╲
══╩╩════╩╩╩════╩╩═══╩╩╩╩╩═══╩╩══════╩╩
██████╗ █████╗ ███╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗
██╔══██╗██╔══██╗████╗ ██║╚██╗ ██╔╝██╔══██╗████╗ ██║
██████╔╝███████║██╔██╗ ██║ ╚████╔╝ ███████║██╔██╗ ██║
██╔══██╗██╔══██║██║╚██╗██║ ╚██╔╝ ██╔══██║██║╚██╗██║
██████╔╝██║ ██║██║ ╚████║ ██║ ██║ ██║██║ ╚████║
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝
tmux + git worktrees + Claude Code, N features in parallel across N repos.
Running multiple AI agents in parallel on the same project means each one needs:
- its own dev ports — agent B can't bind
:8080if agent A already did - its own git worktree per repo so branches don't fight each other
- its own DB / compose stack — you don't want agent B writing to agent A's MySQL
- its own
.envvalues (auth tokens, feature flags) and the run process actually loading them - a Claude session with
--add-dirwired to every repo of the project, not just one - and eventually, merging N branches across N repos without stepping on each other
That's 20–30 minutes of plumbing per agent, every time. So in practice most people just don't — they switch context, one feature at a time, with the rest of the stack idle.
bn myproject wt profile-pageflowchart TB
cmd["bn myproject wt profile-page"] --> agent
agent(["Claude agent<br/>--add-dir on every repo"])
agent --> front["front worktree<br/>📦 :3001"]
agent --> back["back worktree<br/>📦 :8081 + isolated DB"]
agent --> app["app worktree<br/>📦 adb-reverse"]
One feature branch, three worktrees, one agent that sees them all, dev ports allocated dynamically. Talk to the agent in its tmux pane.
bn myproject start profile-pageRuns ./gradlew bootRun, npm run dev, the Android install — each in its own pane, on its own port. Cross-repo env wiring is automatic: the front's REACT_APP_API_URL already points at this feature's back, not someone else's.
bn myproject merge profile-page
bn myproject cleanup profile-pageRebase, push, MR/PR, auto-resolve cross-feature conflicts, merge. Then full teardown: stop processes, remove worktrees, delete branches, drop DB volumes.
flowchart TB
O(["orchestrator agent<br/>sees every worktree<br/>predicts conflicts · drives merges"])
O -.-> F1
O -.-> F2
O -.-> F3
O -.-> F10
F1["<b>profile-page</b><br/>agent · :3001 :8081<br/>own DB"]
F2["<b>tag-filter</b><br/>agent · :3002 :8082<br/>own DB"]
F3["<b>auth-redirect</b><br/>agent · :3003 :8083<br/>own DB"]
F10["...<br/>up to N features"]
Different worktrees, different ports, different DBs, different agents. An orchestrator above the lot watches files × features overlap in real time and tells you the safe merge order.
npm install -g banyan-cli
bn doctorbn doctor walks your environment and prints exactly what's missing plus the command to fix each thing. Run it any time the setup feels off.
Needs Node ≥ 20, tmux ≥ 3, git ≥ 2.5, and the Claude Code CLI. Optional: Docker (compose stacks), gh/glab (merges).
An OpenRouter API key (free tier works fine) is needed if you want bn wt -p "<prompt>" to auto-name the branch from the prompt. Set via $OPENROUTER_API_KEY or ~/.config/banyan/config.yaml under llm.openrouterApiKey. Skip it if you always pass an explicit feature name.
bn init will offer to wire its tmux keybindings into your ~/.tmux.conf on first run.
Install from source (for contributors)
git clone https://github.com/LoicBch/banyan-cli
cd banyan-cli && npm install && npm run build && npm linkSet up your first project from the dashboard wizard:
bn serve
# http://localhost:4242 → "+ new project"Or for power users:
cd ~/front && bn init my-project # bootstrap with the first repo
bn serve # add the remaining repos from the dashboard
bn my-project start$ bn myproject wt profile-page
✓ feature/profile-page worktrees created in front, back, app
✓ .env.local seeded into each worktree
✓ compose stack: myproject-profile-page (mysql:33061)
✓ agent pane opened (mode=live)
$ bn myproject start profile-page
back : SERVER_PORT=8081 JWT_SECRET=… ./gradlew bootRun
front : PORT=3001 REACT_APP_API_URL=http://localhost:8081 npm run dev
app : ./gradlew :app:installDebug (adb reverse 8080→8081)
# A bug comes in. Don't touch profile-page — open a parallel context:
$ bn myproject wt tag-filter -p "fix infinite loop on tag filter"
✓ feature/tag-filter worktrees, agent (mode=delegated), stack on :8082
$ bn myproject status
session 'banyan-myproject': running
features:
profile-page live agent: live stack: running
tag-filter delegated agent: live stack: running stage: planning
…
$ bn myproject merge tag-filter && bn myproject cleanup tag-filter
✓ rebase clean · pushed · MR merged · stack destroyed · worktrees removed
# Tomorrow morning, after reboot:
$ bn myproject resume
✓ panes restored · run processes restarted · claude --continue
Web dashboard · pipeline view, plan review, live conversation, remote mode with QR
bn serve opens it at localhost:4242. Four tabs:
- Pipeline — every feature × every repo, with a stage indicator (Setup → Plan → Execute → Report → Merge), a plan-review dialog when delegated-mode agents pause for approval, a report-review dialog when they finish, and a live conversation modal that streams the claude transcript per feature via SSE and lets you reply inline.
- Config — per-repo run command (with named presets via
activePreset), base branch, env templates, the OpenRouter API key — all saved with a comment-preserving YAML writer. - Shortcuts — the tmux Alt-keybinds banyan ships.
- History — past features with merge timestamps and MR/PR links.
bn serve --remote exposes it over a Cloudflare tunnel (or ngrok) with 32-hex Bearer-token auth and prints a QR code — scan from your phone to monitor builds, approve plans, and chat live with agents from anywhere.
Auto-managed .env · seed gitignored files into worktrees + load them into the run process
repos:
- name: back
copyOnWorktree:
- .env.local
loadEnvFiles:
- .env.localcopyOnWorktreecopies the file from the main checkout into every fresh worktree onbn wt.loadEnvFilesparses.env-style files at spawn time and prependsKEY=valuepairs to the run command — so Spring Boot, Django, plain Node, and Go all see the vars as actual env, not as a file they have to know how to read.- Banyan's dynamic values (allocated port, composePorts, declared
run.env) override the file. Per-feature isolation is automatic — each spawn has its own env block.
Working on existing branches · --prefix + automatic fallback when the branch already exists
By default bn wt <name> creates feature/<name>. To target a branch with a different convention, pass --prefix:
| Existing branch | Command |
|---|---|
feature/login |
bn wt login |
fix/crash-on-startup |
bn wt crash-on-startup --prefix fix |
android/refactor/ui-setup |
bn wt ui-setup --prefix android/refactor |
hotfix-truc (no prefix) |
bn wt hotfix-truc --prefix "" |
When the computed branch already exists in the repo, banyan checks it out as-is in the new worktree instead of failing — so bn wt ui-setup --prefix android/refactor on a pushed branch just spawns a worktree on it, no new commit.
Watch out for:
- "branch is already checked out elsewhere" — git refuses two worktrees on the same branch. Switch the other off (
git switch main) and retry. - Stale pre-v1 worktrees — banyan used
<repo>-<feature>/sibling folders before v1; the current convention isworktree-<repo>/<feature>/. If you have a sibling-style worktree on the same branch, move it (git worktree move) or remove it (git worktree remove --force) before re-spawning. bn cleanupdeletes the branch. Usebn wt-rmif you only want to drop the worktree but keep the branch.
Agent modes · live (default) / delegated (pipeline-gated)
bn myproject wt fix-search -p "the search bar lags above 500 items"
# → mode=delegated (because -p was given)
bn myproject wt fix-search
# → mode=live (default, no prompt)
bn myproject wt fix-search -m delegated # override- live — conversational, no ceremony. The agent is banyan-aware (knows the MCP tools) but you drive. Can edit the main branch directly. Default when no prompt.
- delegated — pipeline-gated: Setup → Plan → human review → Execute → Report → Merge. Loops via a Stop hook until
banyan_report_doneis called. Default when-pis given.
Legacy mode names (interactive, assisted, autonomous, autopilot) are still accepted and normalized to the closest equivalent.
LLM-driven naming: -p "<prompt>" infers the slug via OpenRouter. Skip the prompt and you get a draft worktree where the agent finalizes the name from your first message.
Conflict pulse + AI resolver · see and fix cross-feature conflicts before they hurt
Live file × feature matrix in the dashboard. Color-coded overlap risk. Suggested merge order.
On merge/rebase conflict, banyan launches a headless Claude with --add-dir on every repo (so it sees sibling worktrees) and banyan MCP wired in. It can call banyan_feature_status to check how other features resolved the same files — consistent resolutions across the project, no context pollution in per-feature agents.
Project memory · the orchestrator queries reports + commits + transcripts via MCP
Open the orchestrator pane (bn <p> start or the dashboard's "Open in terminal") and ask it directly:
why did we switch from JWT to OAuth?
what's still pending across features?
what was the hesitation on the cart refactor?
It calls banyan_list_reports, banyan_search_transcripts, reads commits, and answers conversationally. Multi-turn: you can refine, drill in, and have it act on the findings (spawn a fix, merge a feature) in the same conversation.
MCP server · every banyan operation exposed to Claude Code / Cursor
{ "mcpServers": { "banyan": { "command": "banyan", "args": ["mcp-serve"] } } }Tools cover the full lifecycle: banyan_create_feature, banyan_merge_feature, banyan_list_features, banyan_get_stack_ports, todos, reports, and dispatch to per-feature agents. The orchestrator gets these wired in automatically.
Discord Rich Presence · current project + active features in your Discord profile
Optional, off by default. When enabled (~/.config/banyan/discord-rpc.yaml → enabled: true), banyan publishes a Rich Presence card while the dashboard is running:
- Single project — feature names on the top line, project name + count on the second.
- Multiple projects — project names with feature counts on top, totals below.
Uses Banyan's official Discord application — no setup needed beyond flipping the flag. See src/integrations/discord-rpc/README.md for asset details.
Hooks · shell scripts at every lifecycle transition for the long-tail customisation
worktree_created, before_worktree_remove, worktree_removed, stack_up, stack_down, pre_merge, post_merge, pre_test, post_test. Three lookup levels: team-versioned, local override, global per user. Each hook receives BANYAN_PROJECT, BANYAN_FEATURE, BANYAN_REPO, BANYAN_WORKTREE_PATH, etc.
For seeding gitignored files, prefer the declarative copyOnWorktree field. Reach for worktree_created when you need templating, secret managers, hardlinks, or anything the declarative field doesn't cover.
Survives reboots · bn resume restores panes, processes, conversations
Walks every active worktree on disk, recreates the tmux panes, restarts run processes for features that had a bn start, resumes Claude via --continue so each conversation is preserved. Compose volumes survive (Docker keeps them), recorded port allocations survive.
Configuration schema
version: 1
projects:
- name: myproject
repos:
- name: front
path: ~/Dev/myproject/front
baseBranch: develop
copyOnWorktree: [.env.local]
loadEnvFiles: [.env.local]
run:
command: npm run dev
port: 3000
portEnv: PORT
setup: npm install
env:
REACT_APP_API_URL: http://localhost:{{back.port}}
- name: back
path: ~/Dev/myproject/back
baseBranch: develop
copyOnWorktree: [.env.local, src/main/resources/application-local.yml]
loadEnvFiles: [.env.local]
run:
command: ./gradlew bootRun
port: 8080
portEnv: SERVER_PORT
stopCommand: ./gradlew --stop
composePorts:
DB_PORT: mysql-dev:3306
- name: app
path: ~/AndroidStudio/Mobile
baseBranch: develop
copyOnWorktree: [local.properties]
run:
command: ./gradlew :app:installDebug && adb shell am start -n com.example/.MainActivity
- name: infra
type: compose
path: ~/Dev/myproject/back
composeFile: docker-compose.dev.ymlStored at ~/.config/banyan/config.yaml. Edit directly or via the dashboard's Config tab.
Command list
bn ls list projects
bn init <project> create a project
bn doctor check the environment is ready
bn serve [--remote] [--tunnel <p>] web dashboard (--remote = HTTPS tunnel + QR + token auth)
--port <n> · --no-open · --rotate-token
bn <project> start [feature] [repos...] workspace (no feature) or run processes (with)
bn <project> stop <feature> stop run processes
bn <project> close close the tmux session (worktrees on disk kept)
bn <project> status / resume / ports
bn <project> restart-orchestrator relaunch the orchestrator pane
bn <project> wt [feature] [repos...] create worktree(s) + agent pane
-p "<prompt>" first message to the agent + LLM-named slug from prompt; implies -m delegated
-m <mode> live | delegated (legacy names normalized)
--prefix <p> branch prefix (default 'feature')
bn <project> wt-rm <feature> [repo]
bn <project> rebase <feature> [repo]
bn <project> merge <feature> [repo]
bn <project> cleanup <feature> [repo] stop + remove + delete + close + drop
bn <project> env up|down|recreate|logs|exec <feature> [service ...]
If you're inside a configured repo (or its worktree), drop the project name — banyan infers it from cwd. Or symlink banyan to your project name to skip the project arg entirely.
Dev
npm run dev # tsc --watch
npm test # node --test on dist/test
npm run clean300 tests, CI runs on Ubuntu + macOS × Node 20 + 22.
Contributing · Changelog · MIT licensed