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 CHANGED
@@ -22,11 +22,13 @@ codexm save <name>
22
22
  codexm update
23
23
  codexm switch <name>
24
24
  codexm switch --auto --dry-run
25
+ codexm launch [name] [--json]
25
26
  codexm remove <name> --yes
26
27
  codexm rename <old> <new>
27
28
  ```
28
29
 
29
30
  Use `--json` on query and mutation commands when you need machine-readable output.
31
+ `codexm launch` starts Codex Desktop with the current auth, or switches to a saved account first when you pass a name. If Codex Desktop is already running, `codexm launch` asks before relaunching it. If the running Desktop was started by `codexm launch`, later `codexm switch` attempts to refresh that session automatically; if it was started outside `codexm`, `codexm switch` still warns that existing sessions may keep the previous login state. This refresh works by restarting the Codex app server, so any in-progress thread in that Desktop session will be interrupted.
30
32
 
31
33
  ## Typical flow
32
34
 
package/dist/cli.cjs CHANGED
@@ -5,7 +5,9 @@ var __webpack_modules__ = {
5
5
  __webpack_require__.d(__webpack_exports__, {
6
6
  runCli: ()=>runCli
7
7
  });
8
+ const promises_namespaceObject = require("node:fs/promises");
8
9
  const external_node_process_namespaceObject = require("node:process");
10
+ const external_node_path_namespaceObject = require("node:path");
9
11
  const external_dayjs_namespaceObject = require("dayjs");
10
12
  var external_dayjs_default = /*#__PURE__*/ __webpack_require__.n(external_dayjs_namespaceObject);
11
13
  const timezone_js_namespaceObject = require("dayjs/plugin/timezone.js");
@@ -13,10 +15,9 @@ var __webpack_modules__ = {
13
15
  const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
14
16
  var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
15
17
  var package_namespaceObject = {
16
- rE: "0.0.10"
18
+ rE: "0.0.11"
17
19
  };
18
20
  const external_node_crypto_namespaceObject = require("node:crypto");
19
- const promises_namespaceObject = require("node:fs/promises");
20
21
  function isRecord(value) {
21
22
  return "object" == typeof value && null !== value && !Array.isArray(value);
22
23
  }
@@ -221,9 +222,6 @@ var __webpack_modules__ = {
221
222
  return parsed;
222
223
  }
223
224
  const external_node_os_namespaceObject = require("node:os");
224
- const external_node_path_namespaceObject = require("node:path");
225
- const external_node_child_process_namespaceObject = require("node:child_process");
226
- const external_node_util_namespaceObject = require("node:util");
227
225
  const DEFAULT_CHATGPT_BASE_URL = "https://chatgpt.com";
228
226
  const USER_AGENT = "codexm/0.1";
229
227
  const USAGE_FETCH_ATTEMPTS = 3;
@@ -512,7 +510,6 @@ var __webpack_modules__ = {
512
510
  };
513
511
  }
514
512
  }
515
- const execFile = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.execFile);
516
513
  const DIRECTORY_MODE = 448;
517
514
  const FILE_MODE = 384;
518
515
  const SCHEMA_VERSION = 1;
@@ -585,25 +582,6 @@ var __webpack_modules__ = {
585
582
  if (!snapshotUserId) return false;
586
583
  return meta.account_id === getSnapshotAccountId(snapshot);
587
584
  }
588
- async function detectRunningCodexProcesses() {
589
- try {
590
- const { stdout } = await execFile("ps", [
591
- "-Ao",
592
- "pid=,command="
593
- ]);
594
- const pids = [];
595
- for (const line of stdout.split("\n")){
596
- const match = line.trim().match(/^(\d+)\s+(.+)$/);
597
- if (!match) continue;
598
- const pid = Number(match[1]);
599
- const command = match[2];
600
- if (pid !== process.pid && /(^|\s|\/)codex(\s|$)/.test(command) && !command.includes("codex-team")) pids.push(pid);
601
- }
602
- return pids;
603
- } catch {
604
- return [];
605
- }
606
- }
607
585
  class AccountStore {
608
586
  paths;
609
587
  fetchImpl;
@@ -899,8 +877,6 @@ var __webpack_modules__ = {
899
877
  last_switched_account: name,
900
878
  last_backup_path: backupPath
901
879
  });
902
- const runningCodexPids = await detectRunningCodexProcesses();
903
- if (runningCodexPids.length > 0) warnings.push(`Detected running codex processes (${runningCodexPids.join(", ")}). Existing sessions may still hold the previous login state.`);
904
880
  return {
905
881
  account: await this.readManagedAccount(name),
906
882
  warnings,
@@ -1074,6 +1050,252 @@ var __webpack_modules__ = {
1074
1050
  fetchImpl: options?.fetchImpl
1075
1051
  });
1076
1052
  }
1053
+ const external_node_child_process_namespaceObject = require("node:child_process");
1054
+ const external_node_util_namespaceObject = require("node:util");
1055
+ const execFile = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.execFile);
1056
+ const DEFAULT_CODEX_REMOTE_DEBUGGING_PORT = 9223;
1057
+ const DEFAULT_CODEX_DESKTOP_STATE_PATH = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.homedir)(), ".codex-team", "desktop-state.json");
1058
+ const CODEX_BINARY_SUFFIX = "/Contents/MacOS/Codex";
1059
+ const CODEX_APP_NAME = "Codex";
1060
+ const CODEX_LOCAL_HOST_ID = "local";
1061
+ const CODEX_APP_SERVER_RESTART_EXPRESSION = 'window.electronBridge.sendMessageFromView({ type: "codex-app-server-restart", hostId: "local" })';
1062
+ const DEVTOOLS_REQUEST_TIMEOUT_MS = 5000;
1063
+ function codex_desktop_launch_isRecord(value) {
1064
+ return "object" == typeof value && null !== value && !Array.isArray(value);
1065
+ }
1066
+ function isNonEmptyString(value) {
1067
+ return "string" == typeof value && "" !== value.trim();
1068
+ }
1069
+ async function codex_desktop_launch_delay(ms) {
1070
+ await new Promise((resolve)=>setTimeout(resolve, ms));
1071
+ }
1072
+ async function pathExistsViaStat(execFileImpl, path) {
1073
+ try {
1074
+ await execFileImpl("stat", [
1075
+ "-f",
1076
+ "%N",
1077
+ path
1078
+ ]);
1079
+ return true;
1080
+ } catch {
1081
+ return false;
1082
+ }
1083
+ }
1084
+ function parseManagedState(raw) {
1085
+ if ("" === raw.trim()) return null;
1086
+ let parsed;
1087
+ try {
1088
+ parsed = JSON.parse(raw);
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ if (!codex_desktop_launch_isRecord(parsed)) return null;
1093
+ const pid = parsed.pid;
1094
+ const appPath = parsed.app_path;
1095
+ const remoteDebuggingPort = parsed.remote_debugging_port;
1096
+ const managedByCodexm = parsed.managed_by_codexm;
1097
+ const startedAt = parsed.started_at;
1098
+ 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;
1099
+ return {
1100
+ pid,
1101
+ app_path: appPath,
1102
+ remote_debugging_port: remoteDebuggingPort,
1103
+ managed_by_codexm: true,
1104
+ started_at: startedAt
1105
+ };
1106
+ }
1107
+ async function ensureStateDirectory(statePath) {
1108
+ await (0, promises_namespaceObject.mkdir)((0, external_node_path_namespaceObject.dirname)(statePath), {
1109
+ recursive: true,
1110
+ mode: 448
1111
+ });
1112
+ }
1113
+ function createDefaultWebSocket(url) {
1114
+ return new WebSocket(url);
1115
+ }
1116
+ function isDevtoolsTarget(value) {
1117
+ return codex_desktop_launch_isRecord(value);
1118
+ }
1119
+ function isManagedDesktopProcess(runningApps, state) {
1120
+ const expectedBinaryPath = `${state.app_path}${CODEX_BINARY_SUFFIX}`;
1121
+ const expectedPort = `--remote-debugging-port=${state.remote_debugging_port}`;
1122
+ return runningApps.some((entry)=>entry.pid === state.pid && entry.command.includes(expectedBinaryPath) && entry.command.includes(expectedPort));
1123
+ }
1124
+ async function evaluateDevtoolsExpression(createWebSocketImpl, webSocketDebuggerUrl, expression) {
1125
+ const socket = createWebSocketImpl(webSocketDebuggerUrl);
1126
+ await new Promise((resolve, reject)=>{
1127
+ const requestId = 1;
1128
+ const timeout = setTimeout(()=>{
1129
+ cleanup();
1130
+ reject(new Error("Timed out waiting for Codex Desktop devtools response."));
1131
+ }, DEVTOOLS_REQUEST_TIMEOUT_MS);
1132
+ const cleanup = ()=>{
1133
+ clearTimeout(timeout);
1134
+ socket.onopen = null;
1135
+ socket.onmessage = null;
1136
+ socket.onerror = null;
1137
+ socket.onclose = null;
1138
+ socket.close();
1139
+ };
1140
+ socket.onopen = ()=>{
1141
+ socket.send(JSON.stringify({
1142
+ id: requestId,
1143
+ method: "Runtime.evaluate",
1144
+ params: {
1145
+ expression,
1146
+ awaitPromise: true
1147
+ }
1148
+ }));
1149
+ };
1150
+ socket.onmessage = (event)=>{
1151
+ if ("string" != typeof event.data) return;
1152
+ let payload;
1153
+ try {
1154
+ payload = JSON.parse(event.data);
1155
+ } catch {
1156
+ return;
1157
+ }
1158
+ if (!codex_desktop_launch_isRecord(payload) || payload.id !== requestId) return;
1159
+ if (codex_desktop_launch_isRecord(payload.error)) {
1160
+ cleanup();
1161
+ reject(new Error(String(payload.error.message ?? "Codex Desktop devtools request failed.")));
1162
+ return;
1163
+ }
1164
+ const result = codex_desktop_launch_isRecord(payload.result) ? payload.result : null;
1165
+ if (result && codex_desktop_launch_isRecord(result.exceptionDetails)) {
1166
+ cleanup();
1167
+ reject(new Error("Codex Desktop rejected the app-server restart request."));
1168
+ return;
1169
+ }
1170
+ cleanup();
1171
+ resolve();
1172
+ };
1173
+ socket.onerror = ()=>{
1174
+ cleanup();
1175
+ reject(new Error("Failed to communicate with Codex Desktop devtools."));
1176
+ };
1177
+ socket.onclose = ()=>{
1178
+ cleanup();
1179
+ reject(new Error("Codex Desktop devtools connection closed before replying."));
1180
+ };
1181
+ });
1182
+ }
1183
+ function createCodexDesktopLauncher(options = {}) {
1184
+ const execFileImpl = options.execFileImpl ?? execFile;
1185
+ const statePath = options.statePath ?? DEFAULT_CODEX_DESKTOP_STATE_PATH;
1186
+ const readFileImpl = options.readFileImpl ?? (async (path)=>(0, promises_namespaceObject.readFile)(path, "utf8"));
1187
+ const writeFileImpl = options.writeFileImpl ?? promises_namespaceObject.writeFile;
1188
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
1189
+ const createWebSocketImpl = options.createWebSocketImpl ?? createDefaultWebSocket;
1190
+ async function findInstalledApp() {
1191
+ const candidates = [
1192
+ "/Applications/Codex.app",
1193
+ (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.homedir)(), "Applications", "Codex.app")
1194
+ ];
1195
+ for (const candidate of candidates)if (await pathExistsViaStat(execFileImpl, candidate)) return candidate;
1196
+ try {
1197
+ const { stdout } = await execFileImpl("mdfind", [
1198
+ 'kMDItemFSName == "Codex.app"'
1199
+ ]);
1200
+ for (const line of stdout.split("\n")){
1201
+ const candidate = line.trim();
1202
+ if ("" !== candidate) {
1203
+ if (await pathExistsViaStat(execFileImpl, candidate)) return candidate;
1204
+ }
1205
+ }
1206
+ } catch {}
1207
+ return null;
1208
+ }
1209
+ async function listRunningApps() {
1210
+ const { stdout } = await execFileImpl("ps", [
1211
+ "-Ao",
1212
+ "pid=,command="
1213
+ ]);
1214
+ const running = [];
1215
+ for (const line of stdout.split("\n")){
1216
+ const match = line.trim().match(/^(\d+)\s+(.+)$/);
1217
+ if (!match) continue;
1218
+ const pid = Number(match[1]);
1219
+ const command = match[2];
1220
+ if (pid !== process.pid && command.includes(CODEX_BINARY_SUFFIX)) running.push({
1221
+ pid,
1222
+ command
1223
+ });
1224
+ }
1225
+ return running;
1226
+ }
1227
+ async function quitRunningApps() {
1228
+ const running = await listRunningApps();
1229
+ if (0 === running.length) return;
1230
+ await execFileImpl("osascript", [
1231
+ "-e",
1232
+ `tell application "${CODEX_APP_NAME}" to quit`
1233
+ ]);
1234
+ for(let attempt = 0; attempt < 10; attempt += 1){
1235
+ const remaining = await listRunningApps();
1236
+ if (0 === remaining.length) return;
1237
+ await codex_desktop_launch_delay(300);
1238
+ }
1239
+ throw new Error("Timed out waiting for Codex Desktop to quit.");
1240
+ }
1241
+ async function launch(appPath) {
1242
+ await execFileImpl("open", [
1243
+ "-na",
1244
+ appPath,
1245
+ "--args",
1246
+ `--remote-debugging-port=${DEFAULT_CODEX_REMOTE_DEBUGGING_PORT}`
1247
+ ]);
1248
+ }
1249
+ async function readManagedState() {
1250
+ try {
1251
+ return parseManagedState(await readFileImpl(statePath));
1252
+ } catch {
1253
+ return null;
1254
+ }
1255
+ }
1256
+ async function writeManagedState(state) {
1257
+ await ensureStateDirectory(statePath);
1258
+ await writeFileImpl(statePath, `${JSON.stringify(state, null, 2)}\n`);
1259
+ }
1260
+ async function clearManagedState() {
1261
+ await ensureStateDirectory(statePath);
1262
+ await writeFileImpl(statePath, "");
1263
+ }
1264
+ async function isManagedDesktopRunning() {
1265
+ const state = await readManagedState();
1266
+ if (!state) return false;
1267
+ const runningApps = await listRunningApps();
1268
+ return isManagedDesktopProcess(runningApps, state);
1269
+ }
1270
+ async function restartManagedAppServer() {
1271
+ const state = await readManagedState();
1272
+ if (!state) return false;
1273
+ const runningApps = await listRunningApps();
1274
+ if (!isManagedDesktopProcess(runningApps, state)) return false;
1275
+ const response = await fetchImpl(`http://127.0.0.1:${state.remote_debugging_port}/json/list`);
1276
+ if (!response.ok) throw new Error(`Failed to query Codex Desktop devtools targets (HTTP ${response.status}).`);
1277
+ const targets = await response.json();
1278
+ if (!Array.isArray(targets)) throw new Error("Codex Desktop devtools target list was not an array.");
1279
+ const localTarget = targets.find((target)=>{
1280
+ if (!isDevtoolsTarget(target)) return false;
1281
+ return "page" === target.type && target.url === `app://-/index.html?hostId=${CODEX_LOCAL_HOST_ID}` && isNonEmptyString(target.webSocketDebuggerUrl);
1282
+ });
1283
+ if (!localTarget || !isNonEmptyString(localTarget.webSocketDebuggerUrl)) throw new Error("Could not find the local Codex Desktop devtools target.");
1284
+ await evaluateDevtoolsExpression(createWebSocketImpl, localTarget.webSocketDebuggerUrl, CODEX_APP_SERVER_RESTART_EXPRESSION);
1285
+ return true;
1286
+ }
1287
+ return {
1288
+ findInstalledApp,
1289
+ listRunningApps,
1290
+ quitRunningApps,
1291
+ launch,
1292
+ readManagedState,
1293
+ writeManagedState,
1294
+ clearManagedState,
1295
+ isManagedDesktopRunning,
1296
+ restartManagedAppServer
1297
+ };
1298
+ }
1077
1299
  external_dayjs_default().extend(utc_js_default());
