sidekick-shared 0.24.5 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +159 -52
  3. package/dist/accountChangeSignal.d.ts +3 -0
  4. package/dist/accountChangeSignal.js +19 -0
  5. package/dist/accountChanges.d.ts +21 -0
  6. package/dist/accountChanges.js +151 -0
  7. package/dist/accountRegistry.d.ts +3 -1
  8. package/dist/accountRegistry.js +5 -0
  9. package/dist/browser.d.ts +8 -3
  10. package/dist/browser.js +15 -1
  11. package/dist/codexQuotaWatcher.d.ts +6 -0
  12. package/dist/codexQuotaWatcher.js +101 -47
  13. package/dist/index.d.ts +23 -13
  14. package/dist/index.js +34 -10
  15. package/dist/modelAliases.d.ts +13 -0
  16. package/dist/modelAliases.js +58 -0
  17. package/dist/modelCatalog.d.ts +32 -0
  18. package/dist/modelCatalog.js +145 -0
  19. package/dist/modelContext.d.ts +36 -3
  20. package/dist/modelContext.js +109 -34
  21. package/dist/modelInfo.d.ts +30 -1
  22. package/dist/modelInfo.js +116 -26
  23. package/dist/multiProviderQuotaService.d.ts +8 -0
  24. package/dist/multiProviderQuotaService.js +64 -3
  25. package/dist/node.d.ts +4 -4
  26. package/dist/node.js +4 -1
  27. package/dist/observedSessionCollector.d.ts +75 -4
  28. package/dist/observedSessionCollector.js +452 -42
  29. package/dist/parsers/codexHistory.d.ts +4 -2
  30. package/dist/parsers/codexHistory.js +9 -2
  31. package/dist/parsers/sessionActivityDetector.d.ts +6 -0
  32. package/dist/parsers/sessionActivityDetector.js +17 -0
  33. package/dist/providerIds.d.ts +11 -0
  34. package/dist/providerIds.js +9 -0
  35. package/dist/providerQuota.d.ts +3 -1
  36. package/dist/providerQuota.js +3 -0
  37. package/dist/providers/claudeCode.d.ts +10 -1
  38. package/dist/providers/claudeCode.js +204 -39
  39. package/dist/providers/codex.d.ts +18 -1
  40. package/dist/providers/codex.js +269 -40
  41. package/dist/providers/codexDatabase.d.ts +9 -0
  42. package/dist/providers/codexDatabase.js +107 -2
  43. package/dist/providers/diagnostics.d.ts +13 -0
  44. package/dist/providers/diagnostics.js +88 -0
  45. package/dist/providers/factory.d.ts +14 -0
  46. package/dist/providers/factory.js +57 -0
  47. package/dist/providers/openCode.d.ts +20 -1
  48. package/dist/providers/openCode.js +361 -11
  49. package/dist/providers/openCodeDatabase.d.ts +10 -0
  50. package/dist/providers/openCodeDatabase.js +69 -0
  51. package/dist/providers/types.d.ts +52 -2
  52. package/dist/providers/types.js +4 -0
  53. package/dist/quota.d.ts +4 -1
  54. package/dist/quota.js +3 -7
  55. package/dist/quotaHistory.d.ts +2 -1
  56. package/dist/quotaPoller.d.ts +13 -1
  57. package/dist/quotaPoller.js +65 -3
  58. package/dist/schemas/accountManager.d.ts +2 -2
  59. package/dist/schemas/accountManager.js +2 -4
  60. package/dist/schemas/accountStatus.d.ts +3 -0
  61. package/dist/schemas/accountStatus.js +1 -0
  62. package/dist/schemas/index.d.ts +1 -0
  63. package/dist/schemas/index.js +7 -2
  64. package/dist/schemas/observedSessionV1.d.ts +27 -26
  65. package/dist/schemas/observedSessionV1.js +6 -4
  66. package/dist/schemas/quota.d.ts +2 -2
  67. package/dist/schemas/quota.js +3 -6
  68. package/dist/schemas/quotaHistory.d.ts +2 -2
  69. package/dist/schemas/tokenEstimation.d.ts +1 -1
  70. package/dist/sessionMonitor.d.ts +15 -0
  71. package/dist/sessionMonitor.js +142 -0
  72. package/dist/sessionPreviews.d.ts +26 -1
  73. package/dist/sessionPreviews.js +319 -4
  74. package/dist/types/observedSessionV1.d.ts +6 -2
  75. package/dist/types/observedSessionV1.js +14 -8
  76. package/package.json +22 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,41 @@ All notable changes to sidekick-shared will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.25.0] - 2026-08-18
