sidekick-shared 0.27.5 → 0.27.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +35 -34
  3. package/dist/accountHealth.d.ts +14 -0
  4. package/dist/accountHealth.js +61 -14
  5. package/dist/accountLaunch.d.ts +6 -0
  6. package/dist/accountLaunch.js +61 -4
  7. package/dist/accountLease.d.ts +5 -0
  8. package/dist/accountLease.js +98 -0
  9. package/dist/accountManager.d.ts +7 -0
  10. package/dist/accountManager.js +226 -89
  11. package/dist/accountSync.d.ts +1 -5
  12. package/dist/accountSync.js +22 -6
  13. package/dist/accountSyncTypes.d.ts +9 -0
  14. package/dist/accounts.d.ts +75 -18
  15. package/dist/accounts.js +712 -189
  16. package/dist/claudeCredentialDoctor.d.ts +50 -0
  17. package/dist/claudeCredentialDoctor.js +277 -0
  18. package/dist/claudeCredentialLedger.d.ts +15 -0
  19. package/dist/claudeCredentialLedger.js +130 -0
  20. package/dist/claudeCredentials.d.ts +20 -3
  21. package/dist/claudeCredentials.js +50 -3
  22. package/dist/claudeLiveGuard.d.ts +33 -0
  23. package/dist/claudeLiveGuard.js +46 -0
  24. package/dist/claudeProfiles.d.ts +14 -0
  25. package/dist/claudeProfiles.js +22 -0
  26. package/dist/claudeRefreshLock.d.ts +15 -0
  27. package/dist/claudeRefreshLock.js +145 -0
  28. package/dist/codexAuth.d.ts +4 -0
  29. package/dist/codexAuth.js +18 -0
  30. package/dist/codexCredentialDoctor.d.ts +33 -0
  31. package/dist/codexCredentialDoctor.js +302 -0
  32. package/dist/codexCredentialStore.d.ts +29 -0
  33. package/dist/codexCredentialStore.js +213 -0
  34. package/dist/codexKeepAlive.d.ts +2 -0
  35. package/dist/codexKeepAlive.js +116 -0
  36. package/dist/codexPaths.d.ts +8 -2
  37. package/dist/codexPaths.js +51 -7
  38. package/dist/codexProfiles.d.ts +37 -31
  39. package/dist/codexProfiles.js +842 -671
  40. package/dist/credentialIO.d.ts +67 -7
  41. package/dist/credentialIO.js +211 -47
  42. package/dist/ensureDefaultAccounts.js +1 -8
  43. package/dist/index.d.ts +14 -4
  44. package/dist/index.js +29 -6
  45. package/dist/keepAlive.d.ts +2 -1
  46. package/dist/keepAlive.js +116 -29
  47. package/dist/liveCredentialGuard.d.ts +28 -0
  48. package/dist/liveCredentialGuard.js +36 -0
  49. package/dist/processDetection.d.ts +10 -3
  50. package/dist/processDetection.js +44 -11
  51. package/dist/schemas/accountManager.d.ts +22 -0
  52. package/dist/schemas/accountManager.js +4 -0
  53. package/dist/schemas/assistantTurn.d.ts +2 -2
  54. package/dist/terminalSync.d.ts +12 -2
  55. package/dist/terminalSync.js +27 -2
  56. package/package.json +1 -1
