@kisev/skills-opencode 1.1.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 +20 -9
- package/dist/cli-output.d.ts +11 -0
- package/dist/cli-output.js +149 -0
- package/dist/cli.js +54 -7
- package/dist/index.js +1 -1
- package/dist/installer.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npx --yes skills add kisev/skills --agent opencode --skill '*' --copy --yes
|
|
|
16
16
|
|
|
17
17
|
Для одного skill укажите `--skill <name>`. Для воспроизводимой установки можно
|
|
18
18
|
передать URL GitHub tag, например
|
|
19
|
-
`https://github.com/kisev/skills/tree/v1.1.
|
|
19
|
+
`https://github.com/kisev/skills/tree/v1.1.1`. Package никогда не устанавливает
|
|
20
20
|
и не обновляет skills. Если команда не нашла skill, она сообщает точную команду
|
|
21
21
|
`npx skills add` для его установки.
|
|
22
22
|
|
|
@@ -25,16 +25,18 @@ npx --yes skills add kisev/skills --agent opencode --skill '*' --copy --yes
|
|
|
25
25
|
Установите npm package там, где OpenCode сможет разрешить plugin:
|
|
26
26
|
|
|
27
27
|
```shell
|
|
28
|
-
npm install @kisev/skills-opencode@1.1.
|
|
28
|
+
npm install @kisev/skills-opencode@1.1.1
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Сначала покажите план installer.
|
|
31
|
+
Сначала покажите план installer. По умолчанию CLI выводит короткую сводку:
|
|
32
|
+
счётчики по группам, только изменяемые paths, conflicts, restart flag, digest и
|
|
33
|
+
готовую confirm-команду. Эта команда не меняет deployment:
|
|
32
34
|
|
|
33
35
|
```shell
|
|
34
36
|
npm exec -- skills-opencode install --scope global --dry-run
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
Проверьте
|
|
39
|
+
Проверьте сводку и примените только показанный digest:
|
|
38
40
|
|
|
39
41
|
```shell
|
|
40
42
|
npm exec -- skills-opencode install --scope global --confirm <digest>
|
|
@@ -54,6 +56,14 @@ Project assets находятся в `.opencode/agents`, `.opencode/commands` и
|
|
|
54
56
|
перезаписывает неизвестные или изменённые files и сохраняет ownership manifests
|
|
55
57
|
только после confirmed apply.
|
|
56
58
|
|
|
59
|
+
Для automation добавьте `--json`. Этот режим сохраняет полный стабильный
|
|
60
|
+
machine-readable plan, включая `operations` и `requires_restart`:
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
npm exec -- skills-opencode install --scope global --dry-run --json
|
|
64
|
+
npm exec -- skills-opencode agent list --scope global --json
|
|
65
|
+
```
|
|
66
|
+
|
|
57
67
|
Добавьте plugin в `opencode.json` вручную:
|
|
58
68
|
|
|
59
69
|
```json
|
|
@@ -95,11 +105,12 @@ npm exec -- skills-opencode critic add security --scope global \
|
|
|
95
105
|
npm exec -- skills-opencode critic remove security --scope global --dry-run
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
Для любой mutation
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
Для любой mutation используйте готовую confirm-команду из preview либо замените
|
|
109
|
+
`--dry-run` на `--confirm <digest>` и повторите те же аргументы. Человекочитаемый
|
|
110
|
+
plan не печатает полный JSON и сворачивает длинные группы paths. Digest связан с
|
|
111
|
+
одноразовым private receipt, действует 10 минут и повторно не применяется. Для
|
|
112
|
+
machine-readable result добавьте `--json`; поле `requires_restart` сообщает о
|
|
113
|
+
необходимости полностью перезапустить OpenCode.
|
|
103
114
|
|
|
104
115
|
В global scope profile configuration хранится в
|
|
105
116
|
`~/.config/opencode/.skills-opencode/agent-profiles.json`, а semantic deployment
|
|
@@ -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,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { AgentProfileError, applyAgentProfileChange, availableModels, availableModelVariants, FIXED_AGENT_ROLES, listAgentProfiles, previewAgentProfileChange, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
|
|
4
|
-
import {
|
|
4
|
+
import { renderInventory, renderPlan, shellCommand, terminalSafe } from "./cli-output.js";
|
|
5
|
+
import { apply, InstallerError, preview } from "./installer.js";
|
|
5
6
|
import { LifecycleError } from "./lifecycle.js";
|
|
6
7
|
function parseOptions(values) {
|
|
7
|
-
const options = { dryRun: false };
|
|
8
|
+
const options = { dryRun: false, json: false };
|
|
8
9
|
for (let index = 0; index < values.length; index += 1) {
|
|
9
10
|
const value = values[index];
|
|
10
11
|
if (!value.startsWith("--")) {
|
|
@@ -52,6 +53,11 @@ function parseOptions(values) {
|
|
|
52
53
|
throw new InstallerError("invalid_input", "Use only one variant option");
|
|
53
54
|
options.variant = null;
|
|
54
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
|
+
}
|
|
55
61
|
else {
|
|
56
62
|
throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
|
|
57
63
|
}
|
|
@@ -133,12 +139,33 @@ async function runProfile(request, options) {
|
|
|
133
139
|
requireConfirmationMode(options);
|
|
134
140
|
if (options.dryRun) {
|
|
135
141
|
const plan = await previewAgentProfileChange(request, options.scope);
|
|
136
|
-
|
|
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)) }));
|
|
137
146
|
}
|
|
138
147
|
else {
|
|
139
|
-
|
|
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 }));
|
|
140
153
|
}
|
|
141
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
|
+
}
|
|
142
169
|
async function run(arguments_) {
|
|
143
170
|
const [domain, operation, ...rest] = arguments_;
|
|
144
171
|
if (domain === "install" || domain === "uninstall") {
|
|
@@ -151,14 +178,31 @@ async function run(arguments_) {
|
|
|
151
178
|
throw new InstallerError("invalid_input", "Installer accepts only scope and confirmation options");
|
|
152
179
|
requireConfirmationMode(options);
|
|
153
180
|
const action = domain;
|
|
154
|
-
|
|
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
|
+
}
|
|
155
195
|
return;
|
|
156
196
|
}
|
|
157
197
|
if (domain === "agent" && operation === "list") {
|
|
158
198
|
const options = parseOptions(rest);
|
|
159
199
|
if (options.dryRun || options.confirm || options.name || options.provider || options.model || options.variant !== undefined)
|
|
160
200
|
throw new InstallerError("invalid_input", "agent list accepts only --scope");
|
|
161
|
-
|
|
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));
|
|
162
206
|
return;
|
|
163
207
|
}
|
|
164
208
|
if (domain === "agent" && operation === "configure") {
|
|
@@ -212,7 +256,10 @@ async function main() {
|
|
|
212
256
|
}
|
|
213
257
|
catch (error) {
|
|
214
258
|
const known = error instanceof LifecycleError ? error : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
|
|
215
|
-
process.
|
|
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`);
|
|
216
263
|
process.exitCode = 2;
|
|
217
264
|
}
|
|
218
265
|
}
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const CATALOG = {
|
|
|
17
17
|
skills: ["attempt", "goal", "schedule", "multi-run", "usage", "overview", "lsp-report"],
|
|
18
18
|
plugins: ["background-attempts", "goal-loop", "schedule", "autonomy-policy", "rules-injector", "rtk", "zed-bell", "zed-clickable-paths"],
|
|
19
19
|
replacements: ["capabilities", "route", "doctor", "agent_profiles"],
|
|
20
|
-
version: "1.1.
|
|
20
|
+
version: "1.1.1",
|
|
21
21
|
};
|
|
22
22
|
const plugin = (async (input) => {
|
|
23
23
|
const gate = new RoutingGate();
|
package/dist/installer.js
CHANGED
|
@@ -179,7 +179,7 @@ async function build(action, scope, cwd = process.cwd(), home = homedir()) {
|
|
|
179
179
|
mutations.push({ path: MANIFEST_NAME, operation: "remove", expected: { sha256: sha256(owned.raw) } });
|
|
180
180
|
}
|
|
181
181
|
const sorted = operations.sort((left, right) => left.path.localeCompare(right.path) || left.operation.localeCompare(right.operation));
|
|
182
|
-
const base = { schema_version: 1, action, scope, root, package_version: packageVersion(), operations: sorted, requires_restart: profiles.plan.requires_restart ||
|
|
182
|
+
const base = { schema_version: 1, action, scope, root, package_version: packageVersion(), operations: sorted, requires_restart: (action === "install" && owned.manifest?.version !== packageVersion()) || profiles.plan.requires_restart || mutations.some((item) => item.path.startsWith("agents/") || item.path.startsWith("commands/") || item.path.startsWith("plugins/")) };
|
|
183
183
|
return {
|
|
184
184
|
plan: { ...base, digest: digest(base) },
|
|
185
185
|
mutations,
|