sidekick-shared 0.21.1 → 0.21.3

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 (77) hide show
  1. package/README.md +45 -45
  2. package/dist/accountManager.js +9 -7
  3. package/dist/accountRegistry.js +18 -14
  4. package/dist/accountStatus.js +4 -1
  5. package/dist/accounts.js +60 -25
  6. package/dist/aggregation/EventAggregator.d.ts +14 -11
  7. package/dist/aggregation/EventAggregator.js +68 -31
  8. package/dist/aggregation/PatternExtractor.js +5 -4
  9. package/dist/autoSwitch.js +6 -12
  10. package/dist/claudeProfiles.js +3 -3
  11. package/dist/codexProfiles.js +45 -22
  12. package/dist/codexQuota.js +44 -16
  13. package/dist/codexQuotaWatcher.js +10 -3
  14. package/dist/context/composer.js +8 -6
  15. package/dist/context/sessionContext.js +12 -9
  16. package/dist/credentialIO.js +10 -8
  17. package/dist/extractors/sessionAssets.js +9 -1
  18. package/dist/extractors/sources/claudeAssets.js +7 -1
  19. package/dist/extractors/sources/codexAssets.js +14 -3
  20. package/dist/extractors/toolCall.js +1 -4
  21. package/dist/formatters/eventHighlighter.js +148 -28
  22. package/dist/formatters/noiseClassifier.js +3 -11
  23. package/dist/formatters/sessionDump.js +22 -13
  24. package/dist/formatters/toolSummary.js +1 -3
  25. package/dist/index.d.ts +25 -23
  26. package/dist/index.js +12 -3
  27. package/dist/modelContext.js +10 -8
  28. package/dist/modelInfo.js +11 -6
  29. package/dist/multiProviderQuotaService.js +10 -7
  30. package/dist/parsers/codexParser.js +170 -103
  31. package/dist/parsers/debugLogParser.js +5 -4
  32. package/dist/parsers/openCodeParser.js +66 -48
  33. package/dist/parsers/planExtractor.js +3 -3
  34. package/dist/parsers/sessionActivityDetector.js +10 -6
  35. package/dist/parsers/sessionPathResolver.js +29 -34
  36. package/dist/parsers/subagentScanner.js +21 -20
  37. package/dist/parsers/subagentTraceParser.js +7 -4
  38. package/dist/phrases.js +11 -11
  39. package/dist/providerStatus.js +3 -4
  40. package/dist/providers/claudeCode.js +3 -1
  41. package/dist/providers/codex.js +35 -14
  42. package/dist/providers/codexDatabase.js +14 -5
  43. package/dist/providers/detect.js +10 -6
  44. package/dist/providers/openCode.d.ts +4 -4
  45. package/dist/providers/openCode.js +96 -68
  46. package/dist/providers/openCodeDatabase.js +44 -17
  47. package/dist/quota.d.ts +19 -0
  48. package/dist/quota.js +50 -8
  49. package/dist/quotaHistory.js +10 -3
  50. package/dist/quotaSnapshots.d.ts +1 -1
  51. package/dist/quotaSnapshots.js +2 -2
  52. package/dist/readers/decisions.js +1 -1
  53. package/dist/readers/notes.js +5 -3
  54. package/dist/readers/plans.js +43 -23
  55. package/dist/readers/tasks.js +1 -1
  56. package/dist/report/htmlHelpers.js +135 -16
  57. package/dist/report/htmlReportGenerator.js +38 -12
  58. package/dist/report/transcriptParser.js +8 -2
  59. package/dist/schemas/index.d.ts +4 -4
  60. package/dist/schemas/quota.js +2 -10
  61. package/dist/schemas/sessionEvent.js +20 -10
  62. package/dist/search/advancedFilter.js +3 -3
  63. package/dist/search/sessionSearch.js +10 -4
  64. package/dist/terminalSync.js +4 -2
  65. package/dist/turns/assistantTurn.js +5 -1
  66. package/dist/watchers/eventBridge.js +56 -26
  67. package/dist/watchers/factory.js +5 -2
  68. package/dist/watchers/jsonlTail.js +3 -1
  69. package/dist/watchers/jsonlWatcher.js +128 -52
  70. package/dist/watchers/providerReaderWatcher.js +3 -1
  71. package/dist/watchers/sqliteWatcher.js +52 -22
  72. package/dist/zaiQuota.d.ts +6 -6
  73. package/dist/zaiQuota.js +6 -10
  74. package/dist/zaiQuotaApi.d.ts +33 -0
  75. package/dist/zaiQuotaApi.js +279 -0
  76. package/dist/zaiQuotaWatcher.js +10 -9
  77. package/package.json +5 -2
package/README.md CHANGED
@@ -25,49 +25,49 @@ npm install sidekick-shared
25
25
 
26
26
  ## API Overview
27
27
 
28
- | Module | Description |
29
- |--------|-------------|
30
- | **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
31
- | **Paths** | Config directory resolution, project data paths, workspace encoding |
32
- | **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
33
- | **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem |
34
- | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
35
- | **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
36
- | **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
37
- | **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
38
- | **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
39
- | **Session Context** | Provider-neutral context evidence snapshots (`buildSessionContextSnapshot()`, `calculateSessionContextPressure()`, `createSessionContextProjector()`, `readSessionContextSnapshot()`): layered evidence sources, low/medium/high context pressure, and observed capabilities (tools, MCP servers, permission mode, rate limits) |
40
- | **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude `Task` subagent refs without prompt leakage |
41
- | **Report** | Self-contained HTML session report generation |
42
- | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
43
- | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams, and **estimated** z.ai Coding Plan quota derivation from observed OpenCode traffic (`accumulateZaiUsage()`, `inferZaiQuotaState()`, `parseZaiQuotaError()`, `resolveZaiTier()`, `ZAI_TIER_BUDGETS`) — z.ai exposes no usage API, so this is a best-effort estimate, not authoritative |
44
- | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
45
- | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, quota, account status, and quota history — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
46
- | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks), plus Node-only session asset extraction (`gatherAssetsForCwd()`, `readClaudeAssets()`, `readCodexAssets()`) for URLs, file paths, commands, and plans |
47
- | **Model Info & Pricing** | Model family parsing (Anthropic / OpenAI / Google, including legacy `claude-3-opus-…` and `claude-3-5-sonnet-…` IDs), context-window lookup (including Fable 5 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x variants), pricing tables with optional LiteLLM hydration, null-aware cost (`calculateCost()`), provenance-preserving cost (`calculateCostWithProvenance()`, `mergeCostSources()`), and display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`) |
48
- | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback |
49
- | **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; `ZaiQuotaWatcher` derives an estimated z.ai Coding Plan quota from observed OpenCode turns (event-driven, snapshot + history) |
50
- | **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, and `getActiveAccountStatus()` for a single-pass active-account read across providers |
51
- | **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
52
- | **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold |
53
- | **Codex Profiles** | Codex account lifecycle — prepare, finalize, switch, remove — switching atomically swaps the profile's backed-up credentials into the system `~/.codex/auth.json`, with rotated-token staleness protection, one-time dual-home migration, and legacy multi-home session monitoring |
54
- | **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
55
- | **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
28
+ | Module | Description |
29
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
+ | **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
31
+ | **Paths** | Config directory resolution, project data paths, workspace encoding |
32
+ | **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
33
+ | **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem |
34
+ | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
35
+ | **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
36
+ | **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
37
+ | **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
38
+ | **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
39
+ | **Session Context** | Provider-neutral context evidence snapshots (`buildSessionContextSnapshot()`, `calculateSessionContextPressure()`, `createSessionContextProjector()`, `readSessionContextSnapshot()`): layered evidence sources, low/medium/high context pressure, and observed capabilities (tools, MCP servers, permission mode, rate limits) |
40
+ | **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude `Task` subagent refs without prompt leakage |
41
+ | **Report** | Self-contained HTML session report generation |
42
+ | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
43
+ | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams, and authoritative z.ai Coding Plan quota fetching via `resolveZaiQuota()` / `fetchZaiQuotaFromApi()` (reads z.ai's `api/monitor/usage/quota/limit` endpoint with cached-snapshot fallback). The deprecated observed-traffic z.ai estimator helpers remain exported for backward compatibility only |
44
+ | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
45
+ | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, quota, account status, and quota history — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
46
+ | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks), plus Node-only session asset extraction (`gatherAssetsForCwd()`, `readClaudeAssets()`, `readCodexAssets()`) for URLs, file paths, commands, and plans |
47
+ | **Model Info & Pricing** | Model family parsing (Anthropic / OpenAI / Google, including legacy `claude-3-opus-…` and `claude-3-5-sonnet-…` IDs), context-window lookup (including Fable 5 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x variants), pricing tables with optional LiteLLM hydration, null-aware cost (`calculateCost()`), provenance-preserving cost (`calculateCostWithProvenance()`, `mergeCostSources()`), and display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`) |
48
+ | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback |
49
+ | **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; z.ai quota uses the API resolver with snapshot fallback |
50
+ | **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, and `getActiveAccountStatus()` for a single-pass active-account read across providers |
51
+ | **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
52
+ | **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold |
53
+ | **Codex Profiles** | Codex account lifecycle — prepare, finalize, switch, remove — switching atomically swaps the profile's backed-up credentials into the system `~/.codex/auth.json`, with rotated-token staleness protection, one-time dual-home migration, and legacy multi-home session monitoring |
54
+ | **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
55
+ | **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
56
56
 
57
57
  ## Supported import paths
58
58
 
59
59
  `sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime.
60
60
 
61
- | Path | Runtime | What it exposes |
62
- |-----------------------------------|-----------------------------|--------------------------------------------------------------------|
63
- | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
64
- | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: context-window lookup, model parsing, cost math, assistant turn projection. |
65
- | `sidekick-shared/node` | Node only | LiteLLM pricing catalog hydration (`fs` + `path`). |
66
- | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
67
- | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
68
- | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
69
- | `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
70
- | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, quota, account status, quota history) — fs-free, no Node builtins. |
61
+ | Path | Runtime | What it exposes |
62
+ | ------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
63
+ | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
64
+ | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: context-window lookup, model parsing, cost math, assistant turn projection. |
65
+ | `sidekick-shared/node` | Node only | LiteLLM pricing catalog hydration (`fs` + `path`). |
66
+ | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
67
+ | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
68
+ | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
69
+ | `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
70
+ | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, quota, account status, quota history) — fs-free, no Node builtins. |
71
71
 
72
72
  ### Browser / webview runtimes
73
73
 
@@ -191,9 +191,9 @@ console.log(formatCost(cost)); // "$0.0045"
191
191
  ```typescript
192
192
  import { extractTokenUsage, extractToolCall, extractToolCalls } from 'sidekick-shared';
193
193
 
