ai-agent-skills 1.6.2 → 1.7.0
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/cli.js +234 -13
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ const AGENT_PATHS = {
|
|
|
24
24
|
copilot: path.join(process.cwd(), '.github', 'skills'),
|
|
25
25
|
project: path.join(process.cwd(), '.skills'),
|
|
26
26
|
goose: path.join(os.homedir(), '.config', 'goose', 'skills'),
|
|
27
|
-
opencode: path.join(os.homedir(), '.opencode', 'skill'),
|
|
27
|
+
opencode: path.join(os.homedir(), '.config', 'opencode', 'skill'),
|
|
28
28
|
codex: path.join(os.homedir(), '.codex', 'skills'),
|
|
29
29
|
letta: path.join(os.homedir(), '.letta', 'skills'),
|
|
30
30
|
};
|
|
@@ -70,6 +70,41 @@ function saveConfig(config) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// ============ SKILL METADATA SUPPORT ============
|
|
74
|
+
|
|
75
|
+
const SKILL_META_FILE = '.skill-meta.json';
|
|
76
|
+
|
|
77
|
+
function writeSkillMeta(skillPath, meta) {
|
|
78
|
+
try {
|
|
79
|
+
const metaPath = path.join(skillPath, SKILL_META_FILE);
|
|
80
|
+
const now = new Date().toISOString();
|
|
81
|
+
const metadata = {
|
|
82
|
+
...meta,
|
|
83
|
+
// Preserve original installedAt if it exists, otherwise set it
|
|
84
|
+
installedAt: meta.installedAt || now,
|
|
85
|
+
// Always update the updatedAt timestamp
|
|
86
|
+
updatedAt: now
|
|
87
|
+
};
|
|
88
|
+
fs.writeFileSync(metaPath, JSON.stringify(metadata, null, 2));
|
|
89
|
+
return true;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Non-fatal - skill still works without metadata
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readSkillMeta(skillPath) {
|
|
97
|
+
try {
|
|
98
|
+
const metaPath = path.join(skillPath, SKILL_META_FILE);
|
|
99
|
+
if (fs.existsSync(metaPath)) {
|
|
100
|
+
return JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// Ignore - treat as legacy skill
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
73
108
|
// ============ SECURITY VALIDATION ============
|
|
74
109
|
|
|
75
110
|
function validateSkillName(name) {
|
|
@@ -367,6 +402,12 @@ function installSkill(skillName, agent = 'claude', dryRun = false) {
|
|
|
367
402
|
|
|
368
403
|
copyDir(sourcePath, destPath);
|
|
369
404
|
|
|
405
|
+
// Write metadata for update tracking
|
|
406
|
+
writeSkillMeta(destPath, {
|
|
407
|
+
source: 'registry',
|
|
408
|
+
name: skillName
|
|
409
|
+
});
|
|
410
|
+
|
|
370
411
|
success(`\nInstalled: ${skillName}`);
|
|
371
412
|
info(`Agent: ${agent}`);
|
|
372
413
|
info(`Location: ${destPath}`);
|
|
@@ -474,32 +515,133 @@ function listInstalledSkills(agent = 'claude') {
|
|
|
474
515
|
log(`${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --agent ${agent}${colors.reset}`);
|
|
475
516
|
}
|
|
476
517
|
|
|
477
|
-
|
|
518
|
+
// Update from bundled registry
|
|
519
|
+
function updateFromRegistry(skillName, agent, destPath, dryRun) {
|
|
520
|
+
const sourcePath = path.join(SKILLS_DIR, skillName);
|
|
521
|
+
|
|
522
|
+
if (!fs.existsSync(sourcePath)) {
|
|
523
|
+
error(`Skill "${skillName}" not found in repository.`);
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (dryRun) {
|
|
528
|
+
log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
|
|
529
|
+
info(`Would update: ${skillName} (from registry)`);
|
|
530
|
+
info(`Agent: ${agent}`);
|
|
531
|
+
info(`Path: ${destPath}`);
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
|
|
478
535
|
try {
|
|
479
|
-
|
|
536
|
+
fs.rmSync(destPath, { recursive: true });
|
|
537
|
+
copyDir(sourcePath, destPath);
|
|
538
|
+
|
|
539
|
+
// Write metadata
|
|
540
|
+
writeSkillMeta(destPath, {
|
|
541
|
+
source: 'registry',
|
|
542
|
+
name: skillName
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
success(`\nUpdated: ${skillName}`);
|
|
546
|
+
info(`Agent: ${agent}`);
|
|
547
|
+
info(`Location: ${destPath}`);
|
|
548
|
+
return true;
|
|
480
549
|
} catch (e) {
|
|
481
|
-
error(e.message);
|
|
550
|
+
error(`Failed to update skill: ${e.message}`);
|
|
482
551
|
return false;
|
|
483
552
|
}
|
|
553
|
+
}
|
|
484
554
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
555
|
+
// Update from GitHub repository
|
|
556
|
+
function updateFromGitHub(meta, skillName, agent, destPath, dryRun) {
|
|
557
|
+
const { execFileSync } = require('child_process');
|
|
558
|
+
const repo = meta.repo;
|
|
559
|
+
|
|
560
|
+
// Validate repo format
|
|
561
|
+
if (!repo || typeof repo !== 'string' || !repo.includes('/')) {
|
|
562
|
+
error(`Invalid repository in metadata: ${repo}`);
|
|
563
|
+
error(`Try reinstalling the skill from GitHub.`);
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (dryRun) {
|
|
568
|
+
log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
|
|
569
|
+
info(`Would update: ${skillName} (from github:${repo})`);
|
|
570
|
+
info(`Agent: ${agent}`);
|
|
571
|
+
info(`Path: ${destPath}`);
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const tempDir = path.join(os.tmpdir(), `ai-skills-update-${Date.now()}`);
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
info(`Updating ${skillName} from ${repo}...`);
|
|
579
|
+
const repoUrl = `https://github.com/${repo}.git`;
|
|
580
|
+
execFileSync('git', ['clone', '--depth', '1', repoUrl, tempDir], { stdio: 'pipe' });
|
|
581
|
+
|
|
582
|
+
// Determine source path in cloned repo
|
|
583
|
+
let sourcePath;
|
|
584
|
+
if (meta.isRootSkill) {
|
|
585
|
+
sourcePath = tempDir;
|
|
586
|
+
} else if (meta.skillPath) {
|
|
587
|
+
// Check if skills/ subdirectory exists
|
|
588
|
+
const skillsSubdir = path.join(tempDir, 'skills', meta.skillPath);
|
|
589
|
+
const directPath = path.join(tempDir, meta.skillPath);
|
|
590
|
+
sourcePath = fs.existsSync(skillsSubdir) ? skillsSubdir : directPath;
|
|
591
|
+
} else {
|
|
592
|
+
sourcePath = tempDir;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!fs.existsSync(sourcePath) || !fs.existsSync(path.join(sourcePath, 'SKILL.md'))) {
|
|
596
|
+
error(`Skill not found in repository ${repo}`);
|
|
597
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
fs.rmSync(destPath, { recursive: true });
|
|
602
|
+
copyDir(sourcePath, destPath);
|
|
603
|
+
|
|
604
|
+
// Preserve metadata
|
|
605
|
+
writeSkillMeta(destPath, meta);
|
|
606
|
+
|
|
607
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
608
|
+
|
|
609
|
+
success(`\nUpdated: ${skillName}`);
|
|
610
|
+
info(`Source: github:${repo}`);
|
|
611
|
+
info(`Agent: ${agent}`);
|
|
612
|
+
info(`Location: ${destPath}`);
|
|
613
|
+
return true;
|
|
614
|
+
} catch (e) {
|
|
615
|
+
error(`Failed to update from GitHub: ${e.message}`);
|
|
616
|
+
try { fs.rmSync(tempDir, { recursive: true }); } catch {}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Update from local path
|
|
622
|
+
function updateFromLocalPath(meta, skillName, agent, destPath, dryRun) {
|
|
623
|
+
const sourcePath = meta.path;
|
|
624
|
+
|
|
625
|
+
if (!sourcePath || typeof sourcePath !== 'string') {
|
|
626
|
+
error(`Invalid path in metadata.`);
|
|
627
|
+
error(`Try reinstalling the skill from the local path.`);
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
488
630
|
|
|
489
631
|
if (!fs.existsSync(sourcePath)) {
|
|
490
|
-
error(`
|
|
632
|
+
error(`Source path no longer exists: ${sourcePath}`);
|
|
491
633
|
return false;
|
|
492
634
|
}
|
|
493
635
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
636
|
+
// Verify it's still a valid skill directory
|
|
637
|
+
if (!fs.existsSync(path.join(sourcePath, 'SKILL.md'))) {
|
|
638
|
+
error(`Source is no longer a valid skill (missing SKILL.md): ${sourcePath}`);
|
|
497
639
|
return false;
|
|
498
640
|
}
|
|
499
641
|
|
|
500
642
|
if (dryRun) {
|
|
501
643
|
log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
|
|
502
|
-
info(`Would update: ${skillName}`);
|
|
644
|
+
info(`Would update: ${skillName} (from local:${sourcePath})`);
|
|
503
645
|
info(`Agent: ${agent}`);
|
|
504
646
|
info(`Path: ${destPath}`);
|
|
505
647
|
return true;
|
|
@@ -509,16 +651,57 @@ function updateSkill(skillName, agent = 'claude', dryRun = false) {
|
|
|
509
651
|
fs.rmSync(destPath, { recursive: true });
|
|
510
652
|
copyDir(sourcePath, destPath);
|
|
511
653
|
|
|
654
|
+
// Preserve metadata
|
|
655
|
+
writeSkillMeta(destPath, meta);
|
|
656
|
+
|
|
512
657
|
success(`\nUpdated: ${skillName}`);
|
|
658
|
+
info(`Source: local:${sourcePath}`);
|
|
513
659
|
info(`Agent: ${agent}`);
|
|
514
660
|
info(`Location: ${destPath}`);
|
|
515
661
|
return true;
|
|
516
662
|
} catch (e) {
|
|
517
|
-
error(`Failed to update
|
|
663
|
+
error(`Failed to update from local path: ${e.message}`);
|
|
518
664
|
return false;
|
|
519
665
|
}
|
|
520
666
|
}
|
|
521
667
|
|
|
668
|
+
function updateSkill(skillName, agent = 'claude', dryRun = false) {
|
|
669
|
+
try {
|
|
670
|
+
validateSkillName(skillName);
|
|
671
|
+
} catch (e) {
|
|
672
|
+
error(e.message);
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
|
|
677
|
+
const destPath = path.join(destDir, skillName);
|
|
678
|
+
|
|
679
|
+
if (!fs.existsSync(destPath)) {
|
|
680
|
+
error(`Skill "${skillName}" is not installed for ${agent}.`);
|
|
681
|
+
log(`\nUse 'install' to add it first.`);
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Read metadata to determine source
|
|
686
|
+
const meta = readSkillMeta(destPath);
|
|
687
|
+
|
|
688
|
+
if (!meta) {
|
|
689
|
+
// Legacy skill without metadata - try registry
|
|
690
|
+
return updateFromRegistry(skillName, agent, destPath, dryRun);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Route to correct update method based on source
|
|
694
|
+
switch (meta.source) {
|
|
695
|
+
case 'github':
|
|
696
|
+
return updateFromGitHub(meta, skillName, agent, destPath, dryRun);
|
|
697
|
+
case 'local':
|
|
698
|
+
return updateFromLocalPath(meta, skillName, agent, destPath, dryRun);
|
|
699
|
+
case 'registry':
|
|
700
|
+
default:
|
|
701
|
+
return updateFromRegistry(skillName, agent, destPath, dryRun);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
522
705
|
function updateAllSkills(agent = 'claude', dryRun = false) {
|
|
523
706
|
const installed = getInstalledSkills(agent);
|
|
524
707
|
|
|
@@ -910,6 +1093,14 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
|
|
|
910
1093
|
}
|
|
911
1094
|
|
|
912
1095
|
copyDir(skillPath, destPath);
|
|
1096
|
+
|
|
1097
|
+
// Write metadata for update tracking
|
|
1098
|
+
writeSkillMeta(destPath, {
|
|
1099
|
+
source: 'github',
|
|
1100
|
+
repo: `${owner}/${repo}`,
|
|
1101
|
+
skillPath: skillName
|
|
1102
|
+
});
|
|
1103
|
+
|
|
913
1104
|
success(`\nInstalled: ${skillName} from ${owner}/${repo}`);
|
|
914
1105
|
info(`Location: ${destPath}`);
|
|
915
1106
|
} else if (isRootSkill) {
|
|
@@ -933,6 +1124,14 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
|
|
|
933
1124
|
}
|
|
934
1125
|
|
|
935
1126
|
copyDir(tempDir, destPath);
|
|
1127
|
+
|
|
1128
|
+
// Write metadata for update tracking
|
|
1129
|
+
writeSkillMeta(destPath, {
|
|
1130
|
+
source: 'github',
|
|
1131
|
+
repo: `${owner}/${repo}`,
|
|
1132
|
+
isRootSkill: true
|
|
1133
|
+
});
|
|
1134
|
+
|
|
936
1135
|
success(`\nInstalled: ${skillName} from ${owner}/${repo}`);
|
|
937
1136
|
info(`Location: ${destPath}`);
|
|
938
1137
|
} else {
|
|
@@ -952,6 +1151,14 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
|
|
|
952
1151
|
}
|
|
953
1152
|
|
|
954
1153
|
copyDir(skillPath, destPath);
|
|
1154
|
+
|
|
1155
|
+
// Write metadata for update tracking
|
|
1156
|
+
writeSkillMeta(destPath, {
|
|
1157
|
+
source: 'github',
|
|
1158
|
+
repo: `${owner}/${repo}`,
|
|
1159
|
+
skillPath: entry.name
|
|
1160
|
+
});
|
|
1161
|
+
|
|
955
1162
|
log(` ${colors.green}✓${colors.reset} ${entry.name}`);
|
|
956
1163
|
installed++;
|
|
957
1164
|
}
|
|
@@ -1010,6 +1217,13 @@ function installFromLocalPath(source, agent = 'claude', dryRun = false) {
|
|
|
1010
1217
|
}
|
|
1011
1218
|
|
|
1012
1219
|
copyDir(sourcePath, destPath);
|
|
1220
|
+
|
|
1221
|
+
// Write metadata for update tracking
|
|
1222
|
+
writeSkillMeta(destPath, {
|
|
1223
|
+
source: 'local',
|
|
1224
|
+
path: sourcePath
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1013
1227
|
success(`\nInstalled: ${skillName} from local path`);
|
|
1014
1228
|
info(`Location: ${destPath}`);
|
|
1015
1229
|
} else {
|
|
@@ -1029,6 +1243,13 @@ function installFromLocalPath(source, agent = 'claude', dryRun = false) {
|
|
|
1029
1243
|
}
|
|
1030
1244
|
|
|
1031
1245
|
copyDir(skillPath, destPath);
|
|
1246
|
+
|
|
1247
|
+
// Write metadata for update tracking
|
|
1248
|
+
writeSkillMeta(destPath, {
|
|
1249
|
+
source: 'local',
|
|
1250
|
+
path: skillPath
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1032
1253
|
log(` ${colors.green}✓${colors.reset} ${entry.name}`);
|
|
1033
1254
|
installed++;
|
|
1034
1255
|
}
|
package/package.json
CHANGED