@smithers-orchestrator/cli 0.20.0 → 0.20.3
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/dist/agent-detection.d.ts +20 -3
- package/package.json +17 -19
- package/src/AgentAvailability.ts +3 -0
- package/src/agent-detection.js +226 -14
- package/src/ask.js +4 -6
- package/src/index.js +89 -45
- package/src/workflow-pack.js +48 -10
- package/src/tui/app.jsx +0 -139
- package/src/tui/app.tsx +0 -5
- package/src/tui/components/AskModal.jsx +0 -109
- package/src/tui/components/AskModal.tsx +0 -3
- package/src/tui/components/AttentionPane.jsx +0 -112
- package/src/tui/components/AttentionPane.tsx +0 -6
- package/src/tui/components/ChatPane.jsx +0 -57
- package/src/tui/components/ChatPane.tsx +0 -7
- package/src/tui/components/CronList.jsx +0 -87
- package/src/tui/components/CronList.tsx +0 -5
- package/src/tui/components/DetailsPane.jsx +0 -96
- package/src/tui/components/DetailsPane.tsx +0 -7
- package/src/tui/components/FramesPane.jsx +0 -147
- package/src/tui/components/FramesPane.tsx +0 -8
- package/src/tui/components/LogsPane.jsx +0 -46
- package/src/tui/components/LogsPane.tsx +0 -6
- package/src/tui/components/MetricsPane.jsx +0 -108
- package/src/tui/components/MetricsPane.tsx +0 -5
- package/src/tui/components/NodeDetailView.jsx +0 -284
- package/src/tui/components/NodeDetailView.tsx +0 -7
- package/src/tui/components/NodeInspector.jsx +0 -51
- package/src/tui/components/NodeInspector.tsx +0 -7
- package/src/tui/components/RunDetailView.jsx +0 -190
- package/src/tui/components/RunDetailView.tsx +0 -7
- package/src/tui/components/RunsList.jsx +0 -184
- package/src/tui/components/RunsList.tsx +0 -7
- package/src/tui/components/SqliteBrowser.jsx +0 -131
- package/src/tui/components/SqliteBrowser.tsx +0 -5
- package/src/tui/components/WorkflowLauncher.jsx +0 -63
- package/src/tui/components/WorkflowLauncher.tsx +0 -3
|
@@ -2,26 +2,43 @@ type AgentAvailabilityStatus$1 = "likely-subscription" | "api-key" | "binary-onl
|
|
|
2
2
|
|
|
3
3
|
type AgentAvailability$1 = {
|
|
4
4
|
id: "claude" | "codex" | "gemini" | "pi" | "kimi" | "amp";
|
|
5
|
+
displayName: string;
|
|
5
6
|
binary: string;
|
|
6
7
|
hasBinary: boolean;
|
|
7
8
|
hasAuthSignal: boolean;
|
|
8
9
|
hasApiKeySignal: boolean;
|
|
10
|
+
hasProjectTrustSignal: boolean;
|
|
9
11
|
status: AgentAvailabilityStatus$1;
|
|
10
12
|
score: number;
|
|
11
13
|
usable: boolean;
|
|
12
14
|
checks: string[];
|
|
15
|
+
unusableReasons: string[];
|
|
13
16
|
};
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {AgentAvailability} agent
|
|
20
|
+
*/
|
|
21
|
+
declare function describeUnavailableAgent(agent: AgentAvailability): string;
|
|
22
|
+
/**
|
|
23
|
+
* @param {AgentAvailability[]} detections
|
|
24
|
+
*/
|
|
25
|
+
declare function formatNoUsableAgentsMessage(detections: AgentAvailability[]): string;
|
|
15
26
|
/**
|
|
16
27
|
* @param {NodeJS.ProcessEnv} [env]
|
|
28
|
+
* @param {{ cwd?: string }} [options]
|
|
17
29
|
* @returns {AgentAvailability[]}
|
|
18
30
|
*/
|
|
19
|
-
declare function detectAvailableAgents(env?: NodeJS.ProcessEnv
|
|
31
|
+
declare function detectAvailableAgents(env?: NodeJS.ProcessEnv, options?: {
|
|
32
|
+
cwd?: string;
|
|
33
|
+
}): AgentAvailability[];
|
|
20
34
|
/**
|
|
21
35
|
* @param {NodeJS.ProcessEnv} [env]
|
|
36
|
+
* @param {{ cwd?: string }} [options]
|
|
22
37
|
*/
|
|
23
|
-
declare function generateAgentsTs(env?: NodeJS.ProcessEnv
|
|
38
|
+
declare function generateAgentsTs(env?: NodeJS.ProcessEnv, options?: {
|
|
39
|
+
cwd?: string;
|
|
40
|
+
}): string;
|
|
24
41
|
type AgentAvailability = AgentAvailability$1;
|
|
25
42
|
type AgentAvailabilityStatus = AgentAvailabilityStatus$1;
|
|
26
43
|
|
|
27
|
-
export { type AgentAvailability, type AgentAvailabilityStatus, detectAvailableAgents, generateAgentsTs };
|
|
44
|
+
export { type AgentAvailability, type AgentAvailabilityStatus, describeUnavailableAgent, detectAvailableAgents, formatNoUsableAgentsMessage, generateAgentsTs };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/cli",
|
|
3
|
-
"version": "0.20.
|
|
4
|
-
"description": "Smithers command-line interface,
|
|
3
|
+
"version": "0.20.3",
|
|
4
|
+
"description": "Smithers command-line interface, MCP server, and local workflow tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"exports": {
|
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
"@effect/workflow": "^0.18.0",
|
|
26
26
|
"@clack/prompts": "^0.10.1",
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
-
"@opentui/core": "^0.1.100",
|
|
29
|
-
"@opentui/react": "^0.1.100",
|
|
30
28
|
"cron-parser": "^5.5.0",
|
|
31
29
|
"drizzle-orm": "^0.45.2",
|
|
32
30
|
"effect": "^3.21.1",
|
|
@@ -34,21 +32,21 @@
|
|
|
34
32
|
"picocolors": "^1.1.1",
|
|
35
33
|
"react": "^19.2.5",
|
|
36
34
|
"zod": "^4.3.6",
|
|
37
|
-
"@smithers-orchestrator/
|
|
38
|
-
"@smithers-orchestrator/
|
|
39
|
-
"@smithers-orchestrator/
|
|
40
|
-
"@smithers-orchestrator/
|
|
41
|
-
"@smithers-orchestrator/
|
|
42
|
-
"@smithers-orchestrator/
|
|
43
|
-
"@smithers-orchestrator/
|
|
44
|
-
"@smithers-orchestrator/
|
|
45
|
-
"@smithers-orchestrator/
|
|
46
|
-
"@smithers-orchestrator/
|
|
47
|
-
"@smithers-orchestrator/
|
|
48
|
-
"@smithers-orchestrator/
|
|
49
|
-
"@smithers-orchestrator/
|
|
50
|
-
"@smithers-orchestrator/
|
|
51
|
-
"@smithers-orchestrator/time-travel": "0.20.
|
|
35
|
+
"@smithers-orchestrator/accounts": "0.20.3",
|
|
36
|
+
"@smithers-orchestrator/agents": "0.20.3",
|
|
37
|
+
"@smithers-orchestrator/components": "0.20.3",
|
|
38
|
+
"@smithers-orchestrator/db": "0.20.3",
|
|
39
|
+
"@smithers-orchestrator/devtools": "0.20.3",
|
|
40
|
+
"@smithers-orchestrator/driver": "0.20.3",
|
|
41
|
+
"@smithers-orchestrator/engine": "0.20.3",
|
|
42
|
+
"@smithers-orchestrator/errors": "0.20.3",
|
|
43
|
+
"@smithers-orchestrator/memory": "0.20.3",
|
|
44
|
+
"@smithers-orchestrator/observability": "0.20.3",
|
|
45
|
+
"@smithers-orchestrator/openapi": "0.20.3",
|
|
46
|
+
"@smithers-orchestrator/protocol": "0.20.3",
|
|
47
|
+
"@smithers-orchestrator/scheduler": "0.20.3",
|
|
48
|
+
"@smithers-orchestrator/server": "0.20.3",
|
|
49
|
+
"@smithers-orchestrator/time-travel": "0.20.3"
|
|
52
50
|
},
|
|
53
51
|
"devDependencies": {
|
|
54
52
|
"@types/bun": "latest",
|
package/src/AgentAvailability.ts
CHANGED
|
@@ -2,12 +2,15 @@ import type { AgentAvailabilityStatus } from "./AgentAvailabilityStatus.ts";
|
|
|
2
2
|
|
|
3
3
|
export type AgentAvailability = {
|
|
4
4
|
id: "claude" | "codex" | "gemini" | "pi" | "kimi" | "amp";
|
|
5
|
+
displayName: string;
|
|
5
6
|
binary: string;
|
|
6
7
|
hasBinary: boolean;
|
|
7
8
|
hasAuthSignal: boolean;
|
|
8
9
|
hasApiKeySignal: boolean;
|
|
10
|
+
hasProjectTrustSignal: boolean;
|
|
9
11
|
status: AgentAvailabilityStatus;
|
|
10
12
|
score: number;
|
|
11
13
|
usable: boolean;
|
|
12
14
|
checks: string[];
|
|
15
|
+
unusableReasons: string[];
|
|
13
16
|
};
|
package/src/agent-detection.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { join, resolve, sep } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { SmithersError } from "@smithers-orchestrator/errors";
|
|
6
6
|
import { listAccounts } from "@smithers-orchestrator/accounts";
|
|
@@ -10,30 +10,53 @@ import { listAccounts } from "@smithers-orchestrator/accounts";
|
|
|
10
10
|
const DETECTORS = [
|
|
11
11
|
{
|
|
12
12
|
id: "claude",
|
|
13
|
+
displayName: "Claude Code",
|
|
13
14
|
binary: "claude",
|
|
14
|
-
authSignals: (homeDir) => [
|
|
15
|
+
authSignals: (homeDir) => [
|
|
16
|
+
join(homeDir, ".claude", ".credentials.json"),
|
|
17
|
+
join(homeDir, ".claude.json"),
|
|
18
|
+
],
|
|
15
19
|
apiKeys: ["ANTHROPIC_API_KEY"],
|
|
20
|
+
setupHint: "Install the Claude Code CLI and run `claude` then `/login`, or set `ANTHROPIC_API_KEY`.",
|
|
16
21
|
},
|
|
17
22
|
{
|
|
18
23
|
id: "codex",
|
|
24
|
+
displayName: "Codex",
|
|
19
25
|
binary: "codex",
|
|
20
|
-
authSignals: (homeDir) => [join(homeDir, ".codex")],
|
|
26
|
+
authSignals: (homeDir) => [join(homeDir, ".codex", "auth.json")],
|
|
21
27
|
apiKeys: ["OPENAI_API_KEY"],
|
|
28
|
+
setupHint: "Install the Codex CLI and run `codex login`, or set `OPENAI_API_KEY`.",
|
|
22
29
|
},
|
|
23
30
|
{
|
|
24
31
|
id: "gemini",
|
|
32
|
+
displayName: "Gemini",
|
|
25
33
|
binary: "gemini",
|
|
26
|
-
authSignals: (homeDir) =>
|
|
34
|
+
authSignals: (homeDir, env) => {
|
|
35
|
+
const configDir = env.GEMINI_DIR ? resolve(env.GEMINI_DIR) : join(homeDir, ".gemini");
|
|
36
|
+
return [
|
|
37
|
+
join(configDir, "oauth_creds.json"),
|
|
38
|
+
join(configDir, "google_accounts.json"),
|
|
39
|
+
];
|
|
40
|
+
},
|
|
27
41
|
apiKeys: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
42
|
+
projectTrust: (homeDir, env, cwd) => {
|
|
43
|
+
const configDir = env.GEMINI_DIR ? resolve(env.GEMINI_DIR) : join(homeDir, ".gemini");
|
|
44
|
+
const trustFile = join(configDir, "trustedFolders.json");
|
|
45
|
+
return readGeminiProjectTrust(trustFile, cwd);
|
|
46
|
+
},
|
|
47
|
+
setupHint: "Install the Gemini CLI, authenticate it, and trust this project with Gemini, or set `GEMINI_API_KEY` after installing the CLI.",
|
|
28
48
|
},
|
|
29
49
|
{
|
|
30
50
|
id: "pi",
|
|
51
|
+
displayName: "Pi",
|
|
31
52
|
binary: "pi",
|
|
32
53
|
authSignals: (homeDir) => [join(homeDir, ".pi", "agent", "auth.json")],
|
|
33
54
|
apiKeys: [],
|
|
55
|
+
setupHint: "Install and authenticate the `pi` CLI.",
|
|
34
56
|
},
|
|
35
57
|
{
|
|
36
58
|
id: "kimi",
|
|
59
|
+
displayName: "Kimi",
|
|
37
60
|
binary: "kimi",
|
|
38
61
|
authSignals: (homeDir, env) => {
|
|
39
62
|
const signals = [join(homeDir, ".kimi")];
|
|
@@ -42,12 +65,15 @@ const DETECTORS = [
|
|
|
42
65
|
return signals;
|
|
43
66
|
},
|
|
44
67
|
apiKeys: [],
|
|
68
|
+
setupHint: "Install the Kimi CLI and run `kimi login`.",
|
|
45
69
|
},
|
|
46
70
|
{
|
|
47
71
|
id: "amp",
|
|
72
|
+
displayName: "Amp",
|
|
48
73
|
binary: "amp",
|
|
49
74
|
authSignals: (homeDir) => [join(homeDir, ".amp")],
|
|
50
75
|
apiKeys: [],
|
|
76
|
+
setupHint: "Install and authenticate the `amp` CLI.",
|
|
51
77
|
},
|
|
52
78
|
];
|
|
53
79
|
const ROLE_PREFERENCES = {
|
|
@@ -62,6 +88,7 @@ const AGENT_VARIANTS = [
|
|
|
62
88
|
{
|
|
63
89
|
derivedFrom: "claude",
|
|
64
90
|
variantId: "claudeSonnet",
|
|
91
|
+
displayName: "Claude Sonnet",
|
|
65
92
|
constructor: {
|
|
66
93
|
importName: "ClaudeCodeAgent",
|
|
67
94
|
expr: 'new SmithersClaudeCodeAgent({ model: "claude-sonnet-4-6", cwd: process.cwd() })',
|
|
@@ -104,6 +131,82 @@ const CONSTRUCTORS = {
|
|
|
104
131
|
expr: "new SmithersAmpAgent()",
|
|
105
132
|
},
|
|
106
133
|
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} id
|
|
137
|
+
*/
|
|
138
|
+
function detectorForId(id) {
|
|
139
|
+
return DETECTORS.find((detector) => detector.id === id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} id
|
|
144
|
+
*/
|
|
145
|
+
function variantForId(id) {
|
|
146
|
+
return AGENT_VARIANTS.find((variant) => variant.variantId === id);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} id
|
|
151
|
+
*/
|
|
152
|
+
function baseAgentIdForProviderId(id) {
|
|
153
|
+
return variantForId(id)?.derivedFrom ?? id;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} id
|
|
158
|
+
*/
|
|
159
|
+
function displayNameForProviderId(id) {
|
|
160
|
+
return variantForId(id)?.displayName ?? detectorForId(id)?.displayName ?? id;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {unknown} value
|
|
165
|
+
* @returns {string[]}
|
|
166
|
+
*/
|
|
167
|
+
function extractTrustedFolderPaths(value) {
|
|
168
|
+
if (Array.isArray(value)) {
|
|
169
|
+
return value.filter((entry) => typeof entry === "string");
|
|
170
|
+
}
|
|
171
|
+
if (!value || typeof value !== "object") {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
return Object.entries(/** @type {Record<string, unknown>} */ (value))
|
|
175
|
+
.filter(([, trustValue]) => trustValue === true || trustValue === "TRUST_FOLDER")
|
|
176
|
+
.map(([path]) => path);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {string} trustedPath
|
|
181
|
+
* @param {string} cwd
|
|
182
|
+
*/
|
|
183
|
+
function trustedPathMatchesCwd(trustedPath, cwd) {
|
|
184
|
+
const trusted = resolve(trustedPath);
|
|
185
|
+
const current = resolve(cwd);
|
|
186
|
+
return current === trusted || current.startsWith(trusted.endsWith(sep) ? trusted : `${trusted}${sep}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {string} trustFile
|
|
191
|
+
* @param {string} cwd
|
|
192
|
+
* @returns {{ trusted: boolean; checks: string[] }}
|
|
193
|
+
*/
|
|
194
|
+
function readGeminiProjectTrust(trustFile, cwd) {
|
|
195
|
+
let trusted = false;
|
|
196
|
+
if (existsSync(trustFile)) {
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(readFileSync(trustFile, "utf8"));
|
|
199
|
+
trusted = extractTrustedFolderPaths(parsed).some((path) => trustedPathMatchesCwd(path, cwd));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
trusted = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
trusted,
|
|
207
|
+
checks: [`project-trust:${trustFile}:${resolve(cwd)}:${trusted ? "yes" : "no"}`],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
107
210
|
/**
|
|
108
211
|
* @param {string} binary
|
|
109
212
|
* @param {NodeJS.ProcessEnv} env
|
|
@@ -149,32 +252,103 @@ function scoreStatus(status) {
|
|
|
149
252
|
return 0;
|
|
150
253
|
}
|
|
151
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {{ authSignals: (homeDir: string, env: NodeJS.ProcessEnv) => string[]; apiKeys: string[] }} detector
|
|
258
|
+
* @param {string} homeDir
|
|
259
|
+
* @param {NodeJS.ProcessEnv} env
|
|
260
|
+
*/
|
|
261
|
+
function credentialRequirementLabel(detector, homeDir, env) {
|
|
262
|
+
const authSignals = detector.authSignals(homeDir, env);
|
|
263
|
+
const pieces = [
|
|
264
|
+
...authSignals.map((signal) => signal.replace(homeDir, "~")),
|
|
265
|
+
...detector.apiKeys.map((name) => `$${name}`),
|
|
266
|
+
];
|
|
267
|
+
return pieces.length > 0 ? pieces.join(" or ") : "agent credentials";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param {AgentAvailability} agent
|
|
272
|
+
*/
|
|
273
|
+
function formatUnusableReasons(agent) {
|
|
274
|
+
return agent.unusableReasons.length > 0
|
|
275
|
+
? agent.unusableReasons.join("; ")
|
|
276
|
+
: "not enough availability signals";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @param {AgentAvailability} agent
|
|
281
|
+
*/
|
|
282
|
+
export function describeUnavailableAgent(agent) {
|
|
283
|
+
return `${agent.displayName} is unavailable: ${formatUnusableReasons(agent)}. ${agent.displayName === "Codex"
|
|
284
|
+
? "Recommended setup: install the Codex CLI, run `codex login`, then rerun `smithers init`."
|
|
285
|
+
: "Smithers will use another available agent for this role."}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @param {AgentAvailability[]} detections
|
|
290
|
+
*/
|
|
291
|
+
export function formatNoUsableAgentsMessage(detections) {
|
|
292
|
+
const summaries = detections
|
|
293
|
+
.map((entry) => `${entry.displayName}: ${entry.usable ? "usable" : formatUnusableReasons(entry)}`)
|
|
294
|
+
.join(" | ");
|
|
295
|
+
return [
|
|
296
|
+
`No usable agents detected. ${summaries}.`,
|
|
297
|
+
`Checked: ${detections.flatMap((entry) => entry.checks).join(", ")}`,
|
|
298
|
+
"Recommended setup: install the Codex CLI, run `codex login`, then rerun `smithers init`.",
|
|
299
|
+
"If you use API billing, make sure `codex` is installed and set `OPENAI_API_KEY`.",
|
|
300
|
+
].join(" ");
|
|
301
|
+
}
|
|
302
|
+
|
|
152
303
|
/**
|
|
153
304
|
* @param {NodeJS.ProcessEnv} [env]
|
|
305
|
+
* @param {{ cwd?: string }} [options]
|
|
154
306
|
* @returns {AgentAvailability[]}
|
|
155
307
|
*/
|
|
156
|
-
export function detectAvailableAgents(env = process.env) {
|
|
308
|
+
export function detectAvailableAgents(env = process.env, options = {}) {
|
|
157
309
|
const homeDir = env.HOME ?? homedir();
|
|
310
|
+
const cwd = options.cwd ?? process.cwd();
|
|
158
311
|
return DETECTORS.map((detector) => {
|
|
159
312
|
const authSignals = detector.authSignals(homeDir, env);
|
|
160
313
|
const hasBinary = commandExists(detector.binary, env);
|
|
161
|
-
const
|
|
314
|
+
const authSignalChecks = authSignals.map((signal) => ({
|
|
315
|
+
signal,
|
|
316
|
+
exists: existsSync(signal),
|
|
317
|
+
}));
|
|
318
|
+
const hasAuthSignal = authSignalChecks.some((check) => check.exists);
|
|
162
319
|
const hasApiKeySignal = detector.apiKeys.some((name) => Boolean(env[name]));
|
|
320
|
+
const projectTrust = detector.projectTrust?.(homeDir, env, cwd) ?? { trusted: true, checks: [] };
|
|
321
|
+
const hasProjectTrustSignal = projectTrust.trusted;
|
|
163
322
|
const status = computeStatus(hasBinary, hasAuthSignal, hasApiKeySignal);
|
|
323
|
+
const hasCredentialSignal = hasAuthSignal || hasApiKeySignal;
|
|
324
|
+
const unusableReasons = [];
|
|
325
|
+
if (!hasBinary) {
|
|
326
|
+
unusableReasons.push(`missing \`${detector.binary}\` on PATH`);
|
|
327
|
+
}
|
|
328
|
+
if (!hasCredentialSignal) {
|
|
329
|
+
unusableReasons.push(`missing credentials (${credentialRequirementLabel(detector, homeDir, env)})`);
|
|
330
|
+
}
|
|
331
|
+
if (!hasProjectTrustSignal) {
|
|
332
|
+
unusableReasons.push("current project is not trusted by Gemini");
|
|
333
|
+
}
|
|
164
334
|
return {
|
|
165
335
|
id: detector.id,
|
|
336
|
+
displayName: detector.displayName,
|
|
166
337
|
binary: detector.binary,
|
|
167
338
|
hasBinary,
|
|
168
339
|
hasAuthSignal,
|
|
169
340
|
hasApiKeySignal,
|
|
341
|
+
hasProjectTrustSignal,
|
|
170
342
|
status,
|
|
171
343
|
score: scoreStatus(status),
|
|
172
|
-
usable:
|
|
344
|
+
usable: unusableReasons.length === 0,
|
|
173
345
|
checks: [
|
|
174
346
|
`binary:${detector.binary}:${hasBinary ? "yes" : "no"}`,
|
|
175
|
-
...
|
|
347
|
+
...authSignalChecks.map((check) => `auth:${check.signal}:${check.exists ? "yes" : "no"}`),
|
|
176
348
|
...detector.apiKeys.map((name) => `env:${name}:${env[name] ? "yes" : "no"}`),
|
|
349
|
+
...projectTrust.checks,
|
|
177
350
|
],
|
|
351
|
+
unusableReasons,
|
|
178
352
|
};
|
|
179
353
|
});
|
|
180
354
|
}
|
|
@@ -356,15 +530,48 @@ function renderAccountProviderLine(account, homeDir) {
|
|
|
356
530
|
return ` ${camel}: new Smithers${cls}({ ${opts.join(", ")} }),`;
|
|
357
531
|
}
|
|
358
532
|
|
|
533
|
+
/**
|
|
534
|
+
* @param {string} tier
|
|
535
|
+
* @param {string[]} order
|
|
536
|
+
* @param {Set<string>} allProviderIds
|
|
537
|
+
* @param {Map<string, AgentAvailability>} detectionsById
|
|
538
|
+
* @returns {string[]}
|
|
539
|
+
*/
|
|
540
|
+
function renderUnavailablePreferenceComments(tier, order, allProviderIds, detectionsById) {
|
|
541
|
+
const firstAvailablePreferredIndex = order.findIndex((id) => allProviderIds.has(id));
|
|
542
|
+
const cutoff = firstAvailablePreferredIndex === -1 ? order.length : firstAvailablePreferredIndex;
|
|
543
|
+
const comments = [];
|
|
544
|
+
for (const providerId of order.slice(0, cutoff)) {
|
|
545
|
+
const baseId = baseAgentIdForProviderId(providerId);
|
|
546
|
+
const detection = detectionsById.get(baseId);
|
|
547
|
+
if (!detection || detection.usable) continue;
|
|
548
|
+
comments.push(` // ${tier}: Smithers would normally suggest ${displayNameForProviderId(providerId)} here, but ${detection.displayName} is not available: ${formatUnusableReasons(detection)}.`);
|
|
549
|
+
}
|
|
550
|
+
return comments;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* @param {string} tier
|
|
555
|
+
* @param {string[]} providerIds
|
|
556
|
+
* @param {string[]} comments
|
|
557
|
+
*/
|
|
558
|
+
function renderTierLine(tier, providerIds, comments) {
|
|
559
|
+
return [
|
|
560
|
+
...comments,
|
|
561
|
+
` ${tier}: [${providerIds.map((id) => `providers.${id}`).join(", ")}],`,
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
|
|
359
565
|
/**
|
|
360
566
|
* @param {NodeJS.ProcessEnv} [env]
|
|
567
|
+
* @param {{ cwd?: string }} [options]
|
|
361
568
|
*/
|
|
362
|
-
export function generateAgentsTs(env = process.env) {
|
|
569
|
+
export function generateAgentsTs(env = process.env, options = {}) {
|
|
363
570
|
const registeredAccounts = listAccounts(env);
|
|
364
|
-
const detections = detectAvailableAgents(env);
|
|
571
|
+
const detections = detectAvailableAgents(env, options);
|
|
365
572
|
const available = detections.filter((entry) => entry.usable);
|
|
366
573
|
if (available.length === 0 && registeredAccounts.length === 0) {
|
|
367
|
-
throw new SmithersError("NO_USABLE_AGENTS",
|
|
574
|
+
throw new SmithersError("NO_USABLE_AGENTS", formatNoUsableAgentsMessage(detections));
|
|
368
575
|
}
|
|
369
576
|
// When no agents are detected (e.g. fresh machine with only API keys
|
|
370
577
|
// registered via `smithers agent add`), emit the accounts-only shape with
|
|
@@ -411,11 +618,12 @@ export function generateAgentsTs(env = process.env) {
|
|
|
411
618
|
...orderedProviders.map((p) => p.id),
|
|
412
619
|
...activeVariants.map((v) => v.variantId),
|
|
413
620
|
]);
|
|
621
|
+
const detectionsById = new Map(detections.map((entry) => [entry.id, entry]));
|
|
414
622
|
// Fallback: all base provider IDs sorted by score (for tiers with no preferred match)
|
|
415
623
|
const fallbackIds = orderedProviders.map((p) => p.id);
|
|
416
624
|
// Tier lines: detection-resolved members, then accounts whose engine
|
|
417
625
|
// family is in the tier's preference order get appended.
|
|
418
|
-
const tierLines = Object.entries(TIER_PREFERENCES).
|
|
626
|
+
const tierLines = Object.entries(TIER_PREFERENCES).flatMap(([tier, { order, maxSize }]) => {
|
|
419
627
|
let resolved = order
|
|
420
628
|
.filter((id) => allProviderIds.has(id))
|
|
421
629
|
.slice(0, maxSize);
|
|
@@ -427,7 +635,11 @@ export function generateAgentsTs(env = process.env) {
|
|
|
427
635
|
.filter((account) => tierFamilies.has(ACCOUNT_PROVIDER_POOL[account.provider]))
|
|
428
636
|
.map((account) => labelToCamel(account.label));
|
|
429
637
|
const merged = [...resolved, ...tierAccounts];
|
|
430
|
-
return
|
|
638
|
+
return renderTierLine(
|
|
639
|
+
tier,
|
|
640
|
+
merged,
|
|
641
|
+
renderUnavailablePreferenceComments(tier, order, allProviderIds, detectionsById),
|
|
642
|
+
);
|
|
431
643
|
});
|
|
432
644
|
return [
|
|
433
645
|
"// smithers-source: generated",
|
package/src/ask.js
CHANGED
|
@@ -11,7 +11,7 @@ import { KimiAgent } from "@smithers-orchestrator/agents/KimiAgent";
|
|
|
11
11
|
import { PiAgent } from "@smithers-orchestrator/agents/PiAgent";
|
|
12
12
|
import { SmithersError } from "@smithers-orchestrator/errors";
|
|
13
13
|
import { createSmithersAgentContract, renderSmithersAgentPromptGuidance, } from "@smithers-orchestrator/agents/agent-contract";
|
|
14
|
-
import { detectAvailableAgents, } from "./agent-detection.js";
|
|
14
|
+
import { describeUnavailableAgent, detectAvailableAgents, formatNoUsableAgentsMessage, } from "./agent-detection.js";
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {typeof ASK_AGENT_IDS[number]} AskAgentId
|
|
17
17
|
*/
|
|
@@ -193,9 +193,7 @@ function formatAgentChecks(agent) {
|
|
|
193
193
|
* @param {AgentAvailability[]} agents
|
|
194
194
|
*/
|
|
195
195
|
function noUsableAgentError(agents) {
|
|
196
|
-
return new SmithersError("NO_USABLE_AGENTS",
|
|
197
|
-
.map((agent) => `${agent.id} => ${formatAgentChecks(agent)}`)
|
|
198
|
-
.join(" | ")}`);
|
|
196
|
+
return new SmithersError("NO_USABLE_AGENTS", formatNoUsableAgentsMessage(agents));
|
|
199
197
|
}
|
|
200
198
|
/**
|
|
201
199
|
* @param {AgentAvailability[]} agents
|
|
@@ -210,7 +208,7 @@ function selectAgent(agents, options) {
|
|
|
210
208
|
throw new SmithersError("CLI_AGENT_UNSUPPORTED", `Agent "${options.agent}" is not supported for \`smithers ask\`.`, { agentId: options.agent });
|
|
211
209
|
}
|
|
212
210
|
if (!explicit.usable) {
|
|
213
|
-
throw new SmithersError("NO_USABLE_AGENTS",
|
|
211
|
+
throw new SmithersError("NO_USABLE_AGENTS", `${describeUnavailableAgent(explicit)} Checked: ${formatAgentChecks(explicit)}`, { agentId: explicit.id });
|
|
214
212
|
}
|
|
215
213
|
return {
|
|
216
214
|
availability: explicit,
|
|
@@ -404,7 +402,7 @@ function buildAgent(selection, bootstrap, systemPrompt, cwd) {
|
|
|
404
402
|
* @returns {Promise<void>}
|
|
405
403
|
*/
|
|
406
404
|
export async function ask(question, cwd, options = {}) {
|
|
407
|
-
const agents = detectAvailableAgents();
|
|
405
|
+
const agents = detectAvailableAgents(process.env, { cwd });
|
|
408
406
|
if (options.listAgents) {
|
|
409
407
|
let selectedAgentId;
|
|
410
408
|
try {
|