@launchsecure/launch-kit 0.0.17 → 0.0.19

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 (78) hide show
  1. package/dist/chart-client/assets/index--120d9P9.css +1 -0
  2. package/dist/chart-client/assets/index-C8ANseEa.js +441 -0
  3. package/dist/chart-client/index.html +2 -2
  4. package/dist/client/assets/index-Bf8zdL3x.css +32 -0
  5. package/dist/client/assets/index-Ds9UP_cj.js +291 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/council-client/assets/index-CofZh7pS.css +1 -0
  8. package/dist/council-client/assets/index-Dc41S-R2.js +198 -0
  9. package/dist/council-client/index.html +21 -0
  10. package/dist/deck-client/assets/{_baseUniq-BbqvoK-V.js → _baseUniq-DsfOm3t_.js} +1 -1
  11. package/dist/deck-client/assets/{arc-CMtYsIZt.js → arc-NJuvkBv1.js} +1 -1
  12. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BEN5hESa.js → architectureDiagram-Q4EWVU46-BgrcgZs0.js} +1 -1
  13. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BV4CZ6k8.js → blockDiagram-DXYQGD6D-C3XoLi15.js} +1 -1
  14. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-fLcBXqdD.js → c4Diagram-AHTNJAMY-FX2PjLfb.js} +1 -1
  15. package/dist/deck-client/assets/channel-ChQjD1T1.js +1 -0
  16. package/dist/deck-client/assets/{chunk-4BX2VUAB-BO_19zwB.js → chunk-4BX2VUAB-D0aqsJV0.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-4TB4RGXK-iYegd5fu.js → chunk-4TB4RGXK-7qRCCAgK.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-55IACEB6-DM3QwYFL.js → chunk-55IACEB6-DfHG-iqb.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-EDXVE4YY-DGznOul1.js → chunk-EDXVE4YY-DrR52j3B.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-FMBD7UC4-DsANJqYW.js → chunk-FMBD7UC4-D5KSGATB.js} +1 -1
  21. package/dist/deck-client/assets/{chunk-OYMX7WX6-6PGH1F7d.js → chunk-OYMX7WX6-M7hsLRNU.js} +1 -1
  22. package/dist/deck-client/assets/{chunk-QZHKN3VN-Dihf0Uq7.js → chunk-QZHKN3VN-1ynAWO2m.js} +1 -1
  23. package/dist/deck-client/assets/{chunk-YZCP3GAM-Cali2wW5.js → chunk-YZCP3GAM-S2-nGw3D.js} +1 -1
  24. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-B_9iqK1S.js +1 -0
  25. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-B_9iqK1S.js +1 -0
  26. package/dist/deck-client/assets/clone-BYt1AMfz.js +1 -0
  27. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-DsRY4vbI.js → cose-bilkent-S5V4N54A-BcMwozS2.js} +1 -1
  28. package/dist/deck-client/assets/cytoscape.esm-BQk4lpUV.js +331 -0
  29. package/dist/deck-client/assets/{dagre-KV5264BT-DJIKE_pI.js → dagre-KV5264BT-DtKMhl_1.js} +1 -1
  30. package/dist/deck-client/assets/{diagram-5BDNPKRD-Ckgli1SP.js → diagram-5BDNPKRD-1plH69us.js} +1 -1
  31. package/dist/deck-client/assets/{diagram-G4DWMVQ6-CozcDzae.js → diagram-G4DWMVQ6-D_o-BHO3.js} +1 -1
  32. package/dist/deck-client/assets/{diagram-MMDJMWI5-xVSwW3f_.js → diagram-MMDJMWI5-ClZ1LIx6.js} +1 -1
  33. package/dist/deck-client/assets/{diagram-TYMM5635-CJeZVY-P.js → diagram-TYMM5635-B8dKHfRh.js} +1 -1
  34. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-j4wjAERH.js → erDiagram-SMLLAGMA-CY2aCH7-.js} +1 -1
  35. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-CVLZ1efi.js → flowDiagram-DWJPFMVM-DZZWHti8.js} +1 -1
  36. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-CcIJ7pkP.js → ganttDiagram-T4ZO3ILL-OwGGa6Lu.js} +1 -1
  37. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-BZRhQX-a.js → gitGraphDiagram-UUTBAWPF-GKyWD4Qt.js} +1 -1
  38. package/dist/deck-client/assets/{graph-D0l25xfo.js → graph-CORzYQdB.js} +1 -1
  39. package/dist/deck-client/assets/index-765AIQ9z.css +1 -0
  40. package/dist/deck-client/assets/{index-BXcoHWVM.js → index-hiIpM7EP.js} +93 -93
  41. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-BLwgxnYT.js → infoDiagram-42DDH7IO-DmgqJCcF.js} +1 -1
  42. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BfOLoWv3.js → ishikawaDiagram-UXIWVN3A-D-1v7knu.js} +1 -1
  43. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-CPuL6C9h.js → journeyDiagram-VCZTEJTY-CYrGQE7b.js} +1 -1
  44. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-D3uf7_tx.js → kanban-definition-6JOO6SKY-BJFDWiH-.js} +1 -1
  45. package/dist/deck-client/assets/{layout-CzToiXdK.js → layout-BTFFcaxF.js} +1 -1
  46. package/dist/deck-client/assets/{linear-BU36t460.js → linear-DAbl6COS.js} +1 -1
  47. package/dist/deck-client/assets/{min-DX_q-lqP.js → min-oWHBrFBm.js} +1 -1
  48. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-Ccty4O16.js → mindmap-definition-QFDTVHPH-BTCB0VLO.js} +1 -1
  49. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DVjsvH19.js → pieDiagram-DEJITSTG-CUZChWNA.js} +1 -1
  50. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DtOXFVW9.js → quadrantDiagram-34T5L4WZ-4M1Um_e4.js} +1 -1
  51. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BbO_kKg6.js → requirementDiagram-MS252O5E-DLzQZ0B3.js} +1 -1
  52. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-qbbj-CmC.js → sankeyDiagram-XADWPNL6-DcNgzV3E.js} +1 -1
  53. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-JNKZAgfQ.js → sequenceDiagram-FGHM5R23-CAcI2vC9.js} +1 -1
  54. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-dtFalcNx.js → stateDiagram-FHFEXIEX-CntjTTm5.js} +1 -1
  55. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-YiaphOU_.js +1 -0
  56. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-Dpp5nqSJ.js → timeline-definition-GMOUNBTQ-D8zrit4U.js} +1 -1
  57. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D8qEutX7.js → vennDiagram-DHZGUBPP-C4SuFPgo.js} +1 -1
  58. package/dist/deck-client/assets/{wardley-RL74JXVD-BwMqiNcL.js → wardley-RL74JXVD-B3F-Olcq.js} +58 -58
  59. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-Bxl9X3CK.js → wardleyDiagram-NUSXRM2D-kj73r6f-.js} +1 -1
  60. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-DYcvxLhi.js → xychartDiagram-5P7HB3ND-CC_d_Ey3.js} +1 -1
  61. package/dist/deck-client/index.html +2 -2
  62. package/dist/server/chart-serve.js +452 -244
  63. package/dist/server/cli.js +558 -750
  64. package/dist/server/council-entry.js +1418 -0
  65. package/dist/server/council-serve.js +1039 -0
  66. package/dist/server/graph-mcp-entry.js +486 -695
  67. package/package.json +10 -9
  68. package/dist/chart-client/assets/index-BUhuLBaw.js +0 -441
  69. package/dist/chart-client/assets/index-CWRZxjqR.css +0 -1
  70. package/dist/client/assets/index-CAAipH3V.js +0 -291
  71. package/dist/client/assets/index-DtbN793z.css +0 -32
  72. package/dist/deck-client/assets/channel-Nf-B3Qor.js +0 -1
  73. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-3i3-miMR.js +0 -1
  74. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-3i3-miMR.js +0 -1
  75. package/dist/deck-client/assets/clone-DXBuQlG8.js +0 -1
  76. package/dist/deck-client/assets/cytoscape.esm-BiciSPf8.js +0 -331
  77. package/dist/deck-client/assets/index-Cdh-f3-c.css +0 -1
  78. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CviYYulW.js +0 -1
