@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
@@ -0,0 +1,257 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { SmithersError } from "@smithers-orchestrator/errors";
6
+ /** @typedef {import("./AgentAvailability.ts").AgentAvailability} AgentAvailability */
7
+ /** @typedef {import("./AgentAvailabilityStatus.ts").AgentAvailabilityStatus} AgentAvailabilityStatus */
8
+
9
+ const DETECTORS = [
10
+ {
11
+ id: "claude",
12
+ binary: "claude",
13
+ authSignals: (homeDir) => [join(homeDir, ".claude")],
14
+ apiKeys: ["ANTHROPIC_API_KEY"],
15
+ },
16
+ {
17
+ id: "codex",
18
+ binary: "codex",
19
+ authSignals: (homeDir) => [join(homeDir, ".codex")],
20
+ apiKeys: ["OPENAI_API_KEY"],
21
+ },
22
+ {
23
+ id: "gemini",
24
+ binary: "gemini",
25
+ authSignals: (homeDir) => [join(homeDir, ".gemini", "oauth_creds.json")],
26
+ apiKeys: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
27
+ },
28
+ {
29
+ id: "pi",
30
+ binary: "pi",
31
+ authSignals: (homeDir) => [join(homeDir, ".pi", "agent", "auth.json")],
32
+ apiKeys: [],
33
+ },
34
+ {
35
+ id: "kimi",
36
+ binary: "kimi",
37
+ authSignals: (homeDir, env) => {
38
+ const signals = [join(homeDir, ".kimi")];
39
+ if (env.KIMI_SHARE_DIR)
40
+ signals.push(resolve(env.KIMI_SHARE_DIR));
41
+ return signals;
42
+ },
43
+ apiKeys: [],
44
+ },
45
+ {
46
+ id: "amp",
47
+ binary: "amp",
48
+ authSignals: (homeDir) => [join(homeDir, ".amp")],
49
+ apiKeys: [],
50
+ },
51
+ ];
52
+ const ROLE_PREFERENCES = {
53
+ spec: ["claude", "codex"],
54
+ research: ["gemini", "kimi", "codex", "claude"],
55
+ plan: ["gemini", "codex", "claude", "kimi"],
56
+ implement: ["codex", "amp", "gemini", "claude", "kimi"],
57
+ validate: ["codex", "amp", "gemini"],
58
+ review: ["claude", "amp", "codex"],
59
+ };
60
+ const AGENT_VARIANTS = [
61
+ {
62
+ derivedFrom: "claude",
63
+ variantId: "claudeSonnet",
64
+ constructor: {
65
+ importName: "ClaudeCodeAgent",
66
+ expr: 'new ClaudeCodeAgent({ model: "claude-sonnet-4-6" })',
67
+ },
68
+ },
69
+ ];
70
+ const TIER_PREFERENCES = {
71
+ cheapFast: { order: ["kimi", "claudeSonnet", "gemini", "pi"], maxSize: 2 },
72
+ smart: { order: ["codex", "claude", "kimi", "gemini", "amp"], maxSize: 3 },
73
+ smartTool: { order: ["claude", "codex", "kimi", "gemini", "amp"], maxSize: 3 },
74
+ };
75
+ const CONSTRUCTORS = {
76
+ claude: {
77
+ importName: "ClaudeCodeAgent",
78
+ expr: 'new ClaudeCodeAgent({ model: "claude-opus-4-6" })',
79
+ },
80
+ codex: {
81
+ importName: "CodexAgent",
82
+ expr: 'new CodexAgent({ model: "gpt-5.3-codex", skipGitRepoCheck: true })',
83
+ },
84
+ gemini: {
85
+ importName: "GeminiAgent",
86
+ expr: 'new GeminiAgent({ model: "gemini-3.1-pro-preview" })',
87
+ },
88
+ pi: {
89
+ importName: "PiAgent",
90
+ expr: 'new PiAgent({ provider: "openai", model: "gpt-5.3-codex" })',
91
+ },
92
+ kimi: {
93
+ importName: "KimiAgent",
94
+ expr: 'new KimiAgent({ model: "kimi-latest" })',
95
+ },
96
+ amp: {
97
+ importName: "AmpAgent",
98
+ expr: "new AmpAgent()",
99
+ },
100
+ };
101
+ /**
102
+ * @param {string} binary
103
+ * @param {NodeJS.ProcessEnv} env
104
+ */
105
+ function commandExists(binary, env) {
106
+ const result = spawnSync("/bin/bash", ["-c", `command -v ${binary}`], {
107
+ env,
108
+ encoding: "utf8",
109
+ });
110
+ return result.status === 0;
111
+ }
112
+ /**
113
+ * @param {boolean} hasBinary
114
+ * @param {boolean} hasAuthSignal
115
+ * @param {boolean} hasApiKeySignal
116
+ * @returns {AgentAvailabilityStatus}
117
+ */
118
+ function computeStatus(hasBinary, hasAuthSignal, hasApiKeySignal) {
119
+ if (hasBinary && hasAuthSignal)
120
+ return "likely-subscription";
121
+ if (hasBinary && hasApiKeySignal)
122
+ return "api-key";
123
+ if (hasBinary)
124
+ return "binary-only";
125
+ if (hasAuthSignal)
126
+ return "likely-subscription";
127
+ if (hasApiKeySignal)
128
+ return "api-key";
129
+ return "unavailable";
130
+ }
131
+ /**
132
+ * @param {AgentAvailabilityStatus} status
133
+ */
134
+ function scoreStatus(status) {
135
+ switch (status) {
136
+ case "likely-subscription":
137
+ return 4;
138
+ case "api-key":
139
+ return 3;
140
+ case "binary-only":
141
+ return 2;
142
+ default:
143
+ return 0;
144
+ }
145
+ }
146
+ /**
147
+ * @param {NodeJS.ProcessEnv} [env]
148
+ * @returns {AgentAvailability[]}
149
+ */
150
+ export function detectAvailableAgents(env = process.env) {
151
+ const homeDir = env.HOME ?? homedir();
152
+ return DETECTORS.map((detector) => {
153
+ const authSignals = detector.authSignals(homeDir, env);
154
+ const hasBinary = commandExists(detector.binary, env);
155
+ const hasAuthSignal = authSignals.some((signal) => existsSync(signal));
156
+ const hasApiKeySignal = detector.apiKeys.some((name) => Boolean(env[name]));
157
+ const status = computeStatus(hasBinary, hasAuthSignal, hasApiKeySignal);
158
+ return {
159
+ id: detector.id,
160
+ binary: detector.binary,
161
+ hasBinary,
162
+ hasAuthSignal,
163
+ hasApiKeySignal,
164
+ status,
165
+ score: scoreStatus(status),
166
+ usable: scoreStatus(status) > 0,
167
+ checks: [
168
+ `binary:${detector.binary}:${hasBinary ? "yes" : "no"}`,
169
+ ...authSignals.map((signal) => `auth:${signal}:${existsSync(signal) ? "yes" : "no"}`),
170
+ ...detector.apiKeys.map((name) => `env:${name}:${env[name] ? "yes" : "no"}`),
171
+ ],
172
+ };
173
+ });
174
+ }
175
+ /**
176
+ * @param {AgentAvailability[]} available
177
+ */
178
+ function fallbackAgents(available) {
179
+ return [...available].sort((left, right) => {
180
+ if (right.score !== left.score)
181
+ return right.score - left.score;
182
+ return DETECTORS.findIndex((detector) => detector.id === left.id) -
183
+ DETECTORS.findIndex((detector) => detector.id === right.id);
184
+ });
185
+ }
186
+ /**
187
+ * @param {string} role
188
+ * @param {AgentAvailability[]} available
189
+ */
190
+ function resolveRoleAgents(role, available) {
191
+ const preferred = ROLE_PREFERENCES[role] ?? [];
192
+ const filtered = preferred
193
+ .map((id) => available.find((entry) => entry.id === id))
194
+ .filter((entry) => Boolean(entry));
195
+ if (filtered.length > 0)
196
+ return filtered;
197
+ return fallbackAgents(available);
198
+ }
199
+ /**
200
+ * @param {NodeJS.ProcessEnv} [env]
201
+ */
202
+ export function generateAgentsTs(env = process.env) {
203
+ const detections = detectAvailableAgents(env);
204
+ const available = detections.filter((entry) => entry.usable);
205
+ if (available.length === 0) {
206
+ throw new SmithersError("NO_USABLE_AGENTS", `No usable agents detected. Checked: ${detections.flatMap((entry) => entry.checks).join(", ")}`);
207
+ }
208
+ // Base providers in detection order
209
+ const orderedProviders = DETECTORS
210
+ .map((detector) => available.find((entry) => entry.id === detector.id))
211
+ .filter((entry) => Boolean(entry));
212
+ // Derive variants (e.g. claudeSonnet from claude)
213
+ const availableIds = new Set(orderedProviders.map((p) => p.id));
214
+ const activeVariants = AGENT_VARIANTS.filter((v) => availableIds.has(v.derivedFrom));
215
+ // Collect all import names (dedup)
216
+ const importNames = new Set();
217
+ for (const provider of orderedProviders)
218
+ importNames.add(CONSTRUCTORS[provider.id].importName);
219
+ for (const variant of activeVariants)
220
+ importNames.add(variant.constructor.importName);
221
+ // Provider lines: base + variants
222
+ const providerLines = [
223
+ ...orderedProviders.map((provider) => ` ${provider.id}: ${CONSTRUCTORS[provider.id].expr},`),
224
+ ...activeVariants.map((variant) => ` ${variant.variantId}: ${variant.constructor.expr},`),
225
+ ];
226
+ // All known provider/variant IDs for tier resolution
227
+ const allProviderIds = new Set([
228
+ ...orderedProviders.map((p) => p.id),
229
+ ...activeVariants.map((v) => v.variantId),
230
+ ]);
231
+ // Fallback: all base provider IDs sorted by score (for tiers with no preferred match)
232
+ const fallbackIds = orderedProviders.map((p) => p.id);
233
+ // Tier lines
234
+ const tierLines = Object.entries(TIER_PREFERENCES).map(([tier, { order, maxSize }]) => {
235
+ let resolved = order
236
+ .filter((id) => allProviderIds.has(id))
237
+ .slice(0, maxSize);
238
+ // Fallback to any available base providers if no preferred agents matched
239
+ if (resolved.length === 0) {
240
+ resolved = fallbackIds.slice(0, maxSize);
241
+ }
242
+ return ` ${tier}: [${resolved.map((id) => `providers.${id}`).join(", ")}],`;
243
+ });
244
+ return [
245
+ "// smithers-source: generated",
246
+ `import { ${[...importNames].join(", ")}, type AgentLike } from "smithers-orchestrator";`,
247
+ "",
248
+ "export const providers = {",
249
+ ...providerLines,
250
+ "} as const;",
251
+ "",
252
+ "export const agents = {",
253
+ ...tierLines,
254
+ "} as const satisfies Record<string, AgentLike[]>;",
255
+ "",
256
+ ].join("\n");
257
+ }