codexuse-cli 3.1.5 → 3.6.0

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/dist/index.js CHANGED
@@ -2021,428 +2021,143 @@ var require_toml = __commonJS({
2021
2021
  }
2022
2022
  });
2023
2023
 
2024
- // src/commands/daemon.ts
2025
- var import_node_child_process2 = require("child_process");
2026
- var import_node_crypto = require("crypto");
2027
- var import_node_fs = __toESM(require("fs"));
2028
- var import_node_net = __toESM(require("net"));
2029
- var import_node_os = __toESM(require("os"));
2030
- var import_node_path2 = __toESM(require("path"));
2024
+ // src/commands/accountPool.ts
2025
+ var import_node_crypto4 = require("crypto");
2026
+ var import_node_os3 = __toESM(require("os"));
2027
+ var import_node_path7 = __toESM(require("path"));
2031
2028
 
2032
- // ../../packages/shared/src/core/internal-js-runtime.ts
2033
- var import_node_child_process = require("child_process");
2034
- var import_node_path = __toESM(require("path"), 1);
2035
- var cachedBunBinary = null;
2036
- function hasBunRuntime() {
2037
- return typeof process.versions.bun === "string" && process.versions.bun.length > 0;
2038
- }
2039
- function buildRuntimePath(env) {
2040
- const homeDir = env.HOME ?? env.USERPROFILE ?? "";
2041
- const pathHints = (env.CODEX_PATH_HINTS ?? "").split(import_node_path.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
2042
- const entries = [
2043
- env.PATH ?? "",
2044
- import_node_path.default.join(homeDir, ".bun", "bin"),
2045
- "/opt/homebrew/bin",
2046
- "/usr/local/bin",
2047
- import_node_path.default.join(homeDir, ".local", "bin"),
2048
- import_node_path.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
2049
- import_node_path.default.join(homeDir, ".fnm", "current", "bin"),
2050
- ...pathHints
2051
- ];
2052
- return Array.from(
2053
- new Set(
2054
- entries.flatMap((entry) => entry.split(import_node_path.default.delimiter)).map((entry) => entry.trim()).filter(Boolean)
2055
- )
2056
- ).join(import_node_path.default.delimiter);
2057
- }
2058
- function resolveConfiguredBunBinary(env) {
2059
- const candidates = [
2060
- env.CODEXUSE_INTERNAL_BUN_BIN,
2061
- env.BUN_BIN,
2062
- process.env.CODEXUSE_INTERNAL_BUN_BIN,
2063
- process.env.BUN_BIN
2064
- ];
2065
- for (const candidate of candidates) {
2066
- const normalized = candidate?.trim() ?? "";
2067
- if (normalized.length > 0) {
2068
- return normalized;
2069
- }
2029
+ // ../../packages/contracts/src/settings/types.ts
2030
+ var DEFAULT_ACCOUNT_POOL_EXPOSED_MODELS = [
2031
+ "gpt-5.4",
2032
+ "gpt-5.4-mini",
2033
+ "gpt-5.4-nano"
2034
+ ];
2035
+ var DEFAULT_ACCOUNT_POOL_EXPOSED_REASONING_EFFORTS = [
2036
+ "none",
2037
+ "minimal",
2038
+ "low",
2039
+ "medium",
2040
+ "high",
2041
+ "xhigh"
2042
+ ];
2043
+ var DEFAULT_ACCOUNT_POOL_EXPOSED_FAST_MODE_REASONING_EFFORTS = [
2044
+ "xhigh"
2045
+ ];
2046
+
2047
+ // ../../packages/runtime-app-state/src/app/state.ts
2048
+ var import_node_fs2 = require("fs");
2049
+ var import_node_async_hooks = require("async_hooks");
2050
+ var import_node_path2 = __toESM(require("path"), 1);
2051
+ var import_node_os = __toESM(require("os"), 1);
2052
+
2053
+ // ../../packages/contracts/src/settings/chat-scrollback.ts
2054
+ var CHAT_SCROLLBACK_DEFAULT = 200;
2055
+ var CHAT_SCROLLBACK_MIN = 50;
2056
+ var CHAT_SCROLLBACK_MAX = 5e3;
2057
+ function clampChatScrollbackItems(value) {
2058
+ if (!Number.isFinite(value)) {
2059
+ return CHAT_SCROLLBACK_DEFAULT;
2070
2060
  }
2071
- return null;
2061
+ const rounded = Math.round(value);
2062
+ return Math.max(CHAT_SCROLLBACK_MIN, Math.min(CHAT_SCROLLBACK_MAX, rounded));
2072
2063
  }
2073
- function probeBunBinary(env) {
2074
- if (hasBunRuntime()) {
2075
- return process.execPath;
2064
+ function normalizeChatHistoryScrollbackItems(value) {
2065
+ if (value === null) {
2066
+ return null;
2076
2067
  }
2077
- if (cachedBunBinary) {
2078
- return cachedBunBinary;
2068
+ if (typeof value === "number" && Number.isFinite(value)) {
2069
+ return clampChatScrollbackItems(value);
2079
2070
  }
2080
- const homeDir = env.HOME ?? env.USERPROFILE ?? "";
2081
- const candidates = Array.from(
2082
- new Set(
2083
- [
2084
- env.CODEXUSE_INTERNAL_BUN_BIN?.trim(),
2085
- env.BUN_BIN?.trim(),
2086
- import_node_path.default.join(homeDir, ".bun", "bin", "bun"),
2087
- "/opt/homebrew/bin/bun",
2088
- "/usr/local/bin/bun",
2089
- "bun"
2090
- ].filter((candidate) => typeof candidate === "string" && candidate.length > 0)
2091
- )
2092
- );
2093
- for (const candidate of candidates) {
2094
- const probe = (0, import_node_child_process.spawnSync)(candidate, ["--version"], {
2095
- env,
2096
- stdio: "ignore"
2097
- });
2098
- if (!probe.error && probe.status === 0) {
2099
- cachedBunBinary = candidate;
2100
- return candidate;
2071
+ if (typeof value === "string") {
2072
+ const parsed = Number(value);
2073
+ if (Number.isFinite(parsed)) {
2074
+ return clampChatScrollbackItems(parsed);
2101
2075
  }
2102
2076
  }
2103
- return null;
2077
+ return CHAT_SCROLLBACK_DEFAULT;
2104
2078
  }
2105
- function resolveInternalBunRuntime(overrides = {}) {
2106
- const env = {
2107
- ...process.env,
2108
- ...overrides
2109
- };
2110
- env.PATH = buildRuntimePath(env);
2111
- const runtimeBin = hasBunRuntime() ? process.execPath : resolveConfiguredBunBinary(env) ?? probeBunBinary(env);
2112
- if (!runtimeBin) {
2113
- throw new Error(
2114
- "CodexUse desktop requires a Bun runtime to launch internal services. Install Bun or set CODEXUSE_INTERNAL_BUN_BIN."
2115
- );
2079
+
2080
+ // ../../packages/contracts/src/settings/commit-message-prompt.ts
2081
+ var DEFAULT_COMMIT_MESSAGE_PROMPT = `Generate a concise git commit message for the following changes. Follow conventional commit format (e.g., feat:, fix:, refactor:, docs:, etc.). Keep the summary line under 72 characters. Only output the commit message, nothing else.
2082
+
2083
+ Changes:
2084
+ {diff}`;
2085
+ function normalizeLineEndings(value) {
2086
+ return value.replace(/\r\n/g, "\n");
2087
+ }
2088
+ function normalizeCommitMessagePrompt(value) {
2089
+ if (typeof value !== "string") {
2090
+ return null;
2116
2091
  }
2117
- delete env.ELECTRON_RUN_AS_NODE;
2118
- return {
2119
- bin: runtimeBin,
2120
- env
2121
- };
2092
+ const normalized = normalizeLineEndings(value).trim();
2093
+ return normalized.length > 0 ? normalized : null;
2122
2094
  }
2123
2095
 
2124
- // src/commands/daemon.ts
2125
- function normalizeTelegramBotToken(value) {
2126
- const trimmed = value?.trim() ?? "";
2127
- return trimmed.length > 0 ? trimmed : null;
2128
- }
2129
- function hasFlag(args, flag) {
2130
- return args.includes(flag);
2096
+ // ../../packages/runtime-app-state/src/storage/documents.ts
2097
+ var import_node_fs = require("fs");
2098
+ var import_node_path = __toESM(require("path"), 1);
2099
+ var APP_STORAGE_TABLE = "app_storage_documents";
2100
+ var APP_STORAGE_DB_DIR = "t3-projects";
2101
+ var APP_STORAGE_DB_NAME = "state.sqlite";
2102
+ var SQLITE_BUSY_TIMEOUT_MS = 5e3;
2103
+ var SQLITE_BUSY_MAX_ATTEMPTS = 3;
2104
+ var SQLITE_BUSY_RETRY_DELAY_MS = 100;
2105
+ var writeQueueByDbPath = /* @__PURE__ */ new Map();
2106
+ var initializedDbPaths = /* @__PURE__ */ new Set();
2107
+ function isRecord(value) {
2108
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2131
2109
  }
2132
- function stripFlags(args) {
2133
- return args.filter((arg) => !arg.startsWith("-"));
2110
+ function clone(value) {
2111
+ return JSON.parse(JSON.stringify(value));
2134
2112
  }
2135
- function parseStringFlag(flags, name) {
2136
- const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
2137
- if (!explicit) {
2113
+ function safeParseJson(raw) {
2114
+ if (typeof raw !== "string" || raw.trim().length === 0) {
2115
+ return null;
2116
+ }
2117
+ try {
2118
+ return JSON.parse(raw);
2119
+ } catch {
2138
2120
  return null;
2139
2121
  }
2140
- const value = explicit.slice(`${name}=`.length).trim();
2141
- return value.length > 0 ? value : "";
2142
2122
  }
2143
- function printDaemonHelp(version) {
2144
- console.log(`CodexUse CLI v${version}
2145
-
2146
- Usage:
2147
- codexuse daemon start [--telegram-bot-token=<token>] [--project-path=/abs/path]
2148
-
2149
- Notes:
2150
- - Runs the T3 Projects/chat server in headless mode.
2151
- - Requires Bun on PATH because the bundled Projects server runs on Bun.
2152
- - Pass --telegram-bot-token or set CODEXUSE_TELEGRAM_BOT_TOKEN to enable Telegram remote control.
2153
- - --project-path bootstraps that folder as the default project/thread.
2154
- - Stop with Ctrl+C or SIGTERM.
2155
- `);
2123
+ function sleep(ms) {
2124
+ return new Promise((resolve) => setTimeout(resolve, ms));
2156
2125
  }
2157
- function resolveServerEntry() {
2158
- const candidates = [
2159
- import_node_path2.default.resolve(__dirname, "server", "index.mjs"),
2160
- import_node_path2.default.resolve(__dirname, "../../../server/dist/index.mjs")
2161
- ];
2162
- for (const candidate of candidates) {
2163
- if (import_node_fs.default.existsSync(candidate)) {
2164
- return candidate;
2165
- }
2126
+ function isSqliteBusyError(error) {
2127
+ if (!error || typeof error !== "object") {
2128
+ return false;
2166
2129
  }
2167
- return candidates[0];
2130
+ const sqliteError = error;
2131
+ const message = typeof sqliteError.message === "string" ? sqliteError.message.toLowerCase() : "";
2132
+ return sqliteError.code === "SQLITE_BUSY" || sqliteError.errno === 5 || message.includes("sqlite_busy") || message.includes("database is locked");
2168
2133
  }
2169
- function resolveStateDir() {
2170
- return import_node_path2.default.join(import_node_os.default.homedir(), ".codexuse", "t3-daemon");
2134
+ function beginImmediate(db) {
2135
+ db.exec("BEGIN IMMEDIATE");
2171
2136
  }
2172
- function reserveLoopbackPort() {
2173
- return new Promise((resolve, reject) => {
2174
- const server = import_node_net.default.createServer();
2175
- server.unref();
2176
- server.once("error", reject);
2177
- server.listen(0, "127.0.0.1", () => {
2178
- const address = server.address();
2179
- const port = typeof address === "object" && address ? address.port : null;
2180
- server.close((error) => {
2181
- if (error) {
2182
- reject(error);
2183
- return;
2184
- }
2185
- if (typeof port !== "number") {
2186
- reject(new Error("Failed to reserve a loopback port for the Projects server."));
2187
- return;
2188
- }
2189
- resolve(port);
2190
- });
2191
- });
2192
- });
2137
+ function commit(db) {
2138
+ db.exec("COMMIT");
2193
2139
  }
2194
- async function runDaemonStart(options) {
2195
- const entry = resolveServerEntry();
2196
- if (!import_node_fs.default.existsSync(entry)) {
2197
- throw new Error(
2198
- `Missing bundled T3 server build output at ${entry}. Rebuild the CLI package with \`bun run build:cli\`.`
2199
- );
2140
+ function rollback(db) {
2141
+ try {
2142
+ db.exec("ROLLBACK");
2143
+ } catch {
2200
2144
  }
2201
- const port = await reserveLoopbackPort();
2202
- const authToken = (0, import_node_crypto.randomBytes)(24).toString("hex");
2203
- const stateDir = resolveStateDir();
2204
- const projectPath = options.projectPath ? import_node_path2.default.resolve(options.projectPath) : null;
2205
- const childCwd = projectPath ?? process.cwd();
2206
- const telegramBotToken = options.telegramBotToken?.trim() || null;
2207
- const runtime = resolveInternalBunRuntime({
2208
- T3CODE_MODE: "desktop",
2209
- T3CODE_HOST: "127.0.0.1",
2210
- T3CODE_PORT: String(port),
2211
- T3CODE_NO_BROWSER: "1",
2212
- T3CODE_AUTH_TOKEN: authToken,
2213
- T3CODE_STATE_DIR: stateDir,
2214
- T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: projectPath ? "1" : "0",
2215
- CODEXUSE_TELEGRAM_RUNTIME_OWNER: "daemon",
2216
- ...telegramBotToken ? {
2217
- CODEXUSE_TELEGRAM_BOT_TOKEN: telegramBotToken,
2218
- CODEXUSE_TELEGRAM_BRIDGE_ENABLED: "1"
2219
- } : {}
2220
- });
2221
- const child = (0, import_node_child_process2.spawn)(runtime.bin, [entry], {
2222
- cwd: childCwd,
2223
- env: runtime.env,
2224
- stdio: "inherit"
2145
+ }
2146
+ async function withWriteQueue(dbPath, task) {
2147
+ const previous = writeQueueByDbPath.get(dbPath) ?? Promise.resolve();
2148
+ let release;
2149
+ const current = new Promise((resolve) => {
2150
+ release = resolve;
2225
2151
  });
2226
- console.log(`Daemon started. Projects server listening on 127.0.0.1:${port}`);
2227
- console.log(`State dir: ${stateDir}`);
2228
- if (projectPath) {
2229
- console.log(`Bootstrapped project path: ${projectPath}`);
2230
- }
2231
- if (telegramBotToken) {
2232
- console.log("Telegram remote control enabled for this daemon session.");
2233
- }
2234
- console.log("Running until SIGINT/SIGTERM...");
2235
- await waitForChild(child);
2236
- }
2237
- function waitForChild(child) {
2238
- return new Promise((resolve, reject) => {
2239
- let stopping = false;
2240
- const stop = (signal) => {
2241
- if (stopping) {
2242
- return;
2243
- }
2244
- stopping = true;
2245
- child.kill(signal);
2246
- };
2247
- const onSigInt = () => stop("SIGINT");
2248
- const onSigTerm = () => stop("SIGTERM");
2249
- process.once("SIGINT", onSigInt);
2250
- process.once("SIGTERM", onSigTerm);
2251
- child.once("exit", (code, signal) => {
2252
- process.off("SIGINT", onSigInt);
2253
- process.off("SIGTERM", onSigTerm);
2254
- if (code === 0 || stopping) {
2255
- resolve();
2256
- return;
2257
- }
2258
- reject(
2259
- new Error(
2260
- `Projects server exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"}).`
2261
- )
2262
- );
2263
- });
2264
- child.once("error", (error) => {
2265
- process.off("SIGINT", onSigInt);
2266
- process.off("SIGTERM", onSigTerm);
2267
- reject(error);
2268
- });
2269
- });
2270
- }
2271
- async function handleDaemonCommand(args, version) {
2272
- const flags = args.filter((arg) => arg.startsWith("-"));
2273
- const params = stripFlags(args);
2274
- const sub = params[0];
2275
- if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
2276
- printDaemonHelp(version);
2277
- return;
2278
- }
2279
- switch (sub) {
2280
- case "start": {
2281
- const projectPath = parseStringFlag(flags, "--project-path") ?? parseStringFlag(flags, "--project") ?? null;
2282
- const telegramBotToken = parseStringFlag(flags, "--telegram-bot-token") ?? normalizeTelegramBotToken(process.env.CODEXUSE_TELEGRAM_BOT_TOKEN) ?? null;
2283
- await runDaemonStart({
2284
- projectPath,
2285
- telegramBotToken,
2286
- version
2287
- });
2288
- return;
2289
- }
2290
- default:
2291
- printDaemonHelp(version);
2292
- return;
2293
- }
2294
- }
2295
-
2296
- // ../../packages/runtime-profiles/src/license/service.ts
2297
- var import_node_crypto3 = __toESM(require("crypto"), 1);
2298
-
2299
- // ../../packages/contracts/src/settings/auto-roll.ts
2300
- var DEFAULT_AUTO_ROLL_ENABLED = false;
2301
- var DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
2302
- var DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
2303
- var AUTO_ROLL_WARNING_MIN = 50;
2304
- var AUTO_ROLL_WARNING_MAX = 99;
2305
- var AUTO_ROLL_SWITCH_MAX = 100;
2306
- function clampNumber(value, min, max) {
2307
- return Math.min(max, Math.max(min, value));
2308
- }
2309
- function resolveFiniteNumber(value, fallback) {
2310
- return Number.isFinite(value) ? value : fallback;
2311
- }
2312
- function sanitizeAutoRollWarningThreshold(value) {
2313
- const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_WARNING_THRESHOLD);
2314
- return clampNumber(numeric, AUTO_ROLL_WARNING_MIN, AUTO_ROLL_WARNING_MAX);
2315
- }
2316
- function sanitizeAutoRollSwitchThreshold(value, warningThreshold) {
2317
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
2318
- const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD);
2319
- return clampNumber(numeric, sanitizedWarning + 1, AUTO_ROLL_SWITCH_MAX);
2320
- }
2321
- function sanitizeAutoRollThresholds(warningThreshold, switchThreshold) {
2322
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
2323
- const sanitizedSwitch = sanitizeAutoRollSwitchThreshold(switchThreshold, sanitizedWarning);
2324
- return {
2325
- warningThreshold: sanitizedWarning,
2326
- switchThreshold: sanitizedSwitch
2327
- };
2328
- }
2329
- function normalizeAutoRollSettings(raw) {
2330
- const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : DEFAULT_AUTO_ROLL_ENABLED;
2331
- const rawWarning = resolveFiniteNumber(
2332
- typeof raw?.warningThreshold === "number" ? raw.warningThreshold : Number.NaN,
2333
- DEFAULT_AUTO_ROLL_WARNING_THRESHOLD
2334
- );
2335
- const rawSwitch = resolveFiniteNumber(
2336
- typeof raw?.switchThreshold === "number" ? raw.switchThreshold : Number.NaN,
2337
- DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD
2338
- );
2339
- const {
2340
- warningThreshold: normalizedWarning,
2341
- switchThreshold: normalizedSwitch
2342
- } = sanitizeAutoRollThresholds(rawWarning, rawSwitch);
2343
- return {
2344
- enabled,
2345
- warningThreshold: normalizedWarning,
2346
- switchThreshold: normalizedSwitch
2347
- };
2348
- }
2349
-
2350
- // ../../packages/runtime-app-state/src/app/state.ts
2351
- var import_node_fs3 = require("fs");
2352
- var import_node_async_hooks = require("async_hooks");
2353
- var import_node_path4 = __toESM(require("path"), 1);
2354
- var import_node_os2 = __toESM(require("os"), 1);
2355
-
2356
- // ../../packages/contracts/src/settings/chat-scrollback.ts
2357
- var CHAT_SCROLLBACK_DEFAULT = 200;
2358
- var CHAT_SCROLLBACK_MIN = 50;
2359
- var CHAT_SCROLLBACK_MAX = 5e3;
2360
- function clampChatScrollbackItems(value) {
2361
- if (!Number.isFinite(value)) {
2362
- return CHAT_SCROLLBACK_DEFAULT;
2363
- }
2364
- const rounded = Math.round(value);
2365
- return Math.max(CHAT_SCROLLBACK_MIN, Math.min(CHAT_SCROLLBACK_MAX, rounded));
2366
- }
2367
- function normalizeChatHistoryScrollbackItems(value) {
2368
- if (value === null) {
2369
- return null;
2370
- }
2371
- if (typeof value === "number" && Number.isFinite(value)) {
2372
- return clampChatScrollbackItems(value);
2373
- }
2374
- if (typeof value === "string") {
2375
- const parsed = Number(value);
2376
- if (Number.isFinite(parsed)) {
2377
- return clampChatScrollbackItems(parsed);
2378
- }
2379
- }
2380
- return CHAT_SCROLLBACK_DEFAULT;
2381
- }
2382
-
2383
- // ../../packages/contracts/src/settings/commit-message-prompt.ts
2384
- var DEFAULT_COMMIT_MESSAGE_PROMPT = `Generate a concise git commit message for the following changes. Follow conventional commit format (e.g., feat:, fix:, refactor:, docs:, etc.). Keep the summary line under 72 characters. Only output the commit message, nothing else.
2385
-
2386
- Changes:
2387
- {diff}`;
2388
- function normalizeLineEndings(value) {
2389
- return value.replace(/\r\n/g, "\n");
2390
- }
2391
- function normalizeCommitMessagePrompt(value) {
2392
- if (typeof value !== "string") {
2393
- return null;
2394
- }
2395
- const normalized = normalizeLineEndings(value).trim();
2396
- return normalized.length > 0 ? normalized : null;
2397
- }
2398
-
2399
- // ../../packages/runtime-app-state/src/storage/documents.ts
2400
- var import_node_fs2 = require("fs");
2401
- var import_node_path3 = __toESM(require("path"), 1);
2402
- var APP_STORAGE_TABLE = "app_storage_documents";
2403
- var APP_STORAGE_DB_DIR = "t3-projects";
2404
- var APP_STORAGE_DB_NAME = "state.sqlite";
2405
- var SQLITE_BUSY_TIMEOUT_MS = 5e3;
2406
- var SQLITE_BUSY_MAX_ATTEMPTS = 3;
2407
- var SQLITE_BUSY_RETRY_DELAY_MS = 100;
2408
- var initializedDbPaths = /* @__PURE__ */ new Set();
2409
- function isRecord(value) {
2410
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2411
- }
2412
- function clone(value) {
2413
- return JSON.parse(JSON.stringify(value));
2414
- }
2415
- function safeParseJson(raw) {
2416
- if (typeof raw !== "string" || raw.trim().length === 0) {
2417
- return null;
2418
- }
2419
- try {
2420
- return JSON.parse(raw);
2421
- } catch {
2422
- return null;
2423
- }
2424
- }
2425
- function sleep(ms) {
2426
- return new Promise((resolve) => setTimeout(resolve, ms));
2427
- }
2428
- function isSqliteBusyError(error) {
2429
- if (!error || typeof error !== "object") {
2430
- return false;
2431
- }
2432
- const sqliteError = error;
2433
- const message = typeof sqliteError.message === "string" ? sqliteError.message.toLowerCase() : "";
2434
- return sqliteError.code === "SQLITE_BUSY" || sqliteError.errno === 5 || message.includes("sqlite_busy") || message.includes("database is locked");
2435
- }
2436
- function beginImmediate(db) {
2437
- db.exec("BEGIN IMMEDIATE");
2438
- }
2439
- function commit(db) {
2440
- db.exec("COMMIT");
2441
- }
2442
- function rollback(db) {
2443
- try {
2444
- db.exec("ROLLBACK");
2445
- } catch {
2152
+ writeQueueByDbPath.set(dbPath, current);
2153
+ await previous;
2154
+ try {
2155
+ return await task();
2156
+ } finally {
2157
+ release?.();
2158
+ if (writeQueueByDbPath.get(dbPath) === current) {
2159
+ writeQueueByDbPath.delete(dbPath);
2160
+ }
2446
2161
  }
2447
2162
  }
2448
2163
  function applyConnectionPragmas(db) {
@@ -2464,7 +2179,7 @@ function ensureSchema(db, dbPath) {
2464
2179
  initializedDbPaths.add(dbPath);
2465
2180
  }
2466
2181
  async function openDatabase(dbPath) {
2467
- await import_node_fs2.promises.mkdir(import_node_path3.default.dirname(dbPath), { recursive: true });
2182
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(dbPath), { recursive: true });
2468
2183
  if (process.versions.bun !== void 0) {
2469
2184
  const sqlite2 = await Function("return import('bun:sqlite')")();
2470
2185
  const database2 = new sqlite2.Database(dbPath);
@@ -2501,7 +2216,7 @@ async function withDatabase(dbPath, task) {
2501
2216
  throw new Error("App storage database remained busy after retrying.");
2502
2217
  }
2503
2218
  function resolveAppStorageDbPath(userDataDir) {
2504
- return import_node_path3.default.join(userDataDir, APP_STORAGE_DB_DIR, APP_STORAGE_DB_NAME);
2219
+ return import_node_path.default.join(userDataDir, APP_STORAGE_DB_DIR, APP_STORAGE_DB_NAME);
2505
2220
  }
2506
2221
  async function readDocument(dbPath, namespace, normalize) {
2507
2222
  return withDatabase(dbPath, (db) => {
@@ -2516,56 +2231,62 @@ async function readDocument(dbPath, namespace, normalize) {
2516
2231
  });
2517
2232
  }
2518
2233
  async function writeDocument(dbPath, namespace, value) {
2519
- return withDatabase(dbPath, (db) => {
2520
- const normalizedValue = clone(value);
2521
- beginImmediate(db);
2522
- try {
2523
- db.prepare(
2524
- `
2525
- INSERT INTO ${APP_STORAGE_TABLE} (namespace, value_json, updated_at)
2526
- VALUES (?, ?, ?)
2527
- ON CONFLICT(namespace)
2528
- DO UPDATE SET
2529
- value_json = excluded.value_json,
2530
- updated_at = excluded.updated_at
2531
- `
2532
- ).run(namespace, JSON.stringify(normalizedValue), (/* @__PURE__ */ new Date()).toISOString());
2533
- commit(db);
2534
- return normalizedValue;
2535
- } catch (error) {
2536
- rollback(db);
2537
- throw error;
2538
- }
2539
- });
2234
+ return withWriteQueue(
2235
+ dbPath,
2236
+ async () => withDatabase(dbPath, (db) => {
2237
+ const normalizedValue = clone(value);
2238
+ beginImmediate(db);
2239
+ try {
2240
+ db.prepare(
2241
+ `
2242
+ INSERT INTO ${APP_STORAGE_TABLE} (namespace, value_json, updated_at)
2243
+ VALUES (?, ?, ?)
2244
+ ON CONFLICT(namespace)
2245
+ DO UPDATE SET
2246
+ value_json = excluded.value_json,
2247
+ updated_at = excluded.updated_at
2248
+ `
2249
+ ).run(namespace, JSON.stringify(normalizedValue), (/* @__PURE__ */ new Date()).toISOString());
2250
+ commit(db);
2251
+ return normalizedValue;
2252
+ } catch (error) {
2253
+ rollback(db);
2254
+ throw error;
2255
+ }
2256
+ })
2257
+ );
2540
2258
  }
2541
2259
  async function updateDocument(input) {
2542
- return withDatabase(input.dbPath, (db) => {
2543
- beginImmediate(db);
2544
- try {
2545
- const row = db.prepare(
2546
- `SELECT value_json AS valueJson FROM ${APP_STORAGE_TABLE} WHERE namespace = ?`
2547
- ).get(input.namespace);
2548
- const current = isRecord(row) ? input.normalize(
2549
- safeParseJson(row.valueJson) ?? input.fallback()
2550
- ) : input.fallback();
2551
- const next = input.normalize(input.transform(clone(current)));
2552
- db.prepare(
2553
- `
2554
- INSERT INTO ${APP_STORAGE_TABLE} (namespace, value_json, updated_at)
2555
- VALUES (?, ?, ?)
2556
- ON CONFLICT(namespace)
2557
- DO UPDATE SET
2558
- value_json = excluded.value_json,
2559
- updated_at = excluded.updated_at
2560
- `
2561
- ).run(input.namespace, JSON.stringify(next), (/* @__PURE__ */ new Date()).toISOString());
2562
- commit(db);
2563
- return clone(next);
2564
- } catch (error) {
2565
- rollback(db);
2566
- throw error;
2567
- }
2568
- });
2260
+ return withWriteQueue(
2261
+ input.dbPath,
2262
+ async () => withDatabase(input.dbPath, (db) => {
2263
+ beginImmediate(db);
2264
+ try {
2265
+ const row = db.prepare(
2266
+ `SELECT value_json AS valueJson FROM ${APP_STORAGE_TABLE} WHERE namespace = ?`
2267
+ ).get(input.namespace);
2268
+ const current = isRecord(row) ? input.normalize(
2269
+ safeParseJson(row.valueJson) ?? input.fallback()
2270
+ ) : input.fallback();
2271
+ const next = input.normalize(input.transform(clone(current)));
2272
+ db.prepare(
2273
+ `
2274
+ INSERT INTO ${APP_STORAGE_TABLE} (namespace, value_json, updated_at)
2275
+ VALUES (?, ?, ?)
2276
+ ON CONFLICT(namespace)
2277
+ DO UPDATE SET
2278
+ value_json = excluded.value_json,
2279
+ updated_at = excluded.updated_at
2280
+ `
2281
+ ).run(input.namespace, JSON.stringify(next), (/* @__PURE__ */ new Date()).toISOString());
2282
+ commit(db);
2283
+ return clone(next);
2284
+ } catch (error) {
2285
+ rollback(db);
2286
+ throw error;
2287
+ }
2288
+ })
2289
+ );
2569
2290
  }
2570
2291
 
2571
2292
  // ../../packages/runtime-app-state/src/app/state.ts
@@ -2583,27 +2304,27 @@ function clone2(value) {
2583
2304
  return JSON.parse(JSON.stringify(value));
2584
2305
  }
2585
2306
  function resolveDefaultUserDataDir() {
2586
- const home = process.env.HOME || process.env.USERPROFILE || import_node_os2.default.homedir();
2307
+ const home = process.env.HOME || process.env.USERPROFILE || import_node_os.default.homedir();
2587
2308
  if (!home) {
2588
2309
  throw new Error("Unable to resolve home directory for app state.");
2589
2310
  }
2590
2311
  if (process.platform === "darwin") {
2591
- return import_node_path4.default.join(home, "Library", "Application Support", APP_NAME);
2312
+ return import_node_path2.default.join(home, "Library", "Application Support", APP_NAME);
2592
2313
  }
2593
2314
  if (process.platform === "win32") {
2594
2315
  const appData = process.env.APPDATA;
2595
2316
  if (appData) {
2596
- return import_node_path4.default.join(appData, APP_NAME);
2317
+ return import_node_path2.default.join(appData, APP_NAME);
2597
2318
  }
2598
- return import_node_path4.default.join(home, "AppData", "Roaming", APP_NAME);
2319
+ return import_node_path2.default.join(home, "AppData", "Roaming", APP_NAME);
2599
2320
  }
2600
- return import_node_path4.default.join(home, ".config", APP_NAME);
2321
+ return import_node_path2.default.join(home, ".config", APP_NAME);
2601
2322
  }
2602
2323
  function getUserDataDir() {
2603
2324
  return configuredUserDataDir ?? resolveDefaultUserDataDir();
2604
2325
  }
2605
2326
  function resolveLegacyAppStatePath() {
2606
- return import_node_path4.default.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
2327
+ return import_node_path2.default.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
2607
2328
  }
2608
2329
  function resolveStorageDbPath() {
2609
2330
  return resolveAppStorageDbPath(getUserDataDir());
@@ -2693,8 +2414,8 @@ function createDefaultAppState() {
2693
2414
  lastError: null,
2694
2415
  remoteUpdatedAt: null
2695
2416
  },
2696
- telemetry: {
2697
- installId: null,
2417
+ analytics: {
2418
+ anonymousId: null,
2698
2419
  enabled: true,
2699
2420
  lastFlushAt: null,
2700
2421
  lastError: null
@@ -2992,15 +2713,22 @@ function normalizeAppState(raw) {
2992
2713
  merged.sync.lastPullAt = asString(merged.sync.lastPullAt);
2993
2714
  merged.sync.lastError = asString(merged.sync.lastError);
2994
2715
  merged.sync.remoteUpdatedAt = asString(merged.sync.remoteUpdatedAt);
2995
- if (!isRecord2(merged.telemetry)) {
2996
- merged.telemetry = clone2(defaults.telemetry);
2716
+ if (!isRecord2(merged.analytics)) {
2717
+ merged.analytics = isRecord2(merged.telemetry) ? clone2(merged.telemetry) : clone2(defaults.analytics);
2718
+ }
2719
+ merged.analytics.anonymousId = asString(merged.analytics.anonymousId);
2720
+ if (!merged.analytics.anonymousId) {
2721
+ const legacyInstallId = asString(merged.telemetry?.installId);
2722
+ merged.analytics.anonymousId = legacyInstallId;
2997
2723
  }
2998
- merged.telemetry.installId = asString(merged.telemetry.installId);
2999
- if (typeof merged.telemetry.enabled !== "boolean") {
3000
- merged.telemetry.enabled = true;
2724
+ if (typeof merged.analytics.enabled !== "boolean") {
2725
+ merged.analytics.enabled = true;
2726
+ }
2727
+ merged.analytics.lastFlushAt = asString(merged.analytics.lastFlushAt);
2728
+ merged.analytics.lastError = asString(merged.analytics.lastError);
2729
+ if ("telemetry" in merged) {
2730
+ delete merged.telemetry;
3001
2731
  }
3002
- merged.telemetry.lastFlushAt = asString(merged.telemetry.lastFlushAt);
3003
- merged.telemetry.lastError = asString(merged.telemetry.lastError);
3004
2732
  if (!isRecord2(merged.profilesByName)) {
3005
2733
  merged.profilesByName = {};
3006
2734
  }
@@ -3050,7 +2778,7 @@ function deepMerge(base, patch) {
3050
2778
  async function readLegacyAppStateFromDisk() {
3051
2779
  const filePath = resolveLegacyAppStatePath();
3052
2780
  try {
3053
- const raw = await import_node_fs3.promises.readFile(filePath, "utf8");
2781
+ const raw = await import_node_fs2.promises.readFile(filePath, "utf8");
3054
2782
  return normalizeAppState(JSON.parse(raw));
3055
2783
  } catch (error) {
3056
2784
  const code = error.code;
@@ -3130,38 +2858,146 @@ async function patchAppState(patch) {
3130
2858
  });
3131
2859
  }
3132
2860
 
3133
- // ../../packages/runtime-codex/src/codex/settings.ts
3134
- function asString2(value) {
3135
- if (typeof value !== "string") {
3136
- return null;
2861
+ // ../../packages/runtime-app-state/src/app/runtimeSettings.ts
2862
+ var LEGACY_APP_SETTINGS_KEYS = /* @__PURE__ */ new Set([
2863
+ "backendMode",
2864
+ "theme",
2865
+ "commitMessageModelId",
2866
+ "usageShowRemaining",
2867
+ "remoteBackendProvider",
2868
+ "remoteBackendHost",
2869
+ "remoteBackendToken",
2870
+ "remoteBackends",
2871
+ "activeRemoteBackendId",
2872
+ "keepDaemonRunningAfterAppClose"
2873
+ ]);
2874
+ function asRecord(value) {
2875
+ return value && typeof value === "object" ? value : {};
2876
+ }
2877
+ function stripLegacyAppSettingsKeys(settings) {
2878
+ let changed = false;
2879
+ const next = { ...settings };
2880
+ for (const key of LEGACY_APP_SETTINGS_KEYS) {
2881
+ if (!(key in next)) {
2882
+ continue;
2883
+ }
2884
+ changed = true;
2885
+ delete next[key];
3137
2886
  }
3138
- const trimmed = value.trim();
3139
- return trimmed.length > 0 ? trimmed : null;
2887
+ return changed ? next : settings;
3140
2888
  }
3141
- function isRecord3(value) {
3142
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2889
+ async function readAppSettings() {
2890
+ const appState = await getAppState();
2891
+ return stripLegacyAppSettingsKeys(asRecord(appState.runtimeSettings));
3143
2892
  }
3144
- function parseAutoRoll(raw) {
3145
- if (!raw) {
3146
- return null;
3147
- }
3148
- try {
3149
- return normalizeAutoRollSettings(raw);
3150
- } catch {
3151
- return null;
2893
+ async function writeAppSettings(settings, onUpdated) {
2894
+ const nextSettings = stripLegacyAppSettingsKeys(asRecord(settings));
2895
+ await updateAppState(
2896
+ (current) => ({
2897
+ ...current,
2898
+ runtimeSettings: nextSettings
2899
+ }),
2900
+ {
2901
+ mode: "replace",
2902
+ allowBeforeMigrationComplete: false
2903
+ }
2904
+ );
2905
+ if (onUpdated) {
2906
+ try {
2907
+ await onUpdated(nextSettings);
2908
+ } catch (error) {
2909
+ console.warn("Failed to apply runtime settings update hook:", error);
2910
+ }
3152
2911
  }
2912
+ return nextSettings;
3153
2913
  }
3154
- function parseStoredLicense(raw) {
3155
- if (!isRecord3(raw)) {
3156
- return null;
3157
- }
3158
- const statusCandidate = asString2(raw.status);
3159
- const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : void 0;
3160
- const license = {
3161
- licenseKey: asString2(raw.licenseKey ?? raw.license_key),
3162
- purchaseEmail: asString2(raw.purchaseEmail ?? raw.purchase_email),
3163
- lastVerifiedAt: asString2(raw.lastVerifiedAt ?? raw.last_verified_at),
3164
- nextCheckAt: asString2(raw.nextCheckAt ?? raw.next_check_at),
2914
+
2915
+ // ../../packages/runtime-profiles/src/license/service.ts
2916
+ var import_node_crypto2 = __toESM(require("crypto"), 1);
2917
+
2918
+ // ../../packages/contracts/src/settings/auto-roll.ts
2919
+ var DEFAULT_AUTO_ROLL_ENABLED = false;
2920
+ var DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
2921
+ var DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
2922
+ var AUTO_ROLL_WARNING_MIN = 50;
2923
+ var AUTO_ROLL_WARNING_MAX = 99;
2924
+ var AUTO_ROLL_SWITCH_MAX = 100;
2925
+ function clampNumber(value, min, max) {
2926
+ return Math.min(max, Math.max(min, value));
2927
+ }
2928
+ function resolveFiniteNumber(value, fallback) {
2929
+ return Number.isFinite(value) ? value : fallback;
2930
+ }
2931
+ function sanitizeAutoRollWarningThreshold(value) {
2932
+ const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_WARNING_THRESHOLD);
2933
+ return clampNumber(numeric, AUTO_ROLL_WARNING_MIN, AUTO_ROLL_WARNING_MAX);
2934
+ }
2935
+ function sanitizeAutoRollSwitchThreshold(value, warningThreshold) {
2936
+ const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
2937
+ const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD);
2938
+ return clampNumber(numeric, sanitizedWarning + 1, AUTO_ROLL_SWITCH_MAX);
2939
+ }
2940
+ function sanitizeAutoRollThresholds(warningThreshold, switchThreshold) {
2941
+ const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
2942
+ const sanitizedSwitch = sanitizeAutoRollSwitchThreshold(switchThreshold, sanitizedWarning);
2943
+ return {
2944
+ warningThreshold: sanitizedWarning,
2945
+ switchThreshold: sanitizedSwitch
2946
+ };
2947
+ }
2948
+ function normalizeAutoRollSettings(raw) {
2949
+ const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : DEFAULT_AUTO_ROLL_ENABLED;
2950
+ const rawWarning = resolveFiniteNumber(
2951
+ typeof raw?.warningThreshold === "number" ? raw.warningThreshold : Number.NaN,
2952
+ DEFAULT_AUTO_ROLL_WARNING_THRESHOLD
2953
+ );
2954
+ const rawSwitch = resolveFiniteNumber(
2955
+ typeof raw?.switchThreshold === "number" ? raw.switchThreshold : Number.NaN,
2956
+ DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD
2957
+ );
2958
+ const {
2959
+ warningThreshold: normalizedWarning,
2960
+ switchThreshold: normalizedSwitch
2961
+ } = sanitizeAutoRollThresholds(rawWarning, rawSwitch);
2962
+ return {
2963
+ enabled,
2964
+ warningThreshold: normalizedWarning,
2965
+ switchThreshold: normalizedSwitch
2966
+ };
2967
+ }
2968
+
2969
+ // ../../packages/runtime-codex/src/codex/settings.ts
2970
+ function asString2(value) {
2971
+ if (typeof value !== "string") {
2972
+ return null;
2973
+ }
2974
+ const trimmed = value.trim();
2975
+ return trimmed.length > 0 ? trimmed : null;
2976
+ }
2977
+ function isRecord3(value) {
2978
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2979
+ }
2980
+ function parseAutoRoll(raw) {
2981
+ if (!raw) {
2982
+ return null;
2983
+ }
2984
+ try {
2985
+ return normalizeAutoRollSettings(raw);
2986
+ } catch {
2987
+ return null;
2988
+ }
2989
+ }
2990
+ function parseStoredLicense(raw) {
2991
+ if (!isRecord3(raw)) {
2992
+ return null;
2993
+ }
2994
+ const statusCandidate = asString2(raw.status);
2995
+ const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : void 0;
2996
+ const license = {
2997
+ licenseKey: asString2(raw.licenseKey ?? raw.license_key),
2998
+ purchaseEmail: asString2(raw.purchaseEmail ?? raw.purchase_email),
2999
+ lastVerifiedAt: asString2(raw.lastVerifiedAt ?? raw.last_verified_at),
3000
+ nextCheckAt: asString2(raw.nextCheckAt ?? raw.next_check_at),
3165
3001
  lastVerificationError: asString2(raw.lastVerificationError ?? raw.last_verification_error),
3166
3002
  status,
3167
3003
  signature: asString2(raw.signature)
@@ -3280,25 +3116,45 @@ async function writeCodexSettingsJsonRaw(payload) {
3280
3116
  }
3281
3117
 
3282
3118
  // ../../packages/runtime-profiles/src/license/secret.ts
3283
- var import_node_crypto2 = __toESM(require("crypto"), 1);
3119
+ var import_node_fs3 = require("fs");
3120
+ var import_node_crypto = __toESM(require("crypto"), 1);
3121
+ var import_node_path3 = __toESM(require("path"), 1);
3284
3122
  var LICENSE_SECRET_DOCUMENT = "desktop.license-secret";
3123
+ var LEGACY_LICENSE_SECRET_FILE = "license.secret";
3285
3124
  async function generateSecret() {
3286
- return import_node_crypto2.default.randomBytes(32).toString("hex");
3125
+ return import_node_crypto.default.randomBytes(32).toString("hex");
3126
+ }
3127
+ function normalizeSecret(value) {
3128
+ return typeof value === "string" ? value.trim() : "";
3129
+ }
3130
+ async function readLegacyLicenseSecret() {
3131
+ const legacyPath = import_node_path3.default.join(getUserDataDir(), LEGACY_LICENSE_SECRET_FILE);
3132
+ try {
3133
+ return normalizeSecret(await import_node_fs3.promises.readFile(legacyPath, "utf8"));
3134
+ } catch (error) {
3135
+ if (error.code === "ENOENT") {
3136
+ return "";
3137
+ }
3138
+ throw error;
3139
+ }
3140
+ }
3141
+ async function persistLicenseSecret(dbPath, secret) {
3142
+ await writeDocument(dbPath, LICENSE_SECRET_DOCUMENT, secret);
3143
+ return secret;
3287
3144
  }
3288
3145
  async function getLicenseSecret() {
3289
3146
  const dbPath = resolveAppStorageDbPath(getUserDataDir());
3290
- const existing = await readDocument(
3291
- dbPath,
3292
- LICENSE_SECRET_DOCUMENT,
3293
- (value) => typeof value === "string" ? value.trim() : ""
3294
- );
3295
- const normalizedExisting = typeof existing === "string" ? existing.trim() : "";
3147
+ const existing = await readDocument(dbPath, LICENSE_SECRET_DOCUMENT, normalizeSecret);
3148
+ const normalizedExisting = normalizeSecret(existing);
3296
3149
  if (normalizedExisting.length > 0) {
3297
3150
  return normalizedExisting;
3298
3151
  }
3152
+ const legacySecret = await readLegacyLicenseSecret();
3153
+ if (legacySecret.length > 0) {
3154
+ return persistLicenseSecret(dbPath, legacySecret);
3155
+ }
3299
3156
  const secret = await generateSecret();
3300
- await writeDocument(dbPath, LICENSE_SECRET_DOCUMENT, secret);
3301
- return secret;
3157
+ return persistLicenseSecret(dbPath, secret);
3302
3158
  }
3303
3159
 
3304
3160
  // ../../packages/contracts/src/license/offer-config.ts
@@ -3403,7 +3259,7 @@ function licenseSignaturePayload(license) {
3403
3259
  return JSON.stringify(payload);
3404
3260
  }
3405
3261
  function signLicense(license, secret) {
3406
- return import_node_crypto3.default.createHmac("sha256", secret).update(licenseSignaturePayload(license)).digest("hex");
3262
+ return import_node_crypto2.default.createHmac("sha256", secret).update(licenseSignaturePayload(license)).digest("hex");
3407
3263
  }
3408
3264
  function withSignature(license, secret) {
3409
3265
  return {
@@ -3629,7 +3485,7 @@ var LicenseService = class {
3629
3485
  throw new Error("License key is required.");
3630
3486
  }
3631
3487
  const secret = await getLicenseSecret();
3632
- const digest = import_node_crypto3.default.createHash("sha256").update(trimmed).digest("hex");
3488
+ const digest = import_node_crypto2.default.createHash("sha256").update(trimmed).digest("hex");
3633
3489
  const result = await requestGumroadVerify(trimmed, "activation");
3634
3490
  ensureWithinUseLimit(result);
3635
3491
  const normalized = normalizeVerificationResult(result);
@@ -3672,149 +3528,80 @@ var LicenseService = class {
3672
3528
  };
3673
3529
  var licenseService = new LicenseService();
3674
3530
 
3675
- // src/app/help.ts
3676
- function printHelp(version) {
3677
- console.log(`CodexUse CLI v${version}
3678
-
3679
- Usage:
3680
- codexuse profile list [--no-usage] [--compact]
3681
- codexuse profile current
3682
- codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
3683
- codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
3684
- codexuse profile switch <name>
3685
- codexuse profile autoroll [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds]
3686
- codexuse profile delete <name>
3687
- codexuse profile rename <old> <new>
3688
-
3689
- codexuse license status [--refresh]
3690
- codexuse license activate <license-key>
3691
-
3692
- codexuse sync status
3693
- codexuse sync pull
3694
- codexuse sync push
3695
-
3696
- codexuse daemon start [--telegram-bot-token=<token>] [--project-path=/abs/path]
3531
+ // ../../packages/runtime-profiles/src/profiles/profile-manager.ts
3532
+ var import_node_crypto3 = require("crypto");
3533
+ var import_fs = require("fs");
3534
+ var import_path = __toESM(require("path"), 1);
3535
+ var import_path2 = require("path");
3536
+ var import_toml = __toESM(require_toml(), 1);
3697
3537
 
3698
- Flags:
3699
- -h, --help Show help
3700
- -v, --version Show version
3701
- --no-usage Skip rate-limit usage fetch
3702
- --compact Names only
3703
- --device-auth Use device auth for Codex login
3704
- --login=MODE Login mode: browser | device
3705
- --threshold=NN Auto-roll switch threshold percent (50-100)
3706
- --watch Keep checking and auto-switch when threshold is reached
3707
- --interval=SEC Watch interval in seconds (default: 30)
3708
- --dry-run Print planned switch without changing active profile
3709
- --telegram-bot-token=TOKEN Enable Telegram remote control for daemon mode
3710
- --project-path=PATH Optional path to bootstrap as the default Projects root in daemon mode
3711
- `);
3538
+ // ../../packages/contracts/src/profiles/identity.ts
3539
+ function normalizeValue(value) {
3540
+ if (typeof value !== "string") {
3541
+ return null;
3542
+ }
3543
+ const trimmed = value.trim();
3544
+ return trimmed.length > 0 ? trimmed : null;
3712
3545
  }
3713
-
3714
- // src/platform/args.ts
3715
- var DEFAULT_AUTOROLL_INTERVAL_SECONDS = 30;
3716
- var DEFAULT_AUTOROLL_THRESHOLD = 95;
3717
- function hasFlag2(args, flag) {
3718
- return args.includes(flag);
3546
+ function normalizeEmail(value) {
3547
+ const normalized = normalizeValue(value);
3548
+ return normalized ? normalized.toLowerCase() : null;
3719
3549
  }
3720
- function stripFlags2(args) {
3721
- return args.filter((arg) => !arg.startsWith("-"));
3550
+ function encodeIdentityPart(value) {
3551
+ return encodeURIComponent(value);
3722
3552
  }
3723
- function parseLoginMode(flags) {
3724
- if (flags.includes("--device-auth")) return "device";
3725
- const explicit = flags.find((flag) => flag.startsWith("--login="));
3726
- if (!explicit) return null;
3727
- const value = explicit.split("=")[1];
3728
- if (value === "browser" || value === "device") {
3729
- return value;
3553
+ function resolveLegacyProfileAccountKey(profile) {
3554
+ const accountId = normalizeValue(profile.accountId);
3555
+ if (accountId) {
3556
+ return accountId;
3730
3557
  }
3731
- return null;
3732
- }
3733
- function parseNumericFlag(flags, name) {
3734
- const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
3735
- if (!explicit) {
3736
- return null;
3558
+ const email = normalizeValue(profile.email ?? profile.metadata?.email);
3559
+ if (email) {
3560
+ return email;
3737
3561
  }
3738
- const raw = explicit.slice(`${name}=`.length);
3739
- const value = Number.parseFloat(raw);
3740
- return Number.isFinite(value) ? value : Number.NaN;
3562
+ const profileName = normalizeValue(profile.name);
3563
+ return profileName ? `profile:${profileName}` : null;
3741
3564
  }
3742
- function parseIntegerFlag(flags, name) {
3743
- const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
3744
- if (!explicit) {
3745
- return null;
3565
+ function resolveProfileIdentityKey(profile) {
3566
+ const workspaceId = normalizeValue(profile.workspaceId);
3567
+ const accountUserId = normalizeValue(profile.metadata?.chatgptAccountUserId);
3568
+ if (accountUserId) {
3569
+ return `seat:${encodeIdentityPart(accountUserId)}`;
3746
3570
  }
3747
- const raw = explicit.slice(`${name}=`.length);
3748
- const value = Number.parseInt(raw, 10);
3749
- return Number.isFinite(value) ? value : Number.NaN;
3750
- }
3751
-
3752
- // src/commands/license.ts
3753
- async function handleLicense(args, version) {
3754
- const flags = args.filter((arg) => arg.startsWith("-"));
3755
- const params = stripFlags2(args);
3756
- const sub = params[0];
3757
- if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
3758
- printHelp(version);
3759
- return;
3571
+ const userId = normalizeValue(profile.metadata?.userId) ?? normalizeValue(profile.metadata?.chatgptUserId);
3572
+ if (workspaceId && userId) {
3573
+ return `workspace-user:${encodeIdentityPart(workspaceId)}|${encodeIdentityPart(userId)}`;
3760
3574
  }
3761
- switch (sub) {
3762
- case "status": {
3763
- const forceRefresh = hasFlag2(flags, "--refresh");
3764
- const status = await licenseService.getStatus({ forceRefresh });
3765
- console.log(`Tier: ${status.tier}`);
3766
- console.log(`State: ${status.state}`);
3767
- if (status.profileLimit !== null) {
3768
- console.log(`Profile limit: ${status.profileLimit}`);
3769
- }
3770
- if (status.profilesRemaining !== null) {
3771
- console.log(`Profiles remaining: ${status.profilesRemaining}`);
3772
- }
3773
- if (status.purchaseEmail) {
3774
- console.log(`Email: ${status.purchaseEmail}`);
3775
- }
3776
- if (status.maskedLicenseKey) {
3777
- console.log(`License: ${status.maskedLicenseKey}`);
3778
- }
3779
- if (status.message) {
3780
- console.log(`Message: ${status.message}`);
3781
- }
3782
- if (status.error) {
3783
- console.log(`Error: ${status.error}`);
3784
- }
3785
- return;
3786
- }
3787
- case "activate": {
3788
- const key = params[1];
3789
- if (!key) {
3790
- throw new Error("License key is required.");
3791
- }
3792
- const status = await licenseService.activate(key);
3793
- console.log(status.message ?? "License activated.");
3794
- return;
3795
- }
3796
- default:
3797
- printHelp(version);
3575
+ const accountId = normalizeValue(profile.accountId);
3576
+ if (accountId) {
3577
+ return accountId;
3578
+ }
3579
+ const email = normalizeEmail(profile.email ?? profile.metadata?.email);
3580
+ if (workspaceId && email) {
3581
+ return `workspace-email:${encodeIdentityPart(workspaceId)}|${encodeIdentityPart(email)}`;
3582
+ }
3583
+ if (userId) {
3584
+ return `user:${encodeIdentityPart(userId)}`;
3585
+ }
3586
+ if (email) {
3587
+ return `email:${encodeIdentityPart(email)}`;
3798
3588
  }
3589
+ return resolveLegacyProfileAccountKey(profile);
3590
+ }
3591
+ function resolveProfileIdentityKeyFromProfile(profile) {
3592
+ return resolveProfileIdentityKey(profile) ?? `profile:${profile.name}`;
3799
3593
  }
3800
-
3801
- // ../../packages/runtime-profiles/src/profiles/profile-manager.ts
3802
- var import_node_crypto4 = require("crypto");
3803
- var import_fs = require("fs");
3804
- var import_path = __toESM(require("path"), 1);
3805
- var import_path2 = require("path");
3806
- var import_toml = __toESM(require_toml(), 1);
3807
3594
 
3808
3595
  // ../../packages/runtime-codex/src/codex/rpc.ts
3809
- var import_node_child_process3 = require("child_process");
3596
+ var import_node_child_process2 = require("child_process");
3810
3597
  var import_node_readline = __toESM(require("readline"), 1);
3811
3598
  var import_node_fs5 = require("fs");
3812
- var import_node_os3 = __toESM(require("os"), 1);
3599
+ var import_node_os2 = __toESM(require("os"), 1);
3813
3600
  var import_node_path6 = __toESM(require("path"), 1);
3814
3601
 
3815
3602
  // ../../packages/runtime-codex/src/codex/cli.ts
3816
3603
  var import_node_fs4 = require("fs");
3817
- var import_node_path5 = __toESM(require("path"), 1);
3604
+ var import_node_path4 = __toESM(require("path"), 1);
3818
3605
  var STABLE_CHANNEL = "stable";
3819
3606
  var ENV_HINTS = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
3820
3607
  var cachedStatus = null;
@@ -3822,7 +3609,7 @@ function fileExists(candidate) {
3822
3609
  if (!candidate) {
3823
3610
  return null;
3824
3611
  }
3825
- const normalized = import_node_path5.default.resolve(candidate);
3612
+ const normalized = import_node_path4.default.resolve(candidate);
3826
3613
  try {
3827
3614
  const stats = (0, import_node_fs4.statSync)(normalized);
3828
3615
  if (stats.isFile()) {
@@ -3850,11 +3637,11 @@ function resolveCodexFromPath() {
3850
3637
  if (!pathValue) {
3851
3638
  return null;
3852
3639
  }
3853
- const entries = pathValue.split(import_node_path5.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
3640
+ const entries = pathValue.split(import_node_path4.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
3854
3641
  const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
3855
3642
  for (const entry of entries) {
3856
3643
  for (const name of names) {
3857
- const candidate = fileExists(import_node_path5.default.join(entry, name));
3644
+ const candidate = fileExists(import_node_path4.default.join(entry, name));
3858
3645
  if (candidate) {
3859
3646
  return candidate;
3860
3647
  }
@@ -3917,21 +3704,89 @@ async function requireCodexCli() {
3917
3704
  return status.path;
3918
3705
  }
3919
3706
 
3920
- // ../../packages/shared/src/core/logger.ts
3921
- var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
3922
- function isMocked(fn) {
3923
- return Boolean(fn && typeof fn === "function" && "mock" in fn);
3924
- }
3925
- function logWarn(...args) {
3926
- if (isTestEnv && !isMocked(console.warn)) {
3927
- return;
3928
- }
3929
- console.warn(...args);
3930
- }
3931
- function logError(...args) {
3932
- if (isTestEnv && !isMocked(console.error)) {
3933
- return;
3934
- }
3707
+ // ../../packages/runtime-codex/src/codex/command.ts
3708
+ var import_node_child_process = require("child_process");
3709
+ var import_node_path5 = __toESM(require("path"), 1);
3710
+ var cachedNodeRuntime = null;
3711
+ function isJavaScriptEntrypoint(binaryPath) {
3712
+ const normalized = binaryPath.trim().toLowerCase();
3713
+ return normalized.endsWith(".js") || normalized.endsWith(".mjs") || normalized.endsWith(".cjs");
3714
+ }
3715
+ function resolveNodeRuntime(env) {
3716
+ const override = env?.CODEX_NODE_RUNTIME?.trim() || env?.CODEX_NODE_BIN?.trim() || env?.CODEX_NODE?.trim() || process.env.CODEX_NODE_RUNTIME?.trim() || process.env.CODEX_NODE_BIN?.trim() || process.env.CODEX_NODE?.trim() || null;
3717
+ if (override) {
3718
+ return override;
3719
+ }
3720
+ if (cachedNodeRuntime) {
3721
+ return cachedNodeRuntime;
3722
+ }
3723
+ const runtimeEnv = { ...process.env, ...env };
3724
+ const homeDir = runtimeEnv.HOME ?? runtimeEnv.USERPROFILE ?? "";
3725
+ const pathHints = (runtimeEnv.CODEX_PATH_HINTS ?? process.env.CODEX_PATH_HINTS ?? "").split(import_node_path5.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
3726
+ const extraPathEntries = [
3727
+ "/usr/local/bin",
3728
+ "/opt/homebrew/bin",
3729
+ import_node_path5.default.join(homeDir, ".local", "bin"),
3730
+ import_node_path5.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
3731
+ import_node_path5.default.join(homeDir, ".fnm", "current", "bin"),
3732
+ ...pathHints
3733
+ ].filter(Boolean);
3734
+ const currentPath = runtimeEnv.PATH ?? "";
3735
+ runtimeEnv.PATH = Array.from(
3736
+ /* @__PURE__ */ new Set([...extraPathEntries, ...currentPath.split(import_node_path5.default.delimiter).filter(Boolean)])
3737
+ ).join(import_node_path5.default.delimiter);
3738
+ const candidates = Array.from(
3739
+ new Set(
3740
+ [
3741
+ runtimeEnv.CODEX_NODE_RUNTIME?.trim(),
3742
+ runtimeEnv.CODEX_NODE_BIN?.trim(),
3743
+ runtimeEnv.CODEX_NODE?.trim(),
3744
+ "/opt/homebrew/bin/node",
3745
+ "/usr/local/bin/node",
3746
+ "node"
3747
+ ].filter((candidate) => typeof candidate === "string" && candidate.length > 0)
3748
+ )
3749
+ );
3750
+ for (const candidate of candidates) {
3751
+ const probe = (0, import_node_child_process.spawnSync)(candidate, ["-v"], { env: runtimeEnv, stdio: "ignore" });
3752
+ if (!probe.error && probe.status === 0) {
3753
+ cachedNodeRuntime = candidate;
3754
+ return candidate;
3755
+ }
3756
+ }
3757
+ cachedNodeRuntime = process.execPath;
3758
+ return process.execPath;
3759
+ }
3760
+ function buildCodexCommand(binaryPath, args, env) {
3761
+ if (isJavaScriptEntrypoint(binaryPath)) {
3762
+ return {
3763
+ command: resolveNodeRuntime(env),
3764
+ args: [binaryPath, ...args],
3765
+ shell: false
3766
+ };
3767
+ }
3768
+ return {
3769
+ command: binaryPath,
3770
+ args: [...args],
3771
+ shell: process.platform === "win32"
3772
+ };
3773
+ }
3774
+
3775
+ // ../../packages/shared/src/core/logger.ts
3776
+ var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
3777
+ function isMocked(fn) {
3778
+ return Boolean(fn && typeof fn === "function" && "mock" in fn);
3779
+ }
3780
+ function logWarn(...args) {
3781
+ if (isTestEnv && !isMocked(console.warn)) {
3782
+ return;
3783
+ }
3784
+ console.warn(...args);
3785
+ }
3786
+ function logError(...args) {
3787
+ if (isTestEnv && !isMocked(console.error)) {
3788
+ return;
3789
+ }
3935
3790
  console.error(...args);
3936
3791
  }
3937
3792
  function logInfo(...args) {
@@ -4138,11 +3993,11 @@ function parseRateLimitSnapshotFromRpcMessage(message) {
4138
3993
  }
4139
3994
  async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4140
3995
  const binaryPath = options.codexPath ?? await requireCodexCli();
4141
- const tempHome = await import_node_fs5.promises.mkdtemp(import_node_path6.default.join(import_node_os3.default.tmpdir(), "codex-rpc-"));
3996
+ const tempHome = await import_node_fs5.promises.mkdtemp(import_node_path6.default.join(import_node_os2.default.tmpdir(), "codex-rpc-"));
4142
3997
  const tempAuthPath = import_node_path6.default.join(tempHome, "auth.json");
4143
3998
  let initialSourceAuth = null;
4144
3999
  const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path6.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path6.default.join(
4145
- envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os3.default.homedir(),
4000
+ envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os2.default.homedir(),
4146
4001
  ".codex",
4147
4002
  "auth.json"
4148
4003
  ));
@@ -4157,17 +4012,24 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4157
4012
  });
4158
4013
  return null;
4159
4014
  }
4160
- const child = (0, import_node_child_process3.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
4015
+ const childEnv = {
4016
+ ...process.env,
4017
+ ...envOverride ?? {},
4018
+ HOME: tempHome,
4019
+ USERPROFILE: tempHome,
4020
+ CODEX_HOME: tempHome,
4021
+ CODEX_TELEMETRY_LABEL: "codex-rpc",
4022
+ ELECTRON_RUN_AS_NODE: "1"
4023
+ };
4024
+ const command = buildCodexCommand(
4025
+ binaryPath,
4026
+ ["-s", "read-only", "-a", "untrusted", "app-server"],
4027
+ childEnv
4028
+ );
4029
+ const child = (0, import_node_child_process2.spawn)(command.command, command.args, {
4161
4030
  stdio: ["pipe", "pipe", "pipe"],
4162
- env: {
4163
- ...process.env,
4164
- ...envOverride ?? {},
4165
- HOME: tempHome,
4166
- USERPROFILE: tempHome,
4167
- CODEX_HOME: tempHome,
4168
- CODEX_TELEMETRY_LABEL: "codex-rpc",
4169
- ELECTRON_RUN_AS_NODE: "1"
4170
- }
4031
+ env: childEnv,
4032
+ shell: command.shell
4171
4033
  });
4172
4034
  const rl = import_node_readline.default.createInterface({
4173
4035
  input: child.stdout,
@@ -4467,7 +4329,7 @@ var ProfileManager = class {
4467
4329
  workspaceId: null
4468
4330
  };
4469
4331
  }
4470
- const fingerprint = (0, import_node_crypto4.createHash)("sha256").update(trimmed).digest("hex");
4332
+ const fingerprint = (0, import_node_crypto3.createHash)("sha256").update(trimmed).digest("hex");
4471
4333
  try {
4472
4334
  const parsed = JSON.parse(trimmed);
4473
4335
  const normalized = this.normalizeProfileData(parsed);
@@ -4735,6 +4597,7 @@ var ProfileManager = class {
4735
4597
  groups,
4736
4598
  userId,
4737
4599
  chatgptUserId,
4600
+ chatgptAccountUserId,
4738
4601
  emailVerified,
4739
4602
  tokenExpiresAt: this.toIsoStringFromSeconds(exp),
4740
4603
  tokenIssuedAt: this.toIsoStringFromSeconds(iat),
@@ -4742,7 +4605,7 @@ var ProfileManager = class {
4742
4605
  };
4743
4606
  const hasMeaningfulData = Boolean(metadata.planType) || Boolean(
4744
4607
  metadata.subscription && (metadata.subscription.activeStart || metadata.subscription.activeUntil || metadata.subscription.lastChecked)
4745
- ) || Boolean(metadata.organizations && metadata.organizations.length > 0) || Boolean(metadata.groups && metadata.groups.length > 0) || Boolean(metadata.userId) || Boolean(metadata.chatgptUserId) || Boolean(metadata.email) || typeof metadata.emailVerified === "boolean" || Boolean(metadata.tokenExpiresAt) || Boolean(metadata.tokenIssuedAt) || Boolean(metadata.tokenAuthTime);
4608
+ ) || Boolean(metadata.organizations && metadata.organizations.length > 0) || Boolean(metadata.groups && metadata.groups.length > 0) || Boolean(metadata.userId) || Boolean(metadata.chatgptUserId) || Boolean(metadata.chatgptAccountUserId) || Boolean(metadata.email) || typeof metadata.emailVerified === "boolean" || Boolean(metadata.tokenExpiresAt) || Boolean(metadata.tokenIssuedAt) || Boolean(metadata.tokenAuthTime);
4746
4609
  return hasMeaningfulData ? metadata : void 0;
4747
4610
  }
4748
4611
  /**
@@ -4858,13 +4721,35 @@ var ProfileManager = class {
4858
4721
  return accountId.trim();
4859
4722
  }
4860
4723
  }
4861
- const projectId = "project_id" in data && typeof data.project_id === "string" ? data.project_id?.trim() : void 0;
4862
- if (projectId) {
4863
- return projectId;
4864
- }
4865
4724
  const email = "email" in data && typeof data.email === "string" ? data.email?.trim() : void 0;
4866
4725
  return email || void 0;
4867
4726
  }
4727
+ resolveProfileIdentityKey(args) {
4728
+ return resolveProfileIdentityKey(args);
4729
+ }
4730
+ resolveLegacyAccountKey(args) {
4731
+ return resolveLegacyProfileAccountKey(args);
4732
+ }
4733
+ resolveProfileIdentityFromData(name, data, metadata) {
4734
+ const resolvedMetadata = metadata ?? this.extractProfileMetadata(data);
4735
+ const workspace = this.resolveWorkspaceIdentity(data, resolvedMetadata);
4736
+ const email = this.resolveProfileEmail(data, resolvedMetadata) ?? null;
4737
+ return this.resolveProfileIdentityKey({
4738
+ name,
4739
+ accountId: this.getAccountIdFromData(data) ?? null,
4740
+ workspaceId: workspace.id ?? null,
4741
+ email,
4742
+ metadata: resolvedMetadata
4743
+ });
4744
+ }
4745
+ resolveProfileIdentityFromRecord(record) {
4746
+ return this.resolveProfileIdentityFromData(record.name, record.data, record.metadata) ?? this.resolveLegacyAccountKey({
4747
+ name: record.name,
4748
+ accountId: record.accountId,
4749
+ email: record.email,
4750
+ metadata: record.metadata
4751
+ }) ?? resolveFallbackAccountKey(record.name);
4752
+ }
4868
4753
  resolveWorkspaceIdentity(data, metadata) {
4869
4754
  const directId = "workspace_id" in data && typeof data.workspace_id === "string" ? data.workspace_id.trim() : void 0;
4870
4755
  const directName = "workspace_name" in data && typeof data.workspace_name === "string" ? data.workspace_name.trim() : void 0;
@@ -4885,12 +4770,9 @@ var ProfileManager = class {
4885
4770
  const record = await this.readProfileRecord(normalized);
4886
4771
  return record ?? void 0;
4887
4772
  }
4888
- async getProfileRowByAccountId(accountId, options) {
4773
+ async getProfileRowByIdentityKey(identityKey, options) {
4889
4774
  const records = await this.listProfileRecords();
4890
- const matches = records.filter((record) => {
4891
- const storedAccountId = record.accountId ?? this.getAccountIdFromData(record.data);
4892
- return storedAccountId === accountId;
4893
- });
4775
+ const matches = records.filter((record) => this.resolveProfileIdentityFromRecord(record) === identityKey);
4894
4776
  if (matches.length === 0) {
4895
4777
  return void 0;
4896
4778
  }
@@ -4904,12 +4786,11 @@ var ProfileManager = class {
4904
4786
  }
4905
4787
  return matches[0];
4906
4788
  }
4907
- async getProfileRowByAccountAndWorkspace(accountId, workspaceId, options) {
4789
+ async getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, options) {
4908
4790
  const workspaceKey = workspaceId && workspaceId.trim().length > 0 ? workspaceId.trim() : DEFAULT_WORKSPACE_ID;
4909
4791
  const records = await this.listProfileRecords();
4910
4792
  const matches = records.filter((record) => {
4911
- const storedAccountId = record.accountId ?? this.getAccountIdFromData(record.data);
4912
- if (storedAccountId !== accountId) {
4793
+ if (this.resolveProfileIdentityFromRecord(record) !== identityKey) {
4913
4794
  return false;
4914
4795
  }
4915
4796
  const storedWorkspace = record.workspaceId ?? record.data.workspace_id ?? DEFAULT_WORKSPACE_ID;
@@ -5187,11 +5068,17 @@ var ProfileManager = class {
5187
5068
  if (latestRow) {
5188
5069
  existing = latestRow.data;
5189
5070
  }
5190
- const existingAccountId = this.getAccountIdFromData(existing);
5191
- const newAccountId = this.getAccountIdFromData(normalized);
5192
- if (existingAccountId && newAccountId && existingAccountId !== newAccountId) {
5071
+ const existingMetadata = this.extractProfileMetadata(existing);
5072
+ const existingWorkspace = this.resolveWorkspaceIdentity(existing, existingMetadata);
5073
+ existing.workspace_id = existing.workspace_id ?? existingWorkspace.id ?? DEFAULT_WORKSPACE_ID;
5074
+ if (existingWorkspace.name) {
5075
+ existing.workspace_name = existing.workspace_name ?? existingWorkspace.name;
5076
+ }
5077
+ const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
5078
+ const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
5079
+ if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
5193
5080
  logWarn(
5194
- `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to account '${newAccountId}'.`
5081
+ `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`
5195
5082
  );
5196
5083
  return;
5197
5084
  }
@@ -5338,6 +5225,66 @@ var ProfileManager = class {
5338
5225
  }
5339
5226
  }
5340
5227
  }
5228
+ async prepareProfileRuntime(name) {
5229
+ return this.enqueueProfileOperation(
5230
+ name,
5231
+ () => this.enqueueAuthSwap(async () => {
5232
+ await this.initialize();
5233
+ const profileName = this.normalizeProfileName(name);
5234
+ const record = await this.getProfileRowByName(profileName);
5235
+ if (!record) {
5236
+ throw new Error(`Profile '${profileName}' not found!`);
5237
+ }
5238
+ let profileData = record.data;
5239
+ await this.syncProfileTokensFromActiveAuth(profileName, profileData);
5240
+ const updatedRecord = await this.getProfileRowByName(profileName);
5241
+ if (updatedRecord) {
5242
+ profileData = updatedRecord.data;
5243
+ }
5244
+ if (profileData.auth_method && profileData.auth_method !== "codex-cli") {
5245
+ throw new Error("Unsupported auth method. Codex CLI profiles only.");
5246
+ }
5247
+ const codexFormat = this.convertToCodexFormat(profileData);
5248
+ const profileHome = await this.prepareProfileHome(profileName, codexFormat);
5249
+ return {
5250
+ profileHome,
5251
+ env: {
5252
+ ...process.env,
5253
+ HOME: profileHome,
5254
+ USERPROFILE: profileHome,
5255
+ CODEX_HOME: profileHome
5256
+ }
5257
+ };
5258
+ })
5259
+ );
5260
+ }
5261
+ async syncProfileRuntime(name, profileHome) {
5262
+ return this.enqueueProfileOperation(
5263
+ name,
5264
+ () => this.enqueueAuthSwap(async () => {
5265
+ await this.initialize();
5266
+ const profileName = this.normalizeProfileName(name);
5267
+ const record = await this.getProfileRowByName(profileName);
5268
+ if (!record) {
5269
+ throw new Error(`Profile '${profileName}' not found!`);
5270
+ }
5271
+ const authPath = import_path.default.join(profileHome ?? this.getProfileHomePath(profileName), "auth.json");
5272
+ let finalAuthContent = null;
5273
+ try {
5274
+ finalAuthContent = await import_fs.promises.readFile(authPath, "utf8");
5275
+ } catch (error) {
5276
+ if (!this.isNotFoundError(error)) {
5277
+ logWarn(`Failed to read isolated auth for '${profileName}' while syncing runtime:`, error);
5278
+ }
5279
+ }
5280
+ if (finalAuthContent) {
5281
+ await this.syncProfileTokensFromAuthContent(profileName, record.data, finalAuthContent);
5282
+ return;
5283
+ }
5284
+ await this.syncProfileTokensFromActiveAuth(profileName, record.data, authPath);
5285
+ })
5286
+ );
5287
+ }
5341
5288
  /**
5342
5289
  * Create a new profile by running codex login
5343
5290
  */
@@ -5367,15 +5314,6 @@ var ProfileManager = class {
5367
5314
  if (workspace.name) {
5368
5315
  normalizedProfile.workspace_name = normalizedProfile.workspace_name ?? workspace.name;
5369
5316
  }
5370
- const accountId = this.getAccountIdFromData(normalizedProfile);
5371
- if (accountId) {
5372
- const requestedWorkspace = normalizedProfile.workspace_id ?? DEFAULT_WORKSPACE_ID;
5373
- const duplicate = await this.getProfileRowByAccountAndWorkspace(accountId, requestedWorkspace, { authMethod: "codex-cli" });
5374
- if (duplicate) {
5375
- normalizedProfile.workspace_id = `${requestedWorkspace}:${profileName}`;
5376
- normalizedProfile.workspace_name = normalizedProfile.workspace_name ?? profileName;
5377
- }
5378
- }
5379
5317
  const resolvedEmail = this.resolveProfileEmail(normalizedProfile, metadata);
5380
5318
  if (resolvedEmail) {
5381
5319
  normalizedProfile.email = resolvedEmail;
@@ -5430,25 +5368,30 @@ var ProfileManager = class {
5430
5368
  throw new Error("Failed to read Codex auth file. Complete the login and try again.");
5431
5369
  }
5432
5370
  const normalized = this.normalizeProfileData(activeAuth);
5433
- const existingAccountId = this.getAccountIdFromData(existing);
5434
- const newAccountId = this.getAccountIdFromData(normalized);
5371
+ const existingMetadata = record.metadata ?? this.extractProfileMetadata(existing);
5372
+ const currentWorkspace = this.resolveWorkspaceIdentity(existing, existingMetadata);
5373
+ existing.workspace_id = existing.workspace_id ?? currentWorkspace.id ?? DEFAULT_WORKSPACE_ID;
5374
+ if (currentWorkspace.name) {
5375
+ existing.workspace_name = existing.workspace_name ?? currentWorkspace.name;
5376
+ }
5435
5377
  const normalizedMetadata = this.extractProfileMetadata(normalized);
5436
5378
  const normalizedWorkspace = this.resolveWorkspaceIdentity(normalized, normalizedMetadata);
5437
5379
  normalized.workspace_id = normalized.workspace_id ?? normalizedWorkspace.id ?? DEFAULT_WORKSPACE_ID;
5438
5380
  if (normalizedWorkspace.name) {
5439
5381
  normalized.workspace_name = normalized.workspace_name ?? normalizedWorkspace.name;
5440
5382
  }
5441
- if (existingAccountId && newAccountId && existingAccountId !== newAccountId) {
5383
+ const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
5384
+ const newIdentity = this.resolveProfileIdentityFromData(profileName, normalized, normalizedMetadata);
5385
+ if (existingIdentity && newIdentity && existingIdentity !== newIdentity) {
5442
5386
  throw new Error(
5443
- `Active Codex login is for account '${newAccountId}', but profile '${profileName}' is tied to '${existingAccountId}'. Create a new profile for the new account instead.`
5387
+ `Active Codex login is for a different OpenAI identity than profile '${profileName}'. Create a new profile for that login instead.`
5444
5388
  );
5445
5389
  }
5446
- if (existingAccountId && normalized.workspace_id) {
5447
- const currentWorkspace = this.resolveWorkspaceIdentity(existing, this.extractProfileMetadata(existing));
5390
+ if (existingIdentity && normalized.workspace_id) {
5448
5391
  const targetWorkspaceId = normalized.workspace_id;
5449
5392
  const currentWorkspaceId = currentWorkspace.id ?? DEFAULT_WORKSPACE_ID;
5450
5393
  if (targetWorkspaceId !== currentWorkspaceId) {
5451
- const duplicate = await this.getProfileRowByAccountAndWorkspace(existingAccountId, targetWorkspaceId, {
5394
+ const duplicate = await this.getProfileRowByIdentityAndWorkspace(existingIdentity, targetWorkspaceId, {
5452
5395
  authMethod: this.resolveAuthMethod(existing)
5453
5396
  });
5454
5397
  if (duplicate && duplicate.name !== profileName) {
@@ -5665,9 +5608,9 @@ var ProfileManager = class {
5665
5608
  const normalizedAuth = this.normalizeProfileData(activeAuth);
5666
5609
  const authMetadata = this.extractProfileMetadata(normalizedAuth);
5667
5610
  const workspace = this.resolveWorkspaceIdentity(normalizedAuth, authMetadata);
5668
- const activeAccountId = this.getAccountIdFromData(normalizedAuth);
5669
- if (activeAccountId) {
5670
- const scoped = await this.getProfileRowByAccountAndWorkspace(activeAccountId, workspace.id ?? DEFAULT_WORKSPACE_ID, {
5611
+ const activeIdentityKey = this.resolveProfileIdentityFromData(null, normalizedAuth, authMetadata);
5612
+ if (activeIdentityKey) {
5613
+ const scoped = await this.getProfileRowByIdentityAndWorkspace(activeIdentityKey, workspace.id ?? DEFAULT_WORKSPACE_ID, {
5671
5614
  preferAuthMethod: "codex-cli"
5672
5615
  });
5673
5616
  if (scoped) {
@@ -5675,7 +5618,7 @@ var ProfileManager = class {
5675
5618
  await this.persistPreferredProfileName(normalized);
5676
5619
  return { name: normalized, trusted: true };
5677
5620
  }
5678
- const matching = await this.getProfileRowByAccountId(activeAccountId, { preferAuthMethod: "codex-cli" });
5621
+ const matching = await this.getProfileRowByIdentityKey(activeIdentityKey, { preferAuthMethod: "codex-cli" });
5679
5622
  if (matching) {
5680
5623
  const normalized = this.normalizeProfileName(matching.name);
5681
5624
  await this.persistPreferredProfileName(normalized);
@@ -5699,120 +5642,1555 @@ var ProfileManager = class {
5699
5642
  }
5700
5643
  return { name: null, trusted: false };
5701
5644
  }
5702
- /**
5703
- * Export a profile to a portable format.
5704
- * Sensitive tokens are included but should be handled securely.
5705
- */
5706
- async exportProfile(name) {
5707
- await this.initialize();
5708
- const profileName = this.normalizeProfileName(name);
5709
- const record = await this.getProfileRowByName(profileName);
5710
- if (!record) {
5711
- throw new Error(`Profile '${profileName}' not found!`);
5645
+ /**
5646
+ * Export a profile to a portable format.
5647
+ * Sensitive tokens are included but should be handled securely.
5648
+ */
5649
+ async exportProfile(name) {
5650
+ await this.initialize();
5651
+ const profileName = this.normalizeProfileName(name);
5652
+ const record = await this.getProfileRowByName(profileName);
5653
+ if (!record) {
5654
+ throw new Error(`Profile '${profileName}' not found!`);
5655
+ }
5656
+ return {
5657
+ name: record.name,
5658
+ displayName: record.displayName,
5659
+ email: record.email,
5660
+ accountId: record.accountId,
5661
+ workspaceId: record.workspaceId,
5662
+ workspaceName: record.workspaceName,
5663
+ authMethod: record.authMethod,
5664
+ createdAt: record.createdAt,
5665
+ metadata: record.metadata,
5666
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
5667
+ };
5668
+ }
5669
+ /**
5670
+ * Export profiles for cloud sync (includes token-bearing auth data).
5671
+ */
5672
+ async exportProfilesForCloudSync() {
5673
+ await this.initialize();
5674
+ const records = await this.listProfileRecords();
5675
+ return records.map((record) => ({
5676
+ name: record.name,
5677
+ displayName: record.displayName,
5678
+ data: record.data,
5679
+ metadata: record.metadata,
5680
+ accountId: record.accountId,
5681
+ workspaceId: record.workspaceId,
5682
+ workspaceName: record.workspaceName,
5683
+ email: record.email,
5684
+ authMethod: record.authMethod,
5685
+ createdAt: record.createdAt,
5686
+ updatedAt: record.updatedAt
5687
+ }));
5688
+ }
5689
+ /**
5690
+ * Replace local profiles with a full snapshot from cloud sync.
5691
+ */
5692
+ async replaceProfilesFromCloudSync(records) {
5693
+ await this.initialize();
5694
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5695
+ const normalized = /* @__PURE__ */ new Map();
5696
+ const existingMap = await this.readProfilesStateMap();
5697
+ const existingNames = new Set(Object.keys(existingMap));
5698
+ for (const candidate of records ?? []) {
5699
+ try {
5700
+ const name = this.normalizeProfileName(candidate?.name ?? "");
5701
+ const data = candidate && typeof candidate.data === "object" && candidate.data !== null ? candidate.data : null;
5702
+ if (!data) {
5703
+ continue;
5704
+ }
5705
+ const metadata = candidate.metadata && typeof candidate.metadata === "object" ? candidate.metadata : void 0;
5706
+ const record = {
5707
+ name,
5708
+ displayName: typeof candidate.displayName === "string" ? candidate.displayName : null,
5709
+ data,
5710
+ metadata,
5711
+ accountId: typeof candidate.accountId === "string" ? candidate.accountId : null,
5712
+ workspaceId: typeof candidate.workspaceId === "string" ? candidate.workspaceId : null,
5713
+ workspaceName: typeof candidate.workspaceName === "string" ? candidate.workspaceName : null,
5714
+ email: typeof candidate.email === "string" ? candidate.email : null,
5715
+ authMethod: typeof candidate.authMethod === "string" ? candidate.authMethod : this.resolveAuthMethod(data),
5716
+ createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : now,
5717
+ updatedAt: typeof candidate.updatedAt === "string" ? candidate.updatedAt : now
5718
+ };
5719
+ normalized.set(name, record);
5720
+ } catch {
5721
+ }
5722
+ }
5723
+ const nextMap = {};
5724
+ for (const record of normalized.values()) {
5725
+ nextMap[record.name] = this.toStateRecord(record);
5726
+ }
5727
+ await this.writeProfilesStateMap(nextMap);
5728
+ let removed = 0;
5729
+ for (const name of existingNames) {
5730
+ if (normalized.has(name)) {
5731
+ continue;
5732
+ }
5733
+ try {
5734
+ await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
5735
+ removed += 1;
5736
+ } catch (error) {
5737
+ if (!this.isNotFoundError(error)) {
5738
+ logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
5739
+ }
5740
+ }
5741
+ }
5742
+ const preferred = await this.readPreferredProfileName();
5743
+ if (preferred) {
5744
+ try {
5745
+ const normalizedPreferred = this.normalizeProfileName(preferred);
5746
+ if (!normalized.has(normalizedPreferred)) {
5747
+ await this.persistPreferredProfileName(null);
5748
+ }
5749
+ } catch {
5750
+ await this.persistPreferredProfileName(null);
5751
+ }
5752
+ }
5753
+ return {
5754
+ imported: normalized.size,
5755
+ removed
5756
+ };
5757
+ }
5758
+ };
5759
+
5760
+ // src/app/help.ts
5761
+ function printHelp(version) {
5762
+ console.log(`CodexUse CLI v${version}
5763
+
5764
+ Usage:
5765
+ codexuse account-pool status [--runtime=desktop|daemon] [--state-dir=/abs/path] [--port=NNNN]
5766
+ codexuse account-pool enable
5767
+ codexuse account-pool disable
5768
+ codexuse account-pool routing <least-used|round-robin>
5769
+ codexuse account-pool profiles list
5770
+ codexuse account-pool profiles set <name> [more...]
5771
+ codexuse account-pool models list
5772
+ codexuse account-pool keys list [--runtime=desktop|daemon]
5773
+ codexuse account-pool keys create [--runtime=desktop|daemon]
5774
+ codexuse account-pool sessions list [--runtime=desktop|daemon]
5775
+
5776
+ codexuse profile list [--no-usage] [--compact]
5777
+ codexuse profile current
5778
+ codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
5779
+ codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
5780
+ codexuse profile switch <name>
5781
+ codexuse profile autoroll [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds]
5782
+ codexuse profile delete <name>
5783
+ codexuse profile rename <old> <new>
5784
+
5785
+ codexuse license status [--refresh]
5786
+ codexuse license activate <license-key>
5787
+
5788
+ codexuse sync status
5789
+ codexuse sync pull
5790
+ codexuse sync push
5791
+
5792
+ codexuse daemon start [--port=NNNN] [--telegram-bot-token=<token>] [--project-path=/abs/path]
5793
+
5794
+ Flags:
5795
+ -h, --help Show help
5796
+ -v, --version Show version
5797
+ --no-usage Skip rate-limit usage fetch
5798
+ --compact Names only
5799
+ --device-auth Use device auth for Codex login
5800
+ --login=MODE Login mode: browser | device
5801
+ --threshold=NN Auto-roll switch threshold percent (50-100)
5802
+ --watch Keep checking and auto-switch when threshold is reached
5803
+ --interval=SEC Watch interval in seconds (default: 30)
5804
+ --dry-run Print planned switch without changing active profile
5805
+ --runtime=NAME Accounts Pool runtime store: desktop | daemon
5806
+ --state-dir=PATH Override the runtime state dir for Accounts Pool inspection
5807
+ --port=NNNN Stable daemon/API port (or use with account-pool status for daemon base URL)
5808
+ --telegram-bot-token=TOKEN Enable Telegram remote control for daemon mode
5809
+ --project-path=PATH Optional path to bootstrap as the default Projects root in daemon mode
5810
+ `);
5811
+ }
5812
+
5813
+ // src/platform/args.ts
5814
+ var DEFAULT_AUTOROLL_INTERVAL_SECONDS = 30;
5815
+ var DEFAULT_AUTOROLL_THRESHOLD = 95;
5816
+ function hasFlag(args, flag) {
5817
+ return args.includes(flag);
5818
+ }
5819
+ function stripFlags(args) {
5820
+ return args.filter((arg) => !arg.startsWith("-"));
5821
+ }
5822
+ function parseLoginMode(flags) {
5823
+ if (flags.includes("--device-auth")) return "device";
5824
+ const explicit = flags.find((flag) => flag.startsWith("--login="));
5825
+ if (!explicit) return null;
5826
+ const value = explicit.split("=")[1];
5827
+ if (value === "browser" || value === "device") {
5828
+ return value;
5829
+ }
5830
+ return null;
5831
+ }
5832
+ function parseNumericFlag(flags, name) {
5833
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
5834
+ if (!explicit) {
5835
+ return null;
5836
+ }
5837
+ const raw = explicit.slice(`${name}=`.length);
5838
+ const value = Number.parseFloat(raw);
5839
+ return Number.isFinite(value) ? value : Number.NaN;
5840
+ }
5841
+ function parseIntegerFlag(flags, name) {
5842
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
5843
+ if (!explicit) {
5844
+ return null;
5845
+ }
5846
+ const raw = explicit.slice(`${name}=`.length);
5847
+ const value = Number.parseInt(raw, 10);
5848
+ return Number.isFinite(value) ? value : Number.NaN;
5849
+ }
5850
+ function parseStringFlag(flags, name) {
5851
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
5852
+ if (!explicit) {
5853
+ return null;
5854
+ }
5855
+ const value = explicit.slice(`${name}=`.length).trim();
5856
+ return value.length > 0 ? value : "";
5857
+ }
5858
+
5859
+ // src/commands/accountPool.ts
5860
+ var ACCOUNT_POOL_DOCUMENT = "desktop.account-pool.v2";
5861
+ var LEGACY_BROKER_DOCUMENT = "desktop.broker.v1";
5862
+ var ACCOUNT_POOL_PRO_REQUIRED_MESSAGE = "Accounts Pool requires a Pro license.";
5863
+ var DEFAULT_DAEMON_STATE_DIR = import_node_path7.default.join(import_node_os3.default.homedir(), ".codexuse", "t3-daemon");
5864
+ var ACCOUNT_POOL_STORE_VERSION = 3;
5865
+ var MAX_RESPONSE_IDS_PER_SESSION = 64;
5866
+ var MODEL_SLUG_ALIASES = {
5867
+ "5.4": "gpt-5.4",
5868
+ "5.3": "gpt-5.3-codex",
5869
+ "gpt-5.3": "gpt-5.3-codex",
5870
+ "5.3-spark": "gpt-5.3-codex-spark",
5871
+ "gpt-5.3-spark": "gpt-5.3-codex-spark"
5872
+ };
5873
+ function asRecord2(value) {
5874
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5875
+ }
5876
+ function asString3(value) {
5877
+ if (typeof value !== "string") {
5878
+ return null;
5879
+ }
5880
+ const trimmed = value.trim();
5881
+ return trimmed.length > 0 ? trimmed : null;
5882
+ }
5883
+ function asNumber(value) {
5884
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
5885
+ }
5886
+ function normalizeIsoString(value) {
5887
+ const normalized = asString3(value);
5888
+ if (!normalized) {
5889
+ return null;
5890
+ }
5891
+ const parsed = Date.parse(normalized);
5892
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
5893
+ }
5894
+ function normalizeStringArray(value) {
5895
+ const input = Array.isArray(value) ? value : [];
5896
+ const result = [];
5897
+ const seen = /* @__PURE__ */ new Set();
5898
+ for (const entry of input) {
5899
+ const normalized = asString3(entry);
5900
+ if (!normalized || seen.has(normalized)) {
5901
+ continue;
5902
+ }
5903
+ seen.add(normalized);
5904
+ result.push(normalized);
5905
+ }
5906
+ return result;
5907
+ }
5908
+ function normalizeReasoningEffort(value) {
5909
+ const normalized = asString3(value)?.toLowerCase() ?? null;
5910
+ return normalized && normalized.length > 0 ? normalized : null;
5911
+ }
5912
+ function normalizeRoutingStrategy(value) {
5913
+ return value === "round_robin" ? "round_robin" : "least_used";
5914
+ }
5915
+ function normalizeCodexModelSlug(value) {
5916
+ const trimmed = value?.trim();
5917
+ if (!trimmed) {
5918
+ return null;
5919
+ }
5920
+ return MODEL_SLUG_ALIASES[trimmed] ?? trimmed;
5921
+ }
5922
+ function formatRoutingStrategy(value) {
5923
+ return value === "round_robin" ? "round-robin" : "least-used";
5924
+ }
5925
+ function parseRoutingStrategy(value) {
5926
+ const normalized = value?.trim().toLowerCase();
5927
+ if (!normalized) {
5928
+ return null;
5929
+ }
5930
+ if (normalized === "round-robin" || normalized === "round_robin" || normalized === "roundrobin") {
5931
+ return "round_robin";
5932
+ }
5933
+ if (normalized === "least-used" || normalized === "least_used" || normalized === "leastused") {
5934
+ return "least_used";
5935
+ }
5936
+ return null;
5937
+ }
5938
+ function normalizeAccountPoolExposedModels(value, fallbackToDefault) {
5939
+ const result = [];
5940
+ const seen = /* @__PURE__ */ new Set();
5941
+ const input = Array.isArray(value) ? value : [];
5942
+ for (const entry of input) {
5943
+ const raw = asString3(entry);
5944
+ const normalized = normalizeCodexModelSlug(raw) ?? raw;
5945
+ if (!normalized || seen.has(normalized)) {
5946
+ continue;
5947
+ }
5948
+ seen.add(normalized);
5949
+ result.push(normalized);
5950
+ }
5951
+ if (result.length > 0) {
5952
+ return result;
5953
+ }
5954
+ return fallbackToDefault ? [...DEFAULT_ACCOUNT_POOL_EXPOSED_MODELS] : [];
5955
+ }
5956
+ function normalizeAccountPoolReasoningEfforts(value, fallback) {
5957
+ const input = Array.isArray(value) ? value : [];
5958
+ const result = [];
5959
+ const seen = /* @__PURE__ */ new Set();
5960
+ for (const entry of input) {
5961
+ const normalized = normalizeReasoningEffort(entry);
5962
+ if (!normalized || seen.has(normalized)) {
5963
+ continue;
5964
+ }
5965
+ seen.add(normalized);
5966
+ result.push(normalized);
5967
+ }
5968
+ if (result.length > 0 || Array.isArray(value)) {
5969
+ return result;
5970
+ }
5971
+ return fallback ? [...fallback] : [];
5972
+ }
5973
+ function normalizeAccountPoolSettings(settings) {
5974
+ const accountPoolProfilePool = normalizeStringArray(settings.accountPoolProfilePool);
5975
+ return {
5976
+ accountPoolEnabled: settings.accountPoolEnabled === true || settings.brokerEnabled === true,
5977
+ accountPoolRoutingStrategy: normalizeRoutingStrategy(
5978
+ settings.accountPoolRoutingStrategy ?? settings.brokerRoutingStrategy
5979
+ ),
5980
+ accountPoolProfilePool: accountPoolProfilePool.length > 0 ? accountPoolProfilePool : normalizeStringArray(settings.brokerProfilePool),
5981
+ accountPoolExposedModels: normalizeAccountPoolExposedModels(
5982
+ settings.accountPoolExposedModels,
5983
+ true
5984
+ ),
5985
+ accountPoolExposedReasoningEfforts: normalizeAccountPoolReasoningEfforts(
5986
+ settings.accountPoolExposedReasoningEfforts,
5987
+ DEFAULT_ACCOUNT_POOL_EXPOSED_REASONING_EFFORTS
5988
+ ),
5989
+ accountPoolExposedFastModeReasoningEfforts: normalizeAccountPoolReasoningEfforts(
5990
+ settings.accountPoolExposedFastModeReasoningEfforts,
5991
+ DEFAULT_ACCOUNT_POOL_EXPOSED_FAST_MODE_REASONING_EFFORTS
5992
+ )
5993
+ };
5994
+ }
5995
+ function applyAccountPoolSettings(settings, nextAccountPoolSettings) {
5996
+ const next = {
5997
+ ...settings,
5998
+ accountPoolEnabled: nextAccountPoolSettings.accountPoolEnabled,
5999
+ accountPoolRoutingStrategy: nextAccountPoolSettings.accountPoolRoutingStrategy,
6000
+ accountPoolProfilePool: nextAccountPoolSettings.accountPoolProfilePool.slice(),
6001
+ accountPoolExposedModels: nextAccountPoolSettings.accountPoolExposedModels.slice(),
6002
+ accountPoolExposedReasoningEfforts: nextAccountPoolSettings.accountPoolExposedReasoningEfforts.slice(),
6003
+ accountPoolExposedFastModeReasoningEfforts: nextAccountPoolSettings.accountPoolExposedFastModeReasoningEfforts.slice()
6004
+ };
6005
+ delete next.brokerEnabled;
6006
+ delete next.brokerRoutingStrategy;
6007
+ delete next.brokerProfilePool;
6008
+ return next;
6009
+ }
6010
+ function printAccountPoolHelp(version) {
6011
+ console.log(`CodexUse CLI v${version}
6012
+
6013
+ Usage:
6014
+ codexuse account-pool status [--runtime=desktop|daemon] [--state-dir=/abs/path] [--port=NNNN]
6015
+ codexuse account-pool enable
6016
+ codexuse account-pool disable
6017
+ codexuse account-pool routing <least-used|round-robin>
6018
+
6019
+ codexuse account-pool profiles list
6020
+ codexuse account-pool profiles set <name> [more...]
6021
+ codexuse account-pool profiles add <name> [more...]
6022
+ codexuse account-pool profiles remove <name> [more...]
6023
+ codexuse account-pool profiles reset
6024
+
6025
+ codexuse account-pool models list
6026
+ codexuse account-pool models add <model> [more...]
6027
+ codexuse account-pool models remove <model> [more...]
6028
+ codexuse account-pool models reset
6029
+
6030
+ codexuse account-pool reasoning list
6031
+ codexuse account-pool reasoning add <effort> [more...]
6032
+ codexuse account-pool reasoning remove <effort> [more...]
6033
+ codexuse account-pool reasoning reset
6034
+
6035
+ codexuse account-pool fast-mode list
6036
+ codexuse account-pool fast-mode add <effort> [more...]
6037
+ codexuse account-pool fast-mode remove <effort> [more...]
6038
+ codexuse account-pool fast-mode reset
6039
+
6040
+ codexuse account-pool keys list [--runtime=desktop|daemon] [--state-dir=/abs/path]
6041
+ codexuse account-pool keys create [--runtime=desktop|daemon] [--state-dir=/abs/path]
6042
+ codexuse account-pool keys revoke <id> [--runtime=desktop|daemon] [--state-dir=/abs/path]
6043
+
6044
+ codexuse account-pool sessions list [--runtime=desktop|daemon] [--state-dir=/abs/path]
6045
+
6046
+ Notes:
6047
+ - Settings are shared with the desktop app.
6048
+ - Keys and sessions are runtime-specific: desktop and daemon each keep their own pool store.
6049
+ - Session inspection is read-only here on purpose; pooled sessions are live runtime state.
6050
+ - Use --runtime=daemon plus --state-dir or --port when you want to inspect a headless daemon.
6051
+ `);
6052
+ }
6053
+ function normalizeSessionStatus(value) {
6054
+ return value === "running" || value === "error" || value === "rollover_pending" ? value : "idle";
6055
+ }
6056
+ function normalizeApiKeyRecord(id, value) {
6057
+ const record = asRecord2(value);
6058
+ const tokenHash = asString3(record.tokenHash);
6059
+ const tokenPreview = asString3(record.tokenPreview);
6060
+ const createdAt = normalizeIsoString(record.createdAt);
6061
+ if (!tokenHash || !tokenPreview || !createdAt) {
6062
+ return null;
6063
+ }
6064
+ return {
6065
+ id,
6066
+ tokenHash,
6067
+ tokenPreview,
6068
+ createdAt,
6069
+ lastUsedAt: normalizeIsoString(record.lastUsedAt),
6070
+ revokedAt: normalizeIsoString(record.revokedAt)
6071
+ };
6072
+ }
6073
+ function normalizeLegacySessionRecord(id, value) {
6074
+ const record = asRecord2(value);
6075
+ const profileName = asString3(record.profileName);
6076
+ const createdAt = normalizeIsoString(record.createdAt);
6077
+ const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6078
+ if (!profileName || !createdAt || !lastUsedAt) {
6079
+ return null;
6080
+ }
6081
+ const affinityKind = record.affinityKind === "broker_session" || record.affinityKind === "prompt_cache" ? record.affinityKind : "ephemeral";
6082
+ return {
6083
+ id,
6084
+ affinityKind,
6085
+ affinityKey: asString3(record.affinityKey),
6086
+ profileName,
6087
+ threadId: asString3(record.threadId) ?? id,
6088
+ model: asString3(record.model),
6089
+ status: normalizeSessionStatus(record.status),
6090
+ lastError: asString3(record.lastError),
6091
+ createdAt,
6092
+ lastUsedAt,
6093
+ expiresAt: normalizeIsoString(record.expiresAt),
6094
+ lastResponseId: asString3(record.lastResponseId),
6095
+ responseIds: normalizeStringArray(record.responseIds).slice(0, MAX_RESPONSE_IDS_PER_SESSION)
6096
+ };
6097
+ }
6098
+ function normalizeLegacyStore(value) {
6099
+ const record = asRecord2(value);
6100
+ const apiKeysById = {};
6101
+ for (const [id, apiKeyValue] of Object.entries(asRecord2(record.apiKeysById))) {
6102
+ const normalized = normalizeApiKeyRecord(id, apiKeyValue);
6103
+ if (normalized) {
6104
+ apiKeysById[id] = normalized;
6105
+ }
6106
+ }
6107
+ const sessionsById = {};
6108
+ for (const [id, sessionValue] of Object.entries(asRecord2(record.sessionsById))) {
6109
+ const normalized = normalizeLegacySessionRecord(id, sessionValue);
6110
+ if (normalized) {
6111
+ sessionsById[id] = normalized;
6112
+ }
6113
+ }
6114
+ const responseIndex = {};
6115
+ for (const [responseId, sessionId] of Object.entries(asRecord2(record.responseIndex))) {
6116
+ const normalizedSessionId = asString3(sessionId);
6117
+ if (normalizedSessionId) {
6118
+ responseIndex[responseId] = normalizedSessionId;
6119
+ }
6120
+ }
6121
+ return {
6122
+ version: asNumber(record.version) ?? 1,
6123
+ roundRobinCursor: asNumber(record.roundRobinCursor) ?? 0,
6124
+ apiKeysById,
6125
+ sessionsById,
6126
+ responseIndex
6127
+ };
6128
+ }
6129
+ function normalizeSessionRecord(id, value) {
6130
+ const record = asRecord2(value);
6131
+ const activeSegmentId = asString3(record.activeSegmentId);
6132
+ const createdAt = normalizeIsoString(record.createdAt);
6133
+ const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6134
+ if (!activeSegmentId || !createdAt || !lastUsedAt) {
6135
+ return null;
6136
+ }
6137
+ const affinityKind = record.affinityKind === "pool_session" || record.affinityKind === "prompt_cache" ? record.affinityKind : "ephemeral";
6138
+ return {
6139
+ id,
6140
+ affinityKind,
6141
+ affinityKey: asString3(record.affinityKey),
6142
+ activeSegmentId,
6143
+ segmentIds: normalizeStringArray(record.segmentIds),
6144
+ model: asString3(record.model),
6145
+ status: normalizeSessionStatus(record.status),
6146
+ lastError: asString3(record.lastError),
6147
+ createdAt,
6148
+ lastUsedAt,
6149
+ expiresAt: normalizeIsoString(record.expiresAt),
6150
+ lastResponseId: asString3(record.lastResponseId),
6151
+ responseIds: normalizeStringArray(record.responseIds).slice(0, MAX_RESPONSE_IDS_PER_SESSION),
6152
+ rolloverCount: asNumber(record.rolloverCount) ?? 0,
6153
+ lastRolloverReason: asString3(record.lastRolloverReason)
6154
+ };
6155
+ }
6156
+ function normalizeSegmentRecord(id, value) {
6157
+ const record = asRecord2(value);
6158
+ const sessionId = asString3(record.sessionId);
6159
+ const profileName = asString3(record.profileName);
6160
+ const createdAt = normalizeIsoString(record.createdAt);
6161
+ const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6162
+ if (!sessionId || !profileName || !createdAt || !lastUsedAt) {
6163
+ return null;
6164
+ }
6165
+ return {
6166
+ id,
6167
+ sessionId,
6168
+ profileName,
6169
+ createdAt,
6170
+ lastUsedAt
6171
+ };
6172
+ }
6173
+ function normalizeResponseIndexRecord(value) {
6174
+ const record = asRecord2(value);
6175
+ const sessionId = asString3(record.sessionId);
6176
+ const segmentId = asString3(record.segmentId);
6177
+ const createdAt = normalizeIsoString(record.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
6178
+ if (!sessionId || !segmentId) {
6179
+ return null;
6180
+ }
6181
+ return { sessionId, segmentId, createdAt };
6182
+ }
6183
+ function normalizeAccountPoolStore(value) {
6184
+ const record = asRecord2(value);
6185
+ const apiKeysById = {};
6186
+ for (const [id, apiKeyValue] of Object.entries(asRecord2(record.apiKeysById))) {
6187
+ const normalized = normalizeApiKeyRecord(id, apiKeyValue);
6188
+ if (normalized) {
6189
+ apiKeysById[id] = normalized;
6190
+ }
6191
+ }
6192
+ const sessionsById = {};
6193
+ for (const [id, sessionValue] of Object.entries(asRecord2(record.sessionsById))) {
6194
+ const normalized = normalizeSessionRecord(id, sessionValue);
6195
+ if (normalized) {
6196
+ sessionsById[id] = normalized;
6197
+ }
6198
+ }
6199
+ const segmentsById = {};
6200
+ for (const [id, segmentValue] of Object.entries(asRecord2(record.segmentsById))) {
6201
+ const normalized = normalizeSegmentRecord(id, segmentValue);
6202
+ if (normalized) {
6203
+ segmentsById[id] = normalized;
6204
+ }
6205
+ }
6206
+ const responseIndex = {};
6207
+ for (const [responseId, responseValue] of Object.entries(asRecord2(record.responseIndex))) {
6208
+ const normalized = normalizeResponseIndexRecord(responseValue);
6209
+ if (normalized) {
6210
+ responseIndex[responseId] = normalized;
6211
+ }
6212
+ }
6213
+ return {
6214
+ version: asNumber(record.version) ?? ACCOUNT_POOL_STORE_VERSION,
6215
+ roundRobinCursor: asNumber(record.roundRobinCursor) ?? 0,
6216
+ apiKeysById,
6217
+ sessionsById,
6218
+ segmentsById,
6219
+ responseIndex
6220
+ };
6221
+ }
6222
+ function createDefaultStore() {
6223
+ return {
6224
+ version: ACCOUNT_POOL_STORE_VERSION,
6225
+ roundRobinCursor: 0,
6226
+ apiKeysById: {},
6227
+ sessionsById: {},
6228
+ segmentsById: {},
6229
+ responseIndex: {}
6230
+ };
6231
+ }
6232
+ function migrateLegacyStore(legacyStore) {
6233
+ const store = createDefaultStore();
6234
+ store.roundRobinCursor = legacyStore.roundRobinCursor;
6235
+ store.apiKeysById = { ...legacyStore.apiKeysById };
6236
+ for (const session of Object.values(legacyStore.sessionsById)) {
6237
+ const segmentId = `segment_${session.id}`;
6238
+ store.segmentsById[segmentId] = {
6239
+ id: segmentId,
6240
+ sessionId: session.id,
6241
+ profileName: session.profileName,
6242
+ createdAt: session.createdAt,
6243
+ lastUsedAt: session.lastUsedAt
6244
+ };
6245
+ store.sessionsById[session.id] = {
6246
+ id: session.id,
6247
+ affinityKind: session.affinityKind === "broker_session" ? "pool_session" : session.affinityKind,
6248
+ affinityKey: session.affinityKey,
6249
+ activeSegmentId: segmentId,
6250
+ segmentIds: [segmentId],
6251
+ model: session.model,
6252
+ status: session.status,
6253
+ lastError: session.lastError,
6254
+ createdAt: session.createdAt,
6255
+ lastUsedAt: session.lastUsedAt,
6256
+ expiresAt: session.expiresAt,
6257
+ lastResponseId: session.lastResponseId,
6258
+ responseIds: session.responseIds.slice(0, MAX_RESPONSE_IDS_PER_SESSION),
6259
+ rolloverCount: 0,
6260
+ lastRolloverReason: null
6261
+ };
6262
+ }
6263
+ for (const [responseId, sessionId] of Object.entries(legacyStore.responseIndex)) {
6264
+ const session = store.sessionsById[sessionId];
6265
+ if (!session) {
6266
+ continue;
6267
+ }
6268
+ store.responseIndex[responseId] = {
6269
+ sessionId,
6270
+ segmentId: session.activeSegmentId,
6271
+ createdAt: session.lastUsedAt
6272
+ };
6273
+ }
6274
+ return store;
6275
+ }
6276
+ function deleteSessionFromStore(store, sessionId) {
6277
+ const session = store.sessionsById[sessionId];
6278
+ if (!session) {
6279
+ return;
6280
+ }
6281
+ for (const segmentId of session.segmentIds) {
6282
+ delete store.segmentsById[segmentId];
6283
+ }
6284
+ for (const [responseId, response] of Object.entries(store.responseIndex)) {
6285
+ if (response.sessionId === sessionId) {
6286
+ delete store.responseIndex[responseId];
6287
+ }
6288
+ }
6289
+ delete store.sessionsById[sessionId];
6290
+ }
6291
+ function pruneStore(store) {
6292
+ const now = Date.now();
6293
+ for (const [sessionId, session] of Object.entries(store.sessionsById)) {
6294
+ const expiresAtMs = session.expiresAt ? Date.parse(session.expiresAt) : Number.NaN;
6295
+ if (Number.isFinite(expiresAtMs) && expiresAtMs < now) {
6296
+ deleteSessionFromStore(store, sessionId);
6297
+ }
6298
+ }
6299
+ for (const [segmentId, segment] of Object.entries(store.segmentsById)) {
6300
+ if (!store.sessionsById[segment.sessionId]) {
6301
+ delete store.segmentsById[segmentId];
6302
+ }
6303
+ }
6304
+ for (const [responseId, response] of Object.entries(store.responseIndex)) {
6305
+ if (!store.sessionsById[response.sessionId] || !store.segmentsById[response.segmentId]) {
6306
+ delete store.responseIndex[responseId];
6307
+ }
6308
+ }
6309
+ return store;
6310
+ }
6311
+ function resolveAccountPoolDbPath(stateDir) {
6312
+ return import_node_path7.default.join(stateDir, "state.sqlite");
6313
+ }
6314
+ async function readLegacyStore(dbPath) {
6315
+ return readDocument(dbPath, LEGACY_BROKER_DOCUMENT, normalizeLegacyStore);
6316
+ }
6317
+ async function readStore(stateDir) {
6318
+ const dbPath = resolveAccountPoolDbPath(stateDir);
6319
+ const store = await readDocument(
6320
+ dbPath,
6321
+ ACCOUNT_POOL_DOCUMENT,
6322
+ normalizeAccountPoolStore
6323
+ );
6324
+ if (store) {
6325
+ return pruneStore(store);
6326
+ }
6327
+ const legacyStore = await readLegacyStore(dbPath);
6328
+ if (!legacyStore) {
6329
+ return createDefaultStore();
6330
+ }
6331
+ return pruneStore(migrateLegacyStore(legacyStore));
6332
+ }
6333
+ async function updateStore(stateDir, mutate) {
6334
+ const dbPath = resolveAccountPoolDbPath(stateDir);
6335
+ const legacyStore = await readLegacyStore(dbPath);
6336
+ return updateDocument({
6337
+ dbPath,
6338
+ namespace: ACCOUNT_POOL_DOCUMENT,
6339
+ normalize: normalizeAccountPoolStore,
6340
+ fallback: () => legacyStore ? migrateLegacyStore(legacyStore) : createDefaultStore(),
6341
+ transform: (current) => {
6342
+ const next = pruneStore(current);
6343
+ mutate(next);
6344
+ return pruneStore(next);
6345
+ }
6346
+ });
6347
+ }
6348
+ function accountPoolApiKeyView(record) {
6349
+ return {
6350
+ id: record.id,
6351
+ tokenPreview: record.tokenPreview,
6352
+ createdAt: record.createdAt,
6353
+ lastUsedAt: record.lastUsedAt,
6354
+ revokedAt: record.revokedAt
6355
+ };
6356
+ }
6357
+ async function listApiKeys(stateDir) {
6358
+ const store = await readStore(stateDir);
6359
+ return Object.values(store.apiKeysById).filter((key) => key.revokedAt === null).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).map(accountPoolApiKeyView);
6360
+ }
6361
+ function hashToken(token) {
6362
+ return (0, import_node_crypto4.createHash)("sha256").update(token).digest("hex");
6363
+ }
6364
+ async function createApiKey(stateDir) {
6365
+ const plaintextToken = `cux_pool_${(0, import_node_crypto4.randomBytes)(24).toString("hex")}`;
6366
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6367
+ const keyRecord = {
6368
+ id: (0, import_node_crypto4.randomUUID)(),
6369
+ tokenHash: hashToken(plaintextToken),
6370
+ tokenPreview: `${plaintextToken.slice(0, 8)}...${plaintextToken.slice(-6)}`,
6371
+ createdAt: now,
6372
+ lastUsedAt: null,
6373
+ revokedAt: null
6374
+ };
6375
+ await updateStore(stateDir, (store) => {
6376
+ store.apiKeysById[keyRecord.id] = keyRecord;
6377
+ });
6378
+ return {
6379
+ key: accountPoolApiKeyView(keyRecord),
6380
+ plaintextToken
6381
+ };
6382
+ }
6383
+ async function revokeApiKey(stateDir, id) {
6384
+ let revoked = false;
6385
+ await updateStore(stateDir, (store) => {
6386
+ const existing = store.apiKeysById[id];
6387
+ if (!existing || existing.revokedAt !== null) {
6388
+ return;
6389
+ }
6390
+ existing.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
6391
+ revoked = true;
6392
+ });
6393
+ return revoked;
6394
+ }
6395
+ async function listSessions(stateDir) {
6396
+ const store = await readStore(stateDir);
6397
+ return Object.values(store.sessionsById).sort((left, right) => right.lastUsedAt.localeCompare(left.lastUsedAt)).flatMap((session) => {
6398
+ const activeSegment = store.segmentsById[session.activeSegmentId];
6399
+ if (!activeSegment) {
6400
+ return [];
6401
+ }
6402
+ return [{
6403
+ id: session.id,
6404
+ affinityKind: session.affinityKind,
6405
+ affinityKey: session.affinityKey,
6406
+ activeProfileName: activeSegment.profileName,
6407
+ model: session.model,
6408
+ status: session.status,
6409
+ lastError: session.lastError,
6410
+ createdAt: session.createdAt,
6411
+ lastUsedAt: session.lastUsedAt,
6412
+ expiresAt: session.expiresAt,
6413
+ lastResponseId: session.lastResponseId,
6414
+ responseCount: session.responseIds.length,
6415
+ segmentCount: session.segmentIds.length,
6416
+ rolloverCount: session.rolloverCount,
6417
+ lastRolloverReason: session.lastRolloverReason
6418
+ }];
6419
+ });
6420
+ }
6421
+ function resolveRuntimeStateDir(runtime, explicitStateDir) {
6422
+ if (explicitStateDir) {
6423
+ return import_node_path7.default.resolve(explicitStateDir);
6424
+ }
6425
+ if (runtime === "daemon") {
6426
+ return DEFAULT_DAEMON_STATE_DIR;
6427
+ }
6428
+ return import_node_path7.default.join(getUserDataDir(), "t3-projects");
6429
+ }
6430
+ function resolveStateDir(flags) {
6431
+ const runtimeFlag = parseStringFlag(flags, "--runtime")?.trim().toLowerCase();
6432
+ const runtime = runtimeFlag === "daemon" ? "daemon" : "desktop";
6433
+ const explicitStateDir = parseStringFlag(flags, "--state-dir") ?? parseStringFlag(flags, "--state") ?? null;
6434
+ return {
6435
+ runtime,
6436
+ stateDir: resolveRuntimeStateDir(runtime, explicitStateDir)
6437
+ };
6438
+ }
6439
+ function resolveBaseUrlFromPort(runtime, port) {
6440
+ if (runtime === "desktop") {
6441
+ return null;
6442
+ }
6443
+ if (port === null) {
6444
+ return null;
6445
+ }
6446
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
6447
+ throw new Error("Daemon port must be an integer between 1 and 65535.");
6448
+ }
6449
+ return `http://127.0.0.1:${port}`;
6450
+ }
6451
+ function resolveBaseUrl(runtime, flags) {
6452
+ return resolveBaseUrlFromPort(runtime, parseIntegerFlag(flags, "--port"));
6453
+ }
6454
+ async function readAccountPoolRuntimeSummary(input = {}) {
6455
+ const runtime = input.runtime ?? "desktop";
6456
+ const stateDir = resolveRuntimeStateDir(runtime, input.stateDir ?? null);
6457
+ const [license, { normalized }, apiKeys, sessions] = await Promise.all([
6458
+ licenseService.getStatus().catch(() => null),
6459
+ readSettingsSnapshot(),
6460
+ listApiKeys(stateDir),
6461
+ listSessions(stateDir)
6462
+ ]);
6463
+ return {
6464
+ runtime,
6465
+ stateDir,
6466
+ baseUrl: resolveBaseUrlFromPort(runtime, input.port ?? null),
6467
+ enabled: normalized.accountPoolEnabled,
6468
+ hasProLicense: license?.isPro === true,
6469
+ routingStrategy: normalized.accountPoolRoutingStrategy,
6470
+ activeApiKeyCount: apiKeys.length,
6471
+ activeSessionCount: sessions.length
6472
+ };
6473
+ }
6474
+ async function readSettingsSnapshot() {
6475
+ const raw = asRecord2(await readAppSettings());
6476
+ return {
6477
+ raw,
6478
+ normalized: normalizeAccountPoolSettings(raw)
6479
+ };
6480
+ }
6481
+ async function updateSettings(transform) {
6482
+ const { raw, normalized } = await readSettingsSnapshot();
6483
+ const nextSettings = transform(normalized);
6484
+ const license = await licenseService.getStatus().catch(() => null);
6485
+ const forcedDisabledByLicense = !license?.isPro && nextSettings.accountPoolEnabled === true;
6486
+ const sanitizedSettings = {
6487
+ ...nextSettings,
6488
+ accountPoolEnabled: forcedDisabledByLicense ? false : nextSettings.accountPoolEnabled,
6489
+ accountPoolRoutingStrategy: normalizeRoutingStrategy(nextSettings.accountPoolRoutingStrategy),
6490
+ accountPoolProfilePool: normalizeStringArray(nextSettings.accountPoolProfilePool),
6491
+ accountPoolExposedModels: normalizeAccountPoolExposedModels(
6492
+ nextSettings.accountPoolExposedModels,
6493
+ true
6494
+ ),
6495
+ accountPoolExposedReasoningEfforts: normalizeAccountPoolReasoningEfforts(
6496
+ nextSettings.accountPoolExposedReasoningEfforts,
6497
+ null
6498
+ ),
6499
+ accountPoolExposedFastModeReasoningEfforts: normalizeAccountPoolReasoningEfforts(
6500
+ nextSettings.accountPoolExposedFastModeReasoningEfforts,
6501
+ null
6502
+ )
6503
+ };
6504
+ await writeAppSettings(applyAccountPoolSettings(raw, sanitizedSettings));
6505
+ return {
6506
+ settings: sanitizedSettings,
6507
+ forcedDisabledByLicense
6508
+ };
6509
+ }
6510
+ async function requireProAccess() {
6511
+ const license = await licenseService.getStatus();
6512
+ if (!license.isPro) {
6513
+ throw new Error(ACCOUNT_POOL_PRO_REQUIRED_MESSAGE);
6514
+ }
6515
+ }
6516
+ async function listSavedProfiles() {
6517
+ const manager = new ProfileManager();
6518
+ return manager.listProfiles();
6519
+ }
6520
+ function printStringList(label, values) {
6521
+ console.log(`${label}: ${values.length > 0 ? values.join(", ") : "(none)"}`);
6522
+ }
6523
+ function printSessionRow(session) {
6524
+ const fields = [
6525
+ `id=${session.id}`,
6526
+ `profile=${session.activeProfileName}`,
6527
+ `status=${session.status}`,
6528
+ `model=${session.model ?? "-"}`,
6529
+ `responses=${session.responseCount}`,
6530
+ `segments=${session.segmentCount}`
6531
+ ];
6532
+ if (session.lastResponseId) {
6533
+ fields.push(`lastResponse=${session.lastResponseId}`);
6534
+ }
6535
+ if (session.lastRolloverReason) {
6536
+ fields.push(`rollover=${session.lastRolloverReason}`);
6537
+ }
6538
+ console.log(fields.join(" "));
6539
+ }
6540
+ function printApiKeyRow(apiKey) {
6541
+ const fields = [
6542
+ `id=${apiKey.id}`,
6543
+ `preview=${apiKey.tokenPreview}`,
6544
+ `created=${apiKey.createdAt}`
6545
+ ];
6546
+ if (apiKey.lastUsedAt) {
6547
+ fields.push(`lastUsed=${apiKey.lastUsedAt}`);
6548
+ }
6549
+ console.log(fields.join(" "));
6550
+ }
6551
+ function getArrayValues(params, startIndex) {
6552
+ return params.slice(startIndex).map((value) => value.trim()).filter(Boolean);
6553
+ }
6554
+ async function handleStatus(flags) {
6555
+ const license = await licenseService.getStatus().catch(() => null);
6556
+ const { raw, normalized } = await readSettingsSnapshot();
6557
+ const { runtime, stateDir } = resolveStateDir(flags);
6558
+ const baseUrl = resolveBaseUrl(runtime, flags);
6559
+ const [profiles, apiKeys, sessions] = await Promise.all([
6560
+ listSavedProfiles(),
6561
+ listApiKeys(stateDir),
6562
+ listSessions(stateDir)
6563
+ ]);
6564
+ const explicitSelection = normalized.accountPoolProfilePool;
6565
+ const selectedProfiles = explicitSelection.length > 0 ? explicitSelection : profiles.map((profile) => profile.name);
6566
+ const missingSelectedProfiles = explicitSelection.filter(
6567
+ (profileName) => !profiles.some((profile) => profile.name === profileName)
6568
+ );
6569
+ console.log(`License: ${license ? `${license.tier} (${license.state})` : "unknown"}`);
6570
+ console.log(`Enabled: ${normalized.accountPoolEnabled ? "yes" : "no"}`);
6571
+ console.log(`Runtime: ${runtime}`);
6572
+ console.log(`State dir: ${stateDir}`);
6573
+ console.log(`Routing: ${formatRoutingStrategy(normalized.accountPoolRoutingStrategy)}`);
6574
+ console.log(`Public API ready: ${normalized.accountPoolEnabled && license?.isPro === true && apiKeys.length > 0 ? "yes" : "no"}`);
6575
+ console.log(`API keys: ${apiKeys.length}`);
6576
+ console.log(`Sessions: ${sessions.length}`);
6577
+ console.log(`Selection mode: ${explicitSelection.length > 0 ? "explicit" : "all saved profiles"}`);
6578
+ if (baseUrl) {
6579
+ console.log(`Base URL: ${baseUrl}`);
6580
+ } else {
6581
+ console.log(
6582
+ runtime === "desktop" ? "Base URL: check the running desktop app for the current Accounts Pool URL." : "Base URL: daemon port not specified here; use the URL printed by `codexuse daemon start` or pass --port=NNNN."
6583
+ );
6584
+ }
6585
+ printStringList("Selected profiles", selectedProfiles);
6586
+ if (missingSelectedProfiles.length > 0) {
6587
+ printStringList("Missing configured profiles", missingSelectedProfiles);
6588
+ }
6589
+ printStringList("Published models", normalized.accountPoolExposedModels);
6590
+ printStringList("Reasoning aliases", normalized.accountPoolExposedReasoningEfforts);
6591
+ printStringList("Fast-mode aliases", normalized.accountPoolExposedFastModeReasoningEfforts);
6592
+ if (raw.accountPoolEnabled !== true && raw.brokerEnabled === true) {
6593
+ console.log("Legacy broker setting detected: runtime is reading it through the new Accounts Pool surface.");
6594
+ }
6595
+ if (normalized.accountPoolEnabled && license?.isPro !== true) {
6596
+ console.log(ACCOUNT_POOL_PRO_REQUIRED_MESSAGE);
6597
+ }
6598
+ }
6599
+ async function handleEnable() {
6600
+ await requireProAccess();
6601
+ const result = await updateSettings((current) => ({
6602
+ ...current,
6603
+ accountPoolEnabled: true
6604
+ }));
6605
+ console.log(result.settings.accountPoolEnabled ? "Accounts Pool enabled." : "Accounts Pool is disabled.");
6606
+ }
6607
+ async function handleDisable() {
6608
+ const result = await updateSettings((current) => ({
6609
+ ...current,
6610
+ accountPoolEnabled: false
6611
+ }));
6612
+ console.log(result.settings.accountPoolEnabled ? "Accounts Pool is enabled." : "Accounts Pool disabled.");
6613
+ }
6614
+ async function handleRouting(params) {
6615
+ const value = parseRoutingStrategy(params[1]);
6616
+ if (!value) {
6617
+ throw new Error("Routing strategy must be `least-used` or `round-robin`.");
6618
+ }
6619
+ const result = await updateSettings((current) => ({
6620
+ ...current,
6621
+ accountPoolRoutingStrategy: value
6622
+ }));
6623
+ console.log(`Accounts Pool routing set to ${formatRoutingStrategy(result.settings.accountPoolRoutingStrategy)}.`);
6624
+ }
6625
+ async function handleProfiles(params) {
6626
+ const action = params[1];
6627
+ const profiles = await listSavedProfiles();
6628
+ const availableProfileNames = profiles.map((profile) => profile.name);
6629
+ const availableSet = new Set(availableProfileNames);
6630
+ const { normalized } = await readSettingsSnapshot();
6631
+ if (!action || action === "list") {
6632
+ const explicitSelection = normalized.accountPoolProfilePool;
6633
+ if (explicitSelection.length === 0) {
6634
+ console.log("Selection: all saved profiles");
6635
+ } else {
6636
+ printStringList("Explicitly selected profiles", explicitSelection);
6637
+ }
6638
+ for (const profile of profiles) {
6639
+ const selected = explicitSelection.length === 0 || explicitSelection.includes(profile.name);
6640
+ const invalidSuffix = profile.isValid ? "" : " [needs re-auth]";
6641
+ console.log(`${selected ? "*" : "-"} ${profile.name}${invalidSuffix}`);
6642
+ }
6643
+ const missing = explicitSelection.filter((name) => !availableSet.has(name));
6644
+ if (missing.length > 0) {
6645
+ printStringList("Missing configured profiles", missing);
6646
+ }
6647
+ return;
6648
+ }
6649
+ const names = getArrayValues(params, 2);
6650
+ if ((action === "set" || action === "add" || action === "remove") && names.length === 0) {
6651
+ throw new Error(`Profile names are required for \`${action}\`.`);
6652
+ }
6653
+ for (const name of names) {
6654
+ if (!availableSet.has(name)) {
6655
+ throw new Error(`Unknown profile: ${name}`);
6656
+ }
6657
+ }
6658
+ const result = await updateSettings((current) => {
6659
+ const currentSelection = current.accountPoolProfilePool.length > 0 ? current.accountPoolProfilePool.slice() : availableProfileNames.slice();
6660
+ let nextSelection = currentSelection;
6661
+ switch (action) {
6662
+ case "set":
6663
+ nextSelection = names.slice();
6664
+ break;
6665
+ case "add":
6666
+ nextSelection = Array.from(/* @__PURE__ */ new Set([...currentSelection, ...names]));
6667
+ break;
6668
+ case "remove":
6669
+ nextSelection = currentSelection.filter((name) => !names.includes(name));
6670
+ break;
6671
+ case "reset":
6672
+ nextSelection = [];
6673
+ break;
6674
+ default:
6675
+ throw new Error("Unknown profiles action.");
6676
+ }
6677
+ return {
6678
+ ...current,
6679
+ accountPoolProfilePool: action === "reset" || nextSelection.length === availableProfileNames.length ? [] : nextSelection
6680
+ };
6681
+ });
6682
+ if (result.settings.accountPoolProfilePool.length === 0) {
6683
+ console.log("Accounts Pool now uses all saved profiles.");
6684
+ return;
6685
+ }
6686
+ printStringList("Accounts Pool selected profiles", result.settings.accountPoolProfilePool);
6687
+ }
6688
+ async function handleArraySetting(input) {
6689
+ const action = input.params[1];
6690
+ const { normalized } = await readSettingsSnapshot();
6691
+ const currentValues = normalized[input.key];
6692
+ if (!action || action === "list") {
6693
+ printStringList(input.label, currentValues);
6694
+ return;
6695
+ }
6696
+ const rawValues = getArrayValues(input.params, 2);
6697
+ if ((action === "add" || action === "remove") && rawValues.length === 0) {
6698
+ throw new Error(`Values are required for \`${input.actionName} ${action}\`.`);
6699
+ }
6700
+ const normalizedValues = rawValues.map(input.normalizeValue).filter((value) => Boolean(value));
6701
+ if ((action === "add" || action === "remove") && normalizedValues.length === 0) {
6702
+ throw new Error("No valid values were provided.");
6703
+ }
6704
+ const result = await updateSettings((current) => {
6705
+ const existing = current[input.key];
6706
+ switch (action) {
6707
+ case "add":
6708
+ return {
6709
+ ...current,
6710
+ [input.key]: Array.from(/* @__PURE__ */ new Set([...existing, ...normalizedValues]))
6711
+ };
6712
+ case "remove":
6713
+ return {
6714
+ ...current,
6715
+ [input.key]: existing.filter((value) => !normalizedValues.includes(value))
6716
+ };
6717
+ case "reset":
6718
+ return {
6719
+ ...current,
6720
+ [input.key]: [...input.defaultValues]
6721
+ };
6722
+ default:
6723
+ throw new Error(`Unknown ${input.actionName} action.`);
6724
+ }
6725
+ });
6726
+ printStringList(input.label, result.settings[input.key]);
6727
+ }
6728
+ async function handleKeys(params, flags) {
6729
+ const action = params[1];
6730
+ const { runtime, stateDir } = resolveStateDir(flags);
6731
+ const baseUrl = resolveBaseUrl(runtime, flags);
6732
+ if (!action || action === "list") {
6733
+ const apiKeys = await listApiKeys(stateDir);
6734
+ if (apiKeys.length === 0) {
6735
+ console.log("No active Accounts Pool API keys.");
6736
+ return;
6737
+ }
6738
+ for (const apiKey of apiKeys) {
6739
+ printApiKeyRow(apiKey);
6740
+ }
6741
+ return;
6742
+ }
6743
+ switch (action) {
6744
+ case "create": {
6745
+ await requireProAccess();
6746
+ const created = await createApiKey(stateDir);
6747
+ console.log(`Created key: ${created.key.id}`);
6748
+ console.log(`Preview: ${created.key.tokenPreview}`);
6749
+ console.log(`Token: ${created.plaintextToken}`);
6750
+ console.log(`Runtime: ${runtime}`);
6751
+ console.log(`State dir: ${stateDir}`);
6752
+ if (baseUrl) {
6753
+ console.log(`Base URL: ${baseUrl}`);
6754
+ } else if (runtime === "desktop") {
6755
+ console.log("Base URL: use the current Accounts Pool URL shown by the running desktop app.");
6756
+ } else if (runtime === "daemon") {
6757
+ console.log("Base URL: pass --port=NNNN here or use the URL printed by `codexuse daemon start`.");
6758
+ }
6759
+ console.log("This key only works for the selected runtime state dir.");
6760
+ console.log("Store this token now. The plaintext value is only shown once.");
6761
+ return;
6762
+ }
6763
+ case "revoke": {
6764
+ const id = params[2]?.trim();
6765
+ if (!id) {
6766
+ throw new Error("API key id is required.");
6767
+ }
6768
+ const removed = await revokeApiKey(stateDir, id);
6769
+ if (!removed) {
6770
+ throw new Error(`No Accounts Pool API key found for id ${id}.`);
6771
+ }
6772
+ console.log(`Revoked key: ${id}`);
6773
+ return;
6774
+ }
6775
+ default:
6776
+ throw new Error("Unknown keys action.");
6777
+ }
6778
+ }
6779
+ async function handleSessions(params, flags) {
6780
+ const action = params[1];
6781
+ if (action && action !== "list") {
6782
+ throw new Error("Only `sessions list` is supported here. Session mutation stays runtime-owned.");
6783
+ }
6784
+ const { stateDir } = resolveStateDir(flags);
6785
+ const sessions = await listSessions(stateDir);
6786
+ if (sessions.length === 0) {
6787
+ console.log("No pooled sessions in this runtime store.");
6788
+ return;
6789
+ }
6790
+ for (const session of sessions) {
6791
+ printSessionRow(session);
6792
+ }
6793
+ console.log("Session mutation is intentionally not exposed here; pooled sessions belong to the live runtime owner.");
6794
+ }
6795
+ async function handleAccountPoolCommand(args, version) {
6796
+ const flags = args.filter((arg) => arg.startsWith("-"));
6797
+ const params = stripFlags(args);
6798
+ const sub = params[0];
6799
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
6800
+ printAccountPoolHelp(version);
6801
+ return;
6802
+ }
6803
+ switch (sub) {
6804
+ case "status":
6805
+ await handleStatus(flags);
6806
+ return;
6807
+ case "enable":
6808
+ await handleEnable();
6809
+ return;
6810
+ case "disable":
6811
+ await handleDisable();
6812
+ return;
6813
+ case "routing":
6814
+ await handleRouting(params);
6815
+ return;
6816
+ case "profiles":
6817
+ await handleProfiles(params);
6818
+ return;
6819
+ case "models":
6820
+ await handleArraySetting({
6821
+ label: "Published models",
6822
+ actionName: "models",
6823
+ params,
6824
+ key: "accountPoolExposedModels",
6825
+ defaultValues: DEFAULT_ACCOUNT_POOL_EXPOSED_MODELS,
6826
+ normalizeValue: (value) => normalizeCodexModelSlug(value) ?? asString3(value)
6827
+ });
6828
+ return;
6829
+ case "reasoning":
6830
+ await handleArraySetting({
6831
+ label: "Reasoning aliases",
6832
+ actionName: "reasoning",
6833
+ params,
6834
+ key: "accountPoolExposedReasoningEfforts",
6835
+ defaultValues: DEFAULT_ACCOUNT_POOL_EXPOSED_REASONING_EFFORTS,
6836
+ normalizeValue: (value) => normalizeReasoningEffort(value)
6837
+ });
6838
+ return;
6839
+ case "fast-mode":
6840
+ await handleArraySetting({
6841
+ label: "Fast-mode aliases",
6842
+ actionName: "fast-mode",
6843
+ params,
6844
+ key: "accountPoolExposedFastModeReasoningEfforts",
6845
+ defaultValues: DEFAULT_ACCOUNT_POOL_EXPOSED_FAST_MODE_REASONING_EFFORTS,
6846
+ normalizeValue: (value) => normalizeReasoningEffort(value)
6847
+ });
6848
+ return;
6849
+ case "keys":
6850
+ await handleKeys(params, flags);
6851
+ return;
6852
+ case "sessions":
6853
+ await handleSessions(params, flags);
6854
+ return;
6855
+ default:
6856
+ printHelp(version);
6857
+ return;
6858
+ }
6859
+ }
6860
+
6861
+ // src/commands/daemon.ts
6862
+ var import_node_child_process4 = require("child_process");
6863
+ var import_node_crypto5 = require("crypto");
6864
+ var import_node_fs6 = __toESM(require("fs"));
6865
+ var import_node_net = __toESM(require("net"));
6866
+ var import_node_os4 = __toESM(require("os"));
6867
+ var import_node_path9 = __toESM(require("path"));
6868
+
6869
+ // ../../packages/shared/src/core/internal-js-runtime.ts
6870
+ var import_node_child_process3 = require("child_process");
6871
+ var import_node_path8 = __toESM(require("path"), 1);
6872
+ var cachedBunBinary = null;
6873
+ function hasBunRuntime() {
6874
+ return typeof process.versions.bun === "string" && process.versions.bun.length > 0;
6875
+ }
6876
+ function buildRuntimePath(env) {
6877
+ const homeDir = env.HOME ?? env.USERPROFILE ?? "";
6878
+ const pathHints = (env.CODEX_PATH_HINTS ?? "").split(import_node_path8.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
6879
+ const entries = [
6880
+ env.PATH ?? "",
6881
+ import_node_path8.default.join(homeDir, ".bun", "bin"),
6882
+ "/opt/homebrew/bin",
6883
+ "/usr/local/bin",
6884
+ import_node_path8.default.join(homeDir, ".local", "bin"),
6885
+ import_node_path8.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
6886
+ import_node_path8.default.join(homeDir, ".fnm", "current", "bin"),
6887
+ ...pathHints
6888
+ ];
6889
+ return Array.from(
6890
+ new Set(
6891
+ entries.flatMap((entry) => entry.split(import_node_path8.default.delimiter)).map((entry) => entry.trim()).filter(Boolean)
6892
+ )
6893
+ ).join(import_node_path8.default.delimiter);
6894
+ }
6895
+ function resolveConfiguredBunBinary(env) {
6896
+ const candidates = [
6897
+ env.CODEXUSE_INTERNAL_BUN_BIN,
6898
+ env.BUN_BIN,
6899
+ process.env.CODEXUSE_INTERNAL_BUN_BIN,
6900
+ process.env.BUN_BIN
6901
+ ];
6902
+ for (const candidate of candidates) {
6903
+ const normalized = candidate?.trim() ?? "";
6904
+ if (normalized.length > 0) {
6905
+ return normalized;
6906
+ }
6907
+ }
6908
+ return null;
6909
+ }
6910
+ function probeBunBinary(env) {
6911
+ if (hasBunRuntime()) {
6912
+ return process.execPath;
6913
+ }
6914
+ if (cachedBunBinary) {
6915
+ return cachedBunBinary;
6916
+ }
6917
+ const homeDir = env.HOME ?? env.USERPROFILE ?? "";
6918
+ const candidates = Array.from(
6919
+ new Set(
6920
+ [
6921
+ env.CODEXUSE_INTERNAL_BUN_BIN?.trim(),
6922
+ env.BUN_BIN?.trim(),
6923
+ import_node_path8.default.join(homeDir, ".bun", "bin", "bun"),
6924
+ "/opt/homebrew/bin/bun",
6925
+ "/usr/local/bin/bun",
6926
+ "bun"
6927
+ ].filter((candidate) => typeof candidate === "string" && candidate.length > 0)
6928
+ )
6929
+ );
6930
+ for (const candidate of candidates) {
6931
+ const probe = (0, import_node_child_process3.spawnSync)(candidate, ["--version"], {
6932
+ env,
6933
+ stdio: "ignore"
6934
+ });
6935
+ if (!probe.error && probe.status === 0) {
6936
+ cachedBunBinary = candidate;
6937
+ return candidate;
6938
+ }
6939
+ }
6940
+ return null;
6941
+ }
6942
+ function resolveInternalBunRuntime(overrides = {}) {
6943
+ const env = {
6944
+ ...process.env,
6945
+ ...overrides
6946
+ };
6947
+ env.PATH = buildRuntimePath(env);
6948
+ const runtimeBin = hasBunRuntime() ? process.execPath : resolveConfiguredBunBinary(env) ?? probeBunBinary(env);
6949
+ if (!runtimeBin) {
6950
+ throw new Error(
6951
+ "CodexUse desktop requires a Bun runtime to launch internal services. Install Bun or set CODEXUSE_INTERNAL_BUN_BIN."
6952
+ );
6953
+ }
6954
+ delete env.ELECTRON_RUN_AS_NODE;
6955
+ return {
6956
+ bin: runtimeBin,
6957
+ env
6958
+ };
6959
+ }
6960
+
6961
+ // src/commands/daemon.ts
6962
+ function normalizeTelegramBotToken(value) {
6963
+ const trimmed = value?.trim() ?? "";
6964
+ return trimmed.length > 0 ? trimmed : null;
6965
+ }
6966
+ function printDaemonHelp(version) {
6967
+ console.log(`CodexUse CLI v${version}
6968
+
6969
+ Usage:
6970
+ codexuse daemon start [--port=NNNN] [--telegram-bot-token=<token>] [--project-path=/abs/path]
6971
+
6972
+ Notes:
6973
+ - Runs the T3 Projects/chat server in headless mode.
6974
+ - The same daemon can serve Accounts Pool and Telegram remote control.
6975
+ - Requires Bun on PATH because the bundled Projects server runs on Bun.
6976
+ - Pass --port to keep a stable local API URL for Accounts Pool or other local clients.
6977
+ - Pass --telegram-bot-token or set CODEXUSE_TELEGRAM_BOT_TOKEN to enable Telegram remote control.
6978
+ - --project-path bootstraps that folder as the default project/thread.
6979
+ - Stop with Ctrl+C or SIGTERM.
6980
+ `);
6981
+ }
6982
+ function resolveServerEntry() {
6983
+ const candidates = [
6984
+ import_node_path9.default.resolve(__dirname, "server", "index.mjs"),
6985
+ import_node_path9.default.resolve(__dirname, "../../../server/dist/index.mjs")
6986
+ ];
6987
+ for (const candidate of candidates) {
6988
+ if (import_node_fs6.default.existsSync(candidate)) {
6989
+ return candidate;
6990
+ }
6991
+ }
6992
+ return candidates[0];
6993
+ }
6994
+ function resolveStateDir2() {
6995
+ return import_node_path9.default.join(import_node_os4.default.homedir(), ".codexuse", "t3-daemon");
6996
+ }
6997
+ function reserveLoopbackPort(port = 0) {
6998
+ return new Promise((resolve, reject) => {
6999
+ const server = import_node_net.default.createServer();
7000
+ server.unref();
7001
+ server.once("error", reject);
7002
+ server.listen(port, "127.0.0.1", () => {
7003
+ const address = server.address();
7004
+ const port2 = typeof address === "object" && address ? address.port : null;
7005
+ server.close((error) => {
7006
+ if (error) {
7007
+ reject(error);
7008
+ return;
7009
+ }
7010
+ if (typeof port2 !== "number") {
7011
+ reject(new Error("Failed to reserve a loopback port for the Projects server."));
7012
+ return;
7013
+ }
7014
+ resolve(port2);
7015
+ });
7016
+ });
7017
+ });
7018
+ }
7019
+ async function runDaemonStart(options) {
7020
+ const entry = resolveServerEntry();
7021
+ if (!import_node_fs6.default.existsSync(entry)) {
7022
+ throw new Error(
7023
+ `Missing bundled T3 server build output at ${entry}. Rebuild the CLI package with \`bun run build:cli\`.`
7024
+ );
7025
+ }
7026
+ if (options.port !== null && (!Number.isFinite(options.port) || options.port < 1 || options.port > 65535)) {
7027
+ throw new Error("Daemon port must be an integer between 1 and 65535.");
7028
+ }
7029
+ const port = await reserveLoopbackPort(options.port ?? 0);
7030
+ const authToken = (0, import_node_crypto5.randomBytes)(24).toString("hex");
7031
+ const stateDir = resolveStateDir2();
7032
+ const projectPath = options.projectPath ? import_node_path9.default.resolve(options.projectPath) : null;
7033
+ const childCwd = projectPath ?? process.cwd();
7034
+ const telegramBotToken = options.telegramBotToken?.trim() || null;
7035
+ const runtime = resolveInternalBunRuntime({
7036
+ T3CODE_MODE: "desktop",
7037
+ T3CODE_HOST: "127.0.0.1",
7038
+ T3CODE_PORT: String(port),
7039
+ T3CODE_NO_BROWSER: "1",
7040
+ T3CODE_AUTH_TOKEN: authToken,
7041
+ T3CODE_STATE_DIR: stateDir,
7042
+ T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: projectPath ? "1" : "0",
7043
+ CODEXUSE_TELEGRAM_RUNTIME_OWNER: "daemon",
7044
+ ...telegramBotToken ? {
7045
+ CODEXUSE_TELEGRAM_BOT_TOKEN: telegramBotToken,
7046
+ CODEXUSE_TELEGRAM_BRIDGE_ENABLED: "1"
7047
+ } : {}
7048
+ });
7049
+ const child = (0, import_node_child_process4.spawn)(runtime.bin, [entry], {
7050
+ cwd: childCwd,
7051
+ env: runtime.env,
7052
+ stdio: "inherit"
7053
+ });
7054
+ console.log(`Daemon started. Projects server listening on http://127.0.0.1:${port}`);
7055
+ console.log(`State dir: ${stateDir}`);
7056
+ if (projectPath) {
7057
+ console.log(`Bootstrapped project path: ${projectPath}`);
7058
+ }
7059
+ if (telegramBotToken) {
7060
+ console.log("Telegram remote control enabled for this daemon session.");
7061
+ }
7062
+ const accountPool = await readAccountPoolRuntimeSummary({
7063
+ runtime: "daemon",
7064
+ stateDir,
7065
+ port
7066
+ });
7067
+ if (accountPool.enabled && accountPool.hasProLicense) {
7068
+ console.log(`Accounts Pool base URL: ${accountPool.baseUrl ?? `http://127.0.0.1:${port}`}`);
7069
+ if (accountPool.activeApiKeyCount > 0) {
7070
+ console.log(`Accounts Pool active keys: ${accountPool.activeApiKeyCount}`);
7071
+ } else {
7072
+ console.log("Accounts Pool is enabled, but this daemon has no runtime key yet. Create one with `codexuse account-pool keys create --runtime=daemon`.");
7073
+ }
7074
+ } else if (accountPool.enabled) {
7075
+ console.log("Accounts Pool is configured, but Pro is required before this daemon can serve it.");
7076
+ } else {
7077
+ console.log("Accounts Pool is disabled for this daemon. Enable it with `codexuse account-pool enable`.");
7078
+ }
7079
+ if (options.port === null) {
7080
+ console.log("Tip: pass --port=3773 (or another fixed port) if another local client should keep one stable base URL.");
7081
+ }
7082
+ console.log("Running until SIGINT/SIGTERM...");
7083
+ await waitForChild(child);
7084
+ }
7085
+ function waitForChild(child) {
7086
+ return new Promise((resolve, reject) => {
7087
+ let stopping = false;
7088
+ const stop = (signal) => {
7089
+ if (stopping) {
7090
+ return;
7091
+ }
7092
+ stopping = true;
7093
+ child.kill(signal);
7094
+ };
7095
+ const onSigInt = () => stop("SIGINT");
7096
+ const onSigTerm = () => stop("SIGTERM");
7097
+ process.once("SIGINT", onSigInt);
7098
+ process.once("SIGTERM", onSigTerm);
7099
+ child.once("exit", (code, signal) => {
7100
+ process.off("SIGINT", onSigInt);
7101
+ process.off("SIGTERM", onSigTerm);
7102
+ if (code === 0 || stopping) {
7103
+ resolve();
7104
+ return;
7105
+ }
7106
+ reject(
7107
+ new Error(
7108
+ `Projects server exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"}).`
7109
+ )
7110
+ );
7111
+ });
7112
+ child.once("error", (error) => {
7113
+ process.off("SIGINT", onSigInt);
7114
+ process.off("SIGTERM", onSigTerm);
7115
+ reject(error);
7116
+ });
7117
+ });
7118
+ }
7119
+ async function handleDaemonCommand(args, version) {
7120
+ const flags = args.filter((arg) => arg.startsWith("-"));
7121
+ const params = stripFlags(args);
7122
+ const sub = params[0];
7123
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
7124
+ printDaemonHelp(version);
7125
+ return;
7126
+ }
7127
+ switch (sub) {
7128
+ case "start": {
7129
+ const port = parseIntegerFlag(flags, "--port");
7130
+ const projectPath = parseStringFlag(flags, "--project-path") ?? parseStringFlag(flags, "--project") ?? null;
7131
+ const telegramBotToken = parseStringFlag(flags, "--telegram-bot-token") ?? normalizeTelegramBotToken(process.env.CODEXUSE_TELEGRAM_BOT_TOKEN) ?? null;
7132
+ await runDaemonStart({
7133
+ projectPath,
7134
+ port,
7135
+ telegramBotToken,
7136
+ version
7137
+ });
7138
+ return;
5712
7139
  }
5713
- return {
5714
- name: record.name,
5715
- displayName: record.displayName,
5716
- email: record.email,
5717
- accountId: record.accountId,
5718
- workspaceId: record.workspaceId,
5719
- workspaceName: record.workspaceName,
5720
- authMethod: record.authMethod,
5721
- createdAt: record.createdAt,
5722
- metadata: record.metadata,
5723
- exportedAt: (/* @__PURE__ */ new Date()).toISOString()
5724
- };
7140
+ default:
7141
+ printDaemonHelp(version);
7142
+ return;
5725
7143
  }
5726
- /**
5727
- * Export profiles for cloud sync (includes token-bearing auth data).
5728
- */
5729
- async exportProfilesForCloudSync() {
5730
- await this.initialize();
5731
- const records = await this.listProfileRecords();
5732
- return records.map((record) => ({
5733
- name: record.name,
5734
- displayName: record.displayName,
5735
- data: record.data,
5736
- metadata: record.metadata,
5737
- accountId: record.accountId,
5738
- workspaceId: record.workspaceId,
5739
- workspaceName: record.workspaceName,
5740
- email: record.email,
5741
- authMethod: record.authMethod,
5742
- createdAt: record.createdAt,
5743
- updatedAt: record.updatedAt
5744
- }));
7144
+ }
7145
+
7146
+ // src/commands/license.ts
7147
+ async function handleLicense(args, version) {
7148
+ const flags = args.filter((arg) => arg.startsWith("-"));
7149
+ const params = stripFlags(args);
7150
+ const sub = params[0];
7151
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
7152
+ printHelp(version);
7153
+ return;
5745
7154
  }
5746
- /**
5747
- * Replace local profiles with a full snapshot from cloud sync.
5748
- */
5749
- async replaceProfilesFromCloudSync(records) {
5750
- await this.initialize();
5751
- const now = (/* @__PURE__ */ new Date()).toISOString();
5752
- const normalized = /* @__PURE__ */ new Map();
5753
- const existingMap = await this.readProfilesStateMap();
5754
- const existingNames = new Set(Object.keys(existingMap));
5755
- for (const candidate of records ?? []) {
5756
- try {
5757
- const name = this.normalizeProfileName(candidate?.name ?? "");
5758
- const data = candidate && typeof candidate.data === "object" && candidate.data !== null ? candidate.data : null;
5759
- if (!data) {
5760
- continue;
5761
- }
5762
- const metadata = candidate.metadata && typeof candidate.metadata === "object" ? candidate.metadata : void 0;
5763
- const record = {
5764
- name,
5765
- displayName: typeof candidate.displayName === "string" ? candidate.displayName : null,
5766
- data,
5767
- metadata,
5768
- accountId: typeof candidate.accountId === "string" ? candidate.accountId : null,
5769
- workspaceId: typeof candidate.workspaceId === "string" ? candidate.workspaceId : null,
5770
- workspaceName: typeof candidate.workspaceName === "string" ? candidate.workspaceName : null,
5771
- email: typeof candidate.email === "string" ? candidate.email : null,
5772
- authMethod: typeof candidate.authMethod === "string" ? candidate.authMethod : this.resolveAuthMethod(data),
5773
- createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : now,
5774
- updatedAt: typeof candidate.updatedAt === "string" ? candidate.updatedAt : now
5775
- };
5776
- normalized.set(name, record);
5777
- } catch {
7155
+ switch (sub) {
7156
+ case "status": {
7157
+ const forceRefresh = hasFlag(flags, "--refresh");
7158
+ const status = await licenseService.getStatus({ forceRefresh });
7159
+ console.log(`Tier: ${status.tier}`);
7160
+ console.log(`State: ${status.state}`);
7161
+ if (status.profileLimit !== null) {
7162
+ console.log(`Profile limit: ${status.profileLimit}`);
5778
7163
  }
5779
- }
5780
- const nextMap = {};
5781
- for (const record of normalized.values()) {
5782
- nextMap[record.name] = this.toStateRecord(record);
5783
- }
5784
- await this.writeProfilesStateMap(nextMap);
5785
- let removed = 0;
5786
- for (const name of existingNames) {
5787
- if (normalized.has(name)) {
5788
- continue;
7164
+ if (status.profilesRemaining !== null) {
7165
+ console.log(`Profiles remaining: ${status.profilesRemaining}`);
5789
7166
  }
5790
- try {
5791
- await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
5792
- removed += 1;
5793
- } catch (error) {
5794
- if (!this.isNotFoundError(error)) {
5795
- logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
5796
- }
7167
+ if (status.purchaseEmail) {
7168
+ console.log(`Email: ${status.purchaseEmail}`);
7169
+ }
7170
+ if (status.maskedLicenseKey) {
7171
+ console.log(`License: ${status.maskedLicenseKey}`);
7172
+ }
7173
+ if (status.message) {
7174
+ console.log(`Message: ${status.message}`);
7175
+ }
7176
+ if (status.error) {
7177
+ console.log(`Error: ${status.error}`);
5797
7178
  }
7179
+ return;
5798
7180
  }
5799
- const preferred = await this.readPreferredProfileName();
5800
- if (preferred) {
5801
- try {
5802
- const normalizedPreferred = this.normalizeProfileName(preferred);
5803
- if (!normalized.has(normalizedPreferred)) {
5804
- await this.persistPreferredProfileName(null);
5805
- }
5806
- } catch {
5807
- await this.persistPreferredProfileName(null);
7181
+ case "activate": {
7182
+ const key = params[1];
7183
+ if (!key) {
7184
+ throw new Error("License key is required.");
5808
7185
  }
7186
+ const status = await licenseService.activate(key);
7187
+ console.log(status.message ?? "License activated.");
7188
+ return;
5809
7189
  }
5810
- return {
5811
- imported: normalized.size,
5812
- removed
5813
- };
7190
+ default:
7191
+ printHelp(version);
5814
7192
  }
5815
- };
7193
+ }
5816
7194
 
5817
7195
  // ../../packages/runtime-profiles/src/license/guard.ts
5818
7196
  async function assertProfileCreationAllowed(profileManager) {
@@ -5842,15 +7220,15 @@ function maxUsedPercent(snapshot) {
5842
7220
  }
5843
7221
 
5844
7222
  // src/platform/codexCli.ts
5845
- var import_node_child_process4 = require("child_process");
5846
- var import_node_fs6 = require("fs");
5847
- var import_node_path7 = __toESM(require("path"));
7223
+ var import_node_child_process5 = require("child_process");
7224
+ var import_node_fs7 = require("fs");
7225
+ var import_node_path10 = __toESM(require("path"));
5848
7226
  var ENV_HINTS2 = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
5849
7227
  function fileExists2(candidate) {
5850
7228
  if (!candidate) return null;
5851
- const resolved = import_node_path7.default.resolve(candidate);
7229
+ const resolved = import_node_path10.default.resolve(candidate);
5852
7230
  try {
5853
- const stat2 = (0, import_node_fs6.statSync)(resolved);
7231
+ const stat2 = (0, import_node_fs7.statSync)(resolved);
5854
7232
  if (stat2.isFile()) return resolved;
5855
7233
  } catch {
5856
7234
  return null;
@@ -5868,11 +7246,11 @@ function resolveFromEnv() {
5868
7246
  }
5869
7247
  function resolveFromPath() {
5870
7248
  const pathValue = process.env.PATH ?? "";
5871
- const entries = pathValue.split(import_node_path7.default.delimiter).filter(Boolean);
7249
+ const entries = pathValue.split(import_node_path10.default.delimiter).filter(Boolean);
5872
7250
  const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
5873
7251
  for (const entry of entries) {
5874
7252
  for (const name of names) {
5875
- const candidate = fileExists2(import_node_path7.default.join(entry, name));
7253
+ const candidate = fileExists2(import_node_path10.default.join(entry, name));
5876
7254
  if (candidate) return candidate;
5877
7255
  }
5878
7256
  }
@@ -5888,7 +7266,7 @@ function requireCodexBinary(context) {
5888
7266
  const message = context ? `${context} ${hint}` : hint;
5889
7267
  throw new Error(message);
5890
7268
  }
5891
- function buildCodexCommand(codexPath, args) {
7269
+ function buildCodexCommand2(codexPath, args) {
5892
7270
  const normalized = codexPath.toLowerCase();
5893
7271
  const isJs = normalized.endsWith(".js");
5894
7272
  if (isJs) {
@@ -5920,8 +7298,8 @@ async function runCodexLogin(mode) {
5920
7298
  const codexPath = requireCodexBinary("Codex CLI is required to login.");
5921
7299
  const resolvedMode = resolveLoginMode(mode ?? null);
5922
7300
  const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
5923
- const { command, args, shell } = buildCodexCommand(codexPath, loginArgs);
5924
- const child = (0, import_node_child_process4.spawn)(command, args, {
7301
+ const { command, args, shell } = buildCodexCommand2(codexPath, loginArgs);
7302
+ const child = (0, import_node_child_process5.spawn)(command, args, {
5925
7303
  stdio: "inherit",
5926
7304
  env: process.env,
5927
7305
  shell
@@ -5942,7 +7320,7 @@ function formatProfileLabel(name, displayName) {
5942
7320
  return name;
5943
7321
  }
5944
7322
  function profileRateLimitKey(profile) {
5945
- return profile.accountId ?? `profile:${profile.name}`;
7323
+ return resolveProfileIdentityKeyFromProfile(profile);
5946
7324
  }
5947
7325
  function toTitleCase(value) {
5948
7326
  return value.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
@@ -6429,9 +7807,9 @@ function delay(ms) {
6429
7807
  }
6430
7808
  async function handleProfileCommand(args, version) {
6431
7809
  const flags = args.filter((arg) => arg.startsWith("-"));
6432
- const params = stripFlags2(args);
7810
+ const params = stripFlags(args);
6433
7811
  const sub = params[0];
6434
- if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
7812
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
6435
7813
  printHelp(version);
6436
7814
  return;
6437
7815
  }
@@ -6444,8 +7822,8 @@ async function handleProfileCommand(args, version) {
6444
7822
  console.log("No profiles found.");
6445
7823
  return;
6446
7824
  }
6447
- const compact = hasFlag2(flags, "--compact");
6448
- const withUsage = !hasFlag2(flags, "--no-usage");
7825
+ const compact = hasFlag(flags, "--compact");
7826
+ const withUsage = !hasFlag(flags, "--no-usage");
6449
7827
  let usageMap = null;
6450
7828
  if (withUsage) {
6451
7829
  const codexPath = resolveCodexBinary();
@@ -6491,7 +7869,7 @@ async function handleProfileCommand(args, version) {
6491
7869
  throw new Error("Profile name is required.");
6492
7870
  }
6493
7871
  await assertProfileCreationAllowed(manager);
6494
- if (!hasFlag2(flags, "--skip-login")) {
7872
+ if (!hasFlag(flags, "--skip-login")) {
6495
7873
  const loginMode = resolveLoginMode(parseLoginMode(flags));
6496
7874
  await runCodexLogin(loginMode);
6497
7875
  }
@@ -6505,7 +7883,7 @@ async function handleProfileCommand(args, version) {
6505
7883
  if (!name) {
6506
7884
  throw new Error("Profile name is required.");
6507
7885
  }
6508
- if (!hasFlag2(flags, "--skip-login")) {
7886
+ if (!hasFlag(flags, "--skip-login")) {
6509
7887
  const loginMode = resolveLoginMode(parseLoginMode(flags));
6510
7888
  await runCodexLogin(loginMode);
6511
7889
  }
@@ -6525,8 +7903,8 @@ async function handleProfileCommand(args, version) {
6525
7903
  }
6526
7904
  case "autoroll":
6527
7905
  case "auto-roll": {
6528
- const watch = hasFlag2(flags, "--watch");
6529
- const dryRun = hasFlag2(flags, "--dry-run");
7906
+ const watch = hasFlag(flags, "--watch");
7907
+ const dryRun = hasFlag(flags, "--dry-run");
6530
7908
  const settings = await getStoredAutoRollSettings().catch(() => null);
6531
7909
  const fallbackThreshold = typeof settings?.switchThreshold === "number" && Number.isFinite(settings.switchThreshold) ? Math.round(settings.switchThreshold) : DEFAULT_AUTOROLL_THRESHOLD;
6532
7910
  const threshold = parseAutoRollThreshold(flags, fallbackThreshold);
@@ -6581,8 +7959,8 @@ async function handleProfileCommand(args, version) {
6581
7959
  }
6582
7960
 
6583
7961
  // ../../packages/runtime-profiles/src/cloud-sync/service.ts
6584
- var import_node_fs7 = require("fs");
6585
- var import_node_path11 = __toESM(require("path"), 1);
7962
+ var import_node_fs8 = require("fs");
7963
+ var import_node_path14 = __toESM(require("path"), 1);
6586
7964
 
6587
7965
  // ../../packages/contracts/src/cloud-sync/types.ts
6588
7966
  var CLOUD_SYNC_SCHEMA_VERSION = 1;
@@ -6742,9 +8120,9 @@ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
6742
8120
  var import_toml2 = __toESM(require_toml(), 1);
6743
8121
 
6744
8122
  // ../../packages/runtime-codex/src/codex/config-metadata.ts
6745
- var import_node_child_process5 = require("child_process");
8123
+ var import_node_child_process6 = require("child_process");
6746
8124
  var import_promises = require("fs/promises");
6747
- var import_node_path8 = __toESM(require("path"), 1);
8125
+ var import_node_path11 = __toESM(require("path"), 1);
6748
8126
  var CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/openai/codex/main/codex-rs/core/config.schema.json";
6749
8127
  var METADATA_CACHE_TTL_MS = 6e4;
6750
8128
  var FALLBACK_SCHEMA_TOP_LEVEL_KEYS = [
@@ -6854,7 +8232,7 @@ function uniqueSorted(values) {
6854
8232
  }
6855
8233
  async function runCommand(command, args, timeoutMs = 3e3) {
6856
8234
  return new Promise((resolve, reject) => {
6857
- const child = (0, import_node_child_process5.spawn)(command, args, {
8235
+ const child = (0, import_node_child_process6.spawn)(command, args, {
6858
8236
  stdio: ["ignore", "pipe", "pipe"],
6859
8237
  env: process.env
6860
8238
  });
@@ -6930,8 +8308,8 @@ function parseSchemaKeys(schemaRaw) {
6930
8308
  async function readSchemaFromLocal() {
6931
8309
  const candidates = [
6932
8310
  process.env.CODEX_CONFIG_SCHEMA_PATH,
6933
- import_node_path8.default.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
6934
- import_node_path8.default.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json")
8311
+ import_node_path11.default.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
8312
+ import_node_path11.default.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json")
6935
8313
  ].filter((candidate) => Boolean(candidate));
6936
8314
  for (const candidate of candidates) {
6937
8315
  try {
@@ -7036,20 +8414,20 @@ async function getCodexConfigMetadata(forceRefresh = false) {
7036
8414
 
7037
8415
  // ../../packages/runtime-codex/src/codex/config-io.ts
7038
8416
  var import_promises2 = require("fs/promises");
7039
- var import_node_path10 = __toESM(require("path"), 1);
8417
+ var import_node_path13 = __toESM(require("path"), 1);
7040
8418
 
7041
8419
  // ../../packages/runtime-codex/src/codex/home.ts
7042
- var import_node_os4 = __toESM(require("os"), 1);
7043
- var import_node_path9 = __toESM(require("path"), 1);
8420
+ var import_node_os5 = __toESM(require("os"), 1);
8421
+ var import_node_path12 = __toESM(require("path"), 1);
7044
8422
  function resolveHomeDir() {
7045
- return process.env.HOME || process.env.USERPROFILE || import_node_os4.default.homedir();
8423
+ return process.env.HOME || process.env.USERPROFILE || import_node_os5.default.homedir();
7046
8424
  }
7047
8425
  function expandHomePrefix(input, homeDir) {
7048
8426
  if (input === "~") {
7049
8427
  return homeDir;
7050
8428
  }
7051
8429
  if (input.startsWith("~/") || input.startsWith("~\\")) {
7052
- return import_node_path9.default.join(homeDir, input.slice(2));
8430
+ return import_node_path12.default.join(homeDir, input.slice(2));
7053
8431
  }
7054
8432
  return input;
7055
8433
  }
@@ -7065,13 +8443,13 @@ function normalizeCodexHomePath(input) {
7065
8443
  if (!configured) {
7066
8444
  return null;
7067
8445
  }
7068
- return import_node_path9.default.resolve(expandHomePrefix(configured, resolveHomeDir()));
8446
+ return import_node_path12.default.resolve(expandHomePrefix(configured, resolveHomeDir()));
7069
8447
  }
7070
8448
  function resolveDefaultCodexHomeDir() {
7071
- return import_node_path9.default.join(resolveHomeDir(), ".codex");
8449
+ return import_node_path12.default.join(resolveHomeDir(), ".codex");
7072
8450
  }
7073
8451
  function resolveProfileCodexHomeDir(profileName) {
7074
- return import_node_path9.default.join(getUserDataDir(), "profile-homes", profileName);
8452
+ return import_node_path12.default.join(getUserDataDir(), "profile-homes", profileName);
7075
8453
  }
7076
8454
  function resolveCodexHomeDir(options) {
7077
8455
  return normalizeCodexHomePath(options?.codexHomePath) ?? resolveDefaultCodexHomeDir();
@@ -7152,10 +8530,10 @@ var YOLO_PRESET = {
7152
8530
 
7153
8531
  // ../../packages/runtime-codex/src/codex/config-io.ts
7154
8532
  function getConfigPath(options) {
7155
- return import_node_path10.default.join(resolveCodexHomeDir(options), "config.toml");
8533
+ return import_node_path13.default.join(resolveCodexHomeDir(options), "config.toml");
7156
8534
  }
7157
8535
  async function ensureConfigDirExists(filePath) {
7158
- const dir = import_node_path10.default.dirname(filePath);
8536
+ const dir = import_node_path13.default.dirname(filePath);
7159
8537
  await (0, import_promises2.mkdir)(dir, { recursive: true });
7160
8538
  }
7161
8539
  function normalizeLineEndings2(content) {
@@ -7214,41 +8592,171 @@ function stripAnsi(value) {
7214
8592
  function normalizeWhitespace(value) {
7215
8593
  return value.replace(/\s+/g, " ").trim();
7216
8594
  }
7217
- function formatUserFacingError(error, options = {}) {
7218
- const fallback = options.fallback ?? "Something went wrong. Please try again.";
8595
+ function compactErrorText(value, maxLength = 220) {
8596
+ if (!value) {
8597
+ return "";
8598
+ }
8599
+ const cleaned = normalizeWhitespace(stripAnsi(String(value)));
8600
+ if (!cleaned) {
8601
+ return "";
8602
+ }
8603
+ if (cleaned.length <= maxLength) {
8604
+ return cleaned;
8605
+ }
8606
+ return `${cleaned.slice(0, maxLength).trimEnd()}\u2026`;
8607
+ }
8608
+ function createErrorReferenceId() {
8609
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`.toUpperCase();
8610
+ }
8611
+ function serializeUnknownError(value) {
8612
+ if (typeof value === "string") {
8613
+ return value;
8614
+ }
8615
+ if (value instanceof Error) {
8616
+ return value.stack ?? value.message;
8617
+ }
8618
+ try {
8619
+ return JSON.stringify(value, null, 2);
8620
+ } catch {
8621
+ return String(value);
8622
+ }
8623
+ }
8624
+ function extractRawMessage(error) {
7219
8625
  if (error === null || typeof error === "undefined") {
7220
- return fallback;
8626
+ return "";
8627
+ }
8628
+ if (typeof error === "string") {
8629
+ return error;
7221
8630
  }
7222
- const rawMessage = typeof error === "string" ? error : error instanceof Error ? error.message : String(error);
8631
+ if (error instanceof Error) {
8632
+ return error.message;
8633
+ }
8634
+ return String(error);
8635
+ }
8636
+ function normalizeRawMessage(rawMessage) {
7223
8637
  let cleaned = normalizeWhitespace(stripAnsi(rawMessage));
7224
8638
  if (!cleaned) {
7225
- return fallback;
8639
+ return "";
7226
8640
  }
7227
8641
  if (cleaned.startsWith("Error invoking remote method")) {
7228
8642
  cleaned = cleaned.replace(/^Error invoking remote method '[^']+': Error: /, "");
7229
8643
  }
8644
+ if (cleaned.toLowerCase().includes("openai codex") && cleaned.toLowerCase().includes("workdir:")) {
8645
+ const parts = cleaned.split(/error:/i);
8646
+ if (parts.length > 1) {
8647
+ return parts[parts.length - 1]?.trim() ?? cleaned;
8648
+ }
8649
+ }
8650
+ return cleaned;
8651
+ }
8652
+ function resolveCategory(cleaned, options) {
7230
8653
  const lower = cleaned.toLowerCase();
7231
8654
  if (options.notFoundFallback && (lower.includes("enoent") || lower.includes("not found"))) {
7232
- return options.notFoundFallback;
8655
+ return {
8656
+ code: "ERR_NOT_FOUND",
8657
+ title: "We couldn't find that item",
8658
+ message: options.notFoundFallback,
8659
+ recovery: "Check the path or selection, then try again.",
8660
+ retryable: true
8661
+ };
7233
8662
  }
7234
8663
  if (lower.includes("rate limit") || lower.includes("quota exceeded") || lower.includes("too many requests") || lower.includes("429")) {
7235
- cleaned = "Rate limit exceeded. Please wait a moment or upgrade your plan.";
7236
- } else if (lower.includes("timeout") || lower.includes("timed out")) {
7237
- cleaned = "Request timed out. Check your connection and try again.";
7238
- } else if (lower.includes("network") || lower.includes("econn") || lower.includes("networkerror") || lower.includes("connection")) {
7239
- cleaned = "Network issue. Check your connection and retry.";
7240
- } else if (lower.includes("permission") || lower.includes("eacces")) {
7241
- cleaned = "Permission denied. Restart CodexUse and retry.";
7242
- } else if (lower.includes("openai codex") && lower.includes("workdir:")) {
7243
- if (lower.includes("error:")) {
7244
- const parts = cleaned.split(/error:/i);
7245
- if (parts.length > 1) {
7246
- cleaned = parts[parts.length - 1].trim();
7247
- }
7248
- } else {
7249
- cleaned = "Codex CLI failed to respond. Please try again.";
7250
- }
8664
+ return {
8665
+ code: "ERR_RATE_LIMIT",
8666
+ title: "Rate limit reached",
8667
+ message: "The provider is rate limited right now.",
8668
+ recovery: "Wait a moment, switch profiles, or try again.",
8669
+ retryable: true
8670
+ };
8671
+ }
8672
+ if (lower.includes("timeout") || lower.includes("timed out")) {
8673
+ return {
8674
+ code: "ERR_TIMEOUT",
8675
+ title: "Request timed out",
8676
+ message: "The operation took too long to finish.",
8677
+ recovery: "Check the connection and try again.",
8678
+ retryable: true
8679
+ };
8680
+ }
8681
+ if (lower.includes("network") || lower.includes("econn") || lower.includes("networkerror") || lower.includes("connection refused") || lower.includes("socket hang up")) {
8682
+ return {
8683
+ code: "ERR_NETWORK",
8684
+ title: "Can't reach the service",
8685
+ message: "CodexUse couldn't connect to the required service.",
8686
+ recovery: "Check your internet or local server connection, then retry.",
8687
+ retryable: true
8688
+ };
8689
+ }
8690
+ if (lower.includes("token_invalidated") || lower.includes("session invalidated") || lower.includes("session expired") || lower.includes("refresh token") || lower.includes("unauthorized") || lower.includes("401")) {
8691
+ return {
8692
+ code: "ERR_AUTH",
8693
+ title: "Sign-in expired",
8694
+ message: "This account needs to sign in again.",
8695
+ recovery: "Reconnect the profile, then retry the action.",
8696
+ retryable: true
8697
+ };
8698
+ }
8699
+ if (lower.includes("permission") || lower.includes("eacces") || lower.includes("operation not permitted")) {
8700
+ return {
8701
+ code: "ERR_PERMISSION",
8702
+ title: "Permission denied",
8703
+ message: "CodexUse doesn't have permission to do that.",
8704
+ recovery: "Check the file permissions or restart the app and try again.",
8705
+ retryable: true
8706
+ };
8707
+ }
8708
+ if (lower.includes("enospc") || lower.includes("disk full") || lower.includes("no space left")) {
8709
+ return {
8710
+ code: "ERR_DISK_FULL",
8711
+ title: "Disk is full",
8712
+ message: "CodexUse couldn't save data because the disk is full.",
8713
+ recovery: "Free up disk space, then try again.",
8714
+ retryable: true
8715
+ };
7251
8716
  }
8717
+ if (lower.includes("codex cli") || lower.includes("command not found") || lower.includes("spawn codex") || lower.includes("could not find codex")) {
8718
+ return {
8719
+ code: "ERR_CLI_UNAVAILABLE",
8720
+ title: "Codex CLI unavailable",
8721
+ message: "CodexUse couldn't start the Codex CLI.",
8722
+ recovery: "Check Runtime > Codex and confirm the CLI is installed correctly.",
8723
+ retryable: true
8724
+ };
8725
+ }
8726
+ if (lower.includes("provider") || lower.includes("model") || lower.includes("openai") || lower.includes("anthropic")) {
8727
+ return {
8728
+ code: "ERR_PROVIDER",
8729
+ title: "Provider request failed",
8730
+ message: "The AI provider rejected or failed the request.",
8731
+ recovery: "Try again, or switch the active profile or model.",
8732
+ retryable: true
8733
+ };
8734
+ }
8735
+ return {
8736
+ code: "ERR_UNKNOWN",
8737
+ title: "Something went wrong",
8738
+ message: options.fallback,
8739
+ recovery: "Try again. If it keeps happening, report the error with the reference below.",
8740
+ retryable: true
8741
+ };
8742
+ }
8743
+ function resolveUserFacingError(error, options = {}) {
8744
+ const fallback = options.fallback ?? "Something went wrong. Please try again.";
8745
+ const rawMessage = extractRawMessage(error);
8746
+ const cleaned = normalizeRawMessage(rawMessage);
8747
+ const category = resolveCategory(cleaned, {
8748
+ fallback,
8749
+ ...options.notFoundFallback ? { notFoundFallback: options.notFoundFallback } : {}
8750
+ });
8751
+ return {
8752
+ ...category,
8753
+ referenceId: createErrorReferenceId(),
8754
+ technicalMessage: compactErrorText(cleaned || rawMessage || fallback, 400),
8755
+ technicalDetails: compactErrorText(serializeUnknownError(error), 4e3)
8756
+ };
8757
+ }
8758
+ function formatUserFacingError(error, options = {}) {
8759
+ let cleaned = resolveUserFacingError(error, options).message;
7252
8760
  const maxLength = typeof options.maxLength === "number" ? options.maxLength : 220;
7253
8761
  if (cleaned.length > maxLength) {
7254
8762
  cleaned = `${cleaned.slice(0, maxLength).trimEnd()}\u2026`;
@@ -7679,13 +9187,13 @@ function formatMegabytes(bytes) {
7679
9187
  return (bytes / MB_DIVISOR).toFixed(2);
7680
9188
  }
7681
9189
  function resolveSyncBackupsDir() {
7682
- return import_node_path11.default.join(getUserDataDir(), SYNC_BACKUP_DIR);
9190
+ return import_node_path14.default.join(getUserDataDir(), SYNC_BACKUP_DIR);
7683
9191
  }
7684
9192
  function toSafeIsoForFileName(iso) {
7685
9193
  return iso.replace(/:/g, "-");
7686
9194
  }
7687
9195
  async function pruneOldPrePullBackups(backupsDir) {
7688
- const entries = await import_node_fs7.promises.readdir(backupsDir, { withFileTypes: true });
9196
+ const entries = await import_node_fs8.promises.readdir(backupsDir, { withFileTypes: true });
7689
9197
  const files = entries.filter(
7690
9198
  (entry) => entry.isFile() && entry.name.startsWith(PRE_PULL_BACKUP_PREFIX) && entry.name.endsWith(PRE_PULL_BACKUP_SUFFIX)
7691
9199
  ).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
@@ -7693,7 +9201,7 @@ async function pruneOldPrePullBackups(backupsDir) {
7693
9201
  return;
7694
9202
  }
7695
9203
  for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) {
7696
- await import_node_fs7.promises.rm(import_node_path11.default.join(backupsDir, stale), { force: true });
9204
+ await import_node_fs8.promises.rm(import_node_path14.default.join(backupsDir, stale), { force: true });
7697
9205
  }
7698
9206
  }
7699
9207
  async function createPrePullBackup(profileManager) {
@@ -7701,13 +9209,13 @@ async function createPrePullBackup(profileManager) {
7701
9209
  enforcePushGuards: false
7702
9210
  });
7703
9211
  const backupsDir = resolveSyncBackupsDir();
7704
- await import_node_fs7.promises.mkdir(backupsDir, { recursive: true });
9212
+ await import_node_fs8.promises.mkdir(backupsDir, { recursive: true });
7705
9213
  const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
7706
- const backupPath = import_node_path11.default.join(
9214
+ const backupPath = import_node_path14.default.join(
7707
9215
  backupsDir,
7708
9216
  `${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`
7709
9217
  );
7710
- await import_node_fs7.promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}
9218
+ await import_node_fs8.promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}
7711
9219
  `, "utf8");
7712
9220
  await pruneOldPrePullBackups(backupsDir);
7713
9221
  logInfo("[cloud-sync] created pre-pull backup", { backupPath });
@@ -7923,9 +9431,9 @@ function printSyncResult(result) {
7923
9431
  }
7924
9432
  async function handleSync(args, version) {
7925
9433
  const flags = args.filter((arg) => arg.startsWith("-"));
7926
- const params = stripFlags2(args);
9434
+ const params = stripFlags(args);
7927
9435
  const sub = params[0];
7928
- if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
9436
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
7929
9437
  printHelp(version);
7930
9438
  return;
7931
9439
  }
@@ -7976,9 +9484,9 @@ async function handleSync(args, version) {
7976
9484
  }
7977
9485
 
7978
9486
  // ../../packages/runtime-app-state/src/storage/migrations/v1.ts
7979
- var import_node_fs8 = require("fs");
7980
- var import_node_path12 = __toESM(require("path"), 1);
7981
- var import_node_os5 = __toESM(require("os"), 1);
9487
+ var import_node_fs9 = require("fs");
9488
+ var import_node_path15 = __toESM(require("path"), 1);
9489
+ var import_node_os6 = __toESM(require("os"), 1);
7982
9490
 
7983
9491
  // ../../packages/contracts/src/settings/legacy-localstorage-keys.ts
7984
9492
  var LEGACY_LOCALSTORAGE_KEYS = [
@@ -8002,11 +9510,11 @@ var LEGACY_SKILLS_DIR = "skills";
8002
9510
  var LEGACY_SKILL_CACHE_DIR = "skill-cache";
8003
9511
  var LEGACY_SKILLS_REPOS_FILE = "repos.json";
8004
9512
  var LEGACY_SKILL_MANIFEST = ".codexuse-skill.json";
8005
- var LEGACY_LICENSE_SECRET_FILE = "license.secret";
9513
+ var LEGACY_LICENSE_SECRET_FILE2 = "license.secret";
8006
9514
  function isRecord6(value) {
8007
9515
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8008
9516
  }
8009
- function asString3(value) {
9517
+ function asString4(value) {
8010
9518
  if (typeof value !== "string") {
8011
9519
  return null;
8012
9520
  }
@@ -8017,20 +9525,20 @@ function isMissingPathError(error) {
8017
9525
  return error.code === "ENOENT";
8018
9526
  }
8019
9527
  function resolveHomeDir2() {
8020
- const home = process.env.HOME || process.env.USERPROFILE || import_node_os5.default.homedir();
9528
+ const home = process.env.HOME || process.env.USERPROFILE || import_node_os6.default.homedir();
8021
9529
  if (!home) {
8022
9530
  throw new Error("HOME is not set.");
8023
9531
  }
8024
9532
  return home;
8025
9533
  }
8026
9534
  function resolveCodexDir() {
8027
- return import_node_path12.default.join(resolveHomeDir2(), ".codex");
9535
+ return import_node_path15.default.join(resolveHomeDir2(), ".codex");
8028
9536
  }
8029
9537
  function resolveLegacyPath(...segments) {
8030
- return import_node_path12.default.join(resolveCodexDir(), ...segments);
9538
+ return import_node_path15.default.join(resolveCodexDir(), ...segments);
8031
9539
  }
8032
9540
  async function readJsonFile(filePath) {
8033
- const raw = await import_node_fs8.promises.readFile(filePath, "utf8");
9541
+ const raw = await import_node_fs9.promises.readFile(filePath, "utf8");
8034
9542
  return JSON.parse(raw);
8035
9543
  }
8036
9544
  async function readJsonFileIfExists(filePath) {
@@ -8045,14 +9553,14 @@ async function readJsonFileIfExists(filePath) {
8045
9553
  }
8046
9554
  }
8047
9555
  async function copyDirectoryManual(source, destination) {
8048
- await import_node_fs8.promises.mkdir(destination, { recursive: true });
8049
- const entries = await import_node_fs8.promises.readdir(source, { withFileTypes: true });
9556
+ await import_node_fs9.promises.mkdir(destination, { recursive: true });
9557
+ const entries = await import_node_fs9.promises.readdir(source, { withFileTypes: true });
8050
9558
  for (const entry of entries) {
8051
- const srcPath = import_node_path12.default.join(source, entry.name);
8052
- const destPath = import_node_path12.default.join(destination, entry.name);
9559
+ const srcPath = import_node_path15.default.join(source, entry.name);
9560
+ const destPath = import_node_path15.default.join(destination, entry.name);
8053
9561
  if (entry.isSymbolicLink()) {
8054
- const linkTarget = await import_node_fs8.promises.readlink(srcPath);
8055
- await import_node_fs8.promises.symlink(linkTarget, destPath).catch((error) => {
9562
+ const linkTarget = await import_node_fs9.promises.readlink(srcPath);
9563
+ await import_node_fs9.promises.symlink(linkTarget, destPath).catch((error) => {
8056
9564
  if (error.code !== "EEXIST") {
8057
9565
  throw error;
8058
9566
  }
@@ -8064,14 +9572,14 @@ async function copyDirectoryManual(source, destination) {
8064
9572
  continue;
8065
9573
  }
8066
9574
  if (entry.isFile()) {
8067
- await import_node_fs8.promises.copyFile(srcPath, destPath);
9575
+ await import_node_fs9.promises.copyFile(srcPath, destPath);
8068
9576
  }
8069
9577
  }
8070
9578
  }
8071
9579
  async function copyDirIfExists(source, destination) {
8072
9580
  let stats = null;
8073
9581
  try {
8074
- stats = await import_node_fs8.promises.stat(source);
9582
+ stats = await import_node_fs9.promises.stat(source);
8075
9583
  } catch (error) {
8076
9584
  if (error.code === "ENOENT") {
8077
9585
  return;
@@ -8081,10 +9589,10 @@ async function copyDirIfExists(source, destination) {
8081
9589
  if (!stats.isDirectory()) {
8082
9590
  return;
8083
9591
  }
8084
- await import_node_fs8.promises.rm(destination, { recursive: true, force: true });
8085
- await import_node_fs8.promises.mkdir(import_node_path12.default.dirname(destination), { recursive: true });
9592
+ await import_node_fs9.promises.rm(destination, { recursive: true, force: true });
9593
+ await import_node_fs9.promises.mkdir(import_node_path15.default.dirname(destination), { recursive: true });
8086
9594
  try {
8087
- await import_node_fs8.promises.cp(source, destination, {
9595
+ await import_node_fs9.promises.cp(source, destination, {
8088
9596
  recursive: true,
8089
9597
  errorOnExist: false,
8090
9598
  force: true,
@@ -8101,7 +9609,7 @@ async function copyDirIfExists(source, destination) {
8101
9609
  }
8102
9610
  async function copyFileIfExists(source, destination, options) {
8103
9611
  try {
8104
- const sourceStat = await import_node_fs8.promises.stat(source);
9612
+ const sourceStat = await import_node_fs9.promises.stat(source);
8105
9613
  if (!sourceStat.isFile()) {
8106
9614
  return;
8107
9615
  }
@@ -8112,7 +9620,7 @@ async function copyFileIfExists(source, destination, options) {
8112
9620
  throw error;
8113
9621
  }
8114
9622
  try {
8115
- const destinationStat = await import_node_fs8.promises.stat(destination);
9623
+ const destinationStat = await import_node_fs9.promises.stat(destination);
8116
9624
  if (destinationStat.isFile()) {
8117
9625
  return;
8118
9626
  }
@@ -8121,17 +9629,17 @@ async function copyFileIfExists(source, destination, options) {
8121
9629
  throw error;
8122
9630
  }
8123
9631
  }
8124
- await import_node_fs8.promises.mkdir(import_node_path12.default.dirname(destination), { recursive: true });
8125
- await import_node_fs8.promises.copyFile(source, destination);
9632
+ await import_node_fs9.promises.mkdir(import_node_path15.default.dirname(destination), { recursive: true });
9633
+ await import_node_fs9.promises.copyFile(source, destination);
8126
9634
  if (typeof options?.mode === "number") {
8127
- await import_node_fs8.promises.chmod(destination, options.mode).catch(() => void 0);
9635
+ await import_node_fs9.promises.chmod(destination, options.mode).catch(() => void 0);
8128
9636
  }
8129
9637
  }
8130
9638
  async function cleanupCopiedSkillsMetadata(skillsDir) {
8131
- await removeIfExists(import_node_path12.default.join(skillsDir, LEGACY_SKILLS_REPOS_FILE));
9639
+ await removeIfExists(import_node_path15.default.join(skillsDir, LEGACY_SKILLS_REPOS_FILE));
8132
9640
  let entries = [];
8133
9641
  try {
8134
- entries = await import_node_fs8.promises.readdir(skillsDir, { withFileTypes: true });
9642
+ entries = await import_node_fs9.promises.readdir(skillsDir, { withFileTypes: true });
8135
9643
  } catch {
8136
9644
  return;
8137
9645
  }
@@ -8139,30 +9647,30 @@ async function cleanupCopiedSkillsMetadata(skillsDir) {
8139
9647
  if (!entry.isDirectory() || entry.name.startsWith(".")) {
8140
9648
  continue;
8141
9649
  }
8142
- await removeIfExists(import_node_path12.default.join(skillsDir, entry.name, LEGACY_SKILL_MANIFEST));
9650
+ await removeIfExists(import_node_path15.default.join(skillsDir, entry.name, LEGACY_SKILL_MANIFEST));
8143
9651
  }
8144
9652
  }
8145
9653
  async function migrateLegacyDirectoriesToUserData() {
8146
9654
  const userDataDir = getUserDataDir();
8147
9655
  const legacyCodexDir = resolveCodexDir();
8148
9656
  await copyDirIfExists(
8149
- import_node_path12.default.join(legacyCodexDir, LEGACY_PROFILE_HOMES_DIR),
8150
- import_node_path12.default.join(userDataDir, LEGACY_PROFILE_HOMES_DIR)
9657
+ import_node_path15.default.join(legacyCodexDir, LEGACY_PROFILE_HOMES_DIR),
9658
+ import_node_path15.default.join(userDataDir, LEGACY_PROFILE_HOMES_DIR)
8151
9659
  );
8152
9660
  await copyDirIfExists(
8153
- import_node_path12.default.join(legacyCodexDir, LEGACY_SKILLS_DIR),
8154
- import_node_path12.default.join(userDataDir, LEGACY_SKILLS_DIR)
9661
+ import_node_path15.default.join(legacyCodexDir, LEGACY_SKILLS_DIR),
9662
+ import_node_path15.default.join(userDataDir, LEGACY_SKILLS_DIR)
8155
9663
  );
8156
9664
  await copyDirIfExists(
8157
- import_node_path12.default.join(legacyCodexDir, LEGACY_SKILL_CACHE_DIR),
8158
- import_node_path12.default.join(userDataDir, LEGACY_SKILL_CACHE_DIR)
9665
+ import_node_path15.default.join(legacyCodexDir, LEGACY_SKILL_CACHE_DIR),
9666
+ import_node_path15.default.join(userDataDir, LEGACY_SKILL_CACHE_DIR)
8159
9667
  );
8160
9668
  await copyFileIfExists(
8161
- import_node_path12.default.join(legacyCodexDir, LEGACY_LICENSE_SECRET_FILE),
8162
- import_node_path12.default.join(userDataDir, LEGACY_LICENSE_SECRET_FILE),
9669
+ import_node_path15.default.join(legacyCodexDir, LEGACY_LICENSE_SECRET_FILE2),
9670
+ import_node_path15.default.join(userDataDir, LEGACY_LICENSE_SECRET_FILE2),
8163
9671
  { mode: 384 }
8164
9672
  );
8165
- await cleanupCopiedSkillsMetadata(import_node_path12.default.join(userDataDir, LEGACY_SKILLS_DIR));
9673
+ await cleanupCopiedSkillsMetadata(import_node_path15.default.join(userDataDir, LEGACY_SKILLS_DIR));
8166
9674
  }
8167
9675
  function pickAutoRoll(raw) {
8168
9676
  if (!raw) {
@@ -8186,16 +9694,16 @@ function parseLegacyLicense(raw) {
8186
9694
  signature: null
8187
9695
  };
8188
9696
  }
8189
- const statusCandidate = asString3(raw.status);
9697
+ const statusCandidate = asString4(raw.status);
8190
9698
  const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : "inactive";
8191
9699
  return {
8192
- licenseKey: asString3(raw.licenseKey ?? raw.license_key),
8193
- purchaseEmail: asString3(raw.purchaseEmail ?? raw.purchase_email),
8194
- lastVerifiedAt: asString3(raw.lastVerifiedAt ?? raw.last_verified_at),
8195
- nextCheckAt: asString3(raw.nextCheckAt ?? raw.next_check_at),
8196
- lastVerificationError: asString3(raw.lastVerificationError ?? raw.last_verification_error),
9700
+ licenseKey: asString4(raw.licenseKey ?? raw.license_key),
9701
+ purchaseEmail: asString4(raw.purchaseEmail ?? raw.purchase_email),
9702
+ lastVerifiedAt: asString4(raw.lastVerifiedAt ?? raw.last_verified_at),
9703
+ nextCheckAt: asString4(raw.nextCheckAt ?? raw.next_check_at),
9704
+ lastVerificationError: asString4(raw.lastVerificationError ?? raw.last_verification_error),
8197
9705
  status,
8198
- signature: asString3(raw.signature)
9706
+ signature: asString4(raw.signature)
8199
9707
  };
8200
9708
  }
8201
9709
  function parseLegacyProfileRecord(name, raw) {
@@ -8218,16 +9726,16 @@ function parseLegacyProfileRecord(name, raw) {
8218
9726
  }
8219
9727
  return {
8220
9728
  name,
8221
- displayName: asString3(raw.displayName ?? raw.display_name) ?? name,
9729
+ displayName: asString4(raw.displayName ?? raw.display_name) ?? name,
8222
9730
  data,
8223
9731
  metadata: isRecord6(raw.metadata) ? raw.metadata : void 0,
8224
- accountId: asString3(raw.accountId ?? raw.account_id),
8225
- workspaceId: asString3(raw.workspaceId ?? raw.workspace_id),
8226
- workspaceName: asString3(raw.workspaceName ?? raw.workspace_name),
8227
- email: asString3(raw.email),
8228
- authMethod: asString3(raw.authMethod ?? raw.auth_method),
8229
- createdAt: asString3(raw.createdAt ?? raw.created_at),
8230
- updatedAt: asString3(raw.updatedAt ?? raw.updated_at)
9732
+ accountId: asString4(raw.accountId ?? raw.account_id),
9733
+ workspaceId: asString4(raw.workspaceId ?? raw.workspace_id),
9734
+ workspaceName: asString4(raw.workspaceName ?? raw.workspace_name),
9735
+ email: asString4(raw.email),
9736
+ authMethod: asString4(raw.authMethod ?? raw.auth_method),
9737
+ createdAt: asString4(raw.createdAt ?? raw.created_at),
9738
+ updatedAt: asString4(raw.updatedAt ?? raw.updated_at)
8231
9739
  };
8232
9740
  }
8233
9741
  async function loadLegacySettingsPatch() {
@@ -8240,9 +9748,9 @@ async function loadLegacySettingsPatch() {
8240
9748
  const license = parseLegacyLicense(raw.license ?? raw.license_data ?? raw.license_state);
8241
9749
  return {
8242
9750
  app: {
8243
- lastAppVersion: asString3(raw.lastAppVersion ?? raw.last_app_version),
8244
- pendingUpdateVersion: asString3(raw.pendingUpdateVersion ?? raw.pending_update_version),
8245
- lastProfileName: asString3(raw.lastProfileName ?? raw.last_profile_name)
9751
+ lastAppVersion: asString4(raw.lastAppVersion ?? raw.last_app_version),
9752
+ pendingUpdateVersion: asString4(raw.pendingUpdateVersion ?? raw.pending_update_version),
9753
+ lastProfileName: asString4(raw.lastProfileName ?? raw.last_profile_name)
8246
9754
  },
8247
9755
  license,
8248
9756
  autoRoll: autoRoll ? {
@@ -8260,15 +9768,15 @@ async function loadLegacySyncPatch() {
8260
9768
  }
8261
9769
  return {
8262
9770
  sync: {
8263
- lastPushAt: asString3(raw.lastPushAt),
8264
- lastPullAt: asString3(raw.lastPullAt),
8265
- lastError: asString3(raw.lastError),
8266
- remoteUpdatedAt: asString3(raw.remoteUpdatedAt)
9771
+ lastPushAt: asString4(raw.lastPushAt),
9772
+ lastPullAt: asString4(raw.lastPullAt),
9773
+ lastError: asString4(raw.lastError),
9774
+ remoteUpdatedAt: asString4(raw.remoteUpdatedAt)
8267
9775
  }
8268
9776
  };
8269
9777
  }
8270
9778
  async function loadLegacyAppSettingsParityPatch() {
8271
- const filePath = import_node_path12.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE);
9779
+ const filePath = import_node_path15.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE);
8272
9780
  const raw = await readJsonFileIfExists(filePath);
8273
9781
  if (!isRecord6(raw)) {
8274
9782
  return {};
@@ -8281,7 +9789,7 @@ async function loadLegacyProfilesPatch() {
8281
9789
  const root = resolveLegacyPath(LEGACY_PROFILE_HOMES_DIR);
8282
9790
  let entries = [];
8283
9791
  try {
8284
- entries = await import_node_fs8.promises.readdir(root, { withFileTypes: true });
9792
+ entries = await import_node_fs9.promises.readdir(root, { withFileTypes: true });
8285
9793
  } catch (error) {
8286
9794
  if (error.code === "ENOENT") {
8287
9795
  return {};
@@ -8293,7 +9801,7 @@ async function loadLegacyProfilesPatch() {
8293
9801
  if (!entry.isDirectory() || entry.name.startsWith(".")) {
8294
9802
  continue;
8295
9803
  }
8296
- const profileFile = import_node_path12.default.join(root, entry.name, "profile.json");
9804
+ const profileFile = import_node_path15.default.join(root, entry.name, "profile.json");
8297
9805
  const raw = await readJsonFileIfExists(profileFile);
8298
9806
  if (!raw) {
8299
9807
  continue;
@@ -8313,28 +9821,28 @@ function parseSkillInstallMetadata(raw) {
8313
9821
  if (!isRecord6(raw)) {
8314
9822
  return null;
8315
9823
  }
8316
- const id = asString3(raw.id);
9824
+ const id = asString4(raw.id);
8317
9825
  if (!id) {
8318
9826
  return null;
8319
9827
  }
8320
9828
  return {
8321
9829
  id,
8322
- repo: asString3(raw.repo),
8323
- repoPath: asString3(raw.repoPath),
8324
- sourceLabel: asString3(raw.sourceLabel),
9830
+ repo: asString4(raw.repo),
9831
+ repoPath: asString4(raw.repoPath),
9832
+ sourceLabel: asString4(raw.sourceLabel),
8325
9833
  sourceType: raw.sourceType === "official" || raw.sourceType === "community" || raw.sourceType === "local" ? raw.sourceType : void 0,
8326
- viewUrl: asString3(raw.viewUrl),
8327
- createdAt: asString3(raw.createdAt)
9834
+ viewUrl: asString4(raw.viewUrl),
9835
+ createdAt: asString4(raw.createdAt)
8328
9836
  };
8329
9837
  }
8330
9838
  async function loadLegacySkillsPatch() {
8331
9839
  const skillsRoot = resolveLegacyPath(LEGACY_SKILLS_DIR);
8332
- const reposPath = import_node_path12.default.join(skillsRoot, LEGACY_SKILLS_REPOS_FILE);
9840
+ const reposPath = import_node_path15.default.join(skillsRoot, LEGACY_SKILLS_REPOS_FILE);
8333
9841
  const reposRaw = await readJsonFileIfExists(reposPath);
8334
9842
  const installsBySlug = {};
8335
9843
  let dirEntries = [];
8336
9844
  try {
8337
- dirEntries = await import_node_fs8.promises.readdir(skillsRoot, { withFileTypes: true });
9845
+ dirEntries = await import_node_fs9.promises.readdir(skillsRoot, { withFileTypes: true });
8338
9846
  } catch (error) {
8339
9847
  if (error.code !== "ENOENT") {
8340
9848
  throw error;
@@ -8347,7 +9855,7 @@ async function loadLegacySkillsPatch() {
8347
9855
  if (entry.name.startsWith(".")) {
8348
9856
  continue;
8349
9857
  }
8350
- const manifestPath = import_node_path12.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST);
9858
+ const manifestPath = import_node_path15.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST);
8351
9859
  const manifestRaw = await readJsonFileIfExists(manifestPath);
8352
9860
  const parsed = parseSkillInstallMetadata(manifestRaw);
8353
9861
  if (!parsed) {
@@ -8457,29 +9965,29 @@ function mergeLegacyLocalStoragePatch(payload) {
8457
9965
  };
8458
9966
  }
8459
9967
  async function removeIfExists(target) {
8460
- await import_node_fs8.promises.rm(target, { recursive: true, force: true });
9968
+ await import_node_fs9.promises.rm(target, { recursive: true, force: true });
8461
9969
  }
8462
9970
  async function cleanupLegacyCanonicalSources() {
8463
9971
  const homeDir = resolveHomeDir2();
8464
9972
  await removeIfExists(resolveLegacyAppStatePath());
8465
- await removeIfExists(import_node_path12.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
8466
- await removeIfExists(import_node_path12.default.join(homeDir, SQLITE_STORAGE_DIR));
9973
+ await removeIfExists(import_node_path15.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
9974
+ await removeIfExists(import_node_path15.default.join(homeDir, SQLITE_STORAGE_DIR));
8467
9975
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_FILE));
8468
9976
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_BACKUP_FILE));
8469
9977
  await removeIfExists(resolveLegacyPath(LEGACY_SYNC_STATE_FILE));
8470
- await removeIfExists(resolveLegacyPath(LEGACY_LICENSE_SECRET_FILE));
9978
+ await removeIfExists(resolveLegacyPath(LEGACY_LICENSE_SECRET_FILE2));
8471
9979
  await removeIfExists(resolveLegacyPath(LEGACY_SKILLS_DIR, LEGACY_SKILLS_REPOS_FILE));
8472
9980
  const profileHomesRoot = resolveLegacyPath(LEGACY_PROFILE_HOMES_DIR);
8473
9981
  try {
8474
- const profileHomes = await import_node_fs8.promises.readdir(profileHomesRoot, { withFileTypes: true });
9982
+ const profileHomes = await import_node_fs9.promises.readdir(profileHomesRoot, { withFileTypes: true });
8475
9983
  for (const profileHome of profileHomes) {
8476
9984
  if (!profileHome.isDirectory()) {
8477
9985
  continue;
8478
9986
  }
8479
- const profileDir = import_node_path12.default.join(profileHomesRoot, profileHome.name);
9987
+ const profileDir = import_node_path15.default.join(profileHomesRoot, profileHome.name);
8480
9988
  let files = [];
8481
9989
  try {
8482
- files = await import_node_fs8.promises.readdir(profileDir);
9990
+ files = await import_node_fs9.promises.readdir(profileDir);
8483
9991
  } catch {
8484
9992
  continue;
8485
9993
  }
@@ -8487,7 +9995,7 @@ async function cleanupLegacyCanonicalSources() {
8487
9995
  if (!file.startsWith("profile.json")) {
8488
9996
  continue;
8489
9997
  }
8490
- await removeIfExists(import_node_path12.default.join(profileDir, file));
9998
+ await removeIfExists(import_node_path15.default.join(profileDir, file));
8491
9999
  }
8492
10000
  }
8493
10001
  } catch (error) {
@@ -8500,12 +10008,12 @@ async function cleanupLegacyCanonicalSources() {
8500
10008
  }
8501
10009
  const skillsRoot = resolveLegacyPath(LEGACY_SKILLS_DIR);
8502
10010
  try {
8503
- const skillDirs = await import_node_fs8.promises.readdir(skillsRoot, { withFileTypes: true });
10011
+ const skillDirs = await import_node_fs9.promises.readdir(skillsRoot, { withFileTypes: true });
8504
10012
  for (const entry of skillDirs) {
8505
10013
  if (!entry.isDirectory() || entry.name.startsWith(".")) {
8506
10014
  continue;
8507
10015
  }
8508
- await removeIfExists(import_node_path12.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
10016
+ await removeIfExists(import_node_path15.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
8509
10017
  }
8510
10018
  } catch (error) {
8511
10019
  if (!isMissingPathError(error)) {
@@ -8718,24 +10226,29 @@ async function ensureCliStorageReady() {
8718
10226
  }
8719
10227
 
8720
10228
  // src/app/main.ts
8721
- var VERSION = true ? "3.1.5" : "0.0.0";
10229
+ var VERSION = true ? "3.6.0" : "0.0.0";
8722
10230
  async function runCli() {
8723
10231
  const args = process.argv.slice(2);
8724
10232
  if (args.length === 0) {
8725
10233
  printHelp(VERSION);
8726
10234
  return;
8727
10235
  }
8728
- if (args[0]?.startsWith("-") && (hasFlag2(args, "--help") || hasFlag2(args, "-h"))) {
10236
+ if (args[0]?.startsWith("-") && (hasFlag(args, "--help") || hasFlag(args, "-h"))) {
8729
10237
  printHelp(VERSION);
8730
10238
  return;
8731
10239
  }
8732
- if (args[0]?.startsWith("-") && (hasFlag2(args, "--version") || hasFlag2(args, "-v"))) {
10240
+ if (args[0]?.startsWith("-") && (hasFlag(args, "--version") || hasFlag(args, "-v"))) {
8733
10241
  console.log(VERSION);
8734
10242
  return;
8735
10243
  }
8736
10244
  const command = args[0];
8737
10245
  const rest = args.slice(1);
8738
10246
  switch (command) {
10247
+ case "account-pool":
10248
+ case "accounts-pool":
10249
+ await ensureCliStorageReady();
10250
+ await handleAccountPoolCommand(rest, VERSION);
10251
+ return;
8739
10252
  case "profile":
8740
10253
  await ensureCliStorageReady();
8741
10254
  await handleProfileCommand(rest, VERSION);