skillsmgr 0.1.0 → 0.3.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
@@ -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,77 +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
21
  ## Quick Start
24
22
 
25
23
  ```bash
26
24
  # Initialize skills manager
27
- skillsmgr setup
25
+ npx skillsmgr setup
28
26
 
29
27
  # Install official Anthropic skills
30
- skillsmgr install anthropic
28
+ npx skillsmgr install anthropic
31
29
 
32
30
  # Deploy skills to your project
33
31
  cd your-project
34
- skillsmgr init
32
+ npx skillsmgr init
35
33
  ```
36
34
 
37
35
  ## Commands
38
36
 
39
- ### `skillsmgr setup`
37
+ ### `npx skillsmgr setup`
40
38
 
41
39
  Initialize `~/.skills-manager/` directory structure with example skill.
42
40
 
43
41
  ```bash
44
- skillsmgr setup
42
+ npx skillsmgr setup
45
43
  ```
46
44
 
47
- ### `skillsmgr install <source>`
45
+ ### `npx skillsmgr install <source>`
48
46
 
49
47
  Download skills from a repository.
50
48
 
51
49
  ```bash
52
50
  # Install official Anthropic skills
53
- skillsmgr install anthropic
51
+ npx skillsmgr install anthropic
54
52
 
55
53
  # Install from any GitHub repository
56
- skillsmgr install https://github.com/user/skills-repo
54
+ npx skillsmgr install https://github.com/user/skills-repo
57
55
 
58
56
  # Install specific skill
59
- 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
60
58
 
61
59
  # Install all skills without prompting
62
- skillsmgr install anthropic --all
60
+ npx skillsmgr install anthropic --all
63
61
 
64
62
  # Install to custom/ instead of community/
65
- skillsmgr install https://github.com/user/repo --custom
63
+ npx skillsmgr install https://github.com/user/repo --custom
66
64
  ```
67
65
 
68
- ### `skillsmgr list`
66
+ ### `npx skillsmgr list`
69
67
 
70
68
  List available skills.
71
69
 
72
70
  ```bash
73
71
  # List all available skills
74
- skillsmgr list
72
+ npx skillsmgr list
75
73
 
76
74
  # List deployed skills in current project
77
- skillsmgr list --deployed
75
+ npx skillsmgr list --deployed
78
76
  ```
79
77
 
80
- ### `skillsmgr init`
78
+ ### `npx skillsmgr init`
81
79
 
82
80
  Interactive deployment of skills to current project.
83
81
 
84
82
  ```bash
85
- skillsmgr init
83
+ npx skillsmgr init
86
84
  ```
87
85
 
88
86
  Features:
@@ -91,39 +89,39 @@ Features:
91
89
  - Choose skills to deploy with search filter
92
90
  - Incremental updates (add/remove skills)
93
91
 
94
- ### `skillsmgr add <skill>`
92
+ ### `npx skillsmgr add <skill>`
95
93
 
96
94
  Quick add a skill to project.
97
95
 
98
96
  ```bash
99
97
  # Add to all configured tools
100
- skillsmgr add code-review
98
+ npx skillsmgr add code-review
101
99
 
102
100
  # Add to specific tool
103
- skillsmgr add code-review --tool claude-code
101
+ npx skillsmgr add code-review --tool claude-code
104
102
 
105
103
  # Use copy mode instead of symlink
106
- skillsmgr add code-review --copy
104
+ npx skillsmgr add code-review --copy
107
105
  ```
108
106
 
109
- ### `skillsmgr remove <skill>`
107
+ ### `npx skillsmgr remove <skill>`
110
108
 
111
109
  Remove a skill from project.
112
110
 
113
111
  ```bash
114
112
  # Remove from all tools
115
- skillsmgr remove code-review
113
+ npx skillsmgr remove code-review
116
114
 
117
115
  # Remove from specific tool
118
- skillsmgr remove code-review --tool claude-code
116
+ npx skillsmgr remove code-review --tool claude-code
119
117
  ```
