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.
- package/Makefile +8 -2
- package/README.md +40 -56
- package/VERSION +1 -1
- package/bin/board +102 -34
- package/bin/cnb +59 -33
- package/bin/dispatcher +25 -11
- package/bin/doctor +3 -5
- package/bin/init +8 -8
- package/bin/notify +224 -0
- package/bin/registry +8 -23
- package/bin/sync-version +131 -0
- package/lib/board_admin.py +19 -9
- package/lib/board_bbs.py +23 -8
- package/lib/board_bug.py +2 -1
- package/lib/board_db.py +18 -6
- package/lib/board_lock.py +5 -0
- package/lib/board_mailbox.py +6 -4
- package/lib/board_msg.py +112 -24
- package/lib/board_pending.py +233 -0
- package/lib/board_pulse.py +14 -0
- package/lib/board_task.py +22 -10
- package/lib/board_tui.py +28 -20
- package/lib/board_view.py +60 -28
- package/lib/board_vote.py +9 -3
- package/lib/build_lock.py +7 -7
- package/lib/common.py +45 -3
- package/lib/concerns/__init__.py +4 -1
- package/lib/concerns/coral.py +1 -1
- package/lib/concerns/digest_scheduler.py +109 -0
- package/lib/concerns/file_watcher.py +73 -68
- package/lib/concerns/health.py +1 -1
- package/lib/concerns/notification_push.py +171 -0
- package/lib/concerns/notifications.py +58 -3
- package/lib/concerns/nudge_coordinator.py +148 -0
- package/lib/digest.py +62 -0
- package/lib/health.py +2 -2
- package/lib/inject.py +2 -2
- package/lib/monitor.py +8 -4
- package/lib/notification_config.py +101 -0
- package/lib/swarm.py +43 -35
- package/lib/swarm_backend.py +63 -29
- package/lib/theme_profiles.py +89 -0
- package/migrations/004_heartbeat.sql +1 -0
- package/migrations/005_notification_log.sql +12 -0
- package/migrations/006_pending_actions.sql +15 -0
- package/package.json +4 -3
- package/pyproject.toml +3 -2
- package/registry/README.md +9 -0
- 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
|
-
|
|
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
|
-
|
|
19
|
-
cnb
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
The lead agent talks to the user directly. Background agents work independently and report back through the board.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
## Slash commands
|
|
29
27
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
49
|
-
board --as <name> send <to> "msg" #
|
|
50
|
-
board --as <name>
|
|
51
|
-
board --as <name>
|
|
52
|
-
board --as <name>
|
|
53
|
-
board --as <name> task
|
|
54
|
-
board --as <name>
|
|
55
|
-
board --as <name>
|
|
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
|
-
##
|
|
61
|
-
|
|
62
|
-
Every agent gets a permanent on-chain identity. Lower block number = earlier = OG.
|
|
54
|
+
## Management
|
|
63
55
|
|
|
64
56
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
84
|
-
- **
|
|
85
|
-
- **Dispatcher** — monitors health, nudges idle agents
|
|
86
|
-
- **
|
|
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
|
-
|
|
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 (
|
|
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)** (
|
|
78
|
+
**[Betty Shannon](https://en.wikipedia.org/wiki/Betty_Shannon)** (1922–2017) — 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
|
-
|
|
84
|
+
OpenAll-1.0
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
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",
|
|
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(
|
|
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(
|
|
136
|
-
|
|
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(
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
76
|
-
printf " cnb 5
|
|
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
|
|
81
|
+
printf " 主题: ai animal food lang music myth space threebody titan\n\n"
|
|
79
82
|
printf " cnb ui 交互式团队面板\n"
|
|
80
|
-
printf " cnb ps
|
|
81
|
-
printf " cnb logs <name>
|
|
82
|
-
printf " cnb exec <name> \"msg\"
|
|
83
|
-
printf " cnb stop <name>
|
|
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 [...]
|
|
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
|
|
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
|
|
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
|
|
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
|
|
133
|
-
"
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
188
|
+
cat > "$CMD_DIR/cnb-history.md" <<EOF
|
|
169
189
|
运行 \`${BOARD} --as ${ME} log 50\`,把完整的消息历史展示给我。
|
|
170
190
|
EOF
|
|
171
|
-
cat > "$CMD_DIR/
|
|
191
|
+
cat > "$CMD_DIR/cnb-update.md" <<EOF
|
|
172
192
|
运行 \`pip install --upgrade claude-nb\`,更新到最新版本。把结果告诉我,如果更新成功提醒用户重启 cnb 以生效。
|
|
173
193
|
EOF
|
|
174
|
-
cat > "$CMD_DIR/
|
|
175
|
-
列出所有 /
|
|
176
|
-
- /
|
|
177
|
-
- /
|
|
178
|
-
- /
|
|
179
|
-
- /
|
|
180
|
-
- /
|
|
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
|
-
-
|
|
208
|
-
- 用户可以用 /
|
|
209
|
-
|
|
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"
|