joyskills-cli 0.3.4 → 0.3.6

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.4",
3
+ "version": "0.3.6",
4
4
  "description": "JoySkills CLI v2.0 - Multi-agent skill management with JoyCode native support",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3,7 +3,8 @@
3
3
  */
4
4
 
5
5
  import { Command } from 'commander';
6
- import { checkAllSkills, getUpdatableSkills } from '../version-checker.js';
6
+ import { checkAllSkills, getUpdatableSkills, checkSkill } from '../version-checker.js';
7
+ import { SkillLoader } from '../skill-loader.js';
7
8
  import chalk from 'chalk';
8
9
 
9
10
  export function checkCommand(program) {
@@ -11,6 +12,8 @@ export function checkCommand(program) {
11
12
  .command('check')
12
13
  .description('Check for available skill updates')
13
14
  .option('-u, --updatable', 'Show only skills with updates')
15
+ .option('-g, --global', 'Check global (user-level) skills only')
16
+ .option('-p, --project', 'Check project-level skills only')
14
17
  .action(async (options) => {
15
18
  try {
16
19
  const projectRoot = process.cwd();
@@ -19,8 +22,8 @@ export function checkCommand(program) {
19
22
  console.log(chalk.gray('==============================\n'));
20
23
 
21
24
  const results = options.updatable
22
- ? await getUpdatableSkills(projectRoot)
23
- : await checkAllSkills(projectRoot);
25
+ ? await getUpdatableSkills(projectRoot, options.global, options.project)
26
+ : await checkAllSkills(projectRoot, options.global, options.project);
24
27
 
25
28
  if (results.length === 0) {
26
29
  if (options.updatable) {
@@ -8,12 +8,107 @@ import simpleGit from 'simple-git';
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import * as os from 'os';
11
- import { select, confirm } from '@inquirer/prompts';
11
+ import { select, confirm, input } from '@inquirer/prompts';
12
12
  import chalk from 'chalk';
13
13
 
14
+ const JOYSKILL_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
15
+ const REGISTRY_CACHE_DIR = path.join(JOYSKILL_DIR, 'registries');
16
+
14
17
  // Cache directory for cloned repos
15
18
  const CACHE_DIR = process.env.JOYSKILL_CACHE_DIR || path.join(os.homedir(), '.joyskill', 'cache');
16
19
 
20
+ /**
21
+ * Check if a registry exists in global config
22
+ */
23
+ function registryExists(registryName) {
24
+ const configPath = path.join(JOYSKILL_DIR, 'config.json');
25
+ if (!fs.existsSync(configPath)) return false;
26
+
27
+ try {
28
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
29
+ return config.registries && config.registries[registryName];
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Add a registry to global config (used by auto-prompt)
37
+ */
38
+ async function addRegistryInteractive(registryName, suggestedUrl = null) {
39
+ console.log(chalk.yellow(`\n⚠️ Registry '${registryName}' not found in local config.`));
40
+ console.log(chalk.gray(` This skill is from a team registry. Please add it first.`));
41
+
42
+ let gitUrl = suggestedUrl;
43
+
44
+ if (!gitUrl) {
45
+ gitUrl = await input({
46
+ message: `Enter the Git URL for registry '${registryName}':`,
47
+ validate: (value) => {
48
+ if (!value) return 'Git URL is required';
49
+ if (!value.includes('@') && !value.startsWith('http')) {
50
+ return 'Please enter a valid Git URL (SSH or HTTPS)';
51
+ }
52
+ return true;
53
+ }
54
+ });
55
+ }
56
+
57
+ console.log(chalk.blue(`\n📦 Adding team registry: ${registryName}`));
58
+ console.log(chalk.gray(` URL: ${gitUrl}`));
59
+
60
+ // Clone registry to cache
61
+ const registryCachePath = path.join(REGISTRY_CACHE_DIR, registryName);
62
+
63
+ if (fs.existsSync(registryCachePath)) {
64
+ fs.rmSync(registryCachePath, { recursive: true, force: true });
65
+ }
66
+
67
+ fs.mkdirSync(registryCachePath, { recursive: true });
68
+
69
+ // Clone the registry
70
+ const git = simpleGit();
71
+ await git.clone(gitUrl, registryCachePath, ['--depth', '1']);
72
+
73
+ // Save to global config
74
+ const configPath = path.join(JOYSKILL_DIR, 'config.json');
75
+ let config = { registries: {} };
76
+ if (fs.existsSync(configPath)) {
77
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
78
+ }
79
+
80
+ config.registries[registryName] = {
81
+ url: gitUrl,
82
+ path: registryCachePath,
83
+ addedAt: new Date().toISOString()
84
+ };
85
+
86
+ fs.mkdirSync(JOYSKILL_DIR, { recursive: true });
87
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
88
+
89
+ console.log(chalk.green(` ✓ Added registry '${registryName}'`));
90
+
91
+ return registryCachePath;
92
+ }
93
+
94
+ /**
95
+ * Get registry path with auto-prompt for missing registries
96
+ */
97
+ async function getRegistryPathWithPrompt(registryName, options = {}) {
98
+ if (!registryExists(registryName)) {
99
+ if (options.yes || process.env.CI) {
100
+ throw new Error(`Registry '${registryName}' not found. Run: joySkills team add ${registryName} <git-url>`);
101
+ }
102
+
103
+ // Auto-prompt to add registry
104
+ await addRegistryInteractive(registryName);
105
+ }
106
+
107
+ const configPath = path.join(JOYSKILL_DIR, 'config.json');
108
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
109
+ return config.registries[registryName].path;
110
+ }
111
+
17
112
  /**
18
113
  * Get git commit hash for a directory
19
114
  */
@@ -147,21 +242,10 @@ async function resolveAndInstall(skillInput, targetDir, projectRoot, options) {
147
242
  throw new Error(`Invalid team:// URL format: ${skillInput}. Expected: team://<registry>/<skill>`);
148
243
  }
149
244
 
150
- // Load registry config
151
- const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
152
- if (!fs.existsSync(configPath)) {
153
- throw new Error(`No registries configured. Run: joySkills team add <name> <git-url>`);
154
- }
155
-
156
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
157
- const registries = config.registries || {};
158
- const reg = registries[registryName];
245
+ // Get registry path with auto-prompt for missing registries
246
+ const registryPath = await getRegistryPathWithPrompt(registryName, options);
159
247
 
160
- if (!reg?.path || !fs.existsSync(reg.path)) {
161
- throw new Error(`Registry "${registryName}" not found. Run: joySkills team list`);
162
- }
163
-
164
- const hit = await tryInstallFromRegistryDir(skillName, reg.path, targetDir, projectRoot, options, `team:${registryName}`);
248
+ const hit = await tryInstallFromRegistryDir(skillName, registryPath, targetDir, projectRoot, options, `team:${registryName}`);
165
249
  if (!hit) {
166
250
  throw new Error(`Skill "${skillName}" not found in registry "${registryName}"`);
167
251
  }
@@ -218,16 +302,19 @@ async function resolveAndInstall(skillInput, targetDir, projectRoot, options) {
218
302
 
219
303
  const skillMdPath = path.join(skillPath, 'SKILL.md');
220
304
  fs.writeFileSync(skillMdPath, generateSkillTemplate(skillInput));
221
-
222
- const lockfileManager = new LockfileManager(projectRoot);
223
- await lockfileManager.load();
224
- lockfileManager.updateSkill(skillInput, {
225
- version: '1.0.0',
226
- source: 'template',
227
- path: skillPath,
228
- installedAt: new Date().toISOString()
229
- });
230
- await lockfileManager.save();
305
+
306
+ // Update lockfile only for project-level installation (npm-like behavior)
307
+ if (!options.global) {
308
+ const lockfileManager = new LockfileManager(projectRoot);
309
+ await lockfileManager.load();
310
+ lockfileManager.updateSkill(skillInput, {
311
+ version: '1.0.0',
312
+ source: 'template',
313
+ path: skillPath,
314
+ installedAt: new Date().toISOString()
315
+ });
316
+ await lockfileManager.save();
317
+ }
231
318
  console.log(chalk.green(`✅ Created template for ${skillInput} in ${targetDir}`));
232
319
  }
233
320
 
@@ -271,17 +358,20 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
271
358
  // Get git commit for the skill source
272
359
  const commitHash = await getGitCommit(sourcePath);
273
360
 
274
- const lockfileManager = new LockfileManager(projectRoot);
275
- await lockfileManager.load();
276
- lockfileManager.updateSkill(skillName, {
277
- version,
278
- source: sourceLabel,
279
- registry: registryManager.getRegistryInfo().registryId,
280
- installMethod,
281
- commit: commitHash,
282
- installedAt: new Date().toISOString()
283
- });
284
- await lockfileManager.save();
361
+ // Update lockfile only for project-level installation (npm-like behavior)
362
+ if (!options.global) {
363
+ const lockfileManager = new LockfileManager(projectRoot);
364
+ await lockfileManager.load();
365
+ lockfileManager.updateSkill(skillName, {
366
+ version,
367
+ source: sourceLabel,
368
+ registry: registryManager.getRegistryInfo().registryId,
369
+ installMethod,
370
+ commit: commitHash,
371
+ installedAt: new Date().toISOString()
372
+ });
373
+ await lockfileManager.save();
374
+ }
285
375
 
286
376
  const methodLabel = installMethod === 'symlink' ? chalk.gray('(symlink)') : chalk.gray('(copy)');
287
377
  console.log(chalk.green(`✅ Installed ${skillName} v${version} from ${sourceLabel}`) + ' ' + methodLabel);
@@ -302,15 +392,18 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
302
392
  // Get git commit for the skill source
303
393
  const commitHash = await getGitCommit(skill.path);
304
394
 
305
- const lockfileManager = new LockfileManager(projectRoot);
306
- await lockfileManager.load();
307
- lockfileManager.updateSkill(skillName, {
308
- version: skill.version || '1.0.0',
309
- source: sourceLabel,
310
- commit: commitHash,
311
- installedAt: new Date().toISOString()
312
- });
313
- await lockfileManager.save();
395
+ // Update lockfile only for project-level installation (npm-like behavior)
396
+ if (!options.global) {
397
+ const lockfileManager = new LockfileManager(projectRoot);
398
+ await lockfileManager.load();
399
+ lockfileManager.updateSkill(skillName, {
400
+ version: skill.version || '1.0.0',
401
+ source: sourceLabel,
402
+ commit: commitHash,
403
+ installedAt: new Date().toISOString()
404
+ });
405
+ await lockfileManager.save();
406
+ }
314
407
 
315
408
  console.log(chalk.green(`✅ Installed ${skillName} v${skill.version || '1.0.0'} from ${sourceLabel}`));
316
409
  return true;
@@ -377,15 +470,18 @@ async function installFromLocalPath(localPath, targetDir, options) {
377
470
  const versionMatch = content.match(/version:\s*(.+)/);
378
471
  const version = versionMatch?.[1]?.trim() || '1.0.0';
379
472
 
380
- const lockfileManager = new LockfileManager(process.cwd());
381
- await lockfileManager.load();
382
- lockfileManager.updateSkill(skillName, {
383
- version,
384
- source: 'local',
385
- path: absPath,
386
- installedAt: new Date().toISOString()
387
- });
388
- await lockfileManager.save();
473
+ // Update lockfile only for project-level installation (npm-like behavior)
474
+ if (!options.global) {
475
+ const lockfileManager = new LockfileManager(process.cwd());
476
+ await lockfileManager.load();
477
+ lockfileManager.updateSkill(skillName, {
478
+ version,
479
+ source: 'local',
480
+ path: absPath,
481
+ installedAt: new Date().toISOString()
482
+ });
483
+ await lockfileManager.save();
484
+ }
389
485
 
390
486
  console.log(chalk.green(`✅ Installed ${skillName} v${version} from local path`));
391
487
  } else {
@@ -413,20 +509,23 @@ async function installFromLocalPath(localPath, targetDir, options) {
413
509
  });
414
510
  }
415
511
 
416
- const lockfileManager = new LockfileManager(process.cwd());
417
- await lockfileManager.load();
512
+ // Update lockfile only for project-level installation (npm-like behavior)
513
+ const lockfileManager = options.global ? null : new LockfileManager(process.cwd());
514
+ if (lockfileManager) await lockfileManager.load();
418
515
 
419
516
  for (const skill of selectedSkills) {
420
517
  await installSkillFiles(skill, targetDir);
421
- lockfileManager.updateSkill(skill.name, {
422
- version: skill.version || '1.0.0',
423
- source: 'local',
424
- path: skill.path,
425
- installedAt: new Date().toISOString()
426
- });
518
+ if (lockfileManager) {
519
+ lockfileManager.updateSkill(skill.name, {
520
+ version: skill.version || '1.0.0',
521
+ source: 'local',
522
+ path: skill.path,
523
+ installedAt: new Date().toISOString()
524
+ });
525
+ }
427
526
  }
428
527
 
429
- await lockfileManager.save();
528
+ if (lockfileManager) await lockfileManager.save();
430
529
  console.log(chalk.green(`\n✅ Installed ${selectedSkills.length} skill(s)`));
431
530
  }
432
531
  } else {
@@ -499,17 +598,20 @@ async function installFromGitUrl(gitUrl, targetDir, options, subPath = '') {
499
598
  await installSkillFiles(skill, targetDir);
500
599
  }
501
600
 
502
- const lockfileManager = new LockfileManager(process.cwd());
503
- await lockfileManager.load();
504
- for (const skill of selectedSkills) {
505
- lockfileManager.updateSkill(skill.name, {
506
- version: skill.version || '1.0.0',
507
- source: 'git',
508
- repository: gitUrl,
509
- installedAt: new Date().toISOString()
510
- });
601
+ // Update lockfile only for project-level installation (npm-like behavior)
602
+ if (!options.global) {
603
+ const lockfileManager = new LockfileManager(process.cwd());
604
+ await lockfileManager.load();
605
+ for (const skill of selectedSkills) {
606
+ lockfileManager.updateSkill(skill.name, {
607
+ version: skill.version || '1.0.0',
608
+ source: 'git',
609
+ repository: gitUrl,
610
+ installedAt: new Date().toISOString()
611
+ });
612
+ }
613
+ await lockfileManager.save();
511
614
  }
512
- await lockfileManager.save();
513
615
 
514
616
  console.log(chalk.green(`\n✅ Successfully installed ${selectedSkills.length} skill(s)`));
515
617
  }
@@ -590,18 +692,20 @@ async function installFromGitHub(owner, repo, skillPath, targetDir, options) {
590
692
  await installSkillFiles(skill, targetDir);
591
693
  }
592
694
 
593
- // Update lockfile
594
- const lockfileManager = new LockfileManager(process.cwd());
595
- await lockfileManager.load();
596
- for (const skill of selectedSkills) {
597
- lockfileManager.updateSkill(skill.name, {
598
- version: skill.version || '1.0.0',
599
- source: 'github',
600
- repository: `${owner}/${repo}`,
601
- installedAt: new Date().toISOString()
602
- });
695
+ // Update lockfile only for project-level installation (npm-like behavior)
696
+ if (!options.global) {
697
+ const lockfileManager = new LockfileManager(process.cwd());
698
+ await lockfileManager.load();
699
+ for (const skill of selectedSkills) {
700
+ lockfileManager.updateSkill(skill.name, {
701
+ version: skill.version || '1.0.0',
702
+ source: 'github',
703
+ repository: `${owner}/${repo}`,
704
+ installedAt: new Date().toISOString()
705
+ });
706
+ }
707
+ await lockfileManager.save();
603
708
  }
604
- await lockfileManager.save();
605
709
 
606
710
  console.log(chalk.green(`\n✅ Successfully installed ${selectedSkills.length} skill(s)`));
607
711
  }
@@ -647,16 +751,19 @@ async function installFromRegistry(skillName, targetDir, projectRoot, options) {
647
751
  const sourcePath = path.join(registryPath, skill.location || skillName);
648
752
  if (fs.existsSync(sourcePath)) {
649
753
  copyRecursive(sourcePath, skillTargetPath);
650
-
651
- await lockfileManager.load();
652
- lockfileManager.updateSkill(skillName, {
653
- version,
654
- source: 'registry',
655
- registry: registryManager.getRegistryInfo().registryId,
656
- installedAt: new Date().toISOString()
657
- });
658
- await lockfileManager.save();
659
-
754
+
755
+ // Update lockfile only for project-level installation (npm-like behavior)
756
+ if (!options.global) {
757
+ await lockfileManager.load();
758
+ lockfileManager.updateSkill(skillName, {
759
+ version,
760
+ source: 'registry',
761
+ registry: registryManager.getRegistryInfo().registryId,
762
+ installedAt: new Date().toISOString()
763
+ });
764
+ await lockfileManager.save();
765
+ }
766
+
660
767
  console.log(chalk.green(`✅ Installed ${skillName} v${version} from registry`));
661
768
  return;
662
769
  }
@@ -667,14 +774,17 @@ async function installFromRegistry(skillName, targetDir, projectRoot, options) {
667
774
  console.log(chalk.yellow(`⚠️ Skill not found in registry, creating template...`));
668
775
  const templateContent = generateSkillTemplate(skillName);
669
776
  localManager.installSkill(skillName, templateContent);
670
-
671
- await lockfileManager.load();
672
- lockfileManager.updateSkill(skillName, {
673
- version: '1.0.0',
674
- source: 'template',
675
- installedAt: new Date().toISOString()
676
- });
677
- await lockfileManager.save();
777
+
778
+ // Update lockfile only for project-level installation (npm-like behavior)
779
+ if (!options.global) {
780
+ await lockfileManager.load();
781
+ lockfileManager.updateSkill(skillName, {
782
+ version: '1.0.0',
783
+ source: 'template',
784
+ installedAt: new Date().toISOString()
785
+ });
786
+ await lockfileManager.save();
787
+ }
678
788
 
679
789
  console.log(chalk.green(`✅ Created template for ${skillName}`));
680
790
  }
@@ -703,6 +813,15 @@ async function installFromLockfile(targetDir, projectRoot) {
703
813
  } else if (skillInfo.source === 'github' && skillInfo.repository) {
704
814
  const [owner, repo] = skillInfo.repository.split('/');
705
815
  await installFromGitHub(owner, repo, skillName, targetDir, {});
816
+ } else if (skillInfo.source?.startsWith('team:')) {
817
+ // Re-install from team registry with auto-prompt
818
+ const registryName = skillInfo.source.replace('team:', '');
819
+ const registryPath = await getRegistryPathWithPrompt(registryName, {});
820
+ const hit = await tryInstallFromRegistryDir(skillName, registryPath, targetDir, projectRoot, {}, skillInfo.source);
821
+ if (!hit) {
822
+ console.log(chalk.yellow(` ⚠️ Skill "${skillName}" not found in registry "${registryName}", trying resolve chain...`));
823
+ await resolveAndInstall(skillName, targetDir, projectRoot, {});
824
+ }
706
825
  } else {
707
826
  // Try full resolve chain
708
827
  await resolveAndInstall(skillName, targetDir, projectRoot, {});
@@ -68,12 +68,28 @@ export function syncCommand(program) {
68
68
  await lockfileManager.load();
69
69
  for (const skill of allSkills) {
70
70
  const existing = lockfileManager.getSkill(skill.name);
71
- lockfileManager.updateSkill(skill.name, {
71
+
72
+ // Merge existing data with updates, preserving all original fields
73
+ const updatedEntry = {
74
+ ...existing, // Keep all existing fields (registry, commit, etc.)
72
75
  version: skill.version || existing?.version || '1.0.0',
73
- source: existing?.source || 'local',
74
- installedAt: existing?.installedAt || new Date().toISOString(),
75
- path: skill.path
76
- });
76
+ path: skill.path, // Update path (may change if skill moved)
77
+ };
78
+
79
+ // Set source if not exists
80
+ if (!updatedEntry.source) {
81
+ updatedEntry.source = 'local';
82
+ }
83
+
84
+ // Set installedAt if not exists
85
+ if (!updatedEntry.installedAt) {
86
+ updatedEntry.installedAt = new Date().toISOString();
87
+ }
88
+
89
+ // Add syncedAt timestamp
90
+ updatedEntry.syncedAt = new Date().toISOString();
91
+
92
+ lockfileManager.updateSkill(skill.name, updatedEntry);
77
93
  }
78
94
  await lockfileManager.save();
79
95
  console.log(chalk.green(` ✓ Updated joySkills.lock`));
@@ -19,6 +19,8 @@ export function updateCommand(program) {
19
19
  .description('Update installed skills to latest versions')
20
20
  .option('-y, --yes', 'Skip confirmation prompts')
21
21
  .option('-a, --all', 'Update all skills')
22
+ .option('-g, --global', 'Update global (user-level) skills only')
23
+ .option('-p, --project', 'Update project-level skills only')
22
24
  .action(async (skillNames, options) => {
23
25
  try {
24
26
  const projectRoot = process.cwd();
@@ -31,10 +33,10 @@ export function updateCommand(program) {
31
33
 
32
34
  if (options.all || skillNames.length === 0) {
33
35
  // 更新所有有更新的 skills(Git + team://)
34
- skillsToUpdate = await getAllUpdatableSkills(projectRoot);
36
+ skillsToUpdate = await getAllUpdatableSkills(projectRoot, options.global, options.project);
35
37
  } else {
36
38
  // 更新指定的 skills
37
- skillsToUpdate = await getSpecifiedSkills(projectRoot, skillNames);
39
+ skillsToUpdate = await getSpecifiedSkills(projectRoot, skillNames, options.global, options.project);
38
40
  }
39
41
 
40
42
  if (skillsToUpdate.length === 0) {
@@ -104,16 +106,18 @@ export function updateCommand(program) {
104
106
 
105
107
  /**
106
108
  * 获取所有可更新的 Skills(Git + team://)
109
+ * @param {boolean} globalOnly - 只检查全局 skills
110
+ * @param {boolean} projectOnly - 只检查项目级 skills
107
111
  */
108
- async function getAllUpdatableSkills(projectRoot) {
112
+ async function getAllUpdatableSkills(projectRoot, globalOnly = false, projectOnly = false) {
109
113
  const updatable = [];
110
114
 
111
115
  // 1. 检查 Git 仓库类型的 Skills
112
- const gitSkills = await getUpdatableSkills(projectRoot);
116
+ const gitSkills = await getUpdatableSkills(projectRoot, globalOnly, projectOnly);
113
117
  updatable.push(...gitSkills);
114
118
 
115
119
  // 2. 检查 team:// 类型的 Skills
116
- const teamSkills = await getUpdatableTeamSkills(projectRoot);
120
+ const teamSkills = await getUpdatableTeamSkills(projectRoot, globalOnly, projectOnly);
117
121
  updatable.push(...teamSkills);
118
122
 
119
123
  return updatable;
@@ -196,15 +200,27 @@ function copyRecursive(src, dest) {
196
200
 
197
201
  /**
198
202
  * 获取指定的 Skills
203
+ * @param {boolean} globalOnly - 只检查全局 skills
204
+ * @param {boolean} projectOnly - 只检查项目级 skills
199
205
  */
200
- async function getSpecifiedSkills(projectRoot, skillNames) {
206
+ async function getSpecifiedSkills(projectRoot, skillNames, globalOnly = false, projectOnly = false) {
201
207
  const skills = [];
202
208
  const loader = new SkillLoader(projectRoot);
203
209
  const lockfile = new LockfileManager(projectRoot);
204
210
  await lockfile.load();
205
211
 
212
+ // 根据选项选择加载方式
213
+ let allSkills = [];
214
+ if (globalOnly) {
215
+ allSkills = loader.loadGlobalSkills();
216
+ } else if (projectOnly) {
217
+ allSkills = loader.loadProjectSkills();
218
+ } else {
219
+ allSkills = loader.loadAllSkills();
220
+ }
221
+
206
222
  for (const name of skillNames) {
207
- const skill = loader.getSkill(name);
223
+ const skill = allSkills.find(s => s.name === name);
208
224
  if (!skill) {
209
225
  console.log(chalk.red(`❌ Skill not found: ${name}`));
210
226
  continue;
@@ -371,14 +387,23 @@ async function checkGitSkillUpdate(skill) {
371
387
 
372
388
  /**
373
389
  * 获取所有可更新的 team:// Skills
390
+ * @param {boolean} globalOnly - 只检查全局 skills
391
+ * @param {boolean} projectOnly - 只检查项目级 skills
374
392
  */
375
- async function getUpdatableTeamSkills(projectRoot) {
393
+ async function getUpdatableTeamSkills(projectRoot, globalOnly = false, projectOnly = false) {
376
394
  const updatable = [];
377
395
  const loader = new SkillLoader(projectRoot);
378
396
  const lockfile = new LockfileManager(projectRoot);
379
397
  await lockfile.load();
380
398
 
381
- const allSkills = loader.loadAllSkills();
399
+ let allSkills = [];
400
+ if (globalOnly) {
401
+ allSkills = loader.loadGlobalSkills();
402
+ } else if (projectOnly) {
403
+ allSkills = loader.loadProjectSkills();
404
+ } else {
405
+ allSkills = loader.loadAllSkills();
406
+ }
382
407
 
383
408
  for (const skill of allSkills) {
384
409
  const lockData = lockfile.getSkill(skill.name);
@@ -129,16 +129,32 @@ export class SkillLoader {
129
129
  * 仅加载项目级 Skills
130
130
  */
131
131
  loadProjectSkills(agentId = null) {
132
- const allSkills = this.loadAllSkills(agentId);
133
- return allSkills.filter(s => s.scope === 'project');
132
+ const paths = getAllSkillPaths(this.projectRoot, agentId);
133
+ const skills = [];
134
+
135
+ for (const { path: dirPath, scope, agent } of paths) {
136
+ if (scope !== 'project') continue;
137
+ const dirSkills = this.scanDirectory(dirPath, scope, agent);
138
+ skills.push(...dirSkills);
139
+ }
140
+
141
+ return skills;
134
142
  }
135
143
 
136
144
  /**
137
145
  * 仅加载用户级 Skills
138
146
  */
139
147
  loadGlobalSkills(agentId = null) {
140
- const allSkills = this.loadAllSkills(agentId);
141
- return allSkills.filter(s => s.scope === 'global');
148
+ const paths = getAllSkillPaths(this.projectRoot, agentId);
149
+ const skills = [];
150
+
151
+ for (const { path: dirPath, scope, agent } of paths) {
152
+ if (scope !== 'global') continue;
153
+ const dirSkills = this.scanDirectory(dirPath, scope, agent);
154
+ skills.push(...dirSkills);
155
+ }
156
+
157
+ return skills;
142
158
  }
143
159
 
144
160
  /**
@@ -245,10 +245,20 @@ async function checkTeamSkill(skill, source, localCommit) {
245
245
 
246
246
  /**
247
247
  * 检查所有 Skills
248
+ * @param {boolean} globalOnly - 只检查全局 skills
249
+ * @param {boolean} projectOnly - 只检查项目级 skills
248
250
  */
249
- export async function checkAllSkills(projectRoot) {
251
+ export async function checkAllSkills(projectRoot, globalOnly = false, projectOnly = false) {
250
252
  const loader = new SkillLoader(projectRoot);
251
- const skills = loader.loadAllSkills();
253
+ let skills = [];
254
+
255
+ if (globalOnly) {
256
+ skills = loader.loadGlobalSkills();
257
+ } else if (projectOnly) {
258
+ skills = loader.loadProjectSkills();
259
+ } else {
260
+ skills = loader.loadAllSkills();
261
+ }
252
262
 
253
263
  const results = [];
254
264
 
@@ -262,8 +272,10 @@ export async function checkAllSkills(projectRoot) {
262
272
 
263
273
  /**
264
274
  * 获取可更新的 Skills
275
+ * @param {boolean} globalOnly - 只检查全局 skills
276
+ * @param {boolean} projectOnly - 只检查项目级 skills
265
277
  */
266
- export async function getUpdatableSkills(projectRoot) {
267
- const results = await checkAllSkills(projectRoot);
278
+ export async function getUpdatableSkills(projectRoot, globalOnly = false, projectOnly = false) {
279
+ const results = await checkAllSkills(projectRoot, globalOnly, projectOnly);
268
280
  return results.filter(r => r.hasUpdate);
269
281
  }