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 +4 -2
- package/lib/index.d.ts +2 -0
- package/lib/index.js +108 -16
- package/package.json +1 -1
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
package/lib/index.js
CHANGED
|
@@ -407,8 +407,8 @@ const agents = {
|
|
|
407
407
|
cline: {
|
|
408
408
|
name: "cline",
|
|
409
409
|
displayName: "Cline",
|
|
410
|
-
skillsDir: ".
|
|
411
|
-
globalSkillsDir: join$1(home, ".
|
|
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: ".
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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 ${
|
|
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) {
|