exodus-cli 1.1.0 → 1.2.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 +192 -54
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -32203,7 +32203,7 @@ function useColor() {
32203
32203
  // node_modules/commander/index.js
32204
32204
  var program = new Command;
32205
32205
  // package.json
32206
- var version = "1.1.0";
32206
+ var version = "1.2.0";
32207
32207
 
32208
32208
  // src/lib/open-app.ts
32209
32209
  import { join } from "path";
@@ -41605,6 +41605,133 @@ async function listInstalledSkills(skillsDir) {
41605
41605
  return Object.entries(lock.skills).map(([slug, info]) => ({ slug, ...info }));
41606
41606
  }
41607
41607
 
41608
+ // src/lib/update-notifier.ts
41609
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
41610
+ import { dirname, join as join5 } from "path";
41611
+
41612
+ // src/lib/dist-channel.ts
41613
+ var DIST_CHANNEL = "npm";
41614
+
41615
+ // src/lib/updater.ts
41616
+ var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
41617
+ var DEFAULT_GITHUB_API = "https://api.github.com/repos/exodus-ai-org/exodus-cli";
41618
+ function getCurrentVersion() {
41619
+ return version;
41620
+ }
41621
+ function isNewerVersion(current, latest) {
41622
+ const c = current.split(".").map(Number);
41623
+ const l = latest.split(".").map(Number);
41624
+ for (let i = 0;i < 3; i++) {
41625
+ const cv = c[i] ?? 0;
41626
+ const lv = l[i] ?? 0;
41627
+ if (lv > cv)
41628
+ return true;
41629
+ if (lv < cv)
41630
+ return false;
41631
+ }
41632
+ return false;
41633
+ }
41634
+ async function getLatestNpmVersion(baseUrl = DEFAULT_NPM_REGISTRY) {
41635
+ const res = await fetch(`${baseUrl}/exodus-cli/latest`);
41636
+ if (!res.ok)
41637
+ throw new Error(`npm registry lookup failed: ${res.status}`);
41638
+ const body = await res.json();
41639
+ return body.version;
41640
+ }
41641
+ async function getLatestRelease(baseUrl = DEFAULT_GITHUB_API) {
41642
+ const res = await fetch(`${baseUrl}/releases/latest`);
41643
+ if (!res.ok)
41644
+ throw new Error(`GitHub releases lookup failed: ${res.status}`);
41645
+ return await res.json();
41646
+ }
41647
+ async function getLatestVersion() {
41648
+ return DIST_CHANNEL === "npm" ? await getLatestNpmVersion() : (await getLatestRelease()).tag_name.replace(/^v/, "");
41649
+ }
41650
+ async function checkForUpdate() {
41651
+ const current = getCurrentVersion();
41652
+ const latest = await getLatestVersion();
41653
+ if (!isNewerVersion(current, latest)) {
41654
+ return { updateAvailable: false, current };
41655
+ }
41656
+ return { updateAvailable: true, current, latest };
41657
+ }
41658
+
41659
+ // src/lib/update-notifier.ts
41660
+ var CACHE_FILE = "update-check.json";
41661
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
41662
+ var UPDATE_CHECK_ARG = "__update-check";
41663
+ function getUpdateCachePath(exodusHome = getExodusHome()) {
41664
+ return join5(exodusHome, CACHE_FILE);
41665
+ }
41666
+ async function readUpdateCache(exodusHome = getExodusHome()) {
41667
+ try {
41668
+ const parsed = JSON.parse(await readFile2(getUpdateCachePath(exodusHome), "utf-8"));
41669
+ if (typeof parsed?.checkedAt !== "number" || typeof parsed?.latest !== "string")
41670
+ return null;
41671
+ return { checkedAt: parsed.checkedAt, latest: parsed.latest };
41672
+ } catch {
41673
+ return null;
41674
+ }
41675
+ }
41676
+ async function writeUpdateCache(cache3, exodusHome = getExodusHome()) {
41677
+ const path2 = getUpdateCachePath(exodusHome);
41678
+ await mkdir2(dirname(path2), { recursive: true });
41679
+ await writeFile2(path2, JSON.stringify(cache3));
41680
+ }
41681
+ function isCacheStale(cache3, now2 = Date.now()) {
41682
+ if (!cache3)
41683
+ return true;
41684
+ const age = now2 - cache3.checkedAt;
41685
+ return age < 0 || age >= CHECK_INTERVAL_MS;
41686
+ }
41687
+ function getUpdateNotice(current, cache3) {
41688
+ if (!cache3 || !isNewerVersion(current, cache3.latest))
41689
+ return null;
41690
+ return `Update available: ${current} \u2192 ${cache3.latest}. Run: exodus update`;
41691
+ }
41692
+ function formatUpdateBadge(current, cache3) {
41693
+ if (!cache3 || !isNewerVersion(current, cache3.latest))
41694
+ return null;
41695
+ return `Update available: ${cache3.latest}`;
41696
+ }
41697
+ async function getUpdateBadge(exodusHome = getExodusHome(), current = getCurrentVersion()) {
41698
+ return formatUpdateBadge(current, await readUpdateCache(exodusHome));
41699
+ }
41700
+ function buildRefreshCommand(argv = process.argv, execPath = process.execPath) {
41701
+ const entry = argv[1];
41702
+ if (!entry || entry === execPath)
41703
+ return [execPath, UPDATE_CHECK_ARG];
41704
+ return [execPath, entry, UPDATE_CHECK_ARG];
41705
+ }
41706
+ async function refreshUpdateCache(exodusHome = getExodusHome(), fetchLatest = getLatestVersion, now2 = Date.now) {
41707
+ try {
41708
+ const latest = await fetchLatest();
41709
+ await writeUpdateCache({ checkedAt: now2(), latest }, exodusHome);
41710
+ } catch {}
41711
+ }
41712
+ function spawnBackgroundRefresh() {
41713
+ const child = Bun.spawn({
41714
+ cmd: buildRefreshCommand(),
41715
+ stdio: ["ignore", "ignore", "ignore"]
41716
+ });
41717
+ child.unref();
41718
+ }
41719
+ async function prepareUpdateNotice(opts = {}) {
41720
+ const {
41721
+ exodusHome = getExodusHome(),
41722
+ current = getCurrentVersion(),
41723
+ now: now2 = Date.now(),
41724
+ spawnRefresh = spawnBackgroundRefresh
41725
+ } = opts;
41726
+ const cache3 = await readUpdateCache(exodusHome);
41727
+ if (isCacheStale(cache3, now2)) {
41728
+ try {
41729
+ spawnRefresh();
41730
+ } catch {}
41731
+ }
41732
+ return getUpdateNotice(current, cache3);
41733
+ }
41734
+
41608
41735
  // src/tui/app.tsx
41609
41736
  var import_react41 = __toESM(require_react(), 1);
41610
41737
 
@@ -42368,7 +42495,10 @@ function InstalledScreen({ skillsDir = getSkillsDir() }) {
42368
42495
 
42369
42496
  // src/tui/app.tsx
42370
42497
  var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
42371
- function App2({ skillsDir = getSkillsDir() }) {
42498
+ function App2({
42499
+ skillsDir = getSkillsDir(),
42500
+ updateNotice = null
42501
+ }) {
42372
42502
  const [tab2, setTab] = import_react41.useState("discover");
42373
42503
  const [selected, setSelected] = import_react41.useState(null);
42374
42504
  const { exit } = use_app_default();
@@ -42401,11 +42531,29 @@ function App2({ skillsDir = getSkillsDir() }) {
42401
42531
  }, undefined, false, undefined, this)
42402
42532
  ]
42403
42533
  }, undefined, true, undefined, this),