9
+
10
+ ### Added
11
+
12
+ - `listSessionPreviewsAsync()` and `readSessionPreviewAsync()` provide bounded-concurrency, cooperative session preview reads while preserving the synchronous APIs and their signatures
13
+ - Async preview labeling batches Codex and OpenCode database work into at most one `sqlite3` subprocess per provider per few hundred sessions listed (ids are chunked clear of argv limits); when `sqlite3` is absent, the same bounded file-scan fallback remains available
14
+ - `OpenCodeProvider.listAllSessionFiles()` completes native preview enumeration across Claude Code, Codex, and OpenCode
15
+ - `ObservedSessionCollector.subscribe()` and `SessionMonitor.subscribe()` emit debounced, coalesced session changes with the previous and current fingerprints, backed by filesystem watching plus a documented catch-up poll for unreliable filesystems
16
+ - `createSessionProviders({ onDiagnostic })` constructs every usable provider independently and reports unavailable providers without aborting the host
17
+ - `SessionProviderBase.findSessionById()` has provider-native implementations: filename lookup for Claude Code and Codex, and a database lookup with a bounded file-scan fallback for OpenCode
18
+ - `exportResolvedModelCatalog()` and `importResolvedModelCatalog()` transfer serializable context-window and pricing resolutions between Node and browser realms without private hooks
19
+ - `registerModelAlias()` lets hosts resolve short model names to canonical ids, and catalog provenance marks prefix-inherited context and pricing matches so callers can reject them
20
+ - Stable provider-id arrays are exported as runtime values alongside their derived TypeScript unions
21
+ - `onAccountsChanged()` reports process-local and filesystem-observed login, logout, and account-switch changes without account-state polling in each host
22
+
23
+ ### Changed
24
+
25
+ - Provider constructors are environment-independent and perform no filesystem, configuration, database, or binary probing; environmental failures are deferred to first use and returned as structured diagnostics
26
+ - Repeated identical provider degradations are coalesced in the factory diagnostic result, keeping first-use reporting bounded in long-lived hosts
27
+ - `ObservedSessionCollector.collect()` caches parsed sessions by fingerprint, so a second collection over unchanged files performs no content reads
28
+ - Observed sessions expose `fingerprintParts: { sizeBytes, mtimeMs }` alongside the compatible opaque fingerprint string
29
+ - Collector reads accept bounded-concurrency and cooperative-yield hooks; cached observations refresh `observedAt` and activity state while retaining `contentObservedAt` to make the age of cached usage/model data explicit
30
+ - `ObservedSessionDiagnostic` now includes `severity` and `phase` while retaining every existing `kind` value
31
+ - Account and quota entry points guarantee results that validate against the same-release exported schemas, including additive schema fields, and their contract tests enforce the boundary
32
+ - Every documented package subpath now declares explicit `types`, `import`, `require`, and `default` export conditions and resolves in both Vite and TypeScript's legacy `moduleResolution: node` mode without aliases
33
+ - `readCodexHistory()` retains raw epoch-second `ts`, adds normalized `tsMs`, and sizes its tail read from `limit` unless an explicit byte bound is supplied
34
+ - Observed-session projection accepts `observationOnly: true`, which reports transcripts as read-only observations with `capabilities.resume` disabled
35
+ - `QuotaPoller`, `MultiProviderQuotaService`, and `CodexQuotaWatcher` stay dormant when their account is absent and wake on account changes instead of issuing empty polls
36
+
37
+ ### Fixed
38
+
39
+ - Codex and OpenCode database-backed operations attach an availability diagnostic when the external `sqlite3` binary is missing, so an unavailable runtime is distinguishable from an empty workspace
40
+ - A wrong session id resolves to `null` through every provider instead of surfacing discovery or environment errors
41
+ - Exact model pricing/context entries are considered before prefix inheritance across all sources, and prefix-only matches are explicit in provenance rather than silently trusted as exact
42
+
8
43
  ## [0.24.5] - 2026-08-18
