skills-manager 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +98 -78
  2. package/dist/index.mjs +90 -46
  3. package/package.json +6 -6
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # skills-manager
2
2
 
3
- > 一个简单的 CLI 工具,用于管理 Alma 生态系统的技能包
3
+ > 一个简单的 CLI 工具,用于管理技能包
4
4
 
5
5
  ![NPM Version](https://img.shields.io/npm/v/skills-manager?labelColor=%23000&color=%23954)
6
6
  ![NPM Version](https://img.shields.io/npm/l/skills-manager?labelColor=%23000&color=%23954)
@@ -9,8 +9,12 @@
9
9
 
10
10
  - ⚡️ **快速安装** - 使用 degit 从 GitHub 仓库快速克隆
11
11
  - 🎯 **简单易用** - 清晰的 CLI 命令和友好的用户反馈
12
- - 📦 **本地管理** - 技能包安装在当前项目目录
12
+ - 📦 **全局/本地管理** - 技能包支持全局和本地安装模式
13
13
  - 🔄 **依赖轻量** - 基于 degit,无需 git 配置
14
+ - 📁 **智能查找** - 自动识别仓库中的技能文件夹(优先查找 `skills/` 目录)
15
+ - 🚫 **自动清理** - 安装前自动清理旧版本,避免冲突
16
+ - 💾 **智能缓存** - 使用缓存加速重复安装
17
+ - 🎨 **彩色输出** - 使用 chalk 提供清晰的状态反馈
14
18
 
15
19
  ## 安装
16
20
 
@@ -26,46 +30,45 @@ pnpm add -g skills-manager
26
30
 
27
31
  ## 使用
28
32
 
29
- ### 查看可用技能
30
-
31
- ```bash
32
- skills available
33
- ```
34
-
35
33
  ### 安装技能
36
34
 
37
35
  ```bash
38
- skills add <skill-name>
36
+ # 本地安装
37
+ skills-manager add <owner>/<repo> <skill-name>
38
+
39
+ # 全局安装
40
+ skills-manager add <owner>/<repo> <skill-name> --global
39
41
  ```
40
42
 
41
43
  例如:
42
44
 
43
45
  ```bash
44
- skills add vue
45
- skills add vite
46
- skills add nuxt
46
+ # 本地安装
47
+ skills-manager add antfu/skills vue
47
48
  ```
48
49
 
49
50
  ### 列出已安装的技能
50
51
 
51
52
  ```bash
52
- skills list
53
+ # 列出当前目录的技能
54
+ skills-manager list
53
55
  # 或
54
- skills ls
56
+ skills-manager ls
57
+
58
+ # 列出全局技能
59
+ skills-manager list --global
55
60
  ```
56
61
 
57
62
  ### 删除技能
58
63
 
59
64
  ```bash
60
- skills remove <skill-name>
65
+ # 删除当前目录的技能
66
+ skills-manager remove <skill-name>
61
67
  # 或
62
- skills rm <skill-name>
63
- ```
64
-
65
- ### 查看帮助
68
+ skills-manager rm <skill-name>
66
69
 
67
- ```bash
68
- skills --help
70
+ # 删除全局技能
71
+ skills-manager remove <skill-name> --global
69
72
  ```
70
73
 
71
74
  ### 查看版本
@@ -74,82 +77,99 @@ skills --help
74
77
  skills --version
75
78
  ```
76
79
 
77
- ## 可用技能
78
-
79
- 以下是当前支持的技能包:
80
-
81
- | 技能名称 | 描述 |
82
- |---------|------|
83
- | `antfu` | Anthony Fu 的个人技能集 |
84
- | `nuxt` | Nuxt.js 框架技能 |
85
- | `pnpm` | pnpm 包管理器技能 |
86
- | `slidev` | Slidev 演示文稿工具 |
87
- | `tsdown` | Tsdown 构建工具 |
88
- | `unocss` | UnoCSS 引擎技能 |
89
- | `vite` | Vite 构建工具 |
90
- | `vitest` | Vitest 测试框架 |
91
- | `vue-best-practices` | Vue 最佳实践 |
92
- | `vue-router-best-practices` | Vue Router 最佳实践 |
93
- | `vue-testing-best-practices` | Vue 测试最佳实践 |
94
- | `vue` | Vue.js 框架技能 |
95
- | `vueuse-functions` | VueUse 函数集合 |
96
- | `web-design-guidelines` | Web 设计指南 |
97
- | `mcp-builder` | MCP 构建器 |
98
- | `skill-creator` | 技能创建器 |
99
- | `cloudflare` | Cloudflare 平台技能 |
100
- | `wrangler` | Wrangler CLI 工具 |
101
- | `agent-browser` | Agent 浏览器技能 |
102
- | `javascript-testing-patterns` | JavaScript 测试模式 |
103
- | `modern-javascript-patterns` | 现代 JavaScript 模式 |
104
- | `typescript-advanced-types` | TypeScript 高级类型 |
105
-
106
- ## 技能存储位置
107
-
108
- 技能包默认安装到当前工作目录的 `.alma/skills/` 目录下。
109
-
110
80
  ## 工作原理
111
81
 
112
- 1. **技能验证** - 从内置的技能注册表中验证技能名称
113
- 2. **使用 degit 克隆** - 使用 degit 从 GitHub 快速克隆技能包
114
- 3. **本地安装** - 将文件保存到 `./.alma/skills/` 目录
82
+ ### 安装流程
83
+
84
+ 1. **缓存下载** - 使用 degit 下载指定 GitHub 仓库到 `~/.cache/skills-manager`
85
+ 2. **智能查找** - 自动查找技能文件夹:
86
+ - 优先查找 `skills/{skill-name}` 目录
87
+ - 如果未找到,递归搜索整个仓库
88
+ - 显示警告信息并继续查找
89
+ 3. **准备安装** - 确定目标目录(全局或本地),创建必要的目录结构
90
+ 4. **清理旧版** - 自动删除已存在的技能目录,避免冲突
91
+ 5. **复制安装** - 将技能文件夹复制到最终位置
92
+ 6. **完成反馈** - 显示安装成功的彩色信息
93
+
94
+ ### 技能目录结构
95
+
96
+ 技能仓库应该包含以下结构之一:
97
+
98
+ ```
99
+ # 推荐结构(优先匹配)
100
+ skills/
101
+ ├── skill-name/
102
+ │ └── (技能文件)
103
+ └── ...
104
+
105
+ # 或者直接在根目录
106
+ skill-name/
107
+ └── (技能文件)
108
+ ```
115
109
 
116
110
  ## 架构
117
111
 
118
112
  ```
119
113
  src/
120
- ├── index.ts # CLI 入口点
114
+ ├── index.ts # CLI 入口点(使用 cac 框架)
121
115
  ├── manager.ts # 技能管理器(基于 degit)
122
- └── skills.ts # 技能注册表
116
+ ├── utils.ts # 工具函数(文件操作、颜色输出等)
117
+ └── types.ts # TypeScript 类型定义
123
118
  ```
124
119
 
125
- ## 开发
120
+ ## 缓存位置
126
121
 
127
- ```bash
128
- # 克隆项目
129
- git clone https://github.com/jiakun-zhao/skills-manager.git
130
- cd skills-manager
122
+ 技能包缓存存储在 `~/.cache/skills-manager/` 目录下,每个仓库根据名称创建单独的缓存文件夹。
131
123
 
132
- # 安装依赖
133
- pnpm install
124
+ ### 技能安装位置
134
125
 
135
- # 开发模式
136
- pnpm dev
126
+ - **全局技能**: `~/.claude/skills/`
127
+ - **本地技能**: `./.claude/skills/`(相对于当前工作目录)
137
128
 
138
- # 构建
139
- pnpm build
129
+ ## 许可证
140
130
 
141
- # 发布
142
- pnpm release
143
- ```
131
+ [MIT](LICENSE)
144
132
 
145
- ## 依赖
133
+ ## 故障排除
146
134
 
147
- - **[degit](https://github.com/Rich-Harris/degit)** - 用于从 GitHub 克隆仓库
148
- - **[cac](https://github.com/cacjs/cac)** - CLI 命令行框架
135
+ ### 常见问题
149
136
 
150
- ## 许可证
137
+ 1. **技能未找到错误**
151
138
 
152
- [MIT](LICENSE)
139
+ ```
140
+ Error: Skill 'skill-name' not found in repo owner/repo
141
+ ```
142
+
143
+ - 确保仓库中包含名为 `skill-name` 的目录
144
+ - 技能应该位于 `skills/skill-name` 或仓库的根目录
145
+
146
+ 2. **权限错误**
147
+
148
+ ```
149
+ Error: EACCES: permission denied
150
+ ```
151
+
152
+ - 检查对 `~/.claude/skills` 或当前目录的写权限
153
+ - 尝试使用 `sudo`(仅限全局安装)
154
+
155
+ 3. **网络错误**
156
+ - 确保网络连接正常
157
+ - 检查 GitHub 仓库是否可以访问
158
+
159
+ ### 调试模式
160
+
161
+ 如果遇到问题,可以:
162
+
163
+ 1. 检查缓存目录内容:
164
+
165
+ ```bash
166
+ ls ~/.cache/skills-manager/
167
+ ```
168
+
169
+ 2. 清理缓存后重试:
170
+ ```bash
171
+ rm -rf ~/.cache/skills-manager/
172
+ ```
153
173
 
154
174
  ## 作者
155
175
 
package/dist/index.mjs CHANGED
@@ -1,69 +1,113 @@
1
1
  import { cac } from "cac";
2
+ import { cwd, exit } from "node:process";
2
3
  import chalk from "chalk";
3
- import { mkdir, readdir, writeFile } from "node:fs/promises";
4
+ import degit from "degit";
5
+ import { access, copyFile, mkdir, readdir } from "node:fs/promises";
4
6
  import { homedir } from "node:os";
5
- import { dirname, join } from "node:path";
7
+ import { join } from "node:path";
6
8
  import { rimraf } from "rimraf";
7
- import { joinURL } from "ufo";
8
9
 
9
- //#region src/manager.ts
10
+ //#region src/utils.ts
10
11
  const print = console.log;
11
- const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
12
- async function list() {
13
- print(chalk.gray("→"), chalk.gray(CLAUDE_SKILLS_DIR));
14
- print();
15
- (await readdir(CLAUDE_SKILLS_DIR, { withFileTypes: true })).forEach((it) => it.isDirectory() && print(chalk.gray("•"), it.name));
16
- print();
12
+ async function exists(path) {
13
+ try {
14
+ await access(path);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
17
19
  }
18
- async function remove(name) {
19
- await rimraf(join(CLAUDE_SKILLS_DIR, name));
20
- await list();
21
- print(chalk.red("-", name));
22
- print();
20
+ async function copyDirectory(src, dest) {
21
+ await mkdir(dest, { recursive: true });
22
+ const entries = await readdir(src, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ const srcPath = join(src, entry.name);
25
+ const destPath = join(dest, entry.name);
26
+ if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
27
+ else try {
28
+ await copyFile(srcPath, destPath);
29
+ } catch {}
30
+ }
31
+ }
32
+ async function findSkillPath(repoPath, skillName) {
33
+ const skillDir = join(join(repoPath, "skills"), skillName);
34
+ if (await exists(skillDir)) return skillDir;
35
+ async function findDirectory(dir, target) {
36
+ try {
37
+ const entries = await readdir(dir, { withFileTypes: true });
38
+ for (const entry of entries) {
39
+ if (entry.isDirectory() && entry.name === target) return join(dir, target);
40
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
41
+ const result = await findDirectory(join(dir, entry.name), target);
42
+ if (result) return result;
43
+ }
44
+ }
45
+ } catch {}
46
+ return null;
47
+ }
48
+ const result = await findDirectory(repoPath, skillName);
49
+ if (!result) print(chalk.yellow(`Warning: Skill '${skillName}' not found in skills/ directory, searching entire repo...`));
50
+ return result;
23
51
  }
24
- async function add(key, options) {
25
- const { owner, repo, name, dir } = format(key);
26
- const files = (await fetchUnGH(owner, repo, options.branch)).filter((file) => file.path.startsWith(dir)).map((file) => ({
27
- remote: joinURL("https://cdn.jsdelivr.net/gh", owner, repo, file.path),
28
- local: join(CLAUDE_SKILLS_DIR, name, file.path.slice(dir.length))
29
- }));
30
- await rimraf(join(CLAUDE_SKILLS_DIR, name));
31
- await Promise.all(files.map(async (file) => {
32
- await fetch(file.remote).then(async (res) => {
33
- const data = await res.text();
34
- await mkdir(dirname(file.local), { recursive: true });
35
- await writeFile(file.local, data);
36
- });
37
- }));
38
- await list();
39
- print(chalk.green("+", name));
52
+
53
+ //#endregion
54
+ //#region src/manager.ts
55
+ const GLOBAL_SKILLS_DIR = join(homedir(), ".claude", "skills");
56
+ const LOCAL_SKILLS_DIR = join(cwd(), ".claude", "skills");
57
+ const CACHE_DIR = join(homedir(), ".cache", "skills-manager");
58
+ async function list(options = {}) {
59
+ const skillsDir = options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR;
60
+ const type = options.global ? "global" : "local";
61
+ print(chalk.gray("→"), chalk.gray(skillsDir));
40
62
  print();
63
+ try {
64
+ (await readdir(skillsDir, { withFileTypes: true })).forEach((it) => it.isDirectory() && print(chalk.gray("•"), it.name));
65
+ print();
66
+ } catch {
67
+ print(chalk.gray(`No ${type} skills found`));
68
+ }
41
69
  }
42
- function format(key) {
43
- const [owner, repo, ...rest] = key.split("/");
44
- return {
45
- owner,
46
- repo,
47
- name: rest.at(-1),
48
- dir: rest.join("/")
49
- };
70
+ async function remove(name, options = {}) {
71
+ const path = join(options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR, name);
72
+ if (!await exists(path)) throw new Error(`Skill '${name}' not found`);
73
+ await rimraf(path);
74
+ if (!options.global) {
75
+ if ((await readdir(LOCAL_SKILLS_DIR)).length === 0) await rimraf(join(LOCAL_SKILLS_DIR, ".."));
76
+ }
77
+ print(chalk.gray("-", options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR), chalk.red(name));
78
+ print();
50
79
  }
51
- async function fetchUnGH(owner, repo, branch) {
52
- const url = joinURL("https://api.zhaojiakun.com", "ungh", "repos", owner, repo, "files", branch);
53
- return (await fetch(url).then((res) => res.json())).files;
80
+ async function add(repo, skillName, options) {
81
+ const cachePath = join(CACHE_DIR, repo.replace(/\//g, "-"));
82
+ await mkdir(CACHE_DIR, { recursive: true });
83
+ print(chalk.gray("✲", "Downloading repo to cache..."));
84
+ await degit(repo, {
85
+ cache: true,
86
+ force: true
87
+ }).clone(cachePath);
88
+ print(chalk.green("✓", "Repo downloaded to cache"));
89
+ const skillPath = await findSkillPath(cachePath, skillName);
90
+ if (!skillPath) throw new Error(`Skill '${skillName}' not found in repo ${repo}`);
91
+ const skillsDir = options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR;
92
+ const targetDir = join(skillsDir, skillName);
93
+ await mkdir(skillsDir, { recursive: true });
94
+ if (await exists(targetDir)) await rimraf(targetDir);
95
+ await copyDirectory(skillPath, targetDir);
96
+ print(chalk.gray("→", options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR), chalk.green(skillName));
97
+ print();
54
98
  }
55
99
 
56
100
  //#endregion
57
101
  //#region package.json
58
102
  var name = "skills-manager";
59
- var version = "0.0.3";
103
+ var version = "0.0.5";
60
104
 
61
105
  //#endregion
62
106
  //#region src/index.ts
63
107
  const cli = cac(name);
64
- cli.command("add <key>", "Add skill").option("-b, --branch", "Repo branch", { default: "main" }).action(add);
65
- cli.command("list", "List skills").alias("ls").action(list);
66
- cli.command("remove <name>", "Remove skill").alias("rm").action(remove);
108
+ cli.command("add <repo> <skill-name>", "Add skill from GitHub repo").option("-g, --global", "Install globally").action((repo, skillName, options) => add(repo, skillName, options).catch(() => exit(1)));
109
+ cli.command("list", "List skills").alias("ls").option("-g, --global", "List global skills").action((options) => list(options).catch(() => exit(1)));
110
+ cli.command("remove <name>", "Remove skill").alias("rm").option("-g, --global", "Remove from global skills").action((name, options) => remove(name, options).catch(() => exit(1)));
67
111
  cli.version(version);
68
112
  cli.help();
69
113
  cli.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills-manager",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/jiakun-zhao/skills-manager#readme",
6
6
  "bugs": {
@@ -20,16 +20,16 @@
20
20
  "dependencies": {
21
21
  "cac": "^6.7.14",
22
22
  "chalk": "^5.6.2",
23
- "rimraf": "^6.1.2",
24
- "ufo": "^1.6.3"
23
+ "degit": "^2.8.4",
24
+ "rimraf": "^6.1.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@jiakun-zhao/eslint-config": "^4.3.0",
28
28
  "@types/degit": "^2.8.6",
29
- "@types/node": "^25.2.0",
30
- "bumpp": "^10.4.0",
29
+ "@types/node": "^25.2.1",
30
+ "bumpp": "^10.4.1",
31
31
  "eslint": "^9.39.2",
32
- "tsdown": "^0.20.1",
32
+ "tsdown": "^0.20.3",
33
33
  "tsx": "^4.21.0",
34
34
  "typescript": "^5.9.3"
35
35
  },