codexuse-cli 3.8.0 → 3.9.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
@@ -2352,7 +2352,8 @@ function createDefaultAppState() {
2352
2352
  autoRoll: {
2353
2353
  enabled: false,
2354
2354
  warningThreshold: 85,
2355
- switchThreshold: 95
2355
+ switchThreshold: 95,
2356
+ restartOfficialCodexOnAutoRoll: false
2356
2357
  },
2357
2358
  app: {
2358
2359
  lastAppVersion: null,
@@ -2479,6 +2480,9 @@ function normalizeAppState(raw) {
2479
2480
  Math.max(merged.autoRoll.warningThreshold + 1, 95)
2480
2481
  );
2481
2482
  }
2483
+ if (typeof merged.autoRoll.restartOfficialCodexOnAutoRoll !== "boolean") {
2484
+ merged.autoRoll.restartOfficialCodexOnAutoRoll = false;
2485
+ }
2482
2486
  merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
2483
2487
  merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
2484
2488
  merged.app.lastProfileName = asString(merged.app.lastProfileName);
@@ -2872,6 +2876,7 @@ async function patchAppState(patch) {
2872
2876
  }
2873
2877
 
2874
2878
  // ../../packages/runtime-app-state/src/app/runtimeSettings.ts
2879
+ var CUSTOMER_DEFAULTS_MIGRATION_KEY = "customerRateLimitBackgroundDefaultsApplied";
2875
2880
  var LEGACY_APP_SETTINGS_KEYS = /* @__PURE__ */ new Set([
2876
2881
  "backendMode",
2877
2882
  "theme",
@@ -2899,12 +2904,56 @@ function stripLegacyAppSettingsKeys(settings) {
2899
2904
  }
2900
2905
  return changed ? next : settings;
2901
2906
  }
2907
+ function withCurrentRuntimeDefaults(settings, options) {
2908
+ let changed = false;
2909
+ const next = { ...settings };
2910
+ const migrationApplied = next[CUSTOMER_DEFAULTS_MIGRATION_KEY] === true;
2911
+ if (options.migrateLegacyDefaults && !migrationApplied) {
2912
+ if (!("keepAppRunningAfterWindowClose" in next)) {
2913
+ next.keepAppRunningAfterWindowClose = true;
2914
+ changed = true;
2915
+ }
2916
+ } else if (!("keepAppRunningAfterWindowClose" in next)) {
2917
+ next.keepAppRunningAfterWindowClose = true;
2918
+ changed = true;
2919
+ }
2920
+ if (next.rateLimitPercentMode !== "remaining" && next.rateLimitPercentMode !== "used") {
2921
+ next.rateLimitPercentMode = "remaining";
2922
+ changed = true;
2923
+ }
2924
+ if (!migrationApplied) {
2925
+ next[CUSTOMER_DEFAULTS_MIGRATION_KEY] = true;
2926
+ changed = true;
2927
+ }
2928
+ return { settings: changed ? next : settings, changed };
2929
+ }
2902
2930
  async function readAppSettings() {
2903
2931
  const appState = await getAppState();
2904
- return stripLegacyAppSettingsKeys(asRecord(appState.runtimeSettings));
2932
+ const stripped = stripLegacyAppSettingsKeys(asRecord(appState.runtimeSettings));
2933
+ const next = withCurrentRuntimeDefaults(stripped, {
2934
+ migrateLegacyDefaults: true
2935
+ });
2936
+ if (next.changed) {
2937
+ await updateAppState(
2938
+ (current) => ({
2939
+ ...current,
2940
+ runtimeSettings: next.settings
2941
+ }),
2942
+ {
2943
+ mode: "replace",
2944
+ allowBeforeMigrationComplete: false
2945
+ }
2946
+ );
2947
+ }
2948
+ return next.settings;
2905
2949
  }
2906
2950
  async function writeAppSettings(settings, onUpdated) {
2907
- const nextSettings = stripLegacyAppSettingsKeys(asRecord(settings));
2951
+ const nextSettings = withCurrentRuntimeDefaults(
2952
+ stripLegacyAppSettingsKeys(asRecord(settings)),
2953
+ {
2954
+ migrateLegacyDefaults: false
2955
+ }
2956
+ ).settings;
2908
2957
  await updateAppState(
2909
2958
  (current) => ({
2910
2959
  ...current,
@@ -2932,6 +2981,7 @@ var import_node_crypto2 = __toESM(require("crypto"), 1);
2932
2981
  var DEFAULT_AUTO_ROLL_ENABLED = false;
2933
2982
  var DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
2934
2983
  var DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
2984
+ var DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
2935
2985
  var AUTO_ROLL_WARNING_MIN = 50;
2936
2986
  var AUTO_ROLL_WARNING_MAX = 99;
2937
2987
  var AUTO_ROLL_SWITCH_MAX = 100;
@@ -2975,7 +3025,8 @@ function normalizeAutoRollSettings(raw) {
2975
3025
  return {
2976
3026
  enabled,
2977
3027
  warningThreshold: normalizedWarning,
2978
- switchThreshold: normalizedSwitch
3028
+ switchThreshold: normalizedSwitch,
3029
+ restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL
2979
3030
  };
2980
3031
  }
2981
3032
 
@@ -3100,7 +3151,8 @@ async function writeCodexSettingsJsonRaw(payload) {
3100
3151
  autoRoll: autoRoll ? {
3101
3152
  enabled: autoRoll.enabled,
3102
3153
  warningThreshold: autoRoll.warningThreshold,
3103
- switchThreshold: autoRoll.switchThreshold
3154
+ switchThreshold: autoRoll.switchThreshold,
3155
+ restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
3104
3156
  } : void 0,
3105
3157
  license: license ? {
3106
3158
  licenseKey: license.licenseKey ?? null,
@@ -5802,7 +5854,12 @@ function printHelp(version) {
5802
5854
  console.log(`CodexUse CLI v${version}
5803
5855
 
5804
5856
  Usage:
5857
+ codexuse doctor
5858
+ codexuse run --profile <name> -- <codex args>
5859
+ codexuse best [--dry-run] -- <codex args>
5860
+
5805
5861
  codexuse account-pool status [--runtime=desktop|daemon] [--state-dir=/abs/path] [--port=NNNN]
5862
+ codexuse account-pool quickstart
5806
5863
  codexuse account-pool enable
5807
5864
  codexuse account-pool disable
5808
5865
  codexuse account-pool routing <least-used|round-robin>
@@ -5842,6 +5899,7 @@ Flags:
5842
5899
  --watch Keep checking and auto-switch when threshold is reached
5843
5900
  --interval=SEC Watch interval in seconds (default: 30)
5844
5901
  --dry-run Print planned switch without changing active profile
5902
+ --profile=NAME Run Codex with one saved profile
5845
5903
  --runtime=NAME Accounts Pool runtime store: desktop | daemon
5846
5904
  --state-dir=PATH Override the runtime state dir for Accounts Pool inspection
5847
5905
  --port=NNNN Stable daemon/API port (or use with account-pool status for daemon base URL)
@@ -6053,6 +6111,7 @@ function printAccountPoolHelp(version) {
6053
6111
 
6054
6112
  Usage:
6055
6113
  codexuse account-pool status [--runtime=desktop|daemon] [--state-dir=/abs/path] [--port=NNNN]
6114
+ codexuse account-pool quickstart [--runtime=desktop|daemon] [--state-dir=/abs/path] [--port=NNNN]
6056
6115
  codexuse account-pool enable
6057
6116
  codexuse account-pool disable
6058
6117
  codexuse account-pool routing <least-used|round-robin>
@@ -6833,6 +6892,28 @@ async function handleSessions(params, flags) {
6833
6892
  }
6834
6893
  console.log("Session mutation is intentionally not exposed here; pooled sessions belong to the live runtime owner.");
6835
6894
  }
6895
+ async function handleQuickstart(flags) {
6896
+ const { runtime, stateDir } = resolveStateDir(flags);
6897
+ const port = parseIntegerFlag(flags, "--port");
6898
+ const summary = await readAccountPoolRuntimeSummary({ runtime, stateDir, port });
6899
+ const baseUrl = summary.baseUrl ?? "http://127.0.0.1:<port>";
6900
+ console.log("Accounts Pool quickstart");
6901
+ console.log(`Runtime: ${summary.runtime}`);
6902
+ console.log(`Enabled: ${summary.enabled ? "yes" : "no"}`);
6903
+ console.log(`Pro: ${summary.hasProLicense ? "yes" : "no"}`);
6904
+ console.log(`API keys: ${summary.activeApiKeyCount}`);
6905
+ console.log(`Base URL: ${baseUrl}`);
6906
+ console.log("");
6907
+ console.log("1. Enable pool:");
6908
+ console.log(" codexuse account-pool enable");
6909
+ console.log("2. Create key:");
6910
+ console.log(` codexuse account-pool keys create --runtime=${summary.runtime}`);
6911
+ console.log("3. Use OpenAI-compatible settings:");
6912
+ console.log(` Base URL: ${baseUrl}/v1`);
6913
+ console.log(" API key: cux_pool_...");
6914
+ console.log("4. Smoke:");
6915
+ console.log(` curl ${baseUrl}/v1/models -H 'Authorization: Bearer cux_pool_...'`);
6916
+ }
6836
6917
  async function handleAccountPoolCommand(args, version) {
6837
6918
  const flags = args.filter((arg) => arg.startsWith("-"));
6838
6919
  const params = stripFlags(args);
@@ -6845,6 +6926,9 @@ async function handleAccountPoolCommand(args, version) {
6845
6926
  case "status":
6846
6927
  await handleStatus(flags);
6847
6928
  return;
6929
+ case "quickstart":
6930
+ await handleQuickstart(flags);
6931
+ return;
6848
6932
  case "enable":
6849
6933
  await handleEnable();
6850
6934
  return;
@@ -6899,39 +6983,404 @@ async function handleAccountPoolCommand(args, version) {
6899
6983
  }
6900
6984
  }
6901
6985
 
6902
- // src/commands/daemon.ts
6986
+ // src/commands/codex.ts
6903
6987
  var import_node_child_process4 = require("child_process");
6988
+
6989
+ // ../../packages/runtime-profiles/src/profiles/rate-limit-notifier.ts
6990
+ function maxUsedPercent(snapshot) {
6991
+ if (!snapshot) {
6992
+ return null;
6993
+ }
6994
+ const candidates = [
6995
+ snapshot.primary?.usedPercent,
6996
+ snapshot.secondary?.usedPercent
6997
+ ].filter((value) => typeof value === "number" && Number.isFinite(value));
6998
+ if (candidates.length === 0) {
6999
+ return null;
7000
+ }
7001
+ return Math.max(...candidates);
7002
+ }
7003
+
7004
+ // src/platform/codexCli.ts
7005
+ var import_node_child_process3 = require("child_process");
7006
+ var import_node_fs6 = require("fs");
7007
+ var import_node_path8 = __toESM(require("path"));
7008
+ var ENV_HINTS2 = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
7009
+ function fileExists2(candidate) {
7010
+ if (!candidate) return null;
7011
+ const resolved = import_node_path8.default.resolve(candidate);
7012
+ try {
7013
+ const stat2 = (0, import_node_fs6.statSync)(resolved);
7014
+ if (stat2.isFile()) return resolved;
7015
+ } catch {
7016
+ return null;
7017
+ }
7018
+ return null;
7019
+ }
7020
+ function resolveFromEnv() {
7021
+ for (const key of ENV_HINTS2) {
7022
+ const value = process.env[key];
7023
+ if (!value) continue;
7024
+ const resolved = fileExists2(value);
7025
+ if (resolved) return resolved;
7026
+ }
7027
+ return null;
7028
+ }
7029
+ function resolveFromPath() {
7030
+ const pathValue = process.env.PATH ?? "";
7031
+ const entries = pathValue.split(import_node_path8.default.delimiter).filter(Boolean);
7032
+ const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
7033
+ for (const entry of entries) {
7034
+ for (const name of names) {
7035
+ const candidate = fileExists2(import_node_path8.default.join(entry, name));
7036
+ if (candidate) return candidate;
7037
+ }
7038
+ }
7039
+ return null;
7040
+ }
7041
+ function resolveCodexBinary() {
7042
+ return resolveFromEnv() || resolveFromPath();
7043
+ }
7044
+ function requireCodexBinary(context) {
7045
+ const resolved = resolveCodexBinary();
7046
+ if (resolved) return resolved;
7047
+ const hint = "Install Codex CLI (npm i -g @openai/codex) or set CODEX_BINARY.";
7048
+ const message = context ? `${context} ${hint}` : hint;
7049
+ throw new Error(message);
7050
+ }
7051
+ function buildCodexCommand2(codexPath, args) {
7052
+ const normalized = codexPath.toLowerCase();
7053
+ const isJs = normalized.endsWith(".js") || normalized.endsWith(".mjs") || normalized.endsWith(".cjs");
7054
+ if (isJs) {
7055
+ return { command: process.execPath, args: [codexPath, ...args], shell: false };
7056
+ }
7057
+ const useShell = process.platform === "win32";
7058
+ return { command: codexPath, args, shell: useShell };
7059
+ }
7060
+ function isHeadless() {
7061
+ if (process.env.CODEXUSE_HEADLESS === "1") return true;
7062
+ if (process.env.CI) return true;
7063
+ if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
7064
+ if (process.platform === "linux") {
7065
+ if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) return true;
7066
+ }
7067
+ return false;
7068
+ }
7069
+ function resolveLoginMode(preferred) {
7070
+ if (preferred === "browser" || preferred === "device") {
7071
+ return preferred;
7072
+ }
7073
+ const envMode = process.env.CODEXUSE_LOGIN_MODE;
7074
+ if (envMode === "browser" || envMode === "device") {
7075
+ return envMode;
7076
+ }
7077
+ return isHeadless() ? "device" : "browser";
7078
+ }
7079
+ async function runCodexLogin(mode) {
7080
+ const codexPath = requireCodexBinary("Codex CLI is required to login.");
7081
+ const resolvedMode = resolveLoginMode(mode ?? null);
7082
+ const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
7083
+ const { command, args, shell } = buildCodexCommand2(codexPath, loginArgs);
7084
+ const child = (0, import_node_child_process3.spawn)(command, args, {
7085
+ stdio: "inherit",
7086
+ env: process.env,
7087
+ shell
7088
+ });
7089
+ const exitCode = await new Promise((resolve) => {
7090
+ child.on("close", (code) => resolve(code ?? 0));
7091
+ });
7092
+ if (exitCode !== 0) {
7093
+ throw new Error(`Codex CLI login failed (exit code ${exitCode}).`);
7094
+ }
7095
+ }
7096
+
7097
+ // src/commands/codex.ts
7098
+ var DOCTOR_TIMEOUT_MS = 8e3;
7099
+ function splitCommandArgs(args) {
7100
+ const delimiterIndex = args.indexOf("--");
7101
+ const flags = delimiterIndex >= 0 ? args.slice(0, delimiterIndex) : args.slice();
7102
+ const codexArgs = delimiterIndex >= 0 ? args.slice(delimiterIndex + 1) : [];
7103
+ return { flags, codexArgs };
7104
+ }
7105
+ function readOption(flags, longName, shortName) {
7106
+ for (let index = 0; index < flags.length; index += 1) {
7107
+ const flag = flags[index];
7108
+ if (flag === longName) {
7109
+ const next = flags[index + 1];
7110
+ return next && !next.startsWith("-") ? next : "";
7111
+ }
7112
+ if (flag.startsWith(`${longName}=`)) {
7113
+ return flag.slice(longName.length + 1).trim();
7114
+ }
7115
+ if (shortName && flag === shortName) {
7116
+ const next = flags[index + 1];
7117
+ return next && !next.startsWith("-") ? next : "";
7118
+ }
7119
+ }
7120
+ return null;
7121
+ }
7122
+ function firstLine(value) {
7123
+ const line = value.split(/\r?\n/).map((entry) => entry.trim()).find(Boolean);
7124
+ return line ?? null;
7125
+ }
7126
+ function formatUsagePercent(value) {
7127
+ return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}%` : "unknown";
7128
+ }
7129
+ function snapshotResetSeconds(snapshot) {
7130
+ const values = [
7131
+ snapshot?.primary?.resetsInSeconds,
7132
+ snapshot?.secondary?.resetsInSeconds
7133
+ ].filter((value) => typeof value === "number" && Number.isFinite(value));
7134
+ return values.length > 0 ? Math.min(...values) : null;
7135
+ }
7136
+ function fallbackUsedPercent(profile) {
7137
+ const cached = maxUsedPercent(profile.rateLimit ?? null);
7138
+ return typeof cached === "number" && Number.isFinite(cached) ? cached : null;
7139
+ }
7140
+ function compareCandidates(left, right) {
7141
+ const leftUsage = left.usedPercent ?? Number.POSITIVE_INFINITY;
7142
+ const rightUsage = right.usedPercent ?? Number.POSITIVE_INFINITY;
7143
+ if (leftUsage !== rightUsage) {
7144
+ return leftUsage - rightUsage;
7145
+ }
7146
+ const leftReset = left.resetSeconds ?? Number.POSITIVE_INFINITY;
7147
+ const rightReset = right.resetSeconds ?? Number.POSITIVE_INFINITY;
7148
+ if (leftReset !== rightReset) {
7149
+ return leftReset - rightReset;
7150
+ }
7151
+ return left.profile.name.localeCompare(right.profile.name);
7152
+ }
7153
+ async function runCommand(codexPath, args, timeoutMs) {
7154
+ const command = buildCodexCommand2(codexPath, args);
7155
+ return new Promise((resolve) => {
7156
+ const child = (0, import_node_child_process4.spawn)(command.command, command.args, {
7157
+ env: process.env,
7158
+ shell: command.shell,
7159
+ stdio: ["ignore", "pipe", "pipe"]
7160
+ });
7161
+ let stdout = "";
7162
+ let stderr = "";
7163
+ let settled = false;
7164
+ let timedOut = false;
7165
+ let timer;
7166
+ const finish = (code) => {
7167
+ if (settled) {
7168
+ return;
7169
+ }
7170
+ settled = true;
7171
+ clearTimeout(timer);
7172
+ resolve({ code, stdout, stderr, timedOut });
7173
+ };
7174
+ timer = setTimeout(() => {
7175
+ timedOut = true;
7176
+ child.kill("SIGTERM");
7177
+ finish(null);
7178
+ }, timeoutMs);
7179
+ child.stdout?.on("data", (chunk) => {
7180
+ stdout += String(chunk).slice(0, 2e4);
7181
+ });
7182
+ child.stderr?.on("data", (chunk) => {
7183
+ stderr += String(chunk).slice(0, 2e4);
7184
+ });
7185
+ child.on("error", () => finish(null));
7186
+ child.on("close", (code) => finish(code));
7187
+ });
7188
+ }
7189
+ async function selectBestProfile(manager, profiles, codexPath) {
7190
+ const candidates = [];
7191
+ for (const profile of profiles) {
7192
+ if (!profile.isValid || profile.tokenStatus?.requiresUserAction) {
7193
+ continue;
7194
+ }
7195
+ try {
7196
+ const snapshot = await manager.readLiveRateLimits(profile.name, { codexPath });
7197
+ candidates.push({
7198
+ profile,
7199
+ usedPercent: typeof maxUsedPercent(snapshot) === "number" ? maxUsedPercent(snapshot) : fallbackUsedPercent(profile),
7200
+ resetSeconds: snapshotResetSeconds(snapshot)
7201
+ });
7202
+ } catch {
7203
+ candidates.push({
7204
+ profile,
7205
+ usedPercent: fallbackUsedPercent(profile),
7206
+ resetSeconds: snapshotResetSeconds(profile.rateLimit ?? null)
7207
+ });
7208
+ }
7209
+ }
7210
+ return candidates.sort(compareCandidates)[0] ?? null;
7211
+ }
7212
+ async function runCodexWithProfile(manager, profileName, codexArgs) {
7213
+ const codexPath = resolveCodexBinary();
7214
+ if (!codexPath) {
7215
+ throw new Error("Codex CLI not found. Install Codex CLI or set CODEX_BINARY.");
7216
+ }
7217
+ const runtime = await manager.prepareProfileRuntime(profileName);
7218
+ const command = buildCodexCommand2(codexPath, codexArgs);
7219
+ try {
7220
+ const child = (0, import_node_child_process4.spawn)(command.command, command.args, {
7221
+ env: runtime.env,
7222
+ shell: command.shell,
7223
+ stdio: "inherit"
7224
+ });
7225
+ const exitCode = await new Promise((resolve) => {
7226
+ child.on("close", (code) => resolve(code ?? 0));
7227
+ });
7228
+ process.exitCode = exitCode;
7229
+ } finally {
7230
+ await manager.syncProfileRuntime(profileName, runtime.profileHome).catch((error) => {
7231
+ const message = error instanceof Error ? error.message : String(error);
7232
+ console.error(`Warning: failed to sync profile runtime for '${profileName}': ${message}`);
7233
+ });
7234
+ }
7235
+ }
7236
+ async function handleDoctorCommand(args) {
7237
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
7238
+ console.log(`Usage:
7239
+ codexuse doctor
7240
+
7241
+ Checks Codex CLI, auth, saved profiles, license, and Accounts Pool readiness.`);
7242
+ return;
7243
+ }
7244
+ const manager = new ProfileManager();
7245
+ const codexPath = resolveCodexBinary();
7246
+ const [profiles, current, license, accountPool] = await Promise.all([
7247
+ manager.listProfiles(),
7248
+ manager.getCurrentProfile(),
7249
+ licenseService.getStatus().catch(() => null),
7250
+ readAccountPoolRuntimeSummary().catch(() => null)
7251
+ ]);
7252
+ console.log("CodexUse Doctor");
7253
+ if (!codexPath) {
7254
+ console.log("Codex CLI: missing");
7255
+ } else {
7256
+ const version = await runCommand(codexPath, ["--version"], DOCTOR_TIMEOUT_MS);
7257
+ const versionText = firstLine(version.stdout) ?? firstLine(version.stderr);
7258
+ console.log(`Codex CLI: ${version.code === 0 ? "ok" : "check"} (${codexPath})`);
7259
+ console.log(`Version: ${versionText ?? (version.timedOut ? "timed out" : "unknown")}`);
7260
+ const auth = await runCommand(codexPath, ["login", "status"], DOCTOR_TIMEOUT_MS);
7261
+ const authText = firstLine(auth.stdout) ?? firstLine(auth.stderr);
7262
+ console.log(`Auth: ${auth.code === 0 ? "ok" : "check"}${authText ? ` (${authText})` : ""}`);
7263
+ }
7264
+ console.log(`Profiles: ${profiles.length}`);
7265
+ console.log(`Active profile: ${current.name ?? "none"}${current.trusted ? "" : " (untrusted)"}`);
7266
+ console.log(`License: ${license ? `${license.tier} (${license.state})` : "unknown"}`);
7267
+ if (accountPool) {
7268
+ console.log(
7269
+ `Accounts Pool: ${accountPool.enabled ? "on" : "off"} | keys ${accountPool.activeApiKeyCount} | sessions ${accountPool.activeSessionCount}`
7270
+ );
7271
+ } else {
7272
+ console.log("Accounts Pool: unknown");
7273
+ }
7274
+ const fixes = [];
7275
+ if (!codexPath) {
7276
+ fixes.push("Install Codex CLI or set CODEX_BINARY.");
7277
+ }
7278
+ if (profiles.length === 0) {
7279
+ fixes.push("Add first profile: codexuse profile add personal");
7280
+ }
7281
+ if (profiles.length === 1) {
7282
+ fixes.push("Add second profile: codexuse profile add work");
7283
+ }
7284
+ if (!current.name && profiles.length > 0) {
7285
+ fixes.push(`Switch active profile: codexuse profile switch ${profiles[0]?.name}`);
7286
+ }
7287
+ if (accountPool && (!accountPool.enabled || accountPool.activeApiKeyCount === 0)) {
7288
+ fixes.push("Pool quickstart: codexuse account-pool quickstart");
7289
+ }
7290
+ if (fixes.length > 0) {
7291
+ console.log("Fixes:");
7292
+ for (const fix of fixes) {
7293
+ console.log(`- ${fix}`);
7294
+ }
7295
+ return;
7296
+ }
7297
+ console.log("Ready.");
7298
+ }
7299
+ async function handleRunCommand(args) {
7300
+ const { flags, codexArgs } = splitCommandArgs(args);
7301
+ if (hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
7302
+ console.log(`Usage:
7303
+ codexuse run --profile <name> -- <codex args>
7304
+
7305
+ Runs Codex under one saved CodexUse profile.`);
7306
+ return;
7307
+ }
7308
+ const profileName = readOption(flags, "--profile", "-p");
7309
+ if (!profileName) {
7310
+ throw new Error("Profile is required. Use --profile <name>.");
7311
+ }
7312
+ const manager = new ProfileManager();
7313
+ await runCodexWithProfile(manager, profileName, codexArgs);
7314
+ }
7315
+ async function handleBestCommand(args) {
7316
+ const { flags, codexArgs } = splitCommandArgs(args);
7317
+ if (hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
7318
+ console.log(`Usage:
7319
+ codexuse best -- <codex args>
7320
+
7321
+ Picks the healthiest saved profile, then runs Codex. Auto-run requires Pro.`);
7322
+ return;
7323
+ }
7324
+ const codexPath = resolveCodexBinary();
7325
+ if (!codexPath) {
7326
+ throw new Error("Codex CLI not found. Install Codex CLI or set CODEX_BINARY.");
7327
+ }
7328
+ const manager = new ProfileManager();
7329
+ const profiles = await manager.listProfiles();
7330
+ const best = await selectBestProfile(manager, profiles, codexPath);
7331
+ if (!best) {
7332
+ throw new Error("No valid saved profiles found. Add one with `codexuse profile add <name>`.");
7333
+ }
7334
+ const dryRun = hasFlag(flags, "--dry-run");
7335
+ const license = await licenseService.getStatus();
7336
+ const summary = `${best.profile.name} (${formatUsagePercent(best.usedPercent)} used)`;
7337
+ if (!license.isPro) {
7338
+ console.log(`Best profile: ${summary}`);
7339
+ console.log("Auto-run is Pro. Free path:");
7340
+ console.log(` codexuse run --profile ${best.profile.name} -- ${codexArgs.join(" ")}`.trimEnd());
7341
+ return;
7342
+ }
7343
+ if (dryRun) {
7344
+ console.log(`Would run with profile: ${summary}`);
7345
+ return;
7346
+ }
7347
+ console.error(`CodexUse best -> ${summary}`);
7348
+ await runCodexWithProfile(manager, best.profile.name, codexArgs);
7349
+ }
7350
+
7351
+ // src/commands/daemon.ts
7352
+ var import_node_child_process6 = require("child_process");
6904
7353
  var import_node_crypto5 = require("crypto");
6905
- var import_node_fs6 = __toESM(require("fs"));
7354
+ var import_node_fs7 = __toESM(require("fs"));
6906
7355
  var import_node_net = __toESM(require("net"));
6907
7356
  var import_node_os4 = __toESM(require("os"));
6908
- var import_node_path9 = __toESM(require("path"));
7357
+ var import_node_path10 = __toESM(require("path"));
6909
7358
 
6910
7359
  // ../../packages/shared/src/core/internal-js-runtime.ts
6911
- var import_node_child_process3 = require("child_process");
6912
- var import_node_path8 = __toESM(require("path"), 1);
7360
+ var import_node_child_process5 = require("child_process");
7361
+ var import_node_path9 = __toESM(require("path"), 1);
6913
7362
  var cachedBunBinary = null;
6914
7363
  function hasBunRuntime() {
6915
7364
  return typeof process.versions.bun === "string" && process.versions.bun.length > 0;
6916
7365
  }
6917
7366
  function buildRuntimePath(env) {
6918
7367
  const homeDir = env.HOME ?? env.USERPROFILE ?? "";
6919
- const pathHints = (env.CODEX_PATH_HINTS ?? "").split(import_node_path8.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
7368
+ const pathHints = (env.CODEX_PATH_HINTS ?? "").split(import_node_path9.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
6920
7369
  const entries = [
6921
7370
  env.PATH ?? "",
6922
- import_node_path8.default.join(homeDir, ".bun", "bin"),
7371
+ import_node_path9.default.join(homeDir, ".bun", "bin"),
6923
7372
  "/opt/homebrew/bin",
6924
7373
  "/usr/local/bin",
6925
- import_node_path8.default.join(homeDir, ".local", "bin"),
6926
- import_node_path8.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
6927
- import_node_path8.default.join(homeDir, ".fnm", "current", "bin"),
7374
+ import_node_path9.default.join(homeDir, ".local", "bin"),
7375
+ import_node_path9.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
7376
+ import_node_path9.default.join(homeDir, ".fnm", "current", "bin"),
6928
7377
  ...pathHints
6929
7378
  ];
6930
7379
  return Array.from(
6931
7380
  new Set(
6932
- entries.flatMap((entry) => entry.split(import_node_path8.default.delimiter)).map((entry) => entry.trim()).filter(Boolean)
7381
+ entries.flatMap((entry) => entry.split(import_node_path9.default.delimiter)).map((entry) => entry.trim()).filter(Boolean)
6933
7382
  )
6934
- ).join(import_node_path8.default.delimiter);
7383
+ ).join(import_node_path9.default.delimiter);
6935
7384
  }
6936
7385
  function resolveConfiguredBunBinary(env) {
6937
7386
  const candidates = [
@@ -6961,7 +7410,7 @@ function probeBunBinary(env) {
6961
7410
  [
6962
7411
  env.CODEXUSE_INTERNAL_BUN_BIN?.trim(),
6963
7412
  env.BUN_BIN?.trim(),
6964
- import_node_path8.default.join(homeDir, ".bun", "bin", "bun"),
7413
+ import_node_path9.default.join(homeDir, ".bun", "bin", "bun"),
6965
7414
  "/opt/homebrew/bin/bun",
6966
7415
  "/usr/local/bin/bun",
6967
7416
  "bun"
@@ -6969,7 +7418,7 @@ function probeBunBinary(env) {
6969
7418
  )
6970
7419
  );
6971
7420
  for (const candidate of candidates) {
6972
- const probe = (0, import_node_child_process3.spawnSync)(candidate, ["--version"], {
7421
+ const probe = (0, import_node_child_process5.spawnSync)(candidate, ["--version"], {
6973
7422
  env,
6974
7423
  stdio: "ignore"
6975
7424
  });
@@ -7022,18 +7471,18 @@ Notes:
7022
7471
  }
7023
7472
  function resolveServerEntry() {
7024
7473
  const candidates = [
7025
- import_node_path9.default.resolve(__dirname, "server", "index.mjs"),
7026
- import_node_path9.default.resolve(__dirname, "../../../server/dist/index.mjs")
7474
+ import_node_path10.default.resolve(__dirname, "server", "index.mjs"),
7475
+ import_node_path10.default.resolve(__dirname, "../../../server/dist/index.mjs")
7027
7476
  ];
7028
7477
  for (const candidate of candidates) {
7029
- if (import_node_fs6.default.existsSync(candidate)) {
7478
+ if (import_node_fs7.default.existsSync(candidate)) {
7030
7479
  return candidate;
7031
7480
  }
7032
7481
  }
7033
7482
  return candidates[0];
7034
7483
  }
7035
7484
  function resolveStateDir2() {
7036
- return import_node_path9.default.join(import_node_os4.default.homedir(), ".codexuse", "t3-daemon");
7485
+ return import_node_path10.default.join(import_node_os4.default.homedir(), ".codexuse", "t3-daemon");
7037
7486
  }
7038
7487
  function reserveLoopbackPort(port = 0) {
7039
7488
  return new Promise((resolve, reject) => {
@@ -7059,7 +7508,7 @@ function reserveLoopbackPort(port = 0) {
7059
7508
  }
7060
7509
  async function runDaemonStart(options) {
7061
7510
  const entry = resolveServerEntry();
7062
- if (!import_node_fs6.default.existsSync(entry)) {
7511
+ if (!import_node_fs7.default.existsSync(entry)) {
7063
7512
  throw new Error(
7064
7513
  `Missing bundled T3 server build output at ${entry}. Rebuild the CLI package with \`bun run build:cli\`.`
7065
7514
  );
@@ -7070,7 +7519,7 @@ async function runDaemonStart(options) {
7070
7519
  const port = await reserveLoopbackPort(options.port ?? 0);
7071
7520
  const authToken = (0, import_node_crypto5.randomBytes)(24).toString("hex");
7072
7521
  const stateDir = resolveStateDir2();
7073
- const projectPath = options.projectPath ? import_node_path9.default.resolve(options.projectPath) : null;
7522
+ const projectPath = options.projectPath ? import_node_path10.default.resolve(options.projectPath) : null;
7074
7523
  const childCwd = projectPath ?? process.cwd();
7075
7524
  const telegramBotToken = options.telegramBotToken?.trim() || null;
7076
7525
  const runtime = resolveInternalBunRuntime({
@@ -7087,7 +7536,7 @@ async function runDaemonStart(options) {
7087
7536
  CODEXUSE_TELEGRAM_BRIDGE_ENABLED: "1"
7088
7537
  } : {}
7089
7538
  });
7090
- const child = (0, import_node_child_process4.spawn)(runtime.bin, [entry], {
7539
+ const child = (0, import_node_child_process6.spawn)(runtime.bin, [entry], {
7091
7540
  cwd: childCwd,
7092
7541
  env: runtime.env,
7093
7542
  stdio: "inherit"
@@ -7245,114 +7694,6 @@ async function assertProfileCreationAllowed(profileManager) {
7245
7694
  }
7246
7695
  }
7247
7696
 
7248
- // ../../packages/runtime-profiles/src/profiles/rate-limit-notifier.ts
7249
- function maxUsedPercent(snapshot) {
7250
- if (!snapshot) {
7251
- return null;
7252
- }
7253
- const candidates = [
7254
- snapshot.primary?.usedPercent,
7255
- snapshot.secondary?.usedPercent
7256
- ].filter((value) => typeof value === "number" && Number.isFinite(value));
7257
- if (candidates.length === 0) {
7258
- return null;
7259
- }
7260
- return Math.max(...candidates);
7261
- }
7262
-
7263
- // src/platform/codexCli.ts
7264
- var import_node_child_process5 = require("child_process");
7265
- var import_node_fs7 = require("fs");
7266
- var import_node_path10 = __toESM(require("path"));
7267
- var ENV_HINTS2 = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
7268
- function fileExists2(candidate) {
7269
- if (!candidate) return null;
7270
- const resolved = import_node_path10.default.resolve(candidate);
7271
- try {
7272
- const stat2 = (0, import_node_fs7.statSync)(resolved);
7273
- if (stat2.isFile()) return resolved;
7274
- } catch {
7275
- return null;
7276
- }
7277
- return null;
7278
- }
7279
- function resolveFromEnv() {
7280
- for (const key of ENV_HINTS2) {
7281
- const value = process.env[key];
7282
- if (!value) continue;
7283
- const resolved = fileExists2(value);
7284
- if (resolved) return resolved;
7285
- }
7286
- return null;
7287
- }
7288
- function resolveFromPath() {
7289
- const pathValue = process.env.PATH ?? "";
7290
- const entries = pathValue.split(import_node_path10.default.delimiter).filter(Boolean);
7291
- const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
7292
- for (const entry of entries) {
7293
- for (const name of names) {
7294
- const candidate = fileExists2(import_node_path10.default.join(entry, name));
7295
- if (candidate) return candidate;
7296
- }
7297
- }
7298
- return null;
7299
- }
7300
- function resolveCodexBinary() {
7301
- return resolveFromEnv() || resolveFromPath();
7302
- }
7303
- function requireCodexBinary(context) {
7304
- const resolved = resolveCodexBinary();
7305
- if (resolved) return resolved;
7306
- const hint = "Install Codex CLI (npm i -g @openai/codex) or set CODEX_BINARY.";
7307
- const message = context ? `${context} ${hint}` : hint;
7308
- throw new Error(message);
7309
- }
7310
- function buildCodexCommand2(codexPath, args) {
7311
- const normalized = codexPath.toLowerCase();
7312
- const isJs = normalized.endsWith(".js");
7313
- if (isJs) {
7314
- return { command: process.execPath, args: [codexPath, ...args], shell: false };
7315
- }
7316
- const useShell = process.platform === "win32";
7317
- return { command: codexPath, args, shell: useShell };
7318
- }
7319
- function isHeadless() {
7320
- if (process.env.CODEXUSE_HEADLESS === "1") return true;
7321
- if (process.env.CI) return true;
7322
- if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
7323
- if (process.platform === "linux") {
7324
- if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) return true;
7325
- }
7326
- return false;
7327
- }
7328
- function resolveLoginMode(preferred) {
7329
- if (preferred === "browser" || preferred === "device") {
7330
- return preferred;
7331
- }
7332
- const envMode = process.env.CODEXUSE_LOGIN_MODE;
7333
- if (envMode === "browser" || envMode === "device") {
7334
- return envMode;
7335
- }
7336
- return isHeadless() ? "device" : "browser";
7337
- }
7338
- async function runCodexLogin(mode) {
7339
- const codexPath = requireCodexBinary("Codex CLI is required to login.");
7340
- const resolvedMode = resolveLoginMode(mode ?? null);
7341
- const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
7342
- const { command, args, shell } = buildCodexCommand2(codexPath, loginArgs);
7343
- const child = (0, import_node_child_process5.spawn)(command, args, {
7344
- stdio: "inherit",
7345
- env: process.env,
7346
- shell
7347
- });
7348
- const exitCode = await new Promise((resolve) => {
7349
- child.on("close", (code) => resolve(code ?? 0));
7350
- });
7351
- if (exitCode !== 0) {
7352
- throw new Error(`Codex CLI login failed (exit code ${exitCode}).`);
7353
- }
7354
- }
7355
-
7356
7697
  // src/commands/profile.ts
7357
7698
  function formatProfileLabel(name, displayName) {
7358
7699
  if (displayName && displayName.trim() && displayName !== name) {
@@ -8161,7 +8502,7 @@ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
8161
8502
  var import_toml2 = __toESM(require_toml(), 1);
8162
8503
 
8163
8504
  // ../../packages/runtime-codex/src/codex/config-metadata.ts
8164
- var import_node_child_process6 = require("child_process");
8505
+ var import_node_child_process7 = require("child_process");
8165
8506
  var import_promises = require("fs/promises");
8166
8507
  var import_node_path11 = __toESM(require("path"), 1);
8167
8508
  var CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/openai/codex/main/codex-rs/core/config.schema.json";
@@ -8271,9 +8612,9 @@ function isRecord5(value) {
8271
8612
  function uniqueSorted(values) {
8272
8613
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
8273
8614
  }
8274
- async function runCommand(command, args, timeoutMs = 3e3) {
8615
+ async function runCommand2(command, args, timeoutMs = 3e3) {
8275
8616
  return new Promise((resolve, reject) => {
8276
- const child = (0, import_node_child_process6.spawn)(command, args, {
8617
+ const child = (0, import_node_child_process7.spawn)(command, args, {
8277
8618
  stdio: ["ignore", "pipe", "pipe"],
8278
8619
  env: process.env
8279
8620
  });
@@ -8308,7 +8649,7 @@ async function runCodexCommand(args) {
8308
8649
  const isJsLauncher = codexPath.endsWith(".js");
8309
8650
  const command = isJsLauncher ? process.execPath : codexPath;
8310
8651
  const commandArgs = isJsLauncher ? [codexPath, ...args] : args;
8311
- return runCommand(command, commandArgs);
8652
+ return runCommand2(command, commandArgs);
8312
8653
  }
8313
8654
  function parseFeatureCatalog(output) {
8314
8655
  const entries = [];
@@ -9797,7 +10138,8 @@ async function loadLegacySettingsPatch() {
9797
10138
  autoRoll: autoRoll ? {
9798
10139
  enabled: autoRoll.enabled,
9799
10140
  warningThreshold: autoRoll.warningThreshold,
9800
- switchThreshold: autoRoll.switchThreshold
10141
+ switchThreshold: autoRoll.switchThreshold,
10142
+ restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
9801
10143
  } : void 0
9802
10144
  };
9803
10145
  }
@@ -9993,7 +10335,8 @@ function mergeLegacyLocalStoragePatch(payload) {
9993
10335
  patch.autoRoll = {
9994
10336
  enabled: autoRoll.enabled,
9995
10337
  warningThreshold: autoRoll.warningThreshold,
9996
- switchThreshold: autoRoll.switchThreshold
10338
+ switchThreshold: autoRoll.switchThreshold,
10339
+ restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
9997
10340
  };
9998
10341
  markSkippedIfPresent("codex:auto-roll-settings", true);
9999
10342
  } else {
@@ -10267,7 +10610,7 @@ async function ensureCliStorageReady() {
10267
10610
  }
10268
10611
 
10269
10612
  // src/app/main.ts
10270
- var VERSION = true ? "3.8.0" : "0.0.0";
10613
+ var VERSION = true ? "3.9.0" : "0.0.0";
10271
10614
  async function runCli() {
10272
10615
  const args = process.argv.slice(2);
10273
10616
  if (args.length === 0) {
@@ -10285,6 +10628,18 @@ async function runCli() {
10285
10628
  const command = args[0];
10286
10629
  const rest = args.slice(1);
10287
10630
  switch (command) {
10631
+ case "doctor":
10632
+ await ensureCliStorageReady();
10633
+ await handleDoctorCommand(rest);
10634
+ return;
10635
+ case "run":
10636
+ await ensureCliStorageReady();
10637
+ await handleRunCommand(rest);
10638
+ return;
10639
+ case "best":
10640
+ await ensureCliStorageReady();
10641
+ await handleBestCommand(rest);
10642
+ return;
10288
10643
  case "account-pool":
10289
10644
  case "accounts-pool":
10290
10645
  await ensureCliStorageReady();