@kisev/agentomatic 12.0.1-dev.118.geda2dc538b36 → 12.0.1-dev.120.g392563c8f5d9

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.
@@ -106,6 +106,7 @@ type BuiltPlan = {
106
106
  legacyTransferred: string[];
107
107
  };
108
108
  export declare function validateAgentName(name: string): string;
109
+ export declare function suggestCriticName(input: string): string | null;
109
110
  export declare function validateModel(model: string): string;
110
111
  export declare function validateVariant(variant: string | null | undefined): string | undefined;
111
112
  export declare function renderAgentProfile(name: string, config: AgentProfileConfig, canonical: Record<FixedAgentRole, Buffer>): Buffer;
@@ -60,7 +60,19 @@ function emptyConfig() {
60
60
  export function validateAgentName(name) {
61
61
  if (NAME_PATTERN.test(name) || CRITIC_PATTERN.test(name))
62
62
  return name;
63
- throw new AgentProfileError("invalid_name", "Agent must be a fixed role or critic-<safe-suffix>");
63
+ throw new AgentProfileError("invalid_name", "Agent must be a fixed role (manager, architect, mapper, worker, review, critic) or critic-<suffix> where suffix uses lowercase letters, digits, and hyphens between parts (for example critic-sonnet-5-5)");
64
+ }
65
+ // Convert free-form critic input such as "Sonnet 5.5" or "critic-sonnet-5.5" into a
66
+ // safe critic name; null when no safe form exists.
67
+ export function suggestCriticName(input) {
68
+ const cleaned = input.trim().toLowerCase();
69
+ if (CRITIC_PATTERN.test(cleaned))
70
+ return cleaned;
71
+ const body = cleaned
72
+ .replace(/^critic-/, "")
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-+|-+$/g, "");
75
+ return body ? `critic-${body}` : null;
64
76
  }
65
77
  export function validateModel(model) {
66
78
  if (!MODEL_PATTERN.test(model))
@@ -16,6 +16,7 @@ export declare function renderConfigSetup(plan: ConfigSetupPlan, options: {
16
16
  applyHint?: string;
17
17
  hint?: string;
18
18
  }): string;
19
+ export declare function renderCriticsTable(inventory: AgentInventory): string;
19
20
  export declare function renderInventory(inventory: AgentInventory): string;
20
21
  export declare function shellCommand(arguments_: readonly string[]): string;
21
22
  export declare function scopeArguments(scope: Scope): [] | ["--global"];
@@ -165,6 +165,21 @@ function table(rows) {
165
165
  .join(" ")
166
166
  .trimEnd());
167
167
  }
