traderclaw-cli 1.0.73 → 1.0.74

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.
@@ -1562,6 +1562,115 @@ function configureOpenClawLlmProvider({ provider, model, credential }, configPat
1562
1562
  return { configPath, provider, model };
1563
1563
  }
1564
1564
 
1565
+ /**
1566
+ * Sets only `agents.defaults.model.primary` (OAuth / subscription paths where credentials live in OpenClaw auth profiles).
1567
+ * Does not write API keys into config.env.
1568
+ */
1569
+ function configureOpenClawLlmModelPrimaryOnly({ provider, model }, configPath = CONFIG_FILE) {
1570
+ if (!provider || !model) {
1571
+ throw new Error("LLM provider and model are required.");
1572
+ }
1573
+ if (!model.startsWith(`${provider}/`)) {
1574
+ throw new Error(`Selected model '${model}' does not match provider '${provider}'.`);
1575
+ }
1576
+
1577
+ let config = {};
1578
+ try {
1579
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
1580
+ } catch {
1581
+ config = {};
1582
+ }
1583
+
1584
+ if (!config.agents) config.agents = {};
1585
+ if (!config.agents.defaults) config.agents.defaults = {};
1586
+ ensureAgentsDefaultsSchemaCompat(config);
1587
+ if (!config.agents.defaults.model || typeof config.agents.defaults.model !== "object") {
1588
+ config.agents.defaults.model = {};
1589
+ }
1590
+ config.agents.defaults.model.primary = model;
1591
+
1592
+ mkdirSync(CONFIG_DIR, { recursive: true });
1593
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1594
+ return { configPath, provider, model };
1595
+ }
1596
+
1597
+ /**
1598
+ * Runs `openclaw models auth login --provider openai-codex` and feeds the pasted redirect URL or code on stdin
1599
+ * when the CLI prompts (with a timed fallback for non-interactive / SSH).
1600
+ */
1601
+ function runOpenClawCodexOAuthLogin(paste, emitLog) {
1602
+ return new Promise((resolve, reject) => {
1603
+ const child = spawn("openclaw", ["models", "auth", "login", "--provider", "openai-codex"], {
1604
+ stdio: ["pipe", "pipe", "pipe"],
1605
+ shell: false,
1606
+ });
1607
+
1608
+ let stdout = "";
1609
+ let stderr = "";
1610
+ let pasteSent = false;
1611
+
1612
+ const sendPaste = () => {
1613
+ if (pasteSent) return;
1614
+ const p = String(paste || "").trim();
1615
+ if (!p) return;
1616
+ pasteSent = true;
1617
+ try {
1618
+ child.stdin.write(`${p}\n`);
1619
+ } catch {
1620
+ // ignore
1621
+ }
1622
+ };
1623
+
1624
+ let fallbackTimer = setTimeout(() => sendPaste(), 9000);
1625
+
1626
+ const onChunk = (chunk) => {
1627
+ const combined = (stdout + stderr).toLowerCase();
1628
+ const c = typeof chunk === "string" ? chunk : chunk.toString();
1629
+ if (!pasteSent && /paste|authorization|redirect|callback/i.test(combined) && c.length > 0) {
1630
+ clearTimeout(fallbackTimer);
1631
+ fallbackTimer = setTimeout(() => sendPaste(), 400);
1632
+ }
1633
+ };
1634
+
1635
+ child.stdout?.on("data", (d) => {
1636
+ const t = d.toString();
1637
+ stdout += t;
1638
+ const urls = extractUrls(t);
1639
+ emitLog("info", t, urls);
1640
+ onChunk(t);
1641
+ });
1642
+
1643
+ child.stderr?.on("data", (d) => {
1644
+ const t = d.toString();
1645
+ stderr += t;
1646
+ const urls = extractUrls(t);
1647
+ emitLog("warn", t, urls);
1648
+ onChunk(t);
1649
+ });
1650
+
1651
+ child.on("close", (code) => {
1652
+ clearTimeout(fallbackTimer);
1653
+ if (code === 0) {
1654
+ resolve({ stdout, stderr });
1655
+ return;
1656
+ }
1657
+ const detail = `${stderr}\n${stdout}`.trim();
1658
+ const err = new Error(
1659
+ detail || `openclaw models auth login failed with exit code ${code}. Try running the same command in a normal shell, then re-run the wizard with "already logged in" checked.`,
1660
+ );
1661
+ err.code = code;
1662
+ err.stdout = stdout;
1663
+ err.stderr = stderr;
1664
+ reject(err);
1665
+ });
1666
+
1667
+ child.on("error", (e) => {
1668
+ clearTimeout(fallbackTimer);
1669
+ reject(e);
1670
+ });
1671
+ });
1672
+ }
1673
+
1565
1674
  function verifyInstallation(modeConfig, apiKey) {
1566
1675
  const gatewayFile = join(CONFIG_DIR, "gateway", modeConfig.gatewayConfig);
1567
1676
  let llmConfigured = false;
@@ -1645,9 +1754,12 @@ export class InstallerStepEngine {
1645
1754
  this.modeConfig = modeConfig;
1646
1755
  this.options = {
1647
1756
  lane: normalizeLane(options.lane),
1757
+ llmAuthMode: options.llmAuthMode === "oauth" ? "oauth" : "api_key",
1648
1758
  llmProvider: options.llmProvider || "",
1649
1759
  llmModel: options.llmModel || "",
1650
1760
  llmCredential: options.llmCredential || "",
1761
+ llmOAuthPaste: typeof options.llmOAuthPaste === "string" ? options.llmOAuthPaste.trim() : "",
1762
+ llmOAuthSkipLogin: options.llmOAuthSkipLogin === true,
1651
1763
  apiKey: options.apiKey || "",
1652
1764
  orchestratorUrl: options.orchestratorUrl || "https://api.traderclaw.ai",
1653
1765
  gatewayBaseUrl: options.gatewayBaseUrl || "",
@@ -1924,15 +2036,78 @@ export class InstallerStepEngine {
1924
2036
  const provider = String(this.options.llmProvider || "").trim();
1925
2037
  const requestedModel = String(this.options.llmModel || "").trim();
1926
2038
  const credential = String(this.options.llmCredential || "").trim();
1927
- if (!provider || !credential) {
2039
+ const authMode = this.options.llmAuthMode === "oauth" ? "oauth" : "api_key";
2040
+
2041
+ if (!provider) {
1928
2042
  throw new Error(
1929
- "Missing required LLM settings. Select provider and provide credential in the wizard before starting installation.",
2043
+ "Missing required LLM settings. Select provider in the wizard before starting installation.",
1930
2044
  );
1931
2045
  }
1932
2046
  if (!commandExists("openclaw")) {
1933
2047
  throw new Error("OpenClaw is not available yet. Install step must complete before LLM configuration.");
1934
2048
  }
1935
2049
 
2050
+ if (authMode === "oauth") {
2051
+ if (provider !== "openai-codex") {
2052
+ throw new Error("OAuth mode requires LLM provider openai-codex (ChatGPT / Codex subscription).");
2053
+ }
2054
+ const skipLogin = this.options.llmOAuthSkipLogin === true;
2055
+ const oauthPaste = String(this.options.llmOAuthPaste || "").trim();
2056
+ if (!skipLogin && !oauthPaste) {
2057
+ throw new Error(
2058
+ "Codex OAuth requires a pasted authorization code or redirect URL, or enable skip if you already ran openclaw models auth login on this host.",
2059
+ );
2060
+ }
2061
+ if (!skipLogin) {
2062
+ try {
2063
+ await runOpenClawCodexOAuthLogin(oauthPaste, (level, text, urls) =>
2064
+ this.emitLog("configure_llm", level, text, urls || []),
2065
+ );
2066
+ } catch (err) {
2067
+ const tail = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.trim();
2068
+ throw new Error(
2069
+ `${tail}\n\nIf OAuth cannot complete from the wizard, run in a shell: openclaw models auth login --provider openai-codex — then re-run the wizard with "already logged in" checked.`,
2070
+ );
2071
+ }
2072
+ }
2073
+
2074
+ const selection = resolveLlmModelSelection(provider, requestedModel);
2075
+ for (const msg of selection.warnings) {
2076
+ this.emitLog("configure_llm", "warn", msg);
2077
+ }
2078
+ const model = selection.model;
2079
+
2080
+ const saved = configureOpenClawLlmModelPrimaryOnly({ provider, model });
2081
+ this.emitLog(
2082
+ "configure_llm",
2083
+ "info",
2084
+ `Configured OpenClaw model primary=${model} (Codex OAuth; credentials in OpenClaw auth profiles, not OPENAI_API_KEY).`,
2085
+ );
2086
+
2087
+ await runCommandWithEvents("openclaw", ["config", "validate"], {
2088
+ onEvent: (evt) => this.emitLog("configure_llm", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
2089
+ });
2090
+
2091
+ try {
2092
+ await runCommandWithEvents("openclaw", ["models", "status", "--check", "--probe-provider", provider], {
2093
+ onEvent: (evt) => this.emitLog("configure_llm", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
2094
+ });
2095
+ } catch (err) {
2096
+ const details = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.trim();
2097
+ throw new Error(
2098
+ `LLM provider validation failed for '${provider}'. Check OAuth login and model, then retry.\n${details}`,
2099
+ );
2100
+ }
2101
+
2102
+ return { configured: true, provider, model, configPath: saved.configPath, authMode: "oauth" };
2103
+ }
2104
+
2105
+ if (!credential) {
2106
+ throw new Error(
2107
+ "Missing required LLM settings. Paste your API key or token in the wizard before starting installation.",
2108
+ );
2109
+ }
2110
+
1936
2111
  const selection = resolveLlmModelSelection(provider, requestedModel);
1937
2112
  for (const msg of selection.warnings) {
1938
2113
  this.emitLog("configure_llm", "warn", msg);
@@ -1957,7 +2132,7 @@ export class InstallerStepEngine {
1957
2132
  );
1958
2133
  }
1959
2134
 
1960
- return { configured: true, provider, model, configPath: saved.configPath };
2135
+ return { configured: true, provider, model, configPath: saved.configPath, authMode: "api_key" };
1961
2136
  }
1962
2137
 
1963
2138
  buildSetupHandoff() {
@@ -1689,6 +1689,9 @@ function parseInstallWizardArgs(args) {
1689
1689
  llmProvider: "",
1690
1690
  llmModel: "",
1691
1691
  llmCredential: "",
1692
+ llmAuthMode: "api_key",
1693
+ llmOAuthPaste: "",
1694
+ llmOAuthSkipLogin: false,
1692
1695
  orchestratorUrl: "https://api.traderclaw.ai",
1693
1696
  gatewayBaseUrl: "",
1694
1697
  gatewayToken: "",
@@ -1709,6 +1712,12 @@ function parseInstallWizardArgs(args) {
1709
1712
  if (key === "--llm-provider" && next) out.llmProvider = args[++i];
1710
1713
  if (key === "--llm-model" && next) out.llmModel = args[++i];
1711
1714
  if ((key === "--llm-api-key" || key === "--llm-token") && next) out.llmCredential = args[++i];
1715
+ if (key === "--llm-auth" && next) {
1716
+ const v = args[++i];
1717
+ out.llmAuthMode = v === "oauth" ? "oauth" : "api_key";
1718
+ }
1719
+ if (key === "--llm-oauth-paste" && next) out.llmOAuthPaste = args[++i];
1720
+ if (key === "--llm-oauth-skip-login") out.llmOAuthSkipLogin = true;
1712
1721
  if ((key === "--url" || key === "-u") && next) out.orchestratorUrl = args[++i];
1713
1722
  if ((key === "--gateway-base-url" || key === "-g") && next) out.gatewayBaseUrl = args[++i];
1714
1723
  if ((key === "--gateway-token" || key === "-t") && next) out.gatewayToken = args[++i];
@@ -1867,7 +1876,7 @@ async function cmdPrecheck(args) {
1867
1876
 
1868
1877
  log.info("Manual staging run commands:");
1869
1878
  log.info(" 1) traderclaw install --wizard");
1870
- log.info(" 2) In wizard, set LLM provider + credential and Telegram token");
1879
+ log.info(" 2) In wizard, set LLM (API key or Codex OAuth) and Telegram token");
1871
1880
  log.info(" 3) Approve tailscale login in provided URL");
1872
1881
  log.info(" 4) Confirm /v1/responses returns non-404 on funnel host");
1873
1882
  log.info(" 5) Verify Telegram channel setup + probe");
@@ -1944,7 +1953,13 @@ async function loadWizardLlmCatalogAsync() {
1944
1953
  },
1945
1954
  {
1946
1955
  id: "openai-codex",
1947
- models: [{ id: "openai-codex/gpt-5-codex", name: "GPT-5 Codex" }],
1956
+ models: [
1957
+ { id: "openai-codex/gpt-5.4", name: "GPT-5.4 Codex (recommended, ChatGPT OAuth)" },
1958
+ {
1959
+ id: "openai-codex/gpt-5.3-codex-spark",
1960
+ name: "GPT-5.3 Codex Spark (subscription entitlement; experimental)",
1961
+ },
1962
+ ],
1948
1963
  },
1949
1964
  {
1950
1965
  id: "google",
@@ -2027,7 +2042,8 @@ function wizardHtml(defaults) {
2027
2042
  .card { background:#121a31; border:1px solid #22315a; border-radius: 12px; padding: 16px; margin-bottom: 16px; }
2028
2043
  .grid { display:grid; grid-template-columns:1fr 1fr; gap: 12px; }
2029
2044
  label { display:block; font-size: 12px; color:#9cb0de; margin-bottom: 4px; }
2030
- input, select { width:100%; padding:10px; border-radius:8px; border:1px solid #334a87; background:#0d1530; color:#e8eef9; }
2045
+ input, select, textarea { width:100%; padding:10px; border-radius:8px; border:1px solid #334a87; background:#0d1530; color:#e8eef9; font-family: inherit; font-size: 14px; box-sizing: border-box; }
2046
+ textarea { min-height: 88px; resize: vertical; }
2031
2047
  button { border:0; border-radius:8px; padding:10px 14px; background:#4d7cff; color:#fff; cursor:pointer; font-weight:600; }
2032
2048
  button:disabled { opacity:0.6; cursor:not-allowed; }
2033
2049
  .muted { color:#9cb0de; font-size:13px; }
@@ -2069,9 +2085,20 @@ function wizardHtml(defaults) {
2069
2085
  </div>
2070
2086
  <div class="card" id="llmCard">
2071
2087
  <h3>Required: OpenClaw LLM Provider</h3>
2072
- <p class="muted">Pick your LLM provider and paste your credential. Beginner mode supports common API-key providers.</p>
2073
- <div class="grid">
2074
- <div>
2088
+ <p class="muted">Use an API key for OpenAI Platform and other providers, or ChatGPT/Codex OAuth for subscription access (no separate API billing). OAuth tokens are stored by OpenClaw — not as <code>OPENAI_API_KEY</code>.</p>
2089
+ <div style="margin-bottom:12px;">
2090
+ <label style="margin-bottom:8px;">LLM authentication</label>
2091
+ <label style="display:flex; align-items:flex-start; gap:8px; font-size:14px; color:#e8eef9; margin-bottom:6px; cursor:pointer;">
2092
+ <input id="llmAuthModeApiKey" name="llmAuthMode" type="radio" value="api_key" style="width:auto; margin-top:3px;" checked />
2093
+ <span><strong>API key</strong> — OpenAI Platform (<code>openai</code>), Anthropic, OpenRouter, etc.</span>
2094
+ </label>
2095
+ <label style="display:flex; align-items:flex-start; gap:8px; font-size:14px; color:#e8eef9; cursor:pointer;">
2096
+ <input id="llmAuthModeOauth" name="llmAuthMode" type="radio" value="oauth" style="width:auto; margin-top:3px;" />
2097
+ <span><strong>ChatGPT / Codex (OAuth)</strong> — provider <code>openai-codex</code> (Plus/Pro subscription via OpenClaw login)</span>
2098
+ </label>
2099
+ </div>
2100
+ <div class="grid" id="llmProviderModelGrid">
2101
+ <div id="llmProviderWrap">
2075
2102
  <label>LLM provider (required)</label>
2076
2103
  <select id="llmProvider"></select>
2077
2104
  </div>
@@ -2080,21 +2107,31 @@ function wizardHtml(defaults) {
2080
2107
  <select id="llmModel"></select>
2081
2108
  </div>
2082
2109
  </div>
2110
+ <p class="muted hidden" id="llmOauthProviderNote">Provider is fixed to <code>openai-codex</code>. Pick a Codex model below (or enable manual selection).</p>
2083
2111
  <div style="margin-top:8px;">
2084
2112
  <label style="display:flex; align-items:center; gap:8px; font-size:13px; color:#9cb0de;">
2085
2113
  <input id="llmModelManual" type="checkbox" style="width:auto; padding:0; margin:0;" />
2086
2114
  Choose model manually (advanced)
2087
2115
  </label>
2088
2116
  </div>
2089
- <div style="margin-top:12px;">
2117
+ <div style="margin-top:12px;" id="llmApiKeyBlock">
2090
2118
  <label>LLM API key or token (required)</label>
2091
2119
  <input id="llmCredential" type="password" placeholder="Paste the credential for the selected provider/model" />
2092
- <p class="muted">This credential is written to OpenClaw model provider config so your agent can run. If you skip manual model selection, the installer will choose a safe provider default.</p>
2093
- <p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
2094
- <div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
2095
- <span class="spinner" aria-hidden="true"></span>
2096
- <span id="llmLoadingHintText">Fetching provider list...</span>
2097
- </div>
2120
+ <p class="muted">Written to OpenClaw <code>config.env</code> for the selected provider. If you do not choose a model manually, the installer picks a safe default.</p>
2121
+ </div>
2122
+ <div style="margin-top:12px;" id="llmOauthBlock" class="hidden">
2123
+ <label>Paste authorization code or full redirect URL</label>
2124
+ <textarea id="llmOAuthPaste" autocomplete="off" placeholder="After the installer prints an OAuth URL in the log, sign in locally and paste the code or full callback URL here. Leave empty if you use the option below."></textarea>
2125
+ <label style="display:flex; align-items:flex-start; gap:8px; font-size:13px; color:#9cb0de; margin-top:8px; cursor:pointer;">
2126
+ <input id="llmOAuthSkipLogin" type="checkbox" style="width:auto; margin-top:3px;" />
2127
+ <span>I already ran <code>openclaw models auth login --provider openai-codex</code> on this machine</span>
2128
+ </label>
2129
+ <p class="muted">If login hangs over SSH, complete OAuth in a normal shell first, then enable the checkbox above and start again. Live install logs will show the authorize URL when the CLI prints it.</p>
2130
+ </div>
2131
+ <p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
2132
+ <div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
2133
+ <span class="spinner" aria-hidden="true"></span>
2134
+ <span id="llmLoadingHintText">Fetching provider list...</span>
2098
2135
  </div>
2099
2136
  </div>
2100
2137
  <div class="card" id="xCard">
@@ -2228,6 +2265,14 @@ function wizardHtml(defaults) {
2228
2265
  const llmModelEl = document.getElementById("llmModel");
2229
2266
  const llmModelManualEl = document.getElementById("llmModelManual");
2230
2267
  const llmCredentialEl = document.getElementById("llmCredential");
2268
+ const llmAuthModeApiKey = document.getElementById("llmAuthModeApiKey");
2269
+ const llmAuthModeOauth = document.getElementById("llmAuthModeOauth");
2270
+ const llmProviderWrap = document.getElementById("llmProviderWrap");
2271
+ const llmOauthProviderNote = document.getElementById("llmOauthProviderNote");
2272
+ const llmApiKeyBlock = document.getElementById("llmApiKeyBlock");
2273
+ const llmOauthBlock = document.getElementById("llmOauthBlock");
2274
+ const llmOAuthPasteEl = document.getElementById("llmOAuthPaste");
2275
+ const llmOAuthSkipLoginEl = document.getElementById("llmOAuthSkipLogin");
2231
2276
  const telegramTokenEl = document.getElementById("telegramToken");
2232
2277
  const llmLoadStateEl = document.getElementById("llmLoadState");
2233
2278
  const llmLoadingHintEl = document.getElementById("llmLoadingHint");
@@ -2254,14 +2299,61 @@ function wizardHtml(defaults) {
2254
2299
  let pollTimer = null;
2255
2300
  let pollIntervalMs = 1200;
2256
2301
  let installLocked = false;
2302
+ let savedApiKeyProvider = "";
2303
+
2304
+ (function initLlmAuthDefaults() {
2305
+ const mode = ${JSON.stringify(defaults.llmAuthMode || "api_key")};
2306
+ if (mode === "oauth") {
2307
+ llmAuthModeOauth.checked = true;
2308
+ llmAuthModeApiKey.checked = false;
2309
+ }
2310
+ llmOAuthPasteEl.value = ${JSON.stringify(defaults.llmOAuthPaste || "")};
2311
+ if (${defaults.llmOAuthSkipLogin === true ? "true" : "false"}) {
2312
+ llmOAuthSkipLoginEl.checked = true;
2313
+ }
2314
+ applyLlmAuthModeUi();
2315
+ })();
2316
+
2317
+ function isOauthMode() {
2318
+ return llmAuthModeOauth && llmAuthModeOauth.checked;
2319
+ }
2320
+
2321
+ function effectiveLlmProvider() {
2322
+ return isOauthMode() ? "openai-codex" : llmProviderEl.value.trim();
2323
+ }
2324
+
2325
+ function applyLlmAuthModeUi() {
2326
+ if (!llmProviderWrap || !llmApiKeyBlock || !llmOauthBlock) return;
2327
+ if (isOauthMode()) {
2328
+ savedApiKeyProvider = llmProviderEl.value || savedApiKeyProvider;
2329
+ llmProviderEl.value = "openai-codex";
2330
+ llmProviderWrap.classList.add("hidden");
2331
+ llmOauthProviderNote.classList.remove("hidden");
2332
+ llmApiKeyBlock.classList.add("hidden");
2333
+ llmOauthBlock.classList.remove("hidden");
2334
+ } else {
2335
+ llmProviderWrap.classList.remove("hidden");
2336
+ llmOauthProviderNote.classList.add("hidden");
2337
+ llmApiKeyBlock.classList.remove("hidden");
2338
+ llmOauthBlock.classList.add("hidden");
2339
+ if (savedApiKeyProvider) {
2340
+ llmProviderEl.value = savedApiKeyProvider;
2341
+ }
2342
+ }
2343
+ }
2344
+
2345
+ function onLlmAuthModeChange() {
2346
+ applyLlmAuthModeUi();
2347
+ refreshModelOptions("");
2348
+ }
2257
2349
 
2258
2350
  function hasRequiredInputs() {
2259
- return (
2260
- llmCatalogReady
2261
- && Boolean(llmProviderEl.value.trim())
2262
- && Boolean(llmCredentialEl.value.trim())
2263
- && Boolean(telegramTokenEl.value.trim())
2264
- );
2351
+ if (!llmCatalogReady || !Boolean(telegramTokenEl.value.trim())) return false;
2352
+ if (isOauthMode()) {
2353
+ if (llmOAuthSkipLoginEl.checked) return true;
2354
+ return Boolean(llmOAuthPasteEl.value.trim());
2355
+ }
2356
+ return Boolean(llmProviderEl.value.trim()) && Boolean(llmCredentialEl.value.trim());
2265
2357
  }
2266
2358
 
2267
2359
  /** All-or-nothing: 0 or 4 non-empty X fields; partial is invalid. */
@@ -2348,7 +2440,7 @@ function wizardHtml(defaults) {
2348
2440
  }
2349
2441
 
2350
2442
  function refreshModelOptions(preferredModel) {
2351
- const provider = llmProviderEl.value;
2443
+ const provider = effectiveLlmProvider();
2352
2444
  const providerEntry = (llmCatalog.providers || []).find((entry) => entry.id === provider);
2353
2445
  const modelItems = (providerEntry ? providerEntry.models : []).map((item) => ({ value: item.id, label: item.name + " (" + item.id + ")" }));
2354
2446
  if (modelItems.length === 0) {
@@ -2378,8 +2470,9 @@ function wizardHtml(defaults) {
2378
2470
  return;
2379
2471
  }
2380
2472
  setSelectOptions(llmProviderEl, providers, "${defaults.llmProvider}");
2473
+ applyLlmAuthModeUi();
2381
2474
  refreshModelOptions("${defaults.llmModel}");
2382
- const catalogMsg = "Select your provider, paste your API key, and start installation. After setup, run \`openclaw models list\` to explore your live catalog.";
2475
+ const catalogMsg = "Choose API key or Codex OAuth, then start installation. After setup, run \`openclaw models list\` for your live catalog.";
2383
2476
  setLlmCatalogReady(true, catalogMsg, false);
2384
2477
  } catch (err) {
2385
2478
  setLlmCatalogReady(false, "Failed to load LLM providers. Reload the page and try again.", true);
@@ -2433,10 +2526,14 @@ function wizardHtml(defaults) {
2433
2526
  manualEl.textContent = "";
2434
2527
  readyEl.textContent = "Starting installation...";
2435
2528
 
2529
+ const oauth = isOauthMode();
2436
2530
  const payload = {
2437
- llmProvider: llmProviderEl.value.trim(),
2531
+ llmAuthMode: oauth ? "oauth" : "api_key",
2532
+ llmProvider: oauth ? "openai-codex" : llmProviderEl.value.trim(),
2438
2533
  llmModel: llmModelEl.value.trim(),
2439
- llmCredential: llmCredentialEl.value.trim(),
2534
+ llmCredential: oauth ? "" : llmCredentialEl.value.trim(),
2535
+ llmOAuthPaste: llmOAuthPasteEl.value.trim(),
2536
+ llmOAuthSkipLogin: llmOAuthSkipLoginEl.checked,
2440
2537
  apiKey: document.getElementById("apiKey").value.trim(),
2441
2538
  telegramToken: document.getElementById("telegramToken").value.trim(),
2442
2539
  referralCode: document.getElementById("referralCode").value.trim(),
@@ -2445,10 +2542,18 @@ function wizardHtml(defaults) {
2445
2542
  xAccessTokenMain: xAccessTokenMainEl.value.trim(),
2446
2543
  xAccessTokenMainSecret: xAccessTokenMainSecretEl.value.trim(),
2447
2544
  };
2448
- if (!payload.llmProvider || !payload.llmCredential) {
2545
+ if (oauth) {
2546
+ if (!payload.llmOAuthSkipLogin && !payload.llmOAuthPaste) {
2547
+ stateEl.textContent = "blocked";
2548
+ readyEl.textContent = "";
2549
+ manualEl.textContent =
2550
+ "Codex OAuth: paste the authorization code or full redirect URL, or check the box if you already ran openclaw models auth login on this machine.";
2551
+ return;
2552
+ }
2553
+ } else if (!payload.llmProvider || !payload.llmCredential) {
2449
2554
  stateEl.textContent = "blocked";
2450
2555
  readyEl.textContent = "";
2451
- manualEl.textContent = "LLM provider and credential are required before starting installation.";
2556
+ manualEl.textContent = "LLM provider and API key are required before starting installation.";
2452
2557
  return;
2453
2558
  }
2454
2559
  if (!payload.telegramToken) {
@@ -2690,7 +2795,11 @@ function wizardHtml(defaults) {
2690
2795
  llmModelEl.disabled = !llmModelManualEl.checked;
2691
2796
  updateStartButtonState();
2692
2797
  });
2798
+ llmAuthModeApiKey.addEventListener("change", onLlmAuthModeChange);
2799
+ llmAuthModeOauth.addEventListener("change", onLlmAuthModeChange);
2693
2800
  llmCredentialEl.addEventListener("input", updateStartButtonState);
2801
+ llmOAuthPasteEl.addEventListener("input", updateStartButtonState);
2802
+ llmOAuthSkipLoginEl.addEventListener("change", updateStartButtonState);
2694
2803
  telegramTokenEl.addEventListener("input", updateStartButtonState);
2695
2804
  xConsumerKeyEl.addEventListener("input", updateStartButtonState);
2696
2805
  xConsumerSecretEl.addEventListener("input", updateStartButtonState);
@@ -2831,12 +2940,17 @@ async function cmdInstall(args) {
2831
2940
  }
2832
2941
 
2833
2942
  const body = await parseJsonBody(req).catch(() => ({}));
2943
+ const rawLlmAuth = body.llmAuthMode != null ? body.llmAuthMode : defaults.llmAuthMode;
2834
2944
  const wizardOpts = {
2835
2945
  mode: "light",
2836
2946
  lane: defaults.lane,
2947
+ llmAuthMode: rawLlmAuth === "oauth" ? "oauth" : "api_key",
2837
2948
  llmProvider: body.llmProvider || defaults.llmProvider,
2838
2949
  llmModel: body.llmModel || defaults.llmModel,
2839
2950
  llmCredential: body.llmCredential || defaults.llmCredential,
2951
+ llmOAuthPaste: typeof body.llmOAuthPaste === "string" ? body.llmOAuthPaste.trim() : defaults.llmOAuthPaste,
2952
+ llmOAuthSkipLogin:
2953
+ typeof body.llmOAuthSkipLogin === "boolean" ? body.llmOAuthSkipLogin : defaults.llmOAuthSkipLogin === true,
2840
2954
  apiKey: body.apiKey || defaults.apiKey,
2841
2955
  orchestratorUrl: defaults.orchestratorUrl,
2842
2956
  gatewayBaseUrl: defaults.gatewayBaseUrl,
@@ -2870,9 +2984,12 @@ async function cmdInstall(args) {
2870
2984
  {
2871
2985
  mode: "light",
2872
2986
  lane: defaults.lane,
2987
+ llmAuthMode: wizardOpts.llmAuthMode,
2873
2988
  llmProvider: body.llmProvider || defaults.llmProvider,
2874
2989
  llmModel: body.llmModel || defaults.llmModel,
2875
2990
  llmCredential: body.llmCredential || defaults.llmCredential,
2991
+ llmOAuthPaste: wizardOpts.llmOAuthPaste,
2992
+ llmOAuthSkipLogin: wizardOpts.llmOAuthSkipLogin,
2876
2993
  apiKey: body.apiKey || defaults.apiKey,
2877
2994
  orchestratorUrl: defaults.orchestratorUrl,
2878
2995
  gatewayBaseUrl: defaults.gatewayBaseUrl,
@@ -3240,6 +3357,15 @@ Config subcommands:
3240
3357
  config set <k> <v> Update a configuration value
3241
3358
  config reset Remove plugin configuration
3242
3359
 
3360
+ Install wizard (traderclaw install --wizard):
3361
+ --port Local port for the wizard (default 17890)
3362
+ --llm-provider e.g. openai, openai-codex, anthropic
3363
+ --llm-model e.g. openai/gpt-5.4 or openai-codex/gpt-5.4
3364
+ --llm-api-key, --llm-token API key for LLM (api_key mode)
3365
+ --llm-auth api_key|oauth OpenAI Platform key vs ChatGPT/Codex OAuth (openai-codex)
3366
+ --llm-oauth-paste Paste redirect URL or code for Codex OAuth (non-skip)
3367
+ --llm-oauth-skip-login Skip login when you already ran openclaw models auth login
3368
+
3243
3369
  Examples:
3244
3370
  traderclaw signup
3245
3371
  traderclaw setup
@@ -3249,6 +3375,7 @@ Examples:
3249
3375
  traderclaw precheck --allow-install
3250
3376
  traderclaw install --wizard
3251
3377
  traderclaw install --wizard --lane quick-local
3378
+ traderclaw install --wizard --llm-auth oauth --llm-provider openai-codex --llm-oauth-skip-login
3252
3379
  traderclaw repair-openclaw
3253
3380
  traderclaw gateway ensure-persistent
3254
3381
  traderclaw setup --signup --user-id my_agent_001 --referral-code ABCD1234
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.73",
3
+ "version": "1.0.74",
4
4
  "description": "Global TraderClaw CLI (install --wizard, setup, precheck). Installs solana-traderclaw as a dependency for OpenClaw plugin files.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "node": ">=22"
18
18
  },
19
19
  "dependencies": {
20
- "solana-traderclaw": "^1.0.73"
20
+ "solana-traderclaw": "^1.0.74"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",