traderclaw-cli 1.0.116 → 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
  }
@@ -2039,6 +2070,8 @@ export class InstallerStepEngine {
2039
2070
  skipInstallOpenClaw: options.skipInstallOpenClaw === true,
2040
2071
  skipInstallPlugin: options.skipInstallPlugin === true,
2041
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,
2042
2075
  skipGatewayBootstrap: options.skipGatewayBootstrap === true,
2043
2076
  skipGatewayConfig: options.skipGatewayConfig === true,
2044
2077
  // Wizard / CLI — must be preserved for seedXConfig
@@ -2489,7 +2522,11 @@ export class InstallerStepEngine {
2489
2522
  }
2490
2523
 
2491
2524
  if (!this.options.skipInstallOpenClaw) {
2492
- 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
+ );
2493
2530
  }
2494
2531
 
2495
2532
  // Non-fatal: warn when the CLI has devices in pending-approval or repair state.
@@ -2600,6 +2637,14 @@ export class InstallerStepEngine {
2600
2637
  );
2601
2638
  await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "install"]);
2602
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
+ }
2603
2648
  return this.runFunnel();
2604
2649
  } catch (err) {
2605
2650
  const text = `${err?.message || ""}\n${err?.stderr || ""}\n${err?.stdout || ""}`.toLowerCase();
@@ -2615,6 +2660,9 @@ export class InstallerStepEngine {
2615
2660
  if (gatewayStartFailed || gatewayModeUnset) {
2616
2661
  const recovered = await this.tryAutoRecoverGatewayMode("gateway_bootstrap");
2617
2662
  if (recovered.success) {
2663
+ if (this.options.skipFunnel) {
2664
+ return { funnelSkipped: true, recovered: true };
2665
+ }
2618
2666
  return this.runFunnel();
2619
2667
  }
2620
2668
  if (gatewayModeUnset) {
@@ -2806,6 +2854,28 @@ export class InstallerStepEngine {
2806
2854
  }
2807
2855
  }
2808
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
+
2809
2879
  export function assertWizardXCredentials(modeConfig, options = {}) {
2810
2880
  const t = (s) => (typeof s === "string" ? s.trim() : "");
2811
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
  }
@@ -3598,10 +3602,163 @@ function wizardHtml(defaults) {
3598
3602
  </html>`;
3599
3603
  }
3600
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
+
3601
3753
  async function cmdInstall(args) {
3754
+ const headless = args.includes("--headless");
3755
+ if (headless) {
3756
+ await cmdInstallHeadless(args);
3757
+ return;
3758
+ }
3602
3759
  const wizard = args.includes("--wizard");
3603
3760
  if (!wizard) {
3604
- printError("Only wizard mode is currently supported. Use: traderclaw install --wizard");
3761
+ printError('Use: traderclaw install --wizard or traderclaw install --headless (--help)');
3605
3762
  process.exit(1);
3606
3763
  }
3607
3764
 
@@ -4196,6 +4353,10 @@ async function cmdInstall(args) {
4196
4353
  return;
4197
4354
  }
4198
4355
 
4356
+ for (const id of [...oauthSessions.keys()]) {
4357
+ killOauthSession(id);
4358
+ }
4359
+
4199
4360
  running = true;
4200
4361
  runtime.status = "running";
4201
4362
  runtime.logs = [];
@@ -4628,6 +4789,16 @@ Install wizard (traderclaw install --wizard):
4628
4789
  --llm-oauth-paste Paste redirect URL or code for Codex OAuth (non-skip)
4629
4790
  --llm-oauth-skip-login Skip login when you already ran openclaw models auth login
4630
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
+
4631
4802
  Examples:
4632
4803
  traderclaw signup
4633
4804
  traderclaw setup
@@ -4638,6 +4809,9 @@ Examples:
4638
4809
  traderclaw install --wizard
4639
4810
  traderclaw install --wizard --lane quick-local
4640
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
4641
4815
  traderclaw repair-openclaw
4642
4816
  traderclaw gateway ensure-persistent
4643
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.116",
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.116"
20
+ "solana-traderclaw": "^1.0.117"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",