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.
- package/bin/installer-step-engine.mjs +85 -13
- package/bin/openclaw-trader.mjs +212 -8
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 () =>
|
|
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 || {};
|
package/bin/openclaw-trader.mjs
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
4077
|
+
authUrl = tryExtractOpenAiCodexAuthorizeUrl(rawCleaned);
|
|
3891
4078
|
}
|
|
3892
|
-
if (!
|
|
4079
|
+
if (!authUrl) return;
|
|
3893
4080
|
// Strip any trailing ANSI remnants (e.g. \x1b[0m appended by colour reset)
|
|
3894
|
-
|
|
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.
|
|
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.
|
|
20
|
+
"solana-traderclaw": "^1.0.117"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|