skills-manager-cli 2026.9.9 → 2026.9.10-1
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/README.md +45 -46
- package/README.zh-CN.md +44 -46
- package/dist/cli/bootstrap-prompt.js +80 -0
- package/dist/cli/bootstrap-prompt.js.map +1 -0
- package/dist/cli/bootstrap.js +82 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/main.js +114 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/{dashboard/server → core/services}/detection-log.js +5 -1
- package/dist/core/services/detection-log.js.map +1 -0
- package/dist/core/services/detection-service.js +196 -0
- package/dist/core/services/detection-service.js.map +1 -0
- package/dist/core/services/index.js +4 -1
- package/dist/core/services/index.js.map +1 -1
- package/dist/core/services/init-service.js +5 -1
- package/dist/core/services/init-service.js.map +1 -1
- package/dist/core/services/install-service.js +6 -5
- package/dist/core/services/install-service.js.map +1 -1
- package/dist/core/services/manager-skill-service.js +102 -0
- package/dist/core/services/manager-skill-service.js.map +1 -0
- package/dist/core/services/skill-home-resolver.js +5 -2
- package/dist/core/services/skill-home-resolver.js.map +1 -1
- package/dist/core/services/source-service.js +95 -5
- package/dist/core/services/source-service.js.map +1 -1
- package/dist/core/services/update-service.js +14 -13
- package/dist/core/services/update-service.js.map +1 -1
- package/dist/dashboard/server/main.js +13 -178
- package/dist/dashboard/server/main.js.map +1 -1
- package/dist/infra/fs-skill-home.js +8 -0
- package/dist/infra/fs-skill-home.js.map +1 -1
- package/dist/infra/git-cli.js +57 -16
- package/dist/infra/git-cli.js.map +1 -1
- package/dist/infra/runtime.js +3 -2
- package/dist/infra/runtime.js.map +1 -1
- package/dist/infra/shell-runner.js +30 -2
- package/dist/infra/shell-runner.js.map +1 -1
- package/dist/shared/redact.js +15 -0
- package/dist/shared/redact.js.map +1 -0
- package/docs/CLI.md +11 -1
- package/package.json +3 -1
- package/skills/skills-manager/SKILL.md +24 -5
- package/dist/dashboard/server/detection-log.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,62 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Manage your agent skills **from the conversation**: one `npx` command installs the manager skill, and from then on you just ask your agent — install, import, distribute, update, backfill provenance. Works with any skill-loading agent (Claude Code, Codex, Cursor, …).
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
-
**No setup needed** — just run:
|
|
10
|
-
|
|
11
9
|
```sh
|
|
12
|
-
npx skills-manager-cli
|
|
10
|
+
npx skills-manager-cli
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**Already have skills in `~/.claude/skills` or other agent runtimes?** Fold them in with `skills-manager init` — content moves into the hub, originals become symlinks (backed up first):
|
|
13
|
+
That is the whole setup. Bootstrap creates the skill hub (`~/.skills-manager`), installs the **manager skill** into it from the bundled copy, and mounts it (symlink) into the agents it detects on your machine — you confirm the list in the one prompt it shows.
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
skills-manager init --dry-run # preview
|
|
21
|
-
skills-manager init # import
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Want your agent to drive this CLI for you?** This repo ships an official agent skill (`skills/skills-manager/`) covering the full command surface plus the provenance-backfill workflow (adopt lockfile evidence → search & verify candidates → you approve each write, ADR-0012):
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
skills-manager add <this-repo-url> --skill skills-manager
|
|
28
|
-
skills-manager distribute --to user --skill skills-manager
|
|
29
|
-
```
|
|
15
|
+
Then go back to your agent and say:
|
|
30
16
|
|
|
31
|
-
|
|
17
|
+
> **帮我看看我的 skills:有哪些、装到哪些 agent 了、有没有能更新的**
|
|
18
|
+
> (“Take stock of my skills: what I have, which agents they are wired into, and what has updates”)
|
|
32
19
|
|
|
33
|
-
|
|
34
|
-
npm install -g skills-manager-cli
|
|
35
|
-
# or
|
|
36
|
-
pnpm add -g skills-manager-cli
|
|
37
|
-
skills-manager web
|
|
38
|
-
```
|
|
20
|
+
**Already have skills in `~/.claude/skills` or other agent runtimes?** Don't migrate during setup — just ask your agent:
|
|
39
21
|
|
|
40
|
-
|
|
22
|
+
> 帮我把现有的 skills 导入 skills-manager 并整理好来源
|
|
41
23
|
|
|
42
|
-
|
|
24
|
+
Content moves into the hub, originals become symlinks (backed up first). Prefer to preview it yourself first? `skills-manager init --dry-run`.
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
## How it works
|
|
45
27
|
|
|
46
|
-
```
|
|
47
|
-
|
|
28
|
+
```text
|
|
29
|
+
~/.skills-manager ← hub: the only content authority
|
|
30
|
+
skills/<name>/ · registry.yaml
|
|
31
|
+
│
|
|
32
|
+
│ distribute (symlink / copy)
|
|
33
|
+
▼
|
|
34
|
+
~/.claude/skills ~/.cursor/skills … ← agent runtime dirs
|
|
35
|
+
▲
|
|
36
|
+
│ drives, via `npx skills-manager-cli …`
|
|
37
|
+
your agent ⇄ the manager skill
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
npm install -g skills-manager-cli
|
|
54
|
-
# or
|
|
55
|
-
pnpm add -g skills-manager-cli
|
|
56
|
-
skills-manager web
|
|
57
|
-
skills-manager doctor
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
`web` opens a local Vue/Fastify dashboard by default. Use `--no-open` to keep the browser closed.
|
|
40
|
+
- The **manager skill** is the primary interface ([ADR-0014](docs/adr/0014-manager-skill-first-bootstrap.md)): the npx bootstrap installs it and nothing else; every management action afterwards happens through it, in agent conversation. No agent detected yet? Bootstrap still creates the hub and tells you how to mount later.
|
|
41
|
+
- It keeps itself current: every CLI run compares the bundled copy with the hub copy and silently refreshes it — unless you edited the hub copy yourself, in which case it is yours and stays untouched.
|
|
42
|
+
- The CLI underneath is a plain engine (JSON output, non-interactive flags) — see the [CLI reference](docs/CLI.md).
|
|
61
43
|
|
|
62
44
|
## Skill home layout
|
|
63
45
|
|
|
@@ -74,14 +56,15 @@ Skill home resolution priority:
|
|
|
74
56
|
1. `--home <path>`
|
|
75
57
|
2. `SKILL_HOME`
|
|
76
58
|
3. current working directory when it already looks like a skill home
|
|
77
|
-
4. `~/.skills-manager
|
|
59
|
+
4. `~/.skills-manager` — created by bootstrap only; other commands point you at it instead of creating it
|
|
78
60
|
|
|
79
61
|
## Common commands
|
|
80
62
|
|
|
81
63
|
```sh
|
|
82
|
-
skills-manager
|
|
83
|
-
skills-manager
|
|
84
|
-
skills-manager
|
|
64
|
+
npx skills-manager-cli # bootstrap (default when no subcommand)
|
|
65
|
+
skills-manager bootstrap --agent claude-code # scripted bootstrap / mount later
|
|
66
|
+
skills-manager doctor
|
|
67
|
+
skills-manager list
|
|
85
68
|
skills-manager add owner/repo --all --yes
|
|
86
69
|
skills-manager distribute --to user --skill my-skill --agent claude-code
|
|
87
70
|
skills-manager update --plan
|
|
@@ -99,6 +82,22 @@ skills-manager archive old-skill
|
|
|
99
82
|
|
|
100
83
|
Sources can be GitHub shorthand (`owner/repo`), Git URLs, GitHub tree URLs, or local paths.
|
|
101
84
|
|
|
85
|
+
## Optional: global install & dashboard
|
|
86
|
+
|
|
87
|
+
Prefer typing a bare command? Install globally:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
npm install -g skills-manager-cli # or pnpm add -g skills-manager-cli
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Prefer a visual interface? The local dashboard is an optional alternative to the conversation:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
skills-manager web # http://127.0.0.1:4777, --no-open to skip the browser
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
It offers the same library on one page: browse, install, update, and distribute. It never creates the hub — run bootstrap first.
|
|
100
|
+
|
|
102
101
|
## Development
|
|
103
102
|
|
|
104
103
|
```sh
|
|
@@ -109,6 +108,6 @@ pnpm test
|
|
|
109
108
|
|
|
110
109
|
## Documentation
|
|
111
110
|
|
|
112
|
-
- **[Getting Started](docs/GETTING_STARTED.md)** —
|
|
111
|
+
- **[Getting Started](docs/GETTING_STARTED.md)** — First run, importing existing skills, common tasks
|
|
113
112
|
- **[CLI Reference](docs/CLI.md)** — Full command reference and advanced usage
|
|
114
113
|
- **[Architecture](CONTEXT.md)** — Project structure and design decisions
|
package/README.zh-CN.md
CHANGED
|
@@ -2,62 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **简体中文**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
在**对话里**管理你的 agent skills:一条 `npx` 命令装上 manager skill,之后一切都直接吩咐你的 agent——安装、导入、分发、更新、补齐来源。任何会加载 skills 的 agent 都能用(Claude Code、Codex、Cursor、…)。
|
|
6
6
|
|
|
7
7
|
## 快速开始
|
|
8
8
|
|
|
9
|
-
**无需任何安装** —— 直接运行:
|
|
10
|
-
|
|
11
9
|
```sh
|
|
12
|
-
npx skills-manager-cli
|
|
10
|
+
npx skills-manager-cli
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**已经在 `~/.claude/skills` 或其他 agent 运行时目录里有技能了?** 用 `skills-manager init` 一键收纳 —— 内容移入技能库,原位置变成软链接(会先备份):
|
|
13
|
+
这就是全部的安装步骤。bootstrap 会创建技能库(`~/.skills-manager`),从包内副本把 **manager skill** 种进库,并(以软链接)挂载到它检测到的 agent——展示一次多选让你确认清单。
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
skills-manager init --dry-run # 预览
|
|
21
|
-
skills-manager init # 导入
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**想让 agent 直接替你操作这个 CLI?** 本仓库自带一个官方 agent skill(`skills/skills-manager/`),覆盖全部命令,并内置「来源补齐」工作流(采纳锁文件证据 → 搜索并验证候选 → 你逐条拍板写入,ADR-0012):
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
skills-manager add <本仓库地址> --skill skills-manager
|
|
28
|
-
skills-manager distribute --to user --skill skills-manager
|
|
29
|
-
```
|
|
15
|
+
然后回到你的 agent,说:
|
|
30
16
|
|
|
31
|
-
|
|
17
|
+
> **帮我看看我的 skills:有哪些、装到哪些 agent 了、有没有能更新的**
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
npm install -g skills-manager-cli
|
|
35
|
-
# 或
|
|
36
|
-
pnpm add -g skills-manager-cli
|
|
37
|
-
skills-manager web
|
|
38
|
-
```
|
|
19
|
+
**`~/.claude/skills` 等运行时目录里已经有 skills 了?** 不必在安装时迁移——直接吩咐:
|
|
39
20
|
|
|
40
|
-
|
|
21
|
+
> 帮我把现有的 skills 导入 skills-manager 并整理好来源
|
|
41
22
|
|
|
42
|
-
|
|
23
|
+
内容会移入库中,原位置变成软链接(先备份)。想先自己预览?`skills-manager init --dry-run`。
|
|
43
24
|
|
|
44
|
-
|
|
25
|
+
## 工作原理
|
|
45
26
|
|
|
46
|
-
```
|
|
47
|
-
|
|
27
|
+
```text
|
|
28
|
+
~/.skills-manager ← 技能库:唯一的内容权威
|
|
29
|
+
skills/<name>/ · registry.yaml
|
|
30
|
+
│
|
|
31
|
+
│ 分发(软链接 / 拷贝)
|
|
32
|
+
▼
|
|
33
|
+
~/.claude/skills ~/.cursor/skills … ← agent 运行时目录
|
|
34
|
+
▲
|
|
35
|
+
│ 通过 `npx skills-manager-cli …` 驱动
|
|
36
|
+
你的 agent ⇄ manager skill
|
|
48
37
|
```
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
npm install -g skills-manager-cli
|
|
54
|
-
# 或
|
|
55
|
-
pnpm add -g skills-manager-cli
|
|
56
|
-
skills-manager web
|
|
57
|
-
skills-manager doctor
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
`web` 默认自动打开本地 Vue/Fastify 控制台。加 `--no-open` 可保持浏览器关闭。
|
|
39
|
+
- **manager skill** 是主界面([ADR-0014](docs/adr/0014-manager-skill-first-bootstrap.md)):npx bootstrap 只装它、别的什么都不做;之后所有管理动作都通过它在 agent 对话里完成。还没装任何 agent?bootstrap 照样建好技能库,并告诉你之后怎么挂载。
|
|
40
|
+
- 它自己保持最新:CLI 每次运行都会对比包内副本与库内副本并静默刷新——除非你自己改过库内副本,那就是你的了,不再碰它。
|
|
41
|
+
- 底层的 CLI 是纯粹的引擎(JSON 输出、非交互 flag)——见 [CLI 参考](docs/CLI.zh-CN.md)。
|
|
61
42
|
|
|
62
43
|
## 技能库(skill home)结构
|
|
63
44
|
|
|
@@ -74,14 +55,15 @@ skills-manager doctor
|
|
|
74
55
|
1. `--home <path>`
|
|
75
56
|
2. `SKILL_HOME`
|
|
76
57
|
3. 当前目录(当它已经是一个技能库时)
|
|
77
|
-
4. `~/.skills-manager
|
|
58
|
+
4. `~/.skills-manager` —— 仅由 bootstrap 创建;其他命令只会提示你先跑 bootstrap,不再隐式创建
|
|
78
59
|
|
|
79
60
|
## 常用命令
|
|
80
61
|
|
|
81
62
|
```sh
|
|
82
|
-
skills-manager
|
|
83
|
-
skills-manager
|
|
84
|
-
skills-manager
|
|
63
|
+
npx skills-manager-cli # bootstrap(不带子命令时的默认行为)
|
|
64
|
+
skills-manager bootstrap --agent claude-code # 脚本化 bootstrap / 之后再挂载
|
|
65
|
+
skills-manager doctor
|
|
66
|
+
skills-manager list
|
|
85
67
|
skills-manager add owner/repo --all --yes
|
|
86
68
|
skills-manager distribute --to user --skill my-skill --agent claude-code
|
|
87
69
|
skills-manager update --plan
|
|
@@ -99,6 +81,22 @@ skills-manager archive old-skill
|
|
|
99
81
|
|
|
100
82
|
来源(source)支持 GitHub 简写(`owner/repo`)、Git URL、GitHub tree URL 或本地路径。
|
|
101
83
|
|
|
84
|
+
## 可选:全局安装与 dashboard
|
|
85
|
+
|
|
86
|
+
想直接敲裸命令?全局安装:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npm install -g skills-manager-cli # 或 pnpm add -g skills-manager-cli
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
想要可视化界面?本地 dashboard 是对话之外的可选替代:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
skills-manager web # http://127.0.0.1:4777,--no-open 不自动开浏览器
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
它在单页里提供同一个技能库:浏览、安装、更新、分发。它不会创建技能库——请先跑 bootstrap。
|
|
99
|
+
|
|
102
100
|
## 开发
|
|
103
101
|
|
|
104
102
|
```sh
|
|
@@ -109,6 +107,6 @@ pnpm test
|
|
|
109
107
|
|
|
110
108
|
## 文档
|
|
111
109
|
|
|
112
|
-
- **[入门指南](docs/GETTING_STARTED.zh-CN.md)** ——
|
|
110
|
+
- **[入门指南](docs/GETTING_STARTED.zh-CN.md)** —— 首次运行、导入现有技能、常见任务
|
|
113
111
|
- **[CLI 参考](docs/CLI.zh-CN.md)** —— 完整命令参考与进阶用法
|
|
114
112
|
- **[架构](CONTEXT.md)** —— 项目结构与设计决策(英文)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
const DIM = '\x1b[2m';
|
|
3
|
+
const BOLD = '\x1b[1m';
|
|
4
|
+
const CYAN = '\x1b[36m';
|
|
5
|
+
const RESET = '\x1b[0m';
|
|
6
|
+
/**
|
|
7
|
+
* Minimal TTY multi-select (the CLI's only interactive moment — ADR-0014):
|
|
8
|
+
* ↑/↓ move, space toggles, `a` toggles all, enter confirms, ctrl-c aborts.
|
|
9
|
+
* Returns the selected ids; all options start checked unless `allByDefault` is false.
|
|
10
|
+
*/
|
|
11
|
+
export async function multiSelect(title, options, config = {}) {
|
|
12
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
13
|
+
throw new Error('multiSelect requires a TTY');
|
|
14
|
+
if (options.length === 0)
|
|
15
|
+
return [];
|
|
16
|
+
const checked = options.map(() => config.allByDefault !== false);
|
|
17
|
+
let cursor = 0;
|
|
18
|
+
const stdin = process.stdin;
|
|
19
|
+
readline.emitKeypressEvents(stdin);
|
|
20
|
+
const priorRaw = stdin.isRaw ?? false;
|
|
21
|
+
stdin.setRawMode(true);
|
|
22
|
+
stdin.resume();
|
|
23
|
+
process.stdout.write('\x1b[?25l');
|
|
24
|
+
const cleanup = () => {
|
|
25
|
+
process.stdout.write('\x1b[?25h\n');
|
|
26
|
+
stdin.setRawMode(priorRaw);
|
|
27
|
+
if (!priorRaw)
|
|
28
|
+
stdin.pause();
|
|
29
|
+
};
|
|
30
|
+
const render = () => {
|
|
31
|
+
const lines = [
|
|
32
|
+
`${BOLD}${title}${RESET}`,
|
|
33
|
+
...options.map((option, index) => {
|
|
34
|
+
const marker = checked[index] ? '[x]' : '[ ]';
|
|
35
|
+
const pointer = index === cursor ? `${CYAN}❯${RESET} ` : ' ';
|
|
36
|
+
const detail = option.detail ? ` ${DIM}${option.detail}${RESET}` : '';
|
|
37
|
+
return `${pointer}${marker} ${option.label}${detail}`;
|
|
38
|
+
}),
|
|
39
|
+
`${DIM}↑/↓ move · space toggle · a all · enter confirm · ctrl-c abort${RESET}`,
|
|
40
|
+
];
|
|
41
|
+
const total = lines.length + 1; // +1: the newline the previous render ended on
|
|
42
|
+
process.stdout.write(`\x1b[${total}A\x1b[0J`);
|
|
43
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
44
|
+
};
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const finish = (selection) => {
|
|
47
|
+
stdin.removeListener('keypress', onKey);
|
|
48
|
+
cleanup();
|
|
49
|
+
resolve(selection);
|
|
50
|
+
};
|
|
51
|
+
const onKey = (_chunk, key) => {
|
|
52
|
+
if (key.ctrl && key.name === 'c') {
|
|
53
|
+
finish([]);
|
|
54
|
+
process.exitCode = 130;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (key.name === 'up' || key.name === 'k')
|
|
58
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
59
|
+
else if (key.name === 'down' || key.name === 'j')
|
|
60
|
+
cursor = (cursor + 1) % options.length;
|
|
61
|
+
else if (key.name === 'space')
|
|
62
|
+
checked[cursor] = !checked[cursor];
|
|
63
|
+
else if (key.name === 'a') {
|
|
64
|
+
const all = checked.every(Boolean);
|
|
65
|
+
for (let i = 0; i < checked.length; i += 1)
|
|
66
|
+
checked[i] = !all;
|
|
67
|
+
}
|
|
68
|
+
else if (key.name === 'return') {
|
|
69
|
+
finish(options.filter((_, index) => checked[index]).map((option) => option.id));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
return;
|
|
74
|
+
render();
|
|
75
|
+
};
|
|
76
|
+
stdin.on('keypress', onKey);
|
|
77
|
+
render();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=bootstrap-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap-prompt.js","sourceRoot":"","sources":["../../src/cli/bootstrap-prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { createRuntimeServices, projectRootFromImportMeta } from '../infra/runtime.js';
|
|
3
|
+
import { MANAGER_SKILL_NAME } from '../core/index.js';
|
|
4
|
+
import { normalizeGitSourceUrl } from '../core/services/source-service.js';
|
|
5
|
+
import { SkillsManagerError } from '../shared/errors.js';
|
|
6
|
+
import { multiSelect } from './bootstrap-prompt.js';
|
|
7
|
+
const pkg = createRequire(import.meta.url)('../../package.json');
|
|
8
|
+
const projectRoot = projectRootFromImportMeta(import.meta.url);
|
|
9
|
+
const STARTER_PROMPT_ZH = '帮我看看我的 skills:有哪些、装到哪些 agent 了、有没有能更新的';
|
|
10
|
+
const STARTER_PROMPT_EN = 'Take stock of my skills: what I have, which agents they are wired into, and what has updates';
|
|
11
|
+
const IMPORT_PROMPT_ZH = '帮我把现有的 skills 导入 skills-manager 并整理好来源';
|
|
12
|
+
export function managerSkillBundle() {
|
|
13
|
+
const raw = pkg.repository?.url || 'shenysun/skills-manager';
|
|
14
|
+
return { root: projectRoot, version: pkg.version, repoUrl: normalizeGitSourceUrl(raw.replace(/^git\+/, '')) };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The one-time setup, and the product's front door (ADR-0014): create the hub,
|
|
18
|
+
* seed the manager skill from the bundled copy, mount it to the chosen agents,
|
|
19
|
+
* print the starter prompt. It imports nothing — importing existing runtime
|
|
20
|
+
* skills is a conversational act for later, through the manager skill itself.
|
|
21
|
+
*/
|
|
22
|
+
export async function runBootstrap(opts, cmd) {
|
|
23
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
24
|
+
const s = createRuntimeServices({ home: globalOpts.home, ensureDefaultHub: true }, projectRoot);
|
|
25
|
+
const bundle = managerSkillBundle();
|
|
26
|
+
const seeded = s.managerSkill.seed(bundle);
|
|
27
|
+
const seedNotes = {
|
|
28
|
+
seeded: `installed the manager skill (skills-manager v${bundle.version})`,
|
|
29
|
+
refreshed: `updated the manager skill to v${bundle.version}`,
|
|
30
|
+
'up-to-date': `manager skill already current (v${bundle.version})`,
|
|
31
|
+
'user-managed': 'kept the existing skills-manager copy (installed or edited outside bootstrap)',
|
|
32
|
+
};
|
|
33
|
+
console.log(`• skill home: ${s.resolution.root}${s.resolution.created ? ' (created)' : ''}`);
|
|
34
|
+
console.log(`• ${seedNotes[seeded.status]}`);
|
|
35
|
+
const detected = s.catalog.detected();
|
|
36
|
+
const agents = await chooseAgents(opts, detected, s.catalog.resolveGlobalDir.bind(s.catalog));
|
|
37
|
+
if (agents.length > 0) {
|
|
38
|
+
try {
|
|
39
|
+
const applied = s.distribute.apply({ to: 'user', skills: [MANAGER_SKILL_NAME], agents, mode: 'symlink', force: Boolean(opts.force) });
|
|
40
|
+
const dirs = [...new Set(applied.entries.map((entry) => entry.runtimePath))];
|
|
41
|
+
console.log(`• mounted to: ${dirs.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof SkillsManagerError && error.code === 'distribute_foreign_exists') {
|
|
45
|
+
throw new SkillsManagerError('bootstrap_foreign_runtime', `${error.message} Re-run with --force to replace it, or remove it first.`);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log('• manager skill mounted nowhere yet.');
|
|
52
|
+
console.log(' To mount later: npx skills-manager-cli bootstrap --agent <id>');
|
|
53
|
+
}
|
|
54
|
+
// Bootstrap never imports — it only reports what the user can bring in later (ADR-0014).
|
|
55
|
+
try {
|
|
56
|
+
const preview = s.init.run({ dryRun: true });
|
|
57
|
+
if (preview.imported.length > 0) {
|
|
58
|
+
const conflicts = preview.conflicts.length > 0 ? ` (+${preview.conflicts.length} needing decisions)` : '';
|
|
59
|
+
console.log(`• found ${preview.imported.length} existing skill(s) in your agent dirs${conflicts}.`);
|
|
60
|
+
console.log(` Import them later from your agent: “${IMPORT_PROMPT_ZH}”`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* zero detected agents — there is nothing to preview */
|
|
65
|
+
}
|
|
66
|
+
s.activity.record({ action: 'cli-bootstrap', summary: `Bootstrap: ${seeded.status}, mounted to ${agents.join(', ') || 'none'}`, details: { seeded: seeded.status, agents } });
|
|
67
|
+
console.log('\nAll set. Go back to your agent and say:');
|
|
68
|
+
console.log(` “${STARTER_PROMPT_ZH}”`);
|
|
69
|
+
console.log(` (“${STARTER_PROMPT_EN}”)`);
|
|
70
|
+
}
|
|
71
|
+
/** `--agent` wins; one detected agent needs no question; several get the picker in a TTY, all of them otherwise. */
|
|
72
|
+
async function chooseAgents(opts, detected, resolveGlobalDir) {
|
|
73
|
+
if (opts.agent && opts.agent.length > 0)
|
|
74
|
+
return opts.agent;
|
|
75
|
+
if (detected.length <= 1)
|
|
76
|
+
return detected;
|
|
77
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
78
|
+
return detected;
|
|
79
|
+
const options = detected.map((id) => ({ id, label: id, detail: resolveGlobalDir(id) ?? undefined }));
|
|
80
|
+
return multiSelect('Install the manager skill into:', options);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/cli/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAA2B,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAqB,MAAM,uBAAuB,CAAC;AAEvE,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAuD,CAAC;AACvH,MAAM,WAAW,GAAG,yBAAyB,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/D,MAAM,iBAAiB,GAAG,wCAAwC,CAAC;AACnE,MAAM,iBAAiB,GAAG,8FAA8F,CAAC;AACzH,MAAM,gBAAgB,GAAG,wCAAwC,CAAC;AAElE,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,EAAE,GAAG,IAAI,yBAAyB,CAAC;IAC7D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;AAChH,CAAC;AAOD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAsB,EAAE,GAAY;IACrE,MAAM,UAAU,GAAI,GAAG,CAAC,eAAe,EAAwB,CAAC;IAChE,MAAM,CAAC,GAAG,qBAAqB,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;IAChG,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,SAAS,GAA2B;QACxC,MAAM,EAAE,gDAAgD,MAAM,CAAC,OAAO,GAAG;QACzE,SAAS,EAAE,iCAAiC,MAAM,CAAC,OAAO,EAAE;QAC5D,YAAY,EAAE,mCAAmC,MAAM,CAAC,OAAO,GAAG;QAClE,cAAc,EAAE,+EAA+E;KAChG,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAE7C,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9F,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACtI,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC7E,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,IAAI,KAAK,CAAC,IAAI,KAAK,2BAA2B,EAAE,CAAC;gBACtF,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,EAAE,GAAG,KAAK,CAAC,OAAO,yDAAyD,CAAC,CAAC;YACvI,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;IACjF,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1G,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,QAAQ,CAAC,MAAM,wCAAwC,SAAS,GAAG,CAAC,CAAC;YACpG,OAAO,CAAC,GAAG,CAAC,yCAAyC,gBAAgB,GAAG,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;IAED,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9K,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,OAAO,iBAAiB,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,oHAAoH;AACpH,KAAK,UAAU,YAAY,CAAC,IAAsB,EAAE,QAA2B,EAAE,gBAA+C;IAC9H,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IAC3D,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC1C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IACnE,MAAM,OAAO,GAAmB,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;IACrH,OAAO,WAAW,CAAC,iCAAiC,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC"}
|
package/dist/cli/main.js
CHANGED
|
@@ -3,11 +3,38 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { createRuntimeServices, projectRootFromImportMeta } from '../infra/runtime.js';
|
|
5
5
|
import { normalizeGitSourceUrl } from '../core/services/source-service.js';
|
|
6
|
+
import { SkillsManagerError } from '../shared/errors.js';
|
|
7
|
+
import { redactCheckout, redactDiscovered } from '../shared/redact.js';
|
|
8
|
+
import { NodeFileSystem } from '../infra/fs-skill-home.js';
|
|
9
|
+
import { GitHubApiClient } from '../infra/github-api-client.js';
|
|
10
|
+
import { DetectionService, detectionLogPath } from '../core/services/detection-service.js';
|
|
11
|
+
import { runBootstrap, managerSkillBundle } from './bootstrap.js';
|
|
6
12
|
const pkg = createRequire(import.meta.url)('../../package.json');
|
|
7
13
|
const projectRoot = projectRootFromImportMeta(import.meta.url);
|
|
8
|
-
function services(cmd) {
|
|
14
|
+
function services(cmd, options = {}) {
|
|
9
15
|
const opts = cmd.optsWithGlobals();
|
|
10
|
-
|
|
16
|
+
const runtime = createRuntimeServices({ home: opts.home, ensureDefaultHub: Boolean(options.bootstrap) }, projectRoot);
|
|
17
|
+
if (!options.bootstrap && !runtime.resolution.exists) {
|
|
18
|
+
throw new SkillsManagerError('no_skill_home', `No skill home at ${runtime.resolution.root} yet. Run \`npx skills-manager-cli\` first — it creates the hub and installs the manager skill. (Or point --home at an existing hub.)`);
|
|
19
|
+
}
|
|
20
|
+
selfCheckManagerSkill(runtime);
|
|
21
|
+
return runtime;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every CLI run keeps the hub's manager skill in step with the bundled copy
|
|
25
|
+
* (ADR-0014) — refresh silently when we still own it, never touch a
|
|
26
|
+
* user-modified copy, and never let this break the actual command. Notices go
|
|
27
|
+
* to stderr so stdout stays parseable JSON.
|
|
28
|
+
*/
|
|
29
|
+
function selfCheckManagerSkill(s) {
|
|
30
|
+
try {
|
|
31
|
+
const result = s.managerSkill.selfCheck(managerSkillBundle());
|
|
32
|
+
if (result?.status === 'refreshed')
|
|
33
|
+
console.error(`Refreshed the manager skill to v${pkg.version} to match this CLI version.`);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* the manager skill must never break an unrelated command */
|
|
37
|
+
}
|
|
11
38
|
}
|
|
12
39
|
function print(value) {
|
|
13
40
|
if (typeof value === 'string')
|
|
@@ -22,6 +49,22 @@ program
|
|
|
22
49
|
.version(pkg.version)
|
|
23
50
|
.option('--home <path>', 'skill home path; overrides SKILL_HOME and cwd detection')
|
|
24
51
|
.showHelpAfterError();
|
|
52
|
+
// Bootstrap is the product's front door (ADR-0014): the no-arg run and the
|
|
53
|
+
// named command share one handler. `--agent`/`--force` live on the named
|
|
54
|
+
// command only — the no-arg path is the interactive/default experience.
|
|
55
|
+
program.command('bootstrap')
|
|
56
|
+
.description('One-time setup: create the hub, install the manager skill, mount it to your agents (the default when no subcommand is given)')
|
|
57
|
+
.option('-a, --agent <id...>', 'catalog agent ids to mount to; skips the interactive picker')
|
|
58
|
+
.option('--force', 'replace an unmanaged skills-manager directory at a runtime path')
|
|
59
|
+
.action(runBootstrap);
|
|
60
|
+
program.argument('[stray...]');
|
|
61
|
+
program.action((stray, opts, cmd) => {
|
|
62
|
+
// A typo'd subcommand must not silently run bootstrap: with a program-level
|
|
63
|
+
// action, commander hands it over as an operand instead of erroring.
|
|
64
|
+
if (stray && stray.length > 0)
|
|
65
|
+
throw new Error(`unknown command '${stray[0]}'. Run 'skills-manager --help' for the command list.`);
|
|
66
|
+
return runBootstrap(opts, cmd);
|
|
67
|
+
});
|
|
25
68
|
const webCommand = (cmd, description) => cmd
|
|
26
69
|
.description(description)
|
|
27
70
|
.option('-p, --port <port>', 'port', '4777')
|
|
@@ -29,6 +72,12 @@ const webCommand = (cmd, description) => cmd
|
|
|
29
72
|
.option('--no-open', 'do not open a browser')
|
|
30
73
|
.action(async (opts, self) => {
|
|
31
74
|
const globalOpts = self.optsWithGlobals();
|
|
75
|
+
// Single entry point (ADR-0014): the dashboard never creates the hub — bootstrap does.
|
|
76
|
+
const probe = createRuntimeServices({ home: globalOpts.home }, projectRoot);
|
|
77
|
+
if (!probe.resolution.exists) {
|
|
78
|
+
throw new SkillsManagerError('no_skill_home', `No skill home at ${probe.resolution.root} yet. Run \`npx skills-manager-cli\` first — it creates the hub and installs the manager skill.`);
|
|
79
|
+
}
|
|
80
|
+
selfCheckManagerSkill(probe);
|
|
32
81
|
// Lazy: fastify/shiki/markdown load only for the dashboard command, not on every CLI invocation.
|
|
33
82
|
const { startDashboardServer } = await import('../dashboard/server/main.js');
|
|
34
83
|
return startDashboardServer({ home: globalOpts.home, port: Number(opts.port), host: opts.host, open: opts.open, projectRoot });
|
|
@@ -198,13 +247,30 @@ catalog.command('info')
|
|
|
198
247
|
.description('Show the current catalog snapshot stamp and detected agents')
|
|
199
248
|
.action((_opts, cmd) => {
|
|
200
249
|
const s = services(cmd);
|
|
201
|
-
|
|
250
|
+
// Detected agents carry their runtime dir: a bare id list gives a
|
|
251
|
+
// conversation nothing to choose by (ticket manager-skill-first/04).
|
|
252
|
+
const detected = s.catalog.detected().map((id) => ({ id, runtimeDir: s.catalog.resolveGlobalDir(id) }));
|
|
253
|
+
print({ snapshot: s.catalog.snapshotInfo(), detected });
|
|
202
254
|
});
|
|
203
255
|
program.command('list')
|
|
204
256
|
.description('List installed skills')
|
|
205
257
|
.option('--category <category>', 'filter category')
|
|
206
258
|
.option('--include-archived', 'include archived entries')
|
|
207
|
-
.
|
|
259
|
+
.option('--brief', 'compact rows (name, title, category, updatable) — for conversation-sized output')
|
|
260
|
+
.action((opts, cmd) => {
|
|
261
|
+
const rows = services(cmd).registry.listSkills({ category: opts.category, includeArchived: opts.includeArchived });
|
|
262
|
+
if (!opts.brief)
|
|
263
|
+
return print(rows);
|
|
264
|
+
// Full rows carry complete descriptions and consumer lists — tens of KB on
|
|
265
|
+
// a real hub. The brief form answers "what do I have" without them.
|
|
266
|
+
return print(rows.map((row) => ({
|
|
267
|
+
name: row.name,
|
|
268
|
+
title: row.title,
|
|
269
|
+
category: row.category,
|
|
270
|
+
updatable: Boolean(row.source?.url && row.source?.subpath),
|
|
271
|
+
archived: Boolean(row.archived),
|
|
272
|
+
})));
|
|
273
|
+
});
|
|
208
274
|
program.command('add')
|
|
209
275
|
.description('Discover from a source, then install selected skills')
|
|
210
276
|
.argument('<source>', 'Git URL, GitHub owner/repo, GitHub tree URL, or local path')
|
|
@@ -214,25 +280,60 @@ program.command('add')
|
|
|
214
280
|
.option('-y, --yes', 'overwrite existing skills without prompting')
|
|
215
281
|
.action((source, opts, cmd) => {
|
|
216
282
|
const s = services(cmd);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
283
|
+
if (opts.list) {
|
|
284
|
+
return s.source.withCheckout(source, undefined, (checkout) => {
|
|
285
|
+
const discovered = s.source.discover(checkout);
|
|
286
|
+
return print({ source: redactCheckout(checkout), discovered: discovered.map(redactDiscovered) });
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (!opts.all && (opts.skill || []).length === 0)
|
|
223
290
|
throw new Error('Use --all or --skill <name-or-subpath> to choose skills in this non-interactive CLI.');
|
|
291
|
+
// Empty selectors mean "everything discovered" — the --all semantics.
|
|
292
|
+
const selectors = opts.all ? [] : (opts.skill || []);
|
|
224
293
|
const result = s.install.installFromSourceSelection({ source, selectors, overwrite: Boolean(opts.yes) });
|
|
225
294
|
s.activity.record({ action: 'cli-add', summary: `Installed ${result.installed.join(', ')}`, details: { source, installed: result.installed } });
|
|
226
|
-
print(result);
|
|
295
|
+
print({ ...result, plan: { ...result.plan, source: redactCheckout(result.plan.source), selected: result.plan.selected.map(redactDiscovered) } });
|
|
227
296
|
remindStaleTargets(s);
|
|
228
297
|
});
|
|
229
298
|
program.command('update')
|
|
230
299
|
.description('Update skills from registry sources')
|
|
231
300
|
.option('-s, --skill <skill...>', 'skill(s) to update')
|
|
232
301
|
.option('--source <key>', 'source group key from updates plan')
|
|
233
|
-
.option('--plan', 'print update plan')
|
|
234
|
-
.
|
|
302
|
+
.option('--plan', 'print update plan (candidates only)')
|
|
303
|
+
.option('--check', 'run upstream freshness detection: stale / upToDate / failed / skipped per skill (uncalibrated entries adopt their observed anchor, ADR-0013)')
|
|
304
|
+
.action(async (opts, cmd) => {
|
|
235
305
|
const s = services(cmd);
|
|
306
|
+
if (opts.check) {
|
|
307
|
+
const detection = new DetectionService({ githubApi: new GitHubApiClient(), fs: new NodeFileSystem() });
|
|
308
|
+
const listed = s.registry.listSkills({ includeArchived: false });
|
|
309
|
+
const outcomes = await detection.detect(s, listed);
|
|
310
|
+
const detectionLog = detectionLogPath(s.resolution.root);
|
|
311
|
+
const stale = [];
|
|
312
|
+
const failed = [];
|
|
313
|
+
const upToDate = [];
|
|
314
|
+
const skipped = [];
|
|
315
|
+
for (const skill of listed) {
|
|
316
|
+
const outcome = outcomes.get(skill.name);
|
|
317
|
+
// No outcome = not part of this round (source-less rows and friends):
|
|
318
|
+
// counted as skipped, same wording the dashboard uses — never dropped
|
|
319
|
+
// silently (adversary M4).
|
|
320
|
+
if (!outcome || outcome.detection === 'skipped') {
|
|
321
|
+
skipped.push(skill.name);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (outcome.detection === 'failed')
|
|
325
|
+
failed.push({ skill: skill.name, log: detectionLog });
|
|
326
|
+
else if (outcome.hasUpdate)
|
|
327
|
+
stale.push({ skill: skill.name, url: skill.source.url ?? '' });
|
|
328
|
+
else
|
|
329
|
+
upToDate.push(skill.name);
|
|
330
|
+
}
|
|
331
|
+
print({ checked: stale.length + failed.length + upToDate.length + skipped.length, stale, upToDate, failed, skipped });
|
|
332
|
+
// A failed check is a failed check — scripts read the exit code (adversary M6).
|
|
333
|
+
if (failed.length > 0)
|
|
334
|
+
process.exitCode = 1;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
236
337
|
if (opts.plan || (!opts.skill && !opts.source))
|
|
237
338
|
return print(s.update.plan());
|
|
238
339
|
const result = opts.source ? s.update.updateSource(opts.source) : s.update.updateSkills(opts.skill);
|