tokentracker-cli 0.87.3 → 0.87.5

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 (118) hide show
  1. package/dashboard/dist/assets/AchievementBadge-_BbqyV4s.js +1 -0
  2. package/dashboard/dist/assets/{AchievementsPage-SaivmNs0.js → AchievementsPage-DlXdQMuk.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-DhS-6gDl.js → AchievementsSection-BAL23Qum.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-BfCesQLf.js → BadgeDetailModal-D5VYBn1e.js} +1 -1
  5. package/dashboard/dist/assets/{Card-Cjxl-RbP.js → Card-BCAAH2Tv.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-CErdixw_.js → ClawdAnimated-BaeJ4goq.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-MpC6EdTx.js → CommandPalette-DaBo0N_t.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-C27kiCWg.js → CommunityStatsModal-DtnbqllK.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-BzHs6qGZ.js → ConfirmModal-BdIbatZd.js} +1 -1
  10. package/dashboard/dist/assets/{Controls-DJEylji5.js → Controls-CtlnwG2h.js} +1 -1
  11. package/dashboard/dist/assets/DashboardPage-B7YSxJXW.js +1 -0
  12. package/dashboard/dist/assets/{DevicePage-DS8_oq1n.js → DevicePage-Y1QufKqx.js} +1 -1
  13. package/dashboard/dist/assets/{DialogClose-6TytC4Cj.js → DialogClose-BA6Fr6X7.js} +1 -1
  14. package/dashboard/dist/assets/DialogDescription-JjVfYyrp.js +1 -0
  15. package/dashboard/dist/assets/DialogTitle-CQlYwz-I.js +1 -0
  16. package/dashboard/dist/assets/{FadeIn-BzTGgpq_.js → FadeIn-C7oxsfI-.js} +1 -1
  17. package/dashboard/dist/assets/{HeaderGithubStar-CKSGB-Lq.js → HeaderGithubStar-5eKhC60J.js} +1 -1
  18. package/dashboard/dist/assets/{HoverTooltip-Ds-FyYmY.js → HoverTooltip-Tf9XsZ6G.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-DCWqmmXM.js → IpCheckPage-DEK7d2P9.js} +1 -1
  20. package/dashboard/dist/assets/{LandingPage-B57cM3zn.js → LandingPage-Cev8vQ3V.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardAvatar-CYi8ENUq.js → LeaderboardAvatar-BP8r8s_o.js} +1 -1
  22. package/dashboard/dist/assets/LeaderboardPage-CFinKL9O.js +2 -0
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-BGc4myMi.js → LeaderboardProfileModal-DKSPH2_U.js} +4 -4
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-BB5dtkaS.js → LeaderboardProfilePage-DNSIoOdU.js} +3 -3
  25. package/dashboard/dist/assets/{LimitsPage-DofmgnvS.js → LimitsPage-DDljQnW7.js} +4 -4
  26. package/dashboard/dist/assets/{LocalOnlyNotice-CsLVVopf.js → LocalOnlyNotice-BuNHRmPw.js} +1 -1
  27. package/dashboard/dist/assets/{LoginCard-vC7vbKwK.js → LoginCard-Dq5-__UO.js} +1 -1
  28. package/dashboard/dist/assets/{LoginModal-BDKHs5ZP.js → LoginModal-BWuDhVTL.js} +1 -1
  29. package/dashboard/dist/assets/{LoginPage-Bb9LKA69.js → LoginPage-Dwb39YqH.js} +1 -1
  30. package/dashboard/dist/assets/{PetPage-7LGU7zwk.js → PetPage-BcvAOVH2.js} +1 -1
  31. package/dashboard/dist/assets/PopoverPopup-CnywL4jN.js +1 -0
  32. package/dashboard/dist/assets/{ProviderIcon-CBhScwLZ.js → ProviderIcon-DAn58wfZ.js} +1 -1
  33. package/dashboard/dist/assets/{ResetPasswordPage-DRWSTsTW.js → ResetPasswordPage-B-iZ9m8Z.js} +1 -1
  34. package/dashboard/dist/assets/{SegmentedControl-DGeMH2p8.js → SegmentedControl-DUlvUP02.js} +1 -1
  35. package/dashboard/dist/assets/{Select-BQOi6gWt.js → Select-BbVO0yyC.js} +1 -1
  36. package/dashboard/dist/assets/SelectItemText-BCo4iRHj.js +1 -0
  37. package/dashboard/dist/assets/{ServiceStatusPage-CSwBKd-J.js → ServiceStatusPage-Bf65rMHS.js} +1 -1
  38. package/dashboard/dist/assets/{SessionsPage-DqtNnt6J.js → SessionsPage-D2QWuLy9.js} +2 -2
  39. package/dashboard/dist/assets/SettingsPage-BtlZ7v0t.js +1 -0
  40. package/dashboard/dist/assets/SkillsPage-D7CXkuxF.js +1 -0
  41. package/dashboard/dist/assets/{TokenGalaxy-DnUDoJJZ.js → TokenGalaxy-B1VqfzOB.js} +1 -1
  42. package/dashboard/dist/assets/ToolbarRootContext-DlRA_RFX.js +1 -0
  43. package/dashboard/dist/assets/{TrendMonitor-BFvROIwx.js → TrendMonitor-D945Vzv_.js} +1 -1
  44. package/dashboard/dist/assets/{WidgetsPage-zHv1cbWR.js → WidgetsPage-CZLhBVQL.js} +1 -1
  45. package/dashboard/dist/assets/{WrappedPage-CRtyzIB9.js → WrappedPage-Gf15Ax3y.js} +1 -1
  46. package/dashboard/dist/assets/__vite-browser-external-CRYifSM4.css +1 -0
  47. package/dashboard/dist/assets/{__vite-browser-external-RQ7lZFvr.js → __vite-browser-external-_-wIz90P.js} +11 -4
  48. package/dashboard/dist/assets/{agent-logos-BNkfzMFo.js → agent-logos-Ccd-gCmY.js} +1 -1
  49. package/dashboard/dist/assets/{arrow-up-right-Bk4cPgo2.js → arrow-up-right-DksoZ7Fm.js} +1 -1
  50. package/dashboard/dist/assets/{check-CliWbD81.js → check-CFyujViz.js} +1 -1
  51. package/dashboard/dist/assets/{chevron-right-Cm3fepaG.js → chevron-right-D2Y9ycoK.js} +1 -1
  52. package/dashboard/dist/assets/{copy-C10Ij0y6.js → copy-CvzVu4Jv.js} +1 -1
  53. package/dashboard/dist/assets/{download-B33Otf6v.js → download-M9hdGe0X.js} +1 -1
  54. package/dashboard/dist/assets/{external-link-CIFlqTbQ.js → external-link-BrvgYr3H.js} +1 -1
  55. package/dashboard/dist/assets/{flame-D9HUvqS6.js → flame-CanCCWfM.js} +1 -1
  56. package/dashboard/dist/assets/{format-tokens-CHDafmHO.js → format-tokens-D-x5NM5s.js} +1 -1
  57. package/dashboard/dist/assets/{icons-DvNfSar6.js → icons-iDxUXUbD.js} +1 -1
  58. package/dashboard/dist/assets/{index-CA_EbNuG.js → index-BPe3pmGu.js} +1 -1
  59. package/dashboard/dist/assets/{index-Bc5ZBKpe.js → index-CDK-qlvv.js} +1 -1
  60. package/dashboard/dist/assets/index-DhgQVkQR.js +9 -0
  61. package/dashboard/dist/assets/{info-DPC10oQ_.js → info-CS19GYkG.js} +1 -1
  62. package/dashboard/dist/assets/{limits-providers-C0Pz6mbN.js → limits-providers-Cep8gK1J.js} +1 -1
  63. package/dashboard/dist/assets/{link-2-WzNaAMth.js → link-2-_GqbzcZy.js} +1 -1
  64. package/dashboard/dist/assets/{loader-circle-C6xlopLF.js → loader-circle-DQlJKeCp.js} +1 -1
  65. package/dashboard/dist/assets/{maximize-2-0KiBFLqc.js → maximize-2-AaA61WoH.js} +1 -1
  66. package/dashboard/dist/assets/modifiers.esm-DgAD_X3C.js +1 -0
  67. package/dashboard/dist/assets/module-Ci-Lrj0I.js +2 -0
  68. package/dashboard/dist/assets/monitor-smartphone-C-KIKlP-.js +1 -0
  69. package/dashboard/dist/assets/provider-display-BWK3HqpN.js +42 -0
  70. package/dashboard/dist/assets/react-BGB0fHad.js +1 -0
  71. package/dashboard/dist/assets/{search-DcIhgvPK.js → search-BhQx4B4T.js} +1 -1
  72. package/dashboard/dist/assets/skills-api-BtsImOWQ.js +1 -0
  73. package/dashboard/dist/assets/sortable.esm-ew4S8Y1L.js +5 -0
  74. package/dashboard/dist/assets/store-OBiS6gfr.js +1 -0
  75. package/dashboard/dist/assets/{terminal-BrKrjIwy.js → terminal-GvWRPCY9.js} +1 -1
  76. package/dashboard/dist/assets/{trash-2-D-Z1EH3A.js → trash-2-COv_kp05.js} +1 -1
  77. package/dashboard/dist/assets/{use-community-stats-BpeauqPm.js → use-community-stats-COxOg9PE.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-DgfcukGo.js → use-limits-display-prefs-wf8pf1cF.js} +1 -1
  79. package/dashboard/dist/assets/use-ordered-list-Dw-kLFB1.js +1 -0
  80. package/dashboard/dist/assets/use-reduced-motion-B3wPPJY8.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-BQIz2bb3.js → use-session-efficiency-pref-D9X2CzAz.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-0oOtot3w.js → use-transform-BgfBM2dC.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-CIPhW6GX.js → use-usage-limits-CrHu9ZJi.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-D-HSApxR.js → useCurrency-CmI0tWeH.js} +1 -1
  85. package/dashboard/dist/assets/useScrollLock-BtUfInbu.js +1 -0
  86. package/dashboard/dist/assets/{useTokenFormat-DlmxtsxU.js → useTokenFormat-DAHyFBMr.js} +1 -1
  87. package/dashboard/dist/assets/zap-C9NzWrn0.js +1 -0
  88. package/dashboard/dist/index.html +2 -2
  89. package/dashboard/dist/ip-check.html +2 -2
  90. package/dashboard/dist/leaderboard.html +2 -2
  91. package/dashboard/dist/share.html +2 -2
  92. package/package.json +1 -1
  93. package/src/lib/codex-token-refresh.js +35 -7
  94. package/src/lib/local-api.js +1 -1
  95. package/src/lib/pricing/seed-snapshot.json +1 -1
  96. package/src/lib/skills-manager.js +170 -7
  97. package/src/lib/usage-limits.js +39 -28
  98. package/dashboard/dist/assets/AchievementBadge--BdQsk7Z.js +0 -1
  99. package/dashboard/dist/assets/DashboardPage-BmrvChmn.js +0 -1
  100. package/dashboard/dist/assets/DialogDescription-BoEv861o.js +0 -1
  101. package/dashboard/dist/assets/DialogTitle-DpOXGzsp.js +0 -1
  102. package/dashboard/dist/assets/LeaderboardPage-DfL1iYB4.js +0 -2
  103. package/dashboard/dist/assets/PopoverPopup-DRzlyb01.js +0 -1
  104. package/dashboard/dist/assets/SelectItemText-BXpgzRHS.js +0 -1
  105. package/dashboard/dist/assets/SettingsPage-C9Bz2SMu.js +0 -1
  106. package/dashboard/dist/assets/SkillsPage-DrZdvGMd.js +0 -1
  107. package/dashboard/dist/assets/ToolbarRootContext-D9L4QmqU.js +0 -1
  108. package/dashboard/dist/assets/__vite-browser-external-CzQk3FUc.css +0 -1
  109. package/dashboard/dist/assets/index-DjOuhnCT.js +0 -9
  110. package/dashboard/dist/assets/module-Bah3ijPg.js +0 -2
  111. package/dashboard/dist/assets/provider-display-BPM_UujC.js +0 -42
  112. package/dashboard/dist/assets/react-BvxvY3fZ.js +0 -1
  113. package/dashboard/dist/assets/skills-api-Nx7WQwKb.js +0 -1
  114. package/dashboard/dist/assets/store-uw9hVnuM.js +0 -1
  115. package/dashboard/dist/assets/use-ordered-list-CSJMavMY.js +0 -5
  116. package/dashboard/dist/assets/use-reduced-motion-BZLtq0n-.js +0 -1
  117. package/dashboard/dist/assets/useScrollLock-ydGaZTyX.js +0 -1
  118. package/dashboard/dist/assets/zap-CrW1Qm1b.js +0 -1
@@ -14,28 +14,63 @@ const DEFAULT_REPOS = [
14
14
 
15
15
  const TARGETS = {
16
16
  claude: { id: "claude", label: "Claude", dir: () => path.join(os.homedir(), ".claude", "skills") },
17
- codex: { id: "codex", label: "Codex", dir: () => path.join(os.homedir(), ".codex", "skills") },
17
+ codex: { id: "codex", label: "Codex", dir: () => path.join(resolveCodexHome(), "skills") },
18
18
  grok: { id: "grok", label: "Grok", dir: () => path.join(resolveGrokHome(process.env), "skills") },
19
19
  antigravity: { id: "antigravity", label: "Antigravity", dirs: () => resolveAntigravitySkillDirs(process.env) },
20
20
  gemini: { id: "gemini", label: "Gemini", dir: () => path.join(os.homedir(), ".gemini", "skills") },
21
21
  opencode: { id: "opencode", label: "OpenCode", dir: () => path.join(os.homedir(), ".config", "opencode", "skills") },
22
+ openclaw: { id: "openclaw", label: "OpenClaw", dir: resolveOpenClawSkillsDir },
22
23
  hermes: { id: "hermes", label: "Hermes", dir: () => path.join(os.homedir(), ".hermes", "skills") },
24
+ // ZCode currently exposes skills through its plugin cache rather than a
25
+ // documented user-writable skills directory. Surface it in the inventory
26
+ // and filters, but never offer sync/delete operations against the cache.
27
+ zcode: { id: "zcode", label: "ZCode", manageable: false },
23
28
  agents: { id: "agents", label: "Agents", visible: false, dir: () => path.join(os.homedir(), ".agents", "skills") },
24
29
  };
25
30
 
31
+ function resolveCodexHome() {
32
+ return String(process.env.CODEX_HOME || "").trim() || path.join(os.homedir(), ".codex");
33
+ }
34
+
35
+ function resolveZcodeHome() {
36
+ return String(process.env.ZCODE_HOME || "").trim() || path.join(os.homedir(), ".zcode");
37
+ }
38
+
39
+ function resolveOpenClawHome() {
40
+ return String(process.env.TOKENTRACKER_OPENCLAW_HOME || "").trim() || path.join(os.homedir(), ".openclaw");
41
+ }
42
+
43
+ // OpenClaw can point at an arbitrary active workspace. Only scan that one
44
+ // explicit workspace; recursively scanning ~/.openclaw would incorrectly
45
+ // import backup/config snapshots belonging to other devices.
46
+ function resolveOpenClawSkillsDir() {
47
+ const home = resolveOpenClawHome();
48
+ const override = String(process.env.TOKENTRACKER_OPENCLAW_WORKSPACE || "").trim();
49
+ let workspace = override;
50
+ if (!workspace) {
51
+ const config = readJson(path.join(home, "openclaw.json"), null);
52
+ workspace = String(config?.agents?.defaults?.workspace || "").trim();
53
+ }
54
+ if (!workspace) workspace = path.join(home, "workspace");
55
+ if (!path.isAbsolute(workspace)) workspace = path.resolve(home, workspace);
56
+ return path.join(workspace, "skills");
57
+ }
58
+
26
59
  // Dual contract: a target exposes either dir() → string (single path) or
27
60
  // dirs() → string[] (parallel-write to multiple paths, e.g. Antigravity which
28
61
  // has separate user-skills dirs for the main app and the IDE). Consumers must
29
62
  // route through these helpers so the single-path targets stay zero-overhead.
30
63
  function targetDirs(target) {
64
+ if (!target || target.manageable === false) return [];
31
65
  if (typeof target.dirs === "function") return target.dirs();
32
- return [target.dir()];
66
+ if (typeof target.dir === "function") return [target.dir()];
67
+ return [];
33
68
  }
34
69
 
35
70
  // Used for surfacing one path in UI/local-api responses. For multi-dir targets
36
71
  // this is the canonical "first" entry by convention (main app before IDE).
37
72
  function targetPrimaryDir(target) {
38
- return targetDirs(target)[0];
73
+ return targetDirs(target)[0] || null;
39
74
  }
40
75
 
41
76
  const FETCH_TIMEOUT_MS = 20_000;
@@ -203,6 +238,7 @@ function targetList() {
203
238
  id: target.id,
204
239
  label: target.label,
205
240
  path: targetPrimaryDir(target),
241
+ manageable: target.manageable !== false,
206
242
  }));
