@kisev/skills-opencode 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,55 +1,217 @@
1
1
  #!/usr/bin/env node
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";
2
4
  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")
5
+ import { LifecycleError } from "./lifecycle.js";
6
+ function parseOptions(values) {
7
+ const options = { dryRun: false };
8
+ for (let index = 0; index < values.length; index += 1) {
9
+ const value = values[index];
10
+ if (!value.startsWith("--")) {
11
+ if (options.name)
12
+ throw new InstallerError("invalid_input", `Unexpected argument: ${value}`);
13
+ options.name = value;
14
+ }
15
+ else if (value === "--scope") {
16
+ const scope = values[++index];
17
+ if (scope !== "global" && scope !== "project")
15
18
  throw new InstallerError("invalid_input", "--scope must be global or project");
16
- if (scope)
19
+ if (options.scope)
17
20
  throw new InstallerError("invalid_input", "--scope may be supplied once");
18
- scope = candidate;
21
+ options.scope = scope;
19
22
  }
20
23
  else if (value === "--dry-run") {
21
- if (dryRun)
24
+ if (options.dryRun)
22
25
  throw new InstallerError("invalid_input", "--dry-run may be supplied once");
23
- dryRun = true;
26
+ options.dryRun = true;
24
27
  }
25
28
  else if (value === "--confirm") {
26
- if (confirm)
29
+ if (options.confirm)
27
30
  throw new InstallerError("invalid_input", "--confirm may be supplied once");
28
- confirm = rest[++index];
29
- if (!confirm)
31
+ options.confirm = values[++index];
32
+ if (!options.confirm)
30
33
  throw new InstallerError("invalid_input", "--confirm requires a digest");
31
34
  }
35
+ else if (value === "--provider") {
36
+ options.provider = values[++index];
37
+ if (!options.provider)
38
+ throw new InstallerError("invalid_input", "--provider requires a value");
39
+ }
40
+ else if (value === "--model") {
41
+ options.model = values[++index];
42
+ if (!options.model)
43
+ throw new InstallerError("invalid_input", "--model requires a value");
44
+ }
45
+ else if (value === "--variant") {
46
+ options.variant = values[++index];
47
+ if (!options.variant)
48
+ throw new InstallerError("invalid_input", "--variant requires a value");
49
+ }
50
+ else if (value === "--clear-variant") {
51
+ if (options.variant !== undefined)
52
+ throw new InstallerError("invalid_input", "Use only one variant option");
53
+ options.variant = null;
54
+ }
32
55
  else {
33
56
  throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
34
57
  }
35
58
  }
36
- if (!scope)
59
+ if (!options.scope)
37
60
  throw new InstallerError("invalid_input", "--scope is required");
38
- if (dryRun === Boolean(confirm))
61
+ return options;
62
+ }
63
+ function requireConfirmationMode(options) {
64
+ if (options.dryRun === Boolean(options.confirm))
39
65
  throw new InstallerError("invalid_input", "Use exactly one of --dry-run or --confirm <digest>");
40
- return { action, scope, dryRun, confirm };
41
66
  }
