openclaw-manager 0.1.1

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.
Files changed (49) hide show
  1. package/bin/openclaw-manager.js +198 -0
  2. package/dist/app.js +39 -0
  3. package/dist/controllers/auth.controller.js +19 -0
  4. package/dist/controllers/cli.controller.js +10 -0
  5. package/dist/controllers/discord.controller.js +21 -0
  6. package/dist/controllers/jobs.controller.js +138 -0
  7. package/dist/controllers/process.controller.js +26 -0
  8. package/dist/controllers/quickstart.controller.js +11 -0
  9. package/dist/controllers/status.controller.js +10 -0
  10. package/dist/deps.js +1 -0
  11. package/dist/dev.js +156 -0
  12. package/dist/index.js +37 -0
  13. package/dist/lib/auth.js +57 -0
  14. package/dist/lib/commands.js +123 -0
  15. package/dist/lib/config.js +48 -0
  16. package/dist/lib/constants.js +9 -0
  17. package/dist/lib/gateway.js +37 -0
  18. package/dist/lib/jobs.js +117 -0
  19. package/dist/lib/onboarding.js +96 -0
  20. package/dist/lib/runner.js +99 -0
  21. package/dist/lib/static.js +69 -0
  22. package/dist/lib/system.js +31 -0
  23. package/dist/lib/utils.js +31 -0
  24. package/dist/middlewares/auth.js +23 -0
  25. package/dist/routes/auth.js +5 -0
  26. package/dist/routes/cli.js +4 -0
  27. package/dist/routes/discord.js +5 -0
  28. package/dist/routes/health.js +9 -0
  29. package/dist/routes/index.js +18 -0
  30. package/dist/routes/jobs.js +11 -0
  31. package/dist/routes/processes.js +6 -0
  32. package/dist/routes/quickstart.js +4 -0
  33. package/dist/routes/status.js +4 -0
  34. package/dist/services/auth.service.js +24 -0
  35. package/dist/services/cli.service.js +21 -0
  36. package/dist/services/discord.service.js +38 -0
  37. package/dist/services/jobs.service.js +307 -0
  38. package/dist/services/process.service.js +9 -0
  39. package/dist/services/quickstart.service.js +124 -0
  40. package/dist/services/resource.service.js +46 -0
  41. package/dist/services/status.service.js +32 -0
  42. package/package.json +18 -0
  43. package/web-dist/assets/index-BabnD_ew.js +13 -0
  44. package/web-dist/assets/index-CBtcOjoT.css +1 -0
  45. package/web-dist/docker.sh +62 -0
  46. package/web-dist/index.html +13 -0
  47. package/web-dist/install.ps1 +110 -0
  48. package/web-dist/install.sh +261 -0
  49. package/web-dist/stop.sh +52 -0
