sidekick-shared 0.24.3 → 0.24.4

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,412 @@
1
+ # Changelog
2
+
3
+ All notable changes to sidekick-shared will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.24.4] - 2026-07-25
9
+
10
+ ### Fixed
11
+
12
+ - `fetchPeakHoursStatus()` had no request timeout. It was the only network call in the package without one, it targets a third-party host (promoclock.co), and `MultiProviderQuotaService` calls it on every poll, so a hung host stalled the caller indefinitely and repeatedly. It now uses the same abort-timeout shape as every other fetch here, configurable via `FetchPeakHoursOptions.timeoutMs` (default 10s)
13
+ - `recordObservedContextWindow()` deduplicated its write against the process-global override table from `modelContext`, which is not keyed by `cacheDir`. Two cache directories in one process — two temp dirs in a test suite, a multi-tenant host, a CLI and extension host sharing a process — meant the second store was silently never written. The same early return also swallowed retries: because the global was updated before the write was attempted, a transient `EACCES` left the value marked as recorded and no later call tried again. The persistence dedupe is now keyed by resolved store path and recorded only after a successful write
14
+ - `parseModelId()` reported version `"4"` for every hyphenated Anthropic model ID (`claude-sonnet-4-6-20260321`, `claude-sonnet-4-5-20250929`, `claude-opus-4-1-20250805`). The version pattern accepted dots but not hyphens, while the adjacent legacy pattern already normalized both. Pricing and context windows are unaffected — both resolve from the full ID by longest-prefix match — but `parseModelId().version`, `getModelInfo().version`, and the version ordering behind model comparison all change, where 4.5 and 4.6 previously tied at `"4"`
15
+
16
+ ### Added
17
+
18
+ - The config directory is now overridable. `setConfigDir()` redirects it for the process and `SIDEKICK_CONFIG_DIR` does the same from the environment, with `getDefaultConfigDir()` exposing the unmodified platform default. Both routes trim their input, so a padded value resolves to the same root rather than to a relative path under the current directory. Every reader, writer, account-registry path, quota snapshot, and migration resolves the root lazily through `getConfigDir()`, so a single seam covers the whole package. Consumers previously had no way to point the library anywhere but `~/.config/sidekick`, which blocked test isolation, sandboxing, and multi-tenant use
19
+ - The published tarball ships `LICENSE` and `CHANGELOG.md`. `files: ["dist"]` excluded both, and npm only auto-includes a LICENSE from the package directory — so the MIT declaration in `package.json` shipped with nothing behind it
20
+ - `repository` (with `directory`, so npm's source link lands on this package), `homepage`, `bugs`, `keywords`, `author`, and `engines` are declared
21
+ - `typesVersions` maps every non-wildcard `exports` subpath for consumers whose tsconfig still uses `moduleResolution: "node"`, which cannot read `exports` maps at all. Modern resolvers ignore it entirely
22
+ - Every export of the legacy z.ai estimator (`zaiQuota`, `zaiQuotaWatcher`) is marked `@deprecated`. The deprecation was previously recorded in module prose only, so consumers reaching it through the `./dist/*` wildcard got no editor signal
23
+
24
+ ### Changed
25
+
26
+ - `engines` declares Node >= 20, driven by zod 4 and matching the sibling CLI. It is advisory unless a consumer sets `engine-strict`, but it will surface an `EBADENGINE` warning on Node 18
27
+ - `HydrateOptions.cacheDir` is optional and defaults to the config directory, so pricing hydration follows the same override seam
28
+
29
+ ### Documentation
30
+
31
+ - Four README examples were wrong and are corrected: `readTasks` was shown with the wrong first argument and no `await`; `readClaudeMaxCredentials` was shown without `await`, so the always-truthy Promise made `accessToken` undefined and surfaced later as an auth failure; the documented context window was 200000 rather than 1000000; and the documented cost output was never reachable from the stated inputs
32
+ - The headline cost example uses `normalizeProviderUsage` and `calculateNormalizedUsageCost` instead of the deprecated `calculateCost`
33
+ - The external `sqlite3` requirement is documented. `OpenCodeProvider` and `CodexProvider` shell out to it and return empty results rather than an error when it is missing, which is indistinguishable from an empty workspace unless the consumer knows to call `getRuntimeStatus()`
34
+
35
+ ## [0.24.3] - 2026-07-24
36
+
37
+ ### Fixed
38
+
39
+ - Static pricing for `gpt-5.6-luna` ($1/$6 in/out, $1.25/$0.10 cache write/read). Without an entry of its own, `getModelPricing('gpt-5.6-luna')` longest-prefix-matched `gpt-5.6` and returned `gpt-5.6-sol`'s $5/$30 — roughly 5× too high, and indistinguishable from a correct result at the call site
40
+ - Static pricing for `gpt-5.5-pro` and `gpt-5.4-pro` ($30/$180 each) and `gpt-5.4-nano` ($0.20/$1.25), which inherited `gpt-5.5` and `gpt-5.4` the same way. Rates verified against the LiteLLM catalog's bare top-level keys
41
+ - `getModelPricing('claude-sonnet-5')` returned $3/$15 against its actual $2/$10 (cache write $2.50, cache read $0.20). The entry assumed the $3/$15 shared by every earlier Sonnet; all eleven Sonnet 5 rows in the catalog agree on $2/$10
42
+ - A sweep of every bare catalog key against the static table found twelve more chat models resolving through a shorter prefix. New entries: `gpt-5-mini`, `gpt-5-nano`, `gpt-5-pro`, `gpt-5.1-codex-mini`, `gpt-5.2`, `gpt-5.2-pro`, `gpt-5.3-chat-latest`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o-2024-05-13`, `o1-pro`, `o3-pro`, and `o3-deep-research`. Overstatements reached 25× (`gpt-5-nano`) and understatements 17× (`gpt-5.2-pro`)
43
+ - Corrected values on two keys that already existed: `gpt-5.3-codex` to $1.75/$14 and `o1-mini` to $1.10/$4.40. No bare catalog key settles `o1-mini` — Azure's $1.21/$4.84 is its standard 1.1× uplift on that rate, and Replicate's OpenAI passthrough reports it directly
44
+ - Because keys are matched longest-first, each new entry also captures its own dated and fine-tuned variants — `gpt-5.6-luna-20260501` now resolves to Luna rather than to `gpt-5.6`, and one `gpt-5.2` entry covers `gpt-5.2-codex` and the `gpt-5.2-chat*` ids, which bill at the same rate
45
+
46
+ ### Added
47
+
48
+ - `MODEL_CONTEXT_SIZES` entry for `gpt-5.6-luna` at its published 1,050,000 maximum. The value equals what the `gpt-5.6` prefix already returned; it is pinned so a later edit to that entry cannot silently move Luna. Observed per-tier windows still outrank it
49
+
50
+ ### Notes
51
+
52
+ - The `-tts`, `-transcribe`, and `-realtime-preview` variants of `gpt-4o`/`gpt-4o-mini` also bill above the key they inherit and are deliberately not carried in the static table: no coding agent routes to them, and catalog hydration prices them correctly. `gpt-5.3` keeps an estimated rate because the catalog ships no bare key for it
53
+
54
+ ## [0.24.2] - 2026-07-24
55
+
56
+ ### Added
57
+
58
+ - `hydratePricingCatalog()` also builds a context-window override map from the LiteLLM catalog's `max_input_tokens`, so new models resolve without a code change. `HydrateResult` gains `contextWindowEntries`
59
+ - `normalizeLiteLlmContextWindows()` — catalog payload to model → context-window map, exported alongside `normalizeLiteLlmCatalog()`
60
+ - `loadObservedContextWindows()`, `recordObservedContextWindow()`, and `getObservedContextWindowPath()` on `sidekick-shared/node` — a persisted record of the context window a provider actually reported, which outranks the catalog's published maximum. The Codex provider records it from `model_context_window` on `token_count` events
61
+ - Static pricing for `claude-opus-5`, `claude-sonnet-5`, `gpt-5.6`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.5`, and `gpt-5.4-mini`. Sonnet 5 carries the standard $3/$15 rather than the introductory rate, which arrives via hydration
62
+
63
+ ### Changed
64
+
65
+ - `getModelContextWindowSize()` resolution is `[1m]` marker → observed → catalog → static, and tries every layer for an exact match before any layer's prefix match. `GPT-5.4` pricing is corrected to its published $2.50/$15
66
+ - Cache files written before this release have no `contextWindows` key and still load; they contribute no context overrides until the next refresh
67
+
68
+ ### Fixed
69
+
70
+ - An exact entry in the static table is no longer shadowed by a catalog key that happens to be a prefix of the model ID. The catalog has no `claude-sonnet-4-7`, so Sonnet 4.7 was resolving through `claude-sonnet-4` to 128K instead of its real 1,000,000
71
+ - `normalizeLiteLlmContextWindows()` drops a bare-model alias when the `provider/model` entries behind it disagree, rather than taking whichever appeared first in the JSON. A `provider/model` entry describes that provider's deployment window — often a truncation — and the live catalog carries five conflicting windows for `claude-sonnet-4`, from GitHub Copilot's 128K to Vertex's 1M. A top-level catalog entry always wins over a derived alias
72
+
73
+ ## [0.24.1] - 2026-07-22
74
+
75
+ ### Added
76
+
77
+ - Canonical transcripts retain provider/session provenance for safe human-prompt filtering, including entrypoint, meta/sidechain flags, original message roles, cwd, and git branch
78
+ - Provider-backed observed-session collection sources expose their adapter's `ProviderCapabilitiesV1` record
79
+
80
+ ## [0.24.0] - 2026-07-22
81
+
82
+ ### Added
83
+
84
+ - `normalizeProviderUsage()`, `extractNormalizedUsage()`, and `calculateNormalizedUsageCost()` with explicit cache/reasoning semantics and pricing provenance
85
+ - `estimateTextTokens()` / `estimateSerializedTokens()`, canonical transcript/history APIs, transcript schemas, and `ObservedSessionCollector`
86
+
87
+ ### Changed
88
+
89
+ - `SessionEvent` and its Zod schema are discriminated; valid message-less summaries and nested progress envelopes parse directly
90
+ - Claude Code, Codex, OpenCode, report transcripts, observed sessions, and EventAggregator share normalized usage and canonical projection paths
91
+ - Aggregator snapshots advance to schema v4 so older ambiguous-cost snapshots are replayed from source events
92
+ - Provider-reported session costs — including zero — are authoritative and skip catalog pricing, so subscription sessions that report $0 display $0 instead of a catalog estimate
93
+ - `ObservedAgentSessionV1` `usage.costUsd` is now nullable for unpriced usage (still `schemaVersion: 1`); validators built against the earlier number-only shape should accept `null`
94
+
95
+ ### Deprecated
96
+
97
+ - `calculateCost()`, `calculateCostWithPricing()`, `calculateCostWithProvenance()`, `CostTokenUsage`, and `CostProvenanceInput` retain their legacy reasoning-additive behavior for compatibility
98
+
99
+ ## [0.23.1] - 2026-07-21
100
+
101
+ ### Security
102
+
103
+ - Markdown report links refuse `javascript:`/`data:` URLs, and browser opening spawns the platform opener with argument arrays instead of an interpolated shell string
104
+
105
+ ### Fixed
106
+
107
+ - **2026-07 review backlog (shared portion)**: crash guards across parsers, watchers, and readers for malformed rows; index-safe SQL substitution; hardened atomic writers and quota snapshot persistence; and hardened quota polling and provider watchers
108
+ - **Observed-session message-less events**: `derivePendingUserRequestV1()` now tolerates Claude Code summary and other bookkeeping rows without a `message`, continues scanning for unresolved requests, and retains top-level tool requests with a stable event-index fallback ID
109
+ - Hyphenated `claude-opus-4-5` model IDs price at Opus 4.5 rates instead of falling back to legacy Opus pricing, and Codex cache tokens are counted once in usage rollups
110
+ - macOS keychain credential writes escape the secret correctly for the `security -i` tokenizer (single quotes and backslashes in stored JSON no longer corrupt the entry), and the secret is passed over stdin instead of argv
111
+
112
+ ### Changed
113
+
114
+ - Token metric definitions are more truthful: Codex `input_tokens` excludes cached input, per-model totals include cache read/write tokens, and follow-event message counts include only user/assistant messages — dashboard numbers may shift after upgrade
115
+
116
+ ## [0.23.0] - 2026-07-18
117
+
118
+ ### Added
119
+
120
+ - **Observed-session V1 public API**: `ProviderSessionAdapterV1`, `ObservedAgentSessionV1`, `ProviderCapabilitiesV1`, `PendingUserRequestV1`, `SessionEvidenceRefV1`, provenance/confidence values, adapters for all session providers, and corresponding Zod schemas
121
+ - **Shared operational primitives**: atomic and lock-coordinated task/decision/note writers, canonical project identity, typed doctor reports, status-line and burn-rate formatting, identifier-only external-handoff URL rendering, and a public `SessionMonitor`
122
+ - **Analytics engines**: categorized per-tool/hour/model failure rollups and append-only error history, beta quality scoring with weekly trends, historical schema v3 session records, code-impact and per-model churn/cost metrics, and compaction-ledger estimates
123
+ - **Consumer contract tests** for quota, accounts, sessions, assets, costs, turn/reasoning, and observed-session APIs
124
+
125
+ ### Changed
126
+
127
+ - Codex compaction events retain exact before/after counts with reported/heuristic provenance; aggregator snapshots advance to v3; heuristic compactions are recorded once and completed with the next observed context size instead of double-counting
128
+ - Shared ingest owns tool-error taxonomy across Claude Code, OpenCode, and Codex; OpenCode provider errors, retries, and finish reasons are normalized for analytics
129
+ - Canonical symlink-aware project identity replaces duplicated slug probing; OpenCode SQL substitution is index-safe; Codex/OpenCode database row shapes have one source
130
+ - Legacy raw-slug project stores migrate automatically to the canonical slug on first write (`migrateLegacyProjectStores`), so symlinked workspaces keep their existing tasks, decisions, notes, and handoffs
131
+ - Plan aggregation now records per-step start/completion time, duration, tokens, tool calls, and cost
132
+
133
+ ### Removed
134
+
135
+ - **Potentially breaking**: deprecated observed-traffic z.ai estimator exports were removed from the package root. Use `resolveZaiQuota()` and the authoritative quota API surface instead
136
+
137
+ ## [0.22.0] - 2026-07-02
138
+
139
+ ### Changed
140
+
141
+ - **Version bump to 0.22.0**: No functional changes. This release only keeps the shared library in lockstep with the VS Code extension and CLI (which ship user-facing changes this cycle); the published API and behavior are identical to 0.21.6
142
+
143
+ ## [0.21.6] - 2026-07-02
144
+
145
+ ### Added
146
+
147
+ - **Codex reset credits**: New `fetchCodexResetCreditsFromApi()` reads ChatGPT's `wham/rate-limit-reset-credits` endpoint and returns a `CodexResetCreditsSnapshot` (available count plus per-credit title/status/expiry), with the new `CodexResetCredit` / `CodexResetCreditsSnapshot` types and `codexResetCreditSchema` / `codexResetCreditsSnapshotSchema` Zod validators. `fetchCodexQuotaFromApi()` now attaches this snapshot to the returned `QuotaState` (`resetCredits`), authenticating with the Codex `auth.json` `account_id` (sent as `ChatGPT-Account-ID`) when present. `writeQuotaSnapshot`, the `CodexQuotaWatcher`, and the Codex session provider preserve an existing reset-credits snapshot when a session-sourced write omits it (Codex-only), so local session updates never wipe API-fetched credits. Degrades gracefully — every reset-credit fetch failure is swallowed and leaves the quota otherwise intact
148
+
149
+ ## [0.21.5] - 2026-06-30
150
+
151
+ ### Added
152
+
153
+ - **`isAggregateCodexLimit(limitId)`**: Exported helper that identifies the aggregate Codex plan-quota family (`limit_id` `codex`, or absent on older payloads) versus model/feature-specific families (e.g. `codex_bengalfox`, `premium`)
154
+
155
+ ### Fixed
156
+
157
+ - **Prefer the aggregate Codex rate-limit family over model-specific ones**: Codex emits multiple rate-limit families per session keyed by `limit_id` — the aggregate plan quota plus per-model/feature families that can read 0% while the plan is busy. Every local-data selector previously read "the latest `rate_limits`" without inspecting `limit_id`, so a freshly-used per-model family at 0% with a later-resetting window could win the newest-window comparison and mask the real plan quota. The aggregate family is now ranked first in every selection site — `readLatestQuotaFromRollout`, the live `CodexRolloutParser`, the cross-file `isPreferredQuotaHit` ranker, and the `shouldKeepExistingSnapshot` retention guard — falling back to a model-specific sample only when no aggregate one exists. No-op for Claude (no `limit_id`) and z.ai (`zai-*`), where every sample shares the same aggregate-ness
158
+
159
+ ## [0.21.4] - 2026-06-30
160
+
161
+ ### Fixed
162
+
163
+ - **Live-first active-account resolution**: New `resolveActiveClaudeAccount()` / `resolveActiveCodexAccount()` and the `ResolvedActiveAccount` type resolve the currently logged-in account from the live provider auth (`~/.claude/.claude.json` oauthAccount; the `~/.codex/auth.json` id_token JWT — a cheap JWT decode, never the slow `codex login status` subprocess) and fall back to the saved registry. On an unambiguous match they self-heal the stale active pointer (best-effort, never throwing, never creating or deleting profiles) so quota history and auto-switch track the real account. `getActiveAccountStatus()`, the multi-provider Claude path, and the Codex quota watcher now route through them
164
+
165
+ ## [0.21.3] - 2026-06-23
166
+
167
+ ### Added
168
+
169
+ - **Quota projection helpers**: New exported `projectQuotaWindow()` and `withQuotaProjections()`, plus the `FIVE_HOUR_WINDOW_MS` / `SEVEN_DAY_WINDOW_MS` constants and the `QuotaProjectionInput` type, generalize the previously Claude-only end-of-window utilization projection. Codex (`quotaFromCodexRateLimits`, using each window's real `window_minutes`) and z.ai (`quotaStateFromZaiQuotaLimitPayload`) quota states now populate `projectedFiveHour` / `projectedSevenDay`. Projection is idempotent (it only fills fields that are still null, so it never double-counts) and honors a `capturedAt` timestamp so cached snapshots project from capture time
170
+
171
+ ### Fixed
172
+
173
+ - **Bounded synchronous CLI probes**: Every synchronous `execFileSync` / `spawnSync` / `execSync` probe — keychain reads/writes (`credentialIO`, `claudeProfiles`), Codex login status and `pgrep` (`codexProfiles`), `git rev-list` (`providers/openCode`), and `sqlite3` (`providers/codexDatabase`, `providers/openCodeDatabase`) — now runs with `timeout: 4000` and `killSignal: 'SIGKILL'`, so a hung CLI, keychain prompt, or database can no longer block the caller indefinitely
174
+
175
+ ### Security
176
+
177
+ - **npm audit**: Bumped `vitest` to `^4.1.9` to clear reported dev-dependency advisories
178
+
179
+ ## [0.21.2] - 2026-06-22
180
+
181
+ ### Changed
182
+
183
+ - **Authoritative z.ai quota API**: new `zaiQuotaApi.ts` reads z.ai's `api/monitor/usage/quota/limit` endpoint and exports `resolveZaiQuota()`, `fetchZaiQuotaFromApi()`, `readZaiCredentials()`, and `quotaStateFromZaiQuotaLimitPayload()`, mapping the returned `TOKENS_LIMIT` percentages and `nextResetTime` values into the 5-Hour / Weekly `QuotaState` model. Credentials are discovered from OpenCode's stored `zai-coding-plan` → `zai` token, then `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`; API failures fall back to a cached snapshot
184
+ - **Deprecated observed-traffic estimator**: `zaiQuota.ts` and `zaiQuotaWatcher.ts` (and their exports — `accumulateZaiUsage`, `inferZaiQuotaState`, `resolveZaiTier`, `ZaiQuotaWatcher`, `ZAI_TIER_BUDGETS`, …) remain available for compatibility but are no longer used for product quota display
185
+
186
+ ## [0.21.1] - 2026-06-21
187
+
188
+ ### Added
189
+
190
+ - **z.ai Coding Plan quota derivation**: New `zaiQuota.ts` and `zaiQuotaWatcher.ts` modules derive an estimated `QuotaState` for z.ai coding plans from OpenCode assistant turns tagged `providerID ∈ {zai, zai-coding-plan}`. Because z.ai exposes no quota/usage HTTP API (verified against `docs.z.ai/openapi.json`), utilization is computed by accumulating per-turn tokens into 5-hour and 7-day rolling windows and comparing against the published per-tier prompt budgets (Lite 80/400, Pro 400/2000, Max 1600/8000 prompts per 5h/week). Authoritative reset timestamps are extracted from trapped `1308`/`1310`/`1313`/`1309` business error codes when present. Exports include `accumulateZaiUsage()`, `inferZaiQuotaState()`, `parseZaiQuotaError()`, `resolveZaiTier()`, `ZaiQuotaWatcher`, `ZAI_TIER_BUDGETS`, and `ZAI_PROMPT_INVOCATIONS`
191
+ - **`OpenCodeProvider.getZaiQuotaState()`**: The shared OpenCode provider now derives z.ai quota on demand from the on-disk `opencode.db`. `OpenCodeSessionProvider.getQuotaFromSession()` (VS Code wrapper) wires the derived state into the existing session-based quota pipeline so the dashboard, snapshot, and history see z.ai samples automatically
192
+ - **`OpenCodeDatabase.getAssistantMessagesByProviderId()`**: New query method returns assistant rows tagged with the given providerID(s), used by the z.ai accumulator to walk per-turn token records
193
+ - **Runtime quota provider `'zai'`**: `RuntimeQuotaProvider`, `QuotaHistoryRuntimeProvider`, `QuotaState.providerId`, `ProviderQuotaMap`, and the corresponding Zod schemas all accept `'zai'`. `MultiProviderQuotaService` accepts an optional `zaiWatcher` and a new `updateProviderQuota('zai', …)` overload. `QuotaSnapshotProviderId` widens the snapshot store key to `AccountProviderId | 'zai'` so the derived z.ai quota persists across sessions
194
+ - **`getOpenCodeDataDir()` exported**: Now a public helper
195
+
196
+ ### Fixed
197
+
198
+ - **OpenCode data directory resolution**: `getOpenCodeDataDir()` (now exported) probes both the macOS-default `~/Library/Application Support/opencode` and Linux-style `~/.local/share/opencode` candidate paths and returns whichever actually contains `opencode.db`. Previously it returned the platform default unconditionally, which failed on machines where OpenCode writes to the Linux-style path even on macOS
199
+
200
+ ### Limitations
201
+
202
+ - z.ai quota is **estimated, not authoritative**: it is derived only from OpenCode traffic observed on this machine (z.ai exposes no usage API) and compared against provisional per-tier prompt budgets (`ZAI_PROMPT_INVOCATIONS` is a midpoint estimate, not validated). z.ai is **observed-only** — there is no z.ai inference provider and no z.ai account management in this release (`zaiQuotaWatcher.ts`: "z.ai has no full account management in v1"). Auto-tier resolution under-reports early in a weekly cycle; reset times are approximate unless a `1308`/`1310`/`1313`/`1309` error is trapped
203
+
204
+ ## [0.21.0] - 2026-06-21
205
+
206
+ ### Added
207
+
208
+ - **Account Management 2.0 acquisition facade**: New provider-neutral helpers `beginAccountLogin()`, `getAccountLoginStatus()`, `finalizeAccountLogin()`, and `spawnAccountLogin()` let hosts acquire Claude Max and Codex accounts through isolated profile directories before activating them
209
+ - **Provider-neutral account switching**: `listAllAccounts()` and `switchAccount()` expose a single surface over Claude saved accounts and Codex saved profiles. Claude switching now applies canonical profile homes back to the live Claude home, with legacy flat-backup migration handled by `reconcileClaudeAuthState()`
210
+ - **Claude profile primitives**: `getClaudeProfilesDir()`, `getClaudeProfileHome()`, `claudeKeychainSuffix()`, `claudeKeychainService()`, `isClaudeProfileAuthenticated()`, and `readClaudeProfileIdentity()` are exported for hosts that need lower-level profile inspection
211
+ - **Terminal sync helpers**: Opt-in terminal account pointers, shell hook installation/removal, and launcher creation/removal are available through `setTerminalActiveProfile()`, `installShellHook()`, `uninstallShellHook()`, `isShellHookInstalled()`, `writeLauncher()`, and `removeLauncher()`
212
+ - **Quota auto-switch primitives**: `decideAutoSwitch()` and `AutoSwitchController` provide a default-off policy for switching to a healthier saved account after quota crosses a configured threshold
213
+ - **Account Zod schemas**: `sidekick-shared/schemas` and the package root now export `accountProviderIdSchema`, `beginAccountLoginResultSchema`, `accountLoginStatusSchema`, `accountManagerResultSchema`, `accountEntrySchema`, `savedAccountProfileSchema`, and `listAllAccountsResultSchema`
214
+
215
+ ## [0.20.0] - 2026-06-17
216
+
217
+ ### Added
218
+
219
+ - **Actionable session asset extraction**: New Node-only APIs for extracting URLs, filesystem-validated file paths, commands the agent suggested for the user to run, and plan-mode plans from recent Claude Code and Codex sessions for exactly one cwd. `gatherAssetsForCwd()` merges supported agents with recency sorting, dedupe, and per-type caps; `readClaudeAssets()`, `readCodexAssets()`, `claudeSessions()`, `codexSessions()`, `extractUrls()`, `extractFilePaths()`, and `extractCommands()` are exported for lower-level use. These APIs are safe for CLI and VS Code extension-host code, but not for browser/webview bundles
220
+ - **Session asset provenance**: `ExtractedAsset` now includes optional `agent`, `sessionPath`, and `source` metadata while keeping `type`, `text`, `display`, and `timestamp` stable for existing consumers
221
+ - **Test coverage**: Added extractor tests for command parsing, URL cleanup, filesystem-validated paths, message-text file paths, exact-cwd isolation, Codex `CODEX_HOME` discovery, merge caps, and packaging-contract exports
222
+
223
+ Thanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#17](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/17), adapted from his MIT-licensed [`trawl`](https://github.com/B33pBeeps/trawl) project.
224
+
225
+ ## [0.19.3] - 2026-06-17
226
+
227
+ ### Added
228
+
229
+ - **Assistant turn timeline**: `segmentAssistantTurn()` now emits a required v2 `timeline` array that preserves reasoning, narration, and tool groups in original arrival order while keeping the final answer text excluded. The mirrored Zod schemas and public type exports include the new timeline contract for browser and IPC consumers
230
+
231
+ ## [0.18.1] - 2026-05-08
232
+
233
+ ### Added
234
+
235
+ - **Shared display formatting**: `formatTokenCount()` and `formatDurationMs()` are now public from the root, browser, and `formatting` entrypoints, giving CLI, webview, and downstream consumers a single source of truth for compact token and duration rendering
236
+ - **Raw JSONL tailing (`createJsonlTail()`)**: offset-tracked incremental JSONL reads with optional Zod validation, debounced `fs.watch` plus catch-up polling, and a post-batch callback for aggregation-driven consumers that need to defer expensive work until parsing for a chunk is complete
237
+
238
+ ## [0.18.0] - 2026-05-08
239
+
240
+ ### Added
241
+
242
+ - **Provider-aware quota orchestration**: `MultiProviderQuotaService` coordinates Claude polling, peak-hours enrichment, account labels, transient-failure fallback, and optional Codex quota watcher updates behind one typed `{ claude?, codex? }` event stream
243
+ - **Codex quota watcher**: `CodexQuotaWatcher` discovers the active Codex rollout for a workspace, watches it for live rate-limit updates, persists account-scoped snapshots, and falls back to cached or unavailable states when no live data exists
244
+ - **Account status helper**: `getActiveAccountStatus()` returns a single Claude/Codex account status shape for startup and setup flows
245
+ - **Tool-call extraction helper**: `extractToolCall()` extracts top-level `tool_use` events, complementing the existing `extractToolCalls()` assistant-content-block helper
246
+ - **Cost/model helpers**: `calculateCostWithProvenance()`, `mergeCostSources()`, `shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, and `sortModelIds()` provide reusable UI and accounting primitives next to pricing
247
+ - **Phrase categories**: `PHRASE_CATEGORIES` exposes the category structure behind the existing flat `ALL_PHRASES`
248
+
249
+ ### Changed
250
+
251
+ - **Model parsing**: `parseModelId()` now recognizes legacy Claude IDs such as `claude-3-opus-20240229` and `claude-3-5-sonnet-20241022`
252
+
253
+ ## [0.17.7] - 2026-04-28
254
+
255
+ ### Fixed
256
+
257
+ - **Quota snapshot write race**: `writeQuotaSnapshot()` now writes through a per-process unique temp suffix (PID + timestamp + 8 bytes from `crypto.randomBytes`) before atomically renaming to `quota-snapshots.json`, and best-effort removes the temp file if the rename fails. This eliminates fixed-temp collisions and `ENOENT` when multiple Node processes (e.g., the VS Code extension and the CLI) write cached Codex quota snapshots at the same time, and prevents partial writes from leaking orphan `.tmp` files into the config directory
258
+
259
+ ## [0.17.6] - 2026-04-19
260
+
261
+ ### Added
262
+
263
+ - **`fetchPeakHoursStatus()` API**: New top-level export from `sidekick-shared` that fetches Claude's current peak-hours state from the public `promoclock.co/api/status` endpoint (third-party, unaffiliated with Anthropic). Single-shot fetcher — callers own polling. Returns a fully-normalized `PeakHoursState` with a `unavailable: true` fallback on network errors, HTTP non-2xx, or parse failures, so call sites never need try/catch
264
+ - **`PeakHoursState` type**: Exported type covering `status` (`'peak' | 'off_peak' | 'unknown'`), `isPeak`, `sessionLimitSpeed` (`'normal' | 'faster' | 'unknown'`), `label`, `peakHoursDescription`, `nextChange`, `minutesUntilChange`, `note`, `updatedAt`, and `unavailable`. Unexpected upstream values collapse to `'unknown'` rather than widening the union
265
+ - **Test coverage**: `peakHours.test.ts` adds five vitest cases — peak, off-peak, HTTP 500, network error, and unexpected-enum-value handling — mirroring the existing `providerStatus.test.ts` pattern with `vi.stubGlobal('fetch', …)`
266
+
267
+ ## [0.17.5] - 2026-04-18
268
+
269
+ ### Added
270
+
271
+ - **`ensureDefaultAccounts()` API**: New top-level export from `sidekick-shared` that auto-registers the first system Claude Code and Codex credentials as a "Default" saved account when no active account exists for that provider. Idempotent across repeated calls; never overwrites accounts that were manually saved; cleans up orphaned Codex profile directories if `prepareCodexAccount` succeeds but still reports `needsLogin`. Accepts an optional `{ logger }` for diagnostic output — every failure path returns a `'error'` status and is routed through the logger rather than thrown
272
+ - **Account bootstrap types**: `EnsureDefaultAccountsResult`, `EnsureDefaultAccountStatus` (`'registered' | 'skipped' | 'error'`), and `EnsureDefaultAccountsOptions` exported from the package root
273
+ - **Packaging contract coverage**: `packagingContract.test.ts` now asserts `ensureDefaultAccounts` is reachable from the built `dist/index.js`, so the API can't silently drop out of the published artifact
274
+ - **Test coverage**: `ensureDefaultAccounts.test.ts` adds six vitest cases covering happy path, idempotency, Claude-only, Codex-only, respect-existing-accounts, and error-swallowing — using the existing `credentialIO` mock pattern so the suite stays portable across macOS and file-based platforms
275
+
276
+ Thanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#16](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/16).
277
+
278
+ ## [0.16.1] - 2026-03-27
279
+
280
+ ### Fixed
281
+
282
+ - **Account test portability**: `accounts.test.ts` now mocks credential storage through `credentialIO`, so the shared test suite passes consistently on macOS and file-based platforms
283
+
284
+ ## [0.16.0] - 2026-03-23
285
+
286
+ ### Added
287
+
288
+ - **Zod schemas for session events**: `sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`, and `permissionModeSchema` for runtime JSONL validation
289
+ - **Token usage extractor**: Pure function `extractTokenUsage()` normalizes snake_case API usage fields from a single event
290
+ - **Tool call extractor**: Pure function `extractToolCalls()` extracts tool_use content blocks with `toolUseId` from a single event
291
+ - **Model info & pricing module**: `getModelInfo()`, `parseModelId()`, `getModelPricing()`, `calculateCost()`, `calculateCostWithPricing()`, and `formatCost()` — ported from VS Code extension with zero VS Code dependencies
292
+ - **Typed JSONL parser**: Optional `schema` parameter on `JsonlParser` for Zod-validated event parsing; invalid events route to `onError()`
293
+ - **QuotaPoller class**: Reusable polling service with exponential backoff, active/idle interval switching, cached fallback on transient errors, and automatic stop on auth failures
294
+
295
+ ### Changed
296
+
297
+ - **ToolCall type**: Added optional `toolUseId` and `output` fields for tool result correlation
298
+
299
+ ## [0.13.8] - 2026-03-12
300
+
301
+ ### Added
302
+
303
+ - **Structured quota failure metadata**: `QuotaState` unavailable results now optionally include `failureKind`, `httpStatus`, and `retryAfterMs`, and `fetchQuota()` classifies `401`, `429`, `5xx`, other non-OK responses, and transport failures without changing its non-throwing contract
304
+ - **Quota failure presentation helper**: New `describeQuotaFailure()` export maps unavailable quota states to consistent first-party severity, title/message/detail copy, retryability hints, and stable alert keys for CLI and VS Code consumers
305
+
306
+ ## [0.13.7] - 2026-03-11
307
+
308
+ _No shared-specific changes in this release._
309
+
310
+ ## [0.13.6] - 2026-03-11
311
+
312
+ ### Fixed
313
+
314
+ - **OpenCode DB-backed resolution**: OpenCode project discovery now matches DB projects by worktree, sandboxes, and session directory, and synthetic `db-sessions/<projectId>` folders are treated as monitorable paths
315
+ - **OpenCode runtime status**: DB-backed consumers can now distinguish `db_missing` from `sqlite_missing`, `sqlite_blocked`, and `query_failed`, enabling OpenCode-only actionable runtime notices instead of silent fallback to legacy file scanning
316
+
317
+ ## [0.13.5] - 2026-03-10
318
+
319
+ ### Added
320
+
321
+ - **Provider Status module**: `fetchProviderStatus()` for checking Claude API health via `status.claude.com` — returns indicator, affected components, active incidents, with graceful fallback on errors
322
+
323
+ ## [0.13.4] - 2026-03-08
324
+
325
+ _No shared-specific changes in this release._
326
+
327
+ ## [0.13.2] - 2026-03-04
328
+
329
+ ### Added
330
+
331
+ - **Credentials module**: `readClaudeMaxCredentials()` and `readClaudeMaxAccessTokenSync()` for reading Claude Max OAuth credentials from `~/.claude/.credentials.json`
332
+ - **Quota module**: `fetchQuota()` for fetching Claude Max subscription quota (5-hour and 7-day windows)
333
+ - **Vitest config**: Added vitest configuration for shared library tests
334
+
335
+ ## [0.13.1] - 2026-03-04
336
+
337
+ ### Added
338
+
339
+ - **Quota types**: `QuotaWindow` and `QuotaState` types exported for CLI and extension consumption
340
+
341
+ ## [0.13.0] - 2026-03-03
342
+
343
+ ### Changed
344
+
345
+ - **Refactoring**: Removed dead code, cached hot paths, centralized constants across shared modules
346
+
347
+ ## [0.12.10] - 2026-03-01
348
+
349
+ ### Added
350
+
351
+ - **Event aggregation**: `EventAggregator` with frequency tracking, activity heatmaps, and pattern extraction
352
+ - **Analytics engines**: `FrequencyTracker`, `HeatmapTracker`, `PatternExtractor` for session analytics
353
+ - **Snapshot persistence**: `saveSnapshot` / `loadSnapshot` for aggregator state serialization
354
+
355
+ ## [0.12.9] - 2026-02-28
356
+
357
+ ### Added
358
+
359
+ - **Cross-session search**: `searchSessions()` for full-text search across session data
360
+ - **Advanced filter**: `FilterEngine` with substring, fuzzy, regex, and date range modes
361
+ - **Context composer**: `composeContext()` for assembling tasks, decisions, notes, and handoff into a single output
362
+
363
+ ## [0.12.8] - 2026-02-28
364
+
365
+ _No shared-specific changes in this release._
366
+
367
+ ## [0.12.7] - 2026-02-27
368
+
369
+ ### Added
370
+
371
+ - **HTML report generation**: `generateHtmlReport()` produces self-contained HTML session reports with transcript, stats, and tool summaries
372
+ - **Plan extraction**: `PlanExtractor` for cross-provider plan capture (Claude Code, OpenCode, Codex)
373
+ - **Changelog parser**: `parseChangelog()` for reading Keep a Changelog formatted files
374
+
375
+ ## [0.12.6] - 2026-02-26
376
+
377
+ ### Added
378
+
379
+ - **Session dump formatters**: `formatSessionText`, `formatSessionMarkdown`, `formatSessionJson` for exporting session data
380
+ - **Noise classifier**: Event classification for filtering noise from session streams
381
+ - **Event highlighter**: Keyword coloring for session event summaries
382
+ - **Plans reader**: `readPlans`, `readClaudeCodePlanFiles` for plan file discovery
383
+ - **Phrase library**: Shared phrase content moved from CLI to shared library
384
+
385
+ ## [0.12.5] - 2026-02-24
386
+
387
+ _No shared-specific changes in this release._
388
+
389
+ ## [0.12.4] - 2026-02-24
390
+
391
+ _No shared-specific changes in this release._
392
+
393
+ ## [0.12.3] - 2026-02-24
394
+
395
+ ### Added
396
+
397
+ - **Subagent trace parser**: `scanSubagentTraces()` for parsing agent trace files
398
+ - **Session activity detector**: `detectSessionActivity()` for determining session state
399
+ - **Debug log parser**: `parseDebugLog`, `filterByLevel`, `discoverDebugLogs` for Claude Code debug log analysis
400
+
401
+ ## [0.11.0] - 2026-02-19
402
+
403
+ ### Added
404
+
405
+ - **Initial release**: Shared data access library extracted from the VS Code extension
406
+ - Session event types and persistence schemas
407
+ - JSONL, OpenCode, and Codex parsers
408
+ - Claude Code, OpenCode, and Codex session providers
409
+ - Task, decision, note, history, and handoff readers
410
+ - Session path resolution and subagent scanning
411
+ - Watcher framework for live session file monitoring
412
+ - Tool summary formatter
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cesar Andres Lopez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -28,6 +28,52 @@ See the [full changelog](https://github.com/cesarandreslopez/sidekick-agent-hub/
28
28
  npm install sidekick-shared
29
29
  ```
30
30
 
31
+ Requires Node 20 or newer. The only npm dependency is `zod` (v4) — if your app
32
+ also uses zod, keep it on `^4` so schemas from this package validate against
33
+ your instance.
34
+
35
+ ### External runtime requirement: `sqlite3`
36
+
37
+ `OpenCodeProvider` and the database helpers behind it read their session store by
38
+ shelling out to an executable **`sqlite3` on `PATH`**. There is no bundled
39
+ driver. `CodexProvider` shells out to the same binary, but only for an optional
40
+ index.
41
+
42
+ Without it `OpenCodeProvider` returns **empty results rather than an error**,
43
+ which is indistinguishable from an empty workspace. Call `getRuntimeStatus()`,
44
+ implemented by `OpenCodeProvider` only, to tell the two apart:
45
+
46
+ ```typescript
47
+ const status = provider.getRuntimeStatus?.();
48
+ // status.kind: 'available' | 'db_missing' | 'sqlite_missing'
49
+ // | 'sqlite_blocked' | 'query_failed'
50
+ if (status && status.kind !== 'available') {
51
+ console.warn(`Session data unavailable: ${status.kind}`);
52
+ }
53
+ ```
54
+
55
+ `CodexProvider` uses the Codex `state.sqlite` database only as an index and
56
+ falls back to scanning rollout JSONL files, so it keeps working without
57
+ `sqlite3`. `ClaudeCodeProvider` reads JSONL transcripts directly and needs
58
+ nothing extra.
59
+
60
+ ### Overriding the config directory
61
+
62
+ Every reader and writer resolves `~/.config/sidekick` (`%APPDATA%/sidekick` on
63
+ Windows) lazily through `getConfigDir()`. Redirect it with either:
64
+
65
+ ```typescript
66
+ import { setConfigDir } from 'sidekick-shared';
67
+
68
+ setConfigDir('/tmp/sidekick-fixture'); // in-process; pass null to clear
69
+ ```
70
+
71
+ or the `SIDEKICK_CONFIG_DIR` environment variable, which takes effect for the
72
+ whole process and is read fresh on every call. `setConfigDir()` wins when both
73
+ are set. `getConfigDir()` returns the resolved root, `getConfigDirOverride()`
74
+ returns the active in-process override or `null`, and `getDefaultConfigDir()`
75
+ returns the platform default that both overrides bypass.
76
+
31
77
  ## API Overview
32
78
 
33
79
  | Module | Description |
@@ -49,7 +95,7 @@ npm install sidekick-shared
49
95
  | **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 |
50
96
  | **Report** | Self-contained HTML session report generation |
51
97
  | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
52
- | **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 deprecated observed-traffic z.ai estimator exports were removed in 0.23.0 — `resolveZaiQuota()` is the single z.ai quota API |
98
+ | **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 |
53
99
  | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
54
100
  | **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 |
55
101
  | **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 |
@@ -108,9 +154,10 @@ Hydrate the model catalog from the `node` subpath. One fetch covers both prices
108
154
  ```typescript
109
155
  import { hydratePricingCatalog, loadObservedContextWindows } from 'sidekick-shared/node';
110
156
 
111
- const { entries, contextWindowEntries } = await hydratePricingCatalog({
112
- cacheDir: '~/.config/sidekick',
113
- });
157
+ // cacheDir defaults to the Sidekick config dir, which honors
158
+ // SIDEKICK_CONFIG_DIR and setConfigDir(). Pass it only to place the cache
159
+ // somewhere else — and pass a real path, since `~` is not expanded.
160
+ const { entries, contextWindowEntries } = await hydratePricingCatalog();
114
161
 
115
162
  // Context windows a provider reported on an earlier run. These outrank the
116
163
  // catalog, which only knows each model's published maximum — Codex reports the
@@ -238,10 +285,13 @@ const sessions = provider.findAllSessions('/path/to/project');
238
285
  ### Read persisted tasks
239
286
 
240
287
  ```typescript
241
- import { readTasks, getProjectSlug } from 'sidekick-shared';
288
+ import { readTasks, resolveProjectIdentity } from 'sidekick-shared';
242
289
 
243
- const slug = getProjectSlug('/path/to/project');
244
- const tasks = readTasks({ projectSlug: slug });
290
+ // A bare string is treated as an already-encoded slug. Pass a project
291
+ // identity to resolve a working directory — that also gets you the
292
+ // legacy-slug fallback for stores written before symlink resolution.
293
+ const project = resolveProjectIdentity('/path/to/project');
294
+ const tasks = await readTasks(project, { status: 'pending' });
245
295
  console.log(`Found ${tasks.length} tasks`);
246
296
  ```
247
297
 
@@ -277,7 +327,10 @@ if (!peak.unavailable && peak.isPeak) {
277
327
  ```typescript
278
328
  import { fetchQuota, readClaudeMaxCredentials } from 'sidekick-shared';
279
329
 
280
- const creds = readClaudeMaxCredentials();
330
+ // Returns a Promise — without `await`, the object is always truthy and
331
+ // `accessToken` is undefined, which surfaces later as an auth failure.
332
+ // Use `readClaudeMaxAccessTokenSync()` from a synchronous call site.
333
+ const creds = await readClaudeMaxCredentials();
281
334
  if (creds) {
282
335
  const quota = await fetchQuota(creds.accessToken);
283
336
  if (quota.available) {
@@ -299,18 +352,36 @@ For first-party style messaging, `describeQuotaFailure()` maps unavailable quota
299
352
  ### Model info and cost calculation
300
353
 
301
354
  ```typescript
302
- import { getModelInfo, calculateCost, formatCost } from 'sidekick-shared';
355
+ import {
356
+ getModelInfo,
357
+ normalizeProviderUsage,
358
+ calculateNormalizedUsageCost,
359
+ formatCost,
360
+ } from 'sidekick-shared';
303
361
 
304
362
  const info = getModelInfo('claude-sonnet-4-6-20260321');
305
- console.log(info.family, info.version, info.contextWindow); // "sonnet" "4.6" 200000
363
+ console.log(info.family, info.version, info.contextWindow); // "sonnet" "4.6" 1000000
306
364
 
307
- const cost = calculateCost(
308
- { inputTokens: 1000, outputTokens: 500, cacheReadTokens: 200, cacheWriteTokens: 0 },
309
- 'claude-sonnet-4-6-20260321',
310
- );
311
- console.log(formatCost(cost)); // "$0.0045"
365
+ // Normalize first: providers disagree about whether cached and reasoning
366
+ // tokens are already counted in the input/output totals.
367
+ const usage = normalizeProviderUsage({
368
+ inputTokens: 1000,
369
+ outputTokens: 500,
370
+ cacheReadTokens: 200,
371
+ cacheWriteTokens: 0,
372
+ semantics: 'anthropic',
373
+ });
374
+ const { costUsd } = calculateNormalizedUsageCost({
375
+ usage,
376
+ modelId: 'claude-sonnet-4-6-20260321',
377
+ });
378
+ console.log(formatCost(costUsd)); // "$0.01"
312
379
  ```
313
380
 
381
+ `calculateCost()` still works but is deprecated — it predates the
382
+ cache/reasoning normalization above and misprices OpenAI-style usage, where
383
+ cached tokens are already included in the input count.
384
+
314
385
  ### Extract token usage and tool calls from events
315
386
 
316
387
  ```typescript
@@ -360,7 +431,7 @@ const snapshot = readSessionContextSnapshot(provider, '/path/to/session.jsonl');
360
431
 
361
432
  console.log(snapshot.pressure); // 'low' | 'medium' | 'high'
362
433
  console.log(snapshot.contextTokens, '/', snapshot.contextWindow);
363
- console.log(snapshot.capabilities.tools, snapshot.capabilities.mcpServers);
434
+ console.log(snapshot.capabilities.observedTools, snapshot.capabilities.mcpServers);
364
435
  console.log(snapshot.sources.length, 'evidence sources');
365
436
  ```
366
437
 
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export type { HistoricalDataStore, DailyData, HourlyData, MonthlyData, AllTimeSt
25
25
  export { HISTORICAL_DATA_SCHEMA_VERSION, HISTORICAL_SESSION_RETENTION_LIMIT, createEmptyTokenTotals, createEmptyDataStore, } from './types/historicalData';
26
26
  export type { PersistedPlan, PersistedPlanStep, PlanHistoryStore, PlanStepStatus, PlanStatus, PlanSource, PlanStepComplexity, } from './types/plan';
27
27
  export { PLAN_SCHEMA_VERSION, MAX_PLANS_PER_PROJECT } from './types/plan';
28
- export { getConfigDir, getProjectDataPath, getGlobalDataPath, encodeWorkspacePath, getProjectSlug, getProjectSlugRaw, resolveProjectIdentity, } from './paths';
28
+ export { CONFIG_DIR_ENV_VAR, getConfigDir, getConfigDirOverride, getDefaultConfigDir, setConfigDir, getProjectDataPath, getGlobalDataPath, encodeWorkspacePath, getProjectSlug, getProjectSlugRaw, resolveProjectIdentity, } from './paths';
29
29
  export type { ProjectIdentity } from './paths';
30
30
  export { migrateLegacyProjectStores } from './projectMigration';
31
31
  export { readTasks } from './readers/tasks';
@@ -188,5 +188,5 @@ export { fetchProviderStatus, fetchOpenAIStatus } from './providerStatus';
188
188
  export type { ProviderStatusState } from './providerStatus';
189
189
  export { formatHealthReport, getSessionDiagnostics, runDoctor } from './doctor';
190
190
  export type { DeprecatedSetting, DoctorOptions, HealthCheck, HealthCheckStatus, HealthReport, SessionDiagnostics, } from './doctor';
191
- export { createPeakHoursNotApplicableState, fetchPeakHoursStatus, isClaudeCodeSessionProvider, scopePeakHoursToSessionProvider, } from './peakHours';
192
- export type { PeakHoursState } from './peakHours';
191
+ export { createPeakHoursNotApplicableState, DEFAULT_PEAK_HOURS_TIMEOUT_MS, fetchPeakHoursStatus, isClaudeCodeSessionProvider, scopePeakHoursToSessionProvider, } from './peakHours';
192
+ export type { FetchPeakHoursOptions, PeakHoursState } from './peakHours';