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 +1 -1
- package/src/commands/check.js +6 -3
- package/src/commands/install.js +221 -102
- package/src/commands/sync.js +21 -5
- package/src/commands/update.js +34 -9
- package/src/skill-loader.js +20 -4
- package/src/version-checker.js +16 -4
package/package.json
CHANGED
package/src/commands/check.js
CHANGED
|
@@ -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) {
|
package/src/commands/install.js
CHANGED
|
@@ -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
|
-
//
|
|
151
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
417
|
-
|
|
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
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
lockfileManager.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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, {});
|
package/src/commands/sync.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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`));
|
package/src/commands/update.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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);
|
package/src/skill-loader.js
CHANGED
|
@@ -129,16 +129,32 @@ export class SkillLoader {
|
|
|
129
129
|
* 仅加载项目级 Skills
|
|
130
130
|
*/
|
|
131
131
|
loadProjectSkills(agentId = null) {
|
|
132
|
-
const
|
|
133
|
-
|
|
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
|
|
141
|
-
|
|
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
|
/**
|
package/src/version-checker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|