skillsmgr 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Unified skills manager for AI coding tools. Manage skills in `~/.skills-manager/` and deploy them to multiple AI tools.
4
4
 
5
+ [中文文档](./README.zh-CN.md)
6
+
5
7
  ## Supported Tools
6
8
 
7
9
  | Tool | Skills Directory | Mode-Specific |
@@ -12,84 +14,73 @@ Unified skills manager for AI coding tools. Manage skills in `~/.skills-manager/
12
14
  | Cline | `.cline/skills/` | No |
13
15
  | Roo Code | `.roo/skills/` | Yes |
14
16
  | Kilo Code | `.kilocode/skills/` | Yes |
17
+ | OpenCode | `.opencode/skills/` | No |
18
+ | Trae | `.trae/skills/` | No |
15
19
  | Antigravity | `.agent/skills/` | No |
16
20
 
17
- ## Installation
18
-
19
- ```bash
20
- npm install -g skillsmgr
21
- ```
22
-
23
- Or use directly with npx (no installation required):
24
-
25
- ```bash
26
- npx skillsmgr setup
27
- npx skillsmgr install anthropic
28
- ```
29
-
30
21
  ## Quick Start
31
22
 
32
23
  ```bash
33
24
  # Initialize skills manager
34
- skillsmgr setup
25
+ npx skillsmgr setup
35
26
 
36
27
  # Install official Anthropic skills
37
- skillsmgr install anthropic
28
+ npx skillsmgr install anthropic
38
29
 
39
30
  # Deploy skills to your project
40
31
  cd your-project
41
- skillsmgr init
32
+ npx skillsmgr init
42
33
  ```
43
34
 
44
35
  ## Commands
45
36
 
46
- ### `skillsmgr setup`
37
+ ### `npx skillsmgr setup`
47
38
 
48
39
  Initialize `~/.skills-manager/` directory structure with example skill.
49
40
 
50
41
  ```bash
51
- skillsmgr setup
42
+ npx skillsmgr setup
52
43
  ```
53
44
 
54
- ### `skillsmgr install <source>`
45
+ ### `npx skillsmgr install <source>`
55
46
 
56
47
  Download skills from a repository.
57
48
 
58
49
  ```bash
59
50
  # Install official Anthropic skills
60
- skillsmgr install anthropic
51
+ npx skillsmgr install anthropic
61
52
 
62
53
  # Install from any GitHub repository
63
- skillsmgr install https://github.com/user/skills-repo
54
+ npx skillsmgr install https://github.com/user/skills-repo
64
55
 
65
56
  # Install specific skill
66
- skillsmgr install https://github.com/anthropics/skills/tree/main/skills/code-review
57
+ npx skillsmgr install https://github.com/anthropics/skills/tree/main/skills/code-review
67
58
 
68
59
  # Install all skills without prompting
69
- skillsmgr install anthropic --all
60
+ npx skillsmgr install anthropic --all
70
61
 
71
62
  # Install to custom/ instead of community/
72
- skillsmgr install https://github.com/user/repo --custom
63
+ npx skillsmgr install https://github.com/user/repo --custom
73
64
  ```
74
65
 
75
- ### `skillsmgr list`
66
+ ### `npx skillsmgr list`
76
67
 
77
68
  List available skills.
78
69
 
79
70
  ```bash
80
71
  # List all available skills
81
- skillsmgr list
72
+ npx skillsmgr list
82
73
 
83
74
  # List deployed skills in current project
84
- skillsmgr list --deployed
75
+ npx skillsmgr list --deployed
85
76
  ```
86
77
 
87
- ### `skillsmgr init`
78
+ ### `npx skillsmgr init`
88
79
 
89
80
  Interactive deployment of skills to current project.
90
81
 
91
82
  ```bash
92
- skillsmgr init
83
+ npx skillsmgr init
93
84
  ```
94
85
 
95
86
  Features:
@@ -98,39 +89,39 @@ Features:
98
89
  - Choose skills to deploy with search filter
99
90
  - Incremental updates (add/remove skills)
100
91
 
101
- ### `skillsmgr add <skill>`
92
+ ### `npx skillsmgr add <skill>`
102
93
 
103
94
  Quick add a skill to project.
104
95
 
105
96
  ```bash
106
97
  # Add to all configured tools
107
- skillsmgr add code-review
98
+ npx skillsmgr add code-review
108
99
 
109
100
  # Add to specific tool
