skill-atlas-cli 0.1.25 → 0.1.27

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 CHANGED
@@ -25,22 +25,31 @@ npm install -g skill-atlas
25
25
  skill-atlas search deploy
26
26
  ```
27
27
 
28
+ ### `update`
29
+
30
+ 快速升级 CLI 到最新版本。
31
+
32
+ ```bash
33
+ npx skill-atlas-cli update
34
+ npx skill-atlas-cli update -y # 非交互模式,跳过确认直接升级
35
+ ```
36
+
28
37
  ### `install [name]`
29
38
 
30
39
  从 SkillAtlas 安装 skill。支持指定 skill 名称(slug),或进入交互式输入。
31
40
 
32
41
  ```bash
33
42
  # 直接指定 skill 安装
34
- skill-atlas install [name]
43
+ npx skill-atlas-cli install [name]
35
44
 
36
45
  # 交互式输入(无参数时)
37
- skill-atlas install
46
+ npx skill-atlas-cli install
38
47
 
39
48
  # 非交互模式,默认安装到全局(跳过 Agent 与范围选择)
40
- skill-atlas install [name] -y
49
+ npx skill-atlas-cli install [name] -y
41
50
 
42
51
  # 显式安装到全局目录
43
- skill-atlas install [name] --global
52
+ npx skill-atlas-cli install [name] --global
44
53
  ```
45
54
 
46
55
  **选项:**
package/SKILL.md ADDED
@@ -0,0 +1,205 @@
1
+ ---
2
+ name: SkillAtlas
3
+ displayName: SkillAtlas
4
+ description: 帮助用户从 SkillAtlas(ai.skillatlas.cn)技能市场中发现合适的 skills。当用户在寻找某种可能以 skill 形式存在的能力时,应使用此 skill。
5
+ version: 1.0.0
6
+ ---
7
+
8
+ # Find Skills
9
+
10
+ 这个 skill 用于帮助你从 SkillAtlas 技能市场(ai.skillatlas.cn)中发现合适的 skills。
11
+
12
+ ## 何时使用这个 Skill
13
+
14
+ 当用户出现以下情况时,使用这个 skill:
15
+
16
+ - 询问「我怎么做 X」,而 X 可能是已有 skill 支持的常见任务
17
+ - 说「帮我找个做 X 的 skill」或「有没有做 X 的 skill」
18
+ - 询问「你能做 X 吗」,其中 X 是某种专门能力
19
+ - 表达想扩展 agent 能力的意图
20
+ - 想搜索工具、模板或工作流
21
+ - 提到他们需要某个特定场景下的帮助
22
+ - 提到某个平台生态,例如飞书、小红书、抖音、微信等
23
+
24
+ ## 什么是 SkillAtlas CLI?
25
+
26
+ SkillAtlas CLI(`skill-atlas` 或 `npx skill-atlas-cli`)是虾小宝命令行工具,用于从 [ai.skillatlas.cn](https://ai.skillatlas.cn) 搜索并安装 skill 到各类 AI 编程助手(Cursor、OpenClaw、Claude Code、Cline 等)。
27
+
28
+ ### 核心命令
29
+
30
+ - `npx skill-atlas-cli search <关键词>`:按关键词搜索 skills
31
+ - `npx skill-atlas-cli install [name]`:安装 skill(交互式选择目标 agent 和安装范围)
32
+ - `npx skill-atlas-cli install [name] -y`:非交互安装,按默认配置(全局、默认 agents)
33
+ - `npx skill-atlas-cli install [name] -y --agent <agent>`:非交互安装到指定 agent(如 Cursor、OpenClaw)
34
+
35
+ **常用选项:**
36
+
37
+ | 选项 | 说明 |
38
+ | ------------------------ | -------------------------------------------------------- |
39
+ | `-y, --yes` | 非交互模式,跳过所有选择 |
40
+ | `-g, --global` | 安装到全局目录 |
41
+ | `-a, --agent <agent...>` | 指定目标 agent(非 TTY 时推荐传入,如 `--agent cursor`) |
42
+
43
+ **非 TTY 场景(Agent 调用、CI、管道):** 建议传入 `--agent <当前 agent 名>` 以安装到正确目录,例如 Cursor 调用时使用 `--agent cursor`。
44
+
45
+ 浏览 skills:<https://skillatlas.cn>
46
+
47
+ ### 支持的 Agent
48
+
49
+ | Agent | 项目目录 | 全局目录 |
50
+ | ----------- | ---------------- | -------------------------- |
51
+ | Cursor | `.agents/skills` | `~/.cursor/skills` |
52
+ | OpenClaw | `skills` | `~/.openclaw/skills` |
53
+ | Claude Code | `.claude/skills` | `~/.claude/skills` |
54
+ | Cline | `.cline/skills` | `~/.cline/skills` |
55
+ | Qoder | `.qoder/skills` | `~/.qoder/skills` |
56
+ | 更多... | 见 README | skill-atlas 支持多种 Agent |
57
+
58
+ ## 如何帮助用户查找 Skills
59
+
60
+ ### 第 1 步:理解用户需求
61
+
62
+ 当用户请求帮助时,识别以下内容:
63
+
64
+ 1. 具体任务是什么,例如会议纪要、内容选题、财报分析、店铺运营、代码辅助
65
+ 2. 用户是否提到了明确的使用场景
66
+ 3. 用户是否提到了特定平台生态,例如飞书、小红书、抖音、微信
67
+ 4. 是否适合将这些信息组合起来搜索相关 skill
68
+
69
+ ### 第 2 步:搜索 Skills
70
+
71
+ 使用相关关键词进行搜索:
72
+
73
+ ```bash
74
+ skill-atlas search <关键词>
75
+ ```
76
+
77
+ 或使用 npx:
78
+
79
+ ```bash
80
+ npx skill-atlas-cli search <关键词>
81
+ ```
82
+
83
+ 示例:
84
+
85
+ - 用户问「怎么在飞书里自动整理会议纪要?」
86
+ - 搜索词:`会议纪要 飞书`
87
+
88
+ - 用户问「我想做小红书选题和内容规划」
89
+ - 搜索词:`内容规划 小红书`
90
+
91
+ - 用户问「我需要一个财报分析助手」
92
+ - 搜索词:`财报分析`
93
+
94
+ - 用户问「怎么提升抖音短视频运营效率?」
95
+ - 搜索词:`短视频运营 抖音`
96
+
97
+ - 用户问「能帮我做微信私域运营吗?」
98
+ - 搜索词:`私域运营 微信`
99
+
100
+ - 用户问「我想找一个代码开发辅助 skill」
101
+ - 搜索词:`代码助手`
102
+
103
+ - 用户问「我想让 agent 自己优化工作流」
104
+ - 搜索词:`工作流优化`
105
+
106
+ 如果组合关键词没有结果,则按以下顺序逐步简化查询:
107
+
108
+ 1. 只保留任务关键词
109
+ 2. 使用更短、更常见的任务表述
110
+ 3. 任务关键词加上平台名或场景词再次尝试
111
+
112
+ 搜索结果可能返回类似信息:
113
+
114
+ ```
115
+ Install: npx skill-atlas-cli install <skillName>
116
+
117
+ feishu-office-assistant 飞书办公助手 v1.0.0
118
+ ```
119
+
120
+ ### 第 3 步:向用户呈现结果
121
+
122
+ 当找到相关 skill 时,应向用户提供:
123
+
124
+ 1. skill 名称及其作用
125
+ 2. 用户可直接执行的安装命令
126
+ 3. 在 ai.skillatlas.cn 上查看更多信息的链接
127
+ 4. 一句简短说明,说明它为什么匹配当前任务
128
+ 5. 该 skill 的标签值信息(如有)
129
+ 6. 该 skill 的安全等级(如有)
130
+
131
+ 安全等级可使用以下表述:
132
+
133
+ - 已通过完整认证(非常推荐)
134
+ - 已通过重点审查(推荐使用)
135
+ - 已通过基础审核(推荐使用)
136
+
137
+ 示例回复:
138
+
139
+ ```
140
+ 我找到了一个可能适合的 skill。`feishu-office-assistant` 适用于飞书办公场景下的常见任务,例如会议纪要整理、任务跟进和文档协作。
141
+
142
+ 标签值:企业办公、飞书
143
+ 安全等级:已通过完整认证(非常推荐)
144
+
145
+ 安装命令:`skill-atlas install feishu-office-assistant`
146
+
147
+ 了解更多:https://ai.skillatlas.cn
148
+ ```
149
+
150
+ ### 第 4 步:安装(可选)
151
+
152
+ 如果用户希望安装,可提示用户执行:
153
+
154
+ ```bash
155
+ npx skill-atlas-cli install <skillName>
156
+ ```
157
+
158
+ 非交互模式(默认安装到全局、默认 agents):
159
+
160
+ ```bash
161
+ npx skill-atlas-cli install <skillName> -y
162
+ ```
163
+
164
+ **Agent 调用时**(如 Cursor、OpenClaw 内执行):传入 `--agent` 以安装到当前 agent 目录:
165
+
166
+ ```bash
167
+ npx skill-atlas-cli install <skillName> -y --agent cursor
168
+ ```
169
+
170
+ ## 高效搜索建议
171
+
172
+ 1. 优先使用更具体的任务关键词:`会议纪要` 比 `办公` 更有效
173
+ 2. 优先保留用户原话中的关键名词:例如平台名、工作对象、产出物
174
+ 3. 尝试不同任务表述:例如 `内容规划`、`选题策划`、`发布流程` 可能返回不同结果
175
+ 4. 没有结果时逐步回退:先搜较长组合,再退回到更短、更通用的关键词
176
+
177
+ ## 常见 Skill 类别
178
+
179
+ | 类别 | 示例搜索词 |
180
+ | -------- | ---------------------------- |
181
+ | 企业办公 | 会议纪要、飞书、任务跟进 |
182
+ | 内容创作 | 小红书、抖音、选题、内容规划 |
183
+ | 数据分析 | 财报分析、数据透视 |
184
+ | 代码开发 | 代码助手、调试、测试 |
185
+ | 部署运维 | deploy、docker、ci-cd |
186
+ | 私域运营 | 微信、私域、社群 |
187
+
188
+ ## 当没有找到 Skill 时
189
+
190
+ 如果没有找到相关 skill:
191
+
192
+ 1. 明确说明当前没有发现现成的 skill
193
+ 2. 表示仍然可以直接帮助处理该任务,或提供通用建议
194
+ 3. 不要承诺创建、安装或代为配置 skill
195
+
196
+ 示例:
197
+
198
+ ```
199
+ 我搜索了与「微信私域运营」相关的 skills,但没有找到匹配项。
200
+ 不过我仍然可以直接帮你梳理这个任务的通用做法,或帮你缩小搜索范围。
201
+ ```
202
+
203
+ ## 能力边界
204
+
205
+ 这个 skill 只负责发现和推荐,不负责代安装、代创建、代配置。
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { cac } from "cac";
4
- import { checkForUpdate, install, logger, runSearch } from "../lib/index.js";
4
+ import { checkForUpdate, install, logger, runSearch, runUpdate } from "../lib/index.js";
5
5
  //#region src/bin/cli.ts
6
6
  function getPackageJson() {
7
7
  try {
@@ -41,10 +41,19 @@ async function main() {
41
41
  }
42
42
  await runSearch({ keyword: keyword.trim() });
43
43
  });
44
- cli.command("install [name]", "安装 skill(支持 skill 名称)").option("-y, --yes", "非交互模式,默认安装到全局").option("-g, --global", "安装到全局目录").action(async (name, options) => {
44
+ cli.command("update", "快速升级 CLI 到最新版本").option("-y, --yes", "非交互模式,跳过确认直接升级").action(async (options) => {
45
+ await runUpdate({
46
+ pkgName: PKG.name,
47
+ currentVersion: PKG.version,
48
+ yes: options.yes
49
+ });
50
+ });
51
+ cli.command("install [name]", "安装 skill(支持 skill 名称)").option("-y, --yes", "非交互模式,默认安装到全局").option("-g, --global", "安装到全局目录").option("-p, --path <dir>", "安装到自定义路径(目录),如 -p /path/to/skills 或 -p .qoder/skills").option("-a, --agent <agent...>", "非 TTY 模式下指定目标 agent(如 cursor、openclaw),可传多个").action(async (name, options) => {
45
52
  await install.run(name ? [name] : [], {
46
53
  yes: options.yes,
47
- global: options.global
54
+ global: options.global,
55
+ agent: options.agent,
56
+ path: options.path
48
57
  });
49
58
  });
50
59
  cli.help();
package/lib/index.d.ts CHANGED
@@ -17,6 +17,8 @@ interface AddOptions {
17
17
  all?: boolean;
18
18
  fullDepth?: boolean;
19
19
  copy?: boolean;
20
+ /** 安装到自定义路径(目录) */
21
+ path?: string;
20
22
  }
21
23
  declare const _default: {
22
24
  run: (args: string[], options?: AddOptions) => Promise<void>;
@@ -33,6 +35,17 @@ interface SearchOptions {
33
35
  }
34
36
  declare function runSearch(options: SearchOptions): Promise<void>;
35
37
  //#endregion
38
+ //#region src/commands/update.d.ts
39
+ interface UpdateOptions {
40
+ /** 包名 */
41
+ pkgName: string;
42
+ /** 当前版本 */
43
+ currentVersion: string;
44
+ /** 非交互模式,跳过确认 */
45
+ yes?: boolean;
46
+ }
47
+ declare function runUpdate(options: UpdateOptions): Promise<void>;
48
+ //#endregion
36
49
  //#region src/core/logger.d.ts
37
50
  /**
38
51
  * 设置 verbose 模式(启用 debug 输出)
@@ -60,4 +73,4 @@ declare const _default$1: {
60
73
  log: LogFn;
61
74
  };
62
75
  //#endregion
63
- export { checkForUpdate, _default as install, _default$1 as logger, runSearch };
76
+ export { checkForUpdate, _default as install, _default$1 as logger, runSearch, runUpdate };
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ import { Writable } from "stream";
13
13
  import { cp, lstat, mkdir, readdir, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "fs/promises";
14
14
  import { execSync } from "child_process";
15
15
  import { info } from "console";
16
+ import { spawn } from "node:child_process";
16
17
  import { createConsola } from "consola";
17
18
  //#region \0rolldown/runtime.js
18
19
  var __create = Object.create;
@@ -199,7 +200,7 @@ var config_default = {
199
200
  const CACHE_DIR = config_default.CONFIG_DIR;
200
201
  const CACHE_FILE = join(CACHE_DIR, "update-check.json");
201
202
  const CHECK_INTERVAL = 1e3 * 60 * 60;
202
- async function fetchLatestVersion(pkgName) {
203
+ async function fetchLatestVersion$1(pkgName) {
203
204
  const baseUrl = (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/$/, "");
204
205
  const res = await fetch(`${baseUrl}/-/package/${pkgName}/dist-tags`, { signal: AbortSignal.timeout(3e3) });
205
206
  if (!res.ok) throw new Error("Fetch failed");
@@ -228,11 +229,11 @@ function saveCache(latest) {
228
229
  function checkForUpdate(pkg) {
229
230
  const cache = getCache();
230
231
  if (cache?.latest && semver.gt(cache.latest, pkg.version)) {
231
- if (process.stdout.isTTY) console.error(`\n 📦 Update available: \x1b[31m${pkg.version}\x1b[0m → \x1b[32m${cache.latest}\x1b[0m\n 👉 No need to reinstall. Run: \x1b[36mnpm update -g ${pkg.name} --force \x1b[0m\n`);
232
+ if (process.stdout.isTTY) console.error(`\n 📦 Update available: \x1b[31m${pkg.version}\x1b[0m → \x1b[32m${cache.latest}\x1b[0m\n 👉 Run: \x1b[36mskill-atlas update\x1b[0m\n`);
232
233
  }
233
234
  const lastCheck = cache?.lastCheck || 0;
234
235
  if (Date.now() - lastCheck < CHECK_INTERVAL) return;
235
- fetchLatestVersion(pkg.name).then((latest) => {
236
+ fetchLatestVersion$1(pkg.name).then((latest) => {
236
237
  saveCache(latest);
237
238
  }).catch(() => {});
238
239
  }
@@ -407,8 +408,8 @@ const agents = {
407
408
  cline: {
408
409
  name: "cline",
409
410
  displayName: "Cline",
410
- skillsDir: ".agents/skills",
411
- globalSkillsDir: join$1(home, ".agents", "skills"),
411
+ skillsDir: ".cline/skills",
412
+ globalSkillsDir: join$1(home, ".cline", "skills"),
412
413
  detectInstalled: async () => {
413
414
  return existsSync$1(join$1(home, ".cline"));
414
415
  }
@@ -452,7 +453,7 @@ const agents = {
452
453
  "github-copilot": {
453
454
  name: "github-copilot",
454
455
  displayName: "GitHub Copilot",
455
- skillsDir: ".agents/skills",
456
+ skillsDir: ".github/skills",
456
457
  globalSkillsDir: join$1(home, ".copilot/skills"),
457
458
  detectInstalled: async () => {
458
459
  return existsSync$1(join$1(home, ".copilot"));
@@ -503,6 +504,15 @@ const agents = {
503
504
  return existsSync$1(join$1(home, ".qoder"));
504
505
  }
505
506
  },
507
+ qoderwork: {
508
+ name: "qoderwork",
509
+ displayName: "QoderWork",
510
+ skillsDir: ".qoderwork/skills",
511
+ globalSkillsDir: join$1(home, ".qoderwork/skills"),
512
+ detectInstalled: async () => {
513
+ return existsSync$1(join$1(home, ".qoderwork"));
514
+ }
515
+ },
506
516
  "qwen-code": {
507
517
  name: "qwen-code",
508
518
  displayName: "Qwen Code",
@@ -530,6 +540,15 @@ const agents = {
530
540
  return existsSync$1(join$1(home, ".trae-cn"));
531
541
  }
532
542
  },
543
+ windsurf: {
544
+ name: "windsurf",
545
+ displayName: "Windsurf",
546
+ skillsDir: ".windsurf/skills",
547
+ globalSkillsDir: join$1(home, ".codeium/windsurf/skills"),
548
+ detectInstalled: async () => {
549
+ return existsSync$1(join$1(home, ".codeium/windsurf"));
550
+ }
551
+ },
533
552
  universal: {
534
553
  name: "universal",
535
554
  displayName: "Universal",
@@ -816,17 +835,45 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
816
835
  const isGlobal = options.global ?? false;
817
836
  const cwd = options.cwd || process.cwd();
818
837
  const installMode = options.mode ?? "symlink";
838
+ const customPath = options.path?.trim();
839
+ const skillName = sanitizeName(skill.slug);
840
+ let agentDir;
841
+ if (customPath) {
842
+ const resolvedPath = resolve(customPath);
843
+ agentDir = join$1(resolvedPath, skillName);
844
+ if (!isPathSafe(resolvedPath, agentDir)) return {
845
+ success: false,
846
+ path: agentDir,
847
+ mode: "copy",
848
+ error: "Invalid path: potential path traversal detected"
849
+ };
850
+ try {
851
+ await cleanAndCreateDirectory(agentDir);
852
+ await downloadAndExtractPackage(skill, agentDir);
853
+ return {
854
+ success: true,
855
+ path: agentDir,
856
+ mode: "copy"
857
+ };
858
+ } catch (error) {
859
+ return {
860
+ success: false,
861
+ path: agentDir,
862
+ mode: "copy",
863
+ error: error instanceof Error ? error.message : "Unknown error"
864
+ };
865
+ }
866
+ }
819
867
  if (isGlobal && agent.globalSkillsDir === void 0) return {
820
868
  success: false,
821
869
  path: "",
822
870
  mode: installMode,
823
871
  error: `${agent.displayName} does not support global skill installation`
824
872
  };
825
- const skillName = sanitizeName(skill.slug);
826
873
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
827
874
  const canonicalDir = join$1(canonicalBase, skillName);
828
875
  const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
829
- const agentDir = join$1(agentBase, skillName);
876
+ agentDir = join$1(agentBase, skillName);
830
877
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
831
878
  success: false,
832
879
  path: agentDir,
@@ -1000,11 +1047,43 @@ async function resolveSkillName(inputSkill, nonInteractive) {
1000
1047
  if (p.isCancel(input)) cancelAndExit("Cancelled");
1001
1048
  return input.trim();
1002
1049
  }
1050
+ /** 校验并解析传入的 agent 名称(纯函数,可单独测试) */
1051
+ function parseAgentNames(input) {
1052
+ const names = Array.isArray(input) ? input : input ? [input] : [];
1053
+ if (!names.length) return [];
1054
+ const validKeys = new Set(Object.keys(agents));
1055
+ const result = [];
1056
+ const invalid = [];
1057
+ for (const name of names) {
1058
+ const normalized = name.trim().toLowerCase();
1059
+ if (validKeys.has(normalized)) result.push(normalized);
1060
+ else invalid.push(name);
1061
+ }
1062
+ if (invalid.length > 0) return { invalid };
1063
+ return result;
1064
+ }
1065
+ function resolveAgentNamesFromOptions(input) {
1066
+ const parsed = parseAgentNames(input);
1067
+ if (Array.isArray(parsed)) return parsed;
1068
+ errorAndExit(`Unknown agent(s): ${parsed.invalid.join(", ")}`, {
1069
+ title: "Supported agents",
1070
+ body: `Examples: ${[
1071
+ "cursor",
1072
+ "openclaw",
1073
+ "claude-code",
1074
+ "cline",
1075
+ "codex"
1076
+ ].join(", ")}\nFull list: ${Object.keys(agents).join(", ")}`
1077
+ });
1078
+ }
1003
1079
  async function resolveTargetAgents(options, nonInteractive) {
1004
1080
  const allAgentChoices = buildAgentChoices(Boolean(options.global ?? options.yes ?? nonInteractive));
1005
1081
  if (options.yes || nonInteractive) {
1082
+ const specified = resolveAgentNamesFromOptions(options.agent);
1083
+ if (specified.length > 0) return specified;
1006
1084
  const defaults = getDefaultAgents(allAgentChoices);
1007
1085
  if (defaults.length === 0) cancelAndExit("No default agents available");
1086
+ p.log.message(chalk.dim("未指定 --agent,使用默认 agents。可用 --agent cursor 等指定安装目标"));
1008
1087
  return defaults;
1009
1088
  }
1010
1089
  const selected = await promptForAgents("Which agents do you want to install to?", allAgentChoices, getDefaultAgents(allAgentChoices));
@@ -1061,27 +1140,41 @@ const run = async (args, options = {}) => {
1061
1140
  const displayName = asset.displayName || asset.slug;
1062
1141
  const version = asset.currentVersion.version ?? "0.0.0";
1063
1142
  progress.stop(`${chalk.bold(displayName)} ${chalk.dim(`v${version}`)}`);
1064
- const targetAgents = await resolveTargetAgents(options, nonInteractive);
1065
- const installGlobally = await resolveInstallScope(options, targetAgents, nonInteractive);
1066
- const selectedLabels = targetAgents.map((a) => agents[a].displayName);
1067
- p.log.message(chalk.green("Selected:") + " " + selectedLabels.join(", "));
1068
- progress.start("Installing skills...");
1143
+ const customPath = options.path?.trim();
1069
1144
  const installMode = options.copy ? "copy" : "symlink";
1070
1145
  const installResults = [];
1071
- for (const agent of targetAgents) {
1072
- const result = await installWellKnownSkillForAgent(asset, agent, {
1073
- global: installGlobally,
1146
+ if (customPath) {
1147
+ progress.start(`${chalk.dim("Installing to")} ${customPath}...`);
1148
+ const result = await installWellKnownSkillForAgent(asset, "openclaw", {
1149
+ path: customPath,
1074
1150
  mode: installMode
1075
1151
  });
1076
- if (!result.success) throw new Error(`Failed to install to ${agents[agent].displayName}: ${result.error || "Unknown error"}`);
1152
+ if (!result.success) throw new Error(`Failed to install to ${customPath}: ${result.error || "Unknown error"}`);
1077
1153
  installResults.push({
1078
- agent,
1154
+ agent: "openclaw",
1079
1155
  path: result.path
1080
1156
  });
1157
+ } else {
1158
+ const targetAgents = await resolveTargetAgents(options, nonInteractive);
1159
+ const installGlobally = await resolveInstallScope(options, targetAgents, nonInteractive);
1160
+ const selectedLabels = targetAgents.map((a) => agents[a].displayName);
1161
+ p.log.message(chalk.green("Selected:") + " " + selectedLabels.join(", "));
1162
+ progress.start("Installing skills...");
1163
+ for (const agent of targetAgents) {
1164
+ const result = await installWellKnownSkillForAgent(asset, agent, {
1165
+ global: installGlobally,
1166
+ mode: installMode
1167
+ });
1168
+ if (!result.success) throw new Error(`Failed to install to ${agents[agent].displayName}: ${result.error || "Unknown error"}`);
1169
+ installResults.push({
1170
+ agent,
1171
+ path: result.path
1172
+ });
1173
+ }
1081
1174
  }
1082
1175
  progress.stop("Skills installed successfully");
1083
1176
  const title = import_picocolors.default.green("Installed 1 skill");
1084
- const resultLines = installResults.map((r) => ` ${agents[r.agent].displayName}: ${r.path}`);
1177
+ const resultLines = customPath ? [` ${customPath}: ${installResults[0].path}`] : installResults.map((r) => ` ${agents[r.agent].displayName}: ${r.path}`);
1085
1178
  p.note(resultLines.join("\n"), title);
1086
1179
  p.outro(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Skill ready. Review before use."));
1087
1180
  } catch (err) {
@@ -1169,4 +1262,70 @@ var logger_default = {
1169
1262
  log: ((...a) => consola.log(...a))
1170
1263
  };
1171
1264
  //#endregion
1172
- export { checkForUpdate, install_default as install, logger_default as logger, runSearch };
1265
+ //#region src/commands/update.ts
1266
+ /**
1267
+ * update 命令:快速升级 CLI 到最新版本
1268
+ */
1269
+ /** 从 npm registry 获取最新版本 */
1270
+ async function fetchLatestVersion(pkgName) {
1271
+ const baseUrl = (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/$/, "");
1272
+ const res = await fetch(`${baseUrl}/-/package/${pkgName}/dist-tags`, { signal: AbortSignal.timeout(5e3) });
1273
+ if (!res.ok) throw new Error("获取最新版本失败");
1274
+ return (await res.json()).latest || "0.0.0";
1275
+ }
1276
+ /** 执行 npm install -g <pkg>@latest */
1277
+ function runNpmUpdate(pkgName) {
1278
+ return new Promise((resolve) => {
1279
+ const child = spawn("npm", [
1280
+ "install",
1281
+ "-g",
1282
+ `${pkgName}@latest --force`
1283
+ ], {
1284
+ stdio: "inherit",
1285
+ shell: true
1286
+ });
1287
+ child.on("close", (code) => resolve(code ?? 0));
1288
+ child.on("error", () => resolve(1));
1289
+ });
1290
+ }
1291
+ async function runUpdate(options) {
1292
+ const { pkgName, currentVersion, yes } = options;
1293
+ const spinner = p.spinner();
1294
+ spinner.start("检查最新版本...");
1295
+ try {
1296
+ const latest = await fetchLatestVersion(pkgName);
1297
+ spinner.stop("检查完成");
1298
+ if (!semver.valid(latest)) {
1299
+ p.log.error(`无法解析最新版本: ${latest}`);
1300
+ process.exit(1);
1301
+ }
1302
+ if (semver.lte(latest, currentVersion)) {
1303
+ p.log.success(`已是最新版本 ${chalk.cyan(currentVersion)}`);
1304
+ return;
1305
+ }
1306
+ p.log.message(`发现新版本: ${chalk.red(currentVersion)} → ${chalk.green(latest)}`);
1307
+ const proceed = yes || !process.stdin.isTTY ? true : await p.confirm({
1308
+ message: "是否立即升级?",
1309
+ initialValue: true
1310
+ });
1311
+ if (p.isCancel(proceed) || proceed === false) {
1312
+ p.cancel("已取消升级");
1313
+ return;
1314
+ }
1315
+ spinner.start("正在升级...");
1316
+ const code = await runNpmUpdate(pkgName);
1317
+ spinner.stop(code === 0 ? "升级完成" : "升级失败");
1318
+ if (code !== 0) {
1319
+ logger_default.error("升级失败,可手动执行: npm install -g " + pkgName + "@latest");
1320
+ process.exit(1);
1321
+ }
1322
+ p.log.success(`已升级到 ${chalk.green(latest)}`);
1323
+ } catch (err) {
1324
+ spinner.stop("检查失败");
1325
+ p.log.error(err.message);
1326
+ logger_default.info("可手动执行: npm install -g " + pkgName + "@latest");
1327
+ process.exit(1);
1328
+ }
1329
+ }
1330
+ //#endregion
1331
+ export { checkForUpdate, install_default as install, logger_default as logger, runSearch, runUpdate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-atlas-cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "skill-atlas CLI - 虾小宝 命令行工具",
5
5
  "homepage": "https://ai.skillatlas.cn/",
6
6
  "type": "module",
@@ -29,7 +29,8 @@
29
29
  "plugin",
30
30
  "package.json",
31
31
  "README.md",
32
- "install.sh"
32
+ "install.sh",
33
+ "SKILL.md"
33
34
  ],
34
35
  "keywords": [
35
36
  "skill-atlas",
@@ -48,15 +49,15 @@
48
49
  "cac": "^7.0.0",
49
50
  "chalk": "^5.6.2",
50
51
  "consola": "^3.4.2",
51
- "semver": "^7.6.0"
52
+ "semver": "^7.7.4"
52
53
  },
53
54
  "devDependencies": {
54
- "@types/node": "^22.0.0",
55
- "vitest": "^2.1.0",
55
+ "@types/node": "^22.19.15",
56
56
  "picocolors": "^1.1.1",
57
- "simple-git": "^3.32.3",
58
- "tsdown": "^0.21.0",
59
- "typescript": "^5.6.0",
57
+ "simple-git": "^3.33.0",
58
+ "tsdown": "^0.21.4",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^2.1.9",
60
61
  "xdg-basedir": "^5.1.0"
61
62
  },
62
63
  "engines": {