@@ -0,0 +1,213 @@
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.isLegacyCodexLogin = isLegacyCodexLogin;
37
+ exports.isCodexLogin = isCodexLogin;
38
+ exports.codexLoginOnly = codexLoginOnly;
39
+ exports.readCodexCredentialStore = readCodexCredentialStore;
40
+ exports.readCodexBackup = readCodexBackup;
41
+ exports.codexSnapshotFingerprint = codexSnapshotFingerprint;
42
+ exports.codexStoreMatches = codexStoreMatches;
43
+ exports.writeCodexBackup = writeCodexBackup;
44
+ exports.canonicalCodexPath = canonicalCodexPath;
45
+ exports.isSidekickCodexHome = isSidekickCodexHome;
46
+ exports.codexInstalledSnapshot = codexInstalledSnapshot;
47
+ exports.writeInactiveCodexCredentialStore = writeInactiveCodexCredentialStore;
48
+ exports.writeLiveCodexCredentialStoreUnguarded = writeLiveCodexCredentialStoreUnguarded;
49
+ exports.removeCodexProfileCredentials = removeCodexProfileCredentials;
50
+ /** Raw credential I/O. Backups are never Codex homes and never launchable. */
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const crypto_1 = require("crypto");
54
+ const accountRegistry_1 = require("./accountRegistry");
55
+ const codexPaths_1 = require("./codexPaths");
56
+ const codexAuth_1 = require("./codexAuth");
57
+ const atomic_1 = require("./writers/atomic");
58
+ function isLegacyCodexLogin(raw) {
59
+ const value = (0, codexAuth_1.parseAuthJson)(raw);
60
+ return Boolean(value &&
61
+ ['OPENAI_API_KEY', 'CODEX_API_KEY'].some((key) => typeof value[key] === 'string' && value[key].length > 0));
62
+ }
63
+ function isCodexLogin(raw) {
64
+ const parsed = (0, codexAuth_1.parseAuthJson)(raw);
65
+ if (!parsed || Array.isArray(parsed))
66
+ return false;
67
+ if (typeof parsed.OPENAI_API_KEY === 'string' && parsed.OPENAI_API_KEY)
68
+ return true;
69
+ return Boolean(typeof parsed.tokens?.access_token === 'string' &&
70
+ parsed.tokens.access_token &&
71
+ typeof parsed.tokens.refresh_token === 'string' &&
72
+ parsed.tokens.refresh_token);
73
+ }
74
+ function codexLoginOnly(snapshot) {
75
+ return {
76
+ authRaw: snapshot.authRaw,
77
+ legacyRaw: isLegacyCodexLogin(snapshot.legacyRaw) ? snapshot.legacyRaw : null,
78
+ };
79
+ }
80
+ function readRaw(file) {
81
+ try {
82
+ return fs.readFileSync(file, 'utf8');
83
+ }
84
+ catch (error) {
85
+ if (error.code === 'ENOENT')
86
+ return null;
87
+ throw error;
88
+ }
89
+ }
90
+ function readFiles(auth, legacy) {
91
+ try {
92
+ const snapshot = { authRaw: readRaw(auth), legacyRaw: readRaw(legacy) };
93
+ const status = snapshot.authRaw !== null
94
+ ? isCodexLogin(snapshot.authRaw)
95
+ ? 'ok'
96
+ : 'corrupt'
97
+ : isLegacyCodexLogin(snapshot.legacyRaw)
98
+ ? 'ok'
99
+ : 'absent';
100
+ return { status, snapshot };
101
+ }
102
+ catch {
103
+ return { status: 'error' };
104
+ }
105
+ }
106
+ function readCodexCredentialStore(home = (0, codexPaths_1.getSystemCodexHome)()) {
107
+ return readFiles(path.join(home, 'auth.json'), path.join(home, '.credentials.json'));
108
+ }
109
+ function readCodexBackup(id) {
110
+ return readFiles((0, codexPaths_1.getCodexBackupPath)(id), (0, codexPaths_1.getCodexBackupPath)(id, true));
111
+ }
112
+ function codexSnapshotFingerprint(snapshot) {
113
+ return (0, crypto_1.createHash)('sha256').update(JSON.stringify(snapshot)).digest('hex');
114
+ }
115
+ /** The whole snapshot, including absence, must agree before destructive operations. */
116
+ function codexStoreMatches(read, snapshot) {
117
+ return (read.status !== 'error' &&
118
+ codexSnapshotFingerprint(read.snapshot) === codexSnapshotFingerprint(snapshot));
119
+ }
120
+ function writeCodexBackup(id, snapshot) {
121
+ const login = codexLoginOnly(snapshot);
122
+ if (!isCodexLogin(login.authRaw) && !isLegacyCodexLogin(login.legacyRaw)) {
123
+ throw new Error('Refusing to back up an invalid Codex login.');
124
+ }
125
+ for (const [file, raw] of [
126
+ [(0, codexPaths_1.getCodexBackupPath)(id), login.authRaw],
127
+ [(0, codexPaths_1.getCodexBackupPath)(id, true), login.legacyRaw],
128
+ ]) {
129
+ if (raw !== null)
130
+ (0, atomic_1.atomicWriteFileSync)(file, raw);
131
+ else
132
+ fs.rmSync(file, { force: true });
133
+ }
134
+ if (!codexStoreMatches(readCodexBackup(id), login))
135
+ throw new Error('The private Codex backup did not verify.');
136
+ }
137
+ /** Resolve existing parents too, so nonexistent children of a symlink cannot bypass ownership. */
138
+ function canonicalCodexPath(value) {
139
+ const resolved = path.resolve(value);
140
+ try {
141
+ return fs.realpathSync(resolved);
142
+ }
143
+ catch {
144
+ const parent = path.dirname(resolved);
145
+ return parent === resolved
146
+ ? resolved
147
+ : path.join(canonicalCodexPath(parent), path.basename(resolved));
148
+ }
149
+ }
150
+ function isSidekickCodexHome(home) {
151
+ const root = canonicalCodexPath(path.join((0, accountRegistry_1.getAccountsDir)(), 'codex'));
152
+ const resolved = canonicalCodexPath(home);
153
+ return resolved === root || resolved.startsWith(root + path.sep);
154
+ }
155
+ /** Install only account data; an unrelated MCP credential file is never copied or deleted. */
156
+ function codexInstalledSnapshot(before, candidate) {
157
+ const login = codexLoginOnly(candidate);
158
+ if (login.legacyRaw && before.legacyRaw && !isLegacyCodexLogin(before.legacyRaw)) {
159
+ throw new Error('Legacy Codex credentials would replace MCP data; sign in with Codex again.');
160
+ }
161
+ return {
162
+ authRaw: login.authRaw,
163
+ legacyRaw: before.legacyRaw && !isLegacyCodexLogin(before.legacyRaw)
164
+ ? before.legacyRaw
165
+ : login.legacyRaw,
166
+ };
167
+ }
168
+ function writeFiles(home, snapshot) {
169
+ for (const [name, raw] of [
170
+ ['auth.json', snapshot.authRaw],
171
+ ['.credentials.json', snapshot.legacyRaw],
172
+ ]) {
173
+ const file = path.join(home, name);
174
+ // Do not rewrite even identical MCP data; its own refresh writer owns it.
175
+ if (readRaw(file) === raw)
176
+ continue;
177
+ if (raw === null)
178
+ fs.rmSync(file, { force: true });
179
+ else
180
+ (0, atomic_1.atomicWriteFileSync)(file, raw);
181
+ }
182
+ }
183
+ function writeInactiveCodexCredentialStore(home, candidate) {
184
+ if (canonicalCodexPath(home) === canonicalCodexPath((0, codexPaths_1.getSystemCodexHome)())) {
185
+ throw new Error('A profile credential writer cannot write the live Codex home.');
186
+ }
187
+ const before = readCodexCredentialStore(home);
188
+ if (before.status === 'error')
189
+ throw new Error('The Codex profile could not be read.');
190
+ const expected = codexInstalledSnapshot(before.snapshot, candidate);
191
+ writeFiles(home, expected);
192
+ if (!codexStoreMatches(readCodexCredentialStore(home), expected))
193
+ throw new Error('The Codex profile write did not verify.');
194
+ }
195
+ /** Internal capability: only the guarded installer in codexProfiles may call this. */
196
+ function writeLiveCodexCredentialStoreUnguarded(snapshot) {
197
+ const home = (0, codexPaths_1.getSystemCodexHome)();
198
+ if (isSidekickCodexHome(home))
199
+ throw new Error('Unset CODEX_HOME before changing the live login; it names a Sidekick-owned home.');
200
+ writeFiles(home, snapshot);
201
+ }
202
+ function removeCodexProfileCredentials(home, expected) {
203
+ if (canonicalCodexPath(home) === canonicalCodexPath((0, codexPaths_1.getSystemCodexHome)()))
204
+ throw new Error('Refusing to remove live Codex credentials.');
205
+ if (!codexStoreMatches(readCodexCredentialStore(home), expected))
206
+ throw new Error('The Codex profile changed; its credential was kept.');
207
+ if (expected.authRaw !== null)
208
+ fs.rmSync(path.join(home, 'auth.json'), { force: true });
209
+ if (isLegacyCodexLogin(expected.legacyRaw))
210
+ fs.rmSync(path.join(home, '.credentials.json'), { force: true });
211
+ if (readCodexCredentialStore(home).status !== 'absent')
212
+ throw new Error('The Codex profile credential could not be removed.');
213
+ }
@@ -0,0 +1,2 @@
1
+ import type { KeepAliveRunner } from './keepAlive';
2
+ export declare const runCodexKeepAlive: KeepAliveRunner;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCodexKeepAlive = void 0;
4
+ /** A bounded, inference-free official CLI refresh. Never expose child output. */
5
+ const child_process_1 = require("child_process");
6
+ const runCodexKeepAlive = (command, args, env, timeoutMs) => new Promise((resolve) => {
7
+ let child;
8
+ try {
9
+ child = (0, child_process_1.spawn)(command, args, {
10
+ env,
11
+ stdio: ['pipe', 'pipe', 'pipe'],
12
+ windowsHide: true,
13
+ cwd: env.CODEX_HOME,
14
+ });
15
+ }
16
+ catch {
17
+ resolve({ status: null, stdout: '' });
18
+ return;
19
+ }
20
+ let buffer = '';
21
+ let bytes = 0;
22
+ let initialized = false;
23
+ let result = null;
24
+ let finishing = false;
25
+ let settled = false;
26
+ let killTimer;
27
+ const timer = setTimeout(() => finish(null), timeoutMs);
28
+ function finish(status) {
29
+ if (finishing)
30
+ return;
31
+ finishing = true;
32
+ result = status;
33
+ child.stdin?.end();
34
+ // The lease stays held until the child exits, including timeout/error paths.
35
+ killTimer = setTimeout(() => child.kill('SIGKILL'), Math.min(1000, timeoutMs));
36
+ if (status !== 0)
37
+ child.kill('SIGKILL');
38
+ }
39
+ function settle() {
40
+ if (settled)
41
+ return;
42
+ settled = true;
43
+ clearTimeout(timer);
44
+ if (killTimer)
45
+ clearTimeout(killTimer);
46
+ child.stdin?.destroy();
47
+ child.stdout?.destroy();
48
+ child.stderr?.destroy();
49
+ resolve({ status: result, stdout: '' });
50
+ }
51
+ const send = (message) => {
52
+ if (!child.stdin?.writable) {
53
+ finish(null);
54
+ return;
55
+ }
56
+ child.stdin.write(JSON.stringify(message) + '\n');
57
+ };
58
+ child.once('error', settle);
59
+ child.once('close', settle);
60
+ child.once('exit', settle);
61
+ child.stdin?.on('error', () => finish(null));
62
+ child.stderr?.on('data', () => {
63
+ /* Drain, never log possibly sensitive diagnostics. */
64
+ });
65
+ child.stdout?.setEncoding('utf8');
66
+ child.stdout?.on('data', (chunk) => {
67
+ if (finishing)
68
+ return;
69
+ bytes += Buffer.byteLength(chunk);
70
+ if (bytes > 1024 * 1024) {
71
+ finish(null);
72
+ return;
73
+ }
74
+ buffer += chunk;
75
+ let newline;
76
+ while (!finishing && (newline = buffer.indexOf('\n')) >= 0) {
77
+ const line = buffer.slice(0, newline);
78
+ buffer = buffer.slice(newline + 1);
79
+ if (!line.trim())
80
+ continue;
81
+ let message;
82
+ try {
83
+ message = JSON.parse(line);
84
+ }
85
+ catch {
86
+ finish(null);
87
+ return;
88
+ }
89
+ if (!message || typeof message !== 'object') {
90
+ finish(null);
91
+ return;
92
+ }
93
+ if (message.id === 0 && !initialized) {
94
+ if (message.error || !message.result) {
95
+ finish(1);
96
+ return;
97
+ }
98
+ initialized = true;
99
+ send({ method: 'initialized', params: {} });
100
+ send({ method: 'account/read', id: 1, params: { refreshToken: true } });
101
+ }
102
+ else if (message.id === 1 && initialized) {
103
+ finish(!message.error && message.result?.account?.type === 'chatgpt' ? 0 : 1);
104
+ }
105
+ }
106
+ });
107
+ send({
108
+ method: 'initialize',
109
+ id: 0,
110
+ params: {
111
+ clientInfo: { name: 'sidekick_agent_hub', title: 'Sidekick Agent Hub', version: '1' },
112
+ capabilities: null,
113
+ },
114
+ });
115
+ });
116
+ exports.runCodexKeepAlive = runCodexKeepAlive;
@@ -8,10 +8,10 @@ export declare function getCodexProfileHome(profileId: string): string;
8
8
  export declare function getCodexProfileStatePath(profileId: string): string;
9
9
  export declare function ensureCodexProfileDirs(profileId: string): void;
10
10
  export declare function readFileOrNull(filePath: string): string | null;
11
- export type CodexCredentialStoreMode = 'file' | 'keyring' | 'auto' | 'ephemeral' | 'unknown';
11
+ export type CodexCredentialStoreMode = 'file' | 'keyring' | 'auto' | 'ephemeral' | 'unknown' | 'unreadable';
12
12
  /**
13
13
  * How the `codex` CLI persists credentials for a home, from its `config.toml`.
14
- * `unknown` means the key is absent (older CLIs: file; newer CLIs: auto).
14
+ * `unknown` means the key is absent (Codex defaults to file storage).
15
15
  */
16
16
  export declare function getCodexCredentialStoreMode(codexHome?: string): CodexCredentialStoreMode;
17
17
  /**
@@ -19,3 +19,9 @@ export declare function getCodexCredentialStoreMode(codexHome?: string): CodexCr
19
19
  * keyring entry sidekick could never swap. Adds the key when absent.
20
20
  */
21
21
  export declare function forceFileCredentialStore(configToml: string): string;
22
+ export declare function getCodexBackupPath(id: string, legacy?: boolean): string;
23
+ export declare function getCodexLoginsDir(): string;
24
+ export declare function getCodexLoginDir(id: string): string;
25
+ /** Legacy pending profiles remain readable until finalized or discarded. */
26
+ export declare function getCodexLoginHome(id: string): string;
27
+ export declare function getCodexLoginStatePath(id: string): string;
@@ -44,6 +44,11 @@ exports.ensureCodexProfileDirs = ensureCodexProfileDirs;
44
44
  exports.readFileOrNull = readFileOrNull;
