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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- 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
|