@hagicode/skillsbase 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +18 -2
- package/README.zh-CN.md +13 -2
- package/bin/skillsbase.mjs +0 -0
- package/dist/cli.mjs +158 -47
- package/package.json +3 -2
- package/templates/actions/skillsbase-manage/action.yml +96 -0
- package/templates/actions/skillsbase-sync/action.yml +4 -0
- package/templates/docs/maintainer-workflow.md +14 -2
- package/templates/workflows/skills-manage.yml +54 -0
- package/templates/workflows/skills-sync.yml +3 -15
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HagiCode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
6
6
|
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
7
|
-
[](./LICENSE)
|
|
8
|
+
[](https://nodejs.org/)
|
|
8
9
|
|
|
9
10
|
`skillsbase` is a standalone Node.js CLI for bootstrapping and maintaining managed skills repositories.
|
|
10
11
|
It ships the CLI and templates only. The generated `skills/` content belongs in the target repository, not in this package repository.
|
|
@@ -13,7 +14,7 @@ It ships the CLI and templates only. The generated `skills/` content belongs in
|
|
|
13
14
|
|
|
14
15
|
Requirements:
|
|
15
16
|
|
|
16
|
-
- Node.js `>=
|
|
17
|
+
- Node.js `>= 24.0.0`
|
|
17
18
|
- npm `>= 10.9.2`
|
|
18
19
|
|
|
19
20
|
Install globally:
|
|
@@ -48,6 +49,12 @@ Add one skill and sync immediately:
|
|
|
48
49
|
skillsbase add documentation-writer --repo /path/to/target-repo
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
Remove one skill and sync immediately:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
skillsbase remove documentation-writer --repo /path/to/target-repo
|
|
56
|
+
```
|
|
57
|
+
|
|
51
58
|
Generate managed GitHub Actions assets:
|
|
52
59
|
|
|
53
60
|
```bash
|
|
@@ -62,6 +69,7 @@ skillsbase github_action --repo /path/to/target-repo --kind all
|
|
|
62
69
|
| `sync` | Reconcile managed skills from `sources.yaml`. | `skillsbase sync --repo ./my-skills-repo` |
|
|
63
70
|
| `sync --check` | Validate drift without writing files. | `skillsbase sync --check --repo ./my-skills-repo` |
|
|
64
71
|
| `add <skill-name>` | Add a skill to a source block, then run sync. | `skillsbase add documentation-writer --repo ./my-skills-repo` |
|
|
72
|
+
| `remove <skill-name>` | Remove a skill from a source block, then run sync. | `skillsbase remove documentation-writer --repo ./my-skills-repo` |
|
|
65
73
|
| `github_action` | Generate managed GitHub Actions workflow or action files. | `skillsbase github_action --repo ./my-skills-repo --kind workflow` |
|
|
66
74
|
|
|
67
75
|
Global options:
|
|
@@ -88,6 +96,8 @@ Global options:
|
|
|
88
96
|
- first-party: `$HOME/.agents/skills`
|
|
89
97
|
- system: `$HOME/.codex/skills/.system`
|
|
90
98
|
- `add` writes to the first declared source block unless `--source <key>` is provided.
|
|
99
|
+
- `remove` deletes from the only matching source block automatically; if multiple source blocks include the same skill, pass `--source <key>`.
|
|
100
|
+
- `add`, `remove`, and `sync` accept `--allow-missing-sources` to keep path-resolution behavior consistent in CI or repo-external execution.
|
|
91
101
|
- `github_action` defaults to `--kind workflow`.
|
|
92
102
|
- When the CLI does not have enough context to write safely, it fails with diagnostics instead of prompting interactively.
|
|
93
103
|
|
|
@@ -113,4 +123,10 @@ The GitHub Actions workflow supports two npm authentication modes:
|
|
|
113
123
|
- Fallback: `NPM_TOKEN` repository secret mapped to `NODE_AUTH_TOKEN`
|
|
114
124
|
|
|
115
125
|
Before relying on trusted publishing, make sure the npm package settings for `@hagicode/skillsbase` trust the `HagiCode-org/skillsbase` repository.
|
|
126
|
+
Use workflow filename `npm-publish.yml` in npm trusted publisher settings, not the full `.github/workflows/...` path.
|
|
127
|
+
If the package has not been published before, that is still acceptable as long as the `hagicode` scope already exists and the publishing identity can create `@hagicode/skillsbase`.
|
|
116
128
|
The npm docs currently require Node.js `>= 22.14.0` and npm `>= 11.5.1` for trusted publishing.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT. See [LICENSE](./LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
6
6
|
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
7
|
-
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
9
|
`skillsbase` 是一个独立的 Node.js CLI,用于初始化并维护受管 skills 仓库。
|
|
10
10
|
本仓库只提供 CLI 与模板,不提交目标仓库中的 `skills/` 产物。
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
要求:
|
|
15
15
|
|
|
16
|
-
- Node.js `>=
|
|
16
|
+
- Node.js `>= 24.0.0`
|
|
17
17
|
- npm `>= 10.9.2`
|
|
18
18
|
|
|
19
19
|
全局安装:
|
|
@@ -48,6 +48,12 @@ skillsbase sync --repo /path/to/target-repo
|
|
|
48
48
|
skillsbase add documentation-writer --repo /path/to/target-repo
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
删除单个技能并立即同步:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
skillsbase remove documentation-writer --repo /path/to/target-repo
|
|
55
|
+
```
|
|
56
|
+
|
|
51
57
|
生成受管 GitHub Actions 资产:
|
|
52
58
|
|
|
53
59
|
```bash
|
|
@@ -62,6 +68,7 @@ skillsbase github_action --repo /path/to/target-repo --kind all
|
|
|
62
68
|
| `sync` | 按 `sources.yaml` 对账并同步技能 | `skillsbase sync --repo ./my-skills-repo` |
|
|
63
69
|
| `sync --check` | 只校验漂移,不写文件 | `skillsbase sync --check --repo ./my-skills-repo` |
|
|
64
70
|
| `add <skill-name>` | 将技能写入 source block 后执行同步 | `skillsbase add documentation-writer --repo ./my-skills-repo` |
|
|
71
|
+
| `remove <skill-name>` | 从 source block 删除技能后执行同步 | `skillsbase remove documentation-writer --repo ./my-skills-repo` |
|
|
65
72
|
| `github_action` | 生成受管 GitHub Actions 工作流或 action 文件 | `skillsbase github_action --repo ./my-skills-repo --kind workflow` |
|
|
66
73
|
|
|
67
74
|
全局选项:
|
|
@@ -88,6 +95,8 @@ skillsbase github_action --repo /path/to/target-repo --kind all
|
|
|
88
95
|
- first-party:`$HOME/.agents/skills`
|
|
89
96
|
- system:`$HOME/.codex/skills/.system`
|
|
90
97
|
- `add` 默认写入第一个已声明的 source block;如需指定,传 `--source <key>`。
|
|
98
|
+
- `remove` 若仅命中一个 source block,会直接删除;若同名技能存在于多个 source block,必须显式传 `--source <key>`。
|
|
99
|
+
- `add`、`remove`、`sync` 均接受 `--allow-missing-sources`,以便在 CI 或仓库外 cwd 下保持一致的路径解析与失败语义。
|
|
91
100
|
- `github_action` 默认使用 `--kind workflow`。
|
|
92
101
|
- 若上下文不足以安全写入,CLI 会直接失败并输出诊断,不会进入交互提问。
|
|
93
102
|
|
|
@@ -113,4 +122,6 @@ GitHub Actions 发布流支持两种 npm 认证模式:
|
|
|
113
122
|
- 兜底:仓库 `NPM_TOKEN` secret,经 `NODE_AUTH_TOKEN` 注入
|
|
114
123
|
|
|
115
124
|
若使用 trusted publishing,需先在 npm 的 `@hagicode/skillsbase` 包设置中,将 `HagiCode-org/skillsbase` 配置为 trusted publisher。
|
|
125
|
+
在 npm trusted publisher 设置里填写的 workflow filename 应为 `npm-publish.yml`,不要填完整的 `.github/workflows/...` 路径。
|
|
126
|
+
如果这个包还没有首发,只要 npm 上已经存在 `hagicode` scope,且当前发布身份有权创建 `@hagicode/skillsbase`,也仍然可以正常发布。
|
|
116
127
|
npm 官方当前要求 Node.js `>= 22.14.0` 且 npm `>= 11.5.1`。
|
package/bin/skillsbase.mjs
CHANGED
|
File without changes
|
package/dist/cli.mjs
CHANGED
|
@@ -25,12 +25,14 @@ function printCommandUsage(stdout) {
|
|
|
25
25
|
" init Create the managed repository baseline",
|
|
26
26
|
" sync Reconcile managed skills from sources.yaml",
|
|
27
27
|
" add Add a skill to a source block and sync",
|
|
28
|
+
" remove Remove a skill from a source block and sync",
|
|
28
29
|
" github_action Generate managed GitHub Actions assets",
|
|
29
30
|
"",
|
|
30
31
|
"Global Options:",
|
|
31
|
-
" --repo <path>
|
|
32
|
-
" --
|
|
33
|
-
" --
|
|
32
|
+
" --repo <path> Target repository path (default: current directory)",
|
|
33
|
+
" --allow-missing-sources Skip missing local source roots during sync/add/remove",
|
|
34
|
+
" --help, -h Show help",
|
|
35
|
+
" --version, -v Show version",
|
|
34
36
|
""
|
|
35
37
|
].join("\n"));
|
|
36
38
|
}
|
|
@@ -61,7 +63,6 @@ var DEFAULT_INSTALL_AGENT = "codex";
|
|
|
61
63
|
var DEFAULT_SKILLS_ROOT = "skills";
|
|
62
64
|
var DEFAULT_METADATA_FILE = ".skill-source.json";
|
|
63
65
|
var DEFAULT_MANAGED_BY = "skillsbase";
|
|
64
|
-
var DEFAULT_NODE_VERSION = "22.12.0";
|
|
65
66
|
//#endregion
|
|
66
67
|
//#region src/lib/files.ts
|
|
67
68
|
async function pathExists(targetPath) {
|
|
@@ -183,6 +184,41 @@ function resolveSourcePath(repoPath, source, originalName) {
|
|
|
183
184
|
if (isRemoteRepositorySource(source)) return buildSourcePath(source, originalName);
|
|
184
185
|
return path.join(resolveSourceRoot(repoPath, source), originalName);
|
|
185
186
|
}
|
|
187
|
+
function cloneSource(source) {
|
|
188
|
+
return {
|
|
189
|
+
...source,
|
|
190
|
+
include: [...source.include ?? []]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function cloneManifestWithSources(manifest, sources) {
|
|
194
|
+
return {
|
|
195
|
+
...manifest,
|
|
196
|
+
sources: sources.map(cloneSource)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function sortInclude(include) {
|
|
200
|
+
return [...include].sort((left, right) => left.localeCompare(right));
|
|
201
|
+
}
|
|
202
|
+
function assertManifestHasSources(manifest) {
|
|
203
|
+
if (manifest.sources.length === 0) throw new CliError("Manifest does not declare any source blocks.", { details: ["Run `skillsbase init` first or add a source block to `sources.yaml`."] });
|
|
204
|
+
}
|
|
205
|
+
function getSourceKeys(manifest) {
|
|
206
|
+
return manifest.sources.map((source) => source.key).join(", ");
|
|
207
|
+
}
|
|
208
|
+
function findSourceByKey(manifest, sourceKey) {
|
|
209
|
+
const source = manifest.sources.find((candidate) => candidate.key === sourceKey);
|
|
210
|
+
if (!source) throw new CliError(`Unknown source key: ${sourceKey}`, { details: [`Declared sources: ${getSourceKeys(manifest)}`] });
|
|
211
|
+
return source;
|
|
212
|
+
}
|
|
213
|
+
function findSourcesBySkill(manifest, skillName) {
|
|
214
|
+
return manifest.sources.filter((source) => source.include.includes(skillName));
|
|
215
|
+
}
|
|
216
|
+
function buildMissingSkillError(skillName, options = {}) {
|
|
217
|
+
const matchingKeys = options.matchingKeys ?? [];
|
|
218
|
+
const details = options.sourceKey ? [`skill: ${skillName}`, `source: ${options.sourceKey}`] : [`skill: ${skillName}`];
|
|
219
|
+
if (matchingKeys.length > 0) details.push(`matching sources: ${matchingKeys.join(", ")}`);
|
|
220
|
+
return new CliError(options.sourceKey == null ? `Skill "${skillName}" is not declared in sources.yaml.` : `Skill "${skillName}" is not declared in source "${options.sourceKey}".`, { details });
|
|
221
|
+
}
|
|
186
222
|
function createManifest(repoPath, options = {}) {
|
|
187
223
|
return {
|
|
188
224
|
version: 1,
|
|
@@ -346,19 +382,49 @@ function buildManifestEntries(manifest, repoPath = manifest.repoPath) {
|
|
|
346
382
|
return entries.sort((left, right) => left.targetName.localeCompare(right.targetName));
|
|
347
383
|
}
|
|
348
384
|
function addSkillToManifest(manifest, skillName, options = {}) {
|
|
349
|
-
|
|
350
|
-
const selectedSource = options.sourceKey == null ? manifest.sources[0] : manifest
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
selectedSource.include = [...include].sort((left, right) => left.localeCompare(right));
|
|
355
|
-
return {
|
|
356
|
-
...manifest,
|
|
357
|
-
sources: manifest.sources.map((source) => source.key === selectedSource.key ? { ...selectedSource } : {
|
|
385
|
+
assertManifestHasSources(manifest);
|
|
386
|
+
const selectedSource = options.sourceKey == null ? manifest.sources[0] : findSourceByKey(manifest, options.sourceKey);
|
|
387
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
388
|
+
if (source.key !== selectedSource.key) return source;
|
|
389
|
+
return {
|
|
358
390
|
...source,
|
|
359
|
-
include:
|
|
360
|
-
}
|
|
361
|
-
};
|
|
391
|
+
include: sortInclude(new Set(source.include ?? []).add(skillName))
|
|
392
|
+
};
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
function removeSkillFromManifest(manifest, skillName, options = {}) {
|
|
396
|
+
assertManifestHasSources(manifest);
|
|
397
|
+
const selectedSource = options.sourceKey == null ? null : findSourceByKey(manifest, options.sourceKey);
|
|
398
|
+
if (selectedSource != null) {
|
|
399
|
+
if (!selectedSource.include.includes(skillName)) {
|
|
400
|
+
const matchingKeys = findSourcesBySkill(manifest, skillName).map((source) => source.key);
|
|
401
|
+
throw buildMissingSkillError(skillName, {
|
|
402
|
+
sourceKey: selectedSource.key,
|
|
403
|
+
matchingKeys
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
407
|
+
if (source.key !== selectedSource.key) return source;
|
|
408
|
+
return {
|
|
409
|
+
...source,
|
|
410
|
+
include: sortInclude(source.include.filter((candidate) => candidate !== skillName))
|
|
411
|
+
};
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
const matchingSources = findSourcesBySkill(manifest, skillName);
|
|
415
|
+
if (matchingSources.length === 0) throw buildMissingSkillError(skillName);
|
|
416
|
+
if (matchingSources.length > 1) {
|
|
417
|
+
const matchingKeys = matchingSources.map((source) => source.key).sort((left, right) => left.localeCompare(right));
|
|
418
|
+
throw new CliError(`Skill "${skillName}" is declared in multiple sources.`, { details: [`matching sources: ${matchingKeys.join(", ")}`, `Use \`skillsbase remove ${skillName} --source <key>\` to disambiguate.`] });
|
|
419
|
+
}
|
|
420
|
+
const [uniqueSource] = matchingSources;
|
|
421
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
422
|
+
if (source.key !== uniqueSource.key) return source;
|
|
423
|
+
return {
|
|
424
|
+
...source,
|
|
425
|
+
include: sortInclude(source.include.filter((candidate) => candidate !== skillName))
|
|
426
|
+
};
|
|
427
|
+
}));
|
|
362
428
|
}
|
|
363
429
|
function buildMetadata(manifest, entry, installRecord) {
|
|
364
430
|
return {
|
|
@@ -383,6 +449,19 @@ function buildMetadata(manifest, entry, installRecord) {
|
|
|
383
449
|
//#endregion
|
|
384
450
|
//#region src/lib/installer.ts
|
|
385
451
|
var execFile$1 = promisify(execFile);
|
|
452
|
+
var INSTALL_TIMEOUT_MS = 18e4;
|
|
453
|
+
var REMOTE_INSTALL_RETRIES = 3;
|
|
454
|
+
var NPM_ENV_KEYS_TO_UNSET = [
|
|
455
|
+
"INIT_CWD",
|
|
456
|
+
"npm_command",
|
|
457
|
+
"npm_config_local_prefix",
|
|
458
|
+
"npm_config_prefix",
|
|
459
|
+
"npm_execpath",
|
|
460
|
+
"npm_lifecycle_event",
|
|
461
|
+
"npm_lifecycle_script",
|
|
462
|
+
"npm_package_json",
|
|
463
|
+
"npm_prefix"
|
|
464
|
+
];
|
|
386
465
|
function toInstallReference(entry) {
|
|
387
466
|
if (entry.remoteSource) return entry.sourcePath;
|
|
388
467
|
if (path.isAbsolute(entry.sourcePath) || entry.sourcePath.startsWith(`.${path.sep}`) || entry.sourcePath === ".") return entry.sourcePath;
|
|
@@ -403,6 +482,23 @@ function renderExecFailure(error) {
|
|
|
403
482
|
}
|
|
404
483
|
return String(error);
|
|
405
484
|
}
|
|
485
|
+
async function execSkillsAdd(repoPath, manifest, installReference, options) {
|
|
486
|
+
const childEnv = { ...options.env ?? process.env };
|
|
487
|
+
for (const key of NPM_ENV_KEYS_TO_UNSET) delete childEnv[key];
|
|
488
|
+
childEnv.INIT_CWD = repoPath;
|
|
489
|
+
await execFile$1("npx", buildNpxArgs(manifest, "add", [
|
|
490
|
+
installReference,
|
|
491
|
+
"--agent",
|
|
492
|
+
manifest.installAgent,
|
|
493
|
+
"--copy",
|
|
494
|
+
"-y"
|
|
495
|
+
]), {
|
|
496
|
+
cwd: repoPath,
|
|
497
|
+
env: childEnv,
|
|
498
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
499
|
+
timeout: INSTALL_TIMEOUT_MS
|
|
500
|
+
});
|
|
501
|
+
}
|
|
406
502
|
async function installIntoCurrentRepository(repoPath, manifest, entry, options = {}) {
|
|
407
503
|
const installReference = toInstallReference(entry);
|
|
408
504
|
const installPath = path.join(repoPath, ".agents", "skills", entry.originalName);
|
|
@@ -414,22 +510,18 @@ async function installIntoCurrentRepository(repoPath, manifest, entry, options =
|
|
|
414
510
|
lockText: await readFileIfExists(lockPath),
|
|
415
511
|
installReference
|
|
416
512
|
};
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
"-y"
|
|
424
|
-
]), {
|
|
425
|
-
cwd: repoPath,
|
|
426
|
-
env: options.env,
|
|
427
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
428
|
-
timeout: 6e4
|
|
429
|
-
});
|
|
513
|
+
const attempts = entry.remoteSource ? REMOTE_INSTALL_RETRIES : 1;
|
|
514
|
+
const failures = [];
|
|
515
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) try {
|
|
516
|
+
await execSkillsAdd(repoPath, manifest, installReference, options);
|
|
517
|
+
failures.length = 0;
|
|
518
|
+
break;
|
|
430
519
|
} catch (error) {
|
|
431
|
-
|
|
520
|
+
failures.push(`attempt ${attempt}/${attempts}: ${renderExecFailure(error)}`);
|
|
521
|
+
await restoreSnapshot(snapshot);
|
|
522
|
+
if (attempt === attempts) break;
|
|
432
523
|
}
|
|
524
|
+
if (failures.length > 0) throw new CliError(`skills install failed for ${entry.originalName}.`, { details: failures });
|
|
433
525
|
if (!await pathExists(installPath)) throw new CliError(`skills install did not create ${path.relative(repoPath, installPath)}.`, { details: ["The `npx skills` install output was not in the expected current-repository shape."] });
|
|
434
526
|
return {
|
|
435
527
|
installPath,
|
|
@@ -438,26 +530,14 @@ async function installIntoCurrentRepository(repoPath, manifest, entry, options =
|
|
|
438
530
|
snapshot
|
|
439
531
|
};
|
|
440
532
|
}
|
|
441
|
-
async function cleanupInstalledSkill(repoPath,
|
|
442
|
-
let removeError = null;
|
|
443
|
-
try {
|
|
444
|
-
await execFile$1("npx", buildNpxArgs(manifest, "remove", [entry.originalName, "-y"]), {
|
|
445
|
-
cwd: repoPath,
|
|
446
|
-
env: options.env,
|
|
447
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
448
|
-
timeout: 6e4
|
|
449
|
-
});
|
|
450
|
-
} catch (error) {
|
|
451
|
-
removeError = error;
|
|
452
|
-
}
|
|
533
|
+
async function cleanupInstalledSkill(repoPath, _manifest, entry, installState, _options = {}) {
|
|
453
534
|
try {
|
|
454
535
|
await restoreSnapshot(installState.snapshot);
|
|
455
536
|
await removeIfEmptyUpward(path.join(repoPath, ".agents", "skills"), repoPath);
|
|
456
537
|
} catch (restoreError) {
|
|
457
538
|
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
458
|
-
throw new CliError(`Cleanup failed for ${entry.originalName}.`, { details: [restoreMessage
|
|
539
|
+
throw new CliError(`Cleanup failed for ${entry.originalName}.`, { details: [restoreMessage] });
|
|
459
540
|
}
|
|
460
|
-
if (removeError) throw new CliError(`skills uninstall failed for ${entry.originalName}.`, { details: [renderExecFailure(removeError)] });
|
|
461
541
|
}
|
|
462
542
|
async function snapshotTree(rootPath) {
|
|
463
543
|
if (!await pathExists(rootPath)) return null;
|
|
@@ -736,15 +816,21 @@ async function writeManagedFile(targetPath, content, options = {}) {
|
|
|
736
816
|
}
|
|
737
817
|
async function writeGithubActions(repoPath, options = {}) {
|
|
738
818
|
const kind = options.kind ?? "workflow";
|
|
739
|
-
const variables = { NODE_VERSION:
|
|
819
|
+
const variables = { NODE_VERSION: "24" };
|
|
740
820
|
const targets = [];
|
|
741
821
|
if (kind === "workflow" || kind === "all") targets.push({
|
|
742
822
|
relativePath: path.join(".github", "workflows", "skills-sync.yml"),
|
|
743
823
|
template: path.join("workflows", "skills-sync.yml")
|
|
824
|
+
}, {
|
|
825
|
+
relativePath: path.join(".github", "workflows", "skills-manage.yml"),
|
|
826
|
+
template: path.join("workflows", "skills-manage.yml")
|
|
744
827
|
});
|
|
745
|
-
if (kind === "action" || kind === "all") targets.push({
|
|
828
|
+
if (kind === "workflow" || kind === "action" || kind === "all") targets.push({
|
|
746
829
|
relativePath: path.join(".github", "actions", "skillsbase-sync", "action.yml"),
|
|
747
830
|
template: path.join("actions", "skillsbase-sync", "action.yml")
|
|
831
|
+
}, {
|
|
832
|
+
relativePath: path.join(".github", "actions", "skillsbase-manage", "action.yml"),
|
|
833
|
+
template: path.join("actions", "skillsbase-manage", "action.yml")
|
|
748
834
|
});
|
|
749
835
|
if (targets.length === 0) throw new CliError(`Unsupported github_action kind: ${kind}`, { details: ["Supported values: workflow, action, all."] });
|
|
750
836
|
const items = [];
|
|
@@ -854,6 +940,30 @@ async function runInitCommand(context) {
|
|
|
854
940
|
});
|
|
855
941
|
}
|
|
856
942
|
//#endregion
|
|
943
|
+
//#region src/commands/remove.ts
|
|
944
|
+
async function runRemoveCommand(context) {
|
|
945
|
+
const repoFlag = typeof context.flags.repo === "string" ? context.flags.repo : void 0;
|
|
946
|
+
const sourceFlag = typeof context.flags.source === "string" ? context.flags.source : void 0;
|
|
947
|
+
const repoPath = path.resolve(repoFlag ?? context.cwd);
|
|
948
|
+
const skillName = context.args[0];
|
|
949
|
+
if (!skillName) throw new CliError("`skillsbase remove` requires a skill name.", { details: ["Usage: `skillsbase remove <skill-name> [--source <key>]`."] });
|
|
950
|
+
const nextManifest = removeSkillFromManifest(await loadManifest(repoPath), skillName, { sourceKey: sourceFlag });
|
|
951
|
+
await saveManifest(nextManifest);
|
|
952
|
+
const result = await executeSync({
|
|
953
|
+
repoPath,
|
|
954
|
+
manifest: nextManifest,
|
|
955
|
+
env: context.env,
|
|
956
|
+
check: false,
|
|
957
|
+
allowMissingSources: context.flags["allow-missing-sources"] === true
|
|
958
|
+
});
|
|
959
|
+
return {
|
|
960
|
+
...result,
|
|
961
|
+
command: "remove",
|
|
962
|
+
title: `skillsbase remove ${skillName}`,
|
|
963
|
+
items: [`manifest updated: ${path.relative(repoPath, nextManifest.manifestPath) || "sources.yaml"}`, ...result.items]
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
//#endregion
|
|
857
967
|
//#region src/commands/sync.ts
|
|
858
968
|
async function runSyncCommand(context) {
|
|
859
969
|
const repoFlag = typeof context.flags.repo === "string" ? context.flags.repo : void 0;
|
|
@@ -926,6 +1036,7 @@ var commandMap = new Map([
|
|
|
926
1036
|
["init", runInitCommand],
|
|
927
1037
|
["sync", runSyncCommand],
|
|
928
1038
|
["add", runAddCommand],
|
|
1039
|
+
["remove", runRemoveCommand],
|
|
929
1040
|
["github_action", runGithubActionCommand],
|
|
930
1041
|
["github-action", runGithubActionCommand]
|
|
931
1042
|
]);
|
|
@@ -993,5 +1104,5 @@ async function runCli(argv, environment = {}) {
|
|
|
993
1104
|
//#endregion
|
|
994
1105
|
//#region src/cli-entry.ts
|
|
995
1106
|
var exitCode = await runCli(process.argv.slice(2));
|
|
996
|
-
process.exitCode
|
|
1107
|
+
process.exit(exitCode);
|
|
997
1108
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/skillsbase",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Managed skills repository CLI",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"homepage": "https://github.com/HagiCode-org/skillsbase#readme",
|
|
6
7
|
"bugs": {
|
|
7
8
|
"url": "https://github.com/HagiCode-org/skillsbase/issues"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
39
|
+
"node": ">=24.0.0",
|
|
39
40
|
"npm": ">=10.9.2"
|
|
40
41
|
},
|
|
41
42
|
"publishConfig": {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Managed by skillsbase CLI.
|
|
2
|
+
|
|
3
|
+
name: skillsbase-manage
|
|
4
|
+
description: Run a managed add, remove, or sync operation for a skillsbase repository
|
|
5
|
+
|
|
6
|
+
inputs:
|
|
7
|
+
node-version:
|
|
8
|
+
description: Node.js version used for maintenance
|
|
9
|
+
required: false
|
|
10
|
+
default: "{{NODE_VERSION}}"
|
|
11
|
+
operation:
|
|
12
|
+
description: Maintenance operation to run (`add`, `remove`, or `sync`)
|
|
13
|
+
required: true
|
|
14
|
+
skill-name:
|
|
15
|
+
description: Skill name for `add` or `remove`
|
|
16
|
+
required: false
|
|
17
|
+
default: ""
|
|
18
|
+
source:
|
|
19
|
+
description: Optional source key for `add` or `remove`
|
|
20
|
+
required: false
|
|
21
|
+
default: ""
|
|
22
|
+
allow-missing-sources:
|
|
23
|
+
description: Skip missing local roots during the operation
|
|
24
|
+
required: false
|
|
25
|
+
default: "false"
|
|
26
|
+
run-tests:
|
|
27
|
+
description: Whether to run npm test after the maintenance command
|
|
28
|
+
required: false
|
|
29
|
+
default: "true"
|
|
30
|
+
|
|
31
|
+
runs:
|
|
32
|
+
using: composite
|
|
33
|
+
steps:
|
|
34
|
+
- name: Setup Node.js
|
|
35
|
+
uses: actions/setup-node@v4
|
|
36
|
+
with:
|
|
37
|
+
node-version: ${{ inputs.node-version }}
|
|
38
|
+
cache: npm
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
shell: bash
|
|
42
|
+
run: npm ci
|
|
43
|
+
|
|
44
|
+
- name: Upgrade npm
|
|
45
|
+
shell: bash
|
|
46
|
+
run: npm install --global npm@10.9.2
|
|
47
|
+
|
|
48
|
+
- name: Install skillsbase
|
|
49
|
+
shell: bash
|
|
50
|
+
run: npm install --global @hagicode/skillsbase
|
|
51
|
+
|
|
52
|
+
- name: Run maintenance command
|
|
53
|
+
shell: bash
|
|
54
|
+
env:
|
|
55
|
+
OPERATION: ${{ inputs.operation }}
|
|
56
|
+
SKILL_NAME: ${{ inputs.skill-name }}
|
|
57
|
+
SOURCE: ${{ inputs.source }}
|
|
58
|
+
ALLOW_MISSING_SOURCES: ${{ inputs.allow-missing-sources }}
|
|
59
|
+
run: |
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
|
|
62
|
+
base_args=(--repo .)
|
|
63
|
+
if [[ "${ALLOW_MISSING_SOURCES}" == "true" ]]; then
|
|
64
|
+
base_args+=(--allow-missing-sources)
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
case "${OPERATION}" in
|
|
68
|
+
add|remove)
|
|
69
|
+
if [[ -z "${SKILL_NAME}" ]]; then
|
|
70
|
+
echo "::error::skill-name is required when operation=${OPERATION}."
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
command=(skillsbase "${OPERATION}" "${SKILL_NAME}" "${base_args[@]}")
|
|
75
|
+
if [[ -n "${SOURCE}" ]]; then
|
|
76
|
+
command+=(--source "${SOURCE}")
|
|
77
|
+
fi
|
|
78
|
+
;;
|
|
79
|
+
sync)
|
|
80
|
+
command=(skillsbase sync "${base_args[@]}")
|
|
81
|
+
;;
|
|
82
|
+
*)
|
|
83
|
+
echo "::error::Unsupported operation: ${OPERATION}."
|
|
84
|
+
exit 1
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
printf 'Running:'
|
|
89
|
+
printf ' %q' "${command[@]}"
|
|
90
|
+
printf '\n'
|
|
91
|
+
"${command[@]}"
|
|
92
|
+
|
|
93
|
+
- name: Run tests
|
|
94
|
+
if: ${{ inputs.run-tests == 'true' }}
|
|
95
|
+
shell: bash
|
|
96
|
+
run: npm test
|
|
@@ -2,15 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
# Maintainer Workflow
|
|
4
4
|
|
|
5
|
-
The maintainer flow is `init -> add -> sync -> github_action`.
|
|
5
|
+
The maintainer flow is `init -> add/remove -> sync -> github_action`.
|
|
6
6
|
|
|
7
7
|
## Lifecycle
|
|
8
8
|
|
|
9
9
|
1. `skillsbase init`
|
|
10
|
-
2. `skillsbase add <skill-name>`
|
|
10
|
+
2. `skillsbase add <skill-name>` or `skillsbase remove <skill-name>`
|
|
11
11
|
3. `skillsbase sync`
|
|
12
12
|
4. `skillsbase github_action --kind all`
|
|
13
13
|
|
|
14
|
+
## GitHub Maintenance Path
|
|
15
|
+
|
|
16
|
+
Use `.github/workflows/skills-manage.yml` only for explicit non-interactive maintenance from GitHub UI.
|
|
17
|
+
|
|
18
|
+
- `operation` chooses `add`, `remove`, or `sync`
|
|
19
|
+
- `skill-name` is required for `add` and `remove`
|
|
20
|
+
- `source` is optional and maps to `--source`
|
|
21
|
+
- `allow-missing-sources` maps to `--allow-missing-sources`
|
|
22
|
+
- `run-tests` controls the post-operation `npm test`
|
|
23
|
+
- The workflow does not commit, push, or open pull requests
|
|
24
|
+
|
|
14
25
|
## Notes
|
|
15
26
|
|
|
16
27
|
- `sources.yaml` is the single source of truth.
|
|
@@ -18,3 +29,4 @@ The maintainer flow is `init -> add -> sync -> github_action`.
|
|
|
18
29
|
- `.skill-source.json` records source and conversion metadata.
|
|
19
30
|
- `skillsbase sync --check` validates drift without writing files.
|
|
20
31
|
- If a source root is unavailable, use `skillsbase sync --allow-missing-sources` to skip it.
|
|
32
|
+
- Local CLI commands remain the primary maintainer path.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Managed by skillsbase CLI.
|
|
2
|
+
|
|
3
|
+
name: Skills Manage
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
operation:
|
|
9
|
+
description: Operation to run (`add`, `remove`, or `sync`)
|
|
10
|
+
required: true
|
|
11
|
+
type: choice
|
|
12
|
+
default: sync
|
|
13
|
+
options:
|
|
14
|
+
- add
|
|
15
|
+
- remove
|
|
16
|
+
- sync
|
|
17
|
+
skill-name:
|
|
18
|
+
description: Skill name for `add` or `remove`
|
|
19
|
+
required: false
|
|
20
|
+
type: string
|
|
21
|
+
source:
|
|
22
|
+
description: Optional source key for `add` or `remove`
|
|
23
|
+
required: false
|
|
24
|
+
type: string
|
|
25
|
+
allow-missing-sources:
|
|
26
|
+
description: Skip missing local roots during the operation
|
|
27
|
+
required: false
|
|
28
|
+
type: boolean
|
|
29
|
+
default: false
|
|
30
|
+
run-tests:
|
|
31
|
+
description: Run `npm test` after the maintenance command
|
|
32
|
+
required: false
|
|
33
|
+
type: boolean
|
|
34
|
+
default: true
|
|
35
|
+
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
|
|
39
|
+
jobs:
|
|
40
|
+
manage:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout repository
|
|
44
|
+
uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- name: Run skillsbase maintenance
|
|
47
|
+
uses: ./.github/actions/skillsbase-manage
|
|
48
|
+
with:
|
|
49
|
+
node-version: {{NODE_VERSION}}
|
|
50
|
+
operation: ${{ inputs.operation }}
|
|
51
|
+
skill-name: ${{ inputs.skill-name }}
|
|
52
|
+
source: ${{ inputs.source }}
|
|
53
|
+
allow-missing-sources: ${{ inputs.allow-missing-sources }}
|
|
54
|
+
run-tests: ${{ inputs.run-tests }}
|
|
@@ -23,20 +23,8 @@ jobs:
|
|
|
23
23
|
- name: Checkout repository
|
|
24
24
|
uses: actions/checkout@v4
|
|
25
25
|
|
|
26
|
-
- name:
|
|
27
|
-
uses: actions/
|
|
26
|
+
- name: Run skillsbase validation
|
|
27
|
+
uses: ./.github/actions/skillsbase-sync
|
|
28
28
|
with:
|
|
29
29
|
node-version: {{NODE_VERSION}}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- name: Install dependencies
|
|
33
|
-
run: npm ci
|
|
34
|
-
|
|
35
|
-
- name: Install skillsbase
|
|
36
|
-
run: npm install --global @hagicode/skillsbase
|
|
37
|
-
|
|
38
|
-
- name: Run tests
|
|
39
|
-
run: npm test
|
|
40
|
-
|
|
41
|
-
- name: Validate managed repository state
|
|
42
|
-
run: skillsbase sync --check --repo .
|
|
30
|
+
run-tests: "true"
|