traderclaw-cli 1.0.77 → 1.0.79

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.
@@ -1604,14 +1604,14 @@ export function spawnOpenClawCodexAuthLoginChild() {
1604
1604
  if (process.platform === "win32") {
1605
1605
  return spawn("openclaw", argv, { stdio: ["pipe", "pipe", "pipe"], shell: false });
1606
1606
  }
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
1607
  if (commandExists("unbuffer")) {
1610
1608
  return spawn("unbuffer", ["openclaw", ...argv], { stdio: ["pipe", "pipe", "pipe"], shell: false });
1611
1609
  }
1612
1610
  if (commandExists("script")) {
1613
1611
  const cmdline = "openclaw models auth login --provider openai-codex";
1614
- return spawn("script", ["-q", "-c", cmdline, "/dev/null"], {
1612
+ // --return propagates the inner command's exit code (util-linux 2.38+).
1613
+ // Without it, script may exit 0 even if openclaw fails.
1614
+ return spawn("script", ["--return", "-q", "-c", cmdline, "/dev/null"], {
1615
1615
  stdio: ["pipe", "pipe", "pipe"],
1616
1616
  shell: false,
1617
1617
  });
@@ -2093,6 +2093,20 @@ export class InstallerStepEngine {
2093
2093
  }
2094
2094
  }
2095
2095
 
2096
+ const authFile = join(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
2097
+ let hasAuth = false;
2098
+ try {
2099
+ hasAuth = readFileSync(authFile, "utf-8").length > 20;
2100
+ } catch { /* file missing */ }
2101
+ if (!hasAuth) {
2102
+ throw new Error(
2103
+ "No OAuth credentials found at " + authFile + ". " +
2104
+ "The wizard OAuth flow did not save tokens (the callback may not have reached the OpenClaw CLI). " +
2105
+ "Run 'openclaw models auth login --provider openai-codex' in a terminal, " +
2106
+ "then re-run the wizard with the 'already logged in' option.",
2107
+ );
2108
+ }
2109
+
2096
2110
  const selection = resolveLlmModelSelection(provider, requestedModel);
2097
2111
  for (const msg of selection.warnings) {
2098
2112
  this.emitLog("configure_llm", "warn", msg);
@@ -2158,6 +2158,16 @@ function wizardHtml(defaults) {
2158
2158
  <button type="button" id="oauthRetryBtn" class="secondary hidden">Try sign-in again</button>
2159
2159
  </div>
2160
2160
  <p id="oauthFlowStatus" class="muted" style="margin-top:8px;" aria-live="polite">Choose OAuth and wait a moment. We will prepare your sign-in automatically.</p>
2161
+ <div id="oauthFallbackPaste" class="hidden" style="margin-top:12px;padding:12px;background:#111827;border:1px solid #334a87;border-radius:8px;">
2162
+ <p class="muted" style="margin:0 0 8px;font-size:13px;color:#ffcc70;">
2163
+ <strong>Redirect didn't reach us?</strong> Copy the full URL from the error page in your browser (it starts with <code>http://localhost:1455/auth/callback?code=…</code>) and paste it below.
2164
+ </p>
2165
+ <div style="display:flex;gap:8px;">
2166
+ <input id="oauthFallbackUrlInput" type="text" placeholder="Paste the full localhost:1455/auth/callback?code=… URL here" style="flex:1;font-size:12px;padding:8px 10px;border-radius:6px;border:1px solid #334a87;background:#0a1224;color:#e0e8ff;">
2167
+ <button type="button" id="oauthFallbackSubmitBtn" class="secondary" style="padding:8px 14px;white-space:nowrap;">Submit URL</button>
2168
+ </div>
2169
+ <p id="oauthFallbackError" class="muted hidden" style="margin:6px 0 0;font-size:12px;color:#ff6b6b;"></p>
2170
+ </div>
2161
2171
  </div>
2162
2172
  </div>
2163
2173
  <p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
@@ -2344,6 +2354,11 @@ function wizardHtml(defaults) {
2344
2354
  const oauthStepOpen = document.getElementById("oauthStepOpen");
2345
2355
  const oauthStepComplete = document.getElementById("oauthStepComplete");
2346
2356
  const oauthStepVerify = document.getElementById("oauthStepVerify");
2357
+ const oauthFallbackPaste = document.getElementById("oauthFallbackPaste");
2358
+ const oauthFallbackUrlInput = document.getElementById("oauthFallbackUrlInput");
2359
+ const oauthFallbackSubmitBtn = document.getElementById("oauthFallbackSubmitBtn");
2360
+ const oauthFallbackError = document.getElementById("oauthFallbackError");
2361
+ let oauthFallbackTimer = null;
2347
2362
 
2348
2363
  function setOauthStep(stepEl, mode) {
2349
2364
  if (!stepEl) return;
@@ -2402,8 +2417,25 @@ function wizardHtml(defaults) {
2402
2417
  oauthSessionId = null;
2403
2418
  }
2404
2419
 
2420
+ function hideFallbackPaste() {
2421
+ if (oauthFallbackPaste) oauthFallbackPaste.classList.add("hidden");
2422
+ if (oauthFallbackUrlInput) oauthFallbackUrlInput.value = "";
2423
+ if (oauthFallbackError) { oauthFallbackError.textContent = ""; oauthFallbackError.classList.add("hidden"); }
2424
+ if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
2425
+ }
2426
+
2427
+ function showFallbackPasteAfterDelay(ms) {
2428
+ hideFallbackPaste();
2429
+ oauthFallbackTimer = setTimeout(() => {
2430
+ if (oauthFallbackPaste && oauthSessionId && oauthOpenedInBrowser && !oauthWizardLoginDone) {
2431
+ oauthFallbackPaste.classList.remove("hidden");
2432
+ }
2433
+ }, ms);
2434
+ }
2435
+
2405
2436
  function resetOauthWizardState() {
2406
2437
  stopOauthPolling();
2438
+ hideFallbackPaste();
2407
2439
  oauthSessionId = null;
2408
2440
  oauthWizardLoginDone = false;
2409
2441
  oauthStartInFlight = false;
@@ -2526,6 +2558,7 @@ function wizardHtml(defaults) {
2526
2558
  if (state === "succeeded") {
2527
2559
  oauthWizardLoginDone = true;
2528
2560
  oauthSessionId = null;
2561
+ hideFallbackPaste();
2529
2562
  setOauthStep(oauthStepPrepare, "done");
2530
2563
  setOauthStep(oauthStepOpen, "done");
2531
2564
  setOauthStep(oauthStepComplete, "done");
@@ -3068,7 +3101,49 @@ function wizardHtml(defaults) {
3068
3101
  setOauthStep(oauthStepOpen, "done");
3069
3102
  setOauthStep(oauthStepComplete, "active");
3070
3103
  setOauthStep(oauthStepVerify, "active");
3071
- setOauthStatus("Complete ChatGPT approval in this same browser, then return here. We detect completion automatically.");
3104
+ setOauthStatus("Complete ChatGPT approval in this browser, then return here. We detect completion automatically.");
3105
+ showFallbackPasteAfterDelay(15_000);
3106
+ });
3107
+ }
3108
+
3109
+ if (oauthFallbackSubmitBtn) {
3110
+ oauthFallbackSubmitBtn.addEventListener("click", async () => {
3111
+ const raw = (oauthFallbackUrlInput && oauthFallbackUrlInput.value || "").trim();
3112
+ if (!raw || !raw.includes("code=")) {
3113
+ if (oauthFallbackError) {
3114
+ oauthFallbackError.textContent = "Paste the full URL from the browser address bar. It must contain code=…";
3115
+ oauthFallbackError.classList.remove("hidden");
3116
+ }
3117
+ return;
3118
+ }
3119
+ if (oauthFallbackError) oauthFallbackError.classList.add("hidden");
3120
+ oauthFallbackSubmitBtn.disabled = true;
3121
+ oauthFallbackSubmitBtn.textContent = "Submitting…";
3122
+ try {
3123
+ const res = await fetch("/api/llm/oauth/submit-callback-url", {
3124
+ method: "POST",
3125
+ headers: { "content-type": "application/json" },
3126
+ body: JSON.stringify({ sessionId: oauthSessionId, callbackUrl: raw }),
3127
+ });
3128
+ const data = await res.json().catch(() => ({}));
3129
+ if (res.ok && data.ok) {
3130
+ hideFallbackPaste();
3131
+ setOauthStatus("Callback received! Waiting for OpenClaw to finish…", false);
3132
+ } else {
3133
+ if (oauthFallbackError) {
3134
+ oauthFallbackError.textContent = data.message || data.error || "Could not submit the URL. Try again.";
3135
+ oauthFallbackError.classList.remove("hidden");
3136
+ }
3137
+ }
3138
+ } catch (err) {
3139
+ if (oauthFallbackError) {
3140
+ oauthFallbackError.textContent = err.message || "Request failed.";
3141
+ oauthFallbackError.classList.remove("hidden");
3142
+ }
3143
+ } finally {
3144
+ oauthFallbackSubmitBtn.disabled = false;
3145
+ oauthFallbackSubmitBtn.textContent = "Submit URL";
3146
+ }
3072
3147
  });
3073
3148
  }
3074
3149
 
@@ -3182,6 +3257,32 @@ async function cmdInstall(args) {
3182
3257
  }
3183
3258
  }
3184
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
+
3185
3286
  function killOauthSession(sessionId, signal = "SIGTERM") {
3186
3287
  const s = oauthSessions.get(sessionId);
3187
3288
  if (!s) return;
@@ -3313,6 +3414,13 @@ async function cmdInstall(args) {
3313
3414
  killOauthSession(id);
3314
3415
  }
3315
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
+
3316
3424
  const sessionId = randomUUID();
3317
3425
  const child = spawnOpenClawCodexAuthLoginChild();
3318
3426
 
@@ -3327,6 +3435,7 @@ async function cmdInstall(args) {
3327
3435
  /* ignore */
3328
3436
  }
3329
3437
  oauthSessions.delete(sessionId);
3438
+ startCallbackProxy();
3330
3439
  respondJson(504, {
3331
3440
  ok: false,
3332
3441
  error: "oauth_url_timeout",
@@ -3365,6 +3474,7 @@ async function cmdInstall(args) {
3365
3474
  });
3366
3475
  child.on("error", (err) => {
3367
3476
  clearTimeout(urlTimeout);
3477
+ startCallbackProxy();
3368
3478
  if (responded) return;
3369
3479
  responded = true;
3370
3480
  oauthSessions.delete(sessionId);
@@ -3372,6 +3482,7 @@ async function cmdInstall(args) {
3372
3482
  });
3373
3483
  child.on("close", (code) => {
3374
3484
  clearTimeout(urlTimeout);
3485
+ startCallbackProxy();
3375
3486
  if (!responded) {
3376
3487
  responded = true;
3377
3488
  oauthSessions.delete(sessionId);
@@ -3391,9 +3502,15 @@ async function cmdInstall(args) {
3391
3502
  pending.updatedAt = Date.now();
3392
3503
  pending.exitCode = typeof code === "number" ? code : null;
3393
3504
  pending.detail = stripAnsi(combined).slice(-4000);
3394
- if (code === 0) {
3505
+ if (code === 0 && hasOpenaiCodexAuthTokens()) {
3395
3506
  pending.status = "succeeded";
3396
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.";
3397
3514
  } else {
3398
3515
  pending.status = "failed";
3399
3516
  pending.message =
@@ -3486,6 +3603,41 @@ async function cmdInstall(args) {
3486
3603
  return;
3487
3604
  }
3488
3605
 
3606
+ if (req.method === "POST" && req.url === "/api/llm/oauth/submit-callback-url") {
3607
+ const body = await parseJsonBody(req).catch(() => ({}));
3608
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
3609
+ const callbackUrl = typeof body.callbackUrl === "string" ? body.callbackUrl.trim() : "";
3610
+ if (!callbackUrl || !callbackUrl.includes("code=")) {
3611
+ respondJson(400, { ok: false, error: "invalid_url", message: "URL must contain a code= parameter." });
3612
+ return;
3613
+ }
3614
+ const s = sessionId ? oauthSessions.get(sessionId) : findActiveOauthSession();
3615
+ if (!s || !s.child) {
3616
+ respondJson(404, { ok: false, error: "no_active_session", message: "No active OAuth session. Click Try sign-in again." });
3617
+ return;
3618
+ }
3619
+ try {
3620
+ if (s.child.stdin && !s.child.stdin.writableEnded && !s.child.stdin.destroyed) {
3621
+ s.child.stdin.write(`${callbackUrl}\n`, () => {
3622
+ setTimeout(() => {
3623
+ try {
3624
+ if (s.child && s.child.stdin && !s.child.stdin.destroyed && !s.child.stdin.writableEnded) {
3625
+ s.child.stdin.end();
3626
+ }
3627
+ } catch { /* ignore */ }
3628
+ }, 100);
3629
+ });
3630
+ s.updatedAt = Date.now();
3631
+ respondJson(200, { ok: true, message: "Callback URL submitted. Waiting for OpenClaw to complete…" });
3632
+ } else {
3633
+ respondJson(500, { ok: false, error: "stdin_closed", message: "OpenClaw process stdin is closed. Click Try sign-in again." });
3634
+ }
3635
+ } catch (err) {
3636
+ respondJson(500, { ok: false, error: "write_error", message: err.message || "Failed to write to OpenClaw." });
3637
+ }
3638
+ return;
3639
+ }
3640
+
3489
3641
  if (req.method === "POST" && req.url === "/api/llm/oauth/cancel") {
3490
3642
  const body = await parseJsonBody(req).catch(() => ({}));
3491
3643
  const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.77",
3
+ "version": "1.0.79",
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.77"
20
+ "solana-traderclaw": "^1.0.79"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",