@@ -0,0 +1,31 @@
1
+ export function parsePort(value) {
2
+ if (!value)
3
+ return null;
4
+ const parsed = Number(value);
5
+ if (!Number.isFinite(parsed))
6
+ return null;
7
+ if (parsed <= 0 || parsed > 65535)
8
+ return null;
9
+ return parsed;
10
+ }
11
+ export function parsePositiveInt(value) {
12
+ if (!value)
13
+ return null;
14
+ const parsed = Number(value);
15
+ if (!Number.isFinite(parsed))
16
+ return null;
17
+ if (parsed <= 0)
18
+ return null;
19
+ return Math.floor(parsed);
20
+ }
21
+ export function parseOrigins(value) {
22
+ if (!value)
23
+ return [];
24
+ return value
25
+ .split(",")
26
+ .map((item) => item.trim())
27
+ .filter(Boolean);
28
+ }
29
+ export function sleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
@@ -0,0 +1,23 @@
1
+ import { resolveAuthState, verifyAuthHeader } from "../lib/auth.js";
2
+ export function createAuthMiddleware(deps) {
3
+ return async (c, next) => {
4
+ const pathName = c.req.path;
5
+ if (pathName.startsWith("/api/auth/")) {
6
+ return next();
7
+ }
8
+ if (deps.auth.disabled) {
9
+ return next();
10
+ }
11
+ const authState = resolveAuthState(deps.auth.disabled);
12
+ if (!authState.configured) {
13
+ if (deps.auth.allowUnconfigured)
14
+ return next();
15
+ return c.json({ ok: false, error: "auth not configured" }, 401);
16
+ }
17
+ const header = c.req.header("authorization");
18
+ if (!header || !verifyAuthHeader(header, authState)) {
19
+ return c.json({ ok: false, error: "unauthorized" }, 401);
20
+ }
21
+ return next();
22
+ };
23
+ }
@@ -0,0 +1,5 @@
1
+ import { createAuthLoginHandler, createAuthStatusHandler } from "../controllers/auth.controller.js";
2
+ export function registerAuthRoutes(app, deps) {
3
+ app.get("/api/auth/status", createAuthStatusHandler(deps));
4
+ app.post("/api/auth/login", createAuthLoginHandler(deps));
5
+ }
@@ -0,0 +1,4 @@
1
+ import { createCliInstallHandler } from "../controllers/cli.controller.js";
2
+ export function registerCliRoutes(app, deps) {
3
+ app.post("/api/cli/install", createCliInstallHandler(deps));
4
+ }
@@ -0,0 +1,5 @@
1
+ import { createDiscordPairingHandler, createDiscordTokenHandler } from "../controllers/discord.controller.js";
2
+ export function registerDiscordRoutes(app, deps) {
3
+ app.post("/api/discord/token", createDiscordTokenHandler(deps));
4
+ app.post("/api/discord/pairing", createDiscordPairingHandler(deps));
5
+ }
@@ -0,0 +1,9 @@
1
+ export function registerHealthRoutes(app) {
2
+ app.get("/health", (c) => {
3
+ return c.json({
4
+ ok: true,
5
+ time: new Date().toISOString(),
6
+ version: "clawdbot-manager-api"
7
+ });
8
+ });
9
+ }
@@ -0,0 +1,18 @@
1
+ import { registerAuthRoutes } from "./auth.js";
2
+ import { registerCliRoutes } from "./cli.js";
3
+ import { registerDiscordRoutes } from "./discord.js";
4
+ import { registerHealthRoutes } from "./health.js";
5
+ import { registerJobRoutes } from "./jobs.js";
6
+ import { registerProcessRoutes } from "./processes.js";
7
+ import { registerQuickstartRoutes } from "./quickstart.js";
8
+ import { registerStatusRoutes } from "./status.js";
9
+ export function registerRoutes(app, deps) {
10
+ registerHealthRoutes(app);
11
+ registerAuthRoutes(app, deps);
12
+ registerStatusRoutes(app, deps);
13
+ registerProcessRoutes(app, deps);
14
+ registerCliRoutes(app, deps);
15
+ registerDiscordRoutes(app, deps);
16
+ registerQuickstartRoutes(app, deps);
17
+ registerJobRoutes(app, deps);
18
+ }
@@ -0,0 +1,11 @@
1
+ import { createAiAuthJobHandler, createCliInstallJobHandler, createDiscordPairingJobHandler, createDiscordPairingWaitJobHandler, createJobStatusHandler, createJobStreamHandler, createQuickstartJobHandler, createResourceDownloadJobHandler } from "../controllers/jobs.controller.js";
2
+ export function registerJobRoutes(app, deps) {
3
+ app.post("/api/jobs/cli-install", createCliInstallJobHandler(deps));
4
+ app.post("/api/jobs/quickstart", createQuickstartJobHandler(deps));
5
+ app.post("/api/jobs/discord/pairing", createDiscordPairingJobHandler(deps));
6
+ app.post("/api/jobs/discord/pairing/wait", createDiscordPairingWaitJobHandler(deps));
7
+ app.post("/api/jobs/resources/download", createResourceDownloadJobHandler(deps));
8
+ app.post("/api/jobs/ai/auth", createAiAuthJobHandler(deps));
9
+ app.get("/api/jobs/:id", createJobStatusHandler(deps));
10
+ app.get("/api/jobs/:id/stream", createJobStreamHandler(deps));
11
+ }
@@ -0,0 +1,6 @@
1
+ import { createProcessListHandler, createProcessStartHandler, createProcessStopHandler } from "../controllers/process.controller.js";
2
+ export function registerProcessRoutes(app, deps) {
3
+ app.get("/api/processes", createProcessListHandler(deps));
4
+ app.post("/api/processes/start", createProcessStartHandler(deps));
5
+ app.post("/api/processes/stop", createProcessStopHandler(deps));
6
+ }
@@ -0,0 +1,4 @@
1
+ import { createQuickstartHandler } from "../controllers/quickstart.controller.js";
2
+ export function registerQuickstartRoutes(app, deps) {
3
+ app.post("/api/quickstart", createQuickstartHandler(deps));
4
+ }
@@ -0,0 +1,4 @@
1
+ import { createStatusHandler } from "../controllers/status.controller.js";
2
+ export function registerStatusRoutes(app, deps) {
3
+ app.get("/api/status", createStatusHandler(deps));
4
+ }
@@ -0,0 +1,24 @@
1
+ import { resolveAuthState } from "../lib/auth.js";
2
+ export function getAuthStatus(authDisabled) {
3
+ const authState = resolveAuthState(authDisabled);
4
+ return {
5
+ required: !authDisabled,
6
+ configured: authState.configured
7
+ };
8
+ }
9
+ export function loginWithCredentials(authDisabled, username, password) {
10
+ if (authDisabled) {
11
+ return { ok: true, disabled: true };
12
+ }
13
+ const authState = resolveAuthState(authDisabled);
14
+ if (!authState.configured) {
15
+ return { ok: false, error: "auth not configured", status: 400 };
16
+ }
17
+ if (!username || !password) {
18
+ return { ok: false, error: "missing credentials", status: 400 };
19
+ }
20
+ if (!authState.verify(username, password)) {
21
+ return { ok: false, error: "invalid credentials", status: 401 };
22
+ }
23
+ return { ok: true };
24
+ }
@@ -0,0 +1,21 @@
1
+ import { getCliStatus } from "../lib/system.js";
2
+ import { parsePositiveInt } from "../lib/utils.js";
3
+ export async function installCli(runCommand) {
4
+ const cli = await getCliStatus(runCommand);
5
+ if (cli.installed) {
6
+ return { ok: true, alreadyInstalled: true, version: cli.version };
7
+ }
8
+ const timeoutMs = parsePositiveInt(process.env.MANAGER_CLI_INSTALL_TIMEOUT_MS) ?? 600_000;
9
+ try {
10
+ await runCommand("npm", ["i", "-g", "clawdbot@latest"], timeoutMs, {
11
+ ...process.env,
12
+ NPM_CONFIG_AUDIT: "false",
13
+ NPM_CONFIG_FUND: "false"
14
+ });
15
+ const updated = await getCliStatus(runCommand);
16
+ return { ok: true, version: updated.version };
17
+ }
18
+ catch (err) {
19
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
20
+ }
21
+ }
@@ -0,0 +1,38 @@
1
+ export async function saveDiscordToken(runCommand, token) {
2
+ const args = ["config", "set", "channels.discord.token", token];
3
+ try {
4
+ await runCommand("clawdbot", args, 8000);
5
+ await ensureDiscordDmAllow(runCommand);
6
+ return { ok: true };
7
+ }
8
+ catch (err) {
9
+ return {
10
+ ok: false,
11
+ error: err instanceof Error ? err.message : String(err)
12
+ };
13
+ }
14
+ }
15
+ export async function approveDiscordPairing(runCommand, code) {
16
+ const args = ["pairing", "approve", "discord", code];
17
+ return runCommand("clawdbot", args, 8000)
18
+ .then(() => ({ ok: true }))
19
+ .catch((err) => ({
20
+ ok: false,
21
+ error: err instanceof Error ? err.message : String(err)
22
+ }));
23
+ }
24
+ async function ensureDiscordDmAllow(runCommand) {
25
+ const value = await runCommand("clawdbot", ["config", "get", "channels.discord.dm.allowFrom", "--json"], 4000).catch(() => null);
26
+ if (value) {
27
+ try {
28
+ const parsed = JSON.parse(value);
29
+ if (parsed === "*" || (Array.isArray(parsed) && parsed.length > 0)) {
30
+ return;
31
+ }
32
+ }
33
+ catch {
34
+ // ignore parse failure and attempt to set
35
+ }
36
+ }
37
+ await runCommand("clawdbot", ["config", "set", "channels.discord.dm.allowFrom", "*"], 8000);
38
+ }
@@ -0,0 +1,307 @@
1
+ import { getCliStatus } from "../lib/system.js";
2
+ import { runCommandWithLogs } from "../lib/runner.js";
3
+ import { parsePositiveInt, sleep } from "../lib/utils.js";
4
+ import { runQuickstart } from "./quickstart.service.js";
5
+ import { downloadResource } from "./resource.service.js";
6
+ const MINIMAX_CN_BASE_URL = "https://api.minimaxi.com/anthropic";
7
+ const DEFAULT_PAIRING_WAIT_TIMEOUT_MS = 180_000;
8
+ const DEFAULT_PAIRING_POLL_MS = 3000;
9
+ const DEFAULT_PAIRING_APPROVE_TIMEOUT_MS = 8000;
10
+ export function createCliInstallJob(deps) {
11
+ const job = deps.jobStore.createJob("Install Clawdbot CLI");
12
+ deps.jobStore.startJob(job.id);
13
+ deps.jobStore.appendLog(job.id, "开始安装 Clawdbot CLI...");
14
+ const timeoutMs = parsePositiveInt(process.env.MANAGER_CLI_INSTALL_TIMEOUT_MS) ?? 600_000;
15
+ void (async () => {
16
+ const current = await getCliStatus(deps.runCommand);
17
+ if (current.installed) {
18
+ deps.jobStore.appendLog(job.id, `CLI 已安装${current.version ? `(${current.version})` : ""}。`);
19
+ deps.jobStore.completeJob(job.id, { version: current.version ?? null });
20
+ return;
21
+ }
22
+ await runCommandWithLogs("npm", ["i", "-g", "clawdbot@latest"], {
23
+ cwd: deps.repoRoot,
24
+ env: {
25
+ ...process.env,
26
+ NPM_CONFIG_AUDIT: "false",
27
+ NPM_CONFIG_FUND: "false"
28
+ },
29
+ timeoutMs,
30
+ onLog: (line) => deps.jobStore.appendLog(job.id, line)
31
+ });
32
+ const cli = await getCliStatus(deps.runCommand);
33
+ if (cli.version) {
34
+ deps.jobStore.appendLog(job.id, `CLI 版本: ${cli.version}`);
35
+ }
36
+ deps.jobStore.completeJob(job.id, { version: cli.version ?? null });
37
+ })().catch((err) => {
38
+ const message = err instanceof Error ? err.message : String(err);
39
+ deps.jobStore.appendLog(job.id, `安装失败: ${message}`);
40
+ deps.jobStore.failJob(job.id, message);
41
+ });
42
+ return job.id;
43
+ }
44
+ export function createQuickstartJob(deps, body) {
45
+ const job = deps.jobStore.createJob("Quickstart");
46
+ deps.jobStore.startJob(job.id);
47
+ deps.jobStore.appendLog(job.id, "开始执行快速启动...");
48
+ void runQuickstart(deps, body, (line) => deps.jobStore.appendLog(job.id, line))
49
+ .then((result) => {
50
+ if (!result.ok) {
51
+ deps.jobStore.appendLog(job.id, `快速启动失败: ${result.error}`);
52
+ deps.jobStore.failJob(job.id, result.error);
53
+ return;
54
+ }
55
+ deps.jobStore.appendLog(job.id, "快速启动完成。");
56
+ deps.jobStore.completeJob(job.id, {
57
+ gatewayReady: result.gatewayReady,
58
+ probeOk: result.probeOk ?? null
59
+ });
60
+ })
61
+ .catch((err) => {
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ deps.jobStore.appendLog(job.id, `快速启动失败: ${message}`);
64
+ deps.jobStore.failJob(job.id, message);
65
+ });
66
+ return job.id;
67
+ }
68
+ export function createDiscordPairingJob(deps, code) {
69
+ const job = deps.jobStore.createJob("Discord Pairing");
70
+ deps.jobStore.startJob(job.id);
71
+ deps.jobStore.appendLog(job.id, "开始处理配对请求...");
72
+ void runCommandWithLogs("clawdbot", ["pairing", "approve", "discord", code], {
73
+ cwd: deps.repoRoot,
74
+ env: process.env,
75
+ timeoutMs: 8000,
76
+ onLog: (line) => deps.jobStore.appendLog(job.id, line)
77
+ })
78
+ .then(() => {
79
+ deps.jobStore.appendLog(job.id, "配对已提交。");
80
+ deps.jobStore.completeJob(job.id, { code });
81
+ })
82
+ .catch((err) => {
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ deps.jobStore.appendLog(job.id, `配对失败: ${message}`);
85
+ deps.jobStore.failJob(job.id, message);
86
+ });
87
+ return job.id;
88
+ }
89
+ export function createDiscordPairingWaitJob(deps, options) {
90
+ const job = deps.jobStore.createJob("Discord Pairing Wait");
91
+ deps.jobStore.startJob(job.id);
92
+ deps.jobStore.appendLog(job.id, "等待配对请求...");
93
+ const timeoutMs = parsePositiveInt(toOptionalString(options.timeoutMs)) ??
94
+ parsePositiveInt(process.env.MANAGER_PAIRING_TIMEOUT_MS) ??
95
+ DEFAULT_PAIRING_WAIT_TIMEOUT_MS;
96
+ const pollMs = parsePositiveInt(toOptionalString(options.pollMs)) ??
97
+ parsePositiveInt(process.env.MANAGER_PAIRING_POLL_MS) ??
98
+ DEFAULT_PAIRING_POLL_MS;
99
+ const notify = Boolean(options.notify);
100
+ void (async () => {
101
+ const startedAt = Date.now();
102
+ let lastCount = -1;
103
+ while (Date.now() - startedAt < timeoutMs) {
104
+ const snapshot = await fetchDiscordPairings(deps);
105
+ if (snapshot.error) {
106
+ deps.jobStore.appendLog(job.id, `读取配对请求失败: ${snapshot.error}`);
107
+ }
108
+ if (snapshot.count !== lastCount) {
109
+ deps.jobStore.appendLog(job.id, `待处理配对请求: ${snapshot.count}`);
110
+ lastCount = snapshot.count;
111
+ }
112
+ if (snapshot.code) {
113
+ deps.jobStore.appendLog(job.id, "发现配对请求,开始批准...");
114
+ const args = notify
115
+ ? ["pairing", "approve", "--notify", "discord", snapshot.code]
116
+ : ["pairing", "approve", "discord", snapshot.code];
117
+ await runCommandWithLogs("clawdbot", args, {
118
+ cwd: deps.repoRoot,
119
+ env: process.env,
120
+ timeoutMs: DEFAULT_PAIRING_APPROVE_TIMEOUT_MS,
121
+ onLog: (line) => deps.jobStore.appendLog(job.id, line)
122
+ });
123
+ deps.jobStore.appendLog(job.id, "配对已提交。");
124
+ deps.jobStore.completeJob(job.id, { approved: true, notified: notify });
125
+ return;
126
+ }
127
+ await sleep(pollMs);
128
+ }
129
+ deps.jobStore.appendLog(job.id, "等待配对超时。");
130
+ deps.jobStore.failJob(job.id, "pairing timeout");
131
+ })().catch((err) => {
132
+ const message = err instanceof Error ? err.message : String(err);
133
+ deps.jobStore.appendLog(job.id, `配对失败: ${message}`);
134
+ deps.jobStore.failJob(job.id, message);
135
+ });
136
+ return job.id;
137
+ }
138
+ export function createResourceDownloadJob(deps, options) {
139
+ const job = deps.jobStore.createJob("Download Resources");
140
+ deps.jobStore.startJob(job.id);
141
+ const envUrl = process.env.MANAGER_RESOURCE_URL;
142
+ const url = options.url?.trim() || envUrl?.trim() || "";
143
+ const dir = process.env.MANAGER_RESOURCE_DIR;
144
+ const payload = {
145
+ url,
146
+ filename: options.filename,
147
+ dir: dir?.trim() || undefined
148
+ };
149
+ if (!payload.url) {
150
+ deps.jobStore.appendLog(job.id, "未配置资源地址。");
151
+ deps.jobStore.failJob(job.id, "resource url missing");
152
+ return job.id;
153
+ }
154
+ void downloadResource(payload, (line) => deps.jobStore.appendLog(job.id, line))
155
+ .then((result) => {
156
+ deps.jobStore.completeJob(job.id, result);
157
+ })
158
+ .catch((err) => {
159
+ const message = err instanceof Error ? err.message : String(err);
160
+ deps.jobStore.appendLog(job.id, `下载失败: ${message}`);
161
+ deps.jobStore.failJob(job.id, message);
162
+ });
163
+ return job.id;
164
+ }
165
+ function toOptionalString(value) {
166
+ if (typeof value === "number")
167
+ return String(value);
168
+ if (typeof value === "string")
169
+ return value;
170
+ return undefined;
171
+ }
172
+ async function fetchDiscordPairings(deps) {
173
+ try {
174
+ const output = await deps.runCommand("clawdbot", ["pairing", "list", "--channel", "discord", "--json"], 8000);
175
+ const parsed = JSON.parse(output);
176
+ const requests = Array.isArray(parsed?.requests) ? parsed.requests : [];
177
+ const first = requests[0];
178
+ const codeRaw = typeof first?.code === "string" ? first.code : first?.pairingCode;
179
+ const code = typeof codeRaw === "string" && codeRaw.trim() ? codeRaw.trim() : null;
180
+ return { code, count: requests.length };
181
+ }
182
+ catch (err) {
183
+ return {
184
+ code: null,
185
+ count: 0,
186
+ error: err instanceof Error ? err.message : String(err)
187
+ };
188
+ }
189
+ }
190
+ export function createAiAuthJob(deps, options) {
191
+ const job = deps.jobStore.createJob("Configure AI Provider");
192
+ deps.jobStore.startJob(job.id);
193
+ deps.jobStore.appendLog(job.id, "开始配置 AI 凭证...");
194
+ const provider = options.provider.trim().toLowerCase();
195
+ const config = resolveAiProviderConfig(provider);
196
+ if (!config) {
197
+ deps.jobStore.appendLog(job.id, `不支持的 provider: ${provider}`);
198
+ deps.jobStore.failJob(job.id, "unsupported provider");
199
+ return job.id;
200
+ }
201
+ const apiKey = options.apiKey.trim();
202
+ if (!apiKey) {
203
+ deps.jobStore.appendLog(job.id, "API Key 为空。");
204
+ deps.jobStore.failJob(job.id, "missing api key");
205
+ return job.id;
206
+ }
207
+ const timeoutMs = parsePositiveInt(process.env.MANAGER_AI_AUTH_TIMEOUT_MS) ?? 120_000;
208
+ const args = [
209
+ "onboard",
210
+ "--non-interactive",
211
+ "--accept-risk",
212
+ "--flow",
213
+ "manual",
214
+ "--mode",
215
+ "local",
216
+ "--auth-choice",
217
+ config.authChoice,
218
+ "--skip-channels",
219
+ "--skip-skills",
220
+ "--skip-health",
221
+ "--skip-ui",
222
+ "--skip-daemon"
223
+ ];
224
+ void (async () => {
225
+ await runCommandWithLogs("clawdbot", args, {
226
+ cwd: deps.repoRoot,
227
+ env: {
228
+ ...process.env,
229
+ [config.envVar]: apiKey
230
+ },
231
+ timeoutMs,
232
+ onLog: (line) => deps.jobStore.appendLog(job.id, line)
233
+ });
234
+ if (provider === "minimax-cn") {
235
+ await applyMinimaxCnConfig(deps, job.id, timeoutMs);
236
+ }
237
+ deps.jobStore.appendLog(job.id, "AI 凭证配置完成。");
238
+ deps.jobStore.completeJob(job.id, { provider });
239
+ })().catch((err) => {
240
+ const message = err instanceof Error ? err.message : String(err);
241
+ deps.jobStore.appendLog(job.id, `AI 配置失败: ${message}`);
242
+ deps.jobStore.failJob(job.id, message);
243
+ });
244
+ return job.id;
245
+ }
246
+ function resolveAiProviderConfig(provider) {
247
+ if (provider === "anthropic") {
248
+ return { authChoice: "apiKey", envVar: "ANTHROPIC_API_KEY" };
249
+ }
250
+ if (provider === "openai") {
251
+ return { authChoice: "openai-api-key", envVar: "OPENAI_API_KEY" };
252
+ }
253
+ if (provider === "openrouter") {
254
+ return { authChoice: "openrouter-api-key", envVar: "OPENROUTER_API_KEY" };
255
+ }
256
+ if (provider === "ai-gateway") {
257
+ return { authChoice: "ai-gateway-api-key", envVar: "AI_GATEWAY_API_KEY" };
258
+ }
259
+ if (provider === "gemini") {
260
+ return { authChoice: "gemini-api-key", envVar: "GEMINI_API_KEY" };
261
+ }
262
+ if (provider === "zai") {
263
+ return { authChoice: "zai-api-key", envVar: "ZAI_API_KEY" };
264
+ }
265
+ if (provider === "moonshot") {
266
+ return { authChoice: "moonshot-api-key", envVar: "MOONSHOT_API_KEY" };
267
+ }
268
+ if (provider === "kimi-code") {
269
+ return { authChoice: "kimi-code-api-key", envVar: "KIMI_CODE_API_KEY" };
270
+ }
271
+ if (provider === "minimax") {
272
+ return { authChoice: "minimax-api", envVar: "MINIMAX_API_KEY" };
273
+ }
274
+ if (provider === "minimax-cn") {
275
+ return { authChoice: "minimax-api", envVar: "MINIMAX_API_KEY" };
276
+ }
277
+ if (provider === "minimax-lightning") {
278
+ return { authChoice: "minimax-api-lightning", envVar: "MINIMAX_API_KEY" };
279
+ }
280
+ if (provider === "venice") {
281
+ return { authChoice: "venice-api-key", envVar: "VENICE_API_KEY" };
282
+ }
283
+ if (provider === "synthetic") {
284
+ return { authChoice: "synthetic-api-key", envVar: "SYNTHETIC_API_KEY" };
285
+ }
286
+ if (provider === "opencode-zen") {
287
+ return { authChoice: "opencode-zen", envVar: "OPENCODE_ZEN_API_KEY" };
288
+ }
289
+ return null;
290
+ }
291
+ async function applyMinimaxCnConfig(deps, jobId, timeoutMs) {
292
+ deps.jobStore.appendLog(jobId, "配置 MiniMax 国内 API 地址...");
293
+ await runCommandWithLogs("clawdbot", ["config", "set", "models.providers.minimax.baseUrl", MINIMAX_CN_BASE_URL], {
294
+ cwd: deps.repoRoot,
295
+ env: process.env,
296
+ timeoutMs,
297
+ onLog: (line) => deps.jobStore.appendLog(jobId, line)
298
+ });
299
+ deps.jobStore.appendLog(jobId, "配置 MiniMax 国内鉴权头...");
300
+ await runCommandWithLogs("clawdbot", ["config", "set", "models.providers.minimax.authHeader", "true", "--json"], {
301
+ cwd: deps.repoRoot,
302
+ env: process.env,
303
+ timeoutMs,
304
+ onLog: (line) => deps.jobStore.appendLog(jobId, line)
305
+ });
306
+ deps.jobStore.appendLog(jobId, "MiniMax 国内配置完成。");
307
+ }
@@ -0,0 +1,9 @@
1
+ export function listProcesses(deps) {
2
+ return deps.processManager.listProcesses();
3
+ }
4
+ export function startProcess(deps, id) {
5
+ return deps.processManager.startProcess(id);
6
+ }
7
+ export function stopProcess(deps, id) {
8
+ return deps.processManager.stopProcess(id);
9
+ }
@@ -0,0 +1,124 @@
1
+ import { DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT } from "../lib/constants.js";
2
+ import { checkGateway, waitForGateway } from "../lib/gateway.js";
3
+ import { runCommandWithLogs } from "../lib/runner.js";
4
+ import { getCliStatus } from "../lib/system.js";
5
+ import { parsePort, parsePositiveInt, sleep } from "../lib/utils.js";
6
+ import { setLastProbe } from "../lib/onboarding.js";
7
+ export async function runQuickstart(deps, body, log) {
8
+ const runProbe = Boolean(body?.runProbe);
9
+ const startGateway = body?.startGateway !== false;
10
+ const gatewayHost = typeof body?.gatewayHost === "string" ? body.gatewayHost : DEFAULT_GATEWAY_HOST;
11
+ const gatewayPort = typeof body?.gatewayPort === "string"
12
+ ? parsePort(body.gatewayPort) ?? DEFAULT_GATEWAY_PORT
13
+ : DEFAULT_GATEWAY_PORT;
14
+ let gatewayReady = false;
15
+ let probeOk;
16
+ const gatewayTimeoutMs = parsePositiveInt(process.env.MANAGER_GATEWAY_TIMEOUT_MS) ?? 60_000;
17
+ log?.(`网关参数: host=${gatewayHost} port=${gatewayPort} timeout=${gatewayTimeoutMs}ms`);
18
+ log?.("检查 CLI 环境...");
19
+ const cli = await getCliStatus(deps.runCommand);
20
+ if (!cli.installed) {
21
+ log?.("未检测到 CLI。");
22
+ return { ok: false, error: "clawdbot CLI not installed", status: 400 };
23
+ }
24
+ if (cli.path) {
25
+ log?.(`CLI 路径: ${cli.path}`);
26
+ }
27
+ if (cli.version) {
28
+ log?.(`CLI 版本: ${cli.version}`);
29
+ }
30
+ if (startGateway) {
31
+ log?.("初始化网关配置...");
32
+ await runCommandWithLogs("clawdbot", ["config", "set", "gateway.mode", "local"], {
33
+ cwd: deps.repoRoot,
34
+ env: process.env,
35
+ timeoutMs: 8000,
36
+ onLog: (line) => log?.(`[config] ${line}`)
37
+ });
38
+ const gatewayArgs = [
39
+ "gateway",
40
+ "run",
41
+ "--allow-unconfigured",
42
+ "--bind",
43
+ "loopback",
44
+ "--port",
45
+ String(gatewayPort),
46
+ "--force"
47
+ ];
48
+ log?.("启动网关中...");
49
+ log?.(`启动命令: clawdbot ${gatewayArgs.join(" ")}`);
50
+ const started = deps.processManager.startProcess("gateway-run", {
51
+ args: gatewayArgs,
52
+ onLog: (line) => log?.(`[gateway] ${line}`)
53
+ });
54
+ if (!started.ok) {
55
+ return { ok: false, error: started.error ?? "unknown", status: 500 };
56
+ }
57
+ await sleep(500);
58
+ const earlySnapshot = deps.processManager
59
+ .listProcesses()
60
+ .find((process) => process.id === "gateway-run");
61
+ if (earlySnapshot && !earlySnapshot.running) {
62
+ if (earlySnapshot.lastLines.length) {
63
+ log?.("网关进程已退出,输出如下:");
64
+ for (const line of earlySnapshot.lastLines) {
65
+ log?.(`[gateway] ${line}`);
66
+ }
67
+ }
68
+ return { ok: false, error: "gateway process exited", status: 500 };
69
+ }
70
+ gatewayReady = await waitForGateway(gatewayHost, gatewayPort, gatewayTimeoutMs);
71
+ if (!gatewayReady) {
72
+ log?.("网关启动超时。");
73
+ const probe = await checkGateway(gatewayHost, gatewayPort);
74
+ log?.(`网关探测结果: ok=${probe.ok} error=${probe.error ?? "none"} latency=${probe.latencyMs ?? "n/a"}ms`);
75
+ const snapshot = deps.processManager
76
+ .listProcesses()
77
+ .find((process) => process.id === "gateway-run");
78
+ if (snapshot?.lastLines?.length) {
79
+ log?.("网关进程输出(最近日志):");
80
+ for (const line of snapshot.lastLines) {
81
+ log?.(`[gateway] ${line}`);
82
+ }
83
+ }
84
+ if (snapshot && snapshot.exitCode !== null) {
85
+ log?.(`网关进程已退出,exit code: ${snapshot.exitCode}`);
86
+ }
87
+ if (snapshot) {
88
+ log?.(`网关进程状态: running=${snapshot.running} pid=${snapshot.pid ?? "?"}`);
89
+ }
90
+ log?.(`排查建议: 确认端口 ${gatewayPort} 未被占用,或执行 clawdbot logs --follow 查看网关日志。`);
91
+ return { ok: false, error: "gateway not ready", status: 504 };
92
+ }
93
+ log?.("网关已就绪。");
94
+ }
95
+ else {
96
+ log?.("检查网关状态...");
97
+ const snapshot = await checkGateway(gatewayHost, gatewayPort);
98
+ gatewayReady = snapshot.ok;
99
+ if (!gatewayReady) {
100
+ log?.(`网关未就绪: error=${snapshot.error ?? "none"} latency=${snapshot.latencyMs ?? "n/a"}ms`);
101
+ }
102
+ else {
103
+ log?.("网关已就绪。");
104
+ }
105
+ }
106
+ if (runProbe) {
107
+ const probeAttempts = parsePositiveInt(process.env.MANAGER_PROBE_ATTEMPTS) ?? 3;
108
+ const probeDelayMs = parsePositiveInt(process.env.MANAGER_PROBE_DELAY_MS) ?? 2000;
109
+ log?.("执行通道探测...");
110
+ for (let attempt = 1; attempt <= probeAttempts; attempt += 1) {
111
+ probeOk = await deps
112
+ .runCommand("clawdbot", ["channels", "status", "--probe"], 12_000)
113
+ .then(() => true)
114
+ .catch(() => false);
115
+ if (probeOk || attempt >= probeAttempts)
116
+ break;
117
+ log?.(`通道探测未通过,${probeDelayMs}ms 后重试 (${attempt}/${probeAttempts})...`);
118
+ await sleep(probeDelayMs);
119
+ }
120
+ setLastProbe(Boolean(probeOk));
121
+ log?.(probeOk ? "通道探测通过。" : "通道探测未通过。");
122
+ }
123
+ return { ok: true, gatewayReady, probeOk };
124
+ }