skillsmgr 0.5.0 → 0.5.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/dist/index.js +816 -183
- package/package.json +7 -8
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command as Command9 } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/setup.ts
|
|
@@ -53,6 +54,13 @@ function copyFile(src, dest) {
|
|
|
53
54
|
ensureDir(dirname(dest));
|
|
54
55
|
copyFileSync(src, dest);
|
|
55
56
|
}
|
|
57
|
+
function linkFile(src, dest) {
|
|
58
|
+
ensureDir(dirname(dest));
|
|
59
|
+
if (existsSync(dest)) {
|
|
60
|
+
unlinkSync(dest);
|
|
61
|
+
}
|
|
62
|
+
symlinkSync(src, dest);
|
|
63
|
+
}
|
|
56
64
|
function isSymlink(path) {
|
|
57
65
|
try {
|
|
58
66
|
return lstatSync(path).isSymbolicLink();
|
|
@@ -77,11 +85,26 @@ function writeFile(path, content) {
|
|
|
77
85
|
function fileExists(path) {
|
|
78
86
|
return existsSync(path);
|
|
79
87
|
}
|
|
88
|
+
function removeFile(path) {
|
|
89
|
+
if (existsSync(path)) {
|
|
90
|
+
unlinkSync(path);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
80
93
|
function removeDir(dir) {
|
|
81
94
|
if (existsSync(dir)) {
|
|
82
95
|
rmSync(dir, { recursive: true, force: true });
|
|
83
96
|
}
|
|
84
97
|
}
|
|
98
|
+
function getFilesInDir(dir, ext) {
|
|
99
|
+
if (!existsSync(dir)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
103
|
+
return entries.filter((e) => e.isFile() && (!ext || e.name.endsWith(ext))).map((e) => ({
|
|
104
|
+
name: e.name,
|
|
105
|
+
path: join2(dir, e.name)
|
|
106
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
107
|
+
}
|
|
85
108
|
function getDirectoriesInDir(dir) {
|
|
86
109
|
if (!existsSync(dir)) {
|
|
87
110
|
return [];
|
|
@@ -319,6 +342,52 @@ var GitHubService = class {
|
|
|
319
342
|
const content = await response.text();
|
|
320
343
|
writeFileSync2(localPath, content, "utf-8");
|
|
321
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* List commands (.md files) in a GitHub repository's commands directory
|
|
347
|
+
*/
|
|
348
|
+
async listCommands(owner, repo, commandsPath = "commands") {
|
|
349
|
+
const url = `${this.baseApiUrl}/repos/${owner}/${repo}/contents/${commandsPath}`;
|
|
350
|
+
const response = await fetch(url, {
|
|
351
|
+
headers: this.getHeaders()
|
|
352
|
+
});
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
const contents = await response.json();
|
|
357
|
+
return contents.filter((item) => item.type === "file" && item.name.endsWith(".md")).map((item) => ({ name: item.name, path: item.path }));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Download a single command file from GitHub
|
|
361
|
+
*/
|
|
362
|
+
async downloadCommandFile(owner, repo, filePath, targetPath) {
|
|
363
|
+
ensureDir(join5(targetPath, ".."));
|
|
364
|
+
const url = `${this.baseApiUrl}/repos/${owner}/${repo}/contents/${filePath}`;
|
|
365
|
+
const response = await fetch(url, {
|
|
366
|
+
headers: this.getHeaders()
|
|
367
|
+
});
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
throw new Error(`Failed to download command: ${response.statusText}`);
|
|
370
|
+
}
|
|
371
|
+
const content = await response.json();
|
|
372
|
+
if (content.download_url) {
|
|
373
|
+
await this.downloadFile(content.download_url, targetPath);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get the target directory for commands based on source
|
|
378
|
+
*/
|
|
379
|
+
getCommandsTargetDir(owner, repo, isCustom = false) {
|
|
380
|
+
const isAnthropic = owner === "anthropics" && repo === "skills";
|
|
381
|
+
let baseDir;
|
|
382
|
+
if (isAnthropic) {
|
|
383
|
+
baseDir = join5(SKILLS_MANAGER_DIR, "official", "anthropic");
|
|
384
|
+
} else if (isCustom) {
|
|
385
|
+
baseDir = join5(SKILLS_MANAGER_DIR, "custom", repo);
|
|
386
|
+
} else {
|
|
387
|
+
baseDir = join5(SKILLS_MANAGER_DIR, "community", repo);
|
|
388
|
+
}
|
|
389
|
+
return join5(baseDir, "commands");
|
|
390
|
+
}
|
|
322
391
|
/**
|
|
323
392
|
* Get the target directory for a skill based on source
|
|
324
393
|
*/
|
|
@@ -426,13 +495,14 @@ var TOOL_CONFIGS = {
|
|
|
426
495
|
name: "antigravity",
|
|
427
496
|
displayName: "Antigravity",
|
|
428
497
|
skillsDir: ".agent/skills",
|
|
498
|
+
commandsDir: ".agent/workflows",
|
|
429
499
|
supportsLink: true,
|
|
430
500
|
supportsModeSpecific: false
|
|
431
501
|
},
|
|
432
502
|
"codex-cli": {
|
|
433
503
|
name: "codex-cli",
|
|
434
504
|
displayName: "Codex CLI",
|
|
435
|
-
skillsDir: ".
|
|
505
|
+
skillsDir: ".codex/skills",
|
|
436
506
|
supportsLink: true,
|
|
437
507
|
supportsModeSpecific: false
|
|
438
508
|
},
|
|
@@ -440,6 +510,7 @@ var TOOL_CONFIGS = {
|
|
|
440
510
|
name: "roo-code",
|
|
441
511
|
displayName: "Roo Code",
|
|
442
512
|
skillsDir: ".roo/skills",
|
|
513
|
+
commandsDir: ".roo/commands",
|
|
443
514
|
supportsLink: true,
|
|
444
515
|
supportsModeSpecific: true,
|
|
445
516
|
modePattern: "skills-{mode}",
|
|
@@ -449,6 +520,7 @@ var TOOL_CONFIGS = {
|
|
|
449
520
|
name: "claude-code",
|
|
450
521
|
displayName: "Claude Code",
|
|
451
522
|
skillsDir: ".claude/skills",
|
|
523
|
+
commandsDir: ".claude/commands",
|
|
452
524
|
supportsLink: true,
|
|
453
525
|
supportsModeSpecific: false
|
|
454
526
|
},
|
|
@@ -456,6 +528,7 @@ var TOOL_CONFIGS = {
|
|
|
456
528
|
name: "gemini-cli",
|
|
457
529
|
displayName: "Gemini CLI",
|
|
458
530
|
skillsDir: ".gemini/skills",
|
|
531
|
+
commandsDir: ".gemini/commands",
|
|
459
532
|
supportsLink: true,
|
|
460
533
|
supportsModeSpecific: false
|
|
461
534
|
},
|
|
@@ -463,6 +536,7 @@ var TOOL_CONFIGS = {
|
|
|
463
536
|
name: "opencode",
|
|
464
537
|
displayName: "OpenCode",
|
|
465
538
|
skillsDir: ".opencode/skills",
|
|
539
|
+
commandsDir: ".opencode/commands",
|
|
466
540
|
supportsLink: true,
|
|
467
541
|
supportsModeSpecific: false
|
|
468
542
|
},
|
|
@@ -477,6 +551,7 @@ var TOOL_CONFIGS = {
|
|
|
477
551
|
name: "cursor",
|
|
478
552
|
displayName: "Cursor",
|
|
479
553
|
skillsDir: ".cursor/skills",
|
|
554
|
+
commandsDir: ".cursor/commands",
|
|
480
555
|
supportsLink: true,
|
|
481
556
|
supportsModeSpecific: false
|
|
482
557
|
},
|
|
@@ -484,6 +559,7 @@ var TOOL_CONFIGS = {
|
|
|
484
559
|
name: "kilo-code",
|
|
485
560
|
displayName: "Kilo Code",
|
|
486
561
|
skillsDir: ".kilocode/skills",
|
|
562
|
+
commandsDir: ".kilocode/commands",
|
|
487
563
|
supportsLink: true,
|
|
488
564
|
supportsModeSpecific: true,
|
|
489
565
|
modePattern: "skills-{mode}",
|
|
@@ -500,6 +576,7 @@ var TOOL_CONFIGS = {
|
|
|
500
576
|
name: "windsurf",
|
|
501
577
|
displayName: "Windsurf",
|
|
502
578
|
skillsDir: ".windsurf/skills",
|
|
579
|
+
commandsDir: ".windsurf/workflows",
|
|
503
580
|
supportsLink: true,
|
|
504
581
|
supportsModeSpecific: false
|
|
505
582
|
}
|
|
@@ -823,6 +900,34 @@ async function promptSkills(skills, deployedSkillNames = []) {
|
|
|
823
900
|
pageSize: 15
|
|
824
901
|
});
|
|
825
902
|
}
|
|
903
|
+
async function promptCommands(commands, deployedCommandNames = []) {
|
|
904
|
+
const grouped = {};
|
|
905
|
+
for (const command of commands) {
|
|
906
|
+
if (!grouped[command.source]) {
|
|
907
|
+
grouped[command.source] = [];
|
|
908
|
+
}
|
|
909
|
+
grouped[command.source].push(command);
|
|
910
|
+
}
|
|
911
|
+
const choices = [];
|
|
912
|
+
for (const [source, sourceCommands] of Object.entries(grouped)) {
|
|
913
|
+
for (const command of sourceCommands) {
|
|
914
|
+
const isDeployed = deployedCommandNames.includes(command.name);
|
|
915
|
+
choices.push({
|
|
916
|
+
name: `/${command.name}`,
|
|
917
|
+
description: command.description,
|
|
918
|
+
value: command.name,
|
|
919
|
+
checked: isDeployed,
|
|
920
|
+
group: source,
|
|
921
|
+
suffix: isDeployed ? "[deployed]" : void 0
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return interactiveCheckbox({
|
|
926
|
+
message: "Select commands to deploy:",
|
|
927
|
+
choices,
|
|
928
|
+
pageSize: 15
|
|
929
|
+
});
|
|
930
|
+
}
|
|
826
931
|
async function promptSkillsToInstall(skills) {
|
|
827
932
|
const choices = skills.map((skill) => ({
|
|
828
933
|
name: skill.name,
|
|
@@ -850,18 +955,19 @@ async function promptSelect(message, choices) {
|
|
|
850
955
|
handlePromptError(error);
|
|
851
956
|
}
|
|
852
957
|
}
|
|
853
|
-
async function promptSyncAction(filename) {
|
|
958
|
+
async function promptSyncAction(filename, showDiff = true) {
|
|
959
|
+
const choices = [
|
|
960
|
+
{ name: "Overwrite", value: "overwrite" },
|
|
961
|
+
{ name: "Skip", value: "skip" },
|
|
962
|
+
...showDiff ? [{ name: "Show diff", value: "diff" }] : []
|
|
963
|
+
];
|
|
854
964
|
try {
|
|
855
965
|
const { action } = await inquirer.prompt([
|
|
856
966
|
{
|
|
857
967
|
type: "list",
|
|
858
968
|
name: "action",
|
|
859
969
|
message: `${filename}: source changed`,
|
|
860
|
-
choices
|
|
861
|
-
{ name: "Overwrite", value: "overwrite" },
|
|
862
|
-
{ name: "Skip", value: "skip" },
|
|
863
|
-
{ name: "Show diff", value: "diff" }
|
|
864
|
-
]
|
|
970
|
+
choices
|
|
865
971
|
}
|
|
866
972
|
]);
|
|
867
973
|
return action;
|
|
@@ -942,7 +1048,7 @@ var ProgressBar = class {
|
|
|
942
1048
|
|
|
943
1049
|
// src/commands/install.ts
|
|
944
1050
|
var sourcesService = new SourcesService();
|
|
945
|
-
function
|
|
1051
|
+
function parseMdDescription(content) {
|
|
946
1052
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
947
1053
|
if (!frontmatterMatch) {
|
|
948
1054
|
return "";
|
|
@@ -950,17 +1056,58 @@ function parseSkillDescription(content) {
|
|
|
950
1056
|
const descMatch = frontmatterMatch[1].match(/^description:\s*(.+)$/m);
|
|
951
1057
|
return descMatch ? descMatch[1].trim() : "";
|
|
952
1058
|
}
|
|
1059
|
+
async function installCommandsFromGitHub(githubService2, owner, repo, targetBase, defaultBranch) {
|
|
1060
|
+
const commandsPaths = ["commands", "src/commands"];
|
|
1061
|
+
let commandsList = [];
|
|
1062
|
+
for (const commandsPath of commandsPaths) {
|
|
1063
|
+
commandsList = await githubService2.listCommands(owner, repo, commandsPath);
|
|
1064
|
+
if (commandsList.length > 0) break;
|
|
1065
|
+
}
|
|
1066
|
+
if (commandsList.length === 0) {
|
|
1067
|
+
return 0;
|
|
1068
|
+
}
|
|
1069
|
+
console.log(`
|
|
1070
|
+
Found ${commandsList.length} commands, installing...`);
|
|
1071
|
+
const commandsTargetDir = join7(targetBase, "commands");
|
|
1072
|
+
ensureDir(commandsTargetDir);
|
|
1073
|
+
for (const cmd of commandsList) {
|
|
1074
|
+
const targetPath = join7(commandsTargetDir, cmd.name);
|
|
1075
|
+
process.stdout.write(` ${cmd.name.replace(/\.md$/, "")}...`);
|
|
1076
|
+
await githubService2.downloadCommandFile(owner, repo, cmd.path, targetPath);
|
|
1077
|
+
console.log(" \u2713");
|
|
1078
|
+
}
|
|
1079
|
+
return commandsList.length;
|
|
1080
|
+
}
|
|
953
1081
|
async function installFromAnthropic(options) {
|
|
954
1082
|
const githubService2 = new GitHubService();
|
|
955
1083
|
const owner = "anthropics";
|
|
956
1084
|
const repo = "skills";
|
|
957
1085
|
console.log("Fetching available skills from anthropic/skills...");
|
|
958
1086
|
const skillsList = await githubService2.listSkills(owner, repo, "skills");
|
|
1087
|
+
const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
|
|
1088
|
+
const targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
|
|
1089
|
+
let commandsCount = 0;
|
|
959
1090
|
if (skillsList.length === 0) {
|
|
960
|
-
|
|
1091
|
+
commandsCount = await installCommandsFromGitHub(
|
|
1092
|
+
githubService2,
|
|
1093
|
+
owner,
|
|
1094
|
+
repo,
|
|
1095
|
+
targetBase,
|
|
1096
|
+
defaultBranch
|
|
1097
|
+
);
|
|
1098
|
+
if (commandsCount === 0) {
|
|
1099
|
+
console.error("Error: No skills or commands found in repository");
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
console.log(`
|
|
1103
|
+
\u2713 Installed ${commandsCount} commands to ${targetBase}`);
|
|
1104
|
+
sourcesService.addSource("official/anthropic", {
|
|
1105
|
+
url: "https://github.com/anthropics/skills",
|
|
1106
|
+
type: "official",
|
|
1107
|
+
repoName: "anthropic"
|
|
1108
|
+
});
|
|
961
1109
|
return;
|
|
962
1110
|
}
|
|
963
|
-
const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
|
|
964
1111
|
const skills = [];
|
|
965
1112
|
const progress = new ProgressBar(skillsList.length, "Fetching skill info");
|
|
966
1113
|
progress.start();
|
|
@@ -971,7 +1118,7 @@ async function installFromAnthropic(options) {
|
|
|
971
1118
|
);
|
|
972
1119
|
if (response.ok) {
|
|
973
1120
|
const content = await response.text();
|
|
974
|
-
const description =
|
|
1121
|
+
const description = parseMdDescription(content);
|
|
975
1122
|
skills.push({
|
|
976
1123
|
name: skill.name,
|
|
977
1124
|
description,
|
|
@@ -999,15 +1146,23 @@ async function installFromAnthropic(options) {
|
|
|
999
1146
|
}
|
|
1000
1147
|
console.log(`
|
|
1001
1148
|
Downloading ${selectedSkills.length} skills...`);
|
|
1002
|
-
const targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
|
|
1003
1149
|
for (const skill of selectedSkills) {
|
|
1004
1150
|
const targetDir = join7(targetBase, skill.name);
|
|
1005
1151
|
process.stdout.write(` ${skill.name}...`);
|
|
1006
1152
|
await githubService2.downloadSkill(owner, repo, skill.path, targetDir);
|
|
1007
1153
|
console.log(" \u2713");
|
|
1008
1154
|
}
|
|
1155
|
+
commandsCount = await installCommandsFromGitHub(
|
|
1156
|
+
githubService2,
|
|
1157
|
+
owner,
|
|
1158
|
+
repo,
|
|
1159
|
+
targetBase,
|
|
1160
|
+
defaultBranch
|
|
1161
|
+
);
|
|
1162
|
+
const parts = [`${selectedSkills.length} skills`];
|
|
1163
|
+
if (commandsCount > 0) parts.push(`${commandsCount} commands`);
|
|
1009
1164
|
console.log(`
|
|
1010
|
-
\u2713 Installed ${
|
|
1165
|
+
\u2713 Installed ${parts.join(" and ")} to ${targetBase}`);
|
|
1011
1166
|
sourcesService.addSource("official/anthropic", {
|
|
1012
1167
|
url: "https://github.com/anthropics/skills",
|
|
1013
1168
|
type: "official",
|
|
@@ -1030,7 +1185,7 @@ async function installFromGitHubUrl(url, options) {
|
|
|
1030
1185
|
console.log(`\u2713 Installed ${skillName} to ${targetDir}`);
|
|
1031
1186
|
return true;
|
|
1032
1187
|
}
|
|
1033
|
-
console.log(`Fetching available
|
|
1188
|
+
console.log(`Fetching available content from ${owner}/${repo}...`);
|
|
1034
1189
|
let skillsList = [];
|
|
1035
1190
|
const skillsPaths = ["skills", ".", "src/skills"];
|
|
1036
1191
|
for (const skillsPath of skillsPaths) {
|
|
@@ -1041,10 +1196,36 @@ async function installFromGitHubUrl(url, options) {
|
|
|
1041
1196
|
continue;
|
|
1042
1197
|
}
|
|
1043
1198
|
}
|
|
1199
|
+
const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
|
|
1200
|
+
let targetBase;
|
|
1201
|
+
if (isAnthropic) {
|
|
1202
|
+
targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
|
|
1203
|
+
} else if (options.custom) {
|
|
1204
|
+
targetBase = join7(SKILLS_MANAGER_DIR, "custom", repo);
|
|
1205
|
+
} else {
|
|
1206
|
+
targetBase = join7(SKILLS_MANAGER_DIR, "community", repo);
|
|
1207
|
+
}
|
|
1044
1208
|
if (skillsList.length === 0) {
|
|
1045
|
-
|
|
1209
|
+
const commandsCount2 = await installCommandsFromGitHub(
|
|
1210
|
+
githubService2,
|
|
1211
|
+
owner,
|
|
1212
|
+
repo,
|
|
1213
|
+
targetBase,
|
|
1214
|
+
defaultBranch
|
|
1215
|
+
);
|
|
1216
|
+
if (commandsCount2 === 0) {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
console.log(`
|
|
1220
|
+
\u2713 Installed ${commandsCount2} commands to ${targetBase}`);
|
|
1221
|
+
const sourceKey2 = isAnthropic ? "official/anthropic" : options.custom ? `custom/${repo}` : `community/${repo}`;
|
|
1222
|
+
sourcesService.addSource(sourceKey2, {
|
|
1223
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
1224
|
+
type: isAnthropic ? "official" : options.custom ? "custom" : "community",
|
|
1225
|
+
repoName: repo
|
|
1226
|
+
});
|
|
1227
|
+
return true;
|
|
1046
1228
|
}
|
|
1047
|
-
const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
|
|
1048
1229
|
const skills = [];
|
|
1049
1230
|
const progress = new ProgressBar(skillsList.length, "Fetching skill info");
|
|
1050
1231
|
progress.start();
|
|
@@ -1055,7 +1236,7 @@ async function installFromGitHubUrl(url, options) {
|
|
|
1055
1236
|
);
|
|
1056
1237
|
if (response.ok) {
|
|
1057
1238
|
const content = await response.text();
|
|
1058
|
-
const description =
|
|
1239
|
+
const description = parseMdDescription(content);
|
|
1059
1240
|
skills.push({ name: skill.name, description, path: skill.path });
|
|
1060
1241
|
}
|
|
1061
1242
|
} catch {
|
|
@@ -1064,7 +1245,25 @@ async function installFromGitHubUrl(url, options) {
|
|
|
1064
1245
|
}
|
|
1065
1246
|
progress.complete();
|
|
1066
1247
|
if (skills.length === 0) {
|
|
1067
|
-
|
|
1248
|
+
const commandsCount2 = await installCommandsFromGitHub(
|
|
1249
|
+
githubService2,
|
|
1250
|
+
owner,
|
|
1251
|
+
repo,
|
|
1252
|
+
targetBase,
|
|
1253
|
+
defaultBranch
|
|
1254
|
+
);
|
|
1255
|
+
if (commandsCount2 === 0) {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
console.log(`
|
|
1259
|
+
\u2713 Installed ${commandsCount2} commands to ${targetBase}`);
|
|
1260
|
+
const sourceKey2 = isAnthropic ? "official/anthropic" : options.custom ? `custom/${repo}` : `community/${repo}`;
|
|
1261
|
+
sourcesService.addSource(sourceKey2, {
|
|
1262
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
1263
|
+
type: isAnthropic ? "official" : options.custom ? "custom" : "community",
|
|
1264
|
+
repoName: repo
|
|
1265
|
+
});
|
|
1266
|
+
return true;
|
|
1068
1267
|
}
|
|
1069
1268
|
console.log(`Found ${skills.length} skills.
|
|
1070
1269
|
`);
|
|
@@ -1079,22 +1278,23 @@ async function installFromGitHubUrl(url, options) {
|
|
|
1079
1278
|
}
|
|
1080
1279
|
console.log(`
|
|
1081
1280
|
Downloading ${selectedSkills.length} skills...`);
|
|
1082
|
-
let targetBase;
|
|
1083
|
-
if (isAnthropic) {
|
|
1084
|
-
targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
|
|
1085
|
-
} else if (options.custom) {
|
|
1086
|
-
targetBase = join7(SKILLS_MANAGER_DIR, "custom", repo);
|
|
1087
|
-
} else {
|
|
1088
|
-
targetBase = join7(SKILLS_MANAGER_DIR, "community", repo);
|
|
1089
|
-
}
|
|
1090
1281
|
for (const skill of selectedSkills) {
|
|
1091
1282
|
const targetDir = join7(targetBase, skill.name);
|
|
1092
1283
|
process.stdout.write(` ${skill.name}...`);
|
|
1093
1284
|
await githubService2.downloadSkill(owner, repo, skill.path, targetDir);
|
|
1094
1285
|
console.log(" \u2713");
|
|
1095
1286
|
}
|
|
1287
|
+
const commandsCount = await installCommandsFromGitHub(
|
|
1288
|
+
githubService2,
|
|
1289
|
+
owner,
|
|
1290
|
+
repo,
|
|
1291
|
+
targetBase,
|
|
1292
|
+
defaultBranch
|
|
1293
|
+
);
|
|
1294
|
+
const parts = [`${selectedSkills.length} skills`];
|
|
1295
|
+
if (commandsCount > 0) parts.push(`${commandsCount} commands`);
|
|
1096
1296
|
console.log(`
|
|
1097
|
-
\u2713 Installed ${
|
|
1297
|
+
\u2713 Installed ${parts.join(" and ")} to ${targetBase}`);
|
|
1098
1298
|
const sourceKey = isAnthropic ? "official/anthropic" : options.custom ? `custom/${repo}` : `community/${repo}`;
|
|
1099
1299
|
sourcesService.addSource(sourceKey, {
|
|
1100
1300
|
url: `https://github.com/${owner}/${repo}`,
|
|
@@ -1103,6 +1303,17 @@ Downloading ${selectedSkills.length} skills...`);
|
|
|
1103
1303
|
});
|
|
1104
1304
|
return true;
|
|
1105
1305
|
}
|
|
1306
|
+
function countCommandsInRepo(repoPath) {
|
|
1307
|
+
const commandsDirs = ["commands", "src/commands"];
|
|
1308
|
+
for (const dir of commandsDirs) {
|
|
1309
|
+
const commandsDir = join7(repoPath, dir);
|
|
1310
|
+
if (fileExists(commandsDir)) {
|
|
1311
|
+
const mdFiles = getFilesInDir(commandsDir, ".md");
|
|
1312
|
+
return mdFiles.length;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return 0;
|
|
1316
|
+
}
|
|
1106
1317
|
async function installViaGitClone(source, options) {
|
|
1107
1318
|
const gitService = new GitService();
|
|
1108
1319
|
if (gitService.isSpecificSkillUrl(source)) {
|
|
@@ -1129,7 +1340,7 @@ async function installViaGitClone(source, options) {
|
|
|
1129
1340
|
const skillMdPath = join7(dir.path, "SKILL.md");
|
|
1130
1341
|
if (fileExists(skillMdPath)) {
|
|
1131
1342
|
const content = readFileContent(skillMdPath);
|
|
1132
|
-
const description =
|
|
1343
|
+
const description = parseMdDescription(content);
|
|
1133
1344
|
skills.push({
|
|
1134
1345
|
name: dir.name,
|
|
1135
1346
|
description,
|
|
@@ -1137,14 +1348,28 @@ async function installViaGitClone(source, options) {
|
|
|
1137
1348
|
});
|
|
1138
1349
|
}
|
|
1139
1350
|
}
|
|
1351
|
+
const commandsCount = countCommandsInRepo(repoPath);
|
|
1352
|
+
if (skills.length === 0 && commandsCount === 0) {
|
|
1353
|
+
console.error("Error: No skills or commands found in repository");
|
|
1354
|
+
process.exit(1);
|
|
1355
|
+
}
|
|
1356
|
+
if (skills.length > 0) {
|
|
1357
|
+
console.log(`Found ${skills.length} skills.
|
|
1358
|
+
`);
|
|
1359
|
+
}
|
|
1360
|
+
if (commandsCount > 0) {
|
|
1361
|
+
console.log(`Found ${commandsCount} commands (will be installed automatically).
|
|
1362
|
+
`);
|
|
1363
|
+
}
|
|
1140
1364
|
if (skills.length === 0) {
|
|
1141
|
-
console.log(
|
|
1365
|
+
console.log(`\u2713 Installed ${commandsCount} commands to ${repoPath}`);
|
|
1366
|
+
saveGitCloneSource(source, repoPath, options);
|
|
1142
1367
|
return;
|
|
1143
1368
|
}
|
|
1144
|
-
console.log(`Found ${skills.length} skills.
|
|
1145
|
-
`);
|
|
1146
1369
|
if (options.all) {
|
|
1147
|
-
|
|
1370
|
+
const parts2 = [`${skills.length} skills`];
|
|
1371
|
+
if (commandsCount > 0) parts2.push(`${commandsCount} commands`);
|
|
1372
|
+
console.log(`\u2713 Installed ${parts2.join(" and ")} to ${repoPath}`);
|
|
1148
1373
|
saveGitCloneSource(source, repoPath, options);
|
|
1149
1374
|
return;
|
|
1150
1375
|
}
|
|
@@ -1158,8 +1383,10 @@ async function installViaGitClone(source, options) {
|
|
|
1158
1383
|
for (const skill of unselectedSkills) {
|
|
1159
1384
|
removeDir(skill.path);
|
|
1160
1385
|
}
|
|
1386
|
+
const parts = [`${selectedNames.length} skills`];
|
|
1387
|
+
if (commandsCount > 0) parts.push(`${commandsCount} commands`);
|
|
1161
1388
|
console.log(`
|
|
1162
|
-
\u2713 Installed ${
|
|
1389
|
+
\u2713 Installed ${parts.join(" and ")} to ${repoPath}`);
|
|
1163
1390
|
saveGitCloneSource(source, repoPath, options);
|
|
1164
1391
|
}
|
|
1165
1392
|
function saveGitCloneSource(source, repoPath, options) {
|
|
@@ -1198,6 +1425,10 @@ async function executeInstall(source, options) {
|
|
|
1198
1425
|
await installFromAnthropic(options);
|
|
1199
1426
|
return;
|
|
1200
1427
|
}
|
|
1428
|
+
if (!source.includes("://") && /^[^/]+\/[^/]+\/?$/.test(source)) {
|
|
1429
|
+
source = `https://github.com/${source.replace(/\/$/, "")}`;
|
|
1430
|
+
console.log(`Resolved to ${source}`);
|
|
1431
|
+
}
|
|
1201
1432
|
if (source.includes("github.com")) {
|
|
1202
1433
|
const success = await installFromGitHubUrl(source, options);
|
|
1203
1434
|
if (success) return;
|
|
@@ -1211,7 +1442,7 @@ async function executeInstall(source, options) {
|
|
|
1211
1442
|
process.exit(1);
|
|
1212
1443
|
}
|
|
1213
1444
|
}
|
|
1214
|
-
var installCommand = new Command2("install").description("Download skills from a repository").argument("<source>", 'Repository URL or "anthropic" for official skills').option("--all", "Install all skills without prompting").option("--custom", "Install to custom/ instead of community/").action(async (source, options) => {
|
|
1445
|
+
var installCommand = new Command2("install").description("Download skills and commands from a repository").argument("<source>", 'Repository URL or "anthropic" for official skills').option("--all", "Install all skills without prompting").option("--custom", "Install to custom/ instead of community/").action(async (source, options) => {
|
|
1215
1446
|
await executeInstall(source, options);
|
|
1216
1447
|
});
|
|
1217
1448
|
|
|
@@ -1236,58 +1467,101 @@ async function updateSource(key, info) {
|
|
|
1236
1467
|
} else {
|
|
1237
1468
|
targetBase = join8(SKILLS_MANAGER_DIR, "community", info.repoName);
|
|
1238
1469
|
}
|
|
1239
|
-
const localSkills = getDirectoriesInDir(targetBase);
|
|
1240
|
-
if (localSkills.length === 0) {
|
|
1241
|
-
console.log(` No skills installed locally`);
|
|
1242
|
-
return result;
|
|
1243
|
-
}
|
|
1244
1470
|
const defaultBranch = await githubService.getDefaultBranch(owner, repo);
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1471
|
+
const localSkills = getDirectoriesInDir(targetBase);
|
|
1472
|
+
if (localSkills.length > 0) {
|
|
1473
|
+
let skillsBasePath = "skills";
|
|
1474
|
+
const skillsPaths = ["skills", ".", "src/skills"];
|
|
1475
|
+
for (const skillsPath of skillsPaths) {
|
|
1476
|
+
try {
|
|
1477
|
+
const testList = await githubService.listSkills(owner, repo, skillsPath);
|
|
1478
|
+
if (testList.length > 0) {
|
|
1479
|
+
skillsBasePath = skillsPath;
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
} catch {
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
for (const localSkill of localSkills) {
|
|
1487
|
+
const skillName = localSkill.name;
|
|
1488
|
+
if (skillName === "commands") continue;
|
|
1489
|
+
const targetDir = localSkill.path;
|
|
1490
|
+
const localSkillMd = join8(targetDir, "SKILL.md");
|
|
1491
|
+
if (!fileExists(localSkillMd)) {
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
const remotePath = skillsBasePath === "." ? skillName : `${skillsBasePath}/${skillName}`;
|
|
1495
|
+
try {
|
|
1496
|
+
const response = await fetch(
|
|
1497
|
+
`https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${remotePath}/SKILL.md`
|
|
1498
|
+
);
|
|
1499
|
+
if (!response.ok) {
|
|
1500
|
+
console.log(` \u26A0 ${skillName}: not found in remote`);
|
|
1501
|
+
result.failed++;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
const remoteContent = await response.text();
|
|
1505
|
+
const localContent = readFileContent(localSkillMd);
|
|
1506
|
+
if (remoteContent === localContent) {
|
|
1507
|
+
console.log(` \u2713 ${skillName}: up to date`);
|
|
1508
|
+
result.upToDate++;
|
|
1509
|
+
} else {
|
|
1510
|
+
removeDir(targetDir);
|
|
1511
|
+
await githubService.downloadSkill(owner, repo, remotePath, targetDir);
|
|
1512
|
+
console.log(` \u2191 ${skillName}: updated`);
|
|
1513
|
+
result.updated++;
|
|
1514
|
+
}
|
|
1515
|
+
} catch {
|
|
1516
|
+
console.log(` \u2717 ${skillName}: failed to update`);
|
|
1517
|
+
result.failed++;
|
|
1253
1518
|
}
|
|
1254
|
-
} catch {
|
|
1255
|
-
continue;
|
|
1256
1519
|
}
|
|
1257
1520
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1521
|
+
const localCommandsDir = join8(targetBase, "commands");
|
|
1522
|
+
const localCommands = getFilesInDir(localCommandsDir, ".md");
|
|
1523
|
+
if (localCommands.length > 0) {
|
|
1524
|
+
const commandsPaths = ["commands", "src/commands"];
|
|
1525
|
+
let commandsBasePath = "commands";
|
|
1526
|
+
for (const commandsPath of commandsPaths) {
|
|
1527
|
+
const remoteCommands = await githubService.listCommands(owner, repo, commandsPath);
|
|
1528
|
+
if (remoteCommands.length > 0) {
|
|
1529
|
+
commandsBasePath = commandsPath;
|
|
1530
|
+
break;
|
|
1531
|
+
}
|
|
1264
1532
|
}
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1533
|
+
for (const localCommand of localCommands) {
|
|
1534
|
+
const commandName = localCommand.name.replace(/\.md$/, "");
|
|
1535
|
+
const remotePath = `${commandsBasePath}/${localCommand.name}`;
|
|
1536
|
+
try {
|
|
1537
|
+
const response = await fetch(
|
|
1538
|
+
`https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${remotePath}`
|
|
1539
|
+
);
|
|
1540
|
+
if (!response.ok) {
|
|
1541
|
+
console.log(` \u26A0 /${commandName}: not found in remote`);
|
|
1542
|
+
result.failed++;
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
const remoteContent = await response.text();
|
|
1546
|
+
const localContent = readFileContent(localCommand.path);
|
|
1547
|
+
if (remoteContent === localContent) {
|
|
1548
|
+
console.log(` \u2713 /${commandName}: up to date`);
|
|
1549
|
+
result.upToDate++;
|
|
1550
|
+
} else {
|
|
1551
|
+
removeFile(localCommand.path);
|
|
1552
|
+
await githubService.downloadCommandFile(owner, repo, remotePath, localCommand.path);
|
|
1553
|
+
console.log(` \u2191 /${commandName}: updated`);
|
|
1554
|
+
result.updated++;
|
|
1555
|
+
}
|
|
1556
|
+
} catch {
|
|
1557
|
+
console.log(` \u2717 /${commandName}: failed to update`);
|
|
1272
1558
|
result.failed++;
|
|
1273
|
-
continue;
|
|
1274
|
-
}
|
|
1275
|
-
const remoteContent = await response.text();
|
|
1276
|
-
const localContent = readFileContent(localSkillMd);
|
|
1277
|
-
if (remoteContent === localContent) {
|
|
1278
|
-
console.log(` \u2713 ${skillName}: up to date`);
|
|
1279
|
-
result.upToDate++;
|
|
1280
|
-
} else {
|
|
1281
|
-
removeDir(targetDir);
|
|
1282
|
-
await githubService.downloadSkill(owner, repo, remotePath, targetDir);
|
|
1283
|
-
console.log(` \u2191 ${skillName}: updated`);
|
|
1284
|
-
result.updated++;
|
|
1285
1559
|
}
|
|
1286
|
-
} catch {
|
|
1287
|
-
console.log(` \u2717 ${skillName}: failed to update`);
|
|
1288
|
-
result.failed++;
|
|
1289
1560
|
}
|
|
1290
1561
|
}
|
|
1562
|
+
if (localSkills.length === 0 && localCommands.length === 0) {
|
|
1563
|
+
console.log(` No skills or commands installed locally`);
|
|
1564
|
+
}
|
|
1291
1565
|
sourcesService2.updateTimestamp(key);
|
|
1292
1566
|
return result;
|
|
1293
1567
|
}
|
|
@@ -1335,7 +1609,7 @@ Done! ${result.updated} updated, ${result.upToDate} up to date, ${result.failed}
|
|
|
1335
1609
|
}
|
|
1336
1610
|
console.log(`Done! ${totalUpdated} updated, ${totalUpToDate} up to date, ${totalFailed} failed`);
|
|
1337
1611
|
}
|
|
1338
|
-
var updateCommand = new Command3("update").description("Update installed skills to latest version").argument("[source]", 'Specific source to update (e.g., "anthropic")').action(async (source) => {
|
|
1612
|
+
var updateCommand = new Command3("update").description("Update installed skills and commands to latest version").argument("[source]", 'Specific source to update (e.g., "anthropic")').action(async (source) => {
|
|
1339
1613
|
await executeUpdate(source);
|
|
1340
1614
|
});
|
|
1341
1615
|
|
|
@@ -1432,16 +1706,109 @@ var SkillsService = class {
|
|
|
1432
1706
|
}
|
|
1433
1707
|
};
|
|
1434
1708
|
|
|
1435
|
-
// src/services/
|
|
1709
|
+
// src/services/commands.ts
|
|
1436
1710
|
import { join as join10 } from "path";
|
|
1711
|
+
var CommandsService = class {
|
|
1712
|
+
constructor(skillsDir) {
|
|
1713
|
+
this.skillsDir = skillsDir;
|
|
1714
|
+
}
|
|
1715
|
+
getAllCommands() {
|
|
1716
|
+
const commands = [];
|
|
1717
|
+
for (const source of SKILL_SOURCES) {
|
|
1718
|
+
const sourceDir = join10(this.skillsDir, source);
|
|
1719
|
+
const sourceCommands = this.getCommandsFromSource(sourceDir, source);
|
|
1720
|
+
commands.push(...sourceCommands);
|
|
1721
|
+
}
|
|
1722
|
+
return commands;
|
|
1723
|
+
}
|
|
1724
|
+
getCommandsBySource(source) {
|
|
1725
|
+
const sourceDir = join10(this.skillsDir, source);
|
|
1726
|
+
return this.getCommandsFromSource(sourceDir, source);
|
|
1727
|
+
}
|
|
1728
|
+
getCommandByName(name) {
|
|
1729
|
+
const allCommands = this.getAllCommands();
|
|
1730
|
+
return allCommands.find((c) => c.name === name);
|
|
1731
|
+
}
|
|
1732
|
+
getCommandsByNames(names) {
|
|
1733
|
+
const allCommands = this.getAllCommands();
|
|
1734
|
+
return names.map((name) => allCommands.find((c) => c.name === name)).filter((c) => c !== void 0);
|
|
1735
|
+
}
|
|
1736
|
+
findCommandsByName(name) {
|
|
1737
|
+
const allCommands = this.getAllCommands();
|
|
1738
|
+
return allCommands.filter((c) => c.name === name);
|
|
1739
|
+
}
|
|
1740
|
+
getCommandsFromSource(sourceDir, sourcePrefix) {
|
|
1741
|
+
const commands = [];
|
|
1742
|
+
if (!fileExists(sourceDir)) {
|
|
1743
|
+
return commands;
|
|
1744
|
+
}
|
|
1745
|
+
if (sourcePrefix === "custom") {
|
|
1746
|
+
const commandsDir = join10(sourceDir, "commands");
|
|
1747
|
+
const customCommands = this.loadCommandsFromDir(commandsDir, sourcePrefix);
|
|
1748
|
+
commands.push(...customCommands);
|
|
1749
|
+
} else {
|
|
1750
|
+
const repoDirs = getDirectoriesInDir(sourceDir);
|
|
1751
|
+
for (const repoDir of repoDirs) {
|
|
1752
|
+
const source = `${sourcePrefix}/${repoDir.name}`;
|
|
1753
|
+
const commandsDir = join10(repoDir.path, "commands");
|
|
1754
|
+
const repoCommands = this.loadCommandsFromDir(commandsDir, source);
|
|
1755
|
+
commands.push(...repoCommands);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return commands;
|
|
1759
|
+
}
|
|
1760
|
+
loadCommandsFromDir(commandsDir, source) {
|
|
1761
|
+
const commands = [];
|
|
1762
|
+
if (!fileExists(commandsDir)) {
|
|
1763
|
+
return commands;
|
|
1764
|
+
}
|
|
1765
|
+
const mdFiles = getFilesInDir(commandsDir, ".md");
|
|
1766
|
+
for (const file of mdFiles) {
|
|
1767
|
+
const command = this.loadCommand(file.path, file.name, source);
|
|
1768
|
+
if (command) {
|
|
1769
|
+
commands.push(command);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return commands;
|
|
1773
|
+
}
|
|
1774
|
+
loadCommand(filePath, fileName, source) {
|
|
1775
|
+
const content = readFileContent(filePath);
|
|
1776
|
+
const { name, description } = this.parseCommandMd(content);
|
|
1777
|
+
const commandName = name || fileName.replace(/\.md$/, "");
|
|
1778
|
+
return {
|
|
1779
|
+
name: commandName,
|
|
1780
|
+
description: description || "",
|
|
1781
|
+
path: filePath,
|
|
1782
|
+
source
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
parseCommandMd(content) {
|
|
1786
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1787
|
+
if (!frontmatterMatch) {
|
|
1788
|
+
return { name: "", description: "" };
|
|
1789
|
+
}
|
|
1790
|
+
const frontmatter = frontmatterMatch[1];
|
|
1791
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
1792
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
1793
|
+
return {
|
|
1794
|
+
name: nameMatch ? nameMatch[1].trim() : "",
|
|
1795
|
+
description: descMatch ? descMatch[1].trim() : ""
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
// src/services/scanner.ts
|
|
1801
|
+
import { join as join11 } from "path";
|
|
1437
1802
|
import { readdirSync as readdirSync2 } from "fs";
|
|
1438
1803
|
var DeploymentScanner = class {
|
|
1439
1804
|
constructor(projectDir, skillsManagerDir = SKILLS_MANAGER_DIR) {
|
|
1440
1805
|
this.projectDir = projectDir;
|
|
1441
1806
|
this.skillsManagerDir = skillsManagerDir;
|
|
1442
1807
|
this.skillsService = new SkillsService(this.skillsManagerDir);
|
|
1808
|
+
this.commandsService = new CommandsService(this.skillsManagerDir);
|
|
1443
1809
|
}
|
|
1444
1810
|
skillsService;
|
|
1811
|
+
commandsService;
|
|
1445
1812
|
scanAllTools() {
|
|
1446
1813
|
const deployments = [];
|
|
1447
1814
|
for (const toolName of SUPPORTED_TOOLS) {
|
|
@@ -1449,19 +1816,24 @@ var DeploymentScanner = class {
|
|
|
1449
1816
|
const toolDeployments = this.scanToolDeployment(toolName, config);
|
|
1450
1817
|
deployments.push(...toolDeployments);
|
|
1451
1818
|
}
|
|
1452
|
-
return deployments.filter((d) => d.skills.length > 0);
|
|
1819
|
+
return deployments.filter((d) => d.skills.length > 0 || d.commands.length > 0);
|
|
1453
1820
|
}
|
|
1454
1821
|
scanToolDeployment(toolName, config) {
|
|
1455
1822
|
const deployments = [];
|
|
1456
|
-
const baseDir =
|
|
1823
|
+
const baseDir = join11(this.projectDir, config.skillsDir);
|
|
1457
1824
|
const baseDeployment = this.scanDirectory(toolName, baseDir, config.skillsDir);
|
|
1458
|
-
if (
|
|
1825
|
+
if (config.commandsDir) {
|
|
1826
|
+
const commandsDir = join11(this.projectDir, config.commandsDir);
|
|
1827
|
+
const commands = this.scanCommandsDirectory(commandsDir);
|
|
1828
|
+
baseDeployment.commands = commands;
|
|
1829
|
+
}
|
|
1830
|
+
if (baseDeployment.skills.length > 0 || baseDeployment.commands.length > 0) {
|
|
1459
1831
|
deployments.push(baseDeployment);
|
|
1460
1832
|
}
|
|
1461
1833
|
if (config.supportsModeSpecific && config.availableModes) {
|
|
1462
1834
|
for (const mode of config.availableModes) {
|
|
1463
1835
|
const modeDir = getTargetDir(config, mode);
|
|
1464
|
-
const fullModeDir =
|
|
1836
|
+
const fullModeDir = join11(this.projectDir, modeDir);
|
|
1465
1837
|
const modeDeployment = this.scanDirectory(toolName, fullModeDir, modeDir, mode);
|
|
1466
1838
|
if (modeDeployment.skills.length > 0) {
|
|
1467
1839
|
deployments.push(modeDeployment);
|
|
@@ -1475,7 +1847,7 @@ var DeploymentScanner = class {
|
|
|
1475
1847
|
for (const toolName of SUPPORTED_TOOLS) {
|
|
1476
1848
|
const config = TOOL_CONFIGS[toolName];
|
|
1477
1849
|
const deployments = this.scanToolDeployment(toolName, config);
|
|
1478
|
-
if (deployments.some((d) => d.skills.length > 0)) {
|
|
1850
|
+
if (deployments.some((d) => d.skills.length > 0 || d.commands.length > 0)) {
|
|
1479
1851
|
tools.add(toolName);
|
|
1480
1852
|
}
|
|
1481
1853
|
}
|
|
@@ -1485,7 +1857,7 @@ var DeploymentScanner = class {
|
|
|
1485
1857
|
const config = TOOL_CONFIGS[toolName];
|
|
1486
1858
|
if (!config) return false;
|
|
1487
1859
|
const deployments = this.scanToolDeployment(toolName, config);
|
|
1488
|
-
return deployments.some((d) => d.skills.length > 0);
|
|
1860
|
+
return deployments.some((d) => d.skills.length > 0 || d.commands.length > 0);
|
|
1489
1861
|
}
|
|
1490
1862
|
getDeployedSkills(toolName) {
|
|
1491
1863
|
const config = TOOL_CONFIGS[toolName];
|
|
@@ -1493,12 +1865,19 @@ var DeploymentScanner = class {
|
|
|
1493
1865
|
const deployments = this.scanToolDeployment(toolName, config);
|
|
1494
1866
|
return deployments.flatMap((d) => d.skills);
|
|
1495
1867
|
}
|
|
1868
|
+
getDeployedCommands(toolName) {
|
|
1869
|
+
const config = TOOL_CONFIGS[toolName];
|
|
1870
|
+
if (!config || !config.commandsDir) return [];
|
|
1871
|
+
const commandsDir = join11(this.projectDir, config.commandsDir);
|
|
1872
|
+
return this.scanCommandsDirectory(commandsDir);
|
|
1873
|
+
}
|
|
1496
1874
|
scanDirectory(toolName, fullPath, relativePath, mode) {
|
|
1497
1875
|
const deployment = {
|
|
1498
1876
|
toolName,
|
|
1499
1877
|
targetDir: relativePath,
|
|
1500
1878
|
mode,
|
|
1501
|
-
skills: []
|
|
1879
|
+
skills: [],
|
|
1880
|
+
commands: []
|
|
1502
1881
|
};
|
|
1503
1882
|
if (!fileExists(fullPath)) {
|
|
1504
1883
|
return deployment;
|
|
@@ -1506,7 +1885,7 @@ var DeploymentScanner = class {
|
|
|
1506
1885
|
try {
|
|
1507
1886
|
const entries = readdirSync2(fullPath, { withFileTypes: true });
|
|
1508
1887
|
for (const entry of entries) {
|
|
1509
|
-
const skillPath =
|
|
1888
|
+
const skillPath = join11(fullPath, entry.name);
|
|
1510
1889
|
const scanned = this.scanSkill(skillPath, entry.name);
|
|
1511
1890
|
if (scanned) {
|
|
1512
1891
|
deployment.skills.push(scanned);
|
|
@@ -1516,8 +1895,42 @@ var DeploymentScanner = class {
|
|
|
1516
1895
|
}
|
|
1517
1896
|
return deployment;
|
|
1518
1897
|
}
|
|
1898
|
+
scanCommandsDirectory(fullPath) {
|
|
1899
|
+
const commands = [];
|
|
1900
|
+
if (!fileExists(fullPath)) {
|
|
1901
|
+
return commands;
|
|
1902
|
+
}
|
|
1903
|
+
try {
|
|
1904
|
+
const entries = readdirSync2(fullPath, { withFileTypes: true });
|
|
1905
|
+
for (const entry of entries) {
|
|
1906
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1907
|
+
const commandPath = join11(fullPath, entry.name);
|
|
1908
|
+
const commandName = entry.name.replace(/\.md$/, "");
|
|
1909
|
+
if (isSymlink(commandPath)) {
|
|
1910
|
+
const linkTarget = readSymlinkTarget(commandPath);
|
|
1911
|
+
const source = linkTarget ? this.extractSourceFromPath(linkTarget) : null;
|
|
1912
|
+
commands.push({
|
|
1913
|
+
name: commandName,
|
|
1914
|
+
source: source || "unknown",
|
|
1915
|
+
deployMode: "link",
|
|
1916
|
+
path: commandPath
|
|
1917
|
+
});
|
|
1918
|
+
} else {
|
|
1919
|
+
const cmdMatch = this.commandsService.findCommandsByName(commandName);
|
|
1920
|
+
commands.push({
|
|
1921
|
+
name: commandName,
|
|
1922
|
+
source: cmdMatch.length === 1 ? cmdMatch[0].source : "unknown",
|
|
1923
|
+
deployMode: "copy",
|
|
1924
|
+
path: commandPath
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
} catch {
|
|
1929
|
+
}
|
|
1930
|
+
return commands;
|
|
1931
|
+
}
|
|
1519
1932
|
scanSkill(skillPath, name) {
|
|
1520
|
-
const skillMdPath =
|
|
1933
|
+
const skillMdPath = join11(skillPath, "SKILL.md");
|
|
1521
1934
|
if (!fileExists(skillMdPath)) {
|
|
1522
1935
|
return null;
|
|
1523
1936
|
}
|
|
@@ -1590,55 +2003,86 @@ async function listAvailable() {
|
|
|
1590
2003
|
console.log("Skills manager not set up. Run: skillsmgr setup");
|
|
1591
2004
|
process.exit(1);
|
|
1592
2005
|
}
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
2006
|
+
const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
|
|
2007
|
+
const commandsService = new CommandsService(SKILLS_MANAGER_DIR);
|
|
2008
|
+
const skills = skillsService.getAllSkills();
|
|
2009
|
+
const commands = commandsService.getAllCommands();
|
|
2010
|
+
if (skills.length === 0 && commands.length === 0) {
|
|
2011
|
+
console.log("No skills or commands found in ~/.skills-manager/");
|
|
1597
2012
|
console.log("\nRun: skillsmgr install anthropic");
|
|
1598
2013
|
return;
|
|
1599
2014
|
}
|
|
1600
|
-
console.log("Available
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
grouped[skill.source]
|
|
2015
|
+
console.log("Available in ~/.skills-manager/:\n");
|
|
2016
|
+
if (skills.length > 0) {
|
|
2017
|
+
const grouped = {};
|
|
2018
|
+
for (const skill of skills) {
|
|
2019
|
+
if (!grouped[skill.source]) {
|
|
2020
|
+
grouped[skill.source] = [];
|
|
2021
|
+
}
|
|
2022
|
+
grouped[skill.source].push(skill);
|
|
2023
|
+
}
|
|
2024
|
+
for (const [source, sourceSkills] of Object.entries(grouped)) {
|
|
2025
|
+
console.log(`\u2500\u2500 ${source} (${sourceSkills.length} skill${sourceSkills.length > 1 ? "s" : ""}) \u2500\u2500`);
|
|
2026
|
+
for (const skill of sourceSkills) {
|
|
2027
|
+
console.log(` ${skill.name}`);
|
|
2028
|
+
}
|
|
2029
|
+
console.log();
|
|
1605
2030
|
}
|
|
1606
|
-
grouped[skill.source].push(skill);
|
|
1607
2031
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
for (const
|
|
1611
|
-
|
|
2032
|
+
if (commands.length > 0) {
|
|
2033
|
+
const grouped = {};
|
|
2034
|
+
for (const command of commands) {
|
|
2035
|
+
if (!grouped[command.source]) {
|
|
2036
|
+
grouped[command.source] = [];
|
|
2037
|
+
}
|
|
2038
|
+
grouped[command.source].push(command);
|
|
2039
|
+
}
|
|
2040
|
+
for (const [source, sourceCommands] of Object.entries(grouped)) {
|
|
2041
|
+
console.log(`\u2500\u2500 ${source} (${sourceCommands.length} command${sourceCommands.length > 1 ? "s" : ""}) \u2500\u2500`);
|
|
2042
|
+
for (const command of sourceCommands) {
|
|
2043
|
+
console.log(` /${command.name}`);
|
|
2044
|
+
}
|
|
2045
|
+
console.log();
|
|
1612
2046
|
}
|
|
1613
|
-
console.log();
|
|
1614
2047
|
}
|
|
1615
2048
|
}
|
|
1616
2049
|
async function listDeployed() {
|
|
1617
2050
|
const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
|
|
1618
2051
|
const deployments = scanner.scanAllTools();
|
|
1619
2052
|
if (deployments.length === 0) {
|
|
1620
|
-
console.log("No skills deployed in current project.");
|
|
2053
|
+
console.log("No skills or commands deployed in current project.");
|
|
1621
2054
|
console.log("\nRun: skillsmgr init");
|
|
1622
2055
|
return;
|
|
1623
2056
|
}
|
|
1624
|
-
console.log("Deployed
|
|
2057
|
+
console.log("Deployed in current project:\n");
|
|
1625
2058
|
for (const deployment of deployments) {
|
|
1626
2059
|
const config = TOOL_CONFIGS[deployment.toolName];
|
|
1627
2060
|
const displayName = config?.displayName || deployment.toolName;
|
|
1628
2061
|
const dirSuffix = deployment.mode && deployment.mode !== "all" ? ` [${deployment.mode}]` : "";
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2062
|
+
if (deployment.skills.length > 0) {
|
|
2063
|
+
console.log(`${displayName} skills (${deployment.targetDir}/)${dirSuffix}:`);
|
|
2064
|
+
for (const skill of deployment.skills) {
|
|
2065
|
+
const modeStr = skill.deployMode === "link" ? "link" : "copy";
|
|
2066
|
+
if (skill.conflict) {
|
|
2067
|
+
console.log(` \u26A0 ${skill.name.padEnd(16)} (${modeStr}) \u2190 conflict`);
|
|
2068
|
+
} else {
|
|
2069
|
+
console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
|
|
2070
|
+
}
|
|
1636
2071
|
}
|
|
2072
|
+
console.log();
|
|
2073
|
+
}
|
|
2074
|
+
if (deployment.commands.length > 0) {
|
|
2075
|
+
const commandsDirDisplay = config?.commandsDir || "commands";
|
|
2076
|
+
console.log(`${displayName} commands (${commandsDirDisplay}/):`);
|
|
2077
|
+
for (const command of deployment.commands) {
|
|
2078
|
+
const modeStr = command.deployMode === "link" ? "link" : "copy";
|
|
2079
|
+
console.log(` \u25C9 /${command.name.padEnd(15)} (${modeStr}) \u2190 ${command.source}`);
|
|
2080
|
+
}
|
|
2081
|
+
console.log();
|
|
1637
2082
|
}
|
|
1638
|
-
console.log();
|
|
1639
2083
|
}
|
|
1640
2084
|
}
|
|
1641
|
-
var listCommand = new Command4("list").description("List available or deployed skills").option("--deployed", "List deployed skills in current project").action(async (options) => {
|
|
2085
|
+
var listCommand = new Command4("list").description("List available or deployed skills and commands").option("--deployed", "List deployed skills and commands in current project").action(async (options) => {
|
|
1642
2086
|
await executeList(options);
|
|
1643
2087
|
});
|
|
1644
2088
|
|
|
@@ -1646,7 +2090,7 @@ var listCommand = new Command4("list").description("List available or deployed s
|
|
|
1646
2090
|
import { Command as Command5 } from "commander";
|
|
1647
2091
|
|
|
1648
2092
|
// src/services/deployer.ts
|
|
1649
|
-
import { join as
|
|
2093
|
+
import { join as join12 } from "path";
|
|
1650
2094
|
import { existsSync as existsSync3, rmSync as rmSync2 } from "fs";
|
|
1651
2095
|
var Deployer = class {
|
|
1652
2096
|
constructor(projectDir) {
|
|
@@ -1654,9 +2098,9 @@ var Deployer = class {
|
|
|
1654
2098
|
}
|
|
1655
2099
|
deploySkill(skill, toolConfig, mode, targetMode) {
|
|
1656
2100
|
const targetDir = getTargetDir(toolConfig, targetMode);
|
|
1657
|
-
const fullTargetDir =
|
|
2101
|
+
const fullTargetDir = join12(this.projectDir, targetDir);
|
|
1658
2102
|
ensureDir(fullTargetDir);
|
|
1659
|
-
const skillTargetPath =
|
|
2103
|
+
const skillTargetPath = join12(fullTargetDir, skill.name);
|
|
1660
2104
|
if (mode === "link") {
|
|
1661
2105
|
linkDir(skill.path, skillTargetPath);
|
|
1662
2106
|
} else {
|
|
@@ -1670,19 +2114,52 @@ var Deployer = class {
|
|
|
1670
2114
|
}
|
|
1671
2115
|
removeSkill(skillName, toolConfig, targetMode) {
|
|
1672
2116
|
const targetDir = getTargetDir(toolConfig, targetMode);
|
|
1673
|
-
const skillPath =
|
|
2117
|
+
const skillPath = join12(this.projectDir, targetDir, skillName);
|
|
1674
2118
|
if (existsSync3(skillPath)) {
|
|
1675
2119
|
rmSync2(skillPath, { recursive: true, force: true });
|
|
1676
2120
|
}
|
|
1677
2121
|
}
|
|
1678
2122
|
getDeployedSkillPath(skillName, toolConfig, targetMode) {
|
|
1679
2123
|
const targetDir = getTargetDir(toolConfig, targetMode);
|
|
1680
|
-
return
|
|
2124
|
+
return join12(this.projectDir, targetDir, skillName);
|
|
1681
2125
|
}
|
|
1682
2126
|
isSkillDeployed(skillName, toolConfig, targetMode) {
|
|
1683
2127
|
const skillPath = this.getDeployedSkillPath(skillName, toolConfig, targetMode);
|
|
1684
2128
|
return existsSync3(skillPath);
|
|
1685
2129
|
}
|
|
2130
|
+
deployCommand(command, toolConfig, mode) {
|
|
2131
|
+
const commandsDir = toolConfig.commandsDir;
|
|
2132
|
+
if (!commandsDir) return;
|
|
2133
|
+
const fullTargetDir = join12(this.projectDir, commandsDir);
|
|
2134
|
+
ensureDir(fullTargetDir);
|
|
2135
|
+
const commandTargetPath = join12(fullTargetDir, `${command.name}.md`);
|
|
2136
|
+
if (mode === "link") {
|
|
2137
|
+
linkFile(command.path, commandTargetPath);
|
|
2138
|
+
} else {
|
|
2139
|
+
copyFile(command.path, commandTargetPath);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
deployCommands(commands, toolConfig, mode) {
|
|
2143
|
+
for (const command of commands) {
|
|
2144
|
+
this.deployCommand(command, toolConfig, mode);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
removeCommand(commandName, toolConfig) {
|
|
2148
|
+
const commandsDir = toolConfig.commandsDir;
|
|
2149
|
+
if (!commandsDir) return;
|
|
2150
|
+
const commandPath = join12(this.projectDir, commandsDir, `${commandName}.md`);
|
|
2151
|
+
removeFile(commandPath);
|
|
2152
|
+
}
|
|
2153
|
+
getDeployedCommandPath(commandName, toolConfig) {
|
|
2154
|
+
const commandsDir = toolConfig.commandsDir;
|
|
2155
|
+
if (!commandsDir) return void 0;
|
|
2156
|
+
return join12(this.projectDir, commandsDir, `${commandName}.md`);
|
|
2157
|
+
}
|
|
2158
|
+
isCommandDeployed(commandName, toolConfig) {
|
|
2159
|
+
const commandPath = this.getDeployedCommandPath(commandName, toolConfig);
|
|
2160
|
+
if (!commandPath) return false;
|
|
2161
|
+
return existsSync3(commandPath);
|
|
2162
|
+
}
|
|
1686
2163
|
};
|
|
1687
2164
|
|
|
1688
2165
|
// src/commands/init.ts
|
|
@@ -1692,11 +2169,13 @@ async function executeInit(options) {
|
|
|
1692
2169
|
process.exit(1);
|
|
1693
2170
|
}
|
|
1694
2171
|
const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
|
|
2172
|
+
const commandsService = new CommandsService(SKILLS_MANAGER_DIR);
|
|
1695
2173
|
const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
|
|
1696
2174
|
const deployer = new Deployer(process.cwd());
|
|
1697
2175
|
const allSkills = skillsService.getAllSkills();
|
|
1698
|
-
|
|
1699
|
-
|
|
2176
|
+
const allCommands = commandsService.getAllCommands();
|
|
2177
|
+
if (allSkills.length === 0 && allCommands.length === 0) {
|
|
2178
|
+
console.log("No skills or commands found. Run: skillsmgr install anthropic");
|
|
1700
2179
|
process.exit(1);
|
|
1701
2180
|
}
|
|
1702
2181
|
const configuredTools = scanner.getConfiguredTools();
|
|
@@ -1716,75 +2195,123 @@ async function executeInit(options) {
|
|
|
1716
2195
|
const deployed = scanner.getDeployedSkills(toolName);
|
|
1717
2196
|
deployed.forEach((s) => deployedSkillNames.add(s.name));
|
|
1718
2197
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2198
|
+
let selectedSkillNames = [];
|
|
2199
|
+
if (allSkills.length > 0) {
|
|
2200
|
+
selectedSkillNames = await promptSkills(
|
|
2201
|
+
allSkills,
|
|
2202
|
+
Array.from(deployedSkillNames)
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
let selectedCommandNames = [];
|
|
2206
|
+
const toolsWithCommands = selectedTools.filter(
|
|
2207
|
+
(t) => TOOL_CONFIGS[t].commandsDir
|
|
1722
2208
|
);
|
|
1723
|
-
if (
|
|
1724
|
-
|
|
2209
|
+
if (allCommands.length > 0 && toolsWithCommands.length > 0) {
|
|
2210
|
+
const deployedCommandNames = /* @__PURE__ */ new Set();
|
|
2211
|
+
for (const toolName of selectedTools) {
|
|
2212
|
+
const deployed = scanner.getDeployedCommands(toolName);
|
|
2213
|
+
deployed.forEach((c) => deployedCommandNames.add(c.name));
|
|
2214
|
+
}
|
|
2215
|
+
selectedCommandNames = await promptCommands(
|
|
2216
|
+
allCommands,
|
|
2217
|
+
Array.from(deployedCommandNames)
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
if (selectedSkillNames.length === 0 && selectedCommandNames.length === 0) {
|
|
2221
|
+
console.log("No skills or commands selected");
|
|
1725
2222
|
return;
|
|
1726
2223
|
}
|
|
1727
2224
|
const selectedSkills = skillsService.getSkillsByNames(selectedSkillNames);
|
|
2225
|
+
const selectedCommands = commandsService.getCommandsByNames(selectedCommandNames);
|
|
1728
2226
|
const deployMode = options.copy ? "copy" : "link";
|
|
1729
|
-
console.log("\nDeploying
|
|
2227
|
+
console.log("\nDeploying...\n");
|
|
1730
2228
|
for (const toolName of selectedTools) {
|
|
1731
2229
|
const config = TOOL_CONFIGS[toolName];
|
|
1732
2230
|
const mode = toolModes[toolName];
|
|
1733
2231
|
const targetDir = getTargetDir(config, mode);
|
|
1734
2232
|
console.log(`${config.displayName}:`);
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2233
|
+
if (selectedSkills.length > 0) {
|
|
2234
|
+
const previouslyDeployed = scanner.getDeployedSkills(toolName);
|
|
2235
|
+
const previousNames = new Set(previouslyDeployed.map((s) => s.name));
|
|
2236
|
+
const toAdd = selectedSkills.filter((s) => !previousNames.has(s.name));
|
|
2237
|
+
const toKeep = selectedSkills.filter((s) => previousNames.has(s.name));
|
|
2238
|
+
const toRemove = previouslyDeployed.filter(
|
|
2239
|
+
(s) => !selectedSkillNames.includes(s.name) && s.source !== "unknown"
|
|
2240
|
+
);
|
|
2241
|
+
const unmanagedSkills = previouslyDeployed.filter(
|
|
2242
|
+
(s) => s.source === "unknown"
|
|
2243
|
+
);
|
|
2244
|
+
for (const skill of toRemove) {
|
|
2245
|
+
deployer.removeSkill(skill.name, config, mode);
|
|
2246
|
+
console.log(` \u2717 ${skill.name} (removed)`);
|
|
2247
|
+
}
|
|
2248
|
+
for (const skill of toKeep) {
|
|
2249
|
+
console.log(` \xB7 ${skill.name} (unchanged)`);
|
|
2250
|
+
}
|
|
2251
|
+
for (const skill of toAdd) {
|
|
2252
|
+
deployer.deploySkill(skill, config, deployMode, mode);
|
|
2253
|
+
console.log(` \u2713 ${skill.name} (${deployMode === "link" ? "linked" : "copied"})`);
|
|
2254
|
+
}
|
|
2255
|
+
for (const skill of unmanagedSkills) {
|
|
2256
|
+
console.log(` ~ ${skill.name} (unmanaged)`);
|
|
2257
|
+
}
|
|
1748
2258
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2259
|
+
if (config.commandsDir && selectedCommands.length > 0) {
|
|
2260
|
+
const previouslyDeployed = scanner.getDeployedCommands(toolName);
|
|
2261
|
+
const previousNames = new Set(previouslyDeployed.map((c) => c.name));
|
|
2262
|
+
const toAdd = selectedCommands.filter((c) => !previousNames.has(c.name));
|
|
2263
|
+
const toKeep = selectedCommands.filter((c) => previousNames.has(c.name));
|
|
2264
|
+
const toRemove = previouslyDeployed.filter(
|
|
2265
|
+
(c) => !selectedCommandNames.includes(c.name) && c.source !== "unknown"
|
|
2266
|
+
);
|
|
2267
|
+
const unmanagedCommands = previouslyDeployed.filter(
|
|
2268
|
+
(c) => c.source === "unknown"
|
|
2269
|
+
);
|
|
2270
|
+
for (const cmd of toRemove) {
|
|
2271
|
+
deployer.removeCommand(cmd.name, config);
|
|
2272
|
+
console.log(` \u2717 /${cmd.name} (removed)`);
|
|
2273
|
+
}
|
|
2274
|
+
for (const cmd of toKeep) {
|
|
2275
|
+
console.log(` \xB7 /${cmd.name} (unchanged)`);
|
|
2276
|
+
}
|
|
2277
|
+
for (const cmd of toAdd) {
|
|
2278
|
+
deployer.deployCommand(cmd, config, deployMode);
|
|
2279
|
+
console.log(` \u2713 /${cmd.name} (${deployMode === "link" ? "linked" : "copied"})`);
|
|
2280
|
+
}
|
|
2281
|
+
for (const cmd of unmanagedCommands) {
|
|
2282
|
+
console.log(` ~ /${cmd.name} (unmanaged)`);
|
|
2283
|
+
}
|
|
1752
2284
|
}
|
|
1753
2285
|
console.log();
|
|
1754
2286
|
}
|
|
2287
|
+
const parts = [];
|
|
2288
|
+
if (selectedSkillNames.length > 0) parts.push(`${selectedSkillNames.length} skills`);
|
|
2289
|
+
if (selectedCommandNames.length > 0) parts.push(`${selectedCommandNames.length} commands`);
|
|
1755
2290
|
console.log(
|
|
1756
|
-
`Done! Deployed ${
|
|
2291
|
+
`Done! Deployed ${parts.join(" and ")} to ${selectedTools.length} tool${selectedTools.length > 1 ? "s" : ""}.`
|
|
1757
2292
|
);
|
|
1758
2293
|
}
|
|
1759
|
-
var initCommand = new Command5("init").description("Deploy skills to current project").option("--copy", "Copy files instead of creating symlinks").action(async (options) => {
|
|
2294
|
+
var initCommand = new Command5("init").description("Deploy skills and commands to current project").option("--copy", "Copy files instead of creating symlinks").action(async (options) => {
|
|
1760
2295
|
await executeInit(options);
|
|
1761
2296
|
});
|
|
1762
2297
|
|
|
1763
2298
|
// src/commands/add.ts
|
|
1764
2299
|
import { Command as Command6 } from "commander";
|
|
1765
|
-
async function executeAdd(
|
|
2300
|
+
async function executeAdd(name, options) {
|
|
1766
2301
|
if (!fileExists(SKILLS_MANAGER_DIR)) {
|
|
1767
2302
|
console.log("Skills manager not set up. Run: skillsmgr setup");
|
|
1768
2303
|
process.exit(1);
|
|
1769
2304
|
}
|
|
1770
2305
|
const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
|
|
2306
|
+
const commandsService = new CommandsService(SKILLS_MANAGER_DIR);
|
|
1771
2307
|
const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
|
|
1772
2308
|
const deployer = new Deployer(process.cwd());
|
|
1773
|
-
const matchingSkills = skillsService.findSkillsByName(
|
|
1774
|
-
|
|
1775
|
-
|
|
2309
|
+
const matchingSkills = skillsService.findSkillsByName(name);
|
|
2310
|
+
const matchingCommands = commandsService.findCommandsByName(name);
|
|
2311
|
+
if (matchingSkills.length === 0 && matchingCommands.length === 0) {
|
|
2312
|
+
console.log(`'${name}' not found as a skill or command`);
|
|
1776
2313
|
process.exit(1);
|
|
1777
2314
|
}
|
|
1778
|
-
let skill = matchingSkills[0];
|
|
1779
|
-
if (matchingSkills.length > 1) {
|
|
1780
|
-
console.log(`Multiple skills found with name '${skillName}':`);
|
|
1781
|
-
const choices = matchingSkills.map((s, i) => ({
|
|
1782
|
-
name: `${i + 1}. ${s.source}/${s.name}`,
|
|
1783
|
-
value: s.source
|
|
1784
|
-
}));
|
|
1785
|
-
const selectedSource = await promptSelect("Select skill:", choices);
|
|
1786
|
-
skill = matchingSkills.find((s) => s.source === selectedSource);
|
|
1787
|
-
}
|
|
1788
2315
|
let targetTools;
|
|
1789
2316
|
if (options.tool) {
|
|
1790
2317
|
if (!TOOL_CONFIGS[options.tool]) {
|
|
@@ -1800,35 +2327,80 @@ async function executeAdd(skillName, options) {
|
|
|
1800
2327
|
}
|
|
1801
2328
|
}
|
|
1802
2329
|
const deployMode = options.copy ? "copy" : "link";
|
|
1803
|
-
|
|
2330
|
+
if (matchingSkills.length > 0) {
|
|
2331
|
+
let skill = matchingSkills[0];
|
|
2332
|
+
if (matchingSkills.length > 1) {
|
|
2333
|
+
console.log(`Multiple skills found with name '${name}':`);
|
|
2334
|
+
const choices = matchingSkills.map((s, i) => ({
|
|
2335
|
+
name: `${i + 1}. ${s.source}/${s.name}`,
|
|
2336
|
+
value: s.source
|
|
2337
|
+
}));
|
|
2338
|
+
const selectedSource = await promptSelect("Select skill:", choices);
|
|
2339
|
+
skill = matchingSkills.find((s) => s.source === selectedSource);
|
|
2340
|
+
}
|
|
2341
|
+
console.log(`Adding skill ${name} to configured tools...`);
|
|
2342
|
+
for (const toolName of targetTools) {
|
|
2343
|
+
const config = TOOL_CONFIGS[toolName];
|
|
2344
|
+
const deployments = scanner.scanToolDeployment(toolName, config);
|
|
2345
|
+
const mode = deployments.length > 0 && deployments[0].mode ? deployments[0].mode : "all";
|
|
2346
|
+
const existingSkills = scanner.getDeployedSkills(toolName);
|
|
2347
|
+
const alreadyExists = existingSkills.some((s) => s.name === skill.name);
|
|
2348
|
+
if (alreadyExists) {
|
|
2349
|
+
console.log(` \xB7 ${config.displayName} (already deployed)`);
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
deployer.deploySkill(skill, config, deployMode, mode);
|
|
2353
|
+
console.log(
|
|
2354
|
+
` \u2713 ${config.displayName} (${deployMode === "link" ? "linked" : "copied"})`
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
let command = matchingCommands[0];
|
|
2360
|
+
if (matchingCommands.length > 1) {
|
|
2361
|
+
console.log(`Multiple commands found with name '${name}':`);
|
|
2362
|
+
const choices = matchingCommands.map((c, i) => ({
|
|
2363
|
+
name: `${i + 1}. ${c.source}/${c.name}`,
|
|
2364
|
+
value: c.source
|
|
2365
|
+
}));
|
|
2366
|
+
const selectedSource = await promptSelect("Select command:", choices);
|
|
2367
|
+
command = matchingCommands.find((c) => c.source === selectedSource);
|
|
2368
|
+
}
|
|
2369
|
+
console.log(`Adding command /${name} to configured tools...`);
|
|
1804
2370
|
for (const toolName of targetTools) {
|
|
1805
2371
|
const config = TOOL_CONFIGS[toolName];
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2372
|
+
if (!config.commandsDir) {
|
|
2373
|
+
console.log(` \xB7 ${config.displayName} (commands not supported)`);
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
const existingCommands = scanner.getDeployedCommands(toolName);
|
|
2377
|
+
const alreadyExists = existingCommands.some((c) => c.name === command.name);
|
|
1810
2378
|
if (alreadyExists) {
|
|
1811
2379
|
console.log(` \xB7 ${config.displayName} (already deployed)`);
|
|
1812
2380
|
continue;
|
|
1813
2381
|
}
|
|
1814
|
-
deployer.
|
|
2382
|
+
deployer.deployCommand(command, config, deployMode);
|
|
1815
2383
|
console.log(
|
|
1816
2384
|
` \u2713 ${config.displayName} (${deployMode === "link" ? "linked" : "copied"})`
|
|
1817
2385
|
);
|
|
1818
2386
|
}
|
|
1819
2387
|
}
|
|
1820
|
-
var addCommand = new Command6("add").description("Add a skill to the project").argument("<
|
|
1821
|
-
await executeAdd(
|
|
2388
|
+
var addCommand = new Command6("add").description("Add a skill or command to the project").argument("<name>", "Skill or command name to add").option("--tool <tool>", "Add to specific tool only").option("--copy", "Copy files instead of creating symlinks").action(async (name, options) => {
|
|
2389
|
+
await executeAdd(name, options);
|
|
1822
2390
|
});
|
|
1823
2391
|
|
|
1824
2392
|
// src/commands/remove.ts
|
|
1825
2393
|
import { Command as Command7 } from "commander";
|
|
1826
|
-
async function executeRemove(
|
|
2394
|
+
async function executeRemove(name, options) {
|
|
2395
|
+
if (!fileExists(SKILLS_MANAGER_DIR)) {
|
|
2396
|
+
console.log("Skills manager not set up. Run: skillsmgr setup");
|
|
2397
|
+
process.exit(1);
|
|
2398
|
+
}
|
|
1827
2399
|
const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
|
|
1828
2400
|
const deployer = new Deployer(process.cwd());
|
|
1829
2401
|
const configuredTools = scanner.getConfiguredTools();
|
|
1830
2402
|
if (configuredTools.length === 0) {
|
|
1831
|
-
console.log("No skills deployed in current project.");
|
|
2403
|
+
console.log("No skills or commands deployed in current project.");
|
|
1832
2404
|
process.exit(1);
|
|
1833
2405
|
}
|
|
1834
2406
|
let targetTools;
|
|
@@ -1841,41 +2413,55 @@ async function executeRemove(skillName, options) {
|
|
|
1841
2413
|
} else {
|
|
1842
2414
|
targetTools = configuredTools;
|
|
1843
2415
|
}
|
|
1844
|
-
console.log(`Removing ${
|
|
2416
|
+
console.log(`Removing ${name}...`);
|
|
1845
2417
|
let removed = false;
|
|
1846
2418
|
for (const toolName of targetTools) {
|
|
1847
2419
|
const config = TOOL_CONFIGS[toolName];
|
|
1848
2420
|
const deployments = scanner.scanToolDeployment(toolName, config);
|
|
1849
2421
|
for (const deployment of deployments) {
|
|
1850
|
-
const skillToRemove = deployment.skills.find((s) => s.name ===
|
|
2422
|
+
const skillToRemove = deployment.skills.find((s) => s.name === name);
|
|
1851
2423
|
if (!skillToRemove) continue;
|
|
1852
2424
|
const mode = deployment.mode || "all";
|
|
1853
|
-
deployer.removeSkill(
|
|
1854
|
-
console.log(` \u2713 Removed from ${config.displayName}`);
|
|
2425
|
+
deployer.removeSkill(name, config, mode);
|
|
2426
|
+
console.log(` \u2713 Removed skill from ${config.displayName}`);
|
|
1855
2427
|
removed = true;
|
|
1856
2428
|
}
|
|
2429
|
+
if (config.commandsDir) {
|
|
2430
|
+
const deployedCommands = scanner.getDeployedCommands(toolName);
|
|
2431
|
+
const commandToRemove = deployedCommands.find((c) => c.name === name);
|
|
2432
|
+
if (commandToRemove) {
|
|
2433
|
+
deployer.removeCommand(name, config);
|
|
2434
|
+
console.log(` \u2713 Removed command from ${config.displayName}`);
|
|
2435
|
+
removed = true;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
1857
2438
|
}
|
|
1858
2439
|
if (!removed) {
|
|
1859
|
-
console.log(`
|
|
2440
|
+
console.log(`'${name}' not found as a skill or command in any configured tool`);
|
|
1860
2441
|
}
|
|
1861
2442
|
}
|
|
1862
|
-
var removeCommand = new Command7("remove").description("Remove a skill from the project").argument("<
|
|
1863
|
-
await executeRemove(
|
|
2443
|
+
var removeCommand = new Command7("remove").description("Remove a skill or command from the project").argument("<name>", "Skill or command name to remove").option("--tool <tool>", "Remove from specific tool only").action(async (name, options) => {
|
|
2444
|
+
await executeRemove(name, options);
|
|
1864
2445
|
});
|
|
1865
2446
|
|
|
1866
2447
|
// src/commands/sync.ts
|
|
1867
2448
|
import { Command as Command8 } from "commander";
|
|
1868
|
-
import { join as
|
|
2449
|
+
import { join as join13 } from "path";
|
|
1869
2450
|
async function executeSync() {
|
|
2451
|
+
if (!fileExists(SKILLS_MANAGER_DIR)) {
|
|
2452
|
+
console.log("Skills manager not set up. Run: skillsmgr setup");
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
1870
2455
|
const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
|
|
1871
2456
|
const deployer = new Deployer(process.cwd());
|
|
1872
2457
|
const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
|
|
2458
|
+
const commandsService = new CommandsService(SKILLS_MANAGER_DIR);
|
|
1873
2459
|
const deployments = scanner.scanAllTools();
|
|
1874
2460
|
if (deployments.length === 0) {
|
|
1875
|
-
console.log("No skills deployed in current project.");
|
|
2461
|
+
console.log("No skills or commands deployed in current project.");
|
|
1876
2462
|
process.exit(1);
|
|
1877
2463
|
}
|
|
1878
|
-
console.log("Checking deployed skills...\n");
|
|
2464
|
+
console.log("Checking deployed skills and commands...\n");
|
|
1879
2465
|
let updatedCount = 0;
|
|
1880
2466
|
let removedCount = 0;
|
|
1881
2467
|
for (const deployment of deployments) {
|
|
@@ -1883,6 +2469,10 @@ async function executeSync() {
|
|
|
1883
2469
|
const mode = deployment.mode || "all";
|
|
1884
2470
|
console.log(`${config.displayName} (${deployment.targetDir}/):`);
|
|
1885
2471
|
for (const skill of deployment.skills) {
|
|
2472
|
+
if (skill.source === "unknown" && !skill.conflict) {
|
|
2473
|
+
console.log(` ~ ${skill.name} (unmanaged)`);
|
|
2474
|
+
continue;
|
|
2475
|
+
}
|
|
1886
2476
|
if (skill.conflict) {
|
|
1887
2477
|
console.log(` \u26A0 ${skill.name}: conflict (skipped)`);
|
|
1888
2478
|
continue;
|
|
@@ -1910,8 +2500,8 @@ async function executeSync() {
|
|
|
1910
2500
|
continue;
|
|
1911
2501
|
}
|
|
1912
2502
|
if (skill.deployMode === "copy") {
|
|
1913
|
-
const sourceSkillMd =
|
|
1914
|
-
const deployedSkillMd =
|
|
2503
|
+
const sourceSkillMd = join13(sourcePath, "SKILL.md");
|
|
2504
|
+
const deployedSkillMd = join13(deployedPath, "SKILL.md");
|
|
1915
2505
|
if (fileExists(sourceSkillMd) && fileExists(deployedSkillMd)) {
|
|
1916
2506
|
const sourceContent = readFileContent(sourceSkillMd);
|
|
1917
2507
|
const deployedContent = readFileContent(deployedSkillMd);
|
|
@@ -1951,19 +2541,62 @@ async function executeSync() {
|
|
|
1951
2541
|
}
|
|
1952
2542
|
}
|
|
1953
2543
|
}
|
|
2544
|
+
for (const command of deployment.commands) {
|
|
2545
|
+
if (command.source === "unknown") {
|
|
2546
|
+
console.log(` ~ /${command.name} (unmanaged)`);
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
const deployedPath = command.path;
|
|
2550
|
+
let sourceCommand = commandsService.getCommandByName(command.name);
|
|
2551
|
+
if (!sourceCommand || !fileExists(sourceCommand.path)) {
|
|
2552
|
+
console.log(` \u2717 /${command.name}: orphaned (source not found)`);
|
|
2553
|
+
const action = await promptOrphanAction(command.name);
|
|
2554
|
+
if (action === "remove") {
|
|
2555
|
+
deployer.removeCommand(command.name, config);
|
|
2556
|
+
console.log(` \u2713 Removed /${command.name}`);
|
|
2557
|
+
removedCount++;
|
|
2558
|
+
}
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
if (isSymlink(deployedPath)) {
|
|
2562
|
+
console.log(` \u2713 /${command.name}: up to date (link)`);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
if (command.deployMode === "copy") {
|
|
2566
|
+
const sourceContent = readFileContent(sourceCommand.path);
|
|
2567
|
+
const deployedContent = readFileContent(deployedPath);
|
|
2568
|
+
if (sourceContent !== deployedContent) {
|
|
2569
|
+
console.log(` \u26A0 /${command.name}: source changed (copy)`);
|
|
2570
|
+
const action = await promptSyncAction(command.name, false);
|
|
2571
|
+
if (action === "overwrite") {
|
|
2572
|
+
deployer.deployCommand(
|
|
2573
|
+
{ name: command.name, description: "", path: sourceCommand.path, source: command.source },
|
|
2574
|
+
config,
|
|
2575
|
+
"copy"
|
|
2576
|
+
);
|
|
2577
|
+
console.log(` \u2713 Updated /${command.name}`);
|
|
2578
|
+
updatedCount++;
|
|
2579
|
+
}
|
|
2580
|
+
} else {
|
|
2581
|
+
console.log(` \u2713 /${command.name}: up to date (copy)`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
1954
2585
|
console.log();
|
|
1955
2586
|
}
|
|
1956
2587
|
console.log(
|
|
1957
2588
|
`Sync complete: ${updatedCount} updated, ${removedCount} removed`
|
|
1958
2589
|
);
|
|
1959
2590
|
}
|
|
1960
|
-
var syncCommand = new Command8("sync").description("Sync and verify deployed skills").action(async () => {
|
|
2591
|
+
var syncCommand = new Command8("sync").description("Sync and verify deployed skills and commands").action(async () => {
|
|
1961
2592
|
await executeSync();
|
|
1962
2593
|
});
|
|
1963
2594
|
|
|
1964
2595
|
// src/index.ts
|
|
2596
|
+
var require2 = createRequire(import.meta.url);
|
|
2597
|
+
var { version } = require2("../package.json");
|
|
1965
2598
|
var program = new Command9();
|
|
1966
|
-
program.name("skillsmgr").description("Unified skills manager for AI coding tools").version(
|
|
2599
|
+
program.name("skillsmgr").description("Unified skills manager for AI coding tools").version(version);
|
|
1967
2600
|
program.addCommand(setupCommand);
|
|
1968
2601
|
program.addCommand(installCommand);
|
|
1969
2602
|
program.addCommand(updateCommand);
|