@phnx-labs/agents-cli 1.22.21 → 1.22.23

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 (105) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +8 -2
  3. package/dist/commands/doctor.js +15 -7
  4. package/dist/commands/exec.js +20 -6
  5. package/dist/commands/focus.d.ts +76 -4
  6. package/dist/commands/focus.js +219 -40
  7. package/dist/commands/fork.d.ts +20 -2
  8. package/dist/commands/fork.js +91 -64
  9. package/dist/commands/go.d.ts +25 -0
  10. package/dist/commands/go.js +63 -2
  11. package/dist/commands/harness-wizard.d.ts +206 -0
  12. package/dist/commands/harness-wizard.js +403 -0
  13. package/dist/commands/harness.d.ts +12 -0
  14. package/dist/commands/harness.js +97 -101
  15. package/dist/commands/insights.d.ts +32 -0
  16. package/dist/commands/insights.js +478 -0
  17. package/dist/commands/resume.js +10 -3
  18. package/dist/commands/secrets.js +25 -30
  19. package/dist/commands/sessions-picker.d.ts +2 -0
  20. package/dist/commands/sessions-picker.js +1 -0
  21. package/dist/commands/sessions-resume.d.ts +21 -3
  22. package/dist/commands/sessions-resume.js +50 -11
  23. package/dist/commands/sessions.d.ts +81 -5
  24. package/dist/commands/sessions.js +331 -66
  25. package/dist/commands/watchdog.js +13 -2
  26. package/dist/index.js +3 -1
  27. package/dist/lib/agents.d.ts +1 -1
  28. package/dist/lib/agents.js +132 -0
  29. package/dist/lib/codex-policy.d.ts +17 -0
  30. package/dist/lib/codex-policy.js +48 -0
  31. package/dist/lib/crabbox/lease.d.ts +25 -0
  32. package/dist/lib/crabbox/lease.js +62 -0
  33. package/dist/lib/daemon.js +70 -0
  34. package/dist/lib/exec.d.ts +4 -0
  35. package/dist/lib/exec.js +88 -54
  36. package/dist/lib/feed-broadcast.d.ts +1 -20
  37. package/dist/lib/feed-broadcast.js +31 -1
  38. package/dist/lib/hooks.js +12 -2
  39. package/dist/lib/hosts/passthrough.js +1 -0
  40. package/dist/lib/mcp.js +44 -0
  41. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  42. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  43. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  44. package/dist/lib/models.d.ts +0 -5
  45. package/dist/lib/models.js +48 -0
  46. package/dist/lib/picker.d.ts +45 -0
  47. package/dist/lib/picker.js +75 -6
  48. package/dist/lib/plugin-marketplace.js +9 -0
  49. package/dist/lib/pricing/prices.json +119 -92
  50. package/dist/lib/pricing/table.js +13 -0
  51. package/dist/lib/remote-agents-json.d.ts +29 -1
  52. package/dist/lib/remote-agents-json.js +47 -10
  53. package/dist/lib/resources/mcp.js +2 -0
  54. package/dist/lib/resources/permissions.js +3 -0
  55. package/dist/lib/resources/types.d.ts +2 -1
  56. package/dist/lib/runner.js +28 -19
  57. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  58. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  59. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  60. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  61. package/dist/lib/secrets/index.d.ts +15 -1
  62. package/dist/lib/secrets/index.js +118 -49
  63. package/dist/lib/secrets/reaper.d.ts +87 -0
  64. package/dist/lib/secrets/reaper.js +184 -0
  65. package/dist/lib/secrets/remote.d.ts +29 -0
  66. package/dist/lib/secrets/remote.js +37 -1
  67. package/dist/lib/session/active.d.ts +36 -1
  68. package/dist/lib/session/active.js +60 -19
  69. package/dist/lib/session/actor-sidecar.d.ts +14 -0
  70. package/dist/lib/session/actor-sidecar.js +67 -4
  71. package/dist/lib/session/db.d.ts +35 -1
  72. package/dist/lib/session/db.js +132 -1
  73. package/dist/lib/session/digest.d.ts +3 -0
  74. package/dist/lib/session/digest.js +3 -3
  75. package/dist/lib/session/discover.js +168 -0
  76. package/dist/lib/session/insights.d.ts +126 -0
  77. package/dist/lib/session/insights.js +330 -0
  78. package/dist/lib/session/parse.d.ts +29 -2
  79. package/dist/lib/session/parse.js +111 -5
  80. package/dist/lib/session/remote-list.d.ts +10 -1
  81. package/dist/lib/session/remote-list.js +2 -8
  82. package/dist/lib/session/remote.d.ts +57 -3
  83. package/dist/lib/session/remote.js +90 -26
  84. package/dist/lib/session/resume-command.d.ts +6 -0
  85. package/dist/lib/session/resume-command.js +8 -0
  86. package/dist/lib/session/session-cache.d.ts +173 -0
  87. package/dist/lib/session/session-cache.js +399 -0
  88. package/dist/lib/session/types.d.ts +2 -2
  89. package/dist/lib/session/types.js +1 -1
  90. package/dist/lib/session/width.d.ts +1 -1
  91. package/dist/lib/session/width.js +12 -2
  92. package/dist/lib/shims.d.ts +2 -2
  93. package/dist/lib/shims.js +40 -5
  94. package/dist/lib/staleness/writers/hooks.js +1 -1
  95. package/dist/lib/startup/command-registry.d.ts +1 -0
  96. package/dist/lib/startup/command-registry.js +2 -0
  97. package/dist/lib/types.d.ts +1 -1
  98. package/dist/lib/usage.d.ts +13 -0
  99. package/dist/lib/usage.js +254 -3
  100. package/dist/lib/versions.js +13 -2
  101. package/package.json +1 -1
  102. package/dist/bin/agents +0 -0
  103. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  104. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
