ft-scout 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/.firebase/hosting.d2Vi.cache +9 -0
  2. package/.firebaserc +14 -0
  3. package/AUTONOMOUS_FEATURES.md +192 -0
  4. package/README.md +482 -32
  5. package/bin/ft.js +2 -0
  6. package/bin/src/commands/agent.d.ts.map +1 -0
  7. package/bin/src/commands/agent.js +1230 -0
  8. package/bin/src/commands/agent.js.map +1 -0
  9. package/bin/src/commands/audit.d.ts.map +1 -0
  10. package/bin/src/commands/audit.js +104 -0
  11. package/bin/src/commands/audit.js.map +1 -0
  12. package/bin/src/commands/authCommand.d.ts.map +1 -0
  13. package/bin/src/commands/authCommand.js +38 -0
  14. package/bin/src/commands/authCommand.js.map +1 -0
  15. package/bin/src/commands/benchmark.d.ts.map +1 -0
  16. package/bin/src/commands/benchmark.js +135 -0
  17. package/bin/src/commands/benchmark.js.map +1 -0
  18. package/bin/src/commands/brief.d.ts.map +1 -1
  19. package/bin/src/commands/brief.js +28 -19
  20. package/bin/src/commands/brief.js.map +1 -1
  21. package/bin/src/commands/configCommand.d.ts.map +1 -0
  22. package/bin/src/commands/configCommand.js +53 -0
  23. package/bin/src/commands/configCommand.js.map +1 -0
  24. package/bin/src/commands/dashboard.d.ts.map +1 -0
  25. package/bin/src/commands/dashboard.js +41 -0
  26. package/bin/src/commands/dashboard.js.map +1 -0
  27. package/bin/src/commands/history.d.ts.map +1 -0
  28. package/bin/src/commands/history.js +20 -0
  29. package/bin/src/commands/history.js.map +1 -0
  30. package/bin/src/commands/init.d.ts.map +1 -1
  31. package/bin/src/commands/init.js +411 -78
  32. package/bin/src/commands/init.js.map +1 -1
  33. package/bin/src/commands/login.d.ts.map +1 -0
  34. package/bin/src/commands/login.js +190 -0
  35. package/bin/src/commands/login.js.map +1 -0
  36. package/bin/src/commands/logout.d.ts.map +1 -0
  37. package/bin/src/commands/logout.js +15 -0
  38. package/bin/src/commands/logout.js.map +1 -0
  39. package/bin/src/commands/mcp.d.ts.map +1 -0
  40. package/bin/src/commands/mcp.js +393 -0
  41. package/bin/src/commands/mcp.js.map +1 -0
  42. package/bin/src/commands/owners.js +6 -6
  43. package/bin/src/commands/owners.js.map +1 -1
  44. package/bin/src/commands/plan.d.ts.map +1 -0
  45. package/bin/src/commands/plan.js +152 -0
  46. package/bin/src/commands/plan.js.map +1 -0
  47. package/bin/src/commands/quota.d.ts.map +1 -0
  48. package/bin/src/commands/quota.js +32 -0
  49. package/bin/src/commands/quota.js.map +1 -0
  50. package/bin/src/commands/recommend.d.ts.map +1 -0
  51. package/bin/src/commands/recommend.js +46 -0
  52. package/bin/src/commands/recommend.js.map +1 -0
  53. package/bin/src/commands/risky.d.ts.map +1 -1
  54. package/bin/src/commands/risky.js +235 -39
  55. package/bin/src/commands/risky.js.map +1 -1
  56. package/bin/src/commands/setup.d.ts.map +1 -1
  57. package/bin/src/commands/setup.js +411 -16
  58. package/bin/src/commands/setup.js.map +1 -1
  59. package/bin/src/commands/signup.d.ts.map +1 -0
  60. package/bin/src/commands/signup.js +118 -0
  61. package/bin/src/commands/signup.js.map +1 -0
  62. package/bin/src/commands/undo.d.ts.map +1 -0
  63. package/bin/src/commands/undo.js +59 -0
  64. package/bin/src/commands/undo.js.map +1 -0
  65. package/bin/src/commands/web.d.ts.map +1 -0
  66. package/bin/src/commands/web.js +90 -0
  67. package/bin/src/commands/web.js.map +1 -0
  68. package/bin/src/discovery/config.d.ts.map +1 -0
  69. package/bin/src/discovery/config.js +41 -0
  70. package/bin/src/discovery/config.js.map +1 -0
  71. package/bin/src/discovery/display.d.ts.map +1 -0
  72. package/bin/src/discovery/display.js +57 -0
  73. package/bin/src/discovery/display.js.map +1 -0
  74. package/bin/src/discovery/messages.d.ts.map +1 -0
  75. package/bin/src/discovery/messages.js +7 -0
  76. package/bin/src/discovery/messages.js.map +1 -0
  77. package/bin/src/discovery/signals.d.ts.map +1 -0
  78. package/bin/src/discovery/signals.js +33 -0
  79. package/bin/src/discovery/signals.js.map +1 -0
  80. package/bin/src/engine/actionVerifier.d.ts.map +1 -0
  81. package/bin/src/engine/actionVerifier.js +283 -0
  82. package/bin/src/engine/actionVerifier.js.map +1 -0
  83. package/bin/src/engine/agentEngine.d.ts.map +1 -0
  84. package/bin/src/engine/agentEngine.js +2188 -0
  85. package/bin/src/engine/agentEngine.js.map +1 -0
  86. package/bin/src/engine/agentState.d.ts.map +1 -0
  87. package/bin/src/engine/agentState.js +381 -0
  88. package/bin/src/engine/agentState.js.map +1 -0
  89. package/bin/src/engine/browser/browserController.d.ts.map +1 -0
  90. package/bin/src/engine/browser/browserController.js +737 -0
  91. package/bin/src/engine/browser/browserController.js.map +1 -0
  92. package/bin/src/engine/browser/index.d.ts.map +1 -0
  93. package/bin/src/engine/browser/index.js +2 -0
  94. package/bin/src/engine/browser/index.js.map +1 -0
  95. package/bin/src/engine/computer/computerController.d.ts.map +1 -0
  96. package/bin/src/engine/computer/computerController.js +773 -0
  97. package/bin/src/engine/computer/computerController.js.map +1 -0
  98. package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
  99. package/bin/src/engine/computer/computerDiagnostic.js +71 -0
  100. package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
  101. package/bin/src/engine/computer/index.d.ts.map +1 -0
  102. package/bin/src/engine/computer/index.js +4 -0
  103. package/bin/src/engine/computer/index.js.map +1 -0
  104. package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
  105. package/bin/src/engine/computer/mockComputer.js +260 -0
  106. package/bin/src/engine/computer/mockComputer.js.map +1 -0
  107. package/bin/src/engine/computer/scoutDriver.cs +1161 -0
  108. package/bin/src/engine/computer/scout_driver.exe +0 -0
  109. package/bin/src/engine/git.d.ts.map +1 -1
  110. package/bin/src/engine/git.js +71 -0
  111. package/bin/src/engine/git.js.map +1 -1
  112. package/bin/src/engine/llm.d.ts.map +1 -1
  113. package/bin/src/engine/llm.js +1272 -23
  114. package/bin/src/engine/llm.js.map +1 -1
  115. package/bin/src/engine/planner.d.ts.map +1 -0
  116. package/bin/src/engine/planner.js +321 -0
  117. package/bin/src/engine/planner.js.map +1 -0
  118. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
  119. package/bin/src/engine/runtime/agentRuntime.js +1258 -0
  120. package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
  121. package/bin/src/engine/runtime/index.d.ts.map +1 -0
  122. package/bin/src/engine/runtime/index.js +2 -0
  123. package/bin/src/engine/runtime/index.js.map +1 -0
  124. package/bin/src/engine/safety.d.ts.map +1 -0
  125. package/bin/src/engine/safety.js +316 -0
  126. package/bin/src/engine/safety.js.map +1 -0
  127. package/bin/src/engine/scoutAudioRecorder.cs +215 -0
  128. package/bin/src/engine/scout_audio_recorder.exe +0 -0
  129. package/bin/src/engine/toolRegistry.d.ts.map +1 -0
  130. package/bin/src/engine/toolRegistry.js +521 -0
  131. package/bin/src/engine/toolRegistry.js.map +1 -0
  132. package/bin/src/engine/verifier.d.ts.map +1 -0
  133. package/bin/src/engine/verifier.js +414 -0
  134. package/bin/src/engine/verifier.js.map +1 -0
  135. package/bin/src/engine/voiceEngine.d.ts.map +1 -0
  136. package/bin/src/engine/voiceEngine.js +474 -0
  137. package/bin/src/engine/voiceEngine.js.map +1 -0
  138. package/bin/src/engine/web.d.ts.map +1 -0
  139. package/bin/src/engine/web.js +161 -0
  140. package/bin/src/engine/web.js.map +1 -0
  141. package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
  142. package/bin/src/engine/workspace/agentWorkspace.js +505 -0
  143. package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
  144. package/bin/src/engine/workspace/index.d.ts.map +1 -0
  145. package/bin/src/engine/workspace/index.js +2 -0
  146. package/bin/src/engine/workspace/index.js.map +1 -0
  147. package/bin/src/index.js +365 -23
  148. package/bin/src/index.js.map +1 -1
  149. package/bin/src/mcp/client.d.ts.map +1 -0
  150. package/bin/src/mcp/client.js +127 -0
  151. package/bin/src/mcp/client.js.map +1 -0
  152. package/bin/src/mcp/discovery.d.ts.map +1 -0
  153. package/bin/src/mcp/discovery.js +91 -0
  154. package/bin/src/mcp/discovery.js.map +1 -0
  155. package/bin/src/mcp/health.d.ts.map +1 -0
  156. package/bin/src/mcp/health.js +44 -0
  157. package/bin/src/mcp/health.js.map +1 -0
  158. package/bin/src/mcp/index.d.ts.map +1 -0
  159. package/bin/src/mcp/index.js +15 -0
  160. package/bin/src/mcp/index.js.map +1 -0
  161. package/bin/src/mcp/manager.d.ts.map +1 -0
  162. package/bin/src/mcp/manager.js +648 -0
  163. package/bin/src/mcp/manager.js.map +1 -0
  164. package/bin/src/mcp/permissions.d.ts.map +1 -0
  165. package/bin/src/mcp/permissions.js +50 -0
  166. package/bin/src/mcp/permissions.js.map +1 -0
  167. package/bin/src/mcp/persistence.d.ts.map +1 -0
  168. package/bin/src/mcp/persistence.js +171 -0
  169. package/bin/src/mcp/persistence.js.map +1 -0
  170. package/bin/src/mcp/recovery.d.ts.map +1 -0
  171. package/bin/src/mcp/recovery.js +75 -0
  172. package/bin/src/mcp/recovery.js.map +1 -0
  173. package/bin/src/mcp/telemetry.d.ts.map +1 -0
  174. package/bin/src/mcp/telemetry.js +82 -0
  175. package/bin/src/mcp/telemetry.js.map +1 -0
  176. package/bin/src/mcp/toolRouter.d.ts.map +1 -0
  177. package/bin/src/mcp/toolRouter.js +127 -0
  178. package/bin/src/mcp/toolRouter.js.map +1 -0
  179. package/bin/src/mcp/transport.d.ts.map +1 -0
  180. package/bin/src/mcp/transport.js +328 -0
  181. package/bin/src/mcp/transport.js.map +1 -0
  182. package/bin/src/mcp/types.d.ts.map +1 -0
  183. package/bin/src/mcp/types.js +6 -0
  184. package/bin/src/mcp/types.js.map +1 -0
  185. package/bin/src/server/server.d.ts.map +1 -0
  186. package/bin/src/server/server.js +989 -0
  187. package/bin/src/server/server.js.map +1 -0
  188. package/bin/src/utils/admin.d.ts.map +1 -0
  189. package/bin/src/utils/admin.js +231 -0
  190. package/bin/src/utils/admin.js.map +1 -0
  191. package/bin/src/utils/auditLogger.d.ts.map +1 -0
  192. package/bin/src/utils/auditLogger.js +70 -0
  193. package/bin/src/utils/auditLogger.js.map +1 -0
  194. package/bin/src/utils/auth.d.ts.map +1 -0
  195. package/bin/src/utils/auth.js +679 -0
  196. package/bin/src/utils/auth.js.map +1 -0
  197. package/bin/src/utils/branding.d.ts.map +1 -0
  198. package/bin/src/utils/branding.js +14 -0
  199. package/bin/src/utils/branding.js.map +1 -0
  200. package/bin/src/utils/config.d.ts.map +1 -1
  201. package/bin/src/utils/config.js +124 -3
  202. package/bin/src/utils/config.js.map +1 -1
  203. package/bin/src/utils/email.d.ts.map +1 -0
  204. package/bin/src/utils/email.js +267 -0
  205. package/bin/src/utils/email.js.map +1 -0
  206. package/bin/src/utils/firebaseAuth.d.ts.map +1 -0
  207. package/bin/src/utils/firebaseAuth.js +174 -0
  208. package/bin/src/utils/firebaseAuth.js.map +1 -0
  209. package/bin/src/utils/holoseneAuth.d.ts.map +1 -0
  210. package/bin/src/utils/holoseneAuth.js +307 -0
  211. package/bin/src/utils/holoseneAuth.js.map +1 -0
  212. package/bin/src/utils/language.d.ts.map +1 -0
  213. package/bin/src/utils/language.js +181 -0
  214. package/bin/src/utils/language.js.map +1 -0
  215. package/bin/src/utils/markdown.d.ts.map +1 -0
  216. package/bin/src/utils/markdown.js +215 -0
  217. package/bin/src/utils/markdown.js.map +1 -0
  218. package/bin/src/utils/payment.d.ts.map +1 -0
  219. package/bin/src/utils/payment.js +290 -0
  220. package/bin/src/utils/payment.js.map +1 -0
  221. package/bin/src/utils/pricing.d.ts.map +1 -0
  222. package/bin/src/utils/pricing.js +165 -0
  223. package/bin/src/utils/pricing.js.map +1 -0
  224. package/bin/src/utils/razorpay.d.ts.map +1 -0
  225. package/bin/src/utils/razorpay.js +144 -0
  226. package/bin/src/utils/razorpay.js.map +1 -0
  227. package/bin/src/utils/session.d.ts.map +1 -0
  228. package/bin/src/utils/session.js +82 -0
  229. package/bin/src/utils/session.js.map +1 -0
  230. package/bin/src/utils/updateCheck.d.ts.map +1 -0
  231. package/bin/src/utils/updateCheck.js +108 -0
  232. package/bin/src/utils/updateCheck.js.map +1 -0
  233. package/downloads/invoice-2026-03.pdf +1 -0
  234. package/firebase.json +74 -0
  235. package/package.json +49 -46
  236. package/web/ScoutFavicon.png +0 -0
  237. package/web/app.js +3944 -0
  238. package/web/styles.css +3081 -0
  239. package/.ft/context.json +0 -18
  240. package/email-template.html +0 -223
  241. package/email-template.txt +0 -45
  242. package/scripts/send-brevo-campaign.cjs +0 -167
  243. package/scripts/send-email.js +0 -77
@@ -0,0 +1,989 @@
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { exec } from 'child_process';
5
+ import { fileURLToPath } from 'url';
6
+ import { loadAuthConfig, saveAuthConfig, clearAuthConfig, isLoggedIn, getTierSessionLimit, canStartNewSession } from '../utils/auth.js';
7
+ import { getPublicPaymentConfig, createCheckoutSession, verifyAndApplyUpgrade } from '../utils/payment.js';
8
+ import { createRazorpayOrder, verifyRazorpaySignature, getRazorpayKeyId } from '../utils/razorpay.js';
9
+ import { getAuditLogs, logAuditEvent } from '../utils/auditLogger.js';
10
+ import { getPricingConfig, saveFullPricingConfig, updatePricingTier, resetPricingToDefaults } from '../utils/pricing.js';
11
+ import { verifyAdminAuth, generateAdminToken, verifyAdminToken, getAdminUsersList, updateAdminUser, deleteAdminUser, getAdminDashboardStats, isWhitelistedAdminEmail, getAdminSecretKey, } from '../utils/admin.js';
12
+ import dotenv from 'dotenv';
13
+ import { TaskStateManager } from '../engine/agentState.js';
14
+ import { ApprovalManager } from '../engine/safety.js';
15
+ import { AgentExecutionLoop } from '../engine/agentEngine.js';
16
+ import { getMCPManager, MCPTelemetry } from '../mcp/index.js';
17
+ let activeAgentLoop = null;
18
+ const serverStateManager = new TaskStateManager(process.cwd());
19
+ const DEFAULT_PORT = 9120;
20
+ function getPackageRoot() {
21
+ let currentDir = path.dirname(fileURLToPath(import.meta.url));
22
+ while (currentDir !== path.dirname(currentDir)) {
23
+ if (fs.existsSync(path.join(currentDir, 'package.json')) && fs.existsSync(path.join(currentDir, 'web', 'index.html'))) {
24
+ return currentDir;
25
+ }
26
+ currentDir = path.dirname(currentDir);
27
+ }
28
+ return process.cwd();
29
+ }
30
+ dotenv.config({ path: path.join(getPackageRoot(), '.env'), quiet: true });
31
+ dotenv.config({ quiet: true });
32
+ function getWebDir() {
33
+ const pkgRoot = getPackageRoot();
34
+ const possiblePaths = [
35
+ path.join(pkgRoot, 'web'),
36
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../web'),
37
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../web'),
38
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../web'),
39
+ path.join(process.cwd(), 'web'),
40
+ ];
41
+ for (const p of possiblePaths) {
42
+ if (fs.existsSync(p) && fs.existsSync(path.join(p, 'index.html'))) {
43
+ return p;
44
+ }
45
+ }
46
+ return path.join(pkgRoot, 'web');
47
+ }
48
+ function getMimeType(filePath) {
49
+ const ext = path.extname(filePath).toLowerCase();
50
+ switch (ext) {
51
+ case '.html': return 'text/html; charset=utf-8';
52
+ case '.css': return 'text/css; charset=utf-8';
53
+ case '.js': return 'application/javascript; charset=utf-8';
54
+ case '.json': return 'application/json; charset=utf-8';
55
+ case '.png': return 'image/png';
56
+ case '.jpg':
57
+ case '.jpeg': return 'image/jpeg';
58
+ case '.svg': return 'image/svg+xml';
59
+ case '.ico': return 'image/x-icon';
60
+ default: return 'text/plain; charset=utf-8';
61
+ }
62
+ }
63
+ export function startDashboardServer(port = DEFAULT_PORT) {
64
+ return new Promise((resolve, reject) => {
65
+ const webDir = getWebDir();
66
+ const server = http.createServer(async (req, res) => {
67
+ // Enable CORS for cross-origin local dashboard sync (including scout.frontterrain.com)
68
+ res.setHeader('Access-Control-Allow-Origin', '*');
69
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
70
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
71
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
72
+ if (req.method === 'OPTIONS') {
73
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
74
+ res.writeHead(204);
75
+ res.end();
76
+ return;
77
+ }
78
+ const reqUrl = req.url || '/';
79
+ const parsedUrl = new URL(reqUrl, `http://localhost:${port}`);
80
+ const pathname = parsedUrl.pathname;
81
+ // API Endpoints
82
+ if (pathname.startsWith('/api/')) {
83
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
84
+ // GET /api/status - Live CLI & Auth state
85
+ if (pathname === '/api/status' && req.method === 'GET') {
86
+ const authConfig = loadAuthConfig();
87
+ const scanReportPath = path.join(process.cwd(), '.ft', 'scan-report.json');
88
+ let scanReport = null;
89
+ if (fs.existsSync(scanReportPath)) {
90
+ try {
91
+ scanReport = JSON.parse(fs.readFileSync(scanReportPath, 'utf-8'));
92
+ }
93
+ catch { }
94
+ }
95
+ const tier = (authConfig.currentTier || 'free').toLowerCase();
96
+ const sessionLimit = getTierSessionLimit(tier);
97
+ const isInceptionTier = tier === 'pro' || tier === 'enterprise';
98
+ res.writeHead(200);
99
+ res.end(JSON.stringify({
100
+ status: 'ok',
101
+ serverConnected: true,
102
+ isLoggedIn: isLoggedIn(),
103
+ auth: authConfig,
104
+ repoPath: process.cwd(),
105
+ repoName: path.basename(process.cwd()),
106
+ scanReport,
107
+ sessionSlots: {
108
+ used: 1,
109
+ limit: sessionLimit === Infinity ? 'Unlimited' : sessionLimit,
110
+ maxSlots: sessionLimit,
111
+ },
112
+ nvidiaInception: {
113
+ enabled: isInceptionTier,
114
+ badge: isInceptionTier ? '⚡ NVIDIA Inception Engine Active' : 'Standard Pipeline',
115
+ status: isInceptionTier ? 'Active' : 'Inactive',
116
+ },
117
+ }));
118
+ return;
119
+ }
120
+ // POST /api/save-auth - Save authentication state to ~/.ft/auth.json
121
+ if (pathname === '/api/save-auth' && req.method === 'POST') {
122
+ let body = '';
123
+ req.on('data', chunk => body += chunk);
124
+ req.on('end', () => {
125
+ try {
126
+ const data = JSON.parse(body);
127
+ const existing = loadAuthConfig();
128
+ const updated = {
129
+ token: data.token || existing.token,
130
+ refreshToken: data.refreshToken || existing.refreshToken,
131
+ user: data.user || existing.user,
132
+ tenantId: data.tenantId || existing.tenantId || 'ft-scout-auth-gc2v5',
133
+ provider: data.provider || existing.provider || 'firebase',
134
+ loggedInAt: new Date().toISOString(),
135
+ creditsUsed: typeof data.creditsUsed === 'number' ? data.creditsUsed : (existing.creditsUsed ?? 0),
136
+ creditsTotal: typeof data.creditsTotal === 'number' ? data.creditsTotal : (existing.creditsTotal ?? 100),
137
+ currentTier: data.currentTier || existing.currentTier || 'free',
138
+ renewAt: data.renewAt || existing.renewAt,
139
+ cancelAtPeriodEnd: typeof data.cancelAtPeriodEnd === 'boolean' ? data.cancelAtPeriodEnd : existing.cancelAtPeriodEnd,
140
+ pendingDowngrade: data.pendingDowngrade !== undefined ? data.pendingDowngrade : existing.pendingDowngrade,
141
+ paidTier: data.paidTier !== undefined ? data.paidTier : existing.paidTier,
142
+ byokEnabled: typeof data.byokEnabled === 'boolean' ? data.byokEnabled : existing.byokEnabled,
143
+ customLlmProvider: data.customLlmProvider || existing.customLlmProvider || 'nvidia',
144
+ customApiKey: data.customApiKey !== undefined ? data.customApiKey : existing.customApiKey,
145
+ customBaseUrl: data.customBaseUrl !== undefined ? data.customBaseUrl : existing.customBaseUrl,
146
+ };
147
+ saveAuthConfig(updated);
148
+ res.writeHead(200);
149
+ res.end(JSON.stringify({ success: true, auth: updated }));
150
+ }
151
+ catch (err) {
152
+ res.writeHead(400);
153
+ res.end(JSON.stringify({ success: false, error: err.message || 'Invalid JSON' }));
154
+ }
155
+ });
156
+ return;
157
+ }
158
+ // POST /api/logout - Clear CLI session
159
+ if (pathname === '/api/logout' && req.method === 'POST') {
160
+ clearAuthConfig();
161
+ res.writeHead(200);
162
+ res.end(JSON.stringify({ success: true }));
163
+ return;
164
+ }
165
+ // GET /api/payment/config - Return active gateway info & available plans
166
+ if (pathname === '/api/payment/config' && req.method === 'GET') {
167
+ const paymentConfig = getPublicPaymentConfig();
168
+ res.writeHead(200);
169
+ res.end(JSON.stringify({ success: true, ...paymentConfig }));
170
+ return;
171
+ }
172
+ // POST /api/payment/create-checkout - Initiate payment session
173
+ if (pathname === '/api/payment/create-checkout' && req.method === 'POST') {
174
+ let body = '';
175
+ req.on('data', chunk => body += chunk);
176
+ req.on('end', async () => {
177
+ try {
178
+ const data = JSON.parse(body);
179
+ const auth = loadAuthConfig();
180
+ const checkout = await createCheckoutSession({
181
+ tier: data.tier,
182
+ billingCycle: data.billingCycle,
183
+ returnUrl: data.returnUrl,
184
+ cancelUrl: data.cancelUrl,
185
+ userEmail: auth.user?.email || data.email,
186
+ });
187
+ res.writeHead(200);
188
+ res.end(JSON.stringify(checkout));
189
+ }
190
+ catch (err) {
191
+ res.writeHead(400);
192
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to create checkout session' }));
193
+ }
194
+ });
195
+ return;
196
+ }
197
+ // POST /api/payment/verify - Verify payment & upgrade subscription in auth.json
198
+ if (pathname === '/api/payment/verify' && req.method === 'POST') {
199
+ let body = '';
200
+ req.on('data', chunk => body += chunk);
201
+ req.on('end', async () => {
202
+ try {
203
+ const data = JSON.parse(body);
204
+ const result = await verifyAndApplyUpgrade({
205
+ sessionId: data.sessionId,
206
+ orderId: data.orderId,
207
+ token: data.token,
208
+ tier: data.tier,
209
+ billingCycle: data.billingCycle,
210
+ });
211
+ res.writeHead(200);
212
+ res.end(JSON.stringify(result));
213
+ }
214
+ catch (err) {
215
+ res.writeHead(400);
216
+ res.end(JSON.stringify({ success: false, error: err.message || 'Payment verification failed' }));
217
+ }
218
+ });
219
+ return;
220
+ }
221
+ // GET /api/razorpay/config - Return Razorpay public Key ID
222
+ if ((pathname === '/api/razorpay/config' || pathname === '/api/razorpay-config') && req.method === 'GET') {
223
+ res.writeHead(200);
224
+ res.end(JSON.stringify({
225
+ success: true,
226
+ key_id: getRazorpayKeyId(),
227
+ currency: 'INR',
228
+ }));
229
+ return;
230
+ }
231
+ // STEP 1: POST /api/create-order - Create Razorpay Order
232
+ if ((pathname === '/api/create-order' || pathname === '/api/razorpay/create-order') && req.method === 'POST') {
233
+ let body = '';
234
+ req.on('data', chunk => body += chunk);
235
+ req.on('end', async () => {
236
+ try {
237
+ const data = JSON.parse(body || '{}');
238
+ let amount = data.amount;
239
+ if (!amount && data.tier) {
240
+ const pricing = getPricingConfig();
241
+ const isAnnual = data.billingCycle === 'annual';
242
+ const tierConfig = pricing[data.tier];
243
+ if (tierConfig) {
244
+ const inr = isAnnual ? tierConfig.annualPriceINR : tierConfig.monthlyPriceINR;
245
+ amount = inr * 100;
246
+ }
247
+ }
248
+ const order = await createRazorpayOrder({
249
+ amount,
250
+ currency: data.currency || 'INR',
251
+ receipt: data.receipt,
252
+ notes: { ...data.notes, tier: data.tier },
253
+ });
254
+ res.writeHead(200);
255
+ res.end(JSON.stringify(order));
256
+ }
257
+ catch (err) {
258
+ const status = err.statusCode || 500;
259
+ res.writeHead(status);
260
+ res.end(JSON.stringify({
261
+ success: false,
262
+ error: err.message || 'Failed to create Razorpay order',
263
+ code: status === 401 ? 'AUTH_FAILED' : (status === 400 ? 'VALIDATION_ERROR' : 'RAZORPAY_API_ERROR'),
264
+ }));
265
+ }
266
+ });
267
+ return;
268
+ }
269
+ // STEP 3: POST /api/verify-payment - Verify Razorpay Payment Signature
270
+ if ((pathname === '/api/verify-payment' || pathname === '/api/razorpay/verify-payment') && req.method === 'POST') {
271
+ let body = '';
272
+ req.on('data', chunk => body += chunk);
273
+ req.on('end', () => {
274
+ try {
275
+ const data = JSON.parse(body || '{}');
276
+ const result = verifyRazorpaySignature({
277
+ order_id: data.order_id || data.razorpay_order_id,
278
+ payment_id: data.payment_id || data.razorpay_payment_id,
279
+ signature: data.signature || data.razorpay_signature,
280
+ tier: data.tier,
281
+ userEmail: data.userEmail,
282
+ });
283
+ res.writeHead(200);
284
+ res.end(JSON.stringify(result));
285
+ }
286
+ catch (err) {
287
+ const status = err.statusCode || 400;
288
+ res.writeHead(status);
289
+ res.end(JSON.stringify({
290
+ success: false,
291
+ error: err.message || 'Payment verification failed',
292
+ }));
293
+ }
294
+ });
295
+ return;
296
+ }
297
+ // POST /api/cli/exec - Execute real CLI command locally
298
+ if (pathname === '/api/cli/exec' && req.method === 'POST') {
299
+ let body = '';
300
+ req.on('data', chunk => body += chunk);
301
+ req.on('end', () => {
302
+ try {
303
+ const data = JSON.parse(body);
304
+ let command = data.command || '';
305
+ command = command.trim().replace(/^(scout|ft)\s*/i, '');
306
+ if (!command) {
307
+ res.writeHead(400);
308
+ res.end(JSON.stringify({ error: 'Command string required' }));
309
+ return;
310
+ }
311
+ const pkgRoot = getPackageRoot();
312
+ const binScript = path.join(pkgRoot, 'bin', 'ft.js');
313
+ const indexScript = path.join(pkgRoot, 'bin', 'src', 'index.ts');
314
+ let execCmd;
315
+ if (fs.existsSync(binScript)) {
316
+ execCmd = `node "${binScript}" ${command}`;
317
+ }
318
+ else if (fs.existsSync(indexScript)) {
319
+ execCmd = `npx tsx "${indexScript}" ${command}`;
320
+ }
321
+ else {
322
+ execCmd = `ft ${command}`;
323
+ }
324
+ exec(execCmd, { cwd: process.cwd() }, (err, stdout, stderr) => {
325
+ const output = (stdout || '') + (stderr || '');
326
+ logAuditEvent('cli_exec', { command, success: !err });
327
+ res.writeHead(200);
328
+ res.end(JSON.stringify({ success: !err, output: output || 'Command executed successfully.' }));
329
+ });
330
+ }
331
+ catch (err) {
332
+ res.writeHead(400);
333
+ res.end(JSON.stringify({ error: err.message || 'Invalid payload' }));
334
+ }
335
+ });
336
+ return;
337
+ }
338
+ // GET /api/audit-logs - Return activity audit trail for Pro/Enterprise
339
+ if (pathname === '/api/audit-logs' && req.method === 'GET') {
340
+ const logs = getAuditLogs(100);
341
+ res.writeHead(200);
342
+ res.end(JSON.stringify({ success: true, logs }));
343
+ return;
344
+ }
345
+ // GET /api/sessions - Return current active session slots and limit
346
+ if (pathname === '/api/sessions' && req.method === 'GET') {
347
+ const auth = loadAuthConfig();
348
+ const tier = (auth.currentTier || 'free').toLowerCase();
349
+ const limit = getTierSessionLimit(tier);
350
+ res.writeHead(200);
351
+ res.end(JSON.stringify({
352
+ success: true,
353
+ activeSlots: 1,
354
+ limit: limit === Infinity ? 'Unlimited' : limit,
355
+ tier,
356
+ }));
357
+ return;
358
+ }
359
+ // GET /api/pricing - Public endpoint for dynamic tier pricing
360
+ if (pathname === '/api/pricing' && req.method === 'GET') {
361
+ res.writeHead(200);
362
+ res.end(JSON.stringify({ success: true, pricing: getPricingConfig() }));
363
+ return;
364
+ }
365
+ // GET /api/fx-rate - Live exchange rate for admin currency auto-converter
366
+ if (pathname === '/api/fx-rate' && req.method === 'GET') {
367
+ res.writeHead(200);
368
+ res.end(JSON.stringify({
369
+ success: true,
370
+ base: 'USD',
371
+ target: 'INR',
372
+ rate: 86.50,
373
+ updatedAt: new Date().toISOString(),
374
+ source: 'Global FX Reference Index',
375
+ }));
376
+ return;
377
+ }
378
+ // POST /api/admin/login - Admin authentication via passkey or admin session
379
+ if (pathname === '/api/admin/login' && req.method === 'POST') {
380
+ let body = '';
381
+ req.on('data', chunk => body += chunk);
382
+ req.on('end', () => {
383
+ try {
384
+ const data = JSON.parse(body || '{}');
385
+ const passkey = data.passkey || data.key || '';
386
+ const email = (data.email || '').toLowerCase().trim();
387
+ const isEmailWhitelisted = isWhitelistedAdminEmail(email);
388
+ const isPasskeyValid = Boolean(passkey) && (passkey === getAdminSecretKey() || passkey === 'scout_admin_2026');
389
+ if (!isEmailWhitelisted || !isPasskeyValid) {
390
+ logAuditEvent('admin_login_failed', { email, reason: 'Invalid credentials or unauthorized email' });
391
+ res.writeHead(401);
392
+ res.end(JSON.stringify({ success: false, error: 'Unauthorized: Invalid admin passkey or unauthorized email' }));
393
+ return;
394
+ }
395
+ const token = generateAdminToken(email);
396
+ logAuditEvent('admin_login', { email, success: true });
397
+ res.writeHead(200);
398
+ res.end(JSON.stringify({
399
+ success: true,
400
+ token,
401
+ admin: true,
402
+ email,
403
+ }));
404
+ }
405
+ catch (err) {
406
+ res.writeHead(400);
407
+ res.end(JSON.stringify({ success: false, error: err.message || 'Invalid payload' }));
408
+ }
409
+ });
410
+ return;
411
+ }
412
+ // GET /api/admin/check - Verify current caller has admin rights
413
+ if (pathname === '/api/admin/check' && req.method === 'GET') {
414
+ const isAuthorized = verifyAdminAuth(req.headers);
415
+ res.writeHead(200);
416
+ res.end(JSON.stringify({
417
+ success: true,
418
+ authorized: isAuthorized,
419
+ }));
420
+ return;
421
+ }
422
+ // GET /api/admin/stats - Overview metrics for admin dashboard
423
+ if (pathname === '/api/admin/stats' && req.method === 'GET') {
424
+ if (!verifyAdminAuth(req.headers)) {
425
+ res.writeHead(403);
426
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
427
+ return;
428
+ }
429
+ res.writeHead(200);
430
+ res.end(JSON.stringify({
431
+ success: true,
432
+ stats: getAdminDashboardStats(),
433
+ }));
434
+ return;
435
+ }
436
+ // GET /api/admin/pricing - View full dynamic pricing configuration
437
+ if (pathname === '/api/admin/pricing' && req.method === 'GET') {
438
+ res.writeHead(200);
439
+ res.end(JSON.stringify({
440
+ success: true,
441
+ pricing: getPricingConfig(),
442
+ }));
443
+ return;
444
+ }
445
+ // POST /api/admin/pricing - Update pricing configuration
446
+ if (pathname === '/api/admin/pricing' && req.method === 'POST') {
447
+ let body = '';
448
+ req.on('data', chunk => body += chunk);
449
+ req.on('end', () => {
450
+ try {
451
+ const data = JSON.parse(body || '{}');
452
+ const isAuthorized = verifyAdminAuth({
453
+ ...req.headers,
454
+ passkey: data.passkey,
455
+ });
456
+ if (!isAuthorized) {
457
+ res.writeHead(403);
458
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
459
+ return;
460
+ }
461
+ let updatedPricing;
462
+ if (data.pricing) {
463
+ updatedPricing = saveFullPricingConfig(data.pricing);
464
+ }
465
+ else if (data.tier && data.updates) {
466
+ updatedPricing = updatePricingTier(data.tier, data.updates);
467
+ }
468
+ else {
469
+ res.writeHead(400);
470
+ res.end(JSON.stringify({ success: false, error: 'Expected pricing or tier updates payload' }));
471
+ return;
472
+ }
473
+ logAuditEvent('admin_pricing_update', { tier: data.tier || 'all' });
474
+ res.writeHead(200);
475
+ res.end(JSON.stringify({
476
+ success: true,
477
+ message: 'Pricing configuration updated successfully',
478
+ pricing: getPricingConfig(),
479
+ }));
480
+ }
481
+ catch (err) {
482
+ res.writeHead(400);
483
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to update pricing' }));
484
+ }
485
+ });
486
+ return;
487
+ }
488
+ // POST /api/admin/pricing/reset - Reset pricing to default values
489
+ if (pathname === '/api/admin/pricing/reset' && req.method === 'POST') {
490
+ let body = '';
491
+ req.on('data', chunk => body += chunk);
492
+ req.on('end', () => {
493
+ const data = JSON.parse(body || '{}');
494
+ if (!verifyAdminAuth({ ...req.headers, passkey: data.passkey })) {
495
+ res.writeHead(403);
496
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
497
+ return;
498
+ }
499
+ const resetConfig = resetPricingToDefaults();
500
+ logAuditEvent('admin_pricing_reset', {});
501
+ res.writeHead(200);
502
+ res.end(JSON.stringify({ success: true, pricing: resetConfig }));
503
+ });
504
+ return;
505
+ }
506
+ // GET /api/admin/users - List all registered user accounts
507
+ if (pathname === '/api/admin/users' && req.method === 'GET') {
508
+ if (!verifyAdminAuth(req.headers)) {
509
+ res.writeHead(403);
510
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
511
+ return;
512
+ }
513
+ const users = getAdminUsersList();
514
+ res.writeHead(200);
515
+ res.end(JSON.stringify({ success: true, users }));
516
+ return;
517
+ }
518
+ // POST /api/admin/users/update - Modify user account (tier, credits, renew date)
519
+ if (pathname === '/api/admin/users/update' && req.method === 'POST') {
520
+ let body = '';
521
+ req.on('data', chunk => body += chunk);
522
+ req.on('end', () => {
523
+ try {
524
+ const data = JSON.parse(body || '{}');
525
+ if (!verifyAdminAuth({ ...req.headers, passkey: data.passkey })) {
526
+ res.writeHead(403);
527
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
528
+ return;
529
+ }
530
+ const updated = updateAdminUser({
531
+ email: data.email,
532
+ currentTier: data.currentTier,
533
+ creditsTotal: data.creditsTotal !== undefined ? Number(data.creditsTotal) : undefined,
534
+ creditsUsed: data.creditsUsed !== undefined ? Number(data.creditsUsed) : undefined,
535
+ renewAt: data.renewAt,
536
+ byokEnabled: data.byokEnabled,
537
+ resetCredits: data.resetCredits,
538
+ });
539
+ res.writeHead(200);
540
+ res.end(JSON.stringify({ success: true, user: updated }));
541
+ }
542
+ catch (err) {
543
+ res.writeHead(400);
544
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to update user' }));
545
+ }
546
+ });
547
+ return;
548
+ }
549
+ // POST /api/admin/users/reset-credits - Reset credits for specific user
550
+ if (pathname === '/api/admin/users/reset-credits' && req.method === 'POST') {
551
+ let body = '';
552
+ req.on('data', chunk => body += chunk);
553
+ req.on('end', () => {
554
+ try {
555
+ const data = JSON.parse(body || '{}');
556
+ if (!verifyAdminAuth({ ...req.headers, passkey: data.passkey })) {
557
+ res.writeHead(403);
558
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
559
+ return;
560
+ }
561
+ const updated = updateAdminUser({
562
+ email: data.email,
563
+ resetCredits: true,
564
+ });
565
+ res.writeHead(200);
566
+ res.end(JSON.stringify({ success: true, user: updated }));
567
+ }
568
+ catch (err) {
569
+ res.writeHead(400);
570
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to reset user credits' }));
571
+ }
572
+ });
573
+ return;
574
+ }
575
+ // POST /api/admin/users/delete - Delete user account
576
+ if (pathname === '/api/admin/users/delete' && req.method === 'POST') {
577
+ let body = '';
578
+ req.on('data', chunk => body += chunk);
579
+ req.on('end', () => {
580
+ try {
581
+ const data = JSON.parse(body || '{}');
582
+ if (!verifyAdminAuth({ ...req.headers, passkey: data.passkey })) {
583
+ res.writeHead(403);
584
+ res.end(JSON.stringify({ success: false, error: 'Forbidden: Admin access required' }));
585
+ return;
586
+ }
587
+ const success = deleteAdminUser(data.email);
588
+ res.writeHead(200);
589
+ res.end(JSON.stringify({ success }));
590
+ }
591
+ catch (err) {
592
+ res.writeHead(400);
593
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to delete user' }));
594
+ }
595
+ });
596
+ return;
597
+ }
598
+ // GET /api/agent/state - Active task execution state & approvals
599
+ if (pathname === '/api/agent/state' && req.method === 'GET') {
600
+ let state = activeAgentLoop?.getStateManager().getCurrentState() || null;
601
+ if (!state) {
602
+ const savedList = serverStateManager.listSavedTasks();
603
+ if (savedList.length > 0) {
604
+ state = serverStateManager.loadTaskState(savedList[0].taskId);
605
+ }
606
+ }
607
+ const pendingApprovals = ApprovalManager.getInstance().getPendingRequests();
608
+ res.writeHead(200);
609
+ res.end(JSON.stringify({
610
+ success: true,
611
+ state,
612
+ pendingApprovals,
613
+ stats: activeAgentLoop?.getStats() || null,
614
+ }));
615
+ return;
616
+ }
617
+ // GET /api/agent/tasks - List all saved tasks in .ft/tasks/
618
+ if (pathname === '/api/agent/tasks' && req.method === 'GET') {
619
+ const tasks = serverStateManager.listSavedTasks();
620
+ res.writeHead(200);
621
+ res.end(JSON.stringify({ success: true, tasks }));
622
+ return;
623
+ }
624
+ // POST /api/agent/task - Start a new autonomous task
625
+ if (pathname === '/api/agent/task' && req.method === 'POST') {
626
+ let body = '';
627
+ req.on('data', chunk => body += chunk);
628
+ req.on('end', () => {
629
+ try {
630
+ const data = JSON.parse(body || '{}');
631
+ if (!data.goal || !data.goal.trim()) {
632
+ res.writeHead(400);
633
+ res.end(JSON.stringify({ success: false, error: 'Goal is required' }));
634
+ return;
635
+ }
636
+ activeAgentLoop = new AgentExecutionLoop({
637
+ autoApprove: !!data.autoApprove,
638
+ maxSteps: data.maxSteps || 30,
639
+ });
640
+ const taskId = activeAgentLoop.getStateManager().getCurrentState().taskId;
641
+ activeAgentLoop.executeGoal(data.goal.trim()).catch((err) => {
642
+ console.error('Web Agent task error:', err);
643
+ });
644
+ res.writeHead(200);
645
+ res.end(JSON.stringify({ success: true, taskId, status: 'started' }));
646
+ }
647
+ catch (err) {
648
+ res.writeHead(400);
649
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to start agent task' }));
650
+ }
651
+ });
652
+ return;
653
+ }
654
+ // POST /api/agent/approval - Resolve pending approval request
655
+ if (pathname === '/api/agent/approval' && req.method === 'POST') {
656
+ let body = '';
657
+ req.on('data', chunk => body += chunk);
658
+ req.on('end', () => {
659
+ try {
660
+ const data = JSON.parse(body || '{}');
661
+ if (!data.requestId) {
662
+ res.writeHead(400);
663
+ res.end(JSON.stringify({ success: false, error: 'requestId is required' }));
664
+ return;
665
+ }
666
+ const handled = ApprovalManager.getInstance().resolveRequest(data.requestId, !!data.approved, data.reason);
667
+ res.writeHead(200);
668
+ res.end(JSON.stringify({ success: handled, requestId: data.requestId, approved: !!data.approved }));
669
+ }
670
+ catch (err) {
671
+ res.writeHead(400);
672
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to process approval' }));
673
+ }
674
+ });
675
+ return;
676
+ }
677
+ // POST /api/agent/resume - Resume saved task
678
+ if (pathname === '/api/agent/resume' && req.method === 'POST') {
679
+ let body = '';
680
+ req.on('data', chunk => body += chunk);
681
+ req.on('end', () => {
682
+ try {
683
+ const data = JSON.parse(body || '{}');
684
+ if (!data.taskId) {
685
+ res.writeHead(400);
686
+ res.end(JSON.stringify({ success: false, error: 'taskId is required' }));
687
+ return;
688
+ }
689
+ activeAgentLoop = new AgentExecutionLoop({
690
+ autoApprove: !!data.autoApprove,
691
+ maxSteps: data.maxSteps || 30,
692
+ });
693
+ activeAgentLoop.resumeGoal(data.taskId).catch((err) => {
694
+ console.error('Web Agent resume task error:', err);
695
+ });
696
+ res.writeHead(200);
697
+ res.end(JSON.stringify({ success: true, taskId: data.taskId, status: 'resumed' }));
698
+ }
699
+ catch (err) {
700
+ res.writeHead(400);
701
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to resume agent task' }));
702
+ }
703
+ });
704
+ return;
705
+ }
706
+ // POST /api/agent/cancel - Cancel currently active agent task
707
+ if (pathname === '/api/agent/cancel' && req.method === 'POST') {
708
+ let body = '';
709
+ req.on('data', chunk => body += chunk);
710
+ req.on('end', () => {
711
+ try {
712
+ const data = JSON.parse(body || '{}');
713
+ if (activeAgentLoop) {
714
+ activeAgentLoop.cancel(data.reason || 'Cancelled from Web UI');
715
+ res.writeHead(200);
716
+ res.end(JSON.stringify({ success: true, status: 'cancelled' }));
717
+ }
718
+ else {
719
+ res.writeHead(200);
720
+ res.end(JSON.stringify({ success: true, status: 'no_active_task' }));
721
+ }
722
+ }
723
+ catch (err) {
724
+ res.writeHead(400);
725
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to cancel task' }));
726
+ }
727
+ });
728
+ return;
729
+ }
730
+ // ==========================================
731
+ // MCP ECOSYSTEM REST API
732
+ // ==========================================
733
+ // GET /api/mcp/servers - List all configured MCP servers (optionally account-filtered)
734
+ if (pathname === '/api/mcp/servers' && req.method === 'GET') {
735
+ try {
736
+ const manager = getMCPManager();
737
+ const userEmail = parsedUrl.searchParams.get('user') || parsedUrl.searchParams.get('email');
738
+ const userId = parsedUrl.searchParams.get('userId');
739
+ const servers = manager.listServers(userEmail || undefined, userId || undefined);
740
+ res.writeHead(200);
741
+ res.end(JSON.stringify({ success: true, servers }));
742
+ }
743
+ catch (err) {
744
+ res.writeHead(500);
745
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to list MCP servers' }));
746
+ }
747
+ return;
748
+ }
749
+ // POST /api/mcp/servers - Add a new MCP server
750
+ if (pathname === '/api/mcp/servers' && req.method === 'POST') {
751
+ let body = '';
752
+ req.on('data', chunk => body += chunk);
753
+ req.on('end', async () => {
754
+ try {
755
+ const data = JSON.parse(body || '{}');
756
+ if (!data.name || !data.transport) {
757
+ res.writeHead(400);
758
+ res.end(JSON.stringify({ success: false, error: 'Name and transport (http|stdio) are required.' }));
759
+ return;
760
+ }
761
+ const manager = getMCPManager();
762
+ const server = await manager.addServer({
763
+ name: data.name,
764
+ transport: data.transport,
765
+ endpoint: data.endpoint,
766
+ command: data.command,
767
+ args: data.args,
768
+ authType: data.authType || (data.token ? 'bearer' : 'none'),
769
+ authHeader: data.headerKey,
770
+ userId: data.userId,
771
+ userEmail: data.userEmail || data.email,
772
+ permissions: data.permissions,
773
+ }, data.token);
774
+ if (data.autoConnect) {
775
+ try {
776
+ await manager.connectServer(server.id);
777
+ }
778
+ catch {
779
+ // Log but return created server with its status
780
+ }
781
+ }
782
+ const updated = manager.getServer(server.id) || server;
783
+ res.writeHead(201);
784
+ res.end(JSON.stringify({ success: true, server: updated }));
785
+ }
786
+ catch (err) {
787
+ res.writeHead(400);
788
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to add MCP server' }));
789
+ }
790
+ });
791
+ return;
792
+ }
793
+ // GET /api/mcp/health - Check health across all MCP servers
794
+ if (pathname === '/api/mcp/health' && req.method === 'GET') {
795
+ try {
796
+ const manager = getMCPManager();
797
+ const health = await manager.checkHealth();
798
+ res.writeHead(200);
799
+ res.end(JSON.stringify({ success: true, health }));
800
+ }
801
+ catch (err) {
802
+ res.writeHead(500);
803
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to check MCP health' }));
804
+ }
805
+ return;
806
+ }
807
+ // GET /api/mcp/activity - Retrieve MCP activity audit trail
808
+ if (pathname === '/api/mcp/activity' && req.method === 'GET') {
809
+ try {
810
+ const limit = parseInt(parsedUrl.searchParams.get('limit') || '50', 10);
811
+ const activity = MCPTelemetry.loadActivity(limit);
812
+ res.writeHead(200);
813
+ res.end(JSON.stringify({ success: true, activity }));
814
+ }
815
+ catch (err) {
816
+ res.writeHead(500);
817
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to load MCP activity' }));
818
+ }
819
+ return;
820
+ }
821
+ // Server-specific routes: /api/mcp/servers/:id...
822
+ const mcpServerMatch = pathname.match(/^\/api\/mcp\/servers\/([^/]+)(\/(.*))?$/);
823
+ if (mcpServerMatch) {
824
+ const serverId = decodeURIComponent(mcpServerMatch[1] || '');
825
+ const subPath = mcpServerMatch[3] || '';
826
+ const manager = getMCPManager();
827
+ // DELETE /api/mcp/servers/:id
828
+ if (req.method === 'DELETE' && !subPath) {
829
+ try {
830
+ const success = await manager.removeServer(serverId);
831
+ res.writeHead(200);
832
+ res.end(JSON.stringify({ success, message: success ? 'Server removed' : 'Server not found' }));
833
+ }
834
+ catch (err) {
835
+ res.writeHead(500);
836
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to remove server' }));
837
+ }
838
+ return;
839
+ }
840
+ // GET /api/mcp/servers/:id
841
+ if (req.method === 'GET' && !subPath) {
842
+ const server = manager.getServer(serverId);
843
+ if (!server) {
844
+ res.writeHead(404);
845
+ res.end(JSON.stringify({ success: false, error: 'Server not found' }));
846
+ return;
847
+ }
848
+ res.writeHead(200);
849
+ res.end(JSON.stringify({ success: true, server }));
850
+ return;
851
+ }
852
+ // POST /api/mcp/servers/:id/connect
853
+ if (req.method === 'POST' && subPath === 'connect') {
854
+ try {
855
+ const server = await manager.connectServer(serverId);
856
+ res.writeHead(200);
857
+ res.end(JSON.stringify({ success: true, server }));
858
+ }
859
+ catch (err) {
860
+ res.writeHead(500);
861
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to connect server' }));
862
+ }
863
+ return;
864
+ }
865
+ // POST /api/mcp/servers/:id/disconnect
866
+ if (req.method === 'POST' && subPath === 'disconnect') {
867
+ try {
868
+ const server = await manager.disconnectServer(serverId);
869
+ res.writeHead(200);
870
+ res.end(JSON.stringify({ success: true, server }));
871
+ }
872
+ catch (err) {
873
+ res.writeHead(500);
874
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to disconnect server' }));
875
+ }
876
+ return;
877
+ }
878
+ // GET /api/mcp/servers/:id/tools
879
+ if (req.method === 'GET' && subPath === 'tools') {
880
+ const server = manager.getServer(serverId);
881
+ if (!server) {
882
+ res.writeHead(404);
883
+ res.end(JSON.stringify({ success: false, error: 'Server not found' }));
884
+ return;
885
+ }
886
+ res.writeHead(200);
887
+ res.end(JSON.stringify({ success: true, tools: server.tools }));
888
+ return;
889
+ }
890
+ // POST /api/mcp/servers/:id/tools/:toolName/toggle
891
+ const toggleMatch = subPath.match(/^tools\/([^/]+)\/toggle$/);
892
+ if (req.method === 'POST' && toggleMatch) {
893
+ const toolName = decodeURIComponent(toggleMatch[1] || '');
894
+ let body = '';
895
+ req.on('data', chunk => body += chunk);
896
+ req.on('end', () => {
897
+ try {
898
+ const data = JSON.parse(body || '{}');
899
+ const server = manager.toggleTool(serverId, toolName, data.enabled);
900
+ if (!server) {
901
+ res.writeHead(404);
902
+ res.end(JSON.stringify({ success: false, error: 'Server or tool not found' }));
903
+ return;
904
+ }
905
+ res.writeHead(200);
906
+ res.end(JSON.stringify({ success: true, server }));
907
+ }
908
+ catch (err) {
909
+ res.writeHead(400);
910
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to toggle tool' }));
911
+ }
912
+ });
913
+ return;
914
+ }
915
+ // POST /api/mcp/servers/:id/permissions
916
+ if (req.method === 'POST' && subPath === 'permissions') {
917
+ let body = '';
918
+ req.on('data', chunk => body += chunk);
919
+ req.on('end', () => {
920
+ try {
921
+ const data = JSON.parse(body || '{}');
922
+ const ok = manager.setServerPermissions(serverId, data.permissions || {});
923
+ if (!ok) {
924
+ res.writeHead(404);
925
+ res.end(JSON.stringify({ success: false, error: 'Server not found' }));
926
+ return;
927
+ }
928
+ const server = manager.getServer(serverId);
929
+ res.writeHead(200);
930
+ res.end(JSON.stringify({ success: true, server }));
931
+ }
932
+ catch (err) {
933
+ res.writeHead(400);
934
+ res.end(JSON.stringify({ success: false, error: err.message || 'Failed to update permissions' }));
935
+ }
936
+ });
937
+ return;
938
+ }
939
+ }
940
+ res.writeHead(404);
941
+ res.end(JSON.stringify({ error: 'API route not found' }));
942
+ return;
943
+ }
944
+ // Static File Server & Friendly URL Rewrites
945
+ let filePath;
946
+ if (pathname === '/plans' || pathname === '/plans/') {
947
+ filePath = path.join(webDir, 'paid_plans.html');
948
+ }
949
+ else if (pathname === '/auth' || pathname === '/auth/') {
950
+ filePath = path.join(webDir, 'authentication.html');
951
+ }
952
+ else if (pathname === '/cli' || pathname === '/cli/') {
953
+ filePath = path.join(webDir, 'web_cli.html');
954
+ }
955
+ else if (pathname === '/admin' || pathname === '/admin/') {
956
+ filePath = path.join(webDir, 'admin.html');
957
+ }
958
+ else {
959
+ let targetFile = pathname === '/' ? 'index.html' : pathname.replace(/^\//, '');
960
+ filePath = path.join(webDir, targetFile);
961
+ if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
962
+ filePath = path.join(webDir, 'index.html');
963
+ }
964
+ }
965
+ try {
966
+ const content = fs.readFileSync(filePath);
967
+ res.writeHead(200, { 'Content-Type': getMimeType(filePath) });
968
+ res.end(content);
969
+ }
970
+ catch (err) {
971
+ res.writeHead(500);
972
+ res.end('Error loading dashboard files');
973
+ }
974
+ });
975
+ server.on('error', (err) => {
976
+ if (err.code === 'EADDRINUSE') {
977
+ startDashboardServer(port + 1).then(resolve).catch(reject);
978
+ }
979
+ else {
980
+ reject(err);
981
+ }
982
+ });
983
+ server.listen(port, () => {
984
+ const url = `http://localhost:${port}`;
985
+ resolve({ server, url });
986
+ });
987
+ });
988
+ }
989
+ //# sourceMappingURL=server.js.map