42404
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
42405
- dimColor: true,
42406
- italic: true,
42407
- children: "Tab to switch"
42408
- }, undefined, false, undefined, this)
42534
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
42535
+ children: [
42536
+ updateNotice ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
42537
+ marginRight: 1,
42538
+ children: [
42539
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
42540
+ color: "yellow",
42541
+ wrap: "truncate-end",
42542
+ children: updateNotice
42543
+ }, undefined, false, undefined, this),
42544
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
42545
+ dimColor: true,
42546
+ children: " \xB7"
42547
+ }, undefined, false, undefined, this)
42548
+ ]
42549
+ }, undefined, true, undefined, this) : null,
42550
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
42551
+ dimColor: true,
42552
+ italic: true,
42553
+ children: "Tab to switch"
42554
+ }, undefined, false, undefined, this)
42555
+ ]
42556
+ }, undefined, true, undefined, this)
42409
42557
  ]
42410
42558
  }, undefined, true, undefined, this),
42411
42559
  /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Rule, {}, undefined, false, undefined, this),
@@ -42505,8 +42653,11 @@ async function runSkillsUninstall(slug, opts = {}) {
42505
42653
  }
42506
42654
  }
42507
42655
  function registerSkillsCommand(program2) {
42508
- const skills = program2.command("skills").description("Browse and manage skills.sh skills").action(() => {
42509
- render_default(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(App2, {}, undefined, false, undefined, this), { alternateScreen: true });
42656
+ const skills = program2.command("skills").description("Browse and manage skills.sh skills").action(async () => {
42657
+ const updateNotice = await getUpdateBadge();
42658
+ render_default(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(App2, {
42659
+ updateNotice
42660
+ }, undefined, false, undefined, this), { alternateScreen: true });
42510
42661
  });
42511
42662
  skills.command("search <query>").description("Search skills.sh").option("--json", "output JSON instead of a formatted list").action(async (query, opts) => {
42512
42663
  await runSkillsSearch(query, opts);
@@ -42523,50 +42674,6 @@ function registerSkillsCommand(program2) {
42523
42674
  return skills;
42524
42675
  }
42525
42676
 
42526
- // src/lib/dist-channel.ts
42527
- var DIST_CHANNEL = "npm";
42528
-
42529
- // src/lib/updater.ts
42530
- var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
42531
- var DEFAULT_GITHUB_API = "https://api.github.com/repos/exodus-ai-org/exodus-cli";
42532
- function getCurrentVersion() {
42533
- return version;
42534
- }
42535
- function isNewerVersion(current, latest) {
42536
- const c = current.split(".").map(Number);
42537
- const l = latest.split(".").map(Number);
42538
- for (let i = 0;i < 3; i++) {
42539
- const cv = c[i] ?? 0;
42540
- const lv = l[i] ?? 0;
42541
- if (lv > cv)
42542
- return true;
42543
- if (lv < cv)
42544
- return false;
42545
- }
42546
- return false;
42547
- }
42548
- async function getLatestNpmVersion(baseUrl = DEFAULT_NPM_REGISTRY) {
42549
- const res = await fetch(`${baseUrl}/exodus-cli/latest`);
42550
- if (!res.ok)
42551
- throw new Error(`npm registry lookup failed: ${res.status}`);
42552
- const body = await res.json();
42553
- return body.version;
42554
- }
42555
- async function getLatestRelease(baseUrl = DEFAULT_GITHUB_API) {
42556
- const res = await fetch(`${baseUrl}/releases/latest`);
42557
- if (!res.ok)
42558
- throw new Error(`GitHub releases lookup failed: ${res.status}`);
42559
- return await res.json();
42560
- }
42561
- async function checkForUpdate() {
42562
- const current = getCurrentVersion();
42563
- const latest = DIST_CHANNEL === "npm" ? await getLatestNpmVersion() : (await getLatestRelease()).tag_name.replace(/^v/, "");
42564
- if (!isNewerVersion(current, latest)) {
42565
- return { updateAvailable: false, current };
42566
- }
42567
- return { updateAvailable: true, current, latest };
42568
- }
42569
-
42570
42677
  // src/commands/update.ts
42571
42678
  async function runUpdate(opts = {}) {
42572
42679
  try {
@@ -42596,6 +42703,7 @@ function registerUpdateCommand(program2) {
42596
42703
  }
42597
42704
 
42598
42705
  // src/cli.ts
42706
+ var REFRESH_TIMEOUT_MS = 1e4;
42599
42707
  function buildProgram() {
42600
42708
  const program2 = new Command;
42601
42709
  program2.name("exodus").description("exodus-cli \u2014 launch Exodus, manage skills, and self-update").version(version);
@@ -42604,10 +42712,40 @@ function buildProgram() {
42604
42712
  registerSkillsCommand(program2);
42605
42713
  return program2;
42606
42714
  }
42715
+ function wantsUpdateNotice(argv) {
42716
+ if (argv.includes(UPDATE_CHECK_ARG))
42717
+ return false;
42718
+ return argv[2] !== "update";
42719
+ }
42720
+ async function runCli(argv = process.argv, runtime = {}) {
42721
+ const {
42722
+ parse = (a) => buildProgram().parseAsync(a),
42723
+ prepareNotice = prepareUpdateNotice,
42724
+ refreshCache = refreshUpdateCache,
42725
+ writeNotice = (message) => console.error(message),
42726
+ exit = () => process.exit(0),
42727
+ timeoutMs = REFRESH_TIMEOUT_MS
42728
+ } = runtime;
42729
+ if (argv.includes(UPDATE_CHECK_ARG)) {
42730
+ const deadline = new Promise((resolve2) => {
42731
+ setTimeout(resolve2, timeoutMs).unref();
42732
+ });
42733
+ await Promise.race([refreshCache(), deadline.then(exit)]);
42734
+ return;
42735
+ }
42736
+ const pending = wantsUpdateNotice(argv) ? prepareNotice().catch(() => null) : Promise.resolve(null);
42737
+ try {
42738
+ await parse(argv);
42739
+ } finally {
42740
+ const message = await pending;
42741
+ if (message)
42742
+ writeNotice(message);
42743
+ }
42744
+ }
42607
42745
 
42608
42746
  // index.ts
42609
42747
  try {
42610
- await buildProgram().parseAsync(process.argv);
42748
+ await runCli(process.argv);
42611
42749
  } catch (err) {
42612
42750
  console.error(err instanceof Error ? err.message : String(err));
42613
42751
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exodus-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "engines": {
5
5
  "bun": ">=1.3.0"
6
6
  },