sidekick-shared 0.26.5 → 0.26.6
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 +35 -0
- package/README.md +56 -51
- package/dist/accountChanges.js +21 -2
- package/dist/accountHealth.d.ts +72 -0
- package/dist/accountHealth.js +286 -0
- package/dist/accountLaunch.d.ts +30 -0
- package/dist/accountLaunch.js +124 -0
- package/dist/accountManager.d.ts +33 -8
- package/dist/accountManager.js +183 -50
- package/dist/accountRegistry.d.ts +10 -0
- package/dist/accountRegistry.js +42 -22
- package/dist/accountSwitch.d.ts +43 -0
- package/dist/accountSwitch.js +115 -0
- package/dist/accountSync.d.ts +30 -0
- package/dist/accountSync.js +180 -0
- package/dist/accountSyncTypes.d.ts +25 -0
- package/dist/accountSyncTypes.js +7 -0
- package/dist/accounts.d.ts +58 -6
- package/dist/accounts.js +408 -189
- package/dist/claudeCredentials.d.ts +38 -0
- package/dist/claudeCredentials.js +74 -0
- package/dist/claudeProfiles.d.ts +40 -0
- package/dist/claudeProfiles.js +102 -10
- package/dist/codexAuth.d.ts +32 -0
- package/dist/codexAuth.js +127 -0
- package/dist/codexPaths.d.ts +21 -0
- package/dist/codexPaths.js +117 -0
- package/dist/codexProfiles.d.ts +58 -5
- package/dist/codexProfiles.js +574 -289
- package/dist/credentialIO.d.ts +12 -5
- package/dist/credentialIO.js +63 -29
- package/dist/credentials.d.ts +8 -0
- package/dist/credentials.js +28 -32
- package/dist/ensureDefaultAccounts.d.ts +8 -0
- package/dist/ensureDefaultAccounts.js +42 -82
- package/dist/index.d.ts +27 -10
- package/dist/index.js +61 -9
- package/dist/keepAlive.d.ts +44 -0
- package/dist/keepAlive.js +106 -0
- package/dist/processDetection.d.ts +32 -0
- package/dist/processDetection.js +210 -0
- package/dist/schemas/accountManager.d.ts +283 -1
- package/dist/schemas/accountManager.js +122 -1
- package/dist/schemas/assistantTurn.d.ts +2 -2
- package/dist/schemas/index.d.ts +6 -1
- package/dist/schemas/index.js +14 -1
- package/dist/schemas/observedSessionV1.d.ts +21 -21
- package/dist/schemas/quota.d.ts +18 -18
- package/dist/schemas/quotaHistory.d.ts +1 -1
- package/dist/schemas/stateFile.d.ts +5 -5
- package/dist/terminalSync.d.ts +5 -5
- package/dist/terminalSync.js +7 -112
- package/dist/writers/atomic.d.ts +2 -0
- package/dist/writers/atomic.js +49 -2
- package/package.json +1 -1
package/dist/accounts.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Multi-account management for Claude
|
|
3
|
+
* Multi-account management for Claude Code logins.
|
|
4
4
|
*
|
|
5
|
-
* Handles saving, listing, switching, and removing Claude accounts.
|
|
5
|
+
* Handles saving, listing, switching, syncing, and removing Claude accounts.
|
|
6
6
|
* Both the VS Code extension and CLI consume this module.
|
|
7
|
+
*
|
|
8
|
+
* Model: the live home (`CLAUDE_CONFIG_DIR` or `~/.claude`) is the source of
|
|
9
|
+
* truth for whichever account is logged in right now; every saved profile is a
|
|
10
|
+
* backup that the sync core keeps fresh, because Claude Code rotates the
|
|
11
|
+
* refresh token on every refresh and a stale snapshot is a dead login.
|
|
7
12
|
*/
|
|
8
13
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
14
|
if (k2 === undefined) k2 = k;
|
|
@@ -39,11 +44,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
44
|
};
|
|
40
45
|
})();
|
|
41
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.storeClaudeProfileIdentity = storeClaudeProfileIdentity;
|
|
48
|
+
exports.storeClaudeProfileCredentials = storeClaudeProfileCredentials;
|
|
49
|
+
exports.readStoredClaudeAccount = readStoredClaudeAccount;
|
|
42
50
|
exports.readAccountRegistry = readAccountRegistry;
|
|
43
51
|
exports.writeAccountRegistry = writeAccountRegistry;
|
|
44
52
|
exports.readActiveClaudeAccount = readActiveClaudeAccount;
|
|
45
53
|
exports.addCurrentAccount = addCurrentAccount;
|
|
54
|
+
exports.withClaudeAuthSwapLock = withClaudeAuthSwapLock;
|
|
55
|
+
exports.syncClaudeLiveStateUnlocked = syncClaudeLiveStateUnlocked;
|
|
56
|
+
exports.syncClaudeLiveState = syncClaudeLiveState;
|
|
57
|
+
exports.cleanupAbandonedClaudeLogins = cleanupAbandonedClaudeLogins;
|
|
46
58
|
exports.switchToAccount = switchToAccount;
|
|
59
|
+
exports.switchToAccountAsync = switchToAccountAsync;
|
|
60
|
+
exports.applyClaudeProfileToLiveHome = applyClaudeProfileToLiveHome;
|
|
47
61
|
exports.resolveActiveClaudeHome = resolveActiveClaudeHome;
|
|
48
62
|
exports.applyActiveClaudeToLiveHome = applyActiveClaudeToLiveHome;
|
|
49
63
|
exports.reconcileClaudeAuthState = reconcileClaudeAuthState;
|
|
@@ -54,29 +68,16 @@ exports.resolveActiveClaudeAccount = resolveActiveClaudeAccount;
|
|
|
54
68
|
exports.isMultiAccountEnabled = isMultiAccountEnabled;
|
|
55
69
|
const fs = __importStar(require("fs"));
|
|
56
70
|
const path = __importStar(require("path"));
|
|
57
|
-
const os = __importStar(require("os"));
|
|
58
71
|
const credentialIO_1 = require("./credentialIO");
|
|
59
72
|
const atomic_1 = require("./writers/atomic");
|
|
60
73
|
const accountRegistry_1 = require("./accountRegistry");
|
|
61
74
|
const claudeProfiles_1 = require("./claudeProfiles");
|
|
75
|
+
const claudeCredentials_1 = require("./claudeCredentials");
|
|
76
|
+
const accountHealth_1 = require("./accountHealth");
|
|
77
|
+
const accountSwitch_1 = require("./accountSwitch");
|
|
78
|
+
const accountSyncTypes_1 = require("./accountSyncTypes");
|
|
79
|
+
const processDetection_1 = require("./processDetection");
|
|
62
80
|
// ── Paths ────────────────────────────────────────────────────────────────
|
|
63
|
-
function getClaudeDir() {
|
|
64
|
-
return path.join(os.homedir(), '.claude');
|
|
65
|
-
}
|
|
66
|
-
function getClaudeConfigPath() {
|
|
67
|
-
// Primary: ~/.claude/.claude.json
|
|
68
|
-
const primary = path.join(getClaudeDir(), '.claude.json');
|
|
69
|
-
try {
|
|
70
|
-
const data = JSON.parse(fs.readFileSync(primary, 'utf8'));
|
|
71
|
-
if (data?.oauthAccount)
|
|
72
|
-
return primary;
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
/* fall through */
|
|
76
|
-
}
|
|
77
|
-
// Fallback: ~/.claude.json (some Claude Code versions store config here)
|
|
78
|
-
return path.join(os.homedir(), '.claude.json');
|
|
79
|
-
}
|
|
80
81
|
function getCredentialsDir() {
|
|
81
82
|
return path.join((0, accountRegistry_1.getAccountsDir)(), 'credentials');
|
|
82
83
|
}
|
|
@@ -104,15 +105,34 @@ function readOauthAccountFromConfigPath(filePath) {
|
|
|
104
105
|
const parsed = readJsonOrNull(filePath);
|
|
105
106
|
return parsed?.oauthAccount ?? null;
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
/** Store a profile's identity (`oauthAccount`) in its home and the flat backup. */
|
|
109
|
+
function storeClaudeProfileIdentity(uuid, oauthAccount) {
|
|
108
110
|
(0, atomic_1.atomicWriteJsonSync)(path.join((0, claudeProfiles_1.getClaudeProfileHome)(uuid), '.claude.json'), { oauthAccount });
|
|
111
|
+
try {
|
|
112
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${uuid}.config.json`), oauthAccount);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* the profile home is canonical; the flat copy is a fallback */
|
|
116
|
+
}
|
|
109
117
|
}
|
|
110
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The single chokepoint for storing a Claude credential in a profile: profile
|
|
120
|
+
* home store (suffixed Keychain item on macOS, file elsewhere), the flat
|
|
121
|
+
* backup, and the secret-free health sidecar.
|
|
122
|
+
*/
|
|
123
|
+
function storeClaudeProfileCredentials(uuid, credentials, source) {
|
|
111
124
|
(0, credentialIO_1.writeActiveCredentials)(credentials, (0, claudeProfiles_1.getClaudeProfileHome)(uuid));
|
|
125
|
+
try {
|
|
126
|
+
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${uuid}.credentials.json`), credentials);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* the profile home is canonical; the flat copy is a fallback */
|
|
130
|
+
}
|
|
131
|
+
(0, accountHealth_1.writeClaudeHealthSidecar)(uuid, credentials, source);
|
|
112
132
|
}
|
|
113
|
-
function writeClaudeProfileMirror(uuid, credentials, oauthAccount) {
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
function writeClaudeProfileMirror(uuid, credentials, oauthAccount, source) {
|
|
134
|
+
storeClaudeProfileIdentity(uuid, oauthAccount);
|
|
135
|
+
storeClaudeProfileCredentials(uuid, credentials, source);
|
|
116
136
|
}
|
|
117
137
|
function readFlatClaudeCredentials(uuid) {
|
|
118
138
|
return readJsonOrNull(path.join(getCredentialsDir(), `${uuid}.credentials.json`));
|
|
@@ -133,7 +153,7 @@ function readStoredClaudeAccount(uuid) {
|
|
|
133
153
|
const flatOauthAccount = readFlatClaudeOauthAccount(uuid);
|
|
134
154
|
if (flatCredentials && flatOauthAccount) {
|
|
135
155
|
try {
|
|
136
|
-
writeClaudeProfileMirror(uuid, flatCredentials, flatOauthAccount);
|
|
156
|
+
writeClaudeProfileMirror(uuid, flatCredentials, flatOauthAccount, 'migration');
|
|
137
157
|
}
|
|
138
158
|
catch {
|
|
139
159
|
// Flat backups remain the fallback when profile materialization fails.
|
|
@@ -143,45 +163,21 @@ function readStoredClaudeAccount(uuid) {
|
|
|
143
163
|
return null;
|
|
144
164
|
}
|
|
145
165
|
function readCurrentLiveOauthAccount() {
|
|
146
|
-
return readOauthAccountFromConfigPath(getClaudeConfigPath());
|
|
147
|
-
}
|
|
148
|
-
function backupCurrentClaudeLiveHome() {
|
|
149
|
-
const currentActive = readActiveClaudeAccount();
|
|
150
|
-
if (!currentActive)
|
|
151
|
-
return;
|
|
152
|
-
const originalCreds = (0, credentialIO_1.readActiveCredentials)();
|
|
153
|
-
const originalOauthAccount = readCurrentLiveOauthAccount();
|
|
154
|
-
if (originalCreds) {
|
|
155
|
-
try {
|
|
156
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${currentActive.uuid}.credentials.json`), originalCreds);
|
|
157
|
-
writeClaudeProfileCredentials(currentActive.uuid, originalCreds);
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// Best effort only.
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (originalOauthAccount) {
|
|
164
|
-
try {
|
|
165
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${currentActive.uuid}.config.json`), originalOauthAccount);
|
|
166
|
-
writeClaudeProfileConfig(currentActive.uuid, originalOauthAccount);
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// Best effort only.
|
|
170
|
-
}
|
|
171
|
-
}
|
|
166
|
+
return readOauthAccountFromConfigPath((0, claudeProfiles_1.getClaudeConfigPath)());
|
|
172
167
|
}
|
|
173
168
|
function mergeOauthAccountIntoLiveConfig(oauthAccount) {
|
|
174
169
|
let configObj = {};
|
|
170
|
+
const configPath = (0, claudeProfiles_1.getClaudeConfigPath)();
|
|
175
171
|
try {
|
|
176
|
-
configObj = JSON.parse(fs.readFileSync(
|
|
172
|
+
configObj = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
177
173
|
}
|
|
178
174
|
catch {
|
|
179
175
|
// File may not exist yet.
|
|
180
176
|
}
|
|
181
177
|
configObj.oauthAccount = oauthAccount;
|
|
182
|
-
(0, atomic_1.atomicWriteJsonSync)(
|
|
178
|
+
(0, atomic_1.atomicWriteJsonSync)(configPath, configObj);
|
|
183
179
|
}
|
|
184
|
-
// ── Registry operations
|
|
180
|
+
// ── Registry operations (legacy v1 view over the shared v2 registry) ───────
|
|
185
181
|
function readAccountRegistry() {
|
|
186
182
|
const registry = (0, accountRegistry_1.readSavedAccountRegistry)();
|
|
187
183
|
if (!registry)
|
|
@@ -203,11 +199,11 @@ function readAccountRegistry() {
|
|
|
203
199
|
function writeAccountRegistry(registry) {
|
|
204
200
|
(0, accountRegistry_1.replaceSavedAccountProfiles)('claude-code', mapClaudeProfiles(registry), registry.activeAccountUuid);
|
|
205
201
|
}
|
|
206
|
-
/** For callers already holding the registry lock (see withSavedAccountRegistryLock). */
|
|
207
|
-
function writeAccountRegistryUnlocked(registry) {
|
|
208
|
-
(0, accountRegistry_1.replaceSavedAccountProfilesUnlocked)('claude-code', mapClaudeProfiles(registry), registry.activeAccountUuid);
|
|
209
|
-
}
|
|
210
202
|
function mapClaudeProfiles(registry) {
|
|
203
|
+
const existing = new Map((0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map((profile) => [
|
|
204
|
+
profile.providerAccountId ?? profile.id,
|
|
205
|
+
profile,
|
|
206
|
+
]));
|
|
211
207
|
return registry.accounts.map((account) => ({
|
|
212
208
|
id: account.uuid,
|
|
213
209
|
providerId: 'claude-code',
|
|
@@ -216,28 +212,26 @@ function mapClaudeProfiles(registry) {
|
|
|
216
212
|
label: account.label,
|
|
217
213
|
addedAt: account.addedAt,
|
|
218
214
|
metadata: {
|
|
215
|
+
...existing.get(account.uuid)?.metadata,
|
|
219
216
|
email: account.email,
|
|
220
217
|
},
|
|
221
218
|
}));
|
|
222
219
|
}
|
|
223
220
|
// ── Read active Claude account ───────────────────────────────────────────
|
|
224
221
|
function readActiveClaudeAccount() {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
222
|
+
return (0, claudeProfiles_1.readLiveClaudeIdentity)();
|
|
223
|
+
}
|
|
224
|
+
function findClaudeProfile(uuid) {
|
|
225
|
+
return ((0, accountRegistry_1.listSavedAccountProfiles)('claude-code').find((profile) => (profile.providerAccountId ?? profile.id) === uuid) ?? null);
|
|
226
|
+
}
|
|
227
|
+
function findClaudeProfileForIdentity(identity) {
|
|
228
|
+
const profiles = (0, accountRegistry_1.listSavedAccountProfiles)('claude-code');
|
|
229
|
+
return (profiles.find((p) => (p.providerAccountId ?? p.id) === identity.uuid) ??
|
|
230
|
+
profiles.find((p) => (p.email ?? p.metadata?.email) === identity.email) ??
|
|
231
|
+
null);
|
|
237
232
|
}
|
|
238
233
|
// ── Add current account ──────────────────────────────────────────────────
|
|
239
234
|
function addCurrentAccount(label) {
|
|
240
|
-
// 1. Read current Claude identity
|
|
241
235
|
const active = readActiveClaudeAccount();
|
|
242
236
|
if (!active) {
|
|
243
237
|
return {
|
|
@@ -245,130 +239,277 @@ function addCurrentAccount(label) {
|
|
|
245
239
|
error: 'No active Claude account found. Sign in with `claude` first.',
|
|
246
240
|
};
|
|
247
241
|
}
|
|
248
|
-
// 2. Read current credentials (platform-aware: Keychain on macOS, file on Linux)
|
|
249
242
|
const credBlob = (0, credentialIO_1.readActiveCredentials)();
|
|
250
243
|
if (!credBlob) {
|
|
251
244
|
return { success: false, error: 'Could not read Claude credentials.' };
|
|
252
245
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const raw = JSON.parse(fs.readFileSync(getClaudeConfigPath(), 'utf8'));
|
|
257
|
-
configBlob = raw.oauthAccount;
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
246
|
+
const configBlob = readCurrentLiveOauthAccount();
|
|
247
|
+
if (!configBlob) {
|
|
260
248
|
return { success: false, error: 'Could not read Claude config file.' };
|
|
261
249
|
}
|
|
262
250
|
ensureDirs();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
251
|
+
try {
|
|
252
|
+
writeClaudeProfileMirror(active.uuid, credBlob, configBlob, 'login');
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
return { success: false, error: `Failed to back up Claude credentials: ${err}` };
|
|
256
|
+
}
|
|
257
|
+
// One read-modify-write of the registry under the lock so a concurrent add
|
|
258
|
+
// from another process is never dropped.
|
|
266
259
|
return (0, accountRegistry_1.withSavedAccountRegistryLock)(() => {
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// 5. Check if account already exists — update credentials + label
|
|
274
|
-
const existing = registry.accounts.find((a) => a.uuid === active.uuid);
|
|
275
|
-
if (existing) {
|
|
276
|
-
if (label !== undefined)
|
|
277
|
-
existing.label = label;
|
|
278
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${active.uuid}.credentials.json`), credBlob);
|
|
279
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${active.uuid}.config.json`), configBlob);
|
|
280
|
-
try {
|
|
281
|
-
writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
/* flat backups remain available */
|
|
285
|
-
}
|
|
286
|
-
registry.activeAccountUuid = active.uuid;
|
|
287
|
-
writeAccountRegistryUnlocked(registry);
|
|
288
|
-
return { success: true };
|
|
289
|
-
}
|
|
290
|
-
// 6. New account — back up credentials
|
|
291
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getCredentialsDir(), `${active.uuid}.credentials.json`), credBlob);
|
|
292
|
-
(0, atomic_1.atomicWriteJsonSync)(path.join(getConfigsDir(), `${active.uuid}.config.json`), configBlob);
|
|
293
|
-
try {
|
|
294
|
-
writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
/* flat backups remain available */
|
|
298
|
-
}
|
|
299
|
-
// 7. Add entry
|
|
300
|
-
registry.accounts.push({
|
|
301
|
-
uuid: active.uuid,
|
|
260
|
+
const existing = findClaudeProfile(active.uuid);
|
|
261
|
+
const id = existing?.id ?? active.uuid;
|
|
262
|
+
(0, accountRegistry_1.upsertSavedAccountProfileUnlocked)({
|
|
263
|
+
id,
|
|
264
|
+
providerId: 'claude-code',
|
|
265
|
+
providerAccountId: active.uuid,
|
|
302
266
|
email: active.email,
|
|
303
|
-
label,
|
|
304
|
-
addedAt: new Date().toISOString(),
|
|
267
|
+
label: label !== undefined ? label || undefined : existing?.label,
|
|
268
|
+
addedAt: existing?.addedAt ?? new Date().toISOString(),
|
|
269
|
+
metadata: {
|
|
270
|
+
...existing?.metadata,
|
|
271
|
+
email: active.email,
|
|
272
|
+
origin: existing?.metadata?.origin ?? 'manual',
|
|
273
|
+
},
|
|
305
274
|
});
|
|
306
|
-
|
|
307
|
-
writeAccountRegistryUnlocked(registry);
|
|
275
|
+
(0, accountRegistry_1.setActiveSavedAccountUnlocked)('claude-code', id);
|
|
308
276
|
return { success: true };
|
|
309
277
|
});
|
|
310
278
|
}
|
|
311
|
-
// ──
|
|
279
|
+
// ── Live-state sync ──────────────────────────────────────────────────────
|
|
312
280
|
/**
|
|
313
281
|
* Serializes live Claude credential swaps across processes so two concurrent
|
|
314
282
|
* switchers cannot interleave their backup/write/rollback sequences. It cannot
|
|
315
283
|
* exclude Claude Code's own writes to the live home — only our switchers. Lock
|
|
316
284
|
* ordering: this lock first, the registry lock (inside setActiveSavedAccount)
|
|
317
|
-
* inside it — never the reverse.
|
|
285
|
+
* inside it — never the reverse. Never spawn a child process while holding it.
|
|
318
286
|
*/
|
|
319
287
|
function withClaudeAuthSwapLock(operation) {
|
|
320
288
|
const lockDir = path.join((0, accountRegistry_1.getAccountsDir)(), 'claude');
|
|
321
289
|
fs.mkdirSync(lockDir, { recursive: true, mode: 0o700 });
|
|
322
290
|
return (0, atomic_1.withFileLockSync)(path.join(lockDir, 'auth-swap.lock'), operation);
|
|
323
291
|
}
|
|
324
|
-
|
|
292
|
+
/**
|
|
293
|
+
* Fold the live login into the saved profiles: register an unknown login
|
|
294
|
+
* (label = email), refresh the matching profile's backup when the live
|
|
295
|
+
* credential is newer, and re-point the active pointer at it. Caller holds the
|
|
296
|
+
* swap lock. Never throws; problems land in `warnings`.
|
|
297
|
+
*/
|
|
298
|
+
function syncClaudeLiveStateUnlocked(source = 'live-sync') {
|
|
299
|
+
const report = (0, accountSyncTypes_1.emptyProviderSyncReport)();
|
|
325
300
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
301
|
+
const identity = readActiveClaudeAccount();
|
|
302
|
+
if (!identity) {
|
|
303
|
+
report.skipped = 'logged-out';
|
|
304
|
+
return report;
|
|
305
|
+
}
|
|
306
|
+
let profile = findClaudeProfileForIdentity(identity);
|
|
307
|
+
if (!profile) {
|
|
308
|
+
profile = {
|
|
309
|
+
id: identity.uuid,
|
|
310
|
+
providerId: 'claude-code',
|
|
311
|
+
providerAccountId: identity.uuid,
|
|
312
|
+
email: identity.email,
|
|
313
|
+
label: identity.email,
|
|
314
|
+
addedAt: new Date().toISOString(),
|
|
315
|
+
metadata: { email: identity.email, origin: 'live-sync' },
|
|
316
|
+
};
|
|
317
|
+
ensureDirs();
|
|
318
|
+
(0, accountRegistry_1.upsertSavedAccountProfile)(profile);
|
|
319
|
+
report.registered = { id: profile.id, email: identity.email };
|
|
320
|
+
}
|
|
321
|
+
else if (profile.email !== identity.email && identity.email) {
|
|
322
|
+
profile = {
|
|
323
|
+
...profile,
|
|
324
|
+
email: identity.email,
|
|
325
|
+
metadata: { ...profile.metadata, email: identity.email },
|
|
326
|
+
};
|
|
327
|
+
(0, accountRegistry_1.upsertSavedAccountProfile)(profile);
|
|
328
|
+
}
|
|
329
|
+
const uuid = profile.providerAccountId ?? profile.id;
|
|
330
|
+
const liveBlob = (0, credentialIO_1.readActiveCredentials)();
|
|
331
|
+
const live = (0, claudeCredentials_1.parseClaudeCredentialBlob)(liveBlob);
|
|
332
|
+
if (live) {
|
|
333
|
+
const sidecar = (0, accountHealth_1.readHealthSidecar)('claude-code', uuid);
|
|
334
|
+
const storedFreshness = sidecar?.accessExpiresAt ?? sidecar?.refreshExpiresAt ?? 0;
|
|
335
|
+
const liveFreshness = (0, claudeCredentials_1.claudeCredentialFreshness)(live);
|
|
336
|
+
const needsFold = !sidecar ||
|
|
337
|
+
liveFreshness > storedFreshness ||
|
|
338
|
+
(liveFreshness === storedFreshness &&
|
|
339
|
+
!fs.existsSync(path.join((0, claudeProfiles_1.getClaudeProfileHome)(uuid), '.claude.json')));
|
|
340
|
+
if (needsFold) {
|
|
341
|
+
try {
|
|
342
|
+
writeClaudeProfileMirror(uuid, liveBlob, readCurrentLiveOauthAccount() ?? {
|
|
343
|
+
emailAddress: identity.email,
|
|
344
|
+
accountUuid: identity.uuid,
|
|
345
|
+
}, source);
|
|
346
|
+
report.folded = profile.id;
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
report.warnings.push(`Could not back up the live Claude credentials: ${err}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else if (liveBlob === null) {
|
|
354
|
+
report.warnings.push('The live Claude credential store could not be read.');
|
|
355
|
+
}
|
|
356
|
+
const active = (0, accountRegistry_1.getActiveSavedAccount)('claude-code');
|
|
357
|
+
if (!active || active.id !== profile.id) {
|
|
358
|
+
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', profile.id, { silent: true });
|
|
359
|
+
report.repointed = profile.id;
|
|
360
|
+
}
|
|
328
361
|
}
|
|
329
362
|
catch (err) {
|
|
330
|
-
|
|
363
|
+
report.warnings.push(`Claude account sync failed: ${err}`);
|
|
331
364
|
}
|
|
365
|
+
return report;
|
|
332
366
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return
|
|
367
|
+
/** {@link syncClaudeLiveStateUnlocked} under the swap lock. */
|
|
368
|
+
function syncClaudeLiveState(source = 'live-sync') {
|
|
369
|
+
try {
|
|
370
|
+
return withClaudeAuthSwapLock(() => syncClaudeLiveStateUnlocked(source));
|
|
337
371
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
return { success: false, error: `Account ${uuid} not found in registry.` };
|
|
372
|
+
catch (err) {
|
|
373
|
+
return { warnings: [`Could not acquire the account-switch lock: ${err}`] };
|
|
341
374
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Remove isolated login homes that never authenticated (the user closed the
|
|
378
|
+
* browser, the login timed out) once they are older than `olderThanMs`.
|
|
379
|
+
* Returns the removed login ids.
|
|
380
|
+
*/
|
|
381
|
+
function cleanupAbandonedClaudeLogins(olderThanMs = 6 * 60 * 60 * 1000, now = Date.now()) {
|
|
382
|
+
const removed = [];
|
|
383
|
+
let entries = [];
|
|
346
384
|
try {
|
|
347
|
-
|
|
385
|
+
entries = fs.readdirSync((0, claudeProfiles_1.getClaudeProfilesDir)());
|
|
348
386
|
}
|
|
349
387
|
catch {
|
|
350
|
-
|
|
388
|
+
return removed;
|
|
389
|
+
}
|
|
390
|
+
const registered = new Set((0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map((p) => p.id));
|
|
391
|
+
for (const loginId of entries) {
|
|
392
|
+
if (registered.has(loginId))
|
|
393
|
+
continue;
|
|
394
|
+
const dir = (0, claudeProfiles_1.getClaudeProfileDir)(loginId);
|
|
395
|
+
const pendingPath = path.join(dir, 'profile.json');
|
|
396
|
+
const pending = readJsonOrNull(pendingPath);
|
|
397
|
+
if (!pending)
|
|
398
|
+
continue;
|
|
399
|
+
const home = (0, claudeProfiles_1.getClaudeProfileHome)(loginId);
|
|
400
|
+
if ((0, claudeProfiles_1.isClaudeProfileAuthenticated)(home))
|
|
401
|
+
continue;
|
|
402
|
+
const addedAt = pending.addedAt ? Date.parse(pending.addedAt) : NaN;
|
|
403
|
+
const age = Number.isNaN(addedAt) ? Number.POSITIVE_INFINITY : now - addedAt;
|
|
404
|
+
if (age < olderThanMs)
|
|
405
|
+
continue;
|
|
406
|
+
try {
|
|
407
|
+
(0, credentialIO_1.deleteStoredCredentials)(home);
|
|
408
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
409
|
+
removed.push(loginId);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
/* best effort */
|
|
413
|
+
}
|
|
351
414
|
}
|
|
415
|
+
return removed;
|
|
416
|
+
}
|
|
417
|
+
// ── Switch to account ────────────────────────────────────────────────────
|
|
418
|
+
function switchToAccount(uuid, options = {}) {
|
|
419
|
+
const consumers = (0, processDetection_1.detectRunningAccountConsumersSync)('claude-code');
|
|
420
|
+
return runClaudeSwitch(uuid, options, consumers);
|
|
421
|
+
}
|
|
422
|
+
/** {@link switchToAccount} with the process probe off the event loop. */
|
|
423
|
+
async function switchToAccountAsync(uuid, options = {}) {
|
|
424
|
+
const consumers = await (0, processDetection_1.detectRunningAccountConsumers)('claude-code');
|
|
425
|
+
return runClaudeSwitch(uuid, options, consumers);
|
|
426
|
+
}
|
|
427
|
+
function runClaudeSwitch(uuid, options, consumers) {
|
|
352
428
|
try {
|
|
353
|
-
|
|
429
|
+
// Keep the non-throwing result contract when the lock cannot be acquired.
|
|
430
|
+
return withClaudeAuthSwapLock(() => switchToAccountUnlocked(uuid, options, consumers));
|
|
354
431
|
}
|
|
355
|
-
catch {
|
|
356
|
-
|
|
432
|
+
catch (err) {
|
|
433
|
+
return (0, accountSwitch_1.switchFailure)('claude-code', uuid, `Could not acquire the account-switch lock: ${err}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function switchToAccountUnlocked(uuid, options, consumers) {
|
|
437
|
+
const target = findClaudeProfile(uuid);
|
|
438
|
+
if (!target) {
|
|
439
|
+
return (0, accountSwitch_1.switchFailure)('claude-code', uuid, `Account ${uuid} not found in registry.`);
|
|
440
|
+
}
|
|
441
|
+
const targetName = target.label ?? target.email ?? uuid;
|
|
442
|
+
// Refuse to treat one of our own profile homes as the live home: a shell
|
|
443
|
+
// that eval'd `sidekick accounts env` would otherwise overwrite that profile.
|
|
444
|
+
const liveHome = path.resolve((0, claudeProfiles_1.getLiveClaudeHome)());
|
|
445
|
+
const profilesDir = path.resolve((0, claudeProfiles_1.getClaudeProfilesDir)());
|
|
446
|
+
if (liveHome === profilesDir || liveHome.startsWith(profilesDir + path.sep)) {
|
|
447
|
+
return (0, accountSwitch_1.switchFailure)('claude-code', uuid, `CLAUDE_CONFIG_DIR points at a sidekick profile home (${liveHome}); unset it before switching accounts.`);
|
|
448
|
+
}
|
|
449
|
+
// Phase 1: capture the outgoing login (rotated refresh token included) and
|
|
450
|
+
// register it if sidekick has never seen it. Only then is the "previous"
|
|
451
|
+
// account known: it is whatever was really live, not the stale pointer.
|
|
452
|
+
const sync = syncClaudeLiveStateUnlocked('switch');
|
|
453
|
+
const previousAccountId = (0, accountRegistry_1.getActiveSavedAccount)('claude-code')?.id ?? null;
|
|
454
|
+
const base = {
|
|
455
|
+
success: true,
|
|
456
|
+
provider: 'claude-code',
|
|
457
|
+
accountId: target.id,
|
|
458
|
+
previousAccountId,
|
|
459
|
+
verified: false,
|
|
460
|
+
verification: 'none',
|
|
461
|
+
warnings: [...sync.warnings],
|
|
462
|
+
hints: [],
|
|
463
|
+
runningConsumers: consumers,
|
|
464
|
+
email: target.email,
|
|
465
|
+
};
|
|
466
|
+
if (readActiveClaudeAccount()?.uuid === uuid) {
|
|
467
|
+
if (previousAccountId !== target.id)
|
|
468
|
+
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', target.id);
|
|
469
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
470
|
+
...base,
|
|
471
|
+
alreadyActive: true,
|
|
472
|
+
verified: true,
|
|
473
|
+
verification: 'store',
|
|
474
|
+
health: (0, accountHealth_1.getAccountHealth)('claude-code', target.id),
|
|
475
|
+
hints: [`${targetName} is already the active Claude account.`],
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
// Materialise pre-migration flat backups into the profile home first so the
|
|
479
|
+
// health probe (and the install) read one canonical store.
|
|
480
|
+
const stored = readStoredClaudeAccount(uuid);
|
|
481
|
+
if (!stored) {
|
|
482
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
483
|
+
...base,
|
|
484
|
+
success: false,
|
|
485
|
+
needsLogin: true,
|
|
486
|
+
error: `Stored credentials for "${targetName}" not found; sign in again.`,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
// Preflight: never install a credential we already know is dead.
|
|
490
|
+
const health = (0, accountHealth_1.getAccountHealth)('claude-code', target.id, { probe: 'store' });
|
|
491
|
+
base.health = health;
|
|
492
|
+
if ((health.state === 'expired' || health.state === 'missing') && !options.force) {
|
|
493
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
494
|
+
...base,
|
|
495
|
+
success: false,
|
|
496
|
+
needsLogin: true,
|
|
497
|
+
error: `Stored credentials for "${targetName}" have ${health.state === 'missing' ? 'not been saved' : 'expired'}; sign in again.`,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (health.state === 'expiring' && health.reason) {
|
|
501
|
+
base.warnings.push(`"${targetName}": ${health.reason}`);
|
|
357
502
|
}
|
|
503
|
+
// Snapshot the live state for rollback.
|
|
504
|
+
const originalCreds = (0, credentialIO_1.readActiveCredentials)();
|
|
505
|
+
let originalConfig = null;
|
|
358
506
|
try {
|
|
359
|
-
(0,
|
|
507
|
+
originalConfig = fs.readFileSync((0, claudeProfiles_1.getClaudeConfigPath)(), 'utf8');
|
|
360
508
|
}
|
|
361
|
-
catch
|
|
362
|
-
|
|
509
|
+
catch {
|
|
510
|
+
/* no existing config to back up */
|
|
363
511
|
}
|
|
364
|
-
const
|
|
365
|
-
if (!applied.success) {
|
|
366
|
-
try {
|
|
367
|
-
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', registry.activeAccountUuid);
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
/* rollback failed */
|
|
371
|
-
}
|
|
512
|
+
const rollback = () => {
|
|
372
513
|
if (originalCreds) {
|
|
373
514
|
try {
|
|
374
515
|
(0, credentialIO_1.writeActiveCredentials)(originalCreds);
|
|
@@ -377,22 +518,104 @@ function switchToAccountUnlocked(uuid) {
|
|
|
377
518
|
/* rollback failed */
|
|
378
519
|
}
|
|
379
520
|
}
|
|
380
|
-
if (originalConfig) {
|
|
521
|
+
if (originalConfig !== null) {
|
|
381
522
|
try {
|
|
382
|
-
fs.writeFileSync(getClaudeConfigPath(), originalConfig);
|
|
523
|
+
fs.writeFileSync((0, claudeProfiles_1.getClaudeConfigPath)(), originalConfig);
|
|
383
524
|
}
|
|
384
525
|
catch {
|
|
385
526
|
/* rollback failed */
|
|
386
527
|
}
|
|
387
528
|
}
|
|
388
|
-
|
|
529
|
+
try {
|
|
530
|
+
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', previousAccountId, { silent: true });
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
/* rollback failed */
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
// Install.
|
|
537
|
+
try {
|
|
538
|
+
(0, credentialIO_1.writeActiveCredentials)(stored.credentials);
|
|
539
|
+
mergeOauthAccountIntoLiveConfig(stored.oauthAccount);
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
rollback();
|
|
543
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
544
|
+
...base,
|
|
545
|
+
success: false,
|
|
546
|
+
error: `Failed to write Claude credentials: ${err}`,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
// Verify by re-reading the live store.
|
|
550
|
+
const installed = (0, claudeCredentials_1.parseClaudeCredentialBlob)((0, credentialIO_1.readActiveCredentials)());
|
|
551
|
+
const expected = (0, claudeCredentials_1.parseClaudeCredentialBlob)(stored.credentials);
|
|
552
|
+
const liveIdentity = readActiveClaudeAccount();
|
|
553
|
+
const verified = installed !== null &&
|
|
554
|
+
expected !== null &&
|
|
555
|
+
installed.accessToken === expected.accessToken &&
|
|
556
|
+
liveIdentity?.uuid === uuid;
|
|
557
|
+
if (!verified) {
|
|
558
|
+
rollback();
|
|
559
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
560
|
+
...base,
|
|
561
|
+
success: false,
|
|
562
|
+
verification: 'failed',
|
|
563
|
+
error: `The live Claude credential store did not reflect the switch to "${targetName}"; the previous login was restored.`,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
// Pointer last: a crash before this line leaves the registry pointing at the
|
|
567
|
+
// previous account, which the next sync repairs from the live identity.
|
|
568
|
+
try {
|
|
569
|
+
(0, accountRegistry_1.setActiveSavedAccount)('claude-code', target.id);
|
|
570
|
+
}
|
|
571
|
+
catch (err) {
|
|
572
|
+
rollback();
|
|
573
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
574
|
+
...base,
|
|
575
|
+
success: false,
|
|
576
|
+
error: `Failed to update account registry: ${err}`,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
let undoToken;
|
|
580
|
+
try {
|
|
581
|
+
undoToken = (0, accountSwitch_1.writeLastSwitch)('claude-code', previousAccountId, target.id).token;
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
base.warnings.push('The switch succeeded but could not be recorded for undo.');
|
|
585
|
+
}
|
|
586
|
+
return (0, accountSwitch_1.finishSwitchResult)({
|
|
587
|
+
...base,
|
|
588
|
+
verified: true,
|
|
589
|
+
verification: 'store',
|
|
590
|
+
undoToken,
|
|
591
|
+
hints: [`New claude sessions use ${target.email ?? targetName}.`],
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Install a saved profile's credentials into the live home without changing
|
|
596
|
+
* which account is active: used right after a re-login of the live account,
|
|
597
|
+
* whose freshly minted token must replace the (possibly dead) live one.
|
|
598
|
+
*/
|
|
599
|
+
function applyClaudeProfileToLiveHome(uuid) {
|
|
600
|
+
try {
|
|
601
|
+
return withClaudeAuthSwapLock(() => {
|
|
602
|
+
const stored = readStoredClaudeAccount(uuid);
|
|
603
|
+
if (!stored) {
|
|
604
|
+
return { success: false, error: `Stored credentials for ${uuid} not found.` };
|
|
605
|
+
}
|
|
606
|
+
(0, credentialIO_1.writeActiveCredentials)(stored.credentials);
|
|
607
|
+
mergeOauthAccountIntoLiveConfig(stored.oauthAccount);
|
|
608
|
+
return { success: true };
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
return { success: false, error: `Could not install the refreshed credentials: ${err}` };
|
|
389
613
|
}
|
|
390
|
-
return applied;
|
|
391
614
|
}
|
|
392
615
|
function resolveActiveClaudeHome() {
|
|
393
616
|
const active = (0, accountRegistry_1.getActiveSavedAccount)('claude-code');
|
|
394
617
|
if (!active)
|
|
395
|
-
return
|
|
618
|
+
return (0, claudeProfiles_1.getLiveClaudeHome)();
|
|
396
619
|
return (0, claudeProfiles_1.getClaudeProfileHome)(active.providerAccountId ?? active.id);
|
|
397
620
|
}
|
|
398
621
|
function applyActiveClaudeToLiveHome() {
|
|
@@ -417,7 +640,7 @@ function applyActiveClaudeToLiveHomeUnlocked() {
|
|
|
417
640
|
const originalCreds = (0, credentialIO_1.readActiveCredentials)();
|
|
418
641
|
let originalConfig = null;
|
|
419
642
|
try {
|
|
420
|
-
originalConfig = fs.readFileSync(getClaudeConfigPath(), 'utf8');
|
|
643
|
+
originalConfig = fs.readFileSync((0, claudeProfiles_1.getClaudeConfigPath)(), 'utf8');
|
|
421
644
|
}
|
|
422
645
|
catch {
|
|
423
646
|
/* no existing config to back up */
|
|
@@ -450,7 +673,7 @@ function applyActiveClaudeToLiveHomeUnlocked() {
|
|
|
450
673
|
}
|
|
451
674
|
if (originalConfig) {
|
|
452
675
|
try {
|
|
453
|
-
fs.writeFileSync(getClaudeConfigPath(), originalConfig);
|
|
676
|
+
fs.writeFileSync((0, claudeProfiles_1.getClaudeConfigPath)(), originalConfig);
|
|
454
677
|
}
|
|
455
678
|
catch {
|
|
456
679
|
/* rollback failed */
|
|
@@ -478,7 +701,7 @@ function reconcileClaudeAuthState() {
|
|
|
478
701
|
const oauthAccount = readFlatClaudeOauthAccount(uuid);
|
|
479
702
|
if (!credentials || !oauthAccount)
|
|
480
703
|
continue;
|
|
481
|
-
writeClaudeProfileMirror(uuid, credentials, oauthAccount);
|
|
704
|
+
writeClaudeProfileMirror(uuid, credentials, oauthAccount, 'migration');
|
|
482
705
|
}
|
|
483
706
|
catch {
|
|
484
707
|
// Migration is best-effort per account.
|
|
@@ -497,35 +720,32 @@ function reconcileClaudeAuthState() {
|
|
|
497
720
|
}
|
|
498
721
|
// ── Remove account ───────────────────────────────────────────────────────
|
|
499
722
|
function removeAccount(uuid) {
|
|
500
|
-
// One read-modify-write
|
|
723
|
+
// One read-modify-write of the registry under the lock (see addCurrentAccount).
|
|
501
724
|
return (0, accountRegistry_1.withSavedAccountRegistryLock)(() => {
|
|
502
|
-
const
|
|
503
|
-
if (!
|
|
504
|
-
return { success: false, error: 'No account registry found.' };
|
|
505
|
-
}
|
|
506
|
-
const idx = registry.accounts.findIndex((a) => a.uuid === uuid);
|
|
507
|
-
if (idx === -1) {
|
|
725
|
+
const profile = findClaudeProfile(uuid);
|
|
726
|
+
if (!profile) {
|
|
508
727
|
return { success: false, error: `Account ${uuid} not found.` };
|
|
509
728
|
}
|
|
510
|
-
// Remove backed-up files
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
729
|
+
// Remove backed-up files and the profile home (credential store included).
|
|
730
|
+
for (const filePath of [
|
|
731
|
+
path.join(getCredentialsDir(), `${uuid}.credentials.json`),
|
|
732
|
+
path.join(getConfigsDir(), `${uuid}.config.json`),
|
|
733
|
+
]) {
|
|
734
|
+
try {
|
|
735
|
+
fs.unlinkSync(filePath);
|
|
736
|
+
}
|
|
737
|
+
catch {
|
|
738
|
+
/* ok */
|
|
739
|
+
}
|
|
516
740
|
}
|
|
517
741
|
try {
|
|
518
|
-
|
|
742
|
+
(0, credentialIO_1.deleteStoredCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(uuid));
|
|
743
|
+
fs.rmSync((0, claudeProfiles_1.getClaudeProfileDir)(profile.id), { recursive: true, force: true });
|
|
519
744
|
}
|
|
520
745
|
catch {
|
|
521
746
|
/* ok */
|
|
522
747
|
}
|
|
523
|
-
|
|
524
|
-
registry.accounts.splice(idx, 1);
|
|
525
|
-
if (registry.activeAccountUuid === uuid) {
|
|
526
|
-
registry.activeAccountUuid = registry.accounts[0]?.uuid ?? null;
|
|
527
|
-
}
|
|
528
|
-
writeAccountRegistryUnlocked(registry);
|
|
748
|
+
(0, accountRegistry_1.removeSavedAccountProfileUnlocked)('claude-code', profile.id);
|
|
529
749
|
return { success: true };
|
|
530
750
|
});
|
|
531
751
|
}
|
|
@@ -551,22 +771,21 @@ function getActiveAccount() {
|
|
|
551
771
|
}
|
|
552
772
|
/**
|
|
553
773
|
* Resolves the *currently logged-in* Claude account for display, preferring the
|
|
554
|
-
* live
|
|
555
|
-
*
|
|
774
|
+
* live `.claude.json` identity over the saved registry pointer (which only
|
|
775
|
+
* sidekick's own switch flow updates and therefore goes stale after a native
|
|
556
776
|
* `claude /login`).
|
|
557
777
|
*
|
|
558
778
|
* Safe self-heal: when the live account matches a saved profile (by account UUID,
|
|
559
779
|
* email as fallback) that isn't the current active pointer, the pointer is
|
|
560
780
|
* re-pointed so registry-keyed data tracks reality too. Never creates or deletes
|
|
561
|
-
* profiles; an unknown live account is shown as-is with no label and no write
|
|
781
|
+
* profiles; an unknown live account is shown as-is with no label and no write
|
|
782
|
+
* (the sync engine registers it on the next startup or watch event).
|
|
562
783
|
*/
|
|
563
784
|
function resolveActiveClaudeAccount(options = {}) {
|
|
564
785
|
const selfHeal = options.selfHeal ?? true;
|
|
565
786
|
const live = readActiveClaudeAccount();
|
|
566
787
|
if (live) {
|
|
567
|
-
const
|
|
568
|
-
const match = profiles.find((p) => (p.providerAccountId ?? p.id) === live.uuid) ??
|
|
569
|
-
profiles.find((p) => (p.email ?? p.metadata?.email) === live.email);
|
|
788
|
+
const match = findClaudeProfileForIdentity(live);
|
|
570
789
|
if (match) {
|
|
571
790
|
const active = (0, accountRegistry_1.getActiveSavedAccount)('claude-code');
|
|
572
791
|
// Hot paths (the status line runs on every prompt) pass selfHeal: false
|