traderclaw-cli 1.0.117 → 1.0.121
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 +186 -23
- package/package.json +2 -2
|
@@ -284,6 +284,47 @@ function getCommandOutput(cmd, { timeoutMs = 0 } = {}) {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
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
|
+
|
|
287
328
|
function extractUrls(text = "") {
|
|
288
329
|
const matches = text.match(/https?:\/\/[^\s"')]+/g);
|
|
289
330
|
return matches ? [...new Set(matches)] : [];
|
|
@@ -410,7 +451,25 @@ function isOpenClawConfigSchemaFailure(text) {
|
|
|
410
451
|
|
|
411
452
|
function runCommandWithEvents(cmd, args = [], opts = {}) {
|
|
412
453
|
return new Promise((resolve, reject) => {
|
|
413
|
-
const {
|
|
454
|
+
const {
|
|
455
|
+
onEvent,
|
|
456
|
+
timeoutMs = 0,
|
|
457
|
+
heartbeatMs = 0,
|
|
458
|
+
heartbeatText = "command still running…",
|
|
459
|
+
...spawnOpts
|
|
460
|
+
} = opts;
|
|
461
|
+
|
|
462
|
+
let settled = false;
|
|
463
|
+
let timeoutId;
|
|
464
|
+
let heartbeatId;
|
|
465
|
+
const finish = (fn, arg) => {
|
|
466
|
+
if (settled) return;
|
|
467
|
+
settled = true;
|
|
468
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
469
|
+
if (heartbeatId) clearInterval(heartbeatId);
|
|
470
|
+
fn(arg);
|
|
471
|
+
};
|
|
472
|
+
|
|
414
473
|
const isNpm = /(?:^|[\\/])npm(?:\.cmd)?$/.test(cmd) || cmd === "npm";
|
|
415
474
|
if (isNpm && !spawnOpts.env?.NODE_OPTIONS?.includes("max-old-space-size")) {
|
|
416
475
|
spawnOpts.env = {
|
|
@@ -430,6 +489,44 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
|
|
|
430
489
|
const emitFn = typeof onEvent === "function" ? onEvent : null;
|
|
431
490
|
const emit = (event) => emitFn && emitFn(event);
|
|
432
491
|
|
|
492
|
+
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
493
|
+
timeoutId = setTimeout(() => {
|
|
494
|
+
try {
|
|
495
|
+
child.kill("SIGTERM");
|
|
496
|
+
} catch {
|
|
497
|
+
/* ignore */
|
|
498
|
+
}
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
try {
|
|
501
|
+
child.kill("SIGKILL");
|
|
502
|
+
} catch {
|
|
503
|
+
/* ignore */
|
|
504
|
+
}
|
|
505
|
+
}, 12_000);
|
|
506
|
+
const tail = `${stdout}\n${stderr}`.trim().slice(-6000);
|
|
507
|
+
const err = new Error(
|
|
508
|
+
`Timed out after ${timeoutMs}ms: ${cmd} ${args.join(" ")}\n`
|
|
509
|
+
+ `Last output:\n${tail || "(no output yet — possible npm registry or network stall; try again or run the same npm command in a terminal)"}`,
|
|
510
|
+
);
|
|
511
|
+
err.timedOut = true;
|
|
512
|
+
err.stdout = stdout;
|
|
513
|
+
err.stderr = stderr;
|
|
514
|
+
finish(reject, err);
|
|
515
|
+
}, timeoutMs);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (typeof heartbeatMs === "number" && heartbeatMs > 0 && emitFn) {
|
|
519
|
+
const start = Date.now();
|
|
520
|
+
heartbeatId = setInterval(() => {
|
|
521
|
+
const sec = Math.floor((Date.now() - start) / 1000);
|
|
522
|
+
emitFn({
|
|
523
|
+
type: "stdout",
|
|
524
|
+
text: `[installer] ${heartbeatText} (${sec}s elapsed).\n`,
|
|
525
|
+
urls: [],
|
|
526
|
+
});
|
|
527
|
+
}, heartbeatMs);
|
|
528
|
+
}
|
|
529
|
+
|
|
433
530
|
child.stdout?.on("data", (d) => {
|
|
434
531
|
const text = d.toString();
|
|
435
532
|
stdout += text;
|
|
@@ -443,8 +540,9 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
|
|
|
443
540
|
});
|
|
444
541
|
|
|
445
542
|
child.on("close", (code) => {
|
|
543
|
+
if (settled) return;
|
|
446
544
|
const urls = [...new Set([...extractUrls(stdout), ...extractUrls(stderr)])];
|
|
447
|
-
if (code === 0) resolve
|
|
545
|
+
if (code === 0) finish(resolve, { stdout, stderr, code, urls });
|
|
448
546
|
else {
|
|
449
547
|
const isOom = code === 137 || (stderr || stdout || "").includes("Killed");
|
|
450
548
|
const raw = (stderr || "").trim();
|
|
@@ -459,10 +557,10 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
|
|
|
459
557
|
err.stderr = stderr;
|
|
460
558
|
err.urls = urls;
|
|
461
559
|
err.oom = isOom;
|
|
462
|
-
reject
|
|
560
|
+
finish(reject, err);
|
|
463
561
|
}
|
|
464
562
|
});
|
|
465
|
-
child.on("error", reject);
|
|
563
|
+
child.on("error", (e) => finish(reject, e));
|
|
466
564
|
});
|
|
467
565
|
}
|
|
468
566
|
|
|
@@ -494,7 +592,7 @@ export async function ensureOpenClawGlobalPackageDependencies() {
|
|
|
494
592
|
return { skipped: true, reason: "global_openclaw_dir_not_found" };
|
|
495
593
|
}
|
|
496
594
|
const registry = "https://registry.npmjs.org/";
|
|
497
|
-
const installFlags = ["install", "--omit=dev", "--ignore-scripts", "--registry", registry];
|
|
595
|
+
const installFlags = ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--registry", registry];
|
|
498
596
|
await runCommandWithEvents("npm", installFlags, { cwd: dir, shell: false });
|
|
499
597
|
await runCommandWithEvents(
|
|
500
598
|
"npm",
|
|
@@ -503,6 +601,8 @@ export async function ensureOpenClawGlobalPackageDependencies() {
|
|
|
503
601
|
"--omit=dev",
|
|
504
602
|
"--no-save",
|
|
505
603
|
"--ignore-scripts",
|
|
604
|
+
"--no-audit",
|
|
605
|
+
"--no-fund",
|
|
506
606
|
"--registry",
|
|
507
607
|
registry,
|
|
508
608
|
"grammy",
|
|
@@ -533,11 +633,49 @@ async function installOpenClawPlatform(onEvent) {
|
|
|
533
633
|
});
|
|
534
634
|
}
|
|
535
635
|
const npmCwd = getNpmGlobalInstallCwd();
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
636
|
+
const npmTimeoutMs = Number.parseInt(String(process.env.TRADERCLAW_OPENCLAW_NPM_TIMEOUT_MS || "").trim(), 10);
|
|
637
|
+
const effectiveTimeout = Number.isFinite(npmTimeoutMs) && npmTimeoutMs > 0 ? npmTimeoutMs : 1_800_000;
|
|
638
|
+
if (typeof onEvent === "function") {
|
|
639
|
+
onEvent({
|
|
640
|
+
type: "stdout",
|
|
641
|
+
text:
|
|
642
|
+
`Running: npm install -g openclaw@${OPENCLAW_VERSION} (cwd=${npmCwd}, --no-audit --no-fund). `
|
|
643
|
+
+ "First-time or upgrade installs can take several minutes; live npm lines and heartbeats appear below. "
|
|
644
|
+
+ `Also watch the terminal where you started \`traderclaw install --wizard\`. `
|
|
645
|
+
+ `Override stall limit: TRADERCLAW_OPENCLAW_NPM_TIMEOUT_MS (ms), default ${effectiveTimeout}.\n`,
|
|
646
|
+
urls: [],
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
await runCommandWithEvents(
|
|
650
|
+
"npm",
|
|
651
|
+
[
|
|
652
|
+
"install",
|
|
653
|
+
"-g",
|
|
654
|
+
"--ignore-scripts",
|
|
655
|
+
"--no-audit",
|
|
656
|
+
"--no-fund",
|
|
657
|
+
"--loglevel",
|
|
658
|
+
"info",
|
|
659
|
+
"--registry",
|
|
660
|
+
"https://registry.npmjs.org/",
|
|
661
|
+
`openclaw@${OPENCLAW_VERSION}`,
|
|
662
|
+
],
|
|
663
|
+
{
|
|
664
|
+
onEvent,
|
|
665
|
+
cwd: npmCwd,
|
|
666
|
+
shell: false,
|
|
667
|
+
timeoutMs: effectiveTimeout,
|
|
668
|
+
heartbeatMs: 60_000,
|
|
669
|
+
heartbeatText:
|
|
670
|
+
"npm still installing OpenClaw — if this repeats for a long time, check disk space, DNS, and outbound HTTPS to registry.npmjs.org",
|
|
671
|
+
env: {
|
|
672
|
+
...process.env,
|
|
673
|
+
// Non-interactive / fewer slow npm side trips (fundraising prompts, audit).
|
|
674
|
+
...(process.env.CI ? {} : { CI: "true" }),
|
|
675
|
+
npm_config_update_notifier: process.env.npm_config_update_notifier ?? "false",
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
);
|
|
541
679
|
const available = commandExists("openclaw");
|
|
542
680
|
let version = available ? getCommandOutput("openclaw --version", { timeoutMs: OPENCLAW_CLI_VERSION_TIMEOUT_MS }) : null;
|
|
543
681
|
if (available && !version && typeof onEvent === "function") {
|
|
@@ -618,7 +756,7 @@ function isNpmFilesystemPackageSpec(spec) {
|
|
|
618
756
|
* IMPORTANT: run with `{ shell: false }` — `spawn(..., { shell: true })` can drop argv on Unix and npm then mis-resolves the package name.
|
|
619
757
|
*/
|
|
620
758
|
function npmGlobalInstallArgs(spec, { force = false } = {}) {
|
|
621
|
-
const args = ["install", "-g", "--ignore-scripts"];
|
|
759
|
+
const args = ["install", "-g", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
622
760
|
if (force) args.push("--force");
|
|
623
761
|
if (!isNpmFilesystemPackageSpec(spec)) {
|
|
624
762
|
args.push("--registry", "https://registry.npmjs.org/");
|
|
@@ -757,6 +895,31 @@ async function installAndEnableOpenClawPlugin(modeConfig, onEvent, orchestratorU
|
|
|
757
895
|
};
|
|
758
896
|
}
|
|
759
897
|
|
|
898
|
+
/**
|
|
899
|
+
* Idempotent: ensure OpenClaw discovers skills under ~/.openclaw/extensions/<pluginId>/skills (extraDirs).
|
|
900
|
+
* See OpenClaw workspace skill loader: config.skills.load.extraDirs → openclaw-extra.
|
|
901
|
+
* @param {Record<string, unknown>} config
|
|
902
|
+
* @param {string} pluginId
|
|
903
|
+
*/
|
|
904
|
+
function ensureTraderSkillsExtraDir(config, pluginId) {
|
|
905
|
+
const marker = `.openclaw/extensions/${pluginId}/skills`;
|
|
906
|
+
const tildeEntry = `~/.openclaw/extensions/${pluginId}/skills`;
|
|
907
|
+
if (!config.skills || typeof config.skills !== "object") config.skills = {};
|
|
908
|
+
if (!config.skills.load || typeof config.skills.load !== "object") config.skills.load = {};
|
|
909
|
+
const raw = config.skills.load.extraDirs;
|
|
910
|
+
const dirs = Array.isArray(raw) ? [...raw] : [];
|
|
911
|
+
const normalized = (d) => (typeof d === "string" ? d.replace(/\\/g, "/") : "");
|
|
912
|
+
const needle = normalized(tildeEntry);
|
|
913
|
+
const hasMarker = dirs.some((d) => {
|
|
914
|
+
const n = normalized(d);
|
|
915
|
+
return n.includes(marker) || n === needle;
|
|
916
|
+
});
|
|
917
|
+
if (!hasMarker) {
|
|
918
|
+
dirs.push(tildeEntry);
|
|
919
|
+
config.skills.load.extraDirs = dirs;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
760
923
|
function seedPluginConfig(modeConfig, orchestratorUrl, configPath = CONFIG_FILE) {
|
|
761
924
|
const defaultUrl = orchestratorUrl || "https://api.traderclaw.ai";
|
|
762
925
|
|
|
@@ -794,6 +957,8 @@ function seedPluginConfig(modeConfig, orchestratorUrl, configPath = CONFIG_FILE)
|
|
|
794
957
|
|
|
795
958
|
mergeOrchestratorForId(modeConfig.pluginId);
|
|
796
959
|
|
|
960
|
+
ensureTraderSkillsExtraDir(config, modeConfig.pluginId);
|
|
961
|
+
|
|
797
962
|
// Do not set plugins.allow here: OpenClaw validates allow[] against the plugin registry, and
|
|
798
963
|
// the id is not registered until after `openclaw plugins install`. Pre-seeding allow caused:
|
|
799
964
|
// "plugins.allow: plugin not found: <id>".
|
|
@@ -1316,10 +1481,8 @@ function deployGatewayConfig(modeConfig) {
|
|
|
1316
1481
|
const gatewayDir = join(CONFIG_DIR, "gateway");
|
|
1317
1482
|
mkdirSync(gatewayDir, { recursive: true });
|
|
1318
1483
|
const destFile = join(gatewayDir, modeConfig.gatewayConfig);
|
|
1319
|
-
const
|
|
1320
|
-
if (!
|
|
1321
|
-
const src = join(npmRoot, modeConfig.pluginPackage, "config", modeConfig.gatewayConfig);
|
|
1322
|
-
if (!existsSync(src)) return { deployed: false, dest: destFile };
|
|
1484
|
+
const src = resolveGatewayConfigSourcePath(modeConfig, modeConfig.gatewayConfig);
|
|
1485
|
+
if (!src) return { deployed: false, dest: destFile };
|
|
1323
1486
|
writeFileSync(destFile, readFileSync(src));
|
|
1324
1487
|
return { deployed: true, source: src, dest: destFile };
|
|
1325
1488
|
}
|
|
@@ -1356,13 +1519,13 @@ export function resolveAgentWorkspaceDir(configPath = CONFIG_FILE) {
|
|
|
1356
1519
|
}
|
|
1357
1520
|
|
|
1358
1521
|
/**
|
|
1359
|
-
* Copy skills/solana-trader/HEARTBEAT.md from the
|
|
1522
|
+
* Copy skills/solana-trader/HEARTBEAT.md from the plugin package, OpenClaw extension, or global npm into the workspace root.
|
|
1360
1523
|
* Skips overwrite if a non-empty file already exists (user may have customized it).
|
|
1361
1524
|
*/
|
|
1362
1525
|
export function deployWorkspaceHeartbeat(modeConfig) {
|
|
1363
|
-
const
|
|
1364
|
-
if (!
|
|
1365
|
-
const src = join(
|
|
1526
|
+
const skillRoot = resolveSolanaTraderPackagedRoot(modeConfig);
|
|
1527
|
+
if (!skillRoot) return { deployed: false, reason: "source_missing" };
|
|
1528
|
+
const src = join(skillRoot, "HEARTBEAT.md");
|
|
1366
1529
|
if (!existsSync(src)) return { deployed: false, reason: "source_missing", src };
|
|
1367
1530
|
|
|
1368
1531
|
const workspaceDir = resolveAgentWorkspaceDir(CONFIG_FILE);
|
|
@@ -1389,10 +1552,10 @@ export function deployWorkspaceHeartbeat(modeConfig) {
|
|
|
1389
1552
|
* Skips files that already exist and are non-empty so user customisations are preserved.
|
|
1390
1553
|
*/
|
|
1391
1554
|
export function deployWorkspaceBootstrapFiles(modeConfig) {
|
|
1392
|
-
const
|
|
1393
|
-
if (!
|
|
1555
|
+
const skillRoot = resolveSolanaTraderPackagedRoot(modeConfig);
|
|
1556
|
+
if (!skillRoot) return { deployed: [], skipped: [], failed: [], reason: "source_dir_missing" };
|
|
1394
1557
|
|
|
1395
|
-
const srcDir = join(
|
|
1558
|
+
const srcDir = join(skillRoot, "workspace");
|
|
1396
1559
|
if (!existsSync(srcDir)) return { deployed: [], skipped: [], failed: [], reason: "source_dir_missing", srcDir };
|
|
1397
1560
|
|
|
1398
1561
|
const workspaceDir = resolveAgentWorkspaceDir(CONFIG_FILE);
|
|
@@ -2580,7 +2743,7 @@ export class InstallerStepEngine {
|
|
|
2580
2743
|
}
|
|
2581
2744
|
this.emitLog("install_qmd", "info", "Installing @tobilu/qmd globally for vector search memory...");
|
|
2582
2745
|
try {
|
|
2583
|
-
await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--registry", "https://registry.npmjs.org/", "@tobilu/qmd"], {
|
|
2746
|
+
await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--no-audit", "--no-fund", "--registry", "https://registry.npmjs.org/", "@tobilu/qmd"], {
|
|
2584
2747
|
onEvent: (evt) => this.emitLog("install_qmd", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2585
2748
|
});
|
|
2586
2749
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderclaw-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.121",
|
|
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.121"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|