168
+ export function renderCriticsTable(inventory) {
169
+ const critics = inventory.profiles.filter((profile) => profile.name.startsWith("critic-") && profile.ownership !== "user-owned");
170
+ if (!critics.length)
171
+ return "Critics:\n No critics\n";
172
+ const rows = [
173
+ ["NAME", "MODEL", "VARIANT", "PROVIDER"],
174
+ ...critics.map((profile) => [
175
+ terminalSafe(profile.name),
176
+ terminalSafe(profile.model ?? "default"),
177
+ terminalSafe(profile.variant ?? "-"),
178
+ terminalSafe(profile.model?.split("/", 1)[0] ?? "-"),
179
+ ]),
180
+ ];
181
+ return `Critics:\n${table(rows).join("\n")}\n`;
182
+ }
168
183
  export function renderInventory(inventory) {
169
184
  const rows = [
170
185
  ["NAME", "MODEL", "VARIANT", "OWNER", "STATE"],
@@ -1,5 +1,5 @@
1
1
  import { Command, Option, common, applyConfig, configFileSync, reporter } from "./generated/cli.js";
2
- import { applyAgentProfileChange, previewAgentProfileChange, listAgentProfiles, availableModels, availableModelVariants, validateAgentName, validateModel, validateVariant, FIXED_AGENT_ROLES, } from "./agent-profiles.js";
2
+ import { applyAgentProfileChange, previewAgentProfileChange, listAgentProfiles, availableModels, availableModelVariants, validateAgentName, validateModel, validateVariant, suggestCriticName, FIXED_AGENT_ROLES, } from "./agent-profiles.js";
3
3
  import { apply, preview, installedSelection, defaultSelection, normalizeSelection, InstallerError, SKILL_COMMANDS, SELECTABLE_PLUGINS, } from "./installer.js";
4
4
  import { applyConfigSetup, previewConfigSetup, recoverConfigSetup, normalizeConfigSelection, defaultConfigSelection, CONFIG_TARGETS, CONFIG_FRAGMENTS, inspectIntegration, } from "./config-setup.js";
5
5
  import { collectDoctorFacts, doctorExitCode } from "./doctor.js";
@@ -9,7 +9,7 @@ import { skillsInstallerSpec } from "./package-metadata.js";
9
9
  import { LifecycleError } from "./lifecycle.js";
10
10
  import { previewDependencyRemoval, removeDependency, planDependency } from "./self-install.js";
11
11
  import { selectOption, selectOptions, promptText, confirmQuestion } from "./terminal-wizard.js";
12
- import { renderPlan, renderInventory, renderDoctor, renderConfigSetup, renderReconcile, terminalSafe, shellCommand, scopeArguments, } from "./cli-output.js";
12
+ import { renderPlan, renderInventory, renderCriticsTable, renderDoctor, renderConfigSetup, renderReconcile, terminalSafe, shellCommand, scopeArguments, } from "./cli-output.js";
13
13
  let diagnostics;
14
14
  let machineOutput = false;
15
15
  let diagnosticJSON = false;
@@ -27,12 +27,12 @@ const commands = {
27
27
  options: ["commands", "agents", "plugins", "core", "dependency", "targets", "fragments"],
28
28
  },
29
29
  "configure agent": {
30
- description: "Choose a model and variant, or supply --model provider/model directly.",
30
+ description: "Open a staged editor for every agent, or set one model with a name and --model provider/model.",
31
31
  options: ["provider", "model", "variant", "clearVariant"],
32
32
  name: true,
33
33
  },
34
34
  "configure critics": {
35
- description: "Add, configure, or remove additional critics interactively.",
35
+ description: "Alias of configure agent: staged agent models and critic management.",
36
36
  options: [],
37
37
  },
38
38
  "configure integration": {
@@ -104,6 +104,26 @@ function json(value) {
104
104
  function applyHint(args) {
105
105
  return `${shellCommand(args)} (confirm interactively; add --yes outside a terminal)`;
106
106
  }
107
+ // Prompt for a critic name, auto-converting unsafe input such as "sonnet-5.5" to
108
+ // "critic-sonnet-5-5" after explicit confirmation; exact safe names pass untouched.
109
+ async function promptCriticName() {
110
+ for (;;) {
111
+ const input = value(await promptText("Critic name (critic-<suffix>)"));
112
+ const suggestion = suggestCriticName(input);
113
+ if (suggestion !== null && suggestion === input.trim().toLowerCase())
114
+ return suggestion;
115
+ if (suggestion) {
116
+ const accept = value(await selectOption(`"${terminalSafe(input)}" is not a safe critic name`, [
117
+ `Use ${suggestion}`,
118
+ "Enter a different name",
119
+ ]));
120
+ if (accept === 0)
121
+ return suggestion;
122
+ continue;
123
+ }
124
+ process.stderr.write("Invalid critic name: use a fixed role or critic-<suffix> with lowercase letters, digits, and hyphens between parts (for example critic-sonnet-5-5).\n");
125
+ }
126
+ }
107
127
  function help(topic = "") {
108
128
  if (topic && !commands[topic])
109
129
  fail("invalid_input", `Unknown command: ${topic}`);
@@ -348,15 +368,18 @@ async function modelSelection(options, name, current) {
348
368
  const candidates = models.filter((model) => model.startsWith(`${provider}/`));
349
369
  const model = candidates[value(await selectOption("Model", candidates))];
350
370
  const variants = await availableModelVariants(model);
351
- const variant = variants.length
352
- ? [undefined, ...variants][value(await selectOption("Variant", ["(none)", ...variants]))]
353
- : undefined;
354
- return { action: "model-set", name, model, variant: variant ?? null };
371
+ let variant = null;
372
+ if (variants.length) {
373
+ const labels = variants.map((item) => item === current?.variant ? `${item} (default)` : item);
374
+ const choice = value(await selectOption("Variant", ["(none)", ...labels], undefined, undefined, current?.variant ? variants.indexOf(current.variant) + 1 : 0));
375
+ variant = choice === 0 ? null : (variants[choice - 1] ?? null);
376
+ }
377
+ return { action: "model-set", name, model, variant };
355
378
  }
356
379
  catch (error) {
357
380
  if (!(error instanceof LifecycleError && error.code === "catalog_unavailable"))
358
381
  throw error;
359
- process.stderr.write("Model catalog unavailable; enter an explicit model instead of a catalog pick.\n");
382
+ process.stderr.write("Start opencode in another terminal to browse models, or enter provider/model manually\n");
360
383
  const model = validateModel(value(await promptText("Model (provider/model)")));
361
384
  const answer = await promptText(current?.variant ? "Variant (empty keeps current)" : "Variant (optional)");
362
385
  const variant = (answer ? validateVariant(answer) : current?.variant) ?? null;
@@ -375,8 +398,7 @@ async function profileDraft(options, names) {
375
398
  if (action === "Done")
376
399
  return changes;
377
400
  if (action === "Add critic") {
378
- const suffix = value(await promptText("Critic name (critic-<suffix>)"));
379
- const name = validateAgentName(suffix.startsWith("critic-") ? suffix : `critic-${suffix}`);
401
+ const name = await promptCriticName();
380
402
  if (records.has(name))
381
403
  fail("profile_exists", `Profile already exists: ${name}`);
382
404
  const request = { ...(await modelSelection(options, name)), action: "critic-add" };
@@ -821,16 +843,10 @@ async function run(args) {
821
843
  const options = parse(topic, args);
822
844
  if (topic === "configure") {
823
845
  requireApply(options);
824
- const routes = [
825
- "configure components",
826
- "configure agent",
827
- "configure critics",
828
- "configure integration",
829
- ];
846
+ const routes = ["configure components", "configure agent", "configure integration"];
830
847
  const selected = value(await selectOption("What would you like to configure?", [
831
848
  "Installed components",
832
- "Agent models",
833
- "Additional critics",
849
+ "Agent models and critics",
834
850
  "Application connection and presets",
835
851
  ]));
836
852
  topic = routes[selected];
@@ -888,12 +904,32 @@ async function run(args) {
888
904
  process.exitCode = doctorExitCode(report);
889
905
  return;
890
906
  }
891
- if (topic === "configure agent" || topic === "agent add-critic") {
907
+ if (topic === "configure critics" ||
908
+ topic === "configure agent" ||
909
+ topic === "agent add-critic") {
892
910
  const inventory = await listAgentProfiles(options.scope);
911
+ // configure critics is an alias; a nameless configure agent opens the same staged editor.
912
+ if (topic === "configure critics" || (topic === "configure agent" && !options.name)) {
913
+ requireApply(options);
914
+ if (!options.json)
915
+ process.stdout.write(renderCriticsTable(inventory));
916
+ const changes = await profileDraft(options, inventory.profiles
917
+ .filter((profile) => profile.ownership !== "user-owned")
918
+ .map((profile) => profile.name));
919
+ if (!changes.length) {
920
+ if (options.json)
921
+ json({ status: "ok", applied: false });
922
+ else
923
+ process.stdout.write("No profile changes selected.\n");
924
+ return;
925
+ }
926
+ await profileChange(options, { action: "model-set", changes });
927
+ return;
928
+ }
893
929
  let name = options.name;
894
930
  if (!name) {
895
931
  if (topic === "agent add-critic")
896
- name = value(await promptText("Critic name (critic-<suffix>)"));
932
+ name = await promptCriticName();
897
933
  else {
898
934
  const names = inventory.profiles
899
935
  .filter((profile) => profile.ownership !== "user-owned")
@@ -916,23 +952,6 @@ async function run(args) {
916
952
  await profileChange(options, { action: "critic-remove", name: options.name });
917
953
  return;
918
954
  }
919
- if (topic === "configure critics") {
920
- requireApply(options);
921
- const inventory = await listAgentProfiles(options.scope);
922
- const changes = await profileDraft(options, inventory.profiles
923
- .filter((profile) => (profile.name === "critic" || profile.name.startsWith("critic-")) &&
924
- profile.ownership !== "user-owned")
925
- .map((profile) => profile.name));
926
- if (!changes.length) {
927
- if (options.json)
928
- json({ status: "ok", applied: false });
929
- else
930
- process.stdout.write("No profile changes selected.\n");
931
- return;
932
- }
933
- await profileChange(options, { action: "model-set", changes });
934
- return;
935
- }
936
955
  if (topic === "maintenance recover") {
937
956
  requireApply(options);
938
957
  const plan = await recoverConfigSetup(options.scope, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "12.0.1-dev.118.geda2dc538b36",
3
+ "version": "12.0.1-dev.120.g392563c8f5d9",
4
4
  "description": "OpenCode integration and opt-in installer for portable Agent Skills.",
5
5
  "homepage": "https://github.com/kisev/skills#readme",
6
6
  "bugs": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.8.1",
53
- "@kisev/safe-fs": "12.0.1-dev.118.geda2dc538b36",
53
+ "@kisev/safe-fs": "12.0.1-dev.120.g392563c8f5d9",
54
54
  "commander": "15.0.0",
55
55
  "zod": "4.1.8"
56
56
  },