@kisev/skills-opencode 1.0.0 → 1.1.1

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.
@@ -0,0 +1,11 @@
1
+ import type { AgentInventory, AgentProfilePlan } from "./agent-profiles.js";
2
+ import type { Plan as InstallerPlan } from "./installer.js";
3
+ type DisplayPlan = InstallerPlan | AgentProfilePlan;
4
+ export declare function terminalSafe(value: string): string;
5
+ export declare function renderPlan(plan: DisplayPlan, options: {
6
+ applied: boolean;
7
+ confirmationCommand?: string;
8
+ }): string;
9
+ export declare function renderInventory(inventory: AgentInventory): string;
10
+ export declare function shellCommand(arguments_: readonly string[]): string;
11
+ export {};
@@ -0,0 +1,149 @@
1
+ const GROUPS = ["Agents", "Commands", "Plugins", "State"];
2
+ const OPERATIONS = ["create", "update", "remove", "conflict", "missing", "unchanged"];
3
+ const UNSAFE_TERMINAL_CHARACTER = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
4
+ export function terminalSafe(value) {
5
+ return [...value]
6
+ .map((character) => {
7
+ const code = character.codePointAt(0);
8
+ const unsafe = character === "\\" || UNSAFE_TERMINAL_CHARACTER.test(character);
9
+ if (!unsafe)
10
+ return character;
11
+ if (character === "\\")
12
+ return "\\\\";
13
+ if (character === "\n")
14
+ return "\\n";
15
+ if (character === "\r")
16
+ return "\\r";
17
+ if (character === "\t")
18
+ return "\\t";
19
+ if (code <= 0xff)
20
+ return `\\x${code.toString(16).padStart(2, "0")}`;
21
+ if (code <= 0xffff)
22
+ return `\\u${code.toString(16).padStart(4, "0")}`;
23
+ return `\\u{${code.toString(16)}}`;
24
+ })
25
+ .join("");
26
+ }
27
+ function groupFor(path) {
28
+ if (path.startsWith("agents/"))
29
+ return "Agents";
30
+ if (path.startsWith("commands/"))
31
+ return "Commands";
32
+ if (path.startsWith("plugins/"))
33
+ return "Plugins";
34
+ return "State";
35
+ }
36
+ function actionName(action) {
37
+ return {
38
+ install: "Install",
39
+ uninstall: "Uninstall",
40
+ "model-set": "Configure agent model",
41
+ "critic-add": "Add critic",
42
+ "critic-remove": "Remove critic",
43
+ reconcile: "Reconcile agents",
44
+ }[action];
45
+ }
46
+ function operationSummary(operations) {
47
+ return OPERATIONS.map((operation) => {
48
+ const count = operations.filter((item) => item.operation === operation).length;
49
+ return count ? `${operation} ${count}` : undefined;
50
+ })
51
+ .filter(Boolean)
52
+ .join(", ");
53
+ }
54
+ function shortPath(path, group) {
55
+ if (group === "State")
56
+ return terminalSafe(path);
57
+ const value = path.slice(path.indexOf("/") + 1);
58
+ return terminalSafe(value.endsWith(".md") || value.endsWith(".js") ? value.slice(0, -3) : value);
59
+ }
60
+ function detailLines(operations) {
61
+ const lines = [];
62
+ for (const group of GROUPS) {
63
+ const grouped = operations.filter((item) => groupFor(item.path) === group && item.operation !== "unchanged");
64
+ for (const operation of OPERATIONS.filter((value) => value !== "unchanged" && grouped.some((item) => item.operation === value))) {
65
+ const values = grouped
66
+ .filter((item) => item.operation === operation)
67
+ .map((item) => operation === "conflict"
68
+ ? `${shortPath(item.path, group)} (${terminalSafe(item.reason ?? "conflict")})`
69
+ : shortPath(item.path, group));
70
+ const visible = operation === "conflict" ? values : values.slice(0, 8);
71
+ const rest = values.length - visible.length;
72
+ lines.push(` ${group}/${operation}: ${visible.join(", ")}${rest ? ` (+${rest} more)` : ""}`);
73
+ }
74
+ }
75
+ return lines;
76
+ }
77
+ function migrationSummary(operations) {
78
+ const count = operations.filter((item) => item.reason === "v1.0.0 ownership transfer").length;
79
+ return count
80
+ ? ` Ownership migration: ${count} agent${count === 1 ? "" : "s"} from v1.0.0`
81
+ : undefined;
82
+ }
83
+ export function renderPlan(plan, options) {
84
+ const version = "package_version" in plan ? ` @kisev/skills-opencode ${plan.package_version}` : "";
85
+ const lines = [
86
+ `${actionName(plan.action)}${version} (${plan.scope})`,
87
+ `Target: ${terminalSafe(plan.root)}`,
88
+ "",
89
+ options.applied ? "Applied changes:" : "Planned changes:",
90
+ ];
91
+ for (const group of GROUPS) {
92
+ const operations = plan.operations.filter((item) => groupFor(item.path) === group);
93
+ if (operations.length)
94
+ lines.push(` ${group}: ${operationSummary(operations)}`);
95
+ }
96
+ const migration = migrationSummary(plan.operations);
97
+ if (migration)
98
+ lines.push(migration);
99
+ const details = detailLines(plan.operations);
100
+ if (details.length)
101
+ lines.push("", "Details:", ...details);
102
+ const conflicts = plan.operations.filter((item) => item.operation === "conflict").length;
103
+ lines.push("", `Conflicts: ${conflicts || "none"}`);
104
+ lines.push(options.applied
105
+ ? `Restart required: ${plan.requires_restart ? "yes" : "no"}`
106
+ : `Restart after apply: ${plan.requires_restart ? "yes" : "no"}`);
107
+ if (!options.applied) {
108
+ if (plan.receipt_expires_at)
109
+ lines.push(`Confirmation expires: ${plan.receipt_expires_at}`);
110
+ lines.push(`Digest: ${plan.digest}`);
111
+ if (options.confirmationCommand)
112
+ lines.push("", "Apply:", ` ${options.confirmationCommand}`);
113
+ }
114
+ return `${lines.join("\n")}\n`;
115
+ }
116
+ function table(rows) {
117
+ const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column]?.length ?? 0)));
118
+ return rows.map((row) => row
119
+ .map((value, column) => value.padEnd(widths[column]))
120
+ .join(" ")
121
+ .trimEnd());
122
+ }
123
+ export function renderInventory(inventory) {
124
+ const rows = [
125
+ ["NAME", "MODEL", "VARIANT", "OWNER", "STATE"],
126
+ ...inventory.profiles.map((profile) => [
127
+ terminalSafe(profile.name),
128
+ terminalSafe(profile.model ?? "default"),
129
+ terminalSafe(profile.variant ?? "-"),
130
+ profile.ownership,
131
+ profile.state,
132
+ ]),
133
+ ];
134
+ return `${[
135
+ `OpenCode agents (${inventory.scope})`,
136
+ `Target: ${terminalSafe(inventory.root)}`,
137
+ "",
138
+ ...table(rows),
139
+ "",
140
+ `Critic pool: ${inventory.critic_pool.map(terminalSafe).join(", ")}`,
141
+ `Collisions: ${inventory.collisions.length ? inventory.collisions.map(terminalSafe).join(", ") : "none"}`,
142
+ `Drift: ${inventory.drift.length ? inventory.drift.map(terminalSafe).join(", ") : "none"}`,
143
+ ].join("\n")}\n`;
144
+ }
145
+ export function shellCommand(arguments_) {
146
+ return ["npm", "exec", "--", "skills-opencode", ...arguments_]
147
+ .map((value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`)
148
+ .join(" ");
149
+ }
package/dist/cli.js CHANGED
@@ -1,56 +1,265 @@
1
1
  #!/usr/bin/env node
2
- import { apply, InstallerError, preview, result } from "./installer.js";
3
- function parseArguments(arguments_) {
4
- const [action, ...rest] = arguments_;
5
- if (action !== "install" && action !== "uninstall")
6
- throw new InstallerError("invalid_input", "Use install or uninstall");
7
- let scope;
8
- let dryRun = false;
9
- let confirm;
10
- for (let index = 0; index < rest.length; index += 1) {
11
- const value = rest[index];
12
- if (value === "--scope") {
13
- const candidate = rest[++index];
14
- if (candidate !== "global" && candidate !== "project")
2
+ import { createInterface } from "node:readline/promises";
3
+ import { AgentProfileError, applyAgentProfileChange, availableModels, availableModelVariants, FIXED_AGENT_ROLES, listAgentProfiles, previewAgentProfileChange, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
4
+ import { renderInventory, renderPlan, shellCommand, terminalSafe } from "./cli-output.js";
5
+ import { apply, InstallerError, preview } from "./installer.js";
6
+ import { LifecycleError } from "./lifecycle.js";
7
+ function parseOptions(values) {
8
+ const options = { dryRun: false, json: false };
9
+ for (let index = 0; index < values.length; index += 1) {
10
+ const value = values[index];
11
+ if (!value.startsWith("--")) {
12
+ if (options.name)
13
+ throw new InstallerError("invalid_input", `Unexpected argument: ${value}`);
14
+ options.name = value;
15
+ }
16
+ else if (value === "--scope") {
17
+ const scope = values[++index];
18
+ if (scope !== "global" && scope !== "project")
15
19
  throw new InstallerError("invalid_input", "--scope must be global or project");
16
- if (scope)
20
+ if (options.scope)
17
21
  throw new InstallerError("invalid_input", "--scope may be supplied once");
18
- scope = candidate;
22
+ options.scope = scope;
19
23
  }
20
24
  else if (value === "--dry-run") {
21
- if (dryRun)
25
+ if (options.dryRun)
22
26
  throw new InstallerError("invalid_input", "--dry-run may be supplied once");
23
- dryRun = true;
27
+ options.dryRun = true;
24
28
  }
25
29
  else if (value === "--confirm") {
26
- if (confirm)
30
+ if (options.confirm)
27
31
  throw new InstallerError("invalid_input", "--confirm may be supplied once");
28
- confirm = rest[++index];
29
- if (!confirm)
32
+ options.confirm = values[++index];
33
+ if (!options.confirm)
30
34
  throw new InstallerError("invalid_input", "--confirm requires a digest");
31
35
  }
36
+ else if (value === "--provider") {
37
+ options.provider = values[++index];
38
+ if (!options.provider)
39
+ throw new InstallerError("invalid_input", "--provider requires a value");
40
+ }
41
+ else if (value === "--model") {
42
+ options.model = values[++index];
43
+ if (!options.model)
44
+ throw new InstallerError("invalid_input", "--model requires a value");
45
+ }
46
+ else if (value === "--variant") {
47
+ options.variant = values[++index];
48
+ if (!options.variant)
49
+ throw new InstallerError("invalid_input", "--variant requires a value");
50
+ }
51
+ else if (value === "--clear-variant") {
52
+ if (options.variant !== undefined)
53
+ throw new InstallerError("invalid_input", "Use only one variant option");
54
+ options.variant = null;
55
+ }
56
+ else if (value === "--json") {
57
+ if (options.json)
58
+ throw new InstallerError("invalid_input", "--json may be supplied once");
59
+ options.json = true;
60
+ }
32
61
  else {
33
62
  throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
34
63
  }
35
64
  }
36
- if (!scope)
65
+ if (!options.scope)
37
66
  throw new InstallerError("invalid_input", "--scope is required");
38
- if (dryRun === Boolean(confirm))
67
+ return options;
68
+ }
69
+ function requireConfirmationMode(options) {
70
+ if (options.dryRun === Boolean(options.confirm))
39
71
  throw new InstallerError("invalid_input", "Use exactly one of --dry-run or --confirm <digest>");
40
- return { action, scope, dryRun, confirm };
41
72
  }
42
- async function main() {
73
+ function exactModel(options) {
74
+ if (!options.model)
75
+ return undefined;
76
+ if (options.model.includes("/")) {
77
+ const model = validateModel(options.model);
78
+ if (options.provider && model.split("/", 1)[0] !== options.provider)
79
+ throw new InstallerError("invalid_input", "--provider does not match the exact --model value");
80
+ return model;
81
+ }
82
+ if (!options.provider)
83
+ throw new InstallerError("invalid_input", "--provider is required when --model is not provider/model");
84
+ return validateModel(`${options.provider}/${options.model}`);
85
+ }
86
+ async function choose(label, values, input) {
87
+ process.stderr.write(`${label}:\n${values.map((value, index) => ` ${index + 1}. ${value}`).join("\n")}\n`);
88
+ const answer = await input.question("> ");
89
+ const index = Number(answer) - 1;
90
+ if (!Number.isSafeInteger(index) || !values[index])
91
+ throw new InstallerError("invalid_input", `Invalid ${label.toLowerCase()} selection`);
92
+ return values[index];
93
+ }
94
+ async function interactiveSelection(options) {
95
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
96
+ throw new InstallerError("terminal_required", "agent configure requires a terminal or explicit --provider and --model");
97
+ const input = createInterface({ input: process.stdin, output: process.stderr });
43
98
  try {
44
- const parsed = parseArguments(process.argv.slice(2));
45
- if (parsed.dryRun) {
46
- process.stdout.write(`${result(await preview(parsed.action, parsed.scope), false)}\n`);
47
- return;
99
+ const inventory = await listAgentProfiles(options.scope);
100
+ const configurable = inventory.profiles.filter((item) => item.ownership !== "user-owned").map((item) => item.name);
101
+ const name = options.name ? validateAgentName(options.name) : await choose("Agent", configurable.length ? configurable : FIXED_AGENT_ROLES, input);
102
+ if (options.provider && options.model)
103
+ return { name, model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) };
104
+ let models;
105
+ try {
106
+ models = await availableModels();
107
+ }
108
+ catch (error) {
109
+ if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
110
+ throw error;
111
+ const provider = options.provider ?? (await input.question("Provider: ")).trim();
112
+ const model = options.model ?? (await input.question("Model: ")).trim();
113
+ const variant = options.variant === undefined ? (await input.question("Variant (optional): ")).trim() : options.variant;
114
+ return { name, model: validateModel(model.includes("/") ? model : `${provider}/${model}`), ...(validateVariant(variant) ? { variant: validateVariant(variant) } : {}) };
115
+ }
116
+ const providers = [...new Set(models.map((model) => model.split("/", 1)[0]))].sort();
117
+ const provider = options.provider ?? (await choose("Provider", providers, input));
118
+ const selectedModel = options.model
119
+ ? exactModel({ ...options, provider })
120
+ : await choose("Model", models.filter((model) => model.startsWith(`${provider}/`)), input);
121
+ let variants = [];
122
+ try {
123
+ variants = await availableModelVariants(selectedModel);
124
+ }
125
+ catch (error) {
126
+ if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
127
+ throw error;
128
+ }
129
+ const variant = options.variant === null
130
+ ? undefined
131
+ : options.variant ?? (variants.length ? await choose("Variant", ["none", ...variants.filter((value) => value !== "none")], input) : (await input.question("Variant (optional): ")).trim());
132
+ return { name, model: selectedModel, ...(variant && variant !== "none" ? { variant: validateVariant(variant) } : {}) };
133
+ }
134
+ finally {
135
+ input.close();
136
+ }
137
+ }
138
+ async function runProfile(request, options) {
139
+ requireConfirmationMode(options);
140
+ if (options.dryRun) {
141
+ const plan = await previewAgentProfileChange(request, options.scope);
142
+ if (options.json)
143
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan }, null, 2)}\n`);
144
+ else
145
+ process.stdout.write(renderPlan(plan, { applied: false, confirmationCommand: shellCommand(profileConfirmationArguments(request, options.scope, plan.digest)) }));
146
+ }
147
+ else {
148
+ const applied = await applyAgentProfileChange(request, options.scope, options.confirm);
149
+ if (options.json)
150
+ process.stdout.write(`${JSON.stringify(applied, null, 2)}\n`);
151
+ else
152
+ process.stdout.write(renderPlan(applied.plan, { applied: true }));
153
+ }
154
+ }
155
+ function profileConfirmationArguments(request, scope, digest) {
156
+ if (request.action === "reconcile")
157
+ return ["agent", "reconcile", "--scope", scope, "--confirm", digest];
158
+ if (request.action === "critic-remove")
159
+ return ["critic", "remove", request.name, "--scope", scope, "--confirm", digest];
160
+ const command = request.action === "critic-add" ? ["critic", "add", request.name] : ["agent", "model-set", request.name];
161
+ command.push("--scope", scope, "--model", request.model);
162
+ if (request.variant)
163
+ command.push("--variant", request.variant);
164
+ else if (request.action === "model-set")
165
+ command.push("--clear-variant");
166
+ command.push("--confirm", digest);
167
+ return command;
168
+ }
169
+ async function run(arguments_) {
170
+ const [domain, operation, ...rest] = arguments_;
171
+ if (domain === "install" || domain === "uninstall") {
172
+ if (operation?.startsWith("--") || operation === undefined)
173
+ rest.unshift(...(operation ? [operation] : []));
174
+ else
175
+ throw new InstallerError("invalid_input", `Unexpected argument: ${operation}`);
176
+ const options = parseOptions(rest);
177
+ if (options.name || options.provider || options.model || options.variant !== undefined)
178
+ throw new InstallerError("invalid_input", "Installer accepts only scope and confirmation options");
179
+ requireConfirmationMode(options);
180
+ const action = domain;
181
+ if (options.dryRun) {
182
+ const plan = await preview(action, options.scope);
183
+ if (options.json)
184
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan }, null, 2)}\n`);
185
+ else
186
+ process.stdout.write(renderPlan(plan, { applied: false, confirmationCommand: shellCommand([action, "--scope", options.scope, "--confirm", plan.digest]) }));
187
+ }
188
+ else {
189
+ const plan = await apply(action, options.scope, options.confirm);
190
+ if (options.json)
191
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
192
+ else
193
+ process.stdout.write(renderPlan(plan, { applied: true }));
194
+ }
195
+ return;
196
+ }
197
+ if (domain === "agent" && operation === "list") {
198
+ const options = parseOptions(rest);
199
+ if (options.dryRun || options.confirm || options.name || options.provider || options.model || options.variant !== undefined)
200
+ throw new InstallerError("invalid_input", "agent list accepts only --scope");
201
+ const inventory = await listAgentProfiles(options.scope);
202
+ if (options.json)
203
+ process.stdout.write(`${JSON.stringify({ status: "ok", inventory }, null, 2)}\n`);
204
+ else
205
+ process.stdout.write(renderInventory(inventory));
206
+ return;
207
+ }
208
+ if (domain === "agent" && operation === "configure") {
209
+ const options = parseOptions(rest);
210
+ requireConfirmationMode(options);
211
+ const selected = options.provider && options.model && options.name
212
+ ? { name: validateAgentName(options.name), model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) }
213
+ : await interactiveSelection(options);
214
+ await runProfile({ action: "model-set", ...selected, variant: selected.variant ?? null }, options);
215
+ return;
216
+ }
217
+ if (domain === "agent" && operation === "model-set") {
218
+ const options = parseOptions(rest);
219
+ const name = validateAgentName(options.name ?? "");
220
+ const model = exactModel(options);
221
+ if (!model)
222
+ throw new InstallerError("invalid_input", "agent model-set requires --provider and --model, or exact --model provider/model");
223
+ await runProfile({ action: "model-set", name, model, variant: options.variant ?? null }, options);
224
+ return;
225
+ }
226
+ if (domain === "agent" && operation === "reconcile") {
227
+ const options = parseOptions(rest);
228
+ if (options.name || options.provider || options.model || options.variant !== undefined)
229
+ throw new InstallerError("invalid_input", "agent reconcile accepts only scope and confirmation options");
230
+ await runProfile({ action: "reconcile" }, options);
231
+ return;
232
+ }
233
+ if (domain === "critic" && (operation === "add" || operation === "remove")) {
234
+ const options = parseOptions(rest);
235
+ const name = options.name === "critic" || options.name?.startsWith("critic-")
236
+ ? options.name
237
+ : `critic-${options.name ?? ""}`;
238
+ if (operation === "add") {
239
+ const model = exactModel(options);
240
+ if (!model)
241
+ throw new InstallerError("invalid_input", "critic add requires --provider and --model, or exact --model provider/model");
242
+ await runProfile({ action: "critic-add", name, model, variant: options.variant ?? null }, options);
243
+ }
244
+ else {
245
+ if (options.provider || options.model || options.variant !== undefined)
246
+ throw new InstallerError("invalid_input", "critic remove does not accept model options");
247
+ await runProfile({ action: "critic-remove", name }, options);
48
248
  }
49
- process.stdout.write(`${result(await apply(parsed.action, parsed.scope, parsed.confirm), true)}\n`);
249
+ return;
250
+ }
251
+ throw new InstallerError("invalid_input", "Use install, uninstall, agent list|configure|model-set|reconcile, or critic add|remove");
252
+ }
253
+ async function main() {
254
+ try {
255
+ await run(process.argv.slice(2));
50
256
  }
51
257
  catch (error) {
52
- const known = error instanceof InstallerError ? error : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
53
- process.stdout.write(`${JSON.stringify({ status: "error", error: { code: known.code, message: known.message } })}\n`);
258
+ const known = error instanceof LifecycleError ? error : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
259
+ if (process.argv.includes("--json"))
260
+ process.stdout.write(`${JSON.stringify({ status: "error", error: { code: known.code, message: known.message } })}\n`);
261
+ else
262
+ process.stderr.write(`Error [${known.code}]: ${terminalSafe(known.message)}\n`);
54
263
  process.exitCode = 2;
55
264
  }
56
265
  }
@@ -1,10 +1,39 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { COMMAND_REGISTRY, renderCommand } from "./registry.js";
5
5
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
6
- const commandsRoot = resolve(packageRoot, "assets", "commands");
7
- await mkdir(commandsRoot, { recursive: true });
6
+ const arguments_ = process.argv.slice(2);
7
+ const rootIndex = arguments_.indexOf("--root");
8
+ if (rootIndex !== -1 && !arguments_[rootIndex + 1])
9
+ throw new Error("--root requires a path");
10
+ const commandsRoot = rootIndex === -1
11
+ ? resolve(packageRoot, "assets", "commands")
12
+ : resolve(arguments_[rootIndex + 1] ?? "");
13
+ const check = arguments_.includes("--check");
14
+ const expectedNames = new Set(COMMAND_REGISTRY.map((command) => `${command.name}.md`));
15
+ if (!check)
16
+ await mkdir(commandsRoot, { recursive: true });
17
+ const entries = await readdir(commandsRoot, { withFileTypes: true }).catch(() => []);
18
+ for (const entry of entries) {
19
+ if (entry.isFile() && !expectedNames.has(entry.name)) {
20
+ if (check)
21
+ throw new Error(`unexpected generated asset: ${entry.name}`);
22
+ await unlink(resolve(commandsRoot, entry.name));
23
+ }
24
+ else if (!entry.isFile()) {
25
+ throw new Error(`unexpected entry in generated assets: ${entry.name}`);
26
+ }
27
+ }
8
28
  for (const command of COMMAND_REGISTRY) {
9
- await writeFile(resolve(commandsRoot, `${command.name}.md`), renderCommand(command), "utf8");
29
+ const destination = resolve(commandsRoot, `${command.name}.md`);
30
+ const expected = renderCommand(command);
31
+ if (check) {
32
+ const actual = await readFile(destination, "utf8").catch(() => "");
33
+ if (actual !== expected)
34
+ throw new Error(`generated asset drift: ${command.name}.md`);
35
+ }
36
+ else {
37
+ await writeFile(destination, expected, "utf8");
38
+ }
10
39
  }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ import zedBell, { type ZedBellOptions } from "./plugins/zed-bell.js";
8
8
  import zedClickablePaths, { type ZedClickablePathsOptions } from "./plugins/zed-clickable-paths.js";
9
9
  export { COMMAND_REGISTRY, renderCommand } from "./registry.js";
10
10
  export { CATEGORIES, resolveRouting, RoutingGate } from "./routing.js";
11
+ export { AgentProfileError, FIXED_AGENT_ROLES, applyAgentProfileChange, availableModels, availableModelVariants, listAgentProfiles, previewAgentProfileChange, renderAgentProfile, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
12
+ export type { AgentInventory, AgentModelSelection, AgentOwnership, AgentProfileAction, AgentProfileConfig, AgentProfileOperation, AgentProfilePlan, AgentProfileRecord, AgentProfileRequest, AgentProfileResult, AgentProfileScope, AgentState, DeploymentManifest, DeploymentRecord, FixedAgentRole, } from "./agent-profiles.js";
11
13
  export { backgroundAttempts, goalLoop, scheduler, autonomyPolicy, rulesInjector, rtk, zedBell, zedClickablePaths };
12
14
  export type OpenCodeOptions = {
13
15
  backgroundAttempts?: BackgroundAttemptsOptions;
@@ -19,7 +21,9 @@ export type OpenCodeOptions = {
19
21
  zedBell?: ZedBellOptions;
20
22
  zedClickablePaths?: ZedClickablePathsOptions;
21
23
  };
22
- declare const plugin: () => Promise<{
24
+ declare const plugin: (input: {
25
+ directory?: string;
26
+ }) => Promise<{
23
27
  tool: {
24
28
  route: {
25
29
  description: string;
@@ -29,10 +33,10 @@ declare const plugin: () => Promise<{
29
33
  dispatch: "dispatch";
30
34
  }>;
31
35
  category: import("zod").ZodEnum<{
36
+ review: "review";
32
37
  exploration: "exploration";
33
38
  architecture: "architecture";
34
39
  implementation: "implementation";
35
- review: "review";
36
40
  documentation: "documentation";
37
41
  quick: "quick";
38
42
  }>;
@@ -53,7 +57,7 @@ declare const plugin: () => Promise<{
53
57
  };
54
58
  execute(args: {
55
59
  action: "preview" | "dispatch";
56
- category: "exploration" | "architecture" | "implementation" | "review" | "documentation" | "quick";
60
+ category: "review" | "exploration" | "architecture" | "implementation" | "documentation" | "quick";
57
61
  task: string;
58
62
  requirements: string[];
59
63
  agents: {
@@ -80,6 +84,38 @@ declare const plugin: () => Promise<{
80
84
  args: {};
81
85
  execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
82
86
  };
87
+ agent_profiles: {
88
+ description: string;
89
+ args: {
90
+ action: import("zod").ZodEnum<{
91
+ list: "list";
92
+ model_set: "model_set";
93
+ critic_add: "critic_add";
94
+ critic_remove: "critic_remove";
95
+ }>;
96
+ phase: import("zod").ZodOptional<import("zod").ZodEnum<{
97
+ preview: "preview";
98
+ apply: "apply";
99
+ }>>;
100
+ scope: import("zod").ZodEnum<{
101
+ global: "global";
102
+ project: "project";
103
+ }>;
104
+ name: import("zod").ZodOptional<import("zod").ZodString>;
105
+ model: import("zod").ZodOptional<import("zod").ZodString>;
106
+ variant: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
107
+ confirmation_digest: import("zod").ZodOptional<import("zod").ZodString>;
108
+ };
109
+ execute(args: {
110
+ action: "list" | "model_set" | "critic_add" | "critic_remove";
111
+ scope: "global" | "project";
112
+ phase?: "preview" | "apply" | undefined;
113
+ name?: string | undefined;
114
+ model?: string | undefined;
115
+ variant?: string | null | undefined;
116
+ confirmation_digest?: string | undefined;
117
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
118
+ };
83
119
  };
84
120
  "tool.execute.before": (input: {
85
121
  tool: string;
@@ -88,7 +124,9 @@ declare const plugin: () => Promise<{
88
124
  args: unknown;
89
125
  }) => Promise<void>;
90
126
  }>;
91
- export declare const server: () => Promise<{
127
+ export declare const server: (input: {
128
+ directory?: string;
129
+ }) => Promise<{
92
130
  tool: {
93
131
  route: {
94
132
  description: string;
@@ -98,10 +136,10 @@ export declare const server: () => Promise<{
98
136
  dispatch: "dispatch";
99
137
  }>;
100
138
  category: import("zod").ZodEnum<{
139
+ review: "review";
101
140
  exploration: "exploration";
102
141
  architecture: "architecture";
103
142
  implementation: "implementation";
104
- review: "review";
105
143
  documentation: "documentation";
106
144
  quick: "quick";
107
145
  }>;
@@ -122,7 +160,7 @@ export declare const server: () => Promise<{
122
160
  };
123
161
  execute(args: {
124
162
  action: "preview" | "dispatch";
125
- category: "exploration" | "architecture" | "implementation" | "review" | "documentation" | "quick";
163
+ category: "review" | "exploration" | "architecture" | "implementation" | "documentation" | "quick";
126
164
  task: string;
127
165
  requirements: string[];
128
166
  agents: {
@@ -149,6 +187,38 @@ export declare const server: () => Promise<{
149
187
  args: {};
150
188
  execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
151
189
  };
190
+ agent_profiles: {
191
+ description: string;
192
+ args: {
193
+ action: import("zod").ZodEnum<{
194
+ list: "list";
195
+ model_set: "model_set";
196
+ critic_add: "critic_add";
197
+ critic_remove: "critic_remove";
198
+ }>;
199
+ phase: import("zod").ZodOptional<import("zod").ZodEnum<{
200
+ preview: "preview";
201
+ apply: "apply";
202
+ }>>;
203
+ scope: import("zod").ZodEnum<{
204
+ global: "global";
205
+ project: "project";
206
+ }>;
207
+ name: import("zod").ZodOptional<import("zod").ZodString>;
208
+ model: import("zod").ZodOptional<import("zod").ZodString>;
209
+ variant: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
210
+ confirmation_digest: import("zod").ZodOptional<import("zod").ZodString>;
211
+ };
212
+ execute(args: {
213
+ action: "list" | "model_set" | "critic_add" | "critic_remove";
214
+ scope: "global" | "project";
215
+ phase?: "preview" | "apply" | undefined;
216
+ name?: string | undefined;
217
+ model?: string | undefined;
218
+ variant?: string | null | undefined;
219
+ confirmation_digest?: string | undefined;
220
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
221
+ };
152
222
  };
153
223
  "tool.execute.before": (input: {
154
224
  tool: string;