45
45
  exports.getCodexCredentialStoreMode = getCodexCredentialStoreMode;
46
46
  exports.forceFileCredentialStore = forceFileCredentialStore;
47
+ exports.getCodexBackupPath = getCodexBackupPath;
48
+ exports.getCodexLoginsDir = getCodexLoginsDir;
49
+ exports.getCodexLoginDir = getCodexLoginDir;
50
+ exports.getCodexLoginHome = getCodexLoginHome;
51
+ exports.getCodexLoginStatePath = getCodexLoginStatePath;
47
52
  const fs = __importStar(require("fs"));
48
53
  const os = __importStar(require("os"));
49
54
  const path = __importStar(require("path"));
@@ -62,7 +67,12 @@ function getSystemCodexHome() {
62
67
  function getCodexProfilesDir() {
63
68
  return path.join((0, accountRegistry_1.getAccountsDir)(), 'codex', 'profiles');
64
69
  }
70
+ function assertCodexAccountId(id) {
71
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(id))
72
+ throw new Error('Invalid Codex account id.');
73
+ }
65
74
  function getCodexProfileDir(profileId) {
75
+ assertCodexAccountId(profileId);
66
76
  return path.join(getCodexProfilesDir(), profileId);
67
77
  }
68
78
  function getCodexProfileHome(profileId) {
@@ -82,26 +92,40 @@ function readFileOrNull(filePath) {
82
92
  return null;
83
93
  }
84
94
  }