207
243
  }
208
244
 
@@ -267,7 +303,7 @@ function findSkillMarker(dir) {
267
303
 
268
304
  const MAX_LOCAL_SKILL_SCAN_DEPTH = 3;
269
305
 
270
- function scanSkillDirectories(rootDir) {
306
+ function scanSkillDirectories(rootDir, { maxDepth = MAX_LOCAL_SKILL_SCAN_DEPTH } = {}) {
271
307
  const found = [];
272
308
  const walk = (dir, relDir = "", depth = 0) => {
273
309
  let entries = [];
@@ -288,13 +324,136 @@ function scanSkillDirectories(rootDir) {
288
324
  }
289
325
  // Direct symlinked skills are accepted above, but symlinked group folders
290
326
  // are not traversed so the scan stays within the target skills tree.
291
- if (entry.isDirectory() && depth + 1 < MAX_LOCAL_SKILL_SCAN_DEPTH) walk(full, rel, depth + 1);
327
+ if (entry.isDirectory() && depth + 1 < maxDepth) walk(full, rel, depth + 1);
292
328
  }
293
329
  };
294
330
  walk(rootDir);
295
331
  return found;
296
332
  }
297
333
 
334
+ const INVENTORY_PLUGIN_SCAN_DEPTH = 6;
335
+
336
+ function completeTargetStates(activeTargetId) {
337
+ return Object.fromEntries(
338
+ Object.keys(TARGETS).map((id) => [id, id === activeTargetId ? "synced" : "off"]),
339
+ );
340
+ }
341
+
342
+ function inventorySkillFromMarker({
343
+ marker,
344
+ directory,
345
+ id,
346
+ targetId,
347
+ scope,
348
+ sourceName = null,
349
+ }) {
350
+ try {
351
+ const metadata = readSkillMetadata(
352
+ fs.readFileSync(marker, "utf8"),
353
+ installNameFromDirectory(directory) || directory,
354
+ );
355
+ return {
356
+ id,
357
+ key: id,
358
+ name: metadata.name,
359
+ description: metadata.description,
360
+ directory,
361
+ readmeUrl: null,
362
+ repoOwner: null,
363
+ repoName: null,
364
+ repoBranch: null,
365
+ installedAt: null,
366
+ managed: false,
367
+ readOnly: true,
368
+ inventoryOnly: true,
369
+ scope,
370
+ sourceName,
371
+ targets: [targetId],
372
+ targetStates: completeTargetStates(targetId),
373
+ };
374
+ } catch (_e) {
375
+ return null;
376
+ }
377
+ }
378
+
379
+ // Plugin caches use <publisher>/<plugin>/<version>/skills/<skill>/SKILL.md.
380
+ // Walk only far enough to find a directory literally named "skills", then use
381
+ // the bounded skill scanner below it. We never traverse node_modules, hidden
382
+ // metadata directories, or symlinked group directories.
383
+ function scanPluginInventoryRoot({ root, targetId }) {
384
+ const found = [];
385
+ const walk = (dir, depth = 0) => {
386
+ let entries = [];
387
+ try {
388
+ entries = fs.readdirSync(dir, { withFileTypes: true });
389
+ } catch (_e) {
390
+ return;
391
+ }
392
+ entries.sort((a, b) => a.name.localeCompare(b.name));
393
+ for (const entry of entries) {
394
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
395
+ if (!entry.name || entry.name.startsWith(".") || entry.name === "node_modules") continue;
396
+ const full = path.join(dir, entry.name);
397
+ if (entry.name.toLowerCase() === "skills") {
398
+ const container = path.relative(root, full).replace(/\\/g, "/");
399
+ const beforeSkills = container.split("/").filter(Boolean).slice(0, -1);
400
+ // Drop the cache version from the identity so installing a newer plugin
401
+ // version updates one inventory row instead of creating a duplicate.
402
+ const looksLikeVersion = (value) => /^v?\d+(?:\.\d+)*$/.test(String(value || ""));
403
+ const packageParts =
404
+ beforeSkills.length > 1 && looksLikeVersion(beforeSkills[beforeSkills.length - 1])
405
+ ? beforeSkills.slice(0, -1)
406
+ : beforeSkills;
407
+ const sourceName = packageParts.join("/") || beforeSkills.join("/") || null;
408
+ for (const directory of scanSkillDirectories(full, { maxDepth: 5 })) {
409
+ const marker = findSkillMarker(path.join(full, directory));
410
+ if (!marker) continue;
411
+ const sourceKey = sourceName || "plugin";
412
+ const id = `inventory:${targetId}:plugin:${sourceKey}:${directory}`;
413
+ const skill = inventorySkillFromMarker({
414
+ marker,
415
+ directory,
416
+ id,
417
+ targetId,
418
+ scope: "plugin",
419
+ sourceName,
420
+ });
421
+ if (skill) found.push(skill);
422
+ }
423
+ continue;
424
+ }
425
+ if (depth + 1 < INVENTORY_PLUGIN_SCAN_DEPTH) walk(full, depth + 1);
426
+ }
427
+ };
428
+ walk(root);
429
+ return found;
430
+ }
431
+
432
+ function listInventorySkills() {
433
+ const sources = [
434
+ {
435
+ root: path.join(resolveCodexHome(), "plugins", "cache"),
436
+ targetId: "codex",
437
+ },
438
+ {
439
+ root: path.join(os.homedir(), ".claude", "plugins", "cache"),
440
+ targetId: "claude",
441
+ },
442
+ {
443
+ root: path.join(resolveZcodeHome(), "cli", "plugins", "cache"),
444
+ targetId: "zcode",
445
+ },
446
+ ];
447
+ const byId = new Map();
448
+ for (const source of sources) {
449
+ const skills = scanPluginInventoryRoot(source);
450
+ // Later (normally newer, lexically sorted cache version) copies replace an
451
+ // older version with the same stable plugin+skill identity.
452
+ for (const skill of skills) byId.set(skill.id.toLowerCase(), skill);
453
+ }
454
+ return Array.from(byId.values());
455
+ }
456
+
298
457
  const HASH_IGNORE = new Set([".git", ".DS_Store", "Thumbs.db", ".gitignore"]);
299
458
 
300
459
  // Stable content fingerprint of a skill directory: walk files in sorted order,
@@ -625,7 +784,7 @@ function removeEmptyAncestors(startDir, stopDir) {
625
784
 
626
785
  function syncSkillToTarget(directory, targetId) {
627
786
  const target = TARGETS[targetId];
628
- if (!target) throw new Error(`Unsupported target: ${targetId}`);
787
+ if (!target || target.manageable === false) throw new Error(`Unsupported target: ${targetId}`);
629
788
  const source = managedSkillPath(directory);
630
789
  if (!fs.existsSync(source)) throw new Error(`Managed skill not found: ${directory}`);
631
790
  for (const baseDir of targetDirs(target)) {
@@ -738,7 +897,11 @@ function listInstalledSkills() {
738
897
  }
739
898
  }
740
899
 
741
- return [...managed, ...unmanaged.values()].sort((a, b) => a.name.localeCompare(b.name));
900
+ return [...managed, ...unmanaged.values(), ...listInventorySkills()].sort((a, b) => {
901
+ const byName = a.name.localeCompare(b.name);
902
+ if (byName !== 0) return byName;
903
+ return String(a.id || a.key || "").localeCompare(String(b.id || b.key || ""));
904
+ });
742
905
  }
743
906
 
744
907
  async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
@@ -523,15 +523,16 @@ async function fetchCodexUsageLimits(
523
523
  // 401/403/404 from wham means "no usage data available for this auth state" — render
524
524
  // a neutral empty state instead of a red "Fetch failed" error.
525
525
  if (res.status === 401 || res.status === 403 || res.status === 404) {
526
- return { body: null };
526
+ return { body: null, status: res.status };
527
527
  }
528
528
  if (res.status !== 200) {
529
529
  throw new Error(`Codex API returned ${res.status}`);
530
530
  }
531
- return { body: await res.json() };
531
+ return { body: await res.json(), status: res.status };
532
532
  }), "Codex", providerTimeoutMs);