110
- skillsmgr add code-review --tool claude-code
101
+ npx skillsmgr add code-review --tool claude-code
111
102
 
112
103
  # Use copy mode instead of symlink
113
- skillsmgr add code-review --copy
104
+ npx skillsmgr add code-review --copy
114
105
  ```
115
106
 
116
- ### `skillsmgr remove <skill>`
107
+ ### `npx skillsmgr remove <skill>`
117
108
 
118
109
  Remove a skill from project.
119
110
 
120
111
  ```bash
121
112
  # Remove from all tools
122
- skillsmgr remove code-review
113
+ npx skillsmgr remove code-review
123
114
 
124
115
  # Remove from specific tool
125
- skillsmgr remove code-review --tool claude-code
116
+ npx skillsmgr remove code-review --tool claude-code
126
117
  ```
127
118
 
128
- ### `skillsmgr sync`
119
+ ### `npx skillsmgr sync`
129
120
 
130
121
  Sync and verify deployed skills.
131
122
 
132
123
  ```bash
133
- skillsmgr sync
124
+ npx skillsmgr sync
134
125
  ```
135
126
 
136
127
  ## Directory Structure
@@ -151,7 +142,7 @@ skillsmgr sync
151
142
  ## Features
152
143
 
153
144
  - **Unified Management**: Manage all skills in one place
154
- - **Multi-tool Support**: Deploy to 7 different AI tools
145
+ - **Multi-tool Support**: Deploy to 9 different AI tools
155
146
  - **Symlink by Default**: Changes sync automatically
156
147
  - **Search Filter**: Quick search for large skill repositories
157
148
  - **Progress Indicators**: Visual feedback during downloads
@@ -0,0 +1,153 @@
1
+ # skillsmgr
2
+
3
+ AI 编码工具的统一 Skills 管理器。在 `~/.skills-manager/` 中管理 skills,并部署到多个 AI 工具。
4
+
5
+ [English](./README.md)
6
+
7
+ ## 支持的工具
8
+
9
+ | 工具 | Skills 目录 | 支持模式特定 |
10
+ |------|------------|-------------|
11
+ | Claude Code | `.claude/skills/` | 否 |
12
+ | Cursor | `.cursor/skills/` | 否 |
13
+ | Windsurf | `.windsurf/skills/` | 否 |
14
+ | Cline | `.cline/skills/` | 否 |
15
+ | Roo Code | `.roo/skills/` | 是 |
16
+ | Kilo Code | `.kilocode/skills/` | 是 |
17
+ | OpenCode | `.opencode/skills/` | 否 |
18
+ | Trae | `.trae/skills/` | 否 |
19
+ | Antigravity | `.agent/skills/` | 否 |
20
+
21
+ ## 快速开始
22
+
23
+ ```bash
24
+ # 初始化 skills 管理器
25
+ npx skillsmgr setup
26
+
27
+ # 安装官方 Anthropic skills
28
+ npx skillsmgr install anthropic
29
+
30
+ # 部署 skills 到你的项目
31
+ cd your-project
32
+ npx skillsmgr init
33
+ ```
34
+
35
+ ## 命令
36
+
37
+ ### `npx skillsmgr setup`
38
+
39
+ 初始化 `~/.skills-manager/` 目录结构,包含示例 skill。
40
+
41
+ ```bash
42
+ npx skillsmgr setup
43
+ ```
44
+
45
+ ### `npx skillsmgr install <source>`
46
+
47
+ 从仓库下载 skills。
48
+
49
+ ```bash
50
+ # 安装官方 Anthropic skills
51
+ npx skillsmgr install anthropic
52
+
53
+ # 从任意 GitHub 仓库安装
54
+ npx skillsmgr install https://github.com/user/skills-repo
55
+
56
+ # 安装特定 skill
57
+ npx skillsmgr install https://github.com/anthropics/skills/tree/main/skills/code-review
58
+
59
+ # 安装所有 skills(无需确认)
60
+ npx skillsmgr install anthropic --all
61
+
62
+ # 安装到 custom/ 而非 community/
63
+ npx skillsmgr install https://github.com/user/repo --custom
64
+ ```
65
+
66
+ ### `npx skillsmgr list`
67
+
68
+ 列出可用的 skills。
69
+
70
+ ```bash
71
+ # 列出所有可用 skills
72
+ npx skillsmgr list
73
+
74
+ # 列出当前项目已部署的 skills
75
+ npx skillsmgr list --deployed
76
+ ```
77
+
78
+ ### `npx skillsmgr init`
79
+
80
+ 交互式部署 skills 到当前项目。
81
+
82
+ ```bash
83
+ npx skillsmgr init
84
+ ```
85
+
86
+ 功能:
87
+ - 选择目标工具(Claude Code、Cursor 等)
88
+ - 为 Roo Code / Kilo Code 选择模式
89
+ - 通过搜索过滤选择要部署的 skills
90
+ - 增量更新(添加/移除 skills)
91
+
92
+ ### `npx skillsmgr add <skill>`
93
+
94
+ 快速添加 skill 到项目。
95
+
96
+ ```bash
97
+ # 添加到所有已配置的工具
98
+ npx skillsmgr add code-review
99
+
100
+ # 添加到特定工具
101
+ npx skillsmgr add code-review --tool claude-code
102
+
103
+ # 使用复制模式而非符号链接
104
+ npx skillsmgr add code-review --copy
105
+ ```
106
+
107
+ ### `npx skillsmgr remove <skill>`
108
+
109
+ 从项目移除 skill。
110
+
111
+ ```bash
112
+ # 从所有工具移除
113
+ npx skillsmgr remove code-review
114
+
115
+ # 从特定工具移除
116
+ npx skillsmgr remove code-review --tool claude-code
117
+ ```
118
+
119
+ ### `npx skillsmgr sync`
120
+
121
+ 同步并验证已部署的 skills。
122
+
123
+ ```bash
124
+ npx skillsmgr sync
125
+ ```
126
+
127
+ ## 目录结构
128
+
129
+ ```
130
+ ~/.skills-manager/
131
+ ├── official/ # 官方 skills (anthropic/skills)
132
+ │ └── anthropic/
133
+ │ ├── code-review/
134
+ │ └── tdd/
135
+ ├── community/ # 社区 skills (其他仓库)
136
+ │ └── awesome-skills/
137
+ │ └── react-patterns/
138
+ └── custom/ # 本地自定义 skills
139
+ └── my-skill/
140
+ ```
141
+
142
+ ## 特性
143
+
144
+ - **统一管理**:在一处管理所有 skills
145
+ - **多工具支持**:部署到 9 种不同的 AI 工具
146
+ - **默认符号链接**:修改自动同步
147
+ - **搜索过滤**:快速搜索大型 skill 仓库
148
+ - **进度指示**:下载时显示可视化反馈
149
+ - **增量更新**:无需完全重新部署即可添加/移除 skills
150
+
151
+ ## 许可证
152
+
153
+ MIT
package/dist/index.js CHANGED
@@ -13,7 +13,6 @@ import { dirname as dirname2 } from "path";
13
13
  import { homedir } from "os";
14
14
  import { join } from "path";
15
15
  var SKILLS_MANAGER_DIR = join(homedir(), ".skills-manager");
16
- var METADATA_FILENAME = ".skillsmgr.json";
17
16
  var SKILL_SOURCES = ["official", "community", "custom"];
18
17
  var SUPPORTED_TOOLS = [
19
18
  "antigravity",
@@ -39,7 +38,8 @@ import {
39
38
  readdirSync,
40
39
  unlinkSync,
41
40
  writeFileSync,
42
- rmSync
41
+ rmSync,
42
+ readlinkSync
43
43
  } from "fs";
44
44
  import { dirname, join as join2 } from "path";
45
45
  function ensureDir(dir) {
@@ -58,13 +58,16 @@ function isSymlink(path) {
58
58
  return false;
59
59
  }
60
60
  }
61
+ function readSymlinkTarget(path) {
62
+ try {
63
+ return readlinkSync(path);
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
61
68
  function readFileContent(path) {
62
69
  return readFileSync(path, "utf-8");
63
70
  }
64
- function writeFile(path, content) {
65
- ensureDir(dirname(path));
66
- writeFileSync(path, content, "utf-8");
67
- }
68
71
  function fileExists(path) {
69
72
  return existsSync(path);
70
73
  }
@@ -1199,61 +1202,148 @@ var SkillsService = class {
1199
1202
  }
1200
1203
  };
1201
1204
 
1202
- // src/services/metadata.ts
1205
+ // src/services/scanner.ts
1203
1206
  import { join as join8 } from "path";
1204
- var MetadataService = class {
1205
- constructor(projectDir) {
1207
+ import { readdirSync as readdirSync2 } from "fs";
1208
+ var DeploymentScanner = class {
1209
+ constructor(projectDir, skillsManagerDir = SKILLS_MANAGER_DIR) {
1206
1210
  this.projectDir = projectDir;
1207
- this.metadataPath = join8(projectDir, METADATA_FILENAME);
1211
+ this.skillsManagerDir = skillsManagerDir;
1212
+ this.skillsService = new SkillsService(this.skillsManagerDir);
1213
+ }
1214
+ skillsService;
1215
+ scanAllTools() {
1216
+ const deployments = [];
1217
+ for (const toolName of SUPPORTED_TOOLS) {
1218
+ const config = TOOL_CONFIGS[toolName];
1219
+ const toolDeployments = this.scanToolDeployment(toolName, config);
1220
+ deployments.push(...toolDeployments);
1221
+ }
1222
+ return deployments.filter((d) => d.skills.length > 0);
1223
+ }
1224
+ scanToolDeployment(toolName, config) {
1225
+ const deployments = [];
1226
+ const baseDir = join8(this.projectDir, config.skillsDir);
1227
+ const baseDeployment = this.scanDirectory(toolName, baseDir, config.skillsDir);
1228
+ if (baseDeployment.skills.length > 0) {
1229
+ deployments.push(baseDeployment);
1230
+ }
1231
+ if (config.supportsModeSpecific && config.availableModes) {
1232
+ for (const mode of config.availableModes) {
1233
+ const modeDir = getTargetDir(config, mode);
1234
+ const fullModeDir = join8(this.projectDir, modeDir);
1235
+ const modeDeployment = this.scanDirectory(toolName, fullModeDir, modeDir, mode);
1236
+ if (modeDeployment.skills.length > 0) {
1237
+ deployments.push(modeDeployment);
1238
+ }
1239
+ }
1240
+ }
1241
+ return deployments;
1208
1242
  }
1209
- metadataPath;
1210
- load() {
1211
- if (!fileExists(this.metadataPath)) {
1212
- return { version: "1.0", tools: {} };
1243
+ getConfiguredTools() {
1244
+ const tools = /* @__PURE__ */ new Set();
1245
+ for (const toolName of SUPPORTED_TOOLS) {
1246
+ const config = TOOL_CONFIGS[toolName];
1247
+ const deployments = this.scanToolDeployment(toolName, config);
1248
+ if (deployments.some((d) => d.skills.length > 0)) {
1249
+ tools.add(toolName);
1250
+ }
1213
1251
  }
1214
- const content = readFileContent(this.metadataPath);
1215
- return JSON.parse(content);
1252
+ return Array.from(tools);
1216
1253
  }
1217
- save(metadata) {
1218
- writeFile(this.metadataPath, JSON.stringify(metadata, null, 2));
1254
+ isToolConfigured(toolName) {
1255
+ const config = TOOL_CONFIGS[toolName];
1256
+ if (!config) return false;
1257
+ const deployments = this.scanToolDeployment(toolName, config);
1258
+ return deployments.some((d) => d.skills.length > 0);
1219
1259
  }
1220
- addDeployment(toolName, targetDir, mode, skills) {
1221
- const metadata = this.load();
1222
- metadata.tools[toolName] = {
1223
- targetDir,
1260
+ getDeployedSkills(toolName) {
1261
+ const config = TOOL_CONFIGS[toolName];
1262
+ if (!config) return [];
1263
+ const deployments = this.scanToolDeployment(toolName, config);
1264
+ return deployments.flatMap((d) => d.skills);
1265
+ }
1266
+ scanDirectory(toolName, fullPath, relativePath, mode) {
1267
+ const deployment = {
1268
+ toolName,
1269
+ targetDir: relativePath,
1224
1270
  mode,
1225
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
1226
- skills
1271
+ skills: []
1227
1272
  };
1228
- this.save(metadata);
1229
- }
1230
- updateDeployment(toolName, skills) {
1231
- const metadata = this.load();
1232
- if (metadata.tools[toolName]) {
1233
- metadata.tools[toolName].skills = skills;
1234
- metadata.tools[toolName].deployedAt = (/* @__PURE__ */ new Date()).toISOString();
1273
+ if (!fileExists(fullPath)) {
1274
+ return deployment;
1235
1275
  }
1236
- this.save(metadata);
1276
+ try {
1277
+ const entries = readdirSync2(fullPath, { withFileTypes: true });
1278
+ for (const entry of entries) {
1279
+ const skillPath = join8(fullPath, entry.name);
1280
+ const scanned = this.scanSkill(skillPath, entry.name);
1281
+ if (scanned) {
1282
+ deployment.skills.push(scanned);
1283
+ }
1284
+ }
1285
+ } catch {
1286
+ }
1287
+ return deployment;
1237
1288
  }
1238
- removeDeployment(toolName) {
1239
- const metadata = this.load();
1240
- delete metadata.tools[toolName];
1241
- this.save(metadata);
1289
+ scanSkill(skillPath, name) {
1290
+ const skillMdPath = join8(skillPath, "SKILL.md");
1291
+ if (!fileExists(skillMdPath)) {
1292
+ return null;
1293
+ }
1294
+ if (isSymlink(skillPath)) {
1295
+ return this.scanLinkedSkill(skillPath, name);
1296
+ } else {
1297
+ return this.scanCopiedSkill(skillPath, name);
1298
+ }
1242
1299
  }
1243
- getDeployedSkills(toolName) {
1244
- const metadata = this.load();
1245
- return metadata.tools[toolName]?.skills || [];
1300
+ scanLinkedSkill(skillPath, name) {
1301
+ const linkTarget = readSymlinkTarget(skillPath);
1302
+ if (!linkTarget) {
1303
+ return null;
1304
+ }
1305
+ const source = this.extractSourceFromPath(linkTarget);
1306
+ return {
1307
+ name,
1308
+ source: source || "unknown",
1309
+ deployMode: "link",
1310
+ path: skillPath
1311
+ };
1246
1312
  }
1247
- getToolDeployment(toolName) {
1248
- const metadata = this.load();
1249
- return metadata.tools[toolName];
1313
+ scanCopiedSkill(skillPath, name) {
1314
+ const { source, conflict } = this.findSourceByName(name);
1315
+ return {
1316
+ name,
1317
+ source: source || "unknown",
1318
+ deployMode: "copy",
1319
+ path: skillPath,
1320
+ conflict
1321
+ };
1250
1322
  }
1251
- getConfiguredTools() {
1252
- const metadata = this.load();
1253
- return Object.keys(metadata.tools);
1323
+ extractSourceFromPath(linkTarget) {
1324
+ const normalizedTarget = linkTarget.replace(/\\/g, "/");
1325
+ const skillsManagerPattern = ".skills-manager/";
1326
+ const idx = normalizedTarget.indexOf(skillsManagerPattern);
1327
+ if (idx === -1) return null;
1328
+ const afterManager = normalizedTarget.substring(idx + skillsManagerPattern.length);
1329
+ const parts = afterManager.split("/");
1330
+ if (parts[0] === "custom") {
1331
+ return "custom";
1332
+ }
1333
+ if (parts.length >= 2) {
1334
+ return `${parts[0]}/${parts[1]}`;
1335
+ }
1336
+ return null;
1254
1337
  }
1255
- hasMetadata() {
1256
- return fileExists(this.metadataPath);
1338
+ findSourceByName(skillName) {
1339
+ const matches = this.skillsService.findSkillsByName(skillName);
1340
+ if (matches.length === 0) {
1341
+ return { source: null, conflict: false };
1342
+ }
1343
+ if (matches.length > 1) {
1344
+ return { source: null, conflict: true };
1345
+ }
1346
+ return { source: matches[0].source, conflict: false };
1257
1347
  }
1258
1348
  };
1259
1349
 
@@ -1288,29 +1378,32 @@ async function listAvailable() {
1288
1378
  for (const [source, sourceSkills] of Object.entries(grouped)) {
1289
1379
  console.log(`\u2500\u2500 ${source} (${sourceSkills.length} skill${sourceSkills.length > 1 ? "s" : ""}) \u2500\u2500`);
1290
1380
  for (const skill of sourceSkills) {
1291
- console.log(` ${skill.name.padEnd(20)} ${skill.description}`);
1381
+ console.log(` ${skill.name}`);
1292
1382
  }
1293
1383
  console.log();
1294
1384
  }
1295
1385
  }
1296
1386
  async function listDeployed() {
1297
- const metadataService = new MetadataService(process.cwd());
1298
- if (!metadataService.hasMetadata()) {
1387
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1388
+ const deployments = scanner.scanAllTools();
1389
+ if (deployments.length === 0) {
1299
1390
  console.log("No skills deployed in current project.");
1300
1391
  console.log("\nRun: skillsmgr init");
1301
1392
  return;
1302
1393
  }
1303
1394
  console.log("Deployed skills in current project:\n");
1304
- const configuredTools = metadataService.getConfiguredTools();
1305
- for (const toolName of configuredTools) {
1306
- const deployment = metadataService.getToolDeployment(toolName);
1307
- if (!deployment) continue;
1308
- const config = TOOL_CONFIGS[toolName];
1309
- const displayName = config?.displayName || toolName;
1310
- console.log(`${displayName} (${deployment.targetDir}/):`);
1395
+ for (const deployment of deployments) {
1396
+ const config = TOOL_CONFIGS[deployment.toolName];
1397
+ const displayName = config?.displayName || deployment.toolName;
1398
+ const dirSuffix = deployment.mode && deployment.mode !== "all" ? ` [${deployment.mode}]` : "";
1399
+ console.log(`${displayName} (${deployment.targetDir}/)${dirSuffix}:`);
1311
1400
  for (const skill of deployment.skills) {
1312
1401
  const modeStr = skill.deployMode === "link" ? "link" : "copy";
1313
- console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
1402
+ if (skill.conflict) {
1403
+ console.log(` \u26A0 ${skill.name.padEnd(16)} (${modeStr}) \u2190 conflict`);
1404
+ } else {
1405
+ console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
1406
+ }
1314
1407
  }
1315
1408
  console.log();
1316
1409
  }
@@ -1369,14 +1462,14 @@ async function executeInit(options) {
1369
1462
  process.exit(1);
1370
1463
  }
1371
1464
  const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1372
- const metadataService = new MetadataService(process.cwd());
1465
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1373
1466
  const deployer = new Deployer(process.cwd());
1374
1467
  const allSkills = skillsService.getAllSkills();
1375
1468
  if (allSkills.length === 0) {
1376
1469
  console.log("No skills found. Run: skillsmgr install anthropic");
1377
1470
  process.exit(1);
1378
1471
  }
1379
- const configuredTools = metadataService.getConfiguredTools();
1472
+ const configuredTools = scanner.getConfiguredTools();
1380
1473
  const selectedTools = await promptTools(configuredTools);
1381
1474
  const toolModes = {};
1382
1475
  for (const toolName of selectedTools) {
@@ -1390,7 +1483,7 @@ async function executeInit(options) {
1390
1483
  }
1391
1484
  const deployedSkillNames = /* @__PURE__ */ new Set();
1392
1485
  for (const toolName of selectedTools) {
1393
- const deployed = metadataService.getDeployedSkills(toolName);
1486
+ const deployed = scanner.getDeployedSkills(toolName);
1394
1487
  deployed.forEach((s) => deployedSkillNames.add(s.name));
1395
1488
  }
1396
1489
  const selectedSkillNames = await promptSkills(
@@ -1409,7 +1502,7 @@ async function executeInit(options) {
1409
1502
  const mode = toolModes[toolName];
1410
1503
  const targetDir = getTargetDir(config, mode);
1411
1504
  console.log(`${config.displayName}:`);
1412
- const previouslyDeployed = metadataService.getDeployedSkills(toolName);
1505
+ const previouslyDeployed = scanner.getDeployedSkills(toolName);
1413
1506
  const previousNames = new Set(previouslyDeployed.map((s) => s.name));
1414
1507
  const toAdd = selectedSkills.filter((s) => !previousNames.has(s.name));
1415
1508
  const toKeep = selectedSkills.filter((s) => previousNames.has(s.name));
@@ -1427,12 +1520,6 @@ async function executeInit(options) {
1427
1520
  deployer.deploySkill(skill, config, deployMode, mode);
1428
1521
  console.log(` \u2713 ${skill.name} (${deployMode === "link" ? "linked" : "copied"})`);
1429
1522
  }
1430
- const newDeployedSkills = selectedSkills.map((skill) => ({
1431
- name: skill.name,
1432
- source: skill.source,
1433
- deployMode
1434
- }));
1435
- metadataService.addDeployment(toolName, targetDir, mode, newDeployedSkills);
1436
1523
  console.log();
1437
1524
  }
1438
1525
  console.log(
@@ -1451,7 +1538,7 @@ async function executeAdd(skillName, options) {
1451
1538
  process.exit(1);
1452
1539
  }
1453
1540
  const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1454
- const metadataService = new MetadataService(process.cwd());
1541
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1455
1542
  const deployer = new Deployer(process.cwd());
1456
1543
  const matchingSkills = skillsService.findSkillsByName(skillName);
1457
1544
  if (matchingSkills.length === 0) {
@@ -1476,7 +1563,7 @@ async function executeAdd(skillName, options) {
1476
1563
  }
1477
1564
  targetTools = [options.tool];
1478
1565
  } else {
1479
- targetTools = metadataService.getConfiguredTools();
1566
+ targetTools = scanner.getConfiguredTools();
1480
1567
  if (targetTools.length === 0) {
1481
1568
  console.log("No tools configured. Run: skillsmgr init");
1482
1569
  process.exit(1);
@@ -1486,19 +1573,15 @@ async function executeAdd(skillName, options) {
1486
1573
  console.log(`Adding ${skillName} to configured tools...`);
1487
1574
  for (const toolName of targetTools) {
1488
1575
  const config = TOOL_CONFIGS[toolName];
1489
- const deployment = metadataService.getToolDeployment(toolName);
1490
- const mode = deployment?.mode || "all";
1491
- deployer.deploySkill(skill, config, deployMode, mode);
1492
- const existingSkills = metadataService.getDeployedSkills(toolName);
1576
+ const deployments = scanner.scanToolDeployment(toolName, config);
1577
+ const mode = deployments.length > 0 && deployments[0].mode ? deployments[0].mode : "all";
1578
+ const existingSkills = scanner.getDeployedSkills(toolName);
1493
1579
  const alreadyExists = existingSkills.some((s) => s.name === skill.name);
1494
- if (!alreadyExists) {
1495
- const newSkill = {
1496
- name: skill.name,
1497
- source: skill.source,
1498
- deployMode
1499
- };
1500
- metadataService.updateDeployment(toolName, [...existingSkills, newSkill]);
1580
+ if (alreadyExists) {
1581
+ console.log(` \xB7 ${config.displayName} (already deployed)`);
1582
+ continue;
1501
1583
  }
1584
+ deployer.deploySkill(skill, config, deployMode, mode);
1502
1585
  console.log(
1503
1586
  ` \u2713 ${config.displayName} (${deployMode === "link" ? "linked" : "copied"})`
1504
1587
  );
@@ -1511,9 +1594,10 @@ var addCommand = new Command5("add").description("Add a skill to the project").a
1511
1594
  // src/commands/remove.ts
1512
1595
  import { Command as Command6 } from "commander";
1513
1596
  async function executeRemove(skillName, options) {
1514
- const metadataService = new MetadataService(process.cwd());
1597
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1515
1598
  const deployer = new Deployer(process.cwd());
1516
- if (!metadataService.hasMetadata()) {
1599
+ const configuredTools = scanner.getConfiguredTools();
1600
+ if (configuredTools.length === 0) {
1517
1601
  console.log("No skills deployed in current project.");
1518
1602
  process.exit(1);
1519
1603
  }
@@ -1525,22 +1609,21 @@ async function executeRemove(skillName, options) {
1525
1609
  }
1526
1610
  targetTools = [options.tool];
1527
1611
  } else {
1528
- targetTools = metadataService.getConfiguredTools();
1612
+ targetTools = configuredTools;
1529
1613
  }
1530
1614
  console.log(`Removing ${skillName}...`);
1531
1615
  let removed = false;
1532
1616
  for (const toolName of targetTools) {
1533
1617
  const config = TOOL_CONFIGS[toolName];
1534
- const deployment = metadataService.getToolDeployment(toolName);
1535
- if (!deployment) continue;
1536
- const existingSkills = deployment.skills;
1537
- const skillToRemove = existingSkills.find((s) => s.name === skillName);
1538
- if (!skillToRemove) continue;
1539
- deployer.removeSkill(skillName, config, deployment.mode);
1540
- const remainingSkills = existingSkills.filter((s) => s.name !== skillName);
1541
- metadataService.updateDeployment(toolName, remainingSkills);
1542
- console.log(` \u2713 Removed from ${config.displayName}`);
1543
- removed = true;
1618
+ const deployments = scanner.scanToolDeployment(toolName, config);
1619
+ for (const deployment of deployments) {
1620
+ const skillToRemove = deployment.skills.find((s) => s.name === skillName);
1621
+ if (!skillToRemove) continue;
1622
+ const mode = deployment.mode || "all";
1623
+ deployer.removeSkill(skillName, config, mode);
1624
+ console.log(` \u2713 Removed from ${config.displayName}`);
1625
+ removed = true;
1626
+ }
1544
1627
  }
1545
1628
  if (!removed) {
1546
1629
  console.log(`Skill '${skillName}' not found in any configured tool`);
@@ -1554,32 +1637,39 @@ var removeCommand = new Command6("remove").description("Remove a skill from the
1554
1637
  import { Command as Command7 } from "commander";
1555
1638
  import { join as join10 } from "path";
1556
1639
  async function executeSync() {
1557
- const metadataService = new MetadataService(process.cwd());
1640
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1558
1641
  const deployer = new Deployer(process.cwd());
1559
- if (!metadataService.hasMetadata()) {
1642
+ const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1643
+ const deployments = scanner.scanAllTools();
1644
+ if (deployments.length === 0) {
1560
1645
  console.log("No skills deployed in current project.");
1561
1646
  process.exit(1);
1562
1647
  }
1563
1648
  console.log("Checking deployed skills...\n");
1564
- const configuredTools = metadataService.getConfiguredTools();
1565
1649
  let updatedCount = 0;
1566
1650
  let removedCount = 0;
1567
- let untrackedCount = 0;
1568
- for (const toolName of configuredTools) {
1569
- const config = TOOL_CONFIGS[toolName];
1570
- const deployment = metadataService.getToolDeployment(toolName);
1571
- if (!deployment) continue;
1651
+ for (const deployment of deployments) {
1652
+ const config = TOOL_CONFIGS[deployment.toolName];
1653
+ const mode = deployment.mode || "all";
1572
1654
  console.log(`${config.displayName} (${deployment.targetDir}/):`);
1573
1655
  for (const skill of deployment.skills) {
1574
- const deployedPath = join10(process.cwd(), deployment.targetDir, skill.name);
1575
- const sourcePath = join10(SKILLS_MANAGER_DIR, skill.source, skill.name);
1576
- if (!fileExists(sourcePath)) {
1577
- console.log(` \u2717 ${skill.name}: orphaned (source deleted)`);
1656
+ if (skill.conflict) {
1657
+ console.log(` \u26A0 ${skill.name}: conflict (skipped)`);
1658
+ continue;
1659
+ }
1660
+ const deployedPath = skill.path;
1661
+ let sourcePath = null;
1662
+ if (skill.source !== "unknown") {
1663
+ const sourceSkill = skillsService.getSkillByName(skill.name);
1664
+ if (sourceSkill) {
1665
+ sourcePath = sourceSkill.path;
1666
+ }
1667
+ }
1668
+ if (!sourcePath || !fileExists(sourcePath)) {
1669
+ console.log(` \u2717 ${skill.name}: orphaned (source not found)`);
1578
1670
  const action = await promptOrphanAction(skill.name);
1579
1671
  if (action === "remove") {
1580
- deployer.removeSkill(skill.name, config, deployment.mode);
1581
- const remaining = deployment.skills.filter((s) => s.name !== skill.name);
1582
- metadataService.updateDeployment(toolName, remaining);
1672
+ deployer.removeSkill(skill.name, config, mode);
1583
1673
  console.log(` \u2713 Removed ${skill.name}`);
1584
1674
  removedCount++;
1585
1675
  }
@@ -1610,7 +1700,7 @@ async function executeSync() {
1610
1700
  { name: skill.name, description: "", path: sourcePath, source: skill.source },
1611
1701
  config,
1612
1702
  "copy",
1613
- deployment.mode
1703
+ mode
1614
1704
  );
1615
1705
  console.log(` \u2713 Updated ${skill.name}`);
1616
1706
  updatedCount++;
@@ -1620,7 +1710,7 @@ async function executeSync() {
1620
1710
  { name: skill.name, description: "", path: sourcePath, source: skill.source },
1621
1711
  config,
1622
1712
  "copy",
1623
- deployment.mode
1713
+ mode
1624
1714
  );
1625
1715
  console.log(` \u2713 Updated ${skill.name}`);
1626
1716
  updatedCount++;
@@ -1634,7 +1724,7 @@ async function executeSync() {
1634
1724
  console.log();
1635
1725
  }
1636
1726
  console.log(
1637
- `Sync complete: ${updatedCount} updated, ${removedCount} removed, ${untrackedCount} untracked`
1727
+ `Sync complete: ${updatedCount} updated, ${removedCount} removed`
1638
1728
  );
1639
1729
  }
1640
1730
  var syncCommand = new Command7("sync").description("Sync and verify deployed skills").action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillsmgr",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Unified skills manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {