@withtruing/companion 0.13.1 → 0.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +107 -37
- package/dist/codex-binary.js +96 -0
- package/dist/codex-cli-caller.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,10 @@ workspace-task settings; one companion process polls all of them independently.
|
|
|
133
133
|
local development Console therefore does not replace or move projects already connected to the
|
|
134
134
|
hosted Console.
|
|
135
135
|
|
|
136
|
+
If a saved backend is unavailable, `run` prints the exact setup fix and skips only the affected
|
|
137
|
+
Console, project, or workspace-task poll. Other runnable pairings keep working; `run` exits with
|
|
138
|
+
an error only when no configured work remains runnable.
|
|
139
|
+
|
|
136
140
|
To remove one Console and all of its projects from this computer:
|
|
137
141
|
|
|
138
142
|
```bash
|
|
@@ -211,7 +215,7 @@ truing connect \
|
|
|
211
215
|
the companion's own Codex home. This is separate from your normal Codex login:
|
|
212
216
|
|
|
213
217
|
```powershell
|
|
214
|
-
New-Item -ItemType Directory -Force "$HOME\.product-console-companion\codex-home" | Out-Null; $env:CODEX_HOME="$HOME\.product-console-companion\codex-home"; codex login; Remove-Item Env:CODEX_HOME
|
|
218
|
+
New-Item -ItemType Directory -Force "$HOME\.product-console-companion\codex-home" | Out-Null; $env:CODEX_HOME="$HOME\.product-console-companion\codex-home"; codex.cmd login; Remove-Item Env:CODEX_HOME
|
|
215
219
|
```
|
|
216
220
|
|
|
217
221
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -404,6 +404,23 @@ export function backendRequirements(backends) {
|
|
|
404
404
|
needsCli: backends.some((backend) => backend === 'cli'),
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
+
export function partitionPairingsByAvailability(pairings, availability) {
|
|
408
|
+
const partitions = pairings.map((pairing) => {
|
|
409
|
+
const unavailableConnections = new Set(pairing.connections
|
|
410
|
+
.filter((connection) => !availability[connection.backend])
|
|
411
|
+
.map((connection) => connection.connectionId));
|
|
412
|
+
const workspaceTasksUnavailable = Boolean(pairing.pollWorkspaceTasks && !availability[pairing.workspaceBackend ?? 'api-key']);
|
|
413
|
+
return { pairing, unavailableConnections, workspaceTasksUnavailable };
|
|
414
|
+
});
|
|
415
|
+
const isRunnable = ({ pairing }) => pairing.connections.some((connection) => connection.backend === 'codex' || availability[connection.backend])
|
|
416
|
+
|| Boolean(pairing.pollWorkspaceTasks
|
|
417
|
+
&& (pairing.workspaceBackend === 'codex' || availability[pairing.workspaceBackend ?? 'api-key']));
|
|
418
|
+
return {
|
|
419
|
+
pairings: partitions,
|
|
420
|
+
runnablePairings: partitions.filter(isRunnable),
|
|
421
|
+
unavailablePairings: partitions.filter((partition) => !isRunnable(partition)),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
407
424
|
export function createBackendRuntime(config, anthropic, createCodexTextModel = codexTextModel) {
|
|
408
425
|
let usage = null;
|
|
409
426
|
const codexOptions = config.backend === 'codex'
|
|
@@ -444,7 +461,7 @@ export function codexReadinessLines(status) {
|
|
|
444
461
|
}
|
|
445
462
|
return [
|
|
446
463
|
'Codex needs a one-time login for the companion\'s own home:',
|
|
447
|
-
' Windows PowerShell: New-Item -ItemType Directory -Force "$HOME\\.product-console-companion\\codex-home" | Out-Null; $env:CODEX_HOME="$HOME\\.product-console-companion\\codex-home"; codex login; Remove-Item Env:CODEX_HOME',
|
|
464
|
+
' Windows PowerShell: New-Item -ItemType Directory -Force "$HOME\\.product-console-companion\\codex-home" | Out-Null; $env:CODEX_HOME="$HOME\\.product-console-companion\\codex-home"; codex.cmd login; Remove-Item Env:CODEX_HOME',
|
|
448
465
|
' macOS/Linux: mkdir -p "$HOME/.product-console-companion/codex-home" && CODEX_HOME="$HOME/.product-console-companion/codex-home" codex login',
|
|
449
466
|
'This separate login keeps your own ~/.codex instructions out of the companion\'s calls.',
|
|
450
467
|
];
|
|
@@ -886,7 +903,7 @@ async function runPairing(pairing, options) {
|
|
|
886
903
|
connection.connectionId,
|
|
887
904
|
createBackendRuntime(connection, options.anthropic),
|
|
888
905
|
]));
|
|
889
|
-
const workspaceRuntime = pairing.pollWorkspaceTasks &&
|
|
906
|
+
const workspaceRuntime = pairing.pollWorkspaceTasks && !options.workspaceTasksUnavailable
|
|
890
907
|
? createBackendRuntime({ backend: workspaceBackend, codexModel: pairing.codexModel }, options.anthropic)
|
|
891
908
|
: null;
|
|
892
909
|
const repositoryReadiness = new Map();
|
|
@@ -897,7 +914,7 @@ async function runPairing(pairing, options) {
|
|
|
897
914
|
});
|
|
898
915
|
}
|
|
899
916
|
if (activeConnections.length !== pairing.connections.length && activeConnections.length > 0) {
|
|
900
|
-
error('Skipping investigation polling while a configured
|
|
917
|
+
error('Skipping investigation polling while a configured connection backend is unavailable, so the companion cannot claim an investigation it cannot run.');
|
|
901
918
|
}
|
|
902
919
|
const projectCount = activeConnections.length;
|
|
903
920
|
const watching = workspaceRuntime
|
|
@@ -1116,53 +1133,106 @@ async function cmdRun(options = {}) {
|
|
|
1116
1133
|
process.exitCode = 1;
|
|
1117
1134
|
return;
|
|
1118
1135
|
}
|
|
1119
|
-
const
|
|
1120
|
-
if (!config ||
|
|
1136
|
+
const configuredPairings = config?.pairings.filter((pairing) => pairing.connections.length > 0 || pairing.pollWorkspaceTasks) ?? [];
|
|
1137
|
+
if (!config || configuredPairings.length === 0) {
|
|
1121
1138
|
console.error(options.safeStartupDiagnostics
|
|
1122
1139
|
? 'Nothing is set up yet. Run connect first.'
|
|
1123
1140
|
: `Nothing set up yet — ${configPath} is missing or empty. Run "connect" first.`);
|
|
1124
1141
|
process.exitCode = 1;
|
|
1125
1142
|
return;
|
|
1126
1143
|
}
|
|
1127
|
-
const allBackends =
|
|
1144
|
+
const allBackends = configuredPairings.flatMap((pairing) => [
|
|
1128
1145
|
...pairing.connections.map((connection) => connection.backend),
|
|
1129
1146
|
...(pairing.pollWorkspaceTasks ? [pairing.workspaceBackend ?? 'api-key'] : []),
|
|
1130
1147
|
]);
|
|
1131
1148
|
const { needsApiKey, needsCli } = backendRequirements(allBackends);
|
|
1132
1149
|
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (needsCli && !await checkClaudeCliAvailable()) {
|
|
1141
|
-
console.error('This needs Claude Code installed and you logged into it first: npm install -g @anthropic-ai/claude-code, then run `claude` and follow the login prompt.');
|
|
1142
|
-
process.exitCode = 1;
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
const unavailableConnections = new Set();
|
|
1150
|
+
const apiKeyMissingMessage = 'No Anthropic API key found. Get one free at console.anthropic.com, then either run '
|
|
1151
|
+
+ '"connect" again adding --api-key <your key>, or set it yourself: '
|
|
1152
|
+
+ '"set ANTHROPIC_API_KEY=<your key>" (Windows) / "export ANTHROPIC_API_KEY=<your key>" (Mac/Linux), then run "run" again.';
|
|
1153
|
+
const cliMissingMessage = 'This needs Claude Code installed and you logged into it first: npm install -g @anthropic-ai/claude-code, then run `claude` and follow the login prompt.';
|
|
1154
|
+
const cliAvailable = !needsCli || Boolean(await checkClaudeCliAvailable());
|
|
1146
1155
|
let codexAvailable = true;
|
|
1156
|
+
let codexStatus = null;
|
|
1147
1157
|
if (allBackends.includes('codex')) {
|
|
1148
1158
|
const status = await checkCodexLogin(defaultCodexHome());
|
|
1149
1159
|
codexAvailable = status === 'logged-in';
|
|
1150
|
-
if (!codexAvailable)
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1160
|
+
if (!codexAvailable)
|
|
1161
|
+
codexStatus = status;
|
|
1162
|
+
}
|
|
1163
|
+
const partitioned = partitionPairingsByAvailability(configuredPairings, {
|
|
1164
|
+
'api-key': !needsApiKey || Boolean(apiKey),
|
|
1165
|
+
cli: cliAvailable,
|
|
1166
|
+
codex: codexAvailable,
|
|
1167
|
+
});
|
|
1168
|
+
const distinctBackends = new Set(allBackends);
|
|
1169
|
+
if (partitioned.runnablePairings.length === 0 && configuredPairings.length === 1 && distinctBackends.size === 1) {
|
|
1170
|
+
const [backend] = distinctBackends;
|
|
1171
|
+
if (backend === 'api-key')
|
|
1172
|
+
console.error(apiKeyMissingMessage);
|
|
1173
|
+
if (backend === 'cli')
|
|
1174
|
+
console.error(cliMissingMessage);
|
|
1175
|
+
if (backend === 'codex' && codexStatus) {
|
|
1176
|
+
logCodexReadiness(codexStatus);
|
|
1177
|
+
for (const connection of configuredPairings[0].connections) {
|
|
1178
|
+
console.error(`Skipping ${connection.connectionId}: Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed for this connection.`);
|
|
1162
1179
|
}
|
|
1180
|
+
if (configuredPairings[0].pollWorkspaceTasks) {
|
|
1181
|
+
console.error(`Skipping workspace-level tasks for ${configuredPairings[0].url}: Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed.`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
process.exitCode = 1;
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (codexStatus)
|
|
1188
|
+
logCodexReadiness(codexStatus);
|
|
1189
|
+
const backendUnavailableMessage = (backend) => {
|
|
1190
|
+
if (backend === 'api-key')
|
|
1191
|
+
return apiKeyMissingMessage;
|
|
1192
|
+
if (backend === 'cli')
|
|
1193
|
+
return cliMissingMessage;
|
|
1194
|
+
return `Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed.`;
|
|
1195
|
+
};
|
|
1196
|
+
const fullyUnavailablePairings = new Set(partitioned.unavailablePairings);
|
|
1197
|
+
for (const partition of partitioned.pairings) {
|
|
1198
|
+
const { pairing, unavailableConnections, workspaceTasksUnavailable } = partition;
|
|
1199
|
+
const pairingLabel = `${pairing.url}${pairing.workspaceId ? ` (workspace ${pairing.workspaceId})` : ''}`;
|
|
1200
|
+
if (fullyUnavailablePairings.has(partition)) {
|
|
1201
|
+
const unavailableBackends = new Set(pairing.connections.map((connection) => connection.backend));
|
|
1202
|
+
if (pairing.pollWorkspaceTasks)
|
|
1203
|
+
unavailableBackends.add(pairing.workspaceBackend ?? 'api-key');
|
|
1204
|
+
for (const backend of unavailableBackends) {
|
|
1205
|
+
console.error(`Skipping ${pairingLabel}: ${backendUnavailableMessage(backend)}`);
|
|
1206
|
+
}
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
for (const connection of pairing.connections) {
|
|
1210
|
+
if (!unavailableConnections.has(connection.connectionId))
|
|
1211
|
+
continue;
|
|
1212
|
+
console.error(connection.backend === 'codex'
|
|
1213
|
+
? `Skipping ${connection.connectionId}: ${backendUnavailableMessage(connection.backend)}`
|
|
1214
|
+
: `Skipping ${connection.connectionId} for ${pairingLabel}: ${backendUnavailableMessage(connection.backend)}`);
|
|
1215
|
+
}
|
|
1216
|
+
if (workspaceTasksUnavailable) {
|
|
1217
|
+
const workspaceBackend = pairing.workspaceBackend ?? 'api-key';
|
|
1218
|
+
console.error(workspaceBackend === 'codex'
|
|
1219
|
+
? `Skipping workspace-level tasks for ${pairing.url}: ${backendUnavailableMessage(workspaceBackend)}`
|
|
1220
|
+
: `Skipping workspace-level tasks for ${pairingLabel}: ${backendUnavailableMessage(workspaceBackend)}`);
|
|
1163
1221
|
}
|
|
1164
1222
|
}
|
|
1165
|
-
|
|
1223
|
+
if (partitioned.runnablePairings.length === 0) {
|
|
1224
|
+
process.exitCode = 1;
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const remainingBackends = partitioned.runnablePairings.flatMap(({ pairing, unavailableConnections, workspaceTasksUnavailable }) => [
|
|
1228
|
+
...pairing.connections
|
|
1229
|
+
.filter((connection) => !unavailableConnections.has(connection.connectionId))
|
|
1230
|
+
.map((connection) => connection.backend),
|
|
1231
|
+
...(pairing.pollWorkspaceTasks && !workspaceTasksUnavailable
|
|
1232
|
+
? [pairing.workspaceBackend ?? 'api-key']
|
|
1233
|
+
: []),
|
|
1234
|
+
]);
|
|
1235
|
+
const anthropic = backendRequirements(remainingBackends).needsApiKey ? new Anthropic({ apiKey }) : null;
|
|
1166
1236
|
const usageLimitPause = new UsageLimitPause();
|
|
1167
1237
|
let stopping = false;
|
|
1168
1238
|
process.on('SIGINT', () => {
|
|
@@ -1174,21 +1244,21 @@ async function cmdRun(options = {}) {
|
|
|
1174
1244
|
expectedExitReason = 'stopped by Ctrl+C';
|
|
1175
1245
|
stopping = true;
|
|
1176
1246
|
});
|
|
1177
|
-
let activePairings = runnablePairings.length;
|
|
1178
|
-
await Promise.all(runnablePairings.map(async (pairing) => {
|
|
1247
|
+
let activePairings = partitioned.runnablePairings.length;
|
|
1248
|
+
await Promise.all(partitioned.runnablePairings.map(async ({ pairing, unavailableConnections, workspaceTasksUnavailable }) => {
|
|
1179
1249
|
try {
|
|
1180
1250
|
return await runPairing(pairing, {
|
|
1181
1251
|
...options,
|
|
1182
|
-
multiple: runnablePairings.length > 1,
|
|
1252
|
+
multiple: partitioned.runnablePairings.length > 1,
|
|
1183
1253
|
anthropic,
|
|
1184
1254
|
unavailableConnections,
|
|
1185
|
-
|
|
1255
|
+
workspaceTasksUnavailable,
|
|
1186
1256
|
usageLimitPause,
|
|
1187
1257
|
shouldStop: () => stopping,
|
|
1188
1258
|
});
|
|
1189
1259
|
}
|
|
1190
1260
|
catch (reason) {
|
|
1191
|
-
if (runnablePairings.length === 1)
|
|
1261
|
+
if (partitioned.runnablePairings.length === 1)
|
|
1192
1262
|
throw reason;
|
|
1193
1263
|
console.error(`${pairingPrefix(pairing)} The Console at ${pairing.url} stopped because of an unexpected error: ${reason instanceof Error ? reason.message : reason}`);
|
|
1194
1264
|
return 'stopped';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
|
+
const defaultFs = { existsSync, readFileSync };
|
|
4
|
+
let cachedCommand = null;
|
|
5
|
+
let cachedPath;
|
|
6
|
+
let cachedPathExt;
|
|
7
|
+
function envValue(env, name) {
|
|
8
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
9
|
+
return key ? env[key] : undefined;
|
|
10
|
+
}
|
|
11
|
+
function windowsPathEntries(env) {
|
|
12
|
+
return (envValue(env, 'PATH') ?? '')
|
|
13
|
+
.split(';')
|
|
14
|
+
.map((entry) => entry.trim().replace(/^"|"$/g, ''))
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
function windowsExtensions(env) {
|
|
18
|
+
const pathExt = envValue(env, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD';
|
|
19
|
+
return pathExt
|
|
20
|
+
.split(';')
|
|
21
|
+
.map((extension) => extension.trim().toLowerCase())
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map((extension) => extension.startsWith('.') ? extension : `.${extension}`);
|
|
24
|
+
}
|
|
25
|
+
function existingCandidate(directory, extension, fs) {
|
|
26
|
+
const lowerCaseCandidate = join(directory, `codex${extension.toLowerCase()}`);
|
|
27
|
+
if (fs.existsSync(lowerCaseCandidate))
|
|
28
|
+
return resolve(lowerCaseCandidate);
|
|
29
|
+
const originalCaseCandidate = join(directory, `codex${extension}`);
|
|
30
|
+
return fs.existsSync(originalCaseCandidate) ? resolve(originalCaseCandidate) : null;
|
|
31
|
+
}
|
|
32
|
+
function parsedShimScript(shimPath, fs) {
|
|
33
|
+
let contents;
|
|
34
|
+
try {
|
|
35
|
+
contents = fs.readFileSync(shimPath, 'utf8');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const shimDirectory = dirname(shimPath);
|
|
41
|
+
for (const match of contents.matchAll(/"([^"\r\n]*codex\.js)"/gi)) {
|
|
42
|
+
let target = match[1].replace(/%(?:~dp0|dp0)%?/gi, shimDirectory);
|
|
43
|
+
// Tests inject win32 while running on Linux CI; normalizing both separator forms keeps the
|
|
44
|
+
// resolver testable with a real temporary filesystem and is a no-op for native Windows paths.
|
|
45
|
+
target = target.replace(/[\\/]/g, sep);
|
|
46
|
+
if (!isAbsolute(target))
|
|
47
|
+
target = resolve(shimDirectory, target);
|
|
48
|
+
if (fs.existsSync(target))
|
|
49
|
+
return resolve(target);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function scriptForShim(shimPath, fs) {
|
|
54
|
+
const parsed = parsedShimScript(shimPath, fs);
|
|
55
|
+
if (parsed)
|
|
56
|
+
return parsed;
|
|
57
|
+
const conventional = join(dirname(shimPath), 'node_modules', '@openai', 'codex', 'bin', 'codex.js');
|
|
58
|
+
return fs.existsSync(conventional) ? resolve(conventional) : null;
|
|
59
|
+
}
|
|
60
|
+
function resolveUncached(platform, env, fs) {
|
|
61
|
+
if (platform !== 'win32')
|
|
62
|
+
return { command: 'codex', prefixArgs: [] };
|
|
63
|
+
const extensions = windowsExtensions(env);
|
|
64
|
+
for (const directory of windowsPathEntries(env)) {
|
|
65
|
+
for (const extension of extensions) {
|
|
66
|
+
if (extension !== '.cmd' && extension !== '.exe')
|
|
67
|
+
continue;
|
|
68
|
+
const candidate = existingCandidate(directory, extension, fs);
|
|
69
|
+
if (!candidate)
|
|
70
|
+
continue;
|
|
71
|
+
if (extension === '.exe')
|
|
72
|
+
return { command: candidate, prefixArgs: [] };
|
|
73
|
+
const script = scriptForShim(candidate, fs);
|
|
74
|
+
if (script)
|
|
75
|
+
return { command: process.execPath, prefixArgs: [script] };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { command: 'codex', prefixArgs: [] };
|
|
79
|
+
}
|
|
80
|
+
/** Cache each production environment. Injected dependencies bypass the cache so Linux CI can
|
|
81
|
+
* exercise the Windows lookup rules without mutating process globals or depending on Windows
|
|
82
|
+
* filesystem semantics. */
|
|
83
|
+
export function resolveCodexCommand(options = {}) {
|
|
84
|
+
const injected = options.platform !== undefined || options.env !== undefined || options.fs !== undefined;
|
|
85
|
+
const path = envValue(process.env, 'PATH');
|
|
86
|
+
const pathExt = envValue(process.env, 'PATHEXT');
|
|
87
|
+
if (!injected && cachedCommand && path === cachedPath && pathExt === cachedPathExt)
|
|
88
|
+
return cachedCommand;
|
|
89
|
+
const command = resolveUncached(options.platform ?? process.platform, options.env ?? process.env, options.fs ?? defaultFs);
|
|
90
|
+
if (!injected) {
|
|
91
|
+
cachedCommand = command;
|
|
92
|
+
cachedPath = path;
|
|
93
|
+
cachedPathExt = pathExt;
|
|
94
|
+
}
|
|
95
|
+
return command;
|
|
96
|
+
}
|
package/dist/codex-cli-caller.js
CHANGED
|
@@ -9,6 +9,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:pat
|
|
|
9
9
|
import { createInterface } from 'node:readline';
|
|
10
10
|
import { StringDecoder } from 'node:string_decoder';
|
|
11
11
|
import { assertSingleLineArguments } from './cli-arguments.js';
|
|
12
|
+
import { resolveCodexCommand } from './codex-binary.js';
|
|
12
13
|
import { gitErrorMessage } from './sandbox.js';
|
|
13
14
|
import { stripIntroducedNulls, toCodexStrictSchema } from './strict-schema.js';
|
|
14
15
|
const WALL_CLOCK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -347,7 +348,8 @@ removeDirectory = rm) {
|
|
|
347
348
|
let firstUnparseableLineFirst = '';
|
|
348
349
|
let firstUnparseableLineLast = '';
|
|
349
350
|
const typeCounts = new Map();
|
|
350
|
-
const
|
|
351
|
+
const { command, prefixArgs } = resolveCodexCommand();
|
|
352
|
+
const child = spawn(command, [...prefixArgs, ...args], {
|
|
351
353
|
cwd: invocationCwd,
|
|
352
354
|
env: buildCodexEnvironment(codexHome),
|
|
353
355
|
windowsHide: true,
|
|
@@ -522,7 +524,8 @@ export async function checkCodexLogin(codexHome) {
|
|
|
522
524
|
throw error;
|
|
523
525
|
}
|
|
524
526
|
return new Promise((resolvePromise) => {
|
|
525
|
-
const
|
|
527
|
+
const { command, prefixArgs } = resolveCodexCommand();
|
|
528
|
+
const child = spawn(command, [...prefixArgs, 'login', 'status'], {
|
|
526
529
|
env: buildCodexEnvironment(validatedHome),
|
|
527
530
|
windowsHide: true,
|
|
528
531
|
});
|