traderclaw-cli 1.0.76 → 1.0.78
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 +462 -210
- 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,38 @@ 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>
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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>
|
|
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>
|
|
2178
2168
|
</div>
|
|
2179
|
-
<p id="
|
|
2169
|
+
<p id="oauthFallbackError" class="muted hidden" style="margin:6px 0 0;font-size:12px;color:#ff6b6b;"></p>
|
|
2180
2170
|
</div>
|
|
2181
|
-
|
|
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>
|
|
2171
|
+
</div>
|
|
2185
2172
|
</div>
|
|
2186
2173
|
<p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
|
|
2187
2174
|
<div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
|
|
@@ -2326,8 +2313,6 @@ function wizardHtml(defaults) {
|
|
|
2326
2313
|
const llmOauthProviderNote = document.getElementById("llmOauthProviderNote");
|
|
2327
2314
|
const llmApiKeyBlock = document.getElementById("llmApiKeyBlock");
|
|
2328
2315
|
const llmOauthBlock = document.getElementById("llmOauthBlock");
|
|
2329
|
-
const llmOAuthPasteEl = document.getElementById("llmOAuthPaste");
|
|
2330
|
-
const llmOAuthSkipLoginEl = document.getElementById("llmOAuthSkipLogin");
|
|
2331
2316
|
const telegramTokenEl = document.getElementById("telegramToken");
|
|
2332
2317
|
const llmLoadStateEl = document.getElementById("llmLoadState");
|
|
2333
2318
|
const llmLoadingHintEl = document.getElementById("llmLoadingHint");
|
|
@@ -2357,16 +2342,61 @@ function wizardHtml(defaults) {
|
|
|
2357
2342
|
let savedApiKeyProvider = "";
|
|
2358
2343
|
let oauthSessionId = null;
|
|
2359
2344
|
let oauthWizardLoginDone = false;
|
|
2345
|
+
let oauthStartInFlight = false;
|
|
2346
|
+
let oauthPollTimer = null;
|
|
2347
|
+
let oauthOpenedInBrowser = false;
|
|
2348
|
+
let oauthFlowStartedAtMs = 0;
|
|
2360
2349
|
|
|
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
2350
|
const oauthOpenUrlBtn = document.getElementById("oauthOpenUrlBtn");
|
|
2351
|
+
const oauthRetryBtn = document.getElementById("oauthRetryBtn");
|
|
2367
2352
|
const oauthFlowStatus = document.getElementById("oauthFlowStatus");
|
|
2353
|
+
const oauthStepPrepare = document.getElementById("oauthStepPrepare");
|
|
2354
|
+
const oauthStepOpen = document.getElementById("oauthStepOpen");
|
|
2355
|
+
const oauthStepComplete = document.getElementById("oauthStepComplete");
|
|
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;
|
|
2362
|
+
|
|
2363
|
+
function setOauthStep(stepEl, mode) {
|
|
2364
|
+
if (!stepEl) return;
|
|
2365
|
+
stepEl.classList.remove("pending", "active", "done", "error");
|
|
2366
|
+
stepEl.classList.add(mode || "pending");
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
function setOauthStatus(text, isError = false, isSuccess = false) {
|
|
2370
|
+
if (!oauthFlowStatus) return;
|
|
2371
|
+
oauthFlowStatus.className = isError ? "err-banner" : isSuccess ? "ok-banner" : "muted";
|
|
2372
|
+
oauthFlowStatus.textContent = text;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function setOauthOpenButton(url) {
|
|
2376
|
+
if (!oauthOpenUrlBtn) return;
|
|
2377
|
+
if (!url) {
|
|
2378
|
+
oauthOpenUrlBtn.href = "#";
|
|
2379
|
+
oauthOpenUrlBtn.style.opacity = ".55";
|
|
2380
|
+
oauthOpenUrlBtn.style.pointerEvents = "none";
|
|
2381
|
+
oauthOpenUrlBtn.setAttribute("aria-disabled", "true");
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
oauthOpenUrlBtn.href = url;
|
|
2385
|
+
oauthOpenUrlBtn.style.opacity = "1";
|
|
2386
|
+
oauthOpenUrlBtn.style.pointerEvents = "auto";
|
|
2387
|
+
oauthOpenUrlBtn.removeAttribute("aria-disabled");
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
function stopOauthPolling() {
|
|
2391
|
+
if (oauthPollTimer) {
|
|
2392
|
+
clearInterval(oauthPollTimer);
|
|
2393
|
+
oauthPollTimer = null;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2368
2396
|
|
|
2369
2397
|
async function cancelOauthSession() {
|
|
2398
|
+
stopOauthPolling();
|
|
2399
|
+
oauthStartInFlight = false;
|
|
2370
2400
|
if (!oauthSessionId) {
|
|
2371
2401
|
try {
|
|
2372
2402
|
await fetch("/api/llm/oauth/cancel", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
@@ -2387,18 +2417,37 @@ function wizardHtml(defaults) {
|
|
|
2387
2417
|
oauthSessionId = null;
|
|
2388
2418
|
}
|
|
2389
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
|
+
|
|
2390
2436
|
function resetOauthWizardState() {
|
|
2437
|
+
stopOauthPolling();
|
|
2438
|
+
hideFallbackPaste();
|
|
2391
2439
|
oauthSessionId = null;
|
|
2392
2440
|
oauthWizardLoginDone = false;
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
if (
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2441
|
+
oauthStartInFlight = false;
|
|
2442
|
+
oauthOpenedInBrowser = false;
|
|
2443
|
+
oauthFlowStartedAtMs = 0;
|
|
2444
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2445
|
+
setOauthOpenButton("");
|
|
2446
|
+
setOauthStep(oauthStepPrepare, "pending");
|
|
2447
|
+
setOauthStep(oauthStepOpen, "pending");
|
|
2448
|
+
setOauthStep(oauthStepComplete, "pending");
|
|
2449
|
+
setOauthStep(oauthStepVerify, "pending");
|
|
2450
|
+
setOauthStatus("Choose OAuth and wait a moment. We will prepare your sign-in automatically.");
|
|
2402
2451
|
}
|
|
2403
2452
|
|
|
2404
2453
|
(function initLlmAuthDefaults() {
|
|
@@ -2407,10 +2456,6 @@ function wizardHtml(defaults) {
|
|
|
2407
2456
|
llmAuthModeOauth.checked = true;
|
|
2408
2457
|
llmAuthModeApiKey.checked = false;
|
|
2409
2458
|
}
|
|
2410
|
-
llmOAuthPasteEl.value = ${JSON.stringify(defaults.llmOAuthPaste || "")};
|
|
2411
|
-
if (${defaults.llmOAuthSkipLogin === true ? "true" : "false"}) {
|
|
2412
|
-
llmOAuthSkipLoginEl.checked = true;
|
|
2413
|
-
}
|
|
2414
2459
|
applyLlmAuthModeUi();
|
|
2415
2460
|
})();
|
|
2416
2461
|
|
|
@@ -2431,6 +2476,9 @@ function wizardHtml(defaults) {
|
|
|
2431
2476
|
llmOauthProviderNote.classList.remove("hidden");
|
|
2432
2477
|
llmApiKeyBlock.classList.add("hidden");
|
|
2433
2478
|
llmOauthBlock.classList.remove("hidden");
|
|
2479
|
+
if (!oauthWizardLoginDone && !oauthStartInFlight && !oauthSessionId) {
|
|
2480
|
+
void startOauthGuidedFlow();
|
|
2481
|
+
}
|
|
2434
2482
|
} else {
|
|
2435
2483
|
void cancelOauthSession();
|
|
2436
2484
|
resetOauthWizardState();
|
|
@@ -2452,13 +2500,158 @@ function wizardHtml(defaults) {
|
|
|
2452
2500
|
function hasRequiredInputs() {
|
|
2453
2501
|
if (!llmCatalogReady || !Boolean(telegramTokenEl.value.trim())) return false;
|
|
2454
2502
|
if (isOauthMode()) {
|
|
2455
|
-
|
|
2456
|
-
if (llmOAuthSkipLoginEl.checked) return true;
|
|
2457
|
-
return Boolean(llmOAuthPasteEl.value.trim());
|
|
2503
|
+
return oauthWizardLoginDone === true;
|
|
2458
2504
|
}
|
|
2459
2505
|
return Boolean(llmProviderEl.value.trim()) && Boolean(llmCredentialEl.value.trim());
|
|
2460
2506
|
}
|
|
2461
2507
|
|
|
2508
|
+
async function pollOauthStatusOnce() {
|
|
2509
|
+
if (!oauthSessionId || !isOauthMode()) return;
|
|
2510
|
+
try {
|
|
2511
|
+
const sessionId = oauthSessionId;
|
|
2512
|
+
const res = await fetch("/api/llm/oauth/status?sessionId=" + encodeURIComponent(sessionId));
|
|
2513
|
+
const data = await res.json().catch(() => ({}));
|
|
2514
|
+
if (!res.ok) {
|
|
2515
|
+
stopOauthPolling();
|
|
2516
|
+
oauthStartInFlight = false;
|
|
2517
|
+
if (data && data.error === "invalid_or_expired_session") {
|
|
2518
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2519
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2520
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2521
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2522
|
+
setOauthStatus(
|
|
2523
|
+
"Automatic OAuth session expired. This flow requires the wizard and OpenClaw on the same machine and the same browser. Click Try sign-in again.",
|
|
2524
|
+
true,
|
|
2525
|
+
);
|
|
2526
|
+
} else {
|
|
2527
|
+
setOauthStatus((data && (data.message || data.error)) || "Could not read OAuth status.", true);
|
|
2528
|
+
}
|
|
2529
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2530
|
+
updateStartButtonState();
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
const state = typeof data.state === "string" ? data.state : "unknown";
|
|
2534
|
+
if (state === "awaiting_browser_callback") {
|
|
2535
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2536
|
+
setOauthStep(oauthStepOpen, oauthOpenedInBrowser ? "done" : "active");
|
|
2537
|
+
setOauthStep(oauthStepComplete, oauthOpenedInBrowser ? "active" : "pending");
|
|
2538
|
+
setOauthStep(oauthStepVerify, oauthOpenedInBrowser ? "active" : "pending");
|
|
2539
|
+
if (oauthOpenedInBrowser) {
|
|
2540
|
+
const elapsed = oauthFlowStartedAtMs > 0 ? Math.floor((Date.now() - oauthFlowStartedAtMs) / 1000) : 0;
|
|
2541
|
+
if (elapsed >= 90) {
|
|
2542
|
+
setOauthStatus(
|
|
2543
|
+
"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.",
|
|
2544
|
+
true,
|
|
2545
|
+
);
|
|
2546
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2547
|
+
} else {
|
|
2548
|
+
setOauthStatus("Waiting for ChatGPT approval to finish. Return here after you continue.", false);
|
|
2549
|
+
}
|
|
2550
|
+
} else {
|
|
2551
|
+
setOauthStatus("Step 2: click Open ChatGPT sign-in, then complete approval and return here.", false);
|
|
2552
|
+
}
|
|
2553
|
+
updateStartButtonState();
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
stopOauthPolling();
|
|
2557
|
+
oauthStartInFlight = false;
|
|
2558
|
+
if (state === "succeeded") {
|
|
2559
|
+
oauthWizardLoginDone = true;
|
|
2560
|
+
oauthSessionId = null;
|
|
2561
|
+
hideFallbackPaste();
|
|
2562
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2563
|
+
setOauthStep(oauthStepOpen, "done");
|
|
2564
|
+
setOauthStep(oauthStepComplete, "done");
|
|
2565
|
+
setOauthStep(oauthStepVerify, "done");
|
|
2566
|
+
setOauthStatus(data.message || "ChatGPT is connected. You can start installation now.", false, true);
|
|
2567
|
+
setOauthOpenButton("");
|
|
2568
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2569
|
+
updateStartButtonState();
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const errorText = data.message || data.error || "OAuth login failed.";
|
|
2573
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2574
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2575
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2576
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2577
|
+
setOauthStatus(
|
|
2578
|
+
errorText + " Automatic mode only works on the same machine and browser. Click Try sign-in again.",
|
|
2579
|
+
true,
|
|
2580
|
+
);
|
|
2581
|
+
setOauthOpenButton("");
|
|
2582
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2583
|
+
updateStartButtonState();
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
stopOauthPolling();
|
|
2586
|
+
oauthStartInFlight = false;
|
|
2587
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2588
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2589
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2590
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2591
|
+
setOauthStatus(err && err.message ? String(err.message) : "OAuth status request failed.", true);
|
|
2592
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2593
|
+
updateStartButtonState();
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function beginOauthStatusPolling() {
|
|
2598
|
+
stopOauthPolling();
|
|
2599
|
+
oauthPollTimer = setInterval(() => {
|
|
2600
|
+
void pollOauthStatusOnce();
|
|
2601
|
+
}, 1500);
|
|
2602
|
+
void pollOauthStatusOnce();
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
async function startOauthGuidedFlow() {
|
|
2606
|
+
if (!isOauthMode() || oauthStartInFlight) return;
|
|
2607
|
+
oauthStartInFlight = true;
|
|
2608
|
+
oauthWizardLoginDone = false;
|
|
2609
|
+
oauthOpenedInBrowser = false;
|
|
2610
|
+
oauthFlowStartedAtMs = Date.now();
|
|
2611
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.add("hidden");
|
|
2612
|
+
setOauthStep(oauthStepPrepare, "active");
|
|
2613
|
+
setOauthStep(oauthStepOpen, "pending");
|
|
2614
|
+
setOauthStep(oauthStepComplete, "pending");
|
|
2615
|
+
setOauthStep(oauthStepVerify, "pending");
|
|
2616
|
+
setOauthStatus("Preparing ChatGPT sign-in link...");
|
|
2617
|
+
setOauthOpenButton("");
|
|
2618
|
+
updateStartButtonState();
|
|
2619
|
+
try {
|
|
2620
|
+
const res = await fetch("/api/llm/oauth/start", { method: "POST" });
|
|
2621
|
+
const data = await res.json().catch(() => ({}));
|
|
2622
|
+
if (!res.ok) {
|
|
2623
|
+
oauthStartInFlight = false;
|
|
2624
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2625
|
+
setOauthStep(oauthStepOpen, "error");
|
|
2626
|
+
setOauthStep(oauthStepComplete, "error");
|
|
2627
|
+
setOauthStep(oauthStepVerify, "error");
|
|
2628
|
+
setOauthStatus((data && (data.message || data.error)) || "Could not start OAuth sign-in.", true);
|
|
2629
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2630
|
+
updateStartButtonState();
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
oauthSessionId = typeof data.sessionId === "string" ? data.sessionId : "";
|
|
2634
|
+
const authUrl = typeof data.authUrl === "string" ? data.authUrl : "";
|
|
2635
|
+
if (!oauthSessionId || !authUrl) {
|
|
2636
|
+
oauthStartInFlight = false;
|
|
2637
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2638
|
+
setOauthStatus("OpenClaw did not return a valid sign-in URL. Try again.", true);
|
|
2639
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
setOauthOpenButton(authUrl);
|
|
2643
|
+
setOauthStep(oauthStepPrepare, "done");
|
|
2644
|
+
setOauthStep(oauthStepOpen, "active");
|
|
2645
|
+
setOauthStatus("Step 2: click Open ChatGPT sign-in and continue in this same browser.");
|
|
2646
|
+
beginOauthStatusPolling();
|
|
2647
|
+
} catch (err) {
|
|
2648
|
+
oauthStartInFlight = false;
|
|
2649
|
+
setOauthStep(oauthStepPrepare, "error");
|
|
2650
|
+
setOauthStatus(err && err.message ? String(err.message) : "Could not start OAuth sign-in.", true);
|
|
2651
|
+
if (oauthRetryBtn) oauthRetryBtn.classList.remove("hidden");
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2462
2655
|
/** All-or-nothing: 0 or 4 non-empty X fields; partial is invalid. */
|
|
2463
2656
|
function xWizardFieldsStatus() {
|
|
2464
2657
|
const fields = [
|
|
@@ -2635,8 +2828,8 @@ function wizardHtml(defaults) {
|
|
|
2635
2828
|
llmProvider: oauth ? "openai-codex" : llmProviderEl.value.trim(),
|
|
2636
2829
|
llmModel: llmModelEl.value.trim(),
|
|
2637
2830
|
llmCredential: oauth ? "" : llmCredentialEl.value.trim(),
|
|
2638
|
-
llmOAuthPaste:
|
|
2639
|
-
llmOAuthSkipLogin:
|
|
2831
|
+
llmOAuthPaste: "",
|
|
2832
|
+
llmOAuthSkipLogin: oauth ? true : false,
|
|
2640
2833
|
apiKey: document.getElementById("apiKey").value.trim(),
|
|
2641
2834
|
telegramToken: document.getElementById("telegramToken").value.trim(),
|
|
2642
2835
|
referralCode: document.getElementById("referralCode").value.trim(),
|
|
@@ -2645,16 +2838,12 @@ function wizardHtml(defaults) {
|
|
|
2645
2838
|
xAccessTokenMain: xAccessTokenMainEl.value.trim(),
|
|
2646
2839
|
xAccessTokenMainSecret: xAccessTokenMainSecretEl.value.trim(),
|
|
2647
2840
|
};
|
|
2648
|
-
if (oauth && oauthWizardLoginDone) {
|
|
2649
|
-
payload.llmOAuthSkipLogin = true;
|
|
2650
|
-
payload.llmOAuthPaste = "";
|
|
2651
|
-
}
|
|
2652
2841
|
if (oauth) {
|
|
2653
|
-
if (!
|
|
2842
|
+
if (!oauthWizardLoginDone) {
|
|
2654
2843
|
stateEl.textContent = "blocked";
|
|
2655
2844
|
readyEl.textContent = "";
|
|
2656
2845
|
manualEl.textContent =
|
|
2657
|
-
"Codex OAuth
|
|
2846
|
+
"Codex OAuth is not completed yet. Use the guided sign-in above in this same browser and wait for the green success message.";
|
|
2658
2847
|
return;
|
|
2659
2848
|
}
|
|
2660
2849
|
} else if (!payload.llmProvider || !payload.llmCredential) {
|
|
@@ -2905,150 +3094,65 @@ function wizardHtml(defaults) {
|
|
|
2905
3094
|
llmAuthModeApiKey.addEventListener("change", onLlmAuthModeChange);
|
|
2906
3095
|
llmAuthModeOauth.addEventListener("change", onLlmAuthModeChange);
|
|
2907
3096
|
llmCredentialEl.addEventListener("input", updateStartButtonState);
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
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;
|
|
3097
|
+
if (oauthOpenUrlBtn) {
|
|
3098
|
+
oauthOpenUrlBtn.addEventListener("click", () => {
|
|
3099
|
+
if (!oauthSessionId || oauthWizardLoginDone) return;
|
|
3100
|
+
oauthOpenedInBrowser = true;
|
|
3101
|
+
setOauthStep(oauthStepOpen, "done");
|
|
3102
|
+
setOauthStep(oauthStepComplete, "active");
|
|
3103
|
+
setOauthStep(oauthStepVerify, "active");
|
|
3104
|
+
setOauthStatus("Complete ChatGPT approval in this browser, then return here. We detect completion automatically.");
|
|
3105
|
+
showFallbackPasteAfterDelay(15_000);
|
|
2958
3106
|
});
|
|
2959
3107
|
}
|
|
2960
3108
|
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
2963
|
-
const
|
|
2964
|
-
if (!
|
|
2965
|
-
if (
|
|
2966
|
-
|
|
2967
|
-
|
|
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");
|
|
2968
3116
|
}
|
|
2969
3117
|
return;
|
|
2970
3118
|
}
|
|
2971
|
-
if (
|
|
2972
|
-
|
|
2973
|
-
|
|
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
|
-
}
|
|
3119
|
+
if (oauthFallbackError) oauthFallbackError.classList.add("hidden");
|
|
3120
|
+
oauthFallbackSubmitBtn.disabled = true;
|
|
3121
|
+
oauthFallbackSubmitBtn.textContent = "Submitting…";
|
|
2983
3122
|
try {
|
|
2984
|
-
const res = await fetch("/api/llm/oauth/submit", {
|
|
3123
|
+
const res = await fetch("/api/llm/oauth/submit-callback-url", {
|
|
2985
3124
|
method: "POST",
|
|
2986
3125
|
headers: { "content-type": "application/json" },
|
|
2987
|
-
body: JSON.stringify({ sessionId: oauthSessionId,
|
|
3126
|
+
body: JSON.stringify({ sessionId: oauthSessionId, callbackUrl: raw }),
|
|
2988
3127
|
});
|
|
2989
3128
|
const data = await res.json().catch(() => ({}));
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
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");
|
|
2994
3136
|
}
|
|
2995
|
-
oauthSubmitBtn.disabled = false;
|
|
2996
|
-
return;
|
|
2997
3137
|
}
|
|
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
3138
|
} catch (err) {
|
|
3008
|
-
if (
|
|
3009
|
-
|
|
3010
|
-
|
|
3139
|
+
if (oauthFallbackError) {
|
|
3140
|
+
oauthFallbackError.textContent = err.message || "Request failed.";
|
|
3141
|
+
oauthFallbackError.classList.remove("hidden");
|
|
3011
3142
|
}
|
|
3012
|
-
|
|
3143
|
+
} finally {
|
|
3144
|
+
oauthFallbackSubmitBtn.disabled = false;
|
|
3145
|
+
oauthFallbackSubmitBtn.textContent = "Submit URL";
|
|
3013
3146
|
}
|
|
3014
3147
|
});
|
|
3015
3148
|
}
|
|
3016
3149
|
|
|
3017
|
-
if (
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
await
|
|
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
|
-
}
|
|
3033
|
-
});
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
const oauthTerminalCmdDisplay = document.getElementById("oauthTerminalCmdDisplay");
|
|
3037
|
-
const oauthCopyTerminalCmdBtn = document.getElementById("oauthCopyTerminalCmdBtn");
|
|
3038
|
-
if (oauthCopyTerminalCmdBtn && oauthTerminalCmdDisplay) {
|
|
3039
|
-
oauthCopyTerminalCmdBtn.addEventListener("click", async () => {
|
|
3040
|
-
const v = oauthTerminalCmdDisplay.value || "openclaw models auth login --provider openai-codex";
|
|
3041
|
-
try {
|
|
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);
|
|
3150
|
+
if (oauthRetryBtn) {
|
|
3151
|
+
oauthRetryBtn.addEventListener("click", async () => {
|
|
3152
|
+
await cancelOauthSession();
|
|
3153
|
+
resetOauthWizardState();
|
|
3154
|
+
if (isOauthMode()) {
|
|
3155
|
+
await startOauthGuidedFlow();
|
|
3052
3156
|
}
|
|
3053
3157
|
});
|
|
3054
3158
|
}
|
|
@@ -3095,16 +3199,69 @@ async function cmdInstall(args) {
|
|
|
3095
3199
|
let running = false;
|
|
3096
3200
|
let shuttingDown = false;
|
|
3097
3201
|
|
|
3098
|
-
/**
|
|
3202
|
+
/** Guided Codex OAuth sessions keyed by sessionId. */
|
|
3099
3203
|
const oauthSessions = new Map();
|
|
3100
3204
|
const OPENAI_OAUTH_AUTHORIZE_RE = /https:\/\/auth\.openai\.com\/oauth\/authorize\S*/;
|
|
3101
3205
|
const oauthSessionTtlMs = 15 * 60 * 1000;
|
|
3102
3206
|
|
|
3207
|
+
// Long-lived callback proxy on port 1455. Bound at wizard startup so
|
|
3208
|
+
// Cursor/VSCode auto-forwards it to the user's laptop before they begin
|
|
3209
|
+
// the OAuth flow. When ChatGPT redirects the browser to localhost:1455,
|
|
3210
|
+
// this proxy catches the callback and feeds the code to the active
|
|
3211
|
+
// openclaw child process.
|
|
3212
|
+
let oauthCallbackProxy = null;
|
|
3213
|
+
|
|
3214
|
+
function findActiveOauthSession() {
|
|
3215
|
+
for (const [, s] of oauthSessions) {
|
|
3216
|
+
if (s.status === "awaiting_browser_callback" && s.child) return s;
|
|
3217
|
+
}
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
function startCallbackProxy() {
|
|
3222
|
+
if (oauthCallbackProxy) return;
|
|
3223
|
+
try {
|
|
3224
|
+
oauthCallbackProxy = createServer((cbReq, cbRes) => {
|
|
3225
|
+
const s = findActiveOauthSession();
|
|
3226
|
+
const fullUrl = `http://localhost:1455${cbReq.url}`;
|
|
3227
|
+
if (s && cbReq.url && cbReq.url.includes("code=")) {
|
|
3228
|
+
s.updatedAt = Date.now();
|
|
3229
|
+
try {
|
|
3230
|
+
if (s.child && s.child.stdin && !s.child.stdin.writableEnded && !s.child.stdin.destroyed) {
|
|
3231
|
+
s.child.stdin.write(`${fullUrl}\n`, () => {
|
|
3232
|
+
setTimeout(() => {
|
|
3233
|
+
try {
|
|
3234
|
+
if (s.child && s.child.stdin && !s.child.stdin.destroyed && !s.child.stdin.writableEnded) {
|
|
3235
|
+
s.child.stdin.end();
|
|
3236
|
+
}
|
|
3237
|
+
} catch { /* ignore */ }
|
|
3238
|
+
}, 100);
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
} catch { /* ignore */ }
|
|
3242
|
+
cbRes.statusCode = 200;
|
|
3243
|
+
cbRes.setHeader("content-type", "text/html; charset=utf-8");
|
|
3244
|
+
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>`);
|
|
3245
|
+
} else {
|
|
3246
|
+
cbRes.statusCode = 200;
|
|
3247
|
+
cbRes.setHeader("content-type", "text/html; charset=utf-8");
|
|
3248
|
+
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>`);
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
oauthCallbackProxy.listen(1455, "127.0.0.1");
|
|
3252
|
+
oauthCallbackProxy.on("error", () => {
|
|
3253
|
+
oauthCallbackProxy = null;
|
|
3254
|
+
});
|
|
3255
|
+
} catch {
|
|
3256
|
+
oauthCallbackProxy = null;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3103
3260
|
function killOauthSession(sessionId, signal = "SIGTERM") {
|
|
3104
3261
|
const s = oauthSessions.get(sessionId);
|
|
3105
3262
|
if (!s) return;
|
|
3106
3263
|
try {
|
|
3107
|
-
s.child.kill(signal);
|
|
3264
|
+
if (s.child) s.child.kill(signal);
|
|
3108
3265
|
} catch {
|
|
3109
3266
|
/* ignore */
|
|
3110
3267
|
}
|
|
@@ -3114,7 +3271,8 @@ async function cmdInstall(args) {
|
|
|
3114
3271
|
function pruneExpiredOauthSessions() {
|
|
3115
3272
|
const now = Date.now();
|
|
3116
3273
|
for (const [id, s] of oauthSessions) {
|
|
3117
|
-
|
|
3274
|
+
const anchor = s.updatedAt || s.createdAt || now;
|
|
3275
|
+
if (now - anchor > oauthSessionTtlMs) {
|
|
3118
3276
|
killOauthSession(id);
|
|
3119
3277
|
}
|
|
3120
3278
|
}
|
|
@@ -3183,6 +3341,35 @@ async function cmdInstall(args) {
|
|
|
3183
3341
|
return;
|
|
3184
3342
|
}
|
|
3185
3343
|
|
|
3344
|
+
if (req.method === "GET" && req.url.startsWith("/api/llm/oauth/status")) {
|
|
3345
|
+
pruneExpiredOauthSessions();
|
|
3346
|
+
let sessionId = "";
|
|
3347
|
+
try {
|
|
3348
|
+
const u = new URL(req.url, "http://127.0.0.1");
|
|
3349
|
+
sessionId = String(u.searchParams.get("sessionId") || "").trim();
|
|
3350
|
+
} catch {
|
|
3351
|
+
sessionId = "";
|
|
3352
|
+
}
|
|
3353
|
+
if (!sessionId) {
|
|
3354
|
+
respondJson(400, { ok: false, error: "session_id_required" });
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
const s = oauthSessions.get(sessionId);
|
|
3358
|
+
if (!s) {
|
|
3359
|
+
respondJson(404, { ok: false, error: "invalid_or_expired_session", message: "OAuth session expired. Start sign-in again." });
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
respondJson(200, {
|
|
3363
|
+
ok: true,
|
|
3364
|
+
state: s.status || "unknown",
|
|
3365
|
+
message: s.message || "",
|
|
3366
|
+
authUrl: s.authUrl || "",
|
|
3367
|
+
exitCode: typeof s.exitCode === "number" ? s.exitCode : null,
|
|
3368
|
+
detail: s.detail || "",
|
|
3369
|
+
});
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3186
3373
|
if (req.method === "POST" && req.url === "/api/llm/oauth/start") {
|
|
3187
3374
|
pruneExpiredOauthSessions();
|
|
3188
3375
|
if (running) {
|
|
@@ -3219,7 +3406,7 @@ async function cmdInstall(args) {
|
|
|
3219
3406
|
ok: false,
|
|
3220
3407
|
error: "oauth_url_timeout",
|
|
3221
3408
|
message:
|
|
3222
|
-
"OpenClaw did not
|
|
3409
|
+
"OpenClaw did not provide a ChatGPT sign-in URL in time. Try again.",
|
|
3223
3410
|
});
|
|
3224
3411
|
}, 45_000);
|
|
3225
3412
|
|
|
@@ -3229,7 +3416,17 @@ async function cmdInstall(args) {
|
|
|
3229
3416
|
if (!m || !m[0]) return;
|
|
3230
3417
|
clearTimeout(urlTimeout);
|
|
3231
3418
|
responded = true;
|
|
3232
|
-
oauthSessions.set(sessionId, {
|
|
3419
|
+
oauthSessions.set(sessionId, {
|
|
3420
|
+
child,
|
|
3421
|
+
createdAt: Date.now(),
|
|
3422
|
+
updatedAt: Date.now(),
|
|
3423
|
+
status: "awaiting_browser_callback",
|
|
3424
|
+
authUrl: m[0],
|
|
3425
|
+
message: "Sign in in this same browser. OpenClaw is waiting for callback...",
|
|
3426
|
+
detail: "",
|
|
3427
|
+
exitCode: null,
|
|
3428
|
+
submitted: false,
|
|
3429
|
+
});
|
|
3233
3430
|
respondJson(200, { ok: true, sessionId, authUrl: m[0] });
|
|
3234
3431
|
};
|
|
3235
3432
|
|
|
@@ -3258,14 +3455,25 @@ async function cmdInstall(args) {
|
|
|
3258
3455
|
error: "oauth_login_exited_early",
|
|
3259
3456
|
exitCode: code,
|
|
3260
3457
|
message:
|
|
3261
|
-
"OpenClaw exited before showing a sign-in URL.
|
|
3458
|
+
"OpenClaw exited before showing a sign-in URL. Start OAuth again and keep the flow on this same machine/browser.",
|
|
3262
3459
|
detail: stripAnsi(combined).slice(-4000),
|
|
3263
3460
|
});
|
|
3264
3461
|
return;
|
|
3265
3462
|
}
|
|
3266
3463
|
const pending = oauthSessions.get(sessionId);
|
|
3267
|
-
if (pending
|
|
3268
|
-
|
|
3464
|
+
if (pending) {
|
|
3465
|
+
pending.child = null;
|
|
3466
|
+
pending.updatedAt = Date.now();
|
|
3467
|
+
pending.exitCode = typeof code === "number" ? code : null;
|
|
3468
|
+
pending.detail = stripAnsi(combined).slice(-4000);
|
|
3469
|
+
if (code === 0) {
|
|
3470
|
+
pending.status = "succeeded";
|
|
3471
|
+
pending.message = "ChatGPT OAuth completed successfully.";
|
|
3472
|
+
} else {
|
|
3473
|
+
pending.status = "failed";
|
|
3474
|
+
pending.message =
|
|
3475
|
+
"OpenClaw OAuth did not complete. Try again — make sure you continue in the same browser that opened the sign-in link.";
|
|
3476
|
+
}
|
|
3269
3477
|
}
|
|
3270
3478
|
});
|
|
3271
3479
|
return;
|
|
@@ -3353,6 +3561,41 @@ async function cmdInstall(args) {
|
|
|
3353
3561
|
return;
|
|
3354
3562
|
}
|
|
3355
3563
|
|
|
3564
|
+
if (req.method === "POST" && req.url === "/api/llm/oauth/submit-callback-url") {
|
|
3565
|
+
const body = await parseJsonBody(req).catch(() => ({}));
|
|
3566
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
3567
|
+
const callbackUrl = typeof body.callbackUrl === "string" ? body.callbackUrl.trim() : "";
|
|
3568
|
+
if (!callbackUrl || !callbackUrl.includes("code=")) {
|
|
3569
|
+
respondJson(400, { ok: false, error: "invalid_url", message: "URL must contain a code= parameter." });
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
const s = sessionId ? oauthSessions.get(sessionId) : findActiveOauthSession();
|
|
3573
|
+
if (!s || !s.child) {
|
|
3574
|
+
respondJson(404, { ok: false, error: "no_active_session", message: "No active OAuth session. Click Try sign-in again." });
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
try {
|
|
3578
|
+
if (s.child.stdin && !s.child.stdin.writableEnded && !s.child.stdin.destroyed) {
|
|
3579
|
+
s.child.stdin.write(`${callbackUrl}\n`, () => {
|
|
3580
|
+
setTimeout(() => {
|
|
3581
|
+
try {
|
|
3582
|
+
if (s.child && s.child.stdin && !s.child.stdin.destroyed && !s.child.stdin.writableEnded) {
|
|
3583
|
+
s.child.stdin.end();
|
|
3584
|
+
}
|
|
3585
|
+
} catch { /* ignore */ }
|
|
3586
|
+
}, 100);
|
|
3587
|
+
});
|
|
3588
|
+
s.updatedAt = Date.now();
|
|
3589
|
+
respondJson(200, { ok: true, message: "Callback URL submitted. Waiting for OpenClaw to complete…" });
|
|
3590
|
+
} else {
|
|
3591
|
+
respondJson(500, { ok: false, error: "stdin_closed", message: "OpenClaw process stdin is closed. Click Try sign-in again." });
|
|
3592
|
+
}
|
|
3593
|
+
} catch (err) {
|
|
3594
|
+
respondJson(500, { ok: false, error: "write_error", message: err.message || "Failed to write to OpenClaw." });
|
|
3595
|
+
}
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3356
3599
|
if (req.method === "POST" && req.url === "/api/llm/oauth/cancel") {
|
|
3357
3600
|
const body = await parseJsonBody(req).catch(() => ({}));
|
|
3358
3601
|
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
@@ -3522,12 +3765,21 @@ async function cmdInstall(args) {
|
|
|
3522
3765
|
server.listen(defaults.port, "127.0.0.1", resolve);
|
|
3523
3766
|
});
|
|
3524
3767
|
|
|
3768
|
+
// Start the OAuth callback proxy on port 1455 early so Cursor/VSCode
|
|
3769
|
+
// auto-forwards it to the user's laptop before the OAuth flow begins.
|
|
3770
|
+
startCallbackProxy();
|
|
3771
|
+
if (oauthCallbackProxy) {
|
|
3772
|
+
printInfo(`OAuth callback proxy listening on http://127.0.0.1:1455`);
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3525
3775
|
const url = `http://127.0.0.1:${defaults.port}`;
|
|
3526
3776
|
printSuccess(`Installer wizard is running at ${url}`);
|
|
3527
3777
|
if (!openBrowser(url)) {
|
|
3528
3778
|
printInfo(`Open this URL in your browser: ${url}`);
|
|
3529
3779
|
}
|
|
3530
3780
|
printInfo("Press Ctrl+C to stop the wizard server.");
|
|
3781
|
+
printInfo(`If you are on a remote VPS, forward both ports from your local machine:`);
|
|
3782
|
+
printInfo(` ssh -L ${defaults.port}:127.0.0.1:${defaults.port} -L 1455:127.0.0.1:1455 <user>@<your-vps>`);
|
|
3531
3783
|
}
|
|
3532
3784
|
|
|
3533
3785
|
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.78",
|
|
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.78"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|