@spinabot/brigade 1.22.1 → 1.24.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 (92) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-loop.d.ts.map +1 -1
  3. package/dist/agents/agent-loop.js +73 -3
  4. package/dist/agents/agent-loop.js.map +1 -1
  5. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  6. package/dist/agents/channels/inbound-pipeline.js +6 -0
  7. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  8. package/dist/agents/claude-cli/availability.d.ts +12 -0
  9. package/dist/agents/claude-cli/availability.d.ts.map +1 -0
  10. package/dist/agents/claude-cli/availability.js +79 -0
  11. package/dist/agents/claude-cli/availability.js.map +1 -0
  12. package/dist/agents/claude-cli/catalog.d.ts +91 -0
  13. package/dist/agents/claude-cli/catalog.d.ts.map +1 -0
  14. package/dist/agents/claude-cli/catalog.js +260 -0
  15. package/dist/agents/claude-cli/catalog.js.map +1 -0
  16. package/dist/agents/claude-cli/claude-config.d.ts +38 -0
  17. package/dist/agents/claude-cli/claude-config.d.ts.map +1 -0
  18. package/dist/agents/claude-cli/claude-config.js +111 -0
  19. package/dist/agents/claude-cli/claude-config.js.map +1 -0
  20. package/dist/agents/claude-cli/register.d.ts +32 -0
  21. package/dist/agents/claude-cli/register.d.ts.map +1 -0
  22. package/dist/agents/claude-cli/register.js +68 -0
  23. package/dist/agents/claude-cli/register.js.map +1 -0
  24. package/dist/agents/claude-cli/spawn.d.ts +42 -0
  25. package/dist/agents/claude-cli/spawn.d.ts.map +1 -0
  26. package/dist/agents/claude-cli/spawn.js +179 -0
  27. package/dist/agents/claude-cli/spawn.js.map +1 -0
  28. package/dist/agents/claude-cli/stream-json.d.ts +129 -0
  29. package/dist/agents/claude-cli/stream-json.d.ts.map +1 -0
  30. package/dist/agents/claude-cli/stream-json.js +84 -0
  31. package/dist/agents/claude-cli/stream-json.js.map +1 -0
  32. package/dist/agents/claude-cli/stream.d.ts +27 -0
  33. package/dist/agents/claude-cli/stream.d.ts.map +1 -0
  34. package/dist/agents/claude-cli/stream.js +380 -0
  35. package/dist/agents/claude-cli/stream.js.map +1 -0
  36. package/dist/agents/error-classifier.d.ts +2 -2
  37. package/dist/agents/error-classifier.d.ts.map +1 -1
  38. package/dist/agents/error-classifier.js +43 -0
  39. package/dist/agents/error-classifier.js.map +1 -1
  40. package/dist/agents/model-resolution.d.ts +36 -0
  41. package/dist/agents/model-resolution.d.ts.map +1 -1
  42. package/dist/agents/model-resolution.js +59 -0
  43. package/dist/agents/model-resolution.js.map +1 -1
  44. package/dist/agents/retry-policy.d.ts.map +1 -1
  45. package/dist/agents/retry-policy.js +25 -1
  46. package/dist/agents/retry-policy.js.map +1 -1
  47. package/dist/agents/tools/sessions/shared.d.ts.map +1 -1
  48. package/dist/agents/tools/sessions/shared.js +4 -0
  49. package/dist/agents/tools/sessions/shared.js.map +1 -1
  50. package/dist/auth/auth-health.d.ts +107 -1
  51. package/dist/auth/auth-health.d.ts.map +1 -1
  52. package/dist/auth/auth-health.js +245 -2
  53. package/dist/auth/auth-health.js.map +1 -1
  54. package/dist/buildstamp.json +1 -1
  55. package/dist/cli/commands/auth.js +18 -0
  56. package/dist/cli/commands/auth.js.map +1 -1
  57. package/dist/cli/commands/doctor.d.ts.map +1 -1
  58. package/dist/cli/commands/doctor.js +46 -0
  59. package/dist/cli/commands/doctor.js.map +1 -1
  60. package/dist/cli/commands/gateway.d.ts +4 -0
  61. package/dist/cli/commands/gateway.d.ts.map +1 -1
  62. package/dist/cli/commands/gateway.js +52 -6
  63. package/dist/cli/commands/gateway.js.map +1 -1
  64. package/dist/cli/commands/login.d.ts.map +1 -1
  65. package/dist/cli/commands/login.js +107 -19
  66. package/dist/cli/commands/login.js.map +1 -1
  67. package/dist/cli/commands/secrets-audit.d.ts.map +1 -1
  68. package/dist/cli/commands/secrets-audit.js +1 -0
  69. package/dist/cli/commands/secrets-audit.js.map +1 -1
  70. package/dist/core/auth-bridge.d.ts.map +1 -1
  71. package/dist/core/auth-bridge.js +16 -0
  72. package/dist/core/auth-bridge.js.map +1 -1
  73. package/dist/core/server.d.ts.map +1 -1
  74. package/dist/core/server.js +13 -0
  75. package/dist/core/server.js.map +1 -1
  76. package/dist/logging/redact.d.ts +12 -8
  77. package/dist/logging/redact.d.ts.map +1 -1
  78. package/dist/logging/redact.js +13 -8
  79. package/dist/logging/redact.js.map +1 -1
  80. package/dist/providers/catalog.d.ts +13 -0
  81. package/dist/providers/catalog.d.ts.map +1 -1
  82. package/dist/providers/catalog.js +31 -0
  83. package/dist/providers/catalog.js.map +1 -1
  84. package/dist/system-prompt/runtime-params.d.ts +5 -0
  85. package/dist/system-prompt/runtime-params.d.ts.map +1 -1
  86. package/dist/system-prompt/runtime-params.js +2 -1
  87. package/dist/system-prompt/runtime-params.js.map +1 -1
  88. package/dist/ui/onboarding.d.ts +11 -0
  89. package/dist/ui/onboarding.d.ts.map +1 -1
  90. package/dist/ui/onboarding.js +397 -6
  91. package/dist/ui/onboarding.js.map +1 -1
  92. package/package.json +2 -1
@@ -0,0 +1,260 @@
1
+ // Constants + pure builders for the `claude-cli` inference backend — the
2
+ // transport that drives an installed Claude Code binary (`claude`) as a
3
+ // provider so a turn bills against the operator's Claude subscription exactly
4
+ // like the Claude Code CLI / IDE extension, instead of Brigade's raw-HTTP
5
+ // OAuth path (which Anthropic routes into the "extra usage" overage tier).
6
+ //
7
+ // The argv/env construction, model catalog, and alias map are pure. The only
8
+ // side-effecting helper is the bundled-binary resolver (a cached fs read).
9
+ import { createRequire } from "node:module";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ /** The Pi `api` string + provider id + model-ref prefix for this backend. */
13
+ export const CLAUDE_CLI_API = "claude-cli";
14
+ export const CLAUDE_CLI_PROVIDER = "claude-cli";
15
+ /**
16
+ * Resolve the `claude` binary to spawn. Precedence:
17
+ * 1. `BRIGADE_CLAUDE_CLI_PATH` env override (explicit operator choice).
18
+ * 2. The binary BUNDLED with Brigade — `@anthropic-ai/claude-code` is a
19
+ * DIRECT dependency, so every `npm i` of Brigade installs it and we resolve
20
+ * its own `bin` entry. This makes the backend work with zero separate
21
+ * install ("everything automated").
22
+ * 3. `claude` on PATH (a global Claude Code install) — a defensive fallback
23
+ * for the rare case the dependency's binary isn't resolvable on this host.
24
+ */
25
+ export function resolveClaudeCliCommand() {
26
+ const override = process.env.BRIGADE_CLAUDE_CLI_PATH?.trim();
27
+ if (override && override.length > 0)
28
+ return override;
29
+ const bundled = resolveBundledClaudeBinary();
30
+ if (bundled)
31
+ return bundled;
32
+ return "claude";
33
+ }
34
+ let bundledClaudeCache;
35
+ // The vendor tarball ships a ~500-byte PLACEHOLDER at `bin/claude.exe` (a stub
36
+ // that just prints "native binary not installed" and exits 1); the real native
37
+ // binary is hardlinked over it by the package's `postinstall` from a
38
+ // platform-specific optional dependency. In a degraded install
39
+ // (`--ignore-scripts`, `--omit=optional`, or an unsupported platform) the stub
40
+ // is left in place. `existsSync` can't tell the stub from the real binary, so
41
+ // we ALSO gate on size — the vendor's own installer uses `size < 4096` as its
42
+ // stub sentinel, so we mirror it. Below the threshold ⇒ treat as "not bundled"
43
+ // and fall through to `claude` on PATH instead of spawning a broken stub.
44
+ const CLAUDE_BUNDLED_MIN_SIZE_BYTES = 4096;
45
+ /**
46
+ * Resolve the `claude` binary shipped by the `@anthropic-ai/claude-code`
47
+ * dependency, or undefined when it isn't installed OR is only the placeholder
48
+ * stub. Reads the package's own `bin` field so we spawn whatever entry the
49
+ * vendor declares (a native `claude.exe` on Windows, a launcher elsewhere).
50
+ * Cached; never throws.
51
+ */
52
+ export function resolveBundledClaudeBinary() {
53
+ if (bundledClaudeCache !== undefined)
54
+ return bundledClaudeCache ?? undefined;
55
+ try {
56
+ const require = createRequire(import.meta.url);
57
+ const pkgJsonPath = require.resolve("@anthropic-ai/claude-code/package.json");
58
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
59
+ const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.claude;
60
+ if (binRel) {
61
+ const binPath = path.join(path.dirname(pkgJsonPath), binRel);
62
+ // Reject the placeholder stub — a real native binary is far larger, so a
63
+ // tiny file means postinstall never replaced it (degraded install).
64
+ const size = fs.statSync(binPath).size; // throws if missing → caught below
65
+ if (size >= CLAUDE_BUNDLED_MIN_SIZE_BYTES) {
66
+ bundledClaudeCache = binPath;
67
+ return binPath;
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ /* dep not installed / stub missing / unreadable — fall through to PATH */
73
+ }
74
+ bundledClaudeCache = null;
75
+ return undefined;
76
+ }
77
+ /** Test-only reset for the bundled-binary cache. */
78
+ export function __resetBundledClaudeCache() {
79
+ bundledClaudeCache = undefined;
80
+ }
81
+ export const CLAUDE_CLI_MODELS = [
82
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8 (subscription)", cliModel: "claude-opus-4-8", reasoning: true, contextWindow: 200_000, maxTokens: 32_000 },
83
+ { id: "claude-opus-4-7", name: "Claude Opus 4.7 (subscription)", cliModel: "claude-opus-4-7", reasoning: true, contextWindow: 200_000, maxTokens: 32_000 },
84
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (subscription)", cliModel: "claude-sonnet-4-6", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
85
+ { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (subscription)", cliModel: "claude-sonnet-4-5", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
86
+ { id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (subscription)", cliModel: "claude-haiku-4-5", reasoning: false, contextWindow: 200_000, maxTokens: 32_000 },
87
+ ];
88
+ /** Default model ref when the operator picks the backend without naming a model. */
89
+ export const CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-4-6";
90
+ // Sentinel credential seeded for the claude-cli provider so Pi's auth
91
+ // resolution treats a claude-cli turn as "authed" and dispatches to the
92
+ // transport instead of throwing "No API key for provider: claude-cli". It is
93
+ // NEVER a secret and NEVER goes on the wire — the spawned binary authenticates
94
+ // with its OWN stored login. Mirrors the local-Ollama sentinel-key pattern.
95
+ export const CLAUDE_CLI_SENTINEL_KEY = "claude-cli-subscription-login-no-key-required";
96
+ // Bare-family + legacy aliases → the value passed to `--model`. Only consulted
97
+ // when the requested id isn't already a known snapshot; a full id passes through
98
+ // unchanged (the CLI resolves the concrete snapshot itself).
99
+ const CLAUDE_CLI_MODEL_ALIASES = {
100
+ opus: "opus",
101
+ sonnet: "sonnet",
102
+ haiku: "haiku",
103
+ "claude-opus": "opus",
104
+ "claude-sonnet": "sonnet",
105
+ "claude-haiku": "haiku",
106
+ };
107
+ /** Drop a leading `claude-cli/` provider prefix from a model id. */
108
+ export function stripClaudeCliPrefix(modelId) {
109
+ const trimmed = (modelId ?? "").trim();
110
+ return trimmed.startsWith(`${CLAUDE_CLI_PROVIDER}/`)
111
+ ? trimmed.slice(CLAUDE_CLI_PROVIDER.length + 1)
112
+ : trimmed;
113
+ }
114
+ /**
115
+ * Resolve the `--model` value for a requested Brigade model id. A catalogued
116
+ * snapshot or any full `claude-*` id is sent verbatim; a bare family
117
+ * ("opus"/"sonnet"/"haiku") maps through the alias table; anything else falls
118
+ * back to the default so a turn never spawns with an empty `--model`.
119
+ */
120
+ export function resolveCliModelArg(modelId) {
121
+ const id = stripClaudeCliPrefix(modelId).toLowerCase();
122
+ if (!id)
123
+ return CLAUDE_CLI_DEFAULT_MODEL;
124
+ const known = CLAUDE_CLI_MODELS.find((m) => m.id.toLowerCase() === id);
125
+ if (known)
126
+ return known.cliModel;
127
+ if (CLAUDE_CLI_MODEL_ALIASES[id])
128
+ return CLAUDE_CLI_MODEL_ALIASES[id];
129
+ // A full snapshot id we don't catalogue (a newer release) — trust the CLI.
130
+ if (id.startsWith("claude-"))
131
+ return stripClaudeCliPrefix(modelId).trim();
132
+ return CLAUDE_CLI_DEFAULT_MODEL;
133
+ }
134
+ /* ─────────────────────────── env scrubbing ─────────────────────────── */
135
+ // Env vars that must be REMOVED before spawning `claude`. Claude Code honours
136
+ // provider-routing / auth / config-root env BEFORE consulting its own stored
137
+ // login, so an inherited shell override could steer the managed run to a
138
+ // different provider, endpoint, token, config tree, or telemetry mode — and,
139
+ // worst of all, into a metered tier. We pass NO token: the binary uses its own
140
+ // login. Ported from the reference backend's clear list (brand tokens scrubbed).
141
+ export const CLAUDE_CLI_CLEAR_ENV = [
142
+ // Token / credential redirects — force a different identity than the login.
143
+ "ANTHROPIC_API_KEY",
144
+ "ANTHROPIC_API_KEY_OLD",
145
+ "ANTHROPIC_API_TOKEN",
146
+ "ANTHROPIC_AUTH_TOKEN",
147
+ "ANTHROPIC_OAUTH_TOKEN",
148
+ "CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR",
149
+ "CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
150
+ "CLAUDE_CODE_OAUTH_SCOPES",
151
+ "CLAUDE_CODE_OAUTH_TOKEN",
152
+ "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR",
153
+ // Endpoint / provider routing — send traffic off the subscription.
154
+ "ANTHROPIC_BASE_URL",
155
+ "ANTHROPIC_CUSTOM_HEADERS",
156
+ "ANTHROPIC_UNIX_SOCKET",
157
+ "CLAUDE_CODE_REMOTE",
158
+ "CLAUDE_CODE_USE_BEDROCK",
159
+ "CLAUDE_CODE_USE_FOUNDRY",
160
+ "CLAUDE_CODE_USE_VERTEX",
161
+ // Config / plugin tree — load a foreign config root or plugin cache.
162
+ "CLAUDE_CODE_ENTRYPOINT",
163
+ "CLAUDE_CODE_PLUGIN_CACHE_DIR",
164
+ "CLAUDE_CODE_PLUGIN_SEED_DIR",
165
+ "CLAUDE_CODE_USE_COWORK_PLUGINS",
166
+ "CLAUDE_CONFIG_DIR",
167
+ // Telemetry / OTEL bootstrap — alter/exfiltrate telemetry mode.
168
+ "OTEL_EXPORTER_OTLP_ENDPOINT",
169
+ "OTEL_EXPORTER_OTLP_HEADERS",
170
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
171
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
172
+ "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL",
173
+ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
174
+ "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
175
+ "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL",
176
+ "OTEL_EXPORTER_OTLP_PROTOCOL",
177
+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
178
+ "OTEL_EXPORTER_OTLP_TRACES_HEADERS",
179
+ "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL",
180
+ "OTEL_LOGS_EXPORTER",
181
+ "OTEL_METRICS_EXPORTER",
182
+ "OTEL_SDK_DISABLED",
183
+ "OTEL_TRACES_EXPORTER",
184
+ ];
185
+ // Marker that must NEVER be set on the child — unlike the clear list it can't be
186
+ // preserved by any escape hatch. It routes the run into Anthropic's separate
187
+ // host-managed usage tier instead of normal CLI subscription behaviour (i.e. it
188
+ // is one path to the very "extra usage" billing we're avoiding).
189
+ export const CLAUDE_CLI_FORBIDDEN_ENV = "CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST";
190
+ /**
191
+ * Build the scrubbed environment for the child `claude` process from a base
192
+ * env (usually `process.env`). Deletes every clear-list var + the forbidden
193
+ * host-managed marker. Pure: returns a new object, never mutates the input.
194
+ *
195
+ * `configDir` (optional) is Brigade's OWN managed Claude config dir — when
196
+ * Brigade minted its own subscription grant, we point `CLAUDE_CONFIG_DIR` at it
197
+ * so the binary authenticates + refreshes from Brigade's dedicated login
198
+ * instead of the operator's personal `~/.claude` (no split-brain). The scrub
199
+ * still runs first (an INHERITED CLAUDE_CONFIG_DIR is removed), then Brigade's
200
+ * own value is set deliberately — the two never conflict.
201
+ */
202
+ export function buildClaudeCliEnv(baseEnv = process.env, opts = {}) {
203
+ const next = { ...baseEnv };
204
+ for (const key of CLAUDE_CLI_CLEAR_ENV)
205
+ delete next[key];
206
+ delete next[CLAUDE_CLI_FORBIDDEN_ENV];
207
+ if (opts.configDir && opts.configDir.trim().length > 0) {
208
+ next.CLAUDE_CONFIG_DIR = opts.configDir;
209
+ }
210
+ return next;
211
+ }
212
+ /* ─────────────────────────── argv construction ─────────────────────────── */
213
+ // Base flags for every fresh (non-resume) turn. `-p` = headless print mode;
214
+ // stream-json + partial messages give us token-level deltas; `--verbose` is
215
+ // required for stream-json to emit the per-event frames; `--setting-sources
216
+ // user` keeps the run on the user's own login/config and off any project
217
+ // `.claude` tree; `bypassPermissions` stops the CLI blocking on an approval
218
+ // prompt we can't answer headlessly.
219
+ const CLAUDE_CLI_BASE_ARGS = [
220
+ "-p",
221
+ "--output-format",
222
+ "stream-json",
223
+ "--include-partial-messages",
224
+ "--verbose",
225
+ "--setting-sources",
226
+ "user",
227
+ "--permission-mode",
228
+ "bypassPermissions",
229
+ ];
230
+ // Mutating + network tools we deny as defense-in-depth. `bypassPermissions`
231
+ // (kept so the CLI never blocks on an unanswerable approval prompt) overrides
232
+ // the allow/deny lists for read-only tools, so a deny list can't make the CLI
233
+ // perfectly tool-free — but it still keeps the strongest footguns (shell,
234
+ // file writes, edits, network) off the table. The real containment is the
235
+ // isolated empty spawn cwd (see spawn.ts): any tool the CLI does reach acts on
236
+ // a throwaway directory, so a conversational turn stays harmless. The system-
237
+ // prompt nudge (appended below) asks it to just answer.
238
+ const CLAUDE_CLI_DENY_TOOLS = "Bash Edit Write MultiEdit NotebookEdit WebFetch WebSearch";
239
+ // Appended to Brigade's system prompt for claude-cli turns: this backend is a
240
+ // conversational voice, not an autonomous coder in the (throwaway) spawn cwd.
241
+ const CLAUDE_CLI_SYSTEM_SUFFIX = "You are answering as part of an ongoing conversation. Respond directly in prose; " +
242
+ "do not use tools or act on the local filesystem — everything you need is in this conversation.";
243
+ /**
244
+ * Assemble the full argv (excluding the leading command) for a fresh turn.
245
+ * Order: base flags → `--model <m>` → tool policy → `--append-system-prompt`.
246
+ * The prompt itself is delivered on STDIN (never argv), so it never appears in
247
+ * a process listing and has no length limit.
248
+ */
249
+ export function buildClaudeCliArgs(input) {
250
+ const args = [...CLAUDE_CLI_BASE_ARGS];
251
+ args.push("--model", resolveCliModelArg(input.modelId));
252
+ const conversational = input.conversational !== false;
253
+ if (conversational)
254
+ args.push("--disallowedTools", CLAUDE_CLI_DENY_TOOLS);
255
+ const sysParts = [input.systemPrompt?.trim(), conversational ? CLAUDE_CLI_SYSTEM_SUFFIX : ""].filter((p) => !!p && p.length > 0);
256
+ if (sysParts.length > 0)
257
+ args.push("--append-system-prompt", sysParts.join("\n\n"));
258
+ return args;
259
+ }
260
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/agents/claude-cli/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /** The dedicated config dir: `<stateDir>/claude-config`. Overridable for tests. */
2
+ export declare function resolveBrigadeClaudeConfigDir(): string;
3
+ /** The Claude Code on-disk credential shape (`~/.claude/.credentials.json`). */
4
+ export interface ClaudeCodeCredentialFile {
5
+ claudeAiOauth: {
6
+ accessToken: string;
7
+ refreshToken: string;
8
+ /** Absolute epoch-ms. */
9
+ expiresAt: number;
10
+ scopes?: string[];
11
+ subscriptionType?: string;
12
+ };
13
+ }
14
+ /** The scopes pi-ai's Anthropic OAuth requests — the Claude Code set. Written to
15
+ * the credential so the binary's own scope checks are satisfied. */
16
+ export declare const CLAUDE_CODE_OAUTH_SCOPES: string[];
17
+ /**
18
+ * Persist an OAuth credential (minted by Brigade's browser login) into the
19
+ * Brigade-managed Claude config dir, in Claude Code's own on-disk shape. Atomic
20
+ * (tmp + rename) and mode 0600 on POSIX. The binary reads + refreshes it from
21
+ * here on. Returns the dir written to.
22
+ */
23
+ export declare function writeBrigadeClaudeCredential(cred: {
24
+ access: string;
25
+ refresh: string;
26
+ /** Absolute epoch-ms. Coerced to a near-future default when absent so the
27
+ * binary refreshes promptly rather than treating it as non-expiring. */
28
+ expires?: number;
29
+ scopes?: string[];
30
+ subscriptionType?: string;
31
+ }): string;
32
+ /** Whether Brigade holds its own Claude login in the managed dir. */
33
+ export declare function hasBrigadeClaudeLogin(): boolean;
34
+ /** Read the managed credential (for doctor / status), or null. Never throws. */
35
+ export declare function readBrigadeClaudeCredential(): ClaudeCodeCredentialFile["claudeAiOauth"] | null;
36
+ /** Remove the managed login (for a `logout` / re-auth flow). Best-effort. */
37
+ export declare function clearBrigadeClaudeLogin(): void;
38
+ //# sourceMappingURL=claude-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-config.d.ts","sourceRoot":"","sources":["../../../src/agents/claude-cli/claude-config.ts"],"names":[],"mappings":"AA0BA,mFAAmF;AACnF,wBAAgB,6BAA6B,IAAI,MAAM,CAItD;AAMD,gFAAgF;AAChF,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,yBAAyB;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACF;AAED;qEACqE;AACrE,eAAO,MAAM,wBAAwB,UAMpC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;6EACyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CA2BT;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,IAAI,OAAO,CAQ/C;AAED,gFAAgF;AAChF,wBAAgB,2BAA2B,IAAI,wBAAwB,CAAC,eAAe,CAAC,GAAG,IAAI,CAU9F;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,IAAI,IAAI,CAM9C"}
@@ -0,0 +1,111 @@
1
+ // Brigade-managed Claude Code config directory.
2
+ //
3
+ // The claude-cli backend spawns the `claude` binary, which authenticates from a
4
+ // config dir (default `~/.claude`). Rather than depend on — and risk racing —
5
+ // the operator's PERSONAL Claude Code login, Brigade can mint its OWN Claude
6
+ // subscription grant (via the browser OAuth it already drives) and store it in a
7
+ // DEDICATED config dir under `~/.brigade`. The backend then spawns `claude` with
8
+ // `CLAUDE_CONFIG_DIR` pointed there, so:
9
+ // • the binary authenticates from Brigade's own credential,
10
+ // • the binary refreshes that credential autonomously in-place (no Brigade
11
+ // refresh logic, no rotated-token split-brain with the user's `~/.claude`),
12
+ // • the operator never touches a terminal or pastes a token.
13
+ //
14
+ // A Brigade-written `.credentials.json` in this dir IS accepted by the binary —
15
+ // verified live: `CLAUDE_CONFIG_DIR=<dir> claude -p` authenticates from it.
16
+ //
17
+ // Precedence at spawn time (see catalog.buildClaudeCliEnv): if this managed dir
18
+ // holds a credential, use it; otherwise fall back to the binary's default
19
+ // (`~/.claude`) so an operator who already ran `claude` keeps working unchanged.
20
+ import fs from "node:fs";
21
+ import os from "node:os";
22
+ import path from "node:path";
23
+ import { resolveStateDir } from "../../config/paths.js";
24
+ /** The dedicated config dir: `<stateDir>/claude-config`. Overridable for tests. */
25
+ export function resolveBrigadeClaudeConfigDir() {
26
+ const override = process.env.BRIGADE_CLAUDE_CONFIG_DIR?.trim();
27
+ if (override)
28
+ return override;
29
+ return path.join(resolveStateDir(), "claude-config");
30
+ }
31
+ function credentialPath() {
32
+ return path.join(resolveBrigadeClaudeConfigDir(), ".credentials.json");
33
+ }
34
+ /** The scopes pi-ai's Anthropic OAuth requests — the Claude Code set. Written to
35
+ * the credential so the binary's own scope checks are satisfied. */
36
+ export const CLAUDE_CODE_OAUTH_SCOPES = [
37
+ "user:inference",
38
+ "user:profile",
39
+ "user:sessions:claude_code",
40
+ "user:mcp_servers",
41
+ "user:file_upload",
42
+ ];
43
+ /**
44
+ * Persist an OAuth credential (minted by Brigade's browser login) into the
45
+ * Brigade-managed Claude config dir, in Claude Code's own on-disk shape. Atomic
46
+ * (tmp + rename) and mode 0600 on POSIX. The binary reads + refreshes it from
47
+ * here on. Returns the dir written to.
48
+ */
49
+ export function writeBrigadeClaudeCredential(cred) {
50
+ const dir = resolveBrigadeClaudeConfigDir();
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ const file = {
53
+ claudeAiOauth: {
54
+ accessToken: cred.access,
55
+ refreshToken: cred.refresh,
56
+ expiresAt: typeof cred.expires === "number" && Number.isFinite(cred.expires)
57
+ ? cred.expires
58
+ : Date.now() + 60 * 60 * 1000,
59
+ scopes: cred.scopes ?? CLAUDE_CODE_OAUTH_SCOPES,
60
+ ...(cred.subscriptionType ? { subscriptionType: cred.subscriptionType } : {}),
61
+ },
62
+ };
63
+ const target = credentialPath();
64
+ const tmp = `${target}.tmp`;
65
+ fs.writeFileSync(tmp, JSON.stringify(file, null, 2), { mode: 0o600 });
66
+ if (os.platform() !== "win32") {
67
+ try {
68
+ fs.chmodSync(tmp, 0o600);
69
+ }
70
+ catch {
71
+ /* fs may not support chmod */
72
+ }
73
+ }
74
+ fs.renameSync(tmp, target);
75
+ return dir;
76
+ }
77
+ /** Whether Brigade holds its own Claude login in the managed dir. */
78
+ export function hasBrigadeClaudeLogin() {
79
+ try {
80
+ const raw = fs.readFileSync(credentialPath(), "utf8");
81
+ const parsed = JSON.parse(raw);
82
+ return typeof parsed?.claudeAiOauth?.accessToken === "string" && parsed.claudeAiOauth.accessToken.length > 0;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ /** Read the managed credential (for doctor / status), or null. Never throws. */
89
+ export function readBrigadeClaudeCredential() {
90
+ try {
91
+ const raw = fs.readFileSync(credentialPath(), "utf8");
92
+ const parsed = JSON.parse(raw);
93
+ const oauth = parsed?.claudeAiOauth;
94
+ if (oauth && typeof oauth.accessToken === "string" && oauth.accessToken.length > 0)
95
+ return oauth;
96
+ return null;
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ /** Remove the managed login (for a `logout` / re-auth flow). Best-effort. */
103
+ export function clearBrigadeClaudeLogin() {
104
+ try {
105
+ fs.rmSync(credentialPath(), { force: true });
106
+ }
107
+ catch {
108
+ /* nothing to remove */
109
+ }
110
+ }
111
+ //# sourceMappingURL=claude-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-config.js","sourceRoot":"","sources":["../../../src/agents/claude-cli/claude-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Idempotently register the claude-cli transport for `api: "claude-cli"`.
3
+ * Guards on the LIVE registry (`getApiProvider`) rather than a sticky flag, so
4
+ * it self-heals after Pi's `resetApiProviders()` (fired by `ModelRegistry
5
+ * .refresh()`, `AgentSession.reload()`, and register/unregisterProvider). Safe
6
+ * to call at boot, per turn, and before any isolated claude-cli session.
7
+ * Returns true when it actually registered (was absent), false when already up.
8
+ */
9
+ export declare function ensureClaudeCliApiRegistered(): boolean;
10
+ /** Loose Pi `Model` shape — we synthesize rather than construct from a class. */
11
+ export type SynthClaudeCliModel = Record<string, unknown> & {
12
+ provider: string;
13
+ id: string;
14
+ api: string;
15
+ };
16
+ /**
17
+ * Synthesize a Pi `Model` for a `claude-cli` model id. A catalogued snapshot
18
+ * uses its declared metadata; an unknown id (a newer release the operator
19
+ * names explicitly) synthesizes a sensible default so a turn is never blocked
20
+ * on "model not registered" — the CLI validates the concrete id at spawn time.
21
+ * Cost is zero (a subscription turn draws no per-token charge).
22
+ */
23
+ export declare function synthClaudeCliModel(modelId: string): SynthClaudeCliModel;
24
+ /** True when a provider id routes to the claude-cli backend. */
25
+ export declare function isClaudeCliProvider(provider: string | undefined): boolean;
26
+ /**
27
+ * The catalogued claude-cli models as Pi `Model` objects, for merging into the
28
+ * gateway's `/model` list so the backend is selectable. Callers should gate on
29
+ * `isClaudeCliAvailable()` first — advertise it only when the binary exists.
30
+ */
31
+ export declare function listClaudeCliModels(): SynthClaudeCliModel[];
32
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/agents/claude-cli/register.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAetD;AAED,iFAAiF;AACjF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACZ,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAexE;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,mBAAmB,EAAE,CAE3D"}
@@ -0,0 +1,68 @@
1
+ // Register the claude-cli transport into Pi's API-provider registry so a model
2
+ // with `api: "claude-cli"` dispatches to the subprocess stream fn — exactly
3
+ // like the native Ollama transport (`ollama-native/register.ts`). Also exposes
4
+ // a synthesizer that resolves a `claude-cli/<model>` ref to a Pi `Model`
5
+ // object (no models.json write needed; the never-miss resolver calls it).
6
+ import { getApiProvider, registerApiProvider } from "@earendil-works/pi-ai";
7
+ import { CLAUDE_CLI_API, CLAUDE_CLI_DEFAULT_MODEL, CLAUDE_CLI_MODELS, CLAUDE_CLI_PROVIDER, stripClaudeCliPrefix, } from "./catalog.js";
8
+ import { createClaudeCliStreamFn } from "./stream.js";
9
+ const REGISTRY_SOURCE_ID = "brigade-claude-cli";
10
+ /**
11
+ * Idempotently register the claude-cli transport for `api: "claude-cli"`.
12
+ * Guards on the LIVE registry (`getApiProvider`) rather than a sticky flag, so
13
+ * it self-heals after Pi's `resetApiProviders()` (fired by `ModelRegistry
14
+ * .refresh()`, `AgentSession.reload()`, and register/unregisterProvider). Safe
15
+ * to call at boot, per turn, and before any isolated claude-cli session.
16
+ * Returns true when it actually registered (was absent), false when already up.
17
+ */
18
+ export function ensureClaudeCliApiRegistered() {
19
+ if (getApiProvider(CLAUDE_CLI_API))
20
+ return false;
21
+ const streamFn = createClaudeCliStreamFn();
22
+ registerApiProvider({
23
+ api: CLAUDE_CLI_API,
24
+ // One transport serves both surfaces (the CLI has no separate simple-
25
+ // completion path). Cast: the registry's generic stream types are
26
+ // stricter than our loose StreamFn.
27
+ stream: streamFn,
28
+ streamSimple: streamFn,
29
+ }, REGISTRY_SOURCE_ID);
30
+ return true;
31
+ }
32
+ const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
33
+ /**
34
+ * Synthesize a Pi `Model` for a `claude-cli` model id. A catalogued snapshot
35
+ * uses its declared metadata; an unknown id (a newer release the operator
36
+ * names explicitly) synthesizes a sensible default so a turn is never blocked
37
+ * on "model not registered" — the CLI validates the concrete id at spawn time.
38
+ * Cost is zero (a subscription turn draws no per-token charge).
39
+ */
40
+ export function synthClaudeCliModel(modelId) {
41
+ const id = stripClaudeCliPrefix(modelId) || CLAUDE_CLI_DEFAULT_MODEL;
42
+ const known = CLAUDE_CLI_MODELS.find((m) => m.id.toLowerCase() === id.toLowerCase());
43
+ return {
44
+ provider: CLAUDE_CLI_PROVIDER,
45
+ id,
46
+ name: known?.name ?? `${id} (subscription)`,
47
+ api: CLAUDE_CLI_API,
48
+ // No baseUrl — the transport is a subprocess, not an HTTP endpoint.
49
+ reasoning: known?.reasoning ?? /opus|sonnet/i.test(id),
50
+ input: ["text"],
51
+ cost: { ...ZERO_COST },
52
+ contextWindow: known?.contextWindow ?? 200_000,
53
+ maxTokens: known?.maxTokens ?? 32_000,
54
+ };
55
+ }
56
+ /** True when a provider id routes to the claude-cli backend. */
57
+ export function isClaudeCliProvider(provider) {
58
+ return (provider ?? "").trim().toLowerCase() === CLAUDE_CLI_PROVIDER;
59
+ }
60
+ /**
61
+ * The catalogued claude-cli models as Pi `Model` objects, for merging into the
62
+ * gateway's `/model` list so the backend is selectable. Callers should gate on
63
+ * `isClaudeCliAvailable()` first — advertise it only when the binary exists.
64
+ */
65
+ export function listClaudeCliModels() {
66
+ return CLAUDE_CLI_MODELS.map((m) => synthClaudeCliModel(m.id));
67
+ }
68
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/agents/claude-cli/register.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,0EAA0E;AAE1E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5E,OAAO,EACN,cAAc,EACd,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B;IAC3C,IAAI,cAAc,CAAC,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,QAAQ,GAAa,uBAAuB,EAAE,CAAC;IACrD,mBAAmB,CAClB;QACC,GAAG,EAAE,cAAc;QACnB,sEAAsE;QACtE,kEAAkE;QAClE,oCAAoC;QACpC,MAAM,EAAE,QAAiB;QACzB,YAAY,EAAE,QAAiB;KAC/B,EACD,kBAAkB,CAClB,CAAC;IACF,OAAO,IAAI,CAAC;AACb,CAAC;AASD,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAW,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IAClD,MAAM,EAAE,GAAG,oBAAoB,CAAC,OAAO,CAAC,IAAI,wBAAwB,CAAC;IACrE,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACrF,OAAO;QACN,QAAQ,EAAE,mBAAmB;QAC7B,EAAE;QACF,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,GAAG,EAAE,iBAAiB;QAC3C,GAAG,EAAE,cAAc;QACnB,oEAAoE;QACpE,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,KAAK,EAAE,CAAC,MAAM,CAAyB;QACvC,IAAI,EAAE,EAAE,GAAG,SAAS,EAAE;QACtB,aAAa,EAAE,KAAK,EAAE,aAAa,IAAI,OAAO;QAC9C,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,MAAM;KACrC,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,mBAAmB,CAAC,QAA4B;IAC/D,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,mBAAmB,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IAClC,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { spawn } from "node:child_process";
2
+ import { type ClaudeCliFrame } from "./stream-json.js";
3
+ /**
4
+ * No-output watchdog. The timer is reset on every stdout/stderr chunk, so a
5
+ * slow-but-streaming turn survives while a genuinely wedged process (waiting on
6
+ * an interactive prompt that can never arrive, or hung on the network) trips it
7
+ * and is SIGKILL'd. Fresh runs get a longer grace (CLI startup + auth); we have
8
+ * no resume path in v1 so a single profile suffices.
9
+ */
10
+ export declare const CLAUDE_CLI_NO_OUTPUT_TIMEOUT_MS = 180000;
11
+ /** Hard ceiling on a single turn regardless of trickle output. */
12
+ export declare const CLAUDE_CLI_OVERALL_TIMEOUT_MS = 600000;
13
+ export interface SpawnClaudeCliArgs {
14
+ args: string[];
15
+ /** Prompt delivered on stdin (the whole serialized conversation). */
16
+ stdin: string;
17
+ /** External cancel (turn abort). Aborting SIGKILLs the child. */
18
+ signal?: AbortSignal;
19
+ noOutputTimeoutMs?: number;
20
+ overallTimeoutMs?: number;
21
+ /** Injectable spawn for tests. Defaults to node:child_process spawn. */
22
+ spawnFn?: typeof spawn;
23
+ }
24
+ export type SpawnKillReason = "no-output-timeout" | "overall-timeout" | "aborted";
25
+ export interface ClaudeCliRunHandle {
26
+ /** Async iterator of parsed stdout frames (blank/garbage lines skipped). */
27
+ frames: AsyncGenerator<ClaudeCliFrame>;
28
+ /** Resolves once the process exits: its code, and any kill reason we forced. */
29
+ done: Promise<{
30
+ code: number | null;
31
+ killReason?: SpawnKillReason;
32
+ stderr: string;
33
+ }>;
34
+ }
35
+ /**
36
+ * Spawn `claude` with the given argv, feed `stdin`, and stream parsed frames.
37
+ * The isolated empty temp cwd contains the CLI's tool blast radius: even under
38
+ * `bypassPermissions` any file/read tool it reaches acts on a throwaway dir,
39
+ * never the operator's project. The dir is removed when the process exits.
40
+ */
41
+ export declare function spawnClaudeCli(args: SpawnClaudeCliArgs): ClaudeCliRunHandle;
42
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../src/agents/claude-cli/spawn.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAOhF,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,SAAU,CAAC;AACvD,kEAAkE;AAClE,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAElF,MAAM,WAAW,kBAAkB;IAClC,4EAA4E;IAC5E,MAAM,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;IACvC,gFAAgF;IAChF,IAAI,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,CAAC,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAqJ3E"}