@itz4blitz/agentful 1.2.0 → 1.3.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/README.md +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
package/lib/server/index.js
DELETED
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agentful Remote Execution Server
|
|
3
|
-
*
|
|
4
|
-
* Secure HTTP server for remote agent execution with multiple authentication modes.
|
|
5
|
-
*
|
|
6
|
-
* @module server
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import http from 'http';
|
|
10
|
-
import https from 'https';
|
|
11
|
-
import { readFileSync } from 'fs';
|
|
12
|
-
import { createAuthMiddleware, captureRawBody } from './auth.js';
|
|
13
|
-
import {
|
|
14
|
-
executeAgent,
|
|
15
|
-
getExecutionStatus,
|
|
16
|
-
listExecutions,
|
|
17
|
-
startPeriodicCleanup,
|
|
18
|
-
} from './executor.js';
|
|
19
|
-
import { listAvailableAgents } from '../ci/claude-action-integration.js';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Rate limiting configuration
|
|
23
|
-
*/
|
|
24
|
-
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
|
|
25
|
-
const RATE_LIMIT_MAX_REQUESTS = 60; // 60 requests per minute per IP
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* In-memory rate limit store
|
|
29
|
-
*/
|
|
30
|
-
const rateLimitStore = new Map();
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Clean up old rate limit entries periodically (every 2 minutes)
|
|
34
|
-
*/
|
|
35
|
-
setInterval(() => {
|
|
36
|
-
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
|
|
37
|
-
for (const [ip, data] of rateLimitStore.entries()) {
|
|
38
|
-
if (data.windowStart < cutoff) {
|
|
39
|
-
rateLimitStore.delete(ip);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}, 2 * 60 * 1000);
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Rate limiting middleware
|
|
46
|
-
* @param {Object} req - Request object
|
|
47
|
-
* @param {Object} res - Response object
|
|
48
|
-
* @returns {boolean} True if request is allowed
|
|
49
|
-
*/
|
|
50
|
-
function checkRateLimit(req, res) {
|
|
51
|
-
const clientIP = req.socket.remoteAddress;
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
|
|
54
|
-
let rateLimitData = rateLimitStore.get(clientIP);
|
|
55
|
-
|
|
56
|
-
// Initialize or reset window
|
|
57
|
-
if (!rateLimitData || now - rateLimitData.windowStart > RATE_LIMIT_WINDOW_MS) {
|
|
58
|
-
rateLimitData = {
|
|
59
|
-
windowStart: now,
|
|
60
|
-
requestCount: 0,
|
|
61
|
-
};
|
|
62
|
-
rateLimitStore.set(clientIP, rateLimitData);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Increment request count
|
|
66
|
-
rateLimitData.requestCount++;
|
|
67
|
-
|
|
68
|
-
// Check if limit exceeded
|
|
69
|
-
if (rateLimitData.requestCount > RATE_LIMIT_MAX_REQUESTS) {
|
|
70
|
-
res.writeHead(429, {
|
|
71
|
-
'Content-Type': 'application/json',
|
|
72
|
-
'Retry-After': Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
|
|
73
|
-
});
|
|
74
|
-
res.end(
|
|
75
|
-
JSON.stringify({
|
|
76
|
-
error: 'Too Many Requests',
|
|
77
|
-
message: `Rate limit exceeded. Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute.`,
|
|
78
|
-
retryAfter: Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
|
|
79
|
-
})
|
|
80
|
-
);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Parse JSON body manually (to support raw body capture)
|
|
89
|
-
* @param {string} rawBody - Raw request body
|
|
90
|
-
* @returns {Object} Parsed JSON
|
|
91
|
-
*/
|
|
92
|
-
function parseJSON(rawBody) {
|
|
93
|
-
try {
|
|
94
|
-
return JSON.parse(rawBody);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
throw new Error('Invalid JSON body');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Simple request router
|
|
102
|
-
* @param {string} method - HTTP method
|
|
103
|
-
* @param {string} path - Request path
|
|
104
|
-
* @param {Function} handler - Route handler
|
|
105
|
-
* @returns {Object} Route definition
|
|
106
|
-
*/
|
|
107
|
-
function route(method, path, handler) {
|
|
108
|
-
return { method, path, handler };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Match route against request
|
|
113
|
-
* @param {Object} route - Route definition
|
|
114
|
-
* @param {string} method - Request method
|
|
115
|
-
* @param {string} path - Request path
|
|
116
|
-
* @returns {Object|null} Match result with params or null
|
|
117
|
-
*/
|
|
118
|
-
function matchRoute(route, method, path) {
|
|
119
|
-
if (route.method !== method) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Simple path matching with :param support
|
|
124
|
-
const routeParts = route.path.split('/');
|
|
125
|
-
const pathParts = path.split('/');
|
|
126
|
-
|
|
127
|
-
if (routeParts.length !== pathParts.length) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const params = {};
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < routeParts.length; i++) {
|
|
134
|
-
if (routeParts[i].startsWith(':')) {
|
|
135
|
-
const paramName = routeParts[i].substring(1);
|
|
136
|
-
params[paramName] = pathParts[i];
|
|
137
|
-
} else if (routeParts[i] !== pathParts[i]) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return { params };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Create agentful server
|
|
147
|
-
* @param {Object} config - Server configuration
|
|
148
|
-
* @param {string} [config.auth='tailscale'] - Authentication mode
|
|
149
|
-
* @param {number} [config.port=3000] - Server port
|
|
150
|
-
* @param {string} [config.secret] - HMAC secret (required for hmac mode)
|
|
151
|
-
* @param {boolean} [config.https=false] - Enable HTTPS
|
|
152
|
-
* @param {string} [config.cert] - SSL certificate path (for HTTPS)
|
|
153
|
-
* @param {string} [config.key] - SSL key path (for HTTPS)
|
|
154
|
-
* @param {string} [config.projectRoot] - Project root directory
|
|
155
|
-
* @param {string} [config.corsOrigin] - CORS allowed origin (default: same-origin only)
|
|
156
|
-
* @returns {Object} Server instance
|
|
157
|
-
*/
|
|
158
|
-
export function createServer(config = {}) {
|
|
159
|
-
const {
|
|
160
|
-
auth = 'tailscale',
|
|
161
|
-
port = 3000,
|
|
162
|
-
secret,
|
|
163
|
-
https: enableHttps = false,
|
|
164
|
-
cert,
|
|
165
|
-
key,
|
|
166
|
-
projectRoot = process.cwd(),
|
|
167
|
-
corsOrigin = null,
|
|
168
|
-
} = config;
|
|
169
|
-
|
|
170
|
-
// Validate configuration
|
|
171
|
-
if (auth === 'hmac' && !secret) {
|
|
172
|
-
throw new Error('HMAC mode requires --secret');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (enableHttps && (!cert || !key)) {
|
|
176
|
-
throw new Error('HTTPS mode requires --cert and --key');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Create authentication middleware
|
|
180
|
-
const authMiddleware = createAuthMiddleware(auth, { secret });
|
|
181
|
-
|
|
182
|
-
// Define routes
|
|
183
|
-
const routes = [
|
|
184
|
-
// Health check (no auth required)
|
|
185
|
-
route('GET', '/health', async (req, res) => {
|
|
186
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
187
|
-
res.end(
|
|
188
|
-
JSON.stringify({
|
|
189
|
-
status: 'healthy',
|
|
190
|
-
uptime: process.uptime(),
|
|
191
|
-
mode: auth,
|
|
192
|
-
timestamp: Date.now(),
|
|
193
|
-
})
|
|
194
|
-
);
|
|
195
|
-
}),
|
|
196
|
-
|
|
197
|
-
// List available agents
|
|
198
|
-
route('GET', '/agents', async (req, res, params) => {
|
|
199
|
-
try {
|
|
200
|
-
const agents = await listAvailableAgents(projectRoot);
|
|
201
|
-
|
|
202
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
203
|
-
res.end(
|
|
204
|
-
JSON.stringify({
|
|
205
|
-
agents,
|
|
206
|
-
count: agents.length,
|
|
207
|
-
})
|
|
208
|
-
);
|
|
209
|
-
} catch (error) {
|
|
210
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
211
|
-
res.end(
|
|
212
|
-
JSON.stringify({
|
|
213
|
-
error: 'Failed to list agents',
|
|
214
|
-
message: error.message,
|
|
215
|
-
})
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
}),
|
|
219
|
-
|
|
220
|
-
// Trigger agent execution
|
|
221
|
-
route('POST', '/trigger', async (req, res, params) => {
|
|
222
|
-
try {
|
|
223
|
-
const body = parseJSON(req.rawBody || '');
|
|
224
|
-
|
|
225
|
-
// Validate request
|
|
226
|
-
if (!body.agent) {
|
|
227
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
228
|
-
return res.end(
|
|
229
|
-
JSON.stringify({
|
|
230
|
-
error: 'Validation failed',
|
|
231
|
-
message: 'Missing required field: agent',
|
|
232
|
-
})
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (!body.task) {
|
|
237
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
238
|
-
return res.end(
|
|
239
|
-
JSON.stringify({
|
|
240
|
-
error: 'Validation failed',
|
|
241
|
-
message: 'Missing required field: task',
|
|
242
|
-
})
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Log agent execution request
|
|
247
|
-
console.log(`[${new Date().toISOString()}] Executing agent: ${body.agent}`);
|
|
248
|
-
console.log(`[${new Date().toISOString()}] Task: ${body.task}`);
|
|
249
|
-
|
|
250
|
-
// Start agent execution in background (async mode)
|
|
251
|
-
const executionTimeout = body.timeout || 10 * 60 * 1000; // Default 10 min for execution
|
|
252
|
-
|
|
253
|
-
const result = await executeAgent(body.agent, body.task, {
|
|
254
|
-
projectRoot,
|
|
255
|
-
timeout: executionTimeout,
|
|
256
|
-
env: body.env,
|
|
257
|
-
async: true, // Return immediately with executionId
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
console.log(`[${new Date().toISOString()}] Execution started: ${result.executionId}`);
|
|
261
|
-
|
|
262
|
-
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
263
|
-
res.end(
|
|
264
|
-
JSON.stringify({
|
|
265
|
-
executionId: result.executionId,
|
|
266
|
-
message: 'Agent execution started',
|
|
267
|
-
statusUrl: `/status/${result.executionId}`,
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
} catch (error) {
|
|
271
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
272
|
-
res.end(
|
|
273
|
-
JSON.stringify({
|
|
274
|
-
error: 'Bad request',
|
|
275
|
-
message: error.message,
|
|
276
|
-
})
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
}),
|
|
280
|
-
|
|
281
|
-
// Get execution status
|
|
282
|
-
route('GET', '/status/:executionId', async (req, res, params) => {
|
|
283
|
-
const execution = getExecutionStatus(params.executionId);
|
|
284
|
-
|
|
285
|
-
if (!execution) {
|
|
286
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
287
|
-
return res.end(
|
|
288
|
-
JSON.stringify({
|
|
289
|
-
error: 'Not found',
|
|
290
|
-
message: 'Execution not found',
|
|
291
|
-
})
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
296
|
-
res.end(JSON.stringify(execution));
|
|
297
|
-
}),
|
|
298
|
-
|
|
299
|
-
// List executions
|
|
300
|
-
route('GET', '/executions', async (req, res, params) => {
|
|
301
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
302
|
-
const filters = {
|
|
303
|
-
agent: url.searchParams.get('agent'),
|
|
304
|
-
state: url.searchParams.get('state'),
|
|
305
|
-
limit: parseInt(url.searchParams.get('limit') || '100', 10),
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const executions = listExecutions(filters);
|
|
309
|
-
|
|
310
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
311
|
-
res.end(
|
|
312
|
-
JSON.stringify({
|
|
313
|
-
executions,
|
|
314
|
-
count: executions.length,
|
|
315
|
-
})
|
|
316
|
-
);
|
|
317
|
-
}),
|
|
318
|
-
];
|
|
319
|
-
|
|
320
|
-
// Request handler
|
|
321
|
-
const requestHandler = (req, res) => {
|
|
322
|
-
const startTime = Date.now();
|
|
323
|
-
const clientIP = req.socket.remoteAddress;
|
|
324
|
-
|
|
325
|
-
// Log incoming request
|
|
326
|
-
const timestamp = new Date().toISOString();
|
|
327
|
-
console.log(`[${timestamp}] ${req.method} ${req.url} from ${clientIP}`);
|
|
328
|
-
|
|
329
|
-
// Add CORS headers (restricted by default)
|
|
330
|
-
if (corsOrigin) {
|
|
331
|
-
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
|
|
332
|
-
} else {
|
|
333
|
-
// Same-origin only - no CORS header
|
|
334
|
-
// Browsers will enforce same-origin policy
|
|
335
|
-
}
|
|
336
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
337
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Agentful-Signature, X-Agentful-Timestamp');
|
|
338
|
-
|
|
339
|
-
// Handle preflight
|
|
340
|
-
if (req.method === 'OPTIONS') {
|
|
341
|
-
res.writeHead(204);
|
|
342
|
-
const duration = Date.now() - startTime;
|
|
343
|
-
console.log(`[${new Date().toISOString()}] Response sent: 204 (${duration}ms)`);
|
|
344
|
-
return res.end();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Apply rate limiting
|
|
348
|
-
if (!checkRateLimit(req, res)) {
|
|
349
|
-
const duration = Date.now() - startTime;
|
|
350
|
-
console.log(`[${new Date().toISOString()}] Response sent: 429 Rate Limited (${duration}ms)`);
|
|
351
|
-
return; // Rate limit exceeded, response already sent
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Intercept res.end to log responses
|
|
355
|
-
const originalEnd = res.end;
|
|
356
|
-
res.end = function(...args) {
|
|
357
|
-
const duration = Date.now() - startTime;
|
|
358
|
-
const statusCode = res.statusCode;
|
|
359
|
-
console.log(`[${new Date().toISOString()}] Response sent: ${statusCode} (${duration}ms)`);
|
|
360
|
-
originalEnd.apply(res, args);
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
// Capture raw body (needed for HMAC verification)
|
|
364
|
-
captureRawBody(req, res, () => {
|
|
365
|
-
// Apply authentication (except for /health)
|
|
366
|
-
if (req.url !== '/health') {
|
|
367
|
-
authMiddleware(req, res, () => {
|
|
368
|
-
handleRequest(req, res);
|
|
369
|
-
});
|
|
370
|
-
} else {
|
|
371
|
-
handleRequest(req, res);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
// Route request to handler
|
|
377
|
-
function handleRequest(req, res) {
|
|
378
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
379
|
-
const path = url.pathname;
|
|
380
|
-
|
|
381
|
-
// Find matching route
|
|
382
|
-
for (const routeDef of routes) {
|
|
383
|
-
const match = matchRoute(routeDef, req.method, path);
|
|
384
|
-
if (match) {
|
|
385
|
-
return routeDef.handler(req, res, match.params);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// No route matched - 404
|
|
390
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
391
|
-
res.end(
|
|
392
|
-
JSON.stringify({
|
|
393
|
-
error: 'Not found',
|
|
394
|
-
message: `Route not found: ${req.method} ${path}`,
|
|
395
|
-
})
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Create HTTP or HTTPS server
|
|
400
|
-
let server;
|
|
401
|
-
if (enableHttps) {
|
|
402
|
-
const credentials = {
|
|
403
|
-
cert: readFileSync(cert, 'utf-8'),
|
|
404
|
-
key: readFileSync(key, 'utf-8'),
|
|
405
|
-
};
|
|
406
|
-
server = https.createServer(credentials, requestHandler);
|
|
407
|
-
} else {
|
|
408
|
-
server = http.createServer(requestHandler);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Always bind to all interfaces (0.0.0.0)
|
|
412
|
-
// Security is enforced through authentication middleware, not binding address
|
|
413
|
-
const host = '0.0.0.0';
|
|
414
|
-
|
|
415
|
-
return {
|
|
416
|
-
start: () => {
|
|
417
|
-
return new Promise((resolve, reject) => {
|
|
418
|
-
server.listen(port, host, (error) => {
|
|
419
|
-
if (error) {
|
|
420
|
-
return reject(error);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const protocol = enableHttps ? 'https' : 'http';
|
|
424
|
-
console.log(`Agentful server listening on ${protocol}://${host}:${port}`);
|
|
425
|
-
console.log(`Authentication mode: ${auth}`);
|
|
426
|
-
|
|
427
|
-
if (auth === 'none') {
|
|
428
|
-
console.log('Warning: No authentication enabled - use SSH tunnel for secure remote access');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Start periodic cleanup
|
|
432
|
-
startPeriodicCleanup();
|
|
433
|
-
|
|
434
|
-
resolve();
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
stop: () => {
|
|
440
|
-
return new Promise((resolve) => {
|
|
441
|
-
server.close(() => {
|
|
442
|
-
console.log('Server stopped');
|
|
443
|
-
resolve();
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
server,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Start server from CLI arguments
|
|
454
|
-
* @param {Object} args - Parsed CLI arguments
|
|
455
|
-
*/
|
|
456
|
-
export async function startServerFromCLI(args) {
|
|
457
|
-
const config = {
|
|
458
|
-
auth: args.auth || 'tailscale',
|
|
459
|
-
port: args.port || 3000,
|
|
460
|
-
secret: args.secret,
|
|
461
|
-
https: args.https || false,
|
|
462
|
-
cert: args.cert,
|
|
463
|
-
key: args.key,
|
|
464
|
-
projectRoot: args.projectRoot || process.cwd(),
|
|
465
|
-
corsOrigin: args.corsOrigin || null,
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
const server = createServer(config);
|
|
469
|
-
|
|
470
|
-
// Handle graceful shutdown
|
|
471
|
-
process.on('SIGTERM', async () => {
|
|
472
|
-
console.log('Received SIGTERM, shutting down gracefully...');
|
|
473
|
-
await server.stop();
|
|
474
|
-
process.exit(0);
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
process.on('SIGINT', async () => {
|
|
478
|
-
console.log('Received SIGINT, shutting down gracefully...');
|
|
479
|
-
await server.stop();
|
|
480
|
-
process.exit(0);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
await server.start();
|
|
485
|
-
} catch (error) {
|
|
486
|
-
console.error('Failed to start server:', error.message);
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export default {
|
|
492
|
-
createServer,
|
|
493
|
-
startServerFromCLI,
|
|
494
|
-
};
|