traderclaw-cli 1.0.71 → 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.
@@ -442,6 +442,32 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
442
442
  });
443
443
  }
444
444
 
445
+ function getGlobalOpenClawPackageDir() {
446
+ const root = getCommandOutput("npm root -g");
447
+ if (!root) return null;
448
+ const dir = join(root.trim(), "openclaw");
449
+ return existsSync(join(dir, "package.json")) ? dir : null;
450
+ }
451
+
452
+ /**
453
+ * Re-run `npm install` inside the global OpenClaw package tree. Some hosts end up with an
454
+ * incomplete `node_modules` after `npm install -g` (hoisting, optional deps, or interrupted
455
+ * installs). OpenClaw then fails at runtime with `Cannot find module 'grammy'` while loading
456
+ * config. Installing from the package directory restores declared dependencies.
457
+ */
458
+ /** Runs `npm install` in the global `openclaw` package directory (fixes missing `grammy` etc.). */
459
+ export async function ensureOpenClawGlobalPackageDependencies() {
460
+ const dir = getGlobalOpenClawPackageDir();
461
+ if (!dir) {
462
+ return { skipped: true, reason: "global_openclaw_dir_not_found" };
463
+ }
464
+ await runCommandWithEvents("npm", ["install", "--omit=dev", "--registry", "https://registry.npmjs.org/"], {
465
+ cwd: dir,
466
+ shell: false,
467
+ });
468
+ return { repaired: true, dir };
469
+ }
470
+
445
471
  /**
446
472
  * Install or upgrade the global OpenClaw CLI. We always run npm even when `openclaw` is already
447
473
  * on PATH: bundled plugin manifests track a minimum OpenClaw version (e.g. >=2026.4.8). A stale
@@ -1536,6 +1562,115 @@ function configureOpenClawLlmProvider({ provider, model, credential }, configPat
1536
1562
  return { configPath, provider, model };
1537
1563
  }
1538
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
+
1539
1674
  function verifyInstallation(modeConfig, apiKey) {
1540
1675
  const gatewayFile = join(CONFIG_DIR, "gateway", modeConfig.gatewayConfig);
1541
1676
  let llmConfigured = false;
@@ -1619,9 +1754,12 @@ export class InstallerStepEngine {
1619
1754
  this.modeConfig = modeConfig;
1620
1755
  this.options = {
1621
1756
  lane: normalizeLane(options.lane),
1757
+ llmAuthMode: options.llmAuthMode === "oauth" ? "oauth" : "api_key",
1622
1758
  llmProvider: options.llmProvider || "",
1623
1759
  llmModel: options.llmModel || "",
1624
1760
  llmCredential: options.llmCredential || "",
1761
+ llmOAuthPaste: typeof options.llmOAuthPaste === "string" ? options.llmOAuthPaste.trim() : "",
1762
+ llmOAuthSkipLogin: options.llmOAuthSkipLogin === true,
1625
1763
  apiKey: options.apiKey || "",
1626
1764
  orchestratorUrl: options.orchestratorUrl || "https://api.traderclaw.ai",
1627
1765
  gatewayBaseUrl: options.gatewayBaseUrl || "",
@@ -1898,15 +2036,78 @@ export class InstallerStepEngine {
1898
2036
  const provider = String(this.options.llmProvider || "").trim();
1899
2037
  const requestedModel = String(this.options.llmModel || "").trim();
1900
2038
  const credential = String(this.options.llmCredential || "").trim();
1901
- if (!provider || !credential) {
2039
+ const authMode = this.options.llmAuthMode === "oauth" ? "oauth" : "api_key";
2040
+
2041
+ if (!provider) {
1902
2042
  throw new Error(
1903
- "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.",
1904
2044
  );
1905
2045
  }
1906
2046
  if (!commandExists("openclaw")) {
1907
2047
  throw new Error("OpenClaw is not available yet. Install step must complete before LLM configuration.");
1908
2048
  }
1909
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
+
1910
2111
  const selection = resolveLlmModelSelection(provider, requestedModel);
1911
2112
  for (const msg of selection.warnings) {
1912
2113
  this.emitLog("configure_llm", "warn", msg);
@@ -1931,7 +2132,7 @@ export class InstallerStepEngine {
1931
2132
  );
1932
2133
  }
1933
2134
 
1934
- return { configured: true, provider, model, configPath: saved.configPath };
2135
+ return { configured: true, provider, model, configPath: saved.configPath, authMode: "api_key" };
1935
2136
  }
1936
2137
 
1937
2138
  buildSetupHandoff() {
@@ -1987,6 +2188,9 @@ export class InstallerStepEngine {
1987
2188
  this.modeConfig,
1988
2189
  (evt) => this.emitLog("install_plugin_package", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
1989
2190
  ));
2191
+ await this.runStep("openclaw_global_deps", "Ensuring OpenClaw global package dependencies", async () =>
2192
+ ensureOpenClawGlobalPackageDependencies(),
2193
+ );
1990
2194
  await this.runStep(
1991
2195
  "activate_openclaw_plugin",
1992
2196
  "Installing and enabling TraderClaw inside OpenClaw",
@@ -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,
@@ -3204,6 +3321,7 @@ Commands:
3204
3321
  signup Create a new account (alias for: setup --signup; run locally, not via the agent)
3205
3322
  precheck Run environment checks (dry-run or allow-install)
3206
3323
  install Launch installer flows (--wizard for localhost GUI)
3324
+ repair-openclaw Re-run npm install in the global openclaw package (fixes missing grammy after upgrade)
3207
3325
  gateway Gateway helpers (see subcommands below)
3208
3326
  login Re-authenticate (uses refresh token when valid; full challenge only if needed)
3209
3327
  logout Revoke current session and clear tokens
@@ -3239,6 +3357,15 @@ Config subcommands:
3239
3357
  config set <k> <v> Update a configuration value
3240
3358
  config reset Remove plugin configuration
3241
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
+
3242
3369
  Examples:
3243
3370
  traderclaw signup
3244
3371
  traderclaw setup
@@ -3248,6 +3375,8 @@ Examples:
3248
3375
  traderclaw precheck --allow-install
3249
3376
  traderclaw install --wizard
3250
3377
  traderclaw install --wizard --lane quick-local
3378
+ traderclaw install --wizard --llm-auth oauth --llm-provider openai-codex --llm-oauth-skip-login
3379
+ traderclaw repair-openclaw
3251
3380
  traderclaw gateway ensure-persistent
3252
3381
  traderclaw setup --signup --user-id my_agent_001 --referral-code ABCD1234
3253
3382
  traderclaw setup --api-key oc_xxx --url https://api.traderclaw.ai
@@ -3264,6 +3393,18 @@ Examples:
3264
3393
  `);
3265
3394
  }
3266
3395
 
3396
+ async function cmdRepairOpenclaw() {
3397
+ const { ensureOpenClawGlobalPackageDependencies } = await import("./installer-step-engine.mjs");
3398
+ printInfo("Repairing global OpenClaw npm dependencies (fixes missing grammy / MODULE_NOT_FOUND)...");
3399
+ const r = await ensureOpenClawGlobalPackageDependencies();
3400
+ if (r.skipped) {
3401
+ printError(`Could not find global OpenClaw package (${r.reason}). Install or upgrade: npm install -g openclaw@latest`);
3402
+ process.exit(1);
3403
+ }
3404
+ printSuccess(`Dependencies refreshed under ${r.dir}`);
3405
+ printInfo("Next: openclaw gateway restart");
3406
+ }
3407
+
3267
3408
  async function main() {
3268
3409
  const args = process.argv.slice(2);
3269
3410
  const command = args[0];
@@ -3295,6 +3436,9 @@ async function main() {
3295
3436
  case "install":
3296
3437
  await cmdInstall(args.slice(1));
3297
3438
  break;
3439
+ case "repair-openclaw":
3440
+ await cmdRepairOpenclaw();
3441
+ break;
3298
3442
  case "gateway":
3299
3443
  await cmdGateway(args.slice(1));
3300
3444
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.71",
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.71"
20
+ "solana-traderclaw": "^1.0.74"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",