sidekick-shared 0.24.3 → 0.24.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +436 -0
- package/LICENSE +21 -0
- package/README.md +172 -60
- package/dist/accountManager.d.ts +13 -0
- package/dist/accountManager.js +60 -32
- package/dist/accountRegistry.js +59 -41
- package/dist/accounts.js +40 -19
- package/dist/autoSwitch.d.ts +2 -1
- package/dist/autoSwitch.js +32 -7
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +3 -1
- package/dist/codexProfiles.d.ts +14 -0
- package/dist/codexProfiles.js +268 -116
- package/dist/context/sessionContext.js +2 -4
- package/dist/ensureDefaultAccounts.js +4 -4
- package/dist/index.d.ts +14 -7
- package/dist/index.js +33 -7
- package/dist/modelInfo.js +11 -2
- package/dist/node.d.ts +6 -0
- package/dist/node.js +8 -1
- package/dist/observedContextWindows.d.ts +20 -4
- package/dist/observedContextWindows.js +47 -7
- package/dist/parsers/codexHistory.d.ts +30 -0
- package/dist/parsers/codexHistory.js +118 -0
- package/dist/parsers/mcpToolName.d.ts +26 -0
- package/dist/parsers/mcpToolName.js +31 -0
- package/dist/parsers/sessionPathResolver.d.ts +11 -4
- package/dist/parsers/sessionPathResolver.js +11 -4
- package/dist/paths.d.ts +43 -4
- package/dist/paths.js +66 -6
- package/dist/peakHours.d.ts +16 -2
- package/dist/peakHours.js +18 -3
- package/dist/pricingCatalog.d.ts +7 -3
- package/dist/pricingCatalog.js +3 -2
- package/dist/providers/claudeCode.d.ts +2 -1
- package/dist/providers/claudeCode.js +18 -0
- package/dist/providers/codex.d.ts +22 -1
- package/dist/providers/codex.js +58 -0
- package/dist/providers/types.d.ts +5 -0
- package/dist/quotaHistory.d.ts +1 -1
- package/dist/quotaHistory.js +2 -2
- package/dist/quotaSnapshots.js +8 -77
- package/dist/sessionPreviews.d.ts +60 -0
- package/dist/sessionPreviews.js +234 -0
- package/dist/sessionTranscripts.js +10 -4
- package/dist/writers/atomic.d.ts +20 -0
- package/dist/writers/atomic.js +190 -19
- package/dist/zaiQuota.d.ts +87 -0
- package/dist/zaiQuota.js +49 -0
- package/dist/zaiQuotaWatcher.d.ts +7 -0
- package/dist/zaiQuotaWatcher.js +3 -0
- package/package.json +60 -2
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
|
|
|
9
9
|
|
|
10
10
|
## Recent additions
|
|
11
11
|
|
|
12
|
+
- **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
|
+
- **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.
|
|
12
14
|
- **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.
|
|
13
15
|
- **Transcripts and resilient collection** — `projectSessionTranscript()` is browser-safe; Node consumers can use `listRecentSessions()`, `readSessionTranscript()`, and `ObservedSessionCollector` across Claude Code, Codex, and OpenCode readers.
|
|
14
16
|
- **Shared token estimation** — `estimateTextTokens()` / `estimateSerializedTokens()` provide an injectable exact-counter path and the stable tokenizer-free `sidekick-fallback-v1` heuristic.
|
|
@@ -28,58 +30,104 @@ See the [full changelog](https://github.com/cesarandreslopez/sidekick-agent-hub/
|
|
|
28
30
|
npm install sidekick-shared
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
Requires Node 20 or newer. The only npm dependency is `zod` (v4) — if your app
|
|
34
|
+
also uses zod, keep it on `^4` so schemas from this package validate against
|
|
35
|
+
your instance.
|
|
36
|
+
|
|
37
|
+
### External runtime requirement: `sqlite3`
|
|
38
|
+
|
|
39
|
+
`OpenCodeProvider` and the database helpers behind it read their session store by
|
|
40
|
+
shelling out to an executable **`sqlite3` on `PATH`**. There is no bundled
|
|
41
|
+
driver. `CodexProvider` shells out to the same binary, but only for an optional
|
|
42
|
+
index.
|
|
43
|
+
|
|
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:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const status = provider.getRuntimeStatus?.();
|
|
50
|
+
// status.kind: 'available' | 'db_missing' | 'sqlite_missing'
|
|
51
|
+
// | 'sqlite_blocked' | 'query_failed'
|
|
52
|
+
if (status && status.kind !== 'available') {
|
|
53
|
+
console.warn(`Session data unavailable: ${status.kind}`);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`CodexProvider` uses the Codex `state.sqlite` database only as an index and
|
|
58
|
+
falls back to scanning rollout JSONL files, so it keeps working without
|
|
59
|
+
`sqlite3`. `ClaudeCodeProvider` reads JSONL transcripts directly and needs
|
|
60
|
+
nothing extra.
|
|
61
|
+
|
|
62
|
+
### Overriding the config directory
|
|
63
|
+
|
|
64
|
+
Every reader and writer resolves `~/.config/sidekick` (`%APPDATA%/sidekick` on
|
|
65
|
+
Windows) lazily through `getConfigDir()`. Redirect it with either:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { setConfigDir } from 'sidekick-shared';
|
|
69
|
+
|
|
70
|
+
setConfigDir('/tmp/sidekick-fixture'); // in-process; pass null to clear
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
or the `SIDEKICK_CONFIG_DIR` environment variable, which takes effect for the
|
|
74
|
+
whole process and is read fresh on every call. `setConfigDir()` wins when both
|
|
75
|
+
are set. `getConfigDir()` returns the resolved root, `getConfigDirOverride()`
|
|
76
|
+
returns the active in-process override or `null`, and `getDefaultConfigDir()`
|
|
77
|
+
returns the platform default that both overrides bypass.
|
|
78
|
+
|
|
31
79
|
## API Overview
|
|
32
80
|
|
|
33
|
-
| Module | Description
|
|
34
|
-
| ------------------------------- |
|
|
35
|
-
| **Types** | Session events, OpenCode/Codex format types, persistence schemas (tasks, decisions, notes, plans, historical data)
|
|
36
|
-
| **Paths** | Config directory resolution, project data paths, workspace encoding
|
|
37
|
-
| **Readers** | Read tasks, decisions, notes, history, handoff, and plans from `~/.config/sidekick/`
|
|
38
|
-
| **Writers** | Atomic persistence writers — `atomicWriteJson()` / `updateJsonStoreAtomic()` plus lock-coordinated capture helpers `addTask()`, `completeTask()`, `addDecision()`, `addNote()` that merge into the shared stores without clobbering concurrent writers
|
|
39
|
-
| **Providers** | Session provider abstraction with Claude Code, OpenCode, and Codex implementations; auto-detection via filesystem
|
|
40
|
-
| **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`)
|
|
41
|
-
| **Parsers** | JSONL event parsing, OpenCode/Codex format normalization, subagent scanning, session path resolution, debug log parsing
|
|
42
|
-
| **Watchers** | Live session file watching with event bridging, plus `createJsonlTail()` for raw incremental JSONL consumers
|
|
43
|
-
| **Formatters** | Display helpers (`formatTokenCount()`, `formatDurationMs()`), tool summary, noise classification, session dump (text/markdown/JSON), event highlighting
|
|
44
|
-
| **Search** | Cross-session full-text search, advanced filtering (substring, fuzzy, regex, date)
|
|
45
|
-
| **Aggregation** | Event aggregation, frequency tracking, activity heatmaps, pattern extraction
|
|
46
|
-
| **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()`)
|
|
47
|
-
| **Error History** | Shared error taxonomy (`categorizeError()`, `extractErrorMessage()`) plus append-only per-project failure history (`appendErrorHistory()`, `readErrorHistory()`, `getTopFailingTools()`) behind per-tool/hour/model error rollups
|
|
48
|
-
| **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)
|
|
49
|
-
| **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
|
-
| **Report** | Self-contained HTML session report generation
|
|
51
|
-
| **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
|
|
53
|
-
| **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents)
|
|
54
|
-
| **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
|
-
| **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
|
|
56
|
-
| **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
|
|
57
|
-
| **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()`)
|
|
58
|
-
| **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback
|
|
59
|
-
| **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
|
|
60
|
-
| **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
|
|
61
|
-
| **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups
|
|
62
|
-
| **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
|
|
63
|
-
| **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
|
|
64
|
-
| **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback
|
|
65
|
-
| **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
|
|
66
|
-
| **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI
|
|
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 |
|
|
67
115
|
|
|
68
116
|
## Supported import paths
|
|
69
117
|
|
|
70
118
|
`sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime.
|
|
71
119
|
|
|
72
|
-
| Path | Runtime | What it exposes
|
|
73
|
-
| ------------------------------ | -------------------------- |
|
|
74
|
-
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …).
|
|
75
|
-
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math
|
|
76
|
-
| `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, and resilient observed-session collection (`fs` + providers). |
|
|
77
|
-
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`.
|
|
78
|
-
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module.
|
|
79
|
-
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math.
|
|
80
|
-
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers.
|
|
81
|
-
| `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.
|
|
82
|
-
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates.
|
|
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. |
|
|
83
131
|
|
|
84
132
|
### Browser / webview runtimes
|
|
85
133
|
|
|
@@ -108,9 +156,10 @@ Hydrate the model catalog from the `node` subpath. One fetch covers both prices
|
|
|
108
156
|
```typescript
|
|
109
157
|
import { hydratePricingCatalog, loadObservedContextWindows } from 'sidekick-shared/node';
|
|
110
158
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
159
|
+
// cacheDir defaults to the Sidekick config dir, which honors
|
|
160
|
+
// SIDEKICK_CONFIG_DIR and setConfigDir(). Pass it only to place the cache
|
|
161
|
+
// somewhere else — and pass a real path, since `~` is not expanded.
|
|
162
|
+
const { entries, contextWindowEntries } = await hydratePricingCatalog();
|
|
114
163
|
|
|
115
164
|
// Context windows a provider reported on an earlier run. These outrank the
|
|
116
165
|
// catalog, which only knows each model's published maximum — Codex reports the
|
|
@@ -188,6 +237,45 @@ const humanCliPrompts = transcript.messages.filter(
|
|
|
188
237
|
|
|
189
238
|
For an already-open incremental Codex rollout, keep one `CodexRolloutParser` per stream and pass each parsed `SessionEvent[]` batch to `projectSessionTranscript()` (or append the events to an existing projection). This preserves parser state for paired tool calls, model context, and cumulative token deltas; consumers should not convert raw `response_item` rows themselves.
|
|
190
239
|
|
|
240
|
+
### Locate provider-native session files
|
|
241
|
+
|
|
242
|
+
The library exports the same primitives its providers use to find session files on disk, so consumers never need to hand-roll path encoding or directory walks:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import {
|
|
246
|
+
getClaudeSessionDirectory, // ~/.claude/projects/<encoded workspace>
|
|
247
|
+
encodeClaudeWorkspacePath, // the encoding itself, if you need the raw string
|
|
248
|
+
discoverSessionDirectory, // mismatch-tolerant lookup — prefer this for reads
|
|
249
|
+
findAllClaudeSessions,
|
|
250
|
+
findCodexRolloutFile, // Codex session id -> rollout file path
|
|
251
|
+
readCodexHistory, // bounded tail of ~/.codex/history.jsonl, newest first
|
|
252
|
+
} from 'sidekick-shared';
|
|
253
|
+
|
|
254
|
+
const sessions = findAllClaudeSessions('/home/user/code/my-project');
|
|
255
|
+
|
|
256
|
+
const [latest] = readCodexHistory({ limit: 1 });
|
|
257
|
+
const rolloutPath = latest ? findCodexRolloutFile(latest.sessionId) : null;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
> **Naming trap:** the top-level `encodeWorkspacePath` export is Sidekick's own config-store slug (it keeps dots) and does **not** match Claude Code's directory scheme. For `~/.claude/projects/` paths always use `encodeClaudeWorkspacePath` — or better, `getClaudeSessionDirectory` / `discoverSessionDirectory`, which tolerate encoding drift.
|
|
261
|
+
|
|
262
|
+
For a cross-provider "recent sessions" surface, `listSessionPreviews()` enumerates stat-only, sorts by mtime, applies `since`/`limit`, and only then does bounded content reads for the survivors — so cost tracks the size of the answer, not the size of the history:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { ClaudeCodeProvider, CodexProvider, listSessionPreviews } from 'sidekick-shared';
|
|
266
|
+
|
|
267
|
+
const previews = listSessionPreviews([new ClaudeCodeProvider(), new CodexProvider()], {
|
|
268
|
+
limit: 10,
|
|
269
|
+
since: lastRefreshTime, // optional incremental refresh
|
|
270
|
+
});
|
|
271
|
+
// -> [{ provider, sessionId, filePath, modifiedAt, sizeBytes,
|
|
272
|
+
// firstUserPrompt, firstTimestamp, workspacePath }, ...]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`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
|
+
|
|
277
|
+
MCP tool identifiers (`mcp__<server>__<tool>`) split with `parseMcpToolName(name)` — first-double-underscore rule, `null` for malformed names, safe in browser bundles.
|
|
278
|
+
|
|
191
279
|
### Collect observed sessions with isolation and retry
|
|
192
280
|
|
|
193
281
|
```typescript
|
|
@@ -238,10 +326,13 @@ const sessions = provider.findAllSessions('/path/to/project');
|
|
|
238
326
|
### Read persisted tasks
|
|
239
327
|
|
|
240
328
|
```typescript
|
|
241
|
-
import { readTasks,
|
|
329
|
+
import { readTasks, resolveProjectIdentity } from 'sidekick-shared';
|
|
242
330
|
|
|
243
|
-
|
|
244
|
-
|
|
331
|
+
// A bare string is treated as an already-encoded slug. Pass a project
|
|
332
|
+
// identity to resolve a working directory — that also gets you the
|
|
333
|
+
// legacy-slug fallback for stores written before symlink resolution.
|
|
334
|
+
const project = resolveProjectIdentity('/path/to/project');
|
|
335
|
+
const tasks = await readTasks(project, { status: 'pending' });
|
|
245
336
|
console.log(`Found ${tasks.length} tasks`);
|
|
246
337
|
```
|
|
247
338
|
|
|
@@ -277,7 +368,10 @@ if (!peak.unavailable && peak.isPeak) {
|
|
|
277
368
|
```typescript
|
|
278
369
|
import { fetchQuota, readClaudeMaxCredentials } from 'sidekick-shared';
|
|
279
370
|
|
|
280
|
-
|
|
371
|
+
// Returns a Promise — without `await`, the object is always truthy and
|
|
372
|
+
// `accessToken` is undefined, which surfaces later as an auth failure.
|
|
373
|
+
// Use `readClaudeMaxAccessTokenSync()` from a synchronous call site.
|
|
374
|
+
const creds = await readClaudeMaxCredentials();
|
|
281
375
|
if (creds) {
|
|
282
376
|
const quota = await fetchQuota(creds.accessToken);
|
|
283
377
|
if (quota.available) {
|
|
@@ -299,18 +393,36 @@ For first-party style messaging, `describeQuotaFailure()` maps unavailable quota
|
|
|
299
393
|
### Model info and cost calculation
|
|
300
394
|
|
|
301
395
|
```typescript
|
|
302
|
-
import {
|
|
396
|
+
import {
|
|
397
|
+
getModelInfo,
|
|
398
|
+
normalizeProviderUsage,
|
|
399
|
+
calculateNormalizedUsageCost,
|
|
400
|
+
formatCost,
|
|
401
|
+
} from 'sidekick-shared';
|
|
303
402
|
|
|
304
403
|
const info = getModelInfo('claude-sonnet-4-6-20260321');
|
|
305
|
-
console.log(info.family, info.version, info.contextWindow); // "sonnet" "4.6"
|
|
404
|
+
console.log(info.family, info.version, info.contextWindow); // "sonnet" "4.6" 1000000
|
|
306
405
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
406
|
+
// Normalize first: providers disagree about whether cached and reasoning
|
|
407
|
+
// tokens are already counted in the input/output totals.
|
|
408
|
+
const usage = normalizeProviderUsage({
|
|
409
|
+
inputTokens: 1000,
|
|
410
|
+
outputTokens: 500,
|
|
411
|
+
cacheReadTokens: 200,
|
|
412
|
+
cacheWriteTokens: 0,
|
|
413
|
+
semantics: 'anthropic',
|
|
414
|
+
});
|
|
415
|
+
const { costUsd } = calculateNormalizedUsageCost({
|
|
416
|
+
usage,
|
|
417
|
+
modelId: 'claude-sonnet-4-6-20260321',
|
|
418
|
+
});
|
|
419
|
+
console.log(formatCost(costUsd)); // "$0.01"
|
|
312
420
|
```
|
|
313
421
|
|
|
422
|
+
`calculateCost()` still works but is deprecated — it predates the
|
|
423
|
+
cache/reasoning normalization above and misprices OpenAI-style usage, where
|
|
424
|
+
cached tokens are already included in the input count.
|
|
425
|
+
|
|
314
426
|
### Extract token usage and tool calls from events
|
|
315
427
|
|
|
316
428
|
```typescript
|
|
@@ -360,7 +472,7 @@ const snapshot = readSessionContextSnapshot(provider, '/path/to/session.jsonl');
|
|
|
360
472
|
|
|
361
473
|
console.log(snapshot.pressure); // 'low' | 'medium' | 'high'
|
|
362
474
|
console.log(snapshot.contextTokens, '/', snapshot.contextWindow);
|
|
363
|
-
console.log(snapshot.capabilities.
|
|
475
|
+
console.log(snapshot.capabilities.observedTools, snapshot.capabilities.mcpServers);
|
|
364
476
|
console.log(snapshot.sources.length, 'evidence sources');
|
|
365
477
|
```
|
|
366
478
|
|
package/dist/accountManager.d.ts
CHANGED
|
@@ -46,7 +46,20 @@ export declare function beginAccountLogin(provider: AccountProviderId, label: st
|
|
|
46
46
|
loginCommand?: AccountLoginCommand;
|
|
47
47
|
}): BeginAccountLoginResult;
|
|
48
48
|
export declare function getAccountLoginStatus(provider: AccountProviderId, loginId: string): AccountLoginStatus;
|
|
49
|
+
/**
|
|
50
|
+
* {@link getAccountLoginStatus} without blocking the event loop on the codex
|
|
51
|
+
* CLI probe. The sync variant blocks up to 4s per call while a codex login is
|
|
52
|
+
* pending — ruinous for callers that poll it on an interval.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getAccountLoginStatusAsync(provider: AccountProviderId, loginId: string): Promise<AccountLoginStatus>;
|
|
49
55
|
export declare function finalizeAccountLogin(provider: AccountProviderId, loginId: string, opts?: FinalizeAccountLoginOptions): AccountManagerResult;
|
|
56
|
+
/** {@link finalizeAccountLogin} without blocking the event loop on CLI probes. */
|
|
57
|
+
export declare function finalizeAccountLoginAsync(provider: AccountProviderId, loginId: string, opts?: FinalizeAccountLoginOptions): Promise<AccountManagerResult>;
|
|
50
58
|
export declare function spawnAccountLogin(provider: AccountProviderId, label: string, opts?: SpawnAccountLoginOptions): Promise<AccountManagerResult>;
|
|
51
59
|
export declare function switchAccount(provider: AccountProviderId, id: string): AccountManagerResult;
|
|
60
|
+
/**
|
|
61
|
+
* {@link switchAccount} without blocking the event loop on codex CLI probes.
|
|
62
|
+
* The Claude path still performs bounded synchronous keychain reads on macOS.
|
|
63
|
+
*/
|
|
64
|
+
export declare function switchAccountAsync(provider: AccountProviderId, id: string): Promise<AccountManagerResult>;
|
|
52
65
|
export declare function listAllAccounts(): ListAllAccountsResult;
|
package/dist/accountManager.js
CHANGED
|
@@ -36,15 +36,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.resolveClaudeLoginCommand = resolveClaudeLoginCommand;
|
|
37
37
|
exports.beginAccountLogin = beginAccountLogin;
|
|
38
38
|
exports.getAccountLoginStatus = getAccountLoginStatus;
|
|
39
|
+
exports.getAccountLoginStatusAsync = getAccountLoginStatusAsync;
|
|
39
40
|
exports.finalizeAccountLogin = finalizeAccountLogin;
|
|
41
|
+
exports.finalizeAccountLoginAsync = finalizeAccountLoginAsync;
|
|
40
42
|
exports.spawnAccountLogin = spawnAccountLogin;
|
|
41
43
|
exports.switchAccount = switchAccount;
|
|
44
|
+
exports.switchAccountAsync = switchAccountAsync;
|
|
42
45
|
exports.listAllAccounts = listAllAccounts;
|
|
43
46
|
const child_process_1 = require("child_process");
|
|
44
47
|
const crypto_1 = require("crypto");
|
|
45
48
|
const fs = __importStar(require("fs"));
|
|
46
49
|
const path = __importStar(require("path"));
|
|
47
50
|
const accountRegistry_1 = require("./accountRegistry");
|
|
51
|
+
const atomic_1 = require("./writers/atomic");
|
|
48
52
|
const accounts_1 = require("./accounts");
|
|
49
53
|
const claudeProfiles_1 = require("./claudeProfiles");
|
|
50
54
|
const credentialIO_1 = require("./credentialIO");
|
|
@@ -55,25 +59,6 @@ function getClaudeProfileDir(loginId) {
|
|
|
55
59
|
function getPendingClaudeProfilePath(loginId) {
|
|
56
60
|
return path.join(getClaudeProfileDir(loginId), 'profile.json');
|
|
57
61
|
}
|
|
58
|
-
function atomicWriteJson(filePath, data, mode = 0o600) {
|
|
59
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
60
|
-
const tmp = `${filePath}.tmp`;
|
|
61
|
-
const json = JSON.stringify(data, null, 2);
|
|
62
|
-
JSON.parse(json);
|
|
63
|
-
try {
|
|
64
|
-
fs.writeFileSync(tmp, json, { encoding: 'utf8', mode });
|
|
65
|
-
fs.renameSync(tmp, filePath);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
try {
|
|
69
|
-
fs.unlinkSync(tmp);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
/* nothing to clean up */
|
|
73
|
-
}
|
|
74
|
-
throw err;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
62
|
function readPendingClaudeProfile(loginId) {
|
|
78
63
|
try {
|
|
79
64
|
return JSON.parse(fs.readFileSync(getPendingClaudeProfilePath(loginId), 'utf8'));
|
|
@@ -84,7 +69,7 @@ function readPendingClaudeProfile(loginId) {
|
|
|
84
69
|
}
|
|
85
70
|
function writePendingClaudeProfile(loginId, pending) {
|
|
86
71
|
(0, claudeProfiles_1.ensureClaudeProfileDirs)(loginId);
|
|
87
|
-
|
|
72
|
+
(0, atomic_1.atomicWriteJsonSync)(getPendingClaudeProfilePath(loginId), pending);
|
|
88
73
|
}
|
|
89
74
|
function removePendingClaudeProfile(loginId) {
|
|
90
75
|
try {
|
|
@@ -182,6 +167,13 @@ function beginAccountLogin(provider, label, opts = {}) {
|
|
|
182
167
|
configDir: home,
|
|
183
168
|
};
|
|
184
169
|
}
|
|
170
|
+
function getClaudeAccountLoginStatus(loginId) {
|
|
171
|
+
const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
|
|
172
|
+
const identity = (0, claudeProfiles_1.readClaudeProfileIdentity)(home);
|
|
173
|
+
return (0, claudeProfiles_1.isClaudeProfileAuthenticated)(home)
|
|
174
|
+
? { state: 'authenticated', email: identity?.email }
|
|
175
|
+
: { state: 'pending' };
|
|
176
|
+
}
|
|
185
177
|
function getAccountLoginStatus(provider, loginId) {
|
|
186
178
|
if (provider === 'codex') {
|
|
187
179
|
const codexHome = (0, codexProfiles_1.getCodexProfileHome)(loginId);
|
|
@@ -194,16 +186,41 @@ function getAccountLoginStatus(provider, loginId) {
|
|
|
194
186
|
email: metadata.email,
|
|
195
187
|
};
|
|
196
188
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
189
|
+
return getClaudeAccountLoginStatus(loginId);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* {@link getAccountLoginStatus} without blocking the event loop on the codex
|
|
193
|
+
* CLI probe. The sync variant blocks up to 4s per call while a codex login is
|
|
194
|
+
* pending — ruinous for callers that poll it on an interval.
|
|
195
|
+
*/
|
|
196
|
+
async function getAccountLoginStatusAsync(provider, loginId) {
|
|
197
|
+
if (provider === 'codex') {
|
|
198
|
+
const codexHome = (0, codexProfiles_1.getCodexProfileHome)(loginId);
|
|
199
|
+
if (!(await (0, codexProfiles_1.isCodexProfileAuthenticatedAsync)(codexHome))) {
|
|
200
|
+
return { state: 'pending' };
|
|
201
|
+
}
|
|
202
|
+
const metadata = await (0, codexProfiles_1.readCodexAccountMetadataAsync)(codexHome);
|
|
203
|
+
return {
|
|
204
|
+
state: 'authenticated',
|
|
205
|
+
email: metadata.email,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return getClaudeAccountLoginStatus(loginId);
|
|
202
209
|
}
|
|
203
210
|
function finalizeAccountLogin(provider, loginId, opts = {}) {
|
|
204
211
|
if (provider === 'codex') {
|
|
205
212
|
return (0, codexProfiles_1.finalizeCodexAccount)(loginId, opts);
|
|
206
213
|
}
|
|
214
|
+
return finalizeClaudeAccountLogin(loginId, opts);
|
|
215
|
+
}
|
|
216
|
+
/** {@link finalizeAccountLogin} without blocking the event loop on CLI probes. */
|
|
217
|
+
async function finalizeAccountLoginAsync(provider, loginId, opts = {}) {
|
|
218
|
+
if (provider === 'codex') {
|
|
219
|
+
return (0, codexProfiles_1.finalizeCodexAccountAsync)(loginId, opts);
|
|
220
|
+
}
|
|
221
|
+
return finalizeClaudeAccountLogin(loginId, opts);
|
|
222
|
+
}
|
|
223
|
+
function finalizeClaudeAccountLogin(loginId, opts = {}) {
|
|
207
224
|
const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
|
|
208
225
|
const identity = (0, claudeProfiles_1.readClaudeProfileIdentity)(home);
|
|
209
226
|
if (!identity) {
|
|
@@ -218,8 +235,8 @@ function finalizeAccountLogin(provider, loginId, opts = {}) {
|
|
|
218
235
|
return { success: false, error: 'Could not read Claude profile config.' };
|
|
219
236
|
}
|
|
220
237
|
const pending = readPendingClaudeProfile(loginId);
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
(0, atomic_1.atomicWriteJsonSync)(getClaudeCredentialsBackupPath(identity.uuid), credentials);
|
|
239
|
+
(0, atomic_1.atomicWriteJsonSync)(getClaudeConfigBackupPath(identity.uuid), oauthAccount);
|
|
223
240
|
copyClaudeProfileToCanonicalHome(home, identity.uuid, credentials);
|
|
224
241
|
(0, accountRegistry_1.upsertSavedAccountProfile)({
|
|
225
242
|
id: identity.uuid,
|
|
@@ -274,7 +291,7 @@ async function spawnAccountLogin(provider, label, opts = {}) {
|
|
|
274
291
|
if (!begin.success)
|
|
275
292
|
return { success: false, error: begin.error };
|
|
276
293
|
if (begin.alreadyComplete) {
|
|
277
|
-
return
|
|
294
|
+
return finalizeAccountLoginAsync(provider, begin.loginId, { activate: opts.activate ?? true });
|
|
278
295
|
}
|
|
279
296
|
if (!begin.command) {
|
|
280
297
|
return { success: false, error: 'Account login command was not prepared.' };
|
|
@@ -308,9 +325,11 @@ async function spawnAccountLogin(provider, label, opts = {}) {
|
|
|
308
325
|
emitStatus(opts, { state: 'failed', error: 'Account login aborted.' });
|
|
309
326
|
return { success: false, error: 'Account login aborted.' };
|
|
310
327
|
}
|
|
311
|
-
const status = emitStatus(opts,
|
|
328
|
+
const status = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
|
|
312
329
|
if (status.state === 'authenticated') {
|
|
313
|
-
return
|
|
330
|
+
return finalizeAccountLoginAsync(provider, begin.loginId, {
|
|
331
|
+
activate: opts.activate ?? true,
|
|
332
|
+
});
|
|
314
333
|
}
|
|
315
334
|
if (childExited) {
|
|
316
335
|
if (childState.spawnError) {
|
|
@@ -318,9 +337,11 @@ async function spawnAccountLogin(provider, label, opts = {}) {
|
|
|
318
337
|
emitStatus(opts, { state: 'failed', error });
|
|
319
338
|
return { success: false, error };
|
|
320
339
|
}
|
|
321
|
-
const finalStatus = emitStatus(opts,
|
|
340
|
+
const finalStatus = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
|
|
322
341
|
if (finalStatus.state === 'authenticated') {
|
|
323
|
-
return
|
|
342
|
+
return finalizeAccountLoginAsync(provider, begin.loginId, {
|
|
343
|
+
activate: opts.activate ?? true,
|
|
344
|
+
});
|
|
324
345
|
}
|
|
325
346
|
emitStatus(opts, {
|
|
326
347
|
state: 'failed',
|
|
@@ -344,6 +365,13 @@ async function spawnAccountLogin(provider, label, opts = {}) {
|
|
|
344
365
|
function switchAccount(provider, id) {
|
|
345
366
|
return provider === 'codex' ? (0, codexProfiles_1.switchToCodexAccount)(id) : (0, accounts_1.switchToAccount)(id);
|
|
346
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* {@link switchAccount} without blocking the event loop on codex CLI probes.
|
|
370
|
+
* The Claude path still performs bounded synchronous keychain reads on macOS.
|
|
371
|
+
*/
|
|
372
|
+
async function switchAccountAsync(provider, id) {
|
|
373
|
+
return provider === 'codex' ? (0, codexProfiles_1.switchToCodexAccountAsync)(id) : (0, accounts_1.switchToAccount)(id);
|
|
374
|
+
}
|
|
347
375
|
function listAllAccounts() {
|
|
348
376
|
return {
|
|
349
377
|
claude: (0, accounts_1.listAccounts)(),
|