@mclawnet/agent 0.6.35 → 0.6.36

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 (69) hide show
  1. package/cli.js +6 -6
  2. package/dist/__tests__/collect-manifest.test.d.ts +2 -0
  3. package/dist/__tests__/collect-manifest.test.d.ts.map +1 -0
  4. package/dist/__tests__/hub-connection-on-activity.test.d.ts +2 -0
  5. package/dist/__tests__/hub-connection-on-activity.test.d.ts.map +1 -0
  6. package/dist/__tests__/hub-connection-wake-watch.test.d.ts +2 -0
  7. package/dist/__tests__/hub-connection-wake-watch.test.d.ts.map +1 -0
  8. package/dist/__tests__/ideas-rest-client.test.d.ts +2 -0
  9. package/dist/__tests__/ideas-rest-client.test.d.ts.map +1 -0
  10. package/dist/__tests__/legacy-claude-execute-compat.test.d.ts +2 -0
  11. package/dist/__tests__/legacy-claude-execute-compat.test.d.ts.map +1 -0
  12. package/dist/__tests__/no-adapter-cycle.test.d.ts +2 -0
  13. package/dist/__tests__/no-adapter-cycle.test.d.ts.map +1 -0
  14. package/dist/__tests__/session-manager-merge.test.d.ts +2 -0
  15. package/dist/__tests__/session-manager-merge.test.d.ts.map +1 -0
  16. package/dist/__tests__/session-manager-sticky.test.d.ts +2 -0
  17. package/dist/__tests__/session-manager-sticky.test.d.ts.map +1 -0
  18. package/dist/__tests__/session-protocol-dispatch.test.d.ts +2 -0
  19. package/dist/__tests__/session-protocol-dispatch.test.d.ts.map +1 -0
  20. package/dist/__tests__/worktree-bridge.test.d.ts +2 -0
  21. package/dist/__tests__/worktree-bridge.test.d.ts.map +1 -0
  22. package/dist/backend-adapter.d.ts +6 -232
  23. package/dist/backend-adapter.d.ts.map +1 -1
  24. package/dist/backend-factory-AFF6I7YF.js +11 -0
  25. package/dist/backend-factory.d.ts +23 -1
  26. package/dist/backend-factory.d.ts.map +1 -1
  27. package/dist/bootstrap-deps.d.ts +22 -0
  28. package/dist/bootstrap-deps.d.ts.map +1 -1
  29. package/dist/bootstrap-deps.js +23 -4
  30. package/dist/bootstrap-deps.js.map +1 -1
  31. package/dist/{chunk-PJ5M6Q36.js → chunk-376QZ7JB.js} +2 -2
  32. package/dist/chunk-376QZ7JB.js.map +1 -0
  33. package/dist/{chunk-B733MQCA.js → chunk-GOCWMRBB.js} +1772 -284
  34. package/dist/chunk-GOCWMRBB.js.map +1 -0
  35. package/dist/{chunk-M2CDVPQF.js → chunk-JH6RGJBQ.js} +2 -2
  36. package/dist/{chunk-FYM7CXUI.js → chunk-VAEFJLPL.js} +25 -3
  37. package/dist/chunk-VAEFJLPL.js.map +1 -0
  38. package/dist/{dist-EGT2NQEW.js → dist-NWVHAP5R.js} +155 -13
  39. package/dist/dist-NWVHAP5R.js.map +1 -0
  40. package/dist/errors.d.ts +20 -0
  41. package/dist/errors.d.ts.map +1 -1
  42. package/dist/hub-connection.d.ts +25 -1
  43. package/dist/hub-connection.d.ts.map +1 -1
  44. package/dist/ideas-rest-client.d.ts +25 -0
  45. package/dist/ideas-rest-client.d.ts.map +1 -0
  46. package/dist/index.js +3 -3
  47. package/dist/{linux-IHA4O633.js → linux-MBU6ERXL.js} +3 -3
  48. package/dist/{macos-G4VK2253.js → macos-I2DUWFUH.js} +3 -3
  49. package/dist/projects-handler.d.ts +146 -1
  50. package/dist/projects-handler.d.ts.map +1 -1
  51. package/dist/service/index.js +5 -5
  52. package/dist/session-manager.d.ts +58 -0
  53. package/dist/session-manager.d.ts.map +1 -1
  54. package/dist/start.d.ts.map +1 -1
  55. package/dist/start.js +3 -2
  56. package/dist/{windows-P6U3JLUZ.js → windows-PEJ3KOLC.js} +3 -3
  57. package/dist/worktree-bridge.d.ts +51 -0
  58. package/dist/worktree-bridge.d.ts.map +1 -0
  59. package/package.json +9 -8
  60. package/dist/backend-factory-VRPU3534.js +0 -9
  61. package/dist/chunk-B733MQCA.js.map +0 -1
  62. package/dist/chunk-FYM7CXUI.js.map +0 -1
  63. package/dist/chunk-PJ5M6Q36.js.map +0 -1
  64. package/dist/dist-EGT2NQEW.js.map +0 -1
  65. /package/dist/{backend-factory-VRPU3534.js.map → backend-factory-AFF6I7YF.js.map} +0 -0
  66. /package/dist/{chunk-M2CDVPQF.js.map → chunk-JH6RGJBQ.js.map} +0 -0
  67. /package/dist/{linux-IHA4O633.js.map → linux-MBU6ERXL.js.map} +0 -0
  68. /package/dist/{macos-G4VK2253.js.map → macos-I2DUWFUH.js.map} +0 -0
  69. /package/dist/{windows-P6U3JLUZ.js.map → windows-PEJ3KOLC.js.map} +0 -0
package/cli.js CHANGED
@@ -103,7 +103,7 @@ program
103
103
 
104
104
  const hubUrl = opts.hubUrl || existing.hubUrl || "wss://mclaw.work/ws/agent";
105
105
 
106
- saveConfig({ token, name, hubUrl, backendType: "claude-code" });
106
+ saveConfig({ token, name, hubUrl, backendType: "claude" });
107
107
  console.log("\n✓ Saved to ~/.clawnet/settings.json");
108
108
  console.log(" Run 'clawnet-agent start' to start the agent.\n");
109
109
  });
@@ -158,7 +158,7 @@ program
158
158
  .option("--hub-url <url>", "Hub WebSocket URL")
159
159
  .option("--token <token>", "Agent token")
160
160
  .option("--name <name>", "Agent name")
161
- .option("--backend <type>", "Backend type", "claude-code")
161
+ .option("--backend <type>", "Backend type", "claude")
162
162
  .action(async (opts) => {
163
163
  if (opts.foreground) {
164
164
  // Foreground mode: directly run agent (existing behavior)
@@ -177,8 +177,8 @@ program
177
177
  }
178
178
 
179
179
  let adapter;
180
- const backendType = opts.backend || "claude-code";
181
- if (backendType === "claude-code") {
180
+ const backendType = opts.backend || "claude";
181
+ if (backendType === "claude") {
182
182
  try {
183
183
  const { ClaudeCodeAdapter } = await import("@mclawnet/claude-adapter");
184
184
  adapter = new ClaudeCodeAdapter();
@@ -348,8 +348,8 @@ const CONFIG_SCHEMA = [
348
348
  },
349
349
  {
350
350
  key: "backendType",
351
- description: "LLM backend implementation. Currently only 'claude-code' is supported.",
352
- default: "claude-code",
351
+ description: "LLM backend implementation. Currently only 'claude' is supported.",
352
+ default: "claude",
353
353
  },
354
354
  {
355
355
  key: "embedding.provider",
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=collect-manifest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-manifest.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/collect-manifest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hub-connection-on-activity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-connection-on-activity.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hub-connection-on-activity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hub-connection-wake-watch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-connection-wake-watch.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hub-connection-wake-watch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ideas-rest-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ideas-rest-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ideas-rest-client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=legacy-claude-execute-compat.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-claude-execute-compat.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/legacy-claude-execute-compat.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=no-adapter-cycle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-adapter-cycle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/no-adapter-cycle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-manager-merge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager-merge.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-manager-merge.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-manager-sticky.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager-sticky.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-manager-sticky.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-protocol-dispatch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-protocol-dispatch.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-protocol-dispatch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worktree-bridge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-bridge.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/worktree-bridge.test.ts"],"names":[],"mappings":""}
@@ -1,236 +1,10 @@
1
1
  /**
2
- * BackendAdapterpluggable backend for AI model interaction.
2
+ * Backend interface types moved to @mclawnet/backend-types so that
3
+ * adapter packages (claude-adapter, codex-adapter, future ones) can depend
4
+ * on these types WITHOUT taking a build-time dependency on @mclawnet/agent.
3
5
  *
4
- * Implementations:
5
- * - ClaudeCodeAdapter (@mclawnet/claude-adapter): spawn `claude` CLI
6
- * - OpenClawAdapter: spawn `openclaw gateway` (legacy)
7
- * - CodexAdapter: M3.S3 — `codex app-server --listen stdio://`
8
- *
9
- * M3.S1 introduces:
10
- * - Typed permission flow (PermissionRequest / PermissionDecision)
11
- * - Typed BackendOutput envelope replacing the historical `onOutput(msg: unknown)`
12
- * - `backendSessionId` replaces the previous `claudeSessionId` (see plan D7).
13
- */
14
- export interface PermissionRequest {
15
- /** Stable identifier for the pending tool call. */
16
- callId: string;
17
- /** Tool name as reported by the backend (e.g. 'shell', 'apply_patch', 'Write'). */
18
- toolName: string;
19
- /** Raw tool input as the backend supplied it. */
20
- input: unknown;
21
- /** Optional backend-supplied context (sandbox, cwd, reason, ...). */
22
- meta?: {
23
- backend?: "claude" | "codex" | string;
24
- reason?: string;
25
- cwd?: string;
26
- [k: string]: unknown;
27
- };
28
- }
29
- /**
30
- * S0 finding: neither claude `--permission-mode default` (no wire channel)
31
- * nor codex `ReviewDecision` supports "approve with modifiedInput", so the
32
- * S1 contract is a 4-state enum. `allow_session` may degrade to `allow` for
33
- * backends that don't track session-scoped approvals.
34
- */
35
- export interface PermissionDecision {
36
- callId: string;
37
- decision: "allow" | "allow_session" | "deny" | "abort";
38
- reason?: string;
39
- }
40
- export type BackendOutput = {
41
- kind: "assistant_text";
42
- text: string;
43
- } | {
44
- kind: "tool_use";
45
- toolName: string;
46
- input: unknown;
47
- callId: string;
48
- } | {
49
- kind: "tool_result";
50
- callId: string;
51
- output: unknown;
52
- isError?: boolean;
53
- } | {
54
- kind: "system";
55
- subtype: string;
56
- data: unknown;
57
- }
58
- /**
59
- * Adapter-internal diagnostic only. Hub and wire layers MUST NOT consume
60
- * `raw` — promote a new typed variant instead when hub needs to read it.
6
+ * agent still re-exports them via this barrel for backward compatibility,
7
+ * so existing internal imports (`./backend-adapter.js`) keep working.
61
8
  */
62
- | {
63
- kind: "raw";
64
- backend: string;
65
- payload: unknown;
66
- };
67
- export interface BackendProcess {
68
- /** Unique identifier (e.g., Claude session ID) */
69
- id: string;
70
- /** Working directory for this process */
71
- workDir: string;
72
- /**
73
- * OS process id of the underlying backend process, if applicable.
74
- *
75
- * Optional because not every backend is a child process — a future in-process
76
- * adapter (mock / stubbed / WASM) wouldn't have one. SessionManager uses pid
77
- * for the PR-C checkpoint: a restart probes `process.kill(pid, 0)` to decide
78
- * whether the recorded session is `dead` or an `orphan`. Adapters that omit
79
- * pid forfeit the dead/orphan classification — checkpointed sessions will
80
- * silently appear as "no longer here", which is also acceptable behavior.
81
- */
82
- pid?: number;
83
- /** Kill the backend process */
84
- kill(): Promise<void>;
85
- /**
86
- * Whether the backend process is still alive and capable of accepting input.
87
- * Returns false if the process has exited, is being killed, or its stdin is no
88
- * longer writable. Used by SessionManager.isHealthy() to detect stale entries.
89
- */
90
- isAlive(): boolean;
91
- }
92
- export interface SpawnOptions {
93
- /** ClawNet session ID */
94
- sessionId: string;
95
- /** Working directory for the backend process */
96
- workDir?: string;
97
- /** Resume a previous session (e.g., Claude --resume UUID) */
98
- resumeId?: string;
99
- /** System prompt to append (e.g., role prompt for swarm mode) */
100
- systemPrompt?: string;
101
- /** Load BrainCore + CoreSkill via --add-dir */
102
- useBrainCore?: boolean;
103
- /** Path to MCP config JSON file for --mcp-config */
104
- mcpConfigPath?: string;
105
- /** Memory system role ID. When set, SessionManager auto-injects memory prompt + MCP config */
106
- roleId?: string;
107
- /**
108
- * Optional first user input. When set, SessionManager uses this as the
109
- * semantic-retrieval query for `buildMemorySection` (Pipeline A) so the
110
- * injected memory section is relevant to what the user is about to ask.
111
- * Falls back to an empty query (importance-top-K) when omitted.
112
- */
113
- initialUserInput?: string;
114
- /** Extra directories to mount via --add-dir */
115
- additionalDirs?: string[];
116
- /** Optional allowlist of tool names to forward to the backend (PR3.5). */
117
- allowedTools?: string[];
118
- /** Optional denylist of tool names to forward to the backend (PR3.5). */
119
- disallowedTools?: string[];
120
- /**
121
- * Override CLAUDE_CODE_MAX_OUTPUT_TOKENS for this spawn. Drives the
122
- * server-side hard limit (200k − maxOutputTokens) and the CLI's own
123
- * effective window. Lower = wider prompt budget. SessionManager pulls this
124
- * from `getRecommendedMaxOutputTokens()` on each spawn so the ladder
125
- * (32k → 16k → 8k) survives across `--resume` restarts.
126
- *
127
- * Optional: when omitted the CLI uses its own default — adapters that
128
- * don't honour it lose D-lite escalation but never crash.
129
- */
130
- maxOutputTokens?: number;
131
- /**
132
- * Optional backend kind for this spawn. When set, SessionManager resolves
133
- * an adapter via its `resolveAdapter` hook (typically `createBackendAdapter`)
134
- * and uses it for this process instead of the default constructor adapter.
135
- * Omitted = use the default adapter (back-compat).
136
- */
137
- backend?: "claude" | "codex";
138
- /**
139
- * Per-role sandbox level. Each adapter maps to its own permission model:
140
- * - codex-adapter: `-c sandbox_mode="<value>"` (OS-level seatbelt /
141
- * landlock — strong enforcement)
142
- * - claude-adapter: `--permission-mode` (LLM-soft approval; UI should
143
- * tooltip that claude has no real OS sandbox)
144
- *
145
- * Values: "read-only" | "workspace-write" | "full-access".
146
- * Default (undefined): adapter-specific — claude uses constructor
147
- * permissionMode (typically bypassPermissions), codex uses
148
- * "workspace-write".
149
- */
150
- sandbox?: "read-only" | "workspace-write" | "full-access";
151
- }
152
- export interface BackendAdapter {
153
- /** Backend type identifier */
154
- type: string;
155
- /** Spawn a new backend process */
156
- spawn(options: SpawnOptions): Promise<BackendProcess>;
157
- /** Stop a running backend process */
158
- stop(process: BackendProcess): Promise<void>;
159
- /** Send user input to the backend process */
160
- send(process: BackendProcess, input: string): void;
161
- /**
162
- * Register output handler. Handler receives `unknown` today (legacy claude
163
- * stream-json frames); adapters are migrating to emit `BackendOutput`
164
- * envelopes. Consumers that need typed output should narrow with the
165
- * `BackendOutput` discriminator.
166
- */
167
- onOutput(process: BackendProcess, handler: (msg: unknown) => void): void;
168
- /**
169
- * M3.S1 — Optional permission flow.
170
- * - codex-adapter (S3): implemented; maps from `ReviewDecision` wire format.
171
- * - claude-adapter: M3 stays on `bypassPermissions` and does NOT implement
172
- * these. M4+ will add via an MCP permission-prompt tool.
173
- */
174
- onPermissionRequest?(process: BackendProcess, handler: (req: PermissionRequest) => void): void;
175
- respondToPermission?(process: BackendProcess, decision: PermissionDecision): Promise<void>;
176
- /** Turn-complete handler. */
177
- onTurnComplete?(process: BackendProcess, handler: (info: {
178
- backendSessionId?: string;
179
- cost?: number;
180
- duration?: number;
181
- contextUsage?: {
182
- used: number;
183
- total: number;
184
- };
185
- backendMeta?: Record<string, unknown>;
186
- }) => void): void;
187
- /** Register error handler */
188
- onError?(process: BackendProcess, handler: (error: Error) => void): void;
189
- /**
190
- * Register a handler that fires the moment the backend reports its session
191
- * is initialized (e.g. Claude CLI's `system/init` frame, codex `thread_id`).
192
- * Used by hub to persist `backendSessionId` early — without this we have to
193
- * wait for `turn_complete`, and a mid-turn crash would leave db's
194
- * backend_session_id NULL despite a valid jsonl file existing on disk.
195
- *
196
- * Optional: adapters that don't implement this lose the early-backfill
197
- * capability — db's backend_session_id will only be populated on
198
- * `turn_complete`, so any session whose first turn crashes will be
199
- * unrecoverable via `--resume`. Other backends (codex, openclaw) should
200
- * implement this if they have an analogous "session ready" signal.
201
- */
202
- onSessionStarted?(process: BackendProcess, handler: (info: {
203
- backendSessionId: string;
204
- }) => void): void;
205
- /**
206
- * Register a handler that fires when the underlying process exits on its
207
- * own (crash, EPIPE, OOM, manual SIGKILL from outside) — *not* when we
208
- * deliberately call kill() ourselves. Used by SessionManager to auto-evict
209
- * dead Map entries and notify hub via onSessionError.
210
- *
211
- * Optional: adapters that don't implement this lose auto-eviction —
212
- * SessionManager's Map will keep the dead entry, isHealthy() will still
213
- * fall back to BackendProcess.isAlive() so the unhealthy-fallback-resume
214
- * branch in hub-connection.ts still works, but the orphan Map entry will
215
- * accumulate until the next abortSession/closeSession call.
216
- */
217
- onExit?(process: BackendProcess, handler: (code: number | null) => void): void;
218
- /**
219
- * Register a handler for token-budget warnings. Fires when the backend
220
- * estimates the prompt is approaching the server-side hard limit.
221
- * SessionManager uses this to escalate the max_output_tokens ladder so
222
- * the next spawn gets a wider budget.
223
- *
224
- * Optional: adapters that can't introspect token usage simply skip this —
225
- * the ladder stays at index 0 forever, falling back to claude-code's own
226
- * auto-compact (which works most of the time, just less defensively).
227
- */
228
- onTokenBudgetWarning?(process: BackendProcess, handler: (info: {
229
- used: number;
230
- hardLimit: number;
231
- threshold: number;
232
- distanceToHard: number;
233
- maxOutputTokens: number;
234
- }) => void): void;
235
- }
9
+ export type { BackendAdapter, BackendProcess, BackendOutput, PermissionDecision, PermissionRequest, SpawnOptions, } from "@mclawnet/backend-types";
236
10
  //# sourceMappingURL=backend-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend-adapter.d.ts","sourceRoot":"","sources":["../src/backend-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backend-adapter.d.ts","sourceRoot":"","sources":["../src/backend-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,GACb,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import {
2
+ __resetBackendFactoryCache,
3
+ collectAgentManifest,
4
+ createBackendAdapter
5
+ } from "./chunk-VAEFJLPL.js";
6
+ export {
7
+ __resetBackendFactoryCache,
8
+ collectAgentManifest,
9
+ createBackendAdapter
10
+ };
11
+ //# sourceMappingURL=backend-factory-AFF6I7YF.js.map
@@ -1,5 +1,6 @@
1
1
  import type { BackendAdapter } from "./backend-adapter.js";
2
- export type BackendKind = "claude" | "codex";
2
+ import type { AgentManifestPayload, BackendKind } from "@mclawnet/shared";
3
+ export type { BackendKind };
3
4
  export declare function __resetBackendFactoryCache(): void;
4
5
  export interface BackendFactoryContext {
5
6
  /** Role instance / template name — surfaces in error messages so users know
@@ -13,7 +14,28 @@ export interface BackendFactoryContext {
13
14
  * - "codex" → @mclawnet/codex-adapter via dynamic import so the codex bundle
14
15
  * isn't pulled into claude-only deployments.
15
16
  *
17
+ * Why dynamic import (and why codex-adapter is `optionalDependencies` in
18
+ * agent's package.json, not `dependencies`):
19
+ * - agent owns the runtime relationship "I load adapters on demand".
20
+ * - Adapters only depend on @mclawnet/backend-types (the interface package),
21
+ * so there is no build-time cycle.
22
+ * - `optionalDependencies` keeps the "user installs just @mclawnet/agent
23
+ * and codex backend works out of the box" UX: npm/pnpm will try to
24
+ * install codex-adapter; if that fails (network, platform issue, user
25
+ * opted out via --no-optional), the dynamic import below throws and
26
+ * the agent surfaces a clear error instead of crashing at startup.
27
+ * - Promoting codex-adapter to `dependencies` would force install, which
28
+ * is fine for monorepo but unfriendly to claude-only deployments.
29
+ *
16
30
  * Results are cached per kind; subsequent calls return the same instance.
17
31
  */
18
32
  export declare function createBackendAdapter(kind: BackendKind | undefined, ctx?: BackendFactoryContext): Promise<BackendAdapter>;
33
+ /**
34
+ * Probe every known backend adapter and aggregate their `getManifest()` output
35
+ * into a single payload the agent can ship to the hub. Failures (binary not on
36
+ * PATH, adapter package broken, etc.) become `installed: false` entries rather
37
+ * than rejecting the whole collection — the hub still wants to know "we tried
38
+ * but it isn't available" for each kind.
39
+ */
40
+ export declare function collectAgentManifest(): Promise<AgentManifestPayload>;
19
41
  //# sourceMappingURL=backend-factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend-factory.d.ts","sourceRoot":"","sources":["../src/backend-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAI7C,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,MAAM,WAAW,qBAAqB;IACpC;8EAC0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,GAAG,GAAE,qBAA0B,GAC9B,OAAO,CAAC,cAAc,CAAC,CA0CzB"}
1
+ {"version":3,"file":"backend-factory.d.ts","sourceRoot":"","sources":["../src/backend-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAI1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAI5B,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,MAAM,WAAW,qBAAqB;IACpC;8EAC0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,GAAG,GAAE,qBAA0B,GAC9B,OAAO,CAAC,cAAc,CAAC,CAsCzB;AAgBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkB1E"}
@@ -36,6 +36,28 @@ export declare function patchCodexConfig(opts: PatchOptions): PatchResult;
36
36
  */
37
37
  export declare function patchClaudeConfig(opts: PatchOptions): PatchResult;
38
38
  export declare function detectEnvVar(name: string): boolean;
39
+ /**
40
+ * Read ~/.codex/config.toml and return the currently-active model_provider
41
+ * string. Returns undefined when the file is absent, malformed, or has no
42
+ * model_provider key set.
43
+ *
44
+ * Used by bootstrapDeps to gate provider-specific env-var hints — we only
45
+ * warn about COPILOT_API_KEY when the user actually picked copilot-api.
46
+ */
47
+ export declare function detectActiveCodexProvider(homeDir: string): string | undefined;
48
+ /**
49
+ * Build a platform-specific user-facing instruction for setting
50
+ * COPILOT_API_KEY. The copilot-api proxy (the localhost:4141 forwarder)
51
+ * does not validate the bearer token — any non-empty string works — but
52
+ * codex's env_key check fails hard if the variable is missing. So the hint
53
+ * has to tell the user exactly which shell command to run.
54
+ *
55
+ * Platform split kept here (not at the call site) so future locales / new
56
+ * platforms can extend the matrix without touching bootstrapDeps logic.
57
+ */
58
+ export declare function buildCopilotApiEnvHint(opts: {
59
+ platform: NodeJS.Platform | string;
60
+ }): string;
39
61
  export interface BootstrapOptions {
40
62
  homeDir: string;
41
63
  /** When true, prompt user before installing CLIs. */
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-deps.d.ts","sourceRoot":"","sources":["../src/bootstrap-deps.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,CAyB7E;AAED,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAqEhE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAejE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGlD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mDAAmD;IACnD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAmBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqDpF"}
1
+ {"version":3,"file":"bootstrap-deps.d.ts","sourceRoot":"","sources":["../src/bootstrap-deps.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,CAyB7E;AAED,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAqEhE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAejE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGlD;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,CAmB3F;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mDAAmD;IACnD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAmBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwDpF"}
@@ -99,6 +99,23 @@ function detectEnvVar(name) {
99
99
  const v = process.env[name];
100
100
  return typeof v === "string" && v.length > 0;
101
101
  }
102
+ function detectActiveCodexProvider(homeDir) {
103
+ const cfgPath = join(homeDir, ".codex", "config.toml");
104
+ if (!existsSync(cfgPath)) return void 0;
105
+ try {
106
+ const cfg = parseToml(readFileSync(cfgPath, "utf-8"));
107
+ const v = cfg.model_provider;
108
+ return typeof v === "string" && v.length > 0 ? v : void 0;
109
+ } catch {
110
+ return void 0;
111
+ }
112
+ }
113
+ function buildCopilotApiEnvHint(opts) {
114
+ if (opts.platform === "win32") {
115
+ return "COPILOT_API_KEY is not set, and your active codex provider is copilot-api.\n Persist it (takes effect in NEW shells):\n setx COPILOT_API_KEY dummy\n Current PowerShell session only:\n $env:COPILOT_API_KEY = 'dummy'\n The copilot-api proxy does not validate the value (any non-empty string works); codex just needs the variable to exist.";
116
+ }
117
+ return "COPILOT_API_KEY is not set, and your active codex provider is copilot-api.\n Add to ~/.zshrc (or ~/.bashrc) and reload:\n export COPILOT_API_KEY=dummy\n Current shell only:\n export COPILOT_API_KEY=dummy\n The copilot-api proxy does not validate the value (any non-empty string works); codex just needs the variable to exist.";
118
+ }
102
119
  async function defaultInstall(name) {
103
120
  const pkg = name === "claude" ? "@anthropic-ai/claude-code" : "@openai/codex";
104
121
  execFileSync("npm", ["install", "-g", pkg], { stdio: "inherit" });
@@ -137,15 +154,17 @@ async function bootstrapDeps(opts) {
137
154
  const claudeR = patchClaudeConfig({ homeDir: opts.homeDir });
138
155
  report.configChanges.push(...claudeR.patched);
139
156
  if (!detectEnvVar("COPILOT_API_KEY")) {
140
- const shellHint = isWin ? "Set COPILOT_API_KEY in System \u2192 Environment Variables (any non-empty value, e.g. 'dummy')" : "Add to ~/.zshrc (or ~/.bashrc): export COPILOT_API_KEY=dummy";
141
- report.envHints.push(
142
- `COPILOT_API_KEY is not set. ${shellHint}. The copilot-api proxy does not validate the value, but codex requires the variable to exist.`
143
- );
157
+ const activeProvider = detectActiveCodexProvider(opts.homeDir);
158
+ if (activeProvider === "copilot-api") {
159
+ report.envHints.push(buildCopilotApiEnvHint({ platform: process.platform }));
160
+ }
144
161
  }
145
162
  return report;
146
163
  }
147
164
  export {
148
165
  bootstrapDeps,
166
+ buildCopilotApiEnvHint,
167
+ detectActiveCodexProvider,
149
168
  detectCli,
150
169
  detectEnvVar,
151
170
  patchClaudeConfig,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bootstrap-deps.ts"],"sourcesContent":["/**\n * bootstrap-deps — `clawnet-agent init` / `doctor` shared logic.\n *\n * Responsibilities (all idempotent, safe to re-run):\n * - detect whether claude / codex CLIs are installed (which / where)\n * - idempotent-merge ~/.codex/config.toml (default to copilot-api provider)\n * - idempotent-merge ~/.claude/settings.json (create if absent, never overwrite)\n * - detect env vars (COPILOT_API_KEY) and return user-facing hints when missing\n *\n * Side-effects deliberately NOT taken:\n * - never modifies ~/.zshrc / ~/.bashrc / Windows env — dotfiles are sacred.\n * Missing env is reported via `envHints[]` so the caller can print them.\n * - never overwrites user-set TOML / JSON values; only adds missing keys.\n * - when patching an EXISTING TOML file we never round-trip through\n * stringify (smol-toml drops comments). We parse only to detect which\n * keys are missing, then append the missing bits as raw text so user\n * comments and key order survive untouched.\n *\n * Network / subprocess side-effects (npm install) are injected via `installFn`\n * so tests can verify call shape without actually spawning npm.\n */\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { parse as parseToml, stringify as stringifyToml } from \"smol-toml\";\n\nconst isWin = platform() === \"win32\";\n\nexport interface DetectResult {\n found: boolean;\n path?: string;\n}\n\nexport interface DetectOptions {\n /** Override which/where lookup. Tests inject a fake; runtime defaults to execFileSync. */\n whichFn?: (bin: string) => string;\n}\n\nexport function detectCli(bin: string, opts: DetectOptions = {}): DetectResult {\n const whichFn =\n opts.whichFn ??\n ((b: string) => {\n const cmd = isWin ? \"where\" : \"which\";\n // execFileSync (not execSync) — pass `b` as an arg, never interpolate\n // into a shell string. Today callers pass only literal \"claude\"/\"codex\"\n // but the signature accepts any string; using execFile is defense in\n // depth so a future caller passing user input can't smuggle a shell\n // metacharacter and turn this into a command-injection sink.\n return execFileSync(cmd, [b], {\n encoding: \"utf-8\",\n timeout: 1500,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n .trim()\n .split(/\\r?\\n/)[0];\n });\n try {\n const path = whichFn(bin);\n if (path) return { found: true, path };\n return { found: false };\n } catch {\n return { found: false };\n }\n}\n\nexport interface PatchResult {\n /** Human-readable change log entries; empty array = no changes. */\n patched: string[];\n}\n\nexport interface PatchOptions {\n homeDir: string;\n}\n\nconst COPILOT_PROVIDER_DEFAULTS = {\n name: \"copilot-api\",\n base_url: \"http://localhost:4141/v1\",\n wire_api: \"responses\",\n env_key: \"COPILOT_API_KEY\",\n} as const;\n\n/**\n * Merge defaults into ~/.codex/config.toml.\n *\n * Two paths:\n * - File absent → write a fresh stringified TOML with our defaults.\n * - File exists → parse to detect missing keys, then APPEND missing\n * fragments as raw text. We never re-stringify the parsed AST, because\n * smol-toml's stringify drops comments and may reorder keys (verified\n * by regression test). Appending preserves the user's original bytes.\n *\n * Never touches user-set model_provider / model / per-provider fields.\n */\nexport function patchCodexConfig(opts: PatchOptions): PatchResult {\n const codexDir = join(opts.homeDir, \".codex\");\n const cfgPath = join(codexDir, \"config.toml\");\n\n // Fresh-write path.\n if (!existsSync(cfgPath)) {\n const fresh = {\n model_provider: \"copilot-api\",\n model_providers: { \"copilot-api\": COPILOT_PROVIDER_DEFAULTS },\n };\n mkdirSync(codexDir, { recursive: true });\n writeFileSync(cfgPath, stringifyToml(fresh) + \"\\n\");\n return { patched: [\"created ~/.codex/config.toml\"] };\n }\n\n // Existing-file path: parse-then-append-raw.\n const originalText = readFileSync(cfgPath, \"utf-8\");\n let cfg: Record<string, unknown>;\n try {\n cfg = parseToml(originalText) as Record<string, unknown>;\n } catch {\n return { patched: [\"~/.codex/config.toml could not be parsed; left untouched\"] };\n }\n\n // Type-guard model_providers: smol-toml will faithfully parse a wrong-typed\n // `model_providers = \"string\"` as a string, and our previous .cast-and-poke\n // approach would silently overwrite it. Bail loudly instead.\n const mp = cfg.model_providers;\n if (mp !== undefined && (typeof mp !== \"object\" || Array.isArray(mp))) {\n return {\n patched: [\"~/.codex/config.toml has model_providers that is not a table; left untouched\"],\n };\n }\n\n const needsTopLevel = cfg.model_provider === undefined;\n const providers = (mp ?? {}) as Record<string, unknown>;\n const needsProvider = !providers[\"copilot-api\"];\n\n if (!needsTopLevel && !needsProvider) {\n return { patched: [] };\n }\n\n const fragments: string[] = [];\n const patched: string[] = [];\n\n if (needsTopLevel) {\n // Top-level scalar key must precede any [section] header to remain\n // top-level. The safest place to insert is at the very top of the file.\n // We accumulate scalar additions and prepend them in a single splice.\n fragments.push(`model_provider = \"copilot-api\"`);\n patched.push(\"set model_provider=copilot-api\");\n }\n\n let updated = originalText;\n if (fragments.length > 0) {\n updated = fragments.join(\"\\n\") + \"\\n\\n\" + updated;\n }\n\n if (needsProvider) {\n const sectionToml = stringifyToml({\n model_providers: { \"copilot-api\": COPILOT_PROVIDER_DEFAULTS },\n });\n const sep = updated.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n updated = updated + sep + sectionToml + \"\\n\";\n patched.push(\"added [model_providers.copilot-api]\");\n }\n\n writeFileSync(cfgPath, updated);\n return { patched };\n}\n\n/**\n * Ensure ~/.claude/settings.json exists.\n *\n * Currently a thin presence-check: claude-code's own config schema evolves\n * fast and we don't want to fight it. Just create an empty object if absent\n * so downstream tools can rely on the file's existence.\n */\nexport function patchClaudeConfig(opts: PatchOptions): PatchResult {\n const claudeDir = join(opts.homeDir, \".claude\");\n const cfgPath = join(claudeDir, \"settings.json\");\n if (existsSync(cfgPath)) {\n // Validate it parses; if not, surface but don't rewrite.\n try {\n JSON.parse(readFileSync(cfgPath, \"utf-8\"));\n return { patched: [] };\n } catch {\n return { patched: [\"~/.claude/settings.json is not valid JSON; left untouched\"] };\n }\n }\n mkdirSync(claudeDir, { recursive: true });\n writeFileSync(cfgPath, \"{}\\n\");\n return { patched: [\"created ~/.claude/settings.json\"] };\n}\n\nexport function detectEnvVar(name: string): boolean {\n const v = process.env[name];\n return typeof v === \"string\" && v.length > 0;\n}\n\nexport interface BootstrapOptions {\n homeDir: string;\n /** When true, prompt user before installing CLIs. */\n interactive: boolean;\n /** When false, never install (doctor mode). */\n install: boolean;\n whichFn?: (bin: string) => string;\n installFn?: (cliName: \"claude\" | \"codex\") => Promise<void>;\n promptFn?: (question: string) => Promise<string>;\n}\n\nexport interface BootstrapReport {\n /** CLIs that were already installed. */\n present: string[];\n /** CLIs missing at start. */\n missing: string[];\n /** CLIs installed during this run. */\n installed: string[];\n /** Config patcher change logs (codex + claude). */\n configChanges: string[];\n /** Hint strings to print to user about env vars that need attention. */\n envHints: string[];\n}\n\n/**\n * Default npm-based install. Kept here (not in detect.ts) so production\n * callers don't have to wire it; tests inject their own spy.\n *\n * Why npm and not brew/cargo/winget: we picked one source per CLI to keep\n * the matrix small. claude-code is npm-only anyway, and codex npm package\n * works on both macOS and Windows (our codex-adapter already handles the\n * Windows .cmd shim).\n */\nasync function defaultInstall(name: \"claude\" | \"codex\"): Promise<void> {\n const pkg = name === \"claude\" ? \"@anthropic-ai/claude-code\" : \"@openai/codex\";\n // Inherit stdio so the user sees npm's progress; init is interactive.\n // execFileSync with explicit args — never interpolate the package name\n // into a shell string even though it's hardcoded today.\n execFileSync(\"npm\", [\"install\", \"-g\", pkg], { stdio: \"inherit\" });\n}\n\nexport async function bootstrapDeps(opts: BootstrapOptions): Promise<BootstrapReport> {\n const installFn = opts.installFn ?? defaultInstall;\n const promptFn = opts.promptFn ?? (async () => \"y\");\n\n const report: BootstrapReport = {\n present: [],\n missing: [],\n installed: [],\n configChanges: [],\n envHints: [],\n };\n\n // 1. Detect CLIs\n const cliNames: Array<\"claude\" | \"codex\"> = [\"claude\", \"codex\"];\n for (const name of cliNames) {\n const r = detectCli(name, { whichFn: opts.whichFn });\n if (r.found) report.present.push(name);\n else report.missing.push(name);\n }\n\n // 2. Optionally install missing\n if (opts.install && report.missing.length > 0) {\n for (const name of report.missing as Array<\"claude\" | \"codex\">) {\n let shouldInstall = true;\n if (opts.interactive) {\n const ans = (await promptFn(`Install ${name} CLI via npm? [Y/n]: `)).trim().toLowerCase();\n // Default Y (empty input → yes); only explicit \"n\"/\"no\" skips.\n shouldInstall = ans === \"\" || ans === \"y\" || ans === \"yes\";\n }\n if (shouldInstall) {\n await installFn(name);\n report.installed.push(name);\n }\n }\n }\n\n // 3. Patch configs (always — they're idempotent)\n const codexR = patchCodexConfig({ homeDir: opts.homeDir });\n report.configChanges.push(...codexR.patched);\n const claudeR = patchClaudeConfig({ homeDir: opts.homeDir });\n report.configChanges.push(...claudeR.patched);\n\n // 4. Env hints (no dotfile mutation — print only)\n if (!detectEnvVar(\"COPILOT_API_KEY\")) {\n const shellHint = isWin\n ? \"Set COPILOT_API_KEY in System → Environment Variables (any non-empty value, e.g. 'dummy')\"\n : \"Add to ~/.zshrc (or ~/.bashrc): export COPILOT_API_KEY=dummy\";\n report.envHints.push(\n `COPILOT_API_KEY is not set. ${shellHint}. The copilot-api proxy does not validate the value, but codex requires the variable to exist.`,\n );\n }\n\n return report;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/bootstrap-deps.ts"],"sourcesContent":["/**\n * bootstrap-deps — `clawnet-agent init` / `doctor` shared logic.\n *\n * Responsibilities (all idempotent, safe to re-run):\n * - detect whether claude / codex CLIs are installed (which / where)\n * - idempotent-merge ~/.codex/config.toml (default to copilot-api provider)\n * - idempotent-merge ~/.claude/settings.json (create if absent, never overwrite)\n * - detect env vars (COPILOT_API_KEY) and return user-facing hints when missing\n *\n * Side-effects deliberately NOT taken:\n * - never modifies ~/.zshrc / ~/.bashrc / Windows env — dotfiles are sacred.\n * Missing env is reported via `envHints[]` so the caller can print them.\n * - never overwrites user-set TOML / JSON values; only adds missing keys.\n * - when patching an EXISTING TOML file we never round-trip through\n * stringify (smol-toml drops comments). We parse only to detect which\n * keys are missing, then append the missing bits as raw text so user\n * comments and key order survive untouched.\n *\n * Network / subprocess side-effects (npm install) are injected via `installFn`\n * so tests can verify call shape without actually spawning npm.\n */\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { parse as parseToml, stringify as stringifyToml } from \"smol-toml\";\n\nconst isWin = platform() === \"win32\";\n\nexport interface DetectResult {\n found: boolean;\n path?: string;\n}\n\nexport interface DetectOptions {\n /** Override which/where lookup. Tests inject a fake; runtime defaults to execFileSync. */\n whichFn?: (bin: string) => string;\n}\n\nexport function detectCli(bin: string, opts: DetectOptions = {}): DetectResult {\n const whichFn =\n opts.whichFn ??\n ((b: string) => {\n const cmd = isWin ? \"where\" : \"which\";\n // execFileSync (not execSync) — pass `b` as an arg, never interpolate\n // into a shell string. Today callers pass only literal \"claude\"/\"codex\"\n // but the signature accepts any string; using execFile is defense in\n // depth so a future caller passing user input can't smuggle a shell\n // metacharacter and turn this into a command-injection sink.\n return execFileSync(cmd, [b], {\n encoding: \"utf-8\",\n timeout: 1500,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n .trim()\n .split(/\\r?\\n/)[0];\n });\n try {\n const path = whichFn(bin);\n if (path) return { found: true, path };\n return { found: false };\n } catch {\n return { found: false };\n }\n}\n\nexport interface PatchResult {\n /** Human-readable change log entries; empty array = no changes. */\n patched: string[];\n}\n\nexport interface PatchOptions {\n homeDir: string;\n}\n\nconst COPILOT_PROVIDER_DEFAULTS = {\n name: \"copilot-api\",\n base_url: \"http://localhost:4141/v1\",\n wire_api: \"responses\",\n env_key: \"COPILOT_API_KEY\",\n} as const;\n\n/**\n * Merge defaults into ~/.codex/config.toml.\n *\n * Two paths:\n * - File absent → write a fresh stringified TOML with our defaults.\n * - File exists → parse to detect missing keys, then APPEND missing\n * fragments as raw text. We never re-stringify the parsed AST, because\n * smol-toml's stringify drops comments and may reorder keys (verified\n * by regression test). Appending preserves the user's original bytes.\n *\n * Never touches user-set model_provider / model / per-provider fields.\n */\nexport function patchCodexConfig(opts: PatchOptions): PatchResult {\n const codexDir = join(opts.homeDir, \".codex\");\n const cfgPath = join(codexDir, \"config.toml\");\n\n // Fresh-write path.\n if (!existsSync(cfgPath)) {\n const fresh = {\n model_provider: \"copilot-api\",\n model_providers: { \"copilot-api\": COPILOT_PROVIDER_DEFAULTS },\n };\n mkdirSync(codexDir, { recursive: true });\n writeFileSync(cfgPath, stringifyToml(fresh) + \"\\n\");\n return { patched: [\"created ~/.codex/config.toml\"] };\n }\n\n // Existing-file path: parse-then-append-raw.\n const originalText = readFileSync(cfgPath, \"utf-8\");\n let cfg: Record<string, unknown>;\n try {\n cfg = parseToml(originalText) as Record<string, unknown>;\n } catch {\n return { patched: [\"~/.codex/config.toml could not be parsed; left untouched\"] };\n }\n\n // Type-guard model_providers: smol-toml will faithfully parse a wrong-typed\n // `model_providers = \"string\"` as a string, and our previous .cast-and-poke\n // approach would silently overwrite it. Bail loudly instead.\n const mp = cfg.model_providers;\n if (mp !== undefined && (typeof mp !== \"object\" || Array.isArray(mp))) {\n return {\n patched: [\"~/.codex/config.toml has model_providers that is not a table; left untouched\"],\n };\n }\n\n const needsTopLevel = cfg.model_provider === undefined;\n const providers = (mp ?? {}) as Record<string, unknown>;\n const needsProvider = !providers[\"copilot-api\"];\n\n if (!needsTopLevel && !needsProvider) {\n return { patched: [] };\n }\n\n const fragments: string[] = [];\n const patched: string[] = [];\n\n if (needsTopLevel) {\n // Top-level scalar key must precede any [section] header to remain\n // top-level. The safest place to insert is at the very top of the file.\n // We accumulate scalar additions and prepend them in a single splice.\n fragments.push(`model_provider = \"copilot-api\"`);\n patched.push(\"set model_provider=copilot-api\");\n }\n\n let updated = originalText;\n if (fragments.length > 0) {\n updated = fragments.join(\"\\n\") + \"\\n\\n\" + updated;\n }\n\n if (needsProvider) {\n const sectionToml = stringifyToml({\n model_providers: { \"copilot-api\": COPILOT_PROVIDER_DEFAULTS },\n });\n const sep = updated.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n updated = updated + sep + sectionToml + \"\\n\";\n patched.push(\"added [model_providers.copilot-api]\");\n }\n\n writeFileSync(cfgPath, updated);\n return { patched };\n}\n\n/**\n * Ensure ~/.claude/settings.json exists.\n *\n * Currently a thin presence-check: claude-code's own config schema evolves\n * fast and we don't want to fight it. Just create an empty object if absent\n * so downstream tools can rely on the file's existence.\n */\nexport function patchClaudeConfig(opts: PatchOptions): PatchResult {\n const claudeDir = join(opts.homeDir, \".claude\");\n const cfgPath = join(claudeDir, \"settings.json\");\n if (existsSync(cfgPath)) {\n // Validate it parses; if not, surface but don't rewrite.\n try {\n JSON.parse(readFileSync(cfgPath, \"utf-8\"));\n return { patched: [] };\n } catch {\n return { patched: [\"~/.claude/settings.json is not valid JSON; left untouched\"] };\n }\n }\n mkdirSync(claudeDir, { recursive: true });\n writeFileSync(cfgPath, \"{}\\n\");\n return { patched: [\"created ~/.claude/settings.json\"] };\n}\n\nexport function detectEnvVar(name: string): boolean {\n const v = process.env[name];\n return typeof v === \"string\" && v.length > 0;\n}\n\n/**\n * Read ~/.codex/config.toml and return the currently-active model_provider\n * string. Returns undefined when the file is absent, malformed, or has no\n * model_provider key set.\n *\n * Used by bootstrapDeps to gate provider-specific env-var hints — we only\n * warn about COPILOT_API_KEY when the user actually picked copilot-api.\n */\nexport function detectActiveCodexProvider(homeDir: string): string | undefined {\n const cfgPath = join(homeDir, \".codex\", \"config.toml\");\n if (!existsSync(cfgPath)) return undefined;\n try {\n const cfg = parseToml(readFileSync(cfgPath, \"utf-8\")) as Record<string, unknown>;\n const v = cfg.model_provider;\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build a platform-specific user-facing instruction for setting\n * COPILOT_API_KEY. The copilot-api proxy (the localhost:4141 forwarder)\n * does not validate the bearer token — any non-empty string works — but\n * codex's env_key check fails hard if the variable is missing. So the hint\n * has to tell the user exactly which shell command to run.\n *\n * Platform split kept here (not at the call site) so future locales / new\n * platforms can extend the matrix without touching bootstrapDeps logic.\n */\nexport function buildCopilotApiEnvHint(opts: { platform: NodeJS.Platform | string }): string {\n if (opts.platform === \"win32\") {\n return (\n \"COPILOT_API_KEY is not set, and your active codex provider is copilot-api.\\n\" +\n \" Persist it (takes effect in NEW shells):\\n\" +\n \" setx COPILOT_API_KEY dummy\\n\" +\n \" Current PowerShell session only:\\n\" +\n \" $env:COPILOT_API_KEY = 'dummy'\\n\" +\n \" The copilot-api proxy does not validate the value (any non-empty string works); codex just needs the variable to exist.\"\n );\n }\n return (\n \"COPILOT_API_KEY is not set, and your active codex provider is copilot-api.\\n\" +\n \" Add to ~/.zshrc (or ~/.bashrc) and reload:\\n\" +\n \" export COPILOT_API_KEY=dummy\\n\" +\n \" Current shell only:\\n\" +\n \" export COPILOT_API_KEY=dummy\\n\" +\n \" The copilot-api proxy does not validate the value (any non-empty string works); codex just needs the variable to exist.\"\n );\n}\n\nexport interface BootstrapOptions {\n homeDir: string;\n /** When true, prompt user before installing CLIs. */\n interactive: boolean;\n /** When false, never install (doctor mode). */\n install: boolean;\n whichFn?: (bin: string) => string;\n installFn?: (cliName: \"claude\" | \"codex\") => Promise<void>;\n promptFn?: (question: string) => Promise<string>;\n}\n\nexport interface BootstrapReport {\n /** CLIs that were already installed. */\n present: string[];\n /** CLIs missing at start. */\n missing: string[];\n /** CLIs installed during this run. */\n installed: string[];\n /** Config patcher change logs (codex + claude). */\n configChanges: string[];\n /** Hint strings to print to user about env vars that need attention. */\n envHints: string[];\n}\n\n/**\n * Default npm-based install. Kept here (not in detect.ts) so production\n * callers don't have to wire it; tests inject their own spy.\n *\n * Why npm and not brew/cargo/winget: we picked one source per CLI to keep\n * the matrix small. claude-code is npm-only anyway, and codex npm package\n * works on both macOS and Windows (our codex-adapter already handles the\n * Windows .cmd shim).\n */\nasync function defaultInstall(name: \"claude\" | \"codex\"): Promise<void> {\n const pkg = name === \"claude\" ? \"@anthropic-ai/claude-code\" : \"@openai/codex\";\n // Inherit stdio so the user sees npm's progress; init is interactive.\n // execFileSync with explicit args — never interpolate the package name\n // into a shell string even though it's hardcoded today.\n execFileSync(\"npm\", [\"install\", \"-g\", pkg], { stdio: \"inherit\" });\n}\n\nexport async function bootstrapDeps(opts: BootstrapOptions): Promise<BootstrapReport> {\n const installFn = opts.installFn ?? defaultInstall;\n const promptFn = opts.promptFn ?? (async () => \"y\");\n\n const report: BootstrapReport = {\n present: [],\n missing: [],\n installed: [],\n configChanges: [],\n envHints: [],\n };\n\n // 1. Detect CLIs\n const cliNames: Array<\"claude\" | \"codex\"> = [\"claude\", \"codex\"];\n for (const name of cliNames) {\n const r = detectCli(name, { whichFn: opts.whichFn });\n if (r.found) report.present.push(name);\n else report.missing.push(name);\n }\n\n // 2. Optionally install missing\n if (opts.install && report.missing.length > 0) {\n for (const name of report.missing as Array<\"claude\" | \"codex\">) {\n let shouldInstall = true;\n if (opts.interactive) {\n const ans = (await promptFn(`Install ${name} CLI via npm? [Y/n]: `)).trim().toLowerCase();\n // Default Y (empty input → yes); only explicit \"n\"/\"no\" skips.\n shouldInstall = ans === \"\" || ans === \"y\" || ans === \"yes\";\n }\n if (shouldInstall) {\n await installFn(name);\n report.installed.push(name);\n }\n }\n }\n\n // 3. Patch configs (always — they're idempotent)\n const codexR = patchCodexConfig({ homeDir: opts.homeDir });\n report.configChanges.push(...codexR.patched);\n const claudeR = patchClaudeConfig({ homeDir: opts.homeDir });\n report.configChanges.push(...claudeR.patched);\n\n // 4. Env hints (no dotfile mutation — print only). Provider-aware: only\n // warn about COPILOT_API_KEY when the user's active codex provider is\n // actually copilot-api. Otherwise the warning is noise (users on\n // openai / claude / etc. would see a misleading hint about a variable\n // they don't need). Read the *post-patch* config so a fresh-write that\n // just defaulted to copilot-api is detected correctly.\n if (!detectEnvVar(\"COPILOT_API_KEY\")) {\n const activeProvider = detectActiveCodexProvider(opts.homeDir);\n if (activeProvider === \"copilot-api\") {\n report.envHints.push(buildCopilotApiEnvHint({ platform: process.platform }));\n }\n }\n\n return report;\n}\n"],"mappings":"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","names":[]}
@@ -11,7 +11,7 @@ var DEFAULTS = {
11
11
  hubUrl: DEFAULT_HUB_URL,
12
12
  token: "",
13
13
  name: hostname(),
14
- backendType: "claude-code"
14
+ backendType: "claude"
15
15
  };
16
16
  function normalizeHubUrl(url) {
17
17
  url = url.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
@@ -90,4 +90,4 @@ export {
90
90
  loadConfig,
91
91
  saveConfig
92
92
  };
93
- //# sourceMappingURL=chunk-PJ5M6Q36.js.map
93
+ //# sourceMappingURL=chunk-376QZ7JB.js.map