sidekick-shared 0.27.4 → 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.
- package/CHANGELOG.md +43 -0
- package/README.md +92 -45
- package/dist/accountHealth.d.ts +14 -0
- package/dist/accountHealth.js +61 -14
- package/dist/accountLaunch.d.ts +6 -0
- package/dist/accountLaunch.js +61 -4
- package/dist/accountLease.d.ts +5 -0
- package/dist/accountLease.js +98 -0
- package/dist/accountManager.d.ts +7 -0
- package/dist/accountManager.js +226 -89
- package/dist/accountSync.d.ts +1 -5
- package/dist/accountSync.js +22 -6
- package/dist/accountSyncTypes.d.ts +9 -0
- package/dist/accounts.d.ts +75 -18
- package/dist/accounts.js +712 -189
- package/dist/claudeCredentialDoctor.d.ts +50 -0
- package/dist/claudeCredentialDoctor.js +277 -0
- package/dist/claudeCredentialLedger.d.ts +15 -0
- package/dist/claudeCredentialLedger.js +130 -0
- package/dist/claudeCredentials.d.ts +20 -3
- package/dist/claudeCredentials.js +50 -3
- package/dist/claudeLiveGuard.d.ts +33 -0
- package/dist/claudeLiveGuard.js +46 -0
- package/dist/claudeProfiles.d.ts +14 -0
- package/dist/claudeProfiles.js +22 -0
- package/dist/claudeRefreshLock.d.ts +15 -0
- package/dist/claudeRefreshLock.js +145 -0
- package/dist/codexAuth.d.ts +4 -0
- package/dist/codexAuth.js +18 -0
- package/dist/codexCredentialDoctor.d.ts +33 -0
- package/dist/codexCredentialDoctor.js +302 -0
- package/dist/codexCredentialStore.d.ts +29 -0
- package/dist/codexCredentialStore.js +213 -0
- package/dist/codexKeepAlive.d.ts +2 -0
- package/dist/codexKeepAlive.js +116 -0
- package/dist/codexPaths.d.ts +8 -2
- package/dist/codexPaths.js +51 -7
- package/dist/codexProfiles.d.ts +37 -31
- package/dist/codexProfiles.js +842 -671
- package/dist/credentialIO.d.ts +67 -7
- package/dist/credentialIO.js +211 -47
- package/dist/ensureDefaultAccounts.js +1 -8
- package/dist/index.d.ts +17 -4
- package/dist/index.js +36 -11
- package/dist/keepAlive.d.ts +2 -1
- package/dist/keepAlive.js +116 -29
- package/dist/liveCredentialGuard.d.ts +28 -0
- package/dist/liveCredentialGuard.js +36 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.js +4 -2
- package/dist/processDetection.d.ts +10 -3
- package/dist/processDetection.js +44 -11
- package/dist/promptHistory.d.ts +66 -0
- package/dist/promptHistory.js +37 -18
- package/dist/schemas/accountManager.d.ts +22 -0
- package/dist/schemas/accountManager.js +4 -0
- package/dist/schemas/assistantTurn.d.ts +2 -2
- package/dist/sessionPromptHistory.d.ts +120 -0
- package/dist/sessionPromptHistory.js +278 -0
- package/dist/sessionPromptSignals.d.ts +56 -0
- package/dist/sessionPromptSignals.js +355 -0
- package/dist/terminalSync.d.ts +12 -2
- package/dist/terminalSync.js +27 -2
- 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,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;
|
package/dist/codexPaths.d.ts
CHANGED
|
@@ -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 (
|
|
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;
|
package/dist/codexPaths.js
CHANGED
|
@@ -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 =
|
|
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 (
|
|
98
|
+
* `unknown` means the key is absent (Codex defaults to file storage).
|
|
89
99
|
*/
|
|
90
100
|
function getCodexCredentialStoreMode(codexHome = getSystemCodexHome()) {
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
104
|
-
|
|
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
|
+
}
|
package/dist/codexProfiles.d.ts
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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[]>;
|