9
44
 
10
45
  ### Added
package/README.md CHANGED
@@ -9,6 +9,7 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
9
9
 
10
10
  ## Recent additions
11
11
 
12
+ - **Host-safe APIs (0.25.0)** — `listSessionPreviewsAsync()` / `readSessionPreviewAsync()` read previews with bounded concurrency and cooperative yielding; `ObservedSessionCollector.subscribe()` and `SessionMonitor.subscribe()` replace host polling loops with debounced change batches; `createSessionProviders({ onDiagnostic })` constructs every usable provider with structured diagnostics instead of throwing; `findSessionById()` resolves one session without scanning; `exportResolvedModelCatalog()` / `importResolvedModelCatalog()` and `registerModelAlias()` transfer context/pricing resolutions across realms; `onAccountsChanged()` reports login/logout/switch events; provider constructors perform no environment I/O, and quota pollers stay dormant without an account.
12
13
  - **Native session-file helpers** — `encodeClaudeWorkspacePath()` / `getClaudeSessionDirectory()` locate Claude Code's `~/.claude/projects/` directories with the real on-disk encoding, `findCodexRolloutFile()` resolves a Codex session id to its rollout path, `readCodexHistory()` tail-reads `~/.codex/history.jsonl`, `listSessionPreviews()` / `readSessionPreview()` build a cheap stat-first recent-sessions index across providers, and `parseMcpToolName()` splits `mcp__<server>__<tool>` identifiers (browser-safe).
