joyskills-cli 0.1.3 → 0.2.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
@@ -19,29 +19,29 @@
19
19
 
20
20
  ### 🚀 团队协作增强(OpenSkills 缺失的)
21
21
  - ✅ **版本锁定**:joySkills.lock 确保团队版本统一
22
- - ✅ **团队 Registry**:基于 Git 的内部 skill 仓库
22
+ - ✅ **团队 Registry**:从 Git 仓库安装内部 skill
23
23
  - ✅ **安全审计**:自动检测废弃和有风险的 skill
24
24
  - ✅ **状态管理**:draft → review → approved → deprecated
25
- - ✅ **使用追踪**:了解哪些 skill 被使用、被谁使用
26
25
 
27
26
  ## ✨ 核心特性
28
27
 
29
28
  ### 📦 公开 + 内部 Skill 统一管理
30
29
  ```bash
31
- # 项目级安装(默认)
32
- joySkills install team://your-team/rc-onboarding
33
- # → 安装到 .agent/skills/rc-onboarding
30
+ # 安装公开 skill
31
+ joySkills install anthropics/skills/pdf
34
32
 
35
- # 全局安装
36
- joySkills install -g anthropics/skills/pdf
37
- # → 安装到 ~/.agent/skills/pdf
33
+ # 安装内部 skill(需配置 Registry)
34
+ joySkills install team://your-team/rc-onboarding
38
35
 
39
- # 统一同步(扫描所有路径)
40
- joySkills sync
41
- # 扫描: .agent/skills, ~/.agent/skills, .claude/skills, ~/.claude/skills
42
- # 生成: AGENTS.md + joySkills.lock
36
+ # 统一同步
37
+ joySkills sync # 生成 AGENTS.md + joySkills.lock
43
38
  ```
44
39
 
40
+ **核心价值**:
41
+ - ✅ 公开 + 内部 skill 统一管理,一个命令搞定
42
+ - ✅ 版本锁定(joySkills.lock),团队环境一致
43
+ - ✅ 100% 兼容 OpenSkills,随时可回退
44
+
45
45
  **路径设计**:
46
46
  | 安装方式 | joySkills | OpenSkills |
47
47
  |---------|-----------|------------|
@@ -105,30 +105,54 @@ joySkills list
105
105
 
106
106
  ### 场景 2:团队协作(内部 skill)
107
107
 
108
- ```bash
109
- # 1. 安装
110
- npm install -g joyskills-cli
108
+ **痛点**:
109
+ - 😓 内部 skill 群里发 zip,手动下载 + 解压
110
+ - 😓 新人 onboarding,问“哪些 skill 必须装”
111
+ - 😓 skill 更新后,通知所有人重新下载
111
112
 
112
- # 2. 配置团队 Registry
113
- echo '{
113
+ **解决方案**:
114
+ ```bash
115
+ # 1. Tech Lead 搭建团队 Registry(Git 仓库)
116
+ mkdir team-skills && cd team-skills
117
+ mkdir rc-onboarding rc-commit-lint
118
+ # 将 SKILL.md 放入对应目录
119
+ git init && git add . && git commit -m "init"
120
+ git remote add origin https://git.yourcompany.com/team-skills.git
121
+ git push
122
+
123
+ # 2. 团队成员配置(一次)
124
+ cat > .joyskillrc << 'EOF'
125
+ {
114
126
  "registries": [{
115
127
  "id": "team://your-team",
116
- "url": "https://your-git/team-skills.git",
128
+ "url": "https://git.yourcompany.com/team-skills.git",
117
129
  "type": "git"
118
130
  }]
119
- }' > .joyskillrc
131
+ }
132
+ EOF
120
133
 
121
- # 3. 安装团队 skill
134
+ # 3. 安装 skill(像 npm 一样)
122
135
  joySkills install team://your-team/rc-onboarding
123
-
124
- # 4. 生成 AGENTS.md + joySkills.lock
136
+ joySkills install team://your-team/rc-commit-lint
125
137
  joySkills sync
126
138
 
