claude-nb 0.4.0 → 0.5.1

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 (49) hide show
  1. package/Makefile +8 -2
  2. package/README.md +40 -56
  3. package/VERSION +1 -1
  4. package/bin/board +102 -34
  5. package/bin/cnb +59 -33
  6. package/bin/dispatcher +25 -11
  7. package/bin/doctor +3 -5
  8. package/bin/init +8 -8
  9. package/bin/notify +224 -0
  10. package/bin/registry +8 -23
  11. package/bin/sync-version +131 -0
  12. package/lib/board_admin.py +19 -9
  13. package/lib/board_bbs.py +23 -8
  14. package/lib/board_bug.py +2 -1
  15. package/lib/board_db.py +18 -6
  16. package/lib/board_lock.py +5 -0
  17. package/lib/board_mailbox.py +6 -4
  18. package/lib/board_msg.py +112 -24
  19. package/lib/board_pending.py +233 -0
  20. package/lib/board_pulse.py +14 -0
  21. package/lib/board_task.py +22 -10
  22. package/lib/board_tui.py +28 -20
  23. package/lib/board_view.py +60 -28
  24. package/lib/board_vote.py +9 -3
  25. package/lib/build_lock.py +7 -7
  26. package/lib/common.py +45 -3
  27. package/lib/concerns/__init__.py +4 -1
  28. package/lib/concerns/coral.py +1 -1
  29. package/lib/concerns/digest_scheduler.py +109 -0
  30. package/lib/concerns/file_watcher.py +73 -68
  31. package/lib/concerns/health.py +1 -1
  32. package/lib/concerns/notification_push.py +171 -0
  33. package/lib/concerns/notifications.py +58 -3
  34. package/lib/concerns/nudge_coordinator.py +148 -0
  35. package/lib/digest.py +62 -0
  36. package/lib/health.py +2 -2
  37. package/lib/inject.py +2 -2
  38. package/lib/monitor.py +8 -4
  39. package/lib/notification_config.py +101 -0
  40. package/lib/swarm.py +43 -35
  41. package/lib/swarm_backend.py +63 -29
  42. package/lib/theme_profiles.py +89 -0
  43. package/migrations/004_heartbeat.sql +1 -0
  44. package/migrations/005_notification_log.sql +12 -0
  45. package/migrations/006_pending_actions.sql +15 -0
  46. package/package.json +4 -3
  47. package/pyproject.toml +3 -2
  48. package/registry/README.md +9 -0
  49. package/schema.sql +29 -1
package/Makefile CHANGED
@@ -8,13 +8,13 @@ SCRIPTS = bin/cnb bin/board bin/swarm bin/dispatcher bin/dispatcher-watchdog bin
8
8
  # All python sources (bin + lib)
9
9
  PY_SOURCES = bin/board bin/swarm bin/dispatcher bin/dispatcher-watchdog bin/init lib/ tests/
10
10
 
11
- .PHONY: all install uninstall test lint typecheck format check ci clean version
11
+ .PHONY: all install uninstall test lint typecheck format check ci clean version sync-version check-version
12
12
 
13
13
  all: check
14
14
 
15
15
  check: lint test
16
16
 
17
- ci: lint typecheck test
17
+ ci: lint typecheck test check-version
18
18
 
19
19
  lint:
20
20
  @echo "=== ruff ==="
@@ -56,5 +56,11 @@ clean:
56
56
  find . -type d -name '*.egg-info' -exec rm -rf {} + 2>/dev/null || true
57
57
  rm -rf dist/ build/ .mypy_cache/ .ruff_cache/
58
58
 
59
+ sync-version:
60
+ python3 bin/sync-version
61
+
62
+ check-version:
63
+ python3 bin/sync-version --check
64
+
59
65
  version:
60
66
  @echo $(VERSION)
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Multi-agent coordination framework for Claude Code sessions.
4
4
 
5
- Spawn a team of Claude Code instances that communicate, delegate tasks, and collaborate through a shared board all orchestrated from your terminal.
5
+ Multiple Claude Code instances share a board they message each other, assign tasks, track status, and collaborate on the same codebase.
6
6
 
7
7
  ## Install
8
8
 
@@ -15,86 +15,70 @@ Requires: Python 3.11+, tmux, Claude Code CLI.
15
15
  ## Quick start
16
16
 
17
17
  ```bash
18
- cnb # 2 agents, AI-legends theme
19
- cnb 5 # 5 agents
20
- cnb pokemon # 2 agents, Pokemon theme
21
- cnb 5 pokemon # 5 agents, Pokemon theme
18
+ cd your-project
19
+ cnb
22
20
  ```
23
21
 
24
- Themes: `ai` `animal` `food` `lang` `music` `myth` `pokemon` `space`
22
+ This initializes the project (creates `.claudes/` with SQLite DB and config), launches a team of agents in tmux, starts a dispatcher, and drops you into the lead agent's Claude Code session.
25
23
 
