joyskills-cli 0.1.4 → 0.2.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 +76 -32
- package/package.json +1 -1
- package/src/commands/install.js +83 -72
package/README.md
CHANGED
|
@@ -19,29 +19,29 @@
|
|
|
19
19
|
|
|
20
20
|
### 🚀 团队协作增强(OpenSkills 缺失的)
|
|
21
21
|
- ✅ **版本锁定**:joySkills.lock 确保团队版本统一
|
|
22
|
-
- ✅ **团队 Registry
|
|
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
|
|
33
|
-
# → 安装到 .agent/skills/rc-onboarding
|
|
30
|
+
# 安装公开 skill
|
|
31
|
+
joySkills install anthropics/skills/pdf
|
|
34
32
|
|
|
35
|
-
#
|
|
36
|
-
joySkills install -
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
**痛点**:
|
|
109
|
+
- 😓 内部 skill 群里发 zip,手动下载 + 解压
|
|
110
|
+
- 😓 新人 onboarding,问“哪些 skill 必须装”
|
|
111
|
+
- 😓 skill 更新后,通知所有人重新下载
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
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://
|
|
128
|
+
"url": "https://git.yourcompany.com/team-skills.git",
|
|
117
129
|
"type": "git"
|
|
118
130
|
}]
|
|
119
|
-
}
|
|
131
|
+
}
|
|
132
|
+
EOF
|
|
120
133
|
|
|
121
|
-
# 3.
|
|
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
|
-
#
|
|
128
|
-
git
|
|
129
|
-
|
|
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
|
|
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:
|
|
235
|
-
name: "
|
|
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
package/src/commands/install.js
CHANGED
|
@@ -83,7 +83,11 @@ async function installPublicSkill(skillName, options, localManager, lockfileMana
|
|
|
83
83
|
|
|
84
84
|
// 调用 openskills install
|
|
85
85
|
const cmd = `npx openskills install ${skillName}`;
|
|
86
|
-
|
|
86
|
+
try {
|
|
87
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(`openskills install failed: Command failed: ${cmd}`);
|
|
90
|
+
}
|
|
87
91
|
|
|
88
92
|
// 更新 lockfile
|
|
89
93
|
lockfileManager.updateSkill(skillName, {
|
|
@@ -108,92 +112,99 @@ async function installPublicSkill(skillName, options, localManager, lockfileMana
|
|
|
108
112
|
async function installTeamSkill(skillName, options, projectRoot, localManager, lockfileManager) {
|
|
109
113
|
console.log(`🏢 Installing team skill: ${skillName}`);
|
|
110
114
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
registryManager = new RegistryManager(registryPath);
|
|
118
|
-
await registryManager.load();
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error('Warning: Could not load registry:', error.message);
|
|
121
|
-
}
|
|
115
|
+
// 解析 team:// URL
|
|
116
|
+
const teamUrl = skillName.replace(/^team:\/\//, '');
|
|
117
|
+
const parts = teamUrl.split('/');
|
|
118
|
+
if (parts.length !== 2) {
|
|
119
|
+
throw new Error('Invalid team skill format. Expected: team://registry-name/skill-name');
|
|
122
120
|
}
|
|
121
|
+
const [registryName, skillId] = parts;
|
|
123
122
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!options.version) {
|
|
130
|
-
// Get recommended version from registry
|
|
131
|
-
const recommended = registryManager.getRecommendedVersion(skillName);
|
|
132
|
-
if (recommended) {
|
|
133
|
-
targetVersion = recommended.version;
|
|
134
|
-
versionSource = 'registry';
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Validate version
|
|
139
|
-
const validation = registryManager.validateVersion(skillName, targetVersion);
|
|
140
|
-
if (!validation.valid && !options.force) {
|
|
141
|
-
console.error(`❌ Version validation failed: ${validation.error}`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (validation.warning && !options.force) {
|
|
146
|
-
console.log(`⚠️ Warning: ${validation.warning}`);
|
|
147
|
-
console.log(' Use --force to install anyway.');
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
123
|
+
// 读取配置文件
|
|
124
|
+
const configPath = path.join(projectRoot, '.joyskillrc');
|
|
125
|
+
if (!fs.existsSync(configPath)) {
|
|
126
|
+
throw new Error('.joyskillrc not found. Please configure team registry first.');
|
|
127
|
+
}
|
|
150
128
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
129
|
+
let config;
|
|
130
|
+
try {
|
|
131
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(`Failed to parse .joyskillrc: ${error.message}`);
|
|
154
134
|
}
|
|
155
135
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
136
|
+
// 查找 registry 配置
|
|
137
|
+
const registry = config.registries?.find(r => r.id === `team://${registryName}`);
|
|
138
|
+
if (!registry) {
|
|
139
|
+
throw new Error(`Registry "team://${registryName}" not found in .joyskillrc`);
|
|
140
|
+
}
|
|
159
141
|
|
|
160
|
-
|
|
161
|
-
const skillMdContent = `---
|
|
162
|
-
name: ${skillName}
|
|
163
|
-
description: Auto-generated skill for ${skillName}
|
|
164
|
-
version: ${targetVersion}
|
|
165
|
-
---
|
|
142
|
+
console.log(`📦 Fetching from ${registry.url}...`);
|
|
166
143
|
|
|
167
|
-
|
|
144
|
+
// 临时目录
|
|
145
|
+
const tmpDir = path.join(projectRoot, '.joyskill', 'tmp', `${Date.now()}`);
|
|
146
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
168
147
|
|
|
169
|
-
|
|
148
|
+
try {
|
|
149
|
+
// Git clone
|
|
150
|
+
console.log('🔄 Cloning registry...');
|
|
151
|
+
execSync(`git clone --depth 1 ${registry.url} ${tmpDir}`, {
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
encoding: 'utf-8'
|
|
154
|
+
});
|
|
170
155
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
156
|
+
// 检查 skill 是否存在
|
|
157
|
+
const skillPath = path.join(tmpDir, skillId);
|
|
158
|
+
if (!fs.existsSync(skillPath)) {
|
|
159
|
+
throw new Error(`Skill "${skillId}" not found in registry`);
|
|
160
|
+
}
|
|
174
161
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
162
|
+
// 检查 SKILL.md
|
|
163
|
+
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
164
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
165
|
+
throw new Error(`SKILL.md not found for "${skillId}"`);
|
|
166
|
+
}
|
|
179
167
|
|
|
180
|
-
|
|
181
|
-
|
|
168
|
+
// 读取 SKILL.md 内容
|
|
169
|
+
const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8');
|
|
170
|
+
|
|
171
|
+
// 安装 skill
|
|
172
|
+
localManager.installSkill(skillId, skillMdContent);
|
|
173
|
+
|
|
174
|
+
// 复制其他文件(如果有)
|
|
175
|
+
const skillFiles = fs.readdirSync(skillPath);
|
|
176
|
+
const targetDir = path.join(localManager.getSkillsDir(), skillId);
|
|
177
|
+
|
|
178
|
+
for (const file of skillFiles) {
|
|
179
|
+
if (file === 'SKILL.md') continue;
|
|
180
|
+
const srcFile = path.join(skillPath, file);
|
|
181
|
+
const destFile = path.join(targetDir, file);
|
|
182
|
+
|
|
183
|
+
if (fs.statSync(srcFile).isDirectory()) {
|
|
184
|
+
fs.cpSync(srcFile, destFile, { recursive: true });
|
|
185
|
+
} else {
|
|
186
|
+
fs.copyFileSync(srcFile, destFile);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
182
189
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
// 更新 lockfile
|
|
191
|
+
lockfileManager.updateSkill(skillId, {
|
|
192
|
+
version: options.version || 'latest',
|
|
193
|
+
source: 'team',
|
|
194
|
+
registry: `team://${registryName}`,
|
|
195
|
+
installedAt: new Date().toISOString()
|
|
196
|
+
});
|
|
190
197
|
|
|
191
|
-
|
|
198
|
+
await lockfileManager.save();
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
console.log(`✅ Successfully installed ${skillId}`);
|
|
201
|
+
console.log(`💡 Run 'joySkills sync' to update AGENTS.md`);
|
|
194
202
|
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
} finally {
|
|
204
|
+
// 清理临时目录
|
|
205
|
+
if (fs.existsSync(tmpDir)) {
|
|
206
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
207
|
+
}
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
|