ai-agent-router 0.1.0 → 0.1.1

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.
@@ -0,0 +1,227 @@
1
+ ---
2
+ name: ui-ux-pro-max
3
+ description: "UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
4
+ ---
5
+
6
+ # UI/UX Pro Max - Design Intelligence
7
+
8
+ Searchable database of UI styles, color palettes, font pairings, chart types, product recommendations, UX guidelines, and stack-specific best practices.
9
+
10
+ ## Prerequisites
11
+
12
+ Check if Python is installed:
13
+
14
+ ```bash
15
+ python3 --version || python --version
16
+ ```
17
+
18
+ If Python is not installed, instruct the user to install it based on their OS.
19
+
20
+ ---
21
+
22
+ ## How to Use This Skill
23
+
24
+ When user requests UI/UX work (design, build, create, implement, review, fix, improve), follow this workflow:
25
+
26
+ ### Step 1: Analyze User Requirements
27
+
28
+ Extract key information from user request:
29
+ - **Product type**: SaaS, e-commerce, portfolio, dashboard, landing page, etc.
30
+ - **Style keywords**: minimal, playful, professional, elegant, dark mode, etc.
31
+ - **Industry**: healthcare, fintech, gaming, education, etc.
32
+ - **Stack**: React, Vue, Next.js, or default to `html-tailwind`
33
+
34
+ ### Step 2: Search Relevant Domains
35
+
36
+ Use `run_shell_command` to execute the `search.py` script multiple times to gather comprehensive information. Search until you have enough context.
37
+
38
+ **Command Format:**
39
+
40
+ ```bash
41
+ python3 .shared/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain>
42
+ ```
43
+
44
+ **Recommended search order:**
45
+
46
+ 1. **Product** - Get style recommendations for product type
47
+ 2. **Style** - Get detailed style guide (colors, effects, frameworks)
48
+ 3. **Typography** - Get font pairings with Google Fonts imports
49
+ 4. **Color** - Get color palette (Primary, Secondary, CTA, Background, Text, Border)
50
+ 5. **Landing** - Get page structure (if landing page)
51
+ 6. **Chart** - Get chart recommendations (if dashboard/analytics)
52
+ 7. **UX** - Get best practices and anti-patterns
53
+ 8. **Stack** - Get stack-specific guidelines (default: html-tailwind)
54
+
55
+ ### Step 3: Stack Guidelines (Default: html-tailwind)
56
+
57
+ If user doesn't specify a stack, **default to `html-tailwind`**.
58
+
59
+ ```bash
60
+ python3 .shared/ui-ux-pro-max/scripts/search.py "<keyword>" --stack html-tailwind
61
+ ```
62
+
63
+ Available stacks: `html-tailwind`, `react`, `nextjs`, `vue`, `svelte`, `swiftui`, `react-native`, `flutter`
64
+
65
+ ---
66
+
67
+ ## Search Reference
68
+
69
+ ### Available Domains
70
+
71
+ | Domain | Use For | Example Keywords |
72
+ |--------|---------|------------------|
73
+ | `product` | Product type recommendations | SaaS, e-commerce, portfolio, healthcare, beauty, service |
74
+ | `style` | UI styles, colors, effects | glassmorphism, minimalism, dark mode, brutalism |
75
+ | `typography` | Font pairings, Google Fonts | elegant, playful, professional, modern |
76
+ | `color` | Color palettes by product type | saas, ecommerce, healthcare, beauty, fintech, service |
77
+ | `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof |
78
+ | `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie |
79
+ | `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading |
80
+ | `prompt` | AI prompts, CSS keywords | (style name) |
81
+
82
+ ### Available Stacks
83
+
84
+ | Stack | Focus |
85
+ |-------|-------|
86
+ | `html-tailwind` | Tailwind utilities, responsive, a11y (DEFAULT) |
87
+ | `react` | State, hooks, performance, patterns |
88
+ | `nextjs` | SSR, routing, images, API routes |
89
+ | `vue` | Composition API, Pinia, Vue Router |
90
+ | `svelte` | Runes, stores, SvelteKit |
91
+ | `swiftui` | Views, State, Navigation, Animation |
92
+ | `react-native` | Components, Navigation, Lists |
93
+ | `flutter` | Widgets, State, Layout, Theming |
94
+
95
+ ---
96
+
97
+ ## Example Workflow
98
+
99
+ **User request:** "Làm landing page cho dịch vụ chăm sóc da chuyên nghiệp"
100
+
101
+ **AI should:**
102
+
103
+ 1. Search product type:
104
+ ```bash
105
+ python3 .shared/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --domain product
106
+ ```
107
+
108
+ 2. Search style (based on industry: beauty, elegant):
109
+ ```bash
110
+ python3 .shared/ui-ux-pro-max/scripts/search.py "elegant minimal soft" --domain style
111
+ ```
112
+
113
+ 3. Search typography:
114
+ ```bash
115
+ python3 .shared/ui-ux-pro-max/scripts/search.py "elegant luxury" --domain typography
116
+ ```
117
+
118
+ 4. Search color palette:
119
+ ```bash
120
+ python3 .shared/ui-ux-pro-max/scripts/search.py "beauty spa wellness" --domain color
121
+ ```
122
+
123
+ 5. Search landing page structure:
124
+ ```bash
125
+ python3 .shared/ui-ux-pro-max/scripts/search.py "hero-centric social-proof" --domain landing
126
+ ```
127
+
128
+ 6. Search UX guidelines:
129
+ ```bash
130
+ python3 .shared/ui-ux-pro-max/scripts/search.py "animation" --domain ux
131
+ python3 .shared/ui-ux-pro-max/scripts/search.py "accessibility" --domain ux
132
+ ```
133
+
134
+ 7. Search stack guidelines (default: html-tailwind):
135
+ ```bash
136
+ python3 .shared/ui-ux-pro-max/scripts/search.py "layout responsive" --stack html-tailwind
137
+ ```
138
+
139
+ **Then:** Synthesize all search results and implement the design.
140
+
141
+ ---
142
+
143
+ ## Tips for Better Results
144
+
145
+ 1. **Be specific with keywords** - "healthcare SaaS dashboard" > "app"
146
+ 2. **Search multiple times** - Different keywords reveal different insights
147
+ 3. **Combine domains** - Style + Typography + Color = Complete design system
148
+ 4. **Always check UX** - Search "animation", "z-index", "accessibility" for common issues
149
+ 5. **Use stack flag** - Get implementation-specific best practices
150
+ 6. **Iterate** - If first search doesn't match, try different keywords
151
+
152
+ ---
153
+
154
+ ## Common Rules for Professional UI
155
+
156
+ These are frequently overlooked issues that make UI look unprofessional:
157
+
158
+ ### Icons & Visual Elements
159
+
160
+ | Rule | Do | Don't |
161
+ |------|----|----- |
162
+ | **No emoji icons** | Use SVG icons (Heroicons, Lucide, Simple Icons) | Use emojis like 🎨 🚀 ⚙️ as UI icons |
163
+ | **Stable hover states** | Use color/opacity transitions on hover | Use scale transforms that shift layout |
164
+ | **Correct brand logos** | Research official SVG from Simple Icons | Guess or use incorrect logo paths |
165
+ | **Consistent icon sizing** | Use fixed viewBox (24x24) with w-6 h-6 | Mix different icon sizes randomly |
166
+
167
+ ### Interaction & Cursor
168
+
169
+ | Rule | Do | Don't |
170
+ |------|----|----- |
171
+ | **Cursor pointer** | Add `cursor-pointer` to all clickable/hoverable cards | Leave default cursor on interactive elements |
172
+ | **Hover feedback** | Provide visual feedback (color, shadow, border) | No indication element is interactive |
173
+ | **Smooth transitions** | Use `transition-colors duration-200` | Instant state changes or too slow (>500ms) |
174
+
175
+ ### Light/Dark Mode Contrast
176
+
177
+ | Rule | Do | Don't |
178
+ |------|----|----- |
179
+ | **Glass card light mode** | Use `bg-white/80` or higher opacity | Use `bg-white/10` (too transparent) |
180
+ | **Text contrast light** | Use `#0F172A` (slate-900) for text | Use `#94A3B8` (slate-400) for body text |
181
+ | **Muted text light** | Use `#475569` (slate-600) minimum | Use gray-400 or lighter |
182
+ | **Border visibility** | Use `border-gray-200` in light mode | Use `border-white/10` (invisible) |
183
+
184
+ ### Layout & Spacing
185
+
186
+ | Rule | Do | Don't |
187
+ |------|----|----- |
188
+ | **Floating navbar** | Add `top-4 left-4 right-4` spacing | Stick navbar to `top-0 left-0 right-0` |
189
+ | **Content padding** | Account for fixed navbar height | Let content hide behind fixed elements |
190
+ | **Consistent max-width** | Use same `max-w-6xl` or `max-w-7xl` | Mix different container widths |
191
+
192
+ ---
193
+
194
+ ## Pre-Delivery Checklist
195
+
196
+ Before delivering UI code, verify these items:
197
+
198
+ ### Visual Quality
199
+ - [ ] No emojis used as icons (use SVG instead)
200
+ - [ ] All icons from consistent icon set (Heroicons, Lucide, Simple Icons)
201
+ - [ ] Brand logos are correct (verified from Simple Icons)
202
+ - [ ] Hover states don't cause layout shift
203
+ - [ ] Use theme colors directly (bg-primary) not var() wrapper
204
+
205
+ ### Interaction
206
+ - [ ] All clickable elements have `cursor-pointer`
207
+ - [ ] Hover states provide clear visual feedback
208
+ - [ ] Transitions are smooth (150-300ms)
209
+ - [ ] Focus states visible for keyboard navigation
210
+
211
+ ### Light/Dark Mode
212
+ - [ ] Light mode text has sufficient contrast (4.5:1 minimum)
213
+ - [ ] Glass/transparent elements visible in light mode
214
+ - [ ] Borders visible in both modes
215
+ - [ ] Test both modes before delivery
216
+
217
+ ### Layout
218
+ - [ ] Floating elements have proper spacing from edges
219
+ - [ ] No content hidden behind fixed navbars
220
+ - [ ] Responsive at 320px, 768px, 1024px, 1440px
221
+ - [ ] No horizontal scroll on mobile
222
+
223
+ ### Accessibility
224
+ - [ ] All images have alt text
225
+ - [ ] Form inputs have labels
226
+ - [ ] Color is not the only indicator
227
+ - [ ] `prefers-reduced-motion` respected
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "ai-agent-router",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A unified API gateway for managing multiple AI model providers (Anthropic, OpenAI, Gemini, etc.)",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "bin": {
7
7
  "aar": "./dist/src/cli/index.js"
8
8
  },
9
9
  "scripts": {
10
- "dev": "next dev",
10
+ "dev": "next dev -p 9527",
11
11
  "build": "next build && tsc",
12
12
  "postbuild": "node scripts/fix-esm-imports.js",
13
13
  "start": "node dist/src/cli/index.js start",
@@ -9,7 +9,7 @@ export async function GET(request: NextRequest) {
9
9
  try {
10
10
  // Initialize database on request (safer than module load)
11
11
  getDatabase();
12
-
12
+
13
13
  const { searchParams } = new URL(request.url);
14
14
  const key = searchParams.get('key');
15
15
 
@@ -39,18 +39,20 @@ export async function POST(request: NextRequest) {
39
39
  try {
40
40
  // Initialize database on request
41
41
  getDatabase();
42
-
42
+
43
43
  const body = await request.json();
44
44
  const { key, value } = body;
45
45
 
46
- if (!key || value === undefined) {
46
+ if (!key) {
47
47
  return NextResponse.json(
48
- { error: 'Key and value are required' },
48
+ { error: 'Key is required' },
49
49
  { status: 400 }
50
50
  );
51
51
  }
52
52
 
53
- const config = setConfig(key, typeof value === 'string' ? value : JSON.stringify(value));
53
+ // Allow value to be null, undefined, or empty string (save as empty string)
54
+ const config = setConfig(key, (value === undefined || value === null) ? '' : typeof value === 'string' ? value : JSON.stringify(value));
55
+
54
56
  return NextResponse.json(config);
55
57
  } catch (error: any) {
56
58
  console.error('Config POST API error:', error);
package/src/app/page.tsx CHANGED
@@ -93,7 +93,7 @@ export default function Home() {
93
93
  const res = await fetch('/api/config');
94
94
  const data = await res.json();
95
95
  setConfig({
96
- port: data.port || '3000',
96
+ port: data.port || '1357',
97
97
  api_key: data.api_key || '',
98
98
  });
99
99
  } catch (error) {
@@ -124,17 +124,19 @@ export default function Home() {
124
124
 
125
125
  const handleStart = async () => {
126
126
  if (starting || serviceStatus.status === 'running') return;
127
-
127
+
128
128
  setStarting(true);
129
129
  try {
130
- const port = config.port ? parseInt(config.port, 10) : undefined;
130
+ // Use default port if not configured
131
+ const port = config.port ? parseInt(config.port, 10) : 1357;
132
+
131
133
  const res = await fetch('/api/service/start', {
132
134
  method: 'POST',
133
135
  headers: { 'Content-Type': 'application/json' },
134
136
  body: JSON.stringify({ port }),
135
137
  });
136
138
  const data = await res.json();
137
-
139
+
138
140
  if (data.error) {
139
141
  showToast(`启动失败: ${data.error}`, 'error');
140
142
  } else {
@@ -382,10 +384,10 @@ export default function Home() {
382
384
  onChange={(e) => setConfig({ ...config, port: e.target.value })}
383
385
  autoComplete="off"
384
386
  className="block w-full rounded-lg border border-slate-200 bg-white/80 px-3 py-2 text-xs shadow-sm transition-all duration-300 focus:border-emerald-400 focus:ring-2 focus:ring-emerald-400/20 focus:ring-offset-1"
385
- placeholder="3000"
387
+ placeholder="1357"
386
388
  />
387
389
  <p className="mt-1 text-xs text-slate-400">
388
- 提示:如果 Web UI 运行在 3000 端口,建议将服务端口设置为 3001 或其他端口以避免冲突
390
+ 提示:API 网关默认端口为 1357
389
391
  </p>
390
392
  </div>
391
393
 
package/src/cli/index.ts CHANGED
@@ -1,60 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
- import { GatewayServer } from '../server/gateway-server';
5
- import { getDatabase } from '../db/database';
6
- import { getConfig, setConfig } from '../db/queries';
4
+ import { spawn } from 'child_process';
5
+ import path from 'path';
7
6
 
8
7
  const program = new Command();
9
8
 
10
9
  program
11
10
  .name('aar')
12
- .description('AI Agent Router - Unified gateway for managing multiple AI model providers')
11
+ .description('AI Agent Router - Web UI for managing the API gateway')
13
12
  .version('0.1.0');
14
13
 
15
14
  program
16
15
  .command('start')
17
- .description('Start the API gateway server (gateway only, no Web UI)')
18
- .option('-p, --port <port>', 'Port to listen on', '3000')
19
- .option('--hostname <hostname>', 'Hostname to listen on', 'localhost')
16
+ .description('Start the Web UI management interface')
17
+ .option('-p, --port <port>', 'Port for Web UI', '9527')
20
18
  .action(async (options) => {
21
- const port = parseInt(options.port || '3000');
22
- const hostname = options.hostname || 'localhost';
19
+ const port = parseInt(options.port || '9527');
23
20
 
24
- // Initialize database to get config
25
- try {
26
- getDatabase();
27
- } catch (error: any) {
28
- console.error('Failed to initialize database:', error);
29
- process.exit(1);
30
- }
21
+ console.log(`Starting AI Agent Router Web UI`);
22
+ console.log(` Port: ${port}`);
23
+ console.log(` Access the UI at: http://localhost:${port}`);
24
+ console.log('');
31
25
 
32
- // Get API key from config if configured
33
- const apiKeyConfig = getConfig('api_key');
34
- const apiKey = apiKeyConfig ? apiKeyConfig.value : undefined;
26
+ // Start Web UI using Next.js
27
+ const isDev = process.env.NODE_ENV !== 'production';
28
+ let uiProcess: ReturnType<typeof spawn>;
35
29
 
36
- console.log(`Starting AI Agent Router Gateway Server`);
37
- console.log(` Port: ${port}`);
38
- console.log(` Hostname: ${hostname}`);
39
- if (apiKey) {
40
- console.log(` API Key: Configured (authentication enabled)`);
30
+ if (isDev) {
31
+ // In development mode, use next dev
32
+ uiProcess = spawn('npm', ['run', 'dev', '--', '-p', port.toString()], {
33
+ cwd: process.cwd(),
34
+ stdio: ['ignore', 'inherit', 'inherit'],
35
+ env: { ...process.env, NEXT_TELEMETRY_DISABLED: '1', PORT: port.toString() },
36
+ });
41
37
  } else {
42
- console.log(` API Key: Not configured (authentication disabled)`);
38
+ // In production mode, start the built Next.js app
39
+ const serverPath = path.join(process.cwd(), 'node_modules', 'next', 'dist', 'bin', 'next');
40
+ uiProcess = spawn(process.execPath, [serverPath, 'start', '-p', port.toString()], {
41
+ cwd: process.cwd(),
42
+ stdio: ['ignore', 'inherit', 'inherit'],
43
+ env: { ...process.env, PORT: port.toString(), NODE_ENV: 'production' },
44
+ });
43
45
  }
44
46
 
45
- // Create and start gateway server
46
- const server = new GatewayServer({
47
- port,
48
- hostname,
49
- apiKey,
47
+ // Handle UI process exit
48
+ uiProcess.on('exit', (code) => {
49
+ console.log(`Web UI process exited with code ${code}`);
50
+ process.exit(code || 0);
50
51
  });
51
52
 
52
- try {
53
- await server.start();
54
- } catch (error: any) {
55
- console.error(`Failed to start gateway server: ${error.message}`);
53
+ uiProcess.on('error', (error) => {
54
+ console.error(`Failed to start Web UI: ${error.message}`);
56
55
  process.exit(1);
57
- }
56
+ });
57
+
58
+ // Keep the process alive
59
+ process.on('SIGINT', () => {
60
+ console.log('\nShutting down...');
61
+ uiProcess.kill('SIGTERM');
62
+ process.exit(0);
63
+ });
58
64
  });
59
65
 
60
66
  program
@@ -1,8 +1,8 @@
1
- import { spawn, ChildProcess, exec } from 'child_process';
1
+ import { exec } from 'child_process';
2
2
  import { promisify } from 'util';
3
- import { getServiceStatus, setServiceStatus, updateServiceStatus, clearServiceStatus } from '@/db/queries';
4
- import path from 'path';
5
- import fs from 'fs';
3
+ import { getServiceStatus, setServiceStatus, updateServiceStatus, getConfig } from '@/db/queries';
4
+ import { getDatabase } from '@/db/database';
5
+ import { GatewayServer } from './gateway-server';
6
6
  import net from 'net';
7
7
 
8
8
  const execAsync = promisify(exec);
@@ -16,11 +16,11 @@ export interface ServiceStatusResponse {
16
16
  }
17
17
 
18
18
  class ServiceManager {
19
- private childProcess: ChildProcess | null = null;
19
+ private gatewayServer: GatewayServer | null = null;
20
20
  private isStarting: boolean = false;
21
21
 
22
22
  /**
23
- * Check if a process with the given PID exists
23
+ * Check if a process with given PID exists
24
24
  */
25
25
  private async checkProcessExists(pid: number): Promise<boolean> {
26
26
  try {
@@ -39,12 +39,12 @@ class ServiceManager {
39
39
  private async checkPortAvailable(port: number): Promise<boolean> {
40
40
  return new Promise((resolve) => {
41
41
  const server = net.createServer();
42
-
42
+
43
43
  server.listen(port, () => {
44
44
  server.once('close', () => resolve(true));
45
45
  server.close();
46
46
  });
47
-
47
+
48
48
  server.on('error', () => {
49
49
  resolve(false);
50
50
  });
@@ -57,12 +57,12 @@ class ServiceManager {
57
57
  private async checkPortInUse(port: number): Promise<{ inUse: boolean; processInfo?: string }> {
58
58
  return new Promise((resolve) => {
59
59
  const server = net.createServer();
60
-
60
+
61
61
  server.listen(port, () => {
62
62
  server.once('close', () => resolve({ inUse: false }));
63
63
  server.close();
64
64
  });
65
-
65
+
66
66
  server.on('error', (err: any) => {
67
67
  if (err.code === 'EADDRINUSE') {
68
68
  // Try to get process info
@@ -90,7 +90,7 @@ class ServiceManager {
90
90
  */
91
91
  async getStatus(): Promise<ServiceStatusResponse> {
92
92
  const dbStatus = getServiceStatus();
93
-
93
+
94
94
  if (!dbStatus) {
95
95
  return { status: 'stopped' };
96
96
  }
@@ -114,7 +114,7 @@ class ServiceManager {
114
114
  }
115
115
 
116
116
  /**
117
- * Start the gateway service
117
+ * Start gateway service
118
118
  */
119
119
  async start(port: number): Promise<ServiceStatusResponse> {
120
120
  // Prevent concurrent starts
@@ -128,138 +128,42 @@ class ServiceManager {
128
128
  return { status: 'running', error: 'Service is already running', port: currentStatus.port, pid: currentStatus.pid };
129
129
  }
130
130
 
131
- // Check port availability with more details
131
+ // Check port availability
132
132
  const portCheck = await this.checkPortInUse(port);
133
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
134
  return { status: 'stopped', error: portCheck.processInfo || `Port ${port} is already in use` };
143
135
  }
144
136
 
145
137
  this.isStarting = true;
146
138
 
147
139
  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 {
140
+ // Initialize database
141
+ try {
142
+ getDatabase();
143
+ } catch (error: any) {
144
+ console.error('Failed to initialize database:', error);
185
145
  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
- };
146
+ return { status: 'stopped', error: 'Failed to initialize database' };
190
147
  }
191
148
 
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
- });
149
+ // Get API key from config if configured
150
+ const apiKeyConfig = getConfig('api_key');
151
+ const apiKey = apiKeyConfig ? apiKeyConfig.value : undefined;
218
152
 
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 });
153
+ // Create and start gateway server directly in-process
154
+ const server = new GatewayServer({
155
+ port,
156
+ hostname: 'localhost',
157
+ apiKey,
229
158
  });
230
159
 
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
- }
160
+ await server.start();
161
+ this.gatewayServer = server;
259
162
 
260
- // Save status to database
261
- const pid = child.pid || null;
163
+ // Use current process PID
164
+ const pid = process.pid;
262
165
  const startedAt = new Date().toISOString();
166
+
263
167
  setServiceStatus({
264
168
  status: 'running',
265
169
  port,
@@ -269,6 +173,8 @@ class ServiceManager {
269
173
 
270
174
  this.isStarting = false;
271
175
 
176
+ console.log(`Gateway server started on port ${port}`);
177
+
272
178
  return {
273
179
  status: 'running',
274
180
  port,
@@ -277,7 +183,8 @@ class ServiceManager {
277
183
  };
278
184
  } catch (error: any) {
279
185
  this.isStarting = false;
280
- this.childProcess = null;
186
+ this.gatewayServer = null;
187
+ console.error(`Failed to start gateway service: ${error.message}`);
281
188
  return { status: 'stopped', error: error.message || 'Failed to start service' };
282
189
  }
283
190
  }
@@ -287,18 +194,18 @@ class ServiceManager {
287
194
  */
288
195
  async stop(): Promise<ServiceStatusResponse> {
289
196
  const currentStatus = await this.getStatus();
290
-
197
+
291
198
  if (currentStatus.status === 'stopped') {
292
199
  return { status: 'stopped' };
293
200
  }
294
201
 
295
202
  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
203
+ // If we have a reference to gateway server, stop it
204
+ if (this.gatewayServer) {
205
+ await this.gatewayServer.stop();
206
+ this.gatewayServer = null;
207
+ } else if (currentStatus.pid && currentStatus.pid !== process.pid) {
208
+ // Only try to kill by PID if it's a different process
302
209
  try {
303
210
  process.kill(currentStatus.pid, 'SIGTERM');
304
211
  } catch (error) {