@botbuddy/cli 1.27.0 → 1.29.0
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/package.json +1 -1
- package/src/commands.mjs +18 -8
- package/src/pw/run.mjs +2 -2
- package/src/setup-block.mjs +75 -0
- package/src/wait.mjs +7 -4
package/package.json
CHANGED
package/src/commands.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { cmdDocker } from "./docker-hygiene.mjs";
|
|
|
12
12
|
import { cmdRun } from "./run.mjs";
|
|
13
13
|
import { cmdTest } from "./test-lane.mjs";
|
|
14
14
|
import { runWait } from "./wait.mjs";
|
|
15
|
+
import { SETUP_BLOCK } from "./setup-block.mjs";
|
|
15
16
|
import { green, red, cyan, dim, bold, die } from "./utils.mjs";
|
|
16
17
|
import { VERSION } from "./version.mjs";
|
|
17
18
|
import { setupMcpKey, revokeMcpKey, resolveMcpConfigKey, resolveEnvVarName, DEFAULT_MCP_ENV_VAR, McpKeyError, McpKeyStoreError } from "./mcp-key.mjs";
|
|
@@ -114,8 +115,8 @@ async function cmdTelemetry(args) {
|
|
|
114
115
|
return 0;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
function cmdHelp() {
|
|
118
|
-
|
|
118
|
+
export function cmdHelp(log = console.log) {
|
|
119
|
+
log(`${bold("bb")} ${dim(`v${VERSION}`)} — Swarm coordination CLI
|
|
119
120
|
|
|
120
121
|
${dim(`Run everything as ${cyan("bb <command>")} — the short, preferred alias. ${cyan("botbuddy <command>")} is the identical long form.`)}
|
|
121
122
|
|
|
@@ -184,7 +185,9 @@ ${bold("OTHER")}
|
|
|
184
185
|
locks -m [--host name] Reserve typed local resources, including Playwright MCP lanes
|
|
185
186
|
update Install the latest @botbuddy/cli in the background via npm
|
|
186
187
|
help Show this help
|
|
187
|
-
version Show version
|
|
188
|
+
version Show version
|
|
189
|
+
|
|
190
|
+
${SETUP_BLOCK}`);
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
// ─── BOT-876: generic tool access ───────────────────────────────
|
|
@@ -325,8 +328,8 @@ async function cmdLogin(args, { errorLog = (line) => console.error(line) } = {})
|
|
|
325
328
|
}
|
|
326
329
|
}
|
|
327
330
|
|
|
328
|
-
function loginHelp() {
|
|
329
|
-
|
|
331
|
+
export function loginHelp(log = console.log) {
|
|
332
|
+
log(`${bold("botbuddy login")} — authenticate via OAuth (browser + loopback callback)
|
|
330
333
|
|
|
331
334
|
${bold("USAGE")}
|
|
332
335
|
botbuddy login [--no-browser] [--tenant <slug>]
|
|
@@ -390,7 +393,9 @@ ${bold("NOTES")}
|
|
|
390
393
|
${bold("RECOVERY")}
|
|
391
394
|
If ${cyan("botbuddy status")} shows no client key, run ${cyan("botbuddy login")}. For an
|
|
392
395
|
unattended CI host (no browser), mint a key on a machine with a browser
|
|
393
|
-
(${cyan("botbuddy mcp setup")}) and export it as ${cyan("$BOTBUDDY_MCP_KEY")} on the CI host
|
|
396
|
+
(${cyan("botbuddy mcp setup")}) and export it as ${cyan("$BOTBUDDY_MCP_KEY")} on the CI host.
|
|
397
|
+
|
|
398
|
+
${SETUP_BLOCK}`);
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
async function cmdStart(args) {
|
|
@@ -599,7 +604,7 @@ function cmdHeartbeat(args) {
|
|
|
599
604
|
|
|
600
605
|
// ─── BOT-1607: `botbuddy mcp` — the tier-2 bb_mcp_ config key ────────────────
|
|
601
606
|
|
|
602
|
-
function mcpHelp(log) {
|
|
607
|
+
export function mcpHelp(log = console.log) {
|
|
603
608
|
log(`Usage: botbuddy mcp <setup|revoke|status|env> [options]
|
|
604
609
|
|
|
605
610
|
setup [--env <NAME>] [--tenant <slug>] [--label <label>] [--expiry-days <n>] [--force]
|
|
@@ -607,6 +612,9 @@ function mcpHelp(log) {
|
|
|
607
612
|
${cyan("botbuddy login")} owner/client credential), store it in the
|
|
608
613
|
Keychain under ${dim("BOTBUDDY_MCP_KEY")} (or --env <NAME>), and print
|
|
609
614
|
the .mcp.json / .codex/config.toml snippets that reference it.
|
|
615
|
+
A bb_mcp_ key is tenant-sealed: on a machine serving more than
|
|
616
|
+
one tenant, mint each tenant under a distinct --env <NAME> var
|
|
617
|
+
and commit that repo's matching .botbuddy-agent.json mcp_env.
|
|
610
618
|
Refuses an occupied Keychain slot unless --force (revoke the
|
|
611
619
|
old key first — --force leaves it live server-side).
|
|
612
620
|
revoke <agent_id> [--tenant <slug>] [--env <NAME>]
|
|
@@ -625,7 +633,9 @@ function mcpHelp(log) {
|
|
|
625
633
|
or in the desktop LaunchAgent env-install recipe.
|
|
626
634
|
|
|
627
635
|
Present the key from .mcp.json instead of reusing an agent session token — it is
|
|
628
|
-
independently revocable, so a leaked config credential rotates without a re-login
|
|
636
|
+
independently revocable, so a leaked config credential rotates without a re-login.
|
|
637
|
+
|
|
638
|
+
${SETUP_BLOCK}`);
|
|
629
639
|
}
|
|
630
640
|
|
|
631
641
|
// Actionable recovery for an mcp setup/revoke failure code.
|
package/src/pw/run.mjs
CHANGED
|
@@ -35,7 +35,7 @@ async function gate({ env, host, lane, deps }) {
|
|
|
35
35
|
// server's owner_is_caller.
|
|
36
36
|
const sessionToken = deps.sessionToken ?? readAgentKeyEnv(env);
|
|
37
37
|
if (sessionToken && !AGENT_KEY_RE.test(sessionToken)) {
|
|
38
|
-
return { allowed: false, message: "bb-pw: $BOTBUDDY_AGENT_KEY is malformed (expected bb_agent_<64 hex>). Re-register
|
|
38
|
+
return { allowed: false, message: "bb-pw: $BOTBUDDY_AGENT_KEY is malformed (tier-3 session token; expected bb_agent_<64 hex>). Re-register: register_agent → export BOTBUDDY_AGENT_KEY. See docs/agent-connectivity.md. (BB_PW_NO_LOCK=1 runs local-only, unlocked.)" };
|
|
39
39
|
}
|
|
40
40
|
const sessionAgentId = deps.sessionId ?? env.BOTBUDDY_SESSION_ID ?? null;
|
|
41
41
|
const registeredAgentId = await (deps.readSessionAgentId ?? readRegisteredAgentId)();
|
|
@@ -49,7 +49,7 @@ async function gate({ env, host, lane, deps }) {
|
|
|
49
49
|
} catch { token = null; }
|
|
50
50
|
}
|
|
51
51
|
if (!token) {
|
|
52
|
-
return { allowed: false, message: "bb-pw: no BotBuddy credential —
|
|
52
|
+
return { allowed: false, message: "bb-pw: no BotBuddy credential — a lane needs the tier-3 session token (bb_agent_: register_agent → export $BOTBUDDY_AGENT_KEY) or your tier-2 .mcp.json key ($BOTBUDDY_MCP_KEY, bb mcp setup). See docs/agent-connectivity.md. (BB_PW_NO_LOCK=1 runs local-only, unlocked.)" };
|
|
53
53
|
}
|
|
54
54
|
coordinator = createSessionTokenCoordinator({ token, fetchImpl: deps.fetch });
|
|
55
55
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// BOT-1644 — ONE source of truth for the connectivity ladder and the credential
|
|
2
|
+
// tier names, so every CLI help surface (`bb --help`, `bb wait --help`,
|
|
3
|
+
// `bb login --help`, `bb mcp --help`), the recovery strings, and the canonical
|
|
4
|
+
// guide all agree. The snapshot test (agent-connectivity.test.mjs) asserts the
|
|
5
|
+
// SETUP block appears verbatim in all four help outputs, so drift is caught.
|
|
6
|
+
//
|
|
7
|
+
// This documents the CURRENT supported process (BOT-1644 scope). It does NOT
|
|
8
|
+
// describe the P1–P6 "self-healing auth" redesign, which is a separate future
|
|
9
|
+
// project.
|
|
10
|
+
|
|
11
|
+
/** The canonical guide, referenced from every SETUP block and recovery. */
|
|
12
|
+
export const CONNECTIVITY_GUIDE = "docs/agent-connectivity.md";
|
|
13
|
+
|
|
14
|
+
// The three connectivity credential tiers, named consistently everywhere. A
|
|
15
|
+
// connectivity error names the MISSING tier and the exact next command; the
|
|
16
|
+
// guide's tiered table and the help SETUP block use these same labels.
|
|
17
|
+
export const TIERS = Object.freeze({
|
|
18
|
+
// Tier 1 — per-machine client key. `bb login`, USER-scoped (reaches every
|
|
19
|
+
// tenant you belong to; each request pins one). Never a wait credential.
|
|
20
|
+
client: Object.freeze({
|
|
21
|
+
n: 1,
|
|
22
|
+
label: "client key",
|
|
23
|
+
prefix: "bb_cli_",
|
|
24
|
+
env: "BOTBUDDY_CLIENT_KEY",
|
|
25
|
+
setup: "bb login",
|
|
26
|
+
scope: "user",
|
|
27
|
+
}),
|
|
28
|
+
// Tier 2 — per-tenant MCP config key. `bb mcp setup`, TENANT-bound, lives in
|
|
29
|
+
// .mcp.json under $BOTBUDDY_MCP_KEY. Authenticates MCP calls + register_agent.
|
|
30
|
+
mcp: Object.freeze({
|
|
31
|
+
n: 2,
|
|
32
|
+
label: "MCP key",
|
|
33
|
+
prefix: "bb_mcp_",
|
|
34
|
+
env: "BOTBUDDY_MCP_KEY",
|
|
35
|
+
setup: "bb mcp setup",
|
|
36
|
+
scope: "tenant",
|
|
37
|
+
}),
|
|
38
|
+
// Tier 3 — per-session agent token. `register_agent` (over MCP), tenant-bound,
|
|
39
|
+
// 8 h TTL, revoked on re-register. The ONLY credential a wait/run/test/pw
|
|
40
|
+
// accepts. Exported as $BOTBUDDY_AGENT_KEY (+ $BOTBUDDY_SESSION_ID for
|
|
41
|
+
// attribution).
|
|
42
|
+
session: Object.freeze({
|
|
43
|
+
n: 3,
|
|
44
|
+
label: "session token",
|
|
45
|
+
prefix: "bb_agent_",
|
|
46
|
+
env: "BOTBUDDY_AGENT_KEY",
|
|
47
|
+
setup: "register_agent",
|
|
48
|
+
scope: "session",
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// The register_agent → export handoff, named once. Recovery strings reuse it so
|
|
53
|
+
// the exact two export lines never drift from the guide.
|
|
54
|
+
export const EXPORT_STEP =
|
|
55
|
+
"register_agent → export BOTBUDDY_AGENT_KEY=<session_token> (and BOTBUDDY_SESSION_ID=<session_id>)";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The shared SETUP block reproduced verbatim in every `--help` output. Plain
|
|
59
|
+
* text (no ANSI) so the snapshot test can match it byte-for-byte across surfaces.
|
|
60
|
+
* The E2E ladder in order, then the fresh-shell caveat and the guide link.
|
|
61
|
+
*/
|
|
62
|
+
export const SETUP_BLOCK = `SETUP — the connectivity ladder (install → connect → wait)
|
|
63
|
+
1. npm i -g @botbuddy/cli install the CLI
|
|
64
|
+
2. bb login tier 1 · client key (bb_cli_, user-scoped) → Keychain
|
|
65
|
+
3. bb mcp setup tier 2 · MCP key (bb_mcp_, tenant-bound) → Keychain + .mcp.json ($BOTBUDDY_MCP_KEY)
|
|
66
|
+
(Keychainless host: the key is printed ONCE — store + export + edit .mcp.json yourself)
|
|
67
|
+
the repo's pre-committed .botbuddy-agent.json binds tenant + mcp_env
|
|
68
|
+
multi-tenant machine: mint each tenant under a distinct --env <NAME> var (they collide otherwise)
|
|
69
|
+
4. register_agent (over MCP) tier 3 · session token (bb_agent_, per-session, 8 h)
|
|
70
|
+
5. export BOTBUDDY_AGENT_KEY=<session_token> and BOTBUDDY_SESSION_ID=<session_id>
|
|
71
|
+
6. bb wait needs the tier-3 token; bb run/test also accept $BOTBUDDY_SESSION_ID,
|
|
72
|
+
bb pw falls back to the tier-2 MCP key
|
|
73
|
+
NOTE: each Claude Code Bash call is a FRESH shell — exports do NOT persist between tool calls;
|
|
74
|
+
re-export (or pass --agent-key <token>) in the same command that runs the wait.
|
|
75
|
+
Full guide: ${CONNECTIVITY_GUIDE}`;
|
package/src/wait.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { VERSION } from "./version.mjs";
|
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { latestPublicCliCommand } from "./public-invocation.mjs";
|
|
21
21
|
import { AGENT_KEY_RE, readAgentKeyEnv } from "./agent-key.mjs";
|
|
22
|
+
import { SETUP_BLOCK } from "./setup-block.mjs";
|
|
22
23
|
|
|
23
24
|
// A protocol is deliberately distinct from package semver: compatible pinned
|
|
24
25
|
// clients keep working until the server raises this minimum, while a stale
|
|
@@ -36,7 +37,7 @@ const MIN_RECEIPT_MAX_BYTES = 512;
|
|
|
36
37
|
// BOT-1572 bb_sess_ legacy alias, both accepted for one release.
|
|
37
38
|
const SESSION_TOKEN_RE = AGENT_KEY_RE;
|
|
38
39
|
|
|
39
|
-
const HELP = `botbuddy wait — one wait command instead of a polling loop (BOT-989)
|
|
40
|
+
export const HELP = `botbuddy wait — one wait command instead of a polling loop (BOT-989)
|
|
40
41
|
|
|
41
42
|
USAGE
|
|
42
43
|
botbuddy wait [--any] <condition>... [options]
|
|
@@ -159,6 +160,8 @@ EXAMPLES
|
|
|
159
160
|
# after pushing review fixes, wait for the next review comment or a state change
|
|
160
161
|
botbuddy wait --any 'pr-review:repo=bot-buddy/botbuddy-webapp,pr=425' \\
|
|
161
162
|
'pr-state:repo=bot-buddy/botbuddy-webapp,pr=425' --timeout 3600
|
|
163
|
+
|
|
164
|
+
${SETUP_BLOCK}
|
|
162
165
|
`;
|
|
163
166
|
|
|
164
167
|
function parseArgv(argv) {
|
|
@@ -630,11 +633,11 @@ function emit(receipt, { versioned = false, maxBytes = null } = {}) {
|
|
|
630
633
|
// SAME string is written to stderr and the receipt `recovery` field that /waits
|
|
631
634
|
// renders. Keep each to one line and never embed a secret value — names, slots,
|
|
632
635
|
// and env-var names only.
|
|
633
|
-
const RECOVERY = Object.freeze({
|
|
636
|
+
export const RECOVERY = Object.freeze({
|
|
634
637
|
// BOT-1582: $BOTBUDDY_AGENT_KEY (bb_agent_+64hex, renamed from
|
|
635
638
|
// $BOTBUDDY_SESSION_TOKEN) is minted by register_agent; the harness exports it
|
|
636
639
|
// at session start. The legacy var is still accepted for one release.
|
|
637
|
-
sessionToken: "register_agent
|
|
640
|
+
sessionToken: "the tier-3 session token (bb_agent_) is minted by register_agent — export BOTBUDDY_AGENT_KEY=<session_token> (and BOTBUDDY_SESSION_ID=<session_id>); the harness exports both at session start. See docs/agent-connectivity.md",
|
|
638
641
|
// $BOTBUDDY_SESSION_ID is the work-graph session id returned by register_agent.
|
|
639
642
|
sessionId: "register_agent → export BOTBUDDY_SESSION_ID=<session_id> (the work-graph session id from register_agent)",
|
|
640
643
|
// A --token / session-token contradiction: the session token is the whole
|
|
@@ -962,7 +965,7 @@ export async function runWait(argv) {
|
|
|
962
965
|
// register a work agent and export its session token.
|
|
963
966
|
if (err.errorCode === "client_key_cannot_wait") {
|
|
964
967
|
process.stderr.write(
|
|
965
|
-
"botbuddy wait: a client key (botbuddy login) cannot arm a wait — register_agent, export BOTBUDDY_AGENT_KEY=<session_token>, then re-run\n",
|
|
968
|
+
"botbuddy wait: a tier-1 client key (bb_cli_, botbuddy login) cannot arm a wait — you need the tier-3 session token: register_agent, export BOTBUDDY_AGENT_KEY=<session_token>, then re-run\n",
|
|
966
969
|
);
|
|
967
970
|
emitReceipt({
|
|
968
971
|
schema_version: 1,
|