@kisev/agentomatic 11.0.0-dev.46.gfbe1e4c6992e

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 (91) hide show
  1. package/README.md +95 -0
  2. package/README.ru.md +98 -0
  3. package/dist/agent-profiles.d.ts +125 -0
  4. package/dist/agent-profiles.js +820 -0
  5. package/dist/assets/agents/architect.md +67 -0
  6. package/dist/assets/agents/critic.md +55 -0
  7. package/dist/assets/agents/manager.md +40 -0
  8. package/dist/assets/agents/mapper.md +33 -0
  9. package/dist/assets/agents/review.md +37 -0
  10. package/dist/assets/agents/worker.md +78 -0
  11. package/dist/assets/commands/agents-md.md +10 -0
  12. package/dist/assets/commands/askme.md +10 -0
  13. package/dist/assets/commands/ast-grep.md +10 -0
  14. package/dist/assets/commands/briefing.md +10 -0
  15. package/dist/assets/commands/code-explain.md +10 -0
  16. package/dist/assets/commands/code-review.md +10 -0
  17. package/dist/assets/commands/commit-msg.md +10 -0
  18. package/dist/assets/commands/docs-prepare.md +10 -0
  19. package/dist/assets/commands/docs-review.md +10 -0
  20. package/dist/assets/commands/goal.md +10 -0
  21. package/dist/assets/commands/humanize.md +10 -0
  22. package/dist/assets/commands/mattermost-triage.md +10 -0
  23. package/dist/assets/commands/mattermost.md +10 -0
  24. package/dist/assets/commands/mr-prepare.md +10 -0
  25. package/dist/assets/commands/release-prepare.md +10 -0
  26. package/dist/assets/commands/release-review.md +10 -0
  27. package/dist/assets/commands/rtk-stats.md +13 -0
  28. package/dist/assets/commands/rtk.md +10 -0
  29. package/dist/assets/commands/skill-improve.md +10 -0
  30. package/dist/assets/commands/slides-prompts-prepare.md +10 -0
  31. package/dist/assets/commands/spec-manage.md +13 -0
  32. package/dist/assets/commands/stopit.md +10 -0
  33. package/dist/assets/commands/task-prepare.md +10 -0
  34. package/dist/assets/commands/task-review.md +10 -0
  35. package/dist/assets/commands/task-triage.md +10 -0
  36. package/dist/assets/commands/taskmatic.md +10 -0
  37. package/dist/assets/commands/team-retro.md +10 -0
  38. package/dist/assets/commands/team-roadmap.md +10 -0
  39. package/dist/assets/commands/team-sprint-close.md +10 -0
  40. package/dist/assets/commands/team-sprint-start.md +10 -0
  41. package/dist/assets/lsp-catalog.json +30 -0
  42. package/dist/assets/migration-inventory.json +301 -0
  43. package/dist/assets/plugins/memomatic.js +3 -0
  44. package/dist/assets/plugins/rtk.js +3 -0
  45. package/dist/assets/plugins/rules-injector.js +3 -0
  46. package/dist/assets/plugins/zed-bell.js +3 -0
  47. package/dist/catalog.d.ts +8 -0
  48. package/dist/catalog.js +39 -0
  49. package/dist/cli-output.d.ts +27 -0
  50. package/dist/cli-output.js +296 -0
  51. package/dist/cli.d.ts +2 -0
  52. package/dist/cli.js +1135 -0
  53. package/dist/config-setup.d.ts +71 -0
  54. package/dist/config-setup.js +438 -0
  55. package/dist/contracts.d.ts +52 -0
  56. package/dist/contracts.js +102 -0
  57. package/dist/doctor.d.ts +44 -0
  58. package/dist/doctor.js +638 -0
  59. package/dist/generate-assets.d.ts +1 -0
  60. package/dist/generate-assets.js +54 -0
  61. package/dist/index.d.ts +125 -0
  62. package/dist/index.js +141 -0
  63. package/dist/installer.d.ts +56 -0
  64. package/dist/installer.js +710 -0
  65. package/dist/jsonc.d.ts +56 -0
  66. package/dist/jsonc.js +432 -0
  67. package/dist/lifecycle.d.ts +77 -0
  68. package/dist/lifecycle.js +736 -0
  69. package/dist/package-metadata.d.ts +4 -0
  70. package/dist/package-metadata.js +42 -0
  71. package/dist/plugins/memomatic.d.ts +76 -0
  72. package/dist/plugins/memomatic.js +101 -0
  73. package/dist/plugins/rtk.d.ts +41 -0
  74. package/dist/plugins/rtk.js +177 -0
  75. package/dist/plugins/rules-injector.d.ts +22 -0
  76. package/dist/plugins/rules-injector.js +131 -0
  77. package/dist/plugins/zed-bell.d.ts +13 -0
  78. package/dist/plugins/zed-bell.js +14 -0
  79. package/dist/reconcile.d.ts +47 -0
  80. package/dist/reconcile.js +490 -0
  81. package/dist/registry.d.ts +21 -0
  82. package/dist/registry.js +92 -0
  83. package/dist/routing.d.ts +133 -0
  84. package/dist/routing.js +578 -0
  85. package/dist/runtime/state.d.ts +9 -0
  86. package/dist/runtime/state.js +193 -0
  87. package/dist/runtime/worktree.d.ts +33 -0
  88. package/dist/runtime/worktree.js +220 -0
  89. package/dist/terminal-wizard.d.ts +3 -0
  90. package/dist/terminal-wizard.js +237 -0
  91. package/package.json +72 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1135 @@
