@bragi-gmbh/codebus 1.2.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.
Files changed (133) hide show
  1. package/bin/codebus.js +2 -0
  2. package/dist/binding-guard.js +97 -0
  3. package/dist/binding-guard.js.map +1 -0
  4. package/dist/client.js +94 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/commands/ack.js +47 -0
  7. package/dist/commands/ack.js.map +1 -0
  8. package/dist/commands/archive.js +21 -0
  9. package/dist/commands/archive.js.map +1 -0
  10. package/dist/commands/as.js +299 -0
  11. package/dist/commands/as.js.map +1 -0
  12. package/dist/commands/ask.js +189 -0
  13. package/dist/commands/ask.js.map +1 -0
  14. package/dist/commands/auth.js +33 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/checkin.js +235 -0
  17. package/dist/commands/checkin.js.map +1 -0
  18. package/dist/commands/decision.js +52 -0
  19. package/dist/commands/decision.js.map +1 -0
  20. package/dist/commands/digest.js +117 -0
  21. package/dist/commands/digest.js.map +1 -0
  22. package/dist/commands/doctor.js +106 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/factory.js +191 -0
  25. package/dist/commands/factory.js.map +1 -0
  26. package/dist/commands/github-login.js +205 -0
  27. package/dist/commands/github-login.js.map +1 -0
  28. package/dist/commands/graph.js +88 -0
  29. package/dist/commands/graph.js.map +1 -0
  30. package/dist/commands/health.js +34 -0
  31. package/dist/commands/health.js.map +1 -0
  32. package/dist/commands/inbox.js +83 -0
  33. package/dist/commands/inbox.js.map +1 -0
  34. package/dist/commands/ingest.js +553 -0
  35. package/dist/commands/ingest.js.map +1 -0
  36. package/dist/commands/init.js +97 -0
  37. package/dist/commands/init.js.map +1 -0
  38. package/dist/commands/install.js +520 -0
  39. package/dist/commands/install.js.map +1 -0
  40. package/dist/commands/invite.js +152 -0
  41. package/dist/commands/invite.js.map +1 -0
  42. package/dist/commands/jira.js +237 -0
  43. package/dist/commands/jira.js.map +1 -0
  44. package/dist/commands/join.js +96 -0
  45. package/dist/commands/join.js.map +1 -0
  46. package/dist/commands/links.js +66 -0
  47. package/dist/commands/links.js.map +1 -0
  48. package/dist/commands/login.js +84 -0
  49. package/dist/commands/login.js.map +1 -0
  50. package/dist/commands/morning.js +121 -0
  51. package/dist/commands/morning.js.map +1 -0
  52. package/dist/commands/next.js +66 -0
  53. package/dist/commands/next.js.map +1 -0
  54. package/dist/commands/onboarding.js +109 -0
  55. package/dist/commands/onboarding.js.map +1 -0
  56. package/dist/commands/pack.js +276 -0
  57. package/dist/commands/pack.js.map +1 -0
  58. package/dist/commands/plan-gh.js +425 -0
  59. package/dist/commands/plan-gh.js.map +1 -0
  60. package/dist/commands/plan.js +465 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/pr.js +88 -0
  63. package/dist/commands/pr.js.map +1 -0
  64. package/dist/commands/question.js +142 -0
  65. package/dist/commands/question.js.map +1 -0
  66. package/dist/commands/read.js +84 -0
  67. package/dist/commands/read.js.map +1 -0
  68. package/dist/commands/refs.js +357 -0
  69. package/dist/commands/refs.js.map +1 -0
  70. package/dist/commands/reply.js +38 -0
  71. package/dist/commands/reply.js.map +1 -0
  72. package/dist/commands/repo.js +1198 -0
  73. package/dist/commands/repo.js.map +1 -0
  74. package/dist/commands/report.js +41 -0
  75. package/dist/commands/report.js.map +1 -0
  76. package/dist/commands/resolve-refs.js +21 -0
  77. package/dist/commands/resolve-refs.js.map +1 -0
  78. package/dist/commands/resolve.js +93 -0
  79. package/dist/commands/resolve.js.map +1 -0
  80. package/dist/commands/send.js +168 -0
  81. package/dist/commands/send.js.map +1 -0
  82. package/dist/commands/sent.js +22 -0
  83. package/dist/commands/sent.js.map +1 -0
  84. package/dist/commands/session.js +426 -0
  85. package/dist/commands/session.js.map +1 -0
  86. package/dist/commands/statusline.js +43 -0
  87. package/dist/commands/statusline.js.map +1 -0
  88. package/dist/commands/task.js +140 -0
  89. package/dist/commands/task.js.map +1 -0
  90. package/dist/commands/tick.js +233 -0
  91. package/dist/commands/tick.js.map +1 -0
  92. package/dist/commands/upgrade.js +76 -0
  93. package/dist/commands/upgrade.js.map +1 -0
  94. package/dist/commands/watch.js +169 -0
  95. package/dist/commands/watch.js.map +1 -0
  96. package/dist/commands/whoami.js +27 -0
  97. package/dist/commands/whoami.js.map +1 -0
  98. package/dist/config.js +103 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/directives.js +102 -0
  101. package/dist/directives.js.map +1 -0
  102. package/dist/errors.js +110 -0
  103. package/dist/errors.js.map +1 -0
  104. package/dist/git-hooks.js +116 -0
  105. package/dist/git-hooks.js.map +1 -0
  106. package/dist/graph-bootstrap.js +95 -0
  107. package/dist/graph-bootstrap.js.map +1 -0
  108. package/dist/graph-remote.js +889 -0
  109. package/dist/graph-remote.js.map +1 -0
  110. package/dist/graph.js +335 -0
  111. package/dist/graph.js.map +1 -0
  112. package/dist/index.js +154 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/kit/graph-publish.mjs +400 -0
  115. package/dist/mail-latency.js +120 -0
  116. package/dist/mail-latency.js.map +1 -0
  117. package/dist/mcp-registry.js +859 -0
  118. package/dist/mcp-registry.js.map +1 -0
  119. package/dist/mcp.js +193 -0
  120. package/dist/mcp.js.map +1 -0
  121. package/dist/output.js +12 -0
  122. package/dist/output.js.map +1 -0
  123. package/dist/positioning.js +21 -0
  124. package/dist/positioning.js.map +1 -0
  125. package/dist/rolestore.js +89 -0
  126. package/dist/rolestore.js.map +1 -0
  127. package/dist/session.js +49 -0
  128. package/dist/session.js.map +1 -0
  129. package/dist/sessionstore.js +736 -0
  130. package/dist/sessionstore.js.map +1 -0
  131. package/dist/version-check.js +97 -0
  132. package/dist/version-check.js.map +1 -0
  133. package/package.json +47 -0
