agent-skillboard 0.1.0 → 0.1.2
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 +20 -0
- package/README.md +24 -0
- package/docs/install.md +23 -0
- package/docs/versioning.md +41 -12
- package/package.json +19 -18
- package/src/agent-inventory.mjs +129 -34
- package/src/cli.mjs +17 -9
- package/src/inventory-refresh.mjs +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2 — 2026-06-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- CLI `--version` and `-v` flags for quick version verification across npm, global, source-tree, and tarball installs.
|
|
8
|
+
- Test coverage for the new version flags.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- README 5-Minute Quick Start now verifies the installed version as a first step.
|
|
13
|
+
|
|
14
|
+
## 0.1.1 — 2026-06-26
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- npm publication for `agent-skillboard`.
|
|
19
|
+
- Consolidation of duplicate installed skill IDs into canonical entries.
|
|
20
|
+
- Hermes system prompt bridge guide in README and install docs.
|
package/README.md
CHANGED
|
@@ -121,6 +121,7 @@ Run SkillBoard in the project you want to manage:
|
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
npx agent-skillboard init
|
|
124
|
+
npx agent-skillboard --version
|
|
124
125
|
npx agent-skillboard brief
|
|
125
126
|
npx agent-skillboard doctor --summary
|
|
126
127
|
```
|
|
@@ -232,6 +233,29 @@ top categories, next safe action, short section previews, and short action
|
|
|
232
233
|
summaries. Use `skillboard brief --verbose` when you need the full list or
|
|
233
234
|
full copyable command details.
|
|
234
235
|
|
|
236
|
+
### Hermes System Prompt Bridge
|
|
237
|
+
|
|
238
|
+
Hermes does not automatically read `AGENTS.md` or `CLAUDE.md`. To make Hermes
|
|
239
|
+
follow the same SkillBoard policy, add a small bridge instruction to the Hermes
|
|
240
|
+
profile or system prompt for the project:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
Use SkillBoard as the source of truth for agent skill availability.
|
|
244
|
+
Before answering what skills can be used, run:
|
|
245
|
+
skillboard brief --json --dir /path/to/your/project
|
|
246
|
+
|
|
247
|
+
Do not infer availability from installed SKILL.md files. Immediately before
|
|
248
|
+
invoking a skill, run:
|
|
249
|
+
skillboard guard use <skill-id> --workflow <workflow-name> --dir /path/to/your/project
|
|
250
|
+
|
|
251
|
+
For suggested policy changes, ask the user to approve one current action id,
|
|
252
|
+
then run:
|
|
253
|
+
skillboard apply-action <action-id> --dir /path/to/your/project --yes --json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Replace `/path/to/your/project` and `<workflow-name>` with the project and
|
|
257
|
+
workflow Hermes should operate inside.
|
|
258
|
+
|
|
235
259
|
Example: overloaded local agent profile
|
|
236
260
|
|
|
237
261
|
```text
|
package/docs/install.md
CHANGED
|
@@ -144,6 +144,29 @@ primary flow. Raw `skillboard hook install ... --dry-run --json` previews and
|
|
|
144
144
|
the matching non-dry-run command are underlying manual detail for operators who
|
|
145
145
|
need to inspect or materialize an executable guard hook directly.
|
|
146
146
|
|
|
147
|
+
## Hermes System Prompt Bridge
|
|
148
|
+
|
|
149
|
+
Hermes does not automatically read `AGENTS.md` or `CLAUDE.md`. If you want a
|
|
150
|
+
Hermes profile to follow SkillBoard policy, add this bridge to the profile or
|
|
151
|
+
system prompt for the managed project:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
Use SkillBoard as the source of truth for agent skill availability.
|
|
155
|
+
Before answering what skills can be used, run:
|
|
156
|
+
skillboard brief --json --dir /path/to/your/project
|
|
157
|
+
|
|
158
|
+
Do not infer availability from installed SKILL.md files. Immediately before
|
|
159
|
+
invoking a skill, run:
|
|
160
|
+
skillboard guard use <skill-id> --workflow <workflow-name> --dir /path/to/your/project
|
|
161
|
+
|
|
162
|
+
For suggested policy changes, ask the user to approve one current action id,
|
|
163
|
+
then run:
|
|
164
|
+
skillboard apply-action <action-id> --dir /path/to/your/project --yes --json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use the workflow generated by init, such as `hermes-codex-local-manual`, or a
|
|
168
|
+
workflow you explicitly created for that Hermes profile.
|
|
169
|
+
|
|
147
170
|
After installing a new local agent skill pack, plugin, workflow bundle, or
|
|
148
171
|
harness, rescan before enabling anything:
|
|
149
172
|
|
package/docs/versioning.md
CHANGED
|
@@ -8,7 +8,7 @@ source profiles, and the generated lockfile.
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
Current package version: `0.1.
|
|
11
|
+
Current package version: `0.1.2`
|
|
12
12
|
|
|
13
13
|
Current config schema version:
|
|
14
14
|
|
|
@@ -177,17 +177,29 @@ paths while project configs can still keep config-local paths.
|
|
|
177
177
|
`skillboard lock write` refuses to write when verification has errors unless
|
|
178
178
|
`--allow-unverified` is passed explicitly for investigation artifacts.
|
|
179
179
|
|
|
180
|
-
## Release Checklist
|
|
181
|
-
|
|
182
|
-
Before tagging a public release:
|
|
183
|
-
|
|
184
|
-
- Run `npm run check`.
|
|
185
|
-
- Run `node --check` across `src`, `test`, and `bin`.
|
|
186
|
-
- Run `npm pack --dry-run --json` and confirm internal artifacts are excluded.
|
|
187
|
-
- Run at least one CLI smoke test through the public surface.
|
|
188
|
-
- Confirm docs mention any breaking config, profile, or CLI changes.
|
|
189
|
-
- Update package version and release notes.
|
|
190
|
-
|
|
180
|
+
## Release Checklist
|
|
181
|
+
|
|
182
|
+
Before tagging a public release:
|
|
183
|
+
|
|
184
|
+
- Run `npm run check`.
|
|
185
|
+
- Run `node --check` across `src`, `test`, and `bin`.
|
|
186
|
+
- Run `npm pack --dry-run --json` and confirm internal artifacts are excluded.
|
|
187
|
+
- Run at least one CLI smoke test through the public surface.
|
|
188
|
+
- Confirm docs mention any breaking config, profile, or CLI changes.
|
|
189
|
+
- Update package version and release notes.
|
|
190
|
+
- Confirm npm Trusted Publisher is configured for package `agent-skillboard`,
|
|
191
|
+
repository `NyXXiR/skillboard`, workflow filename `publish.yml`, and allowed
|
|
192
|
+
action `npm publish`.
|
|
193
|
+
- If `npm trust github agent-skillboard --repo NyXXiR/skillboard --file
|
|
194
|
+
publish.yml --allow-publish` cannot configure it from the CLI, use the npm
|
|
195
|
+
package settings page and add a GitHub Actions trusted publisher with the
|
|
196
|
+
same repository, workflow filename, and allowed action.
|
|
197
|
+
- Push a version tag that exactly matches `package.json`, for example `v0.1.2`
|
|
198
|
+
for package version `0.1.2`.
|
|
199
|
+
- Let `.github/workflows/publish.yml` publish from the tag. The workflow runs
|
|
200
|
+
the full check suite, validates that the tag matches the package version, and
|
|
201
|
+
skips `npm publish` only when that exact version already exists on npm.
|
|
202
|
+
|
|
191
203
|
For alpha releases, include a short "completion notes" section. Current
|
|
192
204
|
completion notes:
|
|
193
205
|
|
|
@@ -199,3 +211,20 @@ completion notes:
|
|
|
199
211
|
- dry-run plans report a capped YAML semantic change list rather than full patch
|
|
200
212
|
hunks;
|
|
201
213
|
- unusual YAML trivia may still be normalized during structured config writes.
|
|
214
|
+
|
|
215
|
+
## 0.1.1 Completion Notes
|
|
216
|
+
|
|
217
|
+
- duplicate installed skill IDs are consolidated into one canonical skill entry
|
|
218
|
+
with duplicate source locations preserved as metadata;
|
|
219
|
+
- canonical duplicate skills can be shared by Codex and Hermes local workflows
|
|
220
|
+
without generating user-visible `-2` suffixes;
|
|
221
|
+
- README and install docs include a Hermes system prompt bridge for profiles
|
|
222
|
+
that do not automatically read `AGENTS.md` or `CLAUDE.md`.
|
|
223
|
+
|
|
224
|
+
## 0.1.2 Completion Notes
|
|
225
|
+
|
|
226
|
+
- add `--version` and `-v` flags to the CLI for fast version verification in
|
|
227
|
+
npm, global, source-tree, and tarball installs;
|
|
228
|
+
- add a dedicated test for the version flags;
|
|
229
|
+
- update README quick-start to verify the installed version before running
|
|
230
|
+
policy commands.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
2
|
"name": "agent-skillboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Policy and visibility layer for AI agent skills.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"profiles",
|
|
27
27
|
"README.md",
|
|
28
28
|
"CONTRIBUTING.md",
|
|
29
|
+
"CHANGELOG.md",
|
|
29
30
|
"LICENSE",
|
|
30
31
|
"tsconfig.lsp.json"
|
|
31
32
|
],
|
|
@@ -33,9 +34,9 @@
|
|
|
33
34
|
"skillboard": "bin/skillboard.mjs",
|
|
34
35
|
"agent-skillboard": "bin/skillboard.mjs"
|
|
35
36
|
},
|
|
36
|
-
"publishConfig": {
|
|
37
|
-
"access": "public"
|
|
38
|
-
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
39
40
|
"scripts": {
|
|
40
41
|
"test": "node --test",
|
|
41
42
|
"test:unit": "node --test test/advisor-brief-actions.test.mjs test/advisor-brief.test.mjs test/ecosystem-docs.test.mjs test/install-units.test.mjs test/package.test.mjs test/policy.test.mjs test/skillboard.test.mjs",
|
|
@@ -43,16 +44,16 @@
|
|
|
43
44
|
"diagnostics": "tsc -p tsconfig.lsp.json",
|
|
44
45
|
"check": "node --check bin/skillboard.mjs && npm run diagnostics && node --test"
|
|
45
46
|
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"yaml": "^2.8.1"
|
|
48
|
-
},
|
|
49
|
-
"engines": {
|
|
50
|
-
"node": ">=20"
|
|
51
|
-
},
|
|
52
|
-
"license": "MIT",
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@types/node": "^26.0.0",
|
|
55
|
-
"typescript": "^6.0.3",
|
|
56
|
-
"typescript-language-server": "^5.3.0"
|
|
57
|
-
}
|
|
58
|
-
}
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"yaml": "^2.8.1"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^26.0.0",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"typescript-language-server": "^5.3.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/agent-inventory.mjs
CHANGED
|
@@ -125,6 +125,7 @@ export function mergeAgentSkillInventory(configText, inventory) {
|
|
|
125
125
|
const reviewNotes = [];
|
|
126
126
|
const managedSkillsByUnit = new Map();
|
|
127
127
|
const localWorkflowSkillsByUnit = new Map();
|
|
128
|
+
const aliasSkillsByUnit = new Map();
|
|
128
129
|
|
|
129
130
|
for (const skill of inventory.skills) {
|
|
130
131
|
const unit = unitById.get(skill.ownerInstallUnit);
|
|
@@ -134,8 +135,10 @@ export function mergeAgentSkillInventory(configText, inventory) {
|
|
|
134
135
|
skillsMap.set(skill.id, document.createNode(skillNode(skill, defaults)));
|
|
135
136
|
addedSkills.push(skill.id);
|
|
136
137
|
appendManagedSkill(managedSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
138
|
+
appendSourceAliasSkills(aliasSkillsByUnit, skill.sourceAliases ?? [], skill.id);
|
|
137
139
|
if (defaults.attachLocalWorkflow) {
|
|
138
140
|
appendManagedSkill(localWorkflowSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
141
|
+
appendLocalWorkflowAliasSkills(localWorkflowSkillsByUnit, skill.sourceAliases ?? [], skill.id);
|
|
139
142
|
} else if (isTrustedLocalUserUnit(unit)) {
|
|
140
143
|
reviewNotes.push(`Local skill ${skill.id} imported as manual-only candidate; use skillboard add workflow to attach it.`);
|
|
141
144
|
}
|
|
@@ -144,24 +147,36 @@ export function mergeAgentSkillInventory(configText, inventory) {
|
|
|
144
147
|
const owner = readYamlMapString(existing, "owner_install_unit", "");
|
|
145
148
|
if (owner === skill.ownerInstallUnit) {
|
|
146
149
|
appendManagedSkill(managedSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
150
|
+
appendSourceAliases(existing, skill.sourceAliases ?? [], document);
|
|
151
|
+
appendSourceAliasSkills(aliasSkillsByUnit, skill.sourceAliases ?? [], skill.id);
|
|
152
|
+
if (defaults.attachLocalWorkflow) {
|
|
153
|
+
appendManagedSkill(localWorkflowSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
154
|
+
appendLocalWorkflowAliasSkills(localWorkflowSkillsByUnit, skill.sourceAliases ?? [], skill.id);
|
|
155
|
+
}
|
|
147
156
|
} else {
|
|
148
|
-
|
|
157
|
+
const alias = sourceAliasForSkill(skill);
|
|
158
|
+
appendSourceAliases(existing, [alias], document);
|
|
159
|
+
appendManagedSkill(aliasSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
160
|
+
if (defaults.attachLocalWorkflow) {
|
|
161
|
+
appendManagedSkill(localWorkflowSkillsByUnit, skill.ownerInstallUnit, skill.id);
|
|
162
|
+
}
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
|
|
152
166
|
for (const unit of inventory.installUnits) {
|
|
153
167
|
const managedSkills = managedSkillsByUnit.get(unit.id) ?? [];
|
|
154
|
-
|
|
168
|
+
const aliasSkills = aliasSkillsByUnit.get(unit.id) ?? [];
|
|
169
|
+
if (managedSkills.length === 0 && aliasSkills.length === 0 && !hasRuntimeComponents(unit)) {
|
|
155
170
|
continue;
|
|
156
171
|
}
|
|
157
172
|
const existing = unitsMap.get(unit.id, true);
|
|
158
173
|
if (existing === undefined) {
|
|
159
|
-
unitsMap.set(unit.id, document.createNode(installUnitNode(unit, managedSkills)));
|
|
174
|
+
unitsMap.set(unit.id, document.createNode(installUnitNode(unit, managedSkills, aliasSkills)));
|
|
160
175
|
addedInstallUnits.push(unit.id);
|
|
161
176
|
continue;
|
|
162
177
|
}
|
|
163
178
|
const unitMap = requireYamlMap(existing, `install_units.${unit.id}`);
|
|
164
|
-
if (mergeInstallUnitNode(unitMap, unit, managedSkills, document)) {
|
|
179
|
+
if (mergeInstallUnitNode(unitMap, unit, managedSkills, aliasSkills, document)) {
|
|
165
180
|
updatedInstallUnits.push(unit.id);
|
|
166
181
|
}
|
|
167
182
|
}
|
|
@@ -367,11 +382,13 @@ async function findPluginManifests(root) {
|
|
|
367
382
|
async function inventoryFromGroups(groups, home, initialWarnings = []) {
|
|
368
383
|
const skills = [];
|
|
369
384
|
const installUnits = [];
|
|
370
|
-
const
|
|
385
|
+
const skillById = new Map();
|
|
371
386
|
const warnings = [...initialWarnings];
|
|
387
|
+
let scannedSkills = 0;
|
|
372
388
|
|
|
373
|
-
for (const group of groups.sort(
|
|
389
|
+
for (const group of groups.sort(compareInventoryGroups)) {
|
|
374
390
|
const unitSkills = [];
|
|
391
|
+
const aliasSkills = [];
|
|
375
392
|
for (const file of group.files.sort((left, right) => left.localeCompare(right))) {
|
|
376
393
|
let frontmatter;
|
|
377
394
|
try {
|
|
@@ -380,30 +397,57 @@ async function inventoryFromGroups(groups, home, initialWarnings = []) {
|
|
|
380
397
|
warnings.push(`skill file ${displayPath(file, home)} skipped: ${errorMessage(error)}`);
|
|
381
398
|
continue;
|
|
382
399
|
}
|
|
383
|
-
|
|
384
|
-
const id =
|
|
385
|
-
|
|
400
|
+
scannedSkills += 1;
|
|
401
|
+
const id = skillIdFor(group.unit, group.root, file, frontmatter);
|
|
402
|
+
const sourceAlias = {
|
|
403
|
+
ownerInstallUnit: group.unit.id,
|
|
404
|
+
path: skillPath(group.root, file)
|
|
405
|
+
};
|
|
406
|
+
const existing = skillById.get(id);
|
|
407
|
+
if (existing !== undefined) {
|
|
408
|
+
existing.sourceAliases.push(sourceAlias);
|
|
409
|
+
aliasSkills.push(id);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
386
412
|
unitSkills.push(id);
|
|
387
|
-
|
|
413
|
+
const skill = {
|
|
388
414
|
id,
|
|
389
|
-
path:
|
|
415
|
+
path: sourceAlias.path,
|
|
390
416
|
status: "quarantined",
|
|
391
417
|
invocation: "blocked",
|
|
392
418
|
exposure: "exported",
|
|
393
419
|
category: group.unit.category,
|
|
394
420
|
ownerInstallUnit: group.unit.id,
|
|
395
|
-
description: frontmatter.description
|
|
396
|
-
|
|
421
|
+
description: frontmatter.description,
|
|
422
|
+
sourceAliases: []
|
|
423
|
+
};
|
|
424
|
+
skillById.set(id, skill);
|
|
425
|
+
skills.push(skill);
|
|
397
426
|
}
|
|
398
427
|
if (unitSkills.length > 0) {
|
|
399
428
|
group.unit.providedComponents = providedComponentList({ ...group.unit, skills: unitSkills });
|
|
400
429
|
}
|
|
401
|
-
if (unitSkills.length > 0 || hasRuntimeComponents(group.unit)) {
|
|
402
|
-
installUnits.push(installUnitWithSkills(group.unit, unitSkills, group.root, home));
|
|
430
|
+
if (unitSkills.length > 0 || aliasSkills.length > 0 || hasRuntimeComponents(group.unit)) {
|
|
431
|
+
installUnits.push(installUnitWithSkills(group.unit, unitSkills, aliasSkills, group.root, home));
|
|
403
432
|
}
|
|
404
433
|
}
|
|
405
434
|
|
|
406
|
-
return { skills, installUnits, warnings };
|
|
435
|
+
return { skills, installUnits, scannedSkills, warnings };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function compareInventoryGroups(left, right) {
|
|
439
|
+
const rank = inventoryUnitRank(left.unit) - inventoryUnitRank(right.unit);
|
|
440
|
+
return rank === 0 ? left.unit.id.localeCompare(right.unit.id) : rank;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function inventoryUnitRank(unit) {
|
|
444
|
+
if (isTrustedLocalUserUnit(unit)) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
if (unit.kind === "skill") {
|
|
448
|
+
return 1;
|
|
449
|
+
}
|
|
450
|
+
return 2;
|
|
407
451
|
}
|
|
408
452
|
|
|
409
453
|
function normalizeDiscoveryResult(result) {
|
|
@@ -419,7 +463,7 @@ function normalizeDiscoveryResult(result) {
|
|
|
419
463
|
return { groups: [], warnings: [] };
|
|
420
464
|
}
|
|
421
465
|
|
|
422
|
-
function installUnitWithSkills(unit, skills, root, home) {
|
|
466
|
+
function installUnitWithSkills(unit, skills, aliasSkills, root, home) {
|
|
423
467
|
const commands = unit.commands ?? [];
|
|
424
468
|
const hooks = unit.hooks ?? [];
|
|
425
469
|
const mcpServers = unit.mcpServers ?? [];
|
|
@@ -433,7 +477,8 @@ function installUnitWithSkills(unit, skills, root, home) {
|
|
|
433
477
|
mcpServers,
|
|
434
478
|
modifiedConfigFiles: unit.modifiedConfigFiles ?? [],
|
|
435
479
|
permissionRisk: unit.permissionRisk ?? permissionRiskFor({ commands, hooks, mcpServers }),
|
|
436
|
-
skills
|
|
480
|
+
skills,
|
|
481
|
+
sourceAliasSkills: aliasSkills
|
|
437
482
|
};
|
|
438
483
|
}
|
|
439
484
|
|
|
@@ -495,17 +540,18 @@ function skillPath(root, file) {
|
|
|
495
540
|
}
|
|
496
541
|
|
|
497
542
|
function skillNode(skill, defaults = {}) {
|
|
498
|
-
return {
|
|
543
|
+
return stripUndefined({
|
|
499
544
|
path: skill.path,
|
|
500
545
|
status: defaults.status ?? skill.status,
|
|
501
546
|
invocation: defaults.invocation ?? skill.invocation,
|
|
502
547
|
exposure: skill.exposure,
|
|
503
548
|
category: skill.category,
|
|
504
|
-
owner_install_unit: skill.ownerInstallUnit
|
|
505
|
-
|
|
549
|
+
owner_install_unit: skill.ownerInstallUnit,
|
|
550
|
+
source_aliases: sourceAliasesNode(skill.sourceAliases ?? [])
|
|
551
|
+
});
|
|
506
552
|
}
|
|
507
553
|
|
|
508
|
-
function installUnitNode(unit, skills) {
|
|
554
|
+
function installUnitNode(unit, skills, aliasSkills = []) {
|
|
509
555
|
return stripUndefined({
|
|
510
556
|
kind: unit.kind,
|
|
511
557
|
source_class: unit.sourceClass,
|
|
@@ -518,6 +564,7 @@ function installUnitNode(unit, skills) {
|
|
|
518
564
|
provided_components: unit.providedComponents,
|
|
519
565
|
components: {
|
|
520
566
|
skills,
|
|
567
|
+
source_aliases: aliasSkills,
|
|
521
568
|
commands: unit.commands,
|
|
522
569
|
hooks: unit.hooks,
|
|
523
570
|
mcp_servers: unit.mcpServers
|
|
@@ -530,10 +577,11 @@ function installUnitNode(unit, skills) {
|
|
|
530
577
|
});
|
|
531
578
|
}
|
|
532
579
|
|
|
533
|
-
function mergeInstallUnitNode(unitMap, unit, skills, document) {
|
|
580
|
+
function mergeInstallUnitNode(unitMap, unit, skills, aliasSkills, document) {
|
|
534
581
|
let changed = false;
|
|
535
582
|
const components = ensureNestedMap(unitMap, "components", document);
|
|
536
583
|
changed = appendNestedSequenceValues(components, "skills", skills, document) || changed;
|
|
584
|
+
changed = appendNestedSequenceValues(components, "source_aliases", aliasSkills, document) || changed;
|
|
537
585
|
changed = appendNestedSequenceValues(components, "commands", unit.commands ?? [], document) || changed;
|
|
538
586
|
changed = appendNestedSequenceValues(components, "hooks", unit.hooks ?? [], document) || changed;
|
|
539
587
|
changed = appendNestedSequenceValues(components, "mcp_servers", unit.mcpServers ?? [], document) || changed;
|
|
@@ -566,6 +614,64 @@ function appendManagedSkill(map, unitId, skillId) {
|
|
|
566
614
|
map.set(unitId, values);
|
|
567
615
|
}
|
|
568
616
|
|
|
617
|
+
function appendSourceAliasSkills(map, sourceAliases, skillId) {
|
|
618
|
+
for (const alias of sourceAliases) {
|
|
619
|
+
appendManagedSkill(map, alias.ownerInstallUnit, skillId);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function appendLocalWorkflowAliasSkills(map, sourceAliases, skillId) {
|
|
624
|
+
for (const alias of sourceAliases) {
|
|
625
|
+
appendManagedSkill(map, alias.ownerInstallUnit, skillId);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function sourceAliasForSkill(skill) {
|
|
630
|
+
return {
|
|
631
|
+
ownerInstallUnit: skill.ownerInstallUnit,
|
|
632
|
+
path: skill.path
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function sourceAliasesNode(sourceAliases) {
|
|
637
|
+
const aliases = sourceAliases
|
|
638
|
+
.filter((alias) => alias.ownerInstallUnit !== undefined && alias.path !== undefined)
|
|
639
|
+
.map((alias) => ({
|
|
640
|
+
owner_install_unit: alias.ownerInstallUnit,
|
|
641
|
+
path: alias.path
|
|
642
|
+
}));
|
|
643
|
+
return aliases.length === 0 ? undefined : aliases;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function appendSourceAliases(skillNodeValue, sourceAliases, document) {
|
|
647
|
+
const skillMap = requireYamlMap(skillNodeValue, "skills.<id>");
|
|
648
|
+
const validAliases = sourceAliases.filter((alias) => alias.ownerInstallUnit !== undefined && alias.path !== undefined);
|
|
649
|
+
if (validAliases.length === 0) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
const seq = ensureNestedSeq(skillMap, "source_aliases", document);
|
|
653
|
+
const before = seq.items.length;
|
|
654
|
+
for (const alias of validAliases) {
|
|
655
|
+
if (sourceAliasExists(seq, alias)) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
seq.add(document.createNode({
|
|
659
|
+
owner_install_unit: alias.ownerInstallUnit,
|
|
660
|
+
path: alias.path
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
return seq.items.length !== before;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function sourceAliasExists(seq, alias) {
|
|
667
|
+
return seq.items.some((item) => {
|
|
668
|
+
if (!YAML.isMap(item)) {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
return item.get("owner_install_unit") === alias.ownerInstallUnit && item.get("path") === alias.path;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
569
675
|
async function manifestMcpServers(value, pluginRoot) {
|
|
570
676
|
if (typeof value === "string") {
|
|
571
677
|
const path = resolve(pluginRoot, value);
|
|
@@ -737,17 +843,6 @@ function preserveLineEndings(text, reference) {
|
|
|
737
843
|
return reference.includes("\r\n") ? text.replace(/\n/g, "\r\n") : text;
|
|
738
844
|
}
|
|
739
845
|
|
|
740
|
-
function uniqueId(base, used) {
|
|
741
|
-
if (!used.has(base)) {
|
|
742
|
-
return base;
|
|
743
|
-
}
|
|
744
|
-
let counter = 2;
|
|
745
|
-
while (used.has(`${base}-${counter}`)) {
|
|
746
|
-
counter += 1;
|
|
747
|
-
}
|
|
748
|
-
return `${base}-${counter}`;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
846
|
function validId(value) {
|
|
752
847
|
return /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(value);
|
|
753
848
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { dirname, isAbsolute } from "node:path";
|
|
3
4
|
import YAML from "yaml";
|
|
4
5
|
import {
|
|
@@ -49,6 +50,8 @@ import { planGuardHookInstall } from "./control.mjs";
|
|
|
49
50
|
import { writeCheckedConfig } from "./control/config-write.mjs";
|
|
50
51
|
import { runInitCommand, runUninstallCommand } from "./lifecycle-cli.mjs";
|
|
51
52
|
|
|
53
|
+
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
54
|
+
|
|
52
55
|
const APPLY_ACTION_VALUE_OPTIONS = new Set(["workflow", "dir", "config", "skills", "out", "skillboard-bin"]);
|
|
53
56
|
|
|
54
57
|
export async function main(argv, stdout, stderr) {
|
|
@@ -124,15 +127,19 @@ async function run(argv, stdout, stderr) {
|
|
|
124
127
|
return await reconcile(options, stdout);
|
|
125
128
|
case "impact":
|
|
126
129
|
return await impact(argv.slice(1), options, stdout);
|
|
127
|
-
case "help":
|
|
128
|
-
case "--help":
|
|
129
|
-
case "-h":
|
|
130
|
-
stdout.write(helpText());
|
|
131
|
-
return 0;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
stdout.write(
|
|
135
|
-
return
|
|
130
|
+
case "help":
|
|
131
|
+
case "--help":
|
|
132
|
+
case "-h":
|
|
133
|
+
stdout.write(helpText());
|
|
134
|
+
return 0;
|
|
135
|
+
case "--version":
|
|
136
|
+
case "-v":
|
|
137
|
+
stdout.write(`${VERSION}\n`);
|
|
138
|
+
return 0;
|
|
139
|
+
default:
|
|
140
|
+
stderr.write(`Unknown command: ${command}\n`);
|
|
141
|
+
stdout.write(helpText());
|
|
142
|
+
return 1;
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -1124,6 +1131,7 @@ function renderCounts(counts) {
|
|
|
1124
1131
|
function helpText() {
|
|
1125
1132
|
return [
|
|
1126
1133
|
"SkillBoard - workflow-scoped agent skill policy",
|
|
1134
|
+
"Version: see skillboard --version",
|
|
1127
1135
|
"",
|
|
1128
1136
|
"Commands:",
|
|
1129
1137
|
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
@@ -26,7 +26,7 @@ export async function refreshAgentInventory(options = {}) {
|
|
|
26
26
|
changed: plan.changed,
|
|
27
27
|
plan,
|
|
28
28
|
scan: {
|
|
29
|
-
scannedSkills: inventory.skills.length,
|
|
29
|
+
scannedSkills: inventory.scannedSkills ?? inventory.skills.length,
|
|
30
30
|
scannedInstallUnits: inventory.installUnits.length,
|
|
31
31
|
addedSkills: merged.addedSkills,
|
|
32
32
|
addedInstallUnits: merged.addedInstallUnits,
|