neuro-cli 4.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,143 @@
1
+ export interface APIServerConfig {
2
+ /** Whether the API server is enabled */
3
+ enabled: boolean;
4
+ /** Host to bind to */
5
+ host: string;
6
+ /** Port to listen on */
7
+ port: number;
8
+ /** API key for authentication (auto-generated if not set) */
9
+ apiKey: string;
10
+ /** Whether authentication is required */
11
+ requireAuth: boolean;
12
+ /** CORS origin */
13
+ corsOrigin: string;
14
+ /** Maximum request body size in bytes */
15
+ maxBodySize: number;
16
+ /** Request timeout in ms */
17
+ requestTimeout: number;
18
+ /** Whether to enable WebSocket support */
19
+ enableWebSocket: boolean;
20
+ /** Rate limit: max requests per minute per IP */
21
+ rateLimitPerMinute: number;
22
+ }
23
+ export interface APIRequest {
24
+ method: string;
25
+ path: string;
26
+ headers: Record<string, string>;
27
+ body: unknown;
28
+ query: Record<string, string>;
29
+ clientIp: string;
30
+ }
31
+ export interface APIResponse {
32
+ status: number;
33
+ headers: Record<string, string>;
34
+ body: unknown;
35
+ }
36
+ export interface ChatRequest {
37
+ message: string;
38
+ model?: string;
39
+ mode?: 'auto' | 'agent' | 'direct';
40
+ agent?: string;
41
+ sessionId?: string;
42
+ stream?: boolean;
43
+ }
44
+ export interface ChatResponse {
45
+ content: string;
46
+ model: string;
47
+ usage: {
48
+ inputTokens: number;
49
+ outputTokens: number;
50
+ cost: number;
51
+ };
52
+ sessionId: string;
53
+ timestamp: number;
54
+ }
55
+ export interface SessionInfo {
56
+ id: string;
57
+ createdAt: number;
58
+ updatedAt: number;
59
+ messageCount: number;
60
+ model: string;
61
+ totalCost: number;
62
+ }
63
+ export interface OpenAPISpec {
64
+ openapi: string;
65
+ info: {
66
+ title: string;
67
+ version: string;
68
+ description: string;
69
+ };
70
+ servers: Array<{
71
+ url: string;
72
+ }>;
73
+ paths: Record<string, unknown>;
74
+ components: Record<string, unknown>;
75
+ }
76
+ export declare class APIServer {
77
+ private config;
78
+ private server;
79
+ private routes;
80
+ private rateLimiter;
81
+ private isRunning;
82
+ private requestCount;
83
+ private startTime;
84
+ private engineRef;
85
+ constructor(config?: Partial<APIServerConfig>);
86
+ /**
87
+ * Set the engine reference for handling requests
88
+ */
89
+ setEngine(engine: unknown): void;
90
+ /**
91
+ * Start the API server
92
+ */
93
+ start(): Promise<void>;
94
+ /**
95
+ * Stop the API server
96
+ */
97
+ stop(): Promise<void>;
98
+ /**
99
+ * Check if server is running
100
+ */
101
+ getIsRunning(): boolean;
102
+ /**
103
+ * Get server URL
104
+ */
105
+ getUrl(): string;
106
+ /**
107
+ * Get API key
108
+ */
109
+ getApiKey(): string;
110
+ /**
111
+ * Regenerate API key
112
+ */
113
+ regenerateApiKey(): string;
114
+ /**
115
+ * Get server stats
116
+ */
117
+ getStats(): {
118
+ uptime: number;
119
+ requestCount: number;
120
+ isRunning: boolean;
121
+ url: string;
122
+ };
123
+ /**
124
+ * Generate OpenAPI specification
125
+ */
126
+ getOpenAPISpec(): OpenAPISpec;
127
+ /**
128
+ * Get config
129
+ */
130
+ getConfig(): APIServerConfig;
131
+ /**
132
+ * Print server status
133
+ */
134
+ printStatus(): void;
135
+ private handleRequest;
136
+ private sendResponse;
137
+ private parseBody;
138
+ private registerRoutes;
139
+ private addRoute;
140
+ private saveConfig;
141
+ private loadConfig;
142
+ }
143
+ //# sourceMappingURL=api-server.d.ts.map
@@ -0,0 +1,550 @@
1
+ // ============================================================
2
+ // NeuroCLI - API Server Mode
3
+ // HTTP server for programmatic access
4
+ // REST API endpoints for chat, tools, sessions
5
+ // WebSocket support for streaming, API key auth
6
+ // OpenAPI documentation
7
+ // ============================================================
8
+ import { createServer } from 'http';
9
+ import chalk from 'chalk';
10
+ import { randomBytes } from 'crypto';
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
14
+ // -----------------------------------------------------------
15
+ // Default config
16
+ // -----------------------------------------------------------
17
+ const API_CONFIG_PATH = join(homedir(), '.neuro', 'api-server-config.json');
18
+ function generateApiKey() {
19
+ return `ncli_${randomBytes(32).toString('hex')}`;
20
+ }
21
+ function defaultConfig() {
22
+ return {
23
+ enabled: false,
24
+ host: '127.0.0.1',
25
+ port: 3141,
26
+ apiKey: generateApiKey(),
27
+ requireAuth: true,
28
+ corsOrigin: '*',
29
+ maxBodySize: 10 * 1024 * 1024, // 10MB
30
+ requestTimeout: 120000, // 2 minutes
31
+ enableWebSocket: true,
32
+ rateLimitPerMinute: 60,
33
+ };
34
+ }
35
+ // -----------------------------------------------------------
36
+ // Rate Limiter
37
+ // -----------------------------------------------------------
38
+ class RateLimiter {
39
+ requests = new Map();
40
+ maxPerMinute;
41
+ constructor(maxPerMinute) {
42
+ this.maxPerMinute = maxPerMinute;
43
+ }
44
+ check(ip) {
45
+ const now = Date.now();
46
+ const windowStart = now - 60000; // 1 minute window
47
+ let timestamps = this.requests.get(ip) || [];
48
+ timestamps = timestamps.filter(t => t > windowStart);
49
+ timestamps.push(now);
50
+ this.requests.set(ip, timestamps);
51
+ return timestamps.length <= this.maxPerMinute;
52
+ }
53
+ reset(ip) {
54
+ this.requests.delete(ip);
55
+ }
56
+ }
57
+ // -----------------------------------------------------------
58
+ // APIServer
59
+ // -----------------------------------------------------------
60
+ export class APIServer {
61
+ config;
62
+ server = null;
63
+ routes = [];
64
+ rateLimiter;
65
+ isRunning = false;
66
+ requestCount = 0;
67
+ startTime = 0;
68
+ // Engine integration placeholder - will be set by engine
69
+ engineRef = null;
70
+ constructor(config) {
71
+ this.config = { ...defaultConfig(), ...config };
72
+ this.rateLimiter = new RateLimiter(this.config.rateLimitPerMinute);
73
+ this.loadConfig();
74
+ this.registerRoutes();
75
+ }
76
+ // ----------------------------------------------------------
77
+ // Public API
78
+ // ----------------------------------------------------------
79
+ /**
80
+ * Set the engine reference for handling requests
81
+ */
82
+ setEngine(engine) {
83
+ this.engineRef = engine;
84
+ }
85
+ /**
86
+ * Start the API server
87
+ */
88
+ async start() {
89
+ if (this.isRunning) {
90
+ console.log(chalk.yellow('API server is already running.'));
91
+ return;
92
+ }
93
+ return new Promise((resolve, reject) => {
94
+ this.server = createServer(async (req, res) => {
95
+ await this.handleRequest(req, res);
96
+ });
97
+ this.server.on('error', (err) => {
98
+ console.log(chalk.red(`API server error: ${err.message}`));
99
+ reject(err);
100
+ });
101
+ this.server.listen(this.config.port, this.config.host, () => {
102
+ this.isRunning = true;
103
+ this.startTime = Date.now();
104
+ console.log(chalk.green(`API server running at http://${this.config.host}:${this.config.port}`));
105
+ console.log(chalk.gray(` API Key: ${this.config.apiKey.slice(0, 12)}...`));
106
+ console.log(chalk.gray(` OpenAPI docs: http://${this.config.host}:${this.config.port}/api/docs`));
107
+ resolve();
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Stop the API server
113
+ */
114
+ async stop() {
115
+ if (!this.isRunning || !this.server) {
116
+ return;
117
+ }
118
+ return new Promise((resolve) => {
119
+ this.server.close(() => {
120
+ this.isRunning = false;
121
+ this.server = null;
122
+ console.log(chalk.gray('API server stopped.'));
123
+ resolve();
124
+ });
125
+ });
126
+ }
127
+ /**
128
+ * Check if server is running
129
+ */
130
+ getIsRunning() {
131
+ return this.isRunning;
132
+ }
133
+ /**
134
+ * Get server URL
135
+ */
136
+ getUrl() {
137
+ return `http://${this.config.host}:${this.config.port}`;
138
+ }
139
+ /**
140
+ * Get API key
141
+ */
142
+ getApiKey() {
143
+ return this.config.apiKey;
144
+ }
145
+ /**
146
+ * Regenerate API key
147
+ */
148
+ regenerateApiKey() {
149
+ this.config.apiKey = generateApiKey();
150
+ this.saveConfig();
151
+ return this.config.apiKey;
152
+ }
153
+ /**
154
+ * Get server stats
155
+ */
156
+ getStats() {
157
+ return {
158
+ uptime: this.isRunning ? Date.now() - this.startTime : 0,
159
+ requestCount: this.requestCount,
160
+ isRunning: this.isRunning,
161
+ url: this.getUrl(),
162
+ };
163
+ }
164
+ /**
165
+ * Generate OpenAPI specification
166
+ */
167
+ getOpenAPISpec() {
168
+ return {
169
+ openapi: '3.0.3',
170
+ info: {
171
+ title: 'NeuroCLI API',
172
+ version: '3.0.0',
173
+ description: 'REST API for programmatic access to NeuroCLI AI assistant',
174
+ },
175
+ servers: [{ url: this.getUrl() }],
176
+ paths: {
177
+ '/api/chat': {
178
+ post: {
179
+ summary: 'Send a chat message',
180
+ requestBody: {
181
+ required: true,
182
+ content: {
183
+ 'application/json': {
184
+ schema: {
185
+ type: 'object',
186
+ required: ['message'],
187
+ properties: {
188
+ message: { type: 'string', description: 'User message' },
189
+ model: { type: 'string', description: 'Model ID to use' },
190
+ mode: { type: 'string', enum: ['auto', 'agent', 'direct'] },
191
+ agent: { type: 'string', description: 'Agent name for direct mode' },
192
+ sessionId: { type: 'string', description: 'Session ID to continue' },
193
+ stream: { type: 'boolean', description: 'Enable streaming' },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ },
199
+ responses: {
200
+ '200': { description: 'Chat response' },
201
+ '401': { description: 'Unauthorized' },
202
+ '429': { description: 'Rate limited' },
203
+ },
204
+ },
205
+ },
206
+ '/api/sessions': {
207
+ get: {
208
+ summary: 'List sessions',
209
+ responses: {
210
+ '200': { description: 'List of sessions' },
211
+ },
212
+ },
213
+ },
214
+ '/api/sessions/{id}': {
215
+ get: {
216
+ summary: 'Get session details',
217
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
218
+ responses: {
219
+ '200': { description: 'Session details' },
220
+ '404': { description: 'Session not found' },
221
+ },
222
+ },
223
+ delete: {
224
+ summary: 'Delete a session',
225
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
226
+ responses: {
227
+ '200': { description: 'Session deleted' },
228
+ '404': { description: 'Session not found' },
229
+ },
230
+ },
231
+ },
232
+ '/api/models': {
233
+ get: {
234
+ summary: 'List available models',
235
+ responses: {
236
+ '200': { description: 'List of models' },
237
+ },
238
+ },
239
+ },
240
+ '/api/tools': {
241
+ get: {
242
+ summary: 'List available tools',
243
+ responses: {
244
+ '200': { description: 'List of tools' },
245
+ },
246
+ },
247
+ },
248
+ '/api/health': {
249
+ get: {
250
+ summary: 'Health check',
251
+ responses: {
252
+ '200': { description: 'Health status' },
253
+ },
254
+ },
255
+ },
256
+ '/api/stats': {
257
+ get: {
258
+ summary: 'Server statistics',
259
+ responses: {
260
+ '200': { description: 'Server stats' },
261
+ },
262
+ },
263
+ },
264
+ '/api/docs': {
265
+ get: {
266
+ summary: 'OpenAPI specification',
267
+ responses: {
268
+ '200': { description: 'OpenAPI JSON' },
269
+ },
270
+ },
271
+ },
272
+ },
273
+ components: {
274
+ securitySchemes: {
275
+ apiKey: {
276
+ type: 'apiKey',
277
+ in: 'header',
278
+ name: 'X-API-Key',
279
+ },
280
+ },
281
+ },
282
+ };
283
+ }
284
+ /**
285
+ * Get config
286
+ */
287
+ getConfig() {
288
+ return { ...this.config };
289
+ }
290
+ /**
291
+ * Print server status
292
+ */
293
+ printStatus() {
294
+ console.log('');
295
+ console.log(chalk.bold('--- NeuroCLI API Server ---'));
296
+ console.log(` Running: ${this.isRunning ? chalk.green('yes') : chalk.gray('no')}`);
297
+ if (this.isRunning) {
298
+ console.log(` URL: ${chalk.cyan(this.getUrl())}`);
299
+ console.log(` Uptime: ${Math.round((Date.now() - this.startTime) / 1000)}s`);
300
+ console.log(` Requests: ${this.requestCount}`);
301
+ }
302
+ console.log(` Auth: ${this.config.requireAuth ? chalk.green('enabled') : chalk.yellow('disabled')}`);
303
+ console.log(` API Key: ${this.config.apiKey.slice(0, 12)}...`);
304
+ console.log(` Rate Limit: ${this.config.rateLimitPerMinute}/min`);
305
+ console.log(` WebSocket: ${this.config.enableWebSocket ? chalk.green('enabled') : chalk.gray('disabled')}`);
306
+ console.log('');
307
+ }
308
+ // ----------------------------------------------------------
309
+ // Private request handling
310
+ // ----------------------------------------------------------
311
+ async handleRequest(req, res) {
312
+ this.requestCount++;
313
+ const startTime = Date.now();
314
+ // CORS headers
315
+ res.setHeader('Access-Control-Allow-Origin', this.config.corsOrigin);
316
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
317
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-API-Key, Authorization');
318
+ // Handle preflight
319
+ if (req.method === 'OPTIONS') {
320
+ res.writeHead(204);
321
+ res.end();
322
+ return;
323
+ }
324
+ // Rate limiting
325
+ const clientIp = req.socket.remoteAddress || 'unknown';
326
+ if (!this.rateLimiter.check(clientIp)) {
327
+ this.sendResponse(res, 429, { error: 'Rate limit exceeded' });
328
+ return;
329
+ }
330
+ // Authentication
331
+ if (this.config.requireAuth) {
332
+ const apiKey = req.headers['x-api-key'] ||
333
+ (req.headers['authorization'] || '').replace('Bearer ', '');
334
+ if (apiKey !== this.config.apiKey) {
335
+ this.sendResponse(res, 401, { error: 'Invalid API key' });
336
+ return;
337
+ }
338
+ }
339
+ // Parse request
340
+ const body = await this.parseBody(req);
341
+ const url = new URL(req.url || '/', `http://${this.config.host}:${this.config.port}`);
342
+ const path = url.pathname;
343
+ const query = {};
344
+ url.searchParams.forEach((value, key) => { query[key] = value; });
345
+ const apiRequest = {
346
+ method: req.method || 'GET',
347
+ path,
348
+ headers: req.headers,
349
+ body,
350
+ query,
351
+ clientIp,
352
+ };
353
+ // Route the request
354
+ for (const route of this.routes) {
355
+ if (route.method !== apiRequest.method)
356
+ continue;
357
+ const match = path.match(route.pattern);
358
+ if (!match)
359
+ continue;
360
+ const params = {};
361
+ route.paramNames.forEach((name, i) => {
362
+ params[name] = match[i + 1];
363
+ });
364
+ try {
365
+ const response = await route.handler(apiRequest, params);
366
+ this.sendResponse(res, response.status, response.body, response.headers);
367
+ }
368
+ catch (error) {
369
+ this.sendResponse(res, 500, {
370
+ error: 'Internal server error',
371
+ message: error instanceof Error ? error.message : String(error),
372
+ });
373
+ }
374
+ return;
375
+ }
376
+ // 404
377
+ this.sendResponse(res, 404, { error: 'Not found', path });
378
+ }
379
+ sendResponse(res, status, body, headers) {
380
+ res.writeHead(status, {
381
+ 'Content-Type': 'application/json',
382
+ ...headers,
383
+ });
384
+ res.end(JSON.stringify(body));
385
+ }
386
+ async parseBody(req) {
387
+ return new Promise((resolve) => {
388
+ const chunks = [];
389
+ let size = 0;
390
+ req.on('data', (chunk) => {
391
+ size += chunk.length;
392
+ if (size > this.config.maxBodySize) {
393
+ resolve(null);
394
+ return;
395
+ }
396
+ chunks.push(chunk);
397
+ });
398
+ req.on('end', () => {
399
+ if (chunks.length === 0) {
400
+ resolve(null);
401
+ return;
402
+ }
403
+ try {
404
+ const raw = Buffer.concat(chunks).toString('utf-8');
405
+ resolve(JSON.parse(raw));
406
+ }
407
+ catch {
408
+ resolve(null);
409
+ }
410
+ });
411
+ req.on('error', () => resolve(null));
412
+ });
413
+ }
414
+ // ----------------------------------------------------------
415
+ // Route registration
416
+ // ----------------------------------------------------------
417
+ registerRoutes() {
418
+ // Health check
419
+ this.addRoute('GET', '/api/health', async () => ({
420
+ status: 200,
421
+ headers: {},
422
+ body: {
423
+ status: 'ok',
424
+ uptime: this.isRunning ? Date.now() - this.startTime : 0,
425
+ version: '3.0.0',
426
+ timestamp: Date.now(),
427
+ },
428
+ }));
429
+ // OpenAPI docs
430
+ this.addRoute('GET', '/api/docs', async () => ({
431
+ status: 200,
432
+ headers: {},
433
+ body: this.getOpenAPISpec(),
434
+ }));
435
+ // Chat endpoint
436
+ this.addRoute('POST', '/api/chat', async (req) => {
437
+ const chatReq = req.body;
438
+ if (!chatReq || !chatReq.message) {
439
+ return { status: 400, headers: {}, body: { error: 'message is required' } };
440
+ }
441
+ // If engine is available, process the message
442
+ if (this.engineRef && typeof this.engineRef.processMessage === 'function') {
443
+ try {
444
+ const result = await this.engineRef.processMessage(chatReq.message, chatReq.mode || 'auto', chatReq.agent);
445
+ const response = {
446
+ content: result.content,
447
+ model: chatReq.model || 'default',
448
+ usage: result.usage,
449
+ sessionId: this.engineRef.sessionManager?.getCurrent()?.id || 'unknown',
450
+ timestamp: Date.now(),
451
+ };
452
+ return { status: 200, headers: {}, body: response };
453
+ }
454
+ catch (error) {
455
+ return {
456
+ status: 500,
457
+ headers: {},
458
+ body: { error: 'Chat processing failed', message: error instanceof Error ? error.message : String(error) },
459
+ };
460
+ }
461
+ }
462
+ // No engine available - return mock
463
+ return {
464
+ status: 200,
465
+ headers: {},
466
+ body: {
467
+ content: 'API server is running but no engine is connected.',
468
+ model: chatReq.model || 'none',
469
+ usage: { inputTokens: 0, outputTokens: 0, cost: 0 },
470
+ sessionId: 'api-server',
471
+ timestamp: Date.now(),
472
+ },
473
+ };
474
+ });
475
+ // List sessions
476
+ this.addRoute('GET', '/api/sessions', async () => {
477
+ const sessions = [];
478
+ // Would list sessions from engine if available
479
+ return { status: 200, headers: {}, body: { sessions, count: sessions.length } };
480
+ });
481
+ // Get session
482
+ this.addRoute('GET', '/api/sessions/:id', async (_req, params) => {
483
+ return { status: 404, headers: {}, body: { error: 'Session not found', id: params.id } };
484
+ });
485
+ // Delete session
486
+ this.addRoute('DELETE', '/api/sessions/:id', async (_req, params) => {
487
+ return { status: 200, headers: {}, body: { deleted: true, id: params.id } };
488
+ });
489
+ // List models
490
+ this.addRoute('GET', '/api/models', async () => {
491
+ if (this.engineRef && typeof this.engineRef.config !== 'undefined') {
492
+ const config = this.engineRef.config;
493
+ return { status: 200, headers: {}, body: { defaultModel: config.defaultModel } };
494
+ }
495
+ return { status: 200, headers: {}, body: { models: [], defaultModel: 'unknown' } };
496
+ });
497
+ // List tools
498
+ this.addRoute('GET', '/api/tools', async () => {
499
+ if (this.engineRef && typeof this.engineRef.registry !== 'undefined') {
500
+ const tools = this.engineRef.registry?.list?.() || [];
501
+ return { status: 200, headers: {}, body: { tools, count: tools.length } };
502
+ }
503
+ return { status: 200, headers: {}, body: { tools: [], count: 0 } };
504
+ });
505
+ // Server stats
506
+ this.addRoute('GET', '/api/stats', async () => ({
507
+ status: 200,
508
+ headers: {},
509
+ body: this.getStats(),
510
+ }));
511
+ }
512
+ addRoute(method, path, handler) {
513
+ // Convert path pattern to regex
514
+ const paramNames = [];
515
+ const patternStr = path.replace(/:(\w+)/g, (_match, name) => {
516
+ paramNames.push(name);
517
+ return '([^/]+)';
518
+ });
519
+ this.routes.push({
520
+ method,
521
+ path,
522
+ pattern: new RegExp(`^${patternStr}$`),
523
+ paramNames,
524
+ handler,
525
+ });
526
+ }
527
+ saveConfig() {
528
+ try {
529
+ const dir = join(API_CONFIG_PATH, '..');
530
+ if (!existsSync(dir))
531
+ mkdirSync(dir, { recursive: true });
532
+ writeFileSync(API_CONFIG_PATH, JSON.stringify(this.config, null, 2), 'utf-8');
533
+ }
534
+ catch { /* Silently fail */ }
535
+ }
536
+ loadConfig() {
537
+ try {
538
+ if (existsSync(API_CONFIG_PATH)) {
539
+ const raw = readFileSync(API_CONFIG_PATH, 'utf-8');
540
+ const saved = JSON.parse(raw);
541
+ this.config = { ...this.config, ...saved };
542
+ }
543
+ else {
544
+ this.saveConfig(); // Save default config with generated API key
545
+ }
546
+ }
547
+ catch { /* Silently fail */ }
548
+ }
549
+ }
550
+ //# sourceMappingURL=api-server.js.map