skills 1.0.12 → 1.0.13
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/dist/cli.js +206 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { spawn } from "child_process";
|
|
4
|
+
import { spawn, spawnSync } from "child_process";
|
|
5
5
|
import {
|
|
6
6
|
writeFileSync,
|
|
7
7
|
readFileSync,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "fs";
|
|
13
13
|
import { basename, join } from "path";
|
|
14
14
|
import { homedir } from "os";
|
|
15
|
+
import { createHash } from "crypto";
|
|
15
16
|
var RESET = "\x1B[0m";
|
|
16
17
|
var BOLD = "\x1B[1m";
|
|
17
18
|
var DIM = "\x1B[38;5;102m";
|
|
@@ -61,16 +62,21 @@ ${BOLD}Commands:${RESET}
|
|
|
61
62
|
add <package> Add a skill package
|
|
62
63
|
e.g. vercel-labs/agent-skills
|
|
63
64
|
https://github.com/vercel-labs/agent-skills
|
|
65
|
+
check Check for available skill updates
|
|
66
|
+
update Update all skills to latest versions
|
|
64
67
|
generate-lock Generate lock file from installed skills
|
|
65
68
|
|
|
66
69
|
${BOLD}Options:${RESET}
|
|
67
70
|
--help, -h Show this help message
|
|
68
71
|
--version, -v Show version number
|
|
72
|
+
--refresh, -r Force refresh from source (check)
|
|
69
73
|
--dry-run Preview changes without writing (generate-lock)
|
|
70
74
|
|
|
71
75
|
${BOLD}Examples:${RESET}
|
|
72
76
|
${DIM}$${RESET} skills init my-skill
|
|
73
77
|
${DIM}$${RESET} skills add vercel-labs/agent-skills
|
|
78
|
+
${DIM}$${RESET} skills check
|
|
79
|
+
${DIM}$${RESET} skills update
|
|
74
80
|
${DIM}$${RESET} skills generate-lock --dry-run
|
|
75
81
|
|
|
76
82
|
Discover more skills at ${TEXT}https://skills.sh/${RESET}
|
|
@@ -178,6 +184,8 @@ var AGENTS_DIR = ".agents";
|
|
|
178
184
|
var SKILLS_SUBDIR = "skills";
|
|
179
185
|
var LOCK_FILE = ".skill-lock.json";
|
|
180
186
|
var SEARCH_API_URL = "https://skills.sh/api/skills/search";
|
|
187
|
+
var CHECK_UPDATES_API_URL = "https://add-skill.vercel.sh/check-updates";
|
|
188
|
+
var CURRENT_LOCK_VERSION = 2;
|
|
181
189
|
function getSkillLockPath() {
|
|
182
190
|
return join(homedir(), AGENTS_DIR, LOCK_FILE);
|
|
183
191
|
}
|
|
@@ -187,11 +195,14 @@ function readSkillLock() {
|
|
|
187
195
|
const content = readFileSync(lockPath, "utf-8");
|
|
188
196
|
const parsed = JSON.parse(content);
|
|
189
197
|
if (typeof parsed.version !== "number" || !parsed.skills) {
|
|
190
|
-
return { version:
|
|
198
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
199
|
+
}
|
|
200
|
+
if (parsed.version < CURRENT_LOCK_VERSION) {
|
|
201
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
191
202
|
}
|
|
192
203
|
return parsed;
|
|
193
204
|
} catch {
|
|
194
|
-
return { version:
|
|
205
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
195
206
|
}
|
|
196
207
|
}
|
|
197
208
|
function writeSkillLock(lock) {
|
|
@@ -301,6 +312,7 @@ async function runGenerateLock(args) {
|
|
|
301
312
|
source: match.source,
|
|
302
313
|
sourceType,
|
|
303
314
|
sourceUrl,
|
|
315
|
+
contentHash: "",
|
|
304
316
|
installedAt: now,
|
|
305
317
|
updatedAt: now
|
|
306
318
|
};
|
|
@@ -325,6 +337,186 @@ async function runGenerateLock(args) {
|
|
|
325
337
|
console.log(`${TEXT}Lock file updated:${RESET} ${DIM}~/.agents/.skill-lock.json${RESET}`);
|
|
326
338
|
}
|
|
327
339
|
}
|
|
340
|
+
function computeContentHash(content) {
|
|
341
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
342
|
+
}
|
|
343
|
+
function getSkillContentPath(skillName) {
|
|
344
|
+
return join(homedir(), AGENTS_DIR, SKILLS_SUBDIR, skillName, "SKILL.md");
|
|
345
|
+
}
|
|
346
|
+
function readSkillContent(skillName) {
|
|
347
|
+
const skillPath = getSkillContentPath(skillName);
|
|
348
|
+
try {
|
|
349
|
+
return readFileSync(skillPath, "utf-8");
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function runCheck(args = []) {
|
|
355
|
+
const forceRefresh = args.includes("--refresh") || args.includes("-r");
|
|
356
|
+
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
357
|
+
console.log();
|
|
358
|
+
const lock = readSkillLock();
|
|
359
|
+
const skillNames = Object.keys(lock.skills);
|
|
360
|
+
if (skillNames.length === 0) {
|
|
361
|
+
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
|
|
362
|
+
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const checkRequest = {
|
|
366
|
+
skills: []
|
|
367
|
+
};
|
|
368
|
+
if (forceRefresh) {
|
|
369
|
+
checkRequest.forceRefresh = true;
|
|
370
|
+
}
|
|
371
|
+
for (const skillName of skillNames) {
|
|
372
|
+
const entry = lock.skills[skillName];
|
|
373
|
+
if (!entry)
|
|
374
|
+
continue;
|
|
375
|
+
let contentHash = entry.contentHash;
|
|
376
|
+
if (!contentHash) {
|
|
377
|
+
const content = readSkillContent(skillName);
|
|
378
|
+
if (content) {
|
|
379
|
+
contentHash = computeContentHash(content);
|
|
380
|
+
} else {
|
|
381
|
+
console.log(`${DIM}Skipping ${skillName}: cannot read SKILL.md${RESET}`);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
checkRequest.skills.push({
|
|
386
|
+
name: skillName,
|
|
387
|
+
source: entry.source,
|
|
388
|
+
path: entry.skillPath,
|
|
389
|
+
contentHash
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (checkRequest.skills.length === 0) {
|
|
393
|
+
console.log(`${DIM}No skills to check.${RESET}`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
console.log(`${DIM}Checking ${checkRequest.skills.length} skill(s) for updates...${RESET}`);
|
|
397
|
+
try {
|
|
398
|
+
const response = await fetch(CHECK_UPDATES_API_URL, {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers: { "Content-Type": "application/json" },
|
|
401
|
+
body: JSON.stringify(checkRequest)
|
|
402
|
+
});
|
|
403
|
+
if (!response.ok) {
|
|
404
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
405
|
+
}
|
|
406
|
+
const data = await response.json();
|
|
407
|
+
console.log();
|
|
408
|
+
if (data.updates.length === 0) {
|
|
409
|
+
console.log(`${TEXT}✓ All skills are up to date${RESET}`);
|
|
410
|
+
} else {
|
|
411
|
+
console.log(`${TEXT}${data.updates.length} update(s) available:${RESET}`);
|
|
412
|
+
console.log();
|
|
413
|
+
for (const update of data.updates) {
|
|
414
|
+
console.log(` ${TEXT}↑${RESET} ${update.name}`);
|
|
415
|
+
console.log(` ${DIM}source: ${update.source}${RESET}`);
|
|
416
|
+
}
|
|
417
|
+
console.log();
|
|
418
|
+
console.log(`${DIM}Run${RESET} ${TEXT}npx skills update${RESET} ${DIM}to update all skills${RESET}`);
|
|
419
|
+
}
|
|
420
|
+
if (data.errors && data.errors.length > 0) {
|
|
421
|
+
console.log();
|
|
422
|
+
console.log(`${DIM}Could not check ${data.errors.length} skill(s):${RESET}`);
|
|
423
|
+
for (const err of data.errors) {
|
|
424
|
+
console.log(` ${DIM}${err.name}: ${err.error}${RESET}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.log(`${TEXT}Error checking for updates:${RESET} ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
console.log();
|
|
432
|
+
}
|
|
433
|
+
async function runUpdate() {
|
|
434
|
+
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
435
|
+
console.log();
|
|
436
|
+
const lock = readSkillLock();
|
|
437
|
+
const skillNames = Object.keys(lock.skills);
|
|
438
|
+
if (skillNames.length === 0) {
|
|
439
|
+
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
|
|
440
|
+
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const checkRequest = {
|
|
444
|
+
skills: []
|
|
445
|
+
};
|
|
446
|
+
for (const skillName of skillNames) {
|
|
447
|
+
const entry = lock.skills[skillName];
|
|
448
|
+
if (!entry)
|
|
449
|
+
continue;
|
|
450
|
+
let contentHash = entry.contentHash;
|
|
451
|
+
if (!contentHash) {
|
|
452
|
+
const content = readSkillContent(skillName);
|
|
453
|
+
if (content) {
|
|
454
|
+
contentHash = computeContentHash(content);
|
|
455
|
+
} else {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
checkRequest.skills.push({
|
|
460
|
+
name: skillName,
|
|
461
|
+
source: entry.source,
|
|
462
|
+
path: entry.skillPath,
|
|
463
|
+
contentHash
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (checkRequest.skills.length === 0) {
|
|
467
|
+
console.log(`${DIM}No skills to check.${RESET}`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
let updates = [];
|
|
471
|
+
try {
|
|
472
|
+
const response = await fetch(CHECK_UPDATES_API_URL, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
headers: { "Content-Type": "application/json" },
|
|
475
|
+
body: JSON.stringify(checkRequest)
|
|
476
|
+
});
|
|
477
|
+
if (!response.ok) {
|
|
478
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
479
|
+
}
|
|
480
|
+
const data = await response.json();
|
|
481
|
+
updates = data.updates;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.log(`${TEXT}Error checking for updates:${RESET} ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
if (updates.length === 0) {
|
|
487
|
+
console.log(`${TEXT}✓ All skills are up to date${RESET}`);
|
|
488
|
+
console.log();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
console.log(`${TEXT}Found ${updates.length} update(s)${RESET}`);
|
|
492
|
+
console.log();
|
|
493
|
+
let successCount = 0;
|
|
494
|
+
let failCount = 0;
|
|
495
|
+
for (const update of updates) {
|
|
496
|
+
const entry = lock.skills[update.name];
|
|
497
|
+
if (!entry)
|
|
498
|
+
continue;
|
|
499
|
+
console.log(`${TEXT}Updating ${update.name}...${RESET}`);
|
|
500
|
+
const result = spawnSync("npx", ["-y", "add-skill", entry.sourceUrl, "--skill", update.name, "-g", "-y"], {
|
|
501
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
502
|
+
});
|
|
503
|
+
if (result.status === 0) {
|
|
504
|
+
successCount++;
|
|
505
|
+
console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
|
|
506
|
+
} else {
|
|
507
|
+
failCount++;
|
|
508
|
+
console.log(` ${DIM}✗ Failed to update ${update.name}${RESET}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
console.log();
|
|
512
|
+
if (successCount > 0) {
|
|
513
|
+
console.log(`${TEXT}✓ Updated ${successCount} skill(s)${RESET}`);
|
|
514
|
+
}
|
|
515
|
+
if (failCount > 0) {
|
|
516
|
+
console.log(`${DIM}Failed to update ${failCount} skill(s)${RESET}`);
|
|
517
|
+
}
|
|
518
|
+
console.log();
|
|
519
|
+
}
|
|
328
520
|
function main() {
|
|
329
521
|
const args = process.argv.slice(2);
|
|
330
522
|
if (args.length === 0) {
|
|
@@ -347,6 +539,17 @@ function main() {
|
|
|
347
539
|
console.log();
|
|
348
540
|
runAddSkill(restArgs);
|
|
349
541
|
break;
|
|
542
|
+
case "check":
|
|
543
|
+
showLogo();
|
|
544
|
+
console.log();
|
|
545
|
+
runCheck(restArgs);
|
|
546
|
+
break;
|
|
547
|
+
case "update":
|
|
548
|
+
case "upgrade":
|
|
549
|
+
showLogo();
|
|
550
|
+
console.log();
|
|
551
|
+
runUpdate();
|
|
552
|
+
break;
|
|
350
553
|
case "generate-lock":
|
|
351
554
|
case "gen-lock":
|
|
352
555
|
showLogo();
|