120
118
 
121
- ### `skillsmgr sync`
119
+ ### `npx skillsmgr sync`
122
120
 
123
121
  Sync and verify deployed skills.
124
122
 
125
123
  ```bash
126
- skillsmgr sync
124
+ npx skillsmgr sync
127
125
  ```
128
126
 
129
127
  ## Directory Structure
@@ -144,7 +142,7 @@ skillsmgr sync
144
142
  ## Features
145
143
 
146
144
  - **Unified Management**: Manage all skills in one place
147
- - **Multi-tool Support**: Deploy to 7 different AI tools
145
+ - **Multi-tool Support**: Deploy to 9 different AI tools
148
146
  - **Symlink by Default**: Changes sync automatically
149
147
  - **Search Filter**: Quick search for large skill repositories
150
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,16 +13,17 @@ 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 = [
18
+ "antigravity",
19
+ "roo-code",
19
20
  "claude-code",
20
- "cursor",
21
- "windsurf",
21
+ "opencode",
22
22
  "cline",
23
- "roo-code",
23
+ "cursor",
24
24
  "kilo-code",
25
- "antigravity"
25
+ "trae",
26
+ "windsurf"
26
27
  ];
27
28
  var ANTHROPIC_SKILLS_REPO = "https://github.com/anthropics/skills";
28
29
 
@@ -37,7 +38,8 @@ import {
37
38
  readdirSync,
38
39
  unlinkSync,
39
40
  writeFileSync,
40
- rmSync
41
+ rmSync,
42
+ readlinkSync
41
43
  } from "fs";
42
44
  import { dirname, join as join2 } from "path";
43
45
  function ensureDir(dir) {
@@ -56,13 +58,16 @@ function isSymlink(path) {
56
58
  return false;
57
59
  }
58
60
  }
