codex-team 0.0.10 → 0.0.11
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 +2 -0
- package/dist/cli.cjs +396 -27
- package/dist/main.cjs +396 -27
- package/dist/main.js +394 -25
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { chmod, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
1
2
|
import { stderr, stdin, stdout as external_node_process_stdout } from "node:process";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
2
4
|
import dayjs from "dayjs";
|
|
3
5
|
import timezone from "dayjs/plugin/timezone.js";
|
|
4
6
|
import utc from "dayjs/plugin/utc.js";
|
|
5
7
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { chmod, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
|
-
import { basename, dirname, join } from "node:path";
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
var package_namespaceObject = {
|
|
12
|
-
rE: "0.0.
|
|
12
|
+
rE: "0.0.11"
|
|
13
13
|
};
|
|
14
14
|
function isRecord(value) {
|
|
15
15
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -502,7 +502,6 @@ async function fetchQuotaSnapshot(snapshot, options = {}) {
|
|
|
502
502
|
};
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
|
-
const account_store_execFile = promisify(execFile);
|
|
506
505
|
const DIRECTORY_MODE = 448;
|
|
507
506
|
const FILE_MODE = 384;
|
|
508
507
|
const SCHEMA_VERSION = 1;
|
|
@@ -575,25 +574,6 @@ function canAutoMigrateLegacyChatGPTMeta(meta, snapshot) {
|
|
|
575
574
|
if (!snapshotUserId) return false;
|
|
576
575
|
return meta.account_id === getSnapshotAccountId(snapshot);
|
|
577
576
|
}
|
|
578
|
-
async function detectRunningCodexProcesses() {
|
|
579
|
-
try {
|
|
580
|
-
const { stdout } = await account_store_execFile("ps", [
|
|
581
|
-
"-Ao",
|
|
582
|
-
"pid=,command="
|
|
583
|
-
]);
|
|
584
|
-
const pids = [];
|
|
585
|
-
for (const line of stdout.split("\n")){
|
|
586
|
-
const match = line.trim().match(/^(\d+)\s+(.+)$/);
|
|
587
|
-
if (!match) continue;
|
|
588
|
-
const pid = Number(match[1]);
|
|
589
|
-
const command = match[2];
|
|
590
|
-
if (pid !== process.pid && /(^|\s|\/)codex(\s|$)/.test(command) && !command.includes("codex-team")) pids.push(pid);
|
|
591
|
-
}
|
|
592
|
-
return pids;
|
|
593
|
-
} catch {
|
|
594
|
-
return [];
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
577
|
class AccountStore {
|
|
598
578
|
paths;
|
|
599
579
|
fetchImpl;
|
|
@@ -889,8 +869,6 @@ class AccountStore {
|
|
|
889
869
|
last_switched_account: name,
|
|
890
870
|
last_backup_path: backupPath
|
|
891
871
|
});
|
|
892
|
-
const runningCodexPids = await detectRunningCodexProcesses();
|
|
893
|
-
if (runningCodexPids.length > 0) warnings.push(`Detected running codex processes (${runningCodexPids.join(", ")}). Existing sessions may still hold the previous login state.`);
|
|
894
872
|
return {
|
|
895
873
|
account: await this.readManagedAccount(name),
|
|
896
874
|
warnings,
|
|
@@ -1064,6 +1042,250 @@ function createAccountStore(homeDir, options) {
|
|
|
1064
1042
|
fetchImpl: options?.fetchImpl
|
|
1065
1043
|
});
|
|
1066
1044
|
}
|
|
1045
|
+
const codex_desktop_launch_execFile = promisify(execFile);
|
|
1046
|
+
const DEFAULT_CODEX_REMOTE_DEBUGGING_PORT = 9223;
|
|
1047
|
+
const DEFAULT_CODEX_DESKTOP_STATE_PATH = join(homedir(), ".codex-team", "desktop-state.json");
|
|
1048
|
+
const CODEX_BINARY_SUFFIX = "/Contents/MacOS/Codex";
|
|
1049
|
+
const CODEX_APP_NAME = "Codex";
|
|
1050
|
+
const CODEX_LOCAL_HOST_ID = "local";
|
|
1051
|
+
const CODEX_APP_SERVER_RESTART_EXPRESSION = 'window.electronBridge.sendMessageFromView({ type: "codex-app-server-restart", hostId: "local" })';
|
|
1052
|
+
const DEVTOOLS_REQUEST_TIMEOUT_MS = 5000;
|
|
1053
|
+
function codex_desktop_launch_isRecord(value) {
|
|
1054
|
+
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
1055
|
+
}
|
|
1056
|
+
function isNonEmptyString(value) {
|
|
1057
|
+
return "string" == typeof value && "" !== value.trim();
|
|
1058
|
+
}
|
|
1059
|
+
async function codex_desktop_launch_delay(ms) {
|
|
1060
|
+
await new Promise((resolve)=>setTimeout(resolve, ms));
|
|
1061
|
+
}
|
|
1062
|
+
async function pathExistsViaStat(execFileImpl, path) {
|
|
1063
|
+
try {
|
|
1064
|
+
await execFileImpl("stat", [
|
|
1065
|
+
"-f",
|
|
1066
|
+
"%N",
|
|
1067
|
+
path
|
|
1068
|
+
]);
|
|
1069
|
+
return true;
|
|
1070
|
+
} catch {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
function parseManagedState(raw) {
|
|
1075
|
+
if ("" === raw.trim()) return null;
|
|
1076
|
+
let parsed;
|
|
1077
|
+
try {
|
|
1078
|
+
parsed = JSON.parse(raw);
|
|
1079
|
+
} catch {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
if (!codex_desktop_launch_isRecord(parsed)) return null;
|
|
1083
|
+
const pid = parsed.pid;
|
|
1084
|
+
const appPath = parsed.app_path;
|
|
1085
|
+
const remoteDebuggingPort = parsed.remote_debugging_port;
|
|
1086
|
+
const managedByCodexm = parsed.managed_by_codexm;
|
|
1087
|
+
const startedAt = parsed.started_at;
|
|
1088
|
+
if ("number" != typeof pid || !Number.isInteger(pid) || pid <= 0 || !isNonEmptyString(appPath) || "number" != typeof remoteDebuggingPort || !Number.isInteger(remoteDebuggingPort) || remoteDebuggingPort <= 0 || true !== managedByCodexm || !isNonEmptyString(startedAt)) return null;
|
|
1089
|
+
return {
|
|
1090
|
+
pid,
|
|
1091
|
+
app_path: appPath,
|
|
1092
|
+
remote_debugging_port: remoteDebuggingPort,
|
|
1093
|
+
managed_by_codexm: true,
|
|
1094
|
+
started_at: startedAt
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
async function ensureStateDirectory(statePath) {
|
|
1098
|
+
await mkdir(dirname(statePath), {
|
|
1099
|
+
recursive: true,
|
|
1100
|
+
mode: 448
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function createDefaultWebSocket(url) {
|
|
1104
|
+
return new WebSocket(url);
|
|
1105
|
+
}
|
|
1106
|
+
function isDevtoolsTarget(value) {
|
|
1107
|
+
return codex_desktop_launch_isRecord(value);
|
|
1108
|
+
}
|
|
1109
|
+
function isManagedDesktopProcess(runningApps, state) {
|
|
1110
|
+
const expectedBinaryPath = `${state.app_path}${CODEX_BINARY_SUFFIX}`;
|
|
1111
|
+
const expectedPort = `--remote-debugging-port=${state.remote_debugging_port}`;
|
|
1112
|
+
return runningApps.some((entry)=>entry.pid === state.pid && entry.command.includes(expectedBinaryPath) && entry.command.includes(expectedPort));
|
|
1113
|
+
}
|
|
1114
|
+
async function evaluateDevtoolsExpression(createWebSocketImpl, webSocketDebuggerUrl, expression) {
|
|
1115
|
+
const socket = createWebSocketImpl(webSocketDebuggerUrl);
|
|
1116
|
+
await new Promise((resolve, reject)=>{
|
|
1117
|
+
const requestId = 1;
|
|
1118
|
+
const timeout = setTimeout(()=>{
|
|
1119
|
+
cleanup();
|
|
1120
|
+
reject(new Error("Timed out waiting for Codex Desktop devtools response."));
|
|
1121
|
+
}, DEVTOOLS_REQUEST_TIMEOUT_MS);
|
|
1122
|
+
const cleanup = ()=>{
|
|
1123
|
+
clearTimeout(timeout);
|
|
1124
|
+
socket.onopen = null;
|
|
1125
|
+
socket.onmessage = null;
|
|
1126
|
+
socket.onerror = null;
|
|
1127
|
+
socket.onclose = null;
|
|
1128
|
+
socket.close();
|
|
1129
|
+
};
|
|
1130
|
+
socket.onopen = ()=>{
|
|
1131
|
+
socket.send(JSON.stringify({
|
|
1132
|
+
id: requestId,
|
|
1133
|
+
method: "Runtime.evaluate",
|
|
1134
|
+
params: {
|
|
1135
|
+
expression,
|
|
1136
|
+
awaitPromise: true
|
|
1137
|
+
}
|
|
1138
|
+
}));
|
|
1139
|
+
};
|
|
1140
|
+
socket.onmessage = (event)=>{
|
|
1141
|
+
if ("string" != typeof event.data) return;
|
|
1142
|
+
let payload;
|
|
1143
|
+
try {
|
|
1144
|
+
payload = JSON.parse(event.data);
|
|
1145
|
+
} catch {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (!codex_desktop_launch_isRecord(payload) || payload.id !== requestId) return;
|
|
1149
|
+
if (codex_desktop_launch_isRecord(payload.error)) {
|
|
1150
|
+
cleanup();
|
|
1151
|
+
reject(new Error(String(payload.error.message ?? "Codex Desktop devtools request failed.")));
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const result = codex_desktop_launch_isRecord(payload.result) ? payload.result : null;
|
|
1155
|
+
if (result && codex_desktop_launch_isRecord(result.exceptionDetails)) {
|
|
1156
|
+
cleanup();
|
|
1157
|
+
reject(new Error("Codex Desktop rejected the app-server restart request."));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
cleanup();
|
|
1161
|
+
resolve();
|
|
1162
|
+
};
|
|
1163
|
+
socket.onerror = ()=>{
|
|
1164
|
+
cleanup();
|
|
1165
|
+
reject(new Error("Failed to communicate with Codex Desktop devtools."));
|
|
1166
|
+
};
|
|
1167
|
+
socket.onclose = ()=>{
|
|
1168
|
+
cleanup();
|
|
1169
|
+
reject(new Error("Codex Desktop devtools connection closed before replying."));
|
|
1170
|
+
};
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
function createCodexDesktopLauncher(options = {}) {
|
|
1174
|
+
const execFileImpl = options.execFileImpl ?? codex_desktop_launch_execFile;
|
|
1175
|
+
const statePath = options.statePath ?? DEFAULT_CODEX_DESKTOP_STATE_PATH;
|
|
1176
|
+
const readFileImpl = options.readFileImpl ?? (async (path)=>readFile(path, "utf8"));
|
|
1177
|
+
const writeFileImpl = options.writeFileImpl ?? writeFile;
|
|
1178
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
1179
|
+
const createWebSocketImpl = options.createWebSocketImpl ?? createDefaultWebSocket;
|
|
1180
|
+
async function findInstalledApp() {
|
|
1181
|
+
const candidates = [
|
|
1182
|
+
"/Applications/Codex.app",
|
|
1183
|
+
join(homedir(), "Applications", "Codex.app")
|
|
1184
|
+
];
|
|
1185
|
+
for (const candidate of candidates)if (await pathExistsViaStat(execFileImpl, candidate)) return candidate;
|
|
1186
|
+
try {
|
|
1187
|
+
const { stdout } = await execFileImpl("mdfind", [
|
|
1188
|
+
'kMDItemFSName == "Codex.app"'
|
|
1189
|
+
]);
|
|
1190
|
+
for (const line of stdout.split("\n")){
|
|
1191
|
+
const candidate = line.trim();
|
|
1192
|
+
if ("" !== candidate) {
|
|
1193
|
+
if (await pathExistsViaStat(execFileImpl, candidate)) return candidate;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
} catch {}
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
async function listRunningApps() {
|
|
1200
|
+
const { stdout } = await execFileImpl("ps", [
|
|
1201
|
+
"-Ao",
|
|
1202
|
+
"pid=,command="
|
|
1203
|
+
]);
|
|
1204
|
+
const running = [];
|
|
1205
|
+
for (const line of stdout.split("\n")){
|
|
1206
|
+
const match = line.trim().match(/^(\d+)\s+(.+)$/);
|
|
1207
|
+
if (!match) continue;
|
|
1208
|
+
const pid = Number(match[1]);
|
|
1209
|
+
const command = match[2];
|
|
1210
|
+
if (pid !== process.pid && command.includes(CODEX_BINARY_SUFFIX)) running.push({
|
|
1211
|
+
pid,
|
|
1212
|
+
command
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return running;
|
|
1216
|
+
}
|
|
1217
|
+
async function quitRunningApps() {
|
|
1218
|
+
const running = await listRunningApps();
|
|
1219
|
+
if (0 === running.length) return;
|
|
1220
|
+
await execFileImpl("osascript", [
|
|
1221
|
+
"-e",
|
|
1222
|
+
`tell application "${CODEX_APP_NAME}" to quit`
|
|
1223
|
+
]);
|
|
1224
|
+
for(let attempt = 0; attempt < 10; attempt += 1){
|
|
1225
|
+
const remaining = await listRunningApps();
|
|
1226
|
+
if (0 === remaining.length) return;
|
|
1227
|
+
await codex_desktop_launch_delay(300);
|
|
1228
|
+
}
|
|
1229
|
+
throw new Error("Timed out waiting for Codex Desktop to quit.");
|
|
1230
|
+
}
|
|
1231
|
+
async function launch(appPath) {
|
|
1232
|
+
await execFileImpl("open", [
|
|
1233
|
+
"-na",
|
|
1234
|
+
appPath,
|
|
1235
|
+
"--args",
|
|
1236
|
+
`--remote-debugging-port=${DEFAULT_CODEX_REMOTE_DEBUGGING_PORT}`
|
|
1237
|
+
]);
|
|
1238
|
+
}
|
|
1239
|
+
async function readManagedState() {
|
|
1240
|
+
try {
|
|
1241
|
+
return parseManagedState(await readFileImpl(statePath));
|
|
1242
|
+
} catch {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async function writeManagedState(state) {
|
|
1247
|
+
await ensureStateDirectory(statePath);
|
|
1248
|
+
await writeFileImpl(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
1249
|
+
}
|
|
1250
|
+
async function clearManagedState() {
|
|
1251
|
+
await ensureStateDirectory(statePath);
|
|
1252
|
+
await writeFileImpl(statePath, "");
|
|
1253
|
+
}
|
|
1254
|
+
async function isManagedDesktopRunning() {
|
|
1255
|
+
const state = await readManagedState();
|
|
1256
|
+
if (!state) return false;
|
|
1257
|
+
const runningApps = await listRunningApps();
|
|
1258
|
+
return isManagedDesktopProcess(runningApps, state);
|
|
1259
|
+
}
|
|
1260
|
+
async function restartManagedAppServer() {
|
|
1261
|
+
const state = await readManagedState();
|
|
1262
|
+
if (!state) return false;
|
|
1263
|
+
const runningApps = await listRunningApps();
|
|
1264
|
+
if (!isManagedDesktopProcess(runningApps, state)) return false;
|
|
1265
|
+
const response = await fetchImpl(`http://127.0.0.1:${state.remote_debugging_port}/json/list`);
|
|
1266
|
+
if (!response.ok) throw new Error(`Failed to query Codex Desktop devtools targets (HTTP ${response.status}).`);
|
|
1267
|
+
const targets = await response.json();
|
|
1268
|
+
if (!Array.isArray(targets)) throw new Error("Codex Desktop devtools target list was not an array.");
|
|
1269
|
+
const localTarget = targets.find((target)=>{
|
|
1270
|
+
if (!isDevtoolsTarget(target)) return false;
|
|
1271
|
+
return "page" === target.type && target.url === `app://-/index.html?hostId=${CODEX_LOCAL_HOST_ID}` && isNonEmptyString(target.webSocketDebuggerUrl);
|
|
1272
|
+
});
|
|
1273
|
+
if (!localTarget || !isNonEmptyString(localTarget.webSocketDebuggerUrl)) throw new Error("Could not find the local Codex Desktop devtools target.");
|
|
1274
|
+
await evaluateDevtoolsExpression(createWebSocketImpl, localTarget.webSocketDebuggerUrl, CODEX_APP_SERVER_RESTART_EXPRESSION);
|
|
1275
|
+
return true;
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
findInstalledApp,
|
|
1279
|
+
listRunningApps,
|
|
1280
|
+
quitRunningApps,
|
|
1281
|
+
launch,
|
|
1282
|
+
readManagedState,
|
|
1283
|
+
writeManagedState,
|
|
1284
|
+
clearManagedState,
|
|
1285
|
+
isManagedDesktopRunning,
|
|
1286
|
+
restartManagedAppServer
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1067
1289
|
dayjs.extend(utc);
|
|
1068
1290
|
dayjs.extend(timezone);
|
|
1069
1291
|
function parseArgs(argv) {
|
|
@@ -1107,12 +1329,29 @@ Usage:
|
|
|
1107
1329
|
codexm update [--json]
|
|
1108
1330
|
codexm switch <name> [--json]
|
|
1109
1331
|
codexm switch --auto [--dry-run] [--json]
|
|
1332
|
+
codexm launch [name] [--json]
|
|
1110
1333
|
codexm remove <name> [--yes] [--json]
|
|
1111
1334
|
codexm rename <old> <new> [--json]
|
|
1112
1335
|
|
|
1113
1336
|
Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
1114
1337
|
`);
|
|
1115
1338
|
}
|
|
1339
|
+
function stripManagedDesktopWarning(warnings) {
|
|
1340
|
+
return warnings.filter((warning)=>!warning.startsWith("Detected running codex processes (") || !warning.endsWith("Existing sessions may still hold the previous login state."));
|
|
1341
|
+
}
|
|
1342
|
+
async function refreshManagedDesktopAfterSwitch(warnings, desktopLauncher) {
|
|
1343
|
+
try {
|
|
1344
|
+
if (await desktopLauncher.restartManagedAppServer()) return;
|
|
1345
|
+
} catch (error) {
|
|
1346
|
+
warnings.push(`Failed to refresh the running codexm-managed Codex Desktop session: ${error.message}`);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
try {
|
|
1350
|
+
const runningApps = await desktopLauncher.listRunningApps();
|
|
1351
|
+
if (0 === runningApps.length) return;
|
|
1352
|
+
warnings.push(`Detected running codex processes (${runningApps.map((app)=>app.pid).join(", ")}). Existing sessions may still hold the previous login state.`);
|
|
1353
|
+
} catch {}
|
|
1354
|
+
}
|
|
1116
1355
|
function describeCurrentStatus(status) {
|
|
1117
1356
|
const lines = [];
|
|
1118
1357
|
if (status.exists) {
|
|
@@ -1307,6 +1546,67 @@ async function confirmRemoval(name, streams) {
|
|
|
1307
1546
|
streams.stdin.on("data", onData);
|
|
1308
1547
|
});
|
|
1309
1548
|
}
|
|
1549
|
+
async function confirmDesktopRelaunch(streams) {
|
|
1550
|
+
if (!streams.stdin.isTTY) throw new Error("Refusing to relaunch Codex Desktop in a non-interactive terminal.");
|
|
1551
|
+
streams.stdout.write("Codex Desktop is already running. Close it and relaunch with the selected auth? [y/N] ");
|
|
1552
|
+
return await new Promise((resolve)=>{
|
|
1553
|
+
const cleanup = ()=>{
|
|
1554
|
+
streams.stdin.off("data", onData);
|
|
1555
|
+
streams.stdin.pause();
|
|
1556
|
+
};
|
|
1557
|
+
const onData = (buffer)=>{
|
|
1558
|
+
const answer = buffer.toString("utf8").trim().toLowerCase();
|
|
1559
|
+
cleanup();
|
|
1560
|
+
streams.stdout.write("\n");
|
|
1561
|
+
resolve("y" === answer || "yes" === answer);
|
|
1562
|
+
};
|
|
1563
|
+
streams.stdin.resume();
|
|
1564
|
+
streams.stdin.on("data", onData);
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
async function sleep(ms) {
|
|
1568
|
+
await new Promise((resolve)=>setTimeout(resolve, ms));
|
|
1569
|
+
}
|
|
1570
|
+
async function main_pathExists(path) {
|
|
1571
|
+
try {
|
|
1572
|
+
await stat(path);
|
|
1573
|
+
return true;
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
const nodeError = error;
|
|
1576
|
+
if ("ENOENT" === nodeError.code) return false;
|
|
1577
|
+
throw error;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function isRunningDesktopFromApp(app, appPath) {
|
|
1581
|
+
return app.command.includes(`${appPath}/Contents/MacOS/Codex`);
|
|
1582
|
+
}
|
|
1583
|
+
async function resolveManagedDesktopState(desktopLauncher, appPath, existingApps) {
|
|
1584
|
+
const existingPids = new Set(existingApps.map((app)=>app.pid));
|
|
1585
|
+
for(let attempt = 0; attempt < 10; attempt += 1){
|
|
1586
|
+
const runningApps = await desktopLauncher.listRunningApps();
|
|
1587
|
+
const launchedApp = runningApps.filter((app)=>isRunningDesktopFromApp(app, appPath) && !existingPids.has(app.pid)).sort((left, right)=>right.pid - left.pid)[0] ?? runningApps.filter((app)=>isRunningDesktopFromApp(app, appPath)).sort((left, right)=>right.pid - left.pid)[0] ?? null;
|
|
1588
|
+
if (launchedApp) return {
|
|
1589
|
+
pid: launchedApp.pid,
|
|
1590
|
+
app_path: appPath,
|
|
1591
|
+
remote_debugging_port: DEFAULT_CODEX_REMOTE_DEBUGGING_PORT,
|
|
1592
|
+
managed_by_codexm: true,
|
|
1593
|
+
started_at: new Date().toISOString()
|
|
1594
|
+
};
|
|
1595
|
+
await sleep(300);
|
|
1596
|
+
}
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
async function restoreLaunchBackup(store, backupPath) {
|
|
1600
|
+
if (backupPath && await main_pathExists(backupPath)) await copyFile(backupPath, store.paths.currentAuthPath);
|
|
1601
|
+
else await rm(store.paths.currentAuthPath, {
|
|
1602
|
+
force: true
|
|
1603
|
+
});
|
|
1604
|
+
const configBackupPath = join(store.paths.backupsDir, "last-active-config.toml");
|
|
1605
|
+
if (await main_pathExists(configBackupPath)) await copyFile(configBackupPath, store.paths.currentConfigPath);
|
|
1606
|
+
else await rm(store.paths.currentConfigPath, {
|
|
1607
|
+
force: true
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1310
1610
|
async function runCli(argv, options = {}) {
|
|
1311
1611
|
const streams = {
|
|
1312
1612
|
stdin: options.stdin ?? stdin,
|
|
@@ -1314,6 +1614,7 @@ async function runCli(argv, options = {}) {
|
|
|
1314
1614
|
stderr: options.stderr ?? stderr
|
|
1315
1615
|
};
|
|
1316
1616
|
const store = options.store ?? createAccountStore();
|
|
1617
|
+
const desktopLauncher = options.desktopLauncher ?? createCodexDesktopLauncher();
|
|
1317
1618
|
const parsed = parseArgs(argv);
|
|
1318
1619
|
const json = parsed.flags.has("--json");
|
|
1319
1620
|
try {
|
|
@@ -1446,6 +1747,8 @@ async function runCli(argv, options = {}) {
|
|
|
1446
1747
|
}
|
|
1447
1748
|
const result = await store.switchAccount(selected.name);
|
|
1448
1749
|
for (const warning of warnings)result.warnings.push(warning);
|
|
1750
|
+
result.warnings = stripManagedDesktopWarning(result.warnings);
|
|
1751
|
+
await refreshManagedDesktopAfterSwitch(result.warnings, desktopLauncher);
|
|
1449
1752
|
const payload = {
|
|
1450
1753
|
ok: true,
|
|
1451
1754
|
action: "switch",
|
|
@@ -1469,6 +1772,8 @@ async function runCli(argv, options = {}) {
|
|
|
1469
1772
|
}
|
|
1470
1773
|
if (!name) throw new Error("Usage: codexm switch <name>");
|
|
1471
1774
|
const result = await store.switchAccount(name);
|
|
1775
|
+
result.warnings = stripManagedDesktopWarning(result.warnings);
|
|
1776
|
+
await refreshManagedDesktopAfterSwitch(result.warnings, desktopLauncher);
|
|
1472
1777
|
let quota = null;
|
|
1473
1778
|
try {
|
|
1474
1779
|
await store.refreshQuotaForAccount(result.account.name);
|
|
@@ -1500,6 +1805,70 @@ async function runCli(argv, options = {}) {
|
|
|
1500
1805
|
}
|
|
1501
1806
|
return 0;
|
|
1502
1807
|
}
|
|
1808
|
+
case "launch":
|
|
1809
|
+
{
|
|
1810
|
+
const name = parsed.positionals[0] ?? null;
|
|
1811
|
+
if (parsed.positionals.length > 1) throw new Error("Usage: codexm launch [name] [--json]");
|
|
1812
|
+
const warnings = [];
|
|
1813
|
+
const appPath = await desktopLauncher.findInstalledApp();
|
|
1814
|
+
if (!appPath) throw new Error("Codex Desktop not found at /Applications/Codex.app.");
|
|
1815
|
+
const runningApps = await desktopLauncher.listRunningApps();
|
|
1816
|
+
if (runningApps.length > 0) {
|
|
1817
|
+
const confirmed = await confirmDesktopRelaunch(streams);
|
|
1818
|
+
if (!confirmed) {
|
|
1819
|
+
if (json) writeJson(streams.stdout, {
|
|
1820
|
+
ok: false,
|
|
1821
|
+
action: "launch",
|
|
1822
|
+
cancelled: true
|
|
1823
|
+
});
|
|
1824
|
+
else streams.stdout.write("Aborted.\n");
|
|
1825
|
+
return 1;
|
|
1826
|
+
}
|
|
1827
|
+
await desktopLauncher.quitRunningApps();
|
|
1828
|
+
}
|
|
1829
|
+
let switchedAccount = null;
|
|
1830
|
+
let switchBackupPath = null;
|
|
1831
|
+
if (name) {
|
|
1832
|
+
const switchResult = await store.switchAccount(name);
|
|
1833
|
+
warnings.push(...stripManagedDesktopWarning(switchResult.warnings));
|
|
1834
|
+
switchedAccount = switchResult.account;
|
|
1835
|
+
switchBackupPath = switchResult.backup_path;
|
|
1836
|
+
}
|
|
1837
|
+
try {
|
|
1838
|
+
await desktopLauncher.launch(appPath);
|
|
1839
|
+
const managedState = await resolveManagedDesktopState(desktopLauncher, appPath, runningApps);
|
|
1840
|
+
if (!managedState) {
|
|
1841
|
+
await desktopLauncher.clearManagedState().catch(()=>void 0);
|
|
1842
|
+
throw new Error("Failed to confirm the newly launched Codex Desktop process for managed-session tracking.");
|
|
1843
|
+
}
|
|
1844
|
+
await desktopLauncher.writeManagedState(managedState);
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
if (switchedAccount) await restoreLaunchBackup(store, switchBackupPath).catch(()=>void 0);
|
|
1847
|
+
throw error;
|
|
1848
|
+
}
|
|
1849
|
+
if (json) writeJson(streams.stdout, {
|
|
1850
|
+
ok: true,
|
|
1851
|
+
action: "launch",
|
|
1852
|
+
account: switchedAccount ? {
|
|
1853
|
+
name: switchedAccount.name,
|
|
1854
|
+
account_id: switchedAccount.account_id,
|
|
1855
|
+
user_id: switchedAccount.user_id ?? null,
|
|
1856
|
+
identity: switchedAccount.identity,
|
|
1857
|
+
auth_mode: switchedAccount.auth_mode
|
|
1858
|
+
} : null,
|
|
1859
|
+
launched_with_current_auth: null === switchedAccount,
|
|
1860
|
+
app_path: appPath,
|
|
1861
|
+
relaunched: runningApps.length > 0,
|
|
1862
|
+
warnings
|
|
1863
|
+
});
|
|
1864
|
+
else {
|
|
1865
|
+
if (switchedAccount) streams.stdout.write(`Switched to "${switchedAccount.name}" (${maskAccountId(switchedAccount.identity)}).\n`);
|
|
1866
|
+
if (runningApps.length > 0) streams.stdout.write("Closed existing Codex Desktop instance and launched a new one.\n");
|
|
1867
|
+
streams.stdout.write(switchedAccount ? `Launched Codex Desktop with "${switchedAccount.name}" (${maskAccountId(switchedAccount.identity)}).\n` : "Launched Codex Desktop with current auth.\n");
|
|
1868
|
+
for (const warning of warnings)streams.stdout.write(`Warning: ${warning}\n`);
|
|
1869
|
+
}
|
|
1870
|
+
return 0;
|
|
1871
|
+
}
|
|
1503
1872
|
case "remove":
|
|
1504
1873
|
{
|
|
1505
1874
|
const name = parsed.positionals[0];
|