agent-skillboard 0.2.14 → 0.2.16
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 +29 -0
- package/README.md +64 -35
- package/bin/postinstall.mjs +5 -3
- package/docs/ai-skill-routing-goal.md +15 -3
- package/docs/install.md +98 -68
- package/docs/positioning.md +8 -6
- package/docs/reference.md +22 -10
- package/docs/routing.md +15 -0
- package/docs/user-flow.md +61 -32
- package/docs/value-proof.md +6 -3
- package/package.json +3 -2
- package/src/advisor/guidance.mjs +23 -3
- package/src/agent-integration-cli.mjs +184 -0
- package/src/agent-integration-content.mjs +48 -0
- package/src/agent-integration-files.mjs +202 -0
- package/src/agent-integration-home.mjs +185 -0
- package/src/agent-skill-roots.mjs +1 -0
- package/src/brief-renderer.mjs +36 -57
- package/src/cli.mjs +168 -169
- package/src/control/can-use-guard.mjs +12 -0
- package/src/doctor.mjs +4 -1
- package/src/lifecycle-cli.mjs +23 -302
- package/src/lifecycle-content.mjs +4 -2
- package/src/route-advisory.mjs +214 -0
- package/src/route-renderer.mjs +146 -0
- package/src/route-selection.mjs +220 -0
- package/src/route-tokens.mjs +68 -0
- package/src/route.mjs +48 -413
- package/src/source-profiles.mjs +151 -150
- package/src/uninstall.mjs +56 -9
- package/src/workspace.mjs +79 -79
package/src/cli.mjs
CHANGED
|
@@ -58,6 +58,7 @@ import { renderSkillBrief } from "./brief-renderer.mjs";
|
|
|
58
58
|
import { planGuardHookInstall } from "./control.mjs";
|
|
59
59
|
import { writeCheckedConfig } from "./control/config-write.mjs";
|
|
60
60
|
import { runInitCommand, runSetupCommand, runUninstallCommand } from "./lifecycle-cli.mjs";
|
|
61
|
+
import { renderRouteSectionLines } from "./route-renderer.mjs";
|
|
61
62
|
|
|
62
63
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
63
64
|
|
|
@@ -65,7 +66,7 @@ const APPLY_ACTION_VALUE_OPTIONS = new Set(["workflow", "dir", "config", "skills
|
|
|
65
66
|
const COMMAND_USAGE = new Map([
|
|
66
67
|
["setup", ["setup [--yes] [--agent codex[,claude,opencode,hermes]]"]],
|
|
67
68
|
["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]"]],
|
|
68
|
-
["uninstall", ["uninstall [--dir <path>] [--dry-run] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs]"]],
|
|
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]]"]],
|
|
69
70
|
[
|
|
70
71
|
"inventory",
|
|
71
72
|
[
|
|
@@ -141,8 +142,8 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
141
142
|
return await sources(argv.slice(1), options, stdout);
|
|
142
143
|
case "import":
|
|
143
144
|
return await importProfile(options, stdout);
|
|
144
|
-
case "scan":
|
|
145
|
-
return await scan(options, stdout);
|
|
145
|
+
case "scan":
|
|
146
|
+
return await scan(options, stdout);
|
|
146
147
|
case "check":
|
|
147
148
|
return await check(options, stdout, stderr);
|
|
148
149
|
case "doctor":
|
|
@@ -191,10 +192,10 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
191
192
|
return await remove(argv.slice(1), options, stdout);
|
|
192
193
|
case "dashboard":
|
|
193
194
|
return await dashboard(options, stdout);
|
|
194
|
-
case "reconcile":
|
|
195
|
-
return await reconcile(options, stdout);
|
|
196
|
-
case "impact":
|
|
197
|
-
return await impact(argv.slice(1), options, stdout);
|
|
195
|
+
case "reconcile":
|
|
196
|
+
return await reconcile(options, stdout);
|
|
197
|
+
case "impact":
|
|
198
|
+
return await impact(argv.slice(1), options, stdout);
|
|
198
199
|
case "help":
|
|
199
200
|
case "--help":
|
|
200
201
|
case "-h":
|
|
@@ -223,30 +224,30 @@ function selectedHelpText(command, args, options) {
|
|
|
223
224
|
}
|
|
224
225
|
return null;
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
+
|
|
227
228
|
async function importProfile(options, stdout) {
|
|
228
|
-
const profileRef = options.get("profile");
|
|
229
|
-
const sourceRoot = options.get("source-root");
|
|
230
|
-
if (profileRef === undefined || sourceRoot === undefined) {
|
|
231
|
-
throw new Error("Usage: skillboard import --profile <id-or-path> --source-root <dir>");
|
|
232
|
-
}
|
|
233
|
-
const profile = await loadSourceProfile(profileRef, { profileDirs: readCsv(options.get("profile-dirs")) });
|
|
234
|
-
const imported = await importSource({ profile, sourceRoot });
|
|
235
|
-
if (options.get("merge") === "true") {
|
|
236
|
-
return await mergeImport(options, imported, stdout);
|
|
237
|
-
}
|
|
238
|
-
const fragment = renderImportFragment(imported);
|
|
239
|
-
const out = options.get("out");
|
|
240
|
-
if (out === undefined) {
|
|
241
|
-
stdout.write(fragment);
|
|
242
|
-
return 0;
|
|
243
|
-
}
|
|
244
|
-
await mkdir(dirname(out), { recursive: true });
|
|
245
|
-
await writeFile(out, fragment, "utf8");
|
|
246
|
-
stdout.write(`Import fragment written: ${out}\n`);
|
|
247
|
-
return 0;
|
|
248
|
-
}
|
|
249
|
-
|
|
229
|
+
const profileRef = options.get("profile");
|
|
230
|
+
const sourceRoot = options.get("source-root");
|
|
231
|
+
if (profileRef === undefined || sourceRoot === undefined) {
|
|
232
|
+
throw new Error("Usage: skillboard import --profile <id-or-path> --source-root <dir>");
|
|
233
|
+
}
|
|
234
|
+
const profile = await loadSourceProfile(profileRef, { profileDirs: readCsv(options.get("profile-dirs")) });
|
|
235
|
+
const imported = await importSource({ profile, sourceRoot });
|
|
236
|
+
if (options.get("merge") === "true") {
|
|
237
|
+
return await mergeImport(options, imported, stdout);
|
|
238
|
+
}
|
|
239
|
+
const fragment = renderImportFragment(imported);
|
|
240
|
+
const out = options.get("out");
|
|
241
|
+
if (out === undefined) {
|
|
242
|
+
stdout.write(fragment);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
await mkdir(dirname(out), { recursive: true });
|
|
246
|
+
await writeFile(out, fragment, "utf8");
|
|
247
|
+
stdout.write(`Import fragment written: ${out}\n`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
250
251
|
async function mergeImport(options, imported, stdout) {
|
|
251
252
|
const path = configPath(options);
|
|
252
253
|
const originalText = await readFile(path, "utf8");
|
|
@@ -336,21 +337,21 @@ async function sources(argv, options, stdout) {
|
|
|
336
337
|
writeOutput(stdout, result, options, () => renderSourceRefresh(result));
|
|
337
338
|
return 0;
|
|
338
339
|
}
|
|
339
|
-
|
|
340
|
+
|
|
340
341
|
async function scan(options, stdout) {
|
|
341
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
342
|
-
stdout.write(`${JSON.stringify(workspace, null, 2)}\n`);
|
|
343
|
-
return 0;
|
|
344
|
-
}
|
|
345
|
-
|
|
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
|
+
|
|
346
347
|
async function check(options, stdout, stderr) {
|
|
347
348
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
348
349
|
const result = checkPolicy(workspace);
|
|
349
|
-
const output = [...result.errors, ...result.warnings].join("\n");
|
|
350
|
-
if (output.length > 0) {
|
|
351
|
-
(result.ok ? stdout : stderr).write(`${output}\n`);
|
|
352
|
-
}
|
|
353
|
-
stdout.write(result.ok ? "Policy check passed\n" : "Policy check failed\n");
|
|
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");
|
|
354
355
|
return result.ok ? 0 : 1;
|
|
355
356
|
}
|
|
356
357
|
|
|
@@ -982,37 +983,37 @@ async function remove(argv, options, stdout) {
|
|
|
982
983
|
}
|
|
983
984
|
|
|
984
985
|
async function dashboard(options, stdout) {
|
|
985
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
986
|
-
const markdown = renderDashboard(workspace);
|
|
987
|
-
const out = options.get("out");
|
|
988
|
-
if (out === undefined) {
|
|
989
|
-
stdout.write(markdown);
|
|
990
|
-
return 0;
|
|
991
|
-
}
|
|
992
|
-
await mkdir(dirname(out), { recursive: true });
|
|
993
|
-
await writeFile(out, markdown, "utf8");
|
|
994
|
-
stdout.write(`Dashboard written: ${out}\n`);
|
|
995
|
-
return 0;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
async function reconcile(options, stdout) {
|
|
999
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1000
|
-
const plan = reconcileWorkspace(workspace, { actualHarnesses: readCsv(options.get("actual-harnesses")) });
|
|
1001
|
-
const markdown = renderReconcilePlan(plan);
|
|
1002
|
-
const out = options.get("out");
|
|
1003
|
-
if (out === undefined) {
|
|
1004
|
-
stdout.write(markdown);
|
|
1005
|
-
return 0;
|
|
1006
|
-
}
|
|
1007
|
-
await mkdir(dirname(out), { recursive: true });
|
|
1008
|
-
await writeFile(out, markdown, "utf8");
|
|
1009
|
-
stdout.write(`Reconcile plan written: ${out}\n`);
|
|
1010
|
-
return 0;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
async function impact(argv, options, stdout) {
|
|
1014
|
-
if (argv[0] !== "disable" || argv[1] === undefined) {
|
|
1015
|
-
throw new Error("Usage: skillboard impact disable <skill-id>");
|
|
986
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
987
|
+
const markdown = renderDashboard(workspace);
|
|
988
|
+
const out = options.get("out");
|
|
989
|
+
if (out === undefined) {
|
|
990
|
+
stdout.write(markdown);
|
|
991
|
+
return 0;
|
|
992
|
+
}
|
|
993
|
+
await mkdir(dirname(out), { recursive: true });
|
|
994
|
+
await writeFile(out, markdown, "utf8");
|
|
995
|
+
stdout.write(`Dashboard written: ${out}\n`);
|
|
996
|
+
return 0;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function reconcile(options, stdout) {
|
|
1000
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1001
|
+
const plan = reconcileWorkspace(workspace, { actualHarnesses: readCsv(options.get("actual-harnesses")) });
|
|
1002
|
+
const markdown = renderReconcilePlan(plan);
|
|
1003
|
+
const out = options.get("out");
|
|
1004
|
+
if (out === undefined) {
|
|
1005
|
+
stdout.write(markdown);
|
|
1006
|
+
return 0;
|
|
1007
|
+
}
|
|
1008
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1009
|
+
await writeFile(out, markdown, "utf8");
|
|
1010
|
+
stdout.write(`Reconcile plan written: ${out}\n`);
|
|
1011
|
+
return 0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function impact(argv, options, stdout) {
|
|
1015
|
+
if (argv[0] !== "disable" || argv[1] === undefined) {
|
|
1016
|
+
throw new Error("Usage: skillboard impact disable <skill-id>");
|
|
1016
1017
|
}
|
|
1017
1018
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1018
1019
|
const result = impactDisable(workspace, argv[1]);
|
|
@@ -1022,22 +1023,22 @@ async function impact(argv, options, stdout) {
|
|
|
1022
1023
|
}
|
|
1023
1024
|
const markdown = renderImpact(result);
|
|
1024
1025
|
const out = options.get("out");
|
|
1025
|
-
if (out === undefined) {
|
|
1026
|
-
stdout.write(markdown);
|
|
1027
|
-
return 0;
|
|
1028
|
-
}
|
|
1029
|
-
await mkdir(dirname(out), { recursive: true });
|
|
1030
|
-
await writeFile(out, markdown, "utf8");
|
|
1031
|
-
stdout.write(`Impact report written: ${out}\n`);
|
|
1032
|
-
return 0;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
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
|
+
|
|
1035
1036
|
function renderImpact(result) {
|
|
1036
|
-
return [
|
|
1037
|
-
`# Disable Impact: ${result.skillId}`,
|
|
1038
|
-
"",
|
|
1039
|
-
`- Found: \`${result.exists}\``,
|
|
1040
|
-
`- Risk: ${result.risk}`,
|
|
1037
|
+
return [
|
|
1038
|
+
`# Disable Impact: ${result.skillId}`,
|
|
1039
|
+
"",
|
|
1040
|
+
`- Found: \`${result.exists}\``,
|
|
1041
|
+
`- Risk: ${result.risk}`,
|
|
1041
1042
|
`- Affected workflows: ${formatList(result.affectedWorkflows)}`,
|
|
1042
1043
|
`- Affected required outputs: ${formatList(result.affectedOutputs)}`,
|
|
1043
1044
|
`- Alternatives: ${formatList(result.alternatives)}`,
|
|
@@ -1074,24 +1075,24 @@ function commandRoot(options) {
|
|
|
1074
1075
|
}
|
|
1075
1076
|
|
|
1076
1077
|
function parseOptions(args) {
|
|
1077
|
-
const options = new Map();
|
|
1078
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1079
|
-
const arg = args[index];
|
|
1080
|
-
if (!arg.startsWith("--")) {
|
|
1081
|
-
continue;
|
|
1082
|
-
}
|
|
1083
|
-
const key = arg.slice(2);
|
|
1084
|
-
const value = args[index + 1];
|
|
1085
|
-
if (value === undefined || value.startsWith("--")) {
|
|
1086
|
-
options.set(key, "true");
|
|
1087
|
-
continue;
|
|
1088
|
-
}
|
|
1089
|
-
options.set(key, value);
|
|
1090
|
-
index += 1;
|
|
1091
|
-
}
|
|
1092
|
-
return options;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1078
|
+
const options = new Map();
|
|
1079
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1080
|
+
const arg = args[index];
|
|
1081
|
+
if (!arg.startsWith("--")) {
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const key = arg.slice(2);
|
|
1085
|
+
const value = args[index + 1];
|
|
1086
|
+
if (value === undefined || value.startsWith("--")) {
|
|
1087
|
+
options.set(key, "true");
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
options.set(key, value);
|
|
1091
|
+
index += 1;
|
|
1092
|
+
}
|
|
1093
|
+
return options;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1095
1096
|
function formatList(values) {
|
|
1096
1097
|
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
1097
1098
|
}
|
|
@@ -1104,7 +1105,7 @@ function readCsv(value) {
|
|
|
1104
1105
|
if (value === undefined || value.trim() === "") {
|
|
1105
1106
|
return [];
|
|
1106
1107
|
}
|
|
1107
|
-
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1108
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1108
1109
|
}
|
|
1109
1110
|
|
|
1110
1111
|
function renderImportMerge(result) {
|
|
@@ -1353,6 +1354,9 @@ function renderCanUse(result) {
|
|
|
1353
1354
|
`Trust: ${result.trust === null ? "unknown" : result.trust.level}`,
|
|
1354
1355
|
`Roles: ${result.roles.length === 0 ? "none" : result.roles.join(", ")}`
|
|
1355
1356
|
];
|
|
1357
|
+
if (result.allowed) {
|
|
1358
|
+
lines.push("Allowed use: disclose the skill at the start and completion; do not ask for another approval.");
|
|
1359
|
+
}
|
|
1356
1360
|
if (result.reasons.length > 0) {
|
|
1357
1361
|
lines.push("Reasons:");
|
|
1358
1362
|
for (const reason of result.reasons) {
|
|
@@ -1364,40 +1368,7 @@ function renderCanUse(result) {
|
|
|
1364
1368
|
}
|
|
1365
1369
|
|
|
1366
1370
|
function renderRoute(result) {
|
|
1367
|
-
const lines =
|
|
1368
|
-
`Intent: ${result.intent}`,
|
|
1369
|
-
`Workflow: ${result.workflow}`,
|
|
1370
|
-
`Match source: ${result.match_source}`,
|
|
1371
|
-
`Matched capability: ${result.matched_capability ?? "none"}`,
|
|
1372
|
-
`Matched skill: ${result.matched_skill ?? "none"}`,
|
|
1373
|
-
`Confidence: ${result.confidence}`,
|
|
1374
|
-
`Why: ${result.recommendation_reason}`,
|
|
1375
|
-
`Matched terms: ${formatList(result.matched_terms)}`,
|
|
1376
|
-
`Recommended skill: ${result.recommended_skill ?? "none"}`,
|
|
1377
|
-
`Fallback skills: ${formatList(result.fallback_skills)}`
|
|
1378
|
-
];
|
|
1379
|
-
if ((result.route_candidates ?? []).length > 0) {
|
|
1380
|
-
lines.push("Route candidates:");
|
|
1381
|
-
for (const candidate of result.route_candidates) {
|
|
1382
|
-
lines.push(`- ${candidate.skill} (${routeCandidateStatus(candidate)})`);
|
|
1383
|
-
if (!candidate.guard_allowed && candidate.guard_reasons.length > 0) {
|
|
1384
|
-
lines.push(` - ${candidate.guard_reasons[0]}`);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
if (result.guard_command !== null) {
|
|
1389
|
-
lines.push(`Guard: ${result.guard_command}`);
|
|
1390
|
-
}
|
|
1391
|
-
if (result.usage_disclosure !== null && result.usage_disclosure !== undefined) {
|
|
1392
|
-
lines.push(`Disclosure: run the guard automatically, state at the start that ${result.recommended_skill} is being used, and state at completion that it was used. No extra user approval is needed when the guard allows it.`);
|
|
1393
|
-
lines.push(`Say before use: "${result.usage_disclosure.start_message}"`);
|
|
1394
|
-
lines.push(`Say after completion: "${result.usage_disclosure.finish_message}"`);
|
|
1395
|
-
}
|
|
1396
|
-
if (result.post_use_policy_suggestion !== null && result.post_use_policy_suggestion !== undefined) {
|
|
1397
|
-
const suggestion = result.post_use_policy_suggestion;
|
|
1398
|
-
lines.push(`After completion: ${routeAfterUsePromptText(suggestion.question)}`);
|
|
1399
|
-
lines.push(`Policy command after confirmation: ${suggestion.suggested_policy.command_hint}`);
|
|
1400
|
-
}
|
|
1371
|
+
const lines = renderRouteSectionLines(result, { format: "cli", includeWorkflow: true });
|
|
1401
1372
|
if (result.matched_capability === null) {
|
|
1402
1373
|
lines.push("Possible skills:");
|
|
1403
1374
|
for (const skill of result.possible_skills.slice(0, 5)) {
|
|
@@ -1408,18 +1379,6 @@ function renderRoute(result) {
|
|
|
1408
1379
|
return lines.join("\n");
|
|
1409
1380
|
}
|
|
1410
1381
|
|
|
1411
|
-
function routeCandidateStatus(candidate) {
|
|
1412
|
-
return [
|
|
1413
|
-
candidate.role,
|
|
1414
|
-
candidate.selected ? "selected" : null,
|
|
1415
|
-
candidate.guard_allowed ? "allowed" : "denied"
|
|
1416
|
-
].filter((value) => value !== null).join(", ");
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
function routeAfterUsePromptText(question) {
|
|
1420
|
-
return question.replace(/^Should I /u, "ask whether to ").replace(/\?$/u, ".");
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
1382
|
function renderSourceAudit(result) {
|
|
1424
1383
|
const lines = [
|
|
1425
1384
|
`Source audit: ${result.ok ? "passed" : "failed"}`,
|
|
@@ -1543,7 +1502,7 @@ function renderCounts(counts) {
|
|
|
1543
1502
|
|
|
1544
1503
|
function helpText() {
|
|
1545
1504
|
return [
|
|
1546
|
-
"SkillBoard - AI
|
|
1505
|
+
"SkillBoard - permissive AI skill overlap routing",
|
|
1547
1506
|
"Version: see skillboard --version",
|
|
1548
1507
|
"",
|
|
1549
1508
|
"After global install:",
|
|
@@ -1552,12 +1511,13 @@ function helpText() {
|
|
|
1552
1511
|
" The package postinstall auto-runs agent-layer guidance setup on install and update.",
|
|
1553
1512
|
" Under sudo, setup targets SUDO_USER's agent homes while npm still controls the binary prefix.",
|
|
1554
1513
|
" Run skillboard setup later after adding another supported agent or when install scripts were skipped.",
|
|
1514
|
+
" Run skillboard uninstall --agent-layer before package removal when managed agent guidance should disappear.",
|
|
1555
1515
|
"",
|
|
1556
1516
|
"AI/automation operations:",
|
|
1557
1517
|
" setup [--yes] [--agent codex[,claude,opencode,hermes]]",
|
|
1558
1518
|
" import-skill --from <agent> --to <agent> --skill <id-or-dir> [--target-skill <id-or-dir>] [--adapted-file <path>] [--dry-run] [--yes] [--replace] [--json]",
|
|
1559
1519
|
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1560
|
-
" uninstall [--dir <path>] [--dry-run] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs]",
|
|
1520
|
+
" 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]]",
|
|
1561
1521
|
" inventory refresh [--dir <path>] [--config <path>] [--scan-root <dir>[,<dir>]] [--dry-run] [--json]",
|
|
1562
1522
|
" inventory detect --unit <id> --config <path> [--install-output <path>] [--config-file a,b] [--source <value>] [--kind <kind>] [--scope <scope>] [--dry-run] [--json]",
|
|
1563
1523
|
" sources refresh [--dir <path>] [--config <path>] [--unit <id>[,<id>]] [--cache-dir <dir>] [--dry-run] [--json]",
|
|
@@ -1597,10 +1557,10 @@ function helpText() {
|
|
|
1597
1557
|
" impact disable <skill-id> --config <path> --skills <dir> [--out <path>] [--json]",
|
|
1598
1558
|
"",
|
|
1599
1559
|
"AI/automation control loop:",
|
|
1600
|
-
" Goal:
|
|
1560
|
+
" Goal: keep skills broadly available while routing overlaps consistently; see docs/ai-skill-routing-goal.md.",
|
|
1601
1561
|
" Development loop: observe → route → work → explain briefly → ask after → remember policy.",
|
|
1602
1562
|
" For an already-allowed skill, disclose the selected skill at start and completion; do not ask for another approval.",
|
|
1603
|
-
" Translate
|
|
1563
|
+
" Translate an ambiguous request or explicit skill decision into the current brief: skillboard brief --json --config <path> --skills <dir> [--workflow <name>] [--intent <request>] [--include-actions].",
|
|
1604
1564
|
" If a policy-changing action is needed, pick one current action id from that brief and ask the user for one confirmation.",
|
|
1605
1565
|
" Apply one current action with skillboard apply-action <action-id> --config <path> --skills <dir> [--workflow <name>] --yes --json.",
|
|
1606
1566
|
" Read the returned post-apply brief, then run skillboard guard use automatically before invocation.",
|
|
@@ -1631,6 +1591,9 @@ function commandHelpText(command) {
|
|
|
1631
1591
|
if (command === "route") {
|
|
1632
1592
|
return routeHelpText();
|
|
1633
1593
|
}
|
|
1594
|
+
if (command === "can-use") {
|
|
1595
|
+
return canUseHelpText();
|
|
1596
|
+
}
|
|
1634
1597
|
if (command === "guard") {
|
|
1635
1598
|
return guardHelpText();
|
|
1636
1599
|
}
|
|
@@ -1682,6 +1645,7 @@ function setupHelpText() {
|
|
|
1682
1645
|
"Installs or refreshes SkillBoard at the agent layer, not into a project.",
|
|
1683
1646
|
"A normal global package install already runs this automatically for detected supported agents.",
|
|
1684
1647
|
"Use setup later after adding another supported agent, enabling a new agent home, or skipping install scripts.",
|
|
1648
|
+
"You do not need skillboard init for this install-time setup; init is only for a workspace where you want project-local policy files.",
|
|
1685
1649
|
"Without --yes, setup explains the user agent skill files it will write and asks before installing when run in a TTY.",
|
|
1686
1650
|
"In non-interactive automation, rerun with --yes after choosing the target agents.",
|
|
1687
1651
|
"",
|
|
@@ -1689,6 +1653,7 @@ function setupHelpText() {
|
|
|
1689
1653
|
" Writes a SkillBoard guidance skill into detected user agent skill roots.",
|
|
1690
1654
|
" Does not write skillboard.config.yaml, .skillboard/, AGENTS.md, or CLAUDE.md in projects.",
|
|
1691
1655
|
" Teaches agents to use installed skills by default and resolve overlap by workflow priority.",
|
|
1656
|
+
" Remove this managed guidance later with skillboard uninstall --agent-layer.",
|
|
1692
1657
|
"",
|
|
1693
1658
|
"Supported agent homes:",
|
|
1694
1659
|
" codex: CODEX_HOME, AGENTS_HOME, ~/.agents, or ~/.codex",
|
|
@@ -1732,27 +1697,37 @@ function importSkillHelpText() {
|
|
|
1732
1697
|
|
|
1733
1698
|
function uninstallHelpText() {
|
|
1734
1699
|
return [
|
|
1735
|
-
"Usage: skillboard uninstall [--dir <path>] [--dry-run] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs]",
|
|
1700
|
+
"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]]",
|
|
1736
1701
|
"",
|
|
1737
1702
|
"This help is read-only. It does not load config or change project files.",
|
|
1738
1703
|
"",
|
|
1739
|
-
"Removes SkillBoard project bridge files
|
|
1740
|
-
"Default cleanup
|
|
1704
|
+
"Removes SkillBoard project bridge files, generated lifecycle scaffolding, or managed agent-layer guidance.",
|
|
1705
|
+
"Default project cleanup removes SkillBoard settings and generated project state while preserving local skills and user-authored non-SkillBoard content.",
|
|
1741
1706
|
"",
|
|
1742
1707
|
"Options:",
|
|
1743
1708
|
" --dir <path> Project root to clean up; defaults to the current directory.",
|
|
1744
1709
|
" --dry-run Preview the cleanup without writing changes.",
|
|
1745
|
-
" --
|
|
1746
|
-
" --
|
|
1747
|
-
" --
|
|
1748
|
-
" --
|
|
1749
|
-
" --remove-
|
|
1710
|
+
" --keep-settings Preserve project SkillBoard settings and bridge guidance during default cleanup.",
|
|
1711
|
+
" --purge Explicit alias for the default clean project removal; kept for existing scripts.",
|
|
1712
|
+
" --remove-config Legacy partial cleanup: delete skillboard.config.yaml only if generated default.",
|
|
1713
|
+
" --reset-config Legacy partial cleanup: delete skillboard.config.yaml even with policy choices.",
|
|
1714
|
+
" --remove-reports Legacy partial cleanup: delete .skillboard/reports/.",
|
|
1715
|
+
" --remove-hooks Legacy partial cleanup: delete .skillboard/hooks/.",
|
|
1750
1716
|
" --keep-empty-dirs Preserve empty generated directories.",
|
|
1717
|
+
" --agent-layer Remove managed user-agent skillboard guidance instead of project files.",
|
|
1718
|
+
" --agent <list> Target supported agents for --agent-layer cleanup.",
|
|
1751
1719
|
"",
|
|
1752
|
-
"
|
|
1720
|
+
"Default project cleanup:",
|
|
1753
1721
|
" Removes SkillBoard config, bridge blocks, and the entire .skillboard/ project state directory.",
|
|
1754
1722
|
" This includes reports, hooks, source caches, rollout logs, variant snapshots, and profiles.",
|
|
1755
1723
|
" It does not delete local skills under skills/.",
|
|
1724
|
+
" Add --keep-settings when you want to leave project SkillBoard policy and bridge guidance in place.",
|
|
1725
|
+
" Passing a legacy partial cleanup flag without --purge cleans only that requested area instead.",
|
|
1726
|
+
"",
|
|
1727
|
+
"Agent layer:",
|
|
1728
|
+
" Removes only managed skillboard/SKILL.md guidance files containing the SkillBoard agent integration marker.",
|
|
1729
|
+
" It preserves other agent skills and user-authored skillboard skills without that marker.",
|
|
1730
|
+
" Run this before npm uninstall -g agent-skillboard when agent guidance should be removed.",
|
|
1756
1731
|
""
|
|
1757
1732
|
].join("\n");
|
|
1758
1733
|
}
|
|
@@ -1783,7 +1758,7 @@ function briefHelpText() {
|
|
|
1783
1758
|
"Usage: skillboard brief [--workflow <name>] [--intent <request>] [--dir <path>] [--config <path>] [--skills <dir>] [--include-actions] [--verbose] [--json]",
|
|
1784
1759
|
"",
|
|
1785
1760
|
"Reads the current SkillBoard brief without changing project files.",
|
|
1786
|
-
"Use it when a user asks what skills the AI can use,
|
|
1761
|
+
"Use it when a user asks what skills the AI can use, a request has ambiguous skill overlap, or a policy change needs approval.",
|
|
1787
1762
|
"",
|
|
1788
1763
|
"Options:",
|
|
1789
1764
|
" --workflow <name> Evaluate one workflow.",
|
|
@@ -1808,7 +1783,7 @@ function routeHelpText() {
|
|
|
1808
1783
|
return [
|
|
1809
1784
|
"Usage: skillboard route <intent> --workflow <name> [--config <path>] [--skills <dir>] [--json]",
|
|
1810
1785
|
"",
|
|
1811
|
-
"Suggests the
|
|
1786
|
+
"Suggests the routed skill for a user request when several allowed skills may overlap.",
|
|
1812
1787
|
"Use it when the AI needs a skill recommendation without changing policy.",
|
|
1813
1788
|
"",
|
|
1814
1789
|
"Options:",
|
|
@@ -1819,12 +1794,36 @@ function routeHelpText() {
|
|
|
1819
1794
|
" --json Print an agent-readable payload.",
|
|
1820
1795
|
"",
|
|
1821
1796
|
"AI use:",
|
|
1822
|
-
" If a skill is recommended, run the guard before invoking it.",
|
|
1797
|
+
" If a skill is recommended, run the guard automatically before invoking it.",
|
|
1798
|
+
" If the guard allows use, disclose the skill at start and completion; do not ask for another approval.",
|
|
1799
|
+
" If policy memory would reduce ambiguity, ask after completion whether to remember the routed skill.",
|
|
1823
1800
|
" If no skill matches, ask a clarifying question instead of guessing.",
|
|
1824
1801
|
""
|
|
1825
1802
|
].join("\n");
|
|
1826
1803
|
}
|
|
1827
1804
|
|
|
1805
|
+
function canUseHelpText() {
|
|
1806
|
+
return [
|
|
1807
|
+
"Usage: skillboard can-use <skill-id> --workflow <name> [--config <path>] [--skills <dir>] [--json]",
|
|
1808
|
+
"",
|
|
1809
|
+
"Checks whether one skill is currently usable in a workflow without changing policy.",
|
|
1810
|
+
"Use it when the AI needs an availability answer for a named skill.",
|
|
1811
|
+
"",
|
|
1812
|
+
"Options:",
|
|
1813
|
+
" <skill-id> Skill id to check.",
|
|
1814
|
+
" --workflow <name> Workflow that would use the skill.",
|
|
1815
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1816
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1817
|
+
" --json Print an agent-readable payload.",
|
|
1818
|
+
"",
|
|
1819
|
+
"AI use:",
|
|
1820
|
+
" If allowed, use the skill after the final guard check.",
|
|
1821
|
+
" If allowed, disclose the skill at the start and completion; do not ask for another approval.",
|
|
1822
|
+
" If denied, explain the reason or ask for the needed policy change before using it.",
|
|
1823
|
+
""
|
|
1824
|
+
].join("\n");
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1828
1827
|
function guardHelpText() {
|
|
1829
1828
|
return [
|
|
1830
1829
|
"Usage: skillboard guard use <skill-id> --workflow <name> [--config <path>] [--skills <dir>] [--json]",
|
|
@@ -1841,7 +1840,7 @@ function guardHelpText() {
|
|
|
1841
1840
|
" --json Print an agent-readable payload.",
|
|
1842
1841
|
"",
|
|
1843
1842
|
"AI use:",
|
|
1844
|
-
" If allowed, disclose the skill at the start and completion.",
|
|
1843
|
+
" If allowed, disclose the skill at the start and completion; do not ask for another approval.",
|
|
1845
1844
|
" If denied, do not invoke the skill. Explain the reason or ask for the needed policy change.",
|
|
1846
1845
|
""
|
|
1847
1846
|
].join("\n");
|
|
@@ -83,6 +83,7 @@ export function canUseResult(allowed, skillId, workflowName, skill, workflow, re
|
|
|
83
83
|
return {
|
|
84
84
|
allowed,
|
|
85
85
|
automaticAllowed,
|
|
86
|
+
allowedUse: allowed ? allowedUse(skillId) : null,
|
|
86
87
|
skill: skillId,
|
|
87
88
|
workflow: workflowName,
|
|
88
89
|
invocation: skill?.invocation ?? null,
|
|
@@ -94,3 +95,14 @@ export function canUseResult(allowed, skillId, workflowName, skill, workflow, re
|
|
|
94
95
|
reasons
|
|
95
96
|
};
|
|
96
97
|
}
|
|
98
|
+
|
|
99
|
+
function allowedUse(skillId) {
|
|
100
|
+
return {
|
|
101
|
+
confirmationRequired: false,
|
|
102
|
+
start: `State at the start that ${skillId} is being used for this request.`,
|
|
103
|
+
finish: `State at completion that ${skillId} was used.`,
|
|
104
|
+
startMessage: `I will use ${skillId} for this request.`,
|
|
105
|
+
finishMessage: `I used ${skillId} for this request.`,
|
|
106
|
+
askUserWhen: "Ask the user only if the guard denies use or a policy-changing action is needed."
|
|
107
|
+
};
|
|
108
|
+
}
|
package/src/doctor.mjs
CHANGED
|
@@ -16,7 +16,10 @@ export async function doctorProject(options = {}) {
|
|
|
16
16
|
const uninstall = await uninstallProject({
|
|
17
17
|
root,
|
|
18
18
|
dryRun: true,
|
|
19
|
-
|
|
19
|
+
resetConfig: true,
|
|
20
|
+
removeReports: true,
|
|
21
|
+
removeHooks: true,
|
|
22
|
+
removeProjectState: true,
|
|
20
23
|
removeEmptyDirs: true
|
|
21
24
|
});
|
|
22
25
|
const configExists = await exists(configPath);
|