127
- # 5. 团队成员同步
128
- git pull # 拉取 joySkills.lock
129
- joySkills install # 自动安装锁定版本
139
+ # 4. 团队同步(自动化)
140
+ git add AGENTS.md joySkills.lock .joyskillrc
141
+ git commit -m "setup team skills"
142
+ git push
143
+
144
+ # 5. 新成员加入(1 分钟)
145
+ git clone <project-repo>
146
+ joySkills install # 自动安装所有锁定版本
130
147
  ```
131
148
 
149
+ **效果对比**:
150
+ | 场景 | 之前 | 现在 |
151
+ |------|------|------|
152
+ | 新人 onboarding | 群里找 8 个 zip,手动装(30分钟) | `joySkills install`(1分钟) |
153
+ | skill 更新 | 群发通知 + 手动更新(15分钟) | `git pull && joySkills install`(1分钟) |
154
+ | 版本不一致 | 频繁出现,难排查 | joySkills.lock 锁定,不会出现 |
155
+
132
156
  ### 场景 3:从 OpenSkills 迁移
133
157
 
134
158
  ```bash
@@ -152,7 +176,7 @@ joySkills sync
152
176
 
153
177
  ```bash
154
178
  joySkills sync
155
- # ✅ 扫描 skills/ 目录
179
+ # ✅ 扫描 .agent/skills, ~/.agent/skills, .claude/skills, ~/.claude/skills
156
180
  # ✅ 生成 AGENTS.md(OpenSkills 兼容格式)
157
181
  # ✅ 生成 joySkills.lock(版本锁定)
158
182
  # ✅ 版本一致性检查
@@ -223,23 +247,43 @@ joySkills status
223
247
 
224
248
  ## 📖 核心概念
225
249
 
226
- ### Skill Registry(技能注册表)
250
+ ### Skill Registry(团队 skill 仓库)
227
251
 
228
- Registry 是团队统一管理的 Skill 仓库,通常是一个 Git 仓库 + `registry.yaml` 索引文件:
252
+ Registry 是团队统一管理 skill Git 仓库,像 npm 私有源一样。
229
253
 
254
+ **目录结构**:
255
+ ```
256
+ team-skills/ # Git 仓库
257
+ ├── registry.yaml # 版本配置(可选)
258
+ ├── rc-onboarding/
259
+ │ ├── SKILL.md # 必须
260
+ │ ├── scripts/ # 可选:脚本
261
+ │ └── templates/ # 可选:模板
262
+ └── rc-commit-lint/
263
+ └── SKILL.md
264
+ ```
265
+
266
+ **registry.yaml 示例**(可选,用于版本管理):
230
267
  ```yaml
231
268
  registryVersion: 1
232
269
  registryId: team://your-team
233
270
  skills:
234
- - id: web-search
235
- name: "Web 搜索"
236
- visibility: public
271
+ - id: rc-onboarding
272
+ name: "搜推团队 Onboarding"
273
+ visibility: internal # internal / public / restricted
237
274
  versions:
238
275
  - version: 1.2.0
239
- state: approved
276
+ state: approved # draft / pending_review / approved / deprecated
240
277
  recommended: true
