agent-skillboard 0.2.18 → 0.3.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/CHANGELOG.md +43 -0
- package/README.md +129 -258
- package/docs/adapters.md +37 -113
- package/docs/ai-skill-routing-goal.md +35 -109
- package/docs/capabilities.md +17 -104
- package/docs/install.md +36 -485
- package/docs/policy-model.md +50 -280
- package/docs/positioning.md +19 -86
- package/docs/profiles.md +21 -153
- package/docs/reference.md +115 -363
- package/docs/rollout-runbook.md +23 -25
- package/docs/routing.md +23 -90
- package/docs/user-flow.md +60 -284
- package/docs/value-proof.md +23 -181
- package/docs/variant-lifecycle.md +47 -67
- package/docs/versioning.md +31 -264
- package/examples/v2-multi-source.config.yaml +35 -0
- package/examples/v2-policy-error.config.yaml +6 -0
- package/package.json +1 -1
- package/src/advisor/actions.mjs +102 -6
- package/src/advisor/application-commands.mjs +10 -9
- package/src/advisor/apply-action.mjs +74 -1
- package/src/advisor/guidance.mjs +24 -16
- package/src/advisor/schema.mjs +17 -6
- package/src/advisor/skills.mjs +18 -5
- package/src/advisor.mjs +27 -9
- package/src/agent-integration-cli.mjs +13 -4
- package/src/agent-integration-content.mjs +21 -11
- package/src/agent-integration-files.mjs +1 -1
- package/src/agent-inventory-platforms.mjs +10 -0
- package/src/agent-inventory.mjs +23 -1
- package/src/agent-skill-import.mjs +2 -2
- package/src/audit-paths.mjs +42 -0
- package/src/brief-cli.mjs +3 -2
- package/src/brief-renderer.mjs +1 -0
- package/src/cli.mjs +498 -232
- package/src/compatibility.mjs +24 -0
- package/src/control/can-use-guard.mjs +21 -1
- package/src/control/config-write.mjs +32 -2
- package/src/control/skill-crud.mjs +5 -0
- package/src/control/v2-guard.mjs +175 -0
- package/src/control/v2-skill-crud.mjs +32 -0
- package/src/control/v2-skill-forget.mjs +38 -0
- package/src/control/variant-status.mjs +47 -1
- package/src/control.mjs +55 -0
- package/src/doctor.mjs +63 -4
- package/src/domain/v2-policy.mjs +111 -0
- package/src/hook-plan.mjs +33 -3
- package/src/impact.mjs +52 -29
- package/src/index.mjs +25 -1
- package/src/init.mjs +50 -34
- package/src/inventory-install-units.mjs +63 -0
- package/src/inventory-json.mjs +279 -0
- package/src/inventory-refresh.mjs +163 -18
- package/src/lifecycle-cli.mjs +40 -12
- package/src/lifecycle-content.mjs +52 -67
- package/src/migration/v1-to-v2.mjs +212 -0
- package/src/migration/v2-files.mjs +211 -0
- package/src/migration/v2-journal.mjs +169 -0
- package/src/migration/v2-projection.mjs +108 -0
- package/src/migration/v2-transaction.mjs +205 -0
- package/src/policy.mjs +3 -0
- package/src/reconcile.mjs +139 -111
- package/src/report.mjs +168 -148
- package/src/review.mjs +2 -0
- package/src/route-advisory.mjs +47 -2
- package/src/route-selection.mjs +38 -2
- package/src/route.mjs +62 -2
- package/src/shared-skill.mjs +301 -0
- package/src/source-digest.mjs +42 -0
- package/src/source-profiles.mjs +171 -144
- package/src/source-verification.mjs +32 -48
- package/src/uninstall.mjs +22 -0
- package/src/user-state-paths.mjs +19 -0
- package/src/user-uninstall.mjs +146 -0
- package/src/workspace.mjs +119 -79
package/src/cli.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// allow: SIZE_OK - legacy CLI dispatcher split is deferred from the 0.2.7 release gate.
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import YAML from "yaml";
|
|
7
|
+
import { buildGeneratedInventory, mergeGeneratedInventory } from "./inventory-json.mjs";
|
|
7
8
|
import {
|
|
8
9
|
activateSkill,
|
|
9
10
|
addHarness,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
doctorProject,
|
|
20
21
|
explainSkill,
|
|
21
22
|
forkSkillVariant,
|
|
23
|
+
forgetV2Skill,
|
|
22
24
|
importSource,
|
|
23
25
|
installGuardHook,
|
|
24
26
|
impactDisable,
|
|
@@ -33,6 +35,9 @@ import {
|
|
|
33
35
|
quarantineSkill,
|
|
34
36
|
removeSkill,
|
|
35
37
|
resetSkillVariant,
|
|
38
|
+
setV2SkillEnabled,
|
|
39
|
+
setV2SkillPreference,
|
|
40
|
+
setV2SkillShared,
|
|
36
41
|
reconcileWorkspace,
|
|
37
42
|
refreshAgentInventory,
|
|
38
43
|
refreshSourcePins,
|
|
@@ -58,15 +63,22 @@ import { renderSkillBrief } from "./brief-renderer.mjs";
|
|
|
58
63
|
import { planGuardHookInstall } from "./control.mjs";
|
|
59
64
|
import { writeCheckedConfig } from "./control/config-write.mjs";
|
|
60
65
|
import { runInitCommand, runSetupCommand, runUninstallCommand } from "./lifecycle-cli.mjs";
|
|
66
|
+
import { migrateV2 } from "./migration/v2-transaction.mjs";
|
|
67
|
+
import { atomicWrite, optionalRead, withConfigLock } from "./migration/v2-files.mjs";
|
|
68
|
+
import { assertCurrentProjectionVersion } from "./compatibility.mjs";
|
|
61
69
|
import { renderRouteSectionLines } from "./route-renderer.mjs";
|
|
70
|
+
import { resolveUserStatePaths } from "./user-state-paths.mjs";
|
|
71
|
+
import { setSkillSharing } from "./shared-skill.mjs";
|
|
72
|
+
import { supportedAgentNames } from "./agent-skill-roots.mjs";
|
|
62
73
|
|
|
63
74
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
64
75
|
|
|
65
|
-
const APPLY_ACTION_VALUE_OPTIONS = new Set(["workflow", "dir", "config", "skills", "out", "skillboard-bin"]);
|
|
76
|
+
const APPLY_ACTION_VALUE_OPTIONS = new Set(["agent", "workflow", "intent", "dir", "config", "skills", "out", "skillboard-bin"]);
|
|
66
77
|
const COMMAND_USAGE = new Map([
|
|
78
|
+
["skill", ["enable|disable <skill-id> [--dry-run] [--json]", "share|unshare <skill-id> [--dry-run] [--json]", "preference <skill-id> --intent <term>[,<term>] --priority <integer> [--dry-run] [--json]", "forget <skill-id> [--dry-run] [--json]"]],
|
|
67
79
|
["setup", ["setup [--yes] [--agent codex[,claude,opencode,hermes]]"]],
|
|
68
80
|
["import-skill", ["import-skill --from <agent> --to <agent> --skill <id-or-dir> [--target-skill <id-or-dir>] [--adapted-file <path>] [--dry-run] [--yes] [--replace] [--json]"]],
|
|
69
|
-
["uninstall", ["uninstall [--dir <path>] [--dry-run] [--keep-settings] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs] [--agent-layer] [--agent codex[,claude,opencode,hermes]]"]],
|
|
81
|
+
["uninstall", ["uninstall --user (--dry-run|--yes) [--json]", "uninstall [--dir <path>] [--dry-run] [--keep-settings] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs] [--agent-layer] [--agent codex[,claude,opencode,hermes]]"]],
|
|
70
82
|
[
|
|
71
83
|
"inventory",
|
|
72
84
|
[
|
|
@@ -77,21 +89,22 @@ const COMMAND_USAGE = new Map([
|
|
|
77
89
|
["sources", ["sources refresh [--dir <path>] [--config <path>] [--unit <id>[,<id>]] [--cache-dir <dir>] [--dry-run] [--json]"]],
|
|
78
90
|
["import", ["import --profile <id-or-path> --source-root <dir> [--profile-dirs a,b] [--out <path>]", "import --profile <id-or-path> --source-root <dir> --config <path> --merge [--replace] [--dry-run]"]],
|
|
79
91
|
["scan", ["scan --config <path>"]],
|
|
92
|
+
["migrate", ["migrate v2 [--config <path>] [--skills <dir>] [--yes] [--rollback <backup>] [--json]"]],
|
|
80
93
|
["check", ["check --config <path> --skills <dir>"]],
|
|
81
|
-
["list", ["list [skills|workflows|harnesses|install-units] --config <path> --skills <dir>
|
|
94
|
+
["list", ["list [skills|workflows|harnesses|install-units] [--config <path>] [--skills <dir>] [--json]"]],
|
|
82
95
|
["explain", ["explain <skill-id> --config <path> --skills <dir> [--json]"]],
|
|
83
|
-
["can-use", ["can-use <skill-id> --
|
|
96
|
+
["can-use", ["can-use <skill-id> --agent codex|claude|opencode|hermes [--json]"]],
|
|
84
97
|
["audit", ["audit sources --config <path> --skills <dir> [--verify] [--json]"]],
|
|
85
98
|
["rollout", ["rollout [audit|plan|apply|rollback|report] [--dir <path>] [--config <path>] [--skills <dir>] [--transaction <id>] [--json]"]],
|
|
86
99
|
["hook", ["hook install --workflow <name> --config <path> --skills <dir> [--out <path>] [--skillboard-bin <path>] [--dry-run] [--json]"]],
|
|
87
100
|
["lock", ["lock write --config <path> --skills <dir> [--out <path>] [--replace] [--allow-unverified] [--json]"]],
|
|
88
|
-
["review", ["review
|
|
89
|
-
["add", ["add
|
|
90
|
-
["variant", ["variant
|
|
91
|
-
["activate", ["activate
|
|
92
|
-
["block", ["block
|
|
93
|
-
["quarantine", ["quarantine
|
|
94
|
-
["prefer", ["prefer
|
|
101
|
+
["review", ["review is a v1 compatibility command; run migrate v2 before policy changes"]],
|
|
102
|
+
["add", ["add is a v1 compatibility command; run migrate v2, then use skill enable|disable|share|unshare|preference"]],
|
|
103
|
+
["variant", ["variant lifecycle is compatibility-only; keep relationships and checkpoints in content or inventory metadata"]],
|
|
104
|
+
["activate", ["activate is a v1 compatibility command; run migrate v2, then use skill enable"]],
|
|
105
|
+
["block", ["block is a v1 compatibility command; run migrate v2, then use skill disable"]],
|
|
106
|
+
["quarantine", ["quarantine is a v1 compatibility command; run migrate v2, then use skill disable"]],
|
|
107
|
+
["prefer", ["prefer is a v1 compatibility command; run migrate v2, then use skill preference"]],
|
|
95
108
|
["remove", ["remove skill <skill-id> --config <path> --skills <dir> [--force] [--dry-run] [--json]"]],
|
|
96
109
|
["dashboard", ["dashboard --config <path> --skills <dir> [--out <path>]"]],
|
|
97
110
|
["reconcile", ["reconcile --config <path> --skills <dir> [--actual-harnesses a,b] [--out <path>]"]],
|
|
@@ -114,7 +127,7 @@ export async function main(argv, stdout, stderr, stdin = process.stdin) {
|
|
|
114
127
|
async function run(argv, stdout, stderr, stdin) {
|
|
115
128
|
const command = argv[0] ?? "help";
|
|
116
129
|
const commandArgs = argv.slice(1);
|
|
117
|
-
const options = parseOptions(commandArgs);
|
|
130
|
+
const options = parseOptions(commandArgs, command);
|
|
118
131
|
const help = selectedHelpText(command, commandArgs, options);
|
|
119
132
|
if (help !== null) {
|
|
120
133
|
stdout.write(help);
|
|
@@ -142,20 +155,25 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
142
155
|
return await sources(argv.slice(1), options, stdout);
|
|
143
156
|
case "import":
|
|
144
157
|
return await importProfile(options, stdout);
|
|
145
|
-
case "scan":
|
|
146
|
-
return await scan(options, stdout);
|
|
158
|
+
case "scan":
|
|
159
|
+
return await scan(options, stdout);
|
|
160
|
+
case "migrate":
|
|
161
|
+
return await migrateCommand(argv.slice(1), options, stdout);
|
|
147
162
|
case "check":
|
|
148
163
|
return await check(options, stdout, stderr);
|
|
149
164
|
case "doctor":
|
|
150
165
|
case "status":
|
|
151
166
|
return await doctor(options, stdout);
|
|
152
167
|
case "brief":
|
|
168
|
+
assertKnownOptions("brief", options, new Set(["agent", "workflow", "intent", "dir", "config", "skills", "include-actions", "verbose", "json"]));
|
|
153
169
|
return await runBriefCommand(options, stdout, {
|
|
154
170
|
configPath: configPath(options),
|
|
155
171
|
skillsRoot: skillsRoot(options)
|
|
156
172
|
});
|
|
157
173
|
case "apply-action":
|
|
158
174
|
return await applyActionCommand(argv.slice(1), options, stdout, stderr);
|
|
175
|
+
case "skill":
|
|
176
|
+
return await skillPolicy(argv.slice(1), options, stdout);
|
|
159
177
|
case "list":
|
|
160
178
|
return await list(argv.slice(1), options, stdout);
|
|
161
179
|
case "explain":
|
|
@@ -192,10 +210,10 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
192
210
|
return await remove(argv.slice(1), options, stdout);
|
|
193
211
|
case "dashboard":
|
|
194
212
|
return await dashboard(options, stdout);
|
|
195
|
-
case "reconcile":
|
|
196
|
-
return await reconcile(options, stdout);
|
|
197
|
-
case "impact":
|
|
198
|
-
return await impact(argv.slice(1), options, stdout);
|
|
213
|
+
case "reconcile":
|
|
214
|
+
return await reconcile(options, stdout);
|
|
215
|
+
case "impact":
|
|
216
|
+
return await impact(argv.slice(1), options, stdout);
|
|
199
217
|
case "help":
|
|
200
218
|
case "--help":
|
|
201
219
|
case "-h":
|
|
@@ -224,50 +242,88 @@ function selectedHelpText(command, args, options) {
|
|
|
224
242
|
}
|
|
225
243
|
return null;
|
|
226
244
|
}
|
|
227
|
-
|
|
245
|
+
|
|
228
246
|
async function importProfile(options, stdout) {
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
await
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
assertKnownOptions("import", options, new Set(["profile", "source-root", "profile-dirs", "merge", "out", "config", "skills", "replace", "dry-run", "json"]));
|
|
248
|
+
const profileRef = options.get("profile");
|
|
249
|
+
const sourceRoot = options.get("source-root");
|
|
250
|
+
if (profileRef === undefined || sourceRoot === undefined) {
|
|
251
|
+
throw new Error("Usage: skillboard import --profile <id-or-path> --source-root <dir>");
|
|
252
|
+
}
|
|
253
|
+
const profile = await loadSourceProfile(profileRef, { profileDirs: readCsv(options.get("profile-dirs")) });
|
|
254
|
+
const imported = await importSource({ profile, sourceRoot });
|
|
255
|
+
if (options.get("merge") === "true") {
|
|
256
|
+
return await mergeImport(options, imported, stdout);
|
|
257
|
+
}
|
|
258
|
+
const fragment = renderImportFragment(imported);
|
|
259
|
+
const out = options.get("out");
|
|
260
|
+
if (out === undefined) {
|
|
261
|
+
stdout.write(fragment);
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
await mkdir(dirname(out), { recursive: true });
|
|
265
|
+
await writeFile(out, fragment, "utf8");
|
|
266
|
+
stdout.write(`Import fragment written: ${out}\n`);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
251
270
|
async function mergeImport(options, imported, stdout) {
|
|
252
271
|
const path = configPath(options);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
const result = await withConfigLock(path, async () => {
|
|
273
|
+
const originalBytes = await readFile(path);
|
|
274
|
+
const originalText = originalBytes.toString("utf8");
|
|
275
|
+
const originalMode = (await stat(path)).mode & 0o777;
|
|
276
|
+
const merged = mergeImportFragment(originalText, imported, { replace: options.get("replace") === "true" });
|
|
277
|
+
const document = YAML.parseDocument(merged.text);
|
|
278
|
+
if (document.errors.length > 0) {
|
|
279
|
+
throw new Error(`Invalid YAML config after import merge: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
280
|
+
}
|
|
281
|
+
const inventoryPath = resolve(dirname(path), ".skillboard", "inventory.json");
|
|
282
|
+
const previousInventory = document.get("version") === 2 ? await optionalRead(inventoryPath) : null;
|
|
283
|
+
const previousInventoryMode = previousInventory === null ? null : (await stat(inventoryPath)).mode & 0o777;
|
|
284
|
+
let inventoryText = null;
|
|
285
|
+
if (document.get("version") === 2) {
|
|
286
|
+
const added = await buildGeneratedInventory({
|
|
287
|
+
skills: imported.skills,
|
|
288
|
+
installUnits: [imported.installUnit]
|
|
289
|
+
}, { root: dirname(path), home: process.env.HOME });
|
|
290
|
+
inventoryText = mergeGeneratedInventory(previousInventory?.toString("utf8") ?? null, added, {
|
|
291
|
+
replace: options.get("replace") === "true"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const checked = await writeCheckedConfig(document, originalText, {
|
|
295
|
+
configPath: path,
|
|
296
|
+
skillsRoot: skillsRoot(options),
|
|
297
|
+
dryRun: options.get("dry-run") === "true"
|
|
298
|
+
}, "Import merged");
|
|
299
|
+
if (inventoryText !== null && options.get("dry-run") !== "true") {
|
|
300
|
+
try {
|
|
301
|
+
await atomicWrite(inventoryPath, Buffer.from(inventoryText));
|
|
302
|
+
await chmod(inventoryPath, 0o600);
|
|
303
|
+
if (process.env.SKILLBOARD_IMPORT_FAILPOINT === "after-inventory-write") {
|
|
304
|
+
throw new Error("Injected import failure after inventory write.");
|
|
305
|
+
}
|
|
306
|
+
await loadWorkspace({ configPath: path, skillsRoot: skillsRoot(options) });
|
|
307
|
+
} catch (error) {
|
|
308
|
+
await atomicWrite(path, originalBytes);
|
|
309
|
+
await chmod(path, originalMode);
|
|
310
|
+
if (previousInventory === null) await rm(inventoryPath, { force: true });
|
|
311
|
+
else {
|
|
312
|
+
await atomicWrite(inventoryPath, previousInventory);
|
|
313
|
+
await chmod(inventoryPath, previousInventoryMode);
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...checked,
|
|
320
|
+
addedSkills: merged.addedSkills,
|
|
321
|
+
addedInstallUnits: merged.addedInstallUnits,
|
|
322
|
+
replacedSkills: merged.replacedSkills,
|
|
323
|
+
replacedInstallUnits: merged.replacedInstallUnits,
|
|
324
|
+
inventoryPath: inventoryText === null ? null : ".skillboard/inventory.json"
|
|
325
|
+
};
|
|
326
|
+
});
|
|
271
327
|
writeOutput(stdout, result, options, () => renderImportMerge(result));
|
|
272
328
|
return 0;
|
|
273
329
|
}
|
|
@@ -296,11 +352,19 @@ async function inventory(argv, options, stdout) {
|
|
|
296
352
|
if (args[0] !== "refresh") {
|
|
297
353
|
throw new Error("Usage: skillboard inventory refresh|detect ...");
|
|
298
354
|
}
|
|
355
|
+
const allowedOptions = new Set(["dir", "config", "scan-root", "dry-run", "json", "help"]);
|
|
356
|
+
const unknownOption = [...options.keys()].find((key) => !allowedOptions.has(key));
|
|
357
|
+
if (unknownOption !== undefined) {
|
|
358
|
+
throw new Error(`Unknown inventory refresh option: --${unknownOption}`);
|
|
359
|
+
}
|
|
360
|
+
const state = statePaths(options);
|
|
299
361
|
const result = await refreshAgentInventory({
|
|
300
|
-
root: options.get("dir") ??
|
|
301
|
-
configPath:
|
|
362
|
+
root: options.get("dir") ?? dirname(state.configPath),
|
|
363
|
+
configPath: state.configPath,
|
|
302
364
|
roots: readCsv(options.get("scan-root")),
|
|
303
|
-
dryRun: options.get("dry-run") === "true"
|
|
365
|
+
dryRun: options.get("dry-run") === "true",
|
|
366
|
+
home: state.home,
|
|
367
|
+
env: process.env
|
|
304
368
|
});
|
|
305
369
|
writeOutput(stdout, result, options, () => renderInventoryRefresh(result));
|
|
306
370
|
return 0;
|
|
@@ -337,28 +401,55 @@ async function sources(argv, options, stdout) {
|
|
|
337
401
|
writeOutput(stdout, result, options, () => renderSourceRefresh(result));
|
|
338
402
|
return 0;
|
|
339
403
|
}
|
|
340
|
-
|
|
404
|
+
|
|
341
405
|
async function scan(options, stdout) {
|
|
342
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
343
|
-
stdout.write(`${JSON.stringify(workspace, null, 2)}\n`);
|
|
344
|
-
return 0;
|
|
345
|
-
}
|
|
346
|
-
|
|
406
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
407
|
+
stdout.write(`${JSON.stringify(workspace, null, 2)}\n`);
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
347
411
|
async function check(options, stdout, stderr) {
|
|
412
|
+
assertKnownOptions("check", options, new Set(["config", "skills", "json"]));
|
|
348
413
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
349
414
|
const result = checkPolicy(workspace);
|
|
350
|
-
const output = [...result.errors, ...result.warnings].join("\n");
|
|
351
|
-
if (output.length > 0) {
|
|
352
|
-
(result.ok ? stdout : stderr).write(`${output}\n`);
|
|
353
|
-
}
|
|
354
|
-
stdout.write(result.ok ? "Policy check passed\n" : "Policy check failed\n");
|
|
415
|
+
const output = [...result.errors, ...result.warnings].join("\n");
|
|
416
|
+
if (output.length > 0) {
|
|
417
|
+
(result.ok ? stdout : stderr).write(`${output}\n`);
|
|
418
|
+
}
|
|
419
|
+
stdout.write(result.ok ? "Policy check passed\n" : "Policy check failed\n");
|
|
355
420
|
return result.ok ? 0 : 1;
|
|
356
421
|
}
|
|
357
422
|
|
|
423
|
+
async function migrateCommand(argv, options, stdout) {
|
|
424
|
+
const allowedOptions = new Set(["config", "skills", "yes", "rollback", "json", "help"]);
|
|
425
|
+
const unknownOption = [...options.keys()].find((key) => !allowedOptions.has(key));
|
|
426
|
+
if (unknownOption !== undefined) {
|
|
427
|
+
throw new Error(`Unknown migrate option: --${unknownOption}`);
|
|
428
|
+
}
|
|
429
|
+
const args = argv.filter((arg) => !arg.startsWith("--"));
|
|
430
|
+
if (args[0] !== "v2") {
|
|
431
|
+
throw new Error("Usage: skillboard migrate v2 [--config <path>] [--skills <dir>] [--yes] [--rollback <backup>] [--json]");
|
|
432
|
+
}
|
|
433
|
+
if (options.has("yes") && options.has("rollback")) {
|
|
434
|
+
throw new Error("Choose either --yes to apply migration or --rollback <backup>, not both.");
|
|
435
|
+
}
|
|
436
|
+
const result = await migrateV2({
|
|
437
|
+
configPath: configPath(options),
|
|
438
|
+
skillsRoot: skillsRoot(options),
|
|
439
|
+
apply: options.get("yes") === "true",
|
|
440
|
+
rollbackPath: options.get("rollback"),
|
|
441
|
+
failpoint: process.env.SKILLBOARD_MIGRATION_FAILPOINT
|
|
442
|
+
});
|
|
443
|
+
writeOutput(stdout, result, options, () => renderMigration(result));
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
358
447
|
async function doctor(options, stdout) {
|
|
448
|
+
assertKnownOptions("doctor", options, new Set(["dir", "config", "skills", "strict", "verify", "summary", "json"]));
|
|
449
|
+
const state = statePaths(options);
|
|
359
450
|
const result = await doctorProject({
|
|
360
|
-
root: options.get("dir") ??
|
|
361
|
-
configPath:
|
|
451
|
+
root: options.get("dir") ?? dirname(state.configPath),
|
|
452
|
+
configPath: state.configPath,
|
|
362
453
|
skillsRoot: options.get("skills"),
|
|
363
454
|
verifySources: options.get("verify") === "true"
|
|
364
455
|
});
|
|
@@ -369,6 +460,9 @@ async function doctor(options, stdout) {
|
|
|
369
460
|
|
|
370
461
|
async function applyActionCommand(argv, options, stdout, stderr) {
|
|
371
462
|
try {
|
|
463
|
+
assertKnownOptions("apply-action", options, new Set([
|
|
464
|
+
"agent", "workflow", "intent", "dir", "config", "skills", "dry-run", "yes", "allow-destructive", "out", "skillboard-bin", "json"
|
|
465
|
+
]));
|
|
372
466
|
const actionIds = positionalArgs(argv, APPLY_ACTION_VALUE_OPTIONS);
|
|
373
467
|
if (actionIds.length !== 1) {
|
|
374
468
|
throw new ApplyActionError(
|
|
@@ -377,10 +471,16 @@ async function applyActionCommand(argv, options, stdout, stderr) {
|
|
|
377
471
|
);
|
|
378
472
|
}
|
|
379
473
|
const [actionId] = actionIds;
|
|
474
|
+
const state = statePaths(options);
|
|
380
475
|
const result = await applyAdvisorAction(actionId, {
|
|
381
476
|
root: commandRoot(options),
|
|
477
|
+
agent: options.get("agent"),
|
|
382
478
|
workflow: options.get("workflow"),
|
|
479
|
+
intent: options.get("intent"),
|
|
383
480
|
configPath: configPath(options),
|
|
481
|
+
inventoryPath: state.inventoryPath,
|
|
482
|
+
home: state.home,
|
|
483
|
+
env: process.env,
|
|
384
484
|
skillsRoot: skillsRoot(options),
|
|
385
485
|
dryRun: options.get("dry-run") === "true",
|
|
386
486
|
yes: options.get("yes") === "true",
|
|
@@ -401,7 +501,47 @@ async function applyActionCommand(argv, options, stdout, stderr) {
|
|
|
401
501
|
}
|
|
402
502
|
}
|
|
403
503
|
|
|
504
|
+
async function skillPolicy(argv, options, stdout) {
|
|
505
|
+
assertKnownOptions("skill", options, new Set(["config", "skills", "dry-run", "json", "intent", "priority"]));
|
|
506
|
+
const args = positionalArgs(argv);
|
|
507
|
+
const operation = args[0];
|
|
508
|
+
const skillId = args[1];
|
|
509
|
+
if (skillId === undefined) throw new Error("Usage: skillboard skill enable|disable|share|unshare|preference|forget <skill-id>");
|
|
510
|
+
const common = { skillId, configPath: configPath(options), skillsRoot: skillsRoot(options), dryRun: options.get("dry-run") === "true" };
|
|
511
|
+
let result;
|
|
512
|
+
if (operation === "enable" || operation === "disable") {
|
|
513
|
+
result = await setV2SkillEnabled({ ...common, enabled: operation === "enable" });
|
|
514
|
+
} else if (operation === "share" || operation === "unshare") {
|
|
515
|
+
const state = statePaths(options);
|
|
516
|
+
result = await setSkillSharing({
|
|
517
|
+
...common,
|
|
518
|
+
shared: operation === "share",
|
|
519
|
+
configPath: state.configPath,
|
|
520
|
+
inventoryPath: state.inventoryPath,
|
|
521
|
+
home: state.home,
|
|
522
|
+
env: process.env
|
|
523
|
+
});
|
|
524
|
+
writeOutput(stdout, result, options, () => `${operation === "share" ? "Shared" : "Unshared"} ${skillId}\n`);
|
|
525
|
+
return 0;
|
|
526
|
+
} else if (operation === "preference") {
|
|
527
|
+
const priority = Number(options.get("priority"));
|
|
528
|
+
result = await setV2SkillPreference({ ...common, intents: readCsv(options.get("intent")), priority });
|
|
529
|
+
} else if (operation === "forget") {
|
|
530
|
+
const state = statePaths(options);
|
|
531
|
+
result = await forgetV2Skill({
|
|
532
|
+
...common,
|
|
533
|
+
configPath: state.configPath,
|
|
534
|
+
inventoryPath: state.inventoryPath
|
|
535
|
+
});
|
|
536
|
+
} else {
|
|
537
|
+
throw new Error(`Unknown skill policy operation: ${operation}`);
|
|
538
|
+
}
|
|
539
|
+
writeControlResult(stdout, result, options);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
404
543
|
async function list(argv, options, stdout) {
|
|
544
|
+
assertKnownOptions("list", options, new Set(["workflow", "config", "skills", "json"]));
|
|
405
545
|
const kind = positionalArgs(argv)[0] ?? "skills";
|
|
406
546
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
407
547
|
let result;
|
|
@@ -421,6 +561,7 @@ async function list(argv, options, stdout) {
|
|
|
421
561
|
}
|
|
422
562
|
|
|
423
563
|
async function explain(argv, options, stdout) {
|
|
564
|
+
assertKnownOptions("explain", options, new Set(["config", "skills", "json"]));
|
|
424
565
|
const skillId = positionalArgs(argv)[0];
|
|
425
566
|
if (skillId === undefined) {
|
|
426
567
|
throw new Error("Usage: skillboard explain <skill-id>");
|
|
@@ -432,17 +573,23 @@ async function explain(argv, options, stdout) {
|
|
|
432
573
|
}
|
|
433
574
|
|
|
434
575
|
async function route(argv, options, stdout) {
|
|
576
|
+
assertKnownOptions("route", options, new Set(["agent", "workflow", "config", "skills", "json"]));
|
|
435
577
|
const intent = positionalArgs(argv).join(" ").trim();
|
|
436
578
|
const workflow = options.get("workflow");
|
|
437
|
-
if (intent.length === 0
|
|
438
|
-
throw new Error("Usage: skillboard route <intent> --
|
|
579
|
+
if (intent.length === 0) {
|
|
580
|
+
throw new Error("Usage: skillboard route <intent> [--agent <name>]");
|
|
439
581
|
}
|
|
440
582
|
const config = configPath(options);
|
|
441
583
|
const skills = skillsRoot(options);
|
|
442
584
|
const workspace = await loadWorkspace({ configPath: config, skillsRoot: skills });
|
|
585
|
+
assertV2AgentOption(workspace, options, "skillboard route <intent> --agent <name>");
|
|
586
|
+
if (workspace.version === 1 && workflow === undefined) {
|
|
587
|
+
throw new Error("Usage: skillboard route <intent> --workflow <name>");
|
|
588
|
+
}
|
|
443
589
|
const result = routeSkill(workspace, {
|
|
444
590
|
intent,
|
|
445
591
|
workflow,
|
|
592
|
+
agent: options.get("agent"),
|
|
446
593
|
configPath: config,
|
|
447
594
|
skillsRoot: skills
|
|
448
595
|
});
|
|
@@ -451,26 +598,44 @@ async function route(argv, options, stdout) {
|
|
|
451
598
|
}
|
|
452
599
|
|
|
453
600
|
async function canUse(argv, options, stdout) {
|
|
601
|
+
assertKnownOptions("can-use", options, new Set(["agent", "workflow", "config", "skills", "json"]));
|
|
454
602
|
const skillId = positionalArgs(argv)[0];
|
|
455
603
|
const workflow = options.get("workflow");
|
|
456
|
-
if (skillId === undefined
|
|
457
|
-
throw new Error("Usage: skillboard can-use <skill-id> --
|
|
604
|
+
if (skillId === undefined) {
|
|
605
|
+
throw new Error("Usage: skillboard can-use <skill-id> [--agent <name>]");
|
|
458
606
|
}
|
|
459
607
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
460
|
-
|
|
608
|
+
assertV2AgentOption(workspace, options, "skillboard can-use <skill-id> --agent <name>");
|
|
609
|
+
if (workspace.version === 1 && workflow === undefined) {
|
|
610
|
+
throw new Error("Usage: skillboard can-use <skill-id> --workflow <name>");
|
|
611
|
+
}
|
|
612
|
+
const result = canUseSkill(workspace, skillId, workflow, options.get("agent"));
|
|
461
613
|
writeOutput(stdout, result, options, () => renderCanUse(result));
|
|
462
614
|
return result.allowed ? 0 : 2;
|
|
463
615
|
}
|
|
464
616
|
|
|
465
617
|
async function guard(argv, options, stdout) {
|
|
618
|
+
assertKnownOptions("guard", options, new Set(["agent", "workflow", "config", "skills", "json", "hook-projection-version"]));
|
|
466
619
|
const args = positionalArgs(argv);
|
|
467
620
|
const skillId = args[0] === "use" ? args[1] : args[0];
|
|
468
621
|
const workflow = options.get("workflow");
|
|
469
|
-
if (skillId === undefined
|
|
470
|
-
throw new Error("Usage: skillboard guard use <skill-id> --
|
|
622
|
+
if (skillId === undefined) {
|
|
623
|
+
throw new Error("Usage: skillboard guard use <skill-id> [--agent <name>]");
|
|
471
624
|
}
|
|
472
625
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
473
|
-
const
|
|
626
|
+
const hookProjection = options.get("hook-projection-version") ?? process.env.SKILLBOARD_POLICY_PROJECTION_VERSION;
|
|
627
|
+
if (args[0] !== "use" && hookProjection === undefined) {
|
|
628
|
+
throw new Error("This pre-v2 policy projection is stale; regenerate the guard hook from version 2 policy.");
|
|
629
|
+
}
|
|
630
|
+
if (process.env.SKILLBOARD_SKILL_ID !== undefined && hookProjection === undefined) {
|
|
631
|
+
throw new Error("This pre-v2 policy projection is stale; regenerate the guard hook from version 2 policy.");
|
|
632
|
+
}
|
|
633
|
+
assertCurrentProjectionVersion(hookProjection, workspace.version);
|
|
634
|
+
assertV2AgentOption(workspace, options, "skillboard guard use <skill-id> --agent <name>");
|
|
635
|
+
if (workspace.version === 1 && workflow === undefined) {
|
|
636
|
+
throw new Error("Usage: skillboard guard use <skill-id> --workflow <name>");
|
|
637
|
+
}
|
|
638
|
+
const result = canUseSkill(workspace, skillId, workflow, options.get("agent"));
|
|
474
639
|
if (options.get("json") === "true") {
|
|
475
640
|
stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
476
641
|
} else {
|
|
@@ -480,6 +645,7 @@ async function guard(argv, options, stdout) {
|
|
|
480
645
|
}
|
|
481
646
|
|
|
482
647
|
async function audit(argv, options, stdout) {
|
|
648
|
+
assertKnownOptions("audit", options, new Set(["verify", "config", "skills", "json"]));
|
|
483
649
|
const args = positionalArgs(argv);
|
|
484
650
|
if ((args[0] ?? "sources") !== "sources") {
|
|
485
651
|
throw new Error("Usage: skillboard audit sources --config <path> --skills <dir> [--json]");
|
|
@@ -520,6 +686,7 @@ async function rollout(argv, options, stdout) {
|
|
|
520
686
|
}
|
|
521
687
|
|
|
522
688
|
async function hook(argv, options, stdout) {
|
|
689
|
+
assertKnownOptions("hook", options, new Set(["workflow", "out", "skillboard-bin", "config", "skills", "dry-run", "json"]));
|
|
523
690
|
const args = positionalArgs(argv);
|
|
524
691
|
if (args[0] !== "install") {
|
|
525
692
|
throw new Error("Usage: skillboard hook install --workflow <name> [--out <path>] [--skillboard-bin <path>] [--dry-run]");
|
|
@@ -704,6 +871,10 @@ async function addHarnessCommand(args, options, stdout) {
|
|
|
704
871
|
|
|
705
872
|
async function variant(argv, options, stdout) {
|
|
706
873
|
try {
|
|
874
|
+
assertKnownOptions("variant", options, new Set([
|
|
875
|
+
"from", "capability", "workflow", "path", "mode", "category", "owner-install-unit",
|
|
876
|
+
"adapted-for", "config", "skills", "to-base", "to-approved", "yes", "dry-run", "json"
|
|
877
|
+
]));
|
|
707
878
|
return await runVariant(argv, options, stdout);
|
|
708
879
|
} catch (error) {
|
|
709
880
|
const payload = variantErrorPayload(error);
|
|
@@ -718,6 +889,9 @@ async function variant(argv, options, stdout) {
|
|
|
718
889
|
async function runVariant(argv, options, stdout) {
|
|
719
890
|
const args = positionalArgs(argv);
|
|
720
891
|
const subcommand = args[0];
|
|
892
|
+
if (options.get("config") !== undefined && ["add", "fork", "status", "approve", "reset"].includes(subcommand)) {
|
|
893
|
+
await assertVariantPolicyBoundary(configPath(options), subcommand);
|
|
894
|
+
}
|
|
721
895
|
if (subcommand === "add") {
|
|
722
896
|
return await variantAddCommand(args, options, stdout);
|
|
723
897
|
}
|
|
@@ -736,6 +910,20 @@ async function runVariant(argv, options, stdout) {
|
|
|
736
910
|
throw new VariantCliError("unknown_variant_subcommand", `Unknown variant subcommand: ${subcommand ?? "<missing>"}`, 2);
|
|
737
911
|
}
|
|
738
912
|
|
|
913
|
+
async function assertVariantPolicyBoundary(path, subcommand) {
|
|
914
|
+
const document = YAML.parseDocument(await readFile(path, "utf8"));
|
|
915
|
+
if (document.errors.length > 0) {
|
|
916
|
+
throw new Error(`Invalid YAML config: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
917
|
+
}
|
|
918
|
+
if (document.get("version") === 2 && subcommand !== "status") {
|
|
919
|
+
throw new VariantCliError(
|
|
920
|
+
"compatibility_error",
|
|
921
|
+
"Variant policy mutation is unavailable for version 2; use skill enable, disable, share, unshare, or preference. Read-only variant status remains available.",
|
|
922
|
+
1
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
739
927
|
async function variantAddCommand(args, options, stdout) {
|
|
740
928
|
const variantId = args[1];
|
|
741
929
|
const baseId = options.get("from");
|
|
@@ -983,37 +1171,40 @@ async function remove(argv, options, stdout) {
|
|
|
983
1171
|
}
|
|
984
1172
|
|
|
985
1173
|
async function dashboard(options, stdout) {
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
await
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1174
|
+
assertKnownOptions("dashboard", options, new Set(["config", "skills", "out", "json"]));
|
|
1175
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1176
|
+
const markdown = renderDashboard(workspace);
|
|
1177
|
+
const out = options.get("out");
|
|
1178
|
+
if (out === undefined) {
|
|
1179
|
+
stdout.write(markdown);
|
|
1180
|
+
return 0;
|
|
1181
|
+
}
|
|
1182
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1183
|
+
await writeFile(out, markdown, "utf8");
|
|
1184
|
+
stdout.write(`Dashboard written: ${out}\n`);
|
|
1185
|
+
return 0;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function reconcile(options, stdout) {
|
|
1189
|
+
assertKnownOptions("reconcile", options, new Set(["config", "skills", "actual-harnesses", "out", "json"]));
|
|
1190
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1191
|
+
const plan = reconcileWorkspace(workspace, { actualHarnesses: readCsv(options.get("actual-harnesses")) });
|
|
1192
|
+
const markdown = renderReconcilePlan(plan);
|
|
1193
|
+
const out = options.get("out");
|
|
1194
|
+
if (out === undefined) {
|
|
1195
|
+
stdout.write(markdown);
|
|
1196
|
+
return 0;
|
|
1197
|
+
}
|
|
1198
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1199
|
+
await writeFile(out, markdown, "utf8");
|
|
1200
|
+
stdout.write(`Reconcile plan written: ${out}\n`);
|
|
1201
|
+
return 0;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
async function impact(argv, options, stdout) {
|
|
1205
|
+
assertKnownOptions("impact", options, new Set(["config", "skills", "out", "json"]));
|
|
1206
|
+
if (argv[0] !== "disable" || argv[1] === undefined) {
|
|
1207
|
+
throw new Error("Usage: skillboard impact disable <skill-id>");
|
|
1017
1208
|
}
|
|
1018
1209
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1019
1210
|
const result = impactDisable(workspace, argv[1]);
|
|
@@ -1023,22 +1214,22 @@ async function impact(argv, options, stdout) {
|
|
|
1023
1214
|
}
|
|
1024
1215
|
const markdown = renderImpact(result);
|
|
1025
1216
|
const out = options.get("out");
|
|
1026
|
-
if (out === undefined) {
|
|
1027
|
-
stdout.write(markdown);
|
|
1028
|
-
return 0;
|
|
1029
|
-
}
|
|
1030
|
-
await mkdir(dirname(out), { recursive: true });
|
|
1031
|
-
await writeFile(out, markdown, "utf8");
|
|
1032
|
-
stdout.write(`Impact report written: ${out}\n`);
|
|
1033
|
-
return 0;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1217
|
+
if (out === undefined) {
|
|
1218
|
+
stdout.write(markdown);
|
|
1219
|
+
return 0;
|
|
1220
|
+
}
|
|
1221
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1222
|
+
await writeFile(out, markdown, "utf8");
|
|
1223
|
+
stdout.write(`Impact report written: ${out}\n`);
|
|
1224
|
+
return 0;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1036
1227
|
function renderImpact(result) {
|
|
1037
|
-
return [
|
|
1038
|
-
`# Disable Impact: ${result.skillId}`,
|
|
1039
|
-
"",
|
|
1040
|
-
`- Found: \`${result.exists}\``,
|
|
1041
|
-
`- Risk: ${result.risk}`,
|
|
1228
|
+
return [
|
|
1229
|
+
`# Disable Impact: ${result.skillId}`,
|
|
1230
|
+
"",
|
|
1231
|
+
`- Found: \`${result.exists}\``,
|
|
1232
|
+
`- Risk: ${result.risk}`,
|
|
1042
1233
|
`- Affected workflows: ${formatList(result.affectedWorkflows)}`,
|
|
1043
1234
|
`- Affected required outputs: ${formatList(result.affectedOutputs)}`,
|
|
1044
1235
|
`- Alternatives: ${formatList(result.alternatives)}`,
|
|
@@ -1058,41 +1249,74 @@ function formatActiveConflicts(entries) {
|
|
|
1058
1249
|
}
|
|
1059
1250
|
|
|
1060
1251
|
function configPath(options) {
|
|
1061
|
-
return options
|
|
1252
|
+
return statePaths(options).configPath;
|
|
1062
1253
|
}
|
|
1063
1254
|
|
|
1064
1255
|
function skillsRoot(options) {
|
|
1065
|
-
return options.get("skills")
|
|
1256
|
+
return options.get("skills");
|
|
1066
1257
|
}
|
|
1067
1258
|
|
|
1068
1259
|
function commandRoot(options) {
|
|
1260
|
+
return options.get("dir") ?? dirname(configPath(options));
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function statePaths(options) {
|
|
1069
1264
|
const dir = options.get("dir");
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1265
|
+
return resolveUserStatePaths({
|
|
1266
|
+
env: process.env,
|
|
1267
|
+
cwd: process.cwd(),
|
|
1268
|
+
configPath: options.get("config") ?? (dir === undefined ? undefined : resolve(dir, "skillboard.config.yaml"))
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function assertV2AgentOption(workspace, options, usage) {
|
|
1273
|
+
if (workspace.version !== 2) return;
|
|
1274
|
+
const agent = options.get("agent");
|
|
1275
|
+
if (agent === undefined) {
|
|
1276
|
+
throw new Error(`Version 2 availability requires --agent. Usage: ${usage}`);
|
|
1277
|
+
}
|
|
1278
|
+
if (!supportedAgentNames().includes(agent)) {
|
|
1279
|
+
throw new Error(`Unsupported agent: ${agent}. Expected one of: ${supportedAgentNames().join(", ")}.`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const VALUE_OPTIONS = new Set([
|
|
1284
|
+
"actual-harnesses", "adapted-file", "adapted-for", "agent", "cache-dir", "capability", "category",
|
|
1285
|
+
"command", "config", "config-file", "dir", "exposure", "from", "harness", "harness-status",
|
|
1286
|
+
"hook-projection-version", "install-output", "intent", "invocation", "kind", "mode", "out",
|
|
1287
|
+
"owner-install-unit", "path", "priority", "profile", "profile-dirs", "rollback", "rollouts-dir",
|
|
1288
|
+
"scan-root", "scope", "skill", "skillboard-bin", "skills", "source", "source-root", "status",
|
|
1289
|
+
"target-skill", "to", "transaction", "trust-level", "unit", "workflow"
|
|
1290
|
+
]);
|
|
1291
|
+
|
|
1292
|
+
function parseOptions(args, command) {
|
|
1293
|
+
const options = new Map();
|
|
1294
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1295
|
+
const arg = args[index];
|
|
1296
|
+
if (!arg.startsWith("--")) {
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
const key = arg.slice(2);
|
|
1300
|
+
const value = args[index + 1];
|
|
1301
|
+
if (value === undefined || value.startsWith("--")) {
|
|
1302
|
+
if (VALUE_OPTIONS.has(key)) throw new Error(`Missing value for ${command} option: --${key}`);
|
|
1303
|
+
options.set(key, "true");
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
options.set(key, value);
|
|
1307
|
+
index += 1;
|
|
1308
|
+
}
|
|
1309
|
+
return options;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function assertKnownOptions(command, options, allowed) {
|
|
1313
|
+
for (const option of options.keys()) {
|
|
1314
|
+
if (!allowed.has(option)) {
|
|
1315
|
+
throw new Error(`Unknown ${command} option: --${option}`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1096
1320
|
function formatList(values) {
|
|
1097
1321
|
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
1098
1322
|
}
|
|
@@ -1105,7 +1329,7 @@ function readCsv(value) {
|
|
|
1105
1329
|
if (value === undefined || value.trim() === "") {
|
|
1106
1330
|
return [];
|
|
1107
1331
|
}
|
|
1108
|
-
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1332
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1109
1333
|
}
|
|
1110
1334
|
|
|
1111
1335
|
function renderImportMerge(result) {
|
|
@@ -1124,8 +1348,9 @@ function renderImportMerge(result) {
|
|
|
1124
1348
|
}
|
|
1125
1349
|
|
|
1126
1350
|
function renderInventoryRefresh(result) {
|
|
1127
|
-
|
|
1351
|
+
const lines = [
|
|
1128
1352
|
`${result.dryRun ? "Dry run: " : ""}Inventory refreshed: ${result.configPath}`,
|
|
1353
|
+
...(result.bootstrappedV2 ? ["Bootstrapped minimal SkillBoard v2 policy."] : []),
|
|
1129
1354
|
renderChangePlan(result.plan).trimEnd(),
|
|
1130
1355
|
`Scanned skills: ${result.scan.scannedSkills}`,
|
|
1131
1356
|
`Scanned install units: ${result.scan.scannedInstallUnits}`,
|
|
@@ -1138,7 +1363,8 @@ function renderInventoryRefresh(result) {
|
|
|
1138
1363
|
`Review notes: ${formatList(result.scan.reviewNotes ?? [])}`,
|
|
1139
1364
|
`Scan warnings: ${formatList(result.scan.warnings ?? [])}`,
|
|
1140
1365
|
""
|
|
1141
|
-
]
|
|
1366
|
+
];
|
|
1367
|
+
return lines.join("\n");
|
|
1142
1368
|
}
|
|
1143
1369
|
|
|
1144
1370
|
function renderInstallDetection(result) {
|
|
@@ -1303,6 +1529,10 @@ function renderList(kind, values) {
|
|
|
1303
1529
|
}
|
|
1304
1530
|
if (kind === "skills") {
|
|
1305
1531
|
return `${values.map((skill) => {
|
|
1532
|
+
if (typeof skill.enabled === "boolean") {
|
|
1533
|
+
const inventory = skill.inventory ?? { path: skill.path ?? null, owner_install_unit: null };
|
|
1534
|
+
return `${skill.id}\tenabled=${skill.enabled}\tshared=${skill.shared}\tagents=${(inventory.installed_on ?? []).join(",") || "none"}\tpath=${inventory.path ?? "none"}\towner=${inventory.owner_install_unit ?? "none"}`;
|
|
1535
|
+
}
|
|
1306
1536
|
const roles = skill.workflowRoles.length === 0 ? "none" : skill.workflowRoles.join(",");
|
|
1307
1537
|
const owner = skill.ownerInstallUnit ?? "direct";
|
|
1308
1538
|
return `${skill.id}\t${skill.status}\t${skill.invocation}\t${skill.sourceClass}\towner=${owner}\troles=${roles}`;
|
|
@@ -1324,6 +1554,18 @@ function renderList(kind, values) {
|
|
|
1324
1554
|
}
|
|
1325
1555
|
|
|
1326
1556
|
function renderExplain(result) {
|
|
1557
|
+
if (typeof result.enabled === "boolean") {
|
|
1558
|
+
const inventory = result.inventory ?? { path: result.path ?? null, owner_install_unit: null };
|
|
1559
|
+
return [
|
|
1560
|
+
`Skill: ${result.id}`,
|
|
1561
|
+
`Enabled: ${result.enabled}`,
|
|
1562
|
+
`Shared: ${result.shared}`,
|
|
1563
|
+
`Installed on: ${(inventory.installed_on ?? []).join(", ") || "none"}`,
|
|
1564
|
+
`Inventory path: ${inventory.path ?? "none"}`,
|
|
1565
|
+
`Inventory owner: ${inventory.owner_install_unit ?? "none"}`,
|
|
1566
|
+
""
|
|
1567
|
+
].join("\n");
|
|
1568
|
+
}
|
|
1327
1569
|
const workflows = result.workflows.length === 0
|
|
1328
1570
|
? "none"
|
|
1329
1571
|
: result.workflows.map((workflow) => `${workflow.workflow}:${workflow.roles.join(",")}`).join(", ");
|
|
@@ -1379,6 +1621,24 @@ function renderRoute(result) {
|
|
|
1379
1621
|
return lines.join("\n");
|
|
1380
1622
|
}
|
|
1381
1623
|
|
|
1624
|
+
function renderMigration(result) {
|
|
1625
|
+
const lines = [
|
|
1626
|
+
`SkillBoard v2 migration: ${result.mode}`,
|
|
1627
|
+
`Changed: ${result.changed}`,
|
|
1628
|
+
`Target version: ${result.target_version}`,
|
|
1629
|
+
`Input SHA-256: ${result.input_sha256}`,
|
|
1630
|
+
`Config SHA-256: ${result.config_sha256}`
|
|
1631
|
+
];
|
|
1632
|
+
if (result.inventory_sha256 !== null) lines.push(`Inventory SHA-256: ${result.inventory_sha256}`);
|
|
1633
|
+
if (result.backup !== null) lines.push(`Backup: ${result.backup}`);
|
|
1634
|
+
if (result.counts !== undefined) {
|
|
1635
|
+
lines.push(`Skills: ${result.counts.skills} (${result.counts.enabled} enabled, ${result.counts.disabled} disabled)`);
|
|
1636
|
+
lines.push(`Warnings: ${result.counts.warnings}; losses: ${result.counts.losses}`);
|
|
1637
|
+
}
|
|
1638
|
+
lines.push("");
|
|
1639
|
+
return lines.join("\n");
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1382
1642
|
function renderSourceAudit(result) {
|
|
1383
1643
|
const lines = [
|
|
1384
1644
|
`Source audit: ${result.ok ? "passed" : "failed"}`,
|
|
@@ -1511,68 +1771,52 @@ function helpText() {
|
|
|
1511
1771
|
" The package postinstall auto-runs agent-layer guidance setup on install and update.",
|
|
1512
1772
|
" Under sudo, setup targets SUDO_USER's agent homes while npm still controls the binary prefix.",
|
|
1513
1773
|
" Run skillboard setup later after adding another supported agent or when install scripts were skipped.",
|
|
1514
|
-
"
|
|
1774
|
+
" Preview skillboard uninstall --user --dry-run before package removal to clean every managed artifact.",
|
|
1515
1775
|
"",
|
|
1516
|
-
"AI/automation operations:",
|
|
1776
|
+
"Core AI/automation operations:",
|
|
1517
1777
|
" setup [--yes] [--agent codex[,claude,opencode,hermes]]",
|
|
1518
1778
|
" import-skill --from <agent> --to <agent> --skill <id-or-dir> [--target-skill <id-or-dir>] [--adapted-file <path>] [--dry-run] [--yes] [--replace] [--json]",
|
|
1519
|
-
" uninstall
|
|
1779
|
+
" uninstall --user (--dry-run|--yes) [--json]",
|
|
1520
1780
|
" inventory refresh [--dir <path>] [--config <path>] [--scan-root <dir>[,<dir>]] [--dry-run] [--json]",
|
|
1521
|
-
" inventory detect --unit <id> --config <path> [--install-output <path>] [--config-file a,b] [--source <value>] [--kind <kind>] [--scope <scope>] [--dry-run] [--json]",
|
|
1522
|
-
" sources refresh [--dir <path>] [--config <path>] [--unit <id>[,<id>]] [--cache-dir <dir>] [--dry-run] [--json]",
|
|
1523
1781
|
" doctor [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1524
1782
|
" status [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1525
|
-
" brief [--
|
|
1526
|
-
" apply-action <action-id> [--
|
|
1527
|
-
"
|
|
1528
|
-
"
|
|
1529
|
-
"
|
|
1783
|
+
" brief [--agent codex|claude|opencode|hermes] [--intent <request>] [--config <path>] [--include-actions] [--verbose] [--json]",
|
|
1784
|
+
" apply-action <action-id> [--agent codex|claude|opencode|hermes] [--intent <text>] [--config <path>] [--dry-run] [--yes] [--allow-destructive] [--json]",
|
|
1785
|
+
" skill enable|disable <skill-id> [--dry-run] [--json]",
|
|
1786
|
+
" skill share|unshare <skill-id> [--dry-run] [--json]",
|
|
1787
|
+
" skill preference <skill-id> --intent <term>[,<term>] --priority <integer> [--dry-run] [--json]",
|
|
1788
|
+
" skill forget <skill-id> [--dry-run] [--json]",
|
|
1789
|
+
" migrate v2 [--config <path>] [--skills <dir>] [--yes] [--rollback <backup>] [--json]",
|
|
1530
1790
|
" check --config <path> --skills <dir>",
|
|
1531
|
-
" list [skills|workflows|harnesses|install-units] --config <path> --skills <dir>
|
|
1532
|
-
" explain <skill-id> --config <path> --skills <dir> [--json]",
|
|
1533
|
-
" route <intent> --
|
|
1534
|
-
" can-use <skill-id> --
|
|
1535
|
-
" guard use <skill-id> --
|
|
1536
|
-
" audit sources --config <path> --skills <dir> [--verify] [--json]",
|
|
1537
|
-
" rollout [audit|plan|apply|rollback|report] [--dir <path>] [--config <path>] [--skills <dir>] [--transaction <id>] [--json]",
|
|
1538
|
-
" hook install --workflow <name> --config <path> --skills <dir> [--out <path>] [--skillboard-bin <path>] [--dry-run] [--json]",
|
|
1539
|
-
" lock write --config <path> --skills <dir> [--out <path>] [--replace] [--allow-unverified] [--json]",
|
|
1540
|
-
" review install-unit <unit-id> [--trust-level trusted|reviewed|unreviewed|blocked] --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1541
|
-
" add skill <skill-id> --path <relative-skill-path> --config <path> --skills <dir> [--status <status>] [--invocation <mode>] [--exposure <exposure>] [--category <name>] [--workflow <name>] [--dry-run] [--json]",
|
|
1542
|
-
" add workflow <workflow-name> --harness <harness-name> --config <path> --skills <dir> [--skill <id>[,<id>]] [--harness-status <status>] [--require-existing-harness] [--dry-run] [--json]",
|
|
1543
|
-
" add harness <harness-name> --config <path> --skills <dir> [--status <status>] [--command <cmd>[,<cmd>]] [--dry-run] [--json]",
|
|
1544
|
-
" variant add <variant-id> --from <base-id> --capability <name> --workflow <name> --config <path> --skills <dir> [--path <relative-skill-path>] [--mode manual-only|router-only|workflow-auto] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]",
|
|
1545
|
-
" variant fork <variant-id> --from <base-id> --capability <name> --workflow <name> --path <relative-skill-path> --config <path> --skills <dir> [--adapted-for <label>] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]",
|
|
1546
|
-
" variant status <variant-id> --config <path> --skills <dir> [--json]",
|
|
1547
|
-
" variant approve <variant-id> --config <path> --skills <dir> [--mode manual-only|router-only|workflow-auto] [--dry-run] [--json]",
|
|
1548
|
-
" variant reset <variant-id> --to-base|--to-approved --config <path> --skills <dir> [--yes] [--dry-run] [--mode manual-only|router-only|workflow-auto] [--json]",
|
|
1549
|
-
" activate <skill-id> --workflow <name> [--mode manual-only|router-only|workflow-auto] --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1550
|
-
" block <skill-id> --workflow <name> --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1551
|
-
" quarantine <skill-id> --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1552
|
-
" prefer <skill-id> --workflow <name> --capability <name> --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1553
|
-
" remove skill <skill-id> --config <path> --skills <dir> [--force] [--dry-run] [--json]",
|
|
1554
|
-
" dashboard --config <path> --skills <dir> [--out <path>]",
|
|
1555
|
-
" reconcile --config <path> --skills <dir> [--actual-harnesses a,b] [--out <path>]",
|
|
1556
|
-
" impact disable <skill-id> --config <path> --skills <dir> [--out <path>] [--json]",
|
|
1791
|
+
" list [skills|workflows|harnesses|install-units] [--config <path>] [--skills <dir>] [--json]",
|
|
1792
|
+
" explain <skill-id> [--config <path>] [--skills <dir>] [--json]",
|
|
1793
|
+
" route <intent> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1794
|
+
" can-use <skill-id> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1795
|
+
" guard use <skill-id> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1557
1796
|
"",
|
|
1558
|
-
"
|
|
1797
|
+
"Advanced operator commands:",
|
|
1798
|
+
" Import, audit, rollout, hook, lock, variant, reconcile, impact, dashboard, and v1 lifecycle commands remain available through command-specific help and docs/reference.md.",
|
|
1799
|
+
" Audit metadata never changes skill availability; runtime/action permission remains with the agent or harness.",
|
|
1800
|
+
"",
|
|
1801
|
+
"Legacy v1 project policy mode:",
|
|
1559
1802
|
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1560
1803
|
" Deprecated project-local policy bootstrap; not needed for normal use.",
|
|
1561
1804
|
"",
|
|
1562
|
-
"AI/automation control loop:",
|
|
1563
|
-
"
|
|
1564
|
-
"
|
|
1565
|
-
"
|
|
1566
|
-
"
|
|
1567
|
-
"
|
|
1568
|
-
"
|
|
1569
|
-
" Read the returned post-apply brief, then run skillboard guard use automatically before invocation.",
|
|
1570
|
-
" apply-action re-resolves current actions; do not use cached/stale ids, multiple actions, or raw action-card shell text as the primary apply path.",
|
|
1805
|
+
"v2 AI/automation control loop:",
|
|
1806
|
+
" Policy decides only enabled/disabled and per-skill opt-in sharing; see docs/ai-skill-routing-goal.md.",
|
|
1807
|
+
" Valid installed skills default to enabled and agent-local. Optional preference ranks only and never changes availability.",
|
|
1808
|
+
" Read brief --intent --agent <agent>, select an enabled installed skill, and run guard use automatically before use.",
|
|
1809
|
+
" For an allowed skill, work without another approval; ask once only before a persistent policy change.",
|
|
1810
|
+
" Source/provenance observations are audit metadata, never availability. Runtime/action authorization is outside SkillBoard.",
|
|
1811
|
+
" Stale v1 policy is read-only: preview with skillboard migrate v2 --config <path> --json, apply with --yes --json, or restore with --rollback <backup> --json.",
|
|
1571
1812
|
""
|
|
1572
1813
|
].join("\n");
|
|
1573
1814
|
}
|
|
1574
1815
|
|
|
1575
1816
|
function commandHelpText(command) {
|
|
1817
|
+
if (command === "skill") {
|
|
1818
|
+
return skillHelpText();
|
|
1819
|
+
}
|
|
1576
1820
|
if (command === "brief") {
|
|
1577
1821
|
return briefHelpText();
|
|
1578
1822
|
}
|
|
@@ -1607,6 +1851,20 @@ function commandHelpText(command) {
|
|
|
1607
1851
|
return usage === undefined ? null : genericCommandHelpText(usage);
|
|
1608
1852
|
}
|
|
1609
1853
|
|
|
1854
|
+
function skillHelpText() {
|
|
1855
|
+
return [
|
|
1856
|
+
"Usage: skillboard skill enable|disable <skill-id> [--dry-run] [--json]",
|
|
1857
|
+
"Usage: skillboard skill share|unshare <skill-id> [--dry-run] [--json]",
|
|
1858
|
+
"Usage: skillboard skill preference <skill-id> --intent <term>[,<term>] --priority <integer> [--dry-run] [--json]",
|
|
1859
|
+
"Usage: skillboard skill forget <skill-id> [--dry-run] [--json]",
|
|
1860
|
+
"",
|
|
1861
|
+
"Policy records enablement and explicit per-skill sharing. Skills remain agent-local by default.",
|
|
1862
|
+
"Preference only ranks enabled skills installed for the current agent; it never changes availability.",
|
|
1863
|
+
"Forget removes policy only after an unshared skill is no longer present in generated inventory; it never deletes skill files.",
|
|
1864
|
+
""
|
|
1865
|
+
].join("\n");
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1610
1868
|
function genericCommandHelpText(usageLines) {
|
|
1611
1869
|
return [
|
|
1612
1870
|
...usageLines.map((line) => `Usage: skillboard ${line}`),
|
|
@@ -1632,8 +1890,7 @@ function initHelpText() {
|
|
|
1632
1890
|
"",
|
|
1633
1891
|
"What changes:",
|
|
1634
1892
|
" Writes skillboard.config.yaml, skills/, .skillboard/, AGENTS.md, and CLAUDE.md as needed.",
|
|
1635
|
-
"
|
|
1636
|
-
" Keeps runtime, plugin, system, and external skills behind source review before manual activation.",
|
|
1893
|
+
" Scans installed skills into generated inventory without source-based authorization.",
|
|
1637
1894
|
"",
|
|
1638
1895
|
"Legacy next steps:",
|
|
1639
1896
|
" Run skillboard doctor --summary.",
|
|
@@ -1655,8 +1912,8 @@ function setupHelpText() {
|
|
|
1655
1912
|
"",
|
|
1656
1913
|
"What changes after confirmation:",
|
|
1657
1914
|
" Writes a SkillBoard guidance skill into detected user agent skill roots.",
|
|
1658
|
-
"
|
|
1659
|
-
" Teaches agents to use
|
|
1915
|
+
" Creates ~/skillboard.config.yaml and ~/.skillboard/inventory.json; it writes no project files.",
|
|
1916
|
+
" Teaches agents to use local skills by default and share only user-selected skills.",
|
|
1660
1917
|
" Remove this managed guidance later with skillboard uninstall --agent-layer.",
|
|
1661
1918
|
"",
|
|
1662
1919
|
"Supported agent homes:",
|
|
@@ -1701,14 +1958,17 @@ function importSkillHelpText() {
|
|
|
1701
1958
|
|
|
1702
1959
|
function uninstallHelpText() {
|
|
1703
1960
|
return [
|
|
1961
|
+
"Usage: skillboard uninstall --user (--dry-run|--yes) [--json]",
|
|
1704
1962
|
"Usage: skillboard uninstall [--dir <path>] [--dry-run] [--keep-settings] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs] [--agent-layer] [--agent codex[,claude,opencode,hermes]]",
|
|
1705
1963
|
"",
|
|
1706
1964
|
"This help is read-only. It does not load config or change project files.",
|
|
1707
1965
|
"",
|
|
1708
|
-
"Removes SkillBoard project bridge files, generated lifecycle scaffolding, or managed agent-layer guidance.",
|
|
1966
|
+
"Removes complete user-level SkillBoard state, project bridge files, generated lifecycle scaffolding, or managed agent-layer guidance.",
|
|
1709
1967
|
"Default project cleanup removes SkillBoard settings and generated project state while preserving local skills and user-authored non-SkillBoard content.",
|
|
1710
1968
|
"",
|
|
1711
1969
|
"Options:",
|
|
1970
|
+
" --user Remove all marker-owned shared copies, managed guidance, home policy, and generated user state.",
|
|
1971
|
+
" --yes Confirm the user-level removal after previewing it.",
|
|
1712
1972
|
" --dir <path> Project root to clean up; defaults to the current directory.",
|
|
1713
1973
|
" --dry-run Preview the cleanup without writing changes.",
|
|
1714
1974
|
" --keep-settings Preserve project SkillBoard settings and bridge guidance during default cleanup.",
|
|
@@ -1720,6 +1980,12 @@ function uninstallHelpText() {
|
|
|
1720
1980
|
" --keep-empty-dirs Preserve empty generated directories.",
|
|
1721
1981
|
" --agent-layer Remove managed user-agent skillboard guidance instead of project files.",
|
|
1722
1982
|
" --agent <list> Target supported agents for --agent-layer cleanup.",
|
|
1983
|
+
" --json Print a machine-readable user-level removal plan or result.",
|
|
1984
|
+
"",
|
|
1985
|
+
"User-level cleanup:",
|
|
1986
|
+
" Run skillboard uninstall --user --dry-run, inspect the complete plan, then apply it with --yes.",
|
|
1987
|
+
" Removes only marker-owned shared copies and managed guidance; agent-owned and unmanaged skills are preserved.",
|
|
1988
|
+
" Removes ~/skillboard.config.yaml and ~/.skillboard only after managed copies and guidance are handled.",
|
|
1723
1989
|
"",
|
|
1724
1990
|
"Default project cleanup:",
|
|
1725
1991
|
" Removes SkillBoard config, bridge blocks, and the entire .skillboard/ project state directory.",
|
|
@@ -1740,11 +2006,11 @@ function doctorHelpText() {
|
|
|
1740
2006
|
return [
|
|
1741
2007
|
"Usage: skillboard doctor [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1742
2008
|
"",
|
|
1743
|
-
"Checks
|
|
2009
|
+
"Checks the user-level policy and generated inventory health.",
|
|
1744
2010
|
"The status command is an alias for doctor.",
|
|
1745
2011
|
"",
|
|
1746
2012
|
"Options:",
|
|
1747
|
-
" --dir <path>
|
|
2013
|
+
" --dir <path> Advanced legacy workspace root override.",
|
|
1748
2014
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1749
2015
|
" --skills <dir> Use a specific skills directory.",
|
|
1750
2016
|
" --verify Verify local source/cache digests when available.",
|
|
@@ -1752,24 +2018,23 @@ function doctorHelpText() {
|
|
|
1752
2018
|
" --summary Print a short human summary.",
|
|
1753
2019
|
" --json Print an agent-readable payload.",
|
|
1754
2020
|
"",
|
|
1755
|
-
"
|
|
1756
|
-
"It is read-only
|
|
2021
|
+
"Without path overrides, this checks ~/skillboard.config.yaml and ~/.skillboard/inventory.json from any directory.",
|
|
2022
|
+
"It is read-only.",
|
|
1757
2023
|
""
|
|
1758
2024
|
].join("\n");
|
|
1759
2025
|
}
|
|
1760
2026
|
|
|
1761
2027
|
function briefHelpText() {
|
|
1762
2028
|
return [
|
|
1763
|
-
"Usage: skillboard brief [--
|
|
2029
|
+
"Usage: skillboard brief [--agent codex|claude|opencode|hermes] [--intent <request>] [--config <path>] [--skills <dir>] [--include-actions] [--verbose] [--json]",
|
|
1764
2030
|
"",
|
|
1765
|
-
"Reads the current SkillBoard brief without changing
|
|
2031
|
+
"Reads the current user-level SkillBoard brief without changing files.",
|
|
1766
2032
|
"Use it when a user asks what skills the AI can use, a request has ambiguous skill overlap, or a policy change needs approval.",
|
|
1767
2033
|
"",
|
|
1768
2034
|
"Options:",
|
|
1769
|
-
" --
|
|
2035
|
+
" --agent <name> Evaluate actual installation for the current agent; required for v2 availability and routing.",
|
|
1770
2036
|
" --intent <request> Add a natural-language request so SkillBoard can suggest a skill.",
|
|
1771
|
-
" --
|
|
1772
|
-
" --config <path> Use a specific skillboard.config.yaml.",
|
|
2037
|
+
" --config <path> Advanced policy override; defaults to ~/skillboard.config.yaml.",
|
|
1773
2038
|
" --skills <dir> Use a specific skills directory.",
|
|
1774
2039
|
" --include-actions Include current action ids in JSON output.",
|
|
1775
2040
|
" --verbose Show full lists instead of compact previews.",
|
|
@@ -1777,7 +2042,7 @@ function briefHelpText() {
|
|
|
1777
2042
|
"",
|
|
1778
2043
|
"AI use:",
|
|
1779
2044
|
" Read this before answering availability questions.",
|
|
1780
|
-
" Run skillboard guard use <skill-id> --
|
|
2045
|
+
" Run skillboard guard use <skill-id> --agent <agent> before invoking a skill.",
|
|
1781
2046
|
" If guard allows use, disclose the skill at the start and completion; do not ask for another approval.",
|
|
1782
2047
|
" If a policy change is needed, ask the user to approve one current action id from this brief.",
|
|
1783
2048
|
""
|
|
@@ -1786,14 +2051,14 @@ function briefHelpText() {
|
|
|
1786
2051
|
|
|
1787
2052
|
function routeHelpText() {
|
|
1788
2053
|
return [
|
|
1789
|
-
"Usage: skillboard route <intent> --
|
|
2054
|
+
"Usage: skillboard route <intent> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1790
2055
|
"",
|
|
1791
2056
|
"Suggests the routed skill for a user request when several allowed skills may overlap.",
|
|
1792
2057
|
"Use it when the AI needs a skill recommendation without changing policy.",
|
|
1793
2058
|
"",
|
|
1794
2059
|
"Options:",
|
|
1795
2060
|
" <intent> Natural-language request, such as \"write tests first\".",
|
|
1796
|
-
" --
|
|
2061
|
+
" --agent <name> Route among skills installed for this agent.",
|
|
1797
2062
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1798
2063
|
" --skills <dir> Use a specific skills directory.",
|
|
1799
2064
|
" --json Print an agent-readable payload.",
|
|
@@ -1809,14 +2074,14 @@ function routeHelpText() {
|
|
|
1809
2074
|
|
|
1810
2075
|
function canUseHelpText() {
|
|
1811
2076
|
return [
|
|
1812
|
-
"Usage: skillboard can-use <skill-id> --
|
|
2077
|
+
"Usage: skillboard can-use <skill-id> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1813
2078
|
"",
|
|
1814
|
-
"Checks whether one skill is
|
|
2079
|
+
"Checks whether one skill is enabled and installed for the selected agent without changing policy.",
|
|
1815
2080
|
"Use it when the AI needs an availability answer for a named skill.",
|
|
1816
2081
|
"",
|
|
1817
2082
|
"Options:",
|
|
1818
2083
|
" <skill-id> Skill id to check.",
|
|
1819
|
-
" --
|
|
2084
|
+
" --agent <name> Agent that would use the skill.",
|
|
1820
2085
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1821
2086
|
" --skills <dir> Use a specific skills directory.",
|
|
1822
2087
|
" --json Print an agent-readable payload.",
|
|
@@ -1831,7 +2096,7 @@ function canUseHelpText() {
|
|
|
1831
2096
|
|
|
1832
2097
|
function guardHelpText() {
|
|
1833
2098
|
return [
|
|
1834
|
-
"Usage: skillboard guard use <skill-id> --
|
|
2099
|
+
"Usage: skillboard guard use <skill-id> --agent codex|claude|opencode|hermes [--config <path>] [--skills <dir>] [--json]",
|
|
1835
2100
|
"",
|
|
1836
2101
|
"Checks whether one skill may be used right now.",
|
|
1837
2102
|
"Run this immediately before the AI invokes a skill.",
|
|
@@ -1839,7 +2104,7 @@ function guardHelpText() {
|
|
|
1839
2104
|
"Options:",
|
|
1840
2105
|
" use Guard a skill invocation.",
|
|
1841
2106
|
" <skill-id> Skill id to check.",
|
|
1842
|
-
" --
|
|
2107
|
+
" --agent <name> Agent that will use the skill.",
|
|
1843
2108
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1844
2109
|
" --skills <dir> Use a specific skills directory.",
|
|
1845
2110
|
" --json Print an agent-readable payload.",
|
|
@@ -1853,15 +2118,16 @@ function guardHelpText() {
|
|
|
1853
2118
|
|
|
1854
2119
|
function applyActionHelpText() {
|
|
1855
2120
|
return [
|
|
1856
|
-
"Usage: skillboard apply-action <action-id> [--
|
|
2121
|
+
"Usage: skillboard apply-action <action-id> [--agent codex|claude|opencode|hermes] [--intent <text>] [--config <path>] [--skills <dir>] [--dry-run] [--yes] [--allow-destructive] [--json]",
|
|
1857
2122
|
"",
|
|
1858
2123
|
"Applies one current action id from the latest brief.",
|
|
1859
2124
|
"Use it only after the user confirms a policy-changing action.",
|
|
1860
2125
|
"",
|
|
1861
2126
|
"Options:",
|
|
1862
2127
|
" <action-id> One action id from the current brief.",
|
|
1863
|
-
" --
|
|
1864
|
-
" --
|
|
2128
|
+
" --agent <name> Preserve the brief's current-agent context in preview and post-apply output.",
|
|
2129
|
+
" --intent <text> Intent bound to a current preference action.",
|
|
2130
|
+
" --dir <path> Advanced legacy workspace root override.",
|
|
1865
2131
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1866
2132
|
" --skills <dir> Use a specific skills directory.",
|
|
1867
2133
|
" --dry-run Preview the action without writing changes.",
|