hypercore-cli 1.1.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/api-XGC7D5AW.js +162 -0
  4. package/dist/auth-DNQWYQKT.js +21 -0
  5. package/dist/background-2EGCAAQH.js +14 -0
  6. package/dist/backlog-Q2NZCLNY.js +24 -0
  7. package/dist/chunk-2CMSCWQW.js +162 -0
  8. package/dist/chunk-2LJ2DVEB.js +167 -0
  9. package/dist/chunk-3RPFCQKJ.js +288 -0
  10. package/dist/chunk-43OLRXM5.js +263 -0
  11. package/dist/chunk-4DVYJAJL.js +57 -0
  12. package/dist/chunk-6OL3GA3P.js +173 -0
  13. package/dist/chunk-AUHU7ALH.js +2023 -0
  14. package/dist/chunk-B6A2AKLN.js +139 -0
  15. package/dist/chunk-BE46C7JW.js +46 -0
  16. package/dist/chunk-CUVAUOXL.js +58 -0
  17. package/dist/chunk-GH7E2OJE.js +223 -0
  18. package/dist/chunk-GOOTEPBK.js +271 -0
  19. package/dist/chunk-GPPMJYSM.js +133 -0
  20. package/dist/chunk-GU2FZQ6A.js +69 -0
  21. package/dist/chunk-IOPKN5GD.js +190 -0
  22. package/dist/chunk-IXOIOGR5.js +1505 -0
  23. package/dist/chunk-KRPOPWGA.js +251 -0
  24. package/dist/chunk-MGLJ53QN.js +219 -0
  25. package/dist/chunk-MV4TTRYX.js +533 -0
  26. package/dist/chunk-OPZYEVYR.js +150 -0
  27. package/dist/chunk-QTSLP47C.js +166 -0
  28. package/dist/chunk-R3GPQC7I.js +393 -0
  29. package/dist/chunk-RKB2JOV2.js +43 -0
  30. package/dist/chunk-RNG3K465.js +80 -0
  31. package/dist/chunk-TGTYKBGC.js +86 -0
  32. package/dist/chunk-U5SGAIMM.js +681 -0
  33. package/dist/chunk-V5UHPPSY.js +140 -0
  34. package/dist/chunk-WHLVZCQY.js +245 -0
  35. package/dist/chunk-XDRCBMZZ.js +66 -0
  36. package/dist/chunk-XOS6HPEF.js +134 -0
  37. package/dist/chunk-ZSBHUGWR.js +262 -0
  38. package/dist/claude-NSQ442XD.js +12 -0
  39. package/dist/commands-CK3WFAGI.js +128 -0
  40. package/dist/commands-U63OEO5J.js +1044 -0
  41. package/dist/commands-ZE6GD3WC.js +232 -0
  42. package/dist/config-4EW42BSF.js +8 -0
  43. package/dist/config-loader-SXO674TF.js +24 -0
  44. package/dist/diagnose-AFW3ZTZ4.js +12 -0
  45. package/dist/display-IIUBEYWN.js +58 -0
  46. package/dist/extractor-QV53W2YJ.js +129 -0
  47. package/dist/history-WMSCHERZ.js +180 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +406 -0
  50. package/dist/instance-registry-YSIJXSO7.js +15 -0
  51. package/dist/keybindings-JAAMLH3G.js +15 -0
  52. package/dist/loader-WHNTZTLP.js +58 -0
  53. package/dist/network-MM6YWPGO.js +279 -0
  54. package/dist/notify-HPTALZDC.js +14 -0
  55. package/dist/openai-compat-UQWJXBEK.js +12 -0
  56. package/dist/permissions-JUKXMNDH.js +10 -0
  57. package/dist/prompt-QV45TXRL.js +166 -0
  58. package/dist/quality-ST7PPNFR.js +16 -0
  59. package/dist/repl-RT3AHL7M.js +3375 -0
  60. package/dist/roadmap-5OBEKROY.js +17 -0
  61. package/dist/server-PORT7OEG.js +57 -0
  62. package/dist/session-4VUNDWLH.js +21 -0
  63. package/dist/skills-V4A35XKG.js +175 -0
  64. package/dist/store-Y4LU5QTO.js +25 -0
  65. package/dist/team-HO7Z4SIM.js +385 -0
  66. package/dist/telemetry-6R4EIE6O.js +30 -0
  67. package/dist/test-runner-ZQH5Y6OJ.js +619 -0
  68. package/dist/theme-3SYJ3UQA.js +14 -0
  69. package/dist/upgrade-7TGI3SXO.js +83 -0
  70. package/dist/verify-JUDKTPKZ.js +14 -0
  71. package/dist/web/static/app.js +562 -0
  72. package/dist/web/static/index.html +132 -0
  73. package/dist/web/static/mirror.css +1001 -0
  74. package/dist/web/static/mirror.html +184 -0
  75. package/dist/web/static/mirror.js +1125 -0
  76. package/dist/web/static/onboard.css +302 -0
  77. package/dist/web/static/onboard.html +140 -0
  78. package/dist/web/static/onboard.js +260 -0
  79. package/dist/web/static/style.css +602 -0
  80. package/dist/web/static/workspace.css +1568 -0
  81. package/dist/web/static/workspace.html +408 -0
  82. package/dist/web/static/workspace.js +1683 -0
  83. package/dist/web-Z5HSCQHW.js +39 -0
  84. package/package.json +67 -0