278
+ - version: 1.1.0
279
+ state: deprecated
241
280
  ```
242
281
 
282
+ **使用场景**:
283
+ - ✅ 内部业务 skill(如 rc-onboarding、rc-commit-lint)
284
+ - ✅ 公司级通用 skill(如 code-review、test-helper)
285
+ - ✅ 项目级定制 skill(如 project-specific-tools)
286
+
243
287
  ### joySkills.lock(锁文件)
244
288
 
245
289
  项目级锁文件,记录实际使用的 skill 版本:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joyskills-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Team-level skill governance compatible with open skill / Claude Skills",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -74,6 +74,13 @@ async function installPublicSkill(skillName, options, localManager, lockfileMana
74
74
  console.log(`💡 Using openskills install...\n`);
75
75
 
76
76
  try {
77
+ // 检查 openskills 是否可用
78
+ try {
79
+ execSync('npx openskills --version', { stdio: 'pipe' });
80
+ } catch (error) {
81
+ throw new Error('openskills not found. Please install: npm install -g openskills');
82
+ }
83
+
77
84
  // 调用 openskills install
78
85
  const cmd = `npx openskills install ${skillName}`;
79
86
  execSync(cmd, { stdio: 'inherit' });
@@ -101,88 +108,99 @@ async function installPublicSkill(skillName, options, localManager, lockfileMana
101
108
  async function installTeamSkill(skillName, options, projectRoot, localManager, lockfileManager) {
102
109
  console.log(`🏢 Installing team skill: ${skillName}`);
103
110
 
104
- // Check for registry
105
- let registryManager = null;
106
- const registryPath = path.join(projectRoot, '.joyskill/registry');
107
-
108
- if (fs.existsSync(registryPath)) {
109
- try {
110
- registryManager = new RegistryManager(registryPath);
111
- await registryManager.load();
112
- } catch (error) {
113
- console.error('Warning: Could not load registry:', error.message);
114
- }
111
+ // 解析 team:// URL
112
+ const teamUrl = skillName.replace(/^team:\/\//, '');
113
+ const parts = teamUrl.split('/');
114
+ if (parts.length !== 2) {
115
+ throw new Error('Invalid team skill format. Expected: team://registry-name/skill-name');
115
116
  }
117
+ const [registryName, skillId] = parts;
116
118
 
117
- // Determine version to install
118
- let targetVersion = options.version || '1.0.0';
119
- let versionSource = 'default';
120
-
121
- if (registryManager && registryManager.hasSkill(skillName)) {
122
- if (!options.version) {
123
- // Get recommended version from registry
124
- const recommended = registryManager.getRecommendedVersion(skillName);
125
- if (recommended) {
126
- targetVersion = recommended.version;
127
- versionSource = 'registry';
128
- }
129
- }
130
-
131
- // Validate version
132
- const validation = registryManager.validateVersion(skillName, targetVersion);
133
- if (!validation.valid && !options.force) {
134
- console.error(`❌ Version validation failed: ${validation.error}`);
135
- return;
136
- }
119
+ // 读取配置文件
120
+ const configPath = path.join(projectRoot, '.joyskillrc');
121
+ if (!fs.existsSync(configPath)) {
122
+ throw new Error('.joyskillrc not found. Please configure team registry first.');
123
+ }
137
124
 
138
- if (validation.warning && !options.force) {
139
- console.log(`⚠️ Warning: ${validation.warning}`);
140
- console.log(' Use --force to install anyway.');
141
- return;
142
- }
125
+ let config;
126
+ try {
127
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
128
+ } catch (error) {
129
+ throw new Error(`Failed to parse .joyskillrc: ${error.message}`);
130
+ }
143
131
 
144
- console.log(`Installing ${skillName} v${targetVersion} from registry`);
145
- } else {
146
- console.log(`Installing ${skillName} v${targetVersion} (local/custom)`);
132
+ // 查找 registry 配置
133
+ const registry = config.registries?.find(r => r.id === `team://${registryName}`);
134
+ if (!registry) {
135
+ throw new Error(`Registry "team://${registryName}" not found in .joyskillrc`);
147
136
  }
148
137
 
149
- // Create sample SKILL.md content
150
- const skillMdContent = `---
151
- name: ${skillName}
152
- description: Auto-generated skill for ${skillName}
153
- version: ${targetVersion}
154
- ---
138
+ console.log(`📦 Fetching from ${registry.url}...`);
155
139
 
156
- # ${skillName}
140
+ // 临时目录
141
+ const tmpDir = path.join(projectRoot, '.joyskill', 'tmp', `${Date.now()}`);
142
+ fs.mkdirSync(tmpDir, { recursive: true });
157
143
 
158
- This skill was automatically generated by joySkills.
144
+ try {
145
+ // Git clone
146
+ console.log('🔄 Cloning registry...');
147
+ execSync(`git clone --depth 1 ${registry.url} ${tmpDir}`, {
148
+ stdio: 'pipe',
149
+ encoding: 'utf-8'
150
+ });
159
151
 
160
- ## Examples
161
- - Example usage 1
162
- - Example usage 2
152
+ // 检查 skill 是否存在
153
+ const skillPath = path.join(tmpDir, skillId);
154
+ if (!fs.existsSync(skillPath)) {
155
+ throw new Error(`Skill "${skillId}" not found in registry`);
156
+ }
163
157
 
164
- ## Guidelines
165
- - Guideline 1
166
- - Guideline 2
167
- `;
158
+ // 检查 SKILL.md
159
+ const skillMdPath = path.join(skillPath, 'SKILL.md');
160
+ if (!fs.existsSync(skillMdPath)) {
161
+ throw new Error(`SKILL.md not found for "${skillId}"`);
162
+ }
168
163
 