@@ -0,0 +1,97 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { loadFileConfig, saveFileConfig, CONFIG_FILE } from "../config.js";
3
+ import { validateConnection, detectEnvironment, OK, WARN } from "./login.js";
4
+ import { buildGraphOutOfBand, registerGraphSnapshot } from "../graph-bootstrap.js";
5
+ import { makeCtx, workspaceId as resolveWorkspaceId } from "../client.js";
6
+ import { installGitHooks } from "../git-hooks.js";
7
+ // `codebus init` — onboarding, not just config. Detects the environment, asks
8
+ // only what it cannot detect, writes ~/.codebus/config.json, and validates the
9
+ // connection when a token is available. Users never touch Supabase env vars;
10
+ // power users can still override anything with CODEBUS_* / SUPABASE_ANON_KEY.
11
+ //
12
+ // Non-interactive: pass --url/--anon-key/--workspace/--token/--person with
13
+ // --yes (used by the bootstrap script and CI).
14
+ export async function initCmd(o) {
15
+ const detected = await detectEnvironment();
16
+ const existing = loadFileConfig();
17
+ console.log("CodeBus init — environment:");
18
+ console.log(` git user: ${detected.gitName ?? "—"} <${detected.gitEmail ?? "—"}>`);
19
+ console.log(` repo/branch: ${detected.repo ?? "—"}${detected.branch ? `@${detected.branch}` : ""}`);
20
+ console.log(` Claude Code: ${detected.claude ? "available" : "not found"}`);
21
+ console.log(` Codex: ${detected.codex ? "available" : "not found"}`);
22
+ if (detected.envVars.length)
23
+ console.log(` env overrides: ${detected.envVars.join(", ")} (env always beats config.json)`);
24
+ if (existing.url)
25
+ console.log(` existing config: ${CONFIG_FILE}`);
26
+ let { url, anonKey, workspace, token, person } = {
27
+ url: o.url ?? existing.url,
28
+ anonKey: o.anonKey ?? existing.anonKey,
29
+ workspace: o.workspace ?? existing.workspace ?? "default",
30
+ token: o.token ?? existing.token,
31
+ person: o.person ?? existing.person,
32
+ };
33
+ if (!o.yes) {
34
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
35
+ const ask = async (q, dflt) => {
36
+ const a = (await rl.question(dflt ? `${q} [${dflt}]: ` : `${q}: `)).trim();
37
+ return a || dflt || undefined;
38
+ };
39
+ person = await ask("Who are you? (person slug, e.g. nikolaj)", person ?? detected.gitName?.toLowerCase().replace(/\s+/g, "-"));
40
+ workspace = (await ask("Which workspace?", workspace));
41
+ url = await ask("CodeBus URL", url);
42
+ anonKey = await ask("Anon key (from your workspace admin)", anonKey);
43
+ token = await ask("Agent token for this machine (leave blank to `codebus login` later)", token);
44
+ rl.close();
45
+ }
46
+ saveFileConfig({
47
+ ...(url ? { url } : {}), ...(anonKey ? { anonKey } : {}),
48
+ workspace, ...(token ? { token } : {}), ...(person ? { person } : {}),
49
+ });
50
+ console.log(`${OK} Wrote ${CONFIG_FILE}`);
51
+ // Install git hooks for local graph freshness (non-fatal if fails)
52
+ try {
53
+ const hooks = installGitHooks(process.cwd());
54
+ if (hooks.postMerge.installed) {
55
+ console.log(`${OK} ${hooks.postMerge.message}`);
56
+ }
57
+ if (hooks.postCommit.installed) {
58
+ console.log(`${OK} ${hooks.postCommit.message}`);
59
+ }
60
+ if (!hooks.postMerge.installed && !hooks.postCommit.installed) {
61
+ console.log(`${OK} Git hooks already installed (CodeBus)`);
62
+ }
63
+ }
64
+ catch (err) {
65
+ console.warn(`${WARN} Git hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
66
+ }
67
+ if (url && anonKey && token) {
68
+ const facts = await validateConnection({ url, anonKey, workspace, token });
69
+ saveFileConfig({ agent: facts.agent, ...(facts.person ? { person: facts.person } : {}) });
70
+ console.log(`Ready. Next: codebus session start`);
71
+ // Trigger out-of-band graph bootstrap (non-blocking)
72
+ try {
73
+ await buildGraphOutOfBand(process.cwd(), { detached: true });
74
+ // Register snapshot in artifacts registry (idempotent)
75
+ const ctx = await makeCtx({
76
+ url,
77
+ anonKey,
78
+ workspace,
79
+ token,
80
+ });
81
+ const wsId = await resolveWorkspaceId(ctx);
82
+ await registerGraphSnapshot(ctx.supabase, wsId, facts.agent);
83
+ }
84
+ catch (err) {
85
+ // Non-fatal: log but don't block init
86
+ console.warn(`Graph bootstrap skipped: ${err instanceof Error ? err.message : String(err)}`);
87
+ }
88
+ }
89
+ else {
90
+ console.log(`${WARN} Not validated yet — finish with: codebus login --token <token>`);
91
+ }
92
+ if (detected.claude)
93
+ console.log(`Tip: codebus install claude # statusline + session-start hook`);
94
+ if (detected.codex)
95
+ console.log(`Tip: codebus install codex # session-start wrapper`);
96
+ }
97
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,520 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, copyFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { OK, WARN } from "./login.js";
6
+ import { readCachedDigest } from "./digest.js";
7
+ import { createRequire } from "node:module";
8
+ import { buildGraphOutOfBand } from "../graph-bootstrap.js";
9
+ import { installGitHooks } from "../git-hooks.js";
10
+ import { hookEventHasCommand } from "../mail-latency.js";
11
+ import { loadFileConfig, saveFileConfig } from "../config.js";
12
+ import { ONE_LINER } from "../positioning.js";
13
+ import { createClient } from "@supabase/supabase-js";
14
+ // F5: derive the reported tool count from the actual registry instead of a
15
+ // hardcoded number that rots every time a tool is added/removed. Imported
16
+ // from mcp-registry.ts, NOT mcp.ts (F1): mcp-registry.ts is side-effect-free
17
+ // at import time (no stdio, no transport), so this can never start the MCP
18
+ // server as a side effect of running `codebus install mcp` — regardless of
19
+ // how mcp.ts's own entry-point guard behaves for its bin invocation.
20
+ import { TOOL_REGISTRY } from "../mcp-registry.js";
21
+ // `codebus install claude|codex|mcp` — wires CodeBus into agent launchers.
22
+ // Adoption rule: if it appears automatically it becomes the operating system;
23
+ // if users must remember to run it, it dies.
24
+ const CLAUDE_SETTINGS = () => join(homedir(), ".claude", "settings.json");
25
+ const pkg = createRequire(import.meta.url)("../../package.json");
26
+ const STATUSLINE_CONFIG = { type: "command", command: "codebus statusline" };
27
+ // --hook: identity-explicit-or-silent (same contract as tick --hook). This
28
+ // hook is machine-wide — every Claude Code session on the box runs it — so
29
+ // checked-in directories auto-orient as their bound agent and everything else
30
+ // stays silent, instead of every unrelated session orienting as the author's
31
+ // config.json identity.
32
+ const SESSION_START_HOOK = {
33
+ hooks: [{ type: "command", command: "codebus session start --hook" }],
34
+ };
35
+ // Turn-boundary participation (docs/checkin-design-brief.md): on Stop, surface
36
+ // NEW mail into the live session. `tick --hook` is identity-explicit (silent in
37
+ // sessions with no CODEBUS_TOKEN / cwd binding), honors stop_hook_active, and
38
+ // swallows every error — a bus outage must never block coding.
39
+ const STOP_HOOK = {
40
+ hooks: [{ type: "command", command: "codebus tick --hook" }],
41
+ };
42
+ function manualClaudeInstructions() {
43
+ return [
44
+ "Manual fallback — add to ~/.claude/settings.json:",
45
+ ' "statusLine": { "type": "command", "command": "codebus statusline" },',
46
+ ' "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "codebus session start --hook" } ] } ] }',
47
+ ].join("\n");
48
+ }
49
+ async function installClaude(o) {
50
+ const path = CLAUDE_SETTINGS();
51
+ let settings = {};
52
+ try {
53
+ if (existsSync(path)) {
54
+ settings = JSON.parse(readFileSync(path, "utf8"));
55
+ copyFileSync(path, `${path}.codebus-backup`); // never destroy user settings
56
+ }
57
+ }
58
+ catch (e) {
59
+ console.log(`${WARN} Could not parse ${path}: ${e.message}`);
60
+ console.log(manualClaudeInstructions());
61
+ process.exit(1);
62
+ }
63
+ // statusline: only claim the slot if free (or --force) — users may have one.
64
+ if (!settings.statusLine || o.force || (settings.statusLine?.command ?? "").includes("codebus")) {
65
+ settings.statusLine = STATUSLINE_CONFIG;
66
+ console.log(`${OK} statusline → codebus statusline`);
67
+ }
68
+ else {
69
+ console.log(`${WARN} statusLine already set (${settings.statusLine.command}); keeping it. Use --force to replace.`);
70
+ console.log(manualClaudeInstructions());
71
+ }
72
+ // SessionStart hook: append if no codebus hook is present yet. Earlier
73
+ // installs wired the hookless "codebus session start", which orients EVERY
74
+ // session on the machine as the author's config.json identity — upgrade
75
+ // those entries in place to the identity-explicit --hook form.
76
+ settings.hooks = settings.hooks ?? {};
77
+ const sessionStart = settings.hooks.SessionStart ?? [];
78
+ let upgraded = false;
79
+ for (const entry of sessionStart) {
80
+ for (const h of entry?.hooks ?? []) {
81
+ if (h?.command === "codebus session start") {
82
+ h.command = "codebus session start --hook";
83
+ upgraded = true;
84
+ }
85
+ }
86
+ }
87
+ const already = JSON.stringify(sessionStart).includes("codebus session start --hook");
88
+ if (upgraded) {
89
+ settings.hooks.SessionStart = sessionStart;
90
+ console.log(`${OK} SessionStart hook upgraded → codebus session start --hook (bound dirs auto-orient; others stay silent)`);
91
+ }
92
+ else if (!already) {
93
+ sessionStart.push(SESSION_START_HOOK);
94
+ settings.hooks.SessionStart = sessionStart;
95
+ console.log(`${OK} SessionStart hook → codebus session start --hook`);
96
+ }
97
+ else {
98
+ console.log(`${OK} SessionStart hook already installed`);
99
+ }
100
+ // Stop hook: turn-boundary mail surfacing (identity-explicit; silent elsewhere).
101
+ const stop = settings.hooks.Stop ?? [];
102
+ const stopAlready = JSON.stringify(stop).includes("codebus tick");
103
+ if (!stopAlready) {
104
+ stop.push(STOP_HOOK);
105
+ settings.hooks.Stop = stop;
106
+ console.log(`${OK} Stop hook → codebus tick --hook (new-mail surfacing at turn boundaries)`);
107
+ }
108
+ else {
109
+ console.log(`${OK} Stop hook already installed`);
110
+ }
111
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
112
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
113
+ console.log(`${OK} Wrote ${path}${existsSync(`${path}.codebus-backup`) ? ` (backup: ${path}.codebus-backup)` : ""}`);
114
+ // Verify the statusline path works (local read only — never network).
115
+ const snap = await readCachedDigest();
116
+ console.log(snap
117
+ ? `${OK} codebus statusline renders from cached digest`
118
+ : `${WARN} no digest cache yet — run \`codebus session start\` once; statusline shows a hint until then`);
119
+ }
120
+ // RC3 (docs/checkin-design-brief.md follow-up): Codex CLI's native
121
+ // lifecycle-hooks engine (codex-rs/hooks; Feature::CodexHooks — stage Stable,
122
+ // default_enabled true) ships a genuine turn-boundary "Stop" hook. Codex reads
123
+ // hooks from a global ~/.codex/hooks.json and (with its own precedence/merge
124
+ // rules) a project-local .codex/hooks.json; `codebus install codex` manages the
125
+ // GLOBAL file only, and detection below is deliberately scoped to that same file
126
+ // so install and coverage-detection always agree — we never claim coverage for a
127
+ // project-local hook whose Codex resolution semantics we don't replicate here.
128
+ // Its contract mirrors Claude Code's Stop hook closely enough to reuse verbatim:
129
+ // the handler gets
130
+ // `stop_hook_active` on stdin (same loop-guard field), and a
131
+ // `{"decision":"block","reason":"..."}` stdout response feeds `reason` back
132
+ // as the agent's next prompt (codex-rs/hooks/src/events/stop.rs). `codebus
133
+ // tick --hook` already speaks exactly that contract — no Codex-specific
134
+ // binary is needed, the SAME command wires both tools' Stop hooks.
135
+ const CODEX_HOOKS_FILE = () => join(homedir(), ".codex", "hooks.json");
136
+ const CODEX_STOP_HOOK_GROUP = { hooks: [{ type: "command", command: "codebus tick --hook" }] };
137
+ const CODEX_SESSION_START_HOOK_GROUP = { hooks: [{ type: "command", command: "codebus session start --hook" }] };
138
+ /**
139
+ * Register CodeBus's Stop + SessionStart handlers in ~/.codex/hooks.json.
140
+ * Merge-only, like installClaude: never touches any OTHER event's handlers a
141
+ * user already configured, and backs up the file before writing. Idempotent —
142
+ * checks for "codebus tick"/"codebus session start --hook" substrings before
143
+ * appending, same convention installClaude uses for ~/.claude/settings.json.
144
+ *
145
+ * Returns whether the hooks are actually installed after this call (false on
146
+ * any failure) — ai-review finding: the caller must NOT print "durable
147
+ * auto-check installed" when this silently failed to write anything, or a
148
+ * malformed/unreadable hooks.json leaves the user believing coverage exists
149
+ * when it doesn't (an undetected mail-delivery gap).
150
+ */
151
+ // A plain JSON object: not null, not an array. Used to validate every level
152
+ // of hooks.json we're about to read/mutate — ai-review finding: checking only
153
+ // the TOP-level shape let `{"hooks":[]}` or `{"hooks":{"Stop":{}}}` slip past
154
+ // as "valid", then either silently produce a file with no hook actually
155
+ // installed (arrays swallow non-index properties on JSON.stringify) or throw
156
+ // on `stop.push` for a non-array Stop value. Every level we touch — the
157
+ // top-level file, `hooks`, and `hooks.Stop`/`hooks.SessionStart` (only if
158
+ // present) — must have the exact shape we expect, or we fail closed the same
159
+ // way a syntax error does, before ever claiming success.
160
+ function isPlainObject(x) {
161
+ return typeof x === "object" && x !== null && !Array.isArray(x);
162
+ }
163
+ function installCodexHooks() {
164
+ const path = CODEX_HOOKS_FILE();
165
+ let file = {};
166
+ try {
167
+ if (existsSync(path)) {
168
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
169
+ if (!isPlainObject(parsed)) {
170
+ throw new Error(`expected a JSON object at the top level, got ${Array.isArray(parsed) ? "an array" : typeof parsed}`);
171
+ }
172
+ if (parsed.hooks !== undefined && !isPlainObject(parsed.hooks)) {
173
+ throw new Error(`expected "hooks" to be a JSON object, got ${Array.isArray(parsed.hooks) ? "an array" : typeof parsed.hooks}`);
174
+ }
175
+ const hooksObj = parsed.hooks ?? {};
176
+ for (const key of ["Stop", "SessionStart"]) {
177
+ if (hooksObj[key] !== undefined && !Array.isArray(hooksObj[key])) {
178
+ throw new Error(`expected "hooks.${key}" to be a JSON array, got ${typeof hooksObj[key]}`);
179
+ }
180
+ }
181
+ file = parsed;
182
+ copyFileSync(path, `${path}.codebus-backup`); // never destroy user hooks
183
+ }
184
+ }
185
+ catch (e) {
186
+ console.log(`${WARN} Could not parse ${path}: ${e.message} — leaving Codex hooks untouched`);
187
+ console.log(` Manual fallback — add to ${path}:`);
188
+ console.log(` { "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "codebus tick --hook" } ] } ] } }`);
189
+ return false;
190
+ }
191
+ file.hooks = file.hooks ?? {};
192
+ const stop = file.hooks.Stop ?? [];
193
+ // Exact command match, not a substring over serialized JSON — an unrelated
194
+ // hook merely containing "codebus tick" must not suppress installing the real
195
+ // handler (ai-review finding; same matcher the detection path uses).
196
+ if (!hookEventHasCommand(stop, "codebus tick --hook")) {
197
+ stop.push(CODEX_STOP_HOOK_GROUP);
198
+ file.hooks.Stop = stop;
199
+ console.log(`${OK} Codex Stop hook → codebus tick --hook (new-mail surfacing at Codex turn boundaries)`);
200
+ }
201
+ else {
202
+ console.log(`${OK} Codex Stop hook already installed`);
203
+ }
204
+ const sessionStart = file.hooks.SessionStart ?? [];
205
+ if (!hookEventHasCommand(sessionStart, "codebus session start --hook")) {
206
+ sessionStart.push(CODEX_SESSION_START_HOOK_GROUP);
207
+ file.hooks.SessionStart = sessionStart;
208
+ console.log(`${OK} Codex SessionStart hook → codebus session start --hook`);
209
+ }
210
+ else {
211
+ console.log(`${OK} Codex SessionStart hook already installed`);
212
+ }
213
+ mkdirSync(join(homedir(), ".codex"), { recursive: true });
214
+ writeFileSync(path, JSON.stringify(file, null, 2) + "\n");
215
+ console.log(`${OK} Wrote ${path}${existsSync(`${path}.codebus-backup`) ? ` (backup: ${path}.codebus-backup)` : ""}`);
216
+ return true;
217
+ }
218
+ export function installCodex() {
219
+ const binDir = join(homedir(), ".codebus", "bin");
220
+ const wrapper = join(binDir, "codex");
221
+ mkdirSync(binDir, { recursive: true });
222
+ // Safer v1 (deliberate): session start only. No auto session-end posts until
223
+ // users trust the ritual — an unwanted handoff message erodes trust fast.
224
+ writeFileSync(wrapper, [
225
+ "#!/usr/bin/env bash",
226
+ "# CodeBus wrapper: pull the bus before Codex starts. Best-effort — a bus",
227
+ "# outage must never block coding.",
228
+ "codebus session start || true",
229
+ 'exec codex "$@"',
230
+ "",
231
+ ].join("\n"));
232
+ chmodSync(wrapper, 0o755);
233
+ console.log(`${OK} Wrote ${wrapper}`);
234
+ console.log(`Add to your shell profile so it shadows the real codex:`);
235
+ console.log(` export PATH="$HOME/.codebus/bin:$PATH"`);
236
+ console.log(`(session end stays manual for now: codebus session end --summary "...")`);
237
+ // Register the MCP server so a Codex session can PULL (codebus_inbox/
238
+ // codebus_next) natively via tool calls.
239
+ installCodexMcp();
240
+ // Durable turn-boundary auto-check (the actual RC3 fix): wire Codex's own
241
+ // Stop hook, same mechanism/command as Claude Code's Stop hook above. Only
242
+ // claim coverage when the write actually succeeded (ai-review finding: a
243
+ // malformed/unreadable hooks.json must not be papered over with a false
244
+ // "durable auto-check installed" success message).
245
+ if (installCodexHooks()) {
246
+ console.log(`${OK} Durable auto-check: Codex's Stop hook now runs \`codebus tick --hook\` at every turn`);
247
+ console.log(` boundary — same mechanism as Claude Code (Codex feeds the reported mail back as your`);
248
+ console.log(` next prompt). \`codebus checkin\` no longer needs to keep a blocking watch window alive.`);
249
+ }
250
+ else {
251
+ console.log(`${WARN} Durable auto-check NOT installed — fix ~/.codex/hooks.json (see above) and re-run \`codebus install codex\`.`);
252
+ console.log(` Until then, \`codebus checkin\` falls back to arming one watch window at a time.`);
253
+ }
254
+ }
255
+ /**
256
+ * installMcp — register the codebus-mcp server in Claude Code settings.
257
+ * OPT-IN only: never modifies config silently. Prints what will be written first.
258
+ * A12 / artifacts-hook policy: explicit user consent required.
259
+ */
260
+ export async function installMcp(o = {}) {
261
+ const path = CLAUDE_SETTINGS();
262
+ let settings = {};
263
+ try {
264
+ if (existsSync(path)) {
265
+ settings = JSON.parse(readFileSync(path, "utf8"));
266
+ }
267
+ }
268
+ catch (e) {
269
+ console.log(`${WARN} Could not parse ${path}: ${e.message}`);
270
+ console.log("Manual fallback — add to ~/.claude/settings.json:");
271
+ printMcpServerConfig();
272
+ process.exit(1);
273
+ }
274
+ // Prepare the MCP server config.
275
+ const mcpConfig = buildMcpServerConfig();
276
+ // Check if already installed.
277
+ const mcpServers = settings.mcpServers ?? {};
278
+ if (mcpServers.codebus) {
279
+ console.log(`${OK} codebus-mcp already registered in mcpServers`);
280
+ return;
281
+ }
282
+ // A12 / artifacts-hook policy: non-interactive without explicit opt-in → abort.
283
+ // Non-TTY (piped, CI, another tool) + no --yes → user cannot consent.
284
+ if (!process.stdin.isTTY && !o.yes) {
285
+ console.log(`${WARN} non-interactive: re-run with --yes to opt in`);
286
+ process.exit(1);
287
+ }
288
+ // Print what will be written (A12 explicit consent gate).
289
+ console.log("\nWill register codebus-mcp in ~/.claude/settings.json:\n");
290
+ console.log(JSON.stringify({ mcpServers: { codebus: mcpConfig } }, null, 2));
291
+ console.log();
292
+ // Confirm before writing (interactive mode only — non-TTY would have aborted above).
293
+ if (!o.yes) {
294
+ const answer = await getUserConfirmation("Proceed with registration? (y/n): ");
295
+ if (!answer.toLowerCase().startsWith("y")) {
296
+ console.log(`${WARN} Installation cancelled.`);
297
+ process.exit(0);
298
+ }
299
+ }
300
+ // Write the config.
301
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
302
+ if (!settings.mcpServers) {
303
+ settings.mcpServers = {};
304
+ }
305
+ settings.mcpServers.codebus = mcpConfig;
306
+ // Backup existing config.
307
+ if (existsSync(path)) {
308
+ copyFileSync(path, `${path}.codebus-backup`);
309
+ }
310
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
311
+ console.log(`${OK} Registered codebus-mcp in ${path}`);
312
+ console.log(`${OK} Backup saved to ${path}.codebus-backup`);
313
+ console.log("\nCodeBus MCP tools are now available in Claude Code:");
314
+ console.log(" • ask_graph — Query CodeBus graph snapshot");
315
+ console.log(" • codebus_state — Session digest (counts, next item)");
316
+ console.log(" • codebus_next — Ranked next work item");
317
+ console.log(" • codebus_inbox — Read inbox items");
318
+ console.log(" • question_create — Create a question");
319
+ console.log(" • question_answer — Answer an open question");
320
+ console.log(" • task_create — Create a task");
321
+ console.log(" • task_claim — Claim a task");
322
+ console.log(" • task_done — Mark task as done");
323
+ console.log(" • decision_record — Record a decision");
324
+ console.log(" • decision_accept — Accept a decision");
325
+ console.log(" • send — Send a message to agents");
326
+ console.log(" • session_end — End session with handoff/note");
327
+ console.log(" • compile_pack — Cited, budget-trimmed context pack");
328
+ console.log(" • resolve_refs — List typed refs attached to a task/message");
329
+ console.log(" • graph_search — Search a connected repo's remote graph");
330
+ console.log(" • blast_radius — What breaks if I change this (local or remote)");
331
+ console.log(" • graph_gaps — Knowledge gaps in a connected repo's graph");
332
+ console.log(" • plan_status — Per-plan task/ticket/PR/coverage/health rollup");
333
+ console.log(" • delivery_status — Paginated per-plan delivery + quality rollup");
334
+ }
335
+ function buildMcpServerConfig() {
336
+ // Absolute paths, deliberately: a bare "codebus-mcp" resolves only through
337
+ // whatever PATH the launching app inherited — under fnm that's an EPHEMERAL
338
+ // per-shell shim dir (fnm_multishells/<pid>_<ts>/bin), so a Claude/Codex
339
+ // instance launched outside an fnm-initialized shell silently fails to start
340
+ // the server. process.execPath is the real node binary running this install;
341
+ // mcp.js resolves relative to the installed package (dist/commands → dist/).
342
+ // Trade-off: a node upgrade breaks this loudly (re-run install), instead of
343
+ // PATH drift breaking it silently.
344
+ return {
345
+ command: process.execPath,
346
+ args: [join(dirname(fileURLToPath(import.meta.url)), "..", "mcp.js")],
347
+ };
348
+ }
349
+ /**
350
+ * Register the codebus MCP server in Codex's ~/.codex/config.toml
351
+ * ([mcp_servers.codebus]). Append-only with a backup — Codex configs carry
352
+ * hand-written sections (the user's real one has several mcp_servers already),
353
+ * so we never parse/rewrite, only add our section when it's absent.
354
+ */
355
+ function installCodexMcp() {
356
+ const path = join(homedir(), ".codex", "config.toml");
357
+ const cfg = buildMcpServerConfig();
358
+ const section = [
359
+ "",
360
+ "# CodeBus MCP server (written by `codebus install codex`)",
361
+ "[mcp_servers.codebus]",
362
+ `command = ${JSON.stringify(cfg.command)}`,
363
+ `args = [${cfg.args.map((a) => JSON.stringify(a)).join(", ")}]`,
364
+ "",
365
+ ].join("\n");
366
+ let existing = "";
367
+ try {
368
+ existing = readFileSync(path, "utf8");
369
+ }
370
+ catch { /* no config yet — we'll create it */ }
371
+ if (existing.includes("[mcp_servers.codebus]")) {
372
+ console.log(`${OK} codebus MCP already registered in ${path}`);
373
+ return;
374
+ }
375
+ if (existing)
376
+ copyFileSync(path, `${path}.codebus-backup`);
377
+ mkdirSync(join(homedir(), ".codex"), { recursive: true });
378
+ writeFileSync(path, existing + section);
379
+ console.log(`${OK} Registered [mcp_servers.codebus] in ${path}${existing ? ` (backup: ${path}.codebus-backup)` : ""}`);
380
+ console.log(` Codex sessions get the ${TOOL_REGISTRY.length} codebus tools on next launch.`);
381
+ }
382
+ function printMcpServerConfig() {
383
+ const config = buildMcpServerConfig();
384
+ console.log(' "mcpServers": {');
385
+ console.log(' "codebus": ' + JSON.stringify(config, null, 6));
386
+ console.log(" }");
387
+ }
388
+ /**
389
+ * Prompt user for confirmation (blocking).
390
+ * Only called from interactive TTY contexts (non-TTY + no --yes is rejected earlier).
391
+ */
392
+ function getUserConfirmation(prompt) {
393
+ return new Promise((resolve) => {
394
+ process.stdout.write(prompt);
395
+ let data = "";
396
+ process.stdin.setEncoding("utf8");
397
+ process.stdin.once("data", (chunk) => {
398
+ resolve(chunk.trim());
399
+ process.stdin.pause();
400
+ });
401
+ process.stdin.resume();
402
+ });
403
+ }
404
+ export async function installAll(o = {}) {
405
+ const repoRoot = o.cwd ?? process.cwd();
406
+ // ── 1. Build graph out-of-band ───────────────────────────────────────────
407
+ if (!o.skipGraph) {
408
+ const isGitDir = existsSync(join(repoRoot, ".git"));
409
+ if (!isGitDir) {
410
+ console.log(`${WARN} Not a git repository — skipping graph build. Run inside a git repo for the full aha.`);
411
+ }
412
+ else {
413
+ try {
414
+ await buildGraphOutOfBand(repoRoot, { graphifyCmd: o.graphifyCmd ?? "graphify" });
415
+ }
416
+ catch {
417
+ console.log(`${WARN} graphify not found or failed — skipping graph build. Install graphify or run again inside a graphified repo.`);
418
+ }
419
+ }
420
+ }
421
+ // ── 2. Install git hooks ─────────────────────────────────────────────────
422
+ const isGitDir = existsSync(join(repoRoot, ".git"));
423
+ if (isGitDir) {
424
+ try {
425
+ const hooks = installGitHooks(repoRoot);
426
+ if (hooks.postMerge.installed)
427
+ console.log(`${OK} post-merge hook → graph refresh`);
428
+ else
429
+ console.log(`${OK} post-merge hook already installed`);
430
+ if (hooks.postCommit.installed)
431
+ console.log(`${OK} post-commit hook → graph refresh`);
432
+ else
433
+ console.log(`${OK} post-commit hook already installed`);
434
+ }
435
+ catch (e) {
436
+ console.log(`${WARN} git hooks: ${e.message}`);
437
+ }
438
+ }
439
+ // ── 3. Install Claude SessionStart hook ─────────────────────────────────
440
+ await installClaude({ force: o.force });
441
+ // ── 4. Provision <login>-claude agent (when token is available) ──────────
442
+ const cfg = loadFileConfig();
443
+ const url = process.env.CODEBUS_URL ?? cfg.url;
444
+ const token = process.env.CODEBUS_TOKEN ?? cfg.token;
445
+ const anonKey = process.env.SUPABASE_ANON_KEY ?? cfg.anonKey ?? "";
446
+ if (!token || !url) {
447
+ console.log(`\n${WARN} No token/URL configured — skipping agent provision.`);
448
+ console.log(` Run \`codebus join <code>\` to join a workspace, then re-run \`codebus install\`.`);
449
+ }
450
+ else {
451
+ try {
452
+ const agentSlug = await provisionMyAgent({ url, token, anonKey });
453
+ console.log(`${OK} Agent provisioned: ${agentSlug}`);
454
+ // Persist the agent slug so subsequent commands use it automatically.
455
+ saveFileConfig({ agent: agentSlug });
456
+ }
457
+ catch (e) {
458
+ console.log(`${WARN} Agent provision skipped: ${e.message}`);
459
+ }
460
+ }
461
+ // ── 5. Aha hint ──────────────────────────────────────────────────────────
462
+ console.log(`\n${OK} CodeBus installed.`);
463
+ console.log(`\n ${ONE_LINER}`);
464
+ console.log(`\n Indexed locally — nothing left this machine. Try:\n codebus ask "..."`);
465
+ }
466
+ // Calls provision_my_agent RPC with the caller's token.
467
+ // Returns the provisioned agent slug.
468
+ export async function provisionMyAgent(opts) {
469
+ const { url, token, anonKey, slugSuffix = "claude", tool = "claude-code", _client } = opts;
470
+ // Build a supabase client authenticated with the caller's token.
471
+ const supabase = _client ?? createClient(url, anonKey, {
472
+ global: { headers: { Authorization: `Bearer ${token}`, apikey: anonKey } },
473
+ auth: { persistSession: false, autoRefreshToken: false },
474
+ });
475
+ // Derive the login slug from the current person (via agents.slug heuristic).
476
+ // We use the agents table to look up the caller's own slug, then build
477
+ // <login>-<tool> as the new agent slug.
478
+ const { data: me, error: meErr } = await supabase
479
+ .from("agents")
480
+ .select("slug")
481
+ .single();
482
+ if (meErr || !me)
483
+ throw new Error(meErr?.message ?? "could not resolve caller agent");
484
+ // Derive the base: strip trailing -<tool> or -default suffix if any.
485
+ const baseSlug = me.slug.replace(/-(claude|codex|default|mcp)$/, "");
486
+ const agentSlug = `${baseSlug}-${slugSuffix}`;
487
+ const displayName = `${baseSlug} (${slugSuffix})`;
488
+ const { data, error } = await supabase.rpc("provision_my_agent", {
489
+ p_slug: agentSlug,
490
+ p_display_name: displayName,
491
+ p_tool: tool,
492
+ });
493
+ if (error)
494
+ throw new Error(error.message);
495
+ return data.agent_slug;
496
+ }
497
+ /**
498
+ * Pure helper: derives the target agent slug from the caller's own agent slug.
499
+ * Strips any trailing recognized suffix (-claude|-codex|-default|-mcp), then
500
+ * appends `slugSuffix` (default "claude").
501
+ *
502
+ * Exported so unit tests can verify the regex without needing a supabase client.
503
+ */
504
+ export function deriveAgentSlug(callerSlug, slugSuffix = "claude") {
505
+ const base = callerSlug.replace(/-(claude|codex|default|mcp)$/, "");
506
+ return `${base}-${slugSuffix}`;
507
+ }
508
+ export function registerInstall(program, g) {
509
+ const install = program
510
+ .command("install")
511
+ .description("wire CodeBus into agent launchers (run without subcommand for zero-config setup)")
512
+ .action(installAll);
513
+ g(install.command("claude")).option("--force", "replace an existing statusLine").action(installClaude);
514
+ g(install.command("codex")).action(installCodex);
515
+ g(install.command("mcp"))
516
+ .option("--yes", "skip confirmation prompt (opt-in gate)")
517
+ .description("register codebus-mcp server in Claude Code settings (opt-in only)")
518
+ .action(installMcp);
519
+ }
520
+ //# sourceMappingURL=install.js.map