@@ -0,0 +1,2023 @@
1
+ import {
2
+ attachWebSocket
3
+ } from "./chunk-2LJ2DVEB.js";
4
+ import {
5
+ addMember,
6
+ addTask,
7
+ createTeam,
8
+ deleteTask,
9
+ findTeamByJoinCode,
10
+ listRuns,
11
+ listTasks,
12
+ loadRun,
13
+ loadTeam,
14
+ updateTask
15
+ } from "./chunk-6OL3GA3P.js";
16
+ import {
17
+ deleteSession,
18
+ exportSession,
19
+ generateSessionId,
20
+ listSessions,
21
+ loadSession,
22
+ saveSession
23
+ } from "./chunk-XOS6HPEF.js";
24
+ import {
25
+ validateToken
26
+ } from "./chunk-XDRCBMZZ.js";
27
+ import {
28
+ Engine,
29
+ HYPERCORE_DIR,
30
+ initializeWorkspace
31
+ } from "./chunk-MV4TTRYX.js";
32
+ import {
33
+ createToolRegistry
34
+ } from "./chunk-IXOIOGR5.js";
35
+ import {
36
+ estimateCost,
37
+ formatCost
38
+ } from "./chunk-R3GPQC7I.js";
39
+ import {
40
+ createLLMClient,
41
+ streamCallLLM
42
+ } from "./chunk-43OLRXM5.js";
43
+ import {
44
+ createOpenAIClient,
45
+ streamOpenAIChat
46
+ } from "./chunk-GOOTEPBK.js";
47
+ import {
48
+ hookManager
49
+ } from "./chunk-B6A2AKLN.js";
50
+ import {
51
+ HYPERCORE_DIR as HYPERCORE_DIR2,
52
+ loadConfig
53
+ } from "./chunk-V5UHPPSY.js";
54
+ import {
55
+ listLines
56
+ } from "./chunk-WHLVZCQY.js";
57
+
58
+ // src/web/server.ts
59
+ import http from "http";
60
+ import fs from "fs";
61
+ import path from "path";
62
+ import { fileURLToPath } from "url";
63
+
64
+ // src/utils/git.ts
65
+ import { execFileSync } from "child_process";
66
+ function gitExec(args, cwd) {
67
+ try {
68
+ return execFileSync("git", args, {
69
+ encoding: "utf-8",
70
+ cwd: cwd || process.cwd(),
71
+ timeout: 15e3,
72
+ stdio: ["pipe", "pipe", "pipe"]
73
+ }).trim();
74
+ } catch (err) {
75
+ const error = err;
76
+ throw new Error(error.stderr?.trim() || error.message || "git \u547D\u4EE4\u5931\u8D25");
77
+ }
78
+ }
79
+ function gitExecSafe(args, cwd) {
80
+ try {
81
+ return gitExec(args, cwd);
82
+ } catch {
83
+ return "";
84
+ }
85
+ }
86
+ function isGitRepo() {
87
+ try {
88
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
89
+ encoding: "utf-8",
90
+ stdio: "pipe",
91
+ timeout: 5e3
92
+ });
93
+ return true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ function gitBranch() {
99
+ return gitExecSafe(["branch", "--show-current"]) || "HEAD";
100
+ }
101
+ function gitStatus() {
102
+ const output = gitExecSafe(["status", "--porcelain"]);
103
+ if (!output) return { staged: [], modified: [], untracked: [], deleted: [] };
104
+ const staged = [];
105
+ const modified = [];
106
+ const untracked = [];
107
+ const deleted = [];
108
+ for (const line of output.split("\n")) {
109
+ if (!line || line.length < 3) continue;
110
+ const x = line[0];
111
+ const y = line[1];
112
+ const file = line.slice(3);
113
+ if ("MADRC".includes(x) && x !== " " && x !== "?") {
114
+ staged.push(file);
115
+ }
116
+ if (y === "M") {
117
+ modified.push(file);
118
+ }
119
+ if (y === "D" || x === "D") {
120
+ deleted.push(file);
121
+ }
122
+ if (x === "?" && y === "?") {
123
+ untracked.push(file);
124
+ }
125
+ }
126
+ return { staged, modified, untracked, deleted };
127
+ }
128
+ function gitStatusSummary() {
129
+ const s = gitStatus();
130
+ const parts = [];
131
+ if (s.staged.length) parts.push(`${s.staged.length} staged`);
132
+ if (s.modified.length) parts.push(`${s.modified.length} modified`);
133
+ if (s.untracked.length) parts.push(`${s.untracked.length} untracked`);
134
+ if (s.deleted.length) parts.push(`${s.deleted.length} deleted`);
135
+ return parts.length > 0 ? parts.join(", ") : "clean";
136
+ }
137
+ function gitDiffStaged() {
138
+ return gitExecSafe(["diff", "--cached"]);
139
+ }
140
+ function gitDiffStagedStat() {
141
+ return gitExecSafe(["diff", "--cached", "--stat"]);
142
+ }
143
+ function gitDiffUnstaged() {
144
+ return gitExecSafe(["diff"]);
145
+ }
146
+ function gitLog(n = 10) {
147
+ const output = gitExecSafe(["log", "--oneline", `--format=%H|%h|%s|%ai|%an`, `-${n}`]);
148
+ if (!output) return [];
149
+ return output.split("\n").filter(Boolean).map((line) => {
150
+ const [hash, shortHash, message, date, author] = line.split("|");
151
+ return { hash, shortHash, message, date, author };
152
+ });
153
+ }
154
+ function gitDiffFiles(base, head = "HEAD") {
155
+ return gitExecSafe(["diff", "--name-only", `${base}...${head}`]);
156
+ }
157
+ function gitDiffRange(base, head = "HEAD") {
158
+ return gitExecSafe(["diff", "--stat", `${base}...${head}`]);
159
+ }
160
+ function gitStageFiles(files) {
161
+ if (files.length === 0) return;
162
+ gitExec(["add", "--", ...files]);
163
+ }
164
+ function gitCommit(message) {
165
+ return gitExec(["commit", "-m", message]);
166
+ }
167
+ function gitCreateBranch(name) {
168
+ gitExec(["checkout", "-b", name]);
169
+ }
170
+ function gitPushUpstream(remote = "origin", branch) {
171
+ const branchName = branch || gitBranch();
172
+ return gitExec(["push", "-u", remote, branchName]);
173
+ }
174
+ function gitDefaultBranch() {
175
+ const remoteHead = gitExecSafe(["symbolic-ref", "refs/remotes/origin/HEAD"]);
176
+ if (remoteHead) {
177
+ return remoteHead.replace("refs/remotes/origin/", "");
178
+ }
179
+ const branches = gitExecSafe(["branch", "-a"]);
180
+ if (branches.includes("main")) return "main";
181
+ if (branches.includes("master")) return "master";
182
+ return "main";
183
+ }
184
+ function hasUncommittedChanges() {
185
+ const output = gitExecSafe(["status", "--porcelain"]);
186
+ return output.length > 0;
187
+ }
188
+ function gitHeadShort() {
189
+ return gitExecSafe(["rev-parse", "--short", "HEAD"]);
190
+ }
191
+ function gitListBranches() {
192
+ const output = gitExecSafe(["branch", "--format=%(refname:short)|%(HEAD)"]);
193
+ if (!output) return [];
194
+ return output.split("\n").filter(Boolean).map((line) => {
195
+ const [name, head] = line.split("|");
196
+ return { name: name.trim(), current: head.trim() === "*" };
197
+ });
198
+ }
199
+ function gitSwitchBranch(name) {
200
+ return gitExec(["switch", name]);
201
+ }
202
+ function gitDeleteBranch(name, force = false) {
203
+ return gitExec(["branch", force ? "-D" : "-d", name]);
204
+ }
205
+ function gitStash(message) {
206
+ const args = ["stash", "push"];
207
+ if (message) args.push("-m", message);
208
+ return gitExec(args);
209
+ }
210
+ function gitStashPop(index) {
211
+ const args = ["stash", "pop"];
212
+ if (index !== void 0) args.push(`stash@{${index}}`);
213
+ return gitExec(args);
214
+ }
215
+ function gitStashList() {
216
+ return gitExecSafe(["stash", "list"]);
217
+ }
218
+ function gitStashDrop(index) {
219
+ const args = ["stash", "drop"];
220
+ if (index !== void 0) args.push(`stash@{${index}}`);
221
+ return gitExec(args);
222
+ }
223
+ function gitDiffFile(file, staged = false) {
224
+ const args = ["diff"];
225
+ if (staged) args.push("--cached");
226
+ args.push("--", file);
227
+ return gitExecSafe(args);
228
+ }
229
+ function gitWorktreeAdd(path2, branch, createBranch = false) {
230
+ const args = ["worktree", "add"];
231
+ if (createBranch) args.push("-b", branch, path2);
232
+ else args.push(path2, branch);
233
+ return gitExec(args);
234
+ }
235
+ function gitWorktreeList() {
236
+ return gitExecSafe(["worktree", "list"]);
237
+ }
238
+ function gitWorktreeRemove(path2, force = false) {
239
+ const args = ["worktree", "remove"];
240
+ if (force) args.push("--force");
241
+ args.push(path2);
242
+ return gitExec(args);
243
+ }
244
+
245
+ // src/web/api.ts
246
+ import { readdir, readFile, stat, writeFile } from "fs/promises";
247
+ import { join, resolve, relative, isAbsolute } from "path";
248
+ import { existsSync } from "fs";
249
+
250
+ // src/web/repl-bridge.ts
251
+ import { WebSocketServer, WebSocket } from "ws";
252
+ import { EventEmitter } from "events";
253
+ var InputMultiplexer = class extends EventEmitter {
254
+ rl;
255
+ guiQueue = [];
256
+ waiting = null;
257
+ constructor(rl) {
258
+ super();
259
+ this.rl = rl;
260
+ }
261
+ /** 从 GUI WebSocket 推入输入 */
262
+ pushGUIInput(content) {
263
+ if (this.waiting) {
264
+ const resolve2 = this.waiting;
265
+ this.waiting = null;
266
+ resolve2({ source: "gui", content });
267
+ } else {
268
+ this.guiQueue.push(content);
269
+ }
270
+ }
271
+ /** 等待下一个输入(来自 CLI 或 GUI) */
272
+ nextInput(prompt) {
273
+ if (this.guiQueue.length > 0) {
274
+ const content = this.guiQueue.shift();
275
+ return Promise.resolve({ source: "gui", content });
276
+ }
277
+ return new Promise((resolve2) => {
278
+ let settled = false;
279
+ const cleanup = () => {
280
+ settled = true;
281
+ this.waiting = null;
282
+ };
283
+ this.rl.question(prompt, (answer) => {
284
+ if (settled) return;
285
+ cleanup();
286
+ resolve2({ source: "cli", content: answer });
287
+ });
288
+ this.waiting = (input) => {
289
+ if (settled) return;
290
+ cleanup();
291
+ process.stdout.write("\r\x1B[K");
292
+ resolve2(input);
293
+ };
294
+ });
295
+ }
296
+ };
297
+ var guiClients = [];
298
+ var replContext = null;
299
+ var multiplexer = null;
300
+ var clientIdCounter = 0;
301
+ function emitToGUI(type, payload = {}) {
302
+ const msg = {
303
+ type,
304
+ payload,
305
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
306
+ source: "cli"
307
+ };
308
+ const data = JSON.stringify(msg);
309
+ for (const client of guiClients) {
310
+ if (client.ws.readyState === WebSocket.OPEN) {
311
+ client.ws.send(data);
312
+ }
313
+ }
314
+ }
315
+ function registerREPLContext(ctx) {
316
+ replContext = ctx;
317
+ }
318
+ function getREPLContext() {
319
+ return replContext;
320
+ }
321
+ function createMultiplexer(rl) {
322
+ multiplexer = new InputMultiplexer(rl);
323
+ return multiplexer;
324
+ }
325
+ function hasGUIClients() {
326
+ return guiClients.length > 0;
327
+ }
328
+ function buildStateSnapshot() {
329
+ if (!replContext) return {};
330
+ return {
331
+ sessionId: replContext.sessionId,
332
+ model: replContext.config.modelConfig.model,
333
+ provider: replContext.config.modelConfig.provider,
334
+ tokens: replContext.sessionTokens,
335
+ toolCount: replContext.tools.length,
336
+ tools: replContext.tools.map((t) => ({
337
+ name: t.definition.name,
338
+ description: t.definition.description?.slice(0, 100)
339
+ })),
340
+ chatHistory: replContext.chatHistory.map((m) => ({
341
+ role: m.role,
342
+ content: m.content.slice(0, 2e3)
343
+ })),
344
+ cwd: process.cwd()
345
+ };
346
+ }
347
+ function handleGUIMessage(clientId, raw) {
348
+ let msg;
349
+ try {
350
+ msg = JSON.parse(raw);
351
+ } catch {
352
+ return;
353
+ }
354
+ const payload = msg.payload || {};
355
+ switch (msg.type) {
356
+ case "user_input": {
357
+ const content = payload.content;
358
+ if (content && multiplexer) {
359
+ multiplexer.pushGUIInput(content);
360
+ }
361
+ break;
362
+ }
363
+ case "slash_command": {
364
+ const command = payload.command;
365
+ if (command && multiplexer) {
366
+ multiplexer.pushGUIInput(command.startsWith("/") ? command : `/${command}`);
367
+ }
368
+ break;
369
+ }
370
+ case "state_request": {
371
+ const snapshot = buildStateSnapshot();
372
+ const client = guiClients.find((c) => c.id === clientId);
373
+ if (client && client.ws.readyState === WebSocket.OPEN) {
374
+ const response = {
375
+ type: "state_sync",
376
+ payload: snapshot,
377
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
378
+ source: "cli"
379
+ };
380
+ client.ws.send(JSON.stringify(response));
381
+ }
382
+ break;
383
+ }
384
+ case "heartbeat": {
385
+ const client = guiClients.find((c) => c.id === clientId);
386
+ if (client && client.ws.readyState === WebSocket.OPEN) {
387
+ const pong = {
388
+ type: "heartbeat",
389
+ payload: { pong: true },
390
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
391
+ source: "cli"
392
+ };
393
+ client.ws.send(JSON.stringify(pong));
394
+ }
395
+ break;
396
+ }
397
+ default:
398
+ break;
399
+ }
400
+ }
401
+ function attachREPLBridge(_server) {
402
+ const wss = new WebSocketServer({ noServer: true });
403
+ wss.on("connection", (ws) => {
404
+ const id = `gui_${++clientIdCounter}`;
405
+ const client = {
406
+ ws,
407
+ id,
408
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString()
409
+ };
410
+ guiClients.push(client);
411
+ const connected = {
412
+ type: "connected",
413
+ payload: {
414
+ clientId: id,
415
+ state: buildStateSnapshot()
416
+ },
417
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
418
+ source: "cli"
419
+ };
420
+ ws.send(JSON.stringify(connected));
421
+ ws.on("message", (data) => {
422
+ handleGUIMessage(id, data.toString());
423
+ });
424
+ ws.on("close", () => {
425
+ guiClients = guiClients.filter((c) => c.id !== id);
426
+ });
427
+ ws.on("error", (err) => {
428
+ console.error(`[REPL Bridge] GUI \u8FDE\u63A5\u9519\u8BEF (${id}):`, err.message);
429
+ });
430
+ });
431
+ return wss;
432
+ }
433
+
434
+ // src/core/workspace-generator.ts
435
+ import { randomBytes } from "crypto";
436
+ var NEED_TO_LINES = {
437
+ "content-writing": ["\u516C\u4F17\u53F7\u6587\u7AE0", "\u5C0F\u7EA2\u4E66\u7B14\u8BB0"],
438
+ "research": ["\u7ADE\u54C1\u5206\u6790", "\u70ED\u70B9\u8FFD\u8E2A"],
439
+ "reports": ["\u5468\u62A5\u751F\u6210"],
440
+ "social-media": ["\u5C0F\u7EA2\u4E66\u7B14\u8BB0"],
441
+ "competitor-analysis": ["\u7ADE\u54C1\u5206\u6790"],
442
+ "meeting-notes": ["\u4F1A\u8BAE\u7EAA\u8981"],
443
+ "data-analysis": [],
444
+ "customer-outreach": []
445
+ };
446
+ var NEED_TO_AGENTS = {
447
+ "content-writing": ["\u5199\u624B", "\u7B56\u5212\u5E08"],
448
+ "research": ["\u8C03\u7814\u5458"],
449
+ "reports": ["\u5199\u624B"],
450
+ "social-media": ["\u5199\u624B", "\u7B56\u5212\u5E08"],
451
+ "competitor-analysis": ["\u8C03\u7814\u5458"],
452
+ "meeting-notes": ["\u5199\u624B"],
453
+ "data-analysis": ["\u8C03\u7814\u5458"],
454
+ "customer-outreach": ["\u5199\u624B", "\u7B56\u5212\u5E08"]
455
+ };
456
+ var NEED_TO_SKILLS = {
457
+ "content-writing": ["\u516C\u4F17\u53F7\u5199\u4F5C", "\u5C0F\u7EA2\u4E66\u5199\u4F5C"],
458
+ "research": ["\u5E02\u573A\u8C03\u7814", "\u7ADE\u54C1\u5206\u6790", "\u70ED\u70B9\u8FFD\u8E2A"],
459
+ "reports": [],
460
+ "social-media": ["\u5C0F\u7EA2\u4E66\u5199\u4F5C"],
461
+ "competitor-analysis": ["\u7ADE\u54C1\u5206\u6790"],
462
+ "meeting-notes": [],
463
+ "data-analysis": [],
464
+ "customer-outreach": []
465
+ };
466
+ var INDUSTRY_LABELS = {
467
+ "marketing": "\u8425\u9500\u5DE5\u4F5C\u7AD9",
468
+ "ecommerce": "\u7535\u5546\u4E2D\u5FC3",
469
+ "education": "\u5B66\u4E60\u5B9E\u9A8C\u5BA4",
470
+ "tech": "\u6280\u672F\u5DE5\u574A",
471
+ "finance": "\u5206\u6790\u5DE5\u4F5C\u53F0",
472
+ "media": "\u5185\u5BB9\u5DE5\u4F5C\u5BA4",
473
+ "consulting": "\u54A8\u8BE2\u5957\u4EF6",
474
+ "other": "\u667A\u80FD\u5DE5\u4F5C\u53F0"
475
+ };
476
+ var ROLE_TITLES = {
477
+ "founder": "\u521B\u59CB\u4EBA",
478
+ "marketer": "\u8425\u9500\u5B98",
479
+ "content-creator": "\u521B\u4F5C\u8005",
480
+ "product-manager": "\u4EA7\u54C1\u7ECF\u7406",
481
+ "researcher": "\u7814\u7A76\u5458",
482
+ "operations": "\u8FD0\u8425\u5B98",
483
+ "freelancer": "\u81EA\u7531\u804C\u4E1A\u8005"
484
+ };
485
+ function generateQuickCommands(industry, role, needs) {
486
+ const commands = [];
487
+ const safeNeeds = Array.isArray(needs) ? needs : [];
488
+ commands.push({ label: "\u65B0\u5BF9\u8BDD", icon: "\u{1F4AC}", cmd: "/new" });
489
+ if (safeNeeds.length === 0) {
490
+ commands.push({ label: "\u641C\u7D22\u8BB0\u5FC6", icon: "\u{1F9E0}", cmd: "/recall" });
491
+ commands.push({ label: "\u67E5\u770B\u8D39\u7528", icon: "\u{1F4B0}", cmd: "/cost" });
492
+ return commands;
493
+ }
494
+ if (safeNeeds.includes("content-writing")) {
495
+ commands.push({ label: "\u5199\u516C\u4F17\u53F7", icon: "\u270D\uFE0F", cmd: "/run \u516C\u4F17\u53F7\u6587\u7AE0" });
496
+ }
497
+ if (safeNeeds.includes("social-media") || safeNeeds.includes("content-writing")) {
498
+ commands.push({ label: "\u5199\u5C0F\u7EA2\u4E66", icon: "\u{1F4F1}", cmd: "/run \u5C0F\u7EA2\u4E66\u7B14\u8BB0" });
499
+ }
500
+ if (safeNeeds.includes("research") || safeNeeds.includes("competitor-analysis")) {
501
+ commands.push({ label: "\u7ADE\u54C1\u5206\u6790", icon: "\u{1F50D}", cmd: "/run \u7ADE\u54C1\u5206\u6790" });
502
+ }
503
+ if (safeNeeds.includes("reports")) {
504
+ commands.push({ label: "\u751F\u6210\u5468\u62A5", icon: "\u{1F4CA}", cmd: "/run \u5468\u62A5\u751F\u6210" });
505
+ }
506
+ if (industry === "ecommerce" && commands.length < 5) {
507
+ commands.push({ label: "Listing\u6587\u6848", icon: "\u{1F6D2}", cmd: '/run \u516C\u4F17\u53F7\u6587\u7AE0 --topic "\u4EA7\u54C1Listing\u4F18\u5316"' });
508
+ }
509
+ if (industry === "media" && commands.length < 5) {
510
+ commands.push({ label: "\u70ED\u70B9\u8FFD\u8E2A", icon: "\u{1F525}", cmd: "/run \u70ED\u70B9\u8FFD\u8E2A" });
511
+ }
512
+ if (industry === "tech" && commands.length < 5) {
513
+ commands.push({ label: "\u4EE3\u7801\u5BA1\u67E5", icon: "\u{1F527}", cmd: "/run \u4EE3\u7801\u5BA1\u67E5" });
514
+ }
515
+ if (commands.length < 3) {
516
+ commands.push({ label: "\u641C\u7D22\u8BB0\u5FC6", icon: "\u{1F9E0}", cmd: "/recall" });
517
+ }
518
+ return commands.slice(0, 5);
519
+ }
520
+ function generateWelcomeMessage(industry, role) {
521
+ const roleTitle = ROLE_TITLES[role] || "\u7528\u6237";
522
+ const label = INDUSTRY_LABELS[industry] || "\u667A\u80FD\u5DE5\u4F5C\u53F0";
523
+ const greetings = {
524
+ "founder": `\u6B22\u8FCE\u56DE\u6765\uFF0C${roleTitle}\u3002\u4F60\u7684${label}\u5DF2\u5C31\u7EEA\uFF0C\u51C6\u5907\u597D\u5F81\u670D\u5E02\u573A\u4E86\u5417\uFF1F`,
525
+ "marketer": `${roleTitle}\u4F60\u597D\uFF01${label}\u5DF2\u4E3A\u4F60\u5907\u9F50\u5DE5\u5177\uFF0C\u8BA9\u521B\u610F\u843D\u5730\u3002`,
526
+ "content-creator": `\u7075\u611F\u6765\u4E86\u5C31\u52A8\u7B14\uFF0C${roleTitle}\u3002${label}\u968F\u65F6\u652F\u63F4\u3002`,
527
+ "product-manager": `${roleTitle}\uFF0C${label}\u5DF2\u540C\u6B65\u6700\u65B0\u6570\u636E\uFF0C\u5F00\u59CB\u4ECA\u5929\u7684\u5DE5\u4F5C\u5427\u3002`,
528
+ "researcher": `${roleTitle}\uFF0C${label}\u5DF2\u5C31\u4F4D\u3002\u8BA9\u6570\u636E\u8BF4\u8BDD\u3002`,
529
+ "operations": `${roleTitle}\uFF0C${label}\u8FD0\u8F6C\u6B63\u5E38\u3002\u4ECA\u5929\u5904\u7406\u4EC0\u4E48\uFF1F`,
530
+ "freelancer": `\u81EA\u7531\u7684${roleTitle}\uFF0C${label}\u662F\u4F60\u7684\u9AD8\u6548\u540E\u76FE\u3002`
531
+ };
532
+ return greetings[role] || `\u6B22\u8FCE\u4F7F\u7528${label}\uFF0C\u4E00\u5207\u5C31\u7EEA\u3002`;
533
+ }
534
+ function generateWorkspaceProfile(answers) {
535
+ const { industry, role, teamSize, platforms } = answers;
536
+ const automationNeeds = Array.isArray(answers.automationNeeds) ? answers.automationNeeds : [];
537
+ const linesSet = /* @__PURE__ */ new Set();
538
+ const agentsSet = /* @__PURE__ */ new Set();
539
+ const skillsSet = /* @__PURE__ */ new Set();
540
+ for (const need of automationNeeds) {
541
+ (NEED_TO_LINES[need] || []).forEach((l) => linesSet.add(l));
542
+ (NEED_TO_AGENTS[need] || []).forEach((a) => agentsSet.add(a));
543
+ (NEED_TO_SKILLS[need] || []).forEach((s) => skillsSet.add(s));
544
+ }
545
+ const enabledModules = ["chat", "files", "memory", "tools", "team", "network"];
546
+ const workspaceLabel = INDUSTRY_LABELS[industry] || "\u667A\u80FD\u5DE5\u4F5C\u53F0";
547
+ const quickCommands = generateQuickCommands(industry, role, automationNeeds);
548
+ const welcomeMessage = generateWelcomeMessage(industry, role);
549
+ return {
550
+ id: randomBytes(8).toString("hex"),
551
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
552
+ industry,
553
+ role,
554
+ automationNeeds,
555
+ teamSize,
556
+ platforms,
557
+ freeDescription: answers.freeDescription,
558
+ enabledModules,
559
+ recommendedLines: [...linesSet],
560
+ recommendedAgents: [...agentsSet],
561
+ recommendedSkills: [...skillsSet],
562
+ workspaceLabel,
563
+ quickCommands,
564
+ welcomeMessage
565
+ };
566
+ }
567
+
568
+ // src/web/api.ts
569
+ function sendAttachment(res, content, filename, contentType) {
570
+ res.writeHead(200, {
571
+ "Content-Type": contentType,
572
+ "Content-Disposition": `attachment; filename="${filename}"`,
573
+ "Cache-Control": "no-cache, no-store, must-revalidate",
574
+ "Pragma": "no-cache",
575
+ "Expires": "0",
576
+ "Access-Control-Allow-Origin": `http://localhost:${getActivePort()}`,
577
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
578
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
579
+ });
580
+ res.end(content);
581
+ }
582
+ function sendAdminTestError(res, err, fallback = "\u6D4B\u8BD5\u63A5\u53E3\u8C03\u7528\u5931\u8D25") {
583
+ if (err && typeof err === "object" && "statusCode" in err) {
584
+ const e = err;
585
+ sendError(res, e.message || fallback, e.statusCode || 400);
586
+ return;
587
+ }
588
+ sendError(res, String(err));
589
+ }
590
+ async function handleApiRequest(req, res, config, pathname) {
591
+ const method = req.method || "GET";
592
+ if (pathname === "/api/sessions" && method === "GET") {
593
+ const sessions = await listSessions(50);
594
+ sendJSON(res, { sessions });
595
+ return;
596
+ }
597
+ const exportMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/export$/);
598
+ if (exportMatch && method === "GET") {
599
+ try {
600
+ const markdown = await exportSession(exportMatch[1]);
601
+ res.writeHead(200, {
602
+ "Content-Type": "text/markdown; charset=utf-8",
603
+ "Content-Disposition": `attachment; filename="${exportMatch[1]}.md"`,
604
+ "Access-Control-Allow-Origin": `http://localhost:${getActivePort()}`
605
+ });
606
+ res.end(markdown);
607
+ } catch {
608
+ sendError(res, "Session not found", 404);
609
+ }
610
+ return;
611
+ }
612
+ const sessionMatch = pathname.match(/^\/api\/sessions\/([^/]+)$/);
613
+ if (sessionMatch) {
614
+ const sessionId = sessionMatch[1];
615
+ if (method === "GET") {
616
+ try {
617
+ const messages = await loadSession(sessionId);
618
+ sendJSON(res, { id: sessionId, messages });
619
+ } catch {
620
+ sendError(res, "Session not found", 404);
621
+ }
622
+ return;
623
+ }
624
+ if (method === "DELETE") {
625
+ try {
626
+ await deleteSession(sessionId);
627
+ sendJSON(res, { success: true });
628
+ } catch {
629
+ sendError(res, "Failed to delete session", 500);
630
+ }
631
+ return;
632
+ }
633
+ }
634
+ if (pathname === "/api/config" && method === "GET") {
635
+ const replCtx = getREPLContext();
636
+ if (replCtx) {
637
+ sendJSON(res, {
638
+ provider: replCtx.config.modelConfig.provider,
639
+ model: replCtx.config.modelConfig.model,
640
+ outputDir: replCtx.config.outputDir,
641
+ hasSearchKey: !!replCtx.config.tavilyApiKey
642
+ });
643
+ return;
644
+ }
645
+ const currentConfig = await loadConfig();
646
+ sendJSON(res, {
647
+ provider: currentConfig.modelConfig.provider,
648
+ model: currentConfig.modelConfig.model,
649
+ outputDir: currentConfig.outputDir,
650
+ hasSearchKey: !!currentConfig.tavilyApiKey
651
+ });
652
+ return;
653
+ }
654
+ if (pathname === "/api/tools" && method === "GET") {
655
+ const tools = await createToolRegistry(config);
656
+ const toolList = tools.map((t) => ({
657
+ name: t.definition.name,
658
+ description: t.definition.description
659
+ }));
660
+ sendJSON(res, { tools: toolList });
661
+ return;
662
+ }
663
+ if (pathname === "/api/team/local-token" && method === "GET") {
664
+ try {
665
+ const { loadLocalToken } = await import("./auth-DNQWYQKT.js");
666
+ const token = await loadLocalToken();
667
+ sendJSON(res, token || { available: false });
668
+ } catch {
669
+ sendJSON(res, { available: false });
670
+ }
671
+ return;
672
+ }
673
+ if (pathname === "/api/git/status" && method === "GET") {
674
+ if (!isGitRepo()) {
675
+ sendJSON(res, { available: false });
676
+ return;
677
+ }
678
+ try {
679
+ const status = gitStatus();
680
+ const branch = gitBranch();
681
+ const log = gitLog(5);
682
+ const head = gitHeadShort();
683
+ const uncommitted = hasUncommittedChanges();
684
+ const summary = gitStatusSummary();
685
+ sendJSON(res, {
686
+ available: true,
687
+ branch,
688
+ head,
689
+ uncommitted,
690
+ summary,
691
+ status,
692
+ recentCommits: log
693
+ });
694
+ } catch (err) {
695
+ sendJSON(res, { available: false, error: String(err) });
696
+ }
697
+ return;
698
+ }
699
+ const costMatch = pathname.match(/^\/api\/cost\/([^/]+)$/);
700
+ if (costMatch && method === "GET") {
701
+ try {
702
+ const messages = await loadSession(costMatch[1]);
703
+ let totalChars = 0;
704
+ messages.forEach((m) => {
705
+ totalChars += (m.content || "").length;
706
+ });
707
+ const roughInputTokens = Math.floor(totalChars / 4);
708
+ const roughOutputTokens = Math.floor(
709
+ messages.filter((m) => m.role === "assistant").reduce((sum, m) => sum + (m.content || "").length, 0) / 4
710
+ );
711
+ const cost = estimateCost(config.modelConfig.model, roughInputTokens, roughOutputTokens);
712
+ sendJSON(res, {
713
+ sessionId: costMatch[1],
714
+ estimatedTokens: { input: roughInputTokens, output: roughOutputTokens },
715
+ cost: cost ? { usd: cost.usd, cny: cost.cny, formatted: formatCost(cost) } : null,
716
+ model: config.modelConfig.model
717
+ });
718
+ } catch {
719
+ sendError(res, "Session not found", 404);
720
+ }
721
+ return;
722
+ }
723
+ if (pathname === "/api/hooks" && method === "GET") {
724
+ await hookManager.load();
725
+ const hooksByEvent = hookManager.listByEvent();
726
+ sendJSON(res, {
727
+ total: hookManager.count,
728
+ byEvent: hooksByEvent
729
+ });
730
+ return;
731
+ }
732
+ if (pathname === "/api/lines" && method === "GET") {
733
+ try {
734
+ const lines = await listLines(HYPERCORE_DIR2);
735
+ sendJSON(res, { lines });
736
+ } catch {
737
+ sendJSON(res, { lines: [] });
738
+ }
739
+ return;
740
+ }
741
+ if (pathname === "/api/history" && method === "GET") {
742
+ try {
743
+ const outputDir = config.outputDir;
744
+ const files = await readdir(outputDir);
745
+ const runs = [];
746
+ for (const file of files.filter((f) => f.endsWith(".run.json")).reverse().slice(0, 50)) {
747
+ try {
748
+ const content = await readFile(join(outputDir, file), "utf-8");
749
+ const meta = JSON.parse(content);
750
+ runs.push({
751
+ file: file.replace(".run.json", ""),
752
+ lineName: meta.lineName,
753
+ startTime: meta.startTime,
754
+ endTime: meta.endTime,
755
+ model: meta.model,
756
+ totalTokens: meta.totalTokens,
757
+ stationCount: meta.stations?.length || 0
758
+ });
759
+ } catch {
760
+ }
761
+ }
762
+ sendJSON(res, { runs });
763
+ } catch {
764
+ sendJSON(res, { runs: [] });
765
+ }
766
+ return;
767
+ }
768
+ const historyMatch = pathname.match(/^\/api\/history\/([^/]+)$/);
769
+ if (historyMatch && method === "GET") {
770
+ try {
771
+ const filePath = join(config.outputDir, `${historyMatch[1]}.run.json`);
772
+ const content = await readFile(filePath, "utf-8");
773
+ sendJSON(res, JSON.parse(content));
774
+ } catch {
775
+ sendError(res, "Run history not found", 404);
776
+ }
777
+ return;
778
+ }
779
+ if (pathname === "/api/memory" && method === "GET") {
780
+ try {
781
+ const { getMemoryStats, listMemories } = await import("./store-Y4LU5QTO.js");
782
+ const layers = {};
783
+ for (const layer of ["personal", "project"]) {
784
+ try {
785
+ const stats = await getMemoryStats(layer);
786
+ layers[layer] = { count: stats.total, categories: stats.byCategory };
787
+ } catch {
788
+ layers[layer] = { count: 0 };
789
+ }
790
+ }
791
+ let records = [];
792
+ try {
793
+ const personal = await listMemories("personal");
794
+ records = personal.slice(0, 50);
795
+ } catch {
796
+ }
797
+ sendJSON(res, { layers, records });
798
+ } catch (err) {
799
+ sendError(res, String(err));
800
+ }
801
+ return;
802
+ }
803
+ if (pathname === "/api/memory/search" && method === "POST") {
804
+ try {
805
+ const { parseBody: parseBody2 } = await import("./server-PORT7OEG.js");
806
+ const body = await parseBody2(req);
807
+ const query = body.query;
808
+ if (!query) {
809
+ sendError(res, "query is required", 400);
810
+ return;
811
+ }
812
+ const { searchMemories } = await import("./store-Y4LU5QTO.js");
813
+ const results = await searchMemories("personal", query, {
814
+ category: body.category,
815
+ limit: 20
816
+ });
817
+ sendJSON(res, { results });
818
+ } catch (err) {
819
+ sendError(res, String(err));
820
+ }
821
+ return;
822
+ }
823
+ if (pathname === "/api/files" && method === "GET") {
824
+ try {
825
+ const url = new URL(req.url || "/", `http://localhost`);
826
+ const dirPath = url.searchParams.get("path") || ".";
827
+ const cwd = process.cwd();
828
+ const absPath = resolve(cwd, dirPath);
829
+ const rel = relative(cwd, absPath);
830
+ if (rel.startsWith("..") || isAbsolute(rel)) {
831
+ sendError(res, "Path outside working directory", 403);
832
+ return;
833
+ }
834
+ const entries = await readdir(absPath, { withFileTypes: true });
835
+ const items = [];
836
+ for (const entry of entries) {
837
+ if (entry.name.startsWith(".") && entry.name !== ".hypercore") continue;
838
+ try {
839
+ const fullPath = join(absPath, entry.name);
840
+ const s = await stat(fullPath);
841
+ items.push({
842
+ name: entry.name,
843
+ type: entry.isDirectory() ? "directory" : "file",
844
+ size: entry.isFile() ? s.size : void 0
845
+ });
846
+ } catch {
847
+ }
848
+ }
849
+ items.sort((a, b) => {
850
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
851
+ return a.name.localeCompare(b.name);
852
+ });
853
+ sendJSON(res, { path: relative(cwd, absPath) || ".", entries: items });
854
+ } catch (err) {
855
+ sendError(res, String(err));
856
+ }
857
+ return;
858
+ }
859
+ if (pathname === "/api/files/read" && method === "GET") {
860
+ try {
861
+ const url = new URL(req.url || "/", `http://localhost`);
862
+ const filePath = url.searchParams.get("path") || "";
863
+ if (!filePath) {
864
+ sendError(res, "path is required", 400);
865
+ return;
866
+ }
867
+ const cwd = process.cwd();
868
+ const absPath = resolve(cwd, filePath);
869
+ const rel = relative(cwd, absPath);
870
+ if (rel.startsWith("..") || isAbsolute(rel)) {
871
+ sendError(res, "Path outside working directory", 403);
872
+ return;
873
+ }
874
+ const s = await stat(absPath);
875
+ if (s.size > 102400) {
876
+ sendError(res, "File too large (>100KB)", 413);
877
+ return;
878
+ }
879
+ const content = await readFile(absPath, "utf-8");
880
+ sendJSON(res, { path: filePath, content, size: s.size });
881
+ } catch {
882
+ sendError(res, "File not found", 404);
883
+ }
884
+ return;
885
+ }
886
+ if (pathname === "/api/instances" && method === "GET") {
887
+ try {
888
+ const { listInstances } = await import("./instance-registry-YSIJXSO7.js");
889
+ const instances = await listInstances();
890
+ const currentPid = process.pid;
891
+ sendJSON(res, {
892
+ instances: instances.map((inst) => ({
893
+ ...inst,
894
+ isCurrent: inst.pid === currentPid
895
+ })),
896
+ total: instances.length
897
+ });
898
+ } catch {
899
+ sendJSON(res, { instances: [], total: 0 });
900
+ }
901
+ return;
902
+ }
903
+ if (pathname === "/api/repl/state" && method === "GET") {
904
+ const ctx = getREPLContext();
905
+ if (!ctx) {
906
+ sendJSON(res, { active: false });
907
+ return;
908
+ }
909
+ sendJSON(res, {
910
+ active: true,
911
+ sessionId: ctx.sessionId,
912
+ model: ctx.config.modelConfig.model,
913
+ provider: ctx.config.modelConfig.provider,
914
+ tokens: ctx.sessionTokens,
915
+ toolCount: ctx.tools.length,
916
+ chatHistoryLength: ctx.chatHistory.length,
917
+ cwd: process.cwd()
918
+ });
919
+ return;
920
+ }
921
+ if (pathname === "/api/repl/commands" && method === "GET") {
922
+ const ctx = getREPLContext();
923
+ sendJSON(res, {
924
+ commands: [
925
+ { name: "new", description: "\u65B0\u5EFA\u4F1A\u8BDD", category: "session" },
926
+ { name: "save", description: "\u4FDD\u5B58\u4F1A\u8BDD", category: "session" },
927
+ { name: "cost", description: "\u67E5\u770B\u8D39\u7528", category: "session" },
928
+ { name: "compact", description: "\u538B\u7F29\u5386\u53F2", category: "context" },
929
+ { name: "memory", description: "\u8BB0\u5FC6\u7EDF\u8BA1", category: "memory" },
930
+ { name: "remember", description: "\u624B\u52A8\u8BB0\u5FC6", category: "memory" },
931
+ { name: "recall", description: "\u641C\u7D22\u8BB0\u5FC6", category: "memory" }
932
+ ],
933
+ replActive: !!ctx
934
+ });
935
+ return;
936
+ }
937
+ if (pathname === "/api/onboard" && method === "POST") {
938
+ try {
939
+ const { parseBody: parseBody2 } = await import("./server-PORT7OEG.js");
940
+ const body = await parseBody2(req);
941
+ if (!body.industry || !body.role || !body.provider || !body.apiKey) {
942
+ sendError(res, "\u7F3A\u5C11\u5FC5\u8981\u5B57\u6BB5\uFF08industry, role, provider, apiKey\uFF09", 400);
943
+ return;
944
+ }
945
+ const existingProfilePath = join(HYPERCORE_DIR, "workspace-profile.json");
946
+ if (existsSync(existingProfilePath)) {
947
+ try {
948
+ const existing = JSON.parse(await readFile(existingProfilePath, "utf-8"));
949
+ if (existing && existing.id) {
950
+ sendJSON(res, { success: true, profile: existing, alreadyExists: true });
951
+ return;
952
+ }
953
+ } catch {
954
+ }
955
+ }
956
+ const profile = generateWorkspaceProfile(body);
957
+ await initializeWorkspace({
958
+ provider: body.provider,
959
+ apiKey: body.apiKey,
960
+ tavilyKey: body.tavilyKey || void 0,
961
+ role: body.role
962
+ });
963
+ const profilePath = join(HYPERCORE_DIR, "workspace-profile.json");
964
+ await writeFile(profilePath, JSON.stringify(profile, null, 2), "utf-8");
965
+ sendJSON(res, { success: true, profile });
966
+ } catch (err) {
967
+ console.error("[Onboard Error]", err);
968
+ sendError(res, String(err));
969
+ }
970
+ return;
971
+ }
972
+ if (pathname === "/api/workspace-profile" && method === "GET") {
973
+ try {
974
+ const profilePath = join(HYPERCORE_DIR, "workspace-profile.json");
975
+ if (!existsSync(profilePath)) {
976
+ sendJSON(res, { profile: null });
977
+ return;
978
+ }
979
+ const content = await readFile(profilePath, "utf-8");
980
+ sendJSON(res, { profile: JSON.parse(content) });
981
+ } catch (err) {
982
+ console.error("[Profile Error]", err);
983
+ sendJSON(res, { profile: null });
984
+ }
985
+ return;
986
+ }
987
+ if (pathname === "/api/admin/status" && method === "GET") {
988
+ try {
989
+ const { getStats, listItems } = await import("./backlog-Q2NZCLNY.js");
990
+ const { getEventsSummary } = await import("./telemetry-6R4EIE6O.js");
991
+ const { listMilestones } = await import("./roadmap-5OBEKROY.js");
992
+ const [stats, developing, done, summary7d, milestones] = await Promise.all([
993
+ getStats(),
994
+ listItems({ status: "developing" }),
995
+ listItems({ status: "done" }),
996
+ getEventsSummary(7).catch(() => null),
997
+ listMilestones().catch(() => [])
998
+ ]);
999
+ sendJSON(res, {
1000
+ backlog: stats,
1001
+ developing: developing.slice(0, 5).map((i) => ({ id: i.id, title: i.title, priority: i.priority, wuxing: i.wuxing })),
1002
+ recentDone: done.slice(0, 5).map((i) => ({ id: i.id, title: i.title, resolvedAt: i.resolvedAt })),
1003
+ telemetry: summary7d ? {
1004
+ totalEvents: summary7d.totalEvents,
1005
+ avgSessionRounds: summary7d.avgSessionRounds,
1006
+ topErrors: summary7d.errors.slice(0, 3)
1007
+ } : null,
1008
+ milestones: milestones.filter((m) => m.status !== "cancelled").slice(0, 5).map((m) => ({
1009
+ version: m.version,
1010
+ name: m.name,
1011
+ status: m.status
1012
+ }))
1013
+ });
1014
+ } catch (err) {
1015
+ sendError(res, String(err));
1016
+ }
1017
+ return;
1018
+ }
1019
+ if (pathname === "/api/admin/backlog" && method === "GET") {
1020
+ try {
1021
+ const { listItems, getStats } = await import("./backlog-Q2NZCLNY.js");
1022
+ const url = new URL(req.url || "/", "http://localhost");
1023
+ const filters = {};
1024
+ for (const key of ["status", "wuxing", "priority", "type"]) {
1025
+ const val = url.searchParams.get(key);
1026
+ if (val) filters[key] = val;
1027
+ }
1028
+ const [items, stats] = await Promise.all([
1029
+ listItems(filters),
1030
+ getStats()
1031
+ ]);
1032
+ sendJSON(res, { items, stats });
1033
+ } catch (err) {
1034
+ sendError(res, String(err));
1035
+ }
1036
+ return;
1037
+ }
1038
+ if (pathname === "/api/admin/quality" && method === "GET") {
1039
+ try {
1040
+ const { runQualityReport } = await import("./quality-ST7PPNFR.js");
1041
+ const report = await runQualityReport(process.cwd(), { skipTests: true, skipBuild: true });
1042
+ sendJSON(res, report);
1043
+ } catch (err) {
1044
+ sendError(res, String(err));
1045
+ }
1046
+ return;
1047
+ }
1048
+ if (pathname === "/api/admin/diagnose" && method === "GET") {
1049
+ try {
1050
+ const { runRuleDiagnosis } = await import("./diagnose-AFW3ZTZ4.js");
1051
+ const url = new URL(req.url || "/", "http://localhost");
1052
+ const days = parseInt(url.searchParams.get("days") || "7") || 7;
1053
+ const findings = await runRuleDiagnosis(days);
1054
+ sendJSON(res, { findings, total: findings.length });
1055
+ } catch (err) {
1056
+ sendError(res, String(err));
1057
+ }
1058
+ return;
1059
+ }
1060
+ if (pathname === "/api/admin/verify" && method === "GET") {
1061
+ try {
1062
+ const { verifyCompletedItems } = await import("./verify-JUDKTPKZ.js");
1063
+ const report = await verifyCompletedItems();
1064
+ sendJSON(res, report);
1065
+ } catch (err) {
1066
+ sendError(res, String(err));
1067
+ }
1068
+ return;
1069
+ }
1070
+ if (pathname === "/api/admin/roadmap" && method === "GET") {
1071
+ try {
1072
+ const { getRoadmap } = await import("./roadmap-5OBEKROY.js");
1073
+ const versions = await getRoadmap();
1074
+ sendJSON(res, { versions });
1075
+ } catch (err) {
1076
+ sendError(res, String(err));
1077
+ }
1078
+ return;
1079
+ }
1080
+ if (pathname === "/api/admin/telemetry" && method === "GET") {
1081
+ try {
1082
+ const { getEventsSummary } = await import("./telemetry-6R4EIE6O.js");
1083
+ const url = new URL(req.url || "/", "http://localhost");
1084
+ const days = parseInt(url.searchParams.get("days") || "7") || 7;
1085
+ const summary = await getEventsSummary(days);
1086
+ sendJSON(res, summary);
1087
+ } catch (err) {
1088
+ sendError(res, String(err));
1089
+ }
1090
+ return;
1091
+ }
1092
+ if (pathname === "/api/admin/tests" && method === "GET") {
1093
+ try {
1094
+ const { getTestRunnerSnapshot } = await import("./test-runner-ZQH5Y6OJ.js");
1095
+ const snapshot = await getTestRunnerSnapshot({
1096
+ projectRoot: process.cwd(),
1097
+ baseUrl: `http://127.0.0.1:${getActivePort()}`
1098
+ });
1099
+ sendJSON(res, snapshot);
1100
+ } catch (err) {
1101
+ sendAdminTestError(res, err);
1102
+ }
1103
+ return;
1104
+ }
1105
+ if (pathname === "/api/admin/tests/auto" && method === "GET") {
1106
+ try {
1107
+ const { getAutoRegressionSnapshot } = await import("./test-runner-ZQH5Y6OJ.js");
1108
+ const snapshot = await getAutoRegressionSnapshot({
1109
+ projectRoot: process.cwd(),
1110
+ baseUrl: `http://127.0.0.1:${getActivePort()}`
1111
+ });
1112
+ sendJSON(res, snapshot);
1113
+ } catch (err) {
1114
+ sendAdminTestError(res, err, "\u67E5\u8BE2\u81EA\u52A8\u56DE\u5F52\u914D\u7F6E\u5931\u8D25");
1115
+ }
1116
+ return;
1117
+ }
1118
+ if (pathname === "/api/admin/tests/auto" && method === "POST") {
1119
+ try {
1120
+ const { parseBody: parseBody2 } = await import("./server-PORT7OEG.js");
1121
+ const body = await parseBody2(req);
1122
+ const update = {};
1123
+ if (Object.prototype.hasOwnProperty.call(body, "enabled")) {
1124
+ if (typeof body.enabled !== "boolean") {
1125
+ sendError(res, "enabled must be boolean", 400);
1126
+ return;
1127
+ }
1128
+ update.enabled = body.enabled;
1129
+ }
1130
+ if (Object.prototype.hasOwnProperty.call(body, "suiteId")) {
1131
+ if (typeof body.suiteId !== "string") {
1132
+ sendError(res, "suiteId must be string", 400);
1133
+ return;
1134
+ }
1135
+ update.suiteId = body.suiteId;
1136
+ }
1137
+ if (Object.prototype.hasOwnProperty.call(body, "intervalMinutes")) {
1138
+ const intervalRaw = body.intervalMinutes;
1139
+ const interval = typeof intervalRaw === "number" ? intervalRaw : Number(intervalRaw);
1140
+ if (!Number.isFinite(interval)) {
1141
+ sendError(res, "intervalMinutes must be number", 400);
1142
+ return;
1143
+ }
1144
+ update.intervalMinutes = Math.floor(interval);
1145
+ }
1146
+ const { updateAutoRegressionConfig, getTestRunnerSnapshot } = await import("./test-runner-ZQH5Y6OJ.js");
1147
+ await updateAutoRegressionConfig(update, {
1148
+ projectRoot: process.cwd(),
1149
+ baseUrl: `http://127.0.0.1:${getActivePort()}`
1150
+ });
1151
+ const snapshot = await getTestRunnerSnapshot({
1152
+ projectRoot: process.cwd(),
1153
+ baseUrl: `http://127.0.0.1:${getActivePort()}`
1154
+ });
1155
+ sendJSON(res, snapshot);
1156
+ } catch (err) {
1157
+ sendAdminTestError(res, err, "\u81EA\u52A8\u56DE\u5F52\u914D\u7F6E\u66F4\u65B0\u5931\u8D25");
1158
+ }
1159
+ return;
1160
+ }
1161
+ const testRunDownloadMatch = pathname.match(/^\/api\/admin\/tests\/runs\/([^/]+)\/download$/);
1162
+ if (testRunDownloadMatch && method === "GET") {
1163
+ try {
1164
+ const runId = decodeURIComponent(testRunDownloadMatch[1]);
1165
+ const {
1166
+ getTestRunDetail,
1167
+ formatTestRunDownloadText,
1168
+ getTestRunDownloadFileName
1169
+ } = await import("./test-runner-ZQH5Y6OJ.js");
1170
+ const detail = await getTestRunDetail(runId);
1171
+ const filename = getTestRunDownloadFileName(detail.info, "log");
1172
+ const content = formatTestRunDownloadText(detail);
1173
+ sendAttachment(res, content, filename, "text/plain; charset=utf-8");
1174
+ } catch (err) {
1175
+ sendAdminTestError(res, err, "\u4E0B\u8F7D\u6D4B\u8BD5\u65E5\u5FD7\u5931\u8D25");
1176
+ }
1177
+ return;
1178
+ }
1179
+ const testRunExportMatch = pathname.match(/^\/api\/admin\/tests\/runs\/([^/]+)\/export$/);
1180
+ if (testRunExportMatch && method === "GET") {
1181
+ try {
1182
+ const runId = decodeURIComponent(testRunExportMatch[1]);
1183
+ const { getTestRunDetail, getTestRunDownloadFileName } = await import("./test-runner-ZQH5Y6OJ.js");
1184
+ const detail = await getTestRunDetail(runId);
1185
+ const filename = getTestRunDownloadFileName(detail.info, "json");
1186
+ const content = JSON.stringify(detail, null, 2);
1187
+ sendAttachment(res, content, filename, "application/json; charset=utf-8");
1188
+ } catch (err) {
1189
+ sendAdminTestError(res, err, "\u5BFC\u51FA\u6D4B\u8BD5\u8BE6\u60C5\u5931\u8D25");
1190
+ }
1191
+ return;
1192
+ }
1193
+ const testRunDetailMatch = pathname.match(/^\/api\/admin\/tests\/runs\/([^/]+)$/);
1194
+ if (testRunDetailMatch && method === "GET") {
1195
+ try {
1196
+ const runId = decodeURIComponent(testRunDetailMatch[1]);
1197
+ const { getTestRunDetail } = await import("./test-runner-ZQH5Y6OJ.js");
1198
+ const detail = await getTestRunDetail(runId);
1199
+ sendJSON(res, detail);
1200
+ } catch (err) {
1201
+ sendAdminTestError(res, err, "\u67E5\u8BE2\u6D4B\u8BD5\u8BE6\u60C5\u5931\u8D25");
1202
+ }
1203
+ return;
1204
+ }
1205
+ if (pathname === "/api/admin/tests/run" && method === "POST") {
1206
+ try {
1207
+ const { parseBody: parseBody2 } = await import("./server-PORT7OEG.js");
1208
+ const body = await parseBody2(req);
1209
+ const suiteId = typeof body.suiteId === "string" && body.suiteId.trim() ? body.suiteId.trim() : void 0;
1210
+ const { startTestSuiteRun } = await import("./test-runner-ZQH5Y6OJ.js");
1211
+ const snapshot = await startTestSuiteRun({
1212
+ projectRoot: process.cwd(),
1213
+ suiteId,
1214
+ baseUrl: `http://127.0.0.1:${getActivePort()}`
1215
+ });
1216
+ sendJSON(res, snapshot, 202);
1217
+ } catch (err) {
1218
+ sendAdminTestError(res, err, "\u6D4B\u8BD5\u542F\u52A8\u5931\u8D25");
1219
+ }
1220
+ return;
1221
+ }
1222
+ sendError(res, `Not Found: ${pathname}`, 404);
1223
+ }
1224
+
1225
+ // src/web/sse.ts
1226
+ function sseWrite(res, type, data) {
1227
+ res.write(`data: ${JSON.stringify({ type, ...typeof data === "object" && data !== null ? data : { content: data } })}
1228
+
1229
+ `);
1230
+ }
1231
+ function buildSystemPrompt() {
1232
+ return `You are Hypercore AI Assistant, running through the Web Dashboard.
1233
+ You have access to tools for file operations, search, and web fetching.
1234
+ Be concise, helpful, and use markdown formatting in your responses.
1235
+ Current time: ${(/* @__PURE__ */ new Date()).toLocaleString()}`;
1236
+ }
1237
+ async function loadEffectiveConfig(fallbackConfig) {
1238
+ const replContext2 = getREPLContext();
1239
+ if (replContext2) {
1240
+ return replContext2.config;
1241
+ }
1242
+ try {
1243
+ return await loadConfig();
1244
+ } catch {
1245
+ return fallbackConfig;
1246
+ }
1247
+ }
1248
+ async function handleChatSSE(req, res, config) {
1249
+ let body;
1250
+ try {
1251
+ body = await parseBody(req);
1252
+ } catch {
1253
+ res.writeHead(400, { "Content-Type": "application/json" });
1254
+ res.end(JSON.stringify({ error: "Invalid request body" }));
1255
+ return;
1256
+ }
1257
+ const message = body.message;
1258
+ const sessionId = body.sessionId || generateSessionId();
1259
+ if (!message) {
1260
+ res.writeHead(400, { "Content-Type": "application/json" });
1261
+ res.end(JSON.stringify({ error: "Message is required" }));
1262
+ return;
1263
+ }
1264
+ res.writeHead(200, {
1265
+ "Content-Type": "text/event-stream",
1266
+ "Cache-Control": "no-cache",
1267
+ "Connection": "keep-alive",
1268
+ "Access-Control-Allow-Origin": `http://localhost:${getActivePort()}`,
1269
+ "X-Accel-Buffering": "no"
1270
+ });
1271
+ sseWrite(res, "session", { sessionId });
1272
+ try {
1273
+ let history = [];
1274
+ try {
1275
+ history = await loadSession(sessionId);
1276
+ } catch {
1277
+ }
1278
+ const effectiveConfig = await loadEffectiveConfig(config);
1279
+ const { modelConfig } = effectiveConfig;
1280
+ const tools = await createToolRegistry(effectiveConfig);
1281
+ const systemPrompt = buildSystemPrompt();
1282
+ let totalInputTokens = 0;
1283
+ let totalOutputTokens = 0;
1284
+ if (modelConfig.sdkType === "anthropic") {
1285
+ const client = createLLMClient(modelConfig);
1286
+ const result = await streamCallLLM(client, {
1287
+ systemPrompt,
1288
+ userPrompt: message,
1289
+ tools,
1290
+ model: modelConfig.model,
1291
+ history,
1292
+ onText: (text) => {
1293
+ sseWrite(res, "text", { content: text });
1294
+ },
1295
+ onToolCall: (name, input) => {
1296
+ sseWrite(res, "tool_call", { name, input });
1297
+ }
1298
+ });
1299
+ totalInputTokens = result.tokenUsage.inputTokens;
1300
+ totalOutputTokens = result.tokenUsage.outputTokens;
1301
+ for (const tc of result.toolCalls) {
1302
+ sseWrite(res, "tool_result", {
1303
+ name: tc.toolName,
1304
+ output: tc.output.substring(0, 2e3)
1305
+ // Truncate for SSE
1306
+ });
1307
+ }
1308
+ history.push({ role: "user", content: message });
1309
+ history.push({ role: "assistant", content: result.responseText || result.output });
1310
+ await saveSession(sessionId, history);
1311
+ } else {
1312
+ const client = createOpenAIClient(modelConfig);
1313
+ const openaiMessages = history.map((m) => ({
1314
+ role: m.role,
1315
+ content: m.content
1316
+ }));
1317
+ openaiMessages.push({ role: "user", content: message });
1318
+ const result = await streamOpenAIChat(client, openaiMessages, {
1319
+ model: modelConfig.model,
1320
+ tools,
1321
+ onChunk: (text) => {
1322
+ sseWrite(res, "text", { content: text });
1323
+ },
1324
+ onToolCall: (name) => {
1325
+ sseWrite(res, "tool_call", { name, input: {} });
1326
+ }
1327
+ });
1328
+ totalInputTokens = result.tokenUsage.inputTokens;
1329
+ totalOutputTokens = result.tokenUsage.outputTokens;
1330
+ history.push({ role: "user", content: message });
1331
+ history.push({ role: "assistant", content: result.responseContent || result.content });
1332
+ await saveSession(sessionId, history);
1333
+ }
1334
+ const cost = estimateCost(modelConfig.model, totalInputTokens, totalOutputTokens);
1335
+ sseWrite(res, "done", {
1336
+ tokenUsage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
1337
+ cost: cost ? { usd: cost.usd, cny: cost.cny } : null,
1338
+ model: modelConfig.model,
1339
+ sessionId
1340
+ });
1341
+ } catch (err) {
1342
+ sseWrite(res, "error", { content: String(err) });
1343
+ }
1344
+ res.end();
1345
+ }
1346
+ async function handleRunSSE(req, res, config) {
1347
+ res.writeHead(200, {
1348
+ "Content-Type": "text/event-stream",
1349
+ "Cache-Control": "no-cache",
1350
+ "Connection": "keep-alive",
1351
+ "Access-Control-Allow-Origin": `http://localhost:${getActivePort()}`
1352
+ });
1353
+ try {
1354
+ const body = await parseBody(req);
1355
+ const lineName = body.lineName;
1356
+ const userInputs = body.inputs || {};
1357
+ if (!lineName) {
1358
+ sseWrite(res, "error", { content: "\u7F3A\u5C11 lineName \u53C2\u6570" });
1359
+ res.end();
1360
+ return;
1361
+ }
1362
+ const effectiveRunConfig = await loadEffectiveConfig(config);
1363
+ const client = effectiveRunConfig.modelConfig.sdkType === "openai" ? createOpenAIClient(effectiveRunConfig.modelConfig) : createLLMClient(effectiveRunConfig.modelConfig);
1364
+ const engine = new Engine(client, effectiveRunConfig, HYPERCORE_DIR2);
1365
+ sseWrite(res, "run_start", { lineName, inputs: userInputs });
1366
+ const result = await engine.run(lineName, userInputs, {
1367
+ onStationStart: (index, total, name, agentName) => {
1368
+ sseWrite(res, "station_start", { index, total, name, agentName });
1369
+ },
1370
+ onStationText: (text) => {
1371
+ sseWrite(res, "text", { content: text });
1372
+ },
1373
+ onToolCall: (name, input) => {
1374
+ sseWrite(res, "tool_call", { name, input });
1375
+ },
1376
+ onStationComplete: (index, name) => {
1377
+ sseWrite(res, "station_complete", { index, name });
1378
+ },
1379
+ onStationSkipped: (index, name, reason) => {
1380
+ sseWrite(res, "station_skipped", { index, name, reason });
1381
+ },
1382
+ onStationRetry: (index, name, attempt, maxRetry, error) => {
1383
+ sseWrite(res, "station_retry", { index, name, attempt, maxRetry, error });
1384
+ },
1385
+ onCheckpoint: async (type, output, description) => {
1386
+ sseWrite(res, "checkpoint", { type, output: output.slice(0, 2e3), description });
1387
+ return { action: "approve" };
1388
+ },
1389
+ onComplete: (runResult) => {
1390
+ const cost = estimateCost(
1391
+ effectiveRunConfig.modelConfig.model,
1392
+ runResult.totalTokens.inputTokens,
1393
+ runResult.totalTokens.outputTokens
1394
+ );
1395
+ sseWrite(res, "run_complete", {
1396
+ lineName: runResult.lineName,
1397
+ outputPath: runResult.outputPath,
1398
+ totalTokens: runResult.totalTokens,
1399
+ cost: cost ? { usd: cost.usd, cny: cost.cny } : null,
1400
+ duration: runResult.endTime.getTime() - runResult.startTime.getTime(),
1401
+ stationCount: runResult.stationResults.length
1402
+ });
1403
+ }
1404
+ });
1405
+ } catch (err) {
1406
+ sseWrite(res, "error", { content: String(err) });
1407
+ }
1408
+ res.end();
1409
+ }
1410
+
1411
+ // src/team/api.ts
1412
+ function sendJSON2(res, data, status = 200) {
1413
+ res.writeHead(status, {
1414
+ "Content-Type": "application/json",
1415
+ "Access-Control-Allow-Origin": "http://localhost:3210",
1416
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1417
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
1418
+ });
1419
+ res.end(JSON.stringify(data));
1420
+ }
1421
+ function sendError2(res, message, status = 400) {
1422
+ sendJSON2(res, { error: message }, status);
1423
+ }
1424
+ var MAX_BODY_SIZE = 1024 * 1024;
1425
+ async function readBody(req) {
1426
+ return new Promise((resolve2, reject) => {
1427
+ const chunks = [];
1428
+ let totalSize = 0;
1429
+ req.on("data", (chunk) => {
1430
+ totalSize += chunk.length;
1431
+ if (totalSize > MAX_BODY_SIZE) {
1432
+ req.destroy();
1433
+ reject(new Error("Request body too large (>1MB)"));
1434
+ return;
1435
+ }
1436
+ chunks.push(chunk);
1437
+ });
1438
+ req.on("end", () => {
1439
+ try {
1440
+ const body = Buffer.concat(chunks).toString("utf-8");
1441
+ resolve2(body ? JSON.parse(body) : {});
1442
+ } catch {
1443
+ resolve2({});
1444
+ }
1445
+ });
1446
+ req.on("error", reject);
1447
+ });
1448
+ }
1449
+ function getToken(req) {
1450
+ const auth = req.headers["authorization"];
1451
+ if (!auth || !auth.startsWith("Bearer ")) return null;
1452
+ return auth.slice(7);
1453
+ }
1454
+ async function handleTeamApiRequest(req, res, _config, pathname) {
1455
+ const method = req.method || "GET";
1456
+ if (method === "OPTIONS") {
1457
+ res.writeHead(204, {
1458
+ "Access-Control-Allow-Origin": "http://localhost:3210",
1459
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1460
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
1461
+ });
1462
+ res.end();
1463
+ return;
1464
+ }
1465
+ if (pathname === "/api/team/create" && method === "POST") {
1466
+ const body = await readBody(req);
1467
+ const name = String(body.name || "").trim();
1468
+ const ownerName = String(body.ownerName || "").trim();
1469
+ if (!name || !ownerName) {
1470
+ sendError2(res, "\u56E2\u961F\u540D\u79F0\u548C\u521B\u5EFA\u8005\u540D\u79F0\u5FC5\u586B");
1471
+ return;
1472
+ }
1473
+ const { team: team2, ownerToken } = await createTeam(name, ownerName);
1474
+ sendJSON2(res, { team: team2, ownerToken }, 201);
1475
+ return;
1476
+ }
1477
+ if (pathname === "/api/team/join" && method === "POST") {
1478
+ const body = await readBody(req);
1479
+ const joinCode = String(body.joinCode || "").trim();
1480
+ const memberName = String(body.memberName || "").trim();
1481
+ if (!joinCode || !memberName) {
1482
+ sendError2(res, "\u52A0\u5165\u7801\u548C\u6210\u5458\u540D\u79F0\u5FC5\u586B");
1483
+ return;
1484
+ }
1485
+ const team2 = await findTeamByJoinCode(joinCode);
1486
+ if (!team2) {
1487
+ sendError2(res, "\u65E0\u6548\u7684\u52A0\u5165\u7801", 404);
1488
+ return;
1489
+ }
1490
+ const result = await addMember(team2.id, memberName);
1491
+ if (!result) {
1492
+ sendError2(res, "\u65E0\u6CD5\u52A0\u5165\u56E2\u961F");
1493
+ return;
1494
+ }
1495
+ sendJSON2(res, {
1496
+ team: result.team,
1497
+ member: result.member,
1498
+ token: result.member.token
1499
+ }, 201);
1500
+ return;
1501
+ }
1502
+ const teamMatch = pathname.match(/^\/api\/team\/([^/]+)(?:\/(.+))?$/);
1503
+ if (!teamMatch) {
1504
+ sendError2(res, "\u672A\u77E5\u8DEF\u7531", 404);
1505
+ return;
1506
+ }
1507
+ const teamId = teamMatch[1];
1508
+ const subpath = teamMatch[2] || "";
1509
+ const team = await loadTeam(teamId);
1510
+ if (!team) {
1511
+ sendError2(res, "\u56E2\u961F\u4E0D\u5B58\u5728", 404);
1512
+ return;
1513
+ }
1514
+ const token = getToken(req);
1515
+ if (!token) {
1516
+ sendError2(res, "\u672A\u63D0\u4F9B\u8BA4\u8BC1\u4EE4\u724C", 401);
1517
+ return;
1518
+ }
1519
+ const member = validateToken(team, token);
1520
+ if (!member) {
1521
+ sendError2(res, "\u8BA4\u8BC1\u5931\u8D25", 401);
1522
+ return;
1523
+ }
1524
+ if (subpath === "status" && method === "GET") {
1525
+ sendJSON2(res, {
1526
+ team: { id: team.id, name: team.name, joinCode: team.joinCode },
1527
+ members: team.members.map((m) => ({
1528
+ id: m.id,
1529
+ name: m.name,
1530
+ role: m.role,
1531
+ status: m.status,
1532
+ lastSeen: m.lastSeen
1533
+ }))
1534
+ });
1535
+ return;
1536
+ }
1537
+ if (subpath === "tasks" && method === "GET") {
1538
+ const tasks = await listTasks(teamId);
1539
+ sendJSON2(res, { tasks });
1540
+ return;
1541
+ }
1542
+ if (subpath === "tasks" && method === "POST") {
1543
+ const body = await readBody(req);
1544
+ const title = String(body.title || "").trim();
1545
+ if (!title) {
1546
+ sendError2(res, "\u4EFB\u52A1\u6807\u9898\u5FC5\u586B");
1547
+ return;
1548
+ }
1549
+ const task = await addTask(teamId, {
1550
+ title,
1551
+ description: body.description ? String(body.description) : void 0,
1552
+ assignee: body.assignee ? String(body.assignee) : void 0,
1553
+ status: body.status || "todo",
1554
+ priority: body.priority || "B",
1555
+ createdBy: member.id
1556
+ });
1557
+ sendJSON2(res, { task }, 201);
1558
+ return;
1559
+ }
1560
+ const taskMatch = subpath.match(/^tasks\/(\d+)$/);
1561
+ if (taskMatch) {
1562
+ const taskId = taskMatch[1];
1563
+ if (method === "PUT") {
1564
+ const body = await readBody(req);
1565
+ const updated = await updateTask(teamId, taskId, body);
1566
+ if (!updated) {
1567
+ sendError2(res, "\u4EFB\u52A1\u4E0D\u5B58\u5728", 404);
1568
+ return;
1569
+ }
1570
+ sendJSON2(res, { task: updated });
1571
+ return;
1572
+ }
1573
+ if (method === "DELETE") {
1574
+ const ok = await deleteTask(teamId, taskId);
1575
+ if (!ok) {
1576
+ sendError2(res, "\u4EFB\u52A1\u4E0D\u5B58\u5728", 404);
1577
+ return;
1578
+ }
1579
+ sendJSON2(res, { success: true });
1580
+ return;
1581
+ }
1582
+ }
1583
+ if (subpath === "runs" && method === "GET") {
1584
+ const runs = await listRuns(teamId);
1585
+ sendJSON2(res, { runs });
1586
+ return;
1587
+ }
1588
+ const runMatch = subpath.match(/^runs\/([^/]+)$/);
1589
+ if (runMatch && method === "GET") {
1590
+ const run = await loadRun(teamId, runMatch[1]);
1591
+ if (!run) {
1592
+ sendError2(res, "\u6267\u884C\u8BB0\u5F55\u4E0D\u5B58\u5728", 404);
1593
+ return;
1594
+ }
1595
+ sendJSON2(res, { run });
1596
+ return;
1597
+ }
1598
+ sendError2(res, "\u672A\u77E5\u8DEF\u7531", 404);
1599
+ }
1600
+
1601
+ // src/web/server.ts
1602
+ var wsRoutes = /* @__PURE__ */ new Map();
1603
+ function registerWSRoute(pathname, wss) {
1604
+ wsRoutes.set(pathname, wss);
1605
+ }
1606
+ function parseHostnameFromHostHeader(hostHeader) {
1607
+ const normalized = hostHeader.trim();
1608
+ if (!normalized) return "";
1609
+ if (normalized.startsWith("[")) {
1610
+ const end = normalized.indexOf("]");
1611
+ if (end > 1) return normalized.slice(1, end);
1612
+ return normalized;
1613
+ }
1614
+ const firstColon = normalized.indexOf(":");
1615
+ const lastColon = normalized.lastIndexOf(":");
1616
+ if (firstColon >= 0 && firstColon === lastColon) {
1617
+ return normalized.slice(0, firstColon);
1618
+ }
1619
+ return normalized;
1620
+ }
1621
+ function isLocalHostname(hostname) {
1622
+ const normalized = hostname.trim().toLowerCase();
1623
+ return ["localhost", "127.0.0.1", "::1"].includes(normalized);
1624
+ }
1625
+ function isLanTeamMode(host) {
1626
+ return host === "0.0.0.0" || host === "::";
1627
+ }
1628
+ function isLanAllowedApiPath(pathname) {
1629
+ if (pathname === "/api/team/create" || pathname === "/api/team/join") return true;
1630
+ if (pathname === "/api/team/local-token") return false;
1631
+ if (/^\/api\/team\/[^/]+\/.+$/.test(pathname)) return true;
1632
+ if (/^\/api\/network\/[^/]+(?:\/.*)?$/.test(pathname)) return true;
1633
+ return false;
1634
+ }
1635
+ function canAccessApiPath(pathname, localRequest, lanTeamMode) {
1636
+ if (localRequest) return true;
1637
+ if (lanTeamMode && isLanAllowedApiPath(pathname)) return true;
1638
+ return false;
1639
+ }
1640
+ function canAccessWebSocketPath(pathname, localRequest, lanTeamMode) {
1641
+ if (localRequest) return true;
1642
+ if (lanTeamMode && pathname === "/ws/team") return true;
1643
+ return false;
1644
+ }
1645
+ function resolveBrowserHost(host) {
1646
+ if (host === "0.0.0.0") return "127.0.0.1";
1647
+ if (host === "::") return "[::1]";
1648
+ if (host.includes(":") && !host.startsWith("[")) return `[${host}]`;
1649
+ return host;
1650
+ }
1651
+ function installUpgradeRouter(server, host) {
1652
+ const lanTeamMode = isLanTeamMode(host);
1653
+ server.on("upgrade", (req, socket, head) => {
1654
+ const pathname = new URL(req.url || "/", "http://localhost").pathname;
1655
+ const localRequest = isLocalRequest(req);
1656
+ if (!canAccessWebSocketPath(pathname, localRequest, lanTeamMode)) {
1657
+ socket.destroy();
1658
+ return;
1659
+ }
1660
+ const wss = wsRoutes.get(pathname);
1661
+ if (wss) {
1662
+ wss.handleUpgrade(req, socket, head, (ws) => {
1663
+ wss.emit("connection", ws, req);
1664
+ });
1665
+ } else {
1666
+ console.error(` \x1B[33m[WS Router]\x1B[0m No handler for: ${pathname} (registered: ${[...wsRoutes.keys()].join(", ")})`);
1667
+ socket.destroy();
1668
+ }
1669
+ });
1670
+ }
1671
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
1672
+ function resolveStaticDir() {
1673
+ const candidates = [
1674
+ // 1. Dev mode: tsx runs from src/web/server.ts → __dirname = src/web/
1675
+ path.join(__dirname, "static"),
1676
+ // 2. Built mode: tsup bundles to dist/ → __dirname = dist/
1677
+ path.join(__dirname, "web", "static"),
1678
+ // 3. Built mode alt: chunk may be in dist/ subfolder
1679
+ path.join(__dirname, "..", "web", "static"),
1680
+ // 4. CWD is project root (dev)
1681
+ path.join(process.cwd(), "src", "web", "static"),
1682
+ // 5. CWD is project root (built)
1683
+ path.join(process.cwd(), "dist", "web", "static")
1684
+ ];
1685
+ for (const dir of candidates) {
1686
+ if (fs.existsSync(path.join(dir, "index.html"))) {
1687
+ return dir;
1688
+ }
1689
+ }
1690
+ return candidates[0];
1691
+ }
1692
+ var STATIC_DIR = resolveStaticDir();
1693
+ var MIME_TYPES = {
1694
+ ".html": "text/html; charset=utf-8",
1695
+ ".css": "text/css; charset=utf-8",
1696
+ ".js": "application/javascript; charset=utf-8",
1697
+ ".json": "application/json; charset=utf-8",
1698
+ ".png": "image/png",
1699
+ ".svg": "image/svg+xml",
1700
+ ".ico": "image/x-icon"
1701
+ };
1702
+ var MAX_BODY_SIZE2 = 1024 * 1024;
1703
+ function parseBody(req) {
1704
+ return new Promise((resolve2, reject) => {
1705
+ const contentType = (req.headers["content-type"] || "").toLowerCase();
1706
+ if (contentType && !contentType.includes("application/json")) {
1707
+ reject(new Error("Content-Type must be application/json"));
1708
+ return;
1709
+ }
1710
+ const chunks = [];
1711
+ let totalSize = 0;
1712
+ req.on("data", (chunk) => {
1713
+ totalSize += chunk.length;
1714
+ if (totalSize > MAX_BODY_SIZE2) {
1715
+ req.destroy();
1716
+ reject(new Error("Request body too large (>1MB)"));
1717
+ return;
1718
+ }
1719
+ chunks.push(chunk);
1720
+ });
1721
+ req.on("end", () => {
1722
+ try {
1723
+ const body = Buffer.concat(chunks).toString("utf-8");
1724
+ resolve2(body ? JSON.parse(body) : {});
1725
+ } catch {
1726
+ reject(new Error("Invalid JSON body"));
1727
+ }
1728
+ });
1729
+ req.on("error", reject);
1730
+ });
1731
+ }
1732
+ function isLocalRequest(req) {
1733
+ const host = req.headers.host || "";
1734
+ const hostname = parseHostnameFromHostHeader(host);
1735
+ if (!isLocalHostname(hostname)) {
1736
+ return false;
1737
+ }
1738
+ const method = (req.method || "GET").toUpperCase();
1739
+ if (["POST", "PUT", "DELETE", "PATCH"].includes(method)) {
1740
+ const origin = req.headers.origin || "";
1741
+ const referer = req.headers.referer || "";
1742
+ if (origin && !isLocalOrigin(origin)) {
1743
+ return false;
1744
+ }
1745
+ if (!origin && referer && !isLocalOrigin(referer)) {
1746
+ return false;
1747
+ }
1748
+ }
1749
+ return true;
1750
+ }
1751
+ function isLocalOrigin(urlStr) {
1752
+ try {
1753
+ const u = new URL(urlStr);
1754
+ return ["localhost", "127.0.0.1", "::1", "[::1]"].includes(u.hostname);
1755
+ } catch {
1756
+ return false;
1757
+ }
1758
+ }
1759
+ var activePort = 3210;
1760
+ function getActivePort() {
1761
+ return activePort;
1762
+ }
1763
+ function setActivePort(p) {
1764
+ activePort = p;
1765
+ }
1766
+ function sendJSON(res, data, status = 200) {
1767
+ const json = JSON.stringify(data);
1768
+ res.writeHead(status, {
1769
+ "Content-Type": "application/json; charset=utf-8",
1770
+ "Cache-Control": "no-cache, no-store, must-revalidate",
1771
+ "Pragma": "no-cache",
1772
+ "Expires": "0",
1773
+ "Access-Control-Allow-Origin": `http://localhost:${activePort}`,
1774
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1775
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
1776
+ });
1777
+ res.end(json);
1778
+ }
1779
+ function sendError(res, message, status = 500) {
1780
+ sendJSON(res, { error: message }, status);
1781
+ }
1782
+ function hasWorkspaceProfile() {
1783
+ const profilePath = path.join(HYPERCORE_DIR, "workspace-profile.json");
1784
+ if (!fs.existsSync(profilePath)) return false;
1785
+ try {
1786
+ const content = fs.readFileSync(profilePath, "utf-8");
1787
+ const parsed = JSON.parse(content);
1788
+ return !!(parsed && parsed.id);
1789
+ } catch {
1790
+ return false;
1791
+ }
1792
+ }
1793
+ function serveStatic(res, urlPath) {
1794
+ let filePath;
1795
+ if (urlPath === "/onboard") {
1796
+ filePath = "/onboard.html";
1797
+ } else if (urlPath === "/" || urlPath === "/workspace") {
1798
+ const replActive = !!getREPLContext();
1799
+ if (replActive) {
1800
+ filePath = "/workspace.html";
1801
+ } else if (hasWorkspaceProfile()) {
1802
+ filePath = "/index.html";
1803
+ } else {
1804
+ res.writeHead(302, { Location: "/onboard" });
1805
+ res.end();
1806
+ return;
1807
+ }
1808
+ } else if (urlPath === "/dashboard") {
1809
+ filePath = "/index.html";
1810
+ } else if (urlPath === "/mirror") {
1811
+ filePath = "/mirror.html";
1812
+ } else {
1813
+ filePath = urlPath;
1814
+ }
1815
+ const fullPath = path.resolve(STATIC_DIR, filePath.replace(/^\//, ""));
1816
+ if (!fullPath.startsWith(STATIC_DIR + path.sep) && fullPath !== STATIC_DIR) {
1817
+ sendError(res, "Forbidden", 403);
1818
+ return;
1819
+ }
1820
+ const ext = path.extname(fullPath);
1821
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
1822
+ fs.readFile(fullPath, (err, data) => {
1823
+ if (err) {
1824
+ sendError(res, "Not Found", 404);
1825
+ return;
1826
+ }
1827
+ res.writeHead(200, {
1828
+ "Content-Type": mimeType,
1829
+ // Prevent browser caching to ensure fresh JS/CSS/HTML on every load
1830
+ "Cache-Control": "no-cache, no-store, must-revalidate",
1831
+ "Pragma": "no-cache",
1832
+ "Expires": "0"
1833
+ });
1834
+ res.end(data);
1835
+ });
1836
+ }
1837
+ function buildWebServer(config, host = "127.0.0.1") {
1838
+ const lanTeamMode = isLanTeamMode(host);
1839
+ const server = http.createServer(async (req, res) => {
1840
+ const url = new URL(req.url || "/", `http://localhost:${activePort}`);
1841
+ const pathname = url.pathname;
1842
+ if (req.method === "OPTIONS") {
1843
+ res.writeHead(204, {
1844
+ "Access-Control-Allow-Origin": `http://localhost:${activePort}`,
1845
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1846
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
1847
+ "Access-Control-Max-Age": "86400"
1848
+ });
1849
+ res.end();
1850
+ return;
1851
+ }
1852
+ if (pathname.startsWith("/api/") && !canAccessApiPath(pathname, isLocalRequest(req), lanTeamMode)) {
1853
+ sendError(res, "Forbidden: API only accessible from localhost", 403);
1854
+ return;
1855
+ }
1856
+ try {
1857
+ if (pathname === "/api/chat" && req.method === "POST") {
1858
+ await handleChatSSE(req, res, config);
1859
+ return;
1860
+ }
1861
+ if (pathname === "/api/run" && req.method === "POST") {
1862
+ await handleRunSSE(req, res, config);
1863
+ return;
1864
+ }
1865
+ if (pathname.startsWith("/api/network/")) {
1866
+ const { handleNetworkApiRequest } = await import("./api-XGC7D5AW.js");
1867
+ await handleNetworkApiRequest(req, res, config, pathname);
1868
+ return;
1869
+ }
1870
+ if (pathname.startsWith("/api/team/") && pathname !== "/api/team/local-token") {
1871
+ await handleTeamApiRequest(req, res, config, pathname);
1872
+ return;
1873
+ }
1874
+ if (pathname.startsWith("/api/")) {
1875
+ await handleApiRequest(req, res, config, pathname);
1876
+ return;
1877
+ }
1878
+ serveStatic(res, pathname);
1879
+ } catch (err) {
1880
+ console.error("[Server Error]", err);
1881
+ sendError(res, "Internal Server Error", 500);
1882
+ }
1883
+ });
1884
+ installUpgradeRouter(server, host);
1885
+ const teamWss = attachWebSocket(server, config);
1886
+ registerWSRoute("/ws/team", teamWss);
1887
+ const replWss = attachREPLBridge(server);
1888
+ registerWSRoute("/ws/repl", replWss);
1889
+ return server;
1890
+ }
1891
+ function createWebServer(config, port, host = "127.0.0.1") {
1892
+ const server = buildWebServer(config, host);
1893
+ const browserHost = resolveBrowserHost(host);
1894
+ server.listen(port, host, () => {
1895
+ setActivePort(port);
1896
+ import("./test-runner-ZQH5Y6OJ.js").then(({ getAutoRegressionSnapshot }) => {
1897
+ getAutoRegressionSnapshot({
1898
+ projectRoot: process.cwd(),
1899
+ baseUrl: `http://127.0.0.1:${port}`
1900
+ }).catch(() => {
1901
+ });
1902
+ }).catch(() => {
1903
+ });
1904
+ const replActive = !!getREPLContext();
1905
+ if (!replActive) {
1906
+ const httpUrl = `http://${browserHost}:${port}`;
1907
+ console.log(`
1908
+ \x1B[36mHypercore Dashboard\x1B[0m \x1B[4m${httpUrl}\x1B[0m`);
1909
+ console.log(` \x1B[90mPress Ctrl+C to stop\x1B[0m
1910
+ `);
1911
+ }
1912
+ });
1913
+ return server;
1914
+ }
1915
+ function createWebServerAutoPort(config, startPort = 3210, host = "127.0.0.1", maxAttempts = 10, silent = false) {
1916
+ return new Promise((resolve2, reject) => {
1917
+ let attempt = 0;
1918
+ function tryPort(port) {
1919
+ const server = buildWebServer(config, host);
1920
+ const browserHost = resolveBrowserHost(host);
1921
+ const onError = (err) => {
1922
+ if (err.code === "EADDRINUSE") {
1923
+ server.removeListener("error", onError);
1924
+ server.close();
1925
+ attempt++;
1926
+ if (attempt < maxAttempts) {
1927
+ tryPort(port + 1);
1928
+ } else {
1929
+ reject(new Error(`\u7AEF\u53E3 ${startPort}-${startPort + maxAttempts - 1} \u5168\u90E8\u88AB\u5360\u7528`));
1930
+ }
1931
+ } else {
1932
+ reject(err);
1933
+ }
1934
+ };
1935
+ server.on("error", onError);
1936
+ server.listen(port, host, () => {
1937
+ server.removeListener("error", onError);
1938
+ setActivePort(port);
1939
+ import("./telemetry-6R4EIE6O.js").then(({ trackWebStart }) => {
1940
+ trackWebStart(host, port, host === "0.0.0.0" || host === "::");
1941
+ }).catch(() => {
1942
+ });
1943
+ import("./test-runner-ZQH5Y6OJ.js").then(({ getAutoRegressionSnapshot }) => {
1944
+ getAutoRegressionSnapshot({
1945
+ projectRoot: process.cwd(),
1946
+ baseUrl: `http://127.0.0.1:${port}`
1947
+ }).catch(() => {
1948
+ });
1949
+ }).catch(() => {
1950
+ });
1951
+ if (!silent) {
1952
+ const replActive = !!getREPLContext();
1953
+ if (!replActive) {
1954
+ const httpUrl = `http://${browserHost}:${port}`;
1955
+ console.log(`
1956
+ \x1B[36mHypercore Dashboard\x1B[0m \x1B[4m${httpUrl}\x1B[0m`);
1957
+ console.log(` \x1B[90mPress Ctrl+C to stop\x1B[0m
1958
+ `);
1959
+ }
1960
+ }
1961
+ resolve2({ server, port });
1962
+ });
1963
+ }
1964
+ tryPort(startPort);
1965
+ });
1966
+ }
1967
+ if (process.argv[1] && process.argv[1].includes("web/server")) {
1968
+ const { loadConfig: loadConfig2 } = await import("./config-4EW42BSF.js");
1969
+ const config = await loadConfig2();
1970
+ const port = parseInt(process.env.PORT || "3210", 10);
1971
+ createWebServer(config, port);
1972
+ }
1973
+
1974
+ export {
1975
+ isGitRepo,
1976
+ gitBranch,
1977
+ gitStatus,
1978
+ gitStatusSummary,
1979
+ gitDiffStaged,
1980
+ gitDiffStagedStat,
1981
+ gitDiffUnstaged,
1982
+ gitLog,
1983
+ gitDiffFiles,
1984
+ gitDiffRange,
1985
+ gitStageFiles,
1986
+ gitCommit,
1987
+ gitCreateBranch,
1988
+ gitPushUpstream,
1989
+ gitDefaultBranch,
1990
+ gitListBranches,
1991
+ gitSwitchBranch,
1992
+ gitDeleteBranch,
1993
+ gitStash,
1994
+ gitStashPop,
1995
+ gitStashList,
1996
+ gitStashDrop,
1997
+ gitDiffFile,
1998
+ gitWorktreeAdd,
1999
+ gitWorktreeList,
2000
+ gitWorktreeRemove,
2001
+ emitToGUI,
2002
+ registerREPLContext,
2003
+ createMultiplexer,
2004
+ hasGUIClients,
2005
+ registerWSRoute,
2006
+ parseHostnameFromHostHeader,
2007
+ isLocalHostname,
2008
+ isLanTeamMode,
2009
+ isLanAllowedApiPath,
2010
+ canAccessApiPath,
2011
+ canAccessWebSocketPath,
2012
+ resolveBrowserHost,
2013
+ parseBody,
2014
+ isLocalRequest,
2015
+ isLocalOrigin,
2016
+ getActivePort,
2017
+ setActivePort,
2018
+ sendJSON,
2019
+ sendError,
2020
+ buildWebServer,
2021
+ createWebServer,
2022
+ createWebServerAutoPort
2023
+ };