traderclaw-cli 1.0.115 → 1.0.117

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.
@@ -268,9 +268,17 @@ function commandExists(cmd) {
268
268
  }
269
269
  }
270
270
 
271
- function getCommandOutput(cmd) {
271
+ function getCommandOutput(cmd, { timeoutMs = 0 } = {}) {
272
272
  try {
273
- return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true, maxBuffer: 50 * 1024 * 1024, env: NO_COLOR_ENV }).trim();
273
+ const opts = {
274
+ encoding: "utf-8",
275
+ stdio: ["pipe", "pipe", "pipe"],
276
+ shell: true,
277
+ maxBuffer: 50 * 1024 * 1024,
278
+ env: NO_COLOR_ENV,
279
+ };
280
+ if (timeoutMs > 0) opts.timeout = timeoutMs;
281
+ return execSync(cmd, opts).trim();
274
282
  } catch {
275
283
  return null;
276
284
  }
@@ -511,12 +519,35 @@ export async function ensureOpenClawGlobalPackageDependencies() {
511
519
  * global from an older install causes `openclaw plugins install` to fail config validation with
512
520
  * "plugin requires OpenClaw >=… but this host is …".
513
521
  */
514
- async function installOpenClawPlatform() {
522
+ /** Bound `openclaw --version` so a post-OAuth CLI that blocks on gateway/device init cannot hang the wizard. */
523
+ const OPENCLAW_CLI_VERSION_TIMEOUT_MS = 25_000;
524
+
525
+ async function installOpenClawPlatform(onEvent) {
515
526
  const hadOpenclaw = commandExists("openclaw");
516
- const previousVersion = hadOpenclaw ? getCommandOutput("openclaw --version") : null;
517
- await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--registry", "https://registry.npmjs.org/", `openclaw@${OPENCLAW_VERSION}`]);
527
+ const previousVersion = hadOpenclaw ? getCommandOutput("openclaw --version", { timeoutMs: OPENCLAW_CLI_VERSION_TIMEOUT_MS }) : null;
528
+ if (hadOpenclaw && !previousVersion && typeof onEvent === "function") {
529
+ onEvent({
530
+ type: "stderr",
531
+ text: "openclaw --version did not return in time (or failed); continuing with npm install -g anyway.\n",
532
+ urls: [],
533
+ });
534
+ }
535
+ const npmCwd = getNpmGlobalInstallCwd();
536
+ await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--registry", "https://registry.npmjs.org/", `openclaw@${OPENCLAW_VERSION}`], {
537
+ onEvent,
538
+ cwd: npmCwd,
539
+ shell: false,
540
+ });
518
541
  const available = commandExists("openclaw");
519
- const version = available ? getCommandOutput("openclaw --version") : null;
542
+ let version = available ? getCommandOutput("openclaw --version", { timeoutMs: OPENCLAW_CLI_VERSION_TIMEOUT_MS }) : null;
543
+ if (available && !version && typeof onEvent === "function") {
544
+ onEvent({
545
+ type: "stderr",
546
+ text: "openclaw is on PATH but --version timed out; treating install as successful.\n",
547
+ urls: [],
548
+ });
549
+ version = "(version check timed out)";
550
+ }
520
551
  if (!available) {
521
552
  throw new Error(`npm install -g openclaw@${OPENCLAW_VERSION} finished but \`openclaw\` is not available on PATH`);
522
553
  }
@@ -1827,12 +1858,13 @@ function configureOpenClawLlmModelPrimaryOnly({ provider, model }, configPath =
1827
1858
  }
1828
1859
 
1829
1860
  /**
1830
- * Spawns `openclaw models auth login --provider openai-codex` with a pseudo-TTY when possible.
1861
+ * Spawns `openclaw models auth login --provider openai-codex --method oauth` with a pseudo-TTY when possible.
1862
+ * `--method oauth` skips the interactive Browser vs Device pairing menu (OpenClaw 2026.4.29+); device flow is `device-code`.
1831
1863
  * The CLI often exits immediately when stdin/stdout are plain pipes (no TTY). On Unix, `script(1)`
1832
1864
  * allocates a PTY so the same flow works as in an interactive terminal.
1833
1865
  */
1834
1866
  export function spawnOpenClawCodexAuthLoginChild() {
1835
- const argv = ["models", "auth", "login", "--provider", "openai-codex"];
1867
+ const argv = ["models", "auth", "login", "--provider", "openai-codex", "--method", "oauth"];
1836
1868
  if (process.platform === "win32") {
1837
1869
  return spawn("openclaw", argv, { stdio: ["pipe", "pipe", "pipe"], shell: false });
1838
1870
  }
@@ -1846,7 +1878,8 @@ export function spawnOpenClawCodexAuthLoginChild() {
1846
1878
  // --return propagates openclaw's exit code (util-linux 2.38+).
1847
1879
  // -f/--flush: force immediate forwarding of each PTY write to the pipe so the
1848
1880
  // wizard sees the URL as soon as OpenClaw prints it (default is block-buffered).
1849
- const cmdline = "stty cols 32767 rows 50 2>/dev/null; openclaw models auth login --provider openai-codex";
1881
+ const cmdline =
1882
+ "stty cols 32767 rows 50 2>/dev/null; openclaw models auth login --provider openai-codex --method oauth";
1850
1883
  return spawn("script", ["--return", "-f", "-q", "-c", cmdline, "/dev/null"], {
1851
1884
  stdio: ["pipe", "pipe", "pipe"],
1852
1885
  // COLUMNS/LINES: belt-and-suspenders env fallback for programs that read env
@@ -1859,7 +1892,7 @@ export function spawnOpenClawCodexAuthLoginChild() {
1859
1892
  }
1860
1893
 
1861
1894
  /**
1862
- * Runs `openclaw models auth login --provider openai-codex` and feeds the pasted redirect URL or code on stdin
1895
+ * Runs `openclaw models auth login --provider openai-codex --method oauth` and feeds the pasted redirect URL or code on stdin
1863
1896
  * when the CLI prompts (with a timed fallback for non-interactive / SSH).
1864
1897
  */
1865
1898
  function runOpenClawCodexOAuthLogin(paste, emitLog) {
@@ -2037,6 +2070,8 @@ export class InstallerStepEngine {
2037
2070
  skipInstallOpenClaw: options.skipInstallOpenClaw === true,
2038
2071
  skipInstallPlugin: options.skipInstallPlugin === true,
2039
2072
  skipTailscale: options.skipTailscale === true,
2073
+ /** When true (e.g. EC2 bootstrap before Tailscale auth), gateway starts without `tailscale funnel`. */
2074
+ skipFunnel: options.skipFunnel === true,
2040
2075
  skipGatewayBootstrap: options.skipGatewayBootstrap === true,
2041
2076
  skipGatewayConfig: options.skipGatewayConfig === true,
2042
2077
  // Wizard / CLI — must be preserved for seedXConfig
@@ -2333,7 +2368,7 @@ export class InstallerStepEngine {
2333
2368
  } catch (err) {
2334
2369
  const tail = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.trim();
2335
2370
  throw new Error(
2336
- `${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.`,
2371
+ `${tail}\n\nIf OAuth cannot complete from the wizard, run in a shell: openclaw models auth login --provider openai-codex --method oauth — then re-run the wizard with "already logged in" checked.`,
2337
2372
  );
2338
2373
  }
2339
2374
  }
@@ -2347,7 +2382,7 @@ export class InstallerStepEngine {
2347
2382
  throw new Error(
2348
2383
  "No OAuth credentials found at " + authFile + ". " +
2349
2384
  "The wizard OAuth flow did not save tokens (the callback may not have reached the OpenClaw CLI). " +
2350
- "Run 'openclaw models auth login --provider openai-codex' in a terminal, " +
2385
+ "Run 'openclaw models auth login --provider openai-codex --method oauth' in a terminal, " +
2351
2386
  "then re-run the wizard with the 'already logged in' option.",
2352
2387
  );
2353
2388
  }
@@ -2487,7 +2522,11 @@ export class InstallerStepEngine {
2487
2522
  }
2488
2523
 
2489
2524
  if (!this.options.skipInstallOpenClaw) {
2490
- await this.runStep("install_openclaw", "Installing or upgrading OpenClaw platform", async () => installOpenClawPlatform());
2525
+ await this.runStep("install_openclaw", "Installing or upgrading OpenClaw platform", async () =>
2526
+ installOpenClawPlatform((evt) =>
2527
+ this.emitLog("install_openclaw", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
2528
+ ),
2529
+ );
2491
2530
  }
2492
2531
 
2493
2532
  // Non-fatal: warn when the CLI has devices in pending-approval or repair state.
@@ -2598,6 +2637,14 @@ export class InstallerStepEngine {
2598
2637
  );
2599
2638
  await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "install"]);
2600
2639
  await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "restart"]);
2640
+ if (this.options.skipFunnel) {
2641
+ this.emitLog(
2642
+ "gateway_bootstrap",
2643
+ "info",
2644
+ "Skipping Tailscale funnel (skipFunnel). After `tailscale up`, run: traderclaw install --headless --funnel-only",
2645
+ );
2646
+ return { funnelSkipped: true };
2647
+ }
2601
2648
  return this.runFunnel();
2602
2649
  } catch (err) {
2603
2650
  const text = `${err?.message || ""}\n${err?.stderr || ""}\n${err?.stdout || ""}`.toLowerCase();
@@ -2613,6 +2660,9 @@ export class InstallerStepEngine {
2613
2660
  if (gatewayStartFailed || gatewayModeUnset) {
2614
2661
  const recovered = await this.tryAutoRecoverGatewayMode("gateway_bootstrap");
2615
2662
  if (recovered.success) {
2663
+ if (this.options.skipFunnel) {
2664
+ return { funnelSkipped: true, recovered: true };
2665
+ }
2616
2666
  return this.runFunnel();
2617
2667
  }
2618
2668
  if (gatewayModeUnset) {
@@ -2804,6 +2854,28 @@ export class InstallerStepEngine {
2804
2854
  }
2805
2855
  }
2806
2856
 
2857
+ /**
2858
+ * After Tailscale login (`tailscale up`), expose loopback gateway :18789 via funnel.
2859
+ */
2860
+ export async function tailscaleFunnelOpenclaw18789(options = {}) {
2861
+ const onEvt = typeof options.onEvent === "function" ? options.onEvent : () => {};
2862
+ try {
2863
+ await runCommandWithEvents(
2864
+ "tailscale",
2865
+ ["funnel", "--bg", "18789"],
2866
+ { onEvent: (evt) => onEvt(evt) },
2867
+ );
2868
+ } catch (err) {
2869
+ const details = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.toLowerCase();
2870
+ if (details.includes("access denied") || details.includes("operator")) {
2871
+ throw new Error(tailscalePermissionRemediation());
2872
+ }
2873
+ throw err;
2874
+ }
2875
+ const statusOut = getCommandOutput("tailscale funnel status") || "";
2876
+ return { funnelUrl: firstUrl(statusOut) || null, rawStatus: statusOut };
2877
+ }
2878
+
2807
2879
  export function assertWizardXCredentials(modeConfig, options = {}) {
2808
2880
  const t = (s) => (typeof s === "string" ? s.trim() : "");
2809
2881
  const o = options || {};
@@ -2135,6 +2135,10 @@ function parseInstallWizardArgs(args) {
2135
2135
  if ((key === "--gateway-token" || key === "-t") && next) out.gatewayToken = args[++i];
2136
2136
  if (key === "--with-telegram") out.enableTelegram = true;
2137
2137
  if (key === "--telegram-token" && next) out.telegramToken = args[++i];
2138
+ if (key === "--x-consumer-key" && next) out.xConsumerKey = args[++i];
2139
+ if (key === "--x-consumer-secret" && next) out.xConsumerSecret = args[++i];
2140
+ if (key === "--x-access-token-main" && next) out.xAccessTokenMain = args[++i];
2141
+ if (key === "--x-access-token-main-secret" && next) out.xAccessTokenMainSecret = args[++i];
2138
2142
  }
2139
2143
  return out;
2140
2144
  }
@@ -3043,7 +3047,11 @@ function wizardHtml(defaults) {
3043
3047
  setOauthStep(oauthStepOpen, "error");
3044
3048
  setOauthStep(oauthStepComplete, "error");
3045
3049
  setOauthStep(oauthStepVerify, "error");
3046
- setOauthStatus((data && (data.message || data.error)) || "Could not start OAuth sign-in.", true);
3050
+ let msg = (data && (data.message || data.error)) || "Could not start OAuth sign-in.";
3051
+ if (data && typeof data.detail === "string" && data.detail.trim()) {
3052
+ msg += " " + data.detail.trim().slice(0, 1500);
3053
+ }
3054
+ setOauthStatus(msg, true);
3047
3055
  if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
3048
3056
  updateStartButtonState();
3049
3057
  return;
@@ -3594,10 +3602,163 @@ function wizardHtml(defaults) {
3594
3602
  </html>`;
3595
3603
  }
3596
3604
 
3605
+ function traderclawHeadlessEnv(name) {
3606
+ return String(process.env[name] || "").trim();
3607
+ }
3608
+
3609
+ function parseInstallHeadlessMeta(args) {
3610
+ const funnelOnly = args.includes("--funnel-only");
3611
+ const skipTailscale =
3612
+ args.includes("--skip-tailscale") || traderclawHeadlessEnv("TRADERCLAW_INSTALL_SKIP_TAILSCALE") === "1";
3613
+ let skipFunnel = args.includes("--skip-funnel") || traderclawHeadlessEnv("TRADERCLAW_INSTALL_SKIP_FUNNEL") === "1";
3614
+ if (skipTailscale) skipFunnel = true;
3615
+ return { funnelOnly, skipTailscale, skipFunnel };
3616
+ }
3617
+
3618
+ function filterHeadlessArgv(args) {
3619
+ const strip = new Set(["--headless", "--funnel-only", "--skip-tailscale", "--skip-funnel"]);
3620
+ return args.filter((a) => !strip.has(a));
3621
+ }
3622
+
3623
+ async function cmdInstallHeadless(args) {
3624
+ const meta = parseInstallHeadlessMeta(args);
3625
+ const { tailscaleFunnelOpenclaw18789, createInstallerStepEngine, assertWizardXCredentials } = await import(
3626
+ "./installer-step-engine.mjs",
3627
+ );
3628
+
3629
+ if (meta.funnelOnly) {
3630
+ print("\nTraderClaw install — Tailscale funnel (public HTTPS → gateway loopback :18789)\n");
3631
+ try {
3632
+ const r = await tailscaleFunnelOpenclaw18789({
3633
+ onEvent: (evt) => {
3634
+ const t = typeof evt?.text === "string" ? evt.text : "";
3635
+ if (t.trim()) printInfo(t.trimEnd());
3636
+ },
3637
+ });
3638
+ if (r.funnelUrl) printSuccess(`Public gateway URL (Tailscale funnel): ${r.funnelUrl}`);
3639
+ else printWarn("Could not parse funnel URL — on the VPS run: tailscale funnel status");
3640
+ print("");
3641
+ } catch (err) {
3642
+ printError(err?.message || String(err));
3643
+ process.exit(1);
3644
+ }
3645
+ printInfo(
3646
+ "Use this URL as gatewayBaseUrl when running: traderclaw setup --gateway-base-url <url> --gateway-token <token>",
3647
+ );
3648
+ return;
3649
+ }
3650
+
3651
+ let w = parseInstallWizardArgs(filterHeadlessArgv(args));
3652
+ w.llmProvider = w.llmProvider || traderclawHeadlessEnv("TRADERCLAW_HEADLESS_LLM_PROVIDER");
3653
+ w.llmModel = w.llmModel || traderclawHeadlessEnv("TRADERCLAW_HEADLESS_LLM_MODEL");
3654
+ w.llmCredential =
3655
+ w.llmCredential
3656
+ || traderclawHeadlessEnv("TRADERCLAW_HEADLESS_LLM_API_KEY")
3657
+ || traderclawHeadlessEnv("TRADERCLAW_LLM_API_KEY");
3658
+ w.telegramToken =
3659
+ w.telegramToken
3660
+ || traderclawHeadlessEnv("TRADERCLAW_HEADLESS_TELEGRAM_TOKEN")
3661
+ || traderclawHeadlessEnv("OPENCLAW_TELEGRAM_BOT_TOKEN");
3662
+ w.apiKey =
3663
+ w.apiKey
3664
+ || traderclawHeadlessEnv("TRADERCLAW_HEADLESS_ORCHESTRATOR_API_KEY")
3665
+ || traderclawHeadlessEnv("TRADERCLAW_API_KEY");
3666
+ if (!(w.orchestratorUrl || "").trim()) {
3667
+ w.orchestratorUrl = traderclawHeadlessEnv("TRADERCLAW_ORCHESTRATOR_URL") || "https://api.traderclaw.ai";
3668
+ }
3669
+ w.xConsumerKey = w.xConsumerKey || traderclawHeadlessEnv("X_CONSUMER_KEY");
3670
+ w.xConsumerSecret = w.xConsumerSecret || traderclawHeadlessEnv("X_CONSUMER_SECRET");
3671
+ w.xAccessTokenMain = w.xAccessTokenMain || traderclawHeadlessEnv("X_ACCESS_TOKEN_MAIN");
3672
+ w.xAccessTokenMainSecret =
3673
+ w.xAccessTokenMainSecret || traderclawHeadlessEnv("X_ACCESS_TOKEN_MAIN_SECRET");
3674
+
3675
+ if (w.llmAuthMode === "oauth") {
3676
+ printError("traderclaw install --headless does not support LLM OAuth — use traderclaw install --wizard");
3677
+ process.exit(1);
3678
+ }
3679
+ if (!w.llmProvider || !w.llmModel || !w.llmCredential) {
3680
+ printError(
3681
+ "Headless install requires --llm-provider, --llm-model, --llm-api-key (env: TRADERCLAW_HEADLESS_LLM_* ).",
3682
+ );
3683
+ process.exit(1);
3684
+ }
3685
+ if (!w.telegramToken) {
3686
+ printError("Headless install requires --telegram-token (env: TRADERCLAW_HEADLESS_TELEGRAM_TOKEN).");
3687
+ process.exit(1);
3688
+ }
3689
+
3690
+ const modeConfig = {
3691
+ pluginPackage: "solana-traderclaw",
3692
+ pluginId: "solana-trader",
3693
+ cliName: "traderclaw",
3694
+ gatewayConfig: "gateway-v1.json5",
3695
+ agents: ["cto", "onchain-analyst", "alpha-signal-analyst", "risk-officer", "strategy-researcher"],
3696
+ };
3697
+
3698
+ const xErr = assertWizardXCredentials(modeConfig, w);
3699
+ if (xErr) {
3700
+ printError(xErr);
3701
+ process.exit(1);
3702
+ }
3703
+
3704
+ print("\nTraderClaw install — headless (non-interactive)\n");
3705
+ const engine = createInstallerStepEngine(
3706
+ modeConfig,
3707
+ {
3708
+ lane: w.lane === "quick-local" ? "quick-local" : "event-driven",
3709
+ llmAuthMode: "api_key",
3710
+ llmProvider: w.llmProvider,
3711
+ llmModel: w.llmModel,
3712
+ llmCredential: w.llmCredential,
3713
+ apiKey: w.apiKey || "",
3714
+ orchestratorUrl: String(w.orchestratorUrl || "https://api.traderclaw.ai").replace(/\/+$/, ""),
3715
+ gatewayBaseUrl: w.gatewayBaseUrl || "",
3716
+ gatewayToken: w.gatewayToken || "",
3717
+ enableTelegram: true,
3718
+ telegramToken: w.telegramToken,
3719
+ skipTailscale: meta.skipTailscale,
3720
+ skipFunnel: meta.skipFunnel,
3721
+ xConsumerKey: w.xConsumerKey,
3722
+ xConsumerSecret: w.xConsumerSecret,
3723
+ xAccessTokenMain: w.xAccessTokenMain,
3724
+ xAccessTokenMainSecret: w.xAccessTokenMainSecret,
3725
+ },
3726
+ {
3727
+ onLog: ({ text }) => {
3728
+ if (text) print(String(text));
3729
+ },
3730
+ onStepEvent: ({ stepId, status, detail }) => {
3731
+ print(`[install] ${stepId} ${status}${detail ? ` — ${detail}` : ""}`);
3732
+ },
3733
+ },
3734
+ );
3735
+
3736
+ const state = await engine.runAll();
3737
+ if (state.status !== "completed") {
3738
+ printError("Headless install failed.");
3739
+ process.exit(1);
3740
+ }
3741
+
3742
+ printSuccess("\nHeadless install completed.");
3743
+ if (state.setupHandoff?.command) {
3744
+ printInfo(`CLI hand-off: ${state.setupHandoff.command}`);
3745
+ }
3746
+ if (meta.skipFunnel) {
3747
+ printWarn(
3748
+ "Funnel skipped — run `tailscale up` then `traderclaw install --headless --funnel-only`, then traderclaw setup with --gateway-base-url.",
3749
+ );
3750
+ }
3751
+ }
3752
+
3597
3753
  async function cmdInstall(args) {
3754
+ const headless = args.includes("--headless");
3755
+ if (headless) {
3756
+ await cmdInstallHeadless(args);
3757
+ return;
3758
+ }
3598
3759
  const wizard = args.includes("--wizard");
3599
3760
  if (!wizard) {
3600
- printError("Only wizard mode is currently supported. Use: traderclaw install --wizard");
3761
+ printError('Use: traderclaw install --wizard or traderclaw install --headless (--help)');
3601
3762
  process.exit(1);
3602
3763
  }
3603
3764
 
@@ -3629,6 +3790,27 @@ async function cmdInstall(args) {
3629
3790
  // The \S+ (non-whitespace) tail captures the full query string; see trySendUrl()
3630
3791
  // for why we run this against a stripAnsi+CR-stripped copy of the output.
3631
3792
  const OPENAI_OAUTH_AUTHORIZE_RE = /https:\/\/auth\.openai\.com\/(?:oauth\/authorize|authorize)[^\s"]*/;
3793
+ const OPENAI_OAUTH_AUTHORIZE_RE_LOOSE =
3794
+ /https:\/\/(?:[a-z0-9-]+\.)*openai\.com\/[^\s"]*?(?:oauth\/authorize|\/authorize[^\s"]*)/i;
3795
+
3796
+ /** Best-effort: primary regex, then subdomain/openai authorize URLs, then token-like query heuristics. */
3797
+ function tryExtractOpenAiCodexAuthorizeUrl(cleaned) {
3798
+ let m = cleaned.match(OPENAI_OAUTH_AUTHORIZE_RE);
3799
+ if (m?.[0]) return m[0];
3800
+ m = cleaned.match(OPENAI_OAUTH_AUTHORIZE_RE_LOOSE);
3801
+ if (m?.[0]) return m[0];
3802
+ const candidates = cleaned.match(/https:\/\/[^\s"]+/g) || [];
3803
+ for (const u of candidates) {
3804
+ if (
3805
+ /openai\.com/i.test(u)
3806
+ && /authorize/i.test(u)
3807
+ && /oauth|response_type|client_id|redirect_uri|scope=/i.test(u)
3808
+ ) {
3809
+ return u;
3810
+ }
3811
+ }
3812
+ return null;
3813
+ }
3632
3814
  const oauthSessionTtlMs = 15 * 60 * 1000;
3633
3815
 
3634
3816
  // Long-lived callback proxy on port 1455. Bound at wizard startup so
@@ -3856,6 +4038,10 @@ async function cmdInstall(args) {
3856
4038
  const urlTimeout = setTimeout(() => {
3857
4039
  if (responded) return;
3858
4040
  responded = true;
4041
+ const detail = stripAnsi(combined).slice(-4000);
4042
+ if (process.env.TRADERCLAW_WIZARD_OAUTH_DEBUG === "1") {
4043
+ printWarn(`[TRADERCLAW_WIZARD_OAUTH_DEBUG] OpenClaw output tail before oauth_url_timeout:\n${detail}`);
4044
+ }
3859
4045
  try {
3860
4046
  child.kill("SIGTERM");
3861
4047
  } catch {
@@ -3868,6 +4054,7 @@ async function cmdInstall(args) {
3868
4054
  error: "oauth_url_timeout",
3869
4055
  message:
3870
4056
  "OpenClaw did not provide a ChatGPT sign-in URL in time. Try again.",
4057
+ detail,
3871
4058
  });
3872
4059
  }, 120_000);
3873
4060
 
@@ -3881,17 +4068,17 @@ async function cmdInstall(args) {
3881
4068
  const cleanedForUrl = stripAnsi(combined)
3882
4069
  .replace(/\r/g, "")
3883
4070
  .replace(/([A-Za-z0-9%&=+?#:/@._~!$'()*,;-])\n([A-Za-z0-9%&=+?#:/@._~!$'()*,;-])/g, "$1$2");
3884
- let m = cleanedForUrl.match(OPENAI_OAUTH_AUTHORIZE_RE);
4071
+ let authUrl = tryExtractOpenAiCodexAuthorizeUrl(cleanedForUrl);
3885
4072
  // Layer 2: try the raw combined buffer in case stripAnsi dropped a character that
3886
4073
  // broke the URL detection (raw ANSI seqs don't include space so regex still works).
3887
- if (!m || !m[0]) {
4074
+ if (!authUrl) {
3888
4075
  const rawCleaned = combined.replace(/\r/g, "")
3889
4076
  .replace(/([A-Za-z0-9%&=+?#:/@._~!$'()*,;-])\n([A-Za-z0-9%&=+?#:/@._~!$'()*,;-])/g, "$1$2");
3890
- m = rawCleaned.match(OPENAI_OAUTH_AUTHORIZE_RE);
4077
+ authUrl = tryExtractOpenAiCodexAuthorizeUrl(rawCleaned);
3891
4078
  }
3892
- if (!m || !m[0]) return;
4079
+ if (!authUrl) return;
3893
4080
  // Strip any trailing ANSI remnants (e.g. \x1b[0m appended by colour reset)
3894
- const authUrl = stripAnsi(m[0]).replace(/\r/g, "").trim();
4081
+ authUrl = stripAnsi(authUrl).replace(/\r/g, "").trim();
3895
4082
  if (!authUrl.startsWith("https://")) return;
3896
4083
  clearTimeout(urlTimeout);
3897
4084
  responded = true;
@@ -3954,7 +4141,7 @@ async function cmdInstall(args) {
3954
4141
  pending.status = "failed";
3955
4142
  pending.message =
3956
4143
  "OpenClaw exited OK but no auth tokens were saved. " +
3957
- "Run 'openclaw models auth login --provider openai-codex' in a terminal, " +
4144
+ "Run 'openclaw models auth login --provider openai-codex --method oauth' in a terminal, " +
3958
4145
  "then re-run the wizard with the already-logged-in option.";
3959
4146
  } else {
3960
4147
  pending.status = "failed";
@@ -4166,6 +4353,10 @@ async function cmdInstall(args) {
4166
4353
  return;
4167
4354
  }
4168
4355
 
4356
+ for (const id of [...oauthSessions.keys()]) {
4357
+ killOauthSession(id);
4358
+ }
4359
+
4169
4360
  running = true;
4170
4361
  runtime.status = "running";
4171
4362
  runtime.logs = [];
@@ -4598,6 +4789,16 @@ Install wizard (traderclaw install --wizard):
4598
4789
  --llm-oauth-paste Paste redirect URL or code for Codex OAuth (non-skip)
4599
4790
  --llm-oauth-skip-login Skip login when you already ran openclaw models auth login
4600
4791
 
4792
+ Headless VPS / CI (traderclaw install --headless):
4793
+ Requires API-key LLM (no OAuth): --llm-provider, --llm-model, --llm-api-key, --telegram-token
4794
+ Same flags as wizard for --api-key/-k, --url/-u, --gateway-base-url/-g, X/Twitter (optional, all-or-none):
4795
+ --x-consumer-key ... --x-consumer-secret ... --x-access-token-main ... --x-access-token-main-secret ...
4796
+ --skip-tailscale Skip Tailscale install/up (implies --skip-funnel unless overridden)
4797
+ --skip-funnel Do not run tailscale funnel after gateway restart
4798
+ --funnel-only After manual tailscale up: publish gateway :18789 via funnel only
4799
+ Env: TRADERCLAW_HEADLESS_LLM_PROVIDER/MODEL/API_KEY, TRADERCLAW_HEADLESS_TELEGRAM_TOKEN,
4800
+ TRADERCLAW_HEADLESS_ORCHESTRATOR_API_KEY, TRADERCLAW_INSTALL_SKIP_TAILSCALE/FUNNEL, X_* etc.
4801
+
4601
4802
  Examples:
4602
4803
  traderclaw signup
4603
4804
  traderclaw setup
@@ -4608,6 +4809,9 @@ Examples:
4608
4809
  traderclaw install --wizard
4609
4810
  traderclaw install --wizard --lane quick-local
4610
4811
  traderclaw install --wizard --llm-auth oauth --llm-provider openai-codex --llm-oauth-skip-login
4812
+ traderclaw install --headless --skip-tailscale --llm-provider anthropic --llm-model anthropic/claude-sonnet-4-6 \\
4813
+ --llm-api-key \"$ANTHROPIC_KEY\" --telegram-token \"$TG\" --skip-funnel
4814
+ traderclaw install --headless --funnel-only
4611
4815
  traderclaw repair-openclaw
4612
4816
  traderclaw gateway ensure-persistent
4613
4817
  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.115",
3
+ "version": "1.0.117",
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.115"
20
+ "solana-traderclaw": "^1.0.117"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",