nfo-cli 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/claude-command.js +6 -1
  2. package/dist/claude-command.js.map +1 -1
  3. package/dist/claude-trust.js +46 -0
  4. package/dist/claude-trust.js.map +1 -0
  5. package/dist/cli.js +64 -54
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/restore.js +0 -1
  8. package/dist/commands/restore.js.map +1 -1
  9. package/dist/commands/tui.js +6 -4
  10. package/dist/commands/tui.js.map +1 -1
  11. package/dist/mcp/handlers.js +5 -0
  12. package/dist/mcp/handlers.js.map +1 -1
  13. package/dist/mcp/tool-defs.js +10 -0
  14. package/dist/mcp/tool-defs.js.map +1 -1
  15. package/dist/musicians/dismiss.js +1 -1
  16. package/dist/musicians/dismiss.js.map +1 -1
  17. package/dist/musicians/roles.js +15 -0
  18. package/dist/musicians/roles.js.map +1 -0
  19. package/dist/musicians/spawn.js +53 -18
  20. package/dist/musicians/spawn.js.map +1 -1
  21. package/dist/permission.js +14 -8
  22. package/dist/permission.js.map +1 -1
  23. package/dist/prompts/musician-role.js +2 -1
  24. package/dist/prompts/musician-role.js.map +1 -1
  25. package/dist/prompts/orchestrator-role.js +42 -8
  26. package/dist/prompts/orchestrator-role.js.map +1 -1
  27. package/dist/prompts/tool-discipline.js +10 -0
  28. package/dist/prompts/tool-discipline.js.map +1 -1
  29. package/dist/tui/{App.js → components/App.js} +20 -20
  30. package/dist/tui/components/App.js.map +1 -0
  31. package/dist/tui/components/AppView.js +13 -0
  32. package/dist/tui/components/AppView.js.map +1 -0
  33. package/dist/tui/{Auditorium.js → components/Auditorium.js} +2 -2
  34. package/dist/tui/components/Auditorium.js.map +1 -0
  35. package/dist/tui/components/ConcertHall.js.map +1 -0
  36. package/dist/tui/{Help.js → components/Help.js} +0 -8
  37. package/dist/tui/components/Help.js.map +1 -0
  38. package/dist/tui/components/OrchestratorPane.js.map +1 -0
  39. package/dist/tui/components/SidebarHeader.js +6 -0
  40. package/dist/tui/components/SidebarHeader.js.map +1 -0
  41. package/dist/tui/{StatusBar.js → components/StatusBar.js} +1 -1
  42. package/dist/tui/components/StatusBar.js.map +1 -0
  43. package/package.json +8 -1
  44. package/assets/agent-screen.png +0 -0
  45. package/assets/main-screen.png +0 -0
  46. package/assets/orche-clawd.png +0 -0
  47. package/dist/tui/App.js.map +0 -1
  48. package/dist/tui/AppView.js +0 -13
  49. package/dist/tui/AppView.js.map +0 -1
  50. package/dist/tui/Auditorium.js.map +0 -1
  51. package/dist/tui/ConcertHall.js.map +0 -1
  52. package/dist/tui/Help.js.map +0 -1
  53. package/dist/tui/OrchestratorPane.js.map +0 -1
  54. package/dist/tui/SidebarHeader.js +0 -6
  55. package/dist/tui/SidebarHeader.js.map +0 -1
  56. package/dist/tui/StatusBar.js.map +0 -1
  57. package/docs/plans/2026-05-29-nfo-phase-1-bootstrap.md +0 -2152
  58. package/docs/plans/2026-05-29-nfo-phase-2-mcp-musicians.md +0 -2467
  59. package/docs/plans/2026-05-29-nfo-phase-3-ink-tui.md +0 -1611
  60. package/docs/plans/2026-05-29-nfo-phase-4-permission-prompts.md +0 -460
  61. package/docs/plans/2026-05-29-nfo-phase-5-help-and-notify.md +0 -933
  62. package/docs/specs/2026-05-29-nfo-design.md +0 -468
  63. package/src/claude-command.ts +0 -35
  64. package/src/claude-detect.ts +0 -42
  65. package/src/cli.ts +0 -164
  66. package/src/commands/attach.ts +0 -24
  67. package/src/commands/dashboard-window.ts +0 -33
  68. package/src/commands/kill.ts +0 -50
  69. package/src/commands/launch.ts +0 -134
  70. package/src/commands/list.ts +0 -43
  71. package/src/commands/mcp-server.ts +0 -18
  72. package/src/commands/notes.ts +0 -18
  73. package/src/commands/restore.ts +0 -153
  74. package/src/commands/tui.tsx +0 -16
  75. package/src/config.ts +0 -44
  76. package/src/dashboard.ts +0 -1
  77. package/src/mcp/config.ts +0 -39
  78. package/src/mcp/handlers.ts +0 -141
  79. package/src/mcp/server.ts +0 -50
  80. package/src/mcp/tool-defs.ts +0 -151
  81. package/src/musicians/dismiss.ts +0 -60
  82. package/src/musicians/ids.ts +0 -21
  83. package/src/musicians/lookup.ts +0 -13
  84. package/src/musicians/message-log.ts +0 -152
  85. package/src/musicians/message.ts +0 -99
  86. package/src/musicians/query.ts +0 -19
  87. package/src/musicians/spawn.ts +0 -139
  88. package/src/notes.ts +0 -39
  89. package/src/notify.ts +0 -62
  90. package/src/orchestrator/report-back.ts +0 -33
  91. package/src/permission.ts +0 -30
  92. package/src/project-key.ts +0 -12
  93. package/src/prompts/musician-role.ts +0 -22
  94. package/src/prompts/orchestrator-role.ts +0 -60
  95. package/src/prompts/tool-discipline.ts +0 -35
  96. package/src/repo.ts +0 -14
  97. package/src/shell-quote.ts +0 -7
  98. package/src/state-updaters.ts +0 -132
  99. package/src/state.ts +0 -49
  100. package/src/state.types.ts +0 -67
  101. package/src/tmux.ts +0 -226
  102. package/src/tui/App.tsx +0 -532
  103. package/src/tui/AppView.tsx +0 -96
  104. package/src/tui/Auditorium.tsx +0 -56
  105. package/src/tui/ConcertHall.tsx +0 -31
  106. package/src/tui/Help.tsx +0 -72
  107. package/src/tui/OrchestratorPane.tsx +0 -98
  108. package/src/tui/SidebarHeader.tsx +0 -32
  109. package/src/tui/StatusBar.tsx +0 -44
  110. package/src/tui/activity-line.ts +0 -16
  111. package/src/tui/detect-permission.ts +0 -93
  112. package/src/tui/embedded-session-lifecycle.ts +0 -44
  113. package/src/tui/embedded-terminal.ts +0 -325
  114. package/src/tui/format-time.ts +0 -25
  115. package/src/tui/keymap.ts +0 -104
  116. package/src/tui/poll-activity.ts +0 -25
  117. package/src/tui/poll-idle.ts +0 -149
  118. package/src/tui/poll-permission.ts +0 -50
  119. package/src/tui/status-icon.ts +0 -35
  120. package/src/tui/terminal-input.ts +0 -136
  121. package/src/tui/watch-state.ts +0 -43
  122. package/src/worktree.ts +0 -41
  123. package/tests/claude-command.test.ts +0 -30
  124. package/tests/claude-detect.test.ts +0 -14
  125. package/tests/commands/attach.test.ts +0 -60
  126. package/tests/commands/kill.test.ts +0 -66
  127. package/tests/commands/launch.test.ts +0 -75
  128. package/tests/commands/list.test.ts +0 -47
  129. package/tests/commands/notes.test.ts +0 -53
  130. package/tests/commands/restore.test.ts +0 -126
  131. package/tests/helpers/tmp-config.ts +0 -16
  132. package/tests/helpers/tmp-repo.ts +0 -29
  133. package/tests/integration/orchestrator-spawn.test.ts +0 -108
  134. package/tests/mcp/handlers.test.ts +0 -163
  135. package/tests/mcp/tool-defs.test.ts +0 -35
  136. package/tests/musicians/dismiss.test.ts +0 -102
  137. package/tests/musicians/message.test.ts +0 -159
  138. package/tests/musicians/query.test.ts +0 -65
  139. package/tests/musicians/spawn.test.ts +0 -125
  140. package/tests/notes.test.ts +0 -56
  141. package/tests/notify.test.ts +0 -80
  142. package/tests/orchestrator/report-back.test.ts +0 -18
  143. package/tests/permission.test.ts +0 -29
  144. package/tests/project-key.test.ts +0 -33
  145. package/tests/prompts/tool-discipline.test.ts +0 -25
  146. package/tests/repo.test.ts +0 -38
  147. package/tests/state-updaters.test.ts +0 -126
  148. package/tests/state.test.ts +0 -85
  149. package/tests/tmux.test.ts +0 -126
  150. package/tests/tui/AppView.test.tsx +0 -92
  151. package/tests/tui/Auditorium.test.tsx +0 -67
  152. package/tests/tui/ConcertHall.test.tsx +0 -22
  153. package/tests/tui/Help.test.tsx +0 -38
  154. package/tests/tui/OrchestratorPane.test.ts +0 -30
  155. package/tests/tui/SidebarHeader.test.tsx +0 -20
  156. package/tests/tui/StatusBar.test.tsx +0 -51
  157. package/tests/tui/activity-line.test.ts +0 -21
  158. package/tests/tui/detect-permission.test.ts +0 -92
  159. package/tests/tui/embedded-session-lifecycle.test.ts +0 -55
  160. package/tests/tui/embedded-terminal.test.ts +0 -80
  161. package/tests/tui/format-time.test.ts +0 -25
  162. package/tests/tui/keymap.test.ts +0 -93
  163. package/tests/tui/poll-activity.test.ts +0 -81
  164. package/tests/tui/poll-idle.test.ts +0 -159
  165. package/tests/tui/poll-permission.test.ts +0 -222
  166. package/tests/tui/status-icon.test.ts +0 -27
  167. package/tests/tui/terminal-input.test.ts +0 -113
  168. package/tests/tui/watch-state.test.ts +0 -54
  169. package/tests/worktree.test.ts +0 -73
  170. package/tsconfig.json +0 -19
  171. package/vitest.config.ts +0 -12
  172. /package/dist/tui/{ConcertHall.js → components/ConcertHall.js} +0 -0
  173. /package/dist/tui/{OrchestratorPane.js → components/OrchestratorPane.js} +0 -0
@@ -1,468 +0,0 @@
1
- # NoFluffOrchestra (NFO) — Design Spec
2
-
3
- **Status:** Draft
4
- **Date:** 2026-05-29
5
- **Author:** Javier Furus (with Claude)
6
-
7
- ## 1. Overview
8
-
9
- NoFluffOrchestra (NFO) is a TUI for multi-agent work on existing codebases. It lets a user talk to one **Orchestrator** (an LLM) who spawns and coordinates **Musicians** (more LLMs) to do work in parallel or sequence. Each project gets its own **Orchestra**, scoped to one git repository. Orchestras run inside tmux for persistence across terminal disconnects and remote attach.
10
-
11
- NFO's value proposition vs. existing tools (e.g., Gastown): you can latch onto any existing repo, the orchestra survives session loss, and the tool stays out of the way — NFO provides only the mechanics of process choreography, not opinionated workflow logic. The Orchestrator LLM decides what work to spawn and how to coordinate; NFO spawns processes, routes messages, persists state, and renders status.
12
-
13
- ## 2. Goals and non-goals
14
-
15
- ### Goals
16
-
17
- - **Latch onto any existing git repo** with zero project-side setup beyond `nfo`.
18
- - **Survive disconnects** so a closed terminal or dropped ssh session doesn't kill in-flight work.
19
- - **Re-enter from anywhere** — same machine, remote ssh, by orchestra id, or by being in the repo.
20
- - **Use the user's regular Claude Code subscription**, not the separate Agent SDK credit pool.
21
- - **Inherit the user's Claude Code environment** — skills, CLAUDE.md, MCP servers, hooks — so agents feel like "my normal Claude."
22
- - **Run multiple orchestras** (one per project) and let the user switch between them via the Concert Hall.
23
- - **Persistent project memory** the Orchestrator curates over time, so context isn't lost between sessions.
24
-
25
- ### Non-goals (v1)
26
-
27
- - Workflow templates or pre-built agent roles. The Orchestrator decides agent roles via prompting.
28
- - Multi-user / collaborative orchestras. One user per orchestra in v1.
29
- - A vector-indexed knowledge base. Orchestrator-written markdown notes are the entire memory layer.
30
- - Web UI. TUI only.
31
- - Windows-native support. Linux and macOS in v1; Windows via WSL.
32
- - Cross-orchestra coordination. Each orchestra is independent.
33
- - Built-in cost/usage analytics dashboards. We surface basic token counts in the status bar; deep analysis is out of scope.
34
-
35
- ## 3. Architecture
36
-
37
- ### 3.1 Stack
38
-
39
- - **TypeScript / Node.js** end-to-end.
40
- - **Ink** (React for terminals) for the TUI side pane.
41
- - **tmux** for session persistence, layout, and process supervision.
42
- - **Claude Code** (interactive `claude` CLI) for both the Orchestrator and each Musician. Interactive mode (not `claude -p` / Agent SDK) is required so usage draws from the user's regular subscription quota rather than the separate Agent SDK credit pool that takes effect 2026-06-15.
43
- - **Git worktrees** for per-musician filesystem isolation.
44
- - **MCP (stdio)** for the in-process tool surface NFO exposes to every claude session.
45
-
46
- ### 3.2 Process topology per orchestra
47
-
48
- Each orchestra runs inside one tmux session named `nfo-<project-key>`. The session contains:
49
-
50
- - **Window 0 ("main")** with two panes:
51
- - **Left pane (~65%)**: the Orchestrator's interactive `claude` session, started in the repo root with NFO's MCP server attached and a role-specific system prompt addendum.
52
- - **Right pane (~35%)**: the NFO Ink TUI process, showing the Concert Hall, Auditorium, and status bar.
53
- - **Windows 1..N ("musicians")**: one tmux window per active Musician, each running an interactive `claude` session inside that musician's worktree directory, also with the NFO MCP server attached.
54
-
55
- Per-session NFO config is applied via `tmux set-option -t <session>` (mouse on, status-position top, custom bindings to jump between Orchestrator and TUI panes) so user global tmux config is not affected.
56
-
57
- ### 3.3 IPC and state ownership
58
-
59
- There is **no NFO daemon**. Three process types coordinate via the filesystem and tmux:
60
-
61
- 1. **NFO TUI** (Ink app, side pane): reads `state.json`, polls musician panes via `tmux capture-pane`, renders, and handles user keypresses. Optional — if it dies or is closed, the orchestra continues functioning.
62
- 2. **NFO MCP server** (one stdio child per `claude` session, spawned automatically via `--mcp-config`): handles tool calls (`spawn_musician`, `message_musician`, etc.) by running tmux commands directly and writing to `state.json`.
63
- 3. **`claude` sessions**: the Orchestrator and each Musician. They speak MCP to their attached NFO MCP server.
64
-
65
- State is owned by `state.json` under the orchestra's data dir. Writes use atomic write-then-rename plus `flock` advisory locking so concurrent MCP server writes (from different `claude` sessions) cannot corrupt it.
66
-
67
- The NFO TUI is a viewer and remote control; the MCP servers are the actuators. This separation means closing the TUI side pane does not affect running musicians.
68
-
69
- ## 4. Lifecycle
70
-
71
- ### 4.1 Launch (`$ nfo` with no arguments)
72
-
73
- NFO's smart-launch logic:
74
-
75
- 1. **In a git repo + orchestra exists for this repo** → attach to it. If the tmux session is alive, `tmux attach`. If dead, offer to restore (see §4.3).
76
- 2. **In a git repo + no orchestra for this repo** → create a new orchestra. Prompt once for permission level (`auto` / `autonomous` / `supervised` / `strict`). If `auto` is selected, require a second explicit confirmation (§5.2). Persist to `state.json`. Initialize the data dir layout (§6.1). Create the tmux session, layout the panes, start the Orchestrator's `claude` session.
77
- 3. **Not in a git repo + orchestras exist somewhere** → if exactly one orchestra exists AND it is running, attach to it (sensible default). Otherwise, show a picker listing all known orchestras with status (running/stopped), project name, and last activity. User selects one to attach/restore.
78
- 4. **Not in a git repo + no orchestras anywhere** → error: "Open NFO in a git repository to create your first orchestra."
79
-
80
- The project key is computed as `${sha1(absolute_repo_path).slice(0,10)}-${basename(repo_path)}`. Stable across machines that share the same absolute path; otherwise unique per machine.
81
-
82
- ### 4.2 Attaching by id (`$ nfo <id>`)
83
-
84
- Skips repo detection. Looks up `~/.config/nfo/projects/<id>/`. Attaches if the tmux session is alive, restores if dead, errors if the id is unknown.
85
-
86
- ### 4.3 Restoration of a stopped orchestra
87
-
88
- When NFO finds state for an orchestra but the tmux session no longer exists (machine reboot, crash, `tmux kill-server`), it offers to restore:
89
-
90
- 1. Recreate the tmux session with the same name.
91
- 2. Start the Orchestrator's `claude` session with `--resume <orchestrator_session_id>` from state.json, restoring its full conversation history.
92
- 3. For each musician in state.json marked `working` or `idle`, recreate its tmux window in its worktree and start `claude --resume <musician_session_id>`. Musicians marked `stopped` are not restored.
93
- 4. Restart the NFO Ink TUI in the side pane.
94
-
95
- Note: Claude Code persists session conversation history to disk by default; `--resume` reads from that store.
96
-
97
- ### 4.4 Re-entry from remote (ssh)
98
-
99
- User `ssh`'s in, runs `nfo <id>` or `nfo` from the project dir. tmux survived the absence, so `nfo` finds the live session and attaches. The orchestra never actually stopped.
100
-
101
- ### 4.5 Detach
102
-
103
- The user hits tmux's detach binding (`prefix + d`). The orchestra continues in the background. Closing the terminal has the same effect.
104
-
105
- ### 4.6 Musician completion
106
-
107
- A musician's `claude` session goes idle when it finishes a task. NFO determines idle status in one of two ways:
108
-
109
- 1. **Preferred**: the musician calls the `report_done` MCP tool, explicitly marking itself complete and providing a summary.
110
- 2. **Fallback**: NFO's pane-capture loop detects no output change for >30 seconds AND the bottom of the pane shows claude's input prompt (i.e., claude is waiting for user input, not mid-tool-call); marks as `idle`. This is heuristic; `report_done` is the reliable signal and the Orchestrator's prompt encourages musicians to call it.
111
-
112
- Idle musicians remain alive in their tmux window. The Orchestrator can `message_musician` to give them new work or `dismiss_musician` to retire them.
113
-
114
- ### 4.7 Teardown (`$ nfo kill <id>`)
115
-
116
- 1. Confirm with user.
117
- 2. For each musician: send `/quit` via `tmux send-keys` (allows graceful session save), then `tmux kill-window`.
118
- 3. Prompt: archive worktrees (default), discard, or leave in place.
119
- 4. `tmux kill-session -t nfo-<id>`.
120
- 5. Archive `state.json` (move to `archive/`); leave `notes/` untouched (user may want to read them later).
121
-
122
- ## 5. The Orchestrator and Musicians
123
-
124
- ### 5.1 Agent backend
125
-
126
- Every agent — the Orchestrator and every Musician — is an **interactive `claude` CLI session**. This is non-negotiable: it is the only mode that uses the user's regular Claude Code subscription (vs. the separate Agent SDK credit pool). Agents are spawned via `tmux new-window`, NOT via the TypeScript Agent SDK.
127
-
128
- ### 5.2 Permission level
129
-
130
- The orchestra-wide permission level (`auto` / `autonomous` / `supervised` / `strict`) is set at first launch and stored in `state.json`. Adjustable mid-session via an NFO TUI control. Each level is passed to every `claude` session through the appropriate flag:
131
-
132
- - **auto** → claude's bypass-permissions mode (passed via `--dangerously-skip-permissions` or `--permission-mode bypassPermissions` — exact flag confirmed at implementation time). **No prompts at all.** Musicians can run any tool, including arbitrary shell commands, network calls, and destructive operations. Worktree isolation reduces — but does not eliminate — the blast radius (a musician can still touch anything its user account can touch outside the worktree).
133
- - **autonomous** → `--permission-mode acceptEdits`. Free file edits and common filesystem commands (mkdir, mv, cp, touch). Risky tools (most Bash, network) still need an allowlist or a prompt.
134
- - **supervised** → `--permission-mode default`. Claude's standard prompt-on-risky-tool behavior. NFO surfaces prompts in the Auditorium (see §5.2.1).
135
- - **strict** → `--permission-mode plan`. Read-only; no edits without explicit approval.
136
-
137
- **Warning around `auto`:** Selecting `auto` at orchestra creation requires an explicit second confirmation. The prompt reads roughly:
138
-
139
- > ⚠ AUTO mode disables all permission checks. Musicians can execute arbitrary shell commands, modify files anywhere on this system, and access the network without asking. Worktrees limit but do not contain risky operations. Use this only in trusted sandboxes or when you accept these risks. Type "I understand" to continue.
140
-
141
- The same explicit confirmation gate applies when promoting an orchestra to `auto` mid-session.
142
-
143
- The Orchestrator inherits the orchestra's level; spawned Musicians inherit the same level by default. The `spawn_musician` tool does NOT allow musicians to elevate beyond the orchestra's level (a musician spawned by a `supervised` orchestra cannot be `auto`). Musicians can be spawned at a lower (safer) level than the orchestra's — useful for risky work the Orchestrator wants to keep on a short leash.
144
-
145
- ### 5.2.1 Permission prompts
146
-
147
- In `supervised` or `strict` mode, an agent may hit a tool call that requires approval. Their `claude` session renders a permission prompt in their tmux window. Because the user is usually looking at the Orchestrator pane, not the musician's hidden window, NFO must surface the prompt clearly.
148
-
149
- **Detection.** The 2s pane-capture loop scans each musician's window for claude's permission-prompt pattern (recognizable text + numbered choice list). On match, NFO writes `status: "awaiting_permission"` to `state.json` for that musician along with a short summary of the tool being requested (parsed from the prompt text — best-effort).
150
-
151
- **UI signal.** In the Auditorium, the musician's row shows `⚠ awaiting permission` with the requested tool as the activity line (e.g., `Bash: rm -rf node_modules`). The Concert Hall tab shows a `⚠` badge if any musician is in this state. The status bar summarises: `N musicians awaiting permission · [p] jump to next`.
152
-
153
- **Response.** Pressing `p` (or selecting the musician + `Enter`) runs `tmux select-window` to that musician. The user sees claude's prompt and answers normally (claude's own 1/2/3 UI). On the next capture-pane scan NFO sees the prompt is gone and flips status back to `working`.
154
-
155
- **Optional bell.** A config flag `notify_on_permission` (default `false`) enables a terminal bell + `notify-send`/`osascript` desktop notification when any musician enters `awaiting_permission`. Off by default to avoid annoyance during heavy parallel work.
156
-
157
- **Explicit non-behavior.** NFO does not answer prompts on the user's behalf, does not auto-approve based on heuristics, and does not parse permission semantics. It detects and surfaces; the user answers via claude's normal UI in the musician's pane.
158
-
159
- ### 5.3 Inherited Claude Code features
160
-
161
- Each agent is a normal interactive `claude` session and therefore inherits:
162
-
163
- - User skills from `~/.claude/`
164
- - Project CLAUDE.md (and parent-dir CLAUDE.md chain)
165
- - MCP servers from `.mcp.json` (plus the NFO MCP server added on top via `--mcp-config`; this flag is additive to project/user MCP discovery in interactive mode, not replacing)
166
- - Hooks
167
- - User's subscription / API key auth from `~/.claude/`
168
- - Claude Code's normal status line, slash commands, etc. (when the user is actually viewing the pane)
169
-
170
- The one consequence to flag: a user-invoked slash command like `/code-review` is only triggerable when the user is physically focused on that musician's tmux pane and types it. Agents themselves cannot invoke user-defined slash commands. Auto-triggered (description-matched) skills work normally.
171
-
172
- ### 5.4 Prompt composition
173
-
174
- Each `claude` session is launched with `--append-system-prompt-file <role-prompt-file>`. The prompt file contains:
175
-
176
- - The agent's role (Orchestrator vs. Musician).
177
- - The NFO MCP tool surface and when to use each tool.
178
- - A statement that CLAUDE.md provides project-specific guidance and should be respected, with a tiebreaker rule: "For agent coordination decisions, prefer NFO MCP tools over Claude Code's built-in `Task` tool; NFO's tools are how the user tracks your work."
179
-
180
- For the Orchestrator, additional content is prepended at launch:
181
-
182
- - The contents of `notes/overview.md` and `notes/decisions.md` (Orchestrator's curated long-term memory).
183
-
184
- Musician role prompts include a one-time task description provided by the Orchestrator at spawn time. The Claude process is launched directly in the pane (for example via `tmux respawn-pane`) with that task included in the initial command so NFO does not have to type the bootstrap prompt into an interactive shell.
185
-
186
- ## 6. NFO MCP server (tool surface)
187
-
188
- The NFO MCP server is a stdio MCP server that NFO ships and attaches to every `claude` session via `--mcp-config /path/to/nfo-mcp-config.json`. It is a thin process per session. It executes tmux commands and writes `state.json` (with `flock`).
189
-
190
- ### Tools
191
-
192
- **`spawn_musician`**
193
- - Inputs:
194
- - `name: string` — human-friendly identifier the Orchestrator chooses (e.g., `"test-writer"`).
195
- - `task: string` — initial prompt sent as the first message to the new musician.
196
- - `worktree?: boolean` — default `true`. If `false`, the musician runs in the main repo workspace (Orchestrator must explicitly opt out for trivially isolated work like docs).
197
- - `branch_from?: string` — base ref for the worktree. Defaults to current `HEAD`.
198
- - Behavior:
199
- 1. Generate `musician_id` (`mus-<seq>`).
200
- 2. If `worktree: true`, run `git worktree add` at `~/.config/nfo/projects/<key>/worktrees/<musician_id>` on a new branch `nfo/<musician_id>` from `branch_from`.
201
- 3. `tmux new-window -t <session> -n mus-<id>-<name> -c <cwd>`, then `tmux respawn-pane -k -t <window>` with `claude --mcp-config ... --permission-mode <level> --append-system-prompt-file <musician-prompt> '<task>'`.
202
- 4. Wait for `claude` to be ready (poll pane for prompt indicator, ~500ms).
203
- 5. Follow-up iteration still uses `tmux send-keys` into the running Claude process; bootstrap no longer goes through the shell's history file.
204
- 6. Register in `state.json` with `status: "working"`, `tmux_window_id`, `claude_session_id` (captured from claude's startup output), `worktree_path`, `branch`, `spawned_at`.
205
- 7. Return `{ musician_id, status: "working" }`.
206
-
207
- **`message_musician`**
208
- - Inputs: `musician_id: string`, `message: string`.
209
- - Behavior:
210
- - If `message` is short and free of shell-special chars: `tmux send-keys -l -t <window> -- <message> ; send-keys Enter`.
211
- - Otherwise: write to a temp file, `tmux load-buffer <tmpfile>`, `tmux paste-buffer -t <window>`, `tmux send-keys -t <window> Enter`. Avoids argv length limits and quoting hazards.
212
- - Mark `last_activity = now` in state.json.
213
- - Fire-and-forget. Returns immediately.
214
-
215
- **`query_musician`**
216
- - Inputs: `musician_id: string`, `lines?: number` (default 80).
217
- - Behavior: `tmux capture-pane -p -t <window> -S -<lines>`. Returns the raw captured string.
218
-
219
- **`list_musicians`**
220
- - Inputs: none.
221
- - Returns: array of `{ id, name, status, task_summary, worktree_path, spawned_at, last_activity, pending_permission? }` read from state.json. `status` is one of `"working" | "idle" | "awaiting_permission" | "stopped"`. `pending_permission` is a short string describing the requested tool, present only when `status === "awaiting_permission"`.
222
-
223
- **`dismiss_musician`**
224
- - Inputs: `musician_id: string`, `archive_worktree?: boolean` (default `true`).
225
- - Behavior:
226
- 1. `tmux send-keys -t <window> '/quit' Enter` (graceful claude shutdown; persists session for later resume if needed).
227
- 2. Wait up to 5s for pane to close; if not, `tmux kill-window`.
228
- 3. If `archive_worktree`, move worktree to `archive/<musician_id>/worktree/`; otherwise `git worktree remove`.
229
- 4. Update state.json: `status: "stopped"`, retain entry in archive section.
230
-
231
- **`report_done`** (called by Musicians)
232
- - Inputs: `summary: string`, `next_steps?: string`.
233
- - Behavior: marks musician as `idle` in state.json, appends summary to `logs/<musician_id>.log` and `archive/<musician_id>/summary.md`. Surfaces a notification in the Auditorium ("✓ test-writer: done"). Musician process stays alive.
234
-
235
- **`note_write`** / **`note_read`** / **`note_list`** (Orchestrator's curated memory)
236
- - `note_write(filename: string, content: string)` — write or replace `notes/<filename>.md`.
237
- - `note_read(filename: string)` — return contents of `notes/<filename>.md` or empty string if missing.
238
- - `note_list()` — return array of filenames in `notes/`.
239
-
240
- ### What the tool surface deliberately does NOT do
241
-
242
- There is no `plan`, `decide_split`, `phase_complete`, `assign_priority`, or any other workflow-shaped verb. The tools are tmux-shaped primitives. All workflow logic lives in the Orchestrator's reasoning, not in NFO code. This is the central guard against the calcification problem.
243
-
244
- ## 7. Persistent memory
245
-
246
- ### 7.1 Directory layout
247
-
248
- ```
249
- ~/.config/nfo/
250
- ├── config.json # global defaults (permission level, editor, notify_on_permission)
251
- └── projects/
252
- └── <project-key>/ # one dir per orchestra
253
- ├── state.json # orchestra metadata, source of truth
254
- ├── notes/ # Orchestrator-curated memory
255
- │ ├── overview.md
256
- │ ├── decisions.md
257
- │ ├── open-questions.md
258
- │ └── <ad-hoc>.md
259
- ├── logs/ # append-only musician event logs
260
- │ ├── orchestrator.log
261
- │ └── <musician-id>.log
262
- ├── worktrees/ # active musicians' git worktrees
263
- │ └── <musician-id>/
264
- └── archive/ # dismissed musicians
265
- └── <musician-id>/
266
- ├── final-log.txt
267
- ├── summary.md
268
- └── worktree/ # archived if requested
269
- ```
270
-
271
- ### 7.2 state.json schema
272
-
273
- ```jsonc
274
- {
275
- "orchestra_id": "a1b2c3d4ef-myproject",
276
- "project_path": "/home/user/projects/myproject",
277
- "created_at": "2026-05-29T10:00:00Z",
278
- "permission_level": "supervised",
279
- "orchestrator_session_id": "abc-123",
280
- "musicians": [
281
- {
282
- "id": "mus-001",
283
- "name": "test-writer",
284
- "task_summary": "add unit tests for auth.ts",
285
- "status": "working", // "working" | "idle" | "awaiting_permission" | "stopped"
286
- "pending_permission": null, // short string when status === "awaiting_permission"
287
- "tmux_window_id": "@7",
288
- "claude_session_id": "def-456",
289
- "worktree_path": "/home/user/.config/nfo/projects/.../worktrees/mus-001",
290
- "branch": "nfo/mus-001",
291
- "spawned_at": "2026-05-29T10:15:00Z",
292
- "last_activity": "2026-05-29T10:42:00Z"
293
- }
294
- ],
295
- "archived_musicians": [ /* same shape, plus dismissed_at and summary */ ]
296
- }
297
- ```
298
-
299
- ### 7.3 Notes mechanics
300
-
301
- - Plain markdown. The user can read and edit notes directly outside the TUI.
302
- - At Orchestrator startup, `notes/overview.md` and `notes/decisions.md` are concatenated and injected via `--append-system-prompt-file`. The Orchestrator sees its prior context in every fresh session.
303
- - The Orchestrator chooses when and what to write via `note_write`. NFO does not auto-summarize.
304
- - No indexing, no vector search. v1 assumes notes stay small enough to fit in context.
305
-
306
- ### 7.4 Log retention
307
-
308
- Append-only. No rotation in v1. Revisit if logs grow problematic in practice.
309
-
310
- ## 8. TUI design
311
-
312
- ### 8.1 Layout
313
-
314
- The NFO Ink TUI runs in the right pane (~35% of terminal width, usually 40-60 columns). Three vertical sections:
315
-
316
- ```
317
- ╭─ Concert Hall ───────────────────────╮
318
- │ ▸ myproject ●●●◐ (4 active) │
319
- │ other-repo ○○ (2 stopped) │
320
- │ spike ◐ (1 idle) │
321
- ├─ Auditorium ─────────────────────────┤
322
- │ │
323
- │ ▸ ♪ mus-001 test-writer ● │
324
- │ 2m · Running test suite... │
325
- │ │
326
- │ ♪ mus-002 doc-updater ◐ │
327
- │ 12m · ✓ done: docs/README.md │
328
- │ │
329
- │ ♪ mus-003 refactor-auth ● │
330
- │ <1s · Editing src/auth.ts │
331
- │ │
332
- ├──────────────────────────────────────┤
333
- │ supervised · ~12k tokens this session│
334
- │ [↑↓] nav [⏎] enter [n] notes [?] │
335
- ╰──────────────────────────────────────╯
336
- ```
337
-
338
- - **Concert Hall (top)**: list of all known orchestras with compact musician-icon row and counts. Current orchestra marked `▸`. Selecting another detaches the current tmux session and attaches that orchestra's session.
339
- - **Auditorium (middle, largest)**: one row per musician. Icon (`♪`), id, name, status indicator (`●` working, `◐` idle, `○` stopped), time-since-last-activity, and a one-line "current activity" from `tmux capture-pane` heuristic.
340
- - **Status bar (bottom)**: current permission level, an approximate session token indicator (parsed from claude's status line via `capture-pane` if recognizable; otherwise shown as `—`), context-aware key hints. NFO does not compute exact token usage in v1; the indicator is informational only.
341
-
342
- ### 8.2 Keybindings (side pane)
343
-
344
- - `↑/↓` or `j/k` — navigate musicians
345
- - `Enter` — `tmux select-window` to the selected musician's tmux window (full Claude Code view). Hotkey returns to TUI.
346
- - `Tab` / `Shift-Tab` — cycle Concert Hall tabs
347
- - `n` — `$EDITOR ~/.config/nfo/projects/<id>/notes/`
348
- - `d` — dismiss selected musician (confirm prompt)
349
- - `?` — keybinding overlay
350
- - `q` — return focus to Orchestrator pane (via `tmux select-pane`)
351
-
352
- Pane-switch hotkeys are set per-session: `prefix + e` ("enter Auditorium" → select TUI pane), `prefix + o` ("Orchestrator" → select Orchestrator pane).
353
-
354
- ### 8.3 Update mechanism
355
-
356
- Two loops in the Ink app:
357
-
358
- 1. **State loop**: watches `state.json` via `chokidar` (with a 1-second polling fallback for filesystems that don't support inotify). Re-renders on change. Triggered by MCP servers writing state when musicians spawn/finish/get dismissed.
359
- 2. **Activity loop**: every 2 seconds, runs `tmux capture-pane -p -t <window> -S -10` for each active musician's window. Extracts the last non-empty line as the "current activity" hint. Cheap at NFO's scale (≤10 musicians per orchestra is the design point).
360
-
361
- Heuristic-based activity parsing is intentional. The user clicks `Enter` on a musician to see real, detailed activity in their own tmux window. The Auditorium line is a status hint, not a faithful renderer.
362
-
363
- ## 9. tmux integration
364
-
365
- ### 9.1 Naming
366
-
367
- - Session: `nfo-<project-key>`.
368
- - Window 0: `main` (Orchestrator + TUI panes).
369
- - Windows 1..N: `mus-<id>-<name>` (e.g., `mus-001-test-writer`).
370
-
371
- ### 9.2 Commands used
372
-
373
- | Operation | Command (sketch) |
374
- |---|---|
375
- | Create session | `tmux new-session -d -s nfo-<key> -c <repo_root>` |
376
- | Split for TUI | `tmux split-window -h -p 35 -t nfo-<key>:0 'node nfo-tui.js --orchestra <key>'` |
377
- | Start Orchestrator | `tmux respawn-pane -k -t nfo-<key>:0.0 'claude --mcp-config ... --append-system-prompt-file ... --permission-mode ...'` |
378
- | Spawn musician | `tmux new-window -t nfo-<key> -n 'mus-<id>-<name>' -c <worktree> 'claude --mcp-config ... --append-system-prompt-file ... --permission-mode ...'` |
379
- | Message musician | `tmux send-keys -l -t <window> -- '<msg>' \; send-keys Enter` (or via `load-buffer`/`paste-buffer` for long messages) |
380
- | Capture pane | `tmux capture-pane -p -t <window> -S -<n>` |
381
- | Switch to musician | `tmux select-window -t nfo-<key>:mus-<id>-<name>` |
382
- | Check alive | `tmux has-session -t nfo-<key>` |
383
- | Tear down | `tmux kill-session -t nfo-<key>` |
384
-
385
- ### 9.3 Session-scoped tmux config
386
-
387
- Applied with `tmux set-option -t <session>` so user globals are untouched:
388
-
389
- - `mouse on` — enables clicking in the Ink TUI list.
390
- - `status-position top` — orchestra summary at top.
391
- - `bind-key e select-pane -t :0.1` — jump to TUI.
392
- - `bind-key o select-pane -t :0.0` — jump to Orchestrator.
393
-
394
- ### 9.4 Input injection caveats
395
-
396
- `tmux send-keys` semantics:
397
-
398
- - Use `-l` (literal) plus an explicit `Enter` to avoid keystroke interpretation of message content.
399
- - For messages longer than ~2KB or containing complex multi-line content, write to a tempfile and use `tmux load-buffer <file>` + `tmux paste-buffer -t <target>` + `tmux send-keys -t <target> Enter`. This avoids argv length limits and shell quoting issues.
400
-
401
- ## 10. CLI surface
402
-
403
- | Command | Behavior |
404
- |---|---|
405
- | `nfo` | Smart launch (see §4.1). |
406
- | `nfo <id>` | Attach or restore the named orchestra. |
407
- | `nfo list` | List all orchestras with status, project path, last activity, id. |
408
- | `nfo kill <id>` | Tear down. Prompts for worktree handling. |
409
- | `nfo restore <id>` | Force-restore a stopped orchestra. Usually unneeded — `nfo <id>` auto-restores. |
410
- | `nfo notes <id>` | Open the orchestra's `notes/` in `$EDITOR`. |
411
- | `nfo --version` | Print version. |
412
- | `nfo --help` | Print help. |
413
-
414
- Deliberate omissions:
415
-
416
- - No `nfo spawn`, `nfo message`, etc. Agent control is through the Orchestrator's chat, not the CLI.
417
- - No `nfo daemon` — there is no daemon.
418
-
419
- ## 11. Distribution and versioning
420
-
421
- ### 11.1 Distribution
422
-
423
- NFO ships as an npm global package (`nfo-cli` or similar; final name TBD at publish time). Install via `npm i -g <name>`. TypeScript + Node, no native compilation. Linux and macOS in v1. Windows users via WSL.
424
-
425
- ### 11.2 Claude Code compatibility
426
-
427
- NFO depends on stable Claude Code features: `--mcp-config`, `--permission-mode`, `--append-system-prompt-file`, `--resume`, stdio MCP transport, default settings loading.
428
-
429
- At orchestra launch NFO runs `claude --version` and refuses to start below a declared minimum version (the exact minimum will be fixed during implementation against the latest known-good Claude Code release). The error message instructs the user to upgrade.
430
-
431
- No exact-version pinning; Claude Code updates are backwards-compatible in practice.
432
-
433
- ### 11.3 NFO versioning
434
-
435
- Standard semver. Breaking changes to `state.json` schema bump major. State migrations on attach if the schema version on disk is older than the installed binary.
436
-
437
- ## 12. Risks and open questions
438
-
439
- ### 12.1 Risks
440
-
441
- - **`tmux send-keys` reliability for complex input.** Edge cases around terminal control sequences in agent messages are real. Mitigation: paste-buffer path for non-trivial messages.
442
- - **Pane-capture heuristic fragility.** "Last non-empty line" is a crude status indicator. If users find it consistently uninformative, we can extend the musician prompt to ask for periodic status lines in a known format.
443
- - **`claude --resume` after long gaps.** Untested behavior at scale. We assume it works for any session age; if it does not, restoration falls back to fresh sessions with a re-injected context summary from `notes/`.
444
- - **Worktree integration story.** Worktrees solve concurrent-edit safety, not API-coupling integration. The Orchestrator is responsible for sequencing dependent work and spawning explicit integration musicians when needed. This is a real cognitive load on the Orchestrator's prompting.
445
- - **Subscription quota changes.** The 2026-06-15 split between interactive Claude Code and Agent SDK budgets is the reason NFO uses interactive `claude`. If Anthropic later changes interactive billing, NFO's billing assumption may need to be revisited.
446
-
447
- ### 12.2 Open questions (to resolve during implementation)
448
-
449
- - Exact minimum `claude` CLI version to require.
450
- - Exact CLI flag used for the `auto` permission level (`--dangerously-skip-permissions` vs. `--permission-mode bypassPermissions`); confirm against the installed claude version at implementation time.
451
- - npm package name (subject to availability).
452
- - Whether to ship the NFO MCP server as a separate npm package or bundle into the main CLI binary.
453
- - Final color scheme and icon choices for the Ink TUI.
454
- - Whether `report_done` should also accept a structured `artifacts` field listing files changed (would help the Orchestrator inspect outcomes), or whether `query_musician` + worktree inspection is enough for v1.
455
-
456
- ## 13. Out of scope (v1)
457
-
458
- Repeated from §2 for clarity:
459
-
460
- - Workflow templates / pre-built agent personas
461
- - Multi-user orchestras
462
- - Vector-indexed retrieval over notes/logs
463
- - Web UI
464
- - Native Windows
465
- - Cross-orchestra coordination
466
- - Cost analytics beyond basic token counts in the status bar
467
- - Customizable themes (single default theme for v1)
468
- - Persistent musician memory beyond the underlying `claude --resume` mechanism
@@ -1,35 +0,0 @@
1
- import { shellQuote } from "./shell-quote.js";
2
- import type { SubagentModel } from "./state.types.js";
3
-
4
- export interface BuildClaudeCommandOptions {
5
- flags: string[];
6
- mcpConfigPath: string;
7
- promptFile?: string;
8
- resumeSessionId?: string | null;
9
- prompt?: string;
10
- model?: SubagentModel;
11
- }
12
-
13
- export function buildClaudeCommand(opts: BuildClaudeCommandOptions): string {
14
- const args = ["claude", ...opts.flags];
15
-
16
- if (opts.resumeSessionId) {
17
- args.push("--resume", opts.resumeSessionId);
18
- }
19
-
20
- args.push("--mcp-config", opts.mcpConfigPath);
21
-
22
- if (opts.promptFile) {
23
- args.push("--append-system-prompt-file", opts.promptFile);
24
- }
25
-
26
- if (opts.model) {
27
- args.push("--model", opts.model);
28
- }
29
-
30
- if (opts.prompt !== undefined) {
31
- args.push(opts.prompt);
32
- }
33
-
34
- return args.map(shellQuote).join(" ");
35
- }
@@ -1,42 +0,0 @@
1
- import { execa } from 'execa';
2
-
3
- export interface ClaudeInfo {
4
- version: string;
5
- major: number;
6
- minor: number;
7
- patch: number;
8
- }
9
-
10
- const MIN_MAJOR = 2;
11
- const MIN_MINOR = 1;
12
-
13
- export async function detectClaude(): Promise<ClaudeInfo> {
14
- let stdout: string;
15
- try {
16
- const result = await execa('claude', ['--version']);
17
- stdout = result.stdout;
18
- } catch (err) {
19
- throw new Error(
20
- `Failed to run \`claude --version\`. Is Claude Code installed and on PATH?\nDetails: ${(err as Error).message}`,
21
- );
22
- }
23
-
24
- // Match a semver-shaped substring in the output (claude prints e.g. "2.1.128 (Claude Code)").
25
- const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
26
- if (!match) {
27
- throw new Error(`Could not parse Claude Code version from output: ${stdout}`);
28
- }
29
- const [, majS, minS, patS] = match;
30
- const major = Number(majS);
31
- const minor = Number(minS);
32
- const patch = Number(patS);
33
-
34
- if (major < MIN_MAJOR || (major === MIN_MAJOR && minor < MIN_MINOR)) {
35
- throw new Error(
36
- `NFO requires Claude Code ${MIN_MAJOR}.${MIN_MINOR}.0 or newer, found ${major}.${minor}.${patch}. ` +
37
- `Run \`npm i -g @anthropic-ai/claude-code\` (or your package manager equivalent) to upgrade.`,
38
- );
39
- }
40
-
41
- return { version: `${major}.${minor}.${patch}`, major, minor, patch };
42
- }