skillsmgr 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -124,6 +124,18 @@ Sync and verify deployed skills.
124
124
  npx skillsmgr sync
125
125
  ```
126
126
 
127
+ ### `npx skillsmgr update`
128
+
129
+ Update installed skills to latest version from remote.
130
+
131
+ ```bash
132
+ # Update all installed sources
133
+ npx skillsmgr update
134
+
135
+ # Update specific source
136
+ npx skillsmgr update anthropic
137
+ ```
138
+
127
139
  ## Directory Structure
128
140
 
129
141
  ```
package/README.zh-CN.md CHANGED
@@ -124,6 +124,18 @@ npx skillsmgr remove code-review --tool claude-code
124
124
  npx skillsmgr sync
125
125
  ```
126
126
 
127
+ ### `npx skillsmgr update`
128
+
129
+ 从远程更新已安装的 skills 到最新版本。
130
+
131
+ ```bash
132
+ # 更新所有已安装的源
133
+ npx skillsmgr update
134
+
135
+ # 更新特定源
136
+ npx skillsmgr update anthropic
137
+ ```
138
+
127
139
  ## 目录结构
128
140
 
129
141
  ```
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command8 } from "commander";
4
+ import { Command as Command9 } from "commander";
5
5
 
6
6
  // src/commands/setup.ts
7
7
  import { Command } from "commander";
