ai-agent-router 0.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/.claude/commands/openspec/apply.md +23 -0
- package/.claude/commands/openspec/archive.md +27 -0
- package/.claude/commands/openspec/proposal.md +28 -0
- package/.claude/settings.local.json +12 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +228 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
- package/.cursor/commands/openspec-apply.md +23 -0
- package/.cursor/commands/openspec-archive.md +27 -0
- package/.cursor/commands/openspec-proposal.md +28 -0
- package/.cursor/commands/ui-ux-pro-max.md +226 -0
- package/.eslintrc.json +3 -0
- package/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.shared/ui-ux-pro-max/scripts/core.py +238 -0
- package/.shared/ui-ux-pro-max/scripts/search.py +61 -0
- package/AGENTS.md +18 -0
- package/CLAUDE.md +18 -0
- package/IMPLEMENTATION.md +157 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/.next/types/app/api/config/route.js +52 -0
- package/dist/.next/types/app/api/gateway/[...path]/route.js +52 -0
- package/dist/.next/types/app/api/gateway/route.js +52 -0
- package/dist/.next/types/app/api/logs/route.js +52 -0
- package/dist/.next/types/app/api/models/route.js +52 -0
- package/dist/.next/types/app/api/providers/route.js +52 -0
- package/dist/.next/types/app/api/providers/test/route.js +52 -0
- package/dist/.next/types/app/api/service/start/route.js +52 -0
- package/dist/.next/types/app/api/service/status/route.js +52 -0
- package/dist/.next/types/app/api/service/stop/route.js +52 -0
- package/dist/.next/types/app/layout.js +22 -0
- package/dist/.next/types/app/logs/page.js +22 -0
- package/dist/.next/types/app/models/page.js +22 -0
- package/dist/.next/types/app/page.js +22 -0
- package/dist/.next/types/app/providers/page.js +22 -0
- package/dist/src/app/api/config/route.js +43 -0
- package/dist/src/app/api/gateway/[...path]/route.js +83 -0
- package/dist/src/app/api/gateway/route.js +63 -0
- package/dist/src/app/api/logs/route.js +34 -0
- package/dist/src/app/api/models/route.js +152 -0
- package/dist/src/app/api/providers/route.js +118 -0
- package/dist/src/app/api/providers/test/route.js +154 -0
- package/dist/src/app/api/service/start/route.js +55 -0
- package/dist/src/app/api/service/status/route.js +17 -0
- package/dist/src/app/api/service/stop/route.js +20 -0
- package/dist/src/app/components/ConfirmDialog.jsx +31 -0
- package/dist/src/app/components/Nav.jsx +45 -0
- package/dist/src/app/components/Toast.jsx +37 -0
- package/dist/src/app/components/ToastProvider.jsx +21 -0
- package/dist/src/app/layout.jsx +13 -0
- package/dist/src/app/logs/page.jsx +210 -0
- package/dist/src/app/models/page.jsx +291 -0
- package/dist/src/app/page.jsx +236 -0
- package/dist/src/app/providers/page.jsx +402 -0
- package/dist/src/cli/index.js +90 -0
- package/dist/src/db/database.js +69 -0
- package/dist/src/db/queries.js +261 -0
- package/dist/src/db/schema.js +67 -0
- package/dist/src/server/crypto.js +22 -0
- package/dist/src/server/gateway-server.js +200 -0
- package/dist/src/server/gateway.js +76 -0
- package/dist/src/server/logger.js +72 -0
- package/dist/src/server/providers/anthropic.js +52 -0
- package/dist/src/server/providers/gemini.js +64 -0
- package/dist/src/server/providers/index.js +16 -0
- package/dist/src/server/providers/openai.js +86 -0
- package/dist/src/server/providers/types.js +1 -0
- package/dist/src/server/service-manager.js +286 -0
- package/docs/TODO.md +19 -0
- package/next.config.js +7 -0
- package/openspec/AGENTS.md +456 -0
- package/openspec/changes/add-logging/proposal.md +18 -0
- package/openspec/changes/add-logging/specs/core/spec.md +21 -0
- package/openspec/changes/add-logging/tasks.md +16 -0
- package/openspec/changes/add-provider-test-connection/proposal.md +22 -0
- package/openspec/changes/add-provider-test-connection/specs/model-provider/spec.md +68 -0
- package/openspec/changes/add-provider-test-connection/tasks.md +31 -0
- package/openspec/changes/improve-gateway-startup/design.md +137 -0
- package/openspec/changes/improve-gateway-startup/proposal.md +33 -0
- package/openspec/changes/improve-gateway-startup/specs/api-gateway/spec.md +94 -0
- package/openspec/changes/improve-gateway-startup/specs/web-ui/spec.md +67 -0
- package/openspec/changes/improve-gateway-startup/tasks.md +47 -0
- package/openspec/changes/init-api-gateway/design.md +185 -0
- package/openspec/changes/init-api-gateway/proposal.md +30 -0
- package/openspec/changes/init-api-gateway/specs/api-gateway/spec.md +42 -0
- package/openspec/changes/init-api-gateway/specs/cli-tool/spec.md +40 -0
- package/openspec/changes/init-api-gateway/specs/model-management/spec.md +47 -0
- package/openspec/changes/init-api-gateway/specs/model-provider/spec.md +33 -0
- package/openspec/changes/init-api-gateway/specs/request-logging/spec.md +54 -0
- package/openspec/changes/init-api-gateway/specs/web-ui/spec.md +49 -0
- package/openspec/changes/init-api-gateway/tasks.md +84 -0
- package/openspec/project.md +58 -0
- package/package.json +51 -0
- package/postcss.config.js +6 -0
- package/src/app/api/config/route.ts +62 -0
- package/src/app/api/gateway/[...path]/route.ts +118 -0
- package/src/app/api/gateway/route.ts +77 -0
- package/src/app/api/logs/route.ts +48 -0
- package/src/app/api/models/route.ts +210 -0
- package/src/app/api/providers/route.ts +162 -0
- package/src/app/api/providers/test/route.ts +182 -0
- package/src/app/api/service/start/route.ts +73 -0
- package/src/app/api/service/status/route.ts +22 -0
- package/src/app/api/service/stop/route.ts +27 -0
- package/src/app/components/ConfirmDialog.tsx +63 -0
- package/src/app/components/Nav.tsx +66 -0
- package/src/app/components/Toast.tsx +61 -0
- package/src/app/components/ToastProvider.tsx +43 -0
- package/src/app/globals.css +71 -0
- package/src/app/layout.tsx +22 -0
- package/src/app/logs/page.tsx +261 -0
- package/src/app/models/page.tsx +500 -0
- package/src/app/page.tsx +742 -0
- package/src/app/providers/page.tsx +558 -0
- package/src/cli/index.ts +95 -0
- package/src/db/database.ts +125 -0
- package/src/db/queries.ts +339 -0
- package/src/db/schema.ts +117 -0
- package/src/server/crypto.ts +48 -0
- package/src/server/gateway-server.ts +306 -0
- package/src/server/gateway.ts +163 -0
- package/src/server/logger.ts +96 -0
- package/src/server/providers/anthropic.ts +121 -0
- package/src/server/providers/gemini.ts +112 -0
- package/src/server/providers/index.ts +20 -0
- package/src/server/providers/openai.ts +235 -0
- package/src/server/providers/types.ts +20 -0
- package/src/server/service-manager.ts +321 -0
- package/tailwind.config.js +16 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { spawn, ChildProcess, exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { getServiceStatus, setServiceStatus, updateServiceStatus, clearServiceStatus } from '@/db/queries';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import net from 'net';
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
10
|
+
export interface ServiceStatusResponse {
|
|
11
|
+
status: 'running' | 'stopped';
|
|
12
|
+
port?: number;
|
|
13
|
+
pid?: number | null;
|
|
14
|
+
started_at?: string | null;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ServiceManager {
|
|
19
|
+
private childProcess: ChildProcess | null = null;
|
|
20
|
+
private isStarting: boolean = false;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a process with the given PID exists
|
|
24
|
+
*/
|
|
25
|
+
private async checkProcessExists(pid: number): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
// Use 'ps' command to check if process exists
|
|
28
|
+
// This works on Unix-like systems (macOS, Linux)
|
|
29
|
+
await execAsync(`ps -p ${pid} -o pid=`);
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a port is available
|
|
38
|
+
*/
|
|
39
|
+
private async checkPortAvailable(port: number): Promise<boolean> {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const server = net.createServer();
|
|
42
|
+
|
|
43
|
+
server.listen(port, () => {
|
|
44
|
+
server.once('close', () => resolve(true));
|
|
45
|
+
server.close();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
server.on('error', () => {
|
|
49
|
+
resolve(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if a port is in use and get process info
|
|
56
|
+
*/
|
|
57
|
+
private async checkPortInUse(port: number): Promise<{ inUse: boolean; processInfo?: string }> {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const server = net.createServer();
|
|
60
|
+
|
|
61
|
+
server.listen(port, () => {
|
|
62
|
+
server.once('close', () => resolve({ inUse: false }));
|
|
63
|
+
server.close();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
server.on('error', (err: any) => {
|
|
67
|
+
if (err.code === 'EADDRINUSE') {
|
|
68
|
+
// Try to get process info
|
|
69
|
+
execAsync(`lsof -ti:${port} 2>/dev/null || echo ''`)
|
|
70
|
+
.then(({ stdout }) => {
|
|
71
|
+
const pid = stdout.trim();
|
|
72
|
+
if (pid) {
|
|
73
|
+
resolve({ inUse: true, processInfo: `Port ${port} is in use by process ${pid}` });
|
|
74
|
+
} else {
|
|
75
|
+
resolve({ inUse: true, processInfo: `Port ${port} is already in use` });
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.catch(() => {
|
|
79
|
+
resolve({ inUse: true, processInfo: `Port ${port} is already in use` });
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
resolve({ inUse: false });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get current service status, verifying process existence
|
|
90
|
+
*/
|
|
91
|
+
async getStatus(): Promise<ServiceStatusResponse> {
|
|
92
|
+
const dbStatus = getServiceStatus();
|
|
93
|
+
|
|
94
|
+
if (!dbStatus) {
|
|
95
|
+
return { status: 'stopped' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If status is running, verify process actually exists
|
|
99
|
+
if (dbStatus.status === 'running' && dbStatus.pid) {
|
|
100
|
+
const processExists = await this.checkProcessExists(dbStatus.pid);
|
|
101
|
+
if (!processExists) {
|
|
102
|
+
// Process doesn't exist, update database
|
|
103
|
+
updateServiceStatus({ status: 'stopped', pid: null });
|
|
104
|
+
return { status: 'stopped' };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
status: dbStatus.status,
|
|
110
|
+
port: dbStatus.port,
|
|
111
|
+
pid: dbStatus.pid,
|
|
112
|
+
started_at: dbStatus.started_at,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Start the gateway service
|
|
118
|
+
*/
|
|
119
|
+
async start(port: number): Promise<ServiceStatusResponse> {
|
|
120
|
+
// Prevent concurrent starts
|
|
121
|
+
if (this.isStarting) {
|
|
122
|
+
return { status: 'stopped', error: 'Service is already starting' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check if service is already running
|
|
126
|
+
const currentStatus = await this.getStatus();
|
|
127
|
+
if (currentStatus.status === 'running') {
|
|
128
|
+
return { status: 'running', error: 'Service is already running', port: currentStatus.port, pid: currentStatus.pid };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check port availability with more details
|
|
132
|
+
const portCheck = await this.checkPortInUse(port);
|
|
133
|
+
if (portCheck.inUse) {
|
|
134
|
+
// In development, if port 3000 is in use, suggest using a different port
|
|
135
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
136
|
+
if (isDev && port === 3000) {
|
|
137
|
+
return {
|
|
138
|
+
status: 'stopped',
|
|
139
|
+
error: `Port 3000 is already in use (likely by the development server). Please configure a different port (e.g., 3001) in the settings.`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { status: 'stopped', error: portCheck.processInfo || `Port ${port} is already in use` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.isStarting = true;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const cliPath = path.join(process.cwd(), 'dist', 'src', 'cli', 'index.js');
|
|
149
|
+
const tsCliPath = path.join(process.cwd(), 'src', 'cli', 'index.ts');
|
|
150
|
+
|
|
151
|
+
let child: ChildProcess;
|
|
152
|
+
|
|
153
|
+
// Create isolated environment for child process
|
|
154
|
+
// Remove Next.js dev server specific env vars to avoid conflicts
|
|
155
|
+
const childEnv = { ...process.env };
|
|
156
|
+
// Remove PORT if it might conflict
|
|
157
|
+
if (childEnv.PORT && parseInt(childEnv.PORT) === 3000) {
|
|
158
|
+
delete childEnv.PORT;
|
|
159
|
+
}
|
|
160
|
+
// Remove Next.js specific env vars that might cause conflicts
|
|
161
|
+
delete childEnv.NEXT_TELEMETRY_DISABLED;
|
|
162
|
+
// Gateway server doesn't need Next.js, so we can use any NODE_ENV
|
|
163
|
+
childEnv.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
164
|
+
|
|
165
|
+
// Check if compiled code exists
|
|
166
|
+
if (fs.existsSync(cliPath)) {
|
|
167
|
+
// Use compiled JavaScript
|
|
168
|
+
child = spawn(process.execPath, [cliPath, 'start', '-p', port.toString()], {
|
|
169
|
+
detached: false, // Keep attached for proper tracking
|
|
170
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
171
|
+
cwd: process.cwd(),
|
|
172
|
+
env: childEnv,
|
|
173
|
+
});
|
|
174
|
+
} else if (fs.existsSync(tsCliPath)) {
|
|
175
|
+
// In development, try to use tsx via npx
|
|
176
|
+
// Note: tsx should be installed as dev dependency for this to work
|
|
177
|
+
child = spawn('npx', ['--yes', 'tsx', tsCliPath, 'start', '-p', port.toString()], {
|
|
178
|
+
detached: false, // Keep attached for proper tracking
|
|
179
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
180
|
+
cwd: process.cwd(),
|
|
181
|
+
env: childEnv,
|
|
182
|
+
shell: true, // Use shell to resolve npx
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
this.isStarting = false;
|
|
186
|
+
return {
|
|
187
|
+
status: 'stopped',
|
|
188
|
+
error: 'CLI not found. Please run "npm run build" first, or ensure src/cli/index.ts exists.'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.childProcess = child;
|
|
193
|
+
|
|
194
|
+
// Collect error output for better error messages
|
|
195
|
+
let errorOutput = '';
|
|
196
|
+
let hasExited = false;
|
|
197
|
+
let exitCode: number | null = null;
|
|
198
|
+
|
|
199
|
+
// Handle process output (optional, for debugging)
|
|
200
|
+
// Use setImmediate to avoid blocking the event loop
|
|
201
|
+
child.stdout?.on('data', (data) => {
|
|
202
|
+
setImmediate(() => {
|
|
203
|
+
const output = data.toString();
|
|
204
|
+
// Only log if it's not empty and not just whitespace
|
|
205
|
+
if (output.trim()) {
|
|
206
|
+
console.log(`[Gateway Service] ${output}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
child.stderr?.on('data', (data) => {
|
|
212
|
+
setImmediate(() => {
|
|
213
|
+
const errorText = data.toString();
|
|
214
|
+
console.error(`[Gateway Service Error] ${errorText}`);
|
|
215
|
+
errorOutput += errorText;
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Handle process exit
|
|
220
|
+
child.on('exit', (code, signal) => {
|
|
221
|
+
hasExited = true;
|
|
222
|
+
exitCode = code;
|
|
223
|
+
console.log(`[Gateway Service] Process exited with code ${code}, signal ${signal}`);
|
|
224
|
+
this.childProcess = null;
|
|
225
|
+
this.isStarting = false;
|
|
226
|
+
|
|
227
|
+
// Update database status
|
|
228
|
+
updateServiceStatus({ status: 'stopped', pid: null });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Wait a bit to see if process starts successfully
|
|
232
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
233
|
+
|
|
234
|
+
// Check if process exited during startup
|
|
235
|
+
if (hasExited || child.killed || child.exitCode !== null) {
|
|
236
|
+
this.isStarting = false;
|
|
237
|
+
this.childProcess = null;
|
|
238
|
+
|
|
239
|
+
// Extract meaningful error message
|
|
240
|
+
let errorMessage = 'Service failed to start';
|
|
241
|
+
if (errorOutput) {
|
|
242
|
+
// Try to extract the main error message
|
|
243
|
+
const errorMatch = errorOutput.match(/Error: ([^\n]+)/);
|
|
244
|
+
if (errorMatch) {
|
|
245
|
+
errorMessage = errorMatch[1];
|
|
246
|
+
} else if (errorOutput.includes('production build')) {
|
|
247
|
+
errorMessage = 'Production build not found. Please run "npm run build" first, or use development mode.';
|
|
248
|
+
} else {
|
|
249
|
+
// Use first meaningful line of error
|
|
250
|
+
const lines = errorOutput.split('\n').filter(line => line.trim());
|
|
251
|
+
if (lines.length > 0) {
|
|
252
|
+
errorMessage = lines[0].substring(0, 200); // Limit length
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { status: 'stopped', error: errorMessage };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Save status to database
|
|
261
|
+
const pid = child.pid || null;
|
|
262
|
+
const startedAt = new Date().toISOString();
|
|
263
|
+
setServiceStatus({
|
|
264
|
+
status: 'running',
|
|
265
|
+
port,
|
|
266
|
+
pid,
|
|
267
|
+
started_at: startedAt,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
this.isStarting = false;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
status: 'running',
|
|
274
|
+
port,
|
|
275
|
+
pid,
|
|
276
|
+
started_at: startedAt,
|
|
277
|
+
};
|
|
278
|
+
} catch (error: any) {
|
|
279
|
+
this.isStarting = false;
|
|
280
|
+
this.childProcess = null;
|
|
281
|
+
return { status: 'stopped', error: error.message || 'Failed to start service' };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Stop the gateway service
|
|
287
|
+
*/
|
|
288
|
+
async stop(): Promise<ServiceStatusResponse> {
|
|
289
|
+
const currentStatus = await this.getStatus();
|
|
290
|
+
|
|
291
|
+
if (currentStatus.status === 'stopped') {
|
|
292
|
+
return { status: 'stopped' };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
// If we have a reference to the child process, kill it
|
|
297
|
+
if (this.childProcess) {
|
|
298
|
+
this.childProcess.kill('SIGTERM');
|
|
299
|
+
this.childProcess = null;
|
|
300
|
+
} else if (currentStatus.pid) {
|
|
301
|
+
// Try to kill by PID
|
|
302
|
+
try {
|
|
303
|
+
process.kill(currentStatus.pid, 'SIGTERM');
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// Process might not exist
|
|
306
|
+
console.warn(`Failed to kill process ${currentStatus.pid}:`, error);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Update database status
|
|
311
|
+
updateServiceStatus({ status: 'stopped', pid: null });
|
|
312
|
+
|
|
313
|
+
return { status: 'stopped' };
|
|
314
|
+
} catch (error: any) {
|
|
315
|
+
return { status: 'stopped', error: error.message || 'Failed to stop service' };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Export singleton instance
|
|
321
|
+
export const serviceManager = new ServiceManager();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: [
|
|
4
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
5
|
+
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
colors: {
|
|
10
|
+
background: 'var(--background)',
|
|
11
|
+
foreground: 'var(--foreground)',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
plugins: [],
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
5
|
+
"jsx": "preserve",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noEmit": false,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"plugins": [
|
|
18
|
+
{
|
|
19
|
+
"name": "next"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"paths": {
|
|
23
|
+
"@/*": ["./src/*"]
|
|
24
|
+
},
|
|
25
|
+
"outDir": "./dist"
|
|
26
|
+
},
|
|
27
|
+
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"],
|
|
28
|
+
"exclude": ["node_modules", "dist"]
|
|
29
|
+
}
|