533
533
  if (!usage.body) {
534
534
  return {
535
+ upstream_status: usage.status,
535
536
  primary_window: null,
536
537
  secondary_window: null,
537
538
  credit_window: null,
@@ -558,6 +559,7 @@ async function fetchCodexUsageLimits(
558
559
  }
559
560
  }
560
561
  return {
562
+ upstream_status: usage.status,
561
563
  ...normalizeCodexRateWindows(body.rate_limit || {}),
562
564
  credit_window: normalizeCodexCreditWindow(body.spend_control?.individual_limit),
563
565
  ...normalizeCodexSparkRateWindows(body.additional_rate_limits),
@@ -3051,14 +3053,14 @@ async function fetchUsageLimitsUncached({
3051
3053
  ]);
3052
3054
  const claudePlanType = claudeSubscription?.planType || null;
3053
3055
 
3054
- // Proactively refresh Codex tokens that are >8 days stale, mirroring CodexBar's
3055
- // CodexTokenRefresher.swift. Without this, users who logged in once and didn't run
3056
- // `codex` for >a week get wham 401 "Fetch failed" (issue #52). Best-effort: any
3057
- // refresh failure falls through to using the existing (possibly stale) token, then the
3058
- // 4xx graceful path in fetchCodexUsageLimits surfaces a neutral state instead of red.
3056
+ // Match the official Codex CLI: prefer the access token's JWT expiry and refresh only
3057
+ // within five minutes of it; fall back to last_refresh >8 days for opaque/legacy tokens.
3058
+ // Best-effort: a refresh failure still falls through to the existing access token.
3059
3059
  let refreshError = null;
3060
3060
  let codexAuthRefreshed = codexAuth;
3061
- if (codexAuth && isTokenStale(codexAuth.lastRefresh) && codexAuth.refreshToken) {
3061
+ if (codexAuth
3062
+ && isTokenStale(codexAuth.lastRefresh, nowMs, codexAuth.accessToken)
3063
+ && codexAuth.refreshToken) {
3062
3064
  try {
3063
3065
  const newTokens = await refreshCodexTokens({
3064
3066
  refreshToken: codexAuth.refreshToken,
@@ -3225,28 +3227,18 @@ async function fetchUsageLimitsUncached({
3225
3227
  claude.service_status = claudeServiceStatus;
3226
3228
  }
3227
3229
 
3230
+ const codexRefreshRequiresReauth = refreshError?.code === "REFRESH_TOKEN_EXPIRED";
3231
+ const codexLiveUsageSucceeded = codexResult?.status === "fulfilled"
3232
+ && codexResult.value.upstream_status === 200;
3228
3233
  let codex;
3229
3234
  if (!codexToken) {
3230
3235
  codex = { configured: false };
3231
- } else if (refreshError && refreshError.code === "REFRESH_TOKEN_EXPIRED") {
3232
- // Refresh token is dead — the user must re-run `codex` to log in again. Surface a
3233
- // specific, actionable message rather than the generic "Fetch failed".
3234
- codex = {
3235
- configured: true,
3236
- error: refreshError.message,
3237
- auth_action_required: "reauth",
3238
- };
3239
- } else if (!codexResult || codexResult.status === "rejected") {
3240
- // Live fetch failed or timed out (Codex hits chatgpt.com fresh every poll with no
3241
- // rate-limit cooldown, so a slow network shows up here often). Serve the last successful
3242
- // read so the bars stay visible instead of a red error, mirroring the Claude stale path.
3243
- const cached = readCodexLimitsCache({ home, nowMs });
3244
- if (cached) {
3245
- codex = cached;
3246
- } else {
3247
- codex = { configured: true, error: codexResult?.reason?.message || "Unknown error" };
3248
- }
3249
- } else {
3236
+ } else if (codexResult?.status === "fulfilled"
3237
+ && (!codexRefreshRequiresReauth || codexLiveUsageSucceeded)) {
3238
+ // A proactive refresh can fail while the existing access token is still valid.
3239
+ // Prefer a confirmed 200 live usage read over the refresh error so usable quota
3240
+ // data is not discarded before the access token actually expires. A neutral
3241
+ // 401/403/404 no-data response must not mask a failed refresh.
3250
3242
  codex = {
3251
3243
  configured: true,
3252
3244
  error: null,
@@ -3257,13 +3249,32 @@ async function fetchUsageLimitsUncached({
3257
3249
  spark_primary_window: codexResult.value.spark_primary_window,
3258
3250
  spark_secondary_window: codexResult.value.spark_secondary_window,
3259
3251
  reset_credits: codexResult.value.reset_credits,
3260
- // Live read is current as of now; the stale fallback path above serves the
3252
+ // Live read is current as of now; the stale fallback path below serves the
3261
3253
  // disk cache with `stale: true`. Always emitting both lets the client show a
3262
3254
  // data-age label uniformly (see the Claude live-success block).
3263
3255
  stale: false,
3264
3256
  cached_at: new Date(nowMs).toISOString(),
3265
3257
  };
3266
3258
  writeCodexLimitsCache(codex, { home, nowMs });
3259
+ } else if (codexRefreshRequiresReauth) {
3260
+ // Refresh token is dead — the user must re-run `codex` to log in again. Surface a
3261
+ // specific, actionable message rather than the generic "Fetch failed", but only
3262
+ // after the existing access token also failed to fetch live usage above.
3263
+ codex = {
3264
+ configured: true,
3265
+ error: refreshError.message,
3266
+ auth_action_required: "reauth",
3267
+ };
3268
+ } else if (!codexResult || codexResult.status === "rejected") {
3269
+ // Live fetch failed or timed out (Codex hits chatgpt.com fresh every poll with no
3270
+ // rate-limit cooldown, so a slow network shows up here often). Serve the last successful
3271
+ // read so the bars stay visible instead of a red error, mirroring the Claude stale path.
3272
+ const cached = readCodexLimitsCache({ home, nowMs });
3273
+ if (cached) {
3274
+ codex = cached;
3275
+ } else {
3276
+ codex = { configured: true, error: codexResult?.reason?.message || "Unknown error" };
3277
+ }
3267
3278
  }
3268
3279
 
3269
3280
  const data = {
@@ -1 +0,0 @@
1
- import{m as o,d as N,j as r}from"./__vite-browser-external-RQ7lZFvr.js";import{Z as $}from"./zap-CrW1Qm1b.js";import{F as A}from"./flame-D9HUvqS6.js";const j=[["path",{d:"M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2",key:"1ah6g2"}],["rect",{x:"14",y:"2",width:"8",height:"8",rx:"1",key:"88lufb"}]],E=o("blocks",j);const L=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],C=o("brain",L);const T=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M16 14h.01",key:"1gbofw"}],["path",{d:"M8 18h.01",key:"lrp35t"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M16 18h.01",key:"kzsmim"}]],z=o("calendar-days",T);const B=[["path",{d:"M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z",key:"1vdc57"}],["path",{d:"M5 21h14",key:"11awu3"}]],q=o("crown",B);const H=[["path",{d:"M12.67 19a2 2 0 0 0 1.416-.588l6.154-6.172a6 6 0 0 0-8.49-8.49L5.586 9.914A2 2 0 0 0 5 11.328V18a1 1 0 0 0 1 1z",key:"18jl4k"}],["path",{d:"M16 8 2 22",key:"vp34q"}],["path",{d:"M17.5 15H9",key:"1oz8nu"}]],R=o("feather",H);const D=[["path",{d:"M18 19a5 5 0 0 1-5-5v8",key:"sz5oeg"}],["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5",key:"1w6njk"}],["circle",{cx:"13",cy:"12",r:"2",key:"1j92g6"}],["circle",{cx:"20",cy:"19",r:"2",key:"1obnsp"}]],F=o("folder-git-2",D);const I=[["path",{d:"M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",key:"1dudjm"}],["path",{d:"M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z",key:"l2t8xc"}],["path",{d:"M16 17h4",key:"1dejxt"}],["path",{d:"M4 13h4",key:"1bwh8b"}]],G=o("footprints",I);const V=[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",key:"mvr1a0"}]],S=o("heart",V);const Z=[["path",{d:"M7.21 15 2.66 7.14a2 2 0 0 1 .13-2.2L4.4 2.8A2 2 0 0 1 6 2h12a2 2 0 0 1 1.6.8l1.6 2.14a2 2 0 0 1 .14 2.2L16.79 15",key:"143lza"}],["path",{d:"M11 12 5.12 2.2",key:"qhuxz6"}],["path",{d:"m13 12 5.88-9.8",key:"hbye0f"}],["path",{d:"M8 7h8",key:"i86dvs"}],["circle",{cx:"12",cy:"17",r:"5",key:"qbz8iq"}],["path",{d:"M12 18v-2h-.5",key:"fawc4q"}]],O=o("medal",Z);const P=[["path",{d:"M18 5h4",key:"1lhgn2"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",key:"kfwtm"}]],X=o("moon-star",P);const K=[["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}],["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09",key:"u4xsad"}],["path",{d:"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z",key:"676m9"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05",key:"92ym6u"}]],U=o("rocket",K);const W=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Y=o("shield-check",W);const J=[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]],Q=o("trending-up",J),k=[{id:"token_titan",scope:"cloud",icon:q,format:"tokens",art:"/achievements/token-titan.png"},{id:"big_day",scope:"cloud",icon:$,format:"tokens",art:"/achievements/big-day.png"},{id:"wordsmith",scope:"cloud",icon:R,format:"tokens",art:"/achievements/wordsmith.png"},{id:"marathoner",scope:"cloud",icon:G,format:"days",art:"/achievements/marathoner.png"},{id:"streak",scope:"cloud",icon:A,format:"days",art:"/achievements/streak.png"},{id:"weekend_warrior",scope:"cloud",icon:z,format:"days",art:"/achievements/weekend-warrior.png"},{id:"momentum",scope:"cloud",icon:Q,format:"multiplier",art:"/achievements/momentum.png"},{id:"polyglot",scope:"cloud",icon:C,format:"count",art:"/achievements/polyglot.png"},{id:"trendsetter",scope:"cloud",icon:U,format:"count",art:"/achievements/trendsetter.png"},{id:"multitool",scope:"cloud",icon:E,format:"count",art:"/achievements/multitool.png"},{id:"podium",scope:"cloud",icon:O,format:"rank",art:"/achievements/podium.png"},{id:"veteran",scope:"cloud",icon:Y,format:"days",art:"/achievements/veteran.png"},{id:"project_hopper",scope:"local",icon:F,format:"count",art:"/achievements/project-hopper.png"},{id:"project_devotion",scope:"local",icon:S,format:"tokens",art:"/achievements/project-devotion.png"},{id:"night_owl",scope:"local",icon:X,format:"count",art:"/achievements/night-owl.png"}],u=new Map(k.map(e=>[e.id,e])),m=new Map(k.map((e,a)=>[e.id,a]));function de(e,a){return`achievements.badge.${e}.${a}`}function ee(e){return[...e||[]].sort((a,t)=>{const c=(t?.tier||0)-(a?.tier||0);return c!==0?c:(m.get(a?.id)??99)-(m.get(t?.id)??99)})}function re(e){const a=(e||[]).filter(t=>t&&(t.tier||0)>=1&&u.has(t.id));return a.length===0?null:ee(a)[0]}const v={1:"bronze",2:"silver",3:"gold",4:"diamond"},ie=["bronze","silver","gold","diamond"],ae={bronze:{rim:["#a06a44","#5c3a22"],face:["#d29a6c","#96602f"],glyph:"#3f2817",ring:"#e8c3a0",sheen:"rgba(255, 236, 220, 0.45)",label:"#a06a44"},silver:{rim:["#aab3bb","#67707b"],face:["#dde2e7","#9ba5ae"],glyph:"#39424b",ring:"#f2f5f7",sheen:"rgba(255, 255, 255, 0.55)",label:"#8b959f"},gold:{rim:["#c9971c","#8a6508"],face:["#f2ca52","#cf9c26"],glyph:"#5c430e",ring:"#ffe9a8",sheen:"rgba(255, 248, 214, 0.55)",label:"#b8860b"},diamond:{rim:["#62b1e0","#7c6fd9"],face:["#c8ecf6","#84b4ea"],glyph:"#27496d",ring:"#eafcff",sheen:"rgba(255, 255, 255, 0.65)",label:"#5a9bd8"}},y={rim:["#a8ada9","#7c817d"],face:["#cdd2ce","#a3a8a4"],glyph:"#6a706c",ring:"#dde1de",sheen:"rgba(255, 255, 255, 0.25)",label:"#8b908c"};function te(e,a=!1){return a||!e||e<1?y:ae[v[Math.min(e,4)]]||y}function se(e){return v[e]||null}const g={xs:18,sm:24,md:44,xl:88,lg:108},f={xs:.75,sm:1,md:1.5,xl:2,lg:2};function le({badgeId:e,tier:a=0,size:t="md",locked:c=!1,label:i,className:M=""}){const s=u.get(e),h=c||!a||a<1,n=te(a,h),d=t==="fill",l=d?"100%":g[t]||g.md,b=d?4:f[t]||f.md,[_,x]=N.useState(!1),p=s?.icon||null,w=!!s?.art&&!_;return r.jsx("span",{role:i?"img":void 0,"aria-label":i||void 0,"aria-hidden":i?void 0:!0,className:`inline-flex shrink-0 rounded-full ${M}`,style:{width:l,height:l,padding:b,background:`linear-gradient(135deg, ${n.rim[0]} 0%, ${n.ring} 42%, ${n.rim[1]} 100%)`,boxShadow:d||t==="md"||t==="xl"||t==="lg"?"0 0 0 1px rgba(10, 15, 12, 0.06), 0 1px 2px rgba(10, 15, 12, 0.08)":void 0},children:w?r.jsx("img",{src:s.art,alt:"",draggable:!1,loading:"lazy",onError:()=>x(!0),className:"h-full w-full rounded-full object-cover",style:h?{filter:"grayscale(1)",opacity:.55}:void 0}):r.jsx("span",{className:"flex h-full w-full items-center justify-center rounded-full",style:{background:`linear-gradient(145deg, ${n.face[0]}, ${n.face[1]})`},children:p?r.jsx(p,{size:d?64:Math.round(l*.5),color:n.glyph,strokeWidth:2}):null})})}export{le as A,k as B,S as H,Q as T,ae as a,de as b,u as c,ie as d,re as h,te as p,ee as s,se as t};