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.
Files changed (2) hide show
  1. package/dist/index.js +816 -183
  2. 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: ".agents/skills",
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 parseSkillDescription(content) {
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
- console.log("No skills found in repository");
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 = parseSkillDescription(content);
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 ${selectedSkills.length} skills to ${targetBase}`);
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 skills from ${owner}/${repo}...`);
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
- return false;
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 = parseSkillDescription(content);
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
- return false;
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 ${selectedSkills.length} skills to ${targetBase}`);
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 = parseSkillDescription(content);
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("No skills found in repository");
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
- console.log(`\u2713 Installed ${skills.length} skills to ${repoPath}`);
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 ${selectedNames.length} skills to ${repoPath}`);
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
- let skillsBasePath = "skills";
1246
- const skillsPaths = ["skills", ".", "src/skills"];
1247
- for (const skillsPath of skillsPaths) {
1248
- try {
1249
- const testList = await githubService.listSkills(owner, repo, skillsPath);
1250
- if (testList.length > 0) {
1251
- skillsBasePath = skillsPath;
1252
- break;
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
- for (const localSkill of localSkills) {
1259
- const skillName = localSkill.name;
1260
- const targetDir = localSkill.path;
1261
- const localSkillMd = join8(targetDir, "SKILL.md");
1262
- if (!fileExists(localSkillMd)) {
1263
- continue;
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 remotePath = skillsBasePath === "." ? skillName : `${skillsBasePath}/${skillName}`;
1266
- try {
1267
- const response = await fetch(
1268
- `https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${remotePath}/SKILL.md`
1269
- );
1270
- if (!response.ok) {
1271
- console.log(` \u26A0 ${skillName}: not found in remote`);
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/scanner.ts
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 = join10(this.projectDir, config.skillsDir);
1823
+ const baseDir = join11(this.projectDir, config.skillsDir);
1457
1824
  const baseDeployment = this.scanDirectory(toolName, baseDir, config.skillsDir);
1458
- if (baseDeployment.skills.length > 0) {
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 = join10(this.projectDir, modeDir);
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 = join10(fullPath, entry.name);
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 = join10(skillPath, "SKILL.md");
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 service = new SkillsService(SKILLS_MANAGER_DIR);
1594
- const skills = service.getAllSkills();
1595
- if (skills.length === 0) {
1596
- console.log("No skills found in ~/.skills-manager/");
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 skills in ~/.skills-manager/:\n");
1601
- const grouped = {};
1602
- for (const skill of skills) {
1603
- if (!grouped[skill.source]) {
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
- for (const [source, sourceSkills] of Object.entries(grouped)) {
1609
- console.log(`\u2500\u2500 ${source} (${sourceSkills.length} skill${sourceSkills.length > 1 ? "s" : ""}) \u2500\u2500`);
1610
- for (const skill of sourceSkills) {
1611
- console.log(` ${skill.name}`);
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 skills in current project:\n");
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
- console.log(`${displayName} (${deployment.targetDir}/)${dirSuffix}:`);
1630
- for (const skill of deployment.skills) {
1631
- const modeStr = skill.deployMode === "link" ? "link" : "copy";
1632
- if (skill.conflict) {
1633
- console.log(` \u26A0 ${skill.name.padEnd(16)} (${modeStr}) \u2190 conflict`);
1634
- } else {
1635
- console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
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 join11 } from "path";
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 = join11(this.projectDir, targetDir);
2101
+ const fullTargetDir = join12(this.projectDir, targetDir);
1658
2102
  ensureDir(fullTargetDir);
1659
- const skillTargetPath = join11(fullTargetDir, skill.name);
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 = join11(this.projectDir, targetDir, skillName);
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 join11(this.projectDir, targetDir, skillName);
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
- if (allSkills.length === 0) {
1699
- console.log("No skills found. Run: skillsmgr install anthropic");
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
- const selectedSkillNames = await promptSkills(
1720
- allSkills,
1721
- Array.from(deployedSkillNames)
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 (selectedSkillNames.length === 0) {
1724
- console.log("No skills selected");
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 skills...\n");
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
- const previouslyDeployed = scanner.getDeployedSkills(toolName);
1736
- const previousNames = new Set(previouslyDeployed.map((s) => s.name));
1737
- const toAdd = selectedSkills.filter((s) => !previousNames.has(s.name));
1738
- const toKeep = selectedSkills.filter((s) => previousNames.has(s.name));
1739
- const toRemove = previouslyDeployed.filter(
1740
- (s) => !selectedSkillNames.includes(s.name)
1741
- );
1742
- for (const skill of toRemove) {
1743
- deployer.removeSkill(skill.name, config, mode);
1744
- console.log(` \u2717 ${skill.name} (removed)`);
1745
- }
1746
- for (const skill of toKeep) {
1747
- console.log(` \xB7 ${skill.name} (unchanged)`);
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
- for (const skill of toAdd) {
1750
- deployer.deploySkill(skill, config, deployMode, mode);
1751
- console.log(` \u2713 ${skill.name} (${deployMode === "link" ? "linked" : "copied"})`);
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 ${selectedSkillNames.length} skills to ${selectedTools.length} tool${selectedTools.length > 1 ? "s" : ""}.`
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(skillName, options) {
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(skillName);
1774
- if (matchingSkills.length === 0) {
1775
- console.log(`Skill '${skillName}' not found`);
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
- console.log(`Adding ${skillName} to configured tools...`);
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
- const deployments = scanner.scanToolDeployment(toolName, config);
1807
- const mode = deployments.length > 0 && deployments[0].mode ? deployments[0].mode : "all";
1808
- const existingSkills = scanner.getDeployedSkills(toolName);
1809
- const alreadyExists = existingSkills.some((s) => s.name === skill.name);
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.deploySkill(skill, config, deployMode, mode);
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("<skill>", "Skill name to add").option("--tool <tool>", "Add to specific tool only").option("--copy", "Copy files instead of creating symlinks").action(async (skill, options) => {
1821
- await executeAdd(skill, options);
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(skillName, options) {
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 ${skillName}...`);
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 === skillName);
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(skillName, config, mode);
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(`Skill '${skillName}' not found in any configured tool`);
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("<skill>", "Skill name to remove").option("--tool <tool>", "Remove from specific tool only").action(async (skill, options) => {
1863
- await executeRemove(skill, options);
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 join12 } from "path";
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 = join12(sourcePath, "SKILL.md");
1914
- const deployedSkillMd = join12(deployedPath, "SKILL.md");
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("0.1.0");
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);