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.
Files changed (2) hide show
  1. package/dist/cli.js +206 -3
  2. 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: 1, skills: {} };
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: 1, skills: {} };
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {