atabey-mcp 0.0.19 → 0.0.21

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 (70) hide show
  1. package/README.md +2 -2
  2. package/dist/atabey/src/cli/platforms/core.d.ts +4 -0
  3. package/dist/atabey/src/cli/platforms/core.js +12 -8
  4. package/dist/atabey/src/cli/platforms/core.js.map +1 -1
  5. package/dist/atabey/src/cli/platforms/index.d.ts +1 -1
  6. package/dist/atabey/src/cli/platforms/index.js +1 -1
  7. package/dist/atabey/src/cli/platforms/index.js.map +1 -1
  8. package/dist/atabey/src/cli/utils/memory.d.ts +13 -7
  9. package/dist/atabey/src/cli/utils/memory.js +48 -16
  10. package/dist/atabey/src/cli/utils/memory.js.map +1 -1
  11. package/dist/atabey/src/modules/engines/agent-loop.d.ts +34 -0
  12. package/dist/atabey/src/modules/engines/agent-loop.js +102 -0
  13. package/dist/atabey/src/modules/engines/agent-loop.js.map +1 -0
  14. package/dist/atabey-mcp/src/mcp/constants.d.ts +3 -45
  15. package/dist/atabey-mcp/src/mcp/constants.js +3 -62
  16. package/dist/atabey-mcp/src/mcp/constants.js.map +1 -1
  17. package/dist/atabey-mcp/src/mcp/index.js +19 -35
  18. package/dist/atabey-mcp/src/mcp/index.js.map +1 -1
  19. package/dist/atabey-mcp/src/mcp/routes/api-agents.d.ts +3 -0
  20. package/dist/atabey-mcp/src/mcp/routes/api-agents.js +97 -0
  21. package/dist/atabey-mcp/src/mcp/routes/api-agents.js.map +1 -0
  22. package/dist/atabey-mcp/src/mcp/routes/api-common.d.ts +3 -0
  23. package/dist/atabey-mcp/src/mcp/routes/api-common.js +63 -0
  24. package/dist/atabey-mcp/src/mcp/routes/api-common.js.map +1 -0
  25. package/dist/atabey-mcp/src/mcp/routes/api-config.d.ts +3 -0
  26. package/dist/atabey-mcp/src/mcp/routes/api-config.js +639 -0
  27. package/dist/atabey-mcp/src/mcp/routes/api-config.js.map +1 -0
  28. package/dist/atabey-mcp/src/mcp/routes/api-governance.d.ts +3 -0
  29. package/dist/atabey-mcp/src/mcp/routes/api-governance.js +271 -0
  30. package/dist/atabey-mcp/src/mcp/routes/api-governance.js.map +1 -0
  31. package/dist/atabey-mcp/src/mcp/routes/api-orchestrator.d.ts +3 -0
  32. package/dist/atabey-mcp/src/mcp/routes/api-orchestrator.js +145 -0
  33. package/dist/atabey-mcp/src/mcp/routes/api-orchestrator.js.map +1 -0
  34. package/dist/atabey-mcp/src/mcp/routes/api-workspace.d.ts +3 -0
  35. package/dist/atabey-mcp/src/mcp/routes/api-workspace.js +274 -0
  36. package/dist/atabey-mcp/src/mcp/routes/api-workspace.js.map +1 -0
  37. package/dist/atabey-mcp/src/mcp/routes/index.d.ts +2 -0
  38. package/dist/atabey-mcp/src/mcp/routes/index.js +18 -1436
  39. package/dist/atabey-mcp/src/mcp/routes/index.js.map +1 -1
  40. package/dist/atabey-mcp/src/mcp/routes/types.d.ts +18 -0
  41. package/dist/atabey-mcp/src/mcp/routes/types.js +23 -0
  42. package/dist/atabey-mcp/src/mcp/routes/types.js.map +1 -0
  43. package/dist/atabey-mcp/src/mcp/tools/framework/orchestrate.d.ts +1 -1
  44. package/dist/atabey-mcp/src/mcp/tools/framework/orchestrate.js +11 -4
  45. package/dist/atabey-mcp/src/mcp/tools/framework/orchestrate.js.map +1 -1
  46. package/dist/atabey-mcp/src/mcp/utils/client-config.js +1 -1
  47. package/dist/atabey-mcp/src/mcp/utils/client-config.js.map +1 -1
  48. package/dist/atabey-mcp/src/mcp/utils/compliance-bootstrap.d.ts +8 -0
  49. package/dist/atabey-mcp/src/mcp/utils/compliance-bootstrap.js +46 -0
  50. package/dist/atabey-mcp/src/mcp/utils/compliance-bootstrap.js.map +1 -0
  51. package/dist/atabey-mcp/src/mcp/utils/consent.d.ts +25 -0
  52. package/dist/atabey-mcp/src/mcp/utils/consent.js +60 -0
  53. package/dist/atabey-mcp/src/mcp/utils/consent.js.map +1 -0
  54. package/dist/atabey-mcp/src/mcp/utils/finops.d.ts +7 -11
  55. package/dist/atabey-mcp/src/mcp/utils/finops.js +24 -26
  56. package/dist/atabey-mcp/src/mcp/utils/finops.js.map +1 -1
  57. package/dist/atabey-mcp/src/mcp/utils/framework-config.d.ts +27 -0
  58. package/dist/atabey-mcp/src/mcp/utils/framework-config.js +81 -0
  59. package/dist/atabey-mcp/src/mcp/utils/framework-config.js.map +1 -0
  60. package/dist/atabey-mcp/src/mcp/utils/orchestrator-bootstrap.d.ts +14 -0
  61. package/dist/atabey-mcp/src/mcp/utils/orchestrator-bootstrap.js +98 -0
  62. package/dist/atabey-mcp/src/mcp/utils/orchestrator-bootstrap.js.map +1 -0
  63. package/dist/atabey-mcp/src/mcp/utils/web-config.js +17 -35
  64. package/dist/atabey-mcp/src/mcp/utils/web-config.js.map +1 -1
  65. package/dist/dashboard/assets/{index-DHuA2rua.js → index-BUUxi_j8.js} +75 -75
  66. package/dist/dashboard/index.html +1 -1
  67. package/dist/shared/src/constants.d.ts +30 -1
  68. package/dist/shared/src/constants.js +25 -2
  69. package/dist/shared/src/constants.js.map +1 -1
  70. package/package.json +13 -2
@@ -1,15 +1,18 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
- import { Storage } from "../../shared/storage.js";
5
- import { Audit } from "../../shared/audit.js";
6
- import { maskText } from "../../shared/pii.js";
7
4
  import { logger } from "../../shared/logger.js";
8
- function serveJson(res, statusCode, data) {
5
+ import { handleCommonRoutes } from "./api-common.js";
6
+ import { handleAgentsRoutes } from "./api-agents.js";
7
+ import { handleOrchestratorRoutes } from "./api-orchestrator.js";
8
+ import { handleGovernanceRoutes } from "./api-governance.js";
9
+ import { handleWorkspaceRoutes } from "./api-workspace.js";
10
+ import { handleConfigRoutes } from "./api-config.js";
11
+ export function serveJson(res, statusCode, data) {
9
12
  res.writeHead(statusCode, { "Content-Type": "application/json" });
10
13
  res.end(JSON.stringify(data));
11
14
  }
12
- function serveFile(res, filePath, contentType) {
15
+ export function serveFile(res, filePath, contentType) {
13
16
  if (fs.existsSync(filePath)) {
14
17
  try {
15
18
  const content = fs.readFileSync(filePath);
@@ -38,10 +41,9 @@ function parseUrl(url) {
38
41
  return { pathname, params };
39
42
  }
40
43
  export async function handleRequest(req, res, context) {
41
- const { serverVersion, sessions, FRAMEWORK_DIR, PROJECT_ROOT, UI_DIST_PATH, server, broadcastWS, wsClients, HOST, PORT } = context;
44
+ const { serverVersion, sessions, UI_DIST_PATH, server, broadcastWS } = context;
42
45
  const url = req.url || "/";
43
46
  const { pathname, params } = parseUrl(url);
44
- const method = req.method || "GET";
45
47
  // CORS
46
48
  res.setHeader("Access-Control-Allow-Origin", "*");
47
49
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -107,1440 +109,20 @@ export async function handleRequest(req, res, context) {
107
109
  return;
108
110
  }
109
111
  // ───────────── DASHBOARD API ROUTES (/api) ───────────
110
- // Health
111
- if (pathname === "/api/health") {
112
- serveJson(res, 200, { status: "healthy", version: serverVersion, frameworkDir: FRAMEWORK_DIR });
113
- return;
114
- }
115
- // Status
116
- if (pathname === "/api/status") {
117
- const statusPath = path.join(FRAMEWORK_DIR, "memory", "status.json");
118
- if (fs.existsSync(statusPath)) {
119
- serveFile(res, statusPath, "application/json");
112
+ if (isApiRoute) {
113
+ const method = req.method || "GET";
114
+ if (await handleCommonRoutes(pathname, params, method, req, res, context))
120
115
  return;
121
- }
122
- serveJson(res, 200, { success: true, data: { status: "initializing" } });
123
- return;
124
- }
125
- // Memory (PROJECT_MEMORY.md)
126
- if (pathname === "/api/memory") {
127
- const memoryPath = path.join(FRAMEWORK_DIR, "memory", "PROJECT_MEMORY.md");
128
- if (fs.existsSync(memoryPath)) {
129
- serveFile(res, memoryPath, "text/markdown");
116
+ if (await handleAgentsRoutes(pathname, params, method, req, res, context))
130
117
  return;
131
- }
132
- serveJson(res, 200, { success: true, data: "No memory file yet." });
133
- return;
134
- }
135
- // Memory Search
136
- if (pathname === "/api/memory/search") {
137
- try {
138
- const query = params.q || "";
139
- const limit = parseInt(params.limit || "10", 10);
140
- if (!query) {
141
- const db = Storage.getDB();
142
- const rows = db.prepare("SELECT * FROM vector_memory ORDER BY created_at DESC LIMIT ?").all(limit);
143
- const results = rows.map(row => {
144
- const meta = JSON.parse(row.metadata || "{}");
145
- return { id: row.id, content: maskText(row.content), type: row.category || meta.category || "memory", timestamp: meta.createdAt || row.created_at, relevance: 1.0 };
146
- });
147
- serveJson(res, 200, { success: true, data: results });
148
- return;
149
- }
150
- const { CoreMemory } = await import("atabey/src/modules/memory/core.js");
151
- const results = await CoreMemory.recall(query, { limit });
152
- const flattened = results.map(r => ({
153
- id: r.entry.id, content: maskText(r.entry.content), type: r.entry.metadata.category || "memory",
154
- timestamp: r.entry.metadata.createdAt || new Date().toISOString(), relevance: r.score
155
- }));
156
- serveJson(res, 200, { success: true, data: flattened });
157
- }
158
- catch (e) {
159
- serveJson(res, 500, { success: false, error: e.message });
160
- }
161
- return;
162
- }
163
- // Agents
164
- if (pathname === "/api/agents") {
165
- try {
166
- const agents = Storage.getAllAgents();
167
- serveJson(res, 200, { success: true, data: agents });
168
- }
169
- catch (e) {
170
- serveJson(res, 500, { success: false, error: e.message });
171
- }
172
- return;
173
- }
174
- // POST Create Agent
175
- if (pathname === "/api/agents/create" && method === "POST") {
176
- try {
177
- let body = "";
178
- req.on("data", chunk => { body += chunk.toString(); });
179
- req.on("end", async () => {
180
- try {
181
- const parsed = JSON.parse(body);
182
- const { name, description, skills } = parsed;
183
- if (!name) {
184
- serveJson(res, 400, { success: false, error: "name parameter is required" });
185
- return;
186
- }
187
- const { AtabeyStorage } = await import("../../shared/storage.js");
188
- AtabeyStorage.createAgent(name, description || "", skills || "[]");
189
- broadcastWS("agent_update", AtabeyStorage.getAllAgents());
190
- serveJson(res, 200, { success: true, message: `Agent ${name} created.` });
191
- }
192
- catch (err) {
193
- serveJson(res, 400, { success: false, error: err.message });
194
- }
195
- });
196
- }
197
- catch (e) {
198
- serveJson(res, 500, { success: false, error: e.message });
199
- }
200
- return;
201
- }
202
- // POST Delete Agent
203
- if (pathname.startsWith("/api/agents/delete/") && method === "POST") {
204
- const agentName = decodeURIComponent(pathname.replace("/api/agents/delete/", ""));
205
- if (!agentName) {
206
- serveJson(res, 400, { success: false, error: "Agent name required" });
118
+ if (await handleOrchestratorRoutes(pathname, params, method, req, res, context))
207
119
  return;
208
- }
209
- try {
210
- const { AtabeyStorage } = await import("../../shared/storage.js");
211
- const deleted = AtabeyStorage.deleteAgent(agentName);
212
- if (deleted) {
213
- broadcastWS("agent_update", AtabeyStorage.getAllAgents());
214
- serveJson(res, 200, { success: true, message: `Agent ${agentName} deleted.` });
215
- }
216
- else {
217
- serveJson(res, 404, { success: false, error: `Agent ${agentName} not found.` });
218
- }
219
- }
220
- catch (e) {
221
- serveJson(res, 500, { success: false, error: e.message });
222
- }
223
- return;
224
- }
225
- // POST Update Agent Details
226
- if (pathname === "/api/agents/update" && method === "POST") {
227
- try {
228
- let body = "";
229
- req.on("data", chunk => { body += chunk.toString(); });
230
- req.on("end", async () => {
231
- try {
232
- const parsed = JSON.parse(body);
233
- const { name, description, state, task, skills } = parsed;
234
- if (!name) {
235
- serveJson(res, 400, { success: false, error: "name parameter is required" });
236
- return;
237
- }
238
- const { AtabeyStorage } = await import("../../shared/storage.js");
239
- AtabeyStorage.updateAgentDetails(name, description || "Otonom Uzman Ajan", state || "READY", task || "Idle", typeof skills === "string" ? skills : JSON.stringify(skills || []));
240
- broadcastWS("agent_update", AtabeyStorage.getAllAgents());
241
- serveJson(res, 200, { success: true, message: `Agent ${name} updated successfully.` });
242
- }
243
- catch (err) {
244
- serveJson(res, 400, { success: false, error: err.message });
245
- }
246
- });
247
- }
248
- catch (e) {
249
- serveJson(res, 500, { success: false, error: e.message });
250
- }
251
- return;
252
- }
253
- // Messages (Hermes queue)
254
- if (pathname === "/api/messages") {
255
- try {
256
- const messages = Storage.getPendingMessages();
257
- serveJson(res, 200, { success: true, data: messages });
258
- }
259
- catch (e) {
260
- serveJson(res, 500, { success: false, error: e.message });
261
- }
262
- return;
263
- }
264
- // Hermes Stats
265
- if (pathname === "/api/hermes/stats") {
266
- try {
267
- const messages = Storage.getPendingMessages();
268
- const totalMessages = Storage.getDB().prepare("SELECT COUNT(*) as count FROM messages").get();
269
- const byCategory = {};
270
- const byStatus = {};
271
- messages.forEach(m => {
272
- byCategory[m.category] = (byCategory[m.category] || 0) + 1;
273
- byStatus[m.status] = (byStatus[m.status] || 0) + 1;
274
- });
275
- serveJson(res, 200, {
276
- success: true, data: {
277
- total: totalMessages.count, pending: messages.length, byCategory, byStatus,
278
- lastMessage: messages.length > 0 ? messages[messages.length - 1] : null,
279
- }
280
- });
281
- }
282
- catch (e) {
283
- serveJson(res, 500, { success: false, error: e.message });
284
- }
285
- return;
286
- }
287
- // Tasks
288
- if (pathname === "/api/tasks") {
289
- try {
290
- const { asTraceID } = await import("../../shared/types.js");
291
- const traceId = params.traceId ? asTraceID(params.traceId) : undefined;
292
- const tasks = Storage.getTasks(traceId);
293
- const maskedTasks = tasks.map(t => ({
294
- ...t,
295
- description: maskText(t.description),
296
- }));
297
- serveJson(res, 200, { success: true, data: maskedTasks });
298
- }
299
- catch (e) {
300
- serveJson(res, 500, { success: false, error: e.message });
301
- }
302
- return;
303
- }
304
- // Logs
305
- if (pathname.startsWith("/api/logs")) {
306
- try {
307
- const logs = Storage.getLogs();
308
- const agent = pathname.split("/api/logs/")[1];
309
- const filteredLogs = agent ? logs.filter(l => l.agent === agent) : logs;
310
- serveJson(res, 200, { success: true, data: filteredLogs });
311
- }
312
- catch (e) {
313
- serveJson(res, 500, { success: false, error: e.message });
314
- }
315
- return;
316
- }
317
- // Approvals
318
- if (pathname === "/api/approvals") {
319
- try {
320
- const allMessages = Storage.getPendingMessages();
321
- const approvals = allMessages.filter(m => m.category === "ALERT" || m.category === "ACTION")
322
- .map(m => ({
323
- id: String(m.id || m.traceId), traceId: m.traceId,
324
- description: m.content ? (typeof m.content === "string" ? maskText(m.content) : maskText(JSON.stringify(m.content))) : "Approval required",
325
- status: m.status || "PENDING", timestamp: m.timestamp || new Date().toISOString()
326
- }));
327
- serveJson(res, 200, { success: true, data: approvals });
328
- }
329
- catch (e) {
330
- serveJson(res, 500, { success: false, error: e.message });
331
- }
332
- return;
333
- }
334
- // Approve
335
- if (pathname.startsWith("/api/approve/") && method === "POST") {
336
- const traceId = pathname.replace("/api/approve/", "");
337
- if (!traceId) {
338
- serveJson(res, 400, { success: false, error: "Trace ID required" });
120
+ if (await handleGovernanceRoutes(pathname, params, method, req, res, context))
339
121
  return;
340
- }
341
- try {
342
- const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
343
- if (pendingMessages.length === 0) {
344
- serveJson(res, 404, { success: false, error: "No pending approval found" });
345
- return;
346
- }
347
- const { approveOperation } = await import("../utils/human-in-loop.js");
348
- const result = approveOperation(traceId, undefined, undefined, true);
349
- if (result.success) {
350
- broadcastWS("approval", { traceId, status: "APPROVED" });
351
- serveJson(res, 200, { success: true, message: result.message, approved: pendingMessages.length });
352
- }
353
- else {
354
- serveJson(res, 400, { success: false, error: result.message });
355
- }
356
- }
357
- catch (e) {
358
- serveJson(res, 500, { success: false, error: e.message });
359
- }
360
- return;
361
- }
362
- // Reject
363
- if (pathname.startsWith("/api/reject/") && method === "POST") {
364
- const traceId = pathname.replace("/api/reject/", "");
365
- if (!traceId) {
366
- serveJson(res, 400, { success: false, error: "Trace ID required" });
122
+ if (await handleWorkspaceRoutes(pathname, params, method, req, res, context))
123
+ return;
124
+ if (await handleConfigRoutes(pathname, params, method, req, res, context))
367
125
  return;
368
- }
369
- try {
370
- const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
371
- if (pendingMessages.length === 0) {
372
- serveJson(res, 404, { success: false, error: "No pending approval found" });
373
- return;
374
- }
375
- const { rejectOperation } = await import("../utils/human-in-loop.js");
376
- const result = rejectOperation(traceId, undefined, true);
377
- if (result.success) {
378
- broadcastWS("approval", { traceId, status: "REJECTED" });
379
- serveJson(res, 200, { success: true, message: result.message, rejected: pendingMessages.length });
380
- }
381
- else {
382
- serveJson(res, 400, { success: false, error: result.message });
383
- }
384
- }
385
- catch (e) {
386
- serveJson(res, 500, { success: false, error: e.message });
387
- }
388
- return;
389
- }
390
- // Audit (GDPR/KVKK)
391
- if (pathname === "/api/audit") {
392
- try {
393
- Audit.initialize();
394
- const stats = Audit.getStats();
395
- const entries = Audit.query({ limit: 100 });
396
- serveJson(res, 200, { success: true, data: { stats, entries } });
397
- }
398
- catch (e) {
399
- serveJson(res, 500, { success: false, error: e.message });
400
- }
401
- return;
402
- }
403
- // Audit Erase (GDPR/KVKK Right to Erasure)
404
- if (pathname === "/api/audit/erase" && method === "POST") {
405
- try {
406
- Audit.initialize();
407
- let body = "";
408
- req.on("data", chunk => { body += chunk.toString(); });
409
- req.on("end", () => {
410
- try {
411
- const parsed = JSON.parse(body);
412
- const confirmation = parsed.confirmationCode || "";
413
- const changes = Audit.clearAll(confirmation);
414
- serveJson(res, 200, { success: true, message: `${changes} records cleared under KVKK/GDPR right to erasure.` });
415
- }
416
- catch (err) {
417
- serveJson(res, 400, { success: false, error: err.message });
418
- }
419
- });
420
- }
421
- catch (e) {
422
- serveJson(res, 500, { success: false, error: e.message });
423
- }
424
- return;
425
- }
426
- // Compliance
427
- if (pathname === "/api/compliance") {
428
- try {
429
- const { scanProjectCompliance } = await import("atabey/src/cli/utils/compliance.js");
430
- const targetPath = params.path || "src";
431
- const scanPath = path.join(PROJECT_ROOT, targetPath);
432
- const rawIssues = scanProjectCompliance(scanPath);
433
- const violations = rawIssues.map(issue => {
434
- const ruleLower = issue.rule.toLowerCase();
435
- const type = ruleLower.includes("any") ? "any-type" : ruleLower.includes("console") ? "console-log" : "other";
436
- return { file: path.relative(PROJECT_ROOT, issue.file), line: issue.line, type, message: issue.rule.replace(/^\[ERROR\]\s*Corporate\s*Compliance\s*Breach:\s*/i, "") };
437
- });
438
- const violatingFiles = new Set(violations.map(v => v.file));
439
- serveJson(res, 200, {
440
- success: true, data: {
441
- summary: { totalFiles: violatingFiles.size, totalViolations: violations.length, byType: { "any-type": violations.filter(v => v.type === "any-type").length, "console-log": violations.filter(v => v.type === "console-log").length } },
442
- violations
443
- }
444
- });
445
- }
446
- catch (e) {
447
- serveJson(res, 500, { success: false, error: e.message });
448
- }
449
- return;
450
- }
451
- // Quality
452
- if (pathname === "/api/quality") {
453
- try {
454
- const { analyzePathQuality } = await import("atabey/src/cli/utils/quality.js");
455
- const targetPath = params.path || "src";
456
- const result = analyzePathQuality(PROJECT_ROOT, targetPath);
457
- serveJson(res, 200, {
458
- success: true, data: {
459
- totalFiles: result.totalFiles, totalIssues: result.totalIssues,
460
- longFunctions: result.longFunctions, deepNesting: result.deepNesting,
461
- anyTypes: result.anyTypes, issues: result.issues.slice(0, 50),
462
- }
463
- });
464
- }
465
- catch (e) {
466
- serveJson(res, 500, { success: false, error: e.message });
467
- }
468
- return;
469
- }
470
- // ─── Knowledge Base API ────────────────────────────────────────
471
- if (pathname === "/api/knowledge" && req.method === "GET") {
472
- try {
473
- const list = Storage.getKnowledgeList();
474
- serveJson(res, 200, { success: true, data: list });
475
- }
476
- catch (e) {
477
- serveJson(res, 500, { success: false, error: e.message });
478
- }
479
- return;
480
- }
481
- if (pathname === "/api/knowledge/view" && req.method === "GET") {
482
- try {
483
- const file = params.file;
484
- if (!file) {
485
- serveJson(res, 400, { success: false, error: "Missing 'file' parameter" });
486
- return;
487
- }
488
- const doc = Storage.getKnowledgeFile(file);
489
- if (!doc) {
490
- serveJson(res, 404, { success: false, error: "File not found" });
491
- return;
492
- }
493
- serveJson(res, 200, { success: true, data: doc });
494
- }
495
- catch (e) {
496
- serveJson(res, 500, { success: false, error: e.message });
497
- }
498
- return;
499
- }
500
- if (pathname === "/api/knowledge/update" && req.method === "POST") {
501
- try {
502
- let body = "";
503
- req.on("data", chunk => { body += chunk.toString(); });
504
- req.on("end", async () => {
505
- try {
506
- const parsed = JSON.parse(body);
507
- const file = parsed.file;
508
- const content = parsed.content;
509
- if (!file || content === undefined) {
510
- serveJson(res, 400, { success: false, error: "Missing 'file' or 'content' in request body" });
511
- return;
512
- }
513
- let safeFile = path.basename(file);
514
- if (!safeFile.endsWith(".md")) {
515
- safeFile += ".md";
516
- }
517
- Storage.saveKnowledgeFile(safeFile, content);
518
- serveJson(res, 200, { success: true, message: `Knowledge document '${safeFile}' updated successfully.` });
519
- }
520
- catch (err) {
521
- serveJson(res, 400, { success: false, error: err.message });
522
- }
523
- });
524
- }
525
- catch (e) {
526
- serveJson(res, 500, { success: false, error: e.message });
527
- }
528
- return;
529
- }
530
- if (pathname === "/api/knowledge/delete" && req.method === "POST") {
531
- try {
532
- let body = "";
533
- req.on("data", chunk => { body += chunk.toString(); });
534
- req.on("end", async () => {
535
- try {
536
- const parsed = JSON.parse(body);
537
- const file = parsed.file;
538
- if (!file) {
539
- serveJson(res, 400, { success: false, error: "Missing 'file' in request body" });
540
- return;
541
- }
542
- const safeFile = path.basename(file);
543
- Storage.deleteKnowledgeFile(safeFile);
544
- serveJson(res, 200, { success: true, message: `Knowledge document '${safeFile}' deleted.` });
545
- }
546
- catch (err) {
547
- serveJson(res, 400, { success: false, error: err.message });
548
- }
549
- });
550
- }
551
- catch (e) {
552
- serveJson(res, 500, { success: false, error: e.message });
553
- }
554
- return;
555
- }
556
- // ─── Constitution API ─────────────────────────────────────────
557
- if (pathname === "/api/constitution" && req.method === "GET") {
558
- try {
559
- const db = Storage.getDB();
560
- const sections = db.prepare("SELECT * FROM constitution ORDER BY priority DESC").all();
561
- serveJson(res, 200, { success: true, data: sections });
562
- }
563
- catch (e) {
564
- serveJson(res, 500, { success: false, error: e.message });
565
- }
566
- return;
567
- }
568
- if (pathname === "/api/constitution/update" && req.method === "POST") {
569
- try {
570
- let body = "";
571
- req.on("data", chunk => { body += chunk.toString(); });
572
- req.on("end", async () => {
573
- try {
574
- const parsed = JSON.parse(body);
575
- const { section, title, content } = parsed;
576
- if (!section || !content) {
577
- serveJson(res, 400, { success: false, error: "Missing 'section' or 'content'" });
578
- return;
579
- }
580
- const db = Storage.getDB();
581
- db.prepare(`
582
- INSERT INTO constitution (section, title, content, updated_at)
583
- VALUES (?, ?, ?, ?)
584
- ON CONFLICT(section) DO UPDATE SET
585
- title = excluded.title,
586
- content = excluded.content,
587
- updated_at = excluded.updated_at
588
- `).run(section, title || "", content, new Date().toISOString());
589
- serveJson(res, 200, { success: true, message: `Section '${section}' updated.` });
590
- }
591
- catch (err) {
592
- serveJson(res, 400, { success: false, error: err.message });
593
- }
594
- });
595
- }
596
- catch (e) {
597
- serveJson(res, 500, { success: false, error: e.message });
598
- }
599
- return;
600
- }
601
- // ─── CLI Commands API ─────────────────────────────────────────
602
- if (pathname === "/api/cli-commands" && req.method === "GET") {
603
- try {
604
- const db = Storage.getDB();
605
- const commands = db.prepare("SELECT * FROM cli_commands ORDER BY command").all();
606
- serveJson(res, 200, { success: true, data: commands });
607
- }
608
- catch (e) {
609
- serveJson(res, 500, { success: false, error: e.message });
610
- }
611
- return;
612
- }
613
- // ─── Prompts API ──────────────────────────────────────────────
614
- if (pathname === "/api/prompts" && req.method === "GET") {
615
- try {
616
- const db = Storage.getDB();
617
- const prompts = db.prepare("SELECT * FROM prompts ORDER BY category, name").all();
618
- serveJson(res, 200, { success: true, data: prompts });
619
- }
620
- catch (e) {
621
- serveJson(res, 500, { success: false, error: e.message });
622
- }
623
- return;
624
- }
625
- if (pathname === "/api/prompts/update" && req.method === "POST") {
626
- try {
627
- let body = "";
628
- req.on("data", chunk => { body += chunk.toString(); });
629
- req.on("end", async () => {
630
- try {
631
- const parsed = JSON.parse(body);
632
- const { name, category, content } = parsed;
633
- if (!name || !content) {
634
- serveJson(res, 400, { success: false, error: "Missing 'name' or 'content'" });
635
- return;
636
- }
637
- const db = Storage.getDB();
638
- db.prepare(`
639
- INSERT INTO prompts (name, category, content, updated_at)
640
- VALUES (?, ?, ?, ?)
641
- ON CONFLICT(name) DO UPDATE SET
642
- category = excluded.category,
643
- content = excluded.content,
644
- updated_at = excluded.updated_at
645
- `).run(name, category || "general", content, new Date().toISOString());
646
- serveJson(res, 200, { success: true, message: `Prompt '${name}' updated.` });
647
- }
648
- catch (err) {
649
- serveJson(res, 400, { success: false, error: err.message });
650
- }
651
- });
652
- }
653
- catch (e) {
654
- serveJson(res, 500, { success: false, error: e.message });
655
- }
656
- return;
657
- }
658
- // ─── Rules API ────────────────────────────────────────────────
659
- if (pathname === "/api/rules" && req.method === "GET") {
660
- try {
661
- const db = Storage.getDB();
662
- const rules = db.prepare("SELECT * FROM rules ORDER BY name").all();
663
- serveJson(res, 200, { success: true, data: rules });
664
- }
665
- catch (e) {
666
- serveJson(res, 500, { success: false, error: e.message });
667
- }
668
- return;
669
- }
670
- if (pathname === "/api/rules/update" && req.method === "POST") {
671
- try {
672
- let body = "";
673
- req.on("data", chunk => { body += chunk.toString(); });
674
- req.on("end", async () => {
675
- try {
676
- const parsed = JSON.parse(body);
677
- const { name, content, enabled } = parsed;
678
- if (!name) {
679
- serveJson(res, 400, { success: false, error: "Missing 'name'" });
680
- return;
681
- }
682
- const db = Storage.getDB();
683
- db.prepare(`
684
- INSERT INTO rules (name, content, enabled, updated_at)
685
- VALUES (?, ?, ?, ?)
686
- ON CONFLICT(name) DO UPDATE SET
687
- content = excluded.content,
688
- enabled = excluded.enabled,
689
- updated_at = excluded.updated_at
690
- `).run(name, content || "", enabled !== undefined ? (enabled ? 1 : 0) : 1, new Date().toISOString());
691
- serveJson(res, 200, { success: true, message: `Rule '${name}' updated.` });
692
- }
693
- catch (err) {
694
- serveJson(res, 400, { success: false, error: err.message });
695
- }
696
- });
697
- }
698
- catch (e) {
699
- serveJson(res, 500, { success: false, error: e.message });
700
- }
701
- return;
702
- }
703
- // ─── Registry API ─────────────────────────────────────────────
704
- if (pathname === "/api/registry" && req.method === "GET") {
705
- try {
706
- const db = Storage.getDB();
707
- const items = db.prepare("SELECT * FROM registry ORDER BY type, name").all();
708
- serveJson(res, 200, { success: true, data: items });
709
- }
710
- catch (e) {
711
- serveJson(res, 500, { success: false, error: e.message });
712
- }
713
- return;
714
- }
715
- if (pathname === "/api/registry/update" && req.method === "POST") {
716
- try {
717
- let body = "";
718
- req.on("data", chunk => { body += chunk.toString(); });
719
- req.on("end", async () => {
720
- try {
721
- const parsed = JSON.parse(body);
722
- const { name, type, data } = parsed;
723
- if (!name) {
724
- serveJson(res, 400, { success: false, error: "Missing 'name'" });
725
- return;
726
- }
727
- const db = Storage.getDB();
728
- db.prepare(`
729
- INSERT INTO registry (name, type, data, updated_at)
730
- VALUES (?, ?, ?, ?)
731
- ON CONFLICT(name) DO UPDATE SET
732
- type = excluded.type,
733
- data = excluded.data,
734
- updated_at = excluded.updated_at
735
- `).run(name, type || "agent", data || "{}", new Date().toISOString());
736
- serveJson(res, 200, { success: true, message: `Registry '${name}' updated.` });
737
- }
738
- catch (err) {
739
- serveJson(res, 400, { success: false, error: err.message });
740
- }
741
- });
742
- }
743
- catch (e) {
744
- serveJson(res, 500, { success: false, error: e.message });
745
- }
746
- return;
747
- }
748
- // ─── Settings API (from config.json / metadata) ──────────────
749
- if (pathname === "/api/settings" && req.method === "GET") {
750
- try {
751
- const projectPath = Storage.getMetadata("projectPath") || "";
752
- const activeProvider = Storage.getMetadata("activeProvider") || "gemini";
753
- serveJson(res, 200, { success: true, data: { projectPath, activeProvider } });
754
- }
755
- catch (e) {
756
- serveJson(res, 500, { success: false, error: e.message });
757
- }
758
- return;
759
- }
760
- if (pathname === "/api/settings" && req.method === "POST") {
761
- try {
762
- let body = "";
763
- req.on("data", chunk => { body += chunk.toString(); });
764
- req.on("end", async () => {
765
- try {
766
- const parsed = JSON.parse(body);
767
- if (parsed.projectPath !== undefined)
768
- Storage.setMetadata("projectPath", parsed.projectPath);
769
- if (parsed.activeProvider !== undefined)
770
- Storage.setMetadata("activeProvider", parsed.activeProvider);
771
- serveJson(res, 200, { success: true, message: "Settings saved." });
772
- }
773
- catch (err) {
774
- serveJson(res, 400, { success: false, error: err.message });
775
- }
776
- });
777
- }
778
- catch (e) {
779
- serveJson(res, 500, { success: false, error: e.message });
780
- }
781
- return;
782
- }
783
- // ─── Config (project config from .atabey/config.json) ──────
784
- if (pathname === "/api/config" && req.method === "GET") {
785
- try {
786
- const db = Storage.getDB();
787
- const configs = db.prepare("SELECT * FROM metadata WHERE key LIKE 'config.%'").all();
788
- const config = {};
789
- for (const c of configs) {
790
- config[c.key.replace("config.", "")] = c.value;
791
- }
792
- serveJson(res, 200, { success: true, data: config });
793
- }
794
- catch (e) {
795
- serveJson(res, 500, { success: false, error: e.message });
796
- }
797
- return;
798
- }
799
- // Adapter Skills
800
- if (pathname === "/api/adapters/skills") {
801
- try {
802
- const { ADAPTER_SKILLS } = await import("atabey/src/modules/skills/adapter-skills.js");
803
- const summary = Object.entries(ADAPTER_SKILLS).map(([id, config]) => ({
804
- id, skillCount: Object.keys(config.enabledSkills).length,
805
- toolsCount: config.toolMapping ? Object.keys(config.toolMapping).length : 0,
806
- skills: Object.entries(config.enabledSkills).map(([skId, skVal]) => ({
807
- id: skId,
808
- name: skVal.name,
809
- disabled: !!skVal.disabled
810
- })),
811
- }));
812
- serveJson(res, 200, { success: true, data: summary });
813
- }
814
- catch (e) {
815
- serveJson(res, 500, { success: false, error: e.message });
816
- }
817
- return;
818
- }
819
- // POST User Login
820
- if (pathname === "/api/auth/login" && req.method === "POST") {
821
- try {
822
- let body = "";
823
- req.on("data", chunk => { body += chunk.toString(); });
824
- req.on("end", async () => {
825
- try {
826
- const parsed = JSON.parse(body);
827
- const { username, token } = parsed;
828
- if (!username || !token) {
829
- serveJson(res, 400, { success: false, error: "Username and token are required" });
830
- return;
831
- }
832
- const maskedToken = token.length > 8 ? `${token.slice(0, 4)}...${token.slice(-4)}` : "***";
833
- logger.info(`[LOGIN ATTEMPT] username="${username}", token="${maskedToken}"`);
834
- const { authenticateToken } = await import("../utils/auth.js");
835
- const result = authenticateToken(token);
836
- logger.info(`[LOGIN RESULT] authenticated=${result.authenticated}, user="${result.user}"`);
837
- if (result.authenticated && (result.user === username || (result.user === "admin" && username === "admin"))) {
838
- serveJson(res, 200, { success: true, user: { name: username, role: result.user === "admin" ? "admin" : "user" } });
839
- }
840
- else {
841
- serveJson(res, 401, { success: false, error: "Invalid username or token" });
842
- }
843
- }
844
- catch (err) {
845
- logger.error(`[LOGIN ERROR] ${err.message}`);
846
- serveJson(res, 400, { success: false, error: err.message });
847
- }
848
- });
849
- }
850
- catch (e) {
851
- serveJson(res, 500, { success: false, error: e.message });
852
- }
853
- return;
854
- }
855
- // GET Configured Admin Users
856
- if (pathname === "/api/admin/users" && req.method === "GET") {
857
- try {
858
- const users = Storage.getUsers();
859
- serveJson(res, 200, { success: true, data: users });
860
- }
861
- catch (e) {
862
- serveJson(res, 500, { success: false, error: e.message });
863
- }
864
- return;
865
- }
866
- // POST Create/Update Admin User
867
- if (pathname === "/api/admin/users/create" && req.method === "POST") {
868
- try {
869
- let body = "";
870
- req.on("data", chunk => { body += chunk.toString(); });
871
- req.on("end", async () => {
872
- try {
873
- const parsed = JSON.parse(body);
874
- const { name, token, role } = parsed;
875
- if (!name || !token) {
876
- serveJson(res, 400, { success: false, error: "Username and token are required" });
877
- return;
878
- }
879
- Storage.createUser(name.trim(), token.trim(), role || "user");
880
- serveJson(res, 200, { success: true, message: `User ${name} created/updated successfully.` });
881
- }
882
- catch (err) {
883
- serveJson(res, 400, { success: false, error: err.message });
884
- }
885
- });
886
- }
887
- catch (e) {
888
- serveJson(res, 500, { success: false, error: e.message });
889
- }
890
- return;
891
- }
892
- // POST Delete Admin User
893
- if (pathname === "/api/admin/users/delete" && req.method === "POST") {
894
- try {
895
- let body = "";
896
- req.on("data", chunk => { body += chunk.toString(); });
897
- req.on("end", async () => {
898
- try {
899
- const parsed = JSON.parse(body);
900
- const { name } = parsed;
901
- if (!name) {
902
- serveJson(res, 400, { success: false, error: "Username is required" });
903
- return;
904
- }
905
- Storage.deleteUser(name);
906
- serveJson(res, 200, { success: true, message: `User ${name} deleted.` });
907
- }
908
- catch (err) {
909
- serveJson(res, 400, { success: false, error: err.message });
910
- }
911
- });
912
- }
913
- catch (e) {
914
- serveJson(res, 500, { success: false, error: e.message });
915
- }
916
- return;
917
- }
918
- if (pathname === "/api/adapters/skills/toggle" && method === "POST") {
919
- try {
920
- let body = "";
921
- req.on("data", chunk => { body += chunk.toString(); });
922
- req.on("end", async () => {
923
- try {
924
- const parsed = JSON.parse(body);
925
- const { adapterId, skillId } = parsed;
926
- if (!adapterId || !skillId) {
927
- serveJson(res, 400, { success: false, error: "Missing adapterId or skillId" });
928
- return;
929
- }
930
- const { ADAPTER_SKILLS } = await import("atabey/src/modules/skills/adapter-skills.js");
931
- const adapter = ADAPTER_SKILLS[adapterId];
932
- if (!adapter || !adapter.enabledSkills[skillId]) {
933
- serveJson(res, 404, { success: false, error: "Adapter or skill not found" });
934
- return;
935
- }
936
- const skill = adapter.enabledSkills[skillId];
937
- skill.disabled = !skill.disabled;
938
- serveJson(res, 200, { success: true, disabled: !!skill.disabled });
939
- }
940
- catch (err) {
941
- serveJson(res, 400, { success: false, error: err.message });
942
- }
943
- });
944
- }
945
- catch (e) {
946
- serveJson(res, 500, { success: false, error: e.message });
947
- }
948
- return;
949
- }
950
- // ─── Workspace API ────────────────────────────────────────────
951
- if (pathname === "/api/workspace" && method === "GET") {
952
- try {
953
- const { WorkspaceManager } = await import("../utils/workspace.js");
954
- WorkspaceManager.initialize(PROJECT_ROOT);
955
- const workspaces = WorkspaceManager.listWorkspaces();
956
- const stats = WorkspaceManager.getStats();
957
- serveJson(res, 200, { success: true, data: { workspaces, stats } });
958
- }
959
- catch (e) {
960
- serveJson(res, 500, { success: false, error: e.message });
961
- }
962
- return;
963
- }
964
- if (pathname === "/api/workspace/setup" && method === "POST") {
965
- try {
966
- let body = "";
967
- req.on("data", chunk => { body += chunk.toString(); });
968
- req.on("end", async () => {
969
- try {
970
- const parsed = JSON.parse(body);
971
- const { userName } = parsed;
972
- if (!userName) {
973
- serveJson(res, 400, { success: false, error: "userName required" });
974
- return;
975
- }
976
- const { WorkspaceManager } = await import("../utils/workspace.js");
977
- const { ClientConfigGenerator } = await import("../utils/client-config.js");
978
- WorkspaceManager.initialize(PROJECT_ROOT);
979
- const workspace = await WorkspaceManager.getOrCreateWorkspace(userName);
980
- const userToken = process.env[`MCP_TOKEN_${workspace.developerId.toUpperCase()}`] ||
981
- `atabey_${workspace.developerId}_${Date.now().toString(36)}`;
982
- const guide = ClientConfigGenerator.generateSetupGuide(PROJECT_ROOT, userName, userToken, HOST, PORT);
983
- serveJson(res, 200, {
984
- success: true,
985
- data: {
986
- workspace,
987
- userToken,
988
- setupGuide: guide,
989
- configs: ClientConfigGenerator.generateAll(PROJECT_ROOT, userName, userToken, HOST, PORT)
990
- .map(c => ({ clientType: c.clientType, configPath: c.configPath }))
991
- }
992
- });
993
- }
994
- catch (err) {
995
- serveJson(res, 400, { success: false, error: err.message });
996
- }
997
- });
998
- }
999
- catch (e) {
1000
- serveJson(res, 500, { success: false, error: e.message });
1001
- }
1002
- return;
1003
- }
1004
- // MCP Sessions (with user identity)
1005
- if (pathname === "/api/mcp/sessions") {
1006
- const sessionList = Array.from(sessions.entries()).map(([id, session]) => ({
1007
- id,
1008
- user: session.user,
1009
- clientName: session.clientName,
1010
- connectedAt: session.connectedAt,
1011
- lastActivity: session.lastActivity,
1012
- toolCalls: session.toolCalls,
1013
- }));
1014
- serveJson(res, 200, { success: true, data: { total: sessions.size, sessions: sessionList } });
1015
- return;
1016
- }
1017
- // Discipline Stats
1018
- if (pathname === "/api/discipline") {
1019
- try {
1020
- const { getAllDisciplineStats } = await import("../utils/discipline.js");
1021
- const stats = getAllDisciplineStats();
1022
- serveJson(res, 200, { success: true, data: stats });
1023
- }
1024
- catch (e) {
1025
- serveJson(res, 500, { success: false, error: e.message });
1026
- }
1027
- return;
1028
- }
1029
- // Token Economy / Metrics
1030
- if (pathname === "/api/metrics") {
1031
- try {
1032
- const metricsPath = path.join(FRAMEWORK_DIR, "observability/metrics.json");
1033
- let entries = [];
1034
- if (fs.existsSync(metricsPath)) {
1035
- entries = JSON.parse(fs.readFileSync(metricsPath, "utf8"));
1036
- }
1037
- const byAgent = {};
1038
- const byAction = {};
1039
- let totalTokens = 0;
1040
- for (const entry of entries) {
1041
- const agent = entry.agent || "unknown";
1042
- const action = entry.action || "unknown";
1043
- const tokens = entry.estimatedTokens || 0;
1044
- if (!byAgent[agent])
1045
- byAgent[agent] = { calls: 0, tokens: 0, cost: 0 };
1046
- byAgent[agent].calls++;
1047
- byAgent[agent].tokens += tokens;
1048
- byAgent[agent].cost = +(byAgent[agent].tokens / 1000 * 0.003).toFixed(4);
1049
- if (!byAction[action])
1050
- byAction[action] = { calls: 0, tokens: 0 };
1051
- byAction[action].calls++;
1052
- byAction[action].tokens += tokens;
1053
- totalTokens += tokens;
1054
- }
1055
- serveJson(res, 200, {
1056
- success: true, data: {
1057
- totalToolCalls: entries.length, totalEstimatedTokens: totalTokens,
1058
- totalEstimatedCost: +(totalTokens / 1000 * 0.003).toFixed(4),
1059
- mcpSessions: sessions.size, byAgent, byAction, recentEntries: entries.slice(-100),
1060
- }
1061
- });
1062
- }
1063
- catch (e) {
1064
- serveJson(res, 500, { success: false, error: e.message });
1065
- }
1066
- return;
1067
- }
1068
- // Telemetry Status
1069
- if (pathname === "/api/telemetry") {
1070
- try {
1071
- const { telemetryStreamer, TelemetryConfig } = await import("../utils/telemetry-streamer.js");
1072
- const status = telemetryStreamer.getStatus();
1073
- serveJson(res, 200, { success: true, data: { ...status, config: { ...TelemetryConfig, AUTH_TOKEN: TelemetryConfig.AUTH_TOKEN ? "***" : "" } } });
1074
- }
1075
- catch (e) {
1076
- serveJson(res, 500, { success: false, error: e.message });
1077
- }
1078
- return;
1079
- }
1080
- // Loop Detection Stats
1081
- if (pathname === "/api/loop-detector") {
1082
- try {
1083
- const { getAllLoopStats } = await import("../utils/loop-detector.js");
1084
- const agent = params.agent;
1085
- const stats = agent
1086
- ? (await import("../utils/loop-detector.js")).getLoopStats(agent)
1087
- : getAllLoopStats();
1088
- serveJson(res, 200, { success: true, data: stats });
1089
- }
1090
- catch (e) {
1091
- serveJson(res, 500, { success: false, error: e.message });
1092
- }
1093
- return;
1094
- }
1095
- // Clear Loop Cooldown
1096
- if (pathname.startsWith("/api/loop-detector/clear/") && method === "POST") {
1097
- const agent = decodeURIComponent(pathname.replace("/api/loop-detector/clear/", ""));
1098
- try {
1099
- const { clearCooldown } = await import("../utils/loop-detector.js");
1100
- const cleared = clearCooldown(agent);
1101
- serveJson(res, 200, { success: true, data: { agent, cleared } });
1102
- }
1103
- catch (e) {
1104
- serveJson(res, 500, { success: false, error: e.message });
1105
- }
1106
- return;
1107
- }
1108
- // GET Settings
1109
- if (pathname === "/api/settings" && method === "GET") {
1110
- try {
1111
- const { loadWebConfig } = await import("../utils/web-config.js");
1112
- const config = loadWebConfig();
1113
- const safeConfig = {
1114
- projectPath: config.projectPath,
1115
- activeProvider: config.activeProvider,
1116
- geminiApiKey: config.geminiApiKey ? "********" : "",
1117
- anthropicApiKey: config.anthropicApiKey ? "********" : "",
1118
- openaiApiKey: config.openaiApiKey ? "********" : "",
1119
- };
1120
- serveJson(res, 200, { success: true, data: safeConfig });
1121
- }
1122
- catch (e) {
1123
- serveJson(res, 500, { success: false, error: e.message });
1124
- }
1125
- return;
1126
- }
1127
- // POST Settings
1128
- if (pathname === "/api/settings" && method === "POST") {
1129
- try {
1130
- let body = "";
1131
- req.on("data", chunk => { body += chunk.toString(); });
1132
- req.on("end", async () => {
1133
- try {
1134
- const parsed = JSON.parse(body);
1135
- const { saveWebConfig } = await import("../utils/web-config.js");
1136
- const configToSave = {};
1137
- if (parsed.projectPath)
1138
- configToSave.projectPath = parsed.projectPath;
1139
- if (parsed.activeProvider)
1140
- configToSave.activeProvider = parsed.activeProvider;
1141
- if (parsed.geminiApiKey && parsed.geminiApiKey !== "********")
1142
- configToSave.geminiApiKey = parsed.geminiApiKey;
1143
- if (parsed.anthropicApiKey && parsed.anthropicApiKey !== "********")
1144
- configToSave.anthropicApiKey = parsed.anthropicApiKey;
1145
- if (parsed.openaiApiKey && parsed.openaiApiKey !== "********")
1146
- configToSave.openaiApiKey = parsed.openaiApiKey;
1147
- const saved = saveWebConfig(configToSave);
1148
- serveJson(res, 200, { success: true, message: "Settings saved successfully", projectPath: saved.projectPath });
1149
- }
1150
- catch (err) {
1151
- serveJson(res, 400, { success: false, error: err.message });
1152
- }
1153
- });
1154
- }
1155
- catch (e) {
1156
- serveJson(res, 500, { success: false, error: e.message });
1157
- }
1158
- return;
1159
- }
1160
- // POST Dispatch Task (Chat & Agent Selection)
1161
- if (pathname === "/api/chat/dispatch" && method === "POST") {
1162
- try {
1163
- let body = "";
1164
- req.on("data", chunk => { body += chunk.toString(); });
1165
- req.on("end", async () => {
1166
- try {
1167
- const parsed = JSON.parse(body);
1168
- const { projectPath, agent, message } = parsed;
1169
- if (!projectPath || !agent || !message) {
1170
- serveJson(res, 400, { success: false, error: "projectPath, agent, and message are required." });
1171
- return;
1172
- }
1173
- const { saveWebConfig, startOrchestratorLoop } = await import("../utils/web-config.js");
1174
- saveWebConfig({ projectPath });
1175
- const { asAgentID, asTraceID } = await import("../../shared/types.js");
1176
- const traceId = "T-" + Date.now().toString().slice(-6);
1177
- Storage.setMetadata("traceId", traceId);
1178
- Storage.setMetadata("phase", "PHASE_0");
1179
- const hermesMsg = {
1180
- timestamp: new Date().toISOString(),
1181
- from: asAgentID("web-user"),
1182
- to: asAgentID(agent),
1183
- category: "DELEGATION",
1184
- content: message,
1185
- traceId: asTraceID(traceId),
1186
- status: "PENDING",
1187
- priority: "HIGH",
1188
- requiresApproval: false
1189
- };
1190
- Storage.saveMessage(hermesMsg);
1191
- broadcastWS("message", hermesMsg);
1192
- broadcastWS("log", {
1193
- timestamp: new Date().toISOString(),
1194
- agent: "web-user",
1195
- action: "DISPATCH",
1196
- trace_id: traceId,
1197
- status: "SUCCESS",
1198
- summary: `Dispatched task to ${agent}: ${message.substring(0, 50)}...`
1199
- });
1200
- const runResult = startOrchestratorLoop();
1201
- serveJson(res, 200, {
1202
- success: true,
1203
- traceId,
1204
- message: `Task dispatched to ${agent}. ${runResult.message}`
1205
- });
1206
- }
1207
- catch (err) {
1208
- serveJson(res, 400, { success: false, error: err.message });
1209
- }
1210
- });
1211
- }
1212
- catch (e) {
1213
- serveJson(res, 500, { success: false, error: e.message });
1214
- }
1215
- return;
1216
- }
1217
- // GET Browse Directories for Folder Selection
1218
- if (pathname === "/api/project/browse-dirs" && method === "GET") {
1219
- try {
1220
- const fs = await import("fs");
1221
- const path = await import("path");
1222
- const os = await import("os");
1223
- let target = params.path ? String(params.path) : "";
1224
- if (!target) {
1225
- const { loadWebConfig } = await import("../utils/web-config.js");
1226
- target = loadWebConfig().projectPath || os.homedir();
1227
- }
1228
- target = path.resolve(target);
1229
- if (!fs.existsSync(target)) {
1230
- serveJson(res, 404, { success: false, error: "Path not found" });
1231
- return;
1232
- }
1233
- const stats = fs.statSync(target);
1234
- if (!stats.isDirectory()) {
1235
- serveJson(res, 400, { success: false, error: "Not a directory" });
1236
- return;
1237
- }
1238
- const files = fs.readdirSync(target);
1239
- const dirs = [];
1240
- for (const file of files) {
1241
- if (file.startsWith("."))
1242
- continue;
1243
- try {
1244
- const fullPath = path.join(target, file);
1245
- const isDir = fs.statSync(fullPath).isDirectory();
1246
- if (isDir) {
1247
- dirs.push({ name: file, path: fullPath });
1248
- }
1249
- }
1250
- catch {
1251
- // ignore
1252
- }
1253
- }
1254
- serveJson(res, 200, {
1255
- success: true,
1256
- currentPath: target,
1257
- parentPath: path.dirname(target) !== target ? path.dirname(target) : null,
1258
- dirs: dirs.sort((a, b) => a.name.localeCompare(b.name))
1259
- });
1260
- }
1261
- catch (e) {
1262
- serveJson(res, 500, { success: false, error: e.message });
1263
- }
1264
- return;
1265
- }
1266
- // GET Files List for Workspace Viewer
1267
- if (pathname === "/api/project/files" && method === "GET") {
1268
- try {
1269
- const fs = await import("fs");
1270
- const path = await import("path");
1271
- const { loadWebConfig } = await import("../utils/web-config.js");
1272
- const config = loadWebConfig();
1273
- const projectDir = config.projectPath;
1274
- const subDir = params.path ? String(params.path) : "";
1275
- let targetDir = projectDir;
1276
- if (subDir) {
1277
- targetDir = path.resolve(projectDir, subDir);
1278
- if (!targetDir.startsWith(projectDir)) {
1279
- serveJson(res, 403, { success: false, error: "Access denied" });
1280
- return;
1281
- }
1282
- }
1283
- if (!fs.existsSync(targetDir)) {
1284
- serveJson(res, 404, { success: false, error: "Project path not found" });
1285
- return;
1286
- }
1287
- const files = fs.readdirSync(targetDir);
1288
- const items = [];
1289
- for (const file of files) {
1290
- if (file === "node_modules" || file === ".git" || file === ".DS_Store")
1291
- continue;
1292
- try {
1293
- const fullPath = path.join(targetDir, file);
1294
- const stat = fs.statSync(fullPath);
1295
- items.push({
1296
- name: file,
1297
- isDir: stat.isDirectory(),
1298
- relativePath: path.relative(projectDir, fullPath)
1299
- });
1300
- }
1301
- catch {
1302
- // ignore
1303
- }
1304
- }
1305
- serveJson(res, 200, {
1306
- success: true,
1307
- currentPath: projectDir,
1308
- relativePath: path.relative(projectDir, targetDir),
1309
- items: items.sort((a, b) => {
1310
- if (a.isDir && !b.isDir)
1311
- return -1;
1312
- if (!a.isDir && b.isDir)
1313
- return 1;
1314
- return a.name.localeCompare(b.name);
1315
- })
1316
- });
1317
- }
1318
- catch (e) {
1319
- serveJson(res, 500, { success: false, error: e.message });
1320
- }
1321
- return;
1322
- }
1323
- // GET File Content
1324
- if (pathname === "/api/project/file-content" && method === "GET") {
1325
- try {
1326
- const fs = await import("fs");
1327
- const path = await import("path");
1328
- const { loadWebConfig } = await import("../utils/web-config.js");
1329
- const config = loadWebConfig();
1330
- const projectDir = config.projectPath;
1331
- const filePath = params.path ? String(params.path) : "";
1332
- if (!filePath) {
1333
- serveJson(res, 400, { success: false, error: "path parameter is required" });
1334
- return;
1335
- }
1336
- const absoluteFilePath = path.resolve(projectDir, filePath);
1337
- if (!absoluteFilePath.startsWith(projectDir)) {
1338
- serveJson(res, 403, { success: false, error: "Access denied" });
1339
- return;
1340
- }
1341
- if (!fs.existsSync(absoluteFilePath) || fs.statSync(absoluteFilePath).isDirectory()) {
1342
- serveJson(res, 404, { success: false, error: "File not found" });
1343
- return;
1344
- }
1345
- const size = fs.statSync(absoluteFilePath).size;
1346
- if (size > 2 * 1024 * 1024) {
1347
- serveJson(res, 400, { success: false, error: "File is too large (max 2MB)" });
1348
- return;
1349
- }
1350
- const content = fs.readFileSync(absoluteFilePath, "utf8");
1351
- serveJson(res, 200, {
1352
- success: true,
1353
- path: filePath,
1354
- content
1355
- });
1356
- }
1357
- catch (e) {
1358
- serveJson(res, 500, { success: false, error: e.message });
1359
- }
1360
- return;
1361
- }
1362
- // Orchestrator Status
1363
- if (pathname === "/api/orchestrator/status" && method === "GET") {
1364
- try {
1365
- const { getOrchestratorState } = await import("../utils/web-config.js");
1366
- serveJson(res, 200, { success: true, data: getOrchestratorState() });
1367
- }
1368
- catch (e) {
1369
- serveJson(res, 500, { success: false, error: e.message });
1370
- }
1371
- return;
1372
- }
1373
- // Orchestrator Start
1374
- if (pathname === "/api/orchestrator/start" && method === "POST") {
1375
- try {
1376
- const { startOrchestratorLoop } = await import("../utils/web-config.js");
1377
- const result = startOrchestratorLoop();
1378
- if (result.success) {
1379
- broadcastWS("orchestrator_state", { running: true });
1380
- serveJson(res, 200, { success: true, message: result.message });
1381
- }
1382
- else {
1383
- serveJson(res, 400, { success: false, error: result.message });
1384
- }
1385
- }
1386
- catch (e) {
1387
- serveJson(res, 500, { success: false, error: e.message });
1388
- }
1389
- return;
1390
- }
1391
- // Orchestrator Stop
1392
- if (pathname === "/api/orchestrator/stop" && method === "POST") {
1393
- try {
1394
- const { stopOrchestratorLoop } = await import("../utils/web-config.js");
1395
- const result = stopOrchestratorLoop();
1396
- if (result.success) {
1397
- broadcastWS("orchestrator_state", { running: false });
1398
- serveJson(res, 200, { success: true, message: result.message });
1399
- }
1400
- else {
1401
- serveJson(res, 400, { success: false, error: result.message });
1402
- }
1403
- }
1404
- catch (e) {
1405
- serveJson(res, 500, { success: false, error: e.message });
1406
- }
1407
- return;
1408
- }
1409
- // GET Token Budget Config
1410
- if (pathname === "/api/token-budget/config" && method === "GET") {
1411
- try {
1412
- const { getTokenBudgetConfig } = await import("../utils/context-optimizer.js");
1413
- const config = getTokenBudgetConfig();
1414
- serveJson(res, 200, { success: true, data: config });
1415
- }
1416
- catch (e) {
1417
- serveJson(res, 500, { success: false, error: e.message });
1418
- }
1419
- return;
1420
- }
1421
- // UPDATE Token Budget Config
1422
- if (pathname === "/api/token-budget/config/update" && method === "POST") {
1423
- try {
1424
- let body = "";
1425
- req.on("data", chunk => { body += chunk.toString(); });
1426
- req.on("end", async () => {
1427
- try {
1428
- const parsed = JSON.parse(body);
1429
- const { updateTokenBudgetConfig } = await import("../utils/context-optimizer.js");
1430
- updateTokenBudgetConfig(parsed);
1431
- serveJson(res, 200, { success: true, message: "Token budget configuration updated successfully." });
1432
- }
1433
- catch (err) {
1434
- serveJson(res, 400, { success: false, error: err.message });
1435
- }
1436
- });
1437
- }
1438
- catch (e) {
1439
- serveJson(res, 500, { success: false, error: e.message });
1440
- }
1441
- return;
1442
- }
1443
- // RESET Agent Token Budgets
1444
- if (pathname === "/api/token-budget/reset" && method === "POST") {
1445
- try {
1446
- const { resetTokenBudgets } = await import("../utils/context-optimizer.js");
1447
- resetTokenBudgets();
1448
- serveJson(res, 200, { success: true, message: "All agent token budgets reset successfully." });
1449
- }
1450
- catch (e) {
1451
- serveJson(res, 500, { success: false, error: e.message });
1452
- }
1453
- return;
1454
- }
1455
- // Budget / FinOps Status
1456
- if (pathname === "/api/finops") {
1457
- try {
1458
- const { budgetManager } = await import("../utils/finops.js");
1459
- const state = budgetManager.getState();
1460
- serveJson(res, 200, { success: true, data: state });
1461
- }
1462
- catch (e) {
1463
- serveJson(res, 500, { success: false, error: e.message });
1464
- }
1465
- return;
1466
- }
1467
- // Budget Check for an agent
1468
- if (pathname === "/api/finops/check") {
1469
- try {
1470
- const agent = params.agent || "default";
1471
- const { budgetManager } = await import("../utils/finops.js");
1472
- const result = budgetManager.checkBudget(agent);
1473
- serveJson(res, 200, { success: true, data: result });
1474
- }
1475
- catch (e) {
1476
- serveJson(res, 500, { success: false, error: e.message });
1477
- }
1478
- return;
1479
- }
1480
- // Budget Reset
1481
- if (pathname === "/api/finops/reset" && method === "POST") {
1482
- try {
1483
- const { budgetManager } = await import("../utils/finops.js");
1484
- budgetManager.resetPeriod();
1485
- serveJson(res, 200, { success: true, message: "Budget period reset." });
1486
- }
1487
- catch (e) {
1488
- serveJson(res, 500, { success: false, error: e.message });
1489
- }
1490
- return;
1491
- }
1492
- // License Scanner
1493
- if (pathname === "/api/license") {
1494
- try {
1495
- const { scanForLicenses, getLicenseSeveritySummary, LicenseScannerConfig } = await import("../utils/license-scanner.js");
1496
- const filePath = params.path || "";
1497
- const content = params.content || "";
1498
- const matches = filePath && content ? scanForLicenses(filePath, content) : [];
1499
- const summary = matches.length > 0 ? getLicenseSeveritySummary(matches) : null;
1500
- serveJson(res, 200, {
1501
- success: true, data: {
1502
- matches,
1503
- summary,
1504
- config: LicenseScannerConfig,
1505
- }
1506
- });
1507
- }
1508
- catch (e) {
1509
- serveJson(res, 500, { success: false, error: e.message });
1510
- }
1511
- return;
1512
- }
1513
- // Auto-Rollback Stats
1514
- if (pathname === "/api/rollback") {
1515
- try {
1516
- const { AutoRollbackEngine } = await import("../utils/auto-rollback.js");
1517
- const stats = AutoRollbackEngine.getSnapshotStats();
1518
- serveJson(res, 200, { success: true, data: stats });
1519
- }
1520
- catch (e) {
1521
- serveJson(res, 500, { success: false, error: e.message });
1522
- }
1523
- return;
1524
- }
1525
- // All Governance Stats (combined endpoint)
1526
- if (pathname === "/api/governance") {
1527
- try {
1528
- const { getAllLoopStats } = await import("../utils/loop-detector.js");
1529
- const { AutoRollbackEngine } = await import("../utils/auto-rollback.js");
1530
- const { budgetManager } = await import("../utils/finops.js");
1531
- serveJson(res, 200, {
1532
- success: true, data: {
1533
- loopDetection: getAllLoopStats(),
1534
- rollback: AutoRollbackEngine.getSnapshotStats(),
1535
- budget: budgetManager.getState(),
1536
- telemetry: (await import("../utils/telemetry-streamer.js")).telemetryStreamer.getStatus(),
1537
- }
1538
- });
1539
- }
1540
- catch (e) {
1541
- serveJson(res, 500, { success: false, error: e.message });
1542
- }
1543
- return;
1544
126
  }
1545
127
  // SSE (legacy support)
1546
128
  if (pathname === "/api/events") {