@smithers-orchestrator/cli 0.20.4 → 0.21.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.
@@ -27,10 +27,26 @@ const DETECTORS = [
27
27
  apiKeys: ["OPENAI_API_KEY"],
28
28
  setupHint: "Install the Codex CLI and run `codex login`, or set `OPENAI_API_KEY`.",
29
29
  },
30
+ {
31
+ id: "antigravity",
32
+ displayName: "Antigravity",
33
+ binary: "agy",
34
+ authSignals: (homeDir, env) => {
35
+ const configRoot = env.GEMINI_DIR ? resolve(env.GEMINI_DIR) : join(homeDir, ".gemini");
36
+ return [
37
+ join(configRoot, "antigravity-cli", "settings.json"),
38
+ join(configRoot, "antigravity-cli"),
39
+ ];
40
+ },
41
+ apiKeys: [],
42
+ setupHint: "Install the Antigravity CLI, run `agy`, and complete Google Sign-In.",
43
+ },
30
44
  {
31
45
  id: "gemini",
32
46
  displayName: "Gemini",
33
47
  binary: "gemini",
48
+ deprecated: true,
49
+ deprecationReason: "Gemini CLI is deprecated for individual/free users; use Antigravity CLI (`agy`) instead.",
34
50
  authSignals: (homeDir, env) => {
35
51
  const configDir = env.GEMINI_DIR ? resolve(env.GEMINI_DIR) : join(homeDir, ".gemini");
36
52
  return [
@@ -44,7 +60,7 @@ const DETECTORS = [
44
60
  const trustFile = join(configDir, "trustedFolders.json");
45
61
  return readGeminiProjectTrust(trustFile, cwd);
46
62
  },
47
- setupHint: "Install the Gemini CLI, authenticate it, and trust this project with Gemini, or set `GEMINI_API_KEY` after installing the CLI.",
63
+ setupHint: "Gemini CLI is deprecated. Install Antigravity CLI and run `agy`, or continue using Gemini only for legacy/enterprise setups.",
48
64
  },
49
65
  {
50
66
  id: "pi",
@@ -78,10 +94,10 @@ const DETECTORS = [
78
94
  ];
79
95
  const ROLE_PREFERENCES = {
80
96
  spec: ["claude", "codex"],
81
- research: ["gemini", "kimi", "codex", "claude"],
82
- plan: ["gemini", "codex", "claude", "kimi"],
83
- implement: ["codex", "amp", "gemini", "claude", "kimi"],
84
- validate: ["codex", "amp", "gemini"],
97
+ research: ["antigravity", "kimi", "codex", "claude"],
98
+ plan: ["antigravity", "codex", "claude", "kimi"],
99
+ implement: ["codex", "amp", "antigravity", "claude", "kimi"],
100
+ validate: ["codex", "amp", "antigravity"],
85
101
  review: ["claude", "amp", "codex"],
86
102
  };
87
103
  const AGENT_VARIANTS = [
@@ -98,12 +114,28 @@ const AGENT_VARIANTS = [
98
114
  const SCAFFOLDED_PROVIDERS = {
99
115
  claude: "ClaudeCodeAgent",
100
116
  codex: "CodexAgent",
117
+ antigravity: "AntigravityAgent",
118
+ };
119
+ const SCAFFOLDED_PROVIDER_FILES = {
120
+ claude: "claude-code",
121
+ codex: "codex",
122
+ antigravity: "antigravity",
123
+ };
124
+ const LEGACY_SCAFFOLDED_PROVIDERS = {
101
125
  gemini: "GeminiAgent",
102
126
  };
127
+ const LOCAL_SCAFFOLDED_PROVIDERS = {
128
+ ...SCAFFOLDED_PROVIDERS,
129
+ ...LEGACY_SCAFFOLDED_PROVIDERS,
130
+ };
131
+ const LOCAL_SCAFFOLDED_PROVIDER_FILES = {
132
+ ...SCAFFOLDED_PROVIDER_FILES,
133
+ gemini: "gemini",
134
+ };
103
135
  const TIER_PREFERENCES = {
104
- cheapFast: { order: ["kimi", "claudeSonnet", "gemini", "pi"], maxSize: 2 },
105
- smart: { order: ["codex", "claude", "kimi", "gemini", "amp"], maxSize: 3 },
106
- smartTool: { order: ["claude", "codex", "kimi", "gemini", "amp"], maxSize: 3 },
136
+ cheapFast: { order: ["kimi", "claudeSonnet", "antigravity", "pi"], maxSize: 2 },
137
+ smart: { order: ["codex", "claude", "kimi", "antigravity", "amp"], maxSize: 3 },
138
+ smartTool: { order: ["claude", "codex", "kimi", "antigravity", "amp"], maxSize: 3 },
107
139
  };
108
140
  const CONSTRUCTORS = {
109
141
  claude: {
@@ -114,6 +146,10 @@ const CONSTRUCTORS = {
114
146
  importName: "CodexAgent",
115
147
  expr: 'new SmithersCodexAgent({ model: "gpt-5.3-codex", cwd: process.cwd(), skipGitRepoCheck: true })',
116
148
  },
149
+ antigravity: {
150
+ importName: "AntigravityAgent",
151
+ expr: "new SmithersAntigravityAgent({ cwd: process.cwd() })",
152
+ },
117
153
  gemini: {
118
154
  importName: "GeminiAgent",
119
155
  expr: 'new SmithersGeminiAgent({ model: "gemini-3.1-pro-preview", cwd: process.cwd() })',
@@ -131,7 +167,6 @@ const CONSTRUCTORS = {
131
167
  expr: "new SmithersAmpAgent()",
132
168
  },
133
169
  };
134
-
135
170
  /**
136
171
  * @param {string} id
137
172
  */
@@ -153,6 +188,43 @@ function baseAgentIdForProviderId(id) {
153
188
  return variantForId(id)?.derivedFrom ?? id;
154
189
  }
155
190
 
191
+ /**
192
+ * Extracts detection-derived provider ids from a generated `.smithers/agents.ts`.
193
+ * Account labels are deliberately ignored; the accounts registry remains the
194
+ * source of truth for account-backed providers.
195
+ *
196
+ * @param {string} source
197
+ * @returns {Set<string>}
198
+ */
199
+ export function extractGeneratedDetectionProviderIds(source) {
200
+ const ids = new Set();
201
+ if (!source.startsWith("// smithers-source: generated")) {
202
+ return ids;
203
+ }
204
+ const providersMatch = source.match(/export const providers\s*=\s*{([\s\S]*?)}\s*as const;/);
205
+ if (!providersMatch) {
206
+ return ids;
207
+ }
208
+ for (const line of providersMatch[1].split("\n")) {
209
+ const match = line.match(/^\s*([A-Za-z_$][\w$]*)\s*:\s*(.*?)\s*,?\s*$/);
210
+ if (!match)
211
+ continue;
212
+ const [, providerId, initializer] = match;
213
+ const scaffoldedProvider = SCAFFOLDED_PROVIDERS[providerId] ?? LEGACY_SCAFFOLDED_PROVIDERS[providerId];
214
+ const constructorProvider = CONSTRUCTORS[providerId];
215
+ if ((scaffoldedProvider && initializer === scaffoldedProvider) ||
216
+ (constructorProvider && initializer === constructorProvider.expr)) {
217
+ ids.add(providerId);
218
+ continue;
219
+ }
220
+ const variant = variantForId(providerId);
221
+ if (variant && initializer === variant.constructor.expr) {
222
+ ids.add(variant.derivedFrom);
223
+ }
224
+ }
225
+ return ids;
226
+ }
227
+
156
228
  /**
157
229
  * @param {string} id
158
230
  */
@@ -290,7 +362,7 @@ export function describeUnavailableAgent(agent) {
290
362
  */
291
363
  export function formatNoUsableAgentsMessage(detections) {
292
364
  const summaries = detections
293
- .map((entry) => `${entry.displayName}: ${entry.usable ? "usable" : formatUnusableReasons(entry)}`)
365
+ .map((entry) => `${entry.displayName}: ${entry.deprecated ? `deprecated (${entry.deprecationReason})` : entry.usable ? "usable" : formatUnusableReasons(entry)}`)
294
366
  .join(" | ");
295
367
  return [
296
368
  `No usable agents detected. ${summaries}.`,
@@ -335,6 +407,8 @@ export function detectAvailableAgents(env = process.env, options = {}) {
335
407
  id: detector.id,
336
408
  displayName: detector.displayName,
337
409
  binary: detector.binary,
410
+ deprecated: detector.deprecated === true ? true : undefined,
411
+ deprecationReason: detector.deprecationReason,
338
412
  hasBinary,
339
413
  hasAuthSignal,
340
414
  hasApiKeySignal,
@@ -382,6 +456,7 @@ function resolveRoleAgents(role, available) {
382
456
  */
383
457
  const ACCOUNT_PROVIDER_CLASSES = {
384
458
  "claude-code": "ClaudeCodeAgent",
459
+ "antigravity": "AntigravityAgent",
385
460
  "codex": "CodexAgent",
386
461
  "gemini": "GeminiAgent",
387
462
  "kimi": "KimiAgent",
@@ -398,6 +473,7 @@ const ACCOUNT_PROVIDER_CLASSES = {
398
473
  const ACCOUNT_PROVIDER_POOL = {
399
474
  "claude-code": "claude",
400
475
  "anthropic-api": "claude",
476
+ "antigravity": "antigravity",
401
477
  "codex": "codex",
402
478
  "openai-api": "codex",
403
479
  "gemini": "gemini",
@@ -412,6 +488,7 @@ const ACCOUNT_PROVIDER_POOL = {
412
488
  const ACCOUNT_PROVIDER_DEFAULT_MODEL = {
413
489
  "claude-code": "claude-opus-4-7",
414
490
  "anthropic-api": "claude-opus-4-7",
491
+ "antigravity": undefined,
415
492
  "codex": "gpt-5.4-codex",
416
493
  "openai-api": "gpt-5.4-codex",
417
494
  "gemini": "gemini-3.1-pro-preview",
@@ -564,12 +641,34 @@ function renderTierLine(tier, providerIds, comments) {
564
641
 
565
642
  /**
566
643
  * @param {NodeJS.ProcessEnv} [env]
567
- * @param {{ cwd?: string }} [options]
644
+ * @param {{ cwd?: string; preserveProviderIds?: Iterable<string>; scaffoldProviderIds?: Iterable<string> }} [options]
568
645
  */
569
646
  export function generateAgentsTs(env = process.env, options = {}) {
570
647
  const registeredAccounts = listAccounts(env);
571
648
  const detections = detectAvailableAgents(env, options);
572
- const available = detections.filter((entry) => entry.usable);
649
+ const scaffoldProviderIds = new Set(options.scaffoldProviderIds ?? Object.keys(SCAFFOLDED_PROVIDERS));
650
+ const usesLocalScaffold = (providerId) => providerId in LOCAL_SCAFFOLDED_PROVIDERS && scaffoldProviderIds.has(providerId);
651
+ const availableById = new Map(detections.filter((entry) => entry.usable && !entry.deprecated).map((entry) => [entry.id, entry]));
652
+ for (const providerId of options.preserveProviderIds ?? []) {
653
+ const baseId = baseAgentIdForProviderId(providerId);
654
+ const detector = detectorForId(baseId);
655
+ if (!detector || availableById.has(baseId)) continue;
656
+ availableById.set(baseId, {
657
+ id: detector.id,
658
+ displayName: detector.displayName,
659
+ binary: detector.binary,
660
+ hasBinary: false,
661
+ hasAuthSignal: false,
662
+ hasApiKeySignal: false,
663
+ hasProjectTrustSignal: true,
664
+ status: "likely-subscription",
665
+ score: scoreStatus("likely-subscription"),
666
+ usable: true,
667
+ checks: [`preserved:${detector.id}:yes`],
668
+ unusableReasons: [],
669
+ });
670
+ }
671
+ const available = [...availableById.values()];
573
672
  if (available.length === 0 && registeredAccounts.length === 0) {
574
673
  throw new SmithersError("NO_USABLE_AGENTS", formatNoUsableAgentsMessage(detections));
575
674
  }
@@ -581,7 +680,7 @@ export function generateAgentsTs(env = process.env, options = {}) {
581
680
  }
582
681
  // Base providers in detection order
583
682
  const orderedProviders = DETECTORS
584
- .map((detector) => available.find((entry) => entry.id === detector.id))
683
+ .map((detector) => availableById.get(detector.id))
585
684
  .filter((entry) => Boolean(entry));
586
685
  // Derive variants (e.g. claudeSonnet from claude)
587
686
  const availableIds = new Set(orderedProviders.map((p) => p.id));
@@ -590,7 +689,7 @@ export function generateAgentsTs(env = process.env, options = {}) {
590
689
  // detection providers + every account class.
591
690
  const importNames = new Set();
592
691
  for (const provider of orderedProviders) {
593
- if (!(provider.id in SCAFFOLDED_PROVIDERS)) {
692
+ if (!usesLocalScaffold(provider.id)) {
594
693
  importNames.add(CONSTRUCTORS[provider.id].importName);
595
694
  }
596
695
  }
@@ -609,10 +708,16 @@ export function generateAgentsTs(env = process.env, options = {}) {
609
708
  // Provider lines: detection base + variants + accounts (additive — `agent
610
709
  // add` must never silently delete a previously-emitted provider).
611
710
  const providerLines = [
612
- ...orderedProviders.map((provider) => ` ${provider.id}: ${SCAFFOLDED_PROVIDERS[provider.id] ?? CONSTRUCTORS[provider.id].expr},`),
711
+ ...orderedProviders.map((provider) => ` ${provider.id}: ${usesLocalScaffold(provider.id) ? LOCAL_SCAFFOLDED_PROVIDERS[provider.id] : CONSTRUCTORS[provider.id].expr},`),
613
712
  ...activeVariants.map((variant) => ` ${variant.variantId}: ${variant.constructor.expr},`),
614
713
  ...registeredAccounts.map((account) => renderAccountProviderLine(account, homeDir)),
615
714
  ];
715
+ const scaffoldImportLines = orderedProviders
716
+ .filter((provider) => usesLocalScaffold(provider.id))
717
+ .map((provider) => `import { ${LOCAL_SCAFFOLDED_PROVIDERS[provider.id]} } from "./agents/${LOCAL_SCAFFOLDED_PROVIDER_FILES[provider.id]}";`);
718
+ const scaffoldExportLines = orderedProviders
719
+ .filter((provider) => usesLocalScaffold(provider.id))
720
+ .map((provider) => `export { ${LOCAL_SCAFFOLDED_PROVIDERS[provider.id]} } from "./agents/${LOCAL_SCAFFOLDED_PROVIDER_FILES[provider.id]}";`);
616
721
  // All known provider/variant IDs for tier resolution
617
722
  const allProviderIds = new Set([
618
723
  ...orderedProviders.map((p) => p.id),
@@ -646,14 +751,10 @@ export function generateAgentsTs(env = process.env, options = {}) {
646
751
  ...(hasAccounts ? ["// Account providers (camelCase labels) come from ~/.smithers/accounts.json — managed via `smithers agent add|list|remove`."] : []),
647
752
  ...(hasAccounts ? ['import { homedir } from "node:os";', 'import path from "node:path";'] : []),
648
753
  `import { ${smithersImportSpecifiers.join(", ")} } from "smithers-orchestrator";`,
649
- 'import { ClaudeCodeAgent } from "./agents/claude-code";',
650
- 'import { CodexAgent } from "./agents/codex";',
651
- 'import { GeminiAgent } from "./agents/gemini";',
652
- "",
653
- 'export { ClaudeCodeAgent } from "./agents/claude-code";',
654
- 'export { CodexAgent } from "./agents/codex";',
655
- 'export { GeminiAgent } from "./agents/gemini";',
754
+ ...scaffoldImportLines,
656
755
  "",
756
+ ...scaffoldExportLines,
757
+ ...(scaffoldExportLines.length ? [""] : []),
657
758
  "export const providers = {",
658
759
  ...providerLines,
659
760
  "} as const;",
@@ -0,0 +1,73 @@
1
+ const BUILTIN_FLAGS_WITH_VALUES = new Set([
2
+ "--format",
3
+ "--filter-output",
4
+ "--surface",
5
+ "--token-limit",
6
+ "--token-offset",
7
+ ]);
8
+
9
+ /**
10
+ * @param {string | undefined} value
11
+ * @returns {"semantic" | "raw" | "both"}
12
+ */
13
+ function normalizeMcpSurface(value) {
14
+ const surface = value?.trim().toLowerCase();
15
+ if (surface === undefined || surface.length === 0) {
16
+ throw new Error("Missing value for --surface. Expected semantic, raw, or both.");
17
+ }
18
+ if (surface === "semantic" || surface === "raw" || surface === "both") {
19
+ return surface;
20
+ }
21
+ throw new Error(`Invalid --surface value: ${value}. Expected semantic, raw, or both.`);
22
+ }
23
+
24
+ /**
25
+ * @param {string[]} argv
26
+ */
27
+ export function parseMcpSurfaceArgv(argv) {
28
+ let surface = "semantic";
29
+ const filtered = [];
30
+ for (let index = 0; index < argv.length; index++) {
31
+ const arg = argv[index];
32
+ if (arg === "--surface") {
33
+ surface = normalizeMcpSurface(argv[index + 1]);
34
+ index += 1;
35
+ continue;
36
+ }
37
+ if (arg.startsWith("--surface=")) {
38
+ surface = normalizeMcpSurface(arg.slice("--surface=".length));
39
+ continue;
40
+ }
41
+ filtered.push(arg);
42
+ }
43
+ return { surface, argv: filtered };
44
+ }
45
+
46
+ /**
47
+ * @param {string[]} argv
48
+ * @returns {number}
49
+ */
50
+ export function findFirstPositionalIndex(argv, startIndex = 0) {
51
+ for (let index = startIndex; index < argv.length; index++) {
52
+ const arg = argv[index];
53
+ if (!arg.startsWith("-")) {
54
+ return index;
55
+ }
56
+ if (BUILTIN_FLAGS_WITH_VALUES.has(arg)) {
57
+ index++;
58
+ }
59
+ }
60
+ return -1;
61
+ }
62
+
63
+ /**
64
+ * Incur treats union-typed options as value-bearing flags, so a bare
65
+ * `--resume --run-id value` would consume `--run-id` as the resume value.
66
+ *
67
+ * @param {string[]} argv
68
+ */
69
+ export function rewriteBareResumeFlagArgv(argv) {
70
+ return argv.map((arg, index) => arg === "--resume" && (argv[index + 1] === undefined || argv[index + 1]?.startsWith("-"))
71
+ ? "--resume=true"
72
+ : arg);
73
+ }
package/src/ask.js CHANGED
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
+ import { AntigravityAgent } from "@smithers-orchestrator/agents/AntigravityAgent";
7
8
  import { ClaudeCodeAgent } from "@smithers-orchestrator/agents/ClaudeCodeAgent";
8
9
  import { CodexAgent } from "@smithers-orchestrator/agents/CodexAgent";
9
10
  import { GeminiAgent } from "@smithers-orchestrator/agents/GeminiAgent";
@@ -20,7 +21,7 @@ import { describeUnavailableAgent, detectAvailableAgents, formatNoUsableAgentsMe
20
21
  */
21
22
  /** @typedef {import("@smithers-orchestrator/agents/agent-contract").SmithersToolSurface} SmithersToolSurface */
22
23
 
23
- const ASK_AGENT_IDS = ["claude", "codex", "kimi", "gemini", "pi"];
24
+ const ASK_AGENT_IDS = ["claude", "codex", "kimi", "antigravity", "gemini", "pi"];
24
25
  const DEFAULT_SERVER_NAME = "smithers";
25
26
  /**
26
27
  * @param {AgentAvailability["id"]} value
@@ -52,6 +53,7 @@ function resolveBootstrapMode(agentId, noMcp = false) {
52
53
  return "mcp-config-inline";
53
54
  case "gemini":
54
55
  return "mcp-allow-list";
56
+ case "antigravity":
55
57
  case "pi":
56
58
  return "prompt-only";
57
59
  }
@@ -216,7 +218,7 @@ function selectAgent(agents, options) {
216
218
  selectionReason: "requested via --agent",
217
219
  };
218
220
  }
219
- const usable = supported.filter((agent) => agent.usable);
221
+ const usable = supported.filter((agent) => agent.usable && !agent.deprecated);
220
222
  if (usable.length === 0) {
221
223
  throw noUsableAgentError(agents);
222
224
  }
@@ -369,6 +371,15 @@ function buildAgent(selection, bootstrap, systemPrompt, cwd) {
369
371
  }),
370
372
  cleanup() { },
371
373
  };
374
+ case "antigravity":
375
+ return {
376
+ agent: new AntigravityAgent({
377
+ cwd,
378
+ systemPrompt,
379
+ dangerouslySkipPermissions: true,
380
+ }),
381
+ cleanup() { },
382
+ };
372
383
  case "codex":
373
384
  return {
374
385
  agent: new CodexAgent({