@soloco/cli 0.3.1-canary.0

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/dist/index.js ADDED
@@ -0,0 +1,1290 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/program.ts
4
+ import { Command } from "commander";
5
+ import pc5 from "picocolors";
6
+ import * as fs from "fs";
7
+ import * as net from "net";
8
+ import * as os2 from "os";
9
+ import * as path2 from "path";
10
+ import * as readline2 from "readline";
11
+ import { spawn } from "node:child_process";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ // ../packages/local-runtime/src/cloud-auth.ts
15
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ var DEFAULT_CLOUD_BASE_URL = "https://soloco.cloud";
19
+ function resolveDefaultCloudBaseUrl(env = process.env) {
20
+ return env.SOLOCO_CLOUD_URL?.trim() || DEFAULT_CLOUD_BASE_URL;
21
+ }
22
+ function resolveAuthFilePath(env = process.env) {
23
+ const home = resolveSolocoHomeDir(env);
24
+ return path.resolve(home, "auth.json");
25
+ }
26
+ async function loadStoredAuth(filePath = resolveAuthFilePath()) {
27
+ try {
28
+ const raw = await readFile(filePath, "utf8");
29
+ const parsed = JSON.parse(raw);
30
+ if (!isStoredAuth(parsed)) return null;
31
+ return parsed;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function saveStoredAuth(auth, filePath = resolveAuthFilePath()) {
37
+ await mkdir(path.dirname(filePath), { recursive: true, mode: 448 });
38
+ const tempPath = `${filePath}.${process.pid}.tmp`;
39
+ await writeFile(tempPath, `${JSON.stringify(auth, null, 2)}
40
+ `, { encoding: "utf8", mode: 384 });
41
+ try {
42
+ await rename(tempPath, filePath);
43
+ } catch (err) {
44
+ await rm(tempPath, { force: true });
45
+ throw err;
46
+ }
47
+ }
48
+ async function clearStoredAuth(filePath = resolveAuthFilePath()) {
49
+ await rm(filePath, { force: true });
50
+ }
51
+ function normalizeCloudBaseUrl(raw) {
52
+ const trimmed = raw.trim();
53
+ if (!trimmed) throw new Error("Cloud server URL is required");
54
+ return assertHttpUrl(trimmed).replace(/\/+$/, "");
55
+ }
56
+ function createCloudAuthClient(rawBaseUrl) {
57
+ const baseUrl = normalizeCloudBaseUrl(rawBaseUrl);
58
+ return {
59
+ baseUrl,
60
+ start: async (installationName) => requestJson(baseUrl, "/api/soloco/device-auth/start", {
61
+ method: "POST",
62
+ body: { installationName }
63
+ }),
64
+ poll: async (id) => requestJson(
65
+ baseUrl,
66
+ `/api/soloco/device-auth/poll/${encodeURIComponent(id)}`,
67
+ { method: "GET" },
68
+ { mapErrorResponse: mapExpiredPollResponse }
69
+ ),
70
+ refresh: async (refreshToken) => requestJson(baseUrl, "/api/soloco/device-auth/refresh", {
71
+ method: "POST",
72
+ body: { refreshToken }
73
+ })
74
+ };
75
+ }
76
+ async function refreshStoredAccessToken(client, auth) {
77
+ const refreshed = await client.refresh(auth.refreshToken);
78
+ return {
79
+ ...auth,
80
+ accessToken: refreshed.accessToken,
81
+ accessTokenExpiresAt: refreshed.expiresAt,
82
+ // Persist a rotated refresh token when the server returns one; otherwise keep
83
+ // the existing one (launch server does not rotate). Single-use rotation, once
84
+ // enabled server-side, then works without any further client change.
85
+ // Note: DeviceAuthRefreshResponse uses `refreshExpiresAt` (no "Token" infix)
86
+ // while StoredAuth uses `refreshTokenExpiresAt` — intentional asymmetry since
87
+ // the response DTO mirrors the `expiresAt` / `refreshExpiresAt` pair from the
88
+ // server contract, whereas StoredAuth disambiguates access vs refresh locally.
89
+ refreshToken: refreshed.refreshToken ?? auth.refreshToken,
90
+ refreshTokenExpiresAt: refreshed.refreshExpiresAt ?? auth.refreshTokenExpiresAt,
91
+ userId: refreshed.userId,
92
+ installationId: refreshed.installationId
93
+ };
94
+ }
95
+ async function ensureCloudLogin(options) {
96
+ const auth = await loadStoredAuth(options.authPath);
97
+ if (!auth) return runDeviceLogin(options);
98
+ if (isAccessTokenFresh(auth.accessToken)) return auth;
99
+ try {
100
+ const client = (options.clientFactory ?? createCloudAuthClient)(auth.cloudBaseUrl);
101
+ const refreshed = await refreshStoredAccessToken(client, auth);
102
+ await saveStoredAuth(refreshed, options.authPath);
103
+ return refreshed;
104
+ } catch {
105
+ return runDeviceLogin({
106
+ ...options,
107
+ serverUrl: auth.cloudBaseUrl || options.serverUrl
108
+ });
109
+ }
110
+ }
111
+ async function runDeviceLogin(options) {
112
+ const serverUrl = options.serverUrl ?? resolveDefaultCloudBaseUrl();
113
+ const client = createCloudAuthClient(serverUrl);
114
+ const started = await client.start(options.installationName ?? defaultInstallationName());
115
+ await options.onStarted?.(started);
116
+ await options.openBrowser(assertHttpUrl(started.authUrl));
117
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
118
+ const intervalMs = Math.max(1e3, started.pollIntervalMs ?? 2e3);
119
+ const deadline = Date.now() + 10 * 6e4;
120
+ while (Date.now() < deadline) {
121
+ const polled = await client.poll(started.id);
122
+ if (polled.status === "authorized") {
123
+ return saveAuthorizedDeviceAuth(client.baseUrl, polled, options.authPath);
124
+ }
125
+ if (polled.status === "expired") {
126
+ throw new Error("\u767B\u5F55\u6388\u6743\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C soloco login");
127
+ }
128
+ await sleep(intervalMs);
129
+ }
130
+ throw new Error("\u767B\u5F55\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C soloco login");
131
+ }
132
+ async function saveAuthorizedDeviceAuth(cloudBaseUrl, polled, authPath) {
133
+ const auth = {
134
+ version: 1,
135
+ cloudBaseUrl,
136
+ userId: polled.userId,
137
+ installationId: polled.installationId,
138
+ accessToken: polled.accessToken,
139
+ accessTokenExpiresAt: polled.expiresAt,
140
+ refreshToken: polled.refreshToken,
141
+ refreshTokenExpiresAt: polled.refreshExpiresAt,
142
+ lastLoginAt: (/* @__PURE__ */ new Date()).toISOString()
143
+ };
144
+ await saveStoredAuth(auth, authPath);
145
+ return auth;
146
+ }
147
+ function isAccessTokenFresh(token, now = Date.now(), refreshBufferMs = 6e4) {
148
+ const exp = decodeJwtExp(token);
149
+ if (!exp) return false;
150
+ return exp * 1e3 - now > refreshBufferMs;
151
+ }
152
+ function resolveSolocoHomeDir(env) {
153
+ const raw = env.SOLOCO_HOME?.trim();
154
+ if (!raw) return path.resolve(os.homedir(), ".soloco");
155
+ if (raw === "~") return os.homedir();
156
+ if (raw.startsWith("~/")) return path.resolve(os.homedir(), raw.slice(2));
157
+ return path.resolve(raw);
158
+ }
159
+ function isStoredAuth(value) {
160
+ return value.version === 1 && isNonEmptyString(value.cloudBaseUrl) && isNonEmptyString(value.userId) && isNonEmptyString(value.installationId) && isNonEmptyString(value.accessToken) && isNonEmptyString(value.accessTokenExpiresAt) && isNonEmptyString(value.refreshToken) && isNonEmptyString(value.lastLoginAt);
161
+ }
162
+ function isNonEmptyString(value) {
163
+ return typeof value === "string" && value.trim().length > 0;
164
+ }
165
+ async function requestJson(baseUrl, endpoint, opts, behavior = {}) {
166
+ const response = await fetch(`${baseUrl}${endpoint}`, {
167
+ method: opts.method,
168
+ headers: { "Content-Type": "application/json" },
169
+ body: opts.body ? JSON.stringify(stripUndefined(opts.body)) : void 0
170
+ });
171
+ if (!response.ok) {
172
+ const mapped = await behavior.mapErrorResponse?.(response);
173
+ if (mapped) return mapped;
174
+ const text = await response.text().catch(() => "");
175
+ throw new Error(`Cloud auth ${opts.method} ${endpoint} failed (${response.status}): ${text}`);
176
+ }
177
+ return response.json();
178
+ }
179
+ async function mapExpiredPollResponse(response) {
180
+ if (response.status !== 410) return null;
181
+ const payload = await response.json().catch(() => null);
182
+ return payload?.status === "expired" ? { status: "expired" } : null;
183
+ }
184
+ function stripUndefined(input) {
185
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
186
+ }
187
+ function decodeJwtExp(token) {
188
+ const [, encodedPayload] = token.split(".");
189
+ if (!encodedPayload) return null;
190
+ try {
191
+ const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
192
+ return typeof payload.exp === "number" ? payload.exp : null;
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+ function defaultInstallationName() {
198
+ return `Soloco CLI (${os.hostname()})`;
199
+ }
200
+ function assertHttpUrl(rawUrl) {
201
+ let parsed;
202
+ try {
203
+ parsed = new URL(rawUrl);
204
+ } catch {
205
+ throw new Error(`\u65E0\u6548\u7684\u6388\u6743\u94FE\u63A5\uFF1A${rawUrl}`);
206
+ }
207
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
208
+ throw new Error(`\u4E0D\u5B89\u5168\u7684\u6388\u6743\u94FE\u63A5\u534F\u8BAE\uFF1A${parsed.protocol}`);
209
+ }
210
+ return rawUrl;
211
+ }
212
+
213
+ // src/lib/api.ts
214
+ function createApiClient(apiBase) {
215
+ return async function api(method, endpoint, body) {
216
+ const url = `${apiBase}${endpoint}`;
217
+ const res = await fetch(url, {
218
+ method,
219
+ headers: { "Content-Type": "application/json" },
220
+ body: body ? JSON.stringify(body) : void 0
221
+ });
222
+ if (!res.ok) {
223
+ const text = await res.text().catch(() => "");
224
+ throw new Error(`API ${method} ${endpoint} failed (${res.status}): ${text}`);
225
+ }
226
+ const ct = res.headers.get("content-type") || "";
227
+ if (ct.includes("application/json")) return res.json();
228
+ return void 0;
229
+ };
230
+ }
231
+
232
+ // src/lib/cloud-login.ts
233
+ import pc from "picocolors";
234
+ async function ensureCloudLogin2(options) {
235
+ return ensureCloudLogin({
236
+ ...options,
237
+ onStarted: printDeviceAuthStart
238
+ });
239
+ }
240
+ async function runDeviceLogin2(options) {
241
+ return runDeviceLogin({
242
+ ...options,
243
+ onStarted: printDeviceAuthStart
244
+ });
245
+ }
246
+ function printDeviceAuthStart(started) {
247
+ console.log(pc.bold("\u6253\u5F00\u6D4F\u89C8\u5668\u5B8C\u6210 Soloco \u767B\u5F55\u6388\u6743"));
248
+ console.log(pc.dim(started.authUrl));
249
+ }
250
+
251
+ // src/lib/env.ts
252
+ var DEFAULT_API_BASE = "http://localhost:3100";
253
+ function resolveApiBase(env = process.env) {
254
+ return env.SOLOCO_API_BASE?.trim() || DEFAULT_API_BASE;
255
+ }
256
+
257
+ // src/lib/goal-text.ts
258
+ function joinGoalParts(goalParts) {
259
+ return goalParts.join(" ").trim();
260
+ }
261
+ function extractCurrentGoalId(current) {
262
+ if (Array.isArray(current)) {
263
+ return current[0]?.id;
264
+ }
265
+ return current?.id;
266
+ }
267
+ function parseLogLimit(raw, fallback = 20) {
268
+ const parsed = Number.parseInt(raw ?? String(fallback), 10);
269
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
270
+ }
271
+
272
+ // src/lib/print-goal.ts
273
+ import pc3 from "picocolors";
274
+
275
+ // src/lib/format.ts
276
+ import pc2 from "picocolors";
277
+ function colorStatus(status) {
278
+ switch (status) {
279
+ case "running":
280
+ return pc2.green(status);
281
+ case "paused":
282
+ return pc2.yellow(status);
283
+ case "completed":
284
+ return pc2.cyan(status);
285
+ case "aborted":
286
+ return pc2.red(status);
287
+ default:
288
+ return status;
289
+ }
290
+ }
291
+
292
+ // src/lib/print-goal.ts
293
+ function printGoal(goal) {
294
+ console.log(` \u76EE\u6807: ${pc3.bold(goal.description || goal.goal || "-")}`);
295
+ console.log(` ID: ${goal.id}`);
296
+ console.log(` \u72B6\u6001: ${colorStatus(goal.status || "-")}`);
297
+ if (goal.progress != null) console.log(` \u8FDB\u5EA6: ${goal.progress}%`);
298
+ if (goal.budget != null) console.log(` \u9884\u7B97: ${goal.budget}`);
299
+ if (goal.currentCycle != null) console.log(` \u5FAA\u73AF: \u7B2C ${goal.currentCycle} \u8F6E`);
300
+ }
301
+
302
+ // src/lib/monitor.ts
303
+ import readline from "node:readline";
304
+ import pc4 from "picocolors";
305
+ async function monitorGoal(goalId, api, apiBase, sleep) {
306
+ let isRunning = true;
307
+ let isPrompting = false;
308
+ const setupRawMode = () => {
309
+ if (process.stdin.isTTY) {
310
+ process.stdin.setRawMode(true);
311
+ readline.emitKeypressEvents(process.stdin);
312
+ }
313
+ };
314
+ const restoreNormalMode = () => {
315
+ if (process.stdin.isTTY) {
316
+ process.stdin.setRawMode(false);
317
+ }
318
+ };
319
+ setupRawMode();
320
+ const onKeypress = async (str, key) => {
321
+ if (isPrompting) return;
322
+ if (key.ctrl && key.name === "c" || key.name === "q") {
323
+ isRunning = false;
324
+ restoreNormalMode();
325
+ process.stdin.off("keypress", onKeypress);
326
+ console.clear();
327
+ console.log(pc4.green("\u2714 \u5DF2\u9000\u51FA\u76D1\u63A7\u6A21\u5F0F\u3002\u76EE\u6807\u5728\u540E\u53F0\u7EE7\u7EED\u8FD0\u884C\u3002"));
328
+ process.exit(0);
329
+ }
330
+ try {
331
+ if (key.name === "p") {
332
+ console.log(pc4.yellow("\n\u6B63\u5728\u53D1\u9001\u6682\u505C\u8BF7\u6C42..."));
333
+ await api("POST", `/api/goals/${goalId}/pause`);
334
+ console.log(pc4.green("\u2714 \u76EE\u6807\u5DF2\u6682\u505C"));
335
+ await sleep(1e3);
336
+ } else if (key.name === "r") {
337
+ console.log(pc4.green("\n\u6B63\u5728\u53D1\u9001\u6062\u590D\u8BF7\u6C42..."));
338
+ await api("POST", `/api/goals/${goalId}/resume`);
339
+ console.log(pc4.green("\u2714 \u76EE\u6807\u5DF2\u6062\u590D"));
340
+ await sleep(1e3);
341
+ } else if (key.name === "a") {
342
+ restoreNormalMode();
343
+ isPrompting = true;
344
+ const rl = readline.createInterface({
345
+ input: process.stdin,
346
+ output: process.stdout
347
+ });
348
+ rl.question(pc4.red("\n\u786E\u5B9A\u8981\u7EC8\u6B62\u8BE5\u76EE\u6807\u5417\uFF1F(y/N): "), async (answer) => {
349
+ rl.close();
350
+ isPrompting = false;
351
+ setupRawMode();
352
+ if (answer.toLowerCase() === "y") {
353
+ console.log(pc4.red("\u6B63\u5728\u53D1\u9001\u7EC8\u6B62\u8BF7\u6C42..."));
354
+ await api("POST", `/api/goals/${goalId}/abort`);
355
+ console.log(pc4.green("\u2714 \u76EE\u6807\u5DF2\u7EC8\u6B62"));
356
+ await sleep(1e3);
357
+ }
358
+ });
359
+ } else if (key.name === "s") {
360
+ restoreNormalMode();
361
+ isPrompting = true;
362
+ const rl = readline.createInterface({
363
+ input: process.stdin,
364
+ output: process.stdout
365
+ });
366
+ rl.question(pc4.cyan("\n\u8BF7\u8F93\u5165\u63D2\u961F/\u8F6C\u5411\u6307\u4EE4\u5185\u5BB9: "), (content) => {
367
+ if (!content.trim()) {
368
+ rl.close();
369
+ isPrompting = false;
370
+ setupRawMode();
371
+ return;
372
+ }
373
+ rl.question(pc4.yellow("\u662F\u5426\u7ACB\u5373\u5F3A\u5236\u7EC8\u6B62\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684\u5B50\u4EFB\u52A1\uFF1F(y/N): "), async (forceKillAns) => {
374
+ rl.close();
375
+ isPrompting = false;
376
+ setupRawMode();
377
+ const forceKill = forceKillAns.toLowerCase() === "y";
378
+ console.log(pc4.cyan("\u6B63\u5728\u53D1\u9001\u8F6C\u5411\u6307\u4EE4..."));
379
+ await api("POST", `/api/goals/${goalId}/steer`, {
380
+ content,
381
+ forceKill
382
+ });
383
+ console.log(pc4.green("\u2714 \u8F6C\u5411\u6307\u4EE4\u53D1\u9001\u6210\u529F!"));
384
+ await sleep(1500);
385
+ });
386
+ });
387
+ }
388
+ } catch (e) {
389
+ const msg = e instanceof Error ? e.message : String(e);
390
+ console.error(pc4.red(`
391
+ \u64CD\u4F5C\u5931\u8D25: ${msg}`));
392
+ await sleep(2e3);
393
+ }
394
+ };
395
+ process.stdin.on("keypress", onKeypress);
396
+ while (isRunning) {
397
+ if (isPrompting) {
398
+ await sleep(500);
399
+ continue;
400
+ }
401
+ try {
402
+ const goal = await api("GET", `/api/goals/${goalId}`);
403
+ const runsRes = await api("GET", `/api/goals/${goalId}/runs`);
404
+ const runs = runsRes.runs || [];
405
+ const latestRun = runs[0];
406
+ let stdoutTail = "";
407
+ let stderrTail = "";
408
+ if (latestRun) {
409
+ try {
410
+ const logs = await api(
411
+ "GET",
412
+ `/api/goals/${goalId}/runs/${latestRun.id}/logs/tail?bytes=4096`
413
+ );
414
+ stdoutTail = logs.stdoutTail || "";
415
+ stderrTail = logs.stderrTail || "";
416
+ } catch {
417
+ }
418
+ }
419
+ console.clear();
420
+ drawDashboard(goal, latestRun, stdoutTail, stderrTail);
421
+ } catch (e) {
422
+ console.clear();
423
+ console.log(pc4.red("\u26A0\uFE0F \u65E0\u6CD5\u8FDE\u63A5\u5230 Soloco \u63A7\u5236\u9762\u670D\u52A1\u5668\uFF0C\u6B63\u5728\u91CD\u8BD5..."));
424
+ const msg = e instanceof Error ? e.message : String(e);
425
+ console.log(pc4.dim(`\u9519\u8BEF\u8BE6\u60C5: ${msg}`));
426
+ }
427
+ await sleep(1500);
428
+ }
429
+ }
430
+ function drawDashboard(goal, latestRun, stdoutTail, stderrTail) {
431
+ const width = process.stdout.columns || 80;
432
+ const separator = "\u2500".repeat(width);
433
+ console.log(pc4.bold(pc4.cyan(` Soloco Dev Insider Goal Monitor `).padStart(Math.floor((width + 30) / 2))));
434
+ console.log(pc4.dim(separator));
435
+ const budget = (goal.budgetUsd / 100).toFixed(2);
436
+ const spent = (goal.spentUsdCents / 100).toFixed(2);
437
+ const progressPercent = Math.round(goal.progressPercent || 0);
438
+ const progressBar = drawProgressBar(progressPercent, 20);
439
+ console.log(` ${pc4.bold("\u76EE\u6807\u6807\u9898:")} ${pc4.white(goal.title || goal.description.split("\n")[0])}`);
440
+ console.log(` ${pc4.bold("\u76EE\u6807 ID:")} ${pc4.dim(goal.id)}`);
441
+ console.log(
442
+ ` ${pc4.bold("\u72B6\u6001:")} ${colorStatus(goal.status)} ${pc4.bold("\u6A21\u578B:")} ${pc4.magenta(goal.conductorModel || "-")}`
443
+ );
444
+ console.log(
445
+ ` ${pc4.bold("\u5FAA\u73AF:")} \u7B2C ${pc4.yellow(goal.currentCycle)} \u8F6E / \u6700\u591A ${goal.maxCycles} \u8F6E`
446
+ );
447
+ console.log(
448
+ ` ${pc4.bold("\u9884\u7B97/\u82B1\u8D39:")} $${pc4.green(spent)} / $${pc4.red(budget)} USD`
449
+ );
450
+ console.log(` ${pc4.bold("\u8FDB\u5EA6:")} ${progressBar} ${pc4.cyan(`${progressPercent}%`)}`);
451
+ if (goal.status === "initializing" && goal.initializationProgress) {
452
+ const ip = goal.initializationProgress;
453
+ console.log(pc4.dim(separator));
454
+ console.log(pc4.yellow(` \u2699\uFE0F \u521D\u59CB\u5316\u8FDB\u5EA6: [${ip.state}] \u7B2C ${ip.currentRound}/${ip.maxRounds} \u8F6E\u89C4\u5212\u8BC4\u4F30`));
455
+ if (ip.lastSummary) {
456
+ console.log(` ${pc4.dim("\u6700\u65B0\u6458\u8981:")} ${ip.lastSummary}`);
457
+ }
458
+ }
459
+ console.log(pc4.dim(separator));
460
+ if (latestRun) {
461
+ const runDuration = getDuration(latestRun.startedAt, latestRun.endedAt);
462
+ const runCost = (latestRun.costUsdCents / 100).toFixed(4);
463
+ console.log(
464
+ ` ${pc4.bold("\u6700\u65B0\u5B50\u4EFB\u52A1:")} ${pc4.blue(latestRun.command)} ${pc4.dim(latestRun.args.join(" "))}`
465
+ );
466
+ console.log(
467
+ ` ${pc4.bold("\u4EFB\u52A1 ID:")} ${pc4.dim(latestRun.id)}`
468
+ );
469
+ console.log(
470
+ ` ${pc4.bold("\u4EFB\u52A1\u72B6\u6001:")} ${colorStatus(latestRun.status)} ${pc4.bold("\u8017\u65F6:")} ${runDuration} ${pc4.bold("\u6210\u672C:")} $${runCost}`
471
+ );
472
+ console.log(pc4.dim(separator));
473
+ console.log(` ${pc4.bold("\u{1F4C4} \u4EFB\u52A1\u6807\u51C6\u8F93\u51FA (Stdout Tail):")}`);
474
+ if (stdoutTail.trim()) {
475
+ const lines = stdoutTail.split("\n").slice(-12);
476
+ lines.forEach((line) => {
477
+ console.log(` ${pc4.dim(line)}`);
478
+ });
479
+ } else {
480
+ console.log(pc4.dim(" [\u6682\u65E0\u8F93\u51FA\u6216\u6B63\u5728\u542F\u52A8...]"));
481
+ }
482
+ if (stderrTail.trim()) {
483
+ console.log(pc4.dim(separator));
484
+ console.log(` ${pc4.bold(pc4.red("\u26A0\uFE0F \u4EFB\u52A1\u9519\u8BEF\u8F93\u51FA (Stderr Tail):"))}`);
485
+ const lines = stderrTail.split("\n").slice(-5);
486
+ lines.forEach((line) => {
487
+ console.log(` ${pc4.red(line)}`);
488
+ });
489
+ }
490
+ } else {
491
+ console.log(pc4.dim(" \u2139\uFE0F \u5F53\u524D\u6682\u65E0\u5B50\u4EFB\u52A1\u8FD0\u884C\u3002"));
492
+ }
493
+ console.log(pc4.dim(separator));
494
+ console.log(
495
+ ` ${pc4.bold("\u5FEB\u6377\u952E:")} [${pc4.yellow("p")}] \u6682\u505C [${pc4.green("r")}] \u6062\u590D [${pc4.red("a")}] \u7EC8\u6B62 [${pc4.cyan("s")}] \u8F6C\u5411/\u63D2\u961F\u6307\u4EE4 [${pc4.white("q")}] \u9000\u51FA\u76D1\u63A7`
496
+ );
497
+ }
498
+ function drawProgressBar(percent, size) {
499
+ const filledSize = Math.round(percent / 100 * size);
500
+ const emptySize = size - filledSize;
501
+ return pc4.green("\u2588".repeat(filledSize)) + pc4.dim("\u2591".repeat(emptySize));
502
+ }
503
+ function getDuration(start, end) {
504
+ if (!start) return "-";
505
+ const startTime = new Date(start).getTime();
506
+ const endTime = end ? new Date(end).getTime() : Date.now();
507
+ const diffMs = endTime - startTime;
508
+ const secs = Math.floor(diffMs / 1e3) % 60;
509
+ const mins = Math.floor(diffMs / 6e4);
510
+ return `${mins}m ${secs}s`;
511
+ }
512
+
513
+ // src/program.ts
514
+ function createDefaultCliDeps() {
515
+ const apiBase = resolveApiBase();
516
+ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
517
+ return {
518
+ apiBase,
519
+ api: createApiClient(apiBase),
520
+ ensureCloudLogin: () => ensureCloudLogin2({ openBrowser, sleep }),
521
+ sleep,
522
+ openBrowser
523
+ };
524
+ }
525
+ function createCliProgram(deps = createDefaultCliDeps()) {
526
+ const { api, apiBase, ensureCloudLogin: ensureCloudLogin3, sleep, openBrowser: openBrowser2 } = deps;
527
+ const program2 = new Command();
528
+ program2.name("soloco").description("Soloco CLI \u2014 \u7528\u4E00\u53E5\u8BDD\u542F\u52A8 AI \u76EE\u6807\u5FAA\u73AF").version(resolveCliVersion());
529
+ program2.command("login").description("\u767B\u5F55 Soloco Cloud\uFF0C\u7528\u4E8E\u540E\u7EED\u672C\u5730\u8FD0\u884C\u65F6\u540C\u6B65").option(
530
+ "--server-url <url>",
531
+ "Soloco Cloud URL",
532
+ resolveDefaultCloudBaseUrl()
533
+ ).option("--name <name>", "\u5F53\u524D\u8BBE\u5907\u540D\u79F0", defaultInstallationName2()).action(async (opts) => {
534
+ try {
535
+ await runDeviceLogin2({
536
+ installationName: opts.name,
537
+ openBrowser: openBrowser2,
538
+ serverUrl: opts.serverUrl,
539
+ sleep
540
+ });
541
+ console.log(pc5.green("\u767B\u5F55\u6210\u529F\uFF0C\u5DF2\u4FDD\u5B58\u672C\u673A Soloco Cloud \u51ED\u8BC1"));
542
+ } catch (e) {
543
+ const message = e instanceof Error ? e.message : String(e);
544
+ console.error(pc5.red(`\u767B\u5F55\u5931\u8D25: ${message}`));
545
+ process.exit(1);
546
+ }
547
+ });
548
+ program2.command("logout").description("\u5220\u9664\u672C\u673A Soloco Cloud \u767B\u5F55\u51ED\u8BC1").action(async () => {
549
+ await clearStoredAuth();
550
+ console.log(pc5.green("\u5DF2\u9000\u51FA Soloco Cloud \u767B\u5F55"));
551
+ });
552
+ program2.command("whoami").description("\u6821\u9A8C\u5F53\u524D Soloco Cloud \u767B\u5F55\u72B6\u6001").action(async () => {
553
+ try {
554
+ const auth = await loadStoredAuth();
555
+ if (!auth) {
556
+ console.error(pc5.yellow("\u5C1A\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C soloco login"));
557
+ process.exit(1);
558
+ }
559
+ const client = createCloudAuthClient(auth.cloudBaseUrl);
560
+ const refreshed = await refreshStoredAccessToken(client, auth);
561
+ await saveStoredAuth(refreshed);
562
+ console.log(`Soloco Cloud: ${pc5.bold(refreshed.cloudBaseUrl)}`);
563
+ console.log(`User: ${refreshed.userId}`);
564
+ console.log(`Installation: ${refreshed.installationId}`);
565
+ } catch (e) {
566
+ const message = e instanceof Error ? e.message : String(e);
567
+ console.error(pc5.red(`\u767B\u5F55\u6821\u9A8C\u5931\u8D25: ${message}`));
568
+ console.error(pc5.yellow("\u8BF7\u91CD\u65B0\u8FD0\u884C soloco login"));
569
+ process.exit(1);
570
+ }
571
+ });
572
+ program2.argument("[goal...]", "\u76EE\u6807\u63CF\u8FF0").action(async (goalParts, _opts, cmd) => {
573
+ const goal = joinGoalParts(goalParts);
574
+ if (!goal) {
575
+ cmd.help();
576
+ return;
577
+ }
578
+ console.log(pc5.bold(`\u542F\u52A8\u76EE\u6807: ${goal}`));
579
+ console.log();
580
+ let created;
581
+ try {
582
+ await ensureCloudLogin3();
583
+ created = await api("POST", "/api/goals", { description: goal });
584
+ } catch (e) {
585
+ const message = e instanceof Error ? e.message : String(e);
586
+ console.error(pc5.red(`\u521B\u5EFA\u76EE\u6807\u5931\u8D25: ${message}`));
587
+ process.exit(1);
588
+ }
589
+ const goalId = created.id;
590
+ console.log(`\u76EE\u6807\u5DF2\u521B\u5EFA (${pc5.dim(goalId)}), \u5F00\u59CB\u8F6E\u8BE2\u8FDB\u5EA6...`);
591
+ console.log(pc5.dim("\u6309 Ctrl+C \u9000\u51FA\u8F6E\u8BE2 (\u76EE\u6807\u7EE7\u7EED\u5728\u540E\u53F0\u8FD0\u884C)"));
592
+ console.log();
593
+ let lastCycle = -1;
594
+ while (true) {
595
+ try {
596
+ const status = await api("GET", `/api/goals/${goalId}`);
597
+ if (status.currentCycle !== lastCycle) {
598
+ lastCycle = status.currentCycle ?? 0;
599
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
600
+ const summary = status.lastCycleSummary || status.status || "-";
601
+ console.log(`${pc5.dim(ts)} [\u5FAA\u73AF ${lastCycle}] ${summary}`);
602
+ }
603
+ if (["completed", "aborted", "failed"].includes(status.status || "")) {
604
+ console.log();
605
+ console.log(pc5.bold("\u76EE\u6807\u7ED3\u675F"));
606
+ printGoal(status);
607
+ break;
608
+ }
609
+ } catch {
610
+ }
611
+ await sleep(3e3);
612
+ }
613
+ });
614
+ program2.command("status").description("\u67E5\u770B\u76EE\u6807\u72B6\u6001 (\u652F\u6301 --url \u89E3\u6790 Dashboard URL, --json \u673A\u5668\u53EF\u8BFB, --watch \u6301\u7EED\u8F6E\u8BE2)").argument("[goal-id-or-url]", "\u76EE\u6807 ID \u6216 Dashboard URL (\u4E0D\u4F20\u5219\u9ED8\u8BA4\u5F53\u524D\u6D3B\u8DC3\u76EE\u6807)").option("--url <url>", "Dashboard URL (\u540C\u4F4D\u7F6E\u53C2\u6570, \u4F18\u5148\u7EA7\u66F4\u9AD8)").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA (\u5355\u6B21\u6216 NDJSON \u6D41)").option("--deep", "\u4F7F\u7528 /snapshot \u805A\u5408\u7AEF\u70B9 (\u5305\u542B runs/pending/events \u7B49)").option("--watch", "\u6301\u7EED\u8F6E\u8BE2\u76F4\u5230\u7EC8\u6B62\u72B6\u6001").option("--interval <seconds>", "\u8F6E\u8BE2\u95F4\u9694\u79D2\u6570 (--watch \u6A21\u5F0F)", "5").action(async (goalIdOrUrl, opts) => {
615
+ try {
616
+ await ensureCloudLogin3();
617
+ let resolvedGoalId;
618
+ let resolvedApiBase = apiBase;
619
+ const rawUrl = opts.url ?? goalIdOrUrl;
620
+ if (rawUrl && (rawUrl.startsWith("http://") || rawUrl.startsWith("https://"))) {
621
+ try {
622
+ const parsed = new URL(rawUrl);
623
+ resolvedApiBase = `${parsed.protocol}//${parsed.host}`;
624
+ const idParam = parsed.searchParams.get("id");
625
+ if (idParam) {
626
+ resolvedGoalId = idParam;
627
+ } else {
628
+ const m = parsed.pathname.match(/\/goals?\/([a-f0-9-]{36})/i);
629
+ if (m) resolvedGoalId = m[1];
630
+ }
631
+ } catch {
632
+ }
633
+ } else if (rawUrl) {
634
+ resolvedGoalId = rawUrl;
635
+ }
636
+ const resolvedApi = resolvedApiBase !== apiBase ? createApiClient(resolvedApiBase) : api;
637
+ const fetchSnapshot = async () => {
638
+ if (!resolvedGoalId) {
639
+ const current = await resolvedApi("GET", "/api/goals/current");
640
+ const g = Array.isArray(current) ? current[0] : current;
641
+ resolvedGoalId = g?.id;
642
+ }
643
+ if (!resolvedGoalId) return null;
644
+ const endpoint = opts.deep ? `/api/goals/${resolvedGoalId}/snapshot` : `/api/goals/${resolvedGoalId}`;
645
+ return resolvedApi("GET", endpoint);
646
+ };
647
+ const printResult = (data) => {
648
+ if (opts.json) {
649
+ process.stdout.write(JSON.stringify(data) + "\n");
650
+ } else {
651
+ const g = opts.deep ? data.goal ?? data : data;
652
+ printGoal(g);
653
+ if (opts.deep) {
654
+ const snap = data;
655
+ const pending = snap.pending;
656
+ const activeRuns = snap.activeRuns;
657
+ if (activeRuns?.length) {
658
+ console.log(pc5.dim(`
659
+ \u6D3B\u8DC3 runs: ${activeRuns.length} \u4E2A`));
660
+ }
661
+ const pendingCount = (pending?.questions?.length ?? 0) + (pending?.assumptions?.length ?? 0) + (pending?.degradations?.length ?? 0) + (pending?.manualUnlocks?.length ?? 0);
662
+ if (pendingCount > 0) {
663
+ console.log(pc5.yellow(`\u5F85\u5904\u7406\u9879: ${pendingCount} \u4E2A`));
664
+ }
665
+ }
666
+ }
667
+ };
668
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "aborted", "failed"]);
669
+ if (opts.watch) {
670
+ const intervalMs = Math.max(1, Number(opts.interval) || 5) * 1e3;
671
+ while (true) {
672
+ const data = await fetchSnapshot();
673
+ if (data) {
674
+ printResult(data);
675
+ const status = opts.deep ? data.goal?.status : data.status;
676
+ if (terminalStatuses.has(status)) break;
677
+ }
678
+ await sleep(intervalMs);
679
+ }
680
+ } else {
681
+ const data = await fetchSnapshot();
682
+ if (!data || Array.isArray(data) && data.length === 0) {
683
+ if (!opts.json) console.log("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u76EE\u6807");
684
+ else process.stdout.write(JSON.stringify(null) + "\n");
685
+ return;
686
+ }
687
+ printResult(data);
688
+ }
689
+ } catch (e) {
690
+ const message = e instanceof Error ? e.message : String(e);
691
+ console.error(pc5.red(`\u83B7\u53D6\u72B6\u6001\u5931\u8D25: ${message}`));
692
+ process.exit(1);
693
+ }
694
+ });
695
+ program2.command("pause").description("\u6682\u505C\u5F53\u524D\u76EE\u6807\u5FAA\u73AF").action(async () => {
696
+ try {
697
+ await ensureCloudLogin3();
698
+ const current = await api("GET", "/api/goals/current");
699
+ const id = extractCurrentGoalId(current);
700
+ if (!id) {
701
+ console.log("\u6CA1\u6709\u53EF\u6682\u505C\u7684\u6D3B\u8DC3\u76EE\u6807");
702
+ return;
703
+ }
704
+ await api("POST", `/api/goals/${id}/pause`);
705
+ console.log(pc5.yellow("\u76EE\u6807\u5DF2\u6682\u505C"));
706
+ } catch (e) {
707
+ const message = e instanceof Error ? e.message : String(e);
708
+ console.error(pc5.red(`\u6682\u505C\u5931\u8D25: ${message}`));
709
+ process.exit(1);
710
+ }
711
+ });
712
+ program2.command("resume").description("\u6062\u590D\u5DF2\u6682\u505C\u7684\u76EE\u6807\u5FAA\u73AF").action(async () => {
713
+ try {
714
+ await ensureCloudLogin3();
715
+ const current = await api("GET", "/api/goals/current");
716
+ const id = extractCurrentGoalId(current);
717
+ if (!id) {
718
+ console.log("\u6CA1\u6709\u53EF\u6062\u590D\u7684\u76EE\u6807");
719
+ return;
720
+ }
721
+ await api("POST", `/api/goals/${id}/resume`);
722
+ console.log(pc5.green("\u76EE\u6807\u5DF2\u6062\u590D"));
723
+ } catch (e) {
724
+ const message = e instanceof Error ? e.message : String(e);
725
+ console.error(pc5.red(`\u6062\u590D\u5931\u8D25: ${message}`));
726
+ process.exit(1);
727
+ }
728
+ });
729
+ program2.command("abort").description("\u7EC8\u6B62\u5F53\u524D\u76EE\u6807").action(async () => {
730
+ try {
731
+ await ensureCloudLogin3();
732
+ const current = await api("GET", "/api/goals/current");
733
+ const id = extractCurrentGoalId(current);
734
+ if (!id) {
735
+ console.log("\u6CA1\u6709\u53EF\u7EC8\u6B62\u7684\u76EE\u6807");
736
+ return;
737
+ }
738
+ await api("POST", `/api/goals/${id}/abort`);
739
+ console.log(pc5.red("\u76EE\u6807\u5DF2\u7EC8\u6B62"));
740
+ } catch (e) {
741
+ const message = e instanceof Error ? e.message : String(e);
742
+ console.error(pc5.red(`\u7EC8\u6B62\u5931\u8D25: ${message}`));
743
+ process.exit(1);
744
+ }
745
+ });
746
+ program2.command("log").description("\u67E5\u770B\u5FAA\u73AF\u5386\u53F2").option("-n, --limit <count>", "\u663E\u793A\u6761\u6570", "20").action(async (opts) => {
747
+ try {
748
+ await ensureCloudLogin3();
749
+ const current = await api("GET", "/api/goals/current");
750
+ const id = extractCurrentGoalId(current);
751
+ if (!id) {
752
+ console.log("\u6CA1\u6709\u6D3B\u8DC3\u76EE\u6807");
753
+ return;
754
+ }
755
+ const limit = parseLogLimit(opts.limit);
756
+ const cycles = await api("GET", `/api/goals/${id}/cycles?limit=${limit}`);
757
+ const list = Array.isArray(cycles) ? cycles : cycles?.cycles || [];
758
+ if (list.length === 0) {
759
+ console.log("\u6682\u65E0\u5FAA\u73AF\u8BB0\u5F55");
760
+ return;
761
+ }
762
+ for (const c of list) {
763
+ const ts = c.createdAt ? new Date(c.createdAt).toLocaleString() : "-";
764
+ console.log(
765
+ `${pc5.dim(ts)} [\u5FAA\u73AF ${c.cycle ?? c.number ?? "-"}] ${c.summary || c.status || "-"}`
766
+ );
767
+ }
768
+ } catch (e) {
769
+ const message = e instanceof Error ? e.message : String(e);
770
+ console.error(pc5.red(`\u83B7\u53D6\u65E5\u5FD7\u5931\u8D25: ${message}`));
771
+ process.exit(1);
772
+ }
773
+ });
774
+ program2.command("setup").description("\u9996\u6B21\u914D\u7F6E (API key + \u6A21\u578B)").action(async () => {
775
+ const rl = readline2.createInterface({
776
+ input: process.stdin,
777
+ output: process.stdout
778
+ });
779
+ const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
780
+ console.log(pc5.bold("Soloco \u9996\u6B21\u914D\u7F6E"));
781
+ console.log();
782
+ const apiKey = await ask("\u8BF7\u8F93\u5165 QWEN_API_KEY: ");
783
+ const model = await ask("\u6A21\u578B\u540D\u79F0 (\u9ED8\u8BA4 qwen-plus): ") || "qwen-plus";
784
+ rl.close();
785
+ const envPath = path2.resolve(process.cwd(), ".env");
786
+ const lines = [];
787
+ if (fs.existsSync(envPath)) {
788
+ const existing = fs.readFileSync(envPath, "utf-8");
789
+ for (const line of existing.split("\n")) {
790
+ if (line.startsWith("QWEN_API_KEY=") || line.startsWith("SOLOCO_MODEL=")) {
791
+ continue;
792
+ }
793
+ lines.push(line);
794
+ }
795
+ }
796
+ lines.push(`QWEN_API_KEY=${apiKey}`);
797
+ lines.push(`SOLOCO_MODEL=${model}`);
798
+ fs.writeFileSync(envPath, lines.filter((l) => l.trim() !== "").join("\n") + "\n");
799
+ console.log(pc5.green(`\u5DF2\u5199\u5165 ${envPath}`));
800
+ console.log();
801
+ console.log("\u6D4B\u8BD5 API \u8FDE\u901A\u6027...");
802
+ try {
803
+ await fetch(`${apiBase}/api/health`, { signal: AbortSignal.timeout(5e3) });
804
+ console.log(pc5.green("\u670D\u52A1\u5668\u8FDE\u63A5\u6B63\u5E38"));
805
+ } catch {
806
+ console.log(pc5.yellow(`\u65E0\u6CD5\u8FDE\u63A5 ${apiBase} (\u670D\u52A1\u5668\u53EF\u80FD\u672A\u542F\u52A8\uFF0C\u7A0D\u540E\u518D\u8BD5)`));
807
+ }
808
+ });
809
+ program2.command("update").description("\u66F4\u65B0\u76EE\u6807\u7684\u63CF\u8FF0 (Description) \u548C\u6807\u9898 (Title) (Agent Native \u53CB\u597D)").option("-i, --id <goal-id>", "\u76EE\u6807 ID (\u4E0D\u4F20\u5219\u9ED8\u8BA4\u5F53\u524D\u6D3B\u8DC3\u76EE\u6807)").option("-t, --title <title>", "\u65B0\u7684\u76EE\u6807\u6807\u9898").requiredOption("-d, --description <description>", "\u65B0\u7684\u76EE\u6807\u63CF\u8FF0").action(async (opts) => {
810
+ try {
811
+ await ensureCloudLogin3();
812
+ let goalId = opts.id;
813
+ if (!goalId) {
814
+ const current = await api("GET", "/api/goals/current");
815
+ goalId = extractCurrentGoalId(current);
816
+ if (!goalId) {
817
+ console.error(pc5.red("\u9519\u8BEF: \u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u76EE\u6807\uFF0C\u8BF7\u663E\u5F0F\u63D0\u4F9B --id <goal-id>"));
818
+ process.exit(1);
819
+ }
820
+ }
821
+ const body = { description: opts.description };
822
+ if (opts.title) {
823
+ body.title = opts.title;
824
+ }
825
+ await api("PATCH", `/api/goals/${goalId}`, body);
826
+ console.log(pc5.green(`\u76EE\u6807 ${goalId} \u66F4\u65B0\u6210\u529F!`));
827
+ } catch (e) {
828
+ const message = e instanceof Error ? e.message : String(e);
829
+ console.error(pc5.red(`\u66F4\u65B0\u5931\u8D25: ${message}`));
830
+ process.exit(1);
831
+ }
832
+ });
833
+ program2.command("steer").description("\u5411\u8FD0\u884C\u4E2D\u7684\u76EE\u6807\u53D1\u9001\u63D2\u961F/\u8F6C\u5411\u6307\u4EE4\u5E76\u9009\u62E9\u6027\u5F3A\u5236\u7EC8\u6B62\u5F53\u524D\u4EFB\u52A1 (Agent Native \u53CB\u597D)").option("-i, --id <goal-id>", "\u76EE\u6807 ID (\u4E0D\u4F20\u5219\u9ED8\u8BA4\u5F53\u524D\u6D3B\u8DC3\u76EE\u6807)").requiredOption("-c, --content <content>", "\u63D2\u961F/\u8F6C\u5411\u7684\u5177\u4F53\u6307\u4EE4\u5185\u5BB9").option("-f, --force-kill", "\u662F\u5426\u7ACB\u5373\u5F3A\u5236\u7EC8\u6B62\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684\u5B50\u4EFB\u52A1 (SIGTERM)").action(async (opts) => {
834
+ try {
835
+ await ensureCloudLogin3();
836
+ let goalId = opts.id;
837
+ if (!goalId) {
838
+ const current = await api("GET", "/api/goals/current");
839
+ goalId = extractCurrentGoalId(current);
840
+ if (!goalId) {
841
+ console.error(pc5.red("\u9519\u8BEF: \u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u76EE\u6807\uFF0C\u8BF7\u663E\u5F0F\u63D0\u4F9B --id <goal-id>"));
842
+ process.exit(1);
843
+ }
844
+ }
845
+ const body = { content: opts.content };
846
+ if (opts.forceKill) {
847
+ body.forceKill = true;
848
+ }
849
+ const res = await api(
850
+ "POST",
851
+ `/api/goals/${goalId}/steer`,
852
+ body
853
+ );
854
+ console.log(pc5.green(`\u63D2\u961F/\u8F6C\u5411\u6307\u4EE4\u53D1\u9001\u6210\u529F! Message ID: ${res.messageId}`));
855
+ if (opts.forceKill) {
856
+ console.log(pc5.yellow("\u5DF2\u89E6\u53D1\u5F3A\u5236\u7EC8\u6B62\u5F53\u524D\u5B50\u4EFB\u52A1\u3002"));
857
+ }
858
+ } catch (e) {
859
+ const message = e instanceof Error ? e.message : String(e);
860
+ console.error(pc5.red(`\u53D1\u9001\u63D2\u961F\u6307\u4EE4\u5931\u8D25: ${message}`));
861
+ process.exit(1);
862
+ }
863
+ });
864
+ program2.command("monitor").description("\u542F\u52A8\u5F00\u53D1\u8005 Dev Insider \u5B9E\u65F6\u76D1\u63A7\u63A7\u5236\u53F0 (\u652F\u6301 URL/ID \u8BC6\u522B\u4E0E\u952E\u76D8\u5FEB\u6377\u952E\u4EA4\u4E92)").argument("[goal-id-or-url]", "\u76EE\u6807 ID \u6216 Dashboard URL (\u4E0D\u4F20\u5219\u9ED8\u8BA4\u5F53\u524D\u6D3B\u8DC3\u76EE\u6807)").action(async (goalIdOrUrl) => {
865
+ try {
866
+ await ensureCloudLogin3();
867
+ let goalId = goalIdOrUrl;
868
+ if (!goalId) {
869
+ const current = await api("GET", "/api/goals/current");
870
+ goalId = extractCurrentGoalId(current);
871
+ if (!goalId) {
872
+ console.error(pc5.red("\u9519\u8BEF: \u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u76EE\u6807\uFF0C\u8BF7\u63D0\u4F9B\u76EE\u6807 ID \u6216 URL"));
873
+ process.exit(1);
874
+ }
875
+ } else if (goalId.startsWith("http://") || goalId.startsWith("https://")) {
876
+ try {
877
+ const url = new URL(goalId);
878
+ const idParam = url.searchParams.get("id");
879
+ if (idParam) {
880
+ goalId = idParam;
881
+ } else {
882
+ const match = url.pathname.match(/\/goals?\/([a-f0-9-]{36})/i);
883
+ if (match) {
884
+ goalId = match[1];
885
+ }
886
+ }
887
+ } catch {
888
+ }
889
+ }
890
+ await monitorGoal(goalId, api, apiBase, sleep);
891
+ } catch (e) {
892
+ const message = e instanceof Error ? e.message : String(e);
893
+ console.error(pc5.red(`\u76D1\u63A7\u542F\u52A8\u5931\u8D25: ${message}`));
894
+ process.exit(1);
895
+ }
896
+ });
897
+ program2.command("tail").description("\u5B9E\u65F6\u6D41\u5F0F\u8F93\u51FA\u76EE\u6807 subprocess run \u7684 stdout/stderr (NDJSON \u5230 stdout)").argument("[goal-id-or-url]", "\u76EE\u6807 ID \u6216 Dashboard URL (\u4E0D\u4F20\u5219\u9ED8\u8BA4\u5F53\u524D\u6D3B\u8DC3\u76EE\u6807)").option("--url <url>", "Dashboard URL (\u540C\u4F4D\u7F6E\u53C2\u6570)").action(async (goalIdOrUrl, opts) => {
898
+ try {
899
+ await ensureCloudLogin3();
900
+ let resolvedGoalId;
901
+ let resolvedApiBase = apiBase;
902
+ const rawUrl = opts.url ?? goalIdOrUrl;
903
+ if (rawUrl && (rawUrl.startsWith("http://") || rawUrl.startsWith("https://"))) {
904
+ try {
905
+ const parsed = new URL(rawUrl);
906
+ resolvedApiBase = `${parsed.protocol}//${parsed.host}`;
907
+ const idParam = parsed.searchParams.get("id");
908
+ if (idParam) {
909
+ resolvedGoalId = idParam;
910
+ } else {
911
+ const m = parsed.pathname.match(/\/goals?\/([a-f0-9-]{36})/i);
912
+ if (m) resolvedGoalId = m[1];
913
+ }
914
+ } catch {
915
+ }
916
+ } else if (rawUrl) {
917
+ resolvedGoalId = rawUrl;
918
+ }
919
+ const resolvedApi = resolvedApiBase !== apiBase ? createApiClient(resolvedApiBase) : api;
920
+ if (!resolvedGoalId) {
921
+ const current = await resolvedApi("GET", "/api/goals/current");
922
+ const g = Array.isArray(current) ? current[0] : current;
923
+ resolvedGoalId = g?.id;
924
+ }
925
+ if (!resolvedGoalId) {
926
+ console.error(pc5.red("\u9519\u8BEF: \u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u76EE\u6807\uFF0C\u8BF7\u63D0\u4F9B\u76EE\u6807 ID \u6216 URL"));
927
+ process.exit(1);
928
+ }
929
+ const runsData = await resolvedApi(
930
+ "GET",
931
+ `/api/goals/${resolvedGoalId}/runs`
932
+ );
933
+ const runs = runsData?.runs ?? [];
934
+ const active = runs.find((r) => r.status === "running" || r.status === "spawning");
935
+ const target = active ?? runs[0];
936
+ if (!target) {
937
+ console.error(pc5.yellow("\u6CA1\u6709\u627E\u5230 subprocess run"));
938
+ process.exit(1);
939
+ }
940
+ const runId = target.id;
941
+ const wsBase = resolvedApiBase.replace(/^http/, "ws");
942
+ const wsUrl = `${wsBase}/api/goals/${resolvedGoalId}/runs/${runId}/logs/ws`;
943
+ process.stderr.write(pc5.dim(`\u8FDE\u63A5 ${wsUrl}
944
+ `));
945
+ const ws = new WebSocket(wsUrl);
946
+ ws.onmessage = (event) => {
947
+ process.stdout.write(String(event.data));
948
+ if (!String(event.data).endsWith("\n")) process.stdout.write("\n");
949
+ };
950
+ ws.onerror = (err) => {
951
+ process.stderr.write(pc5.red(`WS \u9519\u8BEF: ${err.message ?? err}
952
+ `));
953
+ };
954
+ ws.onclose = () => {
955
+ process.stderr.write(pc5.dim("\u8FDE\u63A5\u5DF2\u5173\u95ED\n"));
956
+ process.exit(0);
957
+ };
958
+ await new Promise((resolve2) => {
959
+ ws.onclose = () => resolve2();
960
+ });
961
+ } catch (e) {
962
+ const message = e instanceof Error ? e.message : String(e);
963
+ console.error(pc5.red(`tail \u5931\u8D25: ${message}`));
964
+ process.exit(1);
965
+ }
966
+ });
967
+ program2.command("usage").description("\u67E5\u770B AI \u7528\u91CF\u62A5\u544A (\u6309\u65E5/\u5468/\u6708, \u652F\u6301\u6309 model/agent/goal \u5206\u7EC4)").argument("[period]", "\u7EDF\u8BA1\u7C92\u5EA6: daily | weekly | monthly (\u9ED8\u8BA4 daily)", "daily").option("--since <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)").option("--until <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)").option("--by-model", "\u6309\u6A21\u578B\u7EC6\u5206").option("--by-agent", "\u6309 agent \u7EC6\u5206").option("--by-goal", "\u6309\u76EE\u6807\u7EC6\u5206").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action(async (period, opts) => {
968
+ if (!["daily", "weekly", "monthly"].includes(period)) {
969
+ console.error(pc5.red(`\u9519\u8BEF: \u7C92\u5EA6\u5FC5\u987B\u662F daily\u3001weekly \u6216 monthly\uFF0C\u6536\u5230: ${period}`));
970
+ process.exit(1);
971
+ }
972
+ let companyId;
973
+ try {
974
+ await ensureCloudLogin3();
975
+ const companies = await api("GET", "/api/companies");
976
+ const first = Array.isArray(companies) ? companies[0] : null;
977
+ if (!first?.id) throw new Error("No company found");
978
+ companyId = first.id;
979
+ } catch (e) {
980
+ console.error(pc5.red(`\u83B7\u53D6\u516C\u53F8\u4FE1\u606F\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`));
981
+ process.exit(1);
982
+ }
983
+ const breakdown = opts.byModel ? "model" : opts.byAgent ? "agent" : opts.byGoal ? "goal" : void 0;
984
+ const qs = new URLSearchParams({ granularity: period });
985
+ if (opts.since) qs.set("from", opts.since);
986
+ if (opts.until) qs.set("to", opts.until);
987
+ if (breakdown) qs.set("breakdown", breakdown);
988
+ let rows;
989
+ try {
990
+ rows = await api(`GET`, `/api/companies/${companyId}/costs/by-period?${qs}`);
991
+ } catch (e) {
992
+ console.error(pc5.red(`\u83B7\u53D6\u7528\u91CF\u6570\u636E\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`));
993
+ process.exit(1);
994
+ }
995
+ if (opts.json) {
996
+ process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
997
+ return;
998
+ }
999
+ if (!rows.length) {
1000
+ console.log(pc5.dim("\u6CA1\u6709\u627E\u5230\u7528\u91CF\u6570\u636E"));
1001
+ return;
1002
+ }
1003
+ const granularityLabel = {
1004
+ daily: "\u65E5\u62A5",
1005
+ weekly: "\u5468\u62A5",
1006
+ monthly: "\u6708\u62A5"
1007
+ };
1008
+ console.log();
1009
+ console.log(pc5.bold(` Soloco AI \u7528\u91CF\u62A5\u544A \u2014 ${granularityLabel[period] ?? period}`));
1010
+ console.log();
1011
+ const fmtCents = (c) => `$${(c / 100).toFixed(2)}`;
1012
+ const fmtTokens = (t) => t >= 1e6 ? `${(t / 1e6).toFixed(1)}M` : t >= 1e3 ? `${(t / 1e3).toFixed(0)}K` : String(t);
1013
+ const COL = { period: 12, input: 9, output: 9, cost: 9 };
1014
+ const header = [
1015
+ "Period".padEnd(COL.period),
1016
+ "Input".padStart(COL.input),
1017
+ "Output".padStart(COL.output),
1018
+ "Cost".padStart(COL.cost)
1019
+ ].join(" ");
1020
+ const divider = "\u2500".repeat(header.length);
1021
+ console.log(pc5.dim(divider));
1022
+ console.log(pc5.bold(header));
1023
+ console.log(pc5.dim(divider));
1024
+ let totalCostCents = 0;
1025
+ let totalInput = 0;
1026
+ let totalOutput = 0;
1027
+ for (const row of rows) {
1028
+ totalCostCents += row.totalCostCents;
1029
+ totalInput += row.inputTokens;
1030
+ totalOutput += row.outputTokens;
1031
+ const line = [
1032
+ row.period.padEnd(COL.period),
1033
+ fmtTokens(row.inputTokens).padStart(COL.input),
1034
+ fmtTokens(row.outputTokens).padStart(COL.output),
1035
+ pc5.green(fmtCents(row.totalCostCents)).padStart(COL.cost)
1036
+ ].join(" ");
1037
+ console.log(line);
1038
+ if (row.breakdown?.length) {
1039
+ for (const b of row.breakdown.sort((a, z) => z.costCents - a.costCents)) {
1040
+ const label = ` ${pc5.dim("\u2514")} ${b.key.length > 30 ? b.key.slice(0, 27) + "..." : b.key}`;
1041
+ const bLine = [
1042
+ label.padEnd(COL.period + 14),
1043
+ // account for color escapes
1044
+ fmtTokens(b.inputTokens).padStart(COL.input),
1045
+ fmtTokens(b.outputTokens).padStart(COL.output),
1046
+ pc5.dim(fmtCents(b.costCents)).padStart(COL.cost)
1047
+ ].join(" ");
1048
+ console.log(bLine);
1049
+ }
1050
+ }
1051
+ }
1052
+ console.log(pc5.dim(divider));
1053
+ const totLine = [
1054
+ pc5.bold("Total").padEnd(COL.period),
1055
+ pc5.bold(fmtTokens(totalInput)).padStart(COL.input),
1056
+ pc5.bold(fmtTokens(totalOutput)).padStart(COL.output),
1057
+ pc5.bold(pc5.green(fmtCents(totalCostCents))).padStart(COL.cost)
1058
+ ].join(" ");
1059
+ console.log(totLine);
1060
+ console.log(pc5.dim(divider));
1061
+ console.log();
1062
+ });
1063
+ program2.command("worktree:make <name>").description("\u65B0\u5EFA\u9694\u79BB worktree\uFF08git worktree add + \u72EC\u7ACB\u5B9E\u4F8B\u76EE\u5F55 + \u7A7A\u95F2\u7AEF\u53E3 + \u6700\u5C0F seed\uFF09").action(async (name) => {
1064
+ const slug = slugifyInstanceId(name);
1065
+ if (!slug) {
1066
+ console.error(pc5.red("\u274C name \u53EA\u80FD\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\u3001\u4E0B\u5212\u7EBF"));
1067
+ process.exit(1);
1068
+ }
1069
+ if (slug === "default") {
1070
+ console.error(pc5.red("\u274C 'default' \u662F\u4E3B\u5B9E\u4F8B\u4FDD\u7559\u540D\uFF0C\u8BF7\u6362\u4E00\u4E2A\u540D\u5B57"));
1071
+ process.exit(1);
1072
+ }
1073
+ const cwd = process.cwd();
1074
+ let gitStat = null;
1075
+ try {
1076
+ gitStat = fs.statSync(path2.join(cwd, ".git"));
1077
+ } catch {
1078
+ }
1079
+ if (!gitStat || !gitStat.isDirectory()) {
1080
+ console.error(pc5.red("\u274C \u8BF7\u5728\u4E3B\u4ED3\u5E93\u6839\u76EE\u5F55\uFF08.git \u662F\u76EE\u5F55\uFF09\u6267\u884C worktree:make"));
1081
+ console.error(pc5.dim(" \u5F53\u524D\u76EE\u5F55\uFF1A" + cwd));
1082
+ process.exit(1);
1083
+ }
1084
+ const worktreePath = path2.resolve(cwd, "..", `soloco-${slug}`);
1085
+ if (fs.existsSync(worktreePath)) {
1086
+ console.error(pc5.red(`\u274C \u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\uFF1A${worktreePath}`));
1087
+ process.exit(1);
1088
+ }
1089
+ let serverPort;
1090
+ let pgPort;
1091
+ try {
1092
+ serverPort = await findFreePort(3101);
1093
+ pgPort = await findFreePort(54331);
1094
+ } catch (e) {
1095
+ console.error(pc5.red("\u274C \u627E\u4E0D\u5230\u7A7A\u95F2\u7AEF\u53E3\uFF1A" + (e instanceof Error ? e.message : String(e))));
1096
+ process.exit(1);
1097
+ }
1098
+ const { execSync } = await import("node:child_process");
1099
+ console.log(pc5.dim(`\u6B63\u5728\u521B\u5EFA worktree\uFF1A${worktreePath}`));
1100
+ try {
1101
+ try {
1102
+ execSync(`git worktree add "${worktreePath}" -b "${slug}"`, { cwd, stdio: "pipe" });
1103
+ } catch {
1104
+ execSync(`git worktree add "${worktreePath}" "${slug}"`, { cwd, stdio: "pipe" });
1105
+ }
1106
+ } catch (e) {
1107
+ console.error(pc5.red("\u274C git worktree add \u5931\u8D25\uFF1A" + (e instanceof Error ? e.message : String(e))));
1108
+ process.exit(1);
1109
+ }
1110
+ try {
1111
+ writeWorktreeIsolation(worktreePath, slug, serverPort, pgPort);
1112
+ } catch (e) {
1113
+ console.error(pc5.red("\u274C \u5199\u5165 .soloco \u914D\u7F6E\u5931\u8D25\uFF1A" + (e instanceof Error ? e.message : String(e))));
1114
+ process.exit(1);
1115
+ }
1116
+ console.log(pc5.green("\u2713 worktree \u5DF2\u521B\u5EFA\u5E76\u5B8C\u6210\u9694\u79BB\u521D\u59CB\u5316"));
1117
+ console.log("");
1118
+ console.log(pc5.bold(" \u8DEF\u5F84\uFF1A") + worktreePath);
1119
+ console.log(pc5.bold(" \u5B9E\u4F8B\uFF1A") + pc5.cyan(slug));
1120
+ console.log(pc5.bold(" \u7AEF\u53E3\uFF1A") + serverPort + pc5.dim(" (PG: " + pgPort + ")"));
1121
+ console.log("");
1122
+ console.log(" \u4E0B\u4E00\u6B65\uFF1A");
1123
+ console.log(pc5.dim(` cd ${worktreePath}`));
1124
+ console.log(pc5.dim(" pnpm dev"));
1125
+ });
1126
+ program2.command("worktree init").description("\u5728\u5F53\u524D worktree \u76EE\u5F55\u521D\u59CB\u5316\u9694\u79BB\u5B9E\u4F8B\uFF08.soloco/.env + \u72EC\u7ACB\u6570\u636E\u76EE\u5F55\uFF09").action(async () => {
1127
+ const cwd = process.cwd();
1128
+ if (fs.existsSync(path2.join(cwd, ".soloco", ".env"))) {
1129
+ console.log(pc5.yellow("\u2139 \u5F53\u524D\u76EE\u5F55\u5DF2\u521D\u59CB\u5316\uFF08.soloco/.env \u5B58\u5728\uFF09"));
1130
+ console.log(pc5.dim(" SOLOCO_INSTANCE_ID: " + (readEnvKey(path2.join(cwd, ".soloco", ".env"), "SOLOCO_INSTANCE_ID") ?? "(\u672A\u77E5)")));
1131
+ return;
1132
+ }
1133
+ const dirName = path2.basename(cwd);
1134
+ const instanceId = slugifyInstanceId(dirName);
1135
+ if (!instanceId) {
1136
+ console.error(pc5.red("\u274C \u65E0\u6CD5\u4ECE\u76EE\u5F55\u540D '" + dirName + "' \u63A8\u5BFC\u5408\u6CD5\u7684\u5B9E\u4F8B ID\uFF08\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\u3001\u4E0B\u5212\u7EBF\uFF09"));
1137
+ console.error(pc5.dim(" \u8BF7\u624B\u52A8\u4F20\u5165 SOLOCO_INSTANCE_ID \u6216\u91CD\u547D\u540D\u76EE\u5F55\u540E\u91CD\u8BD5"));
1138
+ process.exit(1);
1139
+ }
1140
+ if (instanceId === "default") {
1141
+ console.error(pc5.red("\u274C \u76EE\u5F55\u540D\u6620\u5C04\u5230 'default'\uFF0C\u4E0E\u4E3B\u5B9E\u4F8B\u51B2\u7A81\uFF0C\u8BF7\u91CD\u547D\u540D\u76EE\u5F55\u540E\u91CD\u8BD5"));
1142
+ process.exit(1);
1143
+ }
1144
+ let serverPort;
1145
+ let pgPort;
1146
+ try {
1147
+ serverPort = await findFreePort(3101);
1148
+ pgPort = await findFreePort(54331);
1149
+ } catch (e) {
1150
+ console.error(pc5.red("\u274C \u627E\u4E0D\u5230\u7A7A\u95F2\u7AEF\u53E3\uFF1A" + (e instanceof Error ? e.message : String(e))));
1151
+ process.exit(1);
1152
+ }
1153
+ try {
1154
+ writeWorktreeIsolation(cwd, instanceId, serverPort, pgPort);
1155
+ } catch (e) {
1156
+ console.error(pc5.red("\u274C \u5199\u5165 .soloco \u914D\u7F6E\u5931\u8D25\uFF1A" + (e instanceof Error ? e.message : String(e))));
1157
+ process.exit(1);
1158
+ }
1159
+ console.log(pc5.green("\u2713 \u9694\u79BB\u521D\u59CB\u5316\u5B8C\u6210"));
1160
+ console.log("");
1161
+ console.log(pc5.bold(" \u5B9E\u4F8B\uFF1A") + pc5.cyan(instanceId));
1162
+ console.log(pc5.bold(" \u7AEF\u53E3\uFF1A") + serverPort + pc5.dim(" (PG: " + pgPort + ")"));
1163
+ console.log(pc5.bold(" \u6570\u636E\uFF1A") + path2.join(os2.homedir(), ".soloco", "instances", instanceId));
1164
+ console.log("");
1165
+ console.log(" \u73B0\u5728\u53EF\u4EE5\u8FD0\u884C\uFF1A");
1166
+ console.log(pc5.dim(" pnpm dev"));
1167
+ });
1168
+ program2.command("usage:backfill").description("\u8865\u586B\u5386\u53F2 subprocess_runs \u7684 cost \u6570\u636E\uFF08\u626B\u63CF\u65E5\u5FD7\u6587\u4EF6\u56DE\u586B costUsdCents\uFF09").action(async () => {
1169
+ let companyId;
1170
+ try {
1171
+ await ensureCloudLogin3();
1172
+ const companies = await api("GET", "/api/companies");
1173
+ const first = Array.isArray(companies) ? companies[0] : null;
1174
+ if (!first?.id) throw new Error("No company found");
1175
+ companyId = first.id;
1176
+ } catch (e) {
1177
+ console.error(pc5.red("\u83B7\u53D6\u516C\u53F8\u4FE1\u606F\u5931\u8D25: " + (e instanceof Error ? e.message : String(e))));
1178
+ process.exit(1);
1179
+ }
1180
+ console.log(pc5.dim("\u6B63\u5728\u56DE\u586B\u5386\u53F2\u6210\u672C\u6570\u636E..."));
1181
+ try {
1182
+ const result = await api(
1183
+ "POST",
1184
+ "/api/companies/" + companyId + "/costs/backfill"
1185
+ );
1186
+ console.log(pc5.green("\u56DE\u586B\u5B8C\u6210:") + " \u66F4\u65B0 " + pc5.bold(String(result.updated)) + " \u6761\uFF0C\u8DF3\u8FC7 " + pc5.dim(String(result.skipped)) + " \u6761\uFF08\u5171 " + result.total + " \u6761\uFF09");
1187
+ } catch (e) {
1188
+ console.error(pc5.red("\u56DE\u586B\u5931\u8D25: " + (e instanceof Error ? e.message : String(e))));
1189
+ process.exit(1);
1190
+ }
1191
+ });
1192
+ return program2;
1193
+ }
1194
+ function resolveCliVersion() {
1195
+ if ("0.3.1-canary.0") return "0.3.1-canary.0";
1196
+ try {
1197
+ const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
1198
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
1199
+ return pkg.version ?? "0.0.0";
1200
+ } catch {
1201
+ return "0.0.0";
1202
+ }
1203
+ }
1204
+ async function openBrowser(url) {
1205
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
1206
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
1207
+ const child = spawn(command, args, {
1208
+ detached: true,
1209
+ stdio: "ignore"
1210
+ });
1211
+ child.unref();
1212
+ }
1213
+ function defaultInstallationName2() {
1214
+ return `Soloco CLI (${os2.hostname()})`;
1215
+ }
1216
+ function slugifyInstanceId(name) {
1217
+ return name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1218
+ }
1219
+ function isPortFree(port) {
1220
+ return new Promise((resolve2) => {
1221
+ const srv = net.createServer();
1222
+ let done = false;
1223
+ srv.once("error", () => {
1224
+ if (!done) {
1225
+ done = true;
1226
+ resolve2(false);
1227
+ }
1228
+ });
1229
+ srv.once("listening", () => {
1230
+ if (!done) {
1231
+ done = true;
1232
+ srv.close(() => resolve2(true));
1233
+ }
1234
+ });
1235
+ srv.listen(port, "127.0.0.1");
1236
+ });
1237
+ }
1238
+ async function findFreePort(start) {
1239
+ for (let p = start; p < start + 200; p++) {
1240
+ if (await isPortFree(p)) return p;
1241
+ }
1242
+ throw new Error(`${start}\u2013${start + 199} \u8303\u56F4\u5185\u6CA1\u6709\u7A7A\u95F2\u7AEF\u53E3`);
1243
+ }
1244
+ function writeWorktreeIsolation(worktreeRoot, instanceId, serverPort, pgPort) {
1245
+ const solocoDir = path2.join(worktreeRoot, ".soloco");
1246
+ fs.mkdirSync(solocoDir, { recursive: true });
1247
+ const envContent = [
1248
+ `SOLOCO_INSTANCE_ID=${instanceId}`,
1249
+ `PORT=${serverPort}`
1250
+ ].join("\n") + "\n";
1251
+ fs.writeFileSync(path2.join(solocoDir, ".env"), envContent, "utf-8");
1252
+ const instanceBase = `~/.soloco/instances/${instanceId}`;
1253
+ const config = {
1254
+ $meta: { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), source: "onboard" },
1255
+ database: {
1256
+ embeddedPostgresDataDir: `${instanceBase}/db`,
1257
+ embeddedPostgresPort: pgPort,
1258
+ backup: { dir: `${instanceBase}/data/backups` }
1259
+ },
1260
+ logging: {
1261
+ mode: "file",
1262
+ logDir: `${instanceBase}/logs`
1263
+ },
1264
+ storage: {
1265
+ localDisk: { baseDir: `${instanceBase}/data/storage` }
1266
+ },
1267
+ secrets: {
1268
+ localEncrypted: { keyFilePath: `${instanceBase}/secrets/master.key` }
1269
+ }
1270
+ };
1271
+ fs.writeFileSync(path2.join(solocoDir, "config.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
1272
+ fs.mkdirSync(path2.join(os2.homedir(), ".soloco", "instances", instanceId), { recursive: true });
1273
+ }
1274
+ function readEnvKey(envPath, key) {
1275
+ try {
1276
+ const content = fs.readFileSync(envPath, "utf-8");
1277
+ const m = new RegExp(`^${key}=(.*)$`, "m").exec(content);
1278
+ return m ? m[1].trim() : null;
1279
+ } catch {
1280
+ return null;
1281
+ }
1282
+ }
1283
+
1284
+ // src/index.ts
1285
+ var program = createCliProgram();
1286
+ program.parseAsync().catch((err) => {
1287
+ console.error(err instanceof Error ? err.message : String(err));
1288
+ process.exit(1);
1289
+ });
1290
+ //# sourceMappingURL=index.js.map