85
- const STORE_MODE_PATTERN = /^\s*cli_auth_credentials_store\s*=\s*"([a-z]+)"/m;
95
+ const STORE_MODE_PATTERN = /^[ \t]*cli_auth_credentials_store[ \t]*=[ \t]*["']([a-z]+)["']/m;
86
96
  /**
87
97
  * How the `codex` CLI persists credentials for a home, from its `config.toml`.
88
- * `unknown` means the key is absent (older CLIs: file; newer CLIs: auto).
98
+ * `unknown` means the key is absent (Codex defaults to file storage).
89
99
  */
90
100
  function getCodexCredentialStoreMode(codexHome = getSystemCodexHome()) {
91
- const config = readFileOrNull(path.join(codexHome, 'config.toml'));
92
- const match = config?.match(STORE_MODE_PATTERN);
101
+ let config;
102
+ try {
103
+ config = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8');
104
+ }
105
+ catch (error) {
106
+ return error.code === 'ENOENT' ? 'unknown' : 'unreadable';
107
+ }
108
+ const topLevel = config.split(/^[ \t]*\[/m, 1)[0];
109
+ const match = topLevel.match(STORE_MODE_PATTERN);
110
+ if (!match && /^[ \t]*cli_auth_credentials_store\s*=/m.test(topLevel))
111
+ return 'unreadable';
93
112
  const value = match?.[1];
94
113
  return value === 'file' || value === 'keyring' || value === 'auto' || value === 'ephemeral'
95
114
  ? value
96
- : 'unknown';
115
+ : value === undefined
116
+ ? 'unknown'
117
+ : 'unreadable';
97
118
  }
98
119
  /**
99
120
  * Rewrite a `config.toml` so an isolated login writes `auth.json` instead of a
100
121
  * keyring entry sidekick could never swap. Adds the key when absent.
101
122
  */
102
123
  function forceFileCredentialStore(configToml) {
103
- if (STORE_MODE_PATTERN.test(configToml)) {
104
- return configToml.replace(STORE_MODE_PATTERN, 'cli_auth_credentials_store = "file"');
124
+ const firstHeader = configToml.search(/^[ \t]*\[/m);
125
+ const header = firstHeader < 0 ? configToml : configToml.slice(0, firstHeader);
126
+ if (STORE_MODE_PATTERN.test(header)) {
127
+ return (header.replace(STORE_MODE_PATTERN, 'cli_auth_credentials_store = "file"') +
128
+ (firstHeader < 0 ? '' : configToml.slice(firstHeader)));
105
129
  }
106
130
  const line = 'cli_auth_credentials_store = "file"';
107
131
  const trimmed = configToml.replace(/\s*$/, '');
@@ -115,3 +139,23 @@ function forceFileCredentialStore(configToml) {
115
139
  const head = trimmed.slice(0, firstTable).replace(/\s*$/, '');
116
140
  return `${head ? `${head}\n` : ''}${line}\n\n${trimmed.slice(firstTable).replace(/^\s*/, '')}\n`;
117
141
  }
142
+ function getCodexBackupPath(id, legacy = false) {
143
+ assertCodexAccountId(id);
144
+ return path.join((0, accountRegistry_1.getAccountsDir)(), 'codex', 'backups', `${id}.${legacy ? 'credentials' : 'auth'}.json`);
145
+ }
146
+ function getCodexLoginsDir() {
147
+ return path.join((0, accountRegistry_1.getAccountsDir)(), 'codex', 'logins');
148
+ }
149
+ function getCodexLoginDir(id) {
150
+ assertCodexAccountId(id);
151
+ return path.join(getCodexLoginsDir(), id);
152
+ }
153
+ /** Legacy pending profiles remain readable until finalized or discarded. */
154
+ function getCodexLoginHome(id) {
155
+ const dir = getCodexLoginDir(id);
156
+ return fs.existsSync(dir) ? path.join(dir, 'codex-home') : getCodexProfileHome(id);
157
+ }
158
+ function getCodexLoginStatePath(id) {
159
+ const dir = getCodexLoginDir(id);
160
+ return fs.existsSync(dir) ? path.join(dir, 'profile.json') : getCodexProfileStatePath(id);
161
+ }
@@ -3,6 +3,8 @@ import type { AccountManagerResult } from './accounts';
3
3
  import { type AccountHealthSource } from './accountHealth';
4
4
  import { type SwitchAccountOptions, type SwitchAccountResult } from './accountSwitch';
5
5
  import { type ProviderSyncReport } from './accountSyncTypes';
6
+ import { type AccountConsumerScan } from './processDetection';
7
+ import { type CodexAuthSnapshot } from './codexCredentialStore';
6
8
  export { getCodexProfilesDir, getCodexProfileHome, getSystemCodexHome, getCodexCredentialStoreMode, } from './codexPaths';
7
9
  export type { CodexCredentialStoreMode } from './codexPaths';
8
10
  export interface CodexAccountManagerResult extends AccountManagerResult {
@@ -12,11 +14,11 @@ export interface CodexAccountManagerResult extends AccountManagerResult {
12
14
  }
13
15
  export declare const CODEX_KEYRING_FIX_HINT = "Set `cli_auth_credentials_store = \"file\"` in ~/.codex/config.toml and run `codex login` again.";
14
16
  export declare function getCodexMonitoringHomes(): string[];
15
- /**
16
- * The single chokepoint for storing a Codex credential in a profile: the raw
17
- * `auth.json` bytes (never re-serialized), the legacy `.credentials.json`
18
- * when present, and the secret-free health sidecar.
19
- */
17
+ /** Read the freshest saved login without creating a CLI-usable copy. */
18
+ export declare function readStoredCodexAccount(profileId: string): CodexAuthSnapshot | null;
19
+ export declare function isLiveCodexProfile(profile: SavedAccountProfile): boolean;
20
+ /** Caller holds the swap lock. Live-account credentials only enter private storage. */
21
+ export declare function storeCodexProfileAuthUnlocked(profileId: string, authRaw: string | null, legacyRaw: string | null, source: AccountHealthSource): void;
20
22
  export declare function storeCodexProfileAuth(profileId: string, authRaw: string | null, legacyRaw: string | null, source: AccountHealthSource): void;
21
23
  interface CodexLoginStatus {
22
24
  loggedIn: boolean;
@@ -34,7 +36,6 @@ export declare function readCodexAccountMetadata(codexHome: string): AccountIden
34
36
  /** {@link readCodexAccountMetadata} without blocking the event loop on the CLI probe. */
35
37
  export declare function readCodexAccountMetadataAsync(codexHome: string): Promise<AccountIdentityMetadata>;
36
38
  export declare function isCodexProfileAuthenticated(codexHome: string): boolean;
37
- /** {@link isCodexProfileAuthenticated} without blocking the event loop on the CLI probe. */
38
39
  export declare function isCodexProfileAuthenticatedAsync(codexHome: string): Promise<boolean>;
39
40
  export declare function listCodexAccounts(): SavedAccountProfile[];
40
41
  export declare function getActiveCodexAccount(): SavedAccountProfile | null;
@@ -56,13 +57,18 @@ export declare function resolveActiveCodexAccount(options?: {
56
57
  selfHeal?: boolean;
57
58
  }): ResolvedActiveAccount;
58
59
  export declare function getCodexExecutionEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
59
- export declare function prepareCodexAccount(label: string): CodexAccountManagerResult;
60
- /** {@link prepareCodexAccount} without blocking the event loop on CLI probes. */
61
- export declare function prepareCodexAccountAsync(label: string): Promise<CodexAccountManagerResult>;
60
+ /** Legacy callers can import current; explicit sign-in passes importCurrent: false. */
61
+ export declare function prepareCodexAccount(label: string, options?: {
62
+ importCurrent?: boolean;
63
+ }): CodexAccountManagerResult;
64
+ export declare function prepareCodexAccountAsync(label: string, options?: {
65
+ importCurrent?: boolean;
66
+ }): Promise<CodexAccountManagerResult>;
67
+ /** Removes only a pending login, never a registered canonical profile. */
68
+ export declare function discardCodexLogin(id: string): void;
62
69
  export declare function finalizeCodexAccount(profileId: string, opts?: {
63
70
  activate?: boolean;
64
71
  }): CodexAccountManagerResult;
65
- /** {@link finalizeCodexAccount} without blocking the event loop on CLI probes. */
66
72
  export declare function finalizeCodexAccountAsync(profileId: string, opts?: {
67
73
  activate?: boolean;
68
74
  }): Promise<CodexAccountManagerResult>;
@@ -75,33 +81,33 @@ export declare function finalizeCodexAccountAsync(profileId: string, opts?: {
75
81
  * child process while holding it.
76
82
  */
77
83
  export declare function withCodexAuthSwapLock<T>(operation: () => T): T;
78
- /**
79
- * Merge duplicate Codex profiles (same workspace, else same email). The oldest
80
- * profile keeps its id and label, receives the freshest `auth.json` and the
81
- * quota snapshot, and the active pointer; losers are stashed, never deleted.
82
- * Returns the removed ids. Caller holds the swap lock.
83
- */
84
- export declare function dedupeCodexProfiles(): string[];
85
- /**
86
- * Fold the live `auth.json` into the saved profiles: register an unknown login
87
- * (label = email), refresh the matching profile's backup when the live file is
88
- * newer, and re-point the active pointer at it. Caller holds the swap lock.
89
- * Never throws; problems land in `warnings`.
90
- */
84
+ /** Merge owned profiles only after preserving their newest login in a verified backup. */
85
+ export declare function dedupeCodexProfiles(scan?: AccountConsumerScan): string[];
86
+ export declare function hasPendingCodexRepair(): boolean;
87
+ /** No subprocesses inside this function: hosts resolve process scans before taking the lock. */
91
88
  export declare function syncCodexLiveStateUnlocked(source?: AccountHealthSource, probes?: {
89
+ scan?: AccountConsumerScan;
92
90
  systemKeyringLoggedIn?: boolean;
93
91
  }): ProviderSyncReport;
94
- /** {@link syncCodexLiveStateUnlocked} under the swap lock. */
95
92
  export declare function syncCodexLiveState(source?: AccountHealthSource): ProviderSyncReport;
96
- /**
97
- * Remove isolated login homes that never authenticated once they are older
98
- * than `olderThanMs`. Returns the removed profile ids.
99
- */
100
- export declare function cleanupAbandonedCodexLogins(olderThanMs?: number, now?: number): string[];
93
+ export declare function syncCodexLiveStateAsync(source?: AccountHealthSource): Promise<ProviderSyncReport>;
94
+ /** Only abandoned Sidekick-owned login homes; authenticated copies are preserved privately first. */
95
+ export declare function cleanupAbandonedCodexLogins(olderThanMs?: number, now?: number, scan?: AccountConsumerScan): string[];
101
96
  export declare function switchToCodexAccount(profileId: string, options?: SwitchAccountOptions): SwitchAccountResult;
102
- /** {@link switchToCodexAccount} without blocking the event loop on CLI probes. */
103
97
  export declare function switchToCodexAccountAsync(profileId: string, options?: SwitchAccountOptions): Promise<SwitchAccountResult>;
98
+ /** Legacy migration entry points now use the same guarded reconciliation as ordinary sync. */
104
99
  export declare function reconcileCodexAuthState(): void;
105
- /** {@link reconcileCodexAuthState} without blocking the event loop on CLI probes. */
106
100
  export declare function reconcileCodexAuthStateAsync(): Promise<void>;
101
+ export declare function getCodexKeepAliveBlocker(id: string): string | null;
102
+ export declare function beginCodexKeepAlive(id: string, leaseMs: number): {
103
+ ok: true;
104
+ } | {
105
+ ok: false;
106
+ reason: string;
107
+ };
108
+ export declare function finishCodexKeepAlive(id: string): void;
109
+ export declare function restoreCodexProfileFromBackup(id: string, options?: {
110
+ restore?: boolean;
111
+ }): 'restored' | 'present' | 'live' | 'none';
107
112
  export declare function removeCodexAccount(profileId: string): AccountManagerResult;
113
+ export declare function cleanupAbandonedCodexLoginsAsync(olderThanMs?: number, now?: number): Promise<string[]>;