@localskills/cli 0.8.0 → 0.9.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.
Files changed (2) hide show
  1. package/dist/index.js +118 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ var require_src = __commonJS({
161
161
  });
162
162
 
163
163
  // src/index.ts
164
+ import { createRequire } from "module";
164
165
  import { Command as Command10 } from "commander";
165
166
 
166
167
  // src/commands/auth.ts
@@ -1368,6 +1369,11 @@ function setAnonymousKey(key) {
1368
1369
  config.anonymous_key = key;
1369
1370
  saveConfig(config);
1370
1371
  }
1372
+ function setLastUpdateCheck(ts) {
1373
+ const config = loadFullConfig();
1374
+ config.last_update_check = ts;
1375
+ saveFullConfig(config);
1376
+ }
1371
1377
 
1372
1378
  // src/lib/api-client.ts
1373
1379
  var ApiClient = class {
@@ -1593,6 +1599,22 @@ function isValidSemVer(v) {
1593
1599
  function isValidSemVerRange(range) {
1594
1600
  return RANGE_RE.test(range);
1595
1601
  }
1602
+ function compareSemVer(a, b) {
1603
+ if (a.major !== b.major)
1604
+ return a.major > b.major ? 1 : -1;
1605
+ if (a.minor !== b.minor)
1606
+ return a.minor > b.minor ? 1 : -1;
1607
+ if (a.patch !== b.patch)
1608
+ return a.patch > b.patch ? 1 : -1;
1609
+ return 0;
1610
+ }
1611
+ function isGreaterThan(next, prev) {
1612
+ const a = parseSemVer(next);
1613
+ const b = parseSemVer(prev);
1614
+ if (!a || !b)
1615
+ return false;
1616
+ return compareSemVer(a, b) === 1;
1617
+ }
1596
1618
 
1597
1619
  // ../../packages/shared/dist/utils/index.js
1598
1620
  function titleFromSlug(slug) {
@@ -1617,8 +1639,8 @@ function buildVersionQuery(range) {
1617
1639
  console.error(`Invalid version specifier: ${range}`);
1618
1640
  process.exit(1);
1619
1641
  }
1620
- function formatVersionLabel(semver, version) {
1621
- return semver ? `v${semver}` : `v${version}`;
1642
+ function formatVersionLabel(semver, version2) {
1643
+ return semver ? `v${semver}` : `v${version2}`;
1622
1644
  }
1623
1645
  function cancelGuard(value) {
1624
1646
  if (Ct(value)) {
@@ -2320,13 +2342,13 @@ function getCacheDir(slug) {
2320
2342
  }
2321
2343
  return dir;
2322
2344
  }
2323
- function store(slug, content, skill, version) {
2345
+ function store(slug, content, skill, version2) {
2324
2346
  const dir = getCacheDir(slug);
2325
2347
  mkdirSync5(dir, { recursive: true });
2326
2348
  writeFileSync5(join12(dir, "raw.md"), content);
2327
2349
  const meta = {
2328
2350
  hash: skill.contentHash,
2329
- version,
2351
+ version: version2,
2330
2352
  semver: skill.currentSemver ?? null,
2331
2353
  name: skill.name,
2332
2354
  description: skill.description,
@@ -2372,14 +2394,14 @@ function purge(slug) {
2372
2394
  rmSync3(dir, { recursive: true, force: true });
2373
2395
  }
2374
2396
  }
2375
- function storePackage(slug, zipBuffer, manifest, skill, version) {
2397
+ function storePackage(slug, zipBuffer, manifest, skill, version2) {
2376
2398
  const dir = getCacheDir(slug);
2377
2399
  mkdirSync5(dir, { recursive: true });
2378
2400
  writeFileSync5(join12(dir, "package.zip"), zipBuffer);
2379
2401
  writeFileSync5(join12(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
2380
2402
  const meta = {
2381
2403
  hash: skill.contentHash,
2382
- version,
2404
+ version: version2,
2383
2405
  semver: skill.currentSemver ?? null,
2384
2406
  name: skill.name,
2385
2407
  description: skill.description,
@@ -2528,13 +2550,13 @@ function parsePlatforms(raw) {
2528
2550
  }
2529
2551
  return platforms;
2530
2552
  }
2531
- function buildSkillRecord(cacheKey, skill, version, resolvedSemver, requestedRange, existingInstallations, newInstallations) {
2553
+ function buildSkillRecord(cacheKey, skill, version2, resolvedSemver, requestedRange, existingInstallations, newInstallations) {
2532
2554
  return {
2533
2555
  slug: cacheKey,
2534
2556
  name: skill.name,
2535
2557
  type: skill.type ?? "skill",
2536
2558
  hash: skill.contentHash,
2537
- version,
2559
+ version: version2,
2538
2560
  semver: resolvedSemver ?? null,
2539
2561
  semverRange: requestedRange ?? null,
2540
2562
  cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2619,13 +2641,13 @@ var installCommand = new Command2("install").description("Install a skill locall
2619
2641
  const format = resData.format ?? "text";
2620
2642
  const cacheKey = resData.skill.publicId || slug;
2621
2643
  if (format === "package") {
2622
- const { skill: skill2, downloadUrl, manifest, version: version2, semver: resolvedSemver2 } = resData;
2623
- spinner.stop(`Fetched ${skill2.name} ${formatVersionLabel(resolvedSemver2, version2)} (package, ${manifest.files.length} files)`);
2644
+ const { skill: skill2, downloadUrl, manifest, version: version3, semver: resolvedSemver2 } = resData;
2645
+ spinner.stop(`Fetched ${skill2.name} ${formatVersionLabel(resolvedSemver2, version3)} (package, ${manifest.files.length} files)`);
2624
2646
  const dlSpinner = bt2();
2625
2647
  dlSpinner.start("Downloading package...");
2626
2648
  const zipBuffer = await client.fetchBinary(downloadUrl);
2627
2649
  dlSpinner.stop(`Downloaded ${(zipBuffer.length / 1024).toFixed(1)} KB`);
2628
- storePackage(cacheKey, zipBuffer, manifest, skill2, version2);
2650
+ storePackage(cacheKey, zipBuffer, manifest, skill2, version3);
2629
2651
  const installations2 = [];
2630
2652
  const results2 = [];
2631
2653
  for (const platformId of platforms) {
@@ -2656,7 +2678,7 @@ var installCommand = new Command2("install").description("Install a skill locall
2656
2678
  config.installed_skills[cacheKey] = buildSkillRecord(
2657
2679
  cacheKey,
2658
2680
  skill2,
2659
- version2,
2681
+ version3,
2660
2682
  resolvedSemver2,
2661
2683
  requestedRange,
2662
2684
  config.installed_skills[cacheKey]?.installations,
@@ -2669,9 +2691,9 @@ var installCommand = new Command2("install").description("Install a skill locall
2669
2691
  Le(`Done! Installed to ${installations2.length} target(s).`);
2670
2692
  return;
2671
2693
  }
2672
- const { skill, content, version, semver: resolvedSemver } = resData;
2673
- spinner.stop(`Fetched ${skill.name} ${formatVersionLabel(resolvedSemver, version)}`);
2674
- store(cacheKey, content, skill, version);
2694
+ const { skill, content, version: version2, semver: resolvedSemver } = resData;
2695
+ spinner.stop(`Fetched ${skill.name} ${formatVersionLabel(resolvedSemver, version2)}`);
2696
+ store(cacheKey, content, skill, version2);
2675
2697
  const contentType = skill.type ?? "skill";
2676
2698
  const installations = [];
2677
2699
  const results = [];
@@ -2713,7 +2735,7 @@ var installCommand = new Command2("install").description("Install a skill locall
2713
2735
  config.installed_skills[cacheKey] = buildSkillRecord(
2714
2736
  cacheKey,
2715
2737
  skill,
2716
- version,
2738
+ version2,
2717
2739
  resolvedSemver,
2718
2740
  requestedRange,
2719
2741
  config.installed_skills[cacheKey]?.installations,
@@ -2863,7 +2885,7 @@ var pullCommand = new Command5("pull").description("Pull latest versions of all
2863
2885
  }
2864
2886
  const resData = res.data;
2865
2887
  const format = resData.format ?? "text";
2866
- const { skill, version } = resData;
2888
+ const { skill, version: version2 } = resData;
2867
2889
  if (skill.contentHash === installed.hash) {
2868
2890
  spinner.stop(`${slug} \u2014 up to date`);
2869
2891
  skipped++;
@@ -2872,7 +2894,7 @@ var pullCommand = new Command5("pull").description("Pull latest versions of all
2872
2894
  if (format === "package") {
2873
2895
  const { downloadUrl, manifest } = resData;
2874
2896
  const zipBuffer = await client.fetchBinary(downloadUrl);
2875
- storePackage(slug, zipBuffer, manifest, skill, version);
2897
+ storePackage(slug, zipBuffer, manifest, skill, version2);
2876
2898
  for (const installation of installed.installations) {
2877
2899
  const adapter = getAdapter(installation.platform);
2878
2900
  const targetPath = adapter.resolvePath(slug, installation.scope, installation.projectDir, skill.type ?? "skill");
@@ -2881,7 +2903,7 @@ var pullCommand = new Command5("pull").description("Pull latest versions of all
2881
2903
  }
2882
2904
  } else {
2883
2905
  const { content } = resData;
2884
- store(slug, content, skill, version);
2906
+ store(slug, content, skill, version2);
2885
2907
  for (const installation of installed.installations) {
2886
2908
  if (installation.method === "symlink") {
2887
2909
  getPlatformFile(slug, installation.platform, skill);
@@ -2911,11 +2933,11 @@ ${transformed}`
2911
2933
  }
2912
2934
  }
2913
2935
  installed.hash = skill.contentHash;
2914
- installed.version = version;
2936
+ installed.version = version2;
2915
2937
  installed.semver = resData.semver ?? null;
2916
2938
  installed.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
2917
2939
  updated++;
2918
- spinner.stop(`${slug} \u2014 updated to ${formatVersionLabel(res.data.semver, version)}`);
2940
+ spinner.stop(`${slug} \u2014 updated to ${formatVersionLabel(res.data.semver, version2)}`);
2919
2941
  }
2920
2942
  saveConfig(config);
2921
2943
  Le(`Pull complete. ${updated} updated, ${skipped} up to date.`);
@@ -3564,12 +3586,86 @@ profileCommand.command("delete <name>").description("Delete a profile").option("
3564
3586
  console.log(`Profile "${name}" deleted.`);
3565
3587
  });
3566
3588
 
3589
+ // src/lib/update-check.ts
3590
+ var REGISTRY_URL = "https://registry.npmjs.org/@localskills/cli/latest";
3591
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3592
+ var FETCH_TIMEOUT_MS = 500;
3593
+ var pendingCheck = null;
3594
+ var pendingAbort = null;
3595
+ var currentVer = null;
3596
+ function shouldCheck() {
3597
+ if (process.env.LOCALSKILLS_NO_UPDATE_CHECK === "1") return false;
3598
+ try {
3599
+ const config = loadFullConfig();
3600
+ if (config.update_check_opted_out) return false;
3601
+ const last = config.last_update_check;
3602
+ if (last) {
3603
+ const elapsed = Date.now() - new Date(last).getTime();
3604
+ if (elapsed < CHECK_INTERVAL_MS) return false;
3605
+ }
3606
+ } catch {
3607
+ return false;
3608
+ }
3609
+ return true;
3610
+ }
3611
+ function startUpdateCheck(currentVersion) {
3612
+ try {
3613
+ if (!shouldCheck()) return;
3614
+ currentVer = currentVersion;
3615
+ try {
3616
+ setLastUpdateCheck((/* @__PURE__ */ new Date()).toISOString());
3617
+ } catch {
3618
+ }
3619
+ pendingAbort = new AbortController();
3620
+ pendingCheck = fetch(REGISTRY_URL, { signal: pendingAbort.signal }).then((res) => {
3621
+ if (!res.ok) return null;
3622
+ return res.json();
3623
+ }).then((data) => {
3624
+ if (data && typeof data.version === "string" && isGreaterThan(data.version, currentVersion)) {
3625
+ return data.version;
3626
+ }
3627
+ return null;
3628
+ }).catch(() => null);
3629
+ } catch {
3630
+ }
3631
+ }
3632
+ async function printUpdateNotice() {
3633
+ try {
3634
+ if (!pendingCheck) return;
3635
+ const timeout = new Promise((resolve7) => {
3636
+ setTimeout(() => resolve7(null), FETCH_TIMEOUT_MS).unref();
3637
+ });
3638
+ const remoteVersion = await Promise.race([pendingCheck, timeout]);
3639
+ pendingAbort?.abort();
3640
+ pendingAbort = null;
3641
+ pendingCheck = null;
3642
+ if (remoteVersion) {
3643
+ const from = currentVer ?? "unknown";
3644
+ process.stderr.write(
3645
+ `
3646
+ Update available: ${from} \u2192 ${remoteVersion}
3647
+ Run: npm update -g @localskills/cli
3648
+ `
3649
+ );
3650
+ }
3651
+ } catch {
3652
+ }
3653
+ }
3654
+
3567
3655
  // src/index.ts
3656
+ var require2 = createRequire(import.meta.url);
3657
+ var { version } = require2("../package.json");
3568
3658
  var program = new Command10();
3569
- program.name("localskills").description("Install and manage agent skills from localskills.sh").version("0.1.0").option("--profile <name>", "Use a specific profile");
3659
+ program.name("localskills").description("Install and manage agent skills from localskills.sh").version(version).option("--profile <name>", "Use a specific profile").option("--no-update-check", "Skip update check");
3570
3660
  program.hook("preAction", (thisCommand) => {
3571
3661
  const opts = thisCommand.opts();
3572
3662
  setProfileOverride(opts.profile);
3663
+ if (opts.updateCheck !== false) {
3664
+ startUpdateCheck(version);
3665
+ }
3666
+ });
3667
+ program.hook("postAction", async () => {
3668
+ await printUpdateNotice();
3573
3669
  });
3574
3670
  program.addCommand(loginCommand);
3575
3671
  program.addCommand(logoutCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localskills/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "CLI for localskills.sh — install agent skills locally",
5
5
  "type": "module",
6
6
  "bin": {