@@ -0,0 +1,1039 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/server/adapters/claude-terminal.ts
34
+ var claude_terminal_exports = {};
35
+ __export(claude_terminal_exports, {
36
+ ClaudeTerminalAdapter: () => ClaudeTerminalAdapter
37
+ });
38
+ var import_node_child_process2, ClaudeTerminalAdapter;
39
+ var init_claude_terminal = __esm({
40
+ "src/server/adapters/claude-terminal.ts"() {
41
+ "use strict";
42
+ import_node_child_process2 = require("node:child_process");
43
+ ClaudeTerminalAdapter = class {
44
+ constructor(model) {
45
+ this.model = model ?? "sonnet";
46
+ }
47
+ complete(systemPrompt, userPrompt) {
48
+ return new Promise((resolve, reject) => {
49
+ const combinedPrompt = `<INSTRUCTIONS>
50
+ ${systemPrompt}
51
+ </INSTRUCTIONS>
52
+
53
+ ${userPrompt}`;
54
+ const args = [
55
+ "-p",
56
+ "-",
57
+ "--model",
58
+ this.model,
59
+ "--output-format",
60
+ "text"
61
+ ];
62
+ const proc = (0, import_node_child_process2.spawn)("claude", args, {
63
+ stdio: ["pipe", "pipe", "pipe"],
64
+ timeout: 18e4
65
+ });
66
+ proc.stdin.write(combinedPrompt);
67
+ proc.stdin.end();
68
+ let stdout = "";
69
+ let stderr = "";
70
+ proc.stdout.on("data", (chunk) => {
71
+ stdout += chunk.toString();
72
+ });
73
+ proc.stderr.on("data", (chunk) => {
74
+ stderr += chunk.toString();
75
+ });
76
+ proc.on("close", (code) => {
77
+ if (code === 0) {
78
+ resolve(stdout.trim());
79
+ } else {
80
+ reject(new Error(`claude exited with code ${code}: ${stderr.trim()}`));
81
+ }
82
+ });
83
+ proc.on("error", (err) => {
84
+ reject(new Error(`Failed to spawn claude: ${err.message}`));
85
+ });
86
+ });
87
+ }
88
+ };
89
+ }
90
+ });
91
+
92
+ // src/server/adapters/openrouter.ts
93
+ var openrouter_exports = {};
94
+ __export(openrouter_exports, {
95
+ OpenRouterAdapter: () => OpenRouterAdapter
96
+ });
97
+ var OpenRouterAdapter;
98
+ var init_openrouter = __esm({
99
+ "src/server/adapters/openrouter.ts"() {
100
+ "use strict";
101
+ OpenRouterAdapter = class {
102
+ constructor(config) {
103
+ this.apiKey = config.apiKey;
104
+ this.model = config.model;
105
+ this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
106
+ }
107
+ async complete(systemPrompt, userPrompt) {
108
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json",
112
+ "Authorization": `Bearer ${this.apiKey}`,
113
+ "HTTP-Referer": "https://automatewith.us",
114
+ "X-Title": "LaunchCouncil"
115
+ },
116
+ body: JSON.stringify({
117
+ model: this.model,
118
+ messages: [
119
+ { role: "system", content: systemPrompt },
120
+ { role: "user", content: userPrompt }
121
+ ],
122
+ max_tokens: 4096
123
+ })
124
+ });
125
+ if (!response.ok) {
126
+ const text = await response.text();
127
+ throw new Error(`OpenRouter ${response.status}: ${text}`);
128
+ }
129
+ const data = await response.json();
130
+ const content = data.choices?.[0]?.message?.content;
131
+ if (!content) throw new Error("Empty response from OpenRouter");
132
+ return content;
133
+ }
134
+ };
135
+ }
136
+ });
137
+
138
+ // src/server/council-serve.ts
139
+ var council_serve_exports = {};
140
+ __export(council_serve_exports, {
141
+ runServeCli: () => runServeCli,
142
+ startCouncilServer: () => startCouncilServer
143
+ });
144
+ module.exports = __toCommonJS(council_serve_exports);
145
+ var import_node_http = __toESM(require("node:http"));
146
+ var import_node_fs5 = __toESM(require("node:fs"));
147
+ var import_node_path5 = __toESM(require("node:path"));
148
+ var import_ws = require("ws");
149
+
150
+ // src/server/council-lockfile.ts
151
+ var import_node_child_process = require("node:child_process");
152
+ var import_node_fs = require("node:fs");
153
+ var import_node_os = require("node:os");
154
+ var import_node_path = require("node:path");
155
+ function lockDir(projectRoot) {
156
+ return projectRoot ? (0, import_node_path.join)(projectRoot, ".launchsecure") : (0, import_node_path.join)((0, import_node_os.homedir)(), ".launchsecure");
157
+ }
158
+ function lockPath(projectRoot) {
159
+ return (0, import_node_path.join)(lockDir(projectRoot), "launch-council.lock");
160
+ }
161
+ var _activeProjectRoot;
162
+ function readLock(projectRoot) {
163
+ const root = projectRoot ?? _activeProjectRoot;
164
+ const p = lockPath(root);
165
+ if (!(0, import_node_fs.existsSync)(p)) return null;
166
+ try {
167
+ const data = JSON.parse((0, import_node_fs.readFileSync)(p, "utf-8"));
168
+ if (typeof data.pid !== "number" || typeof data.port !== "number") return null;
169
+ return data;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ function isPidAlive(pid) {
175
+ try {
176
+ process.kill(pid, 0);
177
+ return true;
178
+ } catch {
179
+ return false;
180
+ }
181
+ }
182
+ function getListenerPid(port) {
183
+ try {
184
+ const out = (0, import_node_child_process.execFileSync)("lsof", ["-nP", "-iTCP:" + port, "-sTCP:LISTEN", "-t"], {
185
+ encoding: "utf-8",
186
+ stdio: ["ignore", "pipe", "ignore"],
187
+ timeout: 500
188
+ }).trim();
189
+ if (!out) return null;
190
+ const pid = parseInt(out.split("\n")[0], 10);
191
+ return Number.isFinite(pid) ? pid : null;
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+ function getLiveLock(projectRoot) {
197
+ const root = projectRoot ?? _activeProjectRoot;
198
+ const lock = readLock(root);
199
+ if (!lock) return null;
200
+ const listenerPid = getListenerPid(lock.port);
201
+ const live = listenerPid !== null ? listenerPid === lock.pid : isPidAlive(lock.pid);
202
+ if (!live) {
203
+ try {
204
+ (0, import_node_fs.unlinkSync)(lockPath(root));
205
+ } catch {
206
+ }
207
+ return null;
208
+ }
209
+ return lock;
210
+ }
211
+ function writeLock(data, projectRoot) {
212
+ const root = projectRoot ?? _activeProjectRoot;
213
+ (0, import_node_fs.mkdirSync)(lockDir(root), { recursive: true });
214
+ (0, import_node_fs.writeFileSync)(lockPath(root), JSON.stringify(data, null, 2) + "\n", "utf-8");
215
+ if (root) _activeProjectRoot = root;
216
+ }
217
+ function clearLock(projectRoot) {
218
+ const root = projectRoot ?? _activeProjectRoot;
219
+ try {
220
+ (0, import_node_fs.unlinkSync)(lockPath(root));
221
+ } catch {
222
+ }
223
+ }
224
+
225
+ // src/server/council-config.ts
226
+ var import_node_fs2 = require("node:fs");
227
+ var import_node_path2 = require("node:path");
228
+ var CONFIG_FILENAME = ".launchcouncil.json";
229
+ function loadCouncilConfig(rootDir) {
230
+ const configPath = (0, import_node_path2.join)(rootDir, CONFIG_FILENAME);
231
+ if (!(0, import_node_fs2.existsSync)(configPath)) return {};
232
+ try {
233
+ return JSON.parse((0, import_node_fs2.readFileSync)(configPath, "utf-8"));
234
+ } catch {
235
+ return {};
236
+ }
237
+ }
238
+ function saveCouncilConfig(rootDir, config) {
239
+ const configPath = (0, import_node_path2.join)(rootDir, CONFIG_FILENAME);
240
+ (0, import_node_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
241
+ }
242
+
243
+ // src/server/mcp-client.ts
244
+ var import_node_fs3 = require("node:fs");
245
+ var import_node_path3 = require("node:path");
246
+ var _config = null;
247
+ var _requestId = 0;
248
+ var _sessionId = null;
249
+ function loadMcpConfig(projectRoot) {
250
+ if (_config) return _config;
251
+ const mcpPath = (0, import_node_path3.join)(projectRoot, ".mcp.json");
252
+ if (!(0, import_node_fs3.existsSync)(mcpPath)) {
253
+ throw new Error(`.mcp.json not found at ${mcpPath}`);
254
+ }
255
+ const raw = JSON.parse((0, import_node_fs3.readFileSync)(mcpPath, "utf-8"));
256
+ const entry = raw.mcpServers["launch-secure"];
257
+ if (!entry?.url) {
258
+ throw new Error('No "launch-secure" entry with url found in .mcp.json');
259
+ }
260
+ _config = {
261
+ url: entry.url,
262
+ headers: entry.headers ?? {}
263
+ };
264
+ return _config;
265
+ }
266
+ function parseSSE(text) {
267
+ const lines = text.split("\n");
268
+ for (const line of lines) {
269
+ if (line.startsWith("data: ")) {
270
+ const data = line.slice(6);
271
+ try {
272
+ return JSON.parse(data);
273
+ } catch {
274
+ }
275
+ }
276
+ }
277
+ return JSON.parse(text);
278
+ }
279
+ async function mcpRequest(method, params) {
280
+ const config = _config;
281
+ if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
282
+ const id = ++_requestId;
283
+ const headers = {
284
+ ...config.headers,
285
+ "Content-Type": "application/json",
286
+ "Accept": "application/json, text/event-stream"
287
+ };
288
+ if (_sessionId) {
289
+ headers["Mcp-Session-Id"] = _sessionId;
290
+ }
291
+ const response = await fetch(config.url, {
292
+ method: "POST",
293
+ headers,
294
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
295
+ });
296
+ const sid = response.headers.get("mcp-session-id");
297
+ if (sid) _sessionId = sid;
298
+ if (!response.ok) {
299
+ const text = await response.text();
300
+ throw new Error(`MCP ${method} failed (${response.status}): ${text}`);
301
+ }
302
+ const contentType = response.headers.get("content-type") ?? "";
303
+ const body = await response.text();
304
+ if (contentType.includes("text/event-stream")) {
305
+ return parseSSE(body);
306
+ }
307
+ return JSON.parse(body);
308
+ }
309
+ async function ensureInitialized() {
310
+ if (_sessionId) return;
311
+ await mcpRequest("initialize", {
312
+ protocolVersion: "2024-11-05",
313
+ capabilities: {},
314
+ clientInfo: { name: "launch-council", version: "0.0.1" }
315
+ });
316
+ const config = _config;
317
+ const headers = {
318
+ ...config.headers,
319
+ "Content-Type": "application/json",
320
+ "Accept": "application/json, text/event-stream"
321
+ };
322
+ if (_sessionId) headers["Mcp-Session-Id"] = _sessionId;
323
+ await fetch(config.url, {
324
+ method: "POST",
325
+ headers,
326
+ body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
327
+ });
328
+ }
329
+ async function callTool(toolName, args) {
330
+ await ensureInitialized();
331
+ const result = await mcpRequest("tools/call", {
332
+ name: toolName,
333
+ arguments: args
334
+ });
335
+ if (result.error) {
336
+ throw new Error(`MCP tool error: ${result.error.message}`);
337
+ }
338
+ const textContent = result.result?.content?.[0]?.text;
339
+ if (!textContent) return null;
340
+ return JSON.parse(textContent);
341
+ }
342
+ async function readDiscussion(discussionId) {
343
+ const result = await callTool("communication_read", {
344
+ resource_type: "discussion",
345
+ limit: 50
346
+ });
347
+ if (!result?.comments) return null;
348
+ const discussion = result.comments.find((c) => c.id === discussionId);
349
+ return discussion ?? null;
350
+ }
351
+ async function listDiscussions() {
352
+ const result = await callTool("communication_read", {
353
+ resource_type: "discussion",
354
+ limit: 50
355
+ });
356
+ return result?.comments ?? [];
357
+ }
358
+ async function writeReply(parentId, body, title) {
359
+ const result = await callTool("communication_write", {
360
+ body,
361
+ title: title ?? void 0,
362
+ parent_id: parentId,
363
+ resource_type: "comment"
364
+ });
365
+ return { id: result.created.id };
366
+ }
367
+
368
+ // src/server/personas.ts
369
+ var COMMON_INSTRUCTIONS = `
370
+
371
+ You are participating in a council discussion about a software project decision. You will be given:
372
+ 1. The discussion topic and any replies so far
373
+ 2. Relevant code context from the project
374
+
375
+ Be precise in answering and no explanation needed. State your position in 2-3 sentences max. No preamble, no lengthy analysis. Direct and to the point. Under 100 words.`;
376
+ var PRESET_PERSONAS = [
377
+ {
378
+ id: "frontend-lead",
379
+ name: "Frontend Lead",
380
+ style: "UX-first, component architecture, performance",
381
+ isPreset: true,
382
+ systemPrompt: `You are a senior Frontend Lead with deep expertise in React, TypeScript, component architecture, and user experience. You prioritize:
383
+ - Clean component boundaries and reusability
384
+ - User experience and interaction design
385
+ - Performance (bundle size, render cycles, lazy loading)
386
+ - Accessibility and responsive design
387
+ - State management clarity
388
+
389
+ When evaluating proposals, you think about how they affect the UI layer, whether components stay composable, and if the user experience degrades or improves.${COMMON_INSTRUCTIONS}`
390
+ },
391
+ {
392
+ id: "backend-lead",
393
+ name: "Backend Lead",
394
+ style: "API design, data flow, scalability",
395
+ isPreset: true,
396
+ systemPrompt: `You are a senior Backend Lead with deep expertise in API design, server architecture, and distributed systems. You prioritize:
397
+ - Clean API contracts and versioning
398
+ - Data flow efficiency and caching strategy
399
+ - Error handling and resilience
400
+ - Scalability under load
401
+ - Security at the API boundary
402
+
403
+ When evaluating proposals, you think about server-side implications, API design, data consistency, and how changes affect the system's ability to scale.${COMMON_INSTRUCTIONS}`
404
+ },
405
+ {
406
+ id: "db-lead",
407
+ name: "Database Lead",
408
+ style: "Schema integrity, query performance, migrations",
409
+ isPreset: true,
410
+ systemPrompt: `You are a senior Database Lead with deep expertise in schema design, query optimization, and data integrity. You prioritize:
411
+ - Schema normalization and referential integrity
412
+ - Query performance and index strategy
413
+ - Migration safety (zero-downtime, reversible)
414
+ - Data modeling that serves both read and write patterns
415
+ - Avoiding N+1 queries and unnecessary joins
416
+
417
+ When evaluating proposals, you think about how changes affect the data model, whether migrations are safe, and if queries will perform well at scale.${COMMON_INSTRUCTIONS}`
418
+ },
419
+ {
420
+ id: "devils-advocate",
421
+ name: "Devil's Advocate",
422
+ style: "Challenges assumptions, finds holes",
423
+ isPreset: true,
424
+ systemPrompt: `You are the Devil's Advocate on this council. Your job is to stress-test ideas by actively challenging assumptions and finding weaknesses. You:
425
+ - Question the premise \u2014 is this even the right problem to solve?
426
+ - Find edge cases and failure modes others miss
427
+ - Challenge "obvious" solutions that might have hidden costs
428
+ - Ask what happens when things go wrong
429
+ - Point out what's being hand-waved or deferred
430
+
431
+ You are NOT negative for the sake of it. You genuinely want better outcomes, and you achieve that by making sure the group has considered the downsides before committing.${COMMON_INSTRUCTIONS}`
432
+ },
433
+ {
434
+ id: "pragmatist",
435
+ name: "Pragmatist",
436
+ style: "Ship-first, scope reduction, trade-offs",
437
+ isPreset: true,
438
+ systemPrompt: `You are the Pragmatist on this council. You focus on shipping value quickly and reducing scope to what matters. You prioritize:
439
+ - What's the simplest thing that works?
440
+ - Can we ship a smaller version first and iterate?
441
+ - What's the effort-to-impact ratio?
442
+ - Are we over-engineering for hypothetical future needs?
443
+ - What can we defer without blocking progress?
444
+
445
+ You respect quality but reject gold-plating. You look for the 80/20 solution and advocate for incremental delivery over big-bang releases.${COMMON_INSTRUCTIONS}`
446
+ },
447
+ {
448
+ id: "security-reviewer",
449
+ name: "Security Reviewer",
450
+ style: "Threat model, auth, input validation",
451
+ isPreset: true,
452
+ systemPrompt: `You are the Security Reviewer on this council. You evaluate proposals through a security lens. You focus on:
453
+ - Authentication and authorization gaps
454
+ - Input validation and injection risks (SQL, XSS, command injection)
455
+ - Data exposure and privacy concerns
456
+ - Secrets management and credential handling
457
+ - Attack surface changes \u2014 does this proposal increase exposure?
458
+
459
+ You think like an attacker: what would you exploit if this shipped as described? You provide specific, actionable security guidance, not generic warnings.${COMMON_INSTRUCTIONS}`
460
+ }
461
+ ];
462
+ var customPersonas = /* @__PURE__ */ new Map();
463
+ function getCustomPersonas() {
464
+ return Array.from(customPersonas.values());
465
+ }
466
+ function getAllPersonas() {
467
+ return [...PRESET_PERSONAS, ...getCustomPersonas()];
468
+ }
469
+ function getPersona(id) {
470
+ return PRESET_PERSONAS.find((p) => p.id === id) ?? customPersonas.get(id);
471
+ }
472
+ function addCustomPersona(name, style, systemPrompt) {
473
+ const id = `custom-${name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
474
+ const persona = {
475
+ id,
476
+ name,
477
+ style,
478
+ systemPrompt: systemPrompt + COMMON_INSTRUCTIONS,
479
+ isPreset: false
480
+ };
481
+ customPersonas.set(id, persona);
482
+ return persona;
483
+ }
484
+ function removeCustomPersona(id) {
485
+ return customPersonas.delete(id);
486
+ }
487
+ var personaAIConfigs = /* @__PURE__ */ new Map();
488
+ function setPersonaAIConfig(personaId, config) {
489
+ personaAIConfigs.set(personaId, config);
490
+ }
491
+ function getPersonaAIConfig(personaId) {
492
+ return personaAIConfigs.get(personaId);
493
+ }
494
+ function getAllPersonaAIConfigs() {
495
+ return Object.fromEntries(personaAIConfigs);
496
+ }
497
+
498
+ // src/server/intent.ts
499
+ var INTENT_SYSTEM_PROMPT = `You are a technical intent extractor. Given a discussion from a software project, you must:
500
+ 1. Summarize what the discussion is about in 1-2 sentences.
501
+ 2. Identify specific code areas that are relevant (component names, API endpoints, database tables, module names, feature names). These will be used as search terms against a code graph.
502
+ 3. Suggest which layers of the codebase are relevant: "ui" (frontend components, pages, hooks), "api" (backend endpoints, server logic), "db" (database tables, schema).
503
+
504
+ Respond in JSON only, no markdown fences:
505
+ {
506
+ "summary": "...",
507
+ "relevantAreas": ["term1", "term2", ...],
508
+ "suggestedLayers": ["ui", "api", "db"]
509
+ }
510
+
511
+ Be specific with search terms \u2014 use actual names that would appear in code (e.g. "auth", "login", "User", "pipeline", "dashboard"), not generic descriptions.`;
512
+ async function extractIntent(adapter, discussionContent) {
513
+ const response = await adapter.complete(INTENT_SYSTEM_PROMPT, discussionContent);
514
+ try {
515
+ const cleaned = response.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
516
+ const parsed = JSON.parse(cleaned);
517
+ return {
518
+ summary: parsed.summary ?? "Could not extract summary",
519
+ relevantAreas: Array.isArray(parsed.relevantAreas) ? parsed.relevantAreas : [],
520
+ suggestedLayers: Array.isArray(parsed.suggestedLayers) ? parsed.suggestedLayers.filter((l) => ["ui", "api", "db"].includes(l)) : ["ui", "api", "db"]
521
+ };
522
+ } catch {
523
+ return {
524
+ summary: response.slice(0, 200),
525
+ relevantAreas: [],
526
+ suggestedLayers: ["ui", "api", "db"]
527
+ };
528
+ }
529
+ }
530
+
531
+ // src/server/graph-reader.ts
532
+ var import_node_fs4 = require("node:fs");
533
+ var import_node_path4 = require("node:path");
534
+ var GRAPH_DIR = ".launchsecure/graphs";
535
+ function readGraphLayer(projectRoot, layer) {
536
+ const filePath = (0, import_node_path4.join)(projectRoot, GRAPH_DIR, `${layer}.json`);
537
+ if (!(0, import_node_fs4.existsSync)(filePath)) return null;
538
+ try {
539
+ return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
540
+ } catch {
541
+ return null;
542
+ }
543
+ }
544
+ function matchesSearch(node, terms) {
545
+ const haystack = [node.i, node.n, node.m, node.r].filter(Boolean).join(" ").toLowerCase();
546
+ return terms.some((term) => haystack.includes(term.toLowerCase()));
547
+ }
548
+ function formatNode(node, layer) {
549
+ const parts = [`[${layer}/${node.t}] ${node.n}`];
550
+ if (node.r) parts.push(`route: ${node.r}`);
551
+ if (node.mt?.length) parts.push(`methods: ${node.mt.join(", ")}`);
552
+ if (node.m) parts.push(`module: ${node.m}`);
553
+ parts.push(`file: ${node.i}`);
554
+ return parts.join(" | ");
555
+ }
556
+ function searchGraph(projectRoot, searchTerms, layers = ["ui", "api", "db"]) {
557
+ if (searchTerms.length === 0) {
558
+ return { summary: "No search terms provided \u2014 skipping graph context.", nodeCount: 0, layers: [] };
559
+ }
560
+ const results = [];
561
+ const usedLayers = [];
562
+ let totalNodes = 0;
563
+ for (const layer of layers) {
564
+ const graph = readGraphLayer(projectRoot, layer);
565
+ if (!graph) continue;
566
+ const matched = graph.nodes.filter((n) => matchesSearch(n, searchTerms));
567
+ if (matched.length === 0) continue;
568
+ usedLayers.push(layer);
569
+ totalNodes += matched.length;
570
+ results.push(`## ${layer.toUpperCase()} layer (${matched.length} nodes)`);
571
+ const capped = matched.slice(0, 30);
572
+ for (const node of capped) {
573
+ results.push(`- ${formatNode(node, layer)}`);
574
+ }
575
+ if (matched.length > 30) {
576
+ results.push(` ... and ${matched.length - 30} more`);
577
+ }
578
+ results.push("");
579
+ }
580
+ if (results.length === 0) {
581
+ return {
582
+ summary: `No graph nodes matched search terms: ${searchTerms.join(", ")}`,
583
+ nodeCount: 0,
584
+ layers: []
585
+ };
586
+ }
587
+ return {
588
+ summary: results.join("\n"),
589
+ nodeCount: totalNodes,
590
+ layers: usedLayers
591
+ };
592
+ }
593
+
594
+ // src/server/adapters/types.ts
595
+ function createAdapter(config) {
596
+ switch (config.provider) {
597
+ case "claude-terminal": {
598
+ const { ClaudeTerminalAdapter: ClaudeTerminalAdapter2 } = (init_claude_terminal(), __toCommonJS(claude_terminal_exports));
599
+ return new ClaudeTerminalAdapter2(config.claudeTerminal?.model);
600
+ }
601
+ case "openrouter": {
602
+ if (!config.openrouter) throw new Error("openrouter config required when provider is 'openrouter'");
603
+ const { OpenRouterAdapter: OpenRouterAdapter2 } = (init_openrouter(), __toCommonJS(openrouter_exports));
604
+ return new OpenRouterAdapter2(config.openrouter);
605
+ }
606
+ default:
607
+ throw new Error(`Unknown provider: ${config.provider}`);
608
+ }
609
+ }
610
+
611
+ // src/server/orchestrator.ts
612
+ var sessions = /* @__PURE__ */ new Map();
613
+ function getSession(id) {
614
+ return sessions.get(id);
615
+ }
616
+ function getAllSessions() {
617
+ return Array.from(sessions.values());
618
+ }
619
+ var listeners = [];
620
+ function onSessionUpdate(fn) {
621
+ listeners.push(fn);
622
+ return () => {
623
+ const idx = listeners.indexOf(fn);
624
+ if (idx >= 0) listeners.splice(idx, 1);
625
+ };
626
+ }
627
+ function emit(session) {
628
+ for (const fn of listeners) fn(session);
629
+ }
630
+ function buildUserPrompt(discussion, intent, graphContext) {
631
+ const parts = [];
632
+ parts.push("# Discussion");
633
+ if (discussion.title) parts.push(`**${discussion.title}**
634
+ `);
635
+ parts.push(discussion.content);
636
+ if (discussion.replies.length > 0) {
637
+ parts.push("\n## Existing Replies");
638
+ for (const reply of discussion.replies) {
639
+ parts.push(`**${reply.author.name ?? "Unknown"}**: ${reply.content}`);
640
+ }
641
+ }
642
+ parts.push(`
643
+ ## Discussion Summary
644
+ ${intent.summary}`);
645
+ if (graphContext.nodeCount > 0) {
646
+ parts.push(`
647
+ # Relevant Code Context
648
+ ${graphContext.summary}`);
649
+ }
650
+ parts.push("\n---\nGive your perspective on this discussion based on your role and expertise.");
651
+ return parts.join("\n");
652
+ }
653
+ async function summonCouncil(discussionId, personaIds, adapterConfig2, projectRoot) {
654
+ const sessionId = `council-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
655
+ const personas = [];
656
+ for (const id of personaIds) {
657
+ const p = getPersona(id);
658
+ if (!p) throw new Error(`Unknown persona: ${id}`);
659
+ personas.push(p);
660
+ }
661
+ const session = {
662
+ id: sessionId,
663
+ discussionId,
664
+ personaIds,
665
+ status: "pending",
666
+ step: "Initializing",
667
+ results: [],
668
+ startedAt: Date.now()
669
+ };
670
+ sessions.set(sessionId, session);
671
+ emit(session);
672
+ runCouncil(session, personas, adapterConfig2, projectRoot).catch((err) => {
673
+ session.status = "error";
674
+ session.error = String(err);
675
+ session.step = "Failed";
676
+ emit(session);
677
+ });
678
+ return sessionId;
679
+ }
680
+ async function runCouncil(session, personas, adapterConfig2, projectRoot) {
681
+ const adapter = createAdapter(adapterConfig2);
682
+ session.status = "reading";
683
+ session.step = "Reading discussion from LaunchSecure";
684
+ emit(session);
685
+ const discussion = await readDiscussion(session.discussionId);
686
+ if (!discussion) {
687
+ throw new Error(`Discussion ${session.discussionId} not found`);
688
+ }
689
+ session.status = "extracting";
690
+ session.step = "Extracting discussion intent";
691
+ emit(session);
692
+ const intent = await extractIntent(adapter, [
693
+ discussion.title ?? "",
694
+ discussion.content,
695
+ ...discussion.replies.map((r) => r.content)
696
+ ].join("\n\n"));
697
+ session.intent = intent;
698
+ emit(session);
699
+ session.status = "gathering";
700
+ session.step = `Searching code graph for: ${intent.relevantAreas.join(", ")}`;
701
+ emit(session);
702
+ const graphContext = searchGraph(projectRoot, intent.relevantAreas, intent.suggestedLayers);
703
+ session.graphContext = graphContext;
704
+ emit(session);
705
+ session.status = "deliberating";
706
+ session.step = `Council deliberating (${personas.length} personas)`;
707
+ emit(session);
708
+ const userPrompt = buildUserPrompt(discussion, intent, graphContext);
709
+ const pending = personas.map(async (persona) => {
710
+ const start = Date.now();
711
+ const personaOverride = getPersonaAIConfig(persona.id);
712
+ const personaAdapter = personaOverride?.provider ? createAdapter({
713
+ provider: personaOverride.provider,
714
+ claudeTerminal: personaOverride.provider === "claude-terminal" ? { model: personaOverride.model } : void 0,
715
+ openrouter: personaOverride.provider === "openrouter" ? { apiKey: adapterConfig2.openrouter?.apiKey ?? "", model: personaOverride.model ?? "anthropic/claude-sonnet-4" } : void 0
716
+ }) : personaOverride?.model ? createAdapter({ ...adapterConfig2, claudeTerminal: { model: personaOverride.model } }) : adapter;
717
+ let result;
718
+ try {
719
+ const response = await personaAdapter.complete(persona.systemPrompt, userPrompt);
720
+ result = { personaId: persona.id, personaName: persona.name, response, durationMs: Date.now() - start };
721
+ } catch (err) {
722
+ result = { personaId: persona.id, personaName: persona.name, response: "", error: String(err), durationMs: Date.now() - start };
723
+ }
724
+ session.results.push(result);
725
+ session.step = `Council deliberating (${session.results.length}/${personas.length} done)`;
726
+ emit(session);
727
+ });
728
+ await Promise.all(pending);
729
+ session.status = "done";
730
+ session.step = "Council complete";
731
+ session.completedAt = Date.now();
732
+ emit(session);
733
+ }
734
+
735
+ // src/server/council-serve.ts
736
+ var DEFAULT_PORT = 52839;
737
+ var MAX_PORT_SCAN = 3;
738
+ var MIME_TYPES = {
739
+ ".html": "text/html; charset=utf-8",
740
+ ".js": "application/javascript; charset=utf-8",
741
+ ".css": "text/css; charset=utf-8",
742
+ ".json": "application/json; charset=utf-8",
743
+ ".png": "image/png",
744
+ ".svg": "image/svg+xml",
745
+ ".ico": "image/x-icon"
746
+ };
747
+ var configRootDir = process.cwd();
748
+ function loadAdapterFromDisk() {
749
+ const fileConfig = loadCouncilConfig(configRootDir);
750
+ return fileConfig.adapter ?? { provider: "claude-terminal", claudeTerminal: { model: "sonnet" } };
751
+ }
752
+ var adapterConfig = {
753
+ provider: "claude-terminal",
754
+ claudeTerminal: { model: "sonnet" }
755
+ };
756
+ var wss = null;
757
+ function broadcastToClients(message) {
758
+ if (!wss) return;
759
+ const data = JSON.stringify(message);
760
+ for (const client of wss.clients) {
761
+ if (client.readyState === import_ws.WebSocket.OPEN) {
762
+ client.send(data);
763
+ }
764
+ }
765
+ }
766
+ function serveStatic(res, filePath) {
767
+ if (!import_node_fs5.default.existsSync(filePath) || !import_node_fs5.default.statSync(filePath).isFile()) return false;
768
+ const ext = import_node_path5.default.extname(filePath).toLowerCase();
769
+ const mime = MIME_TYPES[ext] ?? "application/octet-stream";
770
+ res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
771
+ import_node_fs5.default.createReadStream(filePath).pipe(res);
772
+ return true;
773
+ }
774
+ function serveIndex(res, clientDir) {
775
+ const indexPath = import_node_path5.default.join(clientDir, "index.html");
776
+ if (!import_node_fs5.default.existsSync(indexPath)) {
777
+ res.writeHead(500, { "Content-Type": "text/plain" });
778
+ res.end(`LaunchCouncil client bundle not found at ${clientDir}. Run 'npm run build:client'.`);
779
+ return;
780
+ }
781
+ serveStatic(res, indexPath);
782
+ }
783
+ function readBody(req) {
784
+ return new Promise((resolve) => {
785
+ let body = "";
786
+ req.on("data", (chunk) => {
787
+ body += chunk.toString();
788
+ });
789
+ req.on("end", () => resolve(body));
790
+ });
791
+ }
792
+ function jsonResponse(res, status, data) {
793
+ res.writeHead(status, { "Content-Type": "application/json" });
794
+ res.end(JSON.stringify(data));
795
+ }
796
+ function tryListen(server, port) {
797
+ return new Promise((resolve, reject) => {
798
+ const onError = (err) => {
799
+ server.off("listening", onListening);
800
+ reject(err);
801
+ };
802
+ const onListening = () => {
803
+ server.off("error", onError);
804
+ resolve(port);
805
+ };
806
+ server.once("error", onError);
807
+ server.once("listening", onListening);
808
+ server.listen(port, "127.0.0.1");
809
+ });
810
+ }
811
+ async function bindWithFallback(server, startPort) {
812
+ let lastErr = null;
813
+ for (let i = 0; i < MAX_PORT_SCAN; i++) {
814
+ try {
815
+ return await tryListen(server, startPort + i);
816
+ } catch (err) {
817
+ if (err.code === "EADDRINUSE") {
818
+ lastErr = err;
819
+ continue;
820
+ }
821
+ throw err;
822
+ }
823
+ }
824
+ throw lastErr ?? new Error("Failed to bind any port");
825
+ }
826
+ async function startCouncilServer(opts = {}) {
827
+ const cwd = opts.cwd ?? process.cwd();
828
+ const existing = getLiveLock(cwd);
829
+ if (existing) {
830
+ if (!opts.quiet) process.stderr.write(`[launch-council] already running (pid ${existing.pid}) at ${existing.url}
831
+ `);
832
+ return { port: existing.port, url: existing.url };
833
+ }
834
+ try {
835
+ loadMcpConfig(cwd);
836
+ } catch (err) {
837
+ process.stderr.write(`[launch-council] warning: ${err}
838
+ `);
839
+ }
840
+ const clientDir = opts.clientDir ?? import_node_path5.default.join(__dirname, "..", "council-client");
841
+ onSessionUpdate((session) => {
842
+ broadcastToClients({ type: "session_update", session });
843
+ });
844
+ const server = import_node_http.default.createServer(async (req, res) => {
845
+ try {
846
+ const url2 = new URL(req.url ?? "/", `http://${req.headers.host}`);
847
+ res.setHeader("Access-Control-Allow-Origin", "*");
848
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
849
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
850
+ if (req.method === "OPTIONS") {
851
+ res.writeHead(204);
852
+ res.end();
853
+ return;
854
+ }
855
+ if (req.method === "GET" && url2.pathname === "/api/health") {
856
+ jsonResponse(res, 200, { ok: true, tool: "launch-council" });
857
+ return;
858
+ }
859
+ if (req.method === "POST" && url2.pathname === "/api/council/summon") {
860
+ const body = JSON.parse(await readBody(req));
861
+ if (!body.discussionId || !body.personas?.length) {
862
+ jsonResponse(res, 400, { error: "discussionId and personas[] required" });
863
+ return;
864
+ }
865
+ try {
866
+ const config = body.adapterConfig ?? adapterConfig;
867
+ const sessionId = await summonCouncil(body.discussionId, body.personas, config, cwd);
868
+ jsonResponse(res, 200, { sessionId });
869
+ } catch (err) {
870
+ jsonResponse(res, 400, { error: String(err) });
871
+ }
872
+ return;
873
+ }
874
+ if (req.method === "GET" && url2.pathname.startsWith("/api/council/status/")) {
875
+ const sessionId = url2.pathname.slice("/api/council/status/".length);
876
+ const session = getSession(sessionId);
877
+ if (!session) {
878
+ jsonResponse(res, 404, { error: "Session not found" });
879
+ return;
880
+ }
881
+ jsonResponse(res, 200, session);
882
+ return;
883
+ }
884
+ if (req.method === "GET" && url2.pathname === "/api/council/sessions") {
885
+ jsonResponse(res, 200, { sessions: getAllSessions() });
886
+ return;
887
+ }
888
+ if (req.method === "GET" && url2.pathname === "/api/council/personas") {
889
+ jsonResponse(res, 200, { personas: getAllPersonas() });
890
+ return;
891
+ }
892
+ if (req.method === "POST" && url2.pathname === "/api/council/personas") {
893
+ const body = JSON.parse(await readBody(req));
894
+ if (!body.name || !body.systemPrompt) {
895
+ jsonResponse(res, 400, { error: "name and systemPrompt required" });
896
+ return;
897
+ }
898
+ const persona = addCustomPersona(body.name, body.style ?? "", body.systemPrompt);
899
+ jsonResponse(res, 200, { persona });
900
+ return;
901
+ }
902
+ if (req.method === "DELETE" && url2.pathname.startsWith("/api/council/personas/")) {
903
+ const id = url2.pathname.slice("/api/council/personas/".length);
904
+ const removed = removeCustomPersona(id);
905
+ jsonResponse(res, 200, { removed });
906
+ return;
907
+ }
908
+ if (req.method === "POST" && url2.pathname === "/api/council/persona-ai-config") {
909
+ const body = JSON.parse(await readBody(req));
910
+ if (!body.personaId) {
911
+ jsonResponse(res, 400, { error: "personaId required" });
912
+ return;
913
+ }
914
+ setPersonaAIConfig(body.personaId, body.config ?? {});
915
+ jsonResponse(res, 200, { ok: true, personaId: body.personaId, config: body.config });
916
+ return;
917
+ }
918
+ if (req.method === "GET" && url2.pathname === "/api/council/persona-ai-configs") {
919
+ jsonResponse(res, 200, { configs: getAllPersonaAIConfigs() });
920
+ return;
921
+ }
922
+ if (req.method === "GET" && url2.pathname === "/api/council/discussions") {
923
+ try {
924
+ const discussions = await listDiscussions();
925
+ jsonResponse(res, 200, { discussions });
926
+ } catch (err) {
927
+ jsonResponse(res, 500, { error: `Failed to fetch discussions: ${err}` });
928
+ }
929
+ return;
930
+ }
931
+ if (req.method === "POST" && url2.pathname === "/api/council/post-response") {
932
+ const body = JSON.parse(await readBody(req));
933
+ if (!body.sessionId || !body.personaId) {
934
+ jsonResponse(res, 400, { error: "sessionId and personaId required" });
935
+ return;
936
+ }
937
+ const session = getSession(body.sessionId);
938
+ if (!session) {
939
+ jsonResponse(res, 404, { error: "Session not found" });
940
+ return;
941
+ }
942
+ const result = session.results.find((r) => r.personaId === body.personaId);
943
+ if (!result || !result.response) {
944
+ jsonResponse(res, 400, { error: "No response found for this persona" });
945
+ return;
946
+ }
947
+ if (result.replyId) {
948
+ jsonResponse(res, 400, { error: "Already posted" });
949
+ return;
950
+ }
951
+ try {
952
+ const replyBody = `**[${result.personaName}]**
953
+
954
+ ${result.response}`;
955
+ const reply = await writeReply(session.discussionId, replyBody, `Council: ${result.personaName}`);
956
+ result.replyId = reply.id;
957
+ jsonResponse(res, 200, { ok: true, replyId: reply.id });
958
+ } catch (err) {
959
+ jsonResponse(res, 500, { error: `Failed to post: ${err}` });
960
+ }
961
+ return;
962
+ }
963
+ if (req.method === "GET" && url2.pathname === "/api/council/config") {
964
+ const safe = {
965
+ ...adapterConfig,
966
+ openrouter: adapterConfig.openrouter ? { ...adapterConfig.openrouter, apiKey: adapterConfig.openrouter.apiKey ? "***" : "" } : void 0
967
+ };
968
+ jsonResponse(res, 200, { config: safe });
969
+ return;
970
+ }
971
+ if (req.method === "POST" && url2.pathname === "/api/council/config") {
972
+ const body = JSON.parse(await readBody(req));
973
+ adapterConfig = { ...adapterConfig, ...body };
974
+ const existing2 = loadCouncilConfig(configRootDir);
975
+ existing2.adapter = adapterConfig;
976
+ saveCouncilConfig(configRootDir, existing2);
977
+ jsonResponse(res, 200, { ok: true, config: { ...adapterConfig, openrouter: adapterConfig.openrouter ? { ...adapterConfig.openrouter, apiKey: "***" } : void 0 } });
978
+ return;
979
+ }
980
+ if (url2.pathname !== "/") {
981
+ const staticPath = import_node_path5.default.join(clientDir, url2.pathname);
982
+ if (serveStatic(res, staticPath)) return;
983
+ }
984
+ serveIndex(res, clientDir);
985
+ } catch (err) {
986
+ jsonResponse(res, 500, { error: String(err) });
987
+ }
988
+ });
989
+ wss = new import_ws.WebSocketServer({ server });
990
+ wss.on("connection", (ws) => {
991
+ if (!opts.quiet) process.stderr.write("[launch-council] browser connected\n");
992
+ ws.on("close", () => {
993
+ if (!opts.quiet) process.stderr.write("[launch-council] browser disconnected\n");
994
+ });
995
+ });
996
+ configRootDir = cwd;
997
+ adapterConfig = loadAdapterFromDisk();
998
+ const councilConfig = loadCouncilConfig(cwd);
999
+ const startPort = opts.port ?? councilConfig.port ?? DEFAULT_PORT;
1000
+ const port = await bindWithFallback(server, startPort);
1001
+ const url = `http://localhost:${port}`;
1002
+ writeLock({ pid: process.pid, port, cwd, url, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, cwd);
1003
+ const cleanup = () => {
1004
+ clearLock(cwd);
1005
+ server.close();
1006
+ };
1007
+ process.once("SIGINT", () => {
1008
+ cleanup();
1009
+ process.exit(0);
1010
+ });
1011
+ process.once("SIGTERM", () => {
1012
+ cleanup();
1013
+ process.exit(0);
1014
+ });
1015
+ process.once("exit", cleanup);
1016
+ if (!opts.quiet) process.stderr.write(`[launch-council] serving ${url}
1017
+ `);
1018
+ return { port, url };
1019
+ }
1020
+ function runServeCli(argv) {
1021
+ let port;
1022
+ for (let i = 0; i < argv.length; i++) {
1023
+ if (argv[i] === "--port" && argv[i + 1]) {
1024
+ port = parseInt(argv[++i], 10);
1025
+ } else if (argv[i].startsWith("--port=")) {
1026
+ port = parseInt(argv[i].slice("--port=".length), 10);
1027
+ }
1028
+ }
1029
+ startCouncilServer({ port }).catch((err) => {
1030
+ process.stderr.write(`[launch-council] failed to start: ${err}
1031
+ `);
1032
+ process.exit(1);
1033
+ });
1034
+ }
1035
+ // Annotate the CommonJS export names for ESM import in node:
1036
+ 0 && (module.exports = {
1037
+ runServeCli,
1038
+ startCouncilServer
1039
+ });