traderclaw-cli 1.0.76 → 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/openclaw-trader.mjs +369 -227
- package/package.json +2 -2
package/bin/openclaw-trader.mjs
CHANGED
|
@@ -2082,12 +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-
|
|
2086
|
-
.oauth-
|
|
2087
|
-
.oauth-
|
|
2088
|
-
.oauth-
|
|
2089
|
-
.oauth-
|
|
2090
|
-
.oauth-
|
|
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; }
|
|
2091
2092
|
.ok-banner { color:#78f0a9; font-size:13px; margin-top:8px; }
|
|
2092
2093
|
.err-banner { color:#ff6b6b; font-size:13px; margin-top:8px; }
|
|
2093
2094
|
</style>
|
|
@@ -2136,52 +2137,28 @@ function wizardHtml(defaults) {
|
|
|
2136
2137
|
</div>
|
|
2137
2138
|
<div style="margin-top:12px;" id="llmOauthBlock" class="hidden">
|
|
2138
2139
|
<p class="muted" style="margin-bottom:12px;">
|
|
2139
|
-
|
|
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.
|
|
2140
2141
|
</p>
|
|
2141
|
-
<div class="oauth-
|
|
2142
|
-
<
|
|
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>
|
|
2143
2150
|
<ol>
|
|
2144
|
-
<li
|
|
2145
|
-
<li
|
|
2146
|
-
<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>
|
|
2147
2155
|
</ol>
|
|
2148
|
-
<div class="oauth-
|
|
2149
|
-
<
|
|
2150
|
-
<button type="button" id="
|
|
2156
|
+
<div class="oauth-actions">
|
|
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>
|
|
2151
2159
|
</div>
|
|
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>
|
|
2152
2161
|
</div>
|
|
2153
|
-
<label style="display:flex; align-items:flex-start; gap:10px; font-size:14px; color:#e8eef9; margin-top:16px; cursor:pointer;">
|
|
2154
|
-
<input id="llmOAuthSkipLogin" type="checkbox" style="width:auto; margin-top:3px;" />
|
|
2155
|
-
<span><strong>ChatGPT login is done</strong> — I ran the command above in a terminal on <strong>this</strong> machine and OpenClaw completed without errors.</span>
|
|
2156
|
-
</label>
|
|
2157
|
-
<details id="oauthWizardAltDetails" class="oauth-details">
|
|
2158
|
-
<summary>Alternative: no shell on this server — sign in using this browser only</summary>
|
|
2159
|
-
<p class="muted" style="margin-top:8px;">
|
|
2160
|
-
Use this when you <strong>cannot</strong> run a terminal on the machine where this wizard runs (typical remote VPS + browser on your laptop). You will copy the <strong>full URL from the address bar</strong> after ChatGPT redirects — even if the page shows “connection refused”, the URL in the bar is still valid.
|
|
2161
|
-
</p>
|
|
2162
|
-
<div class="oauth-flow" style="margin-top:10px;">
|
|
2163
|
-
<strong style="color:#9ee6ff;">Steps</strong>
|
|
2164
|
-
<ol>
|
|
2165
|
-
<li>Click <strong>Get ChatGPT sign-in link</strong> — we run the same OpenClaw login process and show the <code>https://auth.openai.com/oauth/authorize?…</code> URL.</li>
|
|
2166
|
-
<li>Open that URL, sign in, and let the browser redirect toward <code>localhost:1455</code>.</li>
|
|
2167
|
-
<li>Paste the <strong>entire callback URL</strong> from the address bar into the box below, then click <strong>Submit to OpenClaw</strong>.</li>
|
|
2168
|
-
</ol>
|
|
2169
|
-
<div class="oauth-actions">
|
|
2170
|
-
<button type="button" id="oauthGetLinkBtn" class="secondary">Get ChatGPT sign-in link</button>
|
|
2171
|
-
<button type="button" id="oauthSubmitBtn" class="secondary" disabled>Submit to OpenClaw</button>
|
|
2172
|
-
</div>
|
|
2173
|
-
<div id="oauthUrlRow" class="oauth-row hidden">
|
|
2174
|
-
<label style="flex:1 1 100%; font-size:12px; color:#9cb0de;">Sign-in URL (open in browser — not what you paste back)</label>
|
|
2175
|
-
<input type="text" id="oauthUrlDisplay" readonly placeholder="Click “Get ChatGPT sign-in link” first" />
|
|
2176
|
-
<button type="button" id="oauthCopyUrlBtn" class="secondary" disabled>Copy URL</button>
|
|
2177
|
-
<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>
|
|
2178
|
-
</div>
|
|
2179
|
-
<p id="oauthFlowStatus" class="muted" style="margin-top:8px;" aria-live="polite"></p>
|
|
2180
|
-
</div>
|
|
2181
|
-
<label style="display:block; margin-top:14px;">Paste redirect URL or authorization code</label>
|
|
2182
|
-
<textarea id="llmOAuthPaste" autocomplete="off" placeholder="Example: http://localhost:1455/auth/callback?code=… (full URL from address bar). You can paste only the code if that is all you have."></textarea>
|
|
2183
|
-
</details>
|
|
2184
|
-
<p class="muted" style="margin-top:12px;"><strong>SSH tip:</strong> To make your laptop’s browser hit OpenClaw on the server, you can run <code>ssh -L 1455:127.0.0.1:1455 user@this-server</code> before signing in so <code>localhost:1455</code> on your machine forwards to the wizard host.</p>
|
|
2185
2162
|
</div>
|
|
2186
2163
|
<p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
|
|
2187
2164
|
<div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
|
|
@@ -2326,8 +2303,6 @@ function wizardHtml(defaults) {
|
|
|
2326
2303
|
const llmOauthProviderNote = document.getElementById("llmOauthProviderNote");
|
|
2327
2304
|
const llmApiKeyBlock = document.getElementById("llmApiKeyBlock");
|
|
2328
2305
|
const llmOauthBlock = document.getElementById("llmOauthBlock");
|
|
2329
|
-
const llmOAuthPasteEl = document.getElementById("llmOAuthPaste");
|
|
2330
|
-
const llmOAuthSkipLoginEl = document.getElementById("llmOAuthSkipLogin");
|
|
2331
2306
|
const telegramTokenEl = document.getElementById("telegramToken");
|
|
2332
2307
|
const llmLoadStateEl = document.getElementById("llmLoadState");
|
|
2333
2308
|
const llmLoadingHintEl = document.getElementById("llmLoadingHint");
|
|
@@ -2357,16 +2332,56 @@ function wizardHtml(defaults) {
|
|
|
2357
2332
|
let savedApiKeyProvider = "";
|
|
2358
2333
|
let oauthSessionId = null;
|
|
2359
2334
|
let oauthWizardLoginDone = false;
|
|
2335
|
+
let oauthStartInFlight = false;
|
|
2336
|
+
let oauthPollTimer = null;
|
|
2337
|
+
let oauthOpenedInBrowser = false;
|
|
2338
|
+
let oauthFlowStartedAtMs = 0;
|
|
2360
2339
|
|
|
2361
|
-
const oauthGetLinkBtn = document.getElementById("oauthGetLinkBtn");
|
|
2362
|
-
const oauthSubmitBtn = document.getElementById("oauthSubmitBtn");
|
|
2363
|
-
const oauthUrlRow = document.getElementById("oauthUrlRow");
|
|
2364
|
-
const oauthUrlDisplay = document.getElementById("oauthUrlDisplay");
|
|
2365
|
-
const oauthCopyUrlBtn = document.getElementById("oauthCopyUrlBtn");
|
|
2366
2340
|
const oauthOpenUrlBtn = document.getElementById("oauthOpenUrlBtn");
|
|
2341
|
+
const oauthRetryBtn = document.getElementById("oauthRetryBtn");
|
|
2367
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
|
+
}
|
|
2368
2381
|
|
|
2369
2382
|
async function cancelOauthSession() {
|
|
2383
|
+
stopOauthPolling();
|
|
2384
|
+
oauthStartInFlight = false;
|
|
2370
2385
|
if (!oauthSessionId) {
|
|
2371
2386
|
try {
|
|
2372
2387
|
await fetch("/api/llm/oauth/cancel", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
@@ -2388,17 +2403,19 @@ function wizardHtml(defaults) {
|
|
|
2388
2403
|
}
|
|
2389
2404
|
|
|
2390
2405
|
function resetOauthWizardState() {
|
|
2406
|
+
stopOauthPolling();
|
|
2391
2407
|
oauthSessionId = null;
|
|
2392
2408
|
oauthWizardLoginDone = false;
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
if (
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
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.");
|
|
2402
2419
|
}
|
|
2403
2420
|
|
|
2404
2421
|
(function initLlmAuthDefaults() {
|
|
@@ -2407,10 +2424,6 @@ function wizardHtml(defaults) {
|
|
|
2407
2424
|
llmAuthModeOauth.checked = true;
|
|
2408
2425
|
llmAuthModeApiKey.checked = false;
|
|
2409
2426
|
}
|
|
2410
|
-
llmOAuthPasteEl.value = ${JSON.stringify(defaults.llmOAuthPaste || "")};
|
|
2411
|
-
if (${defaults.llmOAuthSkipLogin === true ? "true" : "false"}) {
|
|
2412
|
-
llmOAuthSkipLoginEl.checked = true;
|
|
2413
|
-
}
|
|
2414
2427
|
applyLlmAuthModeUi();
|
|
2415
2428
|
})();
|
|
2416
2429
|
|
|
@@ -2431,6 +2444,9 @@ function wizardHtml(defaults) {
|
|
|
2431
2444
|
llmOauthProviderNote.classList.remove("hidden");
|
|
2432
2445
|
llmApiKeyBlock.classList.add("hidden");
|
|
2433
2446
|
llmOauthBlock.classList.remove("hidden");
|
|
2447
|
+
if (!oauthWizardLoginDone && !oauthStartInFlight && !oauthSessionId) {
|
|
2448
|
+
void startOauthGuidedFlow();
|
|
2449
|
+
}
|
|
2434
2450
|
} else {
|
|
2435
2451
|
void cancelOauthSession();
|
|
2436
2452
|
resetOauthWizardState();
|
|
@@ -2452,13 +2468,157 @@ function wizardHtml(defaults) {
|
|
|
2452
2468
|
function hasRequiredInputs() {
|
|
2453
2469
|
if (!llmCatalogReady || !Boolean(telegramTokenEl.value.trim())) return false;
|
|
2454
2470
|
if (isOauthMode()) {
|
|
2455
|
-
|
|
2456
|
-
if (llmOAuthSkipLoginEl.checked) return true;
|
|
2457
|
-
return Boolean(llmOAuthPasteEl.value.trim());
|
|
2471
|
+
return oauthWizardLoginDone === true;
|
|
2458
2472
|
}
|
|
2459
2473
|
return Boolean(llmProviderEl.value.trim()) && Boolean(llmCredentialEl.value.trim());
|
|
2460
2474
|
}
|
|
2461
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
|
+
|
|
2462
2622
|
/** All-or-nothing: 0 or 4 non-empty X fields; partial is invalid. */
|
|
2463
2623
|
function xWizardFieldsStatus() {
|
|
2464
2624
|
const fields = [
|
|
@@ -2635,8 +2795,8 @@ function wizardHtml(defaults) {
|
|
|
2635
2795
|
llmProvider: oauth ? "openai-codex" : llmProviderEl.value.trim(),
|
|
2636
2796
|
llmModel: llmModelEl.value.trim(),
|
|
2637
2797
|
llmCredential: oauth ? "" : llmCredentialEl.value.trim(),
|
|
2638
|
-
llmOAuthPaste:
|
|
2639
|
-
llmOAuthSkipLogin:
|
|
2798
|
+
llmOAuthPaste: "",
|
|
2799
|
+
llmOAuthSkipLogin: oauth ? true : false,
|
|
2640
2800
|
apiKey: document.getElementById("apiKey").value.trim(),
|
|
2641
2801
|
telegramToken: document.getElementById("telegramToken").value.trim(),
|
|
2642
2802
|
referralCode: document.getElementById("referralCode").value.trim(),
|
|
@@ -2645,16 +2805,12 @@ function wizardHtml(defaults) {
|
|
|
2645
2805
|
xAccessTokenMain: xAccessTokenMainEl.value.trim(),
|
|
2646
2806
|
xAccessTokenMainSecret: xAccessTokenMainSecretEl.value.trim(),
|
|
2647
2807
|
};
|
|
2648
|
-
if (oauth && oauthWizardLoginDone) {
|
|
2649
|
-
payload.llmOAuthSkipLogin = true;
|
|
2650
|
-
payload.llmOAuthPaste = "";
|
|
2651
|
-
}
|
|
2652
2808
|
if (oauth) {
|
|
2653
|
-
if (!
|
|
2809
|
+
if (!oauthWizardLoginDone) {
|
|
2654
2810
|
stateEl.textContent = "blocked";
|
|
2655
2811
|
readyEl.textContent = "";
|
|
2656
2812
|
manualEl.textContent =
|
|
2657
|
-
"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.";
|
|
2658
2814
|
return;
|
|
2659
2815
|
}
|
|
2660
2816
|
} else if (!payload.llmProvider || !payload.llmCredential) {
|
|
@@ -2905,150 +3061,23 @@ function wizardHtml(defaults) {
|
|
|
2905
3061
|
llmAuthModeApiKey.addEventListener("change", onLlmAuthModeChange);
|
|
2906
3062
|
llmAuthModeOauth.addEventListener("change", onLlmAuthModeChange);
|
|
2907
3063
|
llmCredentialEl.addEventListener("input", updateStartButtonState);
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
oauthFlowStatus.textContent = "Starting OpenClaw (same as running models auth login in a terminal)...";
|
|
2917
|
-
}
|
|
2918
|
-
oauthGetLinkBtn.disabled = true;
|
|
2919
|
-
try {
|
|
2920
|
-
const res = await fetch("/api/llm/oauth/start", { method: "POST" });
|
|
2921
|
-
const data = await res.json().catch(() => ({}));
|
|
2922
|
-
if (!res.ok) {
|
|
2923
|
-
if (oauthFlowStatus) {
|
|
2924
|
-
oauthFlowStatus.className = "err-banner";
|
|
2925
|
-
let errText = data.message || data.error || "Could not get sign-in URL.";
|
|
2926
|
-
if (data.detail) {
|
|
2927
|
-
const d = String(data.detail).replace(/\s+/g, " ").trim();
|
|
2928
|
-
errText += d ? " " + d.slice(0, 700) : "";
|
|
2929
|
-
}
|
|
2930
|
-
oauthFlowStatus.textContent = errText;
|
|
2931
|
-
}
|
|
2932
|
-
oauthGetLinkBtn.disabled = false;
|
|
2933
|
-
return;
|
|
2934
|
-
}
|
|
2935
|
-
oauthSessionId = data.sessionId;
|
|
2936
|
-
if (oauthUrlDisplay) oauthUrlDisplay.value = data.authUrl || "";
|
|
2937
|
-
if (oauthUrlRow) oauthUrlRow.classList.remove("hidden");
|
|
2938
|
-
if (oauthCopyUrlBtn) oauthCopyUrlBtn.disabled = !data.authUrl;
|
|
2939
|
-
if (oauthOpenUrlBtn && data.authUrl) {
|
|
2940
|
-
oauthOpenUrlBtn.href = data.authUrl;
|
|
2941
|
-
oauthOpenUrlBtn.removeAttribute("aria-disabled");
|
|
2942
|
-
}
|
|
2943
|
-
if (oauthSubmitBtn) oauthSubmitBtn.disabled = false;
|
|
2944
|
-
const altDetails = document.getElementById("oauthWizardAltDetails");
|
|
2945
|
-
if (altDetails) altDetails.open = true;
|
|
2946
|
-
if (oauthFlowStatus) {
|
|
2947
|
-
oauthFlowStatus.className = "muted";
|
|
2948
|
-
oauthFlowStatus.textContent =
|
|
2949
|
-
"Open this URL in your browser. After ChatGPT redirects, copy the full callback URL from the address bar (localhost:1455?code=…) — even if the page says connection refused — then paste it below and click Submit.";
|
|
2950
|
-
}
|
|
2951
|
-
} catch (err) {
|
|
2952
|
-
if (oauthFlowStatus) {
|
|
2953
|
-
oauthFlowStatus.className = "err-banner";
|
|
2954
|
-
oauthFlowStatus.textContent = err && err.message ? String(err.message) : "Request failed.";
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
oauthGetLinkBtn.disabled = false;
|
|
2958
|
-
});
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
if (oauthSubmitBtn) {
|
|
2962
|
-
oauthSubmitBtn.addEventListener("click", async () => {
|
|
2963
|
-
const paste = llmOAuthPasteEl.value.trim();
|
|
2964
|
-
if (!paste) {
|
|
2965
|
-
if (oauthFlowStatus) {
|
|
2966
|
-
oauthFlowStatus.className = "err-banner";
|
|
2967
|
-
oauthFlowStatus.textContent = "Paste the redirect URL (from the browser address bar) or the authorization code first.";
|
|
2968
|
-
}
|
|
2969
|
-
return;
|
|
2970
|
-
}
|
|
2971
|
-
if (!oauthSessionId) {
|
|
2972
|
-
if (oauthFlowStatus) {
|
|
2973
|
-
oauthFlowStatus.className = "err-banner";
|
|
2974
|
-
oauthFlowStatus.textContent = "Click “Get ChatGPT sign-in link” first, or check “ChatGPT login is done” if you already signed in via the terminal command.";
|
|
2975
|
-
}
|
|
2976
|
-
return;
|
|
2977
|
-
}
|
|
2978
|
-
oauthSubmitBtn.disabled = true;
|
|
2979
|
-
if (oauthFlowStatus) {
|
|
2980
|
-
oauthFlowStatus.className = "muted";
|
|
2981
|
-
oauthFlowStatus.textContent = "Sending to OpenClaw...";
|
|
2982
|
-
}
|
|
2983
|
-
try {
|
|
2984
|
-
const res = await fetch("/api/llm/oauth/submit", {
|
|
2985
|
-
method: "POST",
|
|
2986
|
-
headers: { "content-type": "application/json" },
|
|
2987
|
-
body: JSON.stringify({ sessionId: oauthSessionId, paste }),
|
|
2988
|
-
});
|
|
2989
|
-
const data = await res.json().catch(() => ({}));
|
|
2990
|
-
if (!res.ok) {
|
|
2991
|
-
if (oauthFlowStatus) {
|
|
2992
|
-
oauthFlowStatus.className = "err-banner";
|
|
2993
|
-
oauthFlowStatus.textContent = data.message || data.error || "Login failed.";
|
|
2994
|
-
}
|
|
2995
|
-
oauthSubmitBtn.disabled = false;
|
|
2996
|
-
return;
|
|
2997
|
-
}
|
|
2998
|
-
oauthWizardLoginDone = true;
|
|
2999
|
-
oauthSessionId = null;
|
|
3000
|
-
llmOAuthSkipLoginEl.checked = true;
|
|
3001
|
-
if (oauthFlowStatus) {
|
|
3002
|
-
oauthFlowStatus.className = "ok-banner";
|
|
3003
|
-
oauthFlowStatus.textContent = "OpenClaw saved your ChatGPT login. You can start installation below.";
|
|
3004
|
-
}
|
|
3005
|
-
oauthSubmitBtn.disabled = true;
|
|
3006
|
-
updateStartButtonState();
|
|
3007
|
-
} catch (err) {
|
|
3008
|
-
if (oauthFlowStatus) {
|
|
3009
|
-
oauthFlowStatus.className = "err-banner";
|
|
3010
|
-
oauthFlowStatus.textContent = err && err.message ? String(err.message) : "Request failed.";
|
|
3011
|
-
}
|
|
3012
|
-
oauthSubmitBtn.disabled = false;
|
|
3013
|
-
}
|
|
3014
|
-
});
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
if (oauthCopyUrlBtn && oauthUrlDisplay) {
|
|
3018
|
-
oauthCopyUrlBtn.addEventListener("click", async () => {
|
|
3019
|
-
const v = oauthUrlDisplay.value;
|
|
3020
|
-
if (!v) return;
|
|
3021
|
-
try {
|
|
3022
|
-
await navigator.clipboard.writeText(v);
|
|
3023
|
-
oauthCopyUrlBtn.textContent = "Copied";
|
|
3024
|
-
setTimeout(() => {
|
|
3025
|
-
oauthCopyUrlBtn.textContent = "Copy URL";
|
|
3026
|
-
}, 1500);
|
|
3027
|
-
} catch {
|
|
3028
|
-
oauthCopyUrlBtn.textContent = "Copy failed";
|
|
3029
|
-
setTimeout(() => {
|
|
3030
|
-
oauthCopyUrlBtn.textContent = "Copy URL";
|
|
3031
|
-
}, 1500);
|
|
3032
|
-
}
|
|
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.");
|
|
3033
3072
|
});
|
|
3034
3073
|
}
|
|
3035
3074
|
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
await navigator.clipboard.writeText(v);
|
|
3043
|
-
oauthCopyTerminalCmdBtn.textContent = "Copied";
|
|
3044
|
-
setTimeout(() => {
|
|
3045
|
-
oauthCopyTerminalCmdBtn.textContent = "Copy command";
|
|
3046
|
-
}, 1500);
|
|
3047
|
-
} catch {
|
|
3048
|
-
oauthCopyTerminalCmdBtn.textContent = "Copy failed";
|
|
3049
|
-
setTimeout(() => {
|
|
3050
|
-
oauthCopyTerminalCmdBtn.textContent = "Copy command";
|
|
3051
|
-
}, 1500);
|
|
3075
|
+
if (oauthRetryBtn) {
|
|
3076
|
+
oauthRetryBtn.addEventListener("click", async () => {
|
|
3077
|
+
await cancelOauthSession();
|
|
3078
|
+
resetOauthWizardState();
|
|
3079
|
+
if (isOauthMode()) {
|
|
3080
|
+
await startOauthGuidedFlow();
|
|
3052
3081
|
}
|
|
3053
3082
|
});
|
|
3054
3083
|
}
|
|
@@ -3095,16 +3124,69 @@ async function cmdInstall(args) {
|
|
|
3095
3124
|
let running = false;
|
|
3096
3125
|
let shuttingDown = false;
|
|
3097
3126
|
|
|
3098
|
-
/**
|
|
3127
|
+
/** Guided Codex OAuth sessions keyed by sessionId. */
|
|
3099
3128
|
const oauthSessions = new Map();
|
|
3100
3129
|
const OPENAI_OAUTH_AUTHORIZE_RE = /https:\/\/auth\.openai\.com\/oauth\/authorize\S*/;
|
|
3101
3130
|
const oauthSessionTtlMs = 15 * 60 * 1000;
|
|
3102
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
|
+
|
|
3103
3185
|
function killOauthSession(sessionId, signal = "SIGTERM") {
|
|
3104
3186
|
const s = oauthSessions.get(sessionId);
|
|
3105
3187
|
if (!s) return;
|
|
3106
3188
|
try {
|
|
3107
|
-
s.child.kill(signal);
|
|
3189
|
+
if (s.child) s.child.kill(signal);
|
|
3108
3190
|
} catch {
|
|
3109
3191
|
/* ignore */
|
|
3110
3192
|
}
|
|
@@ -3114,7 +3196,8 @@ async function cmdInstall(args) {
|
|
|
3114
3196
|
function pruneExpiredOauthSessions() {
|
|
3115
3197
|
const now = Date.now();
|
|
3116
3198
|
for (const [id, s] of oauthSessions) {
|
|
3117
|
-
|
|
3199
|
+
const anchor = s.updatedAt || s.createdAt || now;
|
|
3200
|
+
if (now - anchor > oauthSessionTtlMs) {
|
|
3118
3201
|
killOauthSession(id);
|
|
3119
3202
|
}
|
|
3120
3203
|
}
|
|
@@ -3183,6 +3266,35 @@ async function cmdInstall(args) {
|
|
|
3183
3266
|
return;
|
|
3184
3267
|
}
|
|
3185
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
|
+
|
|
3186
3298
|
if (req.method === "POST" && req.url === "/api/llm/oauth/start") {
|
|
3187
3299
|
pruneExpiredOauthSessions();
|
|
3188
3300
|
if (running) {
|
|
@@ -3219,7 +3331,7 @@ async function cmdInstall(args) {
|
|
|
3219
3331
|
ok: false,
|
|
3220
3332
|
error: "oauth_url_timeout",
|
|
3221
3333
|
message:
|
|
3222
|
-
"OpenClaw did not
|
|
3334
|
+
"OpenClaw did not provide a ChatGPT sign-in URL in time. Try again.",
|
|
3223
3335
|
});
|
|
3224
3336
|
}, 45_000);
|
|
3225
3337
|
|
|
@@ -3229,7 +3341,17 @@ async function cmdInstall(args) {
|
|
|
3229
3341
|
if (!m || !m[0]) return;
|
|
3230
3342
|
clearTimeout(urlTimeout);
|
|
3231
3343
|
responded = true;
|
|
3232
|
-
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
|
+
});
|
|
3233
3355
|
respondJson(200, { ok: true, sessionId, authUrl: m[0] });
|
|
3234
3356
|
};
|
|
3235
3357
|
|
|
@@ -3258,14 +3380,25 @@ async function cmdInstall(args) {
|
|
|
3258
3380
|
error: "oauth_login_exited_early",
|
|
3259
3381
|
exitCode: code,
|
|
3260
3382
|
message:
|
|
3261
|
-
"OpenClaw exited before showing a sign-in URL.
|
|
3383
|
+
"OpenClaw exited before showing a sign-in URL. Start OAuth again and keep the flow on this same machine/browser.",
|
|
3262
3384
|
detail: stripAnsi(combined).slice(-4000),
|
|
3263
3385
|
});
|
|
3264
3386
|
return;
|
|
3265
3387
|
}
|
|
3266
3388
|
const pending = oauthSessions.get(sessionId);
|
|
3267
|
-
if (pending
|
|
3268
|
-
|
|
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
|
+
}
|
|
3269
3402
|
}
|
|
3270
3403
|
});
|
|
3271
3404
|
return;
|
|
@@ -3522,12 +3655,21 @@ async function cmdInstall(args) {
|
|
|
3522
3655
|
server.listen(defaults.port, "127.0.0.1", resolve);
|
|
3523
3656
|
});
|
|
3524
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
|
+
|
|
3525
3665
|
const url = `http://127.0.0.1:${defaults.port}`;
|
|
3526
3666
|
printSuccess(`Installer wizard is running at ${url}`);
|
|
3527
3667
|
if (!openBrowser(url)) {
|
|
3528
3668
|
printInfo(`Open this URL in your browser: ${url}`);
|
|
3529
3669
|
}
|
|
3530
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>`);
|
|
3531
3673
|
}
|
|
3532
3674
|
|
|
3533
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",
|