169
- // Install the skill
170
- localManager.installSkill(skillName, skillMdContent);
164
+ // 读取 SKILL.md 内容
165
+ const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8');
166
+
167
+ // 安装 skill
168
+ localManager.installSkill(skillId, skillMdContent);
169
+
170
+ // 复制其他文件(如果有)
171
+ const skillFiles = fs.readdirSync(skillPath);
172
+ const targetDir = path.join(localManager.getSkillsDir(), skillId);
173
+
174
+ for (const file of skillFiles) {
175
+ if (file === 'SKILL.md') continue;
176
+ const srcFile = path.join(skillPath, file);
177
+ const destFile = path.join(targetDir, file);
178
+
179
+ if (fs.statSync(srcFile).isDirectory()) {
180
+ fs.cpSync(srcFile, destFile, { recursive: true });
181
+ } else {
182
+ fs.copyFileSync(srcFile, destFile);
183
+ }
184
+ }
171
185
 
172
- // Update lockfile
173
- lockfileManager.updateSkill(skillName, {
174
- version: targetVersion,
175
- source: 'team',
176
- registry: options.registry || 'local',
177
- installedAt: new Date().toISOString()
178
- });
186
+ // 更新 lockfile
187
+ lockfileManager.updateSkill(skillId, {
188
+ version: options.version || 'latest',
189
+ source: 'team',
190
+ registry: `team://${registryName}`,
191
+ installedAt: new Date().toISOString()
192
+ });
179
193
 
180
- await lockfileManager.save();
194
+ await lockfileManager.save();
181
195
 
182
- console.log(`✅ Successfully installed ${skillName} v${targetVersion}`);
196
+ console.log(`✅ Successfully installed ${skillId}`);
197
+ console.log(`💡 Run 'joySkills sync' to update AGENTS.md`);
183
198
 
184
- if (versionSource === 'registry') {
185
- console.log(' Version selected from registry recommendations.');
199
+ } finally {
200
+ // 清理临时目录
201
+ if (fs.existsSync(tmpDir)) {
202
+ fs.rmSync(tmpDir, { recursive: true, force: true });
203
+ }
186
204
  }
187
205
  }
188
206
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
+ import * as YAML from 'yaml';
5
6
  import { fileURLToPath } from 'url';
6
7
 
7
8
  const __filename = fileURLToPath(import.meta.url);
@@ -122,20 +123,15 @@ function parseSkillMetadata(content) {
122
123
  const match = content.match(/^---\n([\s\S]*?)\n---/);
123
124
  if (!match) return {};
124
125
 
125
- const yaml = match[1];
126
- const metadata = {};
127
-
128
- // 简单的 YAML 解析(关键字段)
129
- const lines = yaml.split('\n');
130
- for (const line of lines) {
131
- const [key, ...valueParts] = line.split(':');
132
- if (!key || !valueParts.length) continue;
133
-
134
- const value = valueParts.join(':').trim().replace(/^["']|["']$/g, '');
135
- metadata[key.trim()] = value;
126
+ const yamlContent = match[1];
127
+
128
+ try {
129
+ // 使用 yaml 包解析(支持完整 YAML 语法)
130
+ return YAML.parse(yamlContent) || {};
131
+ } catch (error) {
132
+ console.warn(`⚠️ YAML 解析失败: ${error.message}`);
133
+ return {};
136
134
  }
137
-
138
- return metadata;
139
135
  }
140
136
 
141
137
  /**