194
- const usage = extractTokenUsage(event); // TokenUsage | null
195
- const tools = extractToolCalls(event); // ToolCall[] — assistant content blocks
196
- const toolFromEvent = extractToolCall(event); // ToolCall | null — top-level `tool_use` events
194
+ const usage = extractTokenUsage(event); // TokenUsage | null
195
+ const tools = extractToolCalls(event); // ToolCall[] — assistant content blocks
196
+ const toolFromEvent = extractToolCall(event); // ToolCall | null — top-level `tool_use` events
197
197
  ```
198
198
 
199
199
  ### Extract actionable assets from recent sessions
@@ -234,7 +234,7 @@ const provider = await detectProvider('/path/to/project');
234
234
  if (provider) {
235
235
  const snapshot = readSessionContextSnapshot(provider, '/path/to/session.jsonl');
236
236
 
237
- console.log(snapshot.pressure); // 'low' | 'medium' | 'high'
237
+ console.log(snapshot.pressure); // 'low' | 'medium' | 'high'
238
238
  console.log(snapshot.contextTokens, '/', snapshot.contextWindow);
239
239
  console.log(snapshot.capabilities.tools, snapshot.capabilities.mcpServers);
240
240
  console.log(snapshot.sources.length, 'evidence sources');
@@ -295,9 +295,9 @@ import { createJsonlTail, sessionEventSchema } from 'sidekick-shared';
295
295
  const tail = createJsonlTail({
296
296
  path: '/path/to/session.jsonl',
297
297
  schema: sessionEventSchema,
298
- onEvent: event => aggregator.processEvent(event),
298
+ onEvent: (event) => aggregator.processEvent(event),
299
299
  onBatchComplete: () => renderMetrics(aggregator.getMetrics()),
300
- onError: error => console.warn(error.message),
300
+ onError: (error) => console.warn(error.message),
301
301
  });
302
302
 
303
303
  tail.start();
@@ -329,7 +329,7 @@ const service = new MultiProviderQuotaService({
329
329
 
330
330
  service.onUpdate(({ claude, codex }) => {
331
331
  if (claude) console.log('Claude:', claude.fiveHour.utilization, claude.peakHours?.label);
332
- if (codex) console.log('Codex:', codex.fiveHour.utilization, codex.accountLabel);
332
+ if (codex) console.log('Codex:', codex.fiveHour.utilization, codex.accountLabel);
333
333
  });
334
334
 
335
335
  service.startPolling();
@@ -68,7 +68,9 @@ function atomicWriteJson(filePath, data, mode = 0o600) {
68
68
  try {
69
69
  fs.unlinkSync(tmp);
70
70
  }
71
- catch { /* nothing to clean up */ }
71
+ catch {
72
+ /* nothing to clean up */
73
+ }
72
74
  throw err;
73
75
  }
74
76
  }
@@ -88,7 +90,9 @@ function removePendingClaudeProfile(loginId) {
88
90
  try {
89
91
  fs.rmSync(getPendingClaudeProfilePath(loginId), { force: true });
90
92
  }
91
- catch { /* best effort */ }
93
+ catch {
94
+ /* best effort */
95
+ }
92
96
  }
93
97
  function getClaudeCredentialsBackupPath(uuid) {
94
98
  return path.join((0, accountRegistry_1.getAccountsDir)(), 'credentials', `${uuid}.credentials.json`);
@@ -246,7 +250,7 @@ function waitForNextPoll(ms, signal) {
246
250
  return Promise.resolve(true);
247
251
  if (signal?.aborted)
248
252
  return Promise.resolve(false);
249
- return new Promise(resolve => {
253
+ return new Promise((resolve) => {
250
254
  const timer = setTimeout(() => {
251
255
  cleanup();
252
256
  resolve(true);
@@ -289,7 +293,7 @@ async function spawnAccountLogin(provider, label, opts = {}) {
289
293
  catch (err) {
290
294
  return { success: false, error: `Could not spawn account login: ${err}` };
291
295
  }
292
- child.on('exit', code => {
296
+ child.on('exit', (code) => {
293
297
  childExited = true;
294
298
  childExitCode = code;
295
299
  });
@@ -328,9 +332,7 @@ async function spawnAccountLogin(provider, label, opts = {}) {
328
332
  }
329
333
  }
330
334
  function switchAccount(provider, id) {
331
- return provider === 'codex'
332
- ? (0, codexProfiles_1.switchToCodexAccount)(id)
333
- : (0, accounts_1.switchToAccount)(id);
335
+ return provider === 'codex' ? (0, codexProfiles_1.switchToCodexAccount)(id) : (0, accounts_1.switchToAccount)(id);
334
336
  }
335
337
  function listAllAccounts() {
336
338
  return {
@@ -81,17 +81,19 @@ function normalizeRegistry(registry) {
81
81
  (account.providerId === 'claude-code' || account.providerId === 'codex') &&
82
82
  typeof account.id === 'string' &&
83
83
  typeof account.addedAt === 'string')
84
- .map(account => ({
84
+ .map((account) => ({
85
85
  ...account,
86
86
  label: account.label || undefined,
87
87
  email: account.email || undefined,
88
88
  providerAccountId: account.providerAccountId || undefined,
89
- metadata: account.metadata ? {
90
- email: account.metadata.email || undefined,
91
- workspaceId: account.metadata.workspaceId || undefined,
92
- planType: account.metadata.planType || undefined,
93
- authMode: account.metadata.authMode || undefined,
94
- } : undefined,
89
+ metadata: account.metadata
90
+ ? {
91
+ email: account.metadata.email || undefined,
92
+ workspaceId: account.metadata.workspaceId || undefined,
93
+ planType: account.metadata.planType || undefined,
94
+ authMode: account.metadata.authMode || undefined,
95
+ }
96
+ : undefined,
95
97
  }))
96
98
  : [];
97
99
  return normalized;
@@ -103,7 +105,7 @@ function migrateLegacyRegistry(legacy) {
103
105
  'claude-code': legacy.activeAccountUuid,
104
106
  codex: null,
105
107
  },
106
- accounts: legacy.accounts.map(account => ({
108
+ accounts: legacy.accounts.map((account) => ({
107
109
  id: account.uuid,
108
110
  providerId: 'claude-code',
109
111
  providerAccountId: account.uuid,
@@ -141,7 +143,7 @@ function listSavedAccountProfiles(providerId) {
141
143
  if (!registry)
142
144
  return [];
143
145
  return providerId
144
- ? registry.accounts.filter(account => account.providerId === providerId)
146
+ ? registry.accounts.filter((account) => account.providerId === providerId)
145
147
  : registry.accounts;
146
148
  }
147
149
  function getActiveSavedAccount(providerId) {
@@ -151,11 +153,11 @@ function getActiveSavedAccount(providerId) {
151
153
  const activeId = registry.activeByProvider[providerId];
152
154
  if (!activeId)
153
155
  return null;
154
- return registry.accounts.find(account => account.providerId === providerId && account.id === activeId) ?? null;
156
+ return (registry.accounts.find((account) => account.providerId === providerId && account.id === activeId) ?? null);
155
157
  }
156
158
  function upsertSavedAccountProfile(profile) {
157
159
  const registry = readSavedAccountRegistry() ?? createEmptyRegistry();
158
- const index = registry.accounts.findIndex(account => account.providerId === profile.providerId && account.id === profile.id);
160
+ const index = registry.accounts.findIndex((account) => account.providerId === profile.providerId && account.id === profile.id);
159
161
  if (index >= 0) {
160
162
  registry.accounts[index] = profile;
161
163
  }
@@ -173,7 +175,9 @@ function setActiveSavedAccount(providerId, accountId) {
173
175
  }
174
176
  function replaceSavedAccountProfiles(providerId, accounts, activeId) {
175
177
  const registry = readSavedAccountRegistry() ?? createEmptyRegistry();
176
- registry.accounts = registry.accounts.filter(account => account.providerId !== providerId).concat(accounts);
178
+ registry.accounts = registry.accounts
179
+ .filter((account) => account.providerId !== providerId)
180
+ .concat(accounts);
177
181
  registry.activeByProvider[providerId] = activeId;
178
182
  writeSavedAccountRegistry(registry);
179
183
  return registry;
@@ -182,13 +186,13 @@ function removeSavedAccountProfile(providerId, accountId) {
182
186
  const registry = readSavedAccountRegistry();
183
187
  if (!registry)
184
188
  return null;
185
- const index = registry.accounts.findIndex(account => account.providerId === providerId && account.id === accountId);
189
+ const index = registry.accounts.findIndex((account) => account.providerId === providerId && account.id === accountId);
186
190
  if (index === -1)
187
191
  return null;
188
192
  const [removed] = registry.accounts.splice(index, 1);
189
193
  if (registry.activeByProvider[providerId] === accountId) {
190
194
  registry.activeByProvider[providerId] =
191
- registry.accounts.find(account => account.providerId === providerId)?.id ?? null;
195
+ registry.accounts.find((account) => account.providerId === providerId)?.id ?? null;
192
196
  }
193
197
  writeSavedAccountRegistry(registry);
194
198
  return removed;
@@ -27,7 +27,10 @@ function getActiveAccountStatus(error) {
27
27
  ? {
28
28
  present: true,
29
29
  email: codexAccount.email ?? codexAccount.metadata?.email,
30
- label: codexAccount.label ?? codexAccount.email ?? codexAccount.metadata?.email ?? codexAccount.id,
30
+ label: codexAccount.label ??
31
+ codexAccount.email ??
32
+ codexAccount.metadata?.email ??
33
+ codexAccount.id,
31
34
  }
32
35
  : { present: false };
33
36
  return {
package/dist/accounts.js CHANGED
@@ -69,7 +69,9 @@ function getClaudeConfigPath() {
69
69
  if (data?.oauthAccount)
70
70
  return primary;
71
71
  }
72
- catch { /* fall through */ }
72
+ catch {
73
+ /* fall through */
74
+ }
73
75
  // Fallback: ~/.claude.json (some Claude Code versions store config here)
74
76
  return path.join(os.homedir(), '.claude.json');
75
77
  }
@@ -193,8 +195,8 @@ function readAccountRegistry() {
193
195
  if (!registry)
194
196
  return null;
195
197
  const claudeAccounts = registry.accounts
196
- .filter(account => account.providerId === 'claude-code')
197
- .map(account => ({
198
+ .filter((account) => account.providerId === 'claude-code')
199
+ .map((account) => ({
198
200
  uuid: account.providerAccountId ?? account.id,
199
201
  email: account.email ?? account.metadata?.email ?? 'unknown',
200
202
  label: account.label,
@@ -207,7 +209,7 @@ function readAccountRegistry() {
207
209
  };
208
210
  }
209
211
  function writeAccountRegistry(registry) {
210
- const mappedClaudeProfiles = registry.accounts.map(account => ({
212
+ const mappedClaudeProfiles = registry.accounts.map((account) => ({
211
213
  id: account.uuid,
212
214
  providerId: 'claude-code',
213
215
  providerAccountId: account.uuid,
@@ -240,7 +242,10 @@ function addCurrentAccount(label) {
240
242
  // 1. Read current Claude identity
241
243
  const active = readActiveClaudeAccount();
242
244
  if (!active) {
243
- return { success: false, error: 'No active Claude account found. Sign in with `claude` first.' };
245
+ return {
246
+ success: false,
247
+ error: 'No active Claude account found. Sign in with `claude` first.',
248
+ };
244
249
  }
245
250
  // 2. Read current credentials (platform-aware: Keychain on macOS, file on Linux)
246
251
  const credBlob = (0, credentialIO_1.readActiveCredentials)();
@@ -264,7 +269,7 @@ function addCurrentAccount(label) {
264
269
  accounts: [],
265
270
  };
266
271
  // 5. Check if account already exists — update credentials + label
267
- const existing = registry.accounts.find(a => a.uuid === active.uuid);
272
+ const existing = registry.accounts.find((a) => a.uuid === active.uuid);
268
273
  if (existing) {
269
274
  if (label !== undefined)
270
275
  existing.label = label;
@@ -273,7 +278,9 @@ function addCurrentAccount(label) {
273
278
  try {
274
279
  writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
275
280
  }
276
- catch { /* flat backups remain available */ }
281
+ catch {
282
+ /* flat backups remain available */
283
+ }
277
284
  registry.activeAccountUuid = active.uuid;
278
285
  writeAccountRegistry(registry);
279
286
  return { success: true };
@@ -284,7 +291,9 @@ function addCurrentAccount(label) {
284
291
  try {
285
292
  writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
286
293
  }
287
- catch { /* flat backups remain available */ }
294
+ catch {
295
+ /* flat backups remain available */
296
+ }
288
297
  // 7. Add entry
289
298
  registry.accounts.push({
290
299
  uuid: active.uuid,
@@ -302,7 +311,7 @@ function switchToAccount(uuid) {
302
311
  if (!registry) {
303
312
  return { success: false, error: 'No account registry found. Save an account first.' };
304
313
  }
305
- const target = registry.accounts.find(a => a.uuid === uuid);
314
+ const target = registry.accounts.find((a) => a.uuid === uuid);
306
315
  if (!target) {
307
316
  return { success: false, error: `Account ${uuid} not found in registry.` };
308
317
  }
@@ -313,11 +322,15 @@ function switchToAccount(uuid) {
313
322
  try {
314
323
  originalConfig = fs.readFileSync(getClaudeConfigPath(), 'utf8');
315
324
  }
316
- catch { /* no existing config to back up */ }
325
+ catch {
326
+ /* no existing config to back up */
327
+ }
317
328
  try {
318
329
  backupCurrentClaudeLiveHome();
319
330
  }
320
- catch { /* best effort */ }
331
+ catch {
332
+ /* best effort */
333
+ }
321
334
  try {
322
335
  (0, accountRegistry_1.setActiveSavedAccount)('claude-code', uuid);
323
336
  }
@@ -329,18 +342,24 @@ function switchToAccount(uuid) {
329
342
  try {
330
343
  (0, accountRegistry_1.setActiveSavedAccount)('claude-code', registry.activeAccountUuid);
331
344
  }
332
- catch { /* rollback failed */ }
345
+ catch {
346
+ /* rollback failed */
347
+ }
333
348
  if (originalCreds) {
334
349
  try {
335
350
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
336
351
  }
337
- catch { /* rollback failed */ }
352
+ catch {
353
+ /* rollback failed */
354
+ }
338
355
  }
339
356
  if (originalConfig) {
340
357
  try {
341
358
  fs.writeFileSync(getClaudeConfigPath(), originalConfig);
342
359
  }
343
- catch { /* rollback failed */ }
360
+ catch {
361
+ /* rollback failed */
362
+ }
344
363
  }
345
364
  return applied;
346
365
  }
@@ -367,7 +386,9 @@ function applyActiveClaudeToLiveHome() {
367
386
  try {
368
387
  originalConfig = fs.readFileSync(getClaudeConfigPath(), 'utf8');
369
388
  }
370
- catch { /* no existing config to back up */ }
389
+ catch {
390
+ /* no existing config to back up */
391
+ }
371
392
  try {
372
393
  (0, credentialIO_1.writeActiveCredentials)(stored.credentials);
373
394
  }
@@ -376,7 +397,9 @@ function applyActiveClaudeToLiveHome() {
376
397
  try {
377
398
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
378
399
  }
379
- catch { /* rollback failed */ }
400
+ catch {
401
+ /* rollback failed */
402
+ }
380
403
  }
381
404
  return { success: false, error: `Failed to write credentials: ${err}` };
382
405
  }
@@ -388,13 +411,17 @@ function applyActiveClaudeToLiveHome() {
388
411
  try {
389
412
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
390
413
  }
391
- catch { /* rollback failed */ }
414
+ catch {
415
+ /* rollback failed */
416
+ }
392
417
  }
393
418
  if (originalConfig) {
394
419
  try {
395
420
  fs.writeFileSync(getClaudeConfigPath(), originalConfig);
396
421
  }
397
- catch { /* rollback failed */ }
422
+ catch {
423
+ /* rollback failed */
424
+ }
398
425
  }
399
426
  return { success: false, error: `Failed to write config: ${err}` };
400
427
  }
@@ -407,10 +434,11 @@ function reconcileClaudeAuthState() {
407
434
  return;
408
435
  const registry = (0, accountRegistry_1.readSavedAccountRegistry)();
409
436
  if (registry) {
410
- for (const profile of registry.accounts.filter(account => account.providerId === 'claude-code')) {
437
+ for (const profile of registry.accounts.filter((account) => account.providerId === 'claude-code')) {
411
438
  try {
412
439
  const uuid = profile.providerAccountId ?? profile.id;
413
- if ((0, credentialIO_1.readActiveCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(uuid)) && readProfileClaudeOauthAccount(uuid)) {
440
+ if ((0, credentialIO_1.readActiveCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(uuid)) &&
441
+ readProfileClaudeOauthAccount(uuid)) {
414
442
  continue;
415
443
  }
416
444
  const credentials = readFlatClaudeCredentials(uuid);
@@ -425,7 +453,10 @@ function reconcileClaudeAuthState() {
425
453
  }
426
454
  }
427
455
  fs.mkdirSync(path.dirname(markerPath), { recursive: true, mode: 0o700 });
428
- fs.writeFileSync(markerPath, new Date().toISOString() + '\n', { encoding: 'utf8', mode: 0o600 });
456
+ fs.writeFileSync(markerPath, new Date().toISOString() + '\n', {
457
+ encoding: 'utf8',
458
+ mode: 0o600,
459
+ });
429
460
  }
430
461
  catch {
431
462
  // Reconciliation must never break startup.
@@ -437,7 +468,7 @@ function removeAccount(uuid) {
437
468
  if (!registry) {
438
469
  return { success: false, error: 'No account registry found.' };
439
470
  }
440
- const idx = registry.accounts.findIndex(a => a.uuid === uuid);
471
+ const idx = registry.accounts.findIndex((a) => a.uuid === uuid);
441
472
  if (idx === -1) {
442
473
  return { success: false, error: `Account ${uuid} not found.` };
443
474
  }
@@ -445,11 +476,15 @@ function removeAccount(uuid) {
445
476
  try {
446
477
  fs.unlinkSync(path.join(getCredentialsDir(), `${uuid}.credentials.json`));
447
478
  }
448
- catch { /* ok */ }
479
+ catch {
480
+ /* ok */
481
+ }
449
482
  try {
450
483
  fs.unlinkSync(path.join(getConfigsDir(), `${uuid}.config.json`));
451
484
  }
452
- catch { /* ok */ }
485
+ catch {
486
+ /* ok */
487
+ }
453
488
  // Remove from registry
454
489
  registry.accounts.splice(idx, 1);
455
490
  if (registry.activeAccountUuid === uuid) {
@@ -460,7 +495,7 @@ function removeAccount(uuid) {
460
495
  }
461
496
  // ── Query helpers ────────────────────────────────────────────────────────
462
497
  function listAccounts() {
463
- return (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map(account => ({
498
+ return (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map((account) => ({
464
499
  uuid: account.providerAccountId ?? account.id,
465
500
  email: account.email ?? account.metadata?.email ?? 'unknown',
466
501
  label: account.label,
@@ -26,17 +26,20 @@ export interface SerializedAggregatorState {
26
26
  cacheRead: number;
27
27
  reportedCost: number;
28
28
  };
29
- modelUsage: Array<[string, {
30
- calls: number;
31
- tokens: number;
32
- inputTokens: number;
33
- outputTokens: number;
34
- cacheWriteTokens: number;
35
- cacheReadTokens: number;
36
- cost: number;
37
- reasoningTokens?: number;
38
- priced?: boolean;
39
- }]>;
29
+ modelUsage: Array<[
30
+ string,
31
+ {
32
+ calls: number;
33
+ tokens: number;
34
+ inputTokens: number;
35
+ outputTokens: number;
36
+ cacheWriteTokens: number;
37
+ cacheReadTokens: number;
38
+ cost: number;
39
+ reasoningTokens?: number;
40
+ priced?: boolean;
41
+ }
42
+ ]>;
40
43
  contextSize: number;
41
44
  previousContextSize: number;
42
45
  compactionEvents: CompactionEvent[];