1
+ #!/usr/bin/env node
2
+ import { AgentProfileError, applyAgentProfileChange, availableModels, availableModelVariants, FIXED_AGENT_ROLES, listAgentProfiles, previewAgentProfileChange, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
3
+ import { renderConfigSetup, renderDoctor, renderInventory, renderPlan, renderReconcile, scopeArguments, shellCommand, terminalSafe, } from "./cli-output.js";
4
+ import { collectDoctorFacts, doctorExitCode } from "./doctor.js";
5
+ import { CATALOG } from "./catalog.js";
6
+ import { applyConfigSetup, CONFIG_FRAGMENTS, CONFIG_TARGETS, defaultConfigSelection, normalizeConfigSelection, previewConfigSetup, } from "./config-setup.js";
7
+ import { apply, defaultSelection, InstallerError, normalizeSelection, preview, SELECTABLE_PLUGINS, SKILL_COMMANDS, } from "./installer.js";
8
+ import { LifecycleError } from "./lifecycle.js";
9
+ import { skillsInstallerSpec } from "./package-metadata.js";
10
+ import { applyReconcile, previewReconcile } from "./reconcile.js";
11
+ import { promptText, selectOption, selectOptions } from "./terminal-wizard.js";
12
+ function parseOptions(values) {
13
+ const options = {
14
+ scope: "project",
15
+ global: false,
16
+ dryRun: false,
17
+ json: false,
18
+ selectionFlag: false,
19
+ };
20
+ for (let index = 0; index < values.length; index += 1) {
21
+ const value = values[index];
22
+ if (!value.startsWith("--")) {
23
+ if (options.name)
24
+ throw new InstallerError("invalid_input", `Unexpected argument: ${value}`);
25
+ options.name = value;
26
+ }
27
+ else if (value === "--global") {
28
+ if (options.global)
29
+ throw new InstallerError("invalid_input", "--global may be supplied once");
30
+ options.global = true;
31
+ options.scope = "global";
32
+ }
33
+ else if (value === "--dry-run") {
34
+ if (options.dryRun)
35
+ throw new InstallerError("invalid_input", "--dry-run may be supplied once");
36
+ options.dryRun = true;
37
+ }
38
+ else if (value === "--confirm") {
39
+ if (options.confirm)
40
+ throw new InstallerError("invalid_input", "--confirm may be supplied once");
41
+ options.confirm = values[++index];
42
+ if (!options.confirm)
43
+ throw new InstallerError("invalid_input", "--confirm requires a digest");
44
+ }
45
+ else if (value === "--provider") {
46
+ options.provider = values[++index];
47
+ if (!options.provider)
48
+ throw new InstallerError("invalid_input", "--provider requires a value");
49
+ }
50
+ else if (value === "--model") {
51
+ options.model = values[++index];
52
+ if (!options.model)
53
+ throw new InstallerError("invalid_input", "--model requires a value");
54
+ }
55
+ else if (value === "--variant") {
56
+ options.variant = values[++index];
57
+ if (!options.variant)
58
+ throw new InstallerError("invalid_input", "--variant requires a value");
59
+ }
60
+ else if (value === "--clear-variant") {
61
+ if (options.variant !== undefined)
62
+ throw new InstallerError("invalid_input", "Use only one variant option");
63
+ options.variant = null;
64
+ }
65
+ else if (value === "--json") {
66
+ if (options.json)
67
+ throw new InstallerError("invalid_input", "--json may be supplied once");
68
+ options.json = true;
69
+ }
70
+ else if (["--commands", "--skill-commands", "--agents", "--plugins"].includes(value)) {
71
+ const raw = values[++index];
72
+ if (!raw)
73
+ throw new InstallerError("invalid_input", `${value} requires a comma-separated value`);
74
+ const target = value === "--agents" ? "agents" : value === "--plugins" ? "plugins" : "commands";
75
+ const names = raw === "none"
76
+ ? []
77
+ : raw
78
+ .split(",")
79
+ .map((item) => item.trim())
80
+ .filter(Boolean);
81
+ options.selectionFlag = true;
82
+ if (value === "--skill-commands") {
83
+ options.commands = [...(options.commands ?? []), ...names];
84
+ }
85
+ else {
86
+ options[target] = names;
87
+ }
88
+ }
89
+ else if (value === "--targets" || value === "--fragments") {
90
+ const raw = values[++index];
91
+ if (!raw)
92
+ throw new InstallerError("invalid_input", `${value} requires a comma-separated value or none`);
93
+ const names = raw === "none"
94
+ ? []
95
+ : raw
96
+ .split(",")
97
+ .map((item) => item.trim())
98
+ .filter(Boolean);
99
+ options.selectionFlag = true;
100
+ if (value === "--targets")
101
+ options.configTargets = names;
102
+ else
103
+ options.configFragments = names;
104
+ }
105
+ else {
106
+ throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
107
+ }
108
+ }
109
+ return options;
110
+ }
111
+ function helpRows(rows) {
112
+ const width = Math.max(...rows.map(([label]) => label.length));
113
+ return rows.map(([label, description]) => ` ${label.padEnd(width)} ${description}`);
114
+ }
115
+ function rootHelp() {
116
+ return [
117
+ `agentomatic ${CATALOG.version}`,
118
+ "",
119
+ "Manage OpenCode integration assets, agents, diagnostics, and migration state.",
120
+ `Portable Agent Skills are installed separately with npx --yes ${skillsInstallerSpec()}.`,
121
+ "",
122
+ "Usage:",
123
+ " agentomatic <command> [options]",
124
+ " Add --help after any command or command group for focused guidance.",
125
+ "",
126
+ "Commands:",
127
+ ...helpRows([
128
+ ["install", "Select and deploy skill commands, agents, and plugin wrappers."],
129
+ [
130
+ "uninstall",
131
+ "Archive and remove exact-owned assets while preserving conflicts and user files.",
132
+ ],
133
+ ["config", "Connect the package and recommended fragments into user configuration files."],
134
+ ["doctor", "Inspect versions, ownership, drift, config, archives, tools, and LSP."],
135
+ ["capabilities", "Print the versioned commands, agents, plugins, and tools catalog as JSON."],
136
+ ["reconcile", "Classify current and retired assets; archive exact-owned retired entries."],
137
+ ["agent list", "List profiles, models, ownership, collisions, and drift."],
138
+ ["agent configure", "Choose an agent model interactively or with explicit options."],
139
+ ["agent model-set", "Set one agent model directly without the interactive wizard."],
140
+ ["agent reconcile", "Re-render package-managed agents from saved profile configuration."],
141
+ ["critic add", "Add a named critic with a selected model."],
142
+ ["critic remove", "Remove a package-managed additional critic."],
143
+ ]),
144
+ "",
145
+ "Common options:",
146
+ ...helpRows([
147
+ ["--global", "Use global scope; project scope is the default."],
148
+ ["--dry-run", "Preview a mutation and issue a one-time confirmation digest."],
149
+ ["--confirm <digest>", "Apply the exact unexpired preview after final revalidation."],
150
+ ["--json", "Emit stable machine-readable output when supported."],
151
+ ["--help", "Show this help and exit."],
152
+ ["--version", "Show the package version and exit."],
153
+ ]),
154
+ "",
155
+ "Install selection:",
156
+ ...helpRows([
157
+ ["--commands <list|none>", "Select adapters for installed portable skills."],
158
+ ["--skill-commands <list|none>", "Select adapters for installed portable skills."],
159
+ ["--agents <list|none>", "Select fixed agents."],
160
+ ["--plugins <list|none>", "Select optional plugin wrappers; rtk is the default."],
161
+ ["--targets <list|none>", "Select config targets for the config command."],
162
+ ["--fragments <list|none>", "Select config fragments for the config command."],
163
+ ]),
164
+ " Lists are comma-separated. Outside a TTY, provide command selection, --agents, and --plugins;",
165
+ " the config command requires --targets and --fragments.",
166
+ "",
167
+ "Agent model options:",
168
+ ...helpRows([
169
+ ["--provider <id>", "Provider for a model name that is not provider/model."],
170
+ ["--model <id>", "Exact provider/model or a model paired with --provider."],
171
+ ["--variant <id>", "Set an optional model variant."],
172
+ ["--clear-variant", "Remove the configured variant during agent model-set."],
173
+ ]),
174
+ " Used by agent configure, agent model-set, and critic add.",
175
+ "",
176
+ "Safe mutation workflow:",
177
+ " 1. Run the command with --dry-run.",
178
+ " 2. Review the target, operations, conflicts, restart requirement, and expiry.",
179
+ " 3. Run the exact Apply command printed by the preview before it expires.",
180
+ " 4. Restart OpenCode when the applied plan requires it.",
181
+ " install and uninstall never edit opencode.json; the config command is the",
182
+ " confirmed path for configuration fragments. No command installs portable skills.",
183
+ "",
184
+ "Scope behavior:",
185
+ ...helpRows([
186
+ ["default", "Targets .opencode under the current directory; run from the project root."],
187
+ ["--global", "Targets ~/.config/opencode and can run from any directory."],
188
+ ]),
189
+ "",
190
+ "Examples:",
191
+ ` ${shellCommand(["capabilities", "--json"])}`,
192
+ ` ${shellCommand(["install", "--global", "--dry-run"])}`,
193
+ ` ${shellCommand(["doctor", "--global"])}`,
194
+ ` ${shellCommand([
195
+ "agent",
196
+ "model-set",
197
+ "worker",
198
+ "--global",
199
+ "--model",
200
+ "openai/gpt-5",
201
+ "--variant",
202
+ "high",
203
+ "--dry-run",
204
+ ])}`,
205
+ "",
206
+ "Documentation:",
207
+ " https://github.com/kisev/skills/blob/main/docs/how-to/opencode-integration.md",
208
+ "",
209
+ ].join("\n");
210
+ }
211
+ function commandHelp(topic, summary, usage, options, behavior, examples) {
212
+ return [
213
+ `agentomatic ${CATALOG.version} - ${topic}`,
214
+ "",
215
+ summary,
216
+ "",
217
+ "Usage:",
218
+ ...usage.map((line) => ` ${line}`),
219
+ "",
220
+ "Options:",
221
+ ...helpRows(options),
222
+ "",
223
+ "Behavior:",
224
+ ...behavior.map((line) => ` ${line}`),
225
+ "",
226
+ "Examples:",
227
+ ...examples.map((line) => ` ${line}`),
228
+ "",
229
+ ].join("\n");
230
+ }
231
+ function groupHelp(topic, summary, usage, commands, behavior, examples) {
232
+ return [
233
+ `agentomatic ${CATALOG.version} - ${topic}`,
234
+ "",
235
+ summary,
236
+ "",
237
+ "Usage:",
238
+ ` ${usage}`,
239
+ "",
240
+ "Commands:",
241
+ ...helpRows(commands),
242
+ "",
243
+ "Behavior:",
244
+ ...behavior.map((line) => ` ${line}`),
245
+ "",
246
+ "Examples:",
247
+ ...examples.map((line) => ` ${line}`),
248
+ "",
249
+ ].join("\n");
250
+ }
251
+ function contextualHelp(arguments_) {
252
+ const [domain, operation] = arguments_.filter((value) => value !== "--help");
253
+ if (domain === "install")
254
+ return commandHelp("install", "Select and deploy package-owned OpenCode integration assets.", [
255
+ "agentomatic install [--global] --dry-run [selection options]",
256
+ "agentomatic install [--global] --confirm <digest> [selection options]",
257
+ ], [
258
+ ["--global", "Use global scope; project scope is the default."],
259
+ ["--dry-run", "Preview operations and issue a one-time confirmation digest."],
260
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
261
+ ["--commands <list|none>", "Select installed-skill adapters."],
262
+ ["--skill-commands <list|none>", "Select installed-skill adapters."],
263
+ ["--agents <list|none>", "Select fixed agents."],
264
+ ["--plugins <list|none>", "Select optional plugin wrappers; rtk is preselected."],
265
+ ["--json", "Emit stable machine-readable output."],
266
+ ["--help", "Show this command help and exit."],
267
+ ], [
268
+ "Without selection flags, a TTY opens command, agent, and plugin selectors.",
269
+ "Use Up/Down to move, Space to toggle, A/N for all/none, and Enter to confirm.",
270
+ "Outside a TTY, provide command selection, --agents, and --plugins.",
271
+ "Writes occur only after confirmation; install never edits opencode.json.",
272
+ "Portable skills are installed separately; restart OpenCode after asset changes.",
273
+ ], [
274
+ shellCommand(["install", "--global", "--dry-run"]),
275
+ shellCommand([
276
+ "install",
277
+ "--commands",
278
+ "doctor,reconcile,agent-profiles",
279
+ "--agents",
280
+ "manager,architect,mapper,worker,review,critic",
281
+ "--plugins",
282
+ "none",
283
+ "--dry-run",
284
+ ]),
285
+ ]);
286
+ if (domain === "config")
287
+ return commandHelp("config", "Connect the package and recommended fragments into user configuration files.", [
288
+ "agentomatic config [--global] --dry-run [selection options]",
289
+ "agentomatic config [--global] --confirm <digest> [selection options]",
290
+ ], [
291
+ ["--global", "Use global scope; project scope is the default."],
292
+ ["--dry-run", "Preview config operations and issue a one-time confirmation digest."],
293
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
294
+ ["--targets <list|none>", `Select targets: ${CONFIG_TARGETS.join(", ")}.`],
295
+ [
296
+ "--fragments <list|none>",
297
+ `Select fragments: ${CONFIG_FRAGMENTS.map((item) => item.name).join(", ")}.`,
298
+ ],
299
+ ["--json", "Emit stable machine-readable output."],
300
+ ["--help", "Show this command help and exit."],
301
+ ], [
302
+ "In a TTY, target and fragment selectors open when flags are omitted.",
303
+ "Global scope edits opencode.json(c), tui.json, kilo.json(c), and mimocode.json(c).",
304
+ "Project scope edits the project opencode.json(c) file only.",
305
+ "Existing entries, comments, and unrelated keys are preserved; only absent keys are added.",
306
+ "Writes occur only after the exact confirmation; conflicting fragments are skipped as findings.",
307
+ ], [
308
+ shellCommand(["config", "--global", "--dry-run"]),
309
+ shellCommand([
310
+ "config",
311
+ "--global",
312
+ "--targets",
313
+ "opencode",
314
+ "--fragments",
315
+ "core-plugin,skills-state-permissions",
316
+ "--dry-run",
317
+ ]),
318
+ ]);
319
+ if (domain === "uninstall")
320
+ return commandHelp("uninstall", "Remove package-owned integration assets safely.", [
321
+ "agentomatic uninstall [--global] --dry-run",
322
+ "agentomatic uninstall [--global] --confirm <digest>",
323
+ ], [
324
+ ["--global", "Use global scope; project scope is the default."],
325
+ ["--dry-run", "Preview removals, archives, and conflicts."],
326
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
327
+ ["--json", "Emit stable machine-readable output."],
328
+ ["--help", "Show this command help and exit."],
329
+ ], [
330
+ "Archives and removes only exact manifest-owned assets after confirmation.",
331
+ "Preserves modified and user-owned files, worktrees, runtime state, and portable skills.",
332
+ "Remove the plugin config entry and npm dependency only after managed assets.",
333
+ ], [
334
+ shellCommand(["uninstall", "--global", "--dry-run"]),
335
+ shellCommand(["uninstall", "--dry-run"]),
336
+ ]);
337
+ if (domain === "doctor")
338
+ return commandHelp("doctor", "Inspect integration health without changing state.", ["agentomatic doctor [--global] [--json]"], [
339
+ ["--global", "Inspect global scope; project scope is the default."],
340
+ ["--json", "Emit the complete stable machine-readable report."],
341
+ ["--help", "Show this command help and exit."],
342
+ ], [
343
+ "Creates no receipts and starts no recovery, plugin factories, or LSP servers.",
344
+ "Reports versions, ownership, drift, collisions, config, archives, tools, and LSP facts.",
345
+ "Exit status: 0 clean, 1 findings, 2 invalid input or incomplete probing.",
346
+ ], [shellCommand(["doctor"]), shellCommand(["doctor", "--global", "--json"])]);
347
+ if (domain === "capabilities")
348
+ return commandHelp("capabilities", "Print the versioned package capability catalog.", ["agentomatic capabilities [--json]"], [
349
+ ["--json", "Explicitly request stable JSON output; JSON is the default."],
350
+ ["--help", "Show this command help and exit."],
351
+ ], [
352
+ "Requires no scope and performs no mutation.",
353
+ "Lists exact skills, commands, agents, plugins, and package tools.",
354
+ ], [shellCommand(["capabilities", "--json"])]);
355
+ if (domain === "reconcile")
356
+ return commandHelp("reconcile", "Classify current and historical assets and retire exact-owned entries.", [
357
+ "agentomatic reconcile [--global] --dry-run",
358
+ "agentomatic reconcile [--global] --confirm <digest>",
359
+ ], [
360
+ ["--global", "Reconcile global scope; project scope is the default."],
361
+ ["--dry-run", "Preview classifications, archives, and blockers."],
362
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
363
+ ["--json", "Emit stable machine-readable output."],
364
+ ["--help", "Show this command help and exit."],
365
+ ], [
366
+ "Preview is read-only and blocks Apply on modified managed files or ownership conflicts.",
367
+ "Confirmed reconcile archives exact-owned retired package assets.",
368
+ "Portable skills are managed separately by the skills CLI.",
369
+ ], [
370
+ shellCommand(["reconcile", "--global", "--dry-run"]),
371
+ shellCommand(["reconcile", "--dry-run", "--json"]),
372
+ ]);
373
+ if (domain === "agent" && operation === "list")
374
+ return commandHelp("agent list", "List agent profiles, models, ownership, collisions, and drift.", ["agentomatic agent list [--global] [--json]"], [
375
+ ["--global", "Inspect global scope; project scope is the default."],
376
+ ["--json", "Emit stable machine-readable inventory."],
377
+ ["--help", "Show this command help and exit."],
378
+ ], ["Read-only inventory; creates no receipts and changes no files or configuration."], [shellCommand(["agent", "list", "--global"]), shellCommand(["agent", "list", "--json"])]);
379
+ if (domain === "agent" && operation === "configure")
380
+ return commandHelp("agent configure", "Choose an agent model interactively or with explicit options.", [
381
+ "agentomatic agent configure [name] [--global] --dry-run",
382
+ "agentomatic agent configure <name> [--global] --model <provider/model> [--variant <id>] --dry-run",
383
+ "agentomatic agent configure <name> [--global] --model <provider/model> [--variant <id>] --confirm <digest>",
384
+ ], [
385
+ ["--global", "Use global scope; project scope is the default."],
386
+ ["--provider <id>", "Provider when --model is not provider/model."],
387
+ ["--model <id>", "Exact provider/model or model paired with --provider."],
388
+ ["--variant <id>", "Set an optional model variant."],
389
+ ["--clear-variant", "Remove the configured variant."],
390
+ ["--dry-run", "Preview the profile change."],
391
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
392
+ ["--json", "Emit stable machine-readable output."],
393
+ ["--help", "Show this command help and exit."],
394
+ ], [
395
+ "Opens a TTY wizard unless the agent and model are explicit.",
396
+ "Uses the cached OpenCode model catalog and makes no LLM call.",
397
+ "The preview normally prints an agent model-set Apply command.",
398
+ ], [
399
+ shellCommand(["agent", "configure", "manager", "--global", "--dry-run"]),
400
+ shellCommand([
401
+ "agent",
402
+ "configure",
403
+ "worker",
404
+ "--global",
405
+ "--model",
406
+ "openai/gpt-5",
407
+ "--variant",
408
+ "high",
409
+ "--dry-run",
410
+ ]),
411
+ ]);
412
+ if (domain === "agent" && operation === "model-set")
413
+ return commandHelp("agent model-set", "Set one agent model and optional variant directly.", [
414
+ "agentomatic agent model-set <name> [--global] --model <provider/model> [--variant <id>] --dry-run",
415
+ "agentomatic agent model-set <name> [--global] --model <provider/model> [--variant <id>] --confirm <digest>",
416
+ ], [
417
+ ["--global", "Use global scope; project scope is the default."],
418
+ ["--provider <id>", "Provider when --model is not provider/model."],
419
+ ["--model <id>", "Exact provider/model or model paired with --provider."],
420
+ ["--variant <id>", "Set an optional model variant."],
421
+ ["--clear-variant", "Remove the configured variant."],
422
+ ["--dry-run", "Preview the profile change."],
423
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
424
+ ["--json", "Emit stable machine-readable output."],
425
+ ["--help", "Show this command help and exit."],
426
+ ], [
427
+ "Accepts exact provider/model or a provider and model pair.",
428
+ "Keeps fixed prompts and permissions unchanged and makes no LLM call.",
429
+ "Writes only after exact confirmation.",
430
+ ], [
431
+ shellCommand([
432
+ "agent",
433
+ "model-set",
434
+ "worker",
435
+ "--global",
436
+ "--model",
437
+ "openai/gpt-5",
438
+ "--variant",
439
+ "high",
440
+ "--dry-run",
441
+ ]),
442
+ shellCommand([
443
+ "agent",
444
+ "model-set",
445
+ "manager",
446
+ "--global",
447
+ "--model",
448
+ "openai/gpt-5",
449
+ "--clear-variant",
450
+ "--dry-run",
451
+ ]),
452
+ ]);
453
+ if (domain === "agent" && operation === "reconcile")
454
+ return commandHelp("agent reconcile", "Re-render managed agent files from saved profile configuration.", [
455
+ "agentomatic agent reconcile [--global] --dry-run",
456
+ "agentomatic agent reconcile [--global] --confirm <digest>",
457
+ ], [
458
+ ["--global", "Use global scope; project scope is the default."],
459
+ ["--dry-run", "Preview rendered agent changes and conflicts."],
460
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
461
+ ["--json", "Emit stable machine-readable output."],
462
+ ["--help", "Show this command help and exit."],
463
+ ], [
464
+ "Uses saved models and variants while preserving package prompts and permissions.",
465
+ "Preserves user-owned files and collisions; writes only after confirmation.",
466
+ ], [
467
+ shellCommand(["agent", "reconcile", "--global", "--dry-run"]),
468
+ shellCommand(["agent", "reconcile", "--dry-run", "--json"]),
469
+ ]);
470
+ if (domain === "agent")
471
+ return groupHelp("agent", "Inspect and manage fixed agents and additional critics.", "agentomatic agent <command> [options]", [
472
+ ["list", "List profiles, models, ownership, collisions, and drift."],
473
+ ["configure", "Choose an agent model interactively or with explicit options."],
474
+ ["model-set", "Set one agent model directly without the interactive wizard."],
475
+ ["reconcile", "Re-render package-managed agents from saved profile configuration."],
476
+ ], [
477
+ "Prompts and permissions remain package-owned.",
478
+ "Profile configuration changes models, variants, and additional critics.",
479
+ "Mutations use preview and exact confirmation.",
480
+ ], [shellCommand(["agent", "list", "--global"]), shellCommand(["agent", "model-set", "--help"])]);
481
+ if (domain === "critic" && operation === "add")
482
+ return commandHelp("critic add", "Add an additional named critic with a selected model.", [
483
+ "agentomatic critic add [name] [--global] --dry-run",
484
+ "agentomatic critic add <name> [--global] --model <provider/model> [--variant <id>] --dry-run",
485
+ "agentomatic critic add <name> [--global] --model <provider/model> [--variant <id>] --confirm <digest>",
486
+ ], [
487
+ ["--global", "Use global scope; project scope is the default."],
488
+ ["--provider <id>", "Provider when --model is not provider/model."],
489
+ ["--model <id>", "Exact provider/model or model paired with --provider."],
490
+ ["--variant <id>", "Set an optional model variant."],
491
+ ["--dry-run", "Preview critic creation and pool changes."],
492
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
493
+ ["--json", "Emit stable machine-readable output."],
494
+ ["--help", "Show this command help and exit."],
495
+ ], [
496
+ "Prefixes names with critic- when needed.",
497
+ "Uses TTY model selection when the model is omitted and makes no LLM call.",
498
+ "Updates manager and review critic pools after confirmation.",
499
+ ], [
500
+ shellCommand([
501
+ "critic",
502
+ "add",
503
+ "security",
504
+ "--global",
505
+ "--model",
506
+ "anthropic/claude-sonnet-4-6",
507
+ "--dry-run",
508
+ ]),
509
+ shellCommand(["critic", "add", "performance", "--global", "--dry-run"]),
510
+ ]);
511
+ if (domain === "critic" && operation === "remove")
512
+ return commandHelp("critic remove", "Remove a package-managed additional critic.", [
513
+ "agentomatic critic remove <name> [--global] --dry-run",
514
+ "agentomatic critic remove <name> [--global] --confirm <digest>",
515
+ ], [
516
+ ["--global", "Use global scope; project scope is the default."],
517
+ ["--dry-run", "Preview critic removal and pool changes."],
518
+ ["--confirm <digest>", "Apply the exact unexpired preview."],
519
+ ["--json", "Emit stable machine-readable output."],
520
+ ["--help", "Show this command help and exit."],
521
+ ], [
522
+ "Accepts the name with or without the critic- prefix.",
523
+ "Preserves the fixed critic and user-owned files.",
524
+ "Updates manager and review critic pools after confirmation.",
525
+ ], [
526
+ shellCommand(["critic", "remove", "security", "--global", "--dry-run"]),
527
+ shellCommand(["critic", "remove", "security", "--dry-run"]),
528
+ ]);
529
+ if (domain === "critic")
530
+ return groupHelp("critic", "Manage additional package-owned critic profiles.", "agentomatic critic <command> [options]", [
531
+ ["add", "Add a named critic with a selected model."],
532
+ ["remove", "Remove a package-managed additional critic."],
533
+ ], [
534
+ "Names use critic-<safe-suffix>.",
535
+ "Manager and review critic pools update with profile configuration.",
536
+ "Mutations use preview and exact confirmation.",
537
+ ], [
538
+ shellCommand(["critic", "add", "security", "--help"]),
539
+ shellCommand(["critic", "remove", "security", "--help"]),
540
+ ]);
541
+ return undefined;
542
+ }
543
+ async function interactiveInstallerSelection() {
544
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
545
+ throw new InstallerError("terminal_required", "install requires explicit selection flags outside a terminal");
546
+ process.stderr.write([
547
+ "Portable skills are installed separately through npx skills.",
548
+ "This installer does not install, update, or remove portable skills.",
549
+ "Skill command adapters are OpenCode slash commands that load an already-installed skill with the same name.",
550
+ "Selecting a command adapter does not select or install its skill.",
551
+ "\n",
552
+ ].join("\n"));
553
+ const group = async (label, names, initialSelected) => {
554
+ const selected = await selectOptions(label, names, initialSelected, process.stdin, process.stderr);
555
+ if (selected === null)
556
+ throw new InstallerError("cancelled", "Wizard cancelled");
557
+ return selected;
558
+ };
559
+ const defaults = defaultSelection();
560
+ const commands = await group("Skill command adapters", SKILL_COMMANDS, SKILL_COMMANDS);
561
+ const agents = await group("Fixed agents", defaults.agents, defaults.agents);
562
+ const plugins = await group("Selectable plugins", SELECTABLE_PLUGINS, defaults.plugins);
563
+ return normalizeSelection({
564
+ commands,
565
+ agents: agents,
566
+ plugins: plugins,
567
+ });
568
+ }
569
+ function installerSelection(options) {
570
+ return normalizeSelection({
571
+ commands: options.commands,
572
+ agents: options.agents,
573
+ plugins: options.plugins,
574
+ });
575
+ }
576
+ function configSelectionFromOptions(options) {
577
+ return {
578
+ targets: options.configTargets,
579
+ fragments: options.configFragments,
580
+ };
581
+ }
582
+ function configSelectionArguments(selection) {
583
+ return [
584
+ "--targets",
585
+ selection.targets.join(",") || "none",
586
+ "--fragments",
587
+ selection.fragments.join(",") || "none",
588
+ ];
589
+ }
590
+ async function interactiveConfigSelection(options) {
591
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
592
+ throw new InstallerError("terminal_required", "config requires --targets and --fragments outside a terminal");
593
+ process.stderr.write([
594
+ "This command edits user-owned configuration files after an exact confirmation.",
595
+ "Global scope targets opencode.json(c), tui.json, kilo.json(c), and mimocode.json(c).",
596
+ "Existing entries, comments, and unrelated keys are preserved.",
597
+ "\n",
598
+ ].join("\n"));
599
+ const defaults = await defaultConfigSelection(options.scope);
600
+ const availableTargets = options.scope === "project" ? ["opencode"] : CONFIG_TARGETS;
601
+ const targets = await selectOptions("Config targets", [...availableTargets], defaults.targets.filter((target) => availableTargets.includes(target)), process.stdin, process.stderr);
602
+ if (targets === null)
603
+ throw new InstallerError("cancelled", "Wizard cancelled");
604
+ const availableFragments = CONFIG_FRAGMENTS.filter((fragment) => fragment.targets.some((target) => targets.includes(target))).map((fragment) => fragment.name);
605
+ if (!availableFragments.length)
606
+ return normalizeConfigSelection(options.scope, {
607
+ targets: targets,
608
+ fragments: [],
609
+ });
610
+ const fragments = await selectOptions("Config fragments", availableFragments, defaults.fragments.filter((fragment) => availableFragments.includes(fragment)), process.stdin, process.stderr);
611
+ if (fragments === null)
612
+ throw new InstallerError("cancelled", "Wizard cancelled");
613
+ return normalizeConfigSelection(options.scope, {
614
+ targets: targets,
615
+ fragments: fragments,
616
+ });
617
+ }
618
+ function selectionArguments(selection) {
619
+ return [
620
+ "--commands",
621
+ selection.commands.join(",") || "none",
622
+ "--agents",
623
+ selection.agents.join(",") || "none",
624
+ "--plugins",
625
+ selection.plugins.join(",") || "none",
626
+ ];
627
+ }
628
+ function requireConfirmationMode(options) {
629
+ if (options.dryRun === Boolean(options.confirm))
630
+ throw new InstallerError("invalid_input", "Use exactly one of --dry-run or --confirm <digest>");
631
+ }
632
+ function exactModel(options) {
633
+ if (!options.model)
634
+ return undefined;
635
+ if (options.model.includes("/")) {
636
+ const model = validateModel(options.model);
637
+ if (options.provider && model.split("/", 1)[0] !== options.provider)
638
+ throw new InstallerError("invalid_input", "--provider does not match the exact --model value");
639
+ return model;
640
+ }
641
+ if (!options.provider)
642
+ throw new InstallerError("invalid_input", "--provider is required when --model is not provider/model");
643
+ return validateModel(`${options.provider}/${options.model}`);
644
+ }
645
+ async function interactiveSelection(options, action = "model-set") {
646
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
647
+ throw new InstallerError("terminal_required", "agent configure requires a terminal or explicit agent <name> and either exact --model provider/model or --provider + --model");
648
+ }
649
+ const inventory = await listAgentProfiles(options.scope);
650
+ const configurable = inventory.profiles.filter((item) => item.ownership !== "user-owned");
651
+ let name;
652
+ if (options.name) {
653
+ name =
654
+ action === "critic-add" && !options.name.startsWith("critic-")
655
+ ? validateAgentName(`critic-${options.name}`)
656
+ : validateAgentName(options.name);
657
+ }
658
+ else if (action === "critic-add") {
659
+ const raw = await promptText("Critic name (critic-<suffix>):");
660
+ if (raw === null)
661
+ throw new InstallerError("cancelled", "Wizard cancelled");
662
+ name = raw.startsWith("critic-") ? raw : `critic-${raw}`;
663
+ validateAgentName(name);
664
+ }
665
+ else {
666
+ const agentNames = configurable.map((item) => item.name);
667
+ if (agentNames.length === 0)
668
+ agentNames.push(...FIXED_AGENT_ROLES);
669
+ const index = await selectOption("Select agent:", agentNames);
670
+ if (index === null)
671
+ throw new InstallerError("cancelled", "Wizard cancelled");
672
+ name = agentNames[index];
673
+ }
674
+ const currentProfile = configurable.find((item) => item.name === name);
675
+ const currentModel = currentProfile?.model;
676
+ const currentVariant = currentProfile?.variant;
677
+ const showTarget = (model, variant) => {
678
+ process.stderr.write(`Target: ${model}${variant ? ` / ${variant}` : ""}\n`);
679
+ };
680
+ if (options.provider && options.model) {
681
+ const model = exactModel(options);
682
+ const variant = validateVariant(options.variant);
683
+ return { name, model, ...(variant ? { variant } : {}) };
684
+ }
685
+ while (true) {
686
+ const actions = [];
687
+ if (currentModel) {
688
+ actions.push(`Keep current (${currentModel}${currentVariant ? ` / ${currentVariant}` : ""})`);
689
+ }
690
+ actions.push("Change model");
691
+ if (currentVariant)
692
+ actions.push("Clear variant");
693
+ if (!options.name && action === "model-set")
694
+ actions.push("Back");
695
+ actions.push("Cancel");
696
+ const currentLabel = `Agent: ${name}${currentModel ? ` (current: ${currentModel}${currentVariant ? ` / ${currentVariant}` : ""})` : ""}`;
697
+ const actionIndex = await selectOption(currentLabel, actions);
698
+ if (actionIndex === null)
699
+ throw new InstallerError("cancelled", "Wizard cancelled");
700
+ const chosen = actions[actionIndex];
701
+ if (chosen.startsWith("Keep current")) {
702
+ if (!currentModel)
703
+ throw new InstallerError("invalid_state", "No current model to keep");
704
+ showTarget(currentModel, currentVariant);
705
+ return { name, model: currentModel, ...(currentVariant ? { variant: currentVariant } : {}) };
706
+ }
707
+ if (chosen === "Change model") {
708
+ let models;
709
+ try {
710
+ models = await availableModels();
711
+ }
712
+ catch (error) {
713
+ if (error instanceof AgentProfileError && error.code === "catalog_unavailable") {
714
+ process.stderr.write("\nModel catalog is unavailable.\nUse direct CLI with exact --model provider/model and optional --variant.\n\n");
715
+ throw new InstallerError("catalog_unavailable", "Use direct CLI with exact --model provider/model");
716
+ }
717
+ throw error;
718
+ }
719
+ const providers = [...new Set(models.map((m) => m.split("/", 1)[0]))].sort();
720
+ const providerIndex = await selectOption("Select provider:", providers);
721
+ if (providerIndex === null)
722
+ continue;
723
+ const provider = providers[providerIndex];
724
+ const filteredModels = models.filter((m) => m.startsWith(`${provider}/`));
725
+ const modelIndex = await selectOption("Select model:", filteredModels);
726
+ if (modelIndex === null)
727
+ continue;
728
+ const selectedModel = filteredModels[modelIndex];
729
+ let selectedVariant;
730
+ try {
731
+ const variants = await availableModelVariants(selectedModel);
732
+ if (variants.length) {
733
+ const variantOptions = ["(none)", ...variants];
734
+ const variantIndex = await selectOption("Select variant:", variantOptions);
735
+ if (variantIndex === null)
736
+ continue;
737
+ if (variantIndex > 0)
738
+ selectedVariant = variantOptions[variantIndex];
739
+ }
740
+ }
741
+ catch (error) {
742
+ if (error instanceof AgentProfileError && error.code === "catalog_unavailable") {
743
+ process.stderr.write("\nModel variant metadata is unavailable.\nUse direct CLI with exact --model provider/model and optional --variant.\n\n");
744
+ throw new InstallerError("catalog_unavailable", "Use direct CLI with exact --model provider/model");
745
+ }
746
+ throw error;
747
+ }
748
+ showTarget(selectedModel, selectedVariant);
749
+ return {
750
+ name,
751
+ model: selectedModel,
752
+ ...(selectedVariant ? { variant: selectedVariant } : {}),
753
+ };
754
+ }
755
+ if (chosen === "Clear variant") {
756
+ if (!currentModel)
757
+ throw new InstallerError("invalid_state", "No model to clear variant for");
758
+ showTarget(currentModel);
759
+ return { name, model: currentModel };
760
+ }
761
+ if (chosen === "Back") {
762
+ return interactiveSelection({ ...options, name: undefined }, action);
763
+ }
764
+ if (chosen === "Cancel") {
765
+ throw new InstallerError("cancelled", "Wizard cancelled");
766
+ }
767
+ }
768
+ }
769
+ async function runProfile(request, options) {
770
+ requireConfirmationMode(options);
771
+ if (options.dryRun) {
772
+ const plan = await previewAgentProfileChange(request, options.scope);
773
+ if (options.json)
774
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan }, null, 2)}\n`);
775
+ else
776
+ process.stdout.write(renderPlan(plan, {
777
+ applied: false,
778
+ confirmationCommand: shellCommand(profileConfirmationArguments(request, options.scope, plan.digest)),
779
+ }));
780
+ }
781
+ else {
782
+ const applied = await applyAgentProfileChange(request, options.scope, options.confirm);
783
+ if (options.json)
784
+ process.stdout.write(`${JSON.stringify(applied, null, 2)}\n`);
785
+ else
786
+ process.stdout.write(renderPlan(applied.plan, { applied: true }));
787
+ }
788
+ }
789
+ function profileConfirmationArguments(request, scope, digest) {
790
+ if (request.action === "reconcile")
791
+ return ["agent", "reconcile", ...scopeArguments(scope), "--confirm", digest];
792
+ if (request.action === "critic-remove")
793
+ return ["critic", "remove", request.name, ...scopeArguments(scope), "--confirm", digest];
794
+ const command = request.action === "critic-add"
795
+ ? ["critic", "add", request.name]
796
+ : ["agent", "model-set", request.name];
797
+ command.push(...scopeArguments(scope), "--model", request.model);
798
+ if (request.variant)
799
+ command.push("--variant", request.variant);
800
+ else if (request.action === "model-set")
801
+ command.push("--clear-variant");
802
+ command.push("--confirm", digest);
803
+ return command;
804
+ }
805
+ async function run(arguments_) {
806
+ const removedScope = arguments_.find((value) => value === "--scope" || value.startsWith("--scope="));
807
+ if (removedScope)
808
+ throw new InstallerError("invalid_input", "--scope has been removed; omit it for project scope or use --global");
809
+ if (arguments_.filter((value) => value === "--global").length > 1)
810
+ throw new InstallerError("invalid_input", "--global may be supplied once");
811
+ if (arguments_[0] === "capabilities" && arguments_.includes("--global"))
812
+ throw new InstallerError("invalid_input", "capabilities accepts only --json");
813
+ if (arguments_.includes("--help")) {
814
+ const [domain, operation] = arguments_.filter((value) => !value.startsWith("--"));
815
+ if ((domain === "agent" &&
816
+ operation &&
817
+ !["list", "configure", "model-set", "reconcile"].includes(operation)) ||
818
+ (domain === "critic" && operation && !["add", "remove"].includes(operation)))
819
+ throw new InstallerError("invalid_input", `Unknown command: ${domain} ${operation}`);
820
+ const page = contextualHelp(arguments_);
821
+ if (!page && arguments_[0] !== "--help")
822
+ throw new InstallerError("invalid_input", `Unknown command: ${arguments_[0]}`);
823
+ for (const value of arguments_.filter((item) => item.startsWith("--") && item !== "--help"))
824
+ if (!(page ?? rootHelp()).includes(value))
825
+ throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
826
+ process.stdout.write(page ?? rootHelp());
827
+ return;
828
+ }
829
+ if (arguments_.length === 0) {
830
+ process.stdout.write(rootHelp());
831
+ return;
832
+ }
833
+ if (arguments_.includes("--version")) {
834
+ if (arguments_.some((value) => value !== "--version" && value !== "--json"))
835
+ throw new InstallerError("invalid_input", "--version accepts only --json");
836
+ process.stdout.write(arguments_.includes("--json")
837
+ ? `${JSON.stringify({ status: "ok", version: CATALOG.version })}\n`
838
+ : `${CATALOG.version}\n`);
839
+ return;
840
+ }
841
+ const [domain, operation, ...rest] = arguments_;
842
+ if (domain === "doctor") {
843
+ if (operation)
844
+ rest.unshift(operation);
845
+ const options = parseOptions(rest);
846
+ if (options.dryRun ||
847
+ options.confirm ||
848
+ options.name ||
849
+ options.provider ||
850
+ options.model ||
851
+ options.variant !== undefined ||
852
+ options.selectionFlag)
853
+ throw new InstallerError("invalid_input", "doctor accepts only --global and --json");
854
+ const report = await collectDoctorFacts(options.scope);
855
+ if (options.json)
856
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
857
+ else
858
+ process.stdout.write(renderDoctor(report));
859
+ process.exitCode = doctorExitCode(report);
860
+ return;
861
+ }
862
+ if (domain === "capabilities") {
863
+ if (operation)
864
+ rest.unshift(operation);
865
+ const options = parseOptions(rest);
866
+ if (options.dryRun ||
867
+ options.confirm ||
868
+ options.name ||
869
+ options.provider ||
870
+ options.model ||
871
+ options.variant !== undefined ||
872
+ options.global ||
873
+ options.selectionFlag)
874
+ throw new InstallerError("invalid_input", "capabilities accepts only --json");
875
+ process.stdout.write(`${JSON.stringify({ schema_version: 1, status: "ok", ...CATALOG }, null, 2)}\n`);
876
+ return;
877
+ }
878
+ if (domain === "reconcile") {
879
+ if (operation)
880
+ rest.unshift(operation);
881
+ const options = parseOptions(rest);
882
+ if (options.name ||
883
+ options.provider ||
884
+ options.model ||
885
+ options.variant !== undefined ||
886
+ options.selectionFlag)
887
+ throw new InstallerError("invalid_input", "reconcile accepts only --global, --dry-run, --confirm, and --json");
888
+ requireConfirmationMode(options);
889
+ if (options.dryRun) {
890
+ const plan = await previewReconcile(options.scope);
891
+ if (options.json)
892
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, plan }, null, 2)}\n`);
893
+ else
894
+ process.stdout.write(renderReconcile(plan, {
895
+ applied: false,
896
+ confirmationCommand: plan.confirmable
897
+ ? shellCommand([
898
+ "reconcile",
899
+ ...scopeArguments(options.scope),
900
+ "--confirm",
901
+ plan.confirmation_digest ?? plan.digest,
902
+ ])
903
+ : undefined,
904
+ }));
905
+ }
906
+ else {
907
+ const applied = await applyReconcile(options.scope, options.confirm);
908
+ if (options.json)
909
+ process.stdout.write(`${JSON.stringify(applied, null, 2)}\n`);
910
+ else
911
+ process.stdout.write(renderReconcile(applied.plan, { applied: true }));
912
+ }
913
+ return;
914
+ }
915
+ if (domain === "install" || domain === "uninstall") {
916
+ if (operation?.startsWith("--") || operation === undefined)
917
+ rest.unshift(...(operation ? [operation] : []));
918
+ else
919
+ throw new InstallerError("invalid_input", `Unexpected argument: ${operation}`);
920
+ const options = parseOptions(rest);
921
+ const action = domain;
922
+ if (options.name ||
923
+ options.provider ||
924
+ options.model ||
925
+ options.variant !== undefined ||
926
+ (action === "uninstall" && options.selectionFlag))
927
+ throw new InstallerError("invalid_input", action === "install"
928
+ ? "install does not accept model options or positional arguments"
929
+ : "uninstall accepts only --global, --dry-run, --confirm, and --json");
930
+ if (action === "install" &&
931
+ options.selectionFlag &&
932
+ (options.commands === undefined ||
933
+ options.agents === undefined ||
934
+ options.plugins === undefined))
935
+ throw new InstallerError("invalid_input", "Non-TTY install requires --commands, --agents, and --plugins");
936
+ const selection = action === "install"
937
+ ? options.selectionFlag
938
+ ? installerSelection(options)
939
+ : await interactiveInstallerSelection()
940
+ : undefined;
941
+ requireConfirmationMode(options);
942
+ if (options.dryRun) {
943
+ const plan = await preview(action, options.scope, process.cwd(), undefined, selection);
944
+ if (options.json)
945
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
946
+ else
947
+ process.stdout.write(renderPlan(plan, {
948
+ applied: false,
949
+ confirmationCommand: shellCommand([
950
+ action,
951
+ ...scopeArguments(options.scope),
952
+ ...(action === "install" ? selectionArguments(plan.selection) : []),
953
+ "--confirm",
954
+ plan.digest,
955
+ ]),
956
+ }));
957
+ }
958
+ else {
959
+ const plan = await apply(action, options.scope, options.confirm, process.cwd(), undefined, {}, selection);
960
+ if (options.json)
961
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
962
+ else
963
+ process.stdout.write(renderPlan(plan, {
964
+ applied: true,
965
+ ...(action === "install"
966
+ ? {
967
+ hint: `Connect the package into user configs: ${shellCommand(["config", ...scopeArguments(options.scope), "--dry-run"])}`,
968
+ }
969
+ : {}),
970
+ }));
971
+ }
972
+ return;
973
+ }
974
+ if (domain === "agent" && operation === "list") {
975
+ const options = parseOptions(rest);
976
+ if (options.dryRun ||
977
+ options.confirm ||
978
+ options.name ||
979
+ options.provider ||
980
+ options.model ||
981
+ options.variant !== undefined ||
982
+ options.selectionFlag)
983
+ throw new InstallerError("invalid_input", "agent list accepts only --global and --json");
984
+ const inventory = await listAgentProfiles(options.scope);
985
+ if (options.json)
986
+ process.stdout.write(`${JSON.stringify({ status: "ok", inventory }, null, 2)}\n`);
987
+ else
988
+ process.stdout.write(renderInventory(inventory));
989
+ return;
990
+ }
991
+ if (domain === "agent" && operation === "configure") {
992
+ const options = parseOptions(rest);
993
+ if (options.selectionFlag)
994
+ throw new InstallerError("invalid_input", "agent configure does not accept selection options");
995
+ requireConfirmationMode(options);
996
+ const model = exactModel(options);
997
+ const selected = options.name && model
998
+ ? {
999
+ name: validateAgentName(options.name),
1000
+ model,
1001
+ ...(validateVariant(options.variant)
1002
+ ? { variant: validateVariant(options.variant) }
1003
+ : {}),
1004
+ }
1005
+ : await interactiveSelection(options, "model-set");
1006
+ await runProfile({ action: "model-set", ...selected, variant: selected.variant ?? null }, options);
1007
+ return;
1008
+ }
1009
+ if (domain === "agent" && operation === "model-set") {
1010
+ const options = parseOptions(rest);
1011
+ if (options.selectionFlag)
1012
+ throw new InstallerError("invalid_input", "agent model-set does not accept selection options");
1013
+ const name = validateAgentName(options.name ?? "");
1014
+ const model = exactModel(options);
1015
+ if (!model)
1016
+ throw new InstallerError("invalid_input", "agent model-set requires --provider and --model, or exact --model provider/model");
1017
+ await runProfile({ action: "model-set", name, model, variant: options.variant ?? null }, options);
1018
+ return;
1019
+ }
1020
+ if (domain === "agent" && operation === "reconcile") {
1021
+ const options = parseOptions(rest);
1022
+ if (options.name ||
1023
+ options.provider ||
1024
+ options.model ||
1025
+ options.variant !== undefined ||
1026
+ options.selectionFlag)
1027
+ throw new InstallerError("invalid_input", "agent reconcile accepts only --global, --dry-run, --confirm, and --json");
1028
+ await runProfile({ action: "reconcile" }, options);
1029
+ return;
1030
+ }
1031
+ if (domain === "critic" && (operation === "add" || operation === "remove")) {
1032
+ const options = parseOptions(rest);
1033
+ if (options.selectionFlag)
1034
+ throw new InstallerError("invalid_input", `critic ${operation} does not accept selection options`);
1035
+ const name = options.name === "critic" || options.name?.startsWith("critic-")
1036
+ ? options.name
1037
+ : `critic-${options.name ?? ""}`;
1038
+ if (operation === "add") {
1039
+ if (options.variant === null)
1040
+ throw new InstallerError("invalid_input", "critic add does not accept --clear-variant");
1041
+ const model = exactModel(options);
1042
+ if (!model) {
1043
+ const selected = await interactiveSelection(options, "critic-add");
1044
+ await runProfile({
1045
+ action: "critic-add",
1046
+ name: selected.name,
1047
+ model: selected.model,
1048
+ variant: selected.variant ?? null,
1049
+ }, options);
1050
+ }
1051
+ else {
1052
+ await runProfile({ action: "critic-add", name, model, variant: options.variant ?? null }, options);
1053
+ }
1054
+ }
1055
+ else {
1056
+ if (options.provider ||
1057
+ options.model ||
1058
+ options.variant !== undefined ||
1059
+ options.selectionFlag)
1060
+ throw new InstallerError("invalid_input", "critic remove does not accept model options");
1061
+ await runProfile({ action: "critic-remove", name }, options);
1062
+ }
1063
+ return;
1064
+ }
1065
+ if (domain === "config") {
1066
+ if (operation?.startsWith("--") || operation === undefined)
1067
+ rest.unshift(...(operation ? [operation] : []));
1068
+ else
1069
+ throw new InstallerError("invalid_input", `Unexpected argument: ${operation}`);
1070
+ const options = parseOptions(rest);
1071
+ if (options.name ||
1072
+ options.provider ||
1073
+ options.model ||
1074
+ options.variant !== undefined ||
1075
+ options.commands !== undefined ||
1076
+ options.agents !== undefined ||
1077
+ options.plugins !== undefined)
1078
+ throw new InstallerError("invalid_input", "config accepts only --global, --targets, --fragments, --dry-run, --confirm, and --json");
1079
+ if (options.selectionFlag &&
1080
+ (options.configTargets === undefined || options.configFragments === undefined))
1081
+ throw new InstallerError("invalid_input", "Non-TTY config requires both --targets and --fragments");
1082
+ const selection = options.selectionFlag
1083
+ ? normalizeConfigSelection(options.scope, configSelectionFromOptions(options))
1084
+ : await interactiveConfigSelection(options);
1085
+ requireConfirmationMode(options);
1086
+ if (options.dryRun) {
1087
+ const plan = await previewConfigSetup(selection, options.scope);
1088
+ if (options.json)
1089
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, plan }, null, 2)}\n`);
1090
+ else
1091
+ process.stdout.write(renderConfigSetup(plan, {
1092
+ applied: false,
1093
+ ...(plan.confirmable
1094
+ ? {
1095
+ confirmationCommand: shellCommand([
1096
+ "config",
1097
+ ...scopeArguments(options.scope),
1098
+ ...configSelectionArguments(plan.selection),
1099
+ "--confirm",
1100
+ plan.confirmation_digest ?? plan.digest,
1101
+ ]),
1102
+ }
1103
+ : {}),
1104
+ }));
1105
+ }
1106
+ else {
1107
+ const plan = await applyConfigSetup(selection, options.scope, options.confirm);
1108
+ if (options.json)
1109
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
1110
+ else
1111
+ process.stdout.write(renderConfigSetup(plan, {
1112
+ applied: true,
1113
+ hint: `Restart OpenCode, Kilo Code, and MiMo Code when their configs changed.`,
1114
+ }));
1115
+ }
1116
+ return;
1117
+ }
1118
+ throw new InstallerError("invalid_input", "Use install, uninstall, doctor, capabilities, reconcile, config, agent list|configure|model-set|reconcile, or critic add|remove");
1119
+ }
1120
+ async function main() {
1121
+ try {
1122
+ await run(process.argv.slice(2));
1123
+ }
1124
+ catch (error) {
1125
+ const known = error instanceof LifecycleError
1126
+ ? error
1127
+ : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
1128
+ if (process.argv.includes("--json"))
1129
+ process.stdout.write(`${JSON.stringify({ status: "error", error: { code: known.code, message: known.message } })}\n`);
1130
+ else
1131
+ process.stderr.write(`Error [${known.code}]: ${terminalSafe(known.message)}\n`);
1132
+ process.exitCode = 2;
1133
+ }
1134
+ }
1135
+ void main();