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.
Files changed (42) hide show
  1. package/README.md +45 -46
  2. package/README.zh-CN.md +44 -46
  3. package/dist/cli/bootstrap-prompt.js +80 -0
  4. package/dist/cli/bootstrap-prompt.js.map +1 -0
  5. package/dist/cli/bootstrap.js +82 -0
  6. package/dist/cli/bootstrap.js.map +1 -0
  7. package/dist/cli/main.js +114 -13
  8. package/dist/cli/main.js.map +1 -1
  9. package/dist/{dashboard/server → core/services}/detection-log.js +5 -1
  10. package/dist/core/services/detection-log.js.map +1 -0
  11. package/dist/core/services/detection-service.js +196 -0
  12. package/dist/core/services/detection-service.js.map +1 -0
  13. package/dist/core/services/index.js +4 -1
  14. package/dist/core/services/index.js.map +1 -1
  15. package/dist/core/services/init-service.js +5 -1
  16. package/dist/core/services/init-service.js.map +1 -1
  17. package/dist/core/services/install-service.js +6 -5
  18. package/dist/core/services/install-service.js.map +1 -1
  19. package/dist/core/services/manager-skill-service.js +102 -0
  20. package/dist/core/services/manager-skill-service.js.map +1 -0
  21. package/dist/core/services/skill-home-resolver.js +5 -2
  22. package/dist/core/services/skill-home-resolver.js.map +1 -1
  23. package/dist/core/services/source-service.js +95 -5
  24. package/dist/core/services/source-service.js.map +1 -1
  25. package/dist/core/services/update-service.js +14 -13
  26. package/dist/core/services/update-service.js.map +1 -1
  27. package/dist/dashboard/server/main.js +13 -178
  28. package/dist/dashboard/server/main.js.map +1 -1
  29. package/dist/infra/fs-skill-home.js +8 -0
  30. package/dist/infra/fs-skill-home.js.map +1 -1
  31. package/dist/infra/git-cli.js +57 -16
  32. package/dist/infra/git-cli.js.map +1 -1
  33. package/dist/infra/runtime.js +3 -2
  34. package/dist/infra/runtime.js.map +1 -1
  35. package/dist/infra/shell-runner.js +30 -2
  36. package/dist/infra/shell-runner.js.map +1 -1
  37. package/dist/shared/redact.js +15 -0
  38. package/dist/shared/redact.js.map +1 -0
  39. package/docs/CLI.md +11 -1
  40. package/package.json +3 -1
  41. package/skills/skills-manager/SKILL.md +24 -5
  42. 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
- `skills-manager-cli` is a local-first CLI and dashboard for managing agent, Claude, and Codex skills from a registry-driven skill home.
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 web
10
+ npx skills-manager-cli
13
11
  ```
14
12
 
15
- This automatically creates and initializes `~/.skills-manager/` on first run.
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
- ```sh
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
- Or install globally:
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
- ```sh
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
- 👉 **[See Getting Started guide](docs/GETTING_STARTED.md)** for detailed instructions and common tasks.
22
+ > 帮我把现有的 skills 导入 skills-manager 并整理好来源
41
23
 
42
- ## Install and Run
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
- Use without installing globally:
26
+ ## How it works
45
27
 
46
- ```sh
47
- npx skills-manager-cli web
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
- Or install globally from a published package or packed tarball:
51
-
52
- ```sh
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`, initialized automatically
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 web --home ~/.skills-manager
83
- skills-manager doctor --home ~/.skills-manager
84
- skills-manager list --home ~/.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)** — Installation, first launch, common tasks
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
- `skills-manager-cli` 是一个本地优先的 CLI 与网页控制台,基于注册表驱动的技能库(skill home)管理 agent、Claude、Codex 的技能。
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 web
10
+ npx skills-manager-cli
13
11
  ```
14
12
 
15
- 首次运行时会自动创建并初始化 `~/.skills-manager/`。
16
-
17
- **已经在 `~/.claude/skills` 或其他 agent 运行时目录里有技能了?** 用 `skills-manager init` 一键收纳 —— 内容移入技能库,原位置变成软链接(会先备份):
13
+ 这就是全部的安装步骤。bootstrap 会创建技能库(`~/.skills-manager`),从包内副本把 **manager skill** 种进库,并(以软链接)挂载到它检测到的 agent——展示一次多选让你确认清单。
18
14
 
19
- ```sh
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
- ```sh
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
- 👉 **[查看入门指南](docs/GETTING_STARTED.zh-CN.md)** 了解详细用法与常见任务。
21
+ > 帮我把现有的 skills 导入 skills-manager 并整理好来源
41
22
 
42
- ## 安装与运行
23
+ 内容会移入库中,原位置变成软链接(先备份)。想先自己预览?`skills-manager init --dry-run`。
43
24
 
44
- 免安装直接使用:
25
+ ## 工作原理
45
26
 
46
- ```sh
47
- npx skills-manager-cli web
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
- 或从已发布的包/本地打包的 tarball 全局安装:
51
-
52
- ```sh
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 web --home ~/.skills-manager
83
- skills-manager doctor --home ~/.skills-manager
84
- skills-manager list --home ~/.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":"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"}
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
- return createRuntimeServices({ home: opts.home }, projectRoot);
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
- print({ snapshot: s.catalog.snapshotInfo(), detected: s.catalog.detected() });
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
- .action((opts, cmd) => print(services(cmd).registry.listSkills({ category: opts.category, includeArchived: opts.includeArchived })));
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
- const checkout = s.source.checkout(source);
218
- const discovered = s.source.discover(checkout);
219
- if (opts.list)
220
- return print({ source: checkout, discovered });
221
- const selectors = opts.all ? discovered.map((skill) => skill.subpath) : (opts.skill || []);
222
- if (!opts.all && selectors.length === 0)
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
- .action((opts, cmd) => {
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);