42
- async function main() {
67
+ function exactModel(options) {
68
+ if (!options.model)
69
+ return undefined;
70
+ if (options.model.includes("/")) {
71
+ const model = validateModel(options.model);
72
+ if (options.provider && model.split("/", 1)[0] !== options.provider)
73
+ throw new InstallerError("invalid_input", "--provider does not match the exact --model value");
74
+ return model;
75
+ }
76
+ if (!options.provider)
77
+ throw new InstallerError("invalid_input", "--provider is required when --model is not provider/model");
78
+ return validateModel(`${options.provider}/${options.model}`);
79
+ }
80
+ async function choose(label, values, input) {
81
+ process.stderr.write(`${label}:\n${values.map((value, index) => ` ${index + 1}. ${value}`).join("\n")}\n`);
82
+ const answer = await input.question("> ");
83
+ const index = Number(answer) - 1;
84
+ if (!Number.isSafeInteger(index) || !values[index])
85
+ throw new InstallerError("invalid_input", `Invalid ${label.toLowerCase()} selection`);
86
+ return values[index];
87
+ }
88
+ async function interactiveSelection(options) {
89
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
90
+ throw new InstallerError("terminal_required", "agent configure requires a terminal or explicit --provider and --model");
91
+ const input = createInterface({ input: process.stdin, output: process.stderr });
43
92
  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;
93
+ const inventory = await listAgentProfiles(options.scope);
94
+ const configurable = inventory.profiles.filter((item) => item.ownership !== "user-owned").map((item) => item.name);
95
+ const name = options.name ? validateAgentName(options.name) : await choose("Agent", configurable.length ? configurable : FIXED_AGENT_ROLES, input);
96
+ if (options.provider && options.model)
97
+ return { name, model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) };
98
+ let models;
99
+ try {
100
+ models = await availableModels();
101
+ }
102
+ catch (error) {
103
+ if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
104
+ throw error;
105
+ const provider = options.provider ?? (await input.question("Provider: ")).trim();
106
+ const model = options.model ?? (await input.question("Model: ")).trim();
107
+ const variant = options.variant === undefined ? (await input.question("Variant (optional): ")).trim() : options.variant;
108
+ return { name, model: validateModel(model.includes("/") ? model : `${provider}/${model}`), ...(validateVariant(variant) ? { variant: validateVariant(variant) } : {}) };
109
+ }
110
+ const providers = [...new Set(models.map((model) => model.split("/", 1)[0]))].sort();
111
+ const provider = options.provider ?? (await choose("Provider", providers, input));
112
+ const selectedModel = options.model
113
+ ? exactModel({ ...options, provider })
114
+ : await choose("Model", models.filter((model) => model.startsWith(`${provider}/`)), input);
115
+ let variants = [];
116
+ try {
117
+ variants = await availableModelVariants(selectedModel);
48
118
  }
49
- process.stdout.write(`${result(await apply(parsed.action, parsed.scope, parsed.confirm), true)}\n`);
119
+ catch (error) {
120
+ if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
121
+ throw error;
122
+ }
123
+ const variant = options.variant === null
124
+ ? undefined
125
+ : options.variant ?? (variants.length ? await choose("Variant", ["none", ...variants.filter((value) => value !== "none")], input) : (await input.question("Variant (optional): ")).trim());
126
+ return { name, model: selectedModel, ...(variant && variant !== "none" ? { variant: validateVariant(variant) } : {}) };
127
+ }
128
+ finally {
129
+ input.close();
130
+ }
131
+ }
132
+ async function runProfile(request, options) {
133
+ requireConfirmationMode(options);
134
+ if (options.dryRun) {
135
+ const plan = await previewAgentProfileChange(request, options.scope);
136
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan }, null, 2)}\n`);
137
+ }
138
+ else {
139
+ process.stdout.write(`${JSON.stringify(await applyAgentProfileChange(request, options.scope, options.confirm), null, 2)}\n`);
140
+ }
141
+ }
142
+ async function run(arguments_) {
143
+ const [domain, operation, ...rest] = arguments_;
144
+ if (domain === "install" || domain === "uninstall") {
145
+ if (operation?.startsWith("--") || operation === undefined)
146
+ rest.unshift(...(operation ? [operation] : []));
147
+ else
148
+ throw new InstallerError("invalid_input", `Unexpected argument: ${operation}`);
149
+ const options = parseOptions(rest);
150
+ if (options.name || options.provider || options.model || options.variant !== undefined)
151
+ throw new InstallerError("invalid_input", "Installer accepts only scope and confirmation options");
152
+ requireConfirmationMode(options);
153
+ const action = domain;
154
+ process.stdout.write(`${result(options.dryRun ? await preview(action, options.scope) : await apply(action, options.scope, options.confirm), !options.dryRun)}\n`);
155
+ return;
156
+ }
157
+ if (domain === "agent" && operation === "list") {
158
+ const options = parseOptions(rest);
159
+ if (options.dryRun || options.confirm || options.name || options.provider || options.model || options.variant !== undefined)
160
+ throw new InstallerError("invalid_input", "agent list accepts only --scope");
161
+ process.stdout.write(`${JSON.stringify({ status: "ok", inventory: await listAgentProfiles(options.scope) }, null, 2)}\n`);
162
+ return;
163
+ }
164
+ if (domain === "agent" && operation === "configure") {
165
+ const options = parseOptions(rest);
166
+ requireConfirmationMode(options);
167
+ const selected = options.provider && options.model && options.name
168
+ ? { name: validateAgentName(options.name), model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) }
169
+ : await interactiveSelection(options);
170
+ await runProfile({ action: "model-set", ...selected, variant: selected.variant ?? null }, options);
171
+ return;
172
+ }
173
+ if (domain === "agent" && operation === "model-set") {
174
+ const options = parseOptions(rest);
175
+ const name = validateAgentName(options.name ?? "");
176
+ const model = exactModel(options);
177
+ if (!model)
178
+ throw new InstallerError("invalid_input", "agent model-set requires --provider and --model, or exact --model provider/model");
179
+ await runProfile({ action: "model-set", name, model, variant: options.variant ?? null }, options);
180
+ return;
181
+ }
182
+ if (domain === "agent" && operation === "reconcile") {
183
+ const options = parseOptions(rest);
184
+ if (options.name || options.provider || options.model || options.variant !== undefined)
185
+ throw new InstallerError("invalid_input", "agent reconcile accepts only scope and confirmation options");
186
+ await runProfile({ action: "reconcile" }, options);
187
+ return;
188
+ }
189
+ if (domain === "critic" && (operation === "add" || operation === "remove")) {
190
+ const options = parseOptions(rest);
191
+ const name = options.name === "critic" || options.name?.startsWith("critic-")
192
+ ? options.name
193
+ : `critic-${options.name ?? ""}`;
194
+ if (operation === "add") {
195
+ const model = exactModel(options);
196
+ if (!model)
197
+ throw new InstallerError("invalid_input", "critic add requires --provider and --model, or exact --model provider/model");
198
+ await runProfile({ action: "critic-add", name, model, variant: options.variant ?? null }, options);
199
+ }
200
+ else {
201
+ if (options.provider || options.model || options.variant !== undefined)
202
+ throw new InstallerError("invalid_input", "critic remove does not accept model options");
203
+ await runProfile({ action: "critic-remove", name }, options);
204
+ }
205
+ return;
206
+ }
207
+ throw new InstallerError("invalid_input", "Use install, uninstall, agent list|configure|model-set|reconcile, or critic add|remove");
208
+ }
209
+ async function main() {
210
+ try {
211
+ await run(process.argv.slice(2));
50
212
  }
51
213
  catch (error) {
52
- const known = error instanceof InstallerError ? error : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
214
+ const known = error instanceof LifecycleError ? error : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
53
215
  process.stdout.write(`${JSON.stringify({ status: "error", error: { code: known.code, message: known.message } })}\n`);
54
216
  process.exitCode = 2;
55
217
  }
@@ -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;
package/dist/index.js CHANGED
@@ -8,16 +8,18 @@ import rulesInjector from "./plugins/rules-injector.js";
8
8
  import rtk from "./plugins/rtk.js";
9
9
  import zedBell from "./plugins/zed-bell.js";
10
10
  import zedClickablePaths from "./plugins/zed-clickable-paths.js";
11
+ import { applyAgentProfileChange, listAgentProfiles, previewAgentProfileChange, } from "./agent-profiles.js";
11
12
  export { COMMAND_REGISTRY, renderCommand } from "./registry.js";
12
13
  export { CATEGORIES, resolveRouting, RoutingGate } from "./routing.js";
14
+ export { AgentProfileError, FIXED_AGENT_ROLES, applyAgentProfileChange, availableModels, availableModelVariants, listAgentProfiles, previewAgentProfileChange, renderAgentProfile, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
13
15
  export { backgroundAttempts, goalLoop, scheduler, autonomyPolicy, rulesInjector, rtk, zedBell, zedClickablePaths };
14
16
  const CATALOG = {
15
17
  skills: ["attempt", "goal", "schedule", "multi-run", "usage", "overview", "lsp-report"],
16
18
  plugins: ["background-attempts", "goal-loop", "schedule", "autonomy-policy", "rules-injector", "rtk", "zed-bell", "zed-clickable-paths"],
17
- replacements: ["capabilities", "route", "doctor"],
18
- version: "1.0.0",
19
+ replacements: ["capabilities", "route", "doctor", "agent_profiles"],
20
+ version: "1.1.0",
19
21
  };
20
- const plugin = (async () => {
22
+ const plugin = (async (input) => {
21
23
  const gate = new RoutingGate();
22
24
  const route = tool({
23
25
  description: "Resolve a capability category and dispatch one eligible agent through a one-use Task receipt gate.",
@@ -52,8 +54,37 @@ const plugin = (async () => {
52
54
  return JSON.stringify({ schema_version: 1, status: "ok", package: "@kisev/skills-opencode", opencode: ">=1.18.29", state: "not-inspected", mutations: false, defaults: { backgroundAttempts: false, goalLoop: false, scheduler: false, autonomyPolicy: false, zedBell: false, zedClickablePaths: false } });
53
55
  },
54
56
  });
57
+ const agentProfiles = tool({
58
+ description: "List, preview, or apply package-owned OpenCode agent profile configuration without editing opencode.json.",
59
+ args: {
60
+ action: tool.schema.enum(["list", "model_set", "critic_add", "critic_remove"]),
61
+ phase: tool.schema.enum(["preview", "apply"]).optional(),
62
+ scope: tool.schema.enum(["global", "project"]),
63
+ name: tool.schema.string().optional(),
64
+ model: tool.schema.string().optional(),
65
+ variant: tool.schema.string().nullable().optional(),
66
+ confirmation_digest: tool.schema.string().optional(),
67
+ },
68
+ async execute(args) {
69
+ const cwd = input.directory ?? process.cwd();
70
+ if (args.action === "list") {
71
+ if (args.phase || args.name || args.model || args.variant !== undefined || args.confirmation_digest)
72
+ throw new Error("agent_profiles list accepts only scope");
73
+ return JSON.stringify({ status: "ok", inventory: await listAgentProfiles(args.scope, cwd) });
74
+ }
75
+ if (!args.phase)
76
+ throw new Error("agent_profiles mutation requires preview or apply phase");
77
+ const action = { model_set: "model-set", critic_add: "critic-add", critic_remove: "critic-remove" }[args.action];
78
+ const request = { action, ...(args.name ? { name: args.name } : {}), ...(args.model ? { model: args.model } : {}), ...(args.variant !== undefined ? { variant: args.variant } : {}) };
79
+ if (args.phase === "preview")
80
+ return JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan: await previewAgentProfileChange(request, args.scope, cwd) });
81
+ if (!args.confirmation_digest)
82
+ throw new Error("agent_profiles apply requires confirmation_digest");
83
+ return JSON.stringify(await applyAgentProfileChange(request, args.scope, args.confirmation_digest, cwd));
84
+ },
85
+ });
55
86
  return {
56
- tool: { route, capabilities, doctor },
87
+ tool: { route, capabilities, doctor, agent_profiles: agentProfiles },
57
88
  "tool.execute.before": async (input, output) => {
58
89
  if (input.tool !== "task")
59
90
  return;
@@ -1,4 +1,5 @@
1
- export type Scope = "global" | "project";
1
+ import { LifecycleError, type Scope, type TransactionOptions } from "./lifecycle.js";
2
+ export type { Scope } from "./lifecycle.js";
2
3
  export type Action = "install" | "uninstall";
3
4
  export type Operation = "create" | "update" | "remove" | "unchanged" | "missing" | "conflict";
4
5
  export type PlanItem = {
@@ -12,14 +13,14 @@ export type Plan = {
12
13
  action: Action;
13
14
  scope: Scope;
14
15
  root: string;
16
+ package_version: string;
15
17
  operations: PlanItem[];
16
- manifest_sha256?: string;
17
18
  digest: string;
19
+ receipt_expires_at?: string;
20
+ requires_restart: boolean;
18
21
  };
19
- export declare class InstallerError extends Error {
20
- readonly code: string;
21
- constructor(code: string, message: string);
22
+ export declare class InstallerError extends LifecycleError {
22
23
  }
23
24
  export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string): Promise<Plan>;
24
- export declare function apply(action: Action, scope: Scope, digest: string, cwd?: string, home?: string): Promise<Plan>;
25
+ export declare function apply(action: Action, scope: Scope, confirmationDigest: string, cwd?: string, home?: string, options?: TransactionOptions): Promise<Plan>;
25
26
  export declare function result(plan: Plan, applied: boolean): string;