traderclaw-cli 1.0.116 → 1.0.120
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 +154 -18
- package/bin/openclaw-trader.mjs +175 -1
- package/package.json +2 -2
|
@@ -268,14 +268,63 @@ 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
|
}
|
|
277
285
|
}
|
|
278
286
|
|
|
287
|
+
/**
|
|
288
|
+
* First existing `skills/solana-trader` directory: local package → OpenClaw extension → global npm.
|
|
289
|
+
* @param {{ pluginId: string, pluginPackage: string }} modeConfig
|
|
290
|
+
* @returns {string|null}
|
|
291
|
+
*/
|
|
292
|
+
export function resolveSolanaTraderPackagedRoot(modeConfig) {
|
|
293
|
+
const candidates = [
|
|
294
|
+
join(PLUGIN_PACKAGE_ROOT, "skills", "solana-trader"),
|
|
295
|
+
join(homedir(), ".openclaw", "extensions", modeConfig.pluginId, "skills", "solana-trader"),
|
|
296
|
+
];
|
|
297
|
+
const npmRoot = getCommandOutput("npm root -g");
|
|
298
|
+
if (npmRoot) {
|
|
299
|
+
candidates.push(join(npmRoot, modeConfig.pluginPackage, "skills", "solana-trader"));
|
|
300
|
+
}
|
|
301
|
+
for (const dir of candidates) {
|
|
302
|
+
if (existsSync(dir)) return dir;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* First existing gateway template file under `config/{filename}`.
|
|
309
|
+
* @param {{ pluginId: string, pluginPackage: string }} modeConfig
|
|
310
|
+
* @param {string} gatewayConfigFilename
|
|
311
|
+
* @returns {string|null}
|
|
312
|
+
*/
|
|
313
|
+
function resolveGatewayConfigSourcePath(modeConfig, gatewayConfigFilename) {
|
|
314
|
+
const candidates = [
|
|
315
|
+
join(PLUGIN_PACKAGE_ROOT, "config", gatewayConfigFilename),
|
|
316
|
+
join(homedir(), ".openclaw", "extensions", modeConfig.pluginId, "config", gatewayConfigFilename),
|
|
317
|
+
];
|
|
318
|
+
const npmRoot = getCommandOutput("npm root -g");
|
|
319
|
+
if (npmRoot) {
|
|
320
|
+
candidates.push(join(npmRoot, modeConfig.pluginPackage, "config", gatewayConfigFilename));
|
|
321
|
+
}
|
|
322
|
+
for (const p of candidates) {
|
|
323
|
+
if (existsSync(p)) return p;
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
279
328
|
function extractUrls(text = "") {
|
|
280
329
|
const matches = text.match(/https?:\/\/[^\s"')]+/g);
|
|
281
330
|
return matches ? [...new Set(matches)] : [];
|
|
@@ -511,12 +560,35 @@ export async function ensureOpenClawGlobalPackageDependencies() {
|
|
|
511
560
|
* global from an older install causes `openclaw plugins install` to fail config validation with
|
|
512
561
|
* "plugin requires OpenClaw >=… but this host is …".
|
|
513
562
|
*/
|
|
514
|
-
|
|
563
|
+
/** Bound `openclaw --version` so a post-OAuth CLI that blocks on gateway/device init cannot hang the wizard. */
|
|
564
|
+
const OPENCLAW_CLI_VERSION_TIMEOUT_MS = 25_000;
|
|
565
|
+
|
|
566
|
+
async function installOpenClawPlatform(onEvent) {
|
|
515
567
|
const hadOpenclaw = commandExists("openclaw");
|
|
516
|
-
const previousVersion = hadOpenclaw ? getCommandOutput("openclaw --version") : null;
|
|
517
|
-
|
|
568
|
+
const previousVersion = hadOpenclaw ? getCommandOutput("openclaw --version", { timeoutMs: OPENCLAW_CLI_VERSION_TIMEOUT_MS }) : null;
|
|
569
|
+
if (hadOpenclaw && !previousVersion && typeof onEvent === "function") {
|
|
570
|
+
onEvent({
|
|
571
|
+
type: "stderr",
|
|
572
|
+
text: "openclaw --version did not return in time (or failed); continuing with npm install -g anyway.\n",
|
|
573
|
+
urls: [],
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
const npmCwd = getNpmGlobalInstallCwd();
|
|
577
|
+
await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--registry", "https://registry.npmjs.org/", `openclaw@${OPENCLAW_VERSION}`], {
|
|
578
|
+
onEvent,
|
|
579
|
+
cwd: npmCwd,
|
|
580
|
+
shell: false,
|
|
581
|
+
});
|
|
518
582
|
const available = commandExists("openclaw");
|
|
519
|
-
|
|
583
|
+
let version = available ? getCommandOutput("openclaw --version", { timeoutMs: OPENCLAW_CLI_VERSION_TIMEOUT_MS }) : null;
|
|
584
|
+
if (available && !version && typeof onEvent === "function") {
|
|
585
|
+
onEvent({
|
|
586
|
+
type: "stderr",
|
|
587
|
+
text: "openclaw is on PATH but --version timed out; treating install as successful.\n",
|
|
588
|
+
urls: [],
|
|
589
|
+
});
|
|
590
|
+
version = "(version check timed out)";
|
|
591
|
+
}
|
|
520
592
|
if (!available) {
|
|
521
593
|
throw new Error(`npm install -g openclaw@${OPENCLAW_VERSION} finished but \`openclaw\` is not available on PATH`);
|
|
522
594
|
}
|
|
@@ -726,6 +798,31 @@ async function installAndEnableOpenClawPlugin(modeConfig, onEvent, orchestratorU
|
|
|
726
798
|
};
|
|
727
799
|
}
|
|
728
800
|
|
|
801
|
+
/**
|
|
802
|
+
* Idempotent: ensure OpenClaw discovers skills under ~/.openclaw/extensions/<pluginId>/skills (extraDirs).
|
|
803
|
+
* See OpenClaw workspace skill loader: config.skills.load.extraDirs → openclaw-extra.
|
|
804
|
+
* @param {Record<string, unknown>} config
|
|
805
|
+
* @param {string} pluginId
|
|
806
|
+
*/
|
|
807
|
+
function ensureTraderSkillsExtraDir(config, pluginId) {
|
|
808
|
+
const marker = `.openclaw/extensions/${pluginId}/skills`;
|
|
809
|
+
const tildeEntry = `~/.openclaw/extensions/${pluginId}/skills`;
|
|
810
|
+
if (!config.skills || typeof config.skills !== "object") config.skills = {};
|
|
811
|
+
if (!config.skills.load || typeof config.skills.load !== "object") config.skills.load = {};
|
|
812
|
+
const raw = config.skills.load.extraDirs;
|
|
813
|
+
const dirs = Array.isArray(raw) ? [...raw] : [];
|
|
814
|
+
const normalized = (d) => (typeof d === "string" ? d.replace(/\\/g, "/") : "");
|
|
815
|
+
const needle = normalized(tildeEntry);
|
|
816
|
+
const hasMarker = dirs.some((d) => {
|
|
817
|
+
const n = normalized(d);
|
|
818
|
+
return n.includes(marker) || n === needle;
|
|
819
|
+
});
|
|
820
|
+
if (!hasMarker) {
|
|
821
|
+
dirs.push(tildeEntry);
|
|
822
|
+
config.skills.load.extraDirs = dirs;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
729
826
|
function seedPluginConfig(modeConfig, orchestratorUrl, configPath = CONFIG_FILE) {
|
|
730
827
|
const defaultUrl = orchestratorUrl || "https://api.traderclaw.ai";
|
|
731
828
|
|
|
@@ -763,6 +860,8 @@ function seedPluginConfig(modeConfig, orchestratorUrl, configPath = CONFIG_FILE)
|
|
|
763
860
|
|
|
764
861
|
mergeOrchestratorForId(modeConfig.pluginId);
|
|
765
862
|
|
|
863
|
+
ensureTraderSkillsExtraDir(config, modeConfig.pluginId);
|
|
864
|
+
|
|
766
865
|
// Do not set plugins.allow here: OpenClaw validates allow[] against the plugin registry, and
|
|
767
866
|
// the id is not registered until after `openclaw plugins install`. Pre-seeding allow caused:
|
|
768
867
|
// "plugins.allow: plugin not found: <id>".
|
|
@@ -1285,10 +1384,8 @@ function deployGatewayConfig(modeConfig) {
|
|
|
1285
1384
|
const gatewayDir = join(CONFIG_DIR, "gateway");
|
|
1286
1385
|
mkdirSync(gatewayDir, { recursive: true });
|
|
1287
1386
|
const destFile = join(gatewayDir, modeConfig.gatewayConfig);
|
|
1288
|
-
const
|
|
1289
|
-
if (!
|
|
1290
|
-
const src = join(npmRoot, modeConfig.pluginPackage, "config", modeConfig.gatewayConfig);
|
|
1291
|
-
if (!existsSync(src)) return { deployed: false, dest: destFile };
|
|
1387
|
+
const src = resolveGatewayConfigSourcePath(modeConfig, modeConfig.gatewayConfig);
|
|
1388
|
+
if (!src) return { deployed: false, dest: destFile };
|
|
1292
1389
|
writeFileSync(destFile, readFileSync(src));
|
|
1293
1390
|
return { deployed: true, source: src, dest: destFile };
|
|
1294
1391
|
}
|
|
@@ -1325,13 +1422,13 @@ export function resolveAgentWorkspaceDir(configPath = CONFIG_FILE) {
|
|
|
1325
1422
|
}
|
|
1326
1423
|
|
|
1327
1424
|
/**
|
|
1328
|
-
* Copy skills/solana-trader/HEARTBEAT.md from the
|
|
1425
|
+
* Copy skills/solana-trader/HEARTBEAT.md from the plugin package, OpenClaw extension, or global npm into the workspace root.
|
|
1329
1426
|
* Skips overwrite if a non-empty file already exists (user may have customized it).
|
|
1330
1427
|
*/
|
|
1331
1428
|
export function deployWorkspaceHeartbeat(modeConfig) {
|
|
1332
|
-
const
|
|
1333
|
-
if (!
|
|
1334
|
-
const src = join(
|
|
1429
|
+
const skillRoot = resolveSolanaTraderPackagedRoot(modeConfig);
|
|
1430
|
+
if (!skillRoot) return { deployed: false, reason: "source_missing" };
|
|
1431
|
+
const src = join(skillRoot, "HEARTBEAT.md");
|
|
1335
1432
|
if (!existsSync(src)) return { deployed: false, reason: "source_missing", src };
|
|
1336
1433
|
|
|
1337
1434
|
const workspaceDir = resolveAgentWorkspaceDir(CONFIG_FILE);
|
|
@@ -1358,10 +1455,10 @@ export function deployWorkspaceHeartbeat(modeConfig) {
|
|
|
1358
1455
|
* Skips files that already exist and are non-empty so user customisations are preserved.
|
|
1359
1456
|
*/
|
|
1360
1457
|
export function deployWorkspaceBootstrapFiles(modeConfig) {
|
|
1361
|
-
const
|
|
1362
|
-
if (!
|
|
1458
|
+
const skillRoot = resolveSolanaTraderPackagedRoot(modeConfig);
|
|
1459
|
+
if (!skillRoot) return { deployed: [], skipped: [], failed: [], reason: "source_dir_missing" };
|
|
1363
1460
|
|
|
1364
|
-
const srcDir = join(
|
|
1461
|
+
const srcDir = join(skillRoot, "workspace");
|
|
1365
1462
|
if (!existsSync(srcDir)) return { deployed: [], skipped: [], failed: [], reason: "source_dir_missing", srcDir };
|
|
1366
1463
|
|
|
1367
1464
|
const workspaceDir = resolveAgentWorkspaceDir(CONFIG_FILE);
|
|
@@ -2039,6 +2136,8 @@ export class InstallerStepEngine {
|
|
|
2039
2136
|
skipInstallOpenClaw: options.skipInstallOpenClaw === true,
|
|
2040
2137
|
skipInstallPlugin: options.skipInstallPlugin === true,
|
|
2041
2138
|
skipTailscale: options.skipTailscale === true,
|
|
2139
|
+
/** When true (e.g. EC2 bootstrap before Tailscale auth), gateway starts without `tailscale funnel`. */
|
|
2140
|
+
skipFunnel: options.skipFunnel === true,
|
|
2042
2141
|
skipGatewayBootstrap: options.skipGatewayBootstrap === true,
|
|
2043
2142
|
skipGatewayConfig: options.skipGatewayConfig === true,
|
|
2044
2143
|
// Wizard / CLI — must be preserved for seedXConfig
|
|
@@ -2489,7 +2588,11 @@ export class InstallerStepEngine {
|
|
|
2489
2588
|
}
|
|
2490
2589
|
|
|
2491
2590
|
if (!this.options.skipInstallOpenClaw) {
|
|
2492
|
-
await this.runStep("install_openclaw", "Installing or upgrading OpenClaw platform", async () =>
|
|
2591
|
+
await this.runStep("install_openclaw", "Installing or upgrading OpenClaw platform", async () =>
|
|
2592
|
+
installOpenClawPlatform((evt) =>
|
|
2593
|
+
this.emitLog("install_openclaw", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2594
|
+
),
|
|
2595
|
+
);
|
|
2493
2596
|
}
|
|
2494
2597
|
|
|
2495
2598
|
// Non-fatal: warn when the CLI has devices in pending-approval or repair state.
|
|
@@ -2600,6 +2703,14 @@ export class InstallerStepEngine {
|
|
|
2600
2703
|
);
|
|
2601
2704
|
await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "install"]);
|
|
2602
2705
|
await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "restart"]);
|
|
2706
|
+
if (this.options.skipFunnel) {
|
|
2707
|
+
this.emitLog(
|
|
2708
|
+
"gateway_bootstrap",
|
|
2709
|
+
"info",
|
|
2710
|
+
"Skipping Tailscale funnel (skipFunnel). After `tailscale up`, run: traderclaw install --headless --funnel-only",
|
|
2711
|
+
);
|
|
2712
|
+
return { funnelSkipped: true };
|
|
2713
|
+
}
|
|
2603
2714
|
return this.runFunnel();
|
|
2604
2715
|
} catch (err) {
|
|
2605
2716
|
const text = `${err?.message || ""}\n${err?.stderr || ""}\n${err?.stdout || ""}`.toLowerCase();
|
|
@@ -2615,6 +2726,9 @@ export class InstallerStepEngine {
|
|
|
2615
2726
|
if (gatewayStartFailed || gatewayModeUnset) {
|
|
2616
2727
|
const recovered = await this.tryAutoRecoverGatewayMode("gateway_bootstrap");
|
|
2617
2728
|
if (recovered.success) {
|
|
2729
|
+
if (this.options.skipFunnel) {
|
|
2730
|
+
return { funnelSkipped: true, recovered: true };
|
|
2731
|
+
}
|
|
2618
2732
|
return this.runFunnel();
|
|
2619
2733
|
}
|
|
2620
2734
|
if (gatewayModeUnset) {
|
|
@@ -2806,6 +2920,28 @@ export class InstallerStepEngine {
|
|
|
2806
2920
|
}
|
|
2807
2921
|
}
|
|
2808
2922
|
|
|
2923
|
+
/**
|
|
2924
|
+
* After Tailscale login (`tailscale up`), expose loopback gateway :18789 via funnel.
|
|
2925
|
+
*/
|
|
2926
|
+
export async function tailscaleFunnelOpenclaw18789(options = {}) {
|
|
2927
|
+
const onEvt = typeof options.onEvent === "function" ? options.onEvent : () => {};
|
|
2928
|
+
try {
|
|
2929
|
+
await runCommandWithEvents(
|
|
2930
|
+
"tailscale",
|
|
2931
|
+
["funnel", "--bg", "18789"],
|
|
2932
|
+
{ onEvent: (evt) => onEvt(evt) },
|
|
2933
|
+
);
|
|
2934
|
+
} catch (err) {
|
|
2935
|
+
const details = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.toLowerCase();
|
|
2936
|
+
if (details.includes("access denied") || details.includes("operator")) {
|
|
2937
|
+
throw new Error(tailscalePermissionRemediation());
|
|
2938
|
+
}
|
|
2939
|
+
throw err;
|
|
2940
|
+
}
|
|
2941
|
+
const statusOut = getCommandOutput("tailscale funnel status") || "";
|
|
2942
|
+
return { funnelUrl: firstUrl(statusOut) || null, rawStatus: statusOut };
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2809
2945
|
export function assertWizardXCredentials(modeConfig, options = {}) {
|
|
2810
2946
|
const t = (s) => (typeof s === "string" ? s.trim() : "");
|
|
2811
2947
|
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
|
}
|
|
@@ -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(
|
|
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.
|
|
3
|
+
"version": "1.0.120",
|
|
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.120"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|