@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.
- package/README.md +81 -11
- package/assets/agents/critic.md +14 -7
- package/assets/agents/manager.md +47 -18
- package/assets/agents/review.md +7 -4
- package/assets/commands/agent-list.md +9 -0
- package/assets/commands/agent-model-set.md +9 -0
- package/assets/commands/capabilities.md +1 -1
- package/assets/commands/critic-add.md +9 -0
- package/assets/commands/critic-remove.md +9 -0
- package/assets/commands/doctor.md +1 -1
- package/assets/commands/route.md +1 -1
- package/dist/agent-profiles.d.ts +122 -0
- package/dist/agent-profiles.js +776 -0
- package/dist/cli-output.d.ts +11 -0
- package/dist/cli-output.js +149 -0
- package/dist/cli.js +240 -31
- package/dist/generate-assets.js +33 -4
- package/dist/index.d.ts +76 -6
- package/dist/index.js +35 -4
- package/dist/installer.d.ts +7 -6
- package/dist/installer.js +203 -282
- package/dist/lifecycle.d.ts +58 -0
- package/dist/lifecycle.js +632 -0
- package/dist/plugins/background-attempts.d.ts +3 -3
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +10 -2
- package/dist/runtime/state.js +21 -39
- package/package.json +6 -2
|
@@ -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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
53
|
-
process.
|
|
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
|
}
|
package/dist/generate-assets.js
CHANGED
|
@@ -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
|
|
7
|
-
|
|
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
|
-
|
|
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: (
|
|
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: "
|
|
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: (
|
|
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: "
|
|
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;
|