joyskills-cli 0.3.1 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joyskills-cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "JoySkills CLI v2.0 - Multi-agent skill management with JoyCode native support",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -50,11 +50,12 @@ export function teamCommand(program) {
50
50
  const registryYamlPath = path.join(registryCachePath, 'registry.yaml');
51
51
  const hasRegistryYaml = fs.existsSync(registryYamlPath);
52
52
 
53
- // Count skill directories (dirs with SKILL.md)
54
- const entries = fs.readdirSync(registryCachePath, { withFileTypes: true });
55
- const skillDirs = entries.filter(e => e.isDirectory() && fs.existsSync(path.join(registryCachePath, e.name, 'SKILL.md')));
53
+ // Count skills using findSkills (supports nested directories)
54
+ const { findSkills } = await import('./install.js');
55
+ const foundSkills = await findSkills(registryCachePath);
56
+ const skillCount = foundSkills.length;
56
57
 
57
- if (!hasRegistryYaml && skillDirs.length === 0) {
58
+ if (!hasRegistryYaml && skillCount === 0) {
58
59
  throw new Error('Invalid registry: no registry.yaml and no skill directories found');
59
60
  }
60
61
 
@@ -66,14 +67,14 @@ export function teamCommand(program) {
66
67
  }
67
68
 
68
69
  let registryId = name;
69
- let skillCount = skillDirs.length;
70
+ let finalSkillCount = skillCount;
70
71
 
71
72
  if (hasRegistryYaml) {
72
73
  const registryManager = new RegistryManager(registryCachePath);
73
74
  await registryManager.load();
74
75
  const info = registryManager.getRegistryInfo();
75
76
  registryId = info.registryId;
76
- skillCount = registryManager.getAllSkills().length;
77
+ finalSkillCount = registryManager.getAllSkills().length;
77
78
  }
78
79
 
79
80
  config.registries[name] = {
@@ -88,7 +89,7 @@ export function teamCommand(program) {
88
89
 
89
90
  console.log(chalk.green(`✅ Successfully added registry: ${name}`));
90
91
  console.log(chalk.gray(` Registry ID: ${registryId}`));
91
- console.log(chalk.gray(` Skills: ${skillCount}${hasRegistryYaml ? '' : ' (no registry.yaml, direct scan)'}`));
92
+ console.log(chalk.gray(` Skills: ${finalSkillCount}${hasRegistryYaml ? '' : ' (no registry.yaml, direct scan)'}`));
92
93
 
93
94
  } catch (error) {
94
95
  console.error(chalk.red(`❌ Failed to add registry: ${error.message}`));
@@ -187,18 +187,26 @@ async function getSpecifiedSkills(projectRoot, skillNames) {
187
187
  continue;
188
188
  }
189
189
 
190
+ let teamSource = null;
190
191
  const lockData = lockfile.getSkill(name);
191
192
 
192
193
  if (lockData?.source?.startsWith('team:')) {
194
+ teamSource = lockData.source;
195
+ } else {
196
+ // 尝试从所有 registry 查找
197
+ teamSource = await findSkillInRegistries(name);
198
+ }
199
+
200
+ if (teamSource) {
193
201
  // team:// 类型
194
- const updateInfo = await checkTeamSkillUpdate(skill, lockData.source);
202
+ const updateInfo = await checkTeamSkillUpdate(skill, teamSource);
195
203
  if (updateInfo.hasUpdate) {
196
204
  skills.push({
197
205
  name: skill.name,
198
206
  path: skill.path,
199
207
  currentVersion: skill.version,
200
208
  latestVersion: updateInfo.latestVersion,
201
- source: lockData.source,
209
+ source: teamSource,
202
210
  type: 'team',
203
211
  });
204
212
  }
@@ -265,6 +273,35 @@ async function checkTeamSkillUpdate(skill, source) {
265
273
  };
266
274
  }
267
275
 
276
+ /**
277
+ * 从所有 registry 中查找 skill
278
+ */
279
+ async function findSkillInRegistries(skillName) {
280
+ const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
281
+ const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
282
+
283
+ if (!fs.existsSync(configPath)) {
284
+ return null;
285
+ }
286
+
287
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
288
+ const registries = config.registries || {};
289
+
290
+ for (const [name, reg] of Object.entries(registries)) {
291
+ if (!reg.path || !fs.existsSync(reg.path)) continue;
292
+
293
+ const { findSkills } = await import('./install.js');
294
+ const skills = await findSkills(reg.path);
295
+ const found = skills.find(s => s.name === skillName);
296
+
297
+ if (found) {
298
+ return `team:${name}`;
299
+ }
300
+ }
301
+
302
+ return null;
303
+ }
304
+
268
305
  /**
269
306
  * 检查 Git 类型的 Skill 是否有更新
270
307
  */
@@ -276,14 +313,24 @@ async function checkGitSkillUpdate(skill) {
276
313
  return { hasUpdate: false, error: 'Not a git repository' };
277
314
  }
278
315
 
316
+ // 获取当前 commit
317
+ const currentLog = await git.log({ maxCount: 1 });
318
+ const currentCommit = currentLog.latest?.hash;
319
+
320
+ // Fetch 远程最新
279
321
  await git.fetch(['--depth', '1']);
280
- const log = await git.log({ maxCount: 1 });
281
- const latestCommit = log.latest;
282
322
 
283
- // 简化:如果有 fetch 成功,认为可能有更新
323
+ // 获取远程最新 commit
324
+ const remoteLog = await git.log({ maxCount: 1 });
325
+ const remoteCommit = remoteLog.latest?.hash;
326
+
327
+ // 比较 commit
328
+ const hasUpdate = remoteCommit !== currentCommit;
329
+
284
330
  return {
285
- hasUpdate: true,
286
- latestVersion: latestCommit?.hash?.slice(0, 7) || 'latest',
331
+ hasUpdate,
332
+ latestVersion: remoteCommit?.slice(0, 7) || 'latest',
333
+ currentVersion: currentCommit?.slice(0, 7),
287
334
  };
288
335
  }
289
336
 
@@ -5,8 +5,10 @@
5
5
 
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
+ import * as os from 'os';
8
9
  import simpleGit from 'simple-git';
9
10
  import { SkillLoader } from './skill-loader.js';
11
+ import { LockfileManager } from './lockfile.js';
10
12
 
11
13
  /**
12
14
  * 获取 Skill 的 Git 信息
@@ -77,7 +79,30 @@ export async function checkRemoteUpdate(skillPath, currentCommit) {
77
79
  /**
78
80
  * 检查单个 Skill
79
81
  */
80
- export async function checkSkill(skill) {
82
+ export async function checkSkill(skill, projectRoot) {
83
+ // 首先尝试从 lockfile 获取 source
84
+ let teamSource = null;
85
+
86
+ if (projectRoot) {
87
+ const lockfile = new LockfileManager(projectRoot);
88
+ await lockfile.load();
89
+ const lockData = lockfile.getSkill(skill.name);
90
+ if (lockData?.source?.startsWith('team:')) {
91
+ teamSource = lockData.source;
92
+ }
93
+ }
94
+
95
+ // 如果没有 lockfile 记录,尝试从所有 registry 查找
96
+ if (!teamSource) {
97
+ teamSource = await findSkillInRegistries(skill.name);
98
+ }
99
+
100
+ // 如果是 team:// skill,对比 registry 版本
101
+ if (teamSource) {
102
+ return await checkTeamSkill(skill, teamSource);
103
+ }
104
+
105
+ // Git skill: 对比 commit
81
106
  const gitInfo = await getSkillGitInfo(skill.path);
82
107
 
83
108
  if (!gitInfo) {
@@ -103,6 +128,93 @@ export async function checkSkill(skill) {
103
128
  };
104
129
  }
105
130
 
131
+ /**
132
+ * 从所有 registry 中查找 skill
133
+ */
134
+ async function findSkillInRegistries(skillName) {
135
+ const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
136
+ const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
137
+
138
+ if (!fs.existsSync(configPath)) {
139
+ return null;
140
+ }
141
+
142
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
143
+ const registries = config.registries || {};
144
+
145
+ for (const [name, reg] of Object.entries(registries)) {
146
+ if (!reg.path || !fs.existsSync(reg.path)) continue;
147
+
148
+ const { findSkills } = await import('./commands/install.js');
149
+ const skills = await findSkills(reg.path);
150
+ const found = skills.find(s => s.name === skillName);
151
+
152
+ if (found) {
153
+ return `team:${name}`;
154
+ }
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ /**
161
+ * 检查 team:// skill 更新
162
+ */
163
+ async function checkTeamSkill(skill, source) {
164
+ const registryName = source.replace('team:', '');
165
+ const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
166
+ const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
167
+
168
+ if (!fs.existsSync(configPath)) {
169
+ return {
170
+ name: skill.name,
171
+ hasUpdate: false,
172
+ currentVersion: skill.version,
173
+ source: 'team',
174
+ error: 'No registry config',
175
+ };
176
+ }
177
+
178
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
179
+ const reg = config.registries?.[registryName];
180
+
181
+ if (!reg?.path || !fs.existsSync(reg.path)) {
182
+ return {
183
+ name: skill.name,
184
+ hasUpdate: false,
185
+ currentVersion: skill.version,
186
+ source: 'team',
187
+ error: `Registry ${registryName} not found`,
188
+ };
189
+ }
190
+
191
+ // 从 registry 查找最新版本
192
+ const { findSkills } = await import('./commands/install.js');
193
+ const registrySkills = await findSkills(reg.path);
194
+ const registrySkill = registrySkills.find(s => s.name === skill.name);
195
+
196
+ if (!registrySkill) {
197
+ return {
198
+ name: skill.name,
199
+ hasUpdate: false,
200
+ currentVersion: skill.version,
201
+ source: 'team',
202
+ error: 'Skill not found in registry',
203
+ };
204
+ }
205
+
206
+ const hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
207
+
208
+ return {
209
+ name: skill.name,
210
+ hasUpdate,
211
+ currentVersion: skill.version,
212
+ latestVersion: registrySkill.version,
213
+ commitsBehind: hasUpdate ? 1 : 0, // 版本不同即视为有更新
214
+ source: `team:${registryName}`,
215
+ };
216
+ }
217
+
106
218
  /**
107
219
  * 检查所有 Skills
108
220
  */
@@ -113,7 +225,7 @@ export async function checkAllSkills(projectRoot) {
113
225
  const results = [];
114
226
 
115
227
  for (const skill of skills) {
116
- const result = await checkSkill(skill);
228
+ const result = await checkSkill(skill, projectRoot);
117
229
  results.push(result);
118
230
  }
119
231