1078
1300
  external_dayjs_default().extend(timezone_js_default());
1079
1301
  function parseArgs(argv) {
@@ -1117,12 +1339,29 @@ Usage:
1117
1339
  codexm update [--json]
1118
1340
  codexm switch <name> [--json]
1119
1341
  codexm switch --auto [--dry-run] [--json]
1342
+ codexm launch [name] [--json]
1120
1343
  codexm remove <name> [--yes] [--json]
1121
1344
  codexm rename <old> <new> [--json]
1122
1345
 
1123
1346
  Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1124
1347
  `);
1125
1348
  }
1349
+ function stripManagedDesktopWarning(warnings) {
1350
+ return warnings.filter((warning)=>!warning.startsWith("Detected running codex processes (") || !warning.endsWith("Existing sessions may still hold the previous login state."));
1351
+ }
1352
+ async function refreshManagedDesktopAfterSwitch(warnings, desktopLauncher) {
1353
+ try {
1354
+ if (await desktopLauncher.restartManagedAppServer()) return;
1355
+ } catch (error) {
1356
+ warnings.push(`Failed to refresh the running codexm-managed Codex Desktop session: ${error.message}`);
1357
+ return;
1358
+ }
1359
+ try {
1360
+ const runningApps = await desktopLauncher.listRunningApps();
1361
+ if (0 === runningApps.length) return;
1362
+ warnings.push(`Detected running codex processes (${runningApps.map((app)=>app.pid).join(", ")}). Existing sessions may still hold the previous login state.`);
1363
+ } catch {}
1364
+ }
1126
1365
  function describeCurrentStatus(status) {
1127
1366
  const lines = [];
1128
1367
  if (status.exists) {
@@ -1317,6 +1556,67 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1317
1556
  streams.stdin.on("data", onData);
1318
1557
  });
1319
1558
  }
1559
+ async function confirmDesktopRelaunch(streams) {
1560
+ if (!streams.stdin.isTTY) throw new Error("Refusing to relaunch Codex Desktop in a non-interactive terminal.");
1561
+ streams.stdout.write("Codex Desktop is already running. Close it and relaunch with the selected auth? [y/N] ");
1562
+ return await new Promise((resolve)=>{
1563
+ const cleanup = ()=>{
1564
+ streams.stdin.off("data", onData);
1565
+ streams.stdin.pause();
1566
+ };
1567
+ const onData = (buffer)=>{
1568
+ const answer = buffer.toString("utf8").trim().toLowerCase();
1569
+ cleanup();
1570
+ streams.stdout.write("\n");
1571
+ resolve("y" === answer || "yes" === answer);
1572
+ };
1573
+ streams.stdin.resume();
1574
+ streams.stdin.on("data", onData);
1575
+ });
1576
+ }
1577
+ async function sleep(ms) {
1578
+ await new Promise((resolve)=>setTimeout(resolve, ms));
1579
+ }
1580
+ async function main_pathExists(path) {
1581
+ try {
1582
+ await (0, promises_namespaceObject.stat)(path);
1583
+ return true;
1584
+ } catch (error) {
1585
+ const nodeError = error;
1586
+ if ("ENOENT" === nodeError.code) return false;
1587
+ throw error;
1588
+ }
1589
+ }
1590
+ function isRunningDesktopFromApp(app, appPath) {
1591
+ return app.command.includes(`${appPath}/Contents/MacOS/Codex`);
1592
+ }
1593
+ async function resolveManagedDesktopState(desktopLauncher, appPath, existingApps) {
1594
+ const existingPids = new Set(existingApps.map((app)=>app.pid));
1595
+ for(let attempt = 0; attempt < 10; attempt += 1){
1596
+ const runningApps = await desktopLauncher.listRunningApps();
1597
+ 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;
1598
+ if (launchedApp) return {
1599
+ pid: launchedApp.pid,
1600
+ app_path: appPath,
1601
+ remote_debugging_port: DEFAULT_CODEX_REMOTE_DEBUGGING_PORT,
1602
+ managed_by_codexm: true,
1603
+ started_at: new Date().toISOString()
1604
+ };
1605
+ await sleep(300);
1606
+ }
1607
+ return null;
1608
+ }
1609
+ async function restoreLaunchBackup(store, backupPath) {
1610
+ if (backupPath && await main_pathExists(backupPath)) await (0, promises_namespaceObject.copyFile)(backupPath, store.paths.currentAuthPath);
1611
+ else await (0, promises_namespaceObject.rm)(store.paths.currentAuthPath, {
1612
+ force: true
1613
+ });
1614
+ const configBackupPath = (0, external_node_path_namespaceObject.join)(store.paths.backupsDir, "last-active-config.toml");
1615
+ if (await main_pathExists(configBackupPath)) await (0, promises_namespaceObject.copyFile)(configBackupPath, store.paths.currentConfigPath);
1616
+ else await (0, promises_namespaceObject.rm)(store.paths.currentConfigPath, {
1617
+ force: true
1618
+ });
1619
+ }
1320
1620
  async function runCli(argv, options = {}) {
1321
1621
  const streams = {
1322
1622
  stdin: options.stdin ?? external_node_process_namespaceObject.stdin,
@@ -1324,6 +1624,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1324
1624
  stderr: options.stderr ?? external_node_process_namespaceObject.stderr
1325
1625
  };
1326
1626
  const store = options.store ?? createAccountStore();
1627
+ const desktopLauncher = options.desktopLauncher ?? createCodexDesktopLauncher();
1327
1628
  const parsed = parseArgs(argv);
1328
1629
  const json = parsed.flags.has("--json");
1329
1630
  try {
@@ -1456,6 +1757,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1456
1757
  }
1457
1758
  const result = await store.switchAccount(selected.name);
1458
1759
  for (const warning of warnings)result.warnings.push(warning);
1760
+ result.warnings = stripManagedDesktopWarning(result.warnings);
1761
+ await refreshManagedDesktopAfterSwitch(result.warnings, desktopLauncher);
1459
1762
  const payload = {
1460
1763
  ok: true,
1461
1764
  action: "switch",
@@ -1479,6 +1782,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1479
1782
  }
1480
1783
  if (!name) throw new Error("Usage: codexm switch <name>");
1481
1784
  const result = await store.switchAccount(name);
1785
+ result.warnings = stripManagedDesktopWarning(result.warnings);
1786
+ await refreshManagedDesktopAfterSwitch(result.warnings, desktopLauncher);
1482
1787
  let quota = null;
1483
1788
  try {
1484
1789
  await store.refreshQuotaForAccount(result.account.name);
@@ -1510,6 +1815,70 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1510
1815
  }
1511
1816
  return 0;
1512
1817
  }
1818
+ case "launch":
1819
+ {
1820
+ const name = parsed.positionals[0] ?? null;
1821
+ if (parsed.positionals.length > 1) throw new Error("Usage: codexm launch [name] [--json]");
1822
+ const warnings = [];
1823
+ const appPath = await desktopLauncher.findInstalledApp();
1824
+ if (!appPath) throw new Error("Codex Desktop not found at /Applications/Codex.app.");
1825
+ const runningApps = await desktopLauncher.listRunningApps();
1826
+ if (runningApps.length > 0) {
1827
+ const confirmed = await confirmDesktopRelaunch(streams);
1828
+ if (!confirmed) {
1829
+ if (json) writeJson(streams.stdout, {
1830
+ ok: false,
1831
+ action: "launch",
1832
+ cancelled: true
1833
+ });
1834
+ else streams.stdout.write("Aborted.\n");
1835
+ return 1;
1836
+ }
1837
+ await desktopLauncher.quitRunningApps();
1838
+ }
1839
+ let switchedAccount = null;
1840
+ let switchBackupPath = null;
1841
+ if (name) {
1842
+ const switchResult = await store.switchAccount(name);
1843
+ warnings.push(...stripManagedDesktopWarning(switchResult.warnings));
1844
+ switchedAccount = switchResult.account;
1845
+ switchBackupPath = switchResult.backup_path;
1846
+ }
1847
+ try {
1848
+ await desktopLauncher.launch(appPath);
1849
+ const managedState = await resolveManagedDesktopState(desktopLauncher, appPath, runningApps);
1850
+ if (!managedState) {
1851
+ await desktopLauncher.clearManagedState().catch(()=>void 0);
1852
+ throw new Error("Failed to confirm the newly launched Codex Desktop process for managed-session tracking.");
1853
+ }
1854
+ await desktopLauncher.writeManagedState(managedState);
1855
+ } catch (error) {
1856
+ if (switchedAccount) await restoreLaunchBackup(store, switchBackupPath).catch(()=>void 0);
1857
+ throw error;
1858
+ }
1859
+ if (json) writeJson(streams.stdout, {
1860
+ ok: true,
1861
+ action: "launch",
1862
+ account: switchedAccount ? {
1863
+ name: switchedAccount.name,
1864
+ account_id: switchedAccount.account_id,
1865
+ user_id: switchedAccount.user_id ?? null,
1866
+ identity: switchedAccount.identity,
1867
+ auth_mode: switchedAccount.auth_mode
1868
+ } : null,
1869
+ launched_with_current_auth: null === switchedAccount,
1870
+ app_path: appPath,
1871
+ relaunched: runningApps.length > 0,
1872
+ warnings
1873
+ });
1874
+ else {
1875
+ if (switchedAccount) streams.stdout.write(`Switched to "${switchedAccount.name}" (${maskAccountId(switchedAccount.identity)}).\n`);
1876
+ if (runningApps.length > 0) streams.stdout.write("Closed existing Codex Desktop instance and launched a new one.\n");
1877
+ streams.stdout.write(switchedAccount ? `Launched Codex Desktop with "${switchedAccount.name}" (${maskAccountId(switchedAccount.identity)}).\n` : "Launched Codex Desktop with current auth.\n");
1878
+ for (const warning of warnings)streams.stdout.write(`Warning: ${warning}\n`);
1879
+ }
1880
+ return 0;
1881
+ }
1513
1882
  case "remove":
1514
1883
  {
1515
1884
  const name = parsed.positionals[0];