61
+ function readSymlinkTarget(path) {
62
+ try {
63
+ return readlinkSync(path);
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
59
68
  function readFileContent(path) {
60
69
  return readFileSync(path, "utf-8");
61
70
  }
62
- function writeFile(path, content) {
63
- ensureDir(dirname(path));
64
- writeFileSync(path, content, "utf-8");
65
- }
66
71
  function fileExists(path) {
67
72
  return existsSync(path);
68
73
  }
@@ -365,6 +370,22 @@ import inquirer from "inquirer";
365
370
 
366
371
  // src/tools/configs.ts
367
372
  var TOOL_CONFIGS = {
373
+ "antigravity": {
374
+ name: "antigravity",
375
+ displayName: "Antigravity",
376
+ skillsDir: ".agent/skills",
377
+ supportsLink: true,
378
+ supportsModeSpecific: false
379
+ },
380
+ "roo-code": {
381
+ name: "roo-code",
382
+ displayName: "Roo Code",
383
+ skillsDir: ".roo/skills",
384
+ supportsLink: true,
385
+ supportsModeSpecific: true,
386
+ modePattern: "skills-{mode}",
387
+ availableModes: ["code", "architect"]
388
+ },
368
389
  "claude-code": {
369
390
  name: "claude-code",
370
391
  displayName: "Claude Code",
@@ -372,17 +393,10 @@ var TOOL_CONFIGS = {
372
393
  supportsLink: true,
373
394
  supportsModeSpecific: false
374
395
  },
375
- "cursor": {
376
- name: "cursor",
377
- displayName: "Cursor",
378
- skillsDir: ".cursor/skills",
379
- supportsLink: true,
380
- supportsModeSpecific: false
381
- },
382
- "windsurf": {
383
- name: "windsurf",
384
- displayName: "Windsurf",
385
- skillsDir: ".windsurf/skills",
396
+ "opencode": {
397
+ name: "opencode",
398
+ displayName: "OpenCode",
399
+ skillsDir: ".opencode/skills",
386
400
  supportsLink: true,
387
401
  supportsModeSpecific: false
388
402
  },
@@ -393,14 +407,12 @@ var TOOL_CONFIGS = {
393
407
  supportsLink: true,
394
408
  supportsModeSpecific: false
395
409
  },
396
- "roo-code": {
397
- name: "roo-code",
398
- displayName: "Roo Code",
399
- skillsDir: ".roo/skills",
410
+ "cursor": {
411
+ name: "cursor",
412
+ displayName: "Cursor",
413
+ skillsDir: ".cursor/skills",
400
414
  supportsLink: true,
401
- supportsModeSpecific: true,
402
- modePattern: "skills-{mode}",
403
- availableModes: ["code", "architect"]
415
+ supportsModeSpecific: false
404
416
  },
405
417
  "kilo-code": {
406
418
  name: "kilo-code",
@@ -411,10 +423,17 @@ var TOOL_CONFIGS = {
411
423
  modePattern: "skills-{mode}",
412
424
  availableModes: ["code", "architect"]
413
425
  },
414
- "antigravity": {
415
- name: "antigravity",
416
- displayName: "Antigravity",
417
- skillsDir: ".agent/skills",
426
+ "trae": {
427
+ name: "trae",
428
+ displayName: "Trae",
429
+ skillsDir: ".trae/skills",
430
+ supportsLink: true,
431
+ supportsModeSpecific: false
432
+ },
433
+ "windsurf": {
434
+ name: "windsurf",
435
+ displayName: "Windsurf",
436
+ skillsDir: ".windsurf/skills",
418
437
  supportsLink: true,
419
438
  supportsModeSpecific: false
420
439
  }
@@ -1183,61 +1202,144 @@ var SkillsService = class {
1183
1202
  }
1184
1203
  };
1185
1204
 
1186
- // src/services/metadata.ts
1205
+ // src/services/scanner.ts
1187
1206
  import { join as join8 } from "path";
1188
- var MetadataService = class {
1189
- constructor(projectDir) {
1207
+ import { readdirSync as readdirSync2 } from "fs";
1208
+ var DeploymentScanner = class {
1209
+ constructor(projectDir, skillsManagerDir = SKILLS_MANAGER_DIR) {
1190
1210
  this.projectDir = projectDir;
1191
- 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;
1192
1242
  }
1193
- metadataPath;
1194
- load() {
1195
- if (!fileExists(this.metadataPath)) {
1196
- 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
+ }
1197
1251
  }
1198
- const content = readFileContent(this.metadataPath);
1199
- return JSON.parse(content);
1252
+ return Array.from(tools);
1200
1253
  }
1201
- save(metadata) {
1202
- 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);
1203
1259
  }
1204
- addDeployment(toolName, targetDir, mode, skills) {
1205
- const metadata = this.load();
1206
- metadata.tools[toolName] = {
1207
- 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,
1208
1270
  mode,
1209
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
1210
- skills
1271
+ skills: []
1211
1272
  };
1212
- this.save(metadata);
1213
- }
1214
- updateDeployment(toolName, skills) {
1215
- const metadata = this.load();
1216
- if (metadata.tools[toolName]) {
1217
- metadata.tools[toolName].skills = skills;
1218
- metadata.tools[toolName].deployedAt = (/* @__PURE__ */ new Date()).toISOString();
1273
+ if (!fileExists(fullPath)) {
1274
+ return deployment;
1219
1275
  }
1220
- 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;
1221
1288
  }
1222
- removeDeployment(toolName) {
1223
- const metadata = this.load();
1224
- delete metadata.tools[toolName];
1225
- 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
+ }
1226
1299
  }
1227
- getDeployedSkills(toolName) {
1228
- const metadata = this.load();
1229
- 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
+ };
1230
1312
  }
1231
- getToolDeployment(toolName) {
1232
- const metadata = this.load();
1233
- return metadata.tools[toolName];
1313
+ scanCopiedSkill(skillPath, name) {
1314
+ const source = this.findSourceByName(name);
1315
+ return {
1316
+ name,
1317
+ source: source || "unknown",
1318
+ deployMode: "copy",
1319
+ path: skillPath
1320
+ };
1234
1321
  }
1235
- getConfiguredTools() {
1236
- const metadata = this.load();
1237
- return Object.keys(metadata.tools);
1322
+ extractSourceFromPath(linkTarget) {
1323
+ const normalizedTarget = linkTarget.replace(/\\/g, "/");
1324
+ const skillsManagerPattern = ".skills-manager/";
1325
+ const idx = normalizedTarget.indexOf(skillsManagerPattern);
1326
+ if (idx === -1) return null;
1327
+ const afterManager = normalizedTarget.substring(idx + skillsManagerPattern.length);
1328
+ const parts = afterManager.split("/");
1329
+ if (parts[0] === "custom") {
1330
+ return "custom";
1331
+ }
1332
+ if (parts.length >= 2) {
1333
+ return `${parts[0]}/${parts[1]}`;
1334
+ }
1335
+ return null;
1238
1336
  }
1239
- hasMetadata() {
1240
- return fileExists(this.metadataPath);
1337
+ findSourceByName(skillName) {
1338
+ const matches = this.skillsService.findSkillsByName(skillName);
1339
+ if (matches.length === 0) {
1340
+ return null;
1341
+ }
1342
+ return matches[0].source;
1241
1343
  }
1242
1344
  };
1243
1345
 
@@ -1272,26 +1374,25 @@ async function listAvailable() {
1272
1374
  for (const [source, sourceSkills] of Object.entries(grouped)) {
1273
1375
  console.log(`\u2500\u2500 ${source} (${sourceSkills.length} skill${sourceSkills.length > 1 ? "s" : ""}) \u2500\u2500`);
1274
1376
  for (const skill of sourceSkills) {
1275
- console.log(` ${skill.name.padEnd(20)} ${skill.description}`);
1377
+ console.log(` ${skill.name}`);
1276
1378
  }
1277
1379
  console.log();
1278
1380
  }
1279
1381
  }
1280
1382
  async function listDeployed() {
1281
- const metadataService = new MetadataService(process.cwd());
1282
- if (!metadataService.hasMetadata()) {
1383
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1384
+ const deployments = scanner.scanAllTools();
1385
+ if (deployments.length === 0) {
1283
1386
  console.log("No skills deployed in current project.");
1284
1387
  console.log("\nRun: skillsmgr init");
1285
1388
  return;
1286
1389
  }
1287
1390
  console.log("Deployed skills in current project:\n");
1288
- const configuredTools = metadataService.getConfiguredTools();
1289
- for (const toolName of configuredTools) {
1290
- const deployment = metadataService.getToolDeployment(toolName);
1291
- if (!deployment) continue;
1292
- const config = TOOL_CONFIGS[toolName];
1293
- const displayName = config?.displayName || toolName;
1294
- console.log(`${displayName} (${deployment.targetDir}/):`);
1391
+ for (const deployment of deployments) {
1392
+ const config = TOOL_CONFIGS[deployment.toolName];
1393
+ const displayName = config?.displayName || deployment.toolName;
1394
+ const dirSuffix = deployment.mode && deployment.mode !== "all" ? ` [${deployment.mode}]` : "";
1395
+ console.log(`${displayName} (${deployment.targetDir}/)${dirSuffix}:`);
1295
1396
  for (const skill of deployment.skills) {
1296
1397
  const modeStr = skill.deployMode === "link" ? "link" : "copy";
1297
1398
  console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
@@ -1353,14 +1454,14 @@ async function executeInit(options) {
1353
1454
  process.exit(1);
1354
1455
  }
1355
1456
  const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1356
- const metadataService = new MetadataService(process.cwd());
1457
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1357
1458
  const deployer = new Deployer(process.cwd());
1358
1459
  const allSkills = skillsService.getAllSkills();
1359
1460
  if (allSkills.length === 0) {
1360
1461
  console.log("No skills found. Run: skillsmgr install anthropic");
1361
1462
  process.exit(1);
1362
1463
  }
1363
- const configuredTools = metadataService.getConfiguredTools();
1464
+ const configuredTools = scanner.getConfiguredTools();
1364
1465
  const selectedTools = await promptTools(configuredTools);
1365
1466
  const toolModes = {};
1366
1467
  for (const toolName of selectedTools) {
@@ -1374,7 +1475,7 @@ async function executeInit(options) {
1374
1475
  }
1375
1476
  const deployedSkillNames = /* @__PURE__ */ new Set();
1376
1477
  for (const toolName of selectedTools) {
1377
- const deployed = metadataService.getDeployedSkills(toolName);
1478
+ const deployed = scanner.getDeployedSkills(toolName);
1378
1479
  deployed.forEach((s) => deployedSkillNames.add(s.name));
1379
1480
  }
1380
1481
  const selectedSkillNames = await promptSkills(
@@ -1393,7 +1494,7 @@ async function executeInit(options) {
1393
1494
  const mode = toolModes[toolName];
1394
1495
  const targetDir = getTargetDir(config, mode);
1395
1496
  console.log(`${config.displayName}:`);
1396
- const previouslyDeployed = metadataService.getDeployedSkills(toolName);
1497
+ const previouslyDeployed = scanner.getDeployedSkills(toolName);
1397
1498
  const previousNames = new Set(previouslyDeployed.map((s) => s.name));
1398
1499
  const toAdd = selectedSkills.filter((s) => !previousNames.has(s.name));
1399
1500
  const toKeep = selectedSkills.filter((s) => previousNames.has(s.name));
@@ -1411,12 +1512,6 @@ async function executeInit(options) {
1411
1512
  deployer.deploySkill(skill, config, deployMode, mode);
1412
1513
  console.log(` \u2713 ${skill.name} (${deployMode === "link" ? "linked" : "copied"})`);
1413
1514
  }
1414
- const newDeployedSkills = selectedSkills.map((skill) => ({
1415
- name: skill.name,
1416
- source: skill.source,
1417
- deployMode
1418
- }));
1419
- metadataService.addDeployment(toolName, targetDir, mode, newDeployedSkills);
1420
1515
  console.log();
1421
1516
  }
1422
1517
  console.log(
@@ -1435,7 +1530,7 @@ async function executeAdd(skillName, options) {
1435
1530
  process.exit(1);
1436
1531
  }
1437
1532
  const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1438
- const metadataService = new MetadataService(process.cwd());
1533
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1439
1534
  const deployer = new Deployer(process.cwd());
1440
1535
  const matchingSkills = skillsService.findSkillsByName(skillName);
1441
1536
  if (matchingSkills.length === 0) {
@@ -1460,7 +1555,7 @@ async function executeAdd(skillName, options) {
1460
1555
  }
1461
1556
  targetTools = [options.tool];
1462
1557
  } else {
1463
- targetTools = metadataService.getConfiguredTools();
1558
+ targetTools = scanner.getConfiguredTools();
1464
1559
  if (targetTools.length === 0) {
1465
1560
  console.log("No tools configured. Run: skillsmgr init");
1466
1561
  process.exit(1);
@@ -1470,19 +1565,15 @@ async function executeAdd(skillName, options) {
1470
1565
  console.log(`Adding ${skillName} to configured tools...`);
1471
1566
  for (const toolName of targetTools) {
1472
1567
  const config = TOOL_CONFIGS[toolName];
1473
- const deployment = metadataService.getToolDeployment(toolName);
1474
- const mode = deployment?.mode || "all";
1475
- deployer.deploySkill(skill, config, deployMode, mode);
1476
- const existingSkills = metadataService.getDeployedSkills(toolName);
1568
+ const deployments = scanner.scanToolDeployment(toolName, config);
1569
+ const mode = deployments.length > 0 && deployments[0].mode ? deployments[0].mode : "all";
1570
+ const existingSkills = scanner.getDeployedSkills(toolName);
1477
1571
  const alreadyExists = existingSkills.some((s) => s.name === skill.name);
1478
- if (!alreadyExists) {
1479
- const newSkill = {
1480
- name: skill.name,
1481
- source: skill.source,
1482
- deployMode
1483
- };
1484
- metadataService.updateDeployment(toolName, [...existingSkills, newSkill]);
1572
+ if (alreadyExists) {
1573
+ console.log(` \xB7 ${config.displayName} (already deployed)`);
1574
+ continue;
1485
1575
  }
1576
+ deployer.deploySkill(skill, config, deployMode, mode);
1486
1577
  console.log(
1487
1578
  ` \u2713 ${config.displayName} (${deployMode === "link" ? "linked" : "copied"})`
1488
1579
  );
@@ -1495,9 +1586,10 @@ var addCommand = new Command5("add").description("Add a skill to the project").a
1495
1586
  // src/commands/remove.ts
1496
1587
  import { Command as Command6 } from "commander";
1497
1588
  async function executeRemove(skillName, options) {
1498
- const metadataService = new MetadataService(process.cwd());
1589
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1499
1590
  const deployer = new Deployer(process.cwd());
1500
- if (!metadataService.hasMetadata()) {
1591
+ const configuredTools = scanner.getConfiguredTools();
1592
+ if (configuredTools.length === 0) {
1501
1593
  console.log("No skills deployed in current project.");
1502
1594
  process.exit(1);
1503
1595
  }
@@ -1509,22 +1601,21 @@ async function executeRemove(skillName, options) {
1509
1601
  }
1510
1602
  targetTools = [options.tool];
1511
1603
  } else {
1512
- targetTools = metadataService.getConfiguredTools();
1604
+ targetTools = configuredTools;
1513
1605
  }
1514
1606
  console.log(`Removing ${skillName}...`);
1515
1607
  let removed = false;
1516
1608
  for (const toolName of targetTools) {
1517
1609
  const config = TOOL_CONFIGS[toolName];
1518
- const deployment = metadataService.getToolDeployment(toolName);
1519
- if (!deployment) continue;
1520
- const existingSkills = deployment.skills;
1521
- const skillToRemove = existingSkills.find((s) => s.name === skillName);
1522
- if (!skillToRemove) continue;
1523
- deployer.removeSkill(skillName, config, deployment.mode);
1524
- const remainingSkills = existingSkills.filter((s) => s.name !== skillName);
1525
- metadataService.updateDeployment(toolName, remainingSkills);
1526
- console.log(` \u2713 Removed from ${config.displayName}`);
1527
- removed = true;
1610
+ const deployments = scanner.scanToolDeployment(toolName, config);
1611
+ for (const deployment of deployments) {
1612
+ const skillToRemove = deployment.skills.find((s) => s.name === skillName);
1613
+ if (!skillToRemove) continue;
1614
+ const mode = deployment.mode || "all";
1615
+ deployer.removeSkill(skillName, config, mode);
1616
+ console.log(` \u2713 Removed from ${config.displayName}`);
1617
+ removed = true;
1618
+ }
1528
1619
  }
1529
1620
  if (!removed) {
1530
1621
  console.log(`Skill '${skillName}' not found in any configured tool`);
@@ -1538,32 +1629,35 @@ var removeCommand = new Command6("remove").description("Remove a skill from the
1538
1629
  import { Command as Command7 } from "commander";
1539
1630
  import { join as join10 } from "path";
1540
1631
  async function executeSync() {
1541
- const metadataService = new MetadataService(process.cwd());
1632
+ const scanner = new DeploymentScanner(process.cwd(), SKILLS_MANAGER_DIR);
1542
1633
  const deployer = new Deployer(process.cwd());
1543
- if (!metadataService.hasMetadata()) {
1634
+ const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1635
+ const deployments = scanner.scanAllTools();
1636
+ if (deployments.length === 0) {
1544
1637
  console.log("No skills deployed in current project.");
1545
1638
  process.exit(1);
1546
1639
  }
1547
1640
  console.log("Checking deployed skills...\n");
1548
- const configuredTools = metadataService.getConfiguredTools();
1549
1641
  let updatedCount = 0;
1550
1642
  let removedCount = 0;
1551
- let untrackedCount = 0;
1552
- for (const toolName of configuredTools) {
1553
- const config = TOOL_CONFIGS[toolName];
1554
- const deployment = metadataService.getToolDeployment(toolName);
1555
- if (!deployment) continue;
1643
+ for (const deployment of deployments) {
1644
+ const config = TOOL_CONFIGS[deployment.toolName];
1645
+ const mode = deployment.mode || "all";
1556
1646
  console.log(`${config.displayName} (${deployment.targetDir}/):`);
1557
1647
  for (const skill of deployment.skills) {
1558
- const deployedPath = join10(process.cwd(), deployment.targetDir, skill.name);
1559
- const sourcePath = join10(SKILLS_MANAGER_DIR, skill.source, skill.name);
1560
- if (!fileExists(sourcePath)) {
1561
- console.log(` \u2717 ${skill.name}: orphaned (source deleted)`);
1648
+ const deployedPath = skill.path;
1649
+ let sourcePath = null;
1650
+ if (skill.source !== "unknown") {
1651
+ const sourceSkill = skillsService.getSkillByName(skill.name);
1652
+ if (sourceSkill) {
1653
+ sourcePath = sourceSkill.path;
1654
+ }
1655
+ }
1656
+ if (!sourcePath || !fileExists(sourcePath)) {
1657
+ console.log(` \u2717 ${skill.name}: orphaned (source not found)`);
1562
1658
  const action = await promptOrphanAction(skill.name);
1563
1659
  if (action === "remove") {
1564
- deployer.removeSkill(skill.name, config, deployment.mode);
1565
- const remaining = deployment.skills.filter((s) => s.name !== skill.name);
1566
- metadataService.updateDeployment(toolName, remaining);
1660
+ deployer.removeSkill(skill.name, config, mode);
1567
1661
  console.log(` \u2713 Removed ${skill.name}`);
1568
1662
  removedCount++;
1569
1663
  }
@@ -1594,7 +1688,7 @@ async function executeSync() {
1594
1688
  { name: skill.name, description: "", path: sourcePath, source: skill.source },
1595
1689
  config,
1596
1690
  "copy",
1597
- deployment.mode
1691
+ mode
1598
1692
  );
1599
1693
  console.log(` \u2713 Updated ${skill.name}`);
1600
1694
  updatedCount++;
@@ -1604,7 +1698,7 @@ async function executeSync() {
1604
1698
  { name: skill.name, description: "", path: sourcePath, source: skill.source },
1605
1699
  config,
1606
1700
  "copy",
1607
- deployment.mode
1701
+ mode
1608
1702
  );
1609
1703
  console.log(` \u2713 Updated ${skill.name}`);
1610
1704
  updatedCount++;
@@ -1618,7 +1712,7 @@ async function executeSync() {
1618
1712
  console.log();
1619
1713
  }
1620
1714
  console.log(
1621
- `Sync complete: ${updatedCount} updated, ${removedCount} removed, ${untrackedCount} untracked`
1715
+ `Sync complete: ${updatedCount} updated, ${removedCount} removed`
1622
1716
  );
1623
1717
  }
1624
1718
  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.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Unified skills manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,8 @@
25
25
  "roo-code",
26
26
  "kilo-code",
27
27
  "antigravity",
28
+ "opencode",
29
+ "trae",
28
30
  "cli"
29
31
  ],
30
32
  "author": "jtianling <jtianling@gmail.com>",