@smithers-orchestrator/cli 0.16.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +55 -0
  3. package/src/AgentAvailability.ts +13 -0
  4. package/src/AgentAvailabilityStatus.ts +5 -0
  5. package/src/AggregateNodeDetailParams.ts +5 -0
  6. package/src/AskOptions.ts +12 -0
  7. package/src/ChatAttemptMeta.ts +7 -0
  8. package/src/ChatAttemptRow.ts +12 -0
  9. package/src/ChatOutputEvent.ts +6 -0
  10. package/src/DiffBundleLike.ts +6 -0
  11. package/src/DiscoveredWorkflow.ts +9 -0
  12. package/src/EnrichedNodeDetail.ts +60 -0
  13. package/src/EventCategory.ts +18 -0
  14. package/src/FindDbWaitOptions.ts +4 -0
  15. package/src/FormatEventLineOptions.ts +4 -0
  16. package/src/HijackCandidate.ts +11 -0
  17. package/src/HijackLaunchSpec.ts +6 -0
  18. package/src/InitWorkflowPackOptions.ts +4 -0
  19. package/src/InitWorkflowPackResult.ts +6 -0
  20. package/src/NativeHijackEngine.ts +8 -0
  21. package/src/NodeDetailAttempt.ts +22 -0
  22. package/src/NodeDetailTokenUsage.ts +11 -0
  23. package/src/NodeDetailToolCall.ts +12 -0
  24. package/src/ParsedNodeOutputEvent.ts +9 -0
  25. package/src/RenderNodeDetailOptions.ts +4 -0
  26. package/src/RunAutoResumeSkipReason.ts +4 -0
  27. package/src/RunDiffCommandInput.ts +13 -0
  28. package/src/RunDiffCommandResult.ts +3 -0
  29. package/src/RunOutputCommandInput.ts +12 -0
  30. package/src/RunOutputCommandResult.ts +3 -0
  31. package/src/RunRewindCommandInput.ts +14 -0
  32. package/src/RunRewindCommandResult.ts +3 -0
  33. package/src/RunTreeCommandInput.ts +14 -0
  34. package/src/RunTreeCommandResult.ts +3 -0
  35. package/src/SmithersEventType.ts +3 -0
  36. package/src/SupervisorOptions.ts +33 -0
  37. package/src/SupervisorPollSummary.ts +6 -0
  38. package/src/TreeRenderOptions.ts +5 -0
  39. package/src/WatchLoopOptions.ts +9 -0
  40. package/src/WatchLoopResult.ts +8 -0
  41. package/src/WatchRenderContext.ts +4 -0
  42. package/src/WhyBlocker.ts +17 -0
  43. package/src/WhyBlockerKind.ts +9 -0
  44. package/src/WhyDiagnosis.ts +10 -0
  45. package/src/WorkflowCta.ts +4 -0
  46. package/src/WorkflowSourceType.ts +1 -0
  47. package/src/agent-detection.js +257 -0
  48. package/src/ask.js +491 -0
  49. package/src/chat.js +226 -0
  50. package/src/diff.js +221 -0
  51. package/src/event-categories.js +141 -0
  52. package/src/find-db.js +93 -0
  53. package/src/format.js +272 -0
  54. package/src/hijack-session.js +207 -0
  55. package/src/hijack.js +226 -0
  56. package/src/index.d.ts +1 -0
  57. package/src/index.js +4868 -0
  58. package/src/mcp/SemanticMcpServerOptions.ts +4 -0
  59. package/src/mcp/SemanticToolCallResult.ts +14 -0
  60. package/src/mcp/SemanticToolContext.ts +6 -0
  61. package/src/mcp/SemanticToolDefinition.ts +13 -0
  62. package/src/mcp/SemanticToolError.ts +6 -0
  63. package/src/mcp/semantic-server.js +41 -0
  64. package/src/mcp/semantic-tools.js +1242 -0
  65. package/src/node-detail.js +682 -0
  66. package/src/output.js +111 -0
  67. package/src/resume-detached.js +37 -0
  68. package/src/rewind.js +88 -0
  69. package/src/scheduler.js +112 -0
  70. package/src/smithersRuntime.js +63 -0
  71. package/src/supervisor.js +418 -0
  72. package/src/tree.js +307 -0
  73. package/src/tui/app.jsx +139 -0
  74. package/src/tui/app.tsx +5 -0
  75. package/src/tui/components/AskModal.jsx +109 -0
  76. package/src/tui/components/AskModal.tsx +3 -0
  77. package/src/tui/components/AttentionPane.jsx +112 -0
  78. package/src/tui/components/AttentionPane.tsx +6 -0
  79. package/src/tui/components/ChatPane.jsx +57 -0
  80. package/src/tui/components/ChatPane.tsx +7 -0
  81. package/src/tui/components/CronList.jsx +87 -0
  82. package/src/tui/components/CronList.tsx +5 -0
  83. package/src/tui/components/DetailsPane.jsx +96 -0
  84. package/src/tui/components/DetailsPane.tsx +7 -0
  85. package/src/tui/components/FramesPane.jsx +147 -0
  86. package/src/tui/components/FramesPane.tsx +8 -0
  87. package/src/tui/components/LogsPane.jsx +46 -0
  88. package/src/tui/components/LogsPane.tsx +6 -0
  89. package/src/tui/components/MetricsPane.jsx +108 -0
  90. package/src/tui/components/MetricsPane.tsx +5 -0
  91. package/src/tui/components/NodeDetailView.jsx +284 -0
  92. package/src/tui/components/NodeDetailView.tsx +7 -0
  93. package/src/tui/components/NodeInspector.jsx +51 -0
  94. package/src/tui/components/NodeInspector.tsx +7 -0
  95. package/src/tui/components/RunDetailView.jsx +190 -0
  96. package/src/tui/components/RunDetailView.tsx +7 -0
  97. package/src/tui/components/RunsList.jsx +184 -0
  98. package/src/tui/components/RunsList.tsx +7 -0
  99. package/src/tui/components/SqliteBrowser.jsx +131 -0
  100. package/src/tui/components/SqliteBrowser.tsx +5 -0
  101. package/src/tui/components/WorkflowLauncher.jsx +63 -0
  102. package/src/tui/components/WorkflowLauncher.tsx +3 -0
  103. package/src/util/CliErrorMapping.ts +7 -0
  104. package/src/util/CliExitCode.ts +10 -0
  105. package/src/util/errorMessage.js +212 -0
  106. package/src/util/exitCodes.js +18 -0
  107. package/src/watch.js +128 -0
  108. package/src/why-diagnosis.js +1000 -0
  109. package/src/workflow-pack.js +2151 -0
  110. package/src/workflows.js +122 -0
package/src/ask.js ADDED
@@ -0,0 +1,491 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
+ import { ClaudeCodeAgent } from "@smithers-orchestrator/agents/ClaudeCodeAgent";
8
+ import { CodexAgent } from "@smithers-orchestrator/agents/CodexAgent";
9
+ import { GeminiAgent } from "@smithers-orchestrator/agents/GeminiAgent";
10
+ import { KimiAgent } from "@smithers-orchestrator/agents/KimiAgent";
11
+ import { PiAgent } from "@smithers-orchestrator/agents/PiAgent";
12
+ import { SmithersError } from "@smithers-orchestrator/errors";
13
+ import { createSmithersAgentContract, renderSmithersAgentPromptGuidance, } from "@smithers-orchestrator/agents/agent-contract";
14
+ import { detectAvailableAgents, } from "./agent-detection.js";
15
+ /**
16
+ * @typedef {typeof ASK_AGENT_IDS[number]} AskAgentId
17
+ */
18
+ /**
19
+ * @typedef {{ agent?: AskAgentId; listAgents?: boolean; dumpPrompt?: boolean; toolSurface?: SmithersToolSurface; noMcp?: boolean; printBootstrap?: boolean; }} AskOptions
20
+ */
21
+ /** @typedef {import("@smithers-orchestrator/agents/agent-contract").SmithersToolSurface} SmithersToolSurface */
22
+
23
+ const ASK_AGENT_IDS = ["claude", "codex", "kimi", "gemini", "pi"];
24
+ const DEFAULT_SERVER_NAME = "smithers";
25
+ /**
26
+ * @param {AgentAvailability["id"]} value
27
+ * @returns {value is AskAgentId}
28
+ */
29
+ function isAskAgentId(value) {
30
+ return ASK_AGENT_IDS.includes(value);
31
+ }
32
+ /**
33
+ * @param {AgentAvailability} availability
34
+ * @returns {availability is AskSupportedAvailability}
35
+ */
36
+ function isSupportedAvailability(availability) {
37
+ return isAskAgentId(availability.id);
38
+ }
39
+ /**
40
+ * @param {AskAgentId} agentId
41
+ * @returns {AskBootstrapMode}
42
+ */
43
+ function resolveBootstrapMode(agentId, noMcp = false) {
44
+ if (noMcp) {
45
+ return "prompt-only";
46
+ }
47
+ switch (agentId) {
48
+ case "claude":
49
+ case "kimi":
50
+ return "mcp-config-file";
51
+ case "codex":
52
+ return "mcp-config-inline";
53
+ case "gemini":
54
+ return "mcp-allow-list";
55
+ case "pi":
56
+ return "prompt-only";
57
+ }
58
+ }
59
+ /**
60
+ * @param {AskBootstrapMode} mode
61
+ */
62
+ function bootstrapRank(mode) {
63
+ switch (mode) {
64
+ case "mcp-config-file":
65
+ case "mcp-config-inline":
66
+ return 3;
67
+ case "mcp-allow-list":
68
+ return 2;
69
+ case "prompt-only":
70
+ return 1;
71
+ }
72
+ }
73
+ /**
74
+ * @param {SmithersToolSurface} [toolSurface]
75
+ * @returns {{ command: string; args: string[] }}
76
+ */
77
+ function buildSmithersMcpLaunchSpec(toolSurface = "semantic") {
78
+ return {
79
+ command: process.execPath,
80
+ args: [
81
+ "run",
82
+ resolve(dirname(fileURLToPath(import.meta.url)), "index.js"),
83
+ "--mcp",
84
+ "--surface",
85
+ toolSurface,
86
+ ],
87
+ };
88
+ }
89
+ /**
90
+ * @param {SmithersToolSurface} toolSurface
91
+ */
92
+ function buildJsonMcpConfig(toolSurface, serverName = DEFAULT_SERVER_NAME) {
93
+ const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
94
+ return {
95
+ mcpServers: {
96
+ [serverName]: {
97
+ command: launchSpec.command,
98
+ args: launchSpec.args,
99
+ },
100
+ },
101
+ };
102
+ }
103
+ /**
104
+ * @param {SmithersToolSurface} [toolSurface]
105
+ */
106
+ function buildSmithersMcpConfigFile(toolSurface = "semantic", serverName = DEFAULT_SERVER_NAME) {
107
+ const dir = mkdtempSync(join(tmpdir(), "smithers-ask-"));
108
+ const configPath = join(dir, "mcp.json");
109
+ const contents = buildJsonMcpConfig(toolSurface, serverName);
110
+ writeFileSync(configPath, JSON.stringify(contents, null, 2));
111
+ return {
112
+ dir,
113
+ path: configPath,
114
+ contents,
115
+ cleanup() {
116
+ rmSync(dir, { recursive: true, force: true });
117
+ },
118
+ };
119
+ }
120
+ /**
121
+ * @param {SmithersToolSurface} toolSurface
122
+ */
123
+ function buildCodexConfigOverrides(toolSurface, serverName = DEFAULT_SERVER_NAME) {
124
+ const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
125
+ return [
126
+ `mcp_servers.${serverName}.command=${JSON.stringify(launchSpec.command)}`,
127
+ `mcp_servers.${serverName}.args=${JSON.stringify(launchSpec.args)}`,
128
+ ];
129
+ }
130
+ /**
131
+ * @param {AskSelection} selection
132
+ * @param {SmithersToolSurface} toolSurface
133
+ * @returns {AskBootstrap}
134
+ */
135
+ function buildBootstrap(selection, toolSurface) {
136
+ switch (selection.bootstrapMode) {
137
+ case "mcp-config-file":
138
+ return {
139
+ mode: "mcp-config-file",
140
+ serverName: DEFAULT_SERVER_NAME,
141
+ toolSurface,
142
+ config: buildJsonMcpConfig(toolSurface),
143
+ };
144
+ case "mcp-config-inline":
145
+ return {
146
+ mode: "mcp-config-inline",
147
+ serverName: DEFAULT_SERVER_NAME,
148
+ toolSurface,
149
+ configOverrides: buildCodexConfigOverrides(toolSurface),
150
+ };
151
+ case "mcp-allow-list":
152
+ return {
153
+ mode: "mcp-allow-list",
154
+ serverName: DEFAULT_SERVER_NAME,
155
+ toolSurface,
156
+ allowedMcpServerNames: [DEFAULT_SERVER_NAME],
157
+ note: "Gemini can only allow-list preconfigured MCP servers. Configure the local Smithers server under the same name before relying on MCP.",
158
+ };
159
+ case "prompt-only":
160
+ return {
161
+ mode: "prompt-only",
162
+ serverName: DEFAULT_SERVER_NAME,
163
+ toolSurface,
164
+ note: selection.availability.id === "pi"
165
+ ? "PI falls back to prompt-only bootstrap for smithers ask."
166
+ : "MCP bootstrap is disabled for this run.",
167
+ };
168
+ }
169
+ }
170
+ /**
171
+ * @param {AskSupportedAvailability} left
172
+ * @param {AskSupportedAvailability} right
173
+ */
174
+ function compareAgents(left, right, noMcp = false) {
175
+ const leftBootstrap = resolveBootstrapMode(left.id, noMcp);
176
+ const rightBootstrap = resolveBootstrapMode(right.id, noMcp);
177
+ const bootstrapDelta = bootstrapRank(rightBootstrap) - bootstrapRank(leftBootstrap);
178
+ if (bootstrapDelta !== 0) {
179
+ return bootstrapDelta;
180
+ }
181
+ if (right.score !== left.score) {
182
+ return right.score - left.score;
183
+ }
184
+ return ASK_AGENT_IDS.indexOf(left.id) - ASK_AGENT_IDS.indexOf(right.id);
185
+ }
186
+ /**
187
+ * @param {AgentAvailability} agent
188
+ */
189
+ function formatAgentChecks(agent) {
190
+ return agent.checks.join(", ");
191
+ }
192
+ /**
193
+ * @param {AgentAvailability[]} agents
194
+ */
195
+ function noUsableAgentError(agents) {
196
+ return new SmithersError("NO_USABLE_AGENTS", `No usable agents detected. Checked: ${agents
197
+ .map((agent) => `${agent.id} => ${formatAgentChecks(agent)}`)
198
+ .join(" | ")}`);
199
+ }
200
+ /**
201
+ * @param {AgentAvailability[]} agents
202
+ * @param {AskOptions} options
203
+ * @returns {AskSelection}
204
+ */
205
+ function selectAgent(agents, options) {
206
+ const supported = agents.filter(isSupportedAvailability);
207
+ if (options.agent) {
208
+ const explicit = supported.find((agent) => agent.id === options.agent);
209
+ if (!explicit) {
210
+ throw new SmithersError("CLI_AGENT_UNSUPPORTED", `Agent "${options.agent}" is not supported for \`smithers ask\`.`, { agentId: options.agent });
211
+ }
212
+ if (!explicit.usable) {
213
+ throw new SmithersError("NO_USABLE_AGENTS", `Agent "${explicit.id}" is not usable. Checked: ${formatAgentChecks(explicit)}`, { agentId: explicit.id });
214
+ }
215
+ return {
216
+ availability: explicit,
217
+ bootstrapMode: resolveBootstrapMode(explicit.id, options.noMcp),
218
+ selectionReason: "requested via --agent",
219
+ };
220
+ }
221
+ const usable = supported.filter((agent) => agent.usable);
222
+ if (usable.length === 0) {
223
+ throw noUsableAgentError(agents);
224
+ }
225
+ const best = [...usable].sort((left, right) => compareAgents(left, right, options.noMcp))[0];
226
+ if (!best) {
227
+ throw noUsableAgentError(agents);
228
+ }
229
+ const bootstrapMode = resolveBootstrapMode(best.id, options.noMcp);
230
+ return {
231
+ availability: best,
232
+ bootstrapMode,
233
+ selectionReason: `best available ${bootstrapMode} bootstrap`,
234
+ };
235
+ }
236
+ /**
237
+ * @param {SmithersAgentContract} contract
238
+ * @param {AskBootstrap} bootstrap
239
+ */
240
+ function buildSystemPrompt(contract, bootstrap) {
241
+ const lines = [
242
+ "You are an autonomous AI agent operating inside the Smithers repository and control plane.",
243
+ bootstrap.mode === "prompt-only"
244
+ ? "MCP is disabled or unavailable for this run. Use the local Smithers repo and CLI directly when shell access is needed."
245
+ : "Prefer the live Smithers MCP tools over shell commands whenever they can answer the request.",
246
+ bootstrap.mode === "prompt-only"
247
+ ? renderSmithersAgentPromptGuidance(contract, { available: false })
248
+ : contract.promptGuidance,
249
+ "If you need repository documentation, read local files in this checkout, starting with docs/llms-full.txt.",
250
+ "Use `smithers` or `bun run src/index.js --help` to inspect the current CLI surface when you need shell fallbacks.",
251
+ "Be concise and act directly.",
252
+ ];
253
+ return lines.join("\n\n");
254
+ }
255
+ /**
256
+ * @param {AskSelection} selection
257
+ * @param {AskBootstrap} bootstrap
258
+ */
259
+ function formatBootstrap(selection, bootstrap) {
260
+ const lines = [
261
+ `agent: ${selection.availability.id}`,
262
+ `selectionReason: ${selection.selectionReason}`,
263
+ `bootstrapMode: ${bootstrap.mode}`,
264
+ `toolSurface: ${bootstrap.toolSurface}`,
265
+ `serverName: ${bootstrap.serverName}`,
266
+ ];
267
+ switch (bootstrap.mode) {
268
+ case "mcp-config-file":
269
+ lines.push("config:");
270
+ lines.push(JSON.stringify(bootstrap.config, null, 2));
271
+ break;
272
+ case "mcp-config-inline":
273
+ lines.push("configOverrides:");
274
+ lines.push(...bootstrap.configOverrides.map((entry) => `- ${entry}`));
275
+ break;
276
+ case "mcp-allow-list":
277
+ lines.push(`allowedMcpServerNames: ${bootstrap.allowedMcpServerNames.join(", ")}`);
278
+ lines.push(`note: ${bootstrap.note}`);
279
+ break;
280
+ case "prompt-only":
281
+ lines.push(`note: ${bootstrap.note}`);
282
+ break;
283
+ }
284
+ return lines.join("\n");
285
+ }
286
+ /**
287
+ * @param {AgentAvailability[]} agents
288
+ * @param {AskOptions} options
289
+ * @param {AskAgentId} [selectedAgentId]
290
+ */
291
+ function formatAgentList(agents, options, selectedAgentId) {
292
+ const supported = agents.filter(isSupportedAvailability);
293
+ return supported
294
+ .sort((left, right) => compareAgents(left, right, options.noMcp))
295
+ .map((agent) => {
296
+ const marker = agent.id === selectedAgentId ? "*" : " ";
297
+ return `${marker} ${agent.id} usable=${agent.usable ? "yes" : "no"} status=${agent.status} bootstrap=${resolveBootstrapMode(agent.id, options.noMcp)}`;
298
+ })
299
+ .join("\n");
300
+ }
301
+ /**
302
+ * @param {AskSelection} selection
303
+ * @param {AskBootstrap} bootstrap
304
+ * @param {string} systemPrompt
305
+ * @param {string} cwd
306
+ * @returns {{ agent: BaseCliAgent; cleanup: () => void }}
307
+ */
308
+ function buildAgent(selection, bootstrap, systemPrompt, cwd) {
309
+ switch (selection.availability.id) {
310
+ case "claude": {
311
+ if (bootstrap.mode !== "mcp-config-file") {
312
+ return {
313
+ agent: new ClaudeCodeAgent({
314
+ cwd,
315
+ model: "claude-sonnet-4-20250514",
316
+ systemPrompt,
317
+ dangerouslySkipPermissions: true,
318
+ }),
319
+ cleanup() { },
320
+ };
321
+ }
322
+ const mcpConfig = buildSmithersMcpConfigFile(bootstrap.toolSurface, bootstrap.serverName);
323
+ return {
324
+ agent: new ClaudeCodeAgent({
325
+ cwd,
326
+ model: "claude-sonnet-4-20250514",
327
+ mcpConfig: [mcpConfig.path],
328
+ strictMcpConfig: true,
329
+ systemPrompt,
330
+ dangerouslySkipPermissions: true,
331
+ }),
332
+ cleanup() {
333
+ mcpConfig.cleanup();
334
+ },
335
+ };
336
+ }
337
+ case "kimi": {
338
+ if (bootstrap.mode !== "mcp-config-file") {
339
+ return {
340
+ agent: new KimiAgent({
341
+ cwd,
342
+ model: "kimi-latest",
343
+ systemPrompt,
344
+ }),
345
+ cleanup() { },
346
+ };
347
+ }
348
+ const mcpConfig = buildSmithersMcpConfigFile(bootstrap.toolSurface, bootstrap.serverName);
349
+ return {
350
+ agent: new KimiAgent({
351
+ cwd,
352
+ model: "kimi-latest",
353
+ mcpConfigFile: [mcpConfig.path],
354
+ systemPrompt,
355
+ }),
356
+ cleanup() {
357
+ mcpConfig.cleanup();
358
+ },
359
+ };
360
+ }
361
+ case "gemini":
362
+ return {
363
+ agent: new GeminiAgent({
364
+ cwd,
365
+ model: "gemini-3.1-pro-preview",
366
+ allowedMcpServerNames: bootstrap.mode === "mcp-allow-list"
367
+ ? bootstrap.allowedMcpServerNames
368
+ : undefined,
369
+ systemPrompt,
370
+ approvalMode: "yolo",
371
+ }),
372
+ cleanup() { },
373
+ };
374
+ case "codex":
375
+ return {
376
+ agent: new CodexAgent({
377
+ cwd,
378
+ model: "gpt-5.3-codex",
379
+ config: bootstrap.mode === "mcp-config-inline"
380
+ ? bootstrap.configOverrides
381
+ : undefined,
382
+ systemPrompt,
383
+ fullAuto: true,
384
+ skipGitRepoCheck: true,
385
+ }),
386
+ cleanup() { },
387
+ };
388
+ case "pi":
389
+ return {
390
+ agent: new PiAgent({
391
+ cwd,
392
+ provider: "openai",
393
+ model: "gpt-5.3-codex",
394
+ systemPrompt,
395
+ }),
396
+ cleanup() { },
397
+ };
398
+ }
399
+ }
400
+ /**
401
+ * @param {string | undefined} question
402
+ * @param {string} cwd
403
+ * @param {AskOptions} [options]
404
+ * @returns {Promise<void>}
405
+ */
406
+ export async function ask(question, cwd, options = {}) {
407
+ const agents = detectAvailableAgents();
408
+ if (options.listAgents) {
409
+ let selectedAgentId;
410
+ try {
411
+ selectedAgentId = selectAgent(agents, options).availability.id;
412
+ }
413
+ catch { }
414
+ process.stdout.write(`${formatAgentList(agents, options, selectedAgentId)}\n`);
415
+ return;
416
+ }
417
+ const selection = selectAgent(agents, options);
418
+ const toolSurface = options.toolSurface ?? "semantic";
419
+ const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
420
+ const transport = new StdioClientTransport({
421
+ command: launchSpec.command,
422
+ args: launchSpec.args,
423
+ cwd,
424
+ stderr: "pipe",
425
+ });
426
+ const client = new Client({
427
+ name: "smithers-ask-contract-probe",
428
+ version: "1.0.0",
429
+ });
430
+ let tools;
431
+ try {
432
+ await client.connect(transport);
433
+ const listed = await client.listTools();
434
+ tools = listed.tools.map((tool) => ({
435
+ name: tool.name,
436
+ description: tool.description,
437
+ }));
438
+ }
439
+ catch (error) {
440
+ throw new SmithersError("ASK_BOOTSTRAP_FAILED", `Failed to probe the live Smithers MCP tools: ${error?.message ?? String(error)}`, {
441
+ cwd,
442
+ toolSurface,
443
+ command: launchSpec.command,
444
+ args: launchSpec.args,
445
+ });
446
+ }
447
+ finally {
448
+ try {
449
+ await client.close();
450
+ }
451
+ catch { }
452
+ try {
453
+ await transport.close();
454
+ }
455
+ catch { }
456
+ }
457
+ const contract = createSmithersAgentContract({
458
+ toolSurface,
459
+ serverName: DEFAULT_SERVER_NAME,
460
+ tools,
461
+ });
462
+ const bootstrap = buildBootstrap(selection, toolSurface);
463
+ const systemPrompt = buildSystemPrompt(contract, bootstrap);
464
+ if (options.dumpPrompt || options.printBootstrap) {
465
+ const sections = [];
466
+ if (options.printBootstrap) {
467
+ sections.push("[bootstrap]");
468
+ sections.push(formatBootstrap(selection, bootstrap));
469
+ }
470
+ if (options.dumpPrompt) {
471
+ sections.push("[system-prompt]");
472
+ sections.push(systemPrompt);
473
+ }
474
+ process.stdout.write(`${sections.join("\n\n")}\n`);
475
+ return;
476
+ }
477
+ if (!question?.trim()) {
478
+ throw new SmithersError("INVALID_ARGUMENT", "A question is required unless you use --list-agents, --dump-prompt, or --print-bootstrap.");
479
+ }
480
+ const { agent, cleanup } = buildAgent(selection, bootstrap, systemPrompt, cwd);
481
+ try {
482
+ await agent.generate({
483
+ prompt: question,
484
+ onStdout: (chunk) => process.stdout.write(chunk),
485
+ });
486
+ process.stdout.write("\n");
487
+ }
488
+ finally {
489
+ cleanup();
490
+ }
491
+ }