sidekick-shared 0.20.0 → 0.21.1
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 +14 -2
- package/dist/accountManager.d.ts +52 -0
- package/dist/accountManager.js +344 -0
- package/dist/accounts.d.ts +3 -0
- package/dist/accounts.js +180 -40
- package/dist/autoSwitch.d.ts +70 -0
- package/dist/autoSwitch.js +160 -0
- package/dist/claudeProfiles.d.ts +12 -0
- package/dist/claudeProfiles.js +96 -0
- package/dist/codexProfiles.d.ts +6 -2
- package/dist/codexProfiles.js +6 -1
- package/dist/credentialIO.d.ts +3 -2
- package/dist/credentialIO.js +11 -9
- package/dist/ensureDefaultAccounts.js +6 -0
- package/dist/index.d.ts +16 -3
- package/dist/index.js +60 -5
- package/dist/multiProviderQuotaService.d.ts +10 -0
- package/dist/multiProviderQuotaService.js +34 -0
- package/dist/providerQuota.d.ts +2 -1
- package/dist/providers/detect.js +3 -13
- package/dist/providers/openCode.d.ts +31 -0
- package/dist/providers/openCode.js +108 -2
- package/dist/providers/openCodeDatabase.d.ts +19 -0
- package/dist/providers/openCodeDatabase.js +28 -0
- package/dist/quota.d.ts +1 -1
- package/dist/quotaHistory.d.ts +1 -1
- package/dist/quotaHistory.js +14 -2
- package/dist/quotaSnapshots.d.ts +8 -2
- package/dist/schemas/accountManager.d.ts +104 -0
- package/dist/schemas/accountManager.js +84 -0
- package/dist/schemas/assistantTurn.d.ts +5 -5
- package/dist/schemas/index.d.ts +5 -1
- package/dist/schemas/index.js +10 -1
- package/dist/schemas/quota.d.ts +185 -20
- package/dist/schemas/quota.js +8 -1
- package/dist/schemas/quotaHistory.d.ts +3 -1
- package/dist/schemas/quotaHistory.js +1 -0
- package/dist/terminalSync.d.ts +7 -0
- package/dist/terminalSync.js +202 -0
- package/dist/zaiQuota.d.ts +209 -0
- package/dist/zaiQuota.js +290 -0
- package/dist/zaiQuotaWatcher.d.ts +97 -0
- package/dist/zaiQuotaWatcher.js +259 -0
- package/package.json +1 -1
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
|
|
@@ -30,14 +40,16 @@ npm install sidekick-shared
|
|
|
30
40
|
| **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude `Task` subagent refs without prompt leakage |
|
|
31
41
|
| **Report** | Self-contained HTML session report generation |
|
|
32
42
|
| **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
|
|
33
|
-
| **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows)
|
|
43
|
+
| **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams, and **estimated** z.ai Coding Plan quota derivation from observed OpenCode traffic (`accumulateZaiUsage()`, `inferZaiQuotaState()`, `parseZaiQuotaError()`, `resolveZaiTier()`, `ZAI_TIER_BUDGETS`) — z.ai exposes no usage API, so this is a best-effort estimate, not authoritative |
|
|
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
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
|
-
| **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex quota updates behind one typed
|
|
49
|
+
| **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; `ZaiQuotaWatcher` derives an estimated z.ai Coding Plan quota from observed OpenCode turns (event-driven, snapshot + history) |
|
|
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 |
|
|
@@ -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
|
+
}
|
package/dist/accounts.d.ts
CHANGED
|
@@ -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;
|