@@ -68,6 +68,10 @@ function readSymlinkTarget(path) {
68
68
  function readFileContent(path) {
69
69
  return readFileSync(path, "utf-8");
70
70
  }
71
+ function writeFile(path, content) {
72
+ ensureDir(dirname(path));
73
+ writeFileSync(path, content, "utf-8");
74
+ }
71
75
  function fileExists(path) {
72
76
  return existsSync(path);
73
77
  }
@@ -137,7 +141,7 @@ var setupCommand = new Command("setup").description("Initialize ~/.skills-manage
137
141
 
138
142
  // src/commands/install.ts
139
143
  import { Command as Command2 } from "commander";
140
- import { join as join6 } from "path";
144
+ import { join as join7 } from "path";
141
145
 
142
146
  // src/services/git.ts
143
147
  import { execSync } from "child_process";
@@ -365,6 +369,52 @@ var GitHubService = class {
365
369
  }
366
370
  };
367
371
 
372
+ // src/services/sources.ts
373
+ import { join as join6 } from "path";
374
+ var SOURCES_FILE = join6(SKILLS_MANAGER_DIR, "sources.json");
375
+ var SourcesService = class {
376
+ load() {
377
+ if (!fileExists(SOURCES_FILE)) {
378
+ return { version: "1.0", sources: {} };
379
+ }
380
+ const content = readFileContent(SOURCES_FILE);
381
+ return JSON.parse(content);
382
+ }
383
+ save(data) {
384
+ writeFile(SOURCES_FILE, JSON.stringify(data, null, 2));
385
+ }
386
+ addSource(key, info) {
387
+ const data = this.load();
388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
389
+ data.sources[key] = {
390
+ ...info,
391
+ installedAt: data.sources[key]?.installedAt || now,
392
+ updatedAt: now
393
+ };
394
+ this.save(data);
395
+ }
396
+ getSource(key) {
397
+ const data = this.load();
398
+ return data.sources[key];
399
+ }
400
+ getAllSources() {
401
+ const data = this.load();
402
+ return data.sources;
403
+ }
404
+ removeSource(key) {
405
+ const data = this.load();
406
+ delete data.sources[key];
407
+ this.save(data);
408
+ }
409
+ updateTimestamp(key) {
410
+ const data = this.load();
411
+ if (data.sources[key]) {
412
+ data.sources[key].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
413
+ this.save(data);
414
+ }
415
+ }
416
+ };
417
+
368
418
  // src/utils/prompts.ts
369
419
  import inquirer from "inquirer";
370
420
 
@@ -875,6 +925,7 @@ var ProgressBar = class {
875
925
  };
876
926
 
877
927
  // src/commands/install.ts
928
+ var sourcesService = new SourcesService();
878
929
  function parseSkillDescription(content) {
879
930
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
880
931
  if (!frontmatterMatch) {
@@ -884,16 +935,16 @@ function parseSkillDescription(content) {
884
935
  return descMatch ? descMatch[1].trim() : "";
885
936
  }
886
937
  async function installFromAnthropic(options) {
887
- const githubService = new GitHubService();
938
+ const githubService2 = new GitHubService();
888
939
  const owner = "anthropics";
889
940
  const repo = "skills";
890
941
  console.log("Fetching available skills from anthropic/skills...");
891
- const skillsList = await githubService.listSkills(owner, repo, "skills");
942
+ const skillsList = await githubService2.listSkills(owner, repo, "skills");
892
943
  if (skillsList.length === 0) {
893
944
  console.log("No skills found in repository");
894
945
  return;
895
946
  }
896
- const defaultBranch = await githubService.getDefaultBranch(owner, repo);
947
+ const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
897
948
  const skills = [];
898
949
  const progress = new ProgressBar(skillsList.length, "Fetching skill info");
899
950
  progress.start();
@@ -932,19 +983,24 @@ async function installFromAnthropic(options) {
932
983
  }
933
984
  console.log(`
934
985
  Downloading ${selectedSkills.length} skills...`);
935
- const targetBase = join6(SKILLS_MANAGER_DIR, "official", "anthropic");
986
+ const targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
936
987
  for (const skill of selectedSkills) {
937
- const targetDir = join6(targetBase, skill.name);
988
+ const targetDir = join7(targetBase, skill.name);
938
989
  process.stdout.write(` ${skill.name}...`);
939
- await githubService.downloadSkill(owner, repo, skill.path, targetDir);
990
+ await githubService2.downloadSkill(owner, repo, skill.path, targetDir);
940
991
  console.log(" \u2713");
941
992
  }
942
993
  console.log(`
943
994
  \u2713 Installed ${selectedSkills.length} skills to ${targetBase}`);
995
+ sourcesService.addSource("official/anthropic", {
996
+ url: "https://github.com/anthropics/skills",
997
+ type: "official",
998
+ repoName: "anthropic"
999
+ });
944
1000
  }
945
1001
  async function installFromGitHubUrl(url, options) {
946
- const githubService = new GitHubService();
947
- const parsed = githubService.parseGitHubUrl(url);
1002
+ const githubService2 = new GitHubService();
1003
+ const parsed = githubService2.parseGitHubUrl(url);
948
1004
  if (!parsed) {
949
1005
  return false;
950
1006
  }
@@ -952,9 +1008,9 @@ async function installFromGitHubUrl(url, options) {
952
1008
  const isAnthropic = owner === "anthropics" && repo === "skills";
953
1009
  if (path) {
954
1010
  const skillName = path.split("/").pop() || path;
955
- const targetDir = githubService.getTargetDir(owner, repo, skillName, options.custom);
1011
+ const targetDir = githubService2.getTargetDir(owner, repo, skillName, options.custom);
956
1012
  console.log(`Downloading ${skillName}...`);
957
- await githubService.downloadSkill(owner, repo, path, targetDir);
1013
+ await githubService2.downloadSkill(owner, repo, path, targetDir);
958
1014
  console.log(`\u2713 Installed ${skillName} to ${targetDir}`);
959
1015
  return true;
960
1016
  }
@@ -963,7 +1019,7 @@ async function installFromGitHubUrl(url, options) {
963
1019
  const skillsPaths = ["skills", ".", "src/skills"];
964
1020
  for (const skillsPath of skillsPaths) {
965
1021
  try {
966
- skillsList = await githubService.listSkills(owner, repo, skillsPath);
1022
+ skillsList = await githubService2.listSkills(owner, repo, skillsPath);
967
1023
  if (skillsList.length > 0) break;
968
1024
  } catch {
969
1025
  continue;
@@ -972,7 +1028,7 @@ async function installFromGitHubUrl(url, options) {
972
1028
  if (skillsList.length === 0) {
973
1029
  return false;
974
1030
  }
975
- const defaultBranch = await githubService.getDefaultBranch(owner, repo);
1031
+ const defaultBranch = await githubService2.getDefaultBranch(owner, repo);
976
1032
  const skills = [];
977
1033
  const progress = new ProgressBar(skillsList.length, "Fetching skill info");
978
1034
  progress.start();
@@ -1009,20 +1065,26 @@ async function installFromGitHubUrl(url, options) {
1009
1065
  Downloading ${selectedSkills.length} skills...`);
1010
1066
  let targetBase;
1011
1067
  if (isAnthropic) {
1012
- targetBase = join6(SKILLS_MANAGER_DIR, "official", "anthropic");
1068
+ targetBase = join7(SKILLS_MANAGER_DIR, "official", "anthropic");
1013
1069
  } else if (options.custom) {
1014
- targetBase = join6(SKILLS_MANAGER_DIR, "custom", repo);
1070
+ targetBase = join7(SKILLS_MANAGER_DIR, "custom", repo);
1015
1071
  } else {
1016
- targetBase = join6(SKILLS_MANAGER_DIR, "community", repo);
1072
+ targetBase = join7(SKILLS_MANAGER_DIR, "community", repo);
1017
1073
  }
1018
1074
  for (const skill of selectedSkills) {
1019
- const targetDir = join6(targetBase, skill.name);
1075
+ const targetDir = join7(targetBase, skill.name);
1020
1076
  process.stdout.write(` ${skill.name}...`);
1021
- await githubService.downloadSkill(owner, repo, skill.path, targetDir);
1077
+ await githubService2.downloadSkill(owner, repo, skill.path, targetDir);
1022
1078
  console.log(" \u2713");
1023
1079
  }
1024
1080
  console.log(`
1025
1081
  \u2713 Installed ${selectedSkills.length} skills to ${targetBase}`);
1082
+ const sourceKey = isAnthropic ? "official/anthropic" : options.custom ? `custom/${repo}` : `community/${repo}`;
1083
+ sourcesService.addSource(sourceKey, {
1084
+ url: `https://github.com/${owner}/${repo}`,
1085
+ type: isAnthropic ? "official" : options.custom ? "custom" : "community",
1086
+ repoName: repo
1087
+ });
1026
1088
  return true;
1027
1089
  }
1028
1090
  async function installViaGitClone(source, options) {
@@ -1040,7 +1102,7 @@ async function installViaGitClone(source, options) {
1040
1102
  const repoPath = gitService.clone(source, options.custom || false);
1041
1103
  let skillsRoot = repoPath;
1042
1104
  if (source === "anthropic") {
1043
- const skillsSubdir = join6(repoPath, "skills");
1105
+ const skillsSubdir = join7(repoPath, "skills");
1044
1106
  if (fileExists(skillsSubdir)) {
1045
1107
  skillsRoot = skillsSubdir;
1046
1108
  }
@@ -1048,7 +1110,7 @@ async function installViaGitClone(source, options) {
1048
1110
  const skillDirs = getDirectoriesInDir(skillsRoot);
1049
1111
  const skills = [];
1050
1112
  for (const dir of skillDirs) {
1051
- const skillMdPath = join6(dir.path, "SKILL.md");
1113
+ const skillMdPath = join7(dir.path, "SKILL.md");
1052
1114
  if (fileExists(skillMdPath)) {
1053
1115
  const content = readFileContent(skillMdPath);
1054
1116
  const description = parseSkillDescription(content);
@@ -1067,6 +1129,7 @@ async function installViaGitClone(source, options) {
1067
1129
  `);
1068
1130
  if (options.all) {
1069
1131
  console.log(`\u2713 Installed ${skills.length} skills to ${repoPath}`);
1132
+ saveGitCloneSource(source, repoPath, options);
1070
1133
  return;
1071
1134
  }
1072
1135
  const selectedNames = await promptSkillsToInstall(skills);
@@ -1081,6 +1144,33 @@ async function installViaGitClone(source, options) {
1081
1144
  }
1082
1145
  console.log(`
1083
1146
  \u2713 Installed ${selectedNames.length} skills to ${repoPath}`);
1147
+ saveGitCloneSource(source, repoPath, options);
1148
+ }
1149
+ function saveGitCloneSource(source, repoPath, options) {
1150
+ const repoName = repoPath.split("/").pop() || source;
1151
+ let type;
1152
+ let sourceKey;
1153
+ if (source === "anthropic" || repoPath.includes("/official/")) {
1154
+ type = "official";
1155
+ sourceKey = "official/anthropic";
1156
+ } else if (options.custom || repoPath.includes("/custom/")) {
1157
+ type = "custom";
1158
+ sourceKey = `custom/${repoName}`;
1159
+ } else {
1160
+ type = "community";
1161
+ sourceKey = `community/${repoName}`;
1162
+ }
1163
+ let url = source;
1164
+ if (source === "anthropic") {
1165
+ url = "https://github.com/anthropics/skills";
1166
+ } else if (!source.startsWith("http")) {
1167
+ url = `https://github.com/${source}`;
1168
+ }
1169
+ sourcesService.addSource(sourceKey, {
1170
+ url,
1171
+ type,
1172
+ repoName
1173
+ });
1084
1174
  }
1085
1175
  async function executeInstall(source, options) {
1086
1176
  if (!fileExists(SKILLS_MANAGER_DIR)) {
@@ -1109,11 +1199,125 @@ var installCommand = new Command2("install").description("Download skills from a
1109
1199
  await executeInstall(source, options);
1110
1200
  });
1111
1201
 
1112
- // src/commands/list.ts
1202
+ // src/commands/update.ts
1113
1203
  import { Command as Command3 } from "commander";
1204
+ import { join as join8 } from "path";
1205
+ var sourcesService2 = new SourcesService();
1206
+ var githubService = new GitHubService();
1207
+ async function updateSource(key, info) {
1208
+ const parsed = githubService.parseGitHubUrl(info.url);
1209
+ if (!parsed) {
1210
+ console.log(` \u26A0 Cannot parse URL: ${info.url}`);
1211
+ return 0;
1212
+ }
1213
+ const { owner, repo } = parsed;
1214
+ let skillsList = [];
1215
+ const skillsPaths = ["skills", ".", "src/skills"];
1216
+ for (const skillsPath of skillsPaths) {
1217
+ try {
1218
+ skillsList = await githubService.listSkills(owner, repo, skillsPath);
1219
+ if (skillsList.length > 0) break;
1220
+ } catch {
1221
+ continue;
1222
+ }
1223
+ }
1224
+ if (skillsList.length === 0) {
1225
+ console.log(` \u26A0 No skills found in ${owner}/${repo}`);
1226
+ return 0;
1227
+ }
1228
+ const defaultBranch = await githubService.getDefaultBranch(owner, repo);
1229
+ const skills = [];
1230
+ const progress = new ProgressBar(skillsList.length, "Checking skills");
1231
+ progress.start();
1232
+ for (const skill of skillsList) {
1233
+ try {
1234
+ const response = await fetch(
1235
+ `https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${skill.path}/SKILL.md`
1236
+ );
1237
+ if (response.ok) {
1238
+ skills.push(skill);
1239
+ }
1240
+ } catch {
1241
+ }
1242
+ progress.tick();
1243
+ }
1244
+ progress.complete();
1245
+ if (skills.length === 0) {
1246
+ return 0;
1247
+ }
1248
+ let targetBase;
1249
+ if (info.type === "official") {
1250
+ targetBase = join8(SKILLS_MANAGER_DIR, "official", info.repoName);
1251
+ } else if (info.type === "custom") {
1252
+ targetBase = join8(SKILLS_MANAGER_DIR, "custom", info.repoName);
1253
+ } else {
1254
+ targetBase = join8(SKILLS_MANAGER_DIR, "community", info.repoName);
1255
+ }
1256
+ let updatedCount = 0;
1257
+ for (const skill of skills) {
1258
+ const targetDir = join8(targetBase, skill.name);
1259
+ try {
1260
+ if (fileExists(targetDir)) {
1261
+ removeDir(targetDir);
1262
+ }
1263
+ await githubService.downloadSkill(owner, repo, skill.path, targetDir);
1264
+ updatedCount++;
1265
+ } catch {
1266
+ console.log(` \u26A0 Failed to update ${skill.name}`);
1267
+ }
1268
+ }
1269
+ sourcesService2.updateTimestamp(key);
1270
+ return updatedCount;
1271
+ }
1272
+ async function executeUpdate(source) {
1273
+ if (!fileExists(SKILLS_MANAGER_DIR)) {
1274
+ console.log("Skills manager not set up. Run: skillsmgr setup");
1275
+ process.exit(1);
1276
+ }
1277
+ const allSources = sourcesService2.getAllSources();
1278
+ if (Object.keys(allSources).length === 0) {
1279
+ console.log("No installed sources found.");
1280
+ console.log("\nRun: skillsmgr install anthropic");
1281
+ return;
1282
+ }
1283
+ if (source) {
1284
+ const matchingKey = Object.keys(allSources).find(
1285
+ (k) => k === source || k.endsWith(`/${source}`) || allSources[k].repoName === source
1286
+ );
1287
+ if (!matchingKey) {
1288
+ console.log(`Source '${source}' not found.`);
1289
+ console.log("\nInstalled sources:");
1290
+ for (const key of Object.keys(allSources)) {
1291
+ console.log(` ${key}`);
1292
+ }
1293
+ return;
1294
+ }
1295
+ console.log(`Updating ${matchingKey}...`);
1296
+ const count = await updateSource(matchingKey, allSources[matchingKey]);
1297
+ console.log(`
1298
+ \u2713 Updated ${count} skills from ${matchingKey}`);
1299
+ return;
1300
+ }
1301
+ console.log("Updating all installed sources...\n");
1302
+ let totalUpdated = 0;
1303
+ for (const [key, info] of Object.entries(allSources)) {
1304
+ console.log(`${key}:`);
1305
+ const count = await updateSource(key, info);
1306
+ console.log(` \u2713 ${count} skills updated
1307
+ `);
1308
+ totalUpdated += count;
1309
+ }
1310
+ console.log(`Done! Updated ${totalUpdated} skills from ${Object.keys(allSources).length} sources.`);
1311
+ }
1312
+ var updateCommand = new Command3("update").description("Update installed skills to latest version").argument("[source]", 'Specific source to update (e.g., "anthropic")').action(async (source) => {
1313
+ await executeUpdate(source);
1314
+ });
1315
+
1316
+ // src/commands/list.ts
1317
+ import { Command as Command4 } from "commander";
1114
1318
 
1115
1319
  // src/services/skills.ts
1116
- import { join as join7 } from "path";
1320
+ import { join as join9 } from "path";
1117
1321
  var SkillsService = class {
1118
1322
  constructor(skillsDir) {
1119
1323
  this.skillsDir = skillsDir;
@@ -1121,14 +1325,14 @@ var SkillsService = class {
1121
1325
  getAllSkills() {
1122
1326
  const skills = [];
1123
1327
  for (const source of SKILL_SOURCES) {
1124
- const sourceDir = join7(this.skillsDir, source);
1328
+ const sourceDir = join9(this.skillsDir, source);
1125
1329
  const sourceSkills = this.getSkillsFromSource(sourceDir, source);
1126
1330
  skills.push(...sourceSkills);
1127
1331
  }
1128
1332
  return skills;
1129
1333
  }
1130
1334
  getSkillsBySource(source) {
1131
- const sourceDir = join7(this.skillsDir, source);
1335
+ const sourceDir = join9(this.skillsDir, source);
1132
1336
  return this.getSkillsFromSource(sourceDir, source);
1133
1337
  }
1134
1338
  getSkillByName(name) {
@@ -1159,7 +1363,7 @@ var SkillsService = class {
1159
1363
  } else {
1160
1364
  const repoDirs = getDirectoriesInDir(sourceDir);
1161
1365
  for (const repoDir of repoDirs) {
1162
- const skillsSubdir = join7(repoDir.path, "skills");
1366
+ const skillsSubdir = join9(repoDir.path, "skills");
1163
1367
  const searchDir = fileExists(skillsSubdir) ? skillsSubdir : repoDir.path;
1164
1368
  const skillDirs = getDirectoriesInDir(searchDir);
1165
1369
  for (const skillDir of skillDirs) {
@@ -1174,7 +1378,7 @@ var SkillsService = class {
1174
1378
  return skills;
1175
1379
  }
1176
1380
  loadSkill(skillPath, source) {
1177
- const skillMdPath = join7(skillPath, "SKILL.md");
1381
+ const skillMdPath = join9(skillPath, "SKILL.md");
1178
1382
  if (!fileExists(skillMdPath)) {
1179
1383
  return void 0;
1180
1384
  }
@@ -1203,7 +1407,7 @@ var SkillsService = class {
1203
1407
  };
1204
1408
 
1205
1409
  // src/services/scanner.ts
1206
- import { join as join8 } from "path";
1410
+ import { join as join10 } from "path";
1207
1411
  import { readdirSync as readdirSync2 } from "fs";
1208
1412
  var DeploymentScanner = class {
1209
1413
  constructor(projectDir, skillsManagerDir = SKILLS_MANAGER_DIR) {
@@ -1223,7 +1427,7 @@ var DeploymentScanner = class {
1223
1427
  }
1224
1428
  scanToolDeployment(toolName, config) {
1225
1429
  const deployments = [];
1226
- const baseDir = join8(this.projectDir, config.skillsDir);
1430
+ const baseDir = join10(this.projectDir, config.skillsDir);
1227
1431
  const baseDeployment = this.scanDirectory(toolName, baseDir, config.skillsDir);
1228
1432
  if (baseDeployment.skills.length > 0) {
1229
1433
  deployments.push(baseDeployment);
@@ -1231,7 +1435,7 @@ var DeploymentScanner = class {
1231
1435
  if (config.supportsModeSpecific && config.availableModes) {
1232
1436
  for (const mode of config.availableModes) {
1233
1437
  const modeDir = getTargetDir(config, mode);
1234
- const fullModeDir = join8(this.projectDir, modeDir);
1438
+ const fullModeDir = join10(this.projectDir, modeDir);
1235
1439
  const modeDeployment = this.scanDirectory(toolName, fullModeDir, modeDir, mode);
1236
1440
  if (modeDeployment.skills.length > 0) {
1237
1441
  deployments.push(modeDeployment);
@@ -1276,7 +1480,7 @@ var DeploymentScanner = class {
1276
1480
  try {
1277
1481
  const entries = readdirSync2(fullPath, { withFileTypes: true });
1278
1482
  for (const entry of entries) {
1279
- const skillPath = join8(fullPath, entry.name);
1483
+ const skillPath = join10(fullPath, entry.name);
1280
1484
  const scanned = this.scanSkill(skillPath, entry.name);
1281
1485
  if (scanned) {
1282
1486
  deployment.skills.push(scanned);
@@ -1287,7 +1491,7 @@ var DeploymentScanner = class {
1287
1491
  return deployment;
1288
1492
  }
1289
1493
  scanSkill(skillPath, name) {
1290
- const skillMdPath = join8(skillPath, "SKILL.md");
1494
+ const skillMdPath = join10(skillPath, "SKILL.md");
1291
1495
  if (!fileExists(skillMdPath)) {
1292
1496
  return null;
1293
1497
  }
@@ -1311,12 +1515,13 @@ var DeploymentScanner = class {
1311
1515
  };
1312
1516
  }
1313
1517
  scanCopiedSkill(skillPath, name) {
1314
- const source = this.findSourceByName(name);
1518
+ const { source, conflict } = this.findSourceByName(name);
1315
1519
  return {
1316
1520
  name,
1317
1521
  source: source || "unknown",
1318
1522
  deployMode: "copy",
1319
- path: skillPath
1523
+ path: skillPath,
1524
+ conflict
1320
1525
  };
1321
1526
  }
1322
1527
  extractSourceFromPath(linkTarget) {
@@ -1337,9 +1542,12 @@ var DeploymentScanner = class {
1337
1542
  findSourceByName(skillName) {
1338
1543
  const matches = this.skillsService.findSkillsByName(skillName);
1339
1544
  if (matches.length === 0) {
1340
- return null;
1545
+ return { source: null, conflict: false };
1546
+ }
1547
+ if (matches.length > 1) {
1548
+ return { source: null, conflict: true };
1341
1549
  }
1342
- return matches[0].source;
1550
+ return { source: matches[0].source, conflict: false };
1343
1551
  }
1344
1552
  };
1345
1553
 
@@ -1395,20 +1603,24 @@ async function listDeployed() {
1395
1603
  console.log(`${displayName} (${deployment.targetDir}/)${dirSuffix}:`);
1396
1604
  for (const skill of deployment.skills) {
1397
1605
  const modeStr = skill.deployMode === "link" ? "link" : "copy";
1398
- console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
1606
+ if (skill.conflict) {
1607
+ console.log(` \u26A0 ${skill.name.padEnd(16)} (${modeStr}) \u2190 conflict`);
1608
+ } else {
1609
+ console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
1610
+ }
1399
1611
  }
1400
1612
  console.log();
1401
1613
  }
1402
1614
  }
1403
- var listCommand = new Command3("list").description("List available or deployed skills").option("--deployed", "List deployed skills in current project").action(async (options) => {
1615
+ var listCommand = new Command4("list").description("List available or deployed skills").option("--deployed", "List deployed skills in current project").action(async (options) => {
1404
1616
  await executeList(options);
1405
1617
  });
1406
1618
 
1407
1619
  // src/commands/init.ts
1408
- import { Command as Command4 } from "commander";
1620
+ import { Command as Command5 } from "commander";
1409
1621
 
1410
1622
  // src/services/deployer.ts
1411
- import { join as join9 } from "path";
1623
+ import { join as join11 } from "path";
1412
1624
  import { existsSync as existsSync3, rmSync as rmSync2 } from "fs";
1413
1625
  var Deployer = class {
1414
1626
  constructor(projectDir) {
@@ -1416,9 +1628,9 @@ var Deployer = class {
1416
1628
  }
1417
1629
  deploySkill(skill, toolConfig, mode, targetMode) {
1418
1630
  const targetDir = getTargetDir(toolConfig, targetMode);
1419
- const fullTargetDir = join9(this.projectDir, targetDir);
1631
+ const fullTargetDir = join11(this.projectDir, targetDir);
1420
1632
  ensureDir(fullTargetDir);
1421
- const skillTargetPath = join9(fullTargetDir, skill.name);
1633
+ const skillTargetPath = join11(fullTargetDir, skill.name);
1422
1634
  if (mode === "link") {
1423
1635
  linkDir(skill.path, skillTargetPath);
1424
1636
  } else {
@@ -1432,14 +1644,14 @@ var Deployer = class {
1432
1644
  }
1433
1645
  removeSkill(skillName, toolConfig, targetMode) {
1434
1646
  const targetDir = getTargetDir(toolConfig, targetMode);
1435
- const skillPath = join9(this.projectDir, targetDir, skillName);
1647
+ const skillPath = join11(this.projectDir, targetDir, skillName);
1436
1648
  if (existsSync3(skillPath)) {
1437
1649
  rmSync2(skillPath, { recursive: true, force: true });
1438
1650
  }
1439
1651
  }
1440
1652
  getDeployedSkillPath(skillName, toolConfig, targetMode) {
1441
1653
  const targetDir = getTargetDir(toolConfig, targetMode);
1442
- return join9(this.projectDir, targetDir, skillName);
1654
+ return join11(this.projectDir, targetDir, skillName);
1443
1655
  }
1444
1656
  isSkillDeployed(skillName, toolConfig, targetMode) {
1445
1657
  const skillPath = this.getDeployedSkillPath(skillName, toolConfig, targetMode);
@@ -1518,12 +1730,12 @@ async function executeInit(options) {
1518
1730
  `Done! Deployed ${selectedSkillNames.length} skills to ${selectedTools.length} tool${selectedTools.length > 1 ? "s" : ""}.`
1519
1731
  );
1520
1732
  }
1521
- var initCommand = new Command4("init").description("Deploy skills to current project").option("--copy", "Copy files instead of creating symlinks").action(async (options) => {
1733
+ var initCommand = new Command5("init").description("Deploy skills to current project").option("--copy", "Copy files instead of creating symlinks").action(async (options) => {
1522
1734
  await executeInit(options);
1523
1735
  });
1524
1736
 
1525
1737
  // src/commands/add.ts
1526
- import { Command as Command5 } from "commander";
1738
+ import { Command as Command6 } from "commander";
1527
1739
  async function executeAdd(skillName, options) {
1528
1740
  if (!fileExists(SKILLS_MANAGER_DIR)) {
1529
1741
  console.log("Skills manager not set up. Run: skillsmgr setup");
@@ -1579,12 +1791,12 @@ async function executeAdd(skillName, options) {
1579
1791
  );
1580
1792
  }
1581
1793
  }
1582
- var addCommand = new Command5("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) => {
1794
+ 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) => {
1583
1795
  await executeAdd(skill, options);
1584
1796
  });
1585
1797
 
1586
1798
  // src/commands/remove.ts
1587
- import { Command as Command6 } from "commander";
1799
+ import { Command as Command7 } from "commander";
1588
1800
  async function executeRemove(skillName, options) {
1589
1801
  const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1590
1802
  const deployer = new Deployer(process.cwd());
@@ -1621,13 +1833,13 @@ async function executeRemove(skillName, options) {
1621
1833
  console.log(`Skill '${skillName}' not found in any configured tool`);
1622
1834
  }
1623
1835
  }
1624
- var removeCommand = new Command6("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) => {
1836
+ 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) => {
1625
1837
  await executeRemove(skill, options);
1626
1838
  });
1627
1839
 
1628
1840
  // src/commands/sync.ts
1629
- import { Command as Command7 } from "commander";
1630
- import { join as join10 } from "path";
1841
+ import { Command as Command8 } from "commander";
1842
+ import { join as join12 } from "path";
1631
1843
  async function executeSync() {
1632
1844
  const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1633
1845
  const deployer = new Deployer(process.cwd());
@@ -1645,6 +1857,10 @@ async function executeSync() {
1645
1857
  const mode = deployment.mode || "all";
1646
1858
  console.log(`${config.displayName} (${deployment.targetDir}/):`);
1647
1859
  for (const skill of deployment.skills) {
1860
+ if (skill.conflict) {
1861
+ console.log(` \u26A0 ${skill.name}: conflict (skipped)`);
1862
+ continue;
1863
+ }
1648
1864
  const deployedPath = skill.path;
1649
1865
  let sourcePath = null;
1650
1866
  if (skill.source !== "unknown") {
@@ -1668,8 +1884,8 @@ async function executeSync() {
1668
1884
  continue;
1669
1885
  }
1670
1886
  if (skill.deployMode === "copy") {
1671
- const sourceSkillMd = join10(sourcePath, "SKILL.md");
1672
- const deployedSkillMd = join10(deployedPath, "SKILL.md");
1887
+ const sourceSkillMd = join12(sourcePath, "SKILL.md");
1888
+ const deployedSkillMd = join12(deployedPath, "SKILL.md");
1673
1889
  if (fileExists(sourceSkillMd) && fileExists(deployedSkillMd)) {
1674
1890
  const sourceContent = readFileContent(sourceSkillMd);
1675
1891
  const deployedContent = readFileContent(deployedSkillMd);
@@ -1715,15 +1931,16 @@ async function executeSync() {
1715
1931
  `Sync complete: ${updatedCount} updated, ${removedCount} removed`
1716
1932
  );
1717
1933
  }
1718
- var syncCommand = new Command7("sync").description("Sync and verify deployed skills").action(async () => {
1934
+ var syncCommand = new Command8("sync").description("Sync and verify deployed skills").action(async () => {
1719
1935
  await executeSync();
1720
1936
  });
1721
1937
 
1722
1938
  // src/index.ts
1723
- var program = new Command8();
1939
+ var program = new Command9();
1724
1940
  program.name("skillsmgr").description("Unified skills manager for AI coding tools").version("0.1.0");
1725
1941
  program.addCommand(setupCommand);
1726
1942
  program.addCommand(installCommand);
1943
+ program.addCommand(updateCommand);
1727
1944
  program.addCommand(listCommand);
1728
1945
  program.addCommand(initCommand);
1729
1946
  program.addCommand(addCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillsmgr",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Unified skills manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {