@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.
- package/LICENSE +21 -0
- package/package.json +55 -0
- package/src/AgentAvailability.ts +13 -0
- package/src/AgentAvailabilityStatus.ts +5 -0
- package/src/AggregateNodeDetailParams.ts +5 -0
- package/src/AskOptions.ts +12 -0
- package/src/ChatAttemptMeta.ts +7 -0
- package/src/ChatAttemptRow.ts +12 -0
- package/src/ChatOutputEvent.ts +6 -0
- package/src/DiffBundleLike.ts +6 -0
- package/src/DiscoveredWorkflow.ts +9 -0
- package/src/EnrichedNodeDetail.ts +60 -0
- package/src/EventCategory.ts +18 -0
- package/src/FindDbWaitOptions.ts +4 -0
- package/src/FormatEventLineOptions.ts +4 -0
- package/src/HijackCandidate.ts +11 -0
- package/src/HijackLaunchSpec.ts +6 -0
- package/src/InitWorkflowPackOptions.ts +4 -0
- package/src/InitWorkflowPackResult.ts +6 -0
- package/src/NativeHijackEngine.ts +8 -0
- package/src/NodeDetailAttempt.ts +22 -0
- package/src/NodeDetailTokenUsage.ts +11 -0
- package/src/NodeDetailToolCall.ts +12 -0
- package/src/ParsedNodeOutputEvent.ts +9 -0
- package/src/RenderNodeDetailOptions.ts +4 -0
- package/src/RunAutoResumeSkipReason.ts +4 -0
- package/src/RunDiffCommandInput.ts +13 -0
- package/src/RunDiffCommandResult.ts +3 -0
- package/src/RunOutputCommandInput.ts +12 -0
- package/src/RunOutputCommandResult.ts +3 -0
- package/src/RunRewindCommandInput.ts +14 -0
- package/src/RunRewindCommandResult.ts +3 -0
- package/src/RunTreeCommandInput.ts +14 -0
- package/src/RunTreeCommandResult.ts +3 -0
- package/src/SmithersEventType.ts +3 -0
- package/src/SupervisorOptions.ts +33 -0
- package/src/SupervisorPollSummary.ts +6 -0
- package/src/TreeRenderOptions.ts +5 -0
- package/src/WatchLoopOptions.ts +9 -0
- package/src/WatchLoopResult.ts +8 -0
- package/src/WatchRenderContext.ts +4 -0
- package/src/WhyBlocker.ts +17 -0
- package/src/WhyBlockerKind.ts +9 -0
- package/src/WhyDiagnosis.ts +10 -0
- package/src/WorkflowCta.ts +4 -0
- package/src/WorkflowSourceType.ts +1 -0
- package/src/agent-detection.js +257 -0
- package/src/ask.js +491 -0
- package/src/chat.js +226 -0
- package/src/diff.js +221 -0
- package/src/event-categories.js +141 -0
- package/src/find-db.js +93 -0
- package/src/format.js +272 -0
- package/src/hijack-session.js +207 -0
- package/src/hijack.js +226 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4868 -0
- package/src/mcp/SemanticMcpServerOptions.ts +4 -0
- package/src/mcp/SemanticToolCallResult.ts +14 -0
- package/src/mcp/SemanticToolContext.ts +6 -0
- package/src/mcp/SemanticToolDefinition.ts +13 -0
- package/src/mcp/SemanticToolError.ts +6 -0
- package/src/mcp/semantic-server.js +41 -0
- package/src/mcp/semantic-tools.js +1242 -0
- package/src/node-detail.js +682 -0
- package/src/output.js +111 -0
- package/src/resume-detached.js +37 -0
- package/src/rewind.js +88 -0
- package/src/scheduler.js +112 -0
- package/src/smithersRuntime.js +63 -0
- package/src/supervisor.js +418 -0
- package/src/tree.js +307 -0
- package/src/tui/app.jsx +139 -0
- package/src/tui/app.tsx +5 -0
- package/src/tui/components/AskModal.jsx +109 -0
- package/src/tui/components/AskModal.tsx +3 -0
- package/src/tui/components/AttentionPane.jsx +112 -0
- package/src/tui/components/AttentionPane.tsx +6 -0
- package/src/tui/components/ChatPane.jsx +57 -0
- package/src/tui/components/ChatPane.tsx +7 -0
- package/src/tui/components/CronList.jsx +87 -0
- package/src/tui/components/CronList.tsx +5 -0
- package/src/tui/components/DetailsPane.jsx +96 -0
- package/src/tui/components/DetailsPane.tsx +7 -0
- package/src/tui/components/FramesPane.jsx +147 -0
- package/src/tui/components/FramesPane.tsx +8 -0
- package/src/tui/components/LogsPane.jsx +46 -0
- package/src/tui/components/LogsPane.tsx +6 -0
- package/src/tui/components/MetricsPane.jsx +108 -0
- package/src/tui/components/MetricsPane.tsx +5 -0
- package/src/tui/components/NodeDetailView.jsx +284 -0
- package/src/tui/components/NodeDetailView.tsx +7 -0
- package/src/tui/components/NodeInspector.jsx +51 -0
- package/src/tui/components/NodeInspector.tsx +7 -0
- package/src/tui/components/RunDetailView.jsx +190 -0
- package/src/tui/components/RunDetailView.tsx +7 -0
- package/src/tui/components/RunsList.jsx +184 -0
- package/src/tui/components/RunsList.tsx +7 -0
- package/src/tui/components/SqliteBrowser.jsx +131 -0
- package/src/tui/components/SqliteBrowser.tsx +5 -0
- package/src/tui/components/WorkflowLauncher.jsx +63 -0
- package/src/tui/components/WorkflowLauncher.tsx +3 -0
- package/src/util/CliErrorMapping.ts +7 -0
- package/src/util/CliExitCode.ts +10 -0
- package/src/util/errorMessage.js +212 -0
- package/src/util/exitCodes.js +18 -0
- package/src/watch.js +128 -0
- package/src/why-diagnosis.js +1000 -0
- package/src/workflow-pack.js +2151 -0
- 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
|
+
}
|