traderclaw-cli 1.0.75 → 1.0.77
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 +26 -4
- package/bin/openclaw-trader.mjs +399 -185
- package/package.json +2 -2
|
@@ -1594,16 +1594,38 @@ function configureOpenClawLlmModelPrimaryOnly({ provider, model }, configPath =
|
|
|
1594
1594
|
return { configPath, provider, model };
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
|
+
/**
|
|
1598
|
+
* Spawns `openclaw models auth login --provider openai-codex` with a pseudo-TTY when possible.
|
|
1599
|
+
* The CLI often exits immediately when stdin/stdout are plain pipes (no TTY). On Unix, `script(1)`
|
|
1600
|
+
* allocates a PTY so the same flow works as in an interactive terminal.
|
|
1601
|
+
*/
|
|
1602
|
+
export function spawnOpenClawCodexAuthLoginChild() {
|
|
1603
|
+
const argv = ["models", "auth", "login", "--provider", "openai-codex"];
|
|
1604
|
+
if (process.platform === "win32") {
|
|
1605
|
+
return spawn("openclaw", argv, { stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
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
|
+
if (commandExists("unbuffer")) {
|
|
1610
|
+
return spawn("unbuffer", ["openclaw", ...argv], { stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
1611
|
+
}
|
|
1612
|
+
if (commandExists("script")) {
|
|
1613
|
+
const cmdline = "openclaw models auth login --provider openai-codex";
|
|
1614
|
+
return spawn("script", ["-q", "-c", cmdline, "/dev/null"], {
|
|
1615
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1616
|
+
shell: false,
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
return spawn("openclaw", argv, { stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1597
1622
|
/**
|
|
1598
1623
|
* Runs `openclaw models auth login --provider openai-codex` and feeds the pasted redirect URL or code on stdin
|
|
1599
1624
|
* when the CLI prompts (with a timed fallback for non-interactive / SSH).
|
|
1600
1625
|
*/
|
|
1601
1626
|
function runOpenClawCodexOAuthLogin(paste, emitLog) {
|
|
1602
1627
|
return new Promise((resolve, reject) => {
|
|
1603
|
-
const child =
|
|
1604
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1605
|
-
shell: false,
|
|
1606
|
-
});
|
|
1628
|
+
const child = spawnOpenClawCodexAuthLoginChild();
|
|
1607
1629
|
|
|
1608
1630
|
let stdout = "";
|
|
1609
1631
|
let stderr = "";
|
package/bin/openclaw-trader.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname, join } from "path";
|
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
import { randomUUID, createPrivateKey, sign as cryptoSign } from "crypto";
|
|
9
|
-
import { execFile, execSync
|
|
9
|
+
import { execFile, execSync } from "child_process";
|
|
10
10
|
import { promisify } from "util";
|
|
11
11
|
import { createServer } from "http";
|
|
12
12
|
import { resolvePluginPackageRoot } from "./resolve-plugin-root.mjs";
|
|
@@ -2082,6 +2082,13 @@ function wizardHtml(defaults) {
|
|
|
2082
2082
|
.oauth-row input[readonly] { flex:1 1 280px; font-size:12px; }
|
|
2083
2083
|
.oauth-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
|
|
2084
2084
|
.oauth-actions button.secondary { background:#334a87; }
|
|
2085
|
+
.oauth-guide { background:#0a1f2e; border:1px solid #2a7a6a; border-radius:10px; padding:14px; margin-top:8px; }
|
|
2086
|
+
.oauth-guide ol { margin:10px 0 0 18px; padding:0; color:#d9e8ff; font-size:13px; line-height:1.55; }
|
|
2087
|
+
.oauth-guide li { margin-bottom:7px; }
|
|
2088
|
+
.oauth-step.pending { color:#9cb0de; }
|
|
2089
|
+
.oauth-step.active { color:#9ee6ff; font-weight:700; }
|
|
2090
|
+
.oauth-step.done { color:#78f0a9; }
|
|
2091
|
+
.oauth-step.error { color:#ff9b9b; font-weight:700; }
|
|
2085
2092
|
.ok-banner { color:#78f0a9; font-size:13px; margin-top:8px; }
|
|
2086
2093
|
.err-banner { color:#ff6b6b; font-size:13px; margin-top:8px; }
|
|
2087
2094
|
</style>
|
|
@@ -2129,36 +2136,29 @@ function wizardHtml(defaults) {
|
|
|
2129
2136
|
<p class="muted">Written to OpenClaw <code>config.env</code> for the selected provider. If you do not choose a model manually, the installer picks a safe default.</p>
|
|
2130
2137
|
</div>
|
|
2131
2138
|
<div style="margin-top:12px;" id="llmOauthBlock" class="hidden">
|
|
2132
|
-
<p class="muted" style="margin-bottom:
|
|
2133
|
-
|
|
2139
|
+
<p class="muted" style="margin-bottom:12px;">
|
|
2140
|
+
OAuth here is fully guided. After you choose OAuth, we start OpenClaw login automatically. Use the sign-in button below, then complete ChatGPT in this browser. You should not need to copy any code.
|
|
2134
2141
|
</p>
|
|
2135
|
-
<div class="oauth-
|
|
2136
|
-
<
|
|
2142
|
+
<div class="oauth-guide">
|
|
2143
|
+
<p class="muted" style="margin-top:0;">
|
|
2144
|
+
<strong>Important:</strong> Complete the ChatGPT sign-in in this browser, then return to this tab. The wizard detects the result automatically.
|
|
2145
|
+
</p>
|
|
2146
|
+
<p class="muted" style="margin-top:4px;font-size:13px;color:#7a8ba8;">
|
|
2147
|
+
<strong>Remote VPS?</strong> Forward port <code>1455</code> alongside the wizard port:
|
|
2148
|
+
<code style="display:block;margin-top:4px;padding:4px 8px;background:#111827;border-radius:4px;font-size:12px;">ssh -L 17890:127.0.0.1:17890 -L 1455:127.0.0.1:1455 user@your-vps</code>
|
|
2149
|
+
</p>
|
|
2137
2150
|
<ol>
|
|
2138
|
-
<li
|
|
2139
|
-
<li
|
|
2140
|
-
<li
|
|
2141
|
-
<li
|
|
2151
|
+
<li class="oauth-step pending" id="oauthStepPrepare">Preparing ChatGPT sign-in...</li>
|
|
2152
|
+
<li class="oauth-step pending" id="oauthStepOpen">Open ChatGPT sign-in in this browser.</li>
|
|
2153
|
+
<li class="oauth-step pending" id="oauthStepComplete">Finish ChatGPT approval, then return here.</li>
|
|
2154
|
+
<li class="oauth-step pending" id="oauthStepVerify">We detect completion automatically.</li>
|
|
2142
2155
|
</ol>
|
|
2143
2156
|
<div class="oauth-actions">
|
|
2144
|
-
<
|
|
2145
|
-
<button type="button" id="
|
|
2157
|
+
<a id="oauthOpenUrlBtn" class="secondary" href="#" target="_blank" rel="noopener noreferrer" style="display:inline-block;padding:10px 14px;border-radius:8px;background:#2d7dff;color:#fff;text-decoration:none;font-weight:600;opacity:.55;pointer-events:none;">Open ChatGPT sign-in</a>
|
|
2158
|
+
<button type="button" id="oauthRetryBtn" class="secondary hidden">Try sign-in again</button>
|
|
2146
2159
|
</div>
|
|
2147
|
-
<
|
|
2148
|
-
<label style="flex:1 1 100%; font-size:12px; color:#9cb0de;">Sign-in URL (open this in your browser — this is not what you paste back)</label>
|
|
2149
|
-
<input type="text" id="oauthUrlDisplay" readonly placeholder="Click “Get ChatGPT sign-in link” first" />
|
|
2150
|
-
<button type="button" id="oauthCopyUrlBtn" class="secondary" disabled>Copy URL</button>
|
|
2151
|
-
<a id="oauthOpenUrlBtn" class="secondary" href="#" target="_blank" rel="noopener noreferrer" style="display:inline-block;padding:10px 14px;border-radius:8px;background:#2d7dff;color:#fff;text-decoration:none;font-weight:600;">Open in browser</a>
|
|
2152
|
-
</div>
|
|
2153
|
-
<p id="oauthFlowStatus" class="muted" style="margin-top:8px;" aria-live="polite"></p>
|
|
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>
|
|
2154
2161
|
</div>
|
|
2155
|
-
<label style="margin-top:14px;">Paste redirect URL or authorization code (step 2 → 3)</label>
|
|
2156
|
-
<textarea id="llmOAuthPaste" autocomplete="off" placeholder="After signing in at ChatGPT, paste here: the full http://127.0.0.1:1455/... or http://localhost:1455/... URL from your browser, OR the code string if the prompt asks for the code only."></textarea>
|
|
2157
|
-
<label style="display:flex; align-items:flex-start; gap:8px; font-size:13px; color:#9cb0de; margin-top:8px; cursor:pointer;">
|
|
2158
|
-
<input id="llmOAuthSkipLogin" type="checkbox" style="width:auto; margin-top:3px;" />
|
|
2159
|
-
<span>I already completed <code>openclaw models auth login --provider openai-codex</code> in a terminal on <strong>this</strong> machine (skip the buttons above)</span>
|
|
2160
|
-
</label>
|
|
2161
|
-
<p class="muted">SSH tip: the browser must reach your machine for localhost callbacks; if that fails, run the same login command in a desktop terminal on this host, then use the checkbox.</p>
|
|
2162
2162
|
</div>
|
|
2163
2163
|
<p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
|
|
2164
2164
|
<div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
|
|
@@ -2303,8 +2303,6 @@ function wizardHtml(defaults) {
|
|
|
2303
2303
|
const llmOauthProviderNote = document.getElementById("llmOauthProviderNote");
|
|
2304
2304
|
const llmApiKeyBlock = document.getElementById("llmApiKeyBlock");
|
|
2305
2305
|
const llmOauthBlock = document.getElementById("llmOauthBlock");
|
|
2306
|
-
const llmOAuthPasteEl = document.getElementById("llmOAuthPaste");
|
|
2307
|
-
const llmOAuthSkipLoginEl = document.getElementById("llmOAuthSkipLogin");
|
|
2308
2306
|
const telegramTokenEl = document.getElementById("telegramToken");
|
|
2309
2307
|
const llmLoadStateEl = document.getElementById("llmLoadState");
|
|
2310
2308
|
const llmLoadingHintEl = document.getElementById("llmLoadingHint");
|
|
@@ -2334,16 +2332,56 @@ function wizardHtml(defaults) {
|
|
|
2334
2332
|
let savedApiKeyProvider = "";
|
|
2335
2333
|
let oauthSessionId = null;
|
|
2336
2334
|
let oauthWizardLoginDone = false;
|
|
2335
|
+
let oauthStartInFlight = false;
|
|
2336
|
+
let oauthPollTimer = null;
|
|
2337
|
+
let oauthOpenedInBrowser = false;
|
|
2338
|
+
let oauthFlowStartedAtMs = 0;
|
|
2337
2339
|
|
|
2338
|
-
const oauthGetLinkBtn = document.getElementById("oauthGetLinkBtn");
|
|
2339
|
-
const oauthSubmitBtn = document.getElementById("oauthSubmitBtn");
|
|
2340
|
-
const oauthUrlRow = document.getElementById("oauthUrlRow");
|
|
2341
|
-
const oauthUrlDisplay = document.getElementById("oauthUrlDisplay");
|
|
2342
|
-
const oauthCopyUrlBtn = document.getElementById("oauthCopyUrlBtn");
|
|
2343
2340
|
const oauthOpenUrlBtn = document.getElementById("oauthOpenUrlBtn");
|
|
2341
|
+
const oauthRetryBtn = document.getElementById("oauthRetryBtn");
|
|
2344
2342
|
const oauthFlowStatus = document.getElementById("oauthFlowStatus");
|
|
2343
|
+
const oauthStepPrepare = document.getElementById("oauthStepPrepare");
|
|
2344
|
+
const oauthStepOpen = document.getElementById("oauthStepOpen");
|
|
2345
|
+
const oauthStepComplete = document.getElementById("oauthStepComplete");
|
|
2346
|
+
const oauthStepVerify = document.getElementById("oauthStepVerify");
|
|
2347
|
+
|
|
2348
|
+
function setOauthStep(stepEl, mode) {
|
|
2349
|
+
if (!stepEl) return;
|
|
2350
|
+
stepEl.classList.remove("pending", "active", "done", "error");
|
|
2351
|
+
stepEl.classList.add(mode || "pending");
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function setOauthStatus(text, isError = false, isSuccess = false) {
|
|
2355
|
+
if (!oauthFlowStatus) return;
|
|
2356
|
+
oauthFlowStatus.className = isError ? "err-banner" : isSuccess ? "ok-banner" : "muted";
|
|
2357
|
+
oauthFlowStatus.textContent = text;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
function setOauthOpenButton(url) {
|
|
2361
|
+
if (!oauthOpenUrlBtn) return;
|
|
2362
|
+
if (!url) {
|
|
2363
|
+
oauthOpenUrlBtn.href = "#";
|
|
2364
|
+
oauthOpenUrlBtn.style.opacity = ".55";
|
|
2365
|
+
oauthOpenUrlBtn.style.pointerEvents = "none";
|
|
2366
|
+
oauthOpenUrlBtn.setAttribute("aria-disabled", "true");
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
oauthOpenUrlBtn.href = url;
|
|
2370
|
+
oauthOpenUrlBtn.style.opacity = "1";
|
|
2371
|
+
oauthOpenUrlBtn.style.pointerEvents = "auto";
|
|
2372
|
+
oauthOpenUrlBtn.removeAttribute("aria-disabled");
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function stopOauthPolling() {
|
|
2376
|
+
if (oauthPollTimer) {
|
|
2377
|
+
clearInterval(oauthPollTimer);
|
|
2378
|
+
oauthPollTimer = null;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2345
2381
|
|
|
2346
2382
|
async function cancelOauthSession() {
|
|
2383
|
+
stopOauthPolling();
|
|
2384
|
+
oauthStartInFlight = false;
|
|
2347
2385
|
if (!oauthSessionId) {
|
|
2348
2386
|
try {
|
|
2349
2387
|
await fetch("/api/llm/oauth/cancel", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
@@ -2365,17 +2403,19 @@ function wizardHtml(defaults) {
|
|
|
2365
2403
|
}
|
|
2366
2404
|
|
|
2367
2405
|
function resetOauthWizardState() {
|
|
2406
|
+
stopOauthPolling();
|
|
2368
2407
|
oauthSessionId = null;
|
|
2369
2408
|
oauthWizardLoginDone = false;
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
if (
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2409
|
+
oauthStartInFlight = false;
|
|
2410
|
+
oauthOpenedInBrowser = false;
|
|
2411
|
+
oauthFlowStartedAtMs = 0;
|
|
2412
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2413
|
+
setOauthOpenButton("");
|
|
2414
|
+
setOauthStep(oauthStepPrepare, "pending");
|
|
2415
|
+
setOauthStep(oauthStepOpen, "pending");
|
|
2416
|
+
setOauthStep(oauthStepComplete, "pending");
|
|
2417
|
+
setOauthStep(oauthStepVerify, "pending");
|
|
2418
|
+
setOauthStatus("Choose OAuth and wait a moment. We will prepare your sign-in automatically.");
|
|
2379
2419
|
}
|
|
2380
2420
|
|
|
2381
2421
|
(function initLlmAuthDefaults() {
|
|
@@ -2384,10 +2424,6 @@ function wizardHtml(defaults) {
|
|
|
2384
2424
|
llmAuthModeOauth.checked = true;
|
|
2385
2425
|
llmAuthModeApiKey.checked = false;
|
|
2386
2426
|
}
|
|
2387
|
-
llmOAuthPasteEl.value = ${JSON.stringify(defaults.llmOAuthPaste || "")};
|
|
2388
|
-
if (${defaults.llmOAuthSkipLogin === true ? "true" : "false"}) {
|
|
2389
|
-
llmOAuthSkipLoginEl.checked = true;
|
|
2390
|
-
}
|
|
2391
2427
|
applyLlmAuthModeUi();
|
|
2392
2428
|
})();
|
|
2393
2429
|
|
|
@@ -2408,6 +2444,9 @@ function wizardHtml(defaults) {
|
|
|
2408
2444
|
llmOauthProviderNote.classList.remove("hidden");
|
|
2409
2445
|
llmApiKeyBlock.classList.add("hidden");
|
|
2410
2446
|
llmOauthBlock.classList.remove("hidden");
|
|
2447
|
+
if (!oauthWizardLoginDone && !oauthStartInFlight && !oauthSessionId) {
|
|
2448
|
+
void startOauthGuidedFlow();
|
|
2449
|
+
}
|
|
2411
2450
|
} else {
|
|
2412
2451
|
void cancelOauthSession();
|
|
2413
2452
|
resetOauthWizardState();
|
|
@@ -2429,13 +2468,157 @@ function wizardHtml(defaults) {
|
|
|
2429
2468
|
function hasRequiredInputs() {
|
|
2430
2469
|
if (!llmCatalogReady || !Boolean(telegramTokenEl.value.trim())) return false;
|
|
2431
2470
|
if (isOauthMode()) {
|
|
2432
|
-
|
|
2433
|
-
if (llmOAuthSkipLoginEl.checked) return true;
|
|
2434
|
-
return Boolean(llmOAuthPasteEl.value.trim());
|
|
2471
|
+
return oauthWizardLoginDone === true;
|
|
2435
2472
|
}
|
|
2436
2473
|
return Boolean(llmProviderEl.value.trim()) && Boolean(llmCredentialEl.value.trim());
|
|
2437
2474
|
}
|
|
2438
2475
|
|
|
2476
|
+
async function pollOauthStatusOnce() {
|
|
2477
|
+
if (!oauthSessionId || !isOauthMode()) return;
|
|
2478
|
+
try {
|
|
2479
|
+
const sessionId = oauthSessionId;
|
|
2480
|
+
const res = await fetch("/api/llm/oauth/status?sessionId=" + encodeURIComponent(sessionId));
|
|
2481
|
+
const data = await res.json().catch(() => ({}));
|
|
2482
|
+
if (!res.ok) {
|
|
2483
|
+
stopOauthPolling();
|
|
2484
|
+
oauthStartInFlight = false;
|
|
2485
|
+
if (data && data.error === "invalid_or_expired_session") {
|
|
2486
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2487
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2488
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2489
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2490
|
+
setOauthStatus(
|
|
2491
|
+
"Automatic OAuth session expired. This flow requires the wizard and OpenClaw on the same machine and the same browser. Click Try sign-in again.",
|
|
2492
|
+
true,
|
|
2493
|
+
);
|
|
2494
|
+
} else {
|
|
2495
|
+
setOauthStatus((data && (data.message || data.error)) || "Could not read OAuth status.", true);
|
|
2496
|
+
}
|
|
2497
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2498
|
+
updateStartButtonState();
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
const state = typeof data.state === "string" ? data.state : "unknown";
|
|
2502
|
+
if (state === "awaiting_browser_callback") {
|
|
2503
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2504
|
+
setOauthStep(oauthStepOpen, oauthOpenedInBrowser ? "done" : "active");
|
|
2505
|
+
setOauthStep(oauthStepComplete, oauthOpenedInBrowser ? "active" : "pending");
|
|
2506
|
+
setOauthStep(oauthStepVerify, oauthOpenedInBrowser ? "active" : "pending");
|
|
2507
|
+
if (oauthOpenedInBrowser) {
|
|
2508
|
+
const elapsed = oauthFlowStartedAtMs > 0 ? Math.floor((Date.now() - oauthFlowStartedAtMs) / 1000) : 0;
|
|
2509
|
+
if (elapsed >= 90) {
|
|
2510
|
+
setOauthStatus(
|
|
2511
|
+
"Still waiting for callback. Keep using this same browser on this same machine. If you opened OAuth on another computer, this automatic flow cannot complete.",
|
|
2512
|
+
true,
|
|
2513
|
+
);
|
|
2514
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2515
|
+
} else {
|
|
2516
|
+
setOauthStatus("Waiting for ChatGPT approval to finish. Return here after you continue.", false);
|
|
2517
|
+
}
|
|
2518
|
+
} else {
|
|
2519
|
+
setOauthStatus("Step 2: click Open ChatGPT sign-in, then complete approval and return here.", false);
|
|
2520
|
+
}
|
|
2521
|
+
updateStartButtonState();
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
stopOauthPolling();
|
|
2525
|
+
oauthStartInFlight = false;
|
|
2526
|
+
if (state === "succeeded") {
|
|
2527
|
+
oauthWizardLoginDone = true;
|
|
2528
|
+
oauthSessionId = null;
|
|
2529
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2530
|
+
setOauthStep(oauthStepOpen, "done");
|
|
2531
|
+
setOauthStep(oauthStepComplete, "done");
|
|
2532
|
+
setOauthStep(oauthStepVerify, "done");
|
|
2533
|
+
setOauthStatus(data.message || "ChatGPT is connected. You can start installation now.", false, true);
|
|
2534
|
+
setOauthOpenButton("");
|
|
2535
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2536
|
+
updateStartButtonState();
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
const errorText = data.message || data.error || "OAuth login failed.";
|
|
2540
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2541
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2542
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2543
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2544
|
+
setOauthStatus(
|
|
2545
|
+
errorText + " Automatic mode only works on the same machine and browser. Click Try sign-in again.",
|
|
2546
|
+
true,
|
|
2547
|
+
);
|
|
2548
|
+
setOauthOpenButton("");
|
|
2549
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2550
|
+
updateStartButtonState();
|
|
2551
|
+
} catch (err) {
|
|
2552
|
+
stopOauthPolling();
|
|
2553
|
+
oauthStartInFlight = false;
|
|
2554
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2555
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2556
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2557
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2558
|
+
setOauthStatus(err && err.message ? String(err.message) : "OAuth status request failed.", true);
|
|
2559
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2560
|
+
updateStartButtonState();
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
function beginOauthStatusPolling() {
|
|
2565
|
+
stopOauthPolling();
|
|
2566
|
+
oauthPollTimer = setInterval(() => {
|
|
2567
|
+
void pollOauthStatusOnce();
|
|
2568
|
+
}, 1500);
|
|
2569
|
+
void pollOauthStatusOnce();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
async function startOauthGuidedFlow() {
|
|
2573
|
+
if (!isOauthMode() || oauthStartInFlight) return;
|
|
2574
|
+
oauthStartInFlight = true;
|
|
2575
|
+
oauthWizardLoginDone = false;
|
|
2576
|
+
oauthOpenedInBrowser = false;
|
|
2577
|
+
oauthFlowStartedAtMs = Date.now();
|
|
2578
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2579
|
+
setOauthStep(oauthStepPrepare, "active");
|
|
2580
|
+
setOauthStep(oauthStepOpen, "pending");
|
|
2581
|
+
setOauthStep(oauthStepComplete, "pending");
|
|
2582
|
+
setOauthStep(oauthStepVerify, "pending");
|
|
2583
|
+
setOauthStatus("Preparing ChatGPT sign-in link...");
|
|
2584
|
+
setOauthOpenButton("");
|
|
2585
|
+
updateStartButtonState();
|
|
2586
|
+
try {
|
|
2587
|
+
const res = await fetch("/api/llm/oauth/start", { method: "POST" });
|
|
2588
|
+
const data = await res.json().catch(() => ({}));
|
|
2589
|
+
if (!res.ok) {
|
|
2590
|
+
oauthStartInFlight = false;
|
|
2591
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2592
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2593
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2594
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2595
|
+
setOauthStatus((data && (data.message || data.error)) || "Could not start OAuth sign-in.", true);
|
|
2596
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2597
|
+
updateStartButtonState();
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
oauthSessionId = typeof data.sessionId === "string" ? data.sessionId : "";
|
|
2601
|
+
const authUrl = typeof data.authUrl === "string" ? data.authUrl : "";
|
|
2602
|
+
if (!oauthSessionId || !authUrl) {
|
|
2603
|
+
oauthStartInFlight = false;
|
|
2604
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2605
|
+
setOauthStatus("OpenClaw did not return a valid sign-in URL. Try again.", true);
|
|
2606
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
setOauthOpenButton(authUrl);
|
|
2610
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2611
|
+
setOauthStep(oauthStepOpen, "active");
|
|
2612
|
+
setOauthStatus("Step 2: click Open ChatGPT sign-in and continue in this same browser.");
|
|
2613
|
+
beginOauthStatusPolling();
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
oauthStartInFlight = false;
|
|
2616
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2617
|
+
setOauthStatus(err && err.message ? String(err.message) : "Could not start OAuth sign-in.", true);
|
|
2618
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2439
2622
|
/** All-or-nothing: 0 or 4 non-empty X fields; partial is invalid. */
|
|
2440
2623
|
function xWizardFieldsStatus() {
|
|
2441
2624
|
const fields = [
|
|
@@ -2612,8 +2795,8 @@ function wizardHtml(defaults) {
|
|
|
2612
2795
|
llmProvider: oauth ? "openai-codex" : llmProviderEl.value.trim(),
|
|
2613
2796
|
llmModel: llmModelEl.value.trim(),
|
|
2614
2797
|
llmCredential: oauth ? "" : llmCredentialEl.value.trim(),
|
|
2615
|
-
llmOAuthPaste:
|
|
2616
|
-
llmOAuthSkipLogin:
|
|
2798
|
+
llmOAuthPaste: "",
|
|
2799
|
+
llmOAuthSkipLogin: oauth ? true : false,
|
|
2617
2800
|
apiKey: document.getElementById("apiKey").value.trim(),
|
|
2618
2801
|
telegramToken: document.getElementById("telegramToken").value.trim(),
|
|
2619
2802
|
referralCode: document.getElementById("referralCode").value.trim(),
|
|
@@ -2622,16 +2805,12 @@ function wizardHtml(defaults) {
|
|
|
2622
2805
|
xAccessTokenMain: xAccessTokenMainEl.value.trim(),
|
|
2623
2806
|
xAccessTokenMainSecret: xAccessTokenMainSecretEl.value.trim(),
|
|
2624
2807
|
};
|
|
2625
|
-
if (oauth && oauthWizardLoginDone) {
|
|
2626
|
-
payload.llmOAuthSkipLogin = true;
|
|
2627
|
-
payload.llmOAuthPaste = "";
|
|
2628
|
-
}
|
|
2629
2808
|
if (oauth) {
|
|
2630
|
-
if (!
|
|
2809
|
+
if (!oauthWizardLoginDone) {
|
|
2631
2810
|
stateEl.textContent = "blocked";
|
|
2632
2811
|
readyEl.textContent = "";
|
|
2633
2812
|
manualEl.textContent =
|
|
2634
|
-
"Codex OAuth
|
|
2813
|
+
"Codex OAuth is not completed yet. Use the guided sign-in above in this same browser and wait for the green success message.";
|
|
2635
2814
|
return;
|
|
2636
2815
|
}
|
|
2637
2816
|
} else if (!payload.llmProvider || !payload.llmCredential) {
|
|
@@ -2882,123 +3061,23 @@ function wizardHtml(defaults) {
|
|
|
2882
3061
|
llmAuthModeApiKey.addEventListener("change", onLlmAuthModeChange);
|
|
2883
3062
|
llmAuthModeOauth.addEventListener("change", onLlmAuthModeChange);
|
|
2884
3063
|
llmCredentialEl.addEventListener("input", updateStartButtonState);
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
oauthFlowStatus.textContent = "Starting OpenClaw (same as running models auth login in a terminal)...";
|
|
2894
|
-
}
|
|
2895
|
-
oauthGetLinkBtn.disabled = true;
|
|
2896
|
-
try {
|
|
2897
|
-
const res = await fetch("/api/llm/oauth/start", { method: "POST" });
|
|
2898
|
-
const data = await res.json().catch(() => ({}));
|
|
2899
|
-
if (!res.ok) {
|
|
2900
|
-
if (oauthFlowStatus) {
|
|
2901
|
-
oauthFlowStatus.className = "err-banner";
|
|
2902
|
-
oauthFlowStatus.textContent = data.message || data.error || "Could not get sign-in URL.";
|
|
2903
|
-
}
|
|
2904
|
-
oauthGetLinkBtn.disabled = false;
|
|
2905
|
-
return;
|
|
2906
|
-
}
|
|
2907
|
-
oauthSessionId = data.sessionId;
|
|
2908
|
-
if (oauthUrlDisplay) oauthUrlDisplay.value = data.authUrl || "";
|
|
2909
|
-
if (oauthUrlRow) oauthUrlRow.classList.remove("hidden");
|
|
2910
|
-
if (oauthCopyUrlBtn) oauthCopyUrlBtn.disabled = !data.authUrl;
|
|
2911
|
-
if (oauthOpenUrlBtn && data.authUrl) {
|
|
2912
|
-
oauthOpenUrlBtn.href = data.authUrl;
|
|
2913
|
-
oauthOpenUrlBtn.removeAttribute("aria-disabled");
|
|
2914
|
-
}
|
|
2915
|
-
if (oauthSubmitBtn) oauthSubmitBtn.disabled = false;
|
|
2916
|
-
if (oauthFlowStatus) {
|
|
2917
|
-
oauthFlowStatus.className = "muted";
|
|
2918
|
-
oauthFlowStatus.textContent =
|
|
2919
|
-
"Open this URL in your browser (on your PC if you use SSH). After ChatGPT sign-in, paste the localhost redirect URL or the code, then click Submit.";
|
|
2920
|
-
}
|
|
2921
|
-
} catch (err) {
|
|
2922
|
-
if (oauthFlowStatus) {
|
|
2923
|
-
oauthFlowStatus.className = "err-banner";
|
|
2924
|
-
oauthFlowStatus.textContent = err && err.message ? String(err.message) : "Request failed.";
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
|
-
oauthGetLinkBtn.disabled = false;
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
if (oauthSubmitBtn) {
|
|
2932
|
-
oauthSubmitBtn.addEventListener("click", async () => {
|
|
2933
|
-
const paste = llmOAuthPasteEl.value.trim();
|
|
2934
|
-
if (!paste) {
|
|
2935
|
-
if (oauthFlowStatus) {
|
|
2936
|
-
oauthFlowStatus.className = "err-banner";
|
|
2937
|
-
oauthFlowStatus.textContent = "Paste the redirect URL (from the browser address bar) or the authorization code first.";
|
|
2938
|
-
}
|
|
2939
|
-
return;
|
|
2940
|
-
}
|
|
2941
|
-
if (!oauthSessionId) {
|
|
2942
|
-
if (oauthFlowStatus) {
|
|
2943
|
-
oauthFlowStatus.className = "err-banner";
|
|
2944
|
-
oauthFlowStatus.textContent = "Click “Get ChatGPT sign-in link” first, or use the “already completed login in terminal” checkbox.";
|
|
2945
|
-
}
|
|
2946
|
-
return;
|
|
2947
|
-
}
|
|
2948
|
-
oauthSubmitBtn.disabled = true;
|
|
2949
|
-
if (oauthFlowStatus) {
|
|
2950
|
-
oauthFlowStatus.className = "muted";
|
|
2951
|
-
oauthFlowStatus.textContent = "Sending to OpenClaw...";
|
|
2952
|
-
}
|
|
2953
|
-
try {
|
|
2954
|
-
const res = await fetch("/api/llm/oauth/submit", {
|
|
2955
|
-
method: "POST",
|
|
2956
|
-
headers: { "content-type": "application/json" },
|
|
2957
|
-
body: JSON.stringify({ sessionId: oauthSessionId, paste }),
|
|
2958
|
-
});
|
|
2959
|
-
const data = await res.json().catch(() => ({}));
|
|
2960
|
-
if (!res.ok) {
|
|
2961
|
-
if (oauthFlowStatus) {
|
|
2962
|
-
oauthFlowStatus.className = "err-banner";
|
|
2963
|
-
oauthFlowStatus.textContent = data.message || data.error || "Login failed.";
|
|
2964
|
-
}
|
|
2965
|
-
oauthSubmitBtn.disabled = false;
|
|
2966
|
-
return;
|
|
2967
|
-
}
|
|
2968
|
-
oauthWizardLoginDone = true;
|
|
2969
|
-
oauthSessionId = null;
|
|
2970
|
-
llmOAuthSkipLoginEl.checked = true;
|
|
2971
|
-
if (oauthFlowStatus) {
|
|
2972
|
-
oauthFlowStatus.className = "ok-banner";
|
|
2973
|
-
oauthFlowStatus.textContent = "OpenClaw saved your ChatGPT login. You can start installation below.";
|
|
2974
|
-
}
|
|
2975
|
-
oauthSubmitBtn.disabled = true;
|
|
2976
|
-
updateStartButtonState();
|
|
2977
|
-
} catch (err) {
|
|
2978
|
-
if (oauthFlowStatus) {
|
|
2979
|
-
oauthFlowStatus.className = "err-banner";
|
|
2980
|
-
oauthFlowStatus.textContent = err && err.message ? String(err.message) : "Request failed.";
|
|
2981
|
-
}
|
|
2982
|
-
oauthSubmitBtn.disabled = false;
|
|
2983
|
-
}
|
|
3064
|
+
if (oauthOpenUrlBtn) {
|
|
3065
|
+
oauthOpenUrlBtn.addEventListener("click", () => {
|
|
3066
|
+
if (!oauthSessionId || oauthWizardLoginDone) return;
|
|
3067
|
+
oauthOpenedInBrowser = true;
|
|
3068
|
+
setOauthStep(oauthStepOpen, "done");
|
|
3069
|
+
setOauthStep(oauthStepComplete, "active");
|
|
3070
|
+
setOauthStep(oauthStepVerify, "active");
|
|
3071
|
+
setOauthStatus("Complete ChatGPT approval in this same browser, then return here. We detect completion automatically.");
|
|
2984
3072
|
});
|
|
2985
3073
|
}
|
|
2986
3074
|
|
|
2987
|
-
if (
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
await
|
|
2993
|
-
oauthCopyUrlBtn.textContent = "Copied";
|
|
2994
|
-
setTimeout(() => {
|
|
2995
|
-
oauthCopyUrlBtn.textContent = "Copy URL";
|
|
2996
|
-
}, 1500);
|
|
2997
|
-
} catch {
|
|
2998
|
-
oauthCopyUrlBtn.textContent = "Copy failed";
|
|
2999
|
-
setTimeout(() => {
|
|
3000
|
-
oauthCopyUrlBtn.textContent = "Copy URL";
|
|
3001
|
-
}, 1500);
|
|
3075
|
+
if (oauthRetryBtn) {
|
|
3076
|
+
oauthRetryBtn.addEventListener("click", async () => {
|
|
3077
|
+
await cancelOauthSession();
|
|
3078
|
+
resetOauthWizardState();
|
|
3079
|
+
if (isOauthMode()) {
|
|
3080
|
+
await startOauthGuidedFlow();
|
|
3002
3081
|
}
|
|
3003
3082
|
});
|
|
3004
3083
|
}
|
|
@@ -3024,7 +3103,8 @@ async function cmdInstall(args) {
|
|
|
3024
3103
|
}
|
|
3025
3104
|
|
|
3026
3105
|
const defaults = parseInstallWizardArgs(args);
|
|
3027
|
-
const { createInstallerStepEngine, assertWizardXCredentials } =
|
|
3106
|
+
const { createInstallerStepEngine, assertWizardXCredentials, spawnOpenClawCodexAuthLoginChild } =
|
|
3107
|
+
await import("./installer-step-engine.mjs");
|
|
3028
3108
|
const modeConfig = {
|
|
3029
3109
|
pluginPackage: "solana-traderclaw",
|
|
3030
3110
|
pluginId: "solana-trader",
|
|
@@ -3044,16 +3124,69 @@ async function cmdInstall(args) {
|
|
|
3044
3124
|
let running = false;
|
|
3045
3125
|
let shuttingDown = false;
|
|
3046
3126
|
|
|
3047
|
-
/**
|
|
3127
|
+
/** Guided Codex OAuth sessions keyed by sessionId. */
|
|
3048
3128
|
const oauthSessions = new Map();
|
|
3049
3129
|
const OPENAI_OAUTH_AUTHORIZE_RE = /https:\/\/auth\.openai\.com\/oauth\/authorize\S*/;
|
|
3050
3130
|
const oauthSessionTtlMs = 15 * 60 * 1000;
|
|
3051
3131
|
|
|
3132
|
+
// Long-lived callback proxy on port 1455. Bound at wizard startup so
|
|
3133
|
+
// Cursor/VSCode auto-forwards it to the user's laptop before they begin
|
|
3134
|
+
// the OAuth flow. When ChatGPT redirects the browser to localhost:1455,
|
|
3135
|
+
// this proxy catches the callback and feeds the code to the active
|
|
3136
|
+
// openclaw child process.
|
|
3137
|
+
let oauthCallbackProxy = null;
|
|
3138
|
+
|
|
3139
|
+
function findActiveOauthSession() {
|
|
3140
|
+
for (const [, s] of oauthSessions) {
|
|
3141
|
+
if (s.status === "awaiting_browser_callback" && s.child) return s;
|
|
3142
|
+
}
|
|
3143
|
+
return null;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
function startCallbackProxy() {
|
|
3147
|
+
if (oauthCallbackProxy) return;
|
|
3148
|
+
try {
|
|
3149
|
+
oauthCallbackProxy = createServer((cbReq, cbRes) => {
|
|
3150
|
+
const s = findActiveOauthSession();
|
|
3151
|
+
const fullUrl = `http://localhost:1455${cbReq.url}`;
|
|
3152
|
+
if (s && cbReq.url && cbReq.url.includes("code=")) {
|
|
3153
|
+
s.updatedAt = Date.now();
|
|
3154
|
+
try {
|
|
3155
|
+
if (s.child && s.child.stdin && !s.child.stdin.writableEnded && !s.child.stdin.destroyed) {
|
|
3156
|
+
s.child.stdin.write(`${fullUrl}\n`, () => {
|
|
3157
|
+
setTimeout(() => {
|
|
3158
|
+
try {
|
|
3159
|
+
if (s.child && s.child.stdin && !s.child.stdin.destroyed && !s.child.stdin.writableEnded) {
|
|
3160
|
+
s.child.stdin.end();
|
|
3161
|
+
}
|
|
3162
|
+
} catch { /* ignore */ }
|
|
3163
|
+
}, 100);
|
|
3164
|
+
});
|
|
3165
|
+
}
|
|
3166
|
+
} catch { /* ignore */ }
|
|
3167
|
+
cbRes.statusCode = 200;
|
|
3168
|
+
cbRes.setHeader("content-type", "text/html; charset=utf-8");
|
|
3169
|
+
cbRes.end(`<html><body style="background:#0a0e1a;color:#78f0a9;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;flex-direction:column;"><h2>ChatGPT sign-in received!</h2><p style="color:#c5d7f5;font-size:16px;">You can close this tab and return to the installer wizard.</p></body></html>`);
|
|
3170
|
+
} else {
|
|
3171
|
+
cbRes.statusCode = 200;
|
|
3172
|
+
cbRes.setHeader("content-type", "text/html; charset=utf-8");
|
|
3173
|
+
cbRes.end(`<html><body style="background:#0a0e1a;color:#9cb0de;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;flex-direction:column;"><h2>TraderClaw OAuth Callback Listener</h2><p>Waiting for ChatGPT redirect. If you see this page, the callback proxy is working.</p></body></html>`);
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
oauthCallbackProxy.listen(1455, "127.0.0.1");
|
|
3177
|
+
oauthCallbackProxy.on("error", () => {
|
|
3178
|
+
oauthCallbackProxy = null;
|
|
3179
|
+
});
|
|
3180
|
+
} catch {
|
|
3181
|
+
oauthCallbackProxy = null;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3052
3185
|
function killOauthSession(sessionId, signal = "SIGTERM") {
|
|
3053
3186
|
const s = oauthSessions.get(sessionId);
|
|
3054
3187
|
if (!s) return;
|
|
3055
3188
|
try {
|
|
3056
|
-
s.child.kill(signal);
|
|
3189
|
+
if (s.child) s.child.kill(signal);
|
|
3057
3190
|
} catch {
|
|
3058
3191
|
/* ignore */
|
|
3059
3192
|
}
|
|
@@ -3063,7 +3196,8 @@ async function cmdInstall(args) {
|
|
|
3063
3196
|
function pruneExpiredOauthSessions() {
|
|
3064
3197
|
const now = Date.now();
|
|
3065
3198
|
for (const [id, s] of oauthSessions) {
|
|
3066
|
-
|
|
3199
|
+
const anchor = s.updatedAt || s.createdAt || now;
|
|
3200
|
+
if (now - anchor > oauthSessionTtlMs) {
|
|
3067
3201
|
killOauthSession(id);
|
|
3068
3202
|
}
|
|
3069
3203
|
}
|
|
@@ -3132,6 +3266,35 @@ async function cmdInstall(args) {
|
|
|
3132
3266
|
return;
|
|
3133
3267
|
}
|
|
3134
3268
|
|
|
3269
|
+
if (req.method === "GET" && req.url.startsWith("/api/llm/oauth/status")) {
|
|
3270
|
+
pruneExpiredOauthSessions();
|
|
3271
|
+
let sessionId = "";
|
|
3272
|
+
try {
|
|
3273
|
+
const u = new URL(req.url, "http://127.0.0.1");
|
|
3274
|
+
sessionId = String(u.searchParams.get("sessionId") || "").trim();
|
|
3275
|
+
} catch {
|
|
3276
|
+
sessionId = "";
|
|
3277
|
+
}
|
|
3278
|
+
if (!sessionId) {
|
|
3279
|
+
respondJson(400, { ok: false, error: "session_id_required" });
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
const s = oauthSessions.get(sessionId);
|
|
3283
|
+
if (!s) {
|
|
3284
|
+
respondJson(404, { ok: false, error: "invalid_or_expired_session", message: "OAuth session expired. Start sign-in again." });
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
respondJson(200, {
|
|
3288
|
+
ok: true,
|
|
3289
|
+
state: s.status || "unknown",
|
|
3290
|
+
message: s.message || "",
|
|
3291
|
+
authUrl: s.authUrl || "",
|
|
3292
|
+
exitCode: typeof s.exitCode === "number" ? s.exitCode : null,
|
|
3293
|
+
detail: s.detail || "",
|
|
3294
|
+
});
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3135
3298
|
if (req.method === "POST" && req.url === "/api/llm/oauth/start") {
|
|
3136
3299
|
pruneExpiredOauthSessions();
|
|
3137
3300
|
if (running) {
|
|
@@ -3151,10 +3314,7 @@ async function cmdInstall(args) {
|
|
|
3151
3314
|
}
|
|
3152
3315
|
|
|
3153
3316
|
const sessionId = randomUUID();
|
|
3154
|
-
const child =
|
|
3155
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
3156
|
-
shell: false,
|
|
3157
|
-
});
|
|
3317
|
+
const child = spawnOpenClawCodexAuthLoginChild();
|
|
3158
3318
|
|
|
3159
3319
|
let combined = "";
|
|
3160
3320
|
let responded = false;
|
|
@@ -3171,7 +3331,7 @@ async function cmdInstall(args) {
|
|
|
3171
3331
|
ok: false,
|
|
3172
3332
|
error: "oauth_url_timeout",
|
|
3173
3333
|
message:
|
|
3174
|
-
"OpenClaw did not
|
|
3334
|
+
"OpenClaw did not provide a ChatGPT sign-in URL in time. Try again.",
|
|
3175
3335
|
});
|
|
3176
3336
|
}, 45_000);
|
|
3177
3337
|
|
|
@@ -3181,7 +3341,17 @@ async function cmdInstall(args) {
|
|
|
3181
3341
|
if (!m || !m[0]) return;
|
|
3182
3342
|
clearTimeout(urlTimeout);
|
|
3183
3343
|
responded = true;
|
|
3184
|
-
oauthSessions.set(sessionId, {
|
|
3344
|
+
oauthSessions.set(sessionId, {
|
|
3345
|
+
child,
|
|
3346
|
+
createdAt: Date.now(),
|
|
3347
|
+
updatedAt: Date.now(),
|
|
3348
|
+
status: "awaiting_browser_callback",
|
|
3349
|
+
authUrl: m[0],
|
|
3350
|
+
message: "Sign in in this same browser. OpenClaw is waiting for callback...",
|
|
3351
|
+
detail: "",
|
|
3352
|
+
exitCode: null,
|
|
3353
|
+
submitted: false,
|
|
3354
|
+
});
|
|
3185
3355
|
respondJson(200, { ok: true, sessionId, authUrl: m[0] });
|
|
3186
3356
|
};
|
|
3187
3357
|
|
|
@@ -3209,13 +3379,26 @@ async function cmdInstall(args) {
|
|
|
3209
3379
|
ok: false,
|
|
3210
3380
|
error: "oauth_login_exited_early",
|
|
3211
3381
|
exitCode: code,
|
|
3212
|
-
|
|
3382
|
+
message:
|
|
3383
|
+
"OpenClaw exited before showing a sign-in URL. Start OAuth again and keep the flow on this same machine/browser.",
|
|
3384
|
+
detail: stripAnsi(combined).slice(-4000),
|
|
3213
3385
|
});
|
|
3214
3386
|
return;
|
|
3215
3387
|
}
|
|
3216
3388
|
const pending = oauthSessions.get(sessionId);
|
|
3217
|
-
if (pending
|
|
3218
|
-
|
|
3389
|
+
if (pending) {
|
|
3390
|
+
pending.child = null;
|
|
3391
|
+
pending.updatedAt = Date.now();
|
|
3392
|
+
pending.exitCode = typeof code === "number" ? code : null;
|
|
3393
|
+
pending.detail = stripAnsi(combined).slice(-4000);
|
|
3394
|
+
if (code === 0) {
|
|
3395
|
+
pending.status = "succeeded";
|
|
3396
|
+
pending.message = "ChatGPT OAuth completed successfully.";
|
|
3397
|
+
} else {
|
|
3398
|
+
pending.status = "failed";
|
|
3399
|
+
pending.message =
|
|
3400
|
+
"OpenClaw OAuth did not complete. Try again — make sure you continue in the same browser that opened the sign-in link.";
|
|
3401
|
+
}
|
|
3219
3402
|
}
|
|
3220
3403
|
});
|
|
3221
3404
|
return;
|
|
@@ -3256,7 +3439,7 @@ async function cmdInstall(args) {
|
|
|
3256
3439
|
error: "oauth_submit_timeout",
|
|
3257
3440
|
message: "Login did not finish in time. Try again or complete login in a normal terminal.",
|
|
3258
3441
|
});
|
|
3259
|
-
},
|
|
3442
|
+
}, 300_000);
|
|
3260
3443
|
|
|
3261
3444
|
child.once("close", (code) => {
|
|
3262
3445
|
if (code === 0) {
|
|
@@ -3272,7 +3455,29 @@ async function cmdInstall(args) {
|
|
|
3272
3455
|
});
|
|
3273
3456
|
|
|
3274
3457
|
try {
|
|
3275
|
-
|
|
3458
|
+
const line = `${paste}\n`;
|
|
3459
|
+
if (child.stdin?.writableEnded || child.stdin?.destroyed) {
|
|
3460
|
+
killOauthSession(sessionId);
|
|
3461
|
+
finish(500, { ok: false, error: "stdin_closed", message: "The login session closed before paste could be sent." });
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
child.stdin.write(line, (err) => {
|
|
3465
|
+
if (err) {
|
|
3466
|
+
killOauthSession(sessionId);
|
|
3467
|
+
finish(500, { ok: false, error: "stdin_write_failed", message: err?.message || String(err) });
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
// End stdin so the PTY/readline layer forwards the line and the CLI can proceed (plain `script` often buffers otherwise).
|
|
3471
|
+
setTimeout(() => {
|
|
3472
|
+
try {
|
|
3473
|
+
if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded) {
|
|
3474
|
+
child.stdin.end();
|
|
3475
|
+
}
|
|
3476
|
+
} catch {
|
|
3477
|
+
/* ignore */
|
|
3478
|
+
}
|
|
3479
|
+
}, 100);
|
|
3480
|
+
});
|
|
3276
3481
|
} catch (err) {
|
|
3277
3482
|
killOauthSession(sessionId);
|
|
3278
3483
|
finish(500, { ok: false, error: "stdin_write_failed", message: err?.message || String(err) });
|
|
@@ -3450,12 +3655,21 @@ async function cmdInstall(args) {
|
|
|
3450
3655
|
server.listen(defaults.port, "127.0.0.1", resolve);
|
|
3451
3656
|
});
|
|
3452
3657
|
|
|
3658
|
+
// Start the OAuth callback proxy on port 1455 early so Cursor/VSCode
|
|
3659
|
+
// auto-forwards it to the user's laptop before the OAuth flow begins.
|
|
3660
|
+
startCallbackProxy();
|
|
3661
|
+
if (oauthCallbackProxy) {
|
|
3662
|
+
printInfo(`OAuth callback proxy listening on http://127.0.0.1:1455`);
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3453
3665
|
const url = `http://127.0.0.1:${defaults.port}`;
|
|
3454
3666
|
printSuccess(`Installer wizard is running at ${url}`);
|
|
3455
3667
|
if (!openBrowser(url)) {
|
|
3456
3668
|
printInfo(`Open this URL in your browser: ${url}`);
|
|
3457
3669
|
}
|
|
3458
3670
|
printInfo("Press Ctrl+C to stop the wizard server.");
|
|
3671
|
+
printInfo(`If you are on a remote VPS, forward both ports from your local machine:`);
|
|
3672
|
+
printInfo(` ssh -L ${defaults.port}:127.0.0.1:${defaults.port} -L 1455:127.0.0.1:1455 <user>@<your-vps>`);
|
|
3459
3673
|
}
|
|
3460
3674
|
|
|
3461
3675
|
async function cmdTestSession(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderclaw-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.77",
|
|
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.77"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|