joyskills-cli 0.3.3 → 0.3.4

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.3",
3
+ "version": "0.3.4",
4
4
  "description": "JoySkills CLI v2.0 - Multi-agent skill management with JoyCode native support",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -14,6 +14,22 @@ import chalk from 'chalk';
14
14
  // Cache directory for cloned repos
15
15
  const CACHE_DIR = process.env.JOYSKILL_CACHE_DIR || path.join(os.homedir(), '.joyskill', 'cache');
16
16
 
17
+ /**
18
+ * Get git commit hash for a directory
19
+ */
20
+ async function getGitCommit(dirPath) {
21
+ try {
22
+ const git = simpleGit(dirPath);
23
+ const isRepo = await git.checkIsRepo();
24
+ if (!isRepo) return null;
25
+
26
+ const log = await git.log({ maxCount: 1 });
27
+ return log.latest?.hash || null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
17
33
  export function installCommand(program) {
18
34
  program
19
35
  .command('install [skill]')
@@ -252,6 +268,9 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
252
268
  const installInfo = fs.lstatSync(targetPath);
253
269
  const installMethod = installInfo.isSymbolicLink() ? 'symlink' : 'copy';
254
270
 
271
+ // Get git commit for the skill source
272
+ const commitHash = await getGitCommit(sourcePath);
273
+
255
274
  const lockfileManager = new LockfileManager(projectRoot);
256
275
  await lockfileManager.load();
257
276
  lockfileManager.updateSkill(skillName, {
@@ -259,6 +278,7 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
259
278
  source: sourceLabel,
260
279
  registry: registryManager.getRegistryInfo().registryId,
261
280
  installMethod,
281
+ commit: commitHash,
262
282
  installedAt: new Date().toISOString()
263
283
  });
264
284
  await lockfileManager.save();
@@ -279,11 +299,15 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
279
299
  const targetPath = path.join(targetDir, skillName);
280
300
  copyRecursive(skill.path, targetPath);
281
301
 
302
+ // Get git commit for the skill source
303
+ const commitHash = await getGitCommit(skill.path);
304
+
282
305
  const lockfileManager = new LockfileManager(projectRoot);
283
306
  await lockfileManager.load();
284
307
  lockfileManager.updateSkill(skillName, {
285
308
  version: skill.version || '1.0.0',
286
309
  source: sourceLabel,
310
+ commit: commitHash,
287
311
  installedAt: new Date().toISOString()
288
312
  });
289
313
  await lockfileManager.save();
@@ -119,6 +119,22 @@ async function getAllUpdatableSkills(projectRoot) {
119
119
  return updatable;
120
120
  }
121
121
 
122
+ /**
123
+ * 获取目录的 git commit hash
124
+ */
125
+ async function getDirCommit(dirPath) {
126
+ try {
127
+ const git = simpleGit(dirPath);
128
+ const isRepo = await git.checkIsRepo();
129
+ if (!isRepo) return null;
130
+
131
+ const log = await git.log({ maxCount: 1 });
132
+ return log.latest?.hash || null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
122
138
  /**
123
139
  * 更新 team:// 类型的 Skill
124
140
  */
@@ -126,7 +142,10 @@ async function updateTeamSkill(skill, projectRoot) {
126
142
  const lockfile = new LockfileManager(projectRoot);
127
143
  await lockfile.load();
128
144
 
129
- const updateInfo = await checkTeamSkillUpdate(skill, skill.source);
145
+ const lockData = lockfile.getSkill(skill.name);
146
+ const localCommit = lockData?.commit;
147
+
148
+ const updateInfo = await checkTeamSkillUpdate(skill, skill.source, localCommit);
130
149
 
131
150
  if (!updateInfo.hasUpdate) {
132
151
  throw new Error('No update available');
@@ -140,10 +159,14 @@ async function updateTeamSkill(skill, projectRoot) {
140
159
  // 复制新版本
141
160
  copyRecursive(updateInfo.registrySkillPath, skill.path);
142
161
 
162
+ // 获取新版本的 commit
163
+ const newCommit = await getDirCommit(updateInfo.registrySkillPath);
164
+
143
165
  // 更新 lockfile
144
166
  lockfile.updateSkill(skill.name, {
145
167
  version: updateInfo.latestVersion,
146
168
  source: skill.source,
169
+ commit: newCommit,
147
170
  updatedAt: new Date().toISOString(),
148
171
  });
149
172
  await lockfile.save();
@@ -199,7 +222,8 @@ async function getSpecifiedSkills(projectRoot, skillNames) {
199
222
 
200
223
  if (teamSource) {
201
224
  // team:// 类型
202
- const updateInfo = await checkTeamSkillUpdate(skill, teamSource);
225
+ const localCommit = lockData?.commit;
226
+ const updateInfo = await checkTeamSkillUpdate(skill, teamSource, localCommit);
203
227
  if (updateInfo.hasUpdate) {
204
228
  skills.push({
205
229
  name: skill.name,
@@ -238,7 +262,7 @@ async function getSpecifiedSkills(projectRoot, skillNames) {
238
262
  /**
239
263
  * 检查 team:// 类型的 Skill 是否有更新
240
264
  */
241
- async function checkTeamSkillUpdate(skill, source) {
265
+ async function checkTeamSkillUpdate(skill, source, localCommit = null) {
242
266
  const registryName = source.replace('team:', '');
243
267
  const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
244
268
  const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
@@ -254,7 +278,7 @@ async function checkTeamSkillUpdate(skill, source) {
254
278
  return { hasUpdate: false, error: `Registry ${registryName} not found` };
255
279
  }
256
280
 
257
- // 从 registry 查找最新版本
281
+ // 从 registry 查找 skill
258
282
  const { findSkills } = await import('./install.js');
259
283
  const registrySkills = await findSkills(reg.path);
260
284
  const registrySkill = registrySkills.find(s => s.name === skill.name);
@@ -263,11 +287,22 @@ async function checkTeamSkillUpdate(skill, source) {
263
287
  return { hasUpdate: false, error: 'Skill not found in registry' };
264
288
  }
265
289
 
266
- const hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
290
+ // 获取 registry skill commit
291
+ const remoteCommit = await getDirCommit(registrySkill.path);
292
+
293
+ // 使用 commit 对比,如果没有 commit 则 fallback 到 version 对比
294
+ let hasUpdate = false;
295
+ if (localCommit && remoteCommit) {
296
+ hasUpdate = localCommit !== remoteCommit;
297
+ } else {
298
+ hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
299
+ }
267
300
 
268
301
  return {
269
302
  hasUpdate,
270
303
  latestVersion: registrySkill.version,
304
+ localCommit,
305
+ remoteCommit,
271
306
  registryPath: reg.path,
272
307
  registrySkillPath: registrySkill.path,
273
308
  };
@@ -348,7 +383,8 @@ async function getUpdatableTeamSkills(projectRoot) {
348
383
  for (const skill of allSkills) {
349
384
  const lockData = lockfile.getSkill(skill.name);
350
385
  if (lockData?.source?.startsWith('team:')) {
351
- const updateInfo = await checkTeamSkillUpdate(skill, lockData.source);
386
+ const localCommit = lockData?.commit;
387
+ const updateInfo = await checkTeamSkillUpdate(skill, lockData.source, localCommit);
352
388
  if (updateInfo.hasUpdate) {
353
389
  updatable.push({
354
390
  name: skill.name,
@@ -80,8 +80,9 @@ export async function checkRemoteUpdate(skillPath, currentCommit) {
80
80
  * 检查单个 Skill
81
81
  */
82
82
  export async function checkSkill(skill, projectRoot) {
83
- // 首先尝试从 lockfile 获取 source
83
+ // 首先尝试从 lockfile 获取 source 和 commit
84
84
  let teamSource = null;
85
+ let localCommit = null;
85
86
 
86
87
  if (projectRoot) {
87
88
  const lockfile = new LockfileManager(projectRoot);
@@ -89,6 +90,7 @@ export async function checkSkill(skill, projectRoot) {
89
90
  const lockData = lockfile.getSkill(skill.name);
90
91
  if (lockData?.source?.startsWith('team:')) {
91
92
  teamSource = lockData.source;
93
+ localCommit = lockData.commit;
92
94
  }
93
95
  }
94
96
 
@@ -97,9 +99,9 @@ export async function checkSkill(skill, projectRoot) {
97
99
  teamSource = await findSkillInRegistries(skill.name);
98
100
  }
99
101
 
100
- // 如果是 team:// skill,对比 registry 版本
102
+ // 如果是 team:// skill,对比 registry commit
101
103
  if (teamSource) {
102
- return await checkTeamSkill(skill, teamSource);
104
+ return await checkTeamSkill(skill, teamSource, localCommit);
103
105
  }
104
106
 
105
107
  // Git skill: 对比 commit
@@ -157,10 +159,26 @@ async function findSkillInRegistries(skillName) {
157
159
  return null;
158
160
  }
159
161
 
162
+ /**
163
+ * 获取目录的 git commit hash
164
+ */
165
+ async function getDirCommit(dirPath) {
166
+ try {
167
+ const git = simpleGit(dirPath);
168
+ const isRepo = await git.checkIsRepo();
169
+ if (!isRepo) return null;
170
+
171
+ const log = await git.log({ maxCount: 1 });
172
+ return log.latest?.hash || null;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+
160
178
  /**
161
179
  * 检查 team:// skill 更新
162
180
  */
163
- async function checkTeamSkill(skill, source) {
181
+ async function checkTeamSkill(skill, source, localCommit) {
164
182
  const registryName = source.replace('team:', '');
165
183
  const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
166
184
  const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
@@ -188,7 +206,7 @@ async function checkTeamSkill(skill, source) {
188
206
  };
189
207
  }
190
208
 
191
- // 从 registry 查找最新版本
209
+ // 从 registry 查找 skill
192
210
  const { findSkills } = await import('./commands/install.js');
193
211
  const registrySkills = await findSkills(reg.path);
194
212
  const registrySkill = registrySkills.find(s => s.name === skill.name);
@@ -203,14 +221,24 @@ async function checkTeamSkill(skill, source) {
203
221
  };
204
222
  }
205
223
 
206
- const hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
224
+ // 获取 registry skill 的最新 commit
225
+ const remoteCommit = await getDirCommit(registrySkill.path);
226
+
227
+ // 如果没有 localCommit(旧版本安装的),使用 version 对比作为 fallback
228
+ let hasUpdate = false;
229
+ if (localCommit && remoteCommit) {
230
+ hasUpdate = localCommit !== remoteCommit;
231
+ } else {
232
+ hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
233
+ }
207
234
 
208
235
  return {
209
236
  name: skill.name,
210
237
  hasUpdate,
211
238
  currentVersion: skill.version,
212
- latestVersion: registrySkill.version,
213
- commitsBehind: hasUpdate ? 1 : 0, // 版本不同即视为有更新
239
+ localCommit: typeof localCommit === 'string' ? localCommit.slice(0, 7) : 'unknown',
240
+ remoteCommit: typeof remoteCommit === 'string' ? remoteCommit.slice(0, 7) : 'unknown',
241
+ commitsBehind: hasUpdate ? 1 : 0,
214
242
  source: `team:${registryName}`,
215
243
  };
216
244
  }