traderclaw-cli 1.0.78 → 1.0.80

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.
@@ -400,6 +400,14 @@ function isOpenClawConfigSchemaFailure(text) {
400
400
  function runCommandWithEvents(cmd, args = [], opts = {}) {
401
401
  return new Promise((resolve, reject) => {
402
402
  const { onEvent, ...spawnOpts } = opts;
403
+ const isNpm = /(?:^|[\\/])npm(?:\.cmd)?$/.test(cmd) || cmd === "npm";
404
+ if (isNpm && !spawnOpts.env?.NODE_OPTIONS?.includes("max-old-space-size")) {
405
+ spawnOpts.env = {
406
+ ...process.env,
407
+ ...spawnOpts.env,
408
+ NODE_OPTIONS: [spawnOpts.env?.NODE_OPTIONS || process.env.NODE_OPTIONS || "", "--max-old-space-size=512"].filter(Boolean).join(" "),
409
+ };
410
+ }
403
411
  const child = spawn(cmd, args, {
404
412
  stdio: "pipe",
405
413
  shell: true,
@@ -427,14 +435,19 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
427
435
  const urls = [...new Set([...extractUrls(stdout), ...extractUrls(stderr)])];
428
436
  if (code === 0) resolve({ stdout, stderr, code, urls });
429
437
  else {
438
+ const isOom = code === 137 || (stderr || stdout || "").includes("Killed");
430
439
  const raw = (stderr || "").trim();
431
440
  const tailLines = raw.split("\n").filter((l) => l.length > 0).slice(-40).join("\n");
432
441
  const stderrPreview = tailLines.length > 8000 ? tailLines.slice(-8000) : tailLines;
433
- const err = new Error(stderrPreview ? `command failed with exit code ${code}: ${stderrPreview}` : `command failed with exit code ${code}`);
442
+ const prefix = isOom
443
+ ? `Out of memory (exit 137 / SIGKILL): the host killed '${cmd}' — try a machine with ≥1 GB free RAM, or reduce concurrency with npm_config_maxsockets=2`
444
+ : `command failed with exit code ${code}`;
445
+ const err = new Error(stderrPreview ? `${prefix}: ${stderrPreview}` : prefix);
434
446
  err.code = code;
435
447
  err.stdout = stdout;
436
448
  err.stderr = stderr;
437
449
  err.urls = urls;
450
+ err.oom = isOom;
438
451
  reject(err);
439
452
  }
440
453
  });
@@ -454,6 +467,14 @@ function getGlobalOpenClawPackageDir() {
454
467
  * incomplete `node_modules` after `npm install -g` (hoisting, optional deps, or interrupted
455
468
  * installs). OpenClaw then fails at runtime with `Cannot find module 'grammy'` while loading
456
469
  * config. Installing from the package directory restores declared dependencies.
470
+ *
471
+ * `--ignore-scripts` avoids OpenClaw's postinstall (and nested installs) failing on hosts without
472
+ * a C toolchain: e.g. `@discordjs/opus` has no prebuild for Node 22 and falls back to `node-gyp`
473
+ * (`make` not found). Skipping scripts still installs declared JS deps (e.g. `grammy`). Users who
474
+ * need native/voice features can install build-essential and re-run `npm install` without
475
+ * `--ignore-scripts` in the global openclaw directory.
476
+ *
477
+ * We still run `npm install grammy @buape/carbon --no-save` with `--ignore-scripts` as a safety net.
457
478
  */
458
479
  /** Runs `npm install` in the global `openclaw` package directory (fixes missing `grammy` etc.). */
459
480
  export async function ensureOpenClawGlobalPackageDependencies() {
@@ -461,10 +482,23 @@ export async function ensureOpenClawGlobalPackageDependencies() {
461
482
  if (!dir) {
462
483
  return { skipped: true, reason: "global_openclaw_dir_not_found" };
463
484
  }
464
- await runCommandWithEvents("npm", ["install", "--omit=dev", "--registry", "https://registry.npmjs.org/"], {
465
- cwd: dir,
466
- shell: false,
467
- });
485
+ const registry = "https://registry.npmjs.org/";
486
+ const installFlags = ["install", "--omit=dev", "--ignore-scripts", "--registry", registry];
487
+ await runCommandWithEvents("npm", installFlags, { cwd: dir, shell: false });
488
+ await runCommandWithEvents(
489
+ "npm",
490
+ [
491
+ "install",
492
+ "--omit=dev",
493
+ "--no-save",
494
+ "--ignore-scripts",
495
+ "--registry",
496
+ registry,
497
+ "grammy",
498
+ "@buape/carbon",
499
+ ],
500
+ { cwd: dir, shell: false },
501
+ );
468
502
  return { repaired: true, dir };
469
503
  }
470
504
 
@@ -477,7 +511,7 @@ export async function ensureOpenClawGlobalPackageDependencies() {
477
511
  async function installOpenClawPlatform() {
478
512
  const hadOpenclaw = commandExists("openclaw");
479
513
  const previousVersion = hadOpenclaw ? getCommandOutput("openclaw --version") : null;
480
- await runCommandWithEvents("npm", ["install", "-g", "--registry", "https://registry.npmjs.org/", "openclaw@latest"]);
514
+ await runCommandWithEvents("npm", ["install", "-g", "--ignore-scripts", "--registry", "https://registry.npmjs.org/", "openclaw@latest"]);
481
515
  const available = commandExists("openclaw");
482
516
  const version = available ? getCommandOutput("openclaw --version") : null;
483
517
  if (!available) {
@@ -514,7 +548,7 @@ function isNpmFilesystemPackageSpec(spec) {
514
548
  * IMPORTANT: run with `{ shell: false }` — `spawn(..., { shell: true })` can drop argv on Unix and npm then mis-resolves the package name.
515
549
  */
516
550
  function npmGlobalInstallArgs(spec, { force = false } = {}) {
517
- const args = ["install", "-g"];
551
+ const args = ["install", "-g", "--ignore-scripts"];
518
552
  if (force) args.push("--force");
519
553
  if (!isNpmFilesystemPackageSpec(spec)) {
520
554
  args.push("--registry", "https://registry.npmjs.org/");
@@ -1604,14 +1638,14 @@ export function spawnOpenClawCodexAuthLoginChild() {
1604
1638
  if (process.platform === "win32") {
1605
1639
  return spawn("openclaw", argv, { stdio: ["pipe", "pipe", "pipe"], shell: false });
1606
1640
  }
1607
- // `unbuffer` (expect package) runs the CLI under a PTY and forwards stdin for the paste step reliably.
1608
- // Plain `script` often does not forward Node's stdin to the inner openclaw process, which causes hangs until timeout.
1609
1641
  if (commandExists("unbuffer")) {
1610
1642
  return spawn("unbuffer", ["openclaw", ...argv], { stdio: ["pipe", "pipe", "pipe"], shell: false });
1611
1643
  }
1612
1644
  if (commandExists("script")) {
1613
1645
  const cmdline = "openclaw models auth login --provider openai-codex";
1614
- return spawn("script", ["-q", "-c", cmdline, "/dev/null"], {
1646
+ // --return propagates the inner command's exit code (util-linux 2.38+).
1647
+ // Without it, script may exit 0 even if openclaw fails.
1648
+ return spawn("script", ["--return", "-q", "-c", cmdline, "/dev/null"], {
1615
1649
  stdio: ["pipe", "pipe", "pipe"],
1616
1650
  shell: false,
1617
1651
  });
@@ -2093,6 +2127,20 @@ export class InstallerStepEngine {
2093
2127
  }
2094
2128
  }
2095
2129
 
2130
+ const authFile = join(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
2131
+ let hasAuth = false;
2132
+ try {
2133
+ hasAuth = readFileSync(authFile, "utf-8").length > 20;
2134
+ } catch { /* file missing */ }
2135
+ if (!hasAuth) {
2136
+ throw new Error(
2137
+ "No OAuth credentials found at " + authFile + ". " +
2138
+ "The wizard OAuth flow did not save tokens (the callback may not have reached the OpenClaw CLI). " +
2139
+ "Run 'openclaw models auth login --provider openai-codex' in a terminal, " +
2140
+ "then re-run the wizard with the 'already logged in' option.",
2141
+ );
2142
+ }
2143
+
2096
2144
  const selection = resolveLlmModelSelection(provider, requestedModel);
2097
2145
  for (const msg of selection.warnings) {
2098
2146
  this.emitLog("configure_llm", "warn", msg);
@@ -3257,6 +3257,32 @@ async function cmdInstall(args) {
3257
3257
  }
3258
3258
  }
3259
3259
 
3260
+ /**
3261
+ * Release port 1455 so the OpenClaw CLI can bind its own callback server
3262
+ * during `openclaw models auth login`. Returns a promise that resolves
3263
+ * once the proxy is fully closed (or after a safety timeout).
3264
+ */
3265
+ function stopCallbackProxy() {
3266
+ return new Promise((resolve) => {
3267
+ if (!oauthCallbackProxy) { resolve(); return; }
3268
+ const proxy = oauthCallbackProxy;
3269
+ oauthCallbackProxy = null;
3270
+ const safety = setTimeout(resolve, 2000);
3271
+ proxy.close(() => { clearTimeout(safety); setTimeout(resolve, 150); });
3272
+ });
3273
+ }
3274
+
3275
+ function hasOpenaiCodexAuthTokens() {
3276
+ try {
3277
+ const authFile = join(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
3278
+ const raw = readFileSync(authFile, "utf-8");
3279
+ const data = JSON.parse(raw);
3280
+ return data && typeof data === "object" && JSON.stringify(data).length > 20;
3281
+ } catch {
3282
+ return false;
3283
+ }
3284
+ }
3285
+
3260
3286
  function killOauthSession(sessionId, signal = "SIGTERM") {
3261
3287
  const s = oauthSessions.get(sessionId);
3262
3288
  if (!s) return;
@@ -3388,6 +3414,13 @@ async function cmdInstall(args) {
3388
3414
  killOauthSession(id);
3389
3415
  }
3390
3416
 
3417
+ // Release port 1455 so the OpenClaw CLI can bind its own callback
3418
+ // server directly. The previous approach relied on the wizard proxy
3419
+ // catching the callback and forwarding the code via stdin, but
3420
+ // `script` (PTY wrapper) does not reliably forward stdin to the
3421
+ // inner process, causing tokens to never be saved.
3422
+ await stopCallbackProxy();
3423
+
3391
3424
  const sessionId = randomUUID();
3392
3425
  const child = spawnOpenClawCodexAuthLoginChild();
3393
3426
 
@@ -3402,6 +3435,7 @@ async function cmdInstall(args) {
3402
3435
  /* ignore */
3403
3436
  }
3404
3437
  oauthSessions.delete(sessionId);
3438
+ startCallbackProxy();
3405
3439
  respondJson(504, {
3406
3440
  ok: false,
3407
3441
  error: "oauth_url_timeout",
@@ -3440,6 +3474,7 @@ async function cmdInstall(args) {
3440
3474
  });
3441
3475
  child.on("error", (err) => {
3442
3476
  clearTimeout(urlTimeout);
3477
+ startCallbackProxy();
3443
3478
  if (responded) return;
3444
3479
  responded = true;
3445
3480
  oauthSessions.delete(sessionId);
@@ -3447,6 +3482,7 @@ async function cmdInstall(args) {
3447
3482
  });
3448
3483
  child.on("close", (code) => {
3449
3484
  clearTimeout(urlTimeout);
3485
+ startCallbackProxy();
3450
3486
  if (!responded) {
3451
3487
  responded = true;
3452
3488
  oauthSessions.delete(sessionId);
@@ -3466,9 +3502,15 @@ async function cmdInstall(args) {
3466
3502
  pending.updatedAt = Date.now();
3467
3503
  pending.exitCode = typeof code === "number" ? code : null;
3468
3504
  pending.detail = stripAnsi(combined).slice(-4000);
3469
- if (code === 0) {
3505
+ if (code === 0 && hasOpenaiCodexAuthTokens()) {
3470
3506
  pending.status = "succeeded";
3471
3507
  pending.message = "ChatGPT OAuth completed successfully.";
3508
+ } else if (code === 0) {
3509
+ pending.status = "failed";
3510
+ pending.message =
3511
+ "OpenClaw exited OK but no auth tokens were saved. " +
3512
+ "Run 'openclaw models auth login --provider openai-codex' in a terminal, " +
3513
+ "then re-run the wizard with the already-logged-in option.";
3472
3514
  } else {
3473
3515
  pending.status = "failed";
3474
3516
  pending.message =
@@ -4036,7 +4078,7 @@ Commands:
4036
4078
  signup Create a new account (alias for: setup --signup; run locally, not via the agent)
4037
4079
  precheck Run environment checks (dry-run or allow-install)
4038
4080
  install Launch installer flows (--wizard for localhost GUI)
4039
- repair-openclaw Re-run npm install in the global openclaw package (fixes missing grammy after upgrade)
4081
+ repair-openclaw Re-run npm install in the global openclaw package (fixes missing grammy / @buape/carbon; uses --ignore-scripts so @discordjs/opus does not require build tools)
4040
4082
  gateway Gateway helpers (see subcommands below)
4041
4083
  login Re-authenticate (uses refresh token when valid; full challenge only if needed)
4042
4084
  logout Revoke current session and clear tokens
@@ -4110,7 +4152,7 @@ Examples:
4110
4152
 
4111
4153
  async function cmdRepairOpenclaw() {
4112
4154
  const { ensureOpenClawGlobalPackageDependencies } = await import("./installer-step-engine.mjs");
4113
- printInfo("Repairing global OpenClaw npm dependencies (fixes missing grammy / MODULE_NOT_FOUND)...");
4155
+ printInfo("Repairing global OpenClaw npm dependencies (fixes missing grammy, @buape/carbon, etc.)...");
4114
4156
  const r = await ensureOpenClawGlobalPackageDependencies();
4115
4157
  if (r.skipped) {
4116
4158
  printError(`Could not find global OpenClaw package (${r.reason}). Install or upgrade: npm install -g openclaw@latest`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
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.78"
20
+ "solana-traderclaw": "^1.0.80"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",