skill-atlas-cli 0.1.25 → 0.1.26

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/bin/cli.js CHANGED
@@ -41,10 +41,12 @@ 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("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
45
  await install.run(name ? [name] : [], {
46
46
  yes: options.yes,
47
- global: options.global
47
+ global: options.global,
48
+ agent: options.agent,
49
+ path: options.path
48
50
  });
49
51
  });
50
52
  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>;
package/lib/index.js CHANGED
@@ -407,8 +407,8 @@ const agents = {
407
407
  cline: {
408
408
  name: "cline",
409
409
  displayName: "Cline",
410
- skillsDir: ".agents/skills",
411
- globalSkillsDir: join$1(home, ".agents", "skills"),
410
+ skillsDir: ".cline/skills",
411
+ globalSkillsDir: join$1(home, ".cline", "skills"),
412
412
  detectInstalled: async () => {
413
413
  return existsSync$1(join$1(home, ".cline"));
414
414
  }
@@ -452,7 +452,7 @@ const agents = {
452
452
  "github-copilot": {
453
453
  name: "github-copilot",
454
454
  displayName: "GitHub Copilot",
455
- skillsDir: ".agents/skills",
455
+ skillsDir: ".github/skills",
456
456
  globalSkillsDir: join$1(home, ".copilot/skills"),
457
457
  detectInstalled: async () => {
458
458
  return existsSync$1(join$1(home, ".copilot"));
@@ -503,6 +503,15 @@ const agents = {
503
503
  return existsSync$1(join$1(home, ".qoder"));
504
504
  }
505
505
  },
506
+ qoderwork: {
507
+ name: "qoderwork",
508
+ displayName: "QoderWork",
509
+ skillsDir: ".qoderwork/skills",
510
+ globalSkillsDir: join$1(home, ".qoderwork/skills"),
511
+ detectInstalled: async () => {
512
+ return existsSync$1(join$1(home, ".qoderwork"));
513
+ }
514
+ },
506
515
  "qwen-code": {
507
516
  name: "qwen-code",
508
517
  displayName: "Qwen Code",
@@ -530,6 +539,15 @@ const agents = {
530
539
  return existsSync$1(join$1(home, ".trae-cn"));
531
540
  }
532
541
  },
542
+ windsurf: {
543
+ name: "windsurf",
544
+ displayName: "Windsurf",
545
+ skillsDir: ".windsurf/skills",
546
+ globalSkillsDir: join$1(home, ".codeium/windsurf/skills"),
547
+ detectInstalled: async () => {
548
+ return existsSync$1(join$1(home, ".codeium/windsurf"));
549
+ }
550
+ },
533
551
  universal: {
534
552
  name: "universal",
535
553
  displayName: "Universal",
@@ -816,17 +834,45 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
816
834
  const isGlobal = options.global ?? false;
817
835
  const cwd = options.cwd || process.cwd();
818
836
  const installMode = options.mode ?? "symlink";
837
+ const customPath = options.path?.trim();
838
+ const skillName = sanitizeName(skill.slug);
839
+ let agentDir;
840
+ if (customPath) {
841
+ const resolvedPath = resolve(customPath);
842
+ agentDir = join$1(resolvedPath, skillName);
843
+ if (!isPathSafe(resolvedPath, agentDir)) return {
844
+ success: false,
845
+ path: agentDir,
846
+ mode: "copy",
847
+ error: "Invalid path: potential path traversal detected"
848
+ };
849
+ try {
850
+ await cleanAndCreateDirectory(agentDir);
851
+ await downloadAndExtractPackage(skill, agentDir);
852
+ return {
853
+ success: true,
854
+ path: agentDir,
855
+ mode: "copy"
856
+ };
857
+ } catch (error) {
858
+ return {
859
+ success: false,
860
+ path: agentDir,
861
+ mode: "copy",
862
+ error: error instanceof Error ? error.message : "Unknown error"
863
+ };
864
+ }
865
+ }
819
866
  if (isGlobal && agent.globalSkillsDir === void 0) return {
820
867
  success: false,
821
868
  path: "",
822
869
  mode: installMode,
823
870
  error: `${agent.displayName} does not support global skill installation`
824
871
  };
825
- const skillName = sanitizeName(skill.slug);
826
872
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
827
873
  const canonicalDir = join$1(canonicalBase, skillName);
828
874
  const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
829
- const agentDir = join$1(agentBase, skillName);
875
+ agentDir = join$1(agentBase, skillName);
830
876
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
831
877
  success: false,
832
878
  path: agentDir,
@@ -1000,11 +1046,43 @@ async function resolveSkillName(inputSkill, nonInteractive) {
1000
1046
  if (p.isCancel(input)) cancelAndExit("Cancelled");
1001
1047
  return input.trim();
1002
1048
  }
1049
+ /** 校验并解析传入的 agent 名称(纯函数,可单独测试) */
1050
+ function parseAgentNames(input) {
1051
+ const names = Array.isArray(input) ? input : input ? [input] : [];
1052
+ if (!names.length) return [];
1053
+ const validKeys = new Set(Object.keys(agents));
1054
+ const result = [];
1055
+ const invalid = [];
1056
+ for (const name of names) {
1057
+ const normalized = name.trim().toLowerCase();
1058
+ if (validKeys.has(normalized)) result.push(normalized);
1059
+ else invalid.push(name);
1060
+ }
1061
+ if (invalid.length > 0) return { invalid };
1062
+ return result;
1063
+ }
1064
+ function resolveAgentNamesFromOptions(input) {
1065
+ const parsed = parseAgentNames(input);
1066
+ if (Array.isArray(parsed)) return parsed;
1067
+ errorAndExit(`Unknown agent(s): ${parsed.invalid.join(", ")}`, {
1068
+ title: "Supported agents",
1069
+ body: `Examples: ${[
1070
+ "cursor",
1071
+ "openclaw",
1072
+ "claude-code",
1073
+ "cline",
1074
+ "codex"
1075
+ ].join(", ")}\nFull list: ${Object.keys(agents).join(", ")}`
1076
+ });
1077
+ }
1003
1078
  async function resolveTargetAgents(options, nonInteractive) {
1004
1079
  const allAgentChoices = buildAgentChoices(Boolean(options.global ?? options.yes ?? nonInteractive));
1005
1080
  if (options.yes || nonInteractive) {
1081
+ const specified = resolveAgentNamesFromOptions(options.agent);
1082
+ if (specified.length > 0) return specified;
1006
1083
  const defaults = getDefaultAgents(allAgentChoices);
1007
1084
  if (defaults.length === 0) cancelAndExit("No default agents available");
1085
+ p.log.message(chalk.dim("未指定 --agent,使用默认 agents。可用 --agent cursor 等指定安装目标"));
1008
1086
  return defaults;
1009
1087
  }
1010
1088
  const selected = await promptForAgents("Which agents do you want to install to?", allAgentChoices, getDefaultAgents(allAgentChoices));
@@ -1061,27 +1139,41 @@ const run = async (args, options = {}) => {
1061
1139
  const displayName = asset.displayName || asset.slug;
1062
1140
  const version = asset.currentVersion.version ?? "0.0.0";
1063
1141
  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...");
1142
+ const customPath = options.path?.trim();
1069
1143
  const installMode = options.copy ? "copy" : "symlink";
1070
1144
  const installResults = [];
1071
- for (const agent of targetAgents) {
1072
- const result = await installWellKnownSkillForAgent(asset, agent, {
1073
- global: installGlobally,
1145
+ if (customPath) {
1146
+ progress.start(`${chalk.dim("Installing to")} ${customPath}...`);
1147
+ const result = await installWellKnownSkillForAgent(asset, "openclaw", {
1148
+ path: customPath,
1074
1149
  mode: installMode
1075
1150
  });
1076
- if (!result.success) throw new Error(`Failed to install to ${agents[agent].displayName}: ${result.error || "Unknown error"}`);
1151
+ if (!result.success) throw new Error(`Failed to install to ${customPath}: ${result.error || "Unknown error"}`);
1077
1152
  installResults.push({
1078
- agent,
1153
+ agent: "openclaw",
1079
1154
  path: result.path
1080
1155
  });
1156
+ } else {
1157
+ const targetAgents = await resolveTargetAgents(options, nonInteractive);
1158
+ const installGlobally = await resolveInstallScope(options, targetAgents, nonInteractive);
1159
+ const selectedLabels = targetAgents.map((a) => agents[a].displayName);
1160
+ p.log.message(chalk.green("Selected:") + " " + selectedLabels.join(", "));
1161
+ progress.start("Installing skills...");
1162
+ for (const agent of targetAgents) {
1163
+ const result = await installWellKnownSkillForAgent(asset, agent, {
1164
+ global: installGlobally,
1165
+ mode: installMode
1166
+ });
1167
+ if (!result.success) throw new Error(`Failed to install to ${agents[agent].displayName}: ${result.error || "Unknown error"}`);
1168
+ installResults.push({
1169
+ agent,
1170
+ path: result.path
1171
+ });
1172
+ }
1081
1173
  }
1082
1174
  progress.stop("Skills installed successfully");
1083
1175
  const title = import_picocolors.default.green("Installed 1 skill");
1084
- const resultLines = installResults.map((r) => ` ${agents[r.agent].displayName}: ${r.path}`);
1176
+ const resultLines = customPath ? [` ${customPath}: ${installResults[0].path}`] : installResults.map((r) => ` ${agents[r.agent].displayName}: ${r.path}`);
1085
1177
  p.note(resultLines.join("\n"), title);
1086
1178
  p.outro(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Skill ready. Review before use."));
1087
1179
  } catch (err) {
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.26",
4
4
  "description": "skill-atlas CLI - 虾小宝 命令行工具",
5
5
  "homepage": "https://ai.skillatlas.cn/",
6
6
  "type": "module",