package/dist/lib/usage.js CHANGED
@@ -139,6 +139,7 @@ const USAGE_SOURCES = {
139
139
  grok: { fetch: getGrokUsageInfo, network: false },
140
140
  cursor: { fetch: getCursorUsageInfo, network: true },
141
141
  antigravity: { fetch: getAntigravityUsageInfo, network: true },
142
+ muse: { fetch: getMuseUsageInfo, network: true },
142
143
  };
143
144
  export const USAGE_SOURCE_AGENT_IDS = Object.keys(USAGE_SOURCES);
144
145
  function getUsageSource(agentId) {
@@ -605,7 +606,34 @@ export function formatUsageSection(usage) {
605
606
  /** Fetch Codex usage by scanning the most recent session files for rate-limit events. */
606
607
  async function getCodexUsageInfo(options) {
607
608
  try {
608
- const files = collectCodexSessionFiles(options?.home);
609
+ // Codex usage is read from on-disk session transcripts, which carry no
610
+ // account identity and are not removed on logout. To keep the bar scoped to
611
+ // the account signed in NOW, floor the scan at the current login time: the
612
+ // id_token's `auth_time` claim — the OIDC time-of-authentication. A session
613
+ // written before that login belongs to whoever was signed in before (e.g.
614
+ // after `codex logout` + login into a different account), and showing its
615
+ // rate_limits is the "wrong usage after switch" bug.
616
+ //
617
+ // `auth_time` — not the auth.json file mtime — is the correct floor: Codex
618
+ // rewrites auth.json on every token refresh (advancing its mtime), but a
619
+ // refresh_token grant does not re-authenticate the user, so `auth_time`
620
+ // stays at the real login. Flooring on mtime would blank the bar after each
621
+ // background refresh; flooring on `auth_time` does not. No readable
622
+ // credential means the version is signed out — report no usage. A credential
623
+ // that carries no `auth_time` falls back to no floor (prior behavior) rather
624
+ // than hide a signed-in account's usage.
625
+ const base = options?.home || os.homedir();
626
+ let sinceMs;
627
+ try {
628
+ const tokens = JSON.parse(fs.readFileSync(path.join(base, '.codex', 'auth.json'), 'utf-8')).tokens;
629
+ const authTime = decodeJwtPayload(tokens?.id_token || tokens?.access_token || '')?.auth_time;
630
+ if (typeof authTime === 'number' && authTime > 0)
631
+ sinceMs = authTime * 1000;
632
+ }
633
+ catch {
634
+ return { snapshot: null, error: null };
635
+ }
636
+ const files = collectCodexSessionFiles(options?.home, sinceMs);
609
637
  for (const filePath of files) {
610
638
  const match = await readLatestCodexRateLimits(filePath);
611
639
  if (!match)
@@ -1099,8 +1127,15 @@ function normalizeDroidWindow(window, key, label, shortLabel) {
1099
1127
  windowMinutes: inferWindowMinutes(key),
1100
1128
  };
1101
1129
  }
1102
- /** Collect Codex JSONL session files sorted newest-first. */
1103
- function collectCodexSessionFiles(home) {
1130
+ /**
1131
+ * Collect Codex JSONL session files sorted newest-first.
1132
+ *
1133
+ * `sinceMs` drops files modified before it. Codex session transcripts are not
1134
+ * tagged with the account that wrote them, so this mtime floor is how usage is
1135
+ * kept account-scoped: a session older than the current login belongs to a
1136
+ * prior account (see {@link getCodexUsageInfo}).
1137
+ */
1138
+ function collectCodexSessionFiles(home, sinceMs) {
1104
1139
  const base = home || os.homedir();
1105
1140
  const dir = path.join(base, '.codex', 'sessions');
1106
1141
  if (!fs.existsSync(dir))
@@ -1115,6 +1150,8 @@ function collectCodexSessionFiles(home) {
1115
1150
  const stat = safeStatSync(filePath);
1116
1151
  if (!stat)
1117
1152
  continue;
1153
+ if (sinceMs !== undefined && stat.mtimeMs < sinceMs)
1154
+ continue;
1118
1155
  files.push({ path: filePath, mtime: stat.mtimeMs });
1119
1156
  }
1120
1157
  files.sort((a, b) => b.mtime - a.mtime);
@@ -1837,6 +1874,220 @@ async function getGrokUsageInfo(options) {
1837
1874
  return { snapshot: null, error: null };
1838
1875
  }
1839
1876
  }
1877
+ /**
1878
+ * Muse Code usage.
1879
+ *
1880
+ * Prefer live Meta Model API rate-limit headers when a key is available
1881
+ * (META_API_KEY / MODEL_API_KEY / ~/.config/muse/auth.json). Fall back to
1882
+ * aggregating `model_completed.usage` from local session.jsonl logs under
1883
+ * ~/.local/share/muse/sessions for a last-7-days token window.
1884
+ */
1885
+ async function getMuseUsageInfo(options) {
1886
+ try {
1887
+ const base = options?.home || os.homedir();
1888
+ const live = await probeMuseRateLimits(base);
1889
+ if (live)
1890
+ return { snapshot: live, error: null };
1891
+ const local = await readMuseLocalSessionUsage(base);
1892
+ if (!local)
1893
+ return { snapshot: null, error: null };
1894
+ return { snapshot: local, error: null };
1895
+ }
1896
+ catch {
1897
+ return { snapshot: null, error: null };
1898
+ }
1899
+ }
1900
+ /** Resolve a Muse API key from env or auth.json without logging the value. */
1901
+ function resolveMuseApiKey(base) {
1902
+ const envKey = process.env.META_API_KEY?.trim() || process.env.MODEL_API_KEY?.trim();
1903
+ if (envKey)
1904
+ return envKey;
1905
+ const authPath = path.join(base, '.config', 'muse', 'auth.json');
1906
+ try {
1907
+ if (!fs.existsSync(authPath))
1908
+ return null;
1909
+ const data = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
1910
+ if (typeof data.access_token === 'string' && data.access_token)
1911
+ return data.access_token;
1912
+ if (typeof data.api_key === 'string' && data.api_key)
1913
+ return data.api_key;
1914
+ for (const slot of Object.values(data)) {
1915
+ if (!slot || typeof slot !== 'object' || Array.isArray(slot))
1916
+ continue;
1917
+ const entry = slot;
1918
+ if (typeof entry.access_token === 'string' && entry.access_token)
1919
+ return entry.access_token;
1920
+ if (typeof entry.api_key === 'string' && entry.api_key)
1921
+ return entry.api_key;
1922
+ }
1923
+ }
1924
+ catch {
1925
+ /* unreadable auth */
1926
+ }
1927
+ return null;
1928
+ }
1929
+ /**
1930
+ * Probe Meta Model API for rate-limit headers. Uses GET /v1/models (no token
1931
+ * spend). Returns null when unauthenticated or the headers are absent.
1932
+ */
1933
+ async function probeMuseRateLimits(base) {
1934
+ if (usageRateLimitedUntil('muse'))
1935
+ return null;
1936
+ const key = resolveMuseApiKey(base);
1937
+ if (!key)
1938
+ return null;
1939
+ try {
1940
+ const response = await fetch('https://api.meta.ai/v1/models', {
1941
+ headers: { Authorization: `Bearer ${key}` },
1942
+ signal: AbortSignal.timeout(8_000),
1943
+ });
1944
+ if (response.status === 429) {
1945
+ noteUsageRateLimited('muse', response.headers.get('retry-after'));
1946
+ return null;
1947
+ }
1948
+ if (!response.ok)
1949
+ return null;
1950
+ const limitTokens = headerNumber(response.headers, 'x-ratelimit-limit-tokens');
1951
+ const remainingTokens = headerNumber(response.headers, 'x-ratelimit-remaining-tokens');
1952
+ const limitRequests = headerNumber(response.headers, 'x-ratelimit-limit-requests');
1953
+ const remainingRequests = headerNumber(response.headers, 'x-ratelimit-remaining-requests');
1954
+ const windows = [];
1955
+ if (limitTokens !== null && remainingTokens !== null && limitTokens > 0) {
1956
+ const used = Math.max(0, Math.min(100, ((limitTokens - remainingTokens) / limitTokens) * 100));
1957
+ windows.push({
1958
+ key: 'session',
1959
+ label: 'Tokens (current window)',
1960
+ shortLabel: 'Tok',
1961
+ usedPercent: used,
1962
+ resetsAt: null,
1963
+ windowMinutes: 1,
1964
+ });
1965
+ }
1966
+ if (limitRequests !== null && remainingRequests !== null && limitRequests > 0) {
1967
+ const used = Math.max(0, Math.min(100, ((limitRequests - remainingRequests) / limitRequests) * 100));
1968
+ windows.push({
1969
+ key: 'session',
1970
+ label: 'Requests (current window)',
1971
+ shortLabel: 'Req',
1972
+ usedPercent: used,
1973
+ resetsAt: null,
1974
+ windowMinutes: 1,
1975
+ });
1976
+ }
1977
+ if (windows.length === 0)
1978
+ return null;
1979
+ return {
1980
+ source: 'live',
1981
+ sourceLabel: 'Meta Model API rate limits',
1982
+ capturedAt: new Date(),
1983
+ windows,
1984
+ plan: 'Meta Model API',
1985
+ };
1986
+ }
1987
+ catch {
1988
+ return null;
1989
+ }
1990
+ }
1991
+ function headerNumber(headers, name) {
1992
+ const raw = headers.get(name);
1993
+ if (raw === null || raw === '')
1994
+ return null;
1995
+ const n = Number(raw);
1996
+ return Number.isFinite(n) ? n : null;
1997
+ }
1998
+ /**
1999
+ * Aggregate Muse session token usage from local session.jsonl files for the
2000
+ * last 7 days. Scales the bar against 10M tokens (soft visibility scale — Meta
2001
+ * is pay-as-you-go with no hard local cap).
2002
+ */
2003
+ async function readMuseLocalSessionUsage(base) {
2004
+ const root = path.join(base, '.local', 'share', 'muse', 'sessions');
2005
+ if (!fs.existsSync(root))
2006
+ return null;
2007
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
2008
+ let inputTokens = 0;
2009
+ let outputTokens = 0;
2010
+ let cachedTokens = 0;
2011
+ let latestAt = null;
2012
+ let files = 0;
2013
+ const walk = (dir) => {
2014
+ let entries;
2015
+ try {
2016
+ entries = fs.readdirSync(dir, { withFileTypes: true });
2017
+ }
2018
+ catch {
2019
+ return;
2020
+ }
2021
+ for (const ent of entries) {
2022
+ const full = path.join(dir, ent.name);
2023
+ if (ent.isDirectory()) {
2024
+ walk(full);
2025
+ continue;
2026
+ }
2027
+ if (ent.name !== 'session.jsonl')
2028
+ continue;
2029
+ try {
2030
+ const st = fs.statSync(full);
2031
+ if (st.mtimeMs < cutoff)
2032
+ continue;
2033
+ files++;
2034
+ if (!latestAt || st.mtime > latestAt)
2035
+ latestAt = st.mtime;
2036
+ const content = fs.readFileSync(full, 'utf-8');
2037
+ for (const line of content.split('\n')) {
2038
+ if (!line.trim())
2039
+ continue;
2040
+ let raw;
2041
+ try {
2042
+ raw = JSON.parse(line);
2043
+ }
2044
+ catch {
2045
+ continue;
2046
+ }
2047
+ const event = raw?.payload?.event ?? raw?.payload;
2048
+ if (!event || event.kind !== 'model_completed')
2049
+ continue;
2050
+ const usage = event.usage;
2051
+ if (!usage || typeof usage !== 'object')
2052
+ continue;
2053
+ if (typeof usage.input_tokens === 'number')
2054
+ inputTokens += usage.input_tokens;
2055
+ if (typeof usage.output_tokens === 'number')
2056
+ outputTokens += usage.output_tokens;
2057
+ if (typeof usage.cached_tokens === 'number')
2058
+ cachedTokens += usage.cached_tokens;
2059
+ }
2060
+ }
2061
+ catch {
2062
+ /* skip unreadable session */
2063
+ }
2064
+ }
2065
+ };
2066
+ walk(root);
2067
+ const total = inputTokens + outputTokens + cachedTokens;
2068
+ if (total === 0 && files === 0)
2069
+ return null;
2070
+ // Soft 10M-token scale for the bar (pay-as-you-go has no hard local cap).
2071
+ const softCap = 10_000_000;
2072
+ const usedPercent = Math.max(0, Math.min(100, (total / softCap) * 100));
2073
+ const formatK = (n) => n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
2074
+ return {
2075
+ source: 'last_seen',
2076
+ sourceLabel: `local Muse sessions · ${formatK(total)} tokens (7d)`,
2077
+ capturedAt: latestAt,
2078
+ windows: [
2079
+ {
2080
+ key: 'week',
2081
+ label: 'Local tokens (7d)',
2082
+ shortLabel: '7d',
2083
+ usedPercent,
2084
+ resetsAt: null,
2085
+ windowMinutes: 7 * 24 * 60,
2086
+ },
2087
+ ],
2088
+ plan: 'Meta Model API',
2089
+ };
2090
+ }
1840
2091
  async function readLatestGrokBilling(filePath) {
1841
2092
  return new Promise((resolve) => {
1842
2093
  let latest = null;
@@ -917,6 +917,17 @@ export function getBinaryPath(agent, version) {
917
917
  ? path.join(getHomeDir(), 'bin', 'droid.exe')
918
918
  : path.join(getHomeDir(), '.local', 'bin', 'droid');
919
919
  }
920
+ if (agent === 'muse') {
921
+ // Muse Code install script drops a self-updating launcher at
922
+ // ~/.local/bin/muse (curl -fsSL https://dev.meta.ai/install.sh | sh).
923
+ // Same global-binary shape as droid: one binary for every version dir,
924
+ // config isolation via version-home HOME rewrite. Mirror the shim's
925
+ // `muse` branch so isVersionInstalled / agents view / agents import
926
+ // agree with what executes.
927
+ return IS_WINDOWS
928
+ ? path.join(getHomeDir(), 'bin', 'muse.exe')
929
+ : path.join(getHomeDir(), '.local', 'bin', 'muse');
930
+ }
920
931
  const versionDir = getVersionDir(agent, version);
921
932
  return path.join(versionDir, 'node_modules', '.bin', agentConfig.cliCommand);
922
933
  }
@@ -1521,14 +1532,14 @@ export async function installVersion(agent, version, onProgress, opts) {
1521
1532
  // ~/.local/bin (or similar) rather than the version's node_modules/.bin.
1522
1533
  //
1523
1534
  // Agents whose binary is special-cased in getBinaryPath (grok ->
1524
- // ~/.grok/downloads, droid -> ~/.local/bin/droid) need no symlink — and
1535
+ // ~/.grok/downloads, droid/muse -> ~/.local/bin/<cli>) need no symlink — and
1525
1536
  // creating one is actively harmful: `which <cli>` can resolve to OUR OWN
1526
1537
  // dispatcher shim, because ~/.agents/.cache/shims sits ahead of ~/.local/bin
1527
1538
  // on PATH. Symlinking node_modules/.bin/<cli> at the shim makes the shim
1528
1539
  // exec itself forever. So we skip the resolver-backed agents here AND, for
1529
1540
  // everyone else, filter the shims dir out of the `which` candidates so the
1530
1541
  // same race can't bite a non-special-cased installScript agent.
1531
- if (agent !== 'grok' && agent !== 'droid') {
1542
+ if (agent !== 'grok' && agent !== 'droid' && agent !== 'muse') {
1532
1543
  // findInPath is a pure-Node PATH scan that already skips our own shims
1533
1544
  // dir — so it returns the genuine install, never our dispatcher shim
1534
1545
  // (which sits ahead of ~/.local/bin on PATH and would otherwise be
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.21",
3
+ "version": "1.22.23",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/dist/bin/agents DELETED
Binary file