traderclaw-cli 1.0.75 → 1.0.76
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 +108 -36
- 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,12 @@ 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-terminal-box { background:#0a1f2e; border:1px solid #2a7a6a; border-radius:10px; padding:14px; margin-top:8px; }
|
|
2086
|
+
.oauth-terminal-box ol { margin:8px 0 0 18px; padding:0; color:#c5d7f5; font-size:13px; line-height:1.55; }
|
|
2087
|
+
.oauth-terminal-box li { margin-bottom:6px; }
|
|
2088
|
+
.oauth-details { margin-top:14px; border:1px solid #2a3f6a; border-radius:10px; padding:10px 14px 14px; background:#0a1224; }
|
|
2089
|
+
.oauth-details summary { list-style-position: outside; padding:4px 0; color:#b8cff5; font-weight:600; font-size:14px; }
|
|
2090
|
+
.oauth-details[open] summary { margin-bottom:8px; }
|
|
2085
2091
|
.ok-banner { color:#78f0a9; font-size:13px; margin-top:8px; }
|
|
2086
2092
|
.err-banner { color:#ff6b6b; font-size:13px; margin-top:8px; }
|
|
2087
2093
|
</style>
|
|
@@ -2129,36 +2135,53 @@ function wizardHtml(defaults) {
|
|
|
2129
2135
|
<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
2136
|
</div>
|
|
2131
2137
|
<div style="margin-top:12px;" id="llmOauthBlock" class="hidden">
|
|
2132
|
-
<p class="muted" style="margin-bottom:
|
|
2133
|
-
|
|
2138
|
+
<p class="muted" style="margin-bottom:12px;">
|
|
2139
|
+
<strong>Why doesn’t OpenClaw “just ask for a link” in the terminal?</strong> It does not need to. In the usual setup, you run OpenClaw’s login on the <strong>same machine</strong> where OpenClaw runs: ChatGPT sends your browser to <code>http://localhost:1455/…</code> and OpenClaw <strong>receives that callback automatically</strong> — no copying from the address bar. You only need to paste a long callback URL when your <strong>browser is on another computer</strong> than the one running OpenClaw (for example, you opened the sign-in page on your laptop but OpenClaw is on a VPS).
|
|
2134
2140
|
</p>
|
|
2135
|
-
<div class="oauth-
|
|
2136
|
-
<strong style="color:#
|
|
2141
|
+
<div class="oauth-terminal-box">
|
|
2142
|
+
<strong style="color:#8ef5d0;">Recommended: sign in from a terminal on this machine</strong>
|
|
2137
2143
|
<ol>
|
|
2138
|
-
<li>
|
|
2139
|
-
<li>
|
|
2140
|
-
<li>
|
|
2141
|
-
<li>Click <strong>Submit to OpenClaw</strong>. When it succeeds, you can start installation — no need to guess what to paste.</li>
|
|
2144
|
+
<li>Copy the command below (or type it).</li>
|
|
2145
|
+
<li>Run it in a normal terminal on <strong>this</strong> host. Your browser may open; sign in with ChatGPT and approve access.</li>
|
|
2146
|
+
<li>When the command finishes successfully, check <strong>“ChatGPT login is done”</strong> below — you do <strong>not</strong> need to paste a callback URL for this path.</li>
|
|
2142
2147
|
</ol>
|
|
2143
|
-
<div class="oauth-
|
|
2144
|
-
<
|
|
2145
|
-
<button type="button" id="
|
|
2148
|
+
<div class="oauth-row" style="margin-top:12px;">
|
|
2149
|
+
<input type="text" id="oauthTerminalCmdDisplay" readonly value="openclaw models auth login --provider openai-codex" style="flex:1 1 320px; font-size:13px;" />
|
|
2150
|
+
<button type="button" id="oauthCopyTerminalCmdBtn" class="secondary">Copy command</button>
|
|
2146
2151
|
</div>
|
|
2147
|
-
<div id="oauthUrlRow" class="oauth-row hidden">
|
|
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>
|
|
2154
2152
|
</div>
|
|
2155
|
-
<label style="
|
|
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;">
|
|
2153
|
+
<label style="display:flex; align-items:flex-start; gap:10px; font-size:14px; color:#e8eef9; margin-top:16px; cursor:pointer;">
|
|
2158
2154
|
<input id="llmOAuthSkipLogin" type="checkbox" style="width:auto; margin-top:3px;" />
|
|
2159
|
-
<span>
|
|
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>
|
|
2160
2156
|
</label>
|
|
2161
|
-
<
|
|
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>
|
|
2162
2185
|
</div>
|
|
2163
2186
|
<p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
|
|
2164
2187
|
<div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
|
|
@@ -2631,7 +2654,7 @@ function wizardHtml(defaults) {
|
|
|
2631
2654
|
stateEl.textContent = "blocked";
|
|
2632
2655
|
readyEl.textContent = "";
|
|
2633
2656
|
manualEl.textContent =
|
|
2634
|
-
"Codex OAuth:
|
|
2657
|
+
"Codex OAuth: check “ChatGPT login is done” after running the terminal command on this machine, or expand “Alternative” and use Get link + paste + Submit, or finish wizard Submit if you already used those buttons.";
|
|
2635
2658
|
return;
|
|
2636
2659
|
}
|
|
2637
2660
|
} else if (!payload.llmProvider || !payload.llmCredential) {
|
|
@@ -2899,7 +2922,12 @@ function wizardHtml(defaults) {
|
|
|
2899
2922
|
if (!res.ok) {
|
|
2900
2923
|
if (oauthFlowStatus) {
|
|
2901
2924
|
oauthFlowStatus.className = "err-banner";
|
|
2902
|
-
|
|
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;
|
|
2903
2931
|
}
|
|
2904
2932
|
oauthGetLinkBtn.disabled = false;
|
|
2905
2933
|
return;
|
|
@@ -2913,10 +2941,12 @@ function wizardHtml(defaults) {
|
|
|
2913
2941
|
oauthOpenUrlBtn.removeAttribute("aria-disabled");
|
|
2914
2942
|
}
|
|
2915
2943
|
if (oauthSubmitBtn) oauthSubmitBtn.disabled = false;
|
|
2944
|
+
const altDetails = document.getElementById("oauthWizardAltDetails");
|
|
2945
|
+
if (altDetails) altDetails.open = true;
|
|
2916
2946
|
if (oauthFlowStatus) {
|
|
2917
2947
|
oauthFlowStatus.className = "muted";
|
|
2918
2948
|
oauthFlowStatus.textContent =
|
|
2919
|
-
"Open this URL in your browser
|
|
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.";
|
|
2920
2950
|
}
|
|
2921
2951
|
} catch (err) {
|
|
2922
2952
|
if (oauthFlowStatus) {
|
|
@@ -2941,7 +2971,7 @@ function wizardHtml(defaults) {
|
|
|
2941
2971
|
if (!oauthSessionId) {
|
|
2942
2972
|
if (oauthFlowStatus) {
|
|
2943
2973
|
oauthFlowStatus.className = "err-banner";
|
|
2944
|
-
oauthFlowStatus.textContent = "Click “Get ChatGPT sign-in link” first, or
|
|
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.";
|
|
2945
2975
|
}
|
|
2946
2976
|
return;
|
|
2947
2977
|
}
|
|
@@ -3003,6 +3033,26 @@ function wizardHtml(defaults) {
|
|
|
3003
3033
|
});
|
|
3004
3034
|
}
|
|
3005
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);
|
|
3052
|
+
}
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3006
3056
|
telegramTokenEl.addEventListener("input", updateStartButtonState);
|
|
3007
3057
|
xConsumerKeyEl.addEventListener("input", updateStartButtonState);
|
|
3008
3058
|
xConsumerSecretEl.addEventListener("input", updateStartButtonState);
|
|
@@ -3024,7 +3074,8 @@ async function cmdInstall(args) {
|
|
|
3024
3074
|
}
|
|
3025
3075
|
|
|
3026
3076
|
const defaults = parseInstallWizardArgs(args);
|
|
3027
|
-
const { createInstallerStepEngine, assertWizardXCredentials } =
|
|
3077
|
+
const { createInstallerStepEngine, assertWizardXCredentials, spawnOpenClawCodexAuthLoginChild } =
|
|
3078
|
+
await import("./installer-step-engine.mjs");
|
|
3028
3079
|
const modeConfig = {
|
|
3029
3080
|
pluginPackage: "solana-traderclaw",
|
|
3030
3081
|
pluginId: "solana-trader",
|
|
@@ -3151,10 +3202,7 @@ async function cmdInstall(args) {
|
|
|
3151
3202
|
}
|
|
3152
3203
|
|
|
3153
3204
|
const sessionId = randomUUID();
|
|
3154
|
-
const child =
|
|
3155
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
3156
|
-
shell: false,
|
|
3157
|
-
});
|
|
3205
|
+
const child = spawnOpenClawCodexAuthLoginChild();
|
|
3158
3206
|
|
|
3159
3207
|
let combined = "";
|
|
3160
3208
|
let responded = false;
|
|
@@ -3209,7 +3257,9 @@ async function cmdInstall(args) {
|
|
|
3209
3257
|
ok: false,
|
|
3210
3258
|
error: "oauth_login_exited_early",
|
|
3211
3259
|
exitCode: code,
|
|
3212
|
-
|
|
3260
|
+
message:
|
|
3261
|
+
"OpenClaw exited before showing a sign-in URL. Try again; or run `openclaw models auth login --provider openai-codex` in a terminal on this machine and use the checkbox below.",
|
|
3262
|
+
detail: stripAnsi(combined).slice(-4000),
|
|
3213
3263
|
});
|
|
3214
3264
|
return;
|
|
3215
3265
|
}
|
|
@@ -3256,7 +3306,7 @@ async function cmdInstall(args) {
|
|
|
3256
3306
|
error: "oauth_submit_timeout",
|
|
3257
3307
|
message: "Login did not finish in time. Try again or complete login in a normal terminal.",
|
|
3258
3308
|
});
|
|
3259
|
-
},
|
|
3309
|
+
}, 300_000);
|
|
3260
3310
|
|
|
3261
3311
|
child.once("close", (code) => {
|
|
3262
3312
|
if (code === 0) {
|
|
@@ -3272,7 +3322,29 @@ async function cmdInstall(args) {
|
|
|
3272
3322
|
});
|
|
3273
3323
|
|
|
3274
3324
|
try {
|
|
3275
|
-
|
|
3325
|
+
const line = `${paste}\n`;
|
|
3326
|
+
if (child.stdin?.writableEnded || child.stdin?.destroyed) {
|
|
3327
|
+
killOauthSession(sessionId);
|
|
3328
|
+
finish(500, { ok: false, error: "stdin_closed", message: "The login session closed before paste could be sent." });
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
child.stdin.write(line, (err) => {
|
|
3332
|
+
if (err) {
|
|
3333
|
+
killOauthSession(sessionId);
|
|
3334
|
+
finish(500, { ok: false, error: "stdin_write_failed", message: err?.message || String(err) });
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
// End stdin so the PTY/readline layer forwards the line and the CLI can proceed (plain `script` often buffers otherwise).
|
|
3338
|
+
setTimeout(() => {
|
|
3339
|
+
try {
|
|
3340
|
+
if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded) {
|
|
3341
|
+
child.stdin.end();
|
|
3342
|
+
}
|
|
3343
|
+
} catch {
|
|
3344
|
+
/* ignore */
|
|
3345
|
+
}
|
|
3346
|
+
}, 100);
|
|
3347
|
+
});
|
|
3276
3348
|
} catch (err) {
|
|
3277
3349
|
killOauthSession(sessionId);
|
|
3278
3350
|
finish(500, { ok: false, error: "stdin_write_failed", message: err?.message || String(err) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderclaw-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.76",
|
|
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.76"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|