traderclaw-cli 1.0.71 → 1.0.74
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 +207 -3
- package/bin/openclaw-trader.mjs +169 -25
- package/package.json +2 -2
|
@@ -442,6 +442,32 @@ function runCommandWithEvents(cmd, args = [], opts = {}) {
|
|
|
442
442
|
});
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
function getGlobalOpenClawPackageDir() {
|
|
446
|
+
const root = getCommandOutput("npm root -g");
|
|
447
|
+
if (!root) return null;
|
|
448
|
+
const dir = join(root.trim(), "openclaw");
|
|
449
|
+
return existsSync(join(dir, "package.json")) ? dir : null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Re-run `npm install` inside the global OpenClaw package tree. Some hosts end up with an
|
|
454
|
+
* incomplete `node_modules` after `npm install -g` (hoisting, optional deps, or interrupted
|
|
455
|
+
* installs). OpenClaw then fails at runtime with `Cannot find module 'grammy'` while loading
|
|
456
|
+
* config. Installing from the package directory restores declared dependencies.
|
|
457
|
+
*/
|
|
458
|
+
/** Runs `npm install` in the global `openclaw` package directory (fixes missing `grammy` etc.). */
|
|
459
|
+
export async function ensureOpenClawGlobalPackageDependencies() {
|
|
460
|
+
const dir = getGlobalOpenClawPackageDir();
|
|
461
|
+
if (!dir) {
|
|
462
|
+
return { skipped: true, reason: "global_openclaw_dir_not_found" };
|
|
463
|
+
}
|
|
464
|
+
await runCommandWithEvents("npm", ["install", "--omit=dev", "--registry", "https://registry.npmjs.org/"], {
|
|
465
|
+
cwd: dir,
|
|
466
|
+
shell: false,
|
|
467
|
+
});
|
|
468
|
+
return { repaired: true, dir };
|
|
469
|
+
}
|
|
470
|
+
|
|
445
471
|
/**
|
|
446
472
|
* Install or upgrade the global OpenClaw CLI. We always run npm even when `openclaw` is already
|
|
447
473
|
* on PATH: bundled plugin manifests track a minimum OpenClaw version (e.g. >=2026.4.8). A stale
|
|
@@ -1536,6 +1562,115 @@ function configureOpenClawLlmProvider({ provider, model, credential }, configPat
|
|
|
1536
1562
|
return { configPath, provider, model };
|
|
1537
1563
|
}
|
|
1538
1564
|
|
|
1565
|
+
/**
|
|
1566
|
+
* Sets only `agents.defaults.model.primary` (OAuth / subscription paths where credentials live in OpenClaw auth profiles).
|
|
1567
|
+
* Does not write API keys into config.env.
|
|
1568
|
+
*/
|
|
1569
|
+
function configureOpenClawLlmModelPrimaryOnly({ provider, model }, configPath = CONFIG_FILE) {
|
|
1570
|
+
if (!provider || !model) {
|
|
1571
|
+
throw new Error("LLM provider and model are required.");
|
|
1572
|
+
}
|
|
1573
|
+
if (!model.startsWith(`${provider}/`)) {
|
|
1574
|
+
throw new Error(`Selected model '${model}' does not match provider '${provider}'.`);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
let config = {};
|
|
1578
|
+
try {
|
|
1579
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1580
|
+
} catch {
|
|
1581
|
+
config = {};
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
if (!config.agents) config.agents = {};
|
|
1585
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
1586
|
+
ensureAgentsDefaultsSchemaCompat(config);
|
|
1587
|
+
if (!config.agents.defaults.model || typeof config.agents.defaults.model !== "object") {
|
|
1588
|
+
config.agents.defaults.model = {};
|
|
1589
|
+
}
|
|
1590
|
+
config.agents.defaults.model.primary = model;
|
|
1591
|
+
|
|
1592
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1593
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1594
|
+
return { configPath, provider, model };
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Runs `openclaw models auth login --provider openai-codex` and feeds the pasted redirect URL or code on stdin
|
|
1599
|
+
* when the CLI prompts (with a timed fallback for non-interactive / SSH).
|
|
1600
|
+
*/
|
|
1601
|
+
function runOpenClawCodexOAuthLogin(paste, emitLog) {
|
|
1602
|
+
return new Promise((resolve, reject) => {
|
|
1603
|
+
const child = spawn("openclaw", ["models", "auth", "login", "--provider", "openai-codex"], {
|
|
1604
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1605
|
+
shell: false,
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
let stdout = "";
|
|
1609
|
+
let stderr = "";
|
|
1610
|
+
let pasteSent = false;
|
|
1611
|
+
|
|
1612
|
+
const sendPaste = () => {
|
|
1613
|
+
if (pasteSent) return;
|
|
1614
|
+
const p = String(paste || "").trim();
|
|
1615
|
+
if (!p) return;
|
|
1616
|
+
pasteSent = true;
|
|
1617
|
+
try {
|
|
1618
|
+
child.stdin.write(`${p}\n`);
|
|
1619
|
+
} catch {
|
|
1620
|
+
// ignore
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
let fallbackTimer = setTimeout(() => sendPaste(), 9000);
|
|
1625
|
+
|
|
1626
|
+
const onChunk = (chunk) => {
|
|
1627
|
+
const combined = (stdout + stderr).toLowerCase();
|
|
1628
|
+
const c = typeof chunk === "string" ? chunk : chunk.toString();
|
|
1629
|
+
if (!pasteSent && /paste|authorization|redirect|callback/i.test(combined) && c.length > 0) {
|
|
1630
|
+
clearTimeout(fallbackTimer);
|
|
1631
|
+
fallbackTimer = setTimeout(() => sendPaste(), 400);
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
child.stdout?.on("data", (d) => {
|
|
1636
|
+
const t = d.toString();
|
|
1637
|
+
stdout += t;
|
|
1638
|
+
const urls = extractUrls(t);
|
|
1639
|
+
emitLog("info", t, urls);
|
|
1640
|
+
onChunk(t);
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
child.stderr?.on("data", (d) => {
|
|
1644
|
+
const t = d.toString();
|
|
1645
|
+
stderr += t;
|
|
1646
|
+
const urls = extractUrls(t);
|
|
1647
|
+
emitLog("warn", t, urls);
|
|
1648
|
+
onChunk(t);
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
child.on("close", (code) => {
|
|
1652
|
+
clearTimeout(fallbackTimer);
|
|
1653
|
+
if (code === 0) {
|
|
1654
|
+
resolve({ stdout, stderr });
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const detail = `${stderr}\n${stdout}`.trim();
|
|
1658
|
+
const err = new Error(
|
|
1659
|
+
detail || `openclaw models auth login failed with exit code ${code}. Try running the same command in a normal shell, then re-run the wizard with "already logged in" checked.`,
|
|
1660
|
+
);
|
|
1661
|
+
err.code = code;
|
|
1662
|
+
err.stdout = stdout;
|
|
1663
|
+
err.stderr = stderr;
|
|
1664
|
+
reject(err);
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
child.on("error", (e) => {
|
|
1668
|
+
clearTimeout(fallbackTimer);
|
|
1669
|
+
reject(e);
|
|
1670
|
+
});
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1539
1674
|
function verifyInstallation(modeConfig, apiKey) {
|
|
1540
1675
|
const gatewayFile = join(CONFIG_DIR, "gateway", modeConfig.gatewayConfig);
|
|
1541
1676
|
let llmConfigured = false;
|
|
@@ -1619,9 +1754,12 @@ export class InstallerStepEngine {
|
|
|
1619
1754
|
this.modeConfig = modeConfig;
|
|
1620
1755
|
this.options = {
|
|
1621
1756
|
lane: normalizeLane(options.lane),
|
|
1757
|
+
llmAuthMode: options.llmAuthMode === "oauth" ? "oauth" : "api_key",
|
|
1622
1758
|
llmProvider: options.llmProvider || "",
|
|
1623
1759
|
llmModel: options.llmModel || "",
|
|
1624
1760
|
llmCredential: options.llmCredential || "",
|
|
1761
|
+
llmOAuthPaste: typeof options.llmOAuthPaste === "string" ? options.llmOAuthPaste.trim() : "",
|
|
1762
|
+
llmOAuthSkipLogin: options.llmOAuthSkipLogin === true,
|
|
1625
1763
|
apiKey: options.apiKey || "",
|
|
1626
1764
|
orchestratorUrl: options.orchestratorUrl || "https://api.traderclaw.ai",
|
|
1627
1765
|
gatewayBaseUrl: options.gatewayBaseUrl || "",
|
|
@@ -1898,15 +2036,78 @@ export class InstallerStepEngine {
|
|
|
1898
2036
|
const provider = String(this.options.llmProvider || "").trim();
|
|
1899
2037
|
const requestedModel = String(this.options.llmModel || "").trim();
|
|
1900
2038
|
const credential = String(this.options.llmCredential || "").trim();
|
|
1901
|
-
|
|
2039
|
+
const authMode = this.options.llmAuthMode === "oauth" ? "oauth" : "api_key";
|
|
2040
|
+
|
|
2041
|
+
if (!provider) {
|
|
1902
2042
|
throw new Error(
|
|
1903
|
-
"Missing required LLM settings. Select provider
|
|
2043
|
+
"Missing required LLM settings. Select provider in the wizard before starting installation.",
|
|
1904
2044
|
);
|
|
1905
2045
|
}
|
|
1906
2046
|
if (!commandExists("openclaw")) {
|
|
1907
2047
|
throw new Error("OpenClaw is not available yet. Install step must complete before LLM configuration.");
|
|
1908
2048
|
}
|
|
1909
2049
|
|
|
2050
|
+
if (authMode === "oauth") {
|
|
2051
|
+
if (provider !== "openai-codex") {
|
|
2052
|
+
throw new Error("OAuth mode requires LLM provider openai-codex (ChatGPT / Codex subscription).");
|
|
2053
|
+
}
|
|
2054
|
+
const skipLogin = this.options.llmOAuthSkipLogin === true;
|
|
2055
|
+
const oauthPaste = String(this.options.llmOAuthPaste || "").trim();
|
|
2056
|
+
if (!skipLogin && !oauthPaste) {
|
|
2057
|
+
throw new Error(
|
|
2058
|
+
"Codex OAuth requires a pasted authorization code or redirect URL, or enable skip if you already ran openclaw models auth login on this host.",
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
if (!skipLogin) {
|
|
2062
|
+
try {
|
|
2063
|
+
await runOpenClawCodexOAuthLogin(oauthPaste, (level, text, urls) =>
|
|
2064
|
+
this.emitLog("configure_llm", level, text, urls || []),
|
|
2065
|
+
);
|
|
2066
|
+
} catch (err) {
|
|
2067
|
+
const tail = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.trim();
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`${tail}\n\nIf OAuth cannot complete from the wizard, run in a shell: openclaw models auth login --provider openai-codex — then re-run the wizard with "already logged in" checked.`,
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
const selection = resolveLlmModelSelection(provider, requestedModel);
|
|
2075
|
+
for (const msg of selection.warnings) {
|
|
2076
|
+
this.emitLog("configure_llm", "warn", msg);
|
|
2077
|
+
}
|
|
2078
|
+
const model = selection.model;
|
|
2079
|
+
|
|
2080
|
+
const saved = configureOpenClawLlmModelPrimaryOnly({ provider, model });
|
|
2081
|
+
this.emitLog(
|
|
2082
|
+
"configure_llm",
|
|
2083
|
+
"info",
|
|
2084
|
+
`Configured OpenClaw model primary=${model} (Codex OAuth; credentials in OpenClaw auth profiles, not OPENAI_API_KEY).`,
|
|
2085
|
+
);
|
|
2086
|
+
|
|
2087
|
+
await runCommandWithEvents("openclaw", ["config", "validate"], {
|
|
2088
|
+
onEvent: (evt) => this.emitLog("configure_llm", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
try {
|
|
2092
|
+
await runCommandWithEvents("openclaw", ["models", "status", "--check", "--probe-provider", provider], {
|
|
2093
|
+
onEvent: (evt) => this.emitLog("configure_llm", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2094
|
+
});
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
const details = `${err?.stderr || ""}\n${err?.stdout || ""}\n${err?.message || ""}`.trim();
|
|
2097
|
+
throw new Error(
|
|
2098
|
+
`LLM provider validation failed for '${provider}'. Check OAuth login and model, then retry.\n${details}`,
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
return { configured: true, provider, model, configPath: saved.configPath, authMode: "oauth" };
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
if (!credential) {
|
|
2106
|
+
throw new Error(
|
|
2107
|
+
"Missing required LLM settings. Paste your API key or token in the wizard before starting installation.",
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1910
2111
|
const selection = resolveLlmModelSelection(provider, requestedModel);
|
|
1911
2112
|
for (const msg of selection.warnings) {
|
|
1912
2113
|
this.emitLog("configure_llm", "warn", msg);
|
|
@@ -1931,7 +2132,7 @@ export class InstallerStepEngine {
|
|
|
1931
2132
|
);
|
|
1932
2133
|
}
|
|
1933
2134
|
|
|
1934
|
-
return { configured: true, provider, model, configPath: saved.configPath };
|
|
2135
|
+
return { configured: true, provider, model, configPath: saved.configPath, authMode: "api_key" };
|
|
1935
2136
|
}
|
|
1936
2137
|
|
|
1937
2138
|
buildSetupHandoff() {
|
|
@@ -1987,6 +2188,9 @@ export class InstallerStepEngine {
|
|
|
1987
2188
|
this.modeConfig,
|
|
1988
2189
|
(evt) => this.emitLog("install_plugin_package", evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
1989
2190
|
));
|
|
2191
|
+
await this.runStep("openclaw_global_deps", "Ensuring OpenClaw global package dependencies", async () =>
|
|
2192
|
+
ensureOpenClawGlobalPackageDependencies(),
|
|
2193
|
+
);
|
|
1990
2194
|
await this.runStep(
|
|
1991
2195
|
"activate_openclaw_plugin",
|
|
1992
2196
|
"Installing and enabling TraderClaw inside OpenClaw",
|
package/bin/openclaw-trader.mjs
CHANGED
|
@@ -1689,6 +1689,9 @@ function parseInstallWizardArgs(args) {
|
|
|
1689
1689
|
llmProvider: "",
|
|
1690
1690
|
llmModel: "",
|
|
1691
1691
|
llmCredential: "",
|
|
1692
|
+
llmAuthMode: "api_key",
|
|
1693
|
+
llmOAuthPaste: "",
|
|
1694
|
+
llmOAuthSkipLogin: false,
|
|
1692
1695
|
orchestratorUrl: "https://api.traderclaw.ai",
|
|
1693
1696
|
gatewayBaseUrl: "",
|
|
1694
1697
|
gatewayToken: "",
|
|
@@ -1709,6 +1712,12 @@ function parseInstallWizardArgs(args) {
|
|
|
1709
1712
|
if (key === "--llm-provider" && next) out.llmProvider = args[++i];
|
|
1710
1713
|
if (key === "--llm-model" && next) out.llmModel = args[++i];
|
|
1711
1714
|
if ((key === "--llm-api-key" || key === "--llm-token") && next) out.llmCredential = args[++i];
|
|
1715
|
+
if (key === "--llm-auth" && next) {
|
|
1716
|
+
const v = args[++i];
|
|
1717
|
+
out.llmAuthMode = v === "oauth" ? "oauth" : "api_key";
|
|
1718
|
+
}
|
|
1719
|
+
if (key === "--llm-oauth-paste" && next) out.llmOAuthPaste = args[++i];
|
|
1720
|
+
if (key === "--llm-oauth-skip-login") out.llmOAuthSkipLogin = true;
|
|
1712
1721
|
if ((key === "--url" || key === "-u") && next) out.orchestratorUrl = args[++i];
|
|
1713
1722
|
if ((key === "--gateway-base-url" || key === "-g") && next) out.gatewayBaseUrl = args[++i];
|
|
1714
1723
|
if ((key === "--gateway-token" || key === "-t") && next) out.gatewayToken = args[++i];
|
|
@@ -1867,7 +1876,7 @@ async function cmdPrecheck(args) {
|
|
|
1867
1876
|
|
|
1868
1877
|
log.info("Manual staging run commands:");
|
|
1869
1878
|
log.info(" 1) traderclaw install --wizard");
|
|
1870
|
-
log.info(" 2) In wizard, set LLM
|
|
1879
|
+
log.info(" 2) In wizard, set LLM (API key or Codex OAuth) and Telegram token");
|
|
1871
1880
|
log.info(" 3) Approve tailscale login in provided URL");
|
|
1872
1881
|
log.info(" 4) Confirm /v1/responses returns non-404 on funnel host");
|
|
1873
1882
|
log.info(" 5) Verify Telegram channel setup + probe");
|
|
@@ -1944,7 +1953,13 @@ async function loadWizardLlmCatalogAsync() {
|
|
|
1944
1953
|
},
|
|
1945
1954
|
{
|
|
1946
1955
|
id: "openai-codex",
|
|
1947
|
-
models: [
|
|
1956
|
+
models: [
|
|
1957
|
+
{ id: "openai-codex/gpt-5.4", name: "GPT-5.4 Codex (recommended, ChatGPT OAuth)" },
|
|
1958
|
+
{
|
|
1959
|
+
id: "openai-codex/gpt-5.3-codex-spark",
|
|
1960
|
+
name: "GPT-5.3 Codex Spark (subscription entitlement; experimental)",
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1948
1963
|
},
|
|
1949
1964
|
{
|
|
1950
1965
|
id: "google",
|
|
@@ -2027,7 +2042,8 @@ function wizardHtml(defaults) {
|
|
|
2027
2042
|
.card { background:#121a31; border:1px solid #22315a; border-radius: 12px; padding: 16px; margin-bottom: 16px; }
|
|
2028
2043
|
.grid { display:grid; grid-template-columns:1fr 1fr; gap: 12px; }
|
|
2029
2044
|
label { display:block; font-size: 12px; color:#9cb0de; margin-bottom: 4px; }
|
|
2030
|
-
input, select { width:100%; padding:10px; border-radius:8px; border:1px solid #334a87; background:#0d1530; color:#e8eef9; }
|
|
2045
|
+
input, select, textarea { width:100%; padding:10px; border-radius:8px; border:1px solid #334a87; background:#0d1530; color:#e8eef9; font-family: inherit; font-size: 14px; box-sizing: border-box; }
|
|
2046
|
+
textarea { min-height: 88px; resize: vertical; }
|
|
2031
2047
|
button { border:0; border-radius:8px; padding:10px 14px; background:#4d7cff; color:#fff; cursor:pointer; font-weight:600; }
|
|
2032
2048
|
button:disabled { opacity:0.6; cursor:not-allowed; }
|
|
2033
2049
|
.muted { color:#9cb0de; font-size:13px; }
|
|
@@ -2069,9 +2085,20 @@ function wizardHtml(defaults) {
|
|
|
2069
2085
|
</div>
|
|
2070
2086
|
<div class="card" id="llmCard">
|
|
2071
2087
|
<h3>Required: OpenClaw LLM Provider</h3>
|
|
2072
|
-
<p class="muted">
|
|
2073
|
-
<div
|
|
2074
|
-
<
|
|
2088
|
+
<p class="muted">Use an API key for OpenAI Platform and other providers, or ChatGPT/Codex OAuth for subscription access (no separate API billing). OAuth tokens are stored by OpenClaw — not as <code>OPENAI_API_KEY</code>.</p>
|
|
2089
|
+
<div style="margin-bottom:12px;">
|
|
2090
|
+
<label style="margin-bottom:8px;">LLM authentication</label>
|
|
2091
|
+
<label style="display:flex; align-items:flex-start; gap:8px; font-size:14px; color:#e8eef9; margin-bottom:6px; cursor:pointer;">
|
|
2092
|
+
<input id="llmAuthModeApiKey" name="llmAuthMode" type="radio" value="api_key" style="width:auto; margin-top:3px;" checked />
|
|
2093
|
+
<span><strong>API key</strong> — OpenAI Platform (<code>openai</code>), Anthropic, OpenRouter, etc.</span>
|
|
2094
|
+
</label>
|
|
2095
|
+
<label style="display:flex; align-items:flex-start; gap:8px; font-size:14px; color:#e8eef9; cursor:pointer;">
|
|
2096
|
+
<input id="llmAuthModeOauth" name="llmAuthMode" type="radio" value="oauth" style="width:auto; margin-top:3px;" />
|
|
2097
|
+
<span><strong>ChatGPT / Codex (OAuth)</strong> — provider <code>openai-codex</code> (Plus/Pro subscription via OpenClaw login)</span>
|
|
2098
|
+
</label>
|
|
2099
|
+
</div>
|
|
2100
|
+
<div class="grid" id="llmProviderModelGrid">
|
|
2101
|
+
<div id="llmProviderWrap">
|
|
2075
2102
|
<label>LLM provider (required)</label>
|
|
2076
2103
|
<select id="llmProvider"></select>
|
|
2077
2104
|
</div>
|
|
@@ -2080,21 +2107,31 @@ function wizardHtml(defaults) {
|
|
|
2080
2107
|
<select id="llmModel"></select>
|
|
2081
2108
|
</div>
|
|
2082
2109
|
</div>
|
|
2110
|
+
<p class="muted hidden" id="llmOauthProviderNote">Provider is fixed to <code>openai-codex</code>. Pick a Codex model below (or enable manual selection).</p>
|
|
2083
2111
|
<div style="margin-top:8px;">
|
|
2084
2112
|
<label style="display:flex; align-items:center; gap:8px; font-size:13px; color:#9cb0de;">
|
|
2085
2113
|
<input id="llmModelManual" type="checkbox" style="width:auto; padding:0; margin:0;" />
|
|
2086
2114
|
Choose model manually (advanced)
|
|
2087
2115
|
</label>
|
|
2088
2116
|
</div>
|
|
2089
|
-
<div style="margin-top:12px;">
|
|
2117
|
+
<div style="margin-top:12px;" id="llmApiKeyBlock">
|
|
2090
2118
|
<label>LLM API key or token (required)</label>
|
|
2091
2119
|
<input id="llmCredential" type="password" placeholder="Paste the credential for the selected provider/model" />
|
|
2092
|
-
<p class="muted">
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2120
|
+
<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>
|
|
2121
|
+
</div>
|
|
2122
|
+
<div style="margin-top:12px;" id="llmOauthBlock" class="hidden">
|
|
2123
|
+
<label>Paste authorization code or full redirect URL</label>
|
|
2124
|
+
<textarea id="llmOAuthPaste" autocomplete="off" placeholder="After the installer prints an OAuth URL in the log, sign in locally and paste the code or full callback URL here. Leave empty if you use the option below."></textarea>
|
|
2125
|
+
<label style="display:flex; align-items:flex-start; gap:8px; font-size:13px; color:#9cb0de; margin-top:8px; cursor:pointer;">
|
|
2126
|
+
<input id="llmOAuthSkipLogin" type="checkbox" style="width:auto; margin-top:3px;" />
|
|
2127
|
+
<span>I already ran <code>openclaw models auth login --provider openai-codex</code> on this machine</span>
|
|
2128
|
+
</label>
|
|
2129
|
+
<p class="muted">If login hangs over SSH, complete OAuth in a normal shell first, then enable the checkbox above and start again. Live install logs will show the authorize URL when the CLI prints it.</p>
|
|
2130
|
+
</div>
|
|
2131
|
+
<p class="muted" id="llmLoadState" aria-live="polite">Loading LLM provider catalog...</p>
|
|
2132
|
+
<div id="llmLoadingHint" class="loading-hint" role="status" aria-live="polite">
|
|
2133
|
+
<span class="spinner" aria-hidden="true"></span>
|
|
2134
|
+
<span id="llmLoadingHintText">Fetching provider list...</span>
|
|
2098
2135
|
</div>
|
|
2099
2136
|
</div>
|
|
2100
2137
|
<div class="card" id="xCard">
|
|
@@ -2228,6 +2265,14 @@ function wizardHtml(defaults) {
|
|
|
2228
2265
|
const llmModelEl = document.getElementById("llmModel");
|
|
2229
2266
|
const llmModelManualEl = document.getElementById("llmModelManual");
|
|
2230
2267
|
const llmCredentialEl = document.getElementById("llmCredential");
|
|
2268
|
+
const llmAuthModeApiKey = document.getElementById("llmAuthModeApiKey");
|
|
2269
|
+
const llmAuthModeOauth = document.getElementById("llmAuthModeOauth");
|
|
2270
|
+
const llmProviderWrap = document.getElementById("llmProviderWrap");
|
|
2271
|
+
const llmOauthProviderNote = document.getElementById("llmOauthProviderNote");
|
|
2272
|
+
const llmApiKeyBlock = document.getElementById("llmApiKeyBlock");
|
|
2273
|
+
const llmOauthBlock = document.getElementById("llmOauthBlock");
|
|
2274
|
+
const llmOAuthPasteEl = document.getElementById("llmOAuthPaste");
|
|
2275
|
+
const llmOAuthSkipLoginEl = document.getElementById("llmOAuthSkipLogin");
|
|
2231
2276
|
const telegramTokenEl = document.getElementById("telegramToken");
|
|
2232
2277
|
const llmLoadStateEl = document.getElementById("llmLoadState");
|
|
2233
2278
|
const llmLoadingHintEl = document.getElementById("llmLoadingHint");
|
|
@@ -2254,14 +2299,61 @@ function wizardHtml(defaults) {
|
|
|
2254
2299
|
let pollTimer = null;
|
|
2255
2300
|
let pollIntervalMs = 1200;
|
|
2256
2301
|
let installLocked = false;
|
|
2302
|
+
let savedApiKeyProvider = "";
|
|
2303
|
+
|
|
2304
|
+
(function initLlmAuthDefaults() {
|
|
2305
|
+
const mode = ${JSON.stringify(defaults.llmAuthMode || "api_key")};
|
|
2306
|
+
if (mode === "oauth") {
|
|
2307
|
+
llmAuthModeOauth.checked = true;
|
|
2308
|
+
llmAuthModeApiKey.checked = false;
|
|
2309
|
+
}
|
|
2310
|
+
llmOAuthPasteEl.value = ${JSON.stringify(defaults.llmOAuthPaste || "")};
|
|
2311
|
+
if (${defaults.llmOAuthSkipLogin === true ? "true" : "false"}) {
|
|
2312
|
+
llmOAuthSkipLoginEl.checked = true;
|
|
2313
|
+
}
|
|
2314
|
+
applyLlmAuthModeUi();
|
|
2315
|
+
})();
|
|
2316
|
+
|
|
2317
|
+
function isOauthMode() {
|
|
2318
|
+
return llmAuthModeOauth && llmAuthModeOauth.checked;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function effectiveLlmProvider() {
|
|
2322
|
+
return isOauthMode() ? "openai-codex" : llmProviderEl.value.trim();
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function applyLlmAuthModeUi() {
|
|
2326
|
+
if (!llmProviderWrap || !llmApiKeyBlock || !llmOauthBlock) return;
|
|
2327
|
+
if (isOauthMode()) {
|
|
2328
|
+
savedApiKeyProvider = llmProviderEl.value || savedApiKeyProvider;
|
|
2329
|
+
llmProviderEl.value = "openai-codex";
|
|
2330
|
+
llmProviderWrap.classList.add("hidden");
|
|
2331
|
+
llmOauthProviderNote.classList.remove("hidden");
|
|
2332
|
+
llmApiKeyBlock.classList.add("hidden");
|
|
2333
|
+
llmOauthBlock.classList.remove("hidden");
|
|
2334
|
+
} else {
|
|
2335
|
+
llmProviderWrap.classList.remove("hidden");
|
|
2336
|
+
llmOauthProviderNote.classList.add("hidden");
|
|
2337
|
+
llmApiKeyBlock.classList.remove("hidden");
|
|
2338
|
+
llmOauthBlock.classList.add("hidden");
|
|
2339
|
+
if (savedApiKeyProvider) {
|
|
2340
|
+
llmProviderEl.value = savedApiKeyProvider;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
function onLlmAuthModeChange() {
|
|
2346
|
+
applyLlmAuthModeUi();
|
|
2347
|
+
refreshModelOptions("");
|
|
2348
|
+
}
|
|
2257
2349
|
|
|
2258
2350
|
function hasRequiredInputs() {
|
|
2259
|
-
return
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
);
|
|
2351
|
+
if (!llmCatalogReady || !Boolean(telegramTokenEl.value.trim())) return false;
|
|
2352
|
+
if (isOauthMode()) {
|
|
2353
|
+
if (llmOAuthSkipLoginEl.checked) return true;
|
|
2354
|
+
return Boolean(llmOAuthPasteEl.value.trim());
|
|
2355
|
+
}
|
|
2356
|
+
return Boolean(llmProviderEl.value.trim()) && Boolean(llmCredentialEl.value.trim());
|
|
2265
2357
|
}
|
|
2266
2358
|
|
|
2267
2359
|
/** All-or-nothing: 0 or 4 non-empty X fields; partial is invalid. */
|
|
@@ -2348,7 +2440,7 @@ function wizardHtml(defaults) {
|
|
|
2348
2440
|
}
|
|
2349
2441
|
|
|
2350
2442
|
function refreshModelOptions(preferredModel) {
|
|
2351
|
-
const provider =
|
|
2443
|
+
const provider = effectiveLlmProvider();
|
|
2352
2444
|
const providerEntry = (llmCatalog.providers || []).find((entry) => entry.id === provider);
|
|
2353
2445
|
const modelItems = (providerEntry ? providerEntry.models : []).map((item) => ({ value: item.id, label: item.name + " (" + item.id + ")" }));
|
|
2354
2446
|
if (modelItems.length === 0) {
|
|
@@ -2378,8 +2470,9 @@ function wizardHtml(defaults) {
|
|
|
2378
2470
|
return;
|
|
2379
2471
|
}
|
|
2380
2472
|
setSelectOptions(llmProviderEl, providers, "${defaults.llmProvider}");
|
|
2473
|
+
applyLlmAuthModeUi();
|
|
2381
2474
|
refreshModelOptions("${defaults.llmModel}");
|
|
2382
|
-
const catalogMsg = "
|
|
2475
|
+
const catalogMsg = "Choose API key or Codex OAuth, then start installation. After setup, run \`openclaw models list\` for your live catalog.";
|
|
2383
2476
|
setLlmCatalogReady(true, catalogMsg, false);
|
|
2384
2477
|
} catch (err) {
|
|
2385
2478
|
setLlmCatalogReady(false, "Failed to load LLM providers. Reload the page and try again.", true);
|
|
@@ -2433,10 +2526,14 @@ function wizardHtml(defaults) {
|
|
|
2433
2526
|
manualEl.textContent = "";
|
|
2434
2527
|
readyEl.textContent = "Starting installation...";
|
|
2435
2528
|
|
|
2529
|
+
const oauth = isOauthMode();
|
|
2436
2530
|
const payload = {
|
|
2437
|
-
|
|
2531
|
+
llmAuthMode: oauth ? "oauth" : "api_key",
|
|
2532
|
+
llmProvider: oauth ? "openai-codex" : llmProviderEl.value.trim(),
|
|
2438
2533
|
llmModel: llmModelEl.value.trim(),
|
|
2439
|
-
llmCredential: llmCredentialEl.value.trim(),
|
|
2534
|
+
llmCredential: oauth ? "" : llmCredentialEl.value.trim(),
|
|
2535
|
+
llmOAuthPaste: llmOAuthPasteEl.value.trim(),
|
|
2536
|
+
llmOAuthSkipLogin: llmOAuthSkipLoginEl.checked,
|
|
2440
2537
|
apiKey: document.getElementById("apiKey").value.trim(),
|
|
2441
2538
|
telegramToken: document.getElementById("telegramToken").value.trim(),
|
|
2442
2539
|
referralCode: document.getElementById("referralCode").value.trim(),
|
|
@@ -2445,10 +2542,18 @@ function wizardHtml(defaults) {
|
|
|
2445
2542
|
xAccessTokenMain: xAccessTokenMainEl.value.trim(),
|
|
2446
2543
|
xAccessTokenMainSecret: xAccessTokenMainSecretEl.value.trim(),
|
|
2447
2544
|
};
|
|
2448
|
-
if (
|
|
2545
|
+
if (oauth) {
|
|
2546
|
+
if (!payload.llmOAuthSkipLogin && !payload.llmOAuthPaste) {
|
|
2547
|
+
stateEl.textContent = "blocked";
|
|
2548
|
+
readyEl.textContent = "";
|
|
2549
|
+
manualEl.textContent =
|
|
2550
|
+
"Codex OAuth: paste the authorization code or full redirect URL, or check the box if you already ran openclaw models auth login on this machine.";
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
} else if (!payload.llmProvider || !payload.llmCredential) {
|
|
2449
2554
|
stateEl.textContent = "blocked";
|
|
2450
2555
|
readyEl.textContent = "";
|
|
2451
|
-
manualEl.textContent = "LLM provider and
|
|
2556
|
+
manualEl.textContent = "LLM provider and API key are required before starting installation.";
|
|
2452
2557
|
return;
|
|
2453
2558
|
}
|
|
2454
2559
|
if (!payload.telegramToken) {
|
|
@@ -2690,7 +2795,11 @@ function wizardHtml(defaults) {
|
|
|
2690
2795
|
llmModelEl.disabled = !llmModelManualEl.checked;
|
|
2691
2796
|
updateStartButtonState();
|
|
2692
2797
|
});
|
|
2798
|
+
llmAuthModeApiKey.addEventListener("change", onLlmAuthModeChange);
|
|
2799
|
+
llmAuthModeOauth.addEventListener("change", onLlmAuthModeChange);
|
|
2693
2800
|
llmCredentialEl.addEventListener("input", updateStartButtonState);
|
|
2801
|
+
llmOAuthPasteEl.addEventListener("input", updateStartButtonState);
|
|
2802
|
+
llmOAuthSkipLoginEl.addEventListener("change", updateStartButtonState);
|
|
2694
2803
|
telegramTokenEl.addEventListener("input", updateStartButtonState);
|
|
2695
2804
|
xConsumerKeyEl.addEventListener("input", updateStartButtonState);
|
|
2696
2805
|
xConsumerSecretEl.addEventListener("input", updateStartButtonState);
|
|
@@ -2831,12 +2940,17 @@ async function cmdInstall(args) {
|
|
|
2831
2940
|
}
|
|
2832
2941
|
|
|
2833
2942
|
const body = await parseJsonBody(req).catch(() => ({}));
|
|
2943
|
+
const rawLlmAuth = body.llmAuthMode != null ? body.llmAuthMode : defaults.llmAuthMode;
|
|
2834
2944
|
const wizardOpts = {
|
|
2835
2945
|
mode: "light",
|
|
2836
2946
|
lane: defaults.lane,
|
|
2947
|
+
llmAuthMode: rawLlmAuth === "oauth" ? "oauth" : "api_key",
|
|
2837
2948
|
llmProvider: body.llmProvider || defaults.llmProvider,
|
|
2838
2949
|
llmModel: body.llmModel || defaults.llmModel,
|
|
2839
2950
|
llmCredential: body.llmCredential || defaults.llmCredential,
|
|
2951
|
+
llmOAuthPaste: typeof body.llmOAuthPaste === "string" ? body.llmOAuthPaste.trim() : defaults.llmOAuthPaste,
|
|
2952
|
+
llmOAuthSkipLogin:
|
|
2953
|
+
typeof body.llmOAuthSkipLogin === "boolean" ? body.llmOAuthSkipLogin : defaults.llmOAuthSkipLogin === true,
|
|
2840
2954
|
apiKey: body.apiKey || defaults.apiKey,
|
|
2841
2955
|
orchestratorUrl: defaults.orchestratorUrl,
|
|
2842
2956
|
gatewayBaseUrl: defaults.gatewayBaseUrl,
|
|
@@ -2870,9 +2984,12 @@ async function cmdInstall(args) {
|
|
|
2870
2984
|
{
|
|
2871
2985
|
mode: "light",
|
|
2872
2986
|
lane: defaults.lane,
|
|
2987
|
+
llmAuthMode: wizardOpts.llmAuthMode,
|
|
2873
2988
|
llmProvider: body.llmProvider || defaults.llmProvider,
|
|
2874
2989
|
llmModel: body.llmModel || defaults.llmModel,
|
|
2875
2990
|
llmCredential: body.llmCredential || defaults.llmCredential,
|
|
2991
|
+
llmOAuthPaste: wizardOpts.llmOAuthPaste,
|
|
2992
|
+
llmOAuthSkipLogin: wizardOpts.llmOAuthSkipLogin,
|
|
2876
2993
|
apiKey: body.apiKey || defaults.apiKey,
|
|
2877
2994
|
orchestratorUrl: defaults.orchestratorUrl,
|
|
2878
2995
|
gatewayBaseUrl: defaults.gatewayBaseUrl,
|
|
@@ -3204,6 +3321,7 @@ Commands:
|
|
|
3204
3321
|
signup Create a new account (alias for: setup --signup; run locally, not via the agent)
|
|
3205
3322
|
precheck Run environment checks (dry-run or allow-install)
|
|
3206
3323
|
install Launch installer flows (--wizard for localhost GUI)
|
|
3324
|
+
repair-openclaw Re-run npm install in the global openclaw package (fixes missing grammy after upgrade)
|
|
3207
3325
|
gateway Gateway helpers (see subcommands below)
|
|
3208
3326
|
login Re-authenticate (uses refresh token when valid; full challenge only if needed)
|
|
3209
3327
|
logout Revoke current session and clear tokens
|
|
@@ -3239,6 +3357,15 @@ Config subcommands:
|
|
|
3239
3357
|
config set <k> <v> Update a configuration value
|
|
3240
3358
|
config reset Remove plugin configuration
|
|
3241
3359
|
|
|
3360
|
+
Install wizard (traderclaw install --wizard):
|
|
3361
|
+
--port Local port for the wizard (default 17890)
|
|
3362
|
+
--llm-provider e.g. openai, openai-codex, anthropic
|
|
3363
|
+
--llm-model e.g. openai/gpt-5.4 or openai-codex/gpt-5.4
|
|
3364
|
+
--llm-api-key, --llm-token API key for LLM (api_key mode)
|
|
3365
|
+
--llm-auth api_key|oauth OpenAI Platform key vs ChatGPT/Codex OAuth (openai-codex)
|
|
3366
|
+
--llm-oauth-paste Paste redirect URL or code for Codex OAuth (non-skip)
|
|
3367
|
+
--llm-oauth-skip-login Skip login when you already ran openclaw models auth login
|
|
3368
|
+
|
|
3242
3369
|
Examples:
|
|
3243
3370
|
traderclaw signup
|
|
3244
3371
|
traderclaw setup
|
|
@@ -3248,6 +3375,8 @@ Examples:
|
|
|
3248
3375
|
traderclaw precheck --allow-install
|
|
3249
3376
|
traderclaw install --wizard
|
|
3250
3377
|
traderclaw install --wizard --lane quick-local
|
|
3378
|
+
traderclaw install --wizard --llm-auth oauth --llm-provider openai-codex --llm-oauth-skip-login
|
|
3379
|
+
traderclaw repair-openclaw
|
|
3251
3380
|
traderclaw gateway ensure-persistent
|
|
3252
3381
|
traderclaw setup --signup --user-id my_agent_001 --referral-code ABCD1234
|
|
3253
3382
|
traderclaw setup --api-key oc_xxx --url https://api.traderclaw.ai
|
|
@@ -3264,6 +3393,18 @@ Examples:
|
|
|
3264
3393
|
`);
|
|
3265
3394
|
}
|
|
3266
3395
|
|
|
3396
|
+
async function cmdRepairOpenclaw() {
|
|
3397
|
+
const { ensureOpenClawGlobalPackageDependencies } = await import("./installer-step-engine.mjs");
|
|
3398
|
+
printInfo("Repairing global OpenClaw npm dependencies (fixes missing grammy / MODULE_NOT_FOUND)...");
|
|
3399
|
+
const r = await ensureOpenClawGlobalPackageDependencies();
|
|
3400
|
+
if (r.skipped) {
|
|
3401
|
+
printError(`Could not find global OpenClaw package (${r.reason}). Install or upgrade: npm install -g openclaw@latest`);
|
|
3402
|
+
process.exit(1);
|
|
3403
|
+
}
|
|
3404
|
+
printSuccess(`Dependencies refreshed under ${r.dir}`);
|
|
3405
|
+
printInfo("Next: openclaw gateway restart");
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3267
3408
|
async function main() {
|
|
3268
3409
|
const args = process.argv.slice(2);
|
|
3269
3410
|
const command = args[0];
|
|
@@ -3295,6 +3436,9 @@ async function main() {
|
|
|
3295
3436
|
case "install":
|
|
3296
3437
|
await cmdInstall(args.slice(1));
|
|
3297
3438
|
break;
|
|
3439
|
+
case "repair-openclaw":
|
|
3440
|
+
await cmdRepairOpenclaw();
|
|
3441
|
+
break;
|
|
3298
3442
|
case "gateway":
|
|
3299
3443
|
await cmdGateway(args.slice(1));
|
|
3300
3444
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderclaw-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.74",
|
|
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.74"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|