sidekick-shared 0.19.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,16 @@ Shared data access library for [Sidekick Agent Hub](https://github.com/cesarandr
7
7
 
8
8
  Types, parsers, providers, readers, formatters, aggregation, search, reporting, credentials, and quota for AI agent session monitoring. Used by both the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=CesarAndresLopez.sidekick-for-max) and the [CLI dashboard](https://www.npmjs.com/package/sidekick-agent-hub).
9
9
 
10
+ ## Recent additions
11
+
12
+ - **Assistant-turn projection** — `segmentAssistantTurn()` / `assistantTurnEventsFromSessionEvents()` segment a provider-normalized turn into a browser-safe Timeline + Process + Answer shape (see [Assistant Turns](#api-overview)).
13
+ - **Session-context snapshots** — `buildSessionContextSnapshot()` / `calculateSessionContextPressure()` expose layered evidence sources and low/medium/high context pressure.
14
+ - **Zod boundary schemas** — runtime validation for data crossing process/IPC boundaries, importable fs-free from [`sidekick-shared/schemas`](#supported-import-paths).
15
+ - **Opus 4.8 & Fable 5 model info** — 1M-token context windows and pricing, plus forgiving (trim/lowercase) model-ID lookups.
16
+ - **Node-only asset extraction** — `gatherAssetsForCwd()` collects URLs, file paths, commands, and plans from recent Claude Code and Codex sessions.
17
+
18
+ See the [full changelog](https://github.com/cesarandreslopez/sidekick-agent-hub/blob/main/CHANGELOG.md) for everything.
19
+
10
20
  ## Installation
11
21
 
12
22
  ```bash
@@ -33,11 +43,13 @@ npm install sidekick-shared
33
43
  | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows) and Codex rate-limit extraction from event streams |
34
44
  | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
35
45
  | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, quota, account status, and quota history — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
36
- | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks) |
46
+ | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks), plus Node-only session asset extraction (`gatherAssetsForCwd()`, `readClaudeAssets()`, `readCodexAssets()`) for URLs, file paths, commands, and plans |
37
47
  | **Model Info & Pricing** | Model family parsing (Anthropic / OpenAI / Google, including legacy `claude-3-opus-…` and `claude-3-5-sonnet-…` IDs), context-window lookup (including Fable 5 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x variants), pricing tables with optional LiteLLM hydration, null-aware cost (`calculateCost()`), provenance-preserving cost (`calculateCostWithProvenance()`, `mergeCostSources()`), and display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`) |
38
48
  | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback |
39
49
  | **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex quota updates behind one typed `{ claude?, codex? }` event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback |
40
50
  | **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, and `getActiveAccountStatus()` for a single-pass active-account read across providers |
51
+ | **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
52
+ | **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold |
41
53
  | **Codex Profiles** | Codex account lifecycle — prepare, finalize, switch, remove — switching atomically swaps the profile's backed-up credentials into the system `~/.codex/auth.json`, with rotated-token staleness protection, one-time dual-home migration, and legacy multi-home session monitoring |
42
54
  | **Quota Snapshots** | Persistent quota caching per provider/account for offline fallback |
43
55
  | **Phrases** | Curated humorous phrases for loading/idle states, available as a flat `ALL_PHRASES` array or grouped via `PHRASE_CATEGORIES` for category-aware UI |
@@ -184,6 +196,33 @@ const tools = extractToolCalls(event); // ToolCall[] — assistant
184
196
  const toolFromEvent = extractToolCall(event); // ToolCall | null — top-level `tool_use` events
185
197
  ```
186
198
 
199
+ ### Extract actionable assets from recent sessions
200
+
201
+ Use the Node-only root API to collect URLs, filesystem-validated paths, commands the agent suggested for the user to run, and plan-mode plans from recent Claude Code and Codex sessions for exactly one working directory.
202
+
203
+ This API powers `sidekick extract` and was contributed by [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie), adapted from his MIT-licensed [`trawl`](https://github.com/B33pBeeps/trawl) project.
204
+
205
+ ```typescript
206
+ import { gatherAssetsForCwd } from 'sidekick-shared';
207
+
208
+ const assets = gatherAssetsForCwd({
209
+ cwd: '/path/to/project',
210
+ agents: ['claude', 'codex'],
211
+ caps: { url: 20, path: 20, command: 20, plan: 10 },
212
+ });
213
+
214
+ console.log(assets.urls);
215
+ console.log(assets.paths);
216
+ console.log(assets.commands);
217
+ console.log(assets.plans);
218
+
219
+ // Each item keeps stable text/display fields plus optional provenance:
220
+ // { agent: 'claude' | 'codex', sessionPath, source }
221
+ console.log(assets.urls[0]?.agent);
222
+ ```
223
+
224
+ The extractor is safe for CLI and VS Code extension-host code, but not for browser or webview bundles because it reads session files and validates paths with Node filesystem APIs.
225
+
187
226
  ### Project session context evidence
188
227
 
189
228
  Build a provider-neutral snapshot of what an assistant has "seen" in a session — layered evidence sources, context pressure, and observed capabilities. Read it through any session provider, or build it directly from a canonical `SessionEvent[]`.
@@ -0,0 +1,52 @@
1
+ import { type AccountProviderId, type SavedAccountProfile } from './accountRegistry';
2
+ import { type AccountEntry, type AccountManagerResult } from './accounts';
3
+ export interface BeginAccountLoginSuccess {
4
+ success: true;
5
+ loginId: string;
6
+ alreadyComplete?: boolean;
7
+ command?: string;
8
+ args?: string[];
9
+ env?: Record<string, string>;
10
+ configDir?: string;
11
+ }
12
+ export interface BeginAccountLoginFailure {
13
+ success: false;
14
+ error: string;
15
+ }
16
+ export type BeginAccountLoginResult = BeginAccountLoginSuccess | BeginAccountLoginFailure;
17
+ export type AccountLoginState = 'pending' | 'authenticated' | 'failed';
18
+ export interface AccountLoginStatus {
19
+ state: AccountLoginState;
20
+ email?: string;
21
+ error?: string;
22
+ }
23
+ export interface FinalizeAccountLoginOptions {
24
+ activate?: boolean;
25
+ }
26
+ export interface AccountLoginCommand {
27
+ command: string;
28
+ args: string[];
29
+ }
30
+ export interface SpawnAccountLoginOptions extends FinalizeAccountLoginOptions {
31
+ onStatus?: (status: AccountLoginStatus) => void;
32
+ signal?: AbortSignal;
33
+ timeoutMs?: number;
34
+ stdio?: 'inherit' | 'pipe';
35
+ loginCommand?: AccountLoginCommand;
36
+ }
37
+ export interface ListAllAccountsResult {
38
+ claude: AccountEntry[];
39
+ codex: SavedAccountProfile[];
40
+ activeByProvider: Record<AccountProviderId, string | null>;
41
+ }
42
+ export declare function resolveClaudeLoginCommand(opts?: {
43
+ loginCommand?: AccountLoginCommand;
44
+ }): AccountLoginCommand;
45
+ export declare function beginAccountLogin(provider: AccountProviderId, label: string, opts?: {
46
+ loginCommand?: AccountLoginCommand;
47
+ }): BeginAccountLoginResult;
48
+ export declare function getAccountLoginStatus(provider: AccountProviderId, loginId: string): AccountLoginStatus;
49
+ export declare function finalizeAccountLogin(provider: AccountProviderId, loginId: string, opts?: FinalizeAccountLoginOptions): AccountManagerResult;
50
+ export declare function spawnAccountLogin(provider: AccountProviderId, label: string, opts?: SpawnAccountLoginOptions): Promise<AccountManagerResult>;
51
+ export declare function switchAccount(provider: AccountProviderId, id: string): AccountManagerResult;
52
+ export declare function listAllAccounts(): ListAllAccountsResult;
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveClaudeLoginCommand = resolveClaudeLoginCommand;
37
+ exports.beginAccountLogin = beginAccountLogin;
38
+ exports.getAccountLoginStatus = getAccountLoginStatus;
39
+ exports.finalizeAccountLogin = finalizeAccountLogin;
40
+ exports.spawnAccountLogin = spawnAccountLogin;
41
+ exports.switchAccount = switchAccount;
42
+ exports.listAllAccounts = listAllAccounts;
43
+ const child_process_1 = require("child_process");
44
+ const crypto_1 = require("crypto");
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const accountRegistry_1 = require("./accountRegistry");
48
+ const accounts_1 = require("./accounts");
49
+ const claudeProfiles_1 = require("./claudeProfiles");
50
+ const credentialIO_1 = require("./credentialIO");
51
+ const codexProfiles_1 = require("./codexProfiles");
52
+ function getClaudeProfileDir(loginId) {
53
+ return path.dirname((0, claudeProfiles_1.getClaudeProfileHome)(loginId));
54
+ }
55
+ function getPendingClaudeProfilePath(loginId) {
56
+ return path.join(getClaudeProfileDir(loginId), 'profile.json');
57
+ }
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 { /* nothing to clean up */ }
72
+ throw err;
73
+ }
74
+ }
75
+ function readPendingClaudeProfile(loginId) {
76
+ try {
77
+ return JSON.parse(fs.readFileSync(getPendingClaudeProfilePath(loginId), 'utf8'));
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ function writePendingClaudeProfile(loginId, pending) {
84
+ (0, claudeProfiles_1.ensureClaudeProfileDirs)(loginId);
85
+ atomicWriteJson(getPendingClaudeProfilePath(loginId), pending);
86
+ }
87
+ function removePendingClaudeProfile(loginId) {
88
+ try {
89
+ fs.rmSync(getPendingClaudeProfilePath(loginId), { force: true });
90
+ }
91
+ catch { /* best effort */ }
92
+ }
93
+ function getClaudeCredentialsBackupPath(uuid) {
94
+ return path.join((0, accountRegistry_1.getAccountsDir)(), 'credentials', `${uuid}.credentials.json`);
95
+ }
96
+ function getClaudeConfigBackupPath(uuid) {
97
+ return path.join((0, accountRegistry_1.getAccountsDir)(), 'configs', `${uuid}.config.json`);
98
+ }
99
+ function readClaudeOauthAccount(home) {
100
+ try {
101
+ const raw = JSON.parse(fs.readFileSync(path.join(home, '.claude.json'), 'utf8'));
102
+ return raw?.oauthAccount ?? null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ function copyClaudeProfileToCanonicalHome(sourceHome, accountUuid, credentials) {
109
+ const canonicalHome = (0, claudeProfiles_1.getClaudeProfileHome)(accountUuid);
110
+ fs.mkdirSync(canonicalHome, { recursive: true, mode: 0o700 });
111
+ if (path.resolve(sourceHome) !== path.resolve(canonicalHome)) {
112
+ try {
113
+ fs.cpSync(sourceHome, canonicalHome, { recursive: true, force: true });
114
+ }
115
+ catch {
116
+ // The credential and identity files are re-written below when possible.
117
+ }
118
+ }
119
+ const sourceConfig = path.join(sourceHome, '.claude.json');
120
+ if (fs.existsSync(sourceConfig)) {
121
+ fs.copyFileSync(sourceConfig, path.join(canonicalHome, '.claude.json'));
122
+ }
123
+ (0, credentialIO_1.writeActiveCredentials)(credentials, canonicalHome);
124
+ }
125
+ function parseClaudeLoginArgs(raw) {
126
+ const trimmed = raw?.trim();
127
+ return trimmed ? trimmed.split(/\s+/) : null;
128
+ }
129
+ function resolveClaudeLoginCommand(opts = {}) {
130
+ if (opts.loginCommand)
131
+ return opts.loginCommand;
132
+ return {
133
+ command: 'claude',
134
+ args: parseClaudeLoginArgs(process.env.SIDEKICK_CLAUDE_LOGIN_ARGS) ?? ['/login'],
135
+ };
136
+ }
137
+ function beginAccountLogin(provider, label, opts = {}) {
138
+ if (provider === 'codex') {
139
+ const prepared = (0, codexProfiles_1.prepareCodexAccount)(label);
140
+ if (!prepared.success || !prepared.profileId || !prepared.codexHome) {
141
+ return { success: false, error: prepared.error ?? 'Could not prepare Codex account login.' };
142
+ }
143
+ if (prepared.needsLogin === false) {
144
+ return {
145
+ success: true,
146
+ loginId: prepared.profileId,
147
+ alreadyComplete: true,
148
+ configDir: prepared.codexHome,
149
+ };
150
+ }
151
+ const loginCommand = opts.loginCommand ?? { command: 'codex', args: ['login'] };
152
+ return {
153
+ success: true,
154
+ loginId: prepared.profileId,
155
+ command: loginCommand.command,
156
+ args: loginCommand.args,
157
+ env: { CODEX_HOME: prepared.codexHome },
158
+ configDir: prepared.codexHome,
159
+ };
160
+ }
161
+ const trimmedLabel = label.trim();
162
+ if (!trimmedLabel) {
163
+ return { success: false, error: 'Claude accounts require a non-empty label.' };
164
+ }
165
+ const loginId = (0, crypto_1.randomUUID)();
166
+ const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
167
+ writePendingClaudeProfile(loginId, {
168
+ label: trimmedLabel,
169
+ addedAt: new Date().toISOString(),
170
+ });
171
+ const loginCommand = resolveClaudeLoginCommand(opts);
172
+ return {
173
+ success: true,
174
+ loginId,
175
+ command: loginCommand.command,
176
+ args: loginCommand.args,
177
+ env: { CLAUDE_CONFIG_DIR: home },
178
+ configDir: home,
179
+ };
180
+ }
181
+ function getAccountLoginStatus(provider, loginId) {
182
+ if (provider === 'codex') {
183
+ const codexHome = (0, codexProfiles_1.getCodexProfileHome)(loginId);
184
+ if (!(0, codexProfiles_1.isCodexProfileAuthenticated)(codexHome)) {
185
+ return { state: 'pending' };
186
+ }
187
+ const metadata = (0, codexProfiles_1.readCodexAccountMetadata)(codexHome);
188
+ return {
189
+ state: 'authenticated',
190
+ email: metadata.email,
191
+ };
192
+ }
193
+ const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
194
+ const identity = (0, claudeProfiles_1.readClaudeProfileIdentity)(home);
195
+ return (0, claudeProfiles_1.isClaudeProfileAuthenticated)(home)
196
+ ? { state: 'authenticated', email: identity?.email }
197
+ : { state: 'pending' };
198
+ }
199
+ function finalizeAccountLogin(provider, loginId, opts = {}) {
200
+ if (provider === 'codex') {
201
+ return (0, codexProfiles_1.finalizeCodexAccount)(loginId, opts);
202
+ }
203
+ const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
204
+ const identity = (0, claudeProfiles_1.readClaudeProfileIdentity)(home);
205
+ if (!identity) {
206
+ return { success: false, error: 'Claude profile is not authenticated yet.' };
207
+ }
208
+ const credentials = (0, credentialIO_1.readActiveCredentials)(home);
209
+ if (!credentials) {
210
+ return { success: false, error: 'Could not read Claude profile credentials.' };
211
+ }
212
+ const oauthAccount = readClaudeOauthAccount(home);
213
+ if (!oauthAccount) {
214
+ return { success: false, error: 'Could not read Claude profile config.' };
215
+ }
216
+ const pending = readPendingClaudeProfile(loginId);
217
+ atomicWriteJson(getClaudeCredentialsBackupPath(identity.uuid), credentials);
218
+ atomicWriteJson(getClaudeConfigBackupPath(identity.uuid), oauthAccount);
219
+ copyClaudeProfileToCanonicalHome(home, identity.uuid, credentials);
220
+ (0, accountRegistry_1.upsertSavedAccountProfile)({
221
+ id: identity.uuid,
222
+ providerId: 'claude-code',
223
+ providerAccountId: identity.uuid,
224
+ email: identity.email,
225
+ label: pending?.label,
226
+ addedAt: pending?.addedAt ?? new Date().toISOString(),
227
+ metadata: {
228
+ email: identity.email,
229
+ },
230
+ });
231
+ removePendingClaudeProfile(loginId);
232
+ if (loginId !== identity.uuid) {
233
+ removePendingClaudeProfile(identity.uuid);
234
+ }
235
+ if (opts.activate === false) {
236
+ return { success: true };
237
+ }
238
+ return (0, accounts_1.switchToAccount)(identity.uuid);
239
+ }
240
+ function emitStatus(opts, status) {
241
+ opts.onStatus?.(status);
242
+ return status;
243
+ }
244
+ function waitForNextPoll(ms, signal) {
245
+ if (ms <= 0)
246
+ return Promise.resolve(true);
247
+ if (signal?.aborted)
248
+ return Promise.resolve(false);
249
+ return new Promise(resolve => {
250
+ const timer = setTimeout(() => {
251
+ cleanup();
252
+ resolve(true);
253
+ }, ms);
254
+ const onAbort = () => {
255
+ cleanup();
256
+ resolve(false);
257
+ };
258
+ const cleanup = () => {
259
+ clearTimeout(timer);
260
+ signal?.removeEventListener('abort', onAbort);
261
+ };
262
+ signal?.addEventListener('abort', onAbort, { once: true });
263
+ });
264
+ }
265
+ async function spawnAccountLogin(provider, label, opts = {}) {
266
+ if (opts.signal?.aborted) {
267
+ return { success: false, error: 'Account login aborted.' };
268
+ }
269
+ const begin = beginAccountLogin(provider, label, { loginCommand: opts.loginCommand });
270
+ if (!begin.success)
271
+ return { success: false, error: begin.error };
272
+ if (begin.alreadyComplete) {
273
+ return finalizeAccountLogin(provider, begin.loginId, { activate: opts.activate ?? true });
274
+ }
275
+ if (!begin.command) {
276
+ return { success: false, error: 'Account login command was not prepared.' };
277
+ }
278
+ let childExited = false;
279
+ let childExitCode = null;
280
+ const timeoutMs = opts.timeoutMs ?? 180_000;
281
+ const deadline = Date.now() + timeoutMs;
282
+ let child;
283
+ try {
284
+ child = (0, child_process_1.spawn)(begin.command, begin.args ?? [], {
285
+ env: { ...process.env, ...(begin.env ?? {}) },
286
+ stdio: opts.stdio ?? 'inherit',
287
+ });
288
+ }
289
+ catch (err) {
290
+ return { success: false, error: `Could not spawn account login: ${err}` };
291
+ }
292
+ child.on('exit', code => {
293
+ childExited = true;
294
+ childExitCode = code;
295
+ });
296
+ while (true) {
297
+ if (opts.signal?.aborted) {
298
+ child.kill();
299
+ emitStatus(opts, { state: 'failed', error: 'Account login aborted.' });
300
+ return { success: false, error: 'Account login aborted.' };
301
+ }
302
+ const status = emitStatus(opts, getAccountLoginStatus(provider, begin.loginId));
303
+ if (status.state === 'authenticated') {
304
+ return finalizeAccountLogin(provider, begin.loginId, { activate: opts.activate ?? true });
305
+ }
306
+ if (childExited) {
307
+ const finalStatus = emitStatus(opts, getAccountLoginStatus(provider, begin.loginId));
308
+ if (finalStatus.state === 'authenticated') {
309
+ return finalizeAccountLogin(provider, begin.loginId, { activate: opts.activate ?? true });
310
+ }
311
+ emitStatus(opts, {
312
+ state: 'failed',
313
+ error: `Account login exited before authentication completed${childExitCode === null ? '.' : ` (exit ${childExitCode}).`}`,
314
+ });
315
+ return { success: false, error: 'Account login did not complete.' };
316
+ }
317
+ const remainingMs = deadline - Date.now();
318
+ if (remainingMs <= 0) {
319
+ emitStatus(opts, { state: 'failed', error: 'Account login timed out.' });
320
+ return { success: false, error: 'Account login timed out.' };
321
+ }
322
+ const shouldContinue = await waitForNextPoll(Math.min(2_000, remainingMs), opts.signal);
323
+ if (!shouldContinue) {
324
+ child.kill();
325
+ emitStatus(opts, { state: 'failed', error: 'Account login aborted.' });
326
+ return { success: false, error: 'Account login aborted.' };
327
+ }
328
+ }
329
+ }
330
+ function switchAccount(provider, id) {
331
+ return provider === 'codex'
332
+ ? (0, codexProfiles_1.switchToCodexAccount)(id)
333
+ : (0, accounts_1.switchToAccount)(id);
334
+ }
335
+ function listAllAccounts() {
336
+ return {
337
+ claude: (0, accounts_1.listAccounts)(),
338
+ codex: (0, codexProfiles_1.listCodexAccounts)(),
339
+ activeByProvider: (0, accountRegistry_1.readSavedAccountRegistry)()?.activeByProvider ?? {
340
+ 'claude-code': null,
341
+ codex: null,
342
+ },
343
+ };
344
+ }
@@ -32,6 +32,9 @@ export declare function writeAccountRegistry(registry: AccountRegistry): void;
32
32
  export declare function readActiveClaudeAccount(): ActiveAccountInfo | null;
33
33
  export declare function addCurrentAccount(label?: string): AccountManagerResult;
34
34
  export declare function switchToAccount(uuid: string): AccountManagerResult;
35
+ export declare function resolveActiveClaudeHome(): string;
36
+ export declare function applyActiveClaudeToLiveHome(): AccountManagerResult;
37
+ export declare function reconcileClaudeAuthState(): void;
35
38
  export declare function removeAccount(uuid: string): AccountManagerResult;
36
39
  export declare function listAccounts(): AccountEntry[];
37
40
  export declare function getActiveAccount(): AccountEntry | null;