13
14
  - **Non-blocking account APIs & locked sync writers** — every login/switch entry point gains an async variant (`switchAccountAsync()`, `getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `prepareCodexAccountAsync()`, `finalizeCodexAccountAsync()`, `switchToCodexAccountAsync()`) that keeps `codex` CLI probes off the event loop, and synchronous callers get the same cross-process store lock via `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()`, with progress-based timeouts and abandoned-lock reclaim.
14
15
  - **Canonical usage and pricing** — provider-specific counters normalize into disjoint input/cache/output/reasoning categories with `normalizeProviderUsage()`; `calculateNormalizedUsageCost()` prevents cache/reasoning double counting and reports pricing provenance.
@@ -41,16 +42,19 @@ shelling out to an executable **`sqlite3` on `PATH`**. There is no bundled
41
42
  driver. `CodexProvider` shells out to the same binary, but only for an optional
42
43
  index.
43
44
 
44
- Without it `OpenCodeProvider` returns **empty results rather than an error**,
45
- which is indistinguishable from an empty workspace. Call `getRuntimeStatus()`,
46
- implemented by `OpenCodeProvider` only, to tell the two apart:
45
+ Without it, database-backed reads still return results rather than throwing —
46
+ but as of 0.25.0 the missing binary is no longer invisible: Codex and OpenCode
47
+ attach a `sqlite_missing` diagnostic to affected operations, so an unavailable
48
+ runtime is distinguishable from an empty workspace. Construct providers through
49
+ `createSessionProviders({ onDiagnostic })` to receive those diagnostics, or ask
50
+ any provider directly — `getRuntimeStatus()` is implemented by all three:
47
51
 
48
52
  ```typescript
49
53
  const status = provider.getRuntimeStatus?.();
50
54
  // status.kind: 'available' | 'db_missing' | 'sqlite_missing'
51
- // | 'sqlite_blocked' | 'query_failed'
55
+ // | 'sqlite_blocked' | 'query_failed' | ...
52
56
  if (status && status.kind !== 'available') {
53
- console.warn(`Session data unavailable: ${status.kind}`);
57
+ console.warn(`Session data degraded: ${status.kind}`);
54
58
  }
55
59
  ```
56
60
 
@@ -78,56 +82,56 @@ returns the platform default that both overrides bypass.
78
82
 
79
83
  ## API Overview
80
84
 
81
- | Module | Description |
82
- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
- | **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
84
- | **Paths** | Config directory resolution, project data paths, workspace encoding |
85
- | **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
86
- | **Writers** | Atomic persistence writers — `atomicWriteJson()` / `updateJsonStoreAtomic()` and their synchronous twins `atomicWriteJsonSync()` / `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()` (same cross-process lock file, progress-based timeouts, abandoned-lock reclaim), plus lock-coordinated capture helpers `addTask()`, `completeTask()`, `addDecision()`, `addNote()` that merge into the shared stores without clobbering concurrent writers |
87
- | **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem |
88
- | **Observed Sessions (V1)** | Versioned provider-neutral observation contracts — `createProviderSessionAdapterV1()` / `derivePendingUserRequestV1()` with `ObservedAgentSessionV1`, `ProviderCapabilitiesV1`, `PendingUserRequestV1`, `SessionEvidenceRefV1`, and provenance/confidence wrapper types (Zod schemas in `sidekick-shared/schemas`) |
89
- | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
90
- | **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
91
- | **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
92
- | **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
93
- | **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
94
- | **Analytics** | Session-quality scoring (beta) with factor contributions and weekly trends (`scoreSessionQuality()`, `calculateQualityTrend()`), code-impact metrics including cost per changed line and per-model churn/cost (`calculateCodeImpact()`), and reported-vs-heuristic compaction accounting (`calculateCompactionLedger()`, `formatCompactionLedger()`) |
95
- | **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
96
- | **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) |
97
- | **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 |
98
- | **Report** | Self-contained HTML session report generation |
99
- | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
100
- | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams (plus Codex reset credits via `fetchCodexResetCreditsFromApi()`, attached to quota state by `fetchCodexQuotaFromApi()`), 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 observed-traffic z.ai estimator was removed from the package root exports in 0.23.0 and every remaining export of it is `@deprecated` — `resolveZaiQuota()` is the single supported z.ai quota API |
101
- | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
102
- | **Doctor** | Typed cross-provider health diagnostics — `runDoctor()` checks project identity, sessions, accounts, providers, and dependencies into a `HealthReport`; `formatHealthReport()` renders it and `getSessionDiagnostics()` exposes the per-provider session probe |
103
- | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, observed-session V1 records, 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 |
104
- | **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 |
105
- | **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, normalized non-double-counting cost (`calculateNormalizedUsageCost()`, fed by `normalizeProviderUsage()` / `extractNormalizedUsage()`), legacy cost helpers retained but deprecated (`calculateCost()`, `calculateCostWithProvenance()`), `mergeCostSources()`, and display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`) |
106
- | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback |
107
- | **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 |
108
- | **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, `getActiveAccountStatus()` for a single-pass active-account read across providers, and live-first `resolveActiveClaudeAccount()` / `resolveActiveCodexAccount()` that report the currently logged-in account (self-healing the saved pointer) for display |
109
- | **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. Every status/finalize/switch entry point also has an `-Async` variant (`getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `switchAccountAsync()`, …) that keeps `codex` CLI probes off the event loop — prefer those from hosts with a UI loop. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
110
- | **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; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()` |
111
- | **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 |
112
- | **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
113
- | **Statusline** | Cache-only one-line account/quota/burn footer — `formatStatusline()`, `selectStatuslineAccount()`, `BurnRateCalculator`, and `estimateTimeToQuota()`, also published via the [`sidekick-shared/statusline`](#supported-import-paths) subpath for fast per-prompt rendering |
114
- | **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
85
+ | Module | Description |
86
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data) |
88
+ | **Paths** | Config directory resolution, project data paths, workspace encoding |
89
+ | **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/` |
90
+ | **Writers** | Atomic persistence writers — `atomicWriteJson()` / `updateJsonStoreAtomic()` and their synchronous twins `atomicWriteJsonSync()` / `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()` (same cross-process lock file, progress-based timeouts, abandoned-lock reclaim), plus lock-coordinated capture helpers `addTask()`, `completeTask()`, `addDecision()`, `addNote()` that merge into the shared stores without clobbering concurrent writers |
91
+ | **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem. As of 0.25.0 constructors perform no environment I/O — build them all with `createSessionProviders({ onDiagnostic })`, which reports unavailable providers as structured diagnostics instead of failing; `findSessionById()` resolves one session through provider-native indexes; stable provider-id const arrays (`SESSION_PROVIDER_IDS`, `ACCOUNT_PROVIDER_IDS`, `QUOTA_PROVIDER_IDS`, `MODEL_PROVIDER_IDS`) accompany the derived unions |
92
+ | **Observed Sessions (V1)** | Versioned provider-neutral observation contracts — `createProviderSessionAdapterV1()` / `derivePendingUserRequestV1()` with `ObservedAgentSessionV1`, `ProviderCapabilitiesV1`, `PendingUserRequestV1`, `SessionEvidenceRefV1`, and provenance/confidence wrapper types (Zod schemas in `sidekick-shared/schemas`) |
93
+ | **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing |
94
+ | **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers |
95
+ | **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting |
96
+ | **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date) |
97
+ | **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction |
98
+ | **Analytics** | Session-quality scoring (beta) with factor contributions and weekly trends (`scoreSessionQuality()`, `calculateQualityTrend()`), code-impact metrics including cost per changed line and per-model churn/cost (`calculateCodeImpact()`), and reported-vs-heuristic compaction accounting (`calculateCompactionLedger()`, `formatCompactionLedger()`) |
99
+ | **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups |
100
+ | **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) |
101
+ | **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 |
102
+ | **Report** | Self-contained HTML session report generation |
103
+ | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
104
+ | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams (plus Codex reset credits via `fetchCodexResetCreditsFromApi()`, attached to quota state by `fetchCodexQuotaFromApi()`), 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 observed-traffic z.ai estimator was removed from the package root exports in 0.23.0 and every remaining export of it is `@deprecated` — `resolveZaiQuota()` is the single supported z.ai quota API |
105
+ | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
106
+ | **Doctor** | Typed cross-provider health diagnostics — `runDoctor()` checks project identity, sessions, accounts, providers, and dependencies into a `HealthReport`; `formatHealthReport()` renders it and `getSessionDiagnostics()` exposes the per-provider session probe |
107
+ | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, observed-session V1 records, quota, account status, account management, and quota history — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Account and quota APIs guarantee results valid against the same-release schemas. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
108
+ | **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 |
109
+ | **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, normalized non-double-counting cost (`calculateNormalizedUsageCost()`, fed by `normalizeProviderUsage()` / `extractNormalizedUsage()`), legacy cost helpers retained but deprecated (`calculateCost()`, `calculateCostWithProvenance()`), `mergeCostSources()`, display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`), and cross-realm catalog transfer — `exportResolvedModelCatalog()` / `importResolvedModelCatalog()` move resolved context windows and pricing (with provenance, prefix inheritance explicit) between Node and browser realms, and `registerModelAlias()` resolves short or host-specific names to canonical ids |
110
+ | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback. Stays dormant while no account exists and wakes through the account-change signal |
111
+ | **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. All three stay dormant without a matching account and wake on account changes |
112
+ | **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, `getActiveAccountStatus()` for a single-pass active-account read across providers, `onAccountsChanged()` for push-based login/logout/switch notification (process-local mutations, filesystem watches, and a catch-up poll), and live-first `resolveActiveClaudeAccount()` / `resolveActiveCodexAccount()` that report the currently logged-in account (self-healing the saved pointer) for display |
113
+ | **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. Every status/finalize/switch entry point also has an `-Async` variant (`getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `switchAccountAsync()`, …) that keeps `codex` CLI probes off the event loop — prefer those from hosts with a UI loop. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
114
+ | **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; its `switchAccount` callback may return a Promise and defaults to `switchAccountAsync()` |
115
+ | **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 |
116
+ | **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
117
+ | **Statusline** | Cache-only one-line account/quota/burn footer — `formatStatusline()`, `selectStatuslineAccount()`, `BurnRateCalculator`, and `estimateTimeToQuota()`, also published via the [`sidekick-shared/statusline`](#supported-import-paths) subpath for fast per-prompt rendering |
118
+ | **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
115
119
 
116
120
  ## Supported import paths
117
121
 
118
- `sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime.
122
+ `sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime. As of 0.25.0 every documented subpath declares explicit `types` / `import` / `require` / `default` export conditions and resolves under Vite and TypeScript's legacy `moduleResolution: node` without consumer-side aliases.
119
123
 
120
- | Path | Runtime | What it exposes |
121
- | ------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
122
- | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
123
- | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`. |
124
- | `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, session previews (`listSessionPreviews()` / `readSessionPreview()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), and resilient observed-session collection (`fs` + providers). |
125
- | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
126
- | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
127
- | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
128
- | `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
129
- | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, quota history) — fs-free, no Node builtins. |
130
- | `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates. |
124
+ | Path | Runtime | What it exposes |
125
+ | ------------------------------ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
127
+ | `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math, `parseMcpToolName()`. |
128
+ | `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, session previews (`listSessionPreviews()` / `readSessionPreview()` plus the bounded-concurrency `listSessionPreviewsAsync()` / `readSessionPreviewAsync()`), Codex history/rollout lookup (`readCodexHistory()`, `findCodexRolloutFile()`), and resilient observed-session collection (`fs` + providers). |
129
+ | `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
130
+ | `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
131
+ | `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
132
+ | `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
133
+ | `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, account management, quota history) — fs-free, no Node builtins. |
134
+ | `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates. |
131
135
 
132
136
  ### Browser / webview runtimes
133
137
 
@@ -169,6 +173,31 @@ await loadObservedContextWindows();
169
173
 
170
174
  Both calls always resolve; a network failure, missing cache, or malformed store leaves lookups on the static tables. To record a window a provider reports at runtime, call `recordObservedContextWindow(modelId, window)` — it persists only when the value changes.
171
175
 
176
+ ### Transfer resolved model data across realms
177
+
178
+ A realm that can hydrate (extension host, CLI) can hand its resolved context
179
+ windows and pricing to one that cannot (webview, browser bundle) as one
180
+ serializable snapshot — no private hooks, no re-hydration:
181
+
182
+ ```typescript
183
+ // Node side (after hydratePricingCatalog / loadObservedContextWindows):
184
+ import { exportResolvedModelCatalog } from 'sidekick-shared';
185
+ const snapshot = exportResolvedModelCatalog(); // or pass specific model ids
186
+
187
+ // Browser side:
188
+ import { importResolvedModelCatalog, registerModelAlias } from 'sidekick-shared/browser';
189
+ const { imported, diagnostics } = importResolvedModelCatalog(snapshot);
190
+ ```
191
+
192
+ Entries carry provenance (`source`, `match`, `matchedModelId`, and
193
+ `inheritedByPrefix` for pricing), so consumers can reject prefix-inherited
194
+ guesses. Unresolved entries (`default` window / `none` pricing) are skipped on
195
+ import rather than pinned, successive partial imports merge, and imported data
196
+ ranks below anything the importing realm has observed first-hand.
197
+ `registerModelAlias('sonnet', 'claude-sonnet-5')` resolves short or
198
+ host-specific names everywhere lookups happen; data recorded under the alias id
199
+ itself keeps winning over the canonical fallback.
200
+
172
201
  ### Normalize and price provider usage
173
202
 
174
203
  ```typescript
@@ -274,6 +303,17 @@ const previews = listSessionPreviews([new ClaudeCodeProvider(), new CodexProvide
274
303
 
275
304
  `readSessionPreview(provider, filePath)` reads the same bounded preview for one known session file — `null` when the file is missing or unreadable, `null` fields when its content is malformed.
276
305
 
306
+ Hosts with a UI loop should prefer the async variants from `sidekick-shared/node`. They read with bounded concurrency, yield between files, batch Codex/OpenCode label lookups into non-blocking `sqlite3` subprocesses, and — unlike their sync twins — return degradation status alongside the data:
307
+
308
+ ```typescript
309
+ import { listSessionPreviewsAsync, readSessionPreviewAsync } from 'sidekick-shared/node';
310
+
311
+ const { previews, diagnostics } = await listSessionPreviewsAsync(providers, { limit: 10 });
312
+ const { preview } = await readSessionPreviewAsync(provider, filePath);
313
+ ```
314
+
315
+ To resolve one session id to its file without scanning, every bundled provider implements `findSessionById(workspacePath, sessionId)` — a filename join for Claude Code and Codex, a database lookup for OpenCode — returning `null` for misses instead of reading transcripts. (It is optional on the `SessionProviderBase` interface so pre-0.25 external implementations keep compiling; fall back to `findAllSessions()` when absent.)
316
+
277
317
  MCP tool identifiers (`mcp__<server>__<tool>`) split with `parseMcpToolName(name)` — first-double-underscore rule, `null` for malformed names, safe in browser bundles.
278
318
 
279
319
  ### Collect observed sessions with isolation and retry
@@ -299,10 +339,55 @@ applyReadOnlyProjection(source.capabilities);
299
339
 
300
340
  Discovery and reads are isolated, retries use bounded 30-second-to-5-minute exponential backoff, file changes bypass delay, duplicate identical failures are suppressed, and recovery emits a content-free diagnostic. Clock and fingerprint callbacks are injectable for deterministic hosts/tests.
301
341
 
342
+ As of 0.25.0, parses are cached by fingerprint — a second `collect()` over unchanged files performs no content reads (cache hits refresh `observedAt` and activity state; `contentObservedAt` reports how old the cached usage/model data actually is, and `cacheHit` marks them). Observations expose structured `fingerprintParts: { sizeBytes, mtimeMs }` alongside the opaque fingerprint string, and diagnostics carry `severity` and `phase` in addition to their existing `kind`.
343
+
344
+ Instead of a host polling loop, subscribe to debounced change batches. Each change carries the previous and current fingerprints; filesystem watching is combined with a catch-up poll for filesystems that drop events:
345
+
346
+ ```typescript
347
+ const subscription = collector.subscribe(
348
+ (batch) => {
349
+ for (const change of batch.changes) {
350
+ // change.type: 'added' | 'changed' | 'removed'
351
+ console.log(batch.providerId, change.type, change.reference.sessionId);
352
+ console.log(change.previousFingerprint, '→', change.fingerprint);
353
+ }
354
+ void collector.collect(); // consume changed sessions through the parse cache
355
+ },
356
+ { debounceMs: 250, pollIntervalMs: 30_000 },
357
+ );
358
+ // later: subscription.dispose();
359
+ ```
360
+
361
+ `SessionMonitor.subscribe()` offers the same push model at the session-event level.
362
+
302
363
  ## Usage Examples
303
364
 
365
+ ### Construct session providers for a long-lived host
366
+
367
+ `createSessionProviders()` builds every requested provider without probing the
368
+ filesystem or binaries — constructors are I/O-free as of 0.25.0, so one
369
+ unavailable provider cannot abort host startup. Environmental failures surface
370
+ on first use as structured diagnostics instead:
371
+
372
+ ```typescript
373
+ import { createSessionProviders } from 'sidekick-shared';
374
+
375
+ const { providers, diagnostics } = createSessionProviders({
376
+ onDiagnostic: (diagnostic) => {
377
+ // e.g. { providerId: 'opencode', kind: 'sqlite_missing',
378
+ // severity: 'warning', phase: 'query', message: '...' }
379
+ log(diagnostic);
380
+ },
381
+ });
382
+ ```
383
+
384
+ Repeated identical degradations are coalesced, so the diagnostic stream stays
385
+ bounded across long-lived polling and subscription cycles.
386
+
304
387
  ### Detect the active session provider
305
388
 
389
+ For a single-provider surface, auto-detect and construct just one:
390
+
306
391
  ```typescript
307
392
  import {
308
393
  detectProvider,
@@ -518,7 +603,7 @@ const parser = new JsonlParser(
518
603
  parser.processChunk(rawData);
519
604
  ```
520
605
 
521
- The boundary schemas — `sessionEventSchema` plus the quota, account-status, and quota-history schemas — are also importable fs-free from `sidekick-shared/schemas`, which keeps Zod out of bundles that only need the pure math/formatting helpers. `extractSessionEvents()` from the same subpath unwraps Claude Code `progress`-wrapped events into canonical `SessionEvent[]`.
606
+ The boundary schemas — `sessionEventSchema` plus the quota, account-status, account-management, and quota-history schemas — are also importable fs-free from `sidekick-shared/schemas`, which keeps Zod out of bundles that only need the pure math/formatting helpers. Account and quota APIs are guaranteed to return values valid against the schemas exported in the same release, so consumers do not need to parse those library results again. Additive account or quota fields are added to the matching exported schema in the same release. `extractSessionEvents()` from the same subpath unwraps Claude Code `progress`-wrapped events into canonical `SessionEvent[]`.
522
607
 
523
608
  ### Tail raw JSONL events incrementally
524
609
 
@@ -594,6 +679,28 @@ console.log(status.claude.present, status.claude.email);
594
679
  console.log(status.codex.present, status.codex.label);
595
680
  ```
596
681
 
682
+ To react to logins, logouts, and switches without polling that status yourself,
683
+ subscribe to `onAccountsChanged()`. It combines process-local mutation signals,
684
+ filesystem watches on the account stores, and a low-frequency catch-up poll —
685
+ and only emits when the status actually changed:
686
+
687
+ ```typescript
688
+ import { onAccountsChanged } from 'sidekick-shared';
689
+
690
+ const subscription = onAccountsChanged(
691
+ ({ reason, status }) => {
692
+ // reason: 'local' | 'filesystem' | 'poll'
693
+ render(status.claude, status.codex);
694
+ },
695
+ { emitCurrent: true },
696
+ );
697
+ // later: subscription.dispose();
698
+ ```
699
+
700
+ `QuotaPoller`, `MultiProviderQuotaService`, and `CodexQuotaWatcher` use the same
701
+ signal internally: they stay dormant while no matching account exists and wake
702
+ when one appears.
703
+
597
704
  For display surfaces that must reflect the **currently logged-in** account — even after a native `claude /login` or `codex login` outside Sidekick — use the live-first resolvers. They prefer the live provider auth (`~/.claude/.claude.json` oauthAccount; the `~/.codex/auth.json` id_token JWT) over the saved `activeByProvider` pointer, fall back to the registry, and self-heal the saved pointer on an unambiguous match (best-effort, never creating or deleting profiles):
598
705
 
599
706
  ```typescript
@@ -0,0 +1,3 @@
1
+ export type AccountChangeReason = 'local' | 'filesystem' | 'poll';
2
+ export declare function _onRawAccountsChanged(listener: (reason: AccountChangeReason) => void): () => void;
3
+ export declare function _emitAccountsChanged(reason: AccountChangeReason): void;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._onRawAccountsChanged = _onRawAccountsChanged;
4
+ exports._emitAccountsChanged = _emitAccountsChanged;
5
+ const listeners = new Set();
6
+ function _onRawAccountsChanged(listener) {
7
+ listeners.add(listener);
8
+ return () => listeners.delete(listener);
9
+ }
10
+ function _emitAccountsChanged(reason) {
11
+ for (const listener of listeners) {
12
+ try {
13
+ listener(reason);
14
+ }
15
+ catch {
16
+ // Account notifications are observational and cannot break mutations.
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { type AccountChangeReason } from './accountChangeSignal';
2
+ import { type ActiveAccountStatus } from './accountStatus';
3
+ import type { Disposable } from './quotaPoller';
4
+ export interface AccountsChangedEvent {
5
+ reason: AccountChangeReason;
6
+ status: ActiveAccountStatus;
7
+ }
8
+ export interface OnAccountsChangedOptions {
9
+ /** Emit the current status immediately after subscribing. */
10
+ emitCurrent?: boolean;
11
+ /** Catch-up poll for filesystems where watch delivery is unreliable. Default: 30s. */
12
+ pollIntervalMs?: number;
13
+ }
14
+ /**
15
+ * Observe login, logout, and active-account switches. Local Sidekick mutations
16
+ * signal immediately; filesystem watchers cover external CLIs; a low-frequency
17
+ * catch-up poll protects network mounts and watcher-hostile filesystems.
18
+ */
19
+ export declare function onAccountsChanged(listener: (event: AccountsChangedEvent) => void, options?: OnAccountsChangedOptions): Disposable;
20
+ /** Internal bridge for account mutations outside the registry module. */
21
+ export declare function _notifyAccountsChanged(): void;