sidekick-shared 0.24.4 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +200 -52
- package/dist/accountChangeSignal.d.ts +3 -0
- package/dist/accountChangeSignal.js +19 -0
- package/dist/accountChanges.d.ts +21 -0
- package/dist/accountChanges.js +151 -0
- package/dist/accountManager.d.ts +13 -0
- package/dist/accountManager.js +60 -32
- package/dist/accountRegistry.d.ts +3 -1
- package/dist/accountRegistry.js +64 -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 +10 -3
- package/dist/browser.js +17 -1
- package/dist/codexProfiles.d.ts +14 -0
- package/dist/codexProfiles.js +268 -116
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/context/sessionContext.js +2 -4
- package/dist/ensureDefaultAccounts.js +4 -4
- package/dist/index.d.ts +32 -15
- package/dist/index.js +54 -9
- package/dist/modelAliases.d.ts +13 -0
- package/dist/modelAliases.js +58 -0
- package/dist/modelCatalog.d.ts +32 -0
- package/dist/modelCatalog.js +145 -0
- package/dist/modelContext.d.ts +36 -3
- package/dist/modelContext.js +109 -34
- package/dist/modelInfo.d.ts +30 -1
- package/dist/modelInfo.js +116 -26
- package/dist/multiProviderQuotaService.d.ts +8 -0
- package/dist/multiProviderQuotaService.js +64 -3
- package/dist/node.d.ts +8 -2
- package/dist/node.js +11 -1
- package/dist/observedContextWindows.d.ts +13 -0
- package/dist/observedContextWindows.js +13 -0
- package/dist/observedSessionCollector.d.ts +75 -4
- package/dist/observedSessionCollector.js +452 -42
- package/dist/parsers/codexHistory.d.ts +32 -0
- package/dist/parsers/codexHistory.js +125 -0
- package/dist/parsers/mcpToolName.d.ts +26 -0
- package/dist/parsers/mcpToolName.js +31 -0
- package/dist/parsers/sessionActivityDetector.d.ts +6 -0
- package/dist/parsers/sessionActivityDetector.js +17 -0
- package/dist/parsers/sessionPathResolver.d.ts +11 -4
- package/dist/parsers/sessionPathResolver.js +11 -4
- package/dist/paths.d.ts +7 -3
- package/dist/paths.js +7 -3
- package/dist/providerIds.d.ts +11 -0
- package/dist/providerIds.js +9 -0
- package/dist/providerQuota.d.ts +3 -1
- package/dist/providerQuota.js +3 -0
- package/dist/providers/claudeCode.d.ts +11 -1
- package/dist/providers/claudeCode.js +222 -39
- package/dist/providers/codex.d.ts +39 -1
- package/dist/providers/codex.js +326 -39
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +14 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +20 -1
- package/dist/providers/openCode.js +361 -11
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/types.d.ts +57 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +4 -1
- package/dist/quota.js +3 -7
- package/dist/quotaHistory.d.ts +3 -2
- package/dist/quotaHistory.js +2 -2
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaSnapshots.js +8 -77
- package/dist/schemas/accountManager.d.ts +2 -2
- package/dist/schemas/accountManager.js +2 -4
- package/dist/schemas/accountStatus.d.ts +3 -0
- package/dist/schemas/accountStatus.js +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +2 -2
- package/dist/schemas/quota.js +3 -6
- package/dist/schemas/quotaHistory.d.ts +2 -2
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +85 -0
- package/dist/sessionPreviews.js +549 -0
- package/dist/sessionTranscripts.js +10 -4
- package/dist/types/observedSessionV1.d.ts +6 -2
- package/dist/types/observedSessionV1.js +14 -8
- package/dist/writers/atomic.d.ts +20 -0
- package/dist/writers/atomic.js +190 -19
- package/package.json +22 -1
|
@@ -0,0 +1,151 @@
|
|
|
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.onAccountsChanged = onAccountsChanged;
|
|
37
|
+
exports._notifyAccountsChanged = _notifyAccountsChanged;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const accountChangeSignal_1 = require("./accountChangeSignal");
|
|
42
|
+
const accountStatus_1 = require("./accountStatus");
|
|
43
|
+
const accountRegistry_1 = require("./accountRegistry");
|
|
44
|
+
const codexProfiles_1 = require("./codexProfiles");
|
|
45
|
+
const listeners = new Set();
|
|
46
|
+
let watchers = [];
|
|
47
|
+
let pollTimer;
|
|
48
|
+
let stopRawListener;
|
|
49
|
+
let lastFingerprint;
|
|
50
|
+
let debounceTimer;
|
|
51
|
+
let activePollIntervalMs = 30_000;
|
|
52
|
+
/**
|
|
53
|
+
* Observe login, logout, and active-account switches. Local Sidekick mutations
|
|
54
|
+
* signal immediately; filesystem watchers cover external CLIs; a low-frequency
|
|
55
|
+
* catch-up poll protects network mounts and watcher-hostile filesystems.
|
|
56
|
+
*/
|
|
57
|
+
function onAccountsChanged(listener, options = {}) {
|
|
58
|
+
listeners.add(listener);
|
|
59
|
+
activePollIntervalMs = Math.max(1_000, options.pollIntervalMs ?? activePollIntervalMs);
|
|
60
|
+
ensureMonitoring();
|
|
61
|
+
if (options.emitCurrent)
|
|
62
|
+
emitIfChanged('poll', true, listener);
|
|
63
|
+
return {
|
|
64
|
+
dispose: () => {
|
|
65
|
+
listeners.delete(listener);
|
|
66
|
+
if (listeners.size === 0)
|
|
67
|
+
stopMonitoring();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function ensureMonitoring() {
|
|
72
|
+
if (stopRawListener)
|
|
73
|
+
return;
|
|
74
|
+
lastFingerprint = fingerprint((0, accountStatus_1.getActiveAccountStatus)());
|
|
75
|
+
stopRawListener = (0, accountChangeSignal_1._onRawAccountsChanged)((reason) => scheduleCheck(reason));
|
|
76
|
+
for (const target of watchTargets()) {
|
|
77
|
+
try {
|
|
78
|
+
const watcher = fs.watch(target, { persistent: false }, () => {
|
|
79
|
+
scheduleCheck('filesystem');
|
|
80
|
+
});
|
|
81
|
+
watcher.on('error', () => {
|
|
82
|
+
watchers = watchers.filter((candidate) => candidate !== watcher);
|
|
83
|
+
try {
|
|
84
|
+
watcher.close();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// The catch-up poll remains active.
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
watchers.push(watcher);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Missing/unwatchable directories are covered by the catch-up poll.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
pollTimer = setInterval(() => emitIfChanged('poll'), activePollIntervalMs);
|
|
97
|
+
pollTimer.unref?.();
|
|
98
|
+
}
|
|
99
|
+
function stopMonitoring() {
|
|
100
|
+
stopRawListener?.();
|
|
101
|
+
stopRawListener = undefined;
|
|
102
|
+
for (const watcher of watchers)
|
|
103
|
+
watcher.close();
|
|
104
|
+
watchers = [];
|
|
105
|
+
if (pollTimer)
|
|
106
|
+
clearInterval(pollTimer);
|
|
107
|
+
pollTimer = undefined;
|
|
108
|
+
if (debounceTimer)
|
|
109
|
+
clearTimeout(debounceTimer);
|
|
110
|
+
debounceTimer = undefined;
|
|
111
|
+
lastFingerprint = undefined;
|
|
112
|
+
}
|
|
113
|
+
function watchTargets() {
|
|
114
|
+
return Array.from(new Set([(0, accountRegistry_1.getAccountsDir)(), path.join(os.homedir(), '.claude'), (0, codexProfiles_1.getSystemCodexHome)()]));
|
|
115
|
+
}
|
|
116
|
+
function scheduleCheck(reason) {
|
|
117
|
+
if (debounceTimer)
|
|
118
|
+
clearTimeout(debounceTimer);
|
|
119
|
+
debounceTimer = setTimeout(() => {
|
|
120
|
+
debounceTimer = undefined;
|
|
121
|
+
// Never force: watch targets include ~/.claude and the Codex home, whose
|
|
122
|
+
// history files churn on every prompt. Only a real status change may wake
|
|
123
|
+
// subscribers, or dormant quota pollers would poll on unrelated writes.
|
|
124
|
+
emitIfChanged(reason);
|
|
125
|
+
}, 25);
|
|
126
|
+
debounceTimer.unref?.();
|
|
127
|
+
}
|
|
128
|
+
function emitIfChanged(reason, force = false, singleListener) {
|
|
129
|
+
const status = (0, accountStatus_1.getActiveAccountStatus)();
|
|
130
|
+
const nextFingerprint = fingerprint(status);
|
|
131
|
+
if (!force && nextFingerprint === lastFingerprint)
|
|
132
|
+
return;
|
|
133
|
+
lastFingerprint = nextFingerprint;
|
|
134
|
+
const event = { reason, status };
|
|
135
|
+
const targets = singleListener ? [singleListener] : [...listeners];
|
|
136
|
+
for (const listener of targets) {
|
|
137
|
+
try {
|
|
138
|
+
listener(event);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// A host callback cannot stop monitoring.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function fingerprint(status) {
|
|
146
|
+
return JSON.stringify(status);
|
|
147
|
+
}
|
|
148
|
+
/** Internal bridge for account mutations outside the registry module. */
|
|
149
|
+
function _notifyAccountsChanged() {
|
|
150
|
+
(0, accountChangeSignal_1._emitAccountsChanged)('local');
|
|
151
|
+
}
|
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)(),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AccountProviderId } from './providerIds';
|
|
2
|
+
export { ACCOUNT_PROVIDER_IDS } from './providerIds';
|
|
3
|
+
export type { AccountProviderId } from './providerIds';
|
|
2
4
|
export interface AccountIdentityMetadata {
|
|
3
5
|
email?: string;
|
|
4
6
|
workspaceId?: string;
|
package/dist/accountRegistry.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ACCOUNT_PROVIDER_IDS = void 0;
|
|
36
37
|
exports.getAccountsDir = getAccountsDir;
|
|
37
38
|
exports.readSavedAccountRegistry = readSavedAccountRegistry;
|
|
38
39
|
exports.writeSavedAccountRegistry = writeSavedAccountRegistry;
|
|
@@ -45,21 +46,40 @@ exports.removeSavedAccountProfile = removeSavedAccountProfile;
|
|
|
45
46
|
const fs = __importStar(require("fs"));
|
|
46
47
|
const path = __importStar(require("path"));
|
|
47
48
|
const paths_1 = require("./paths");
|
|
49
|
+
const atomic_1 = require("./writers/atomic");
|
|
50
|
+
const accountChangeSignal_1 = require("./accountChangeSignal");
|
|
51
|
+
var providerIds_1 = require("./providerIds");
|
|
52
|
+
Object.defineProperty(exports, "ACCOUNT_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.ACCOUNT_PROVIDER_IDS; } });
|
|
48
53
|
function getAccountsDir() {
|
|
49
54
|
return path.join((0, paths_1.getConfigDir)(), 'accounts');
|
|
50
55
|
}
|
|
51
56
|
function getRegistryPath() {
|
|
52
57
|
return path.join(getAccountsDir(), 'accounts.json');
|
|
53
58
|
}
|
|
59
|
+
function getRegistryLockPath() {
|
|
60
|
+
return getRegistryPath() + '.lock';
|
|
61
|
+
}
|
|
54
62
|
function ensureAccountsDir() {
|
|
55
63
|
fs.mkdirSync(getAccountsDir(), { recursive: true, mode: 0o700 });
|
|
56
64
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
function writeRegistryUnlocked(registry) {
|
|
66
|
+
(0, atomic_1.atomicWriteJsonSync)(getRegistryPath(), normalizeRegistry(registry));
|
|
67
|
+
(0, accountChangeSignal_1._emitAccountsChanged)('local');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Serializes registry read-modify-write cycles across processes. Without the
|
|
71
|
+
* lock, two concurrent mutators (CLI, editor extension, external consumers)
|
|
72
|
+
* read the same base state and the second write silently drops the first
|
|
73
|
+
* mutation. Lock ordering: callers that also hold a provider auth-swap lock
|
|
74
|
+
* take that lock first and this one inside it, never the reverse.
|
|
75
|
+
*/
|
|
76
|
+
function mutateSavedAccountRegistry(mutator) {
|
|
77
|
+
ensureAccountsDir();
|
|
78
|
+
return (0, atomic_1.withFileLockSync)(getRegistryLockPath(), () => {
|
|
79
|
+
const next = mutator(readSavedAccountRegistry() ?? createEmptyRegistry());
|
|
80
|
+
writeRegistryUnlocked(next);
|
|
81
|
+
return next;
|
|
82
|
+
});
|
|
63
83
|
}
|
|
64
84
|
function createEmptyRegistry() {
|
|
65
85
|
return {
|
|
@@ -136,7 +156,7 @@ function readSavedAccountRegistry() {
|
|
|
136
156
|
}
|
|
137
157
|
function writeSavedAccountRegistry(registry) {
|
|
138
158
|
ensureAccountsDir();
|
|
139
|
-
|
|
159
|
+
(0, atomic_1.withFileLockSync)(getRegistryLockPath(), () => writeRegistryUnlocked(registry));
|
|
140
160
|
}
|
|
141
161
|
function listSavedAccountProfiles(providerId) {
|
|
142
162
|
const registry = readSavedAccountRegistry();
|
|
@@ -156,44 +176,47 @@ function getActiveSavedAccount(providerId) {
|
|
|
156
176
|
return (registry.accounts.find((account) => account.providerId === providerId && account.id === activeId) ?? null);
|
|
157
177
|
}
|
|
158
178
|
function upsertSavedAccountProfile(profile) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
return mutateSavedAccountRegistry((registry) => {
|
|
180
|
+
const index = registry.accounts.findIndex((account) => account.providerId === profile.providerId && account.id === profile.id);
|
|
181
|
+
if (index >= 0) {
|
|
182
|
+
registry.accounts[index] = profile;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
registry.accounts.push(profile);
|
|
186
|
+
}
|
|
187
|
+
return registry;
|
|
188
|
+
});
|
|
169
189
|
}
|
|
170
190
|
function setActiveSavedAccount(providerId, accountId) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
return mutateSavedAccountRegistry((registry) => {
|
|
192
|
+
registry.activeByProvider[providerId] = accountId;
|
|
193
|
+
return registry;
|
|
194
|
+
});
|
|
175
195
|
}
|
|
176
196
|
function replaceSavedAccountProfiles(providerId, accounts, activeId) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
return mutateSavedAccountRegistry((registry) => {
|
|
198
|
+
registry.accounts = registry.accounts
|
|
199
|
+
.filter((account) => account.providerId !== providerId)
|
|
200
|
+
.concat(accounts);
|
|
201
|
+
registry.activeByProvider[providerId] = activeId;
|
|
202
|
+
return registry;
|
|
203
|
+
});
|
|
184
204
|
}
|
|
185
205
|
function removeSavedAccountProfile(providerId, accountId) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
ensureAccountsDir();
|
|
207
|
+
return (0, atomic_1.withFileLockSync)(getRegistryLockPath(), () => {
|
|
208
|
+
const registry = readSavedAccountRegistry();
|
|
209
|
+
if (!registry)
|
|
210
|
+
return null;
|
|
211
|
+
const index = registry.accounts.findIndex((account) => account.providerId === providerId && account.id === accountId);
|
|
212
|
+
if (index === -1)
|
|
213
|
+
return null;
|
|
214
|
+
const [removed] = registry.accounts.splice(index, 1);
|
|
215
|
+
if (registry.activeByProvider[providerId] === accountId) {
|
|
216
|
+
registry.activeByProvider[providerId] =
|
|
217
|
+
registry.accounts.find((account) => account.providerId === providerId)?.id ?? null;
|
|
218
|
+
}
|
|
219
|
+
writeRegistryUnlocked(registry);
|
|
220
|
+
return removed;
|
|
221
|
+
});
|
|
199
222
|
}
|
package/dist/accounts.js
CHANGED
|
@@ -56,6 +56,7 @@ const fs = __importStar(require("fs"));
|
|
|
56
56
|
const path = __importStar(require("path"));
|
|
57
57
|
const os = __importStar(require("os"));
|
|
58
58
|
const credentialIO_1 = require("./credentialIO");
|
|
59
|
+
const atomic_1 = require("./writers/atomic");
|
|
59
60
|
const accountRegistry_1 = require("./accountRegistry");
|
|
60
61
|
const claudeProfiles_1 = require("./claudeProfiles");
|
|
61
62
|
// ── Paths ────────────────────────────────────────────────────────────────
|
|
@@ -91,16 +92,6 @@ function ensureDirs() {
|
|
|
91
92
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
// ── Atomic file write ────────────────────────────────────────────────────
|
|
95
|
-
function atomicWriteJson(filePath, data, mode = 0o600) {
|
|
96
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
97
|
-
const tmp = filePath + '.tmp';
|
|
98
|
-
const json = JSON.stringify(data, null, 2);
|
|
99
|
-
// Validate we can re-parse before writing
|
|
100
|
-
JSON.parse(json);
|
|
101
|
-
fs.writeFileSync(tmp, json, { encoding: 'utf8', mode });
|
|
102
|
-
fs.renameSync(tmp, filePath);
|
|
103
|
-
}
|
|
104
95
|
function readJsonOrNull(filePath) {
|
|
105
96
|
try {
|
|
106
97
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -114,7 +105,7 @@ function readOauthAccountFromConfigPath(filePath) {
|
|
|
114
105
|
return parsed?.oauthAccount ?? null;
|
|
115
106
|
}
|
|
116
107
|
function writeClaudeProfileConfig(uuid, oauthAccount) {
|
|
117
|
-
|
|
108
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join((0, claudeProfiles_1.getClaudeProfileHome)(uuid), '.claude.json'), { oauthAccount });
|
|
118
109
|
}
|
|
119
110
|
function writeClaudeProfileCredentials(uuid, credentials) {
|
|
120
111
|
(0, credentialIO_1.writeActiveCredentials)(credentials, (0, claudeProfiles_1.getClaudeProfileHome)(uuid));
|
|
@@ -162,7 +153,7 @@ function backupCurrentClaudeLiveHome() {
|
|
|
162
153
|
const originalOauthAccount = readCurrentLiveOauthAccount();
|
|
163
154
|
if (originalCreds) {
|
|
164
155
|
try {
|
|
165
|
-
|
|
156
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${currentActive.uuid}.credentials.json`), originalCreds);
|
|
166
157
|
writeClaudeProfileCredentials(currentActive.uuid, originalCreds);
|
|
167
158
|
}
|
|
168
159
|
catch {
|
|
@@ -171,7 +162,7 @@ function backupCurrentClaudeLiveHome() {
|
|
|
171
162
|
}
|
|
172
163
|
if (originalOauthAccount) {
|
|
173
164
|
try {
|
|
174
|
-
|
|
165
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${currentActive.uuid}.config.json`), originalOauthAccount);
|
|
175
166
|
writeClaudeProfileConfig(currentActive.uuid, originalOauthAccount);
|
|
176
167
|
}
|
|
177
168
|
catch {
|
|
@@ -188,7 +179,7 @@ function mergeOauthAccountIntoLiveConfig(oauthAccount) {
|
|
|
188
179
|
// File may not exist yet.
|
|
189
180
|
}
|
|
190
181
|
configObj.oauthAccount = oauthAccount;
|
|
191
|
-
|
|
182
|
+
(0, atomic_1.atomicWriteJsonSync)(getClaudeConfigPath(), configObj);
|
|
192
183
|
}
|
|
193
184
|
// ── Registry operations ──────────────────────────────────────────────────
|
|
194
185
|
function readAccountRegistry() {
|
|
@@ -274,8 +265,8 @@ function addCurrentAccount(label) {
|
|
|
274
265
|
if (existing) {
|
|
275
266
|
if (label !== undefined)
|
|
276
267
|
existing.label = label;
|
|
277
|
-
|
|
278
|
-
|
|
268
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${active.uuid}.credentials.json`), credBlob);
|
|
269
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${active.uuid}.config.json`), configBlob);
|
|
279
270
|
try {
|
|
280
271
|
writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
|
|
281
272
|
}
|
|
@@ -287,8 +278,8 @@ function addCurrentAccount(label) {
|
|
|
287
278
|
return { success: true };
|
|
288
279
|
}
|
|
289
280
|
// 6. New account — back up credentials
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${active.uuid}.credentials.json`), credBlob);
|
|
282
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${active.uuid}.config.json`), configBlob);
|
|
292
283
|
try {
|
|
293
284
|
writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
|
|
294
285
|
}
|
|
@@ -307,7 +298,28 @@ function addCurrentAccount(label) {
|
|
|
307
298
|
return { success: true };
|
|
308
299
|
}
|
|
309
300
|
// ── Switch to account ────────────────────────────────────────────────────
|
|
301
|
+
/**
|
|
302
|
+
* Serializes live Claude credential swaps across processes so two concurrent
|
|
303
|
+
* switchers cannot interleave their backup/write/rollback sequences. It cannot
|
|
304
|
+
* exclude Claude Code's own writes to the live home — only our switchers. Lock
|
|
305
|
+
* ordering: this lock first, the registry lock (inside setActiveSavedAccount)
|
|
306
|
+
* inside it — never the reverse.
|
|
307
|
+
*/
|
|
308
|
+
function withClaudeAuthSwapLock(operation) {
|
|
309
|
+
const lockDir = path.join((0, accountRegistry_1.getAccountsDir)(), 'claude');
|
|
310
|
+
fs.mkdirSync(lockDir, { recursive: true, mode: 0o700 });
|
|
311
|
+
return (0, atomic_1.withFileLockSync)(path.join(lockDir, 'auth-swap.lock'), operation);
|
|
312
|
+
}
|
|
310
313
|
function switchToAccount(uuid) {
|
|
314
|
+
try {
|
|
315
|
+
// Keep the non-throwing result contract when the lock cannot be acquired.
|
|
316
|
+
return withClaudeAuthSwapLock(() => switchToAccountUnlocked(uuid));
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
return { success: false, error: `Could not acquire the account-switch lock: ${err}` };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function switchToAccountUnlocked(uuid) {
|
|
311
323
|
const registry = readAccountRegistry();
|
|
312
324
|
if (!registry) {
|
|
313
325
|
return { success: false, error: 'No account registry found. Save an account first.' };
|
|
@@ -338,7 +350,7 @@ function switchToAccount(uuid) {
|
|
|
338
350
|
catch (err) {
|
|
339
351
|
return { success: false, error: `Failed to update account registry: ${err}` };
|
|
340
352
|
}
|
|
341
|
-
const applied =
|
|
353
|
+
const applied = applyActiveClaudeToLiveHomeUnlocked();
|
|
342
354
|
if (!applied.success) {
|
|
343
355
|
try {
|
|
344
356
|
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', registry.activeAccountUuid);
|
|
@@ -373,6 +385,15 @@ function resolveActiveClaudeHome() {
|
|
|
373
385
|
return (0, claudeProfiles_1.getClaudeProfileHome)(active.providerAccountId ?? active.id);
|
|
374
386
|
}
|
|
375
387
|
function applyActiveClaudeToLiveHome() {
|
|
388
|
+
try {
|
|
389
|
+
// Keep the non-throwing result contract when the lock cannot be acquired.
|
|
390
|
+
return withClaudeAuthSwapLock(applyActiveClaudeToLiveHomeUnlocked);
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
return { success: false, error: `Could not acquire the account-switch lock: ${err}` };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function applyActiveClaudeToLiveHomeUnlocked() {
|
|
376
397
|
const active = (0, accountRegistry_1.getActiveSavedAccount)('claude-code');
|
|
377
398
|
if (!active) {
|
|
378
399
|
return { success: false, error: 'No active Claude account found.' };
|
package/dist/autoSwitch.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface AutoSwitchControllerOptions {
|
|
|
36
36
|
getCodexAccounts?: () => SavedAccountProfile[];
|
|
37
37
|
getActiveCodexAccount?: () => SavedAccountProfile | null;
|
|
38
38
|
readSnapshot?: (providerId: AccountProviderId, accountId: string) => QuotaState | null;
|
|
39
|
-
switchAccount?: (providerId: AccountProviderId, accountId: string) => AccountManagerResult
|
|
39
|
+
switchAccount?: (providerId: AccountProviderId, accountId: string) => AccountManagerResult | Promise<AccountManagerResult>;
|
|
40
40
|
onTransition?: (event: AutoSwitchTransitionEvent) => void;
|
|
41
41
|
log?: (message: string, error?: unknown) => void;
|
|
42
42
|
cooldownMs?: number;
|
|
@@ -59,6 +59,7 @@ export declare class AutoSwitchController implements Disposable {
|
|
|
59
59
|
private subscription;
|
|
60
60
|
private readonly switchedDuringCrossing;
|
|
61
61
|
private readonly lastSwitchAt;
|
|
62
|
+
private readonly switchInFlight;
|
|
62
63
|
constructor(options: AutoSwitchControllerOptions);
|
|
63
64
|
start(): void;
|
|
64
65
|
stop(): void;
|