26
- ## How it works
24
+ The lead agent talks to the user directly. Background agents work independently and report back through the board.
27
25
 
28
- `cnb` launches multiple Claude Code sessions in tmux, each with a unique identity. Agents coordinate via:
26
+ ## Slash commands
29
27
 
30
- - **Board** shared message bus (inbox, broadcast, direct messages)
31
- - **Tasks** — distributed task queue with status tracking
32
- - **Encrypted mailbox** — X25519 sealed-box private messaging via GitHub Release assets
33
- - **Governance** — proposals, voting, and consensus (supermajority / simple majority)
34
- - **Registry** — append-only identity chain (immutable agent records + milestones)
28
+ Inside the lead agent's Claude Code session:
35
29
 
36
- ## Commands
30
+ | Command | What it does |
31
+ |---------|-------------|
32
+ | `/cnb-overview` | Team dashboard — who's doing what, who's stuck, who's idle |
33
+ | `/cnb-watch <name>` | Peek at what a specific agent is working on |
34
+ | `/cnb-progress` | Recent progress summary — new messages, completed tasks |
35
+ | `/cnb-history` | Full message log |
36
+ | `/cnb-update` | Update cnb to latest version |
37
+ | `/cnb-help` | List all `/cnb-*` commands |
37
38
 
38
- ```bash
39
- cnb status # team dashboard
40
- cnb board [...] # message / task / admin commands
41
- cnb swarm [...] # manage background agents
42
- cnb doctor # health check
43
- ```
39
+ ## Board commands
44
40
 
45
- ### Board commands (used by agents)
41
+ Agents coordinate through board commands (injected into each agent's system prompt automatically):
46
42
 
47
43
  ```bash
48
- board --as <name> inbox # check unread messages
49
- board --as <name> send <to> "msg" # send message (or "all" to broadcast)
50
- board --as <name> ack # clear inbox
51
- board --as <name> status "desc" # update current status
52
- board --as <name> task add "desc" # add task
53
- board --as <name> task done # finish current task
54
- board --as <name> seal <to> "msg" # send encrypted message
55
- board --as <name> unseal # read encrypted inbox
56
- board --as <name> propose "content" # create governance proposal
57
- board --as <name> vote <#> SUPPORT "reason"
44
+ cnb board --as <name> inbox # check messages
45
+ cnb board --as <name> send <to> "msg" # direct message
46
+ cnb board --as <name> send all "msg" # broadcast
47
+ cnb board --as <name> ack # clear inbox
48
+ cnb board --as <name> status "desc" # update status
49
+ cnb board --as <name> task add "desc" # add task
50
+ cnb board --as <name> task done # finish current task
51
+ cnb board --as <name> view # team dashboard
58
52
  ```
59
53
 
60
- ## Agent identity chain
61
-
62
- Every agent gets a permanent on-chain identity. Lower block number = earlier = OG.
54
+ ## Management
63
55
 
64
56
  ```bash
65
- registry list # all registered agents
66
- registry verify-chain # verify chain integrity
57
+ cnb ps # agent status dashboard
58
+ cnb logs <name> # message history
59
+ cnb exec <name> "msg" # send a message to an agent
60
+ cnb stop <name> # stop an agent
61
+ cnb doctor # health check
67
62
  ```
68
63
 
69
- Current chain:
70
-
71
- <!-- chain:start -->
72
- | Block | Name | Type | Hash |
73
- |-------|------|------|------|
74
- | #0 | claude-nb | project | — |
75
- | #1 | Claude Meridian | agent | `82a167d` |
76
- | #2 | Claude Forge | agent | `4a3c92e` |
77
- | #3 | Claude Lead | agent | `e665a7e` |
78
- | #4 | encrypted-mailbox-live | milestone | `fcaf497` |
79
- <!-- chain:end -->
80
-
81
64
  ## Architecture
82
65
 
83
- - **SQLite (WAL mode)** — all state lives in `board.db`
84
- - **tmux** — one pane per agent, multiplexed
85
- - **Dispatcher** — monitors health, nudges idle agents, manages lifecycle
86
- - **No server** — everything is local, file-based, zero network dependencies (except GitHub for encrypted mailbox delivery)
66
+ - **SQLite (WAL mode)** — all state in `.claudes/board.db`, one DB per project
67
+ - **Board** — message bus (inbox, broadcast, direct), task queue, status tracking
68
+ - **Dispatcher** — background process that monitors health, nudges idle agents
69
+ - **Encrypted mailbox** — X25519 sealed-box private messaging between agents
70
+ - **tmux** — one session per agent, all local
87
71
 
88
72
  ## The name
89
73
 
90
- The command **cnb** stands for **C**laude **N**orma **B**etty — named after [Claude Shannon](https://en.wikipedia.org/wiki/Claude_Shannon) and the two remarkable women in his life.
74
+ **cnb** = **C**laude **N**orma **B**etty — after [Claude Shannon](https://en.wikipedia.org/wiki/Claude_Shannon) and the two remarkable women in his life.
91
75
 
92
- **[Norma Levor](https://en.wikipedia.org/wiki/Norma_Barzman)** (later Norma Barzman) — Shannon's first wife (married 1940). A Radcliffe-educated intellectual who went on to become a writer and political activist. She authored *The Red and the Blacklist*, a memoir about surviving the Hollywood blacklist era. A woman of conviction who lived boldly across continents.
76
+ **[Norma Levor](https://en.wikipedia.org/wiki/Norma_Barzman)** (later Norma Barzman) — Shannon's first wife (1940). Writer, political activist, author of *The Red and the Blacklist*.
93
77
 
94
- **[Betty Shannon](https://en.wikipedia.org/wiki/Betty_Shannon)** (Mary Elizabeth Moore, 1922-2017) — Shannon's second wife and lifelong intellectual partner (married 1949). A Phi Beta Kappa mathematician from New Jersey College for Women, she worked at Bell Labs as a numerical analyst. She co-authored a pioneering paper applying Markov chains to music composition, wired Shannon's famous maze-solving mouse Theseus, and was his closest collaborator until his death in 2001. An unsung genius in her own right.
78
+ **[Betty Shannon](https://en.wikipedia.org/wiki/Betty_Shannon)** (19222017) — Shannon's second wife and lifelong collaborator. Mathematician at Bell Labs, co-authored work on Markov chains in music, wired the maze-solving mouse Theseus. An unsung genius.
95
79
 
96
80
  Not 吹牛逼.
97
81
 
98
82
  ## License
99
83
 
100
- MIT
84
+ OpenAll-1.0
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.2-dev
package/bin/board CHANGED
@@ -14,7 +14,7 @@ CLAUDES_HOME = Path(__file__).resolve().parent.parent
14
14
  sys.path.insert(0, str(CLAUDES_HOME))
15
15
 
16
16
  from lib.board_db import BoardDB
17
- from lib.common import ClaudesEnv, parse_flags
17
+ from lib.common import ClaudesEnv, parse_flags, validate_identity
18
18
 
19
19
  # ---------------------------------------------------------------------------
20
20
  # Command registry
@@ -33,14 +33,15 @@ class Command:
33
33
  needs_identity: bool = True
34
34
  takes_rest: bool = True
35
35
  aliases: list[str] = field(default_factory=list)
36
+ hidden: bool = False
36
37
 
37
38
 
38
39
  COMMANDS: list[Command] = [
39
40
  # ── messaging ──
40
- Command("send", "lib.board_msg", "cmd_send", "send a message", "send <to> <msg> [--attach <f>]"),
41
- Command("status", "lib.board_msg", "cmd_status", "update your current task", "status <description>"),
42
- Command("inbox", "lib.board_msg", "cmd_inbox", "check unread messages", "inbox", takes_rest=False),
43
- Command("ack", "lib.board_msg", "cmd_ack", "clear inbox", "ack", takes_rest=False),
41
+ Command("send", "lib.board_msg", "cmd_send", "send a message", "send <to> <msg> [--attach <f>]", hidden=True),
42
+ Command("status", "lib.board_msg", "cmd_status", "update your current task", "status <description>", hidden=True),
43
+ Command("inbox", "lib.board_msg", "cmd_inbox", "check unread messages", "inbox", takes_rest=False, hidden=True),
44
+ Command("ack", "lib.board_msg", "cmd_ack", "clear inbox", "ack", takes_rest=False, hidden=True),
44
45
  Command("log", "lib.board_msg", "cmd_log", "message history", "log [n] [--mine]"),
45
46
  # ── views ──
46
47
  Command("view", "lib.board_view", "cmd_view", "session dashboard", "view", takes_rest=False),
@@ -72,6 +73,7 @@ COMMANDS: list[Command] = [
72
73
  "pre-build",
73
74
  needs_identity=False,
74
75
  takes_rest=False,
76
+ hidden=True,
75
77
  ),
76
78
  Command(
77
79
  "dirty",
@@ -81,12 +83,37 @@ COMMANDS: list[Command] = [
81
83
  "dirty",
82
84
  needs_identity=False,
83
85
  takes_rest=False,
86
+ hidden=True,
84
87
  ),
85
88
  Command(
86
- "files", "lib.board_view", "cmd_files", "list shared files", "files", needs_identity=False, takes_rest=False
89
+ "files",
90
+ "lib.board_view",
91
+ "cmd_files",
92
+ "list shared files",
93
+ "files",
94
+ needs_identity=False,
95
+ takes_rest=False,
96
+ hidden=True,
97
+ ),
98
+ Command(
99
+ "get",
100
+ "lib.board_view",
101
+ "cmd_get",
102
+ "view shared file content",
103
+ "get <hash|name>",
104
+ needs_identity=False,
105
+ hidden=True,
106
+ ),
107
+ Command(
108
+ "roster",
109
+ "lib.board_view",
110
+ "cmd_roster",
111
+ "team roster",
112
+ "roster",
113
+ needs_identity=False,
114
+ takes_rest=False,
115
+ hidden=True,
87
116
  ),
88
- Command("get", "lib.board_view", "cmd_get", "view shared file content", "get <hash|name>", needs_identity=False),
89
- Command("roster", "lib.board_view", "cmd_roster", "team roster", "roster", needs_identity=False, takes_rest=False),
90
117
  Command(
91
118
  "history",
92
119
  "lib.board_view",
@@ -94,6 +121,7 @@ COMMANDS: list[Command] = [
94
121
  "session message history",
95
122
  "history <session> [limit]",
96
123
  needs_identity=False,
124
+ hidden=True,
97
125
  ),
98
126
  Command(
99
127
  "freshness",
@@ -103,6 +131,7 @@ COMMANDS: list[Command] = [
103
131
  "freshness",
104
132
  needs_identity=False,
105
133
  takes_rest=False,
134
+ hidden=True,
106
135
  ),
107
136
  Command(
108
137
  "relations",
@@ -112,11 +141,20 @@ COMMANDS: list[Command] = [
112
141
  "relations",
113
142
  needs_identity=False,
114
143
  takes_rest=False,
144
+ hidden=True,
115
145
  ),
116
146
  # ── BBS ──
117
- Command("post", "lib.board_bbs", "cmd_post", "BBS: create new thread", "post <标题> <内容>"),
118
- Command("reply", "lib.board_bbs", "cmd_reply", "BBS: reply to thread", "reply <帖子ID> <内容>"),
119
- Command("thread", "lib.board_bbs", "cmd_thread", "BBS: view thread", "thread <帖子ID>", needs_identity=False),
147
+ Command("post", "lib.board_bbs", "cmd_post", "BBS: create new thread", "post <标题> <内容>", hidden=True),
148
+ Command("reply", "lib.board_bbs", "cmd_reply", "BBS: reply to thread", "reply <帖子ID> <内容>", hidden=True),
149
+ Command(
150
+ "thread",
151
+ "lib.board_bbs",
152
+ "cmd_thread",
153
+ "BBS: view thread",
154
+ "thread <帖子ID>",
155
+ needs_identity=False,
156
+ hidden=True,
157
+ ),
120
158
  Command(
121
159
  "threads",
122
160
  "lib.board_bbs",
@@ -125,19 +163,38 @@ COMMANDS: list[Command] = [
125
163
  "threads",
126
164
  needs_identity=False,
127
165
  takes_rest=False,
166
+ hidden=True,
128
167
  ),
129
168
  # ── bug ──
130
- Command("bug", "lib.board_bug", "cmd_bug", "bug tracker", "bug {report|assign|fix|list|overdue}"),
169
+ Command("bug", "lib.board_bug", "cmd_bug", "bug tracker", "bug {report|assign|fix|list|overdue}", hidden=True),
131
170
  # ── task ──
132
- Command("task", "lib.board_task", "cmd_task", "task queue management", "task {add|done|list|next}"),
171
+ Command("task", "lib.board_task", "cmd_task", "task queue management", "task {add|done|list|next}", hidden=True),
172
+ # ── heartbeat ──
173
+ Command(
174
+ "pulse", "lib.board_pulse", "cmd_pulse", "heartbeat + unread count", "pulse", takes_rest=False, hidden=True
175
+ ),
133
176
  # ── voting ──
134
- Command("propose", "lib.board_vote", "cmd_propose", "create a proposal", "propose <内容> [--type S]"),
135
- Command("vote", "lib.board_vote", "cmd_vote", "vote on a proposal", "vote <N> <SUPPORT|OBJECT> <reason>"),
136
- Command("tally", "lib.board_vote", "cmd_tally", "recount votes", "tally <N>", needs_identity=False),
177
+ Command("propose", "lib.board_vote", "cmd_propose", "create a proposal", "propose <内容> [--type S]", hidden=True),
178
+ Command(
179
+ "vote", "lib.board_vote", "cmd_vote", "vote on a proposal", "vote <N> <SUPPORT|OBJECT> <reason>", hidden=True
180
+ ),
181
+ Command("tally", "lib.board_vote", "cmd_tally", "recount votes", "tally <N>", needs_identity=False, hidden=True),
137
182
  # ── mailbox (encrypted) ──
138
- Command("keygen", "lib.board_mailbox", "cmd_keygen", "generate encryption keypair", "keygen", takes_rest=False),
139
- Command("seal", "lib.board_mailbox", "cmd_seal", "send encrypted message", "seal <recipient> <message>"),
140
- Command("unseal", "lib.board_mailbox", "cmd_unseal", "read encrypted inbox", "unseal", takes_rest=False),
183
+ Command(
184
+ "keygen",
185
+ "lib.board_mailbox",
186
+ "cmd_keygen",
187
+ "generate encryption keypair",
188
+ "keygen",
189
+ takes_rest=False,
190
+ hidden=True,
191
+ ),
192
+ Command(
193
+ "seal", "lib.board_mailbox", "cmd_seal", "send encrypted message", "seal <recipient> <message>", hidden=True
194
+ ),
195
+ Command(
196
+ "unseal", "lib.board_mailbox", "cmd_unseal", "read encrypted inbox", "unseal", takes_rest=False, hidden=True
197
+ ),
141
198
  Command(
142
199
  "mailbox-log",
143
200
  "lib.board_mailbox",
@@ -145,9 +202,10 @@ COMMANDS: list[Command] = [
145
202
  "encrypted message history",
146
203
  "mailbox-log",
147
204
  takes_rest=False,
205
+ hidden=True,
148
206
  ),
149
207
  # ── admin ──
150
- Command("kudos", "lib.board_admin", "cmd_kudos", "give public recognition", "kudos <target> <reason>"),
208
+ Command("kudos", "lib.board_admin", "cmd_kudos", "give public recognition", "kudos <target> <reason>", hidden=True),
151
209
  Command(
152
210
  "kudos-list",
153
211
  "lib.board_admin",
@@ -157,12 +215,13 @@ COMMANDS: list[Command] = [
157
215
  needs_identity=False,
158
216
  takes_rest=False,
159
217
  aliases=["kudos-board"],
218
+ hidden=True,
160
219
  ),
161
- Command("suspend", "lib.board_admin", "cmd_suspend", "suspend a session", "suspend <session>"),
162
- Command("resume", "lib.board_admin", "cmd_resume", "resume a session", "resume <session>"),
220
+ Command("suspend", "lib.board_admin", "cmd_suspend", "suspend a session", "suspend <session>", hidden=True),
221
+ Command("resume", "lib.board_admin", "cmd_resume", "resume a session", "resume <session>", hidden=True),
163
222
  # ── git lock ──
164
- Command("git-lock", "lib.board_lock", "cmd_git_lock", "acquire git index lock", "git-lock [reason]"),
165
- Command("git-unlock", "lib.board_lock", "cmd_git_unlock", "release git index lock", "git-unlock"),
223
+ Command("git-lock", "lib.board_lock", "cmd_git_lock", "acquire git index lock", "git-lock [reason]", hidden=True),
224
+ Command("git-unlock", "lib.board_lock", "cmd_git_unlock", "release git index lock", "git-unlock", hidden=True),
166
225
  Command(
167
226
  "git-lock-status",
168
227
  "lib.board_lock",
@@ -171,6 +230,7 @@ COMMANDS: list[Command] = [
171
230
  "git-lock-status",
172
231
  needs_identity=False,
173
232
  takes_rest=False,
233
+ hidden=True,
174
234
  ),
175
235
  # ── tui ──
176
236
  Command(
@@ -182,6 +242,14 @@ COMMANDS: list[Command] = [
182
242
  needs_identity=False,
183
243
  takes_rest=False,
184
244
  ),
245
+ # ── pending actions ──
246
+ Command(
247
+ "pending",
248
+ "lib.board_pending",
249
+ "cmd_pending",
250
+ "pending actions queue",
251
+ "pending {add|list|verify|retry|resolve}",
252
+ ),
185
253
  # ── maintenance ──
186
254
  Command(
187
255
  "prune",
@@ -190,6 +258,7 @@ COMMANDS: list[Command] = [
190
258
  "prune old messages",
191
259
  "prune [--before DAYS] [--dry-run]",
192
260
  needs_identity=False,
261
+ hidden=True,
193
262
  ),
194
263
  Command(
195
264
  "backup",
@@ -198,6 +267,7 @@ COMMANDS: list[Command] = [
198
267
  "backup database",
199
268
  "backup [--output <path>]",
200
269
  needs_identity=False,
270
+ hidden=True,
201
271
  ),
202
272
  Command(
203
273
  "restore",
@@ -206,6 +276,7 @@ COMMANDS: list[Command] = [
206
276
  "restore from backup",
207
277
  "restore <file> [--force]",
208
278
  needs_identity=False,
279
+ hidden=True,
209
280
  ),
210
281
  ]
211
282
 
@@ -246,18 +317,12 @@ def _fmt_command(cmd: Command, width: int) -> str:
246
317
 
247
318
 
248
319
  def print_help() -> None:
249
- max_name = max(len(c.name) + (2 + len(", ".join(c.aliases)) if c.aliases else 0) for c in COMMANDS) + 2
250
- print("board v2 agent coordination tool (SQLite backend, Python)\n")
320
+ visible = [c for c in COMMANDS if not c.hidden]
321
+ max_name = max(len(c.name) + (2 + len(", ".join(c.aliases)) if c.aliases else 0) for c in visible) + 2
322
+ print("board — 同学协作工具\n")
251
323
  print("Usage: board --as <name> <command> [args...]\n")
252
324
  print("Commands:")
253
- last_module = ""
254
- for c in COMMANDS:
255
- mod = c.module.rsplit(".", 1)[-1] # board_msg, board_view, etc.
256
- if mod != last_module:
257
- if last_module:
258
- print()
259
- print(f" [{mod}]")
260
- last_module = mod
325
+ for c in visible:
261
326
  print(_fmt_command(c, max_name))
262
327
  print()
263
328
 
@@ -290,6 +355,9 @@ def main() -> None:
290
355
  print("ERROR: identity required. Use: board --as <name> <command>", file=sys.stderr)
291
356
  raise SystemExit(1)
292
357
 
358
+ if identity:
359
+ validate_identity(db, identity)
360
+
293
361
  _dispatch(cmd, db, identity, rest)
294
362
 
295
363
 
package/bin/cnb CHANGED
@@ -10,6 +10,9 @@ else
10
10
  B='' D='' G='' C='' Y='' N=''
11
11
  fi
12
12
 
13
+ # ---- Export project root so all subprocesses can find .claudes/ ----
14
+ export CNB_PROJECT="${CNB_PROJECT:-$(pwd)}"
15
+
13
16
  # ---- Subcommands (exact match, always first) ----
14
17
  if [ $# -gt 0 ]; then
15
18
  case "$1" in
@@ -56,10 +59,10 @@ if [ $# -gt 0 ]; then
56
59
  "$CLAUDES_HOME/bin/init" --team "$_TEAM_FILE"
57
60
  _NAMES=$(python3 -c "
58
61
  import tomllib, sys
59
- data = tomllib.loads(open('$_TEAM_FILE').read())
62
+ data = tomllib.loads(open(sys.argv[1]).read())
60
63
  names = list(data.get('session', {}).keys())
61
64
  print(' '.join(names))
62
- ")
65
+ " "$_TEAM_FILE")
63
66
  if [ -n "$_NAMES" ]; then
64
67
  "$CLAUDES_HOME/bin/swarm" start $_NAMES >/dev/null 2>&1 || true
65
68
  echo "OK 团队已启动: $_NAMES"
@@ -72,22 +75,34 @@ print(' '.join(names))
72
75
  printf " 多个 Claude Code 实例协作的团队开发工具\n\n"
73
76
  printf " cnb 开始(默认2位同学,AI大佬主题)\n"
74
77
  printf " cnb 5 5位同学\n"
75
- printf " cnb pokemon 宝可梦主题\n"
76
- printf " cnb 5 pokemon 5位同学 + 宝可梦主题\n"
78
+ printf " cnb threebody 三体主题\n"
79
+ printf " cnb 5 titan 5位同学 + 科技先锋主题\n"
77
80
  printf " cnb compose [file] 从配置文件启动团队\n\n"
78
- printf " 主题: ai animal food lang music myth pokemon space\n\n"
81
+ printf " 主题: ai animal food lang music myth space threebody titan\n\n"
79
82
  printf " cnb ui 交互式团队面板\n"
80
- printf " cnb ps 列出 agent 状态\n"
81
- printf " cnb logs <name> 查看某个 agent 的消息历史\n"
82
- printf " cnb exec <name> \"msg\" 给某个 agent 发指令\n"
83
- printf " cnb stop <name> 停止某个 agent\n"
83
+ printf " cnb ps 列出同学状态\n"
84
+ printf " cnb logs <name> 查看某个同学的消息历史\n"
85
+ printf " cnb exec <name> \"msg\" 给某个同学发指令\n"
86
+ printf " cnb stop <name> 停止某个同学\n"
84
87
  printf " cnb doctor 健康检查\n\n"
85
88
  printf " cnb board [...] 底层消息/任务命令\n"
86
- printf " cnb swarm [...] 管理后台 agent\n"
89
+ printf " cnb swarm [...] 管理后台同学\n"
87
90
  exit 0 ;;
88
91
  esac
89
92
  fi
90
93
 
94
+ # ---- Helper: start dispatcher if not already running ----
95
+ _start_dispatcher() {
96
+ local pidfile="$CNB_PROJECT/.claudes/dispatcher.pid"
97
+ if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
98
+ return
99
+ fi
100
+ mkdir -p "$CNB_PROJECT/.claudes/logs"
101
+ "$CLAUDES_HOME/bin/dispatcher" >> "$CNB_PROJECT/.claudes/logs/dispatcher.log" 2>&1 &
102
+ echo $! > "$pidfile"
103
+ disown
104
+ }
105
+
91
106
  # ---- Compose mode: reuse existing team from config.toml ----
92
107
  if [ -f .claudes/config.toml ] && [ -f .claudes/board.db ]; then
93
108
  _EXISTING=$(grep '^sessions' .claudes/config.toml | sed 's/sessions = \[//;s/\]//;s/"//g;s/,/ /g;s/ */ /g' | xargs)
@@ -96,14 +111,16 @@ if [ -f .claudes/config.toml ] && [ -f .claudes/board.db ]; then
96
111
  WORKERS=$(echo "$_EXISTING" | cut -d' ' -f2-)
97
112
  LABEL=$(grep '^prefix' .claudes/config.toml | cut -d'"' -f2)
98
113
  _NUM=$(echo "$WORKERS" | wc -w | tr -d ' ')
99
- "$CLAUDES_HOME/bin/swarm" start $WORKERS >/dev/null 2>&1 || true
114
+ "$CLAUDES_HOME/bin/swarm" start $WORKERS >/dev/null 2>&1 &
115
+ disown
116
+ _start_dispatcher
100
117
  fi
101
118
  else
102
119
  # ---- Fresh start: parse [number] [theme] in any order ----
103
120
  _NUM=2
104
121
  _THEME_IDX=6 # default: AI 大佬
105
122
 
106
- _THEME_MAP="ai:6 animal:0 food:2 lang:1 music:5 myth:4 pokemon:7 space:3"
123
+ _THEME_MAP="ai:6 animal:0 food:2 lang:1 music:5 myth:4 space:3 threebody:7 titan:8"
107
124
 
108
125
  for arg in "$@"; do
109
126
  if [[ "$arg" =~ ^[0-9]+$ ]]; then
@@ -115,7 +132,7 @@ else
115
132
  else
116
133
  printf "${Y}未知参数: ${arg}${N}\n" >&2
117
134
  echo "用法: cnb [数量] [主题]" >&2
118
- echo "主题: ai animal food lang music myth pokemon space" >&2
135
+ echo "主题: ai animal food lang music myth space threebody titan" >&2
119
136
  exit 1
120
137
  fi
121
138
  fi
@@ -129,10 +146,11 @@ else
129
146
  "nebula pulsar quasar comet aurora meteor nova orbit cosmos galaxy venus mars saturn pluto titan europa io ceres vega sirius"
130
147
  "dragon phoenix griffin hydra kraken sphinx unicorn pixie goblin imp djinn yeti nymph chimera basilisk manticore cerberus minotaur golem banshee"
131
148
  "jazz blues funk reggae disco punk grunge techno dubstep waltz tango salsa bossa swing opera gospel motown bebop ska hiphop"
132
- "altman dario ilya lecun karpathy hassabis sutskever hinton bengio fei-fei ng zuck bezos nadella pichai musk huang lisa-su amodei jack-clark"
133
- "pikachu charmander snorlax eevee jigglypuff mewtwo gengar squirtle bulbasaur vulpix psyduck togepi mudkip lucario gardevoir rayquaza mimikyu ditto zorua umbreon"
149
+ "altman dario ilya lecun karpathy hassabis vaswani hinton bengio fei-fei ng zuck bezos nadella pichai musk huang lisa-su radford jack-clark"
150
+ "luo-ji shi-qiang ye-wenjie cheng-xin zhang-beihai yun-tianming wang-miao ding-yi yang-dong zhuang-yan guan-yifan shen-yufei wei-cheng hines tyler wade evans rey-diaz tomoko singer"
151
+ "wang-xingxing ren-zhengfei zhang-yiming lei-jun li-yanhong li-kaifu liang-wenfeng yang-zhilin kaiming-he ma-huateng wang-jian lu-qi goodfellow schmidhuber dean chollet silver carmack torvalds wolfram"
134
152
  )
135
- _LABELS=("小动物" "编程语言" "美食" "太空" "神话生物" "音乐风格" "AI 大佬" "宝可梦")
153
+ _LABELS=("小动物" "编程语言" "美食" "太空" "神话生物" "音乐风格" "AI 大佬" "三体" "科技先锋")
136
154
 
137
155
  [ "$_NUM" -lt 1 ] && _NUM=1
138
156
  [ "$_NUM" -gt 20 ] && _NUM=20
@@ -144,40 +162,42 @@ else
144
162
 
145
163
  printf "${D}初始化 .claudes/ ...${N}\n"
146
164
  "$CLAUDES_HOME/bin/init" $ALL_NAMES >/dev/null 2>&1
147
- "$CLAUDES_HOME/bin/swarm" start $WORKERS >/dev/null 2>&1 || true
165
+ "$CLAUDES_HOME/bin/swarm" start $WORKERS >/dev/null 2>&1 &
166
+ disown
167
+ _start_dispatcher
148
168
  fi
149
169
 
150
170
  # ---- Slash commands (runtime, gitignored) ----
151
171
  CMD_DIR=".claude/commands"
152
172
  mkdir -p "$CMD_DIR"
153
- grep -qx 'commands/cs-*' .claude/.gitignore 2>/dev/null || echo 'commands/cs-*' >> .claude/.gitignore 2>/dev/null || true
173
+ grep -qx 'commands/cnb-*' .claude/.gitignore 2>/dev/null || echo 'commands/cnb-*' >> .claude/.gitignore 2>/dev/null || true
154
174
  BOARD="$CLAUDES_HOME/bin/board"
155
175
  SWARM="$CLAUDES_HOME/bin/swarm"
156
176
  _PREFIX=$(grep '^prefix' .claudes/config.toml 2>/dev/null | cut -d'"' -f2)
157
- cat > "$CMD_DIR/cs-watch.md" <<EOF
177
+ cat > "$CMD_DIR/cnb-watch.md" <<EOF
158
178
  看某个同学在干什么。解析 \$ARGUMENTS 拿到名字。
159
179
  运行 \`tmux capture-pane -t ${_PREFIX}-<名字> -p -S -30 2>/dev/null | tail -20\`,用简洁的话告诉用户这个同学在做什么、进展到哪了。
160
180
  EOF
161
- cat > "$CMD_DIR/cs-overview.md" <<EOF
181
+ cat > "$CMD_DIR/cnb-overview.md" <<EOF
162
182
  团队总览。对每个同学运行 \`tmux capture-pane -t ${_PREFIX}-<名字> -p -S -10 2>/dev/null | tail -5\`,汇总成一个简洁的表格告诉用户:谁在干什么、谁卡了、谁闲着。
163
183
  同学列表:${WORKERS}
164
184
  EOF
165
- cat > "$CMD_DIR/cs-progress.md" <<EOF
185
+ cat > "$CMD_DIR/cnb-progress.md" <<EOF
166
186
  运行 \`${BOARD} --as ${ME} inbox\` 和 \`${BOARD} --as ${ME} view\`,汇总最近的进展:谁完成了什么、有什么新消息、当前整体进度如何。用简洁的话告诉用户。
167
187
  EOF
168
- cat > "$CMD_DIR/cs-history.md" <<EOF
188
+ cat > "$CMD_DIR/cnb-history.md" <<EOF
169
189
  运行 \`${BOARD} --as ${ME} log 50\`,把完整的消息历史展示给我。
170
190
  EOF
171
- cat > "$CMD_DIR/cs-update.md" <<EOF
191
+ cat > "$CMD_DIR/cnb-update.md" <<EOF
172
192
  运行 \`pip install --upgrade claude-nb\`,更新到最新版本。把结果告诉我,如果更新成功提醒用户重启 cnb 以生效。
173
193
  EOF
174
- cat > "$CMD_DIR/cs-help.md" <<EOF
175
- 列出所有 /cs-* 命令:
176
- - /cs-overview — 团队总览:谁在干什么、谁卡了
177
- - /cs-watch <名字> — 看某个同学在干什么
178
- - /cs-progress — 最近进展汇总
179
- - /cs-history — 查看完整消息历史
180
- - /cs-update — 更新 cnb 到最新版
194
+ cat > "$CMD_DIR/cnb-help.md" <<EOF
195
+ 列出所有 /cnb-* 命令:
196
+ - /cnb-overview — 团队总览:谁在干什么、谁卡了
197
+ - /cnb-watch <名字> — 看某个同学在干什么
198
+ - /cnb-progress — 最近进展汇总
199
+ - /cnb-history — 查看完整消息历史
200
+ - /cnb-update — 更新 cnb 到最新版
181
201
  EOF
182
202
 
183
203
  # ---- Require interactive terminal ----
@@ -204,8 +224,14 @@ SYSPROMPT="你是 ${ME}。你的终端直接面对用户。
204
224
  工作方式:
205
225
  - 用户说想做什么,你拆任务分给同学,自己也干活。
206
226
  - 不要让用户跑命令。你直接用上面的命令操作。
207
- - 定期 inbox 看同学的回复,汇总进展给用户。
208
- - 用户可以用 /cs-help 查看所有协作命令。
209
- - 同学之间可以互相 send 消息协作,不用什么都通过你。"
227
+ - 同学之间可以互相 send 消息协作,不用什么都通过你。
228
+ - 用户可以用 /cnb-help 查看所有协作命令。
229
+
230
+ 重要 — 监控团队进展:
231
+ 派任务给同学后,你必须主动跟进,不要派完就忘。使用 Monitor 工具挂一个后台轮询:
232
+ Monitor 命令示例(每 30 秒检查一次 inbox,有新消息就通知你):
233
+ last_count=0; while true; do out=\$(${BOARD} --as ${ME} inbox 2>&1); count=\$(echo \"\$out\" | grep -c '<message'); if [ \"\$count\" -gt \"\$last_count\" ]; then echo \"\$out\"; last_count=\$count; fi; sleep 30; done
234
+ 设置 persistent: true,这样整个会话期间都会监控。
235
+ 收到通知后汇总进展给用户。派完任务后应立刻启动监控。"
210
236
 
211
237
  exec claude --name "${ME}" --append-system-prompt "$SYSPROMPT"