@hagicode/skillsbase 0.1.0-dev.20260406025440.0.0.local → 0.1.0-dev.3.1.cbbc838
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 +107 -53
- package/README.zh-CN.md +127 -0
- package/bin/skillsbase.mjs +0 -0
- package/dist/cli.mjs +205 -65
- package/package.json +8 -4
- package/templates/actions/skillsbase-manage/action.yml +96 -0
- package/templates/actions/skillsbase-sync/action.yml +9 -1
- package/templates/docs/maintainer-workflow.md +19 -7
- package/templates/skills/README.md +4 -4
- package/templates/workflows/skills-manage.yml +54 -0
- package/templates/workflows/skills-sync.yml +3 -12
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
|
@@ -1,78 +1,132 @@
|
|
|
1
|
-
# skillsbase
|
|
1
|
+
# @hagicode/skillsbase
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
重点是:本仓库只放 CLI 代码与模板,不提交受管 `skills/` 内容。
|
|
5
|
-
当前源码使用 `TypeScript + Vite 8`。
|
|
3
|
+
[中文文档](./README.zh-CN.md)
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
6
|
+
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
|
|
10
|
+
`skillsbase` is a standalone Node.js CLI for bootstrapping and maintaining managed skills repositories.
|
|
11
|
+
It ships the CLI and templates only. The generated `skills/` content belongs in the target repository, not in this package repository.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Requirements:
|
|
16
|
+
|
|
17
|
+
- Node.js `>= 22.12.0`
|
|
18
|
+
- npm `>= 10.9.2`
|
|
19
|
+
|
|
20
|
+
Install globally:
|
|
8
21
|
|
|
9
22
|
```bash
|
|
10
|
-
|
|
11
|
-
node ./bin/skillsbase.mjs sync
|
|
12
|
-
node ./bin/skillsbase.mjs sync --check
|
|
13
|
-
node ./bin/skillsbase.mjs add <skill-name>
|
|
14
|
-
node ./bin/skillsbase.mjs github_action --kind all
|
|
23
|
+
npm install --global @hagicode/skillsbase
|
|
15
24
|
```
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
Then run:
|
|
18
27
|
|
|
19
28
|
```bash
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
skillsbase --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
Initialize a managed repository:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
skillsbase init --repo /path/to/target-repo
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Sync managed skills from `sources.yaml`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
skillsbase sync --repo /path/to/target-repo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add one skill and sync immediately:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
skillsbase add documentation-writer --repo /path/to/target-repo
|
|
25
50
|
```
|
|
26
51
|
|
|
27
|
-
|
|
52
|
+
Remove one skill and sync immediately:
|
|
28
53
|
|
|
29
|
-
|
|
54
|
+
```bash
|
|
55
|
+
skillsbase remove documentation-writer --repo /path/to/target-repo
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Generate managed GitHub Actions assets:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
skillsbase github_action --repo /path/to/target-repo --kind all
|
|
62
|
+
```
|
|
30
63
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
| Command | Purpose | Example |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| `init` | Create the managed repository baseline. | `skillsbase init --repo ./my-skills-repo` |
|
|
69
|
+
| `sync` | Reconcile managed skills from `sources.yaml`. | `skillsbase sync --repo ./my-skills-repo` |
|
|
70
|
+
| `sync --check` | Validate drift without writing files. | `skillsbase sync --check --repo ./my-skills-repo` |
|
|
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` |
|
|
73
|
+
| `github_action` | Generate managed GitHub Actions workflow or action files. | `skillsbase github_action --repo ./my-skills-repo --kind workflow` |
|
|
74
|
+
|
|
75
|
+
Global options:
|
|
38
76
|
|
|
39
|
-
|
|
77
|
+
- `--repo <path>`: target repository path, defaults to the current directory
|
|
78
|
+
- `--help`, `-h`: show help
|
|
79
|
+
- `--version`, `-v`: show version
|
|
40
80
|
|
|
41
|
-
## Managed
|
|
81
|
+
## Managed Repository Contract
|
|
42
82
|
|
|
43
|
-
|
|
44
|
-
清单单一真相源,声明来源根目录、命名规则、包含列表与非交互默认值。
|
|
45
|
-
- `skills/<name>/SKILL.md`
|
|
46
|
-
受管输出。内容来自当前仓库内临时 `npx skills add` 安装结果,再转换为最终形态。
|
|
47
|
-
- `skills/<name>/.skill-source.json`
|
|
48
|
-
来源、转换、目标路径与安装元数据。
|
|
49
|
-
- `.github/workflows/skills-sync.yml`
|
|
50
|
-
受管 workflow,执行 `npm test` 与 `skillsbase sync --check`。
|
|
51
|
-
- `.github/actions/skillsbase-sync/action.yml`
|
|
52
|
-
可复用 composite action。
|
|
83
|
+
`skillsbase` manages files in the target repository, including:
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
- `sources.yaml`: the single source of truth for source roots, naming rules, include lists, and defaults
|
|
86
|
+
- `skills/<name>/SKILL.md`: managed skill output converted from installed source content
|
|
87
|
+
- `skills/<name>/.skill-source.json`: source, conversion, target-path, and install metadata
|
|
88
|
+
- `docs/maintainer-workflow.md`: maintainer guidance generated from the bundled templates
|
|
89
|
+
- `.github/workflows/skills-sync.yml`: reusable workflow for validation and sync checks
|
|
90
|
+
- `.github/actions/skillsbase-sync/action.yml`: reusable composite action
|
|
55
91
|
|
|
56
92
|
## Non-Interactive Defaults
|
|
57
93
|
|
|
58
|
-
-
|
|
59
|
-
- `init`
|
|
94
|
+
- The target repository defaults to the current working directory.
|
|
95
|
+
- `init` defaults the source roots to:
|
|
60
96
|
- first-party: `$HOME/.agents/skills`
|
|
61
97
|
- system: `$HOME/.codex/skills/.system`
|
|
62
|
-
- `add`
|
|
63
|
-
- `
|
|
64
|
-
-
|
|
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.
|
|
101
|
+
- `github_action` defaults to `--kind workflow`.
|
|
102
|
+
- When the CLI does not have enough context to write safely, it fails with diagnostics instead of prompting interactively.
|
|
65
103
|
|
|
66
|
-
##
|
|
104
|
+
## Development
|
|
67
105
|
|
|
68
|
-
|
|
69
|
-
- `sync --check` 不修改最终仓库状态;若发现漂移则返回非零退出码。
|
|
70
|
-
- `sync --allow-missing-sources` 可在来源根目录暂缺时跳过该来源。
|
|
71
|
-
- `github_action` 仅覆盖带 `Managed by skillsbase CLI` 标记的文件;冲突文件需显式 `--force`。
|
|
106
|
+
Common commands for working on this package:
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
```bash
|
|
109
|
+
npm run build
|
|
110
|
+
npm run cli -- --help
|
|
111
|
+
npm test
|
|
112
|
+
npm run smoke
|
|
113
|
+
npm run pack:check
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The published entry point is `bin/skillsbase.mjs`. In development, `npm run cli -- <args>` runs the TypeScript entry directly.
|
|
117
|
+
|
|
118
|
+
## Publishing Notes
|
|
119
|
+
|
|
120
|
+
The GitHub Actions workflow supports two npm authentication modes:
|
|
121
|
+
|
|
122
|
+
- Preferred: npm trusted publishing via GitHub Actions OIDC
|
|
123
|
+
- Fallback: `NPM_TOKEN` repository secret mapped to `NODE_AUTH_TOKEN`
|
|
124
|
+
|
|
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`.
|
|
128
|
+
The npm docs currently require Node.js `>= 22.14.0` and npm `>= 11.5.1` for trusted publishing.
|
|
129
|
+
|
|
130
|
+
## License
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
跑 CLI 单测。
|
|
77
|
-
- `npm run smoke`
|
|
78
|
-
在临时目录创建一个示例 managed repo,验证 `init -> sync -> sync --check -> github_action`。
|
|
132
|
+
MIT. See [LICENSE](./LICENSE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @hagicode/skillsbase
|
|
2
|
+
|
|
3
|
+
[English README](./README.md)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
6
|
+
[](https://www.npmjs.com/package/@hagicode/skillsbase)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
`skillsbase` 是一个独立的 Node.js CLI,用于初始化并维护受管 skills 仓库。
|
|
10
|
+
本仓库只提供 CLI 与模板,不提交目标仓库中的 `skills/` 产物。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
要求:
|
|
15
|
+
|
|
16
|
+
- Node.js `>= 22.12.0`
|
|
17
|
+
- npm `>= 10.9.2`
|
|
18
|
+
|
|
19
|
+
全局安装:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --global @hagicode/skillsbase
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
安装后可直接执行:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
skillsbase --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
初始化受管仓库:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
skillsbase init --repo /path/to/target-repo
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
按 `sources.yaml` 同步技能:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
skillsbase sync --repo /path/to/target-repo
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
添加单个技能并立即同步:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
skillsbase add documentation-writer --repo /path/to/target-repo
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
删除单个技能并立即同步:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
skillsbase remove documentation-writer --repo /path/to/target-repo
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
生成受管 GitHub Actions 资产:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
skillsbase github_action --repo /path/to/target-repo --kind all
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 命令
|
|
64
|
+
|
|
65
|
+
| 命令 | 用途 | 示例 |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `init` | 创建受管仓库基础结构 | `skillsbase init --repo ./my-skills-repo` |
|
|
68
|
+
| `sync` | 按 `sources.yaml` 对账并同步技能 | `skillsbase sync --repo ./my-skills-repo` |
|
|
69
|
+
| `sync --check` | 只校验漂移,不写文件 | `skillsbase sync --check --repo ./my-skills-repo` |
|
|
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` |
|
|
72
|
+
| `github_action` | 生成受管 GitHub Actions 工作流或 action 文件 | `skillsbase github_action --repo ./my-skills-repo --kind workflow` |
|
|
73
|
+
|
|
74
|
+
全局选项:
|
|
75
|
+
|
|
76
|
+
- `--repo <path>`:目标仓库路径,默认当前目录
|
|
77
|
+
- `--help`、`-h`:显示帮助
|
|
78
|
+
- `--version`、`-v`:显示版本
|
|
79
|
+
|
|
80
|
+
## 受管仓库约定
|
|
81
|
+
|
|
82
|
+
`skillsbase` 会在目标仓库中管理以下文件:
|
|
83
|
+
|
|
84
|
+
- `sources.yaml`:来源根目录、命名规则、包含列表与默认值的单一真相源
|
|
85
|
+
- `skills/<name>/SKILL.md`:由安装源内容转换得到的受管技能输出
|
|
86
|
+
- `skills/<name>/.skill-source.json`:来源、转换、目标路径与安装元数据
|
|
87
|
+
- `docs/maintainer-workflow.md`:由模板生成的维护说明
|
|
88
|
+
- `.github/workflows/skills-sync.yml`:用于校验与同步检查的工作流
|
|
89
|
+
- `.github/actions/skillsbase-sync/action.yml`:可复用 composite action
|
|
90
|
+
|
|
91
|
+
## 非交互默认行为
|
|
92
|
+
|
|
93
|
+
- 目标仓库默认是当前工作目录。
|
|
94
|
+
- `init` 默认来源根目录为:
|
|
95
|
+
- first-party:`$HOME/.agents/skills`
|
|
96
|
+
- system:`$HOME/.codex/skills/.system`
|
|
97
|
+
- `add` 默认写入第一个已声明的 source block;如需指定,传 `--source <key>`。
|
|
98
|
+
- `remove` 若仅命中一个 source block,会直接删除;若同名技能存在于多个 source block,必须显式传 `--source <key>`。
|
|
99
|
+
- `add`、`remove`、`sync` 均接受 `--allow-missing-sources`,以便在 CI 或仓库外 cwd 下保持一致的路径解析与失败语义。
|
|
100
|
+
- `github_action` 默认使用 `--kind workflow`。
|
|
101
|
+
- 若上下文不足以安全写入,CLI 会直接失败并输出诊断,不会进入交互提问。
|
|
102
|
+
|
|
103
|
+
## 开发
|
|
104
|
+
|
|
105
|
+
常用命令:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm run build
|
|
109
|
+
npm run cli -- --help
|
|
110
|
+
npm test
|
|
111
|
+
npm run smoke
|
|
112
|
+
npm run pack:check
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
发布入口为 `bin/skillsbase.mjs`。开发期可用 `npm run cli -- <args>` 直接运行 TypeScript 入口。
|
|
116
|
+
|
|
117
|
+
## 发布说明
|
|
118
|
+
|
|
119
|
+
GitHub Actions 发布流支持两种 npm 认证模式:
|
|
120
|
+
|
|
121
|
+
- 推荐:GitHub Actions OIDC 的 npm trusted publishing
|
|
122
|
+
- 兜底:仓库 `NPM_TOKEN` secret,经 `NODE_AUTH_TOKEN` 注入
|
|
123
|
+
|
|
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`,也仍然可以正常发布。
|
|
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
|
}
|
|
@@ -168,6 +170,56 @@ function validateSource(source) {
|
|
|
168
170
|
include: [...source.include]
|
|
169
171
|
};
|
|
170
172
|
}
|
|
173
|
+
function isRemoteRepositorySource(source) {
|
|
174
|
+
return source.kind === "github-repository";
|
|
175
|
+
}
|
|
176
|
+
function buildSourcePath(source, originalName) {
|
|
177
|
+
if (isRemoteRepositorySource(source)) return `${source.root}@${originalName}`;
|
|
178
|
+
return path.join(source.root, originalName);
|
|
179
|
+
}
|
|
180
|
+
function resolveSourceRoot(repoPath, source) {
|
|
181
|
+
if (isRemoteRepositorySource(source) || path.isAbsolute(source.root)) return source.root;
|
|
182
|
+
return path.resolve(repoPath, source.root);
|
|
183
|
+
}
|
|
184
|
+
function resolveSourcePath(repoPath, source, originalName) {
|
|
185
|
+
if (isRemoteRepositorySource(source)) return buildSourcePath(source, originalName);
|
|
186
|
+
return path.join(resolveSourceRoot(repoPath, source), originalName);
|
|
187
|
+
}
|
|
188
|
+
function cloneSource(source) {
|
|
189
|
+
return {
|
|
190
|
+
...source,
|
|
191
|
+
include: [...source.include ?? []]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function cloneManifestWithSources(manifest, sources) {
|
|
195
|
+
return {
|
|
196
|
+
...manifest,
|
|
197
|
+
sources: sources.map(cloneSource)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function sortInclude(include) {
|
|
201
|
+
return [...include].sort((left, right) => left.localeCompare(right));
|
|
202
|
+
}
|
|
203
|
+
function assertManifestHasSources(manifest) {
|
|
204
|
+
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`."] });
|
|
205
|
+
}
|
|
206
|
+
function getSourceKeys(manifest) {
|
|
207
|
+
return manifest.sources.map((source) => source.key).join(", ");
|
|
208
|
+
}
|
|
209
|
+
function findSourceByKey(manifest, sourceKey) {
|
|
210
|
+
const source = manifest.sources.find((candidate) => candidate.key === sourceKey);
|
|
211
|
+
if (!source) throw new CliError(`Unknown source key: ${sourceKey}`, { details: [`Declared sources: ${getSourceKeys(manifest)}`] });
|
|
212
|
+
return source;
|
|
213
|
+
}
|
|
214
|
+
function findSourcesBySkill(manifest, skillName) {
|
|
215
|
+
return manifest.sources.filter((source) => source.include.includes(skillName));
|
|
216
|
+
}
|
|
217
|
+
function buildMissingSkillError(skillName, options = {}) {
|
|
218
|
+
const matchingKeys = options.matchingKeys ?? [];
|
|
219
|
+
const details = options.sourceKey ? [`skill: ${skillName}`, `source: ${options.sourceKey}`] : [`skill: ${skillName}`];
|
|
220
|
+
if (matchingKeys.length > 0) details.push(`matching sources: ${matchingKeys.join(", ")}`);
|
|
221
|
+
return new CliError(options.sourceKey == null ? `Skill "${skillName}" is not declared in sources.yaml.` : `Skill "${skillName}" is not declared in source "${options.sourceKey}".`, { details });
|
|
222
|
+
}
|
|
171
223
|
function createManifest(repoPath, options = {}) {
|
|
172
224
|
return {
|
|
173
225
|
version: 1,
|
|
@@ -258,7 +310,7 @@ async function loadManifest(repoPath) {
|
|
|
258
310
|
const sourceStartMatch = /^ - key:\s*(.+)$/.exec(line);
|
|
259
311
|
if (sourceStartMatch) {
|
|
260
312
|
currentSource = {
|
|
261
|
-
key: parseScalar(sourceStartMatch[1]),
|
|
313
|
+
key: String(parseScalar(sourceStartMatch[1])),
|
|
262
314
|
include: []
|
|
263
315
|
};
|
|
264
316
|
manifest.sources.push(currentSource);
|
|
@@ -300,19 +352,25 @@ async function saveManifest(manifest) {
|
|
|
300
352
|
}
|
|
301
353
|
function buildManifestEntries(manifest, repoPath = manifest.repoPath) {
|
|
302
354
|
const entries = [];
|
|
303
|
-
for (const source of manifest.sources)
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
355
|
+
for (const source of manifest.sources) {
|
|
356
|
+
const resolvedSourceRoot = resolveSourceRoot(repoPath, source);
|
|
357
|
+
for (const originalName of source.include ?? []) {
|
|
358
|
+
const targetName = `${source.targetPrefix ?? ""}${originalName}`;
|
|
359
|
+
entries.push({
|
|
360
|
+
sourceKey: source.key,
|
|
361
|
+
sourceLabel: source.label,
|
|
362
|
+
sourceKind: source.kind,
|
|
363
|
+
remoteSource: isRemoteRepositorySource(source),
|
|
364
|
+
sourceRoot: source.root,
|
|
365
|
+
sourcePath: buildSourcePath(source, originalName),
|
|
366
|
+
resolvedSourceRoot,
|
|
367
|
+
resolvedSourcePath: resolveSourcePath(repoPath, source, originalName),
|
|
368
|
+
originalName,
|
|
369
|
+
targetName,
|
|
370
|
+
targetPath: path.join(repoPath, manifest.skillsRoot, targetName),
|
|
371
|
+
targetPathRelative: toPosix(path.join(manifest.skillsRoot, targetName))
|
|
372
|
+
});
|
|
373
|
+
}
|
|
316
374
|
}
|
|
317
375
|
const collisions = /* @__PURE__ */ new Map();
|
|
318
376
|
for (const entry of entries) {
|
|
@@ -325,19 +383,49 @@ function buildManifestEntries(manifest, repoPath = manifest.repoPath) {
|
|
|
325
383
|
return entries.sort((left, right) => left.targetName.localeCompare(right.targetName));
|
|
326
384
|
}
|
|
327
385
|
function addSkillToManifest(manifest, skillName, options = {}) {
|
|
328
|
-
|
|
329
|
-
const selectedSource = options.sourceKey == null ? manifest.sources[0] : manifest
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
selectedSource.include = [...include].sort((left, right) => left.localeCompare(right));
|
|
334
|
-
return {
|
|
335
|
-
...manifest,
|
|
336
|
-
sources: manifest.sources.map((source) => source.key === selectedSource.key ? { ...selectedSource } : {
|
|
386
|
+
assertManifestHasSources(manifest);
|
|
387
|
+
const selectedSource = options.sourceKey == null ? manifest.sources[0] : findSourceByKey(manifest, options.sourceKey);
|
|
388
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
389
|
+
if (source.key !== selectedSource.key) return source;
|
|
390
|
+
return {
|
|
337
391
|
...source,
|
|
338
|
-
include:
|
|
339
|
-
}
|
|
340
|
-
};
|
|
392
|
+
include: sortInclude(new Set(source.include ?? []).add(skillName))
|
|
393
|
+
};
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
function removeSkillFromManifest(manifest, skillName, options = {}) {
|
|
397
|
+
assertManifestHasSources(manifest);
|
|
398
|
+
const selectedSource = options.sourceKey == null ? null : findSourceByKey(manifest, options.sourceKey);
|
|
399
|
+
if (selectedSource != null) {
|
|
400
|
+
if (!selectedSource.include.includes(skillName)) {
|
|
401
|
+
const matchingKeys = findSourcesBySkill(manifest, skillName).map((source) => source.key);
|
|
402
|
+
throw buildMissingSkillError(skillName, {
|
|
403
|
+
sourceKey: selectedSource.key,
|
|
404
|
+
matchingKeys
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
408
|
+
if (source.key !== selectedSource.key) return source;
|
|
409
|
+
return {
|
|
410
|
+
...source,
|
|
411
|
+
include: sortInclude(source.include.filter((candidate) => candidate !== skillName))
|
|
412
|
+
};
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
415
|
+
const matchingSources = findSourcesBySkill(manifest, skillName);
|
|
416
|
+
if (matchingSources.length === 0) throw buildMissingSkillError(skillName);
|
|
417
|
+
if (matchingSources.length > 1) {
|
|
418
|
+
const matchingKeys = matchingSources.map((source) => source.key).sort((left, right) => left.localeCompare(right));
|
|
419
|
+
throw new CliError(`Skill "${skillName}" is declared in multiple sources.`, { details: [`matching sources: ${matchingKeys.join(", ")}`, `Use \`skillsbase remove ${skillName} --source <key>\` to disambiguate.`] });
|
|
420
|
+
}
|
|
421
|
+
const [uniqueSource] = matchingSources;
|
|
422
|
+
return cloneManifestWithSources(manifest, manifest.sources.map((source) => {
|
|
423
|
+
if (source.key !== uniqueSource.key) return source;
|
|
424
|
+
return {
|
|
425
|
+
...source,
|
|
426
|
+
include: sortInclude(source.include.filter((candidate) => candidate !== skillName))
|
|
427
|
+
};
|
|
428
|
+
}));
|
|
341
429
|
}
|
|
342
430
|
function buildMetadata(manifest, entry, installRecord) {
|
|
343
431
|
return {
|
|
@@ -362,6 +450,24 @@ function buildMetadata(manifest, entry, installRecord) {
|
|
|
362
450
|
//#endregion
|
|
363
451
|
//#region src/lib/installer.ts
|
|
364
452
|
var execFile$1 = promisify(execFile);
|
|
453
|
+
var INSTALL_TIMEOUT_MS = 18e4;
|
|
454
|
+
var REMOTE_INSTALL_RETRIES = 3;
|
|
455
|
+
var NPM_ENV_KEYS_TO_UNSET = [
|
|
456
|
+
"INIT_CWD",
|
|
457
|
+
"npm_command",
|
|
458
|
+
"npm_config_local_prefix",
|
|
459
|
+
"npm_config_prefix",
|
|
460
|
+
"npm_execpath",
|
|
461
|
+
"npm_lifecycle_event",
|
|
462
|
+
"npm_lifecycle_script",
|
|
463
|
+
"npm_package_json",
|
|
464
|
+
"npm_prefix"
|
|
465
|
+
];
|
|
466
|
+
function toInstallReference(entry) {
|
|
467
|
+
if (entry.remoteSource) return entry.sourcePath;
|
|
468
|
+
if (path.isAbsolute(entry.sourcePath) || entry.sourcePath.startsWith(`.${path.sep}`) || entry.sourcePath === ".") return entry.sourcePath;
|
|
469
|
+
return `.${path.sep}${entry.sourcePath}`;
|
|
470
|
+
}
|
|
365
471
|
function buildNpxArgs(manifest, subcommand, extraArgs) {
|
|
366
472
|
return [
|
|
367
473
|
"--yes",
|
|
@@ -377,7 +483,25 @@ function renderExecFailure(error) {
|
|
|
377
483
|
}
|
|
378
484
|
return String(error);
|
|
379
485
|
}
|
|
486
|
+
async function execSkillsAdd(repoPath, manifest, installReference, options) {
|
|
487
|
+
const childEnv = { ...options.env ?? process.env };
|
|
488
|
+
for (const key of NPM_ENV_KEYS_TO_UNSET) delete childEnv[key];
|
|
489
|
+
childEnv.INIT_CWD = repoPath;
|
|
490
|
+
await execFile$1("npx", buildNpxArgs(manifest, "add", [
|
|
491
|
+
installReference,
|
|
492
|
+
"--agent",
|
|
493
|
+
manifest.installAgent,
|
|
494
|
+
"--copy",
|
|
495
|
+
"-y"
|
|
496
|
+
]), {
|
|
497
|
+
cwd: repoPath,
|
|
498
|
+
env: childEnv,
|
|
499
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
500
|
+
timeout: INSTALL_TIMEOUT_MS
|
|
501
|
+
});
|
|
502
|
+
}
|
|
380
503
|
async function installIntoCurrentRepository(repoPath, manifest, entry, options = {}) {
|
|
504
|
+
const installReference = toInstallReference(entry);
|
|
381
505
|
const installPath = path.join(repoPath, ".agents", "skills", entry.originalName);
|
|
382
506
|
const lockPath = path.join(repoPath, "skills-lock.json");
|
|
383
507
|
const snapshot = {
|
|
@@ -385,52 +509,36 @@ async function installIntoCurrentRepository(repoPath, manifest, entry, options =
|
|
|
385
509
|
lockPath,
|
|
386
510
|
installTree: await snapshotTree(installPath),
|
|
387
511
|
lockText: await readFileIfExists(lockPath),
|
|
388
|
-
installReference
|
|
512
|
+
installReference
|
|
389
513
|
};
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
"-y"
|
|
397
|
-
]), {
|
|
398
|
-
cwd: repoPath,
|
|
399
|
-
env: options.env,
|
|
400
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
401
|
-
timeout: 6e4
|
|
402
|
-
});
|
|
514
|
+
const attempts = entry.remoteSource ? REMOTE_INSTALL_RETRIES : 1;
|
|
515
|
+
const failures = [];
|
|
516
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) try {
|
|
517
|
+
await execSkillsAdd(repoPath, manifest, installReference, options);
|
|
518
|
+
failures.length = 0;
|
|
519
|
+
break;
|
|
403
520
|
} catch (error) {
|
|
404
|
-
|
|
521
|
+
failures.push(`attempt ${attempt}/${attempts}: ${renderExecFailure(error)}`);
|
|
522
|
+
await restoreSnapshot(snapshot);
|
|
523
|
+
if (attempt === attempts) break;
|
|
405
524
|
}
|
|
525
|
+
if (failures.length > 0) throw new CliError(`skills install failed for ${entry.originalName}.`, { details: failures });
|
|
406
526
|
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."] });
|
|
407
527
|
return {
|
|
408
528
|
installPath,
|
|
409
529
|
lockPath,
|
|
410
|
-
installReference
|
|
530
|
+
installReference,
|
|
411
531
|
snapshot
|
|
412
532
|
};
|
|
413
533
|
}
|
|
414
|
-
async function cleanupInstalledSkill(repoPath,
|
|
415
|
-
let removeError = null;
|
|
416
|
-
try {
|
|
417
|
-
await execFile$1("npx", buildNpxArgs(manifest, "remove", [entry.originalName, "-y"]), {
|
|
418
|
-
cwd: repoPath,
|
|
419
|
-
env: options.env,
|
|
420
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
421
|
-
timeout: 6e4
|
|
422
|
-
});
|
|
423
|
-
} catch (error) {
|
|
424
|
-
removeError = error;
|
|
425
|
-
}
|
|
534
|
+
async function cleanupInstalledSkill(repoPath, _manifest, entry, installState, _options = {}) {
|
|
426
535
|
try {
|
|
427
536
|
await restoreSnapshot(installState.snapshot);
|
|
428
537
|
await removeIfEmptyUpward(path.join(repoPath, ".agents", "skills"), repoPath);
|
|
429
538
|
} catch (restoreError) {
|
|
430
539
|
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
431
|
-
throw new CliError(`Cleanup failed for ${entry.originalName}.`, { details: [restoreMessage
|
|
540
|
+
throw new CliError(`Cleanup failed for ${entry.originalName}.`, { details: [restoreMessage] });
|
|
432
541
|
}
|
|
433
|
-
if (removeError) throw new CliError(`skills uninstall failed for ${entry.originalName}.`, { details: [renderExecFailure(removeError)] });
|
|
434
542
|
}
|
|
435
543
|
async function snapshotTree(rootPath) {
|
|
436
544
|
if (!await pathExists(rootPath)) return null;
|
|
@@ -524,14 +632,15 @@ async function convertInstalledSkill(_manifest, entry, installState) {
|
|
|
524
632
|
//#endregion
|
|
525
633
|
//#region src/lib/sync-engine.ts
|
|
526
634
|
async function assertSourceState(entry, allowMissingSources) {
|
|
527
|
-
if (
|
|
635
|
+
if (entry.remoteSource) return { skip: false };
|
|
636
|
+
if (!await pathExists(entry.resolvedSourceRoot)) {
|
|
528
637
|
if (allowMissingSources) return {
|
|
529
638
|
skip: true,
|
|
530
|
-
reason: `missing source root: ${entry.
|
|
639
|
+
reason: `missing source root: ${entry.resolvedSourceRoot}`
|
|
531
640
|
};
|
|
532
|
-
throw new CliError(`Managed source root does not exist: ${entry.
|
|
641
|
+
throw new CliError(`Managed source root does not exist: ${entry.resolvedSourceRoot}`, { details: ["Use `skillsbase sync --allow-missing-sources` to skip missing roots."] });
|
|
533
642
|
}
|
|
534
|
-
if (!await pathExists(entry.
|
|
643
|
+
if (!await pathExists(entry.resolvedSourcePath)) throw new CliError(`Managed skill is missing from source root: ${entry.resolvedSourcePath}`, { details: [`source: ${entry.sourceKey}`, `skill: ${entry.originalName}`] });
|
|
535
644
|
return { skip: false };
|
|
536
645
|
}
|
|
537
646
|
async function assertManagedTargetWritable(manifest, entry) {
|
|
@@ -713,10 +822,16 @@ async function writeGithubActions(repoPath, options = {}) {
|
|
|
713
822
|
if (kind === "workflow" || kind === "all") targets.push({
|
|
714
823
|
relativePath: path.join(".github", "workflows", "skills-sync.yml"),
|
|
715
824
|
template: path.join("workflows", "skills-sync.yml")
|
|
825
|
+
}, {
|
|
826
|
+
relativePath: path.join(".github", "workflows", "skills-manage.yml"),
|
|
827
|
+
template: path.join("workflows", "skills-manage.yml")
|
|
716
828
|
});
|
|
717
|
-
if (kind === "action" || kind === "all") targets.push({
|
|
829
|
+
if (kind === "workflow" || kind === "action" || kind === "all") targets.push({
|
|
718
830
|
relativePath: path.join(".github", "actions", "skillsbase-sync", "action.yml"),
|
|
719
831
|
template: path.join("actions", "skillsbase-sync", "action.yml")
|
|
832
|
+
}, {
|
|
833
|
+
relativePath: path.join(".github", "actions", "skillsbase-manage", "action.yml"),
|
|
834
|
+
template: path.join("actions", "skillsbase-manage", "action.yml")
|
|
720
835
|
});
|
|
721
836
|
if (targets.length === 0) throw new CliError(`Unsupported github_action kind: ${kind}`, { details: ["Supported values: workflow, action, all."] });
|
|
722
837
|
const items = [];
|
|
@@ -826,6 +941,30 @@ async function runInitCommand(context) {
|
|
|
826
941
|
});
|
|
827
942
|
}
|
|
828
943
|
//#endregion
|
|
944
|
+
//#region src/commands/remove.ts
|
|
945
|
+
async function runRemoveCommand(context) {
|
|
946
|
+
const repoFlag = typeof context.flags.repo === "string" ? context.flags.repo : void 0;
|
|
947
|
+
const sourceFlag = typeof context.flags.source === "string" ? context.flags.source : void 0;
|
|
948
|
+
const repoPath = path.resolve(repoFlag ?? context.cwd);
|
|
949
|
+
const skillName = context.args[0];
|
|
950
|
+
if (!skillName) throw new CliError("`skillsbase remove` requires a skill name.", { details: ["Usage: `skillsbase remove <skill-name> [--source <key>]`."] });
|
|
951
|
+
const nextManifest = removeSkillFromManifest(await loadManifest(repoPath), skillName, { sourceKey: sourceFlag });
|
|
952
|
+
await saveManifest(nextManifest);
|
|
953
|
+
const result = await executeSync({
|
|
954
|
+
repoPath,
|
|
955
|
+
manifest: nextManifest,
|
|
956
|
+
env: context.env,
|
|
957
|
+
check: false,
|
|
958
|
+
allowMissingSources: context.flags["allow-missing-sources"] === true
|
|
959
|
+
});
|
|
960
|
+
return {
|
|
961
|
+
...result,
|
|
962
|
+
command: "remove",
|
|
963
|
+
title: `skillsbase remove ${skillName}`,
|
|
964
|
+
items: [`manifest updated: ${path.relative(repoPath, nextManifest.manifestPath) || "sources.yaml"}`, ...result.items]
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
829
968
|
//#region src/commands/sync.ts
|
|
830
969
|
async function runSyncCommand(context) {
|
|
831
970
|
const repoFlag = typeof context.flags.repo === "string" ? context.flags.repo : void 0;
|
|
@@ -898,6 +1037,7 @@ var commandMap = new Map([
|
|
|
898
1037
|
["init", runInitCommand],
|
|
899
1038
|
["sync", runSyncCommand],
|
|
900
1039
|
["add", runAddCommand],
|
|
1040
|
+
["remove", runRemoveCommand],
|
|
901
1041
|
["github_action", runGithubActionCommand],
|
|
902
1042
|
["github-action", runGithubActionCommand]
|
|
903
1043
|
]);
|
|
@@ -965,5 +1105,5 @@ async function runCli(argv, environment = {}) {
|
|
|
965
1105
|
//#endregion
|
|
966
1106
|
//#region src/cli-entry.ts
|
|
967
1107
|
var exitCode = await runCli(process.argv.slice(2));
|
|
968
|
-
process.exitCode
|
|
1108
|
+
process.exit(exitCode);
|
|
969
1109
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/skillsbase",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.3.1.cbbc838",
|
|
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"
|
|
@@ -19,17 +20,20 @@
|
|
|
19
20
|
"bin",
|
|
20
21
|
"dist",
|
|
21
22
|
"templates",
|
|
22
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.zh-CN.md"
|
|
23
25
|
],
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "vite build",
|
|
26
28
|
"clean": "rm -rf dist",
|
|
27
29
|
"cli": "node --import tsx ./src/cli-entry.ts",
|
|
28
30
|
"pack:check": "node ./scripts/verify-package.mjs",
|
|
29
|
-
"publish:
|
|
31
|
+
"publish:resolve-dev-version": "node ./scripts/resolve-dev-version.mjs",
|
|
32
|
+
"publish:verify-readiness": "node ./scripts/verify-publish-readiness.mjs",
|
|
30
33
|
"publish:verify-release": "node ./scripts/verify-release-version.mjs",
|
|
31
34
|
"test": "node --import tsx --test ./tests/*.test.ts",
|
|
32
|
-
"smoke": "node --import tsx ./scripts/smoke.mjs"
|
|
35
|
+
"smoke": "node --import tsx ./scripts/smoke.mjs",
|
|
36
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
33
37
|
},
|
|
34
38
|
"engines": {
|
|
35
39
|
"node": ">=22.12.0",
|
|
@@ -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
|
|
@@ -26,6 +26,14 @@ runs:
|
|
|
26
26
|
shell: bash
|
|
27
27
|
run: npm ci
|
|
28
28
|
|
|
29
|
+
- name: Upgrade npm
|
|
30
|
+
shell: bash
|
|
31
|
+
run: npm install --global npm@10.9.2
|
|
32
|
+
|
|
33
|
+
- name: Install skillsbase
|
|
34
|
+
shell: bash
|
|
35
|
+
run: npm install --global @hagicode/skillsbase
|
|
36
|
+
|
|
29
37
|
- name: Run tests
|
|
30
38
|
if: ${{ inputs.run-tests == 'true' }}
|
|
31
39
|
shell: bash
|
|
@@ -33,4 +41,4 @@ runs:
|
|
|
33
41
|
|
|
34
42
|
- name: Run sync check
|
|
35
43
|
shell: bash
|
|
36
|
-
run:
|
|
44
|
+
run: skillsbase sync --check --repo .
|
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Maintainer Workflow
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
- `sources.yaml`
|
|
17
|
-
- `skills/`
|
|
18
|
-
- `.skill-source.json`
|
|
19
|
-
- `skillsbase sync --check`
|
|
20
|
-
-
|
|
27
|
+
- `sources.yaml` is the single source of truth.
|
|
28
|
+
- `skills/` stores managed output only.
|
|
29
|
+
- `.skill-source.json` records source and conversion metadata.
|
|
30
|
+
- `skillsbase sync --check` validates drift without writing files.
|
|
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.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Managed Skills
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This directory contains managed skill output only.
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- Every skill directory must include `SKILL.md`
|
|
8
|
+
- Every managed directory must include `.skill-source.json`
|
|
9
|
+
- Do not edit managed files by hand; make changes in `sources.yaml` and the upstream source instead
|
|
@@ -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,17 +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: Run tests
|
|
36
|
-
run: npm test
|
|
37
|
-
|
|
38
|
-
- name: Validate managed repository state
|
|
39
|
-
run: node ./bin/skillsbase.mjs sync --check
|
|
30
|
+
run-tests: "true"
|