devmentorai-server 1.0.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 +141 -0
- package/dist/chunk-QFZAYHDT.js +241 -0
- package/dist/chunk-QFZAYHDT.js.map +1 -0
- package/dist/cli.js +452 -0
- package/dist/cli.js.map +1 -0
- package/dist/server.js +2704 -0
- package/dist/server.js.map +1 -0
- package/package.json +81 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DATA_DIR,
|
|
4
|
+
DEFAULT_CONFIG,
|
|
5
|
+
IMAGES_DIR,
|
|
6
|
+
LOG_DIR,
|
|
7
|
+
LOG_FILE,
|
|
8
|
+
PID_FILE,
|
|
9
|
+
__require,
|
|
10
|
+
ensureDir
|
|
11
|
+
} from "./chunk-QFZAYHDT.js";
|
|
12
|
+
|
|
13
|
+
// src/lib/daemon.ts
|
|
14
|
+
import { fork } from "child_process";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import http from "http";
|
|
18
|
+
var DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;
|
|
19
|
+
function writePid(pid) {
|
|
20
|
+
fs.writeFileSync(PID_FILE, String(pid), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function readPid() {
|
|
23
|
+
try {
|
|
24
|
+
const content = fs.readFileSync(PID_FILE, "utf-8").trim();
|
|
25
|
+
const pid = parseInt(content, 10);
|
|
26
|
+
return isNaN(pid) ? null : pid;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function removePid() {
|
|
32
|
+
try {
|
|
33
|
+
fs.unlinkSync(PID_FILE);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isProcessRunning(pid) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0);
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function healthcheck(port = DEFAULT_PORT, timeoutMs = 3e3) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: timeoutMs }, (res) => {
|
|
48
|
+
let body = "";
|
|
49
|
+
res.on("data", (chunk) => {
|
|
50
|
+
body += chunk.toString();
|
|
51
|
+
});
|
|
52
|
+
res.on("end", () => {
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(body);
|
|
55
|
+
resolve({ ok: res.statusCode === 200, data });
|
|
56
|
+
} catch {
|
|
57
|
+
resolve({ ok: false });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
req.on("error", () => resolve({ ok: false }));
|
|
62
|
+
req.on("timeout", () => {
|
|
63
|
+
req.destroy();
|
|
64
|
+
resolve({ ok: false });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function isServerRunning(port = DEFAULT_PORT) {
|
|
69
|
+
const pid = readPid();
|
|
70
|
+
if (pid && isProcessRunning(pid)) {
|
|
71
|
+
const { ok: ok2 } = await healthcheck(port);
|
|
72
|
+
return { running: true, pid, healthy: ok2 };
|
|
73
|
+
}
|
|
74
|
+
if (pid) {
|
|
75
|
+
removePid();
|
|
76
|
+
}
|
|
77
|
+
const { ok } = await healthcheck(port);
|
|
78
|
+
return { running: ok, pid: null, healthy: ok };
|
|
79
|
+
}
|
|
80
|
+
function spawnServer(port = DEFAULT_PORT) {
|
|
81
|
+
ensureDir(LOG_DIR);
|
|
82
|
+
const logFd = fs.openSync(LOG_FILE, "a");
|
|
83
|
+
const serverEntry = path.resolve(path.dirname(new URL(import.meta.url).pathname), "server.js");
|
|
84
|
+
const child = fork(serverEntry, [], {
|
|
85
|
+
detached: true,
|
|
86
|
+
stdio: ["ignore", logFd, logFd, "ipc"],
|
|
87
|
+
env: {
|
|
88
|
+
...process.env,
|
|
89
|
+
DEVMENTORAI_PORT: String(port),
|
|
90
|
+
NODE_ENV: process.env.NODE_ENV || "production"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (child.pid) {
|
|
94
|
+
writePid(child.pid);
|
|
95
|
+
}
|
|
96
|
+
child.unref();
|
|
97
|
+
child.disconnect();
|
|
98
|
+
fs.closeSync(logFd);
|
|
99
|
+
return child;
|
|
100
|
+
}
|
|
101
|
+
async function waitForHealthy(port = DEFAULT_PORT, maxWaitMs = 1e4) {
|
|
102
|
+
const start = Date.now();
|
|
103
|
+
const interval = 500;
|
|
104
|
+
while (Date.now() - start < maxWaitMs) {
|
|
105
|
+
const { ok } = await healthcheck(port);
|
|
106
|
+
if (ok) return true;
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
async function stopServer() {
|
|
112
|
+
const pid = readPid();
|
|
113
|
+
if (!pid) return false;
|
|
114
|
+
if (!isProcessRunning(pid)) {
|
|
115
|
+
removePid();
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
process.kill(pid, "SIGTERM");
|
|
119
|
+
const maxWait = 5e3;
|
|
120
|
+
const start = Date.now();
|
|
121
|
+
while (Date.now() - start < maxWait) {
|
|
122
|
+
if (!isProcessRunning(pid)) {
|
|
123
|
+
removePid();
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
process.kill(pid, "SIGKILL");
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
removePid();
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/cli/start.ts
|
|
137
|
+
var DEFAULT_PORT2 = DEFAULT_CONFIG.DEFAULT_PORT;
|
|
138
|
+
async function startCommand(options) {
|
|
139
|
+
const port = options.port || DEFAULT_PORT2;
|
|
140
|
+
const status = await isServerRunning(port);
|
|
141
|
+
if (status.running) {
|
|
142
|
+
console.log(`
|
|
143
|
+
\u2713 DevMentorAI server is already running (PID: ${status.pid || "unknown"})`);
|
|
144
|
+
console.log(` \u2192 http://127.0.0.1:${port}`);
|
|
145
|
+
console.log(` Health: ${status.healthy ? "\u2713 healthy" : "\u2717 unhealthy"}
|
|
146
|
+
`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (options.foreground) {
|
|
150
|
+
console.log(`
|
|
151
|
+
\u{1F680} Starting DevMentorAI server on port ${port} (foreground)...
|
|
152
|
+
`);
|
|
153
|
+
process.env.DEVMENTORAI_PORT = String(port);
|
|
154
|
+
const { createServer } = await import("./server.js");
|
|
155
|
+
const fastify = await createServer();
|
|
156
|
+
await fastify.listen({ port, host: "0.0.0.0" });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.log(`
|
|
160
|
+
\u{1F680} Starting DevMentorAI server on port ${port}...`);
|
|
161
|
+
spawnServer(port);
|
|
162
|
+
const healthy = await waitForHealthy(port);
|
|
163
|
+
if (healthy) {
|
|
164
|
+
console.log(`\u2713 Server started successfully`);
|
|
165
|
+
console.log(` \u2192 http://127.0.0.1:${port}`);
|
|
166
|
+
console.log(` Logs: ${LOG_FILE}
|
|
167
|
+
`);
|
|
168
|
+
} else {
|
|
169
|
+
console.error(`\u2717 Server started but healthcheck failed`);
|
|
170
|
+
console.error(` Check logs: ${LOG_FILE}
|
|
171
|
+
`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/cli/stop.ts
|
|
177
|
+
var DEFAULT_PORT3 = DEFAULT_CONFIG.DEFAULT_PORT;
|
|
178
|
+
async function stopCommand() {
|
|
179
|
+
const pid = readPid();
|
|
180
|
+
const status = await isServerRunning(DEFAULT_PORT3);
|
|
181
|
+
if (!status.running) {
|
|
182
|
+
console.log("\n\u2298 DevMentorAI server is not running.\n");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
console.log(`
|
|
186
|
+
\u23F3 Stopping DevMentorAI server (PID: ${pid || "unknown"})...`);
|
|
187
|
+
const stopped = await stopServer();
|
|
188
|
+
if (stopped) {
|
|
189
|
+
console.log("\u2713 Server stopped successfully.\n");
|
|
190
|
+
} else {
|
|
191
|
+
console.error("\u2717 Failed to stop server. You may need to kill the process manually.\n");
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/cli/status.ts
|
|
197
|
+
var DEFAULT_PORT4 = DEFAULT_CONFIG.DEFAULT_PORT;
|
|
198
|
+
async function statusCommand() {
|
|
199
|
+
const status = await isServerRunning(DEFAULT_PORT4);
|
|
200
|
+
console.log("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
201
|
+
console.log("\u2551 DevMentorAI Server Status \u2551");
|
|
202
|
+
console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n");
|
|
203
|
+
if (!status.running) {
|
|
204
|
+
console.log(" Status: \u2298 stopped");
|
|
205
|
+
console.log(` Port: ${DEFAULT_PORT4}`);
|
|
206
|
+
console.log(` Data: ${DATA_DIR}
|
|
207
|
+
`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const pid = readPid();
|
|
211
|
+
const health = await healthcheck(DEFAULT_PORT4);
|
|
212
|
+
console.log(` Status: \u2713 running`);
|
|
213
|
+
console.log(` PID: ${pid || "unknown"}`);
|
|
214
|
+
console.log(` Port: ${DEFAULT_PORT4}`);
|
|
215
|
+
console.log(` URL: http://127.0.0.1:${DEFAULT_PORT4}`);
|
|
216
|
+
console.log(` Health: ${health.ok ? "\u2713 healthy" : "\u2717 unhealthy"}`);
|
|
217
|
+
if (health.ok && health.data?.data) {
|
|
218
|
+
const data = health.data.data;
|
|
219
|
+
if (data.uptime) {
|
|
220
|
+
console.log(` Uptime: ${formatUptime(data.uptime)}`);
|
|
221
|
+
}
|
|
222
|
+
if (data.copilotConnected !== void 0) {
|
|
223
|
+
console.log(` Copilot: ${data.copilotConnected ? "\u2713 connected" : "\u2298 disconnected (mock mode)"}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
console.log(` PID file: ${PID_FILE}`);
|
|
227
|
+
console.log(` Logs: ${LOG_FILE}`);
|
|
228
|
+
console.log(` Data: ${DATA_DIR}
|
|
229
|
+
`);
|
|
230
|
+
}
|
|
231
|
+
function formatUptime(seconds) {
|
|
232
|
+
if (seconds < 60) return `${seconds}s`;
|
|
233
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
234
|
+
const hours = Math.floor(seconds / 3600);
|
|
235
|
+
const mins = Math.floor(seconds % 3600 / 60);
|
|
236
|
+
return `${hours}h ${mins}m`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/cli/logs.ts
|
|
240
|
+
import fs2 from "fs";
|
|
241
|
+
async function logsCommand(options) {
|
|
242
|
+
const lines = options.lines || 50;
|
|
243
|
+
if (!fs2.existsSync(LOG_FILE)) {
|
|
244
|
+
console.log(`
|
|
245
|
+
\u2298 No log file found at ${LOG_FILE}`);
|
|
246
|
+
console.log(" The server may not have been started yet.\n");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const content = fs2.readFileSync(LOG_FILE, "utf-8");
|
|
250
|
+
const allLines = content.split("\n");
|
|
251
|
+
const tailLines = allLines.slice(-lines).join("\n");
|
|
252
|
+
console.log(`
|
|
253
|
+
\u{1F4CB} Last ${lines} lines of ${LOG_FILE}:
|
|
254
|
+
`);
|
|
255
|
+
console.log(tailLines);
|
|
256
|
+
console.log("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/cli/doctor.ts
|
|
260
|
+
import { execSync } from "child_process";
|
|
261
|
+
import fs3 from "fs";
|
|
262
|
+
import net from "net";
|
|
263
|
+
var DEFAULT_PORT5 = DEFAULT_CONFIG.DEFAULT_PORT;
|
|
264
|
+
async function doctorCommand() {
|
|
265
|
+
console.log("\n\u{1F50D} DevMentorAI Server Doctor\n");
|
|
266
|
+
const checks = [];
|
|
267
|
+
checks.push(checkNodeVersion());
|
|
268
|
+
checks.push(checkDataDirectory());
|
|
269
|
+
checks.push(await checkPort(DEFAULT_PORT5));
|
|
270
|
+
checks.push(checkCopilotCli());
|
|
271
|
+
checks.push(checkNativeDep("better-sqlite3"));
|
|
272
|
+
checks.push(checkNativeDep("sharp"));
|
|
273
|
+
let hasFailure = false;
|
|
274
|
+
for (const check of checks) {
|
|
275
|
+
const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "\u26A0" : "\u2717";
|
|
276
|
+
console.log(` ${icon} ${check.name}: ${check.message}`);
|
|
277
|
+
if (check.status === "fail") hasFailure = true;
|
|
278
|
+
}
|
|
279
|
+
console.log("");
|
|
280
|
+
if (hasFailure) {
|
|
281
|
+
console.log(" Some checks failed. Please fix the issues above.\n");
|
|
282
|
+
process.exit(1);
|
|
283
|
+
} else {
|
|
284
|
+
console.log(" All checks passed! The server is ready to run.\n");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function checkNodeVersion() {
|
|
288
|
+
const version = process.version;
|
|
289
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
290
|
+
if (major >= 20) {
|
|
291
|
+
return { name: "Node.js", status: "pass", message: `${version} (>= 20 required)` };
|
|
292
|
+
}
|
|
293
|
+
return { name: "Node.js", status: "fail", message: `${version} \u2014 Node.js >= 20 is required` };
|
|
294
|
+
}
|
|
295
|
+
function checkDataDirectory() {
|
|
296
|
+
const dirs = [DATA_DIR, LOG_DIR, IMAGES_DIR];
|
|
297
|
+
const missing = [];
|
|
298
|
+
for (const dir of dirs) {
|
|
299
|
+
if (!fs3.existsSync(dir)) {
|
|
300
|
+
missing.push(dir);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (missing.length === 0) {
|
|
304
|
+
return { name: "Data directory", status: "pass", message: DATA_DIR };
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
for (const dir of missing) {
|
|
308
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
309
|
+
}
|
|
310
|
+
return { name: "Data directory", status: "pass", message: `${DATA_DIR} (created)` };
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return { name: "Data directory", status: "fail", message: `Cannot create ${DATA_DIR}: ${err}` };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function checkPort(port) {
|
|
316
|
+
return new Promise((resolve) => {
|
|
317
|
+
const server = net.createServer();
|
|
318
|
+
server.once("error", (err) => {
|
|
319
|
+
if (err.code === "EADDRINUSE") {
|
|
320
|
+
resolve({ name: `Port ${port}`, status: "warn", message: `Port ${port} is in use (server may already be running)` });
|
|
321
|
+
} else {
|
|
322
|
+
resolve({ name: `Port ${port}`, status: "fail", message: `Cannot bind port ${port}: ${err.message}` });
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
server.once("listening", () => {
|
|
326
|
+
server.close(() => {
|
|
327
|
+
resolve({ name: `Port ${port}`, status: "pass", message: `Port ${port} is available` });
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
server.listen(port, "127.0.0.1");
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function checkCopilotCli() {
|
|
334
|
+
try {
|
|
335
|
+
const version = execSync("github-copilot --version 2>/dev/null || copilot --version 2>/dev/null", {
|
|
336
|
+
encoding: "utf-8",
|
|
337
|
+
timeout: 5e3
|
|
338
|
+
}).trim();
|
|
339
|
+
return { name: "Copilot CLI", status: "pass", message: version || "installed" };
|
|
340
|
+
} catch {
|
|
341
|
+
return { name: "Copilot CLI", status: "warn", message: "Not found \u2014 server will run in mock mode" };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function checkNativeDep(name) {
|
|
345
|
+
try {
|
|
346
|
+
__require.resolve(name);
|
|
347
|
+
return { name, status: "pass", message: "installed" };
|
|
348
|
+
} catch {
|
|
349
|
+
try {
|
|
350
|
+
const modulePath = `${process.cwd()}/node_modules/${name}`;
|
|
351
|
+
if (fs3.existsSync(modulePath)) {
|
|
352
|
+
return { name, status: "pass", message: "installed" };
|
|
353
|
+
}
|
|
354
|
+
return { name, status: "fail", message: "not found \u2014 run npm install" };
|
|
355
|
+
} catch {
|
|
356
|
+
return { name, status: "fail", message: "not found \u2014 run npm install" };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/cli.ts
|
|
362
|
+
var VERSION = "1.0.0";
|
|
363
|
+
var HELP = `
|
|
364
|
+
devmentorai-server v${VERSION}
|
|
365
|
+
|
|
366
|
+
Usage:
|
|
367
|
+
devmentorai-server [command] [options]
|
|
368
|
+
|
|
369
|
+
Commands:
|
|
370
|
+
start Start the server in background (default)
|
|
371
|
+
stop Stop the running server
|
|
372
|
+
status Show server status
|
|
373
|
+
logs Tail server logs
|
|
374
|
+
doctor Check system requirements and dependencies
|
|
375
|
+
|
|
376
|
+
Options:
|
|
377
|
+
--port <port> Port to listen on (default: 3847)
|
|
378
|
+
--foreground, -f Run in foreground (don't daemonize)
|
|
379
|
+
--help, -h Show this help message
|
|
380
|
+
--version, -v Show version
|
|
381
|
+
|
|
382
|
+
Examples:
|
|
383
|
+
npx devmentorai-server # Start server in background
|
|
384
|
+
npx devmentorai-server status # Check if server is running
|
|
385
|
+
npx devmentorai-server stop # Stop the server
|
|
386
|
+
npx devmentorai-server doctor # Verify system setup
|
|
387
|
+
`;
|
|
388
|
+
async function main() {
|
|
389
|
+
const args = process.argv.slice(2);
|
|
390
|
+
const command = args[0] || "start";
|
|
391
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
392
|
+
console.log(HELP);
|
|
393
|
+
process.exit(0);
|
|
394
|
+
}
|
|
395
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
396
|
+
console.log(VERSION);
|
|
397
|
+
process.exit(0);
|
|
398
|
+
}
|
|
399
|
+
const options = parseOptions(args.slice(command === "start" || command === "stop" || command === "status" || command === "logs" || command === "doctor" ? 1 : 0));
|
|
400
|
+
try {
|
|
401
|
+
switch (command) {
|
|
402
|
+
case "start":
|
|
403
|
+
await startCommand(options);
|
|
404
|
+
break;
|
|
405
|
+
case "stop":
|
|
406
|
+
await stopCommand();
|
|
407
|
+
break;
|
|
408
|
+
case "status":
|
|
409
|
+
await statusCommand();
|
|
410
|
+
break;
|
|
411
|
+
case "logs":
|
|
412
|
+
await logsCommand(options);
|
|
413
|
+
break;
|
|
414
|
+
case "doctor":
|
|
415
|
+
await doctorCommand();
|
|
416
|
+
break;
|
|
417
|
+
default:
|
|
418
|
+
console.error(`Unknown command: ${command}`);
|
|
419
|
+
console.log(HELP);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error(`
|
|
424
|
+
\u2717 Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function parseOptions(args) {
|
|
429
|
+
const options = {};
|
|
430
|
+
for (let i = 0; i < args.length; i++) {
|
|
431
|
+
switch (args[i]) {
|
|
432
|
+
case "--port":
|
|
433
|
+
options.port = parseInt(args[++i], 10);
|
|
434
|
+
if (isNaN(options.port)) {
|
|
435
|
+
console.error("Error: --port requires a valid number");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
case "--foreground":
|
|
440
|
+
case "-f":
|
|
441
|
+
options.foreground = true;
|
|
442
|
+
break;
|
|
443
|
+
case "--lines":
|
|
444
|
+
case "-n":
|
|
445
|
+
options.lines = parseInt(args[++i], 10);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return options;
|
|
450
|
+
}
|
|
451
|
+
main();
|
|
452
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/daemon.ts","../src/cli/start.ts","../src/cli/stop.ts","../src/cli/status.ts","../src/cli/logs.ts","../src/cli/doctor.ts","../src/cli.ts"],"sourcesContent":["/**\n * Daemon management utilities\n *\n * Handles PID file, process spawning, and healthcheck for the background server.\n */\n\nimport { fork, type ChildProcess } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport http from 'node:http';\nimport { PID_FILE, LOG_FILE, LOG_DIR, ensureDir } from './paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\n/** Write PID to file */\nexport function writePid(pid: number): void {\n fs.writeFileSync(PID_FILE, String(pid), 'utf-8');\n}\n\n/** Read PID from file, returns null if not found */\nexport function readPid(): number | null {\n try {\n const content = fs.readFileSync(PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\n/** Remove PID file */\nexport function removePid(): void {\n try {\n fs.unlinkSync(PID_FILE);\n } catch {\n // Ignore if file doesn't exist\n }\n}\n\n/** Check if a process with the given PID is running */\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** HTTP healthcheck against the running server */\nexport function healthcheck(port: number = DEFAULT_PORT, timeoutMs: number = 3000): Promise<{\n ok: boolean;\n data?: Record<string, unknown>;\n}> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: timeoutMs }, (res) => {\n let body = '';\n res.on('data', (chunk: Buffer) => { body += chunk.toString(); });\n res.on('end', () => {\n try {\n const data = JSON.parse(body);\n resolve({ ok: res.statusCode === 200, data });\n } catch {\n resolve({ ok: false });\n }\n });\n });\n\n req.on('error', () => resolve({ ok: false }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false });\n });\n });\n}\n\n/** Check if the server is already running (PID + healthcheck) */\nexport async function isServerRunning(port: number = DEFAULT_PORT): Promise<{\n running: boolean;\n pid: number | null;\n healthy: boolean;\n}> {\n const pid = readPid();\n\n if (pid && isProcessRunning(pid)) {\n const { ok } = await healthcheck(port);\n return { running: true, pid, healthy: ok };\n }\n\n // PID file exists but process is dead — clean up stale PID\n if (pid) {\n removePid();\n }\n\n // Maybe the server is running without our PID file (unlikely but check)\n const { ok } = await healthcheck(port);\n return { running: ok, pid: null, healthy: ok };\n}\n\n/** Spawn the server as a detached background process */\nexport function spawnServer(port: number = DEFAULT_PORT): ChildProcess {\n ensureDir(LOG_DIR);\n\n const logFd = fs.openSync(LOG_FILE, 'a');\n const serverEntry = path.resolve(path.dirname(new URL(import.meta.url).pathname), 'server.js');\n\n const child = fork(serverEntry, [], {\n detached: true,\n stdio: ['ignore', logFd, logFd, 'ipc'],\n env: {\n ...process.env,\n DEVMENTORAI_PORT: String(port),\n NODE_ENV: process.env.NODE_ENV || 'production',\n },\n });\n\n // Write PID file\n if (child.pid) {\n writePid(child.pid);\n }\n\n // Disconnect IPC so parent can exit\n child.unref();\n child.disconnect();\n\n fs.closeSync(logFd);\n\n return child;\n}\n\n/** Wait for the server to become healthy */\nexport async function waitForHealthy(port: number = DEFAULT_PORT, maxWaitMs: number = 10000): Promise<boolean> {\n const start = Date.now();\n const interval = 500;\n\n while (Date.now() - start < maxWaitMs) {\n const { ok } = await healthcheck(port);\n if (ok) return true;\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n return false;\n}\n\n/** Gracefully stop the server process */\nexport async function stopServer(): Promise<boolean> {\n const pid = readPid();\n if (!pid) return false;\n\n if (!isProcessRunning(pid)) {\n removePid();\n return false;\n }\n\n // Send SIGTERM for graceful shutdown\n process.kill(pid, 'SIGTERM');\n\n // Wait for process to exit\n const maxWait = 5000;\n const start = Date.now();\n while (Date.now() - start < maxWait) {\n if (!isProcessRunning(pid)) {\n removePid();\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n\n // Force kill if still running\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // Process might have exited between check and kill\n }\n removePid();\n return true;\n}\n","/**\n * CLI: start command\n * Starts the DevMentorAI server in background or foreground mode.\n */\n\nimport type { CliOptions } from '../cli.js';\nimport { isServerRunning, spawnServer, waitForHealthy } from '../lib/daemon.js';\nimport { LOG_FILE } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function startCommand(options: CliOptions): Promise<void> {\n const port = options.port || DEFAULT_PORT;\n\n // Check if already running\n const status = await isServerRunning(port);\n if (status.running) {\n console.log(`\\n✓ DevMentorAI server is already running (PID: ${status.pid || 'unknown'})`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Health: ${status.healthy ? '✓ healthy' : '✗ unhealthy'}\\n`);\n return;\n }\n\n // Foreground mode — import and run server directly\n if (options.foreground) {\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port} (foreground)...\\n`);\n process.env.DEVMENTORAI_PORT = String(port);\n const { createServer } = await import('../server.js');\n const fastify = await createServer();\n await fastify.listen({ port, host: '0.0.0.0' });\n return;\n }\n\n // Background mode\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port}...`);\n\n spawnServer(port);\n\n // Wait for the server to become healthy\n const healthy = await waitForHealthy(port);\n\n if (healthy) {\n console.log(`✓ Server started successfully`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Logs: ${LOG_FILE}\\n`);\n } else {\n console.error(`✗ Server started but healthcheck failed`);\n console.error(` Check logs: ${LOG_FILE}\\n`);\n process.exit(1);\n }\n}\n","/**\n * CLI: stop command\n * Stops the running DevMentorAI server.\n */\n\nimport { isServerRunning, stopServer, readPid } from '../lib/daemon.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function stopCommand(): Promise<void> {\n const pid = readPid();\n const status = await isServerRunning(DEFAULT_PORT);\n\n if (!status.running) {\n console.log('\\n⊘ DevMentorAI server is not running.\\n');\n return;\n }\n\n console.log(`\\n⏳ Stopping DevMentorAI server (PID: ${pid || 'unknown'})...`);\n\n const stopped = await stopServer();\n\n if (stopped) {\n console.log('✓ Server stopped successfully.\\n');\n } else {\n console.error('✗ Failed to stop server. You may need to kill the process manually.\\n');\n process.exit(1);\n }\n}\n","/**\n * CLI: status command\n * Shows the current status of the DevMentorAI server.\n */\n\nimport { isServerRunning, readPid, healthcheck } from '../lib/daemon.js';\nimport { PID_FILE, LOG_FILE, DATA_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function statusCommand(): Promise<void> {\n const status = await isServerRunning(DEFAULT_PORT);\n\n console.log('\\n╔══════════════════════════════════════╗');\n console.log('║ DevMentorAI Server Status ║');\n console.log('╚══════════════════════════════════════╝\\n');\n\n if (!status.running) {\n console.log(' Status: ⊘ stopped');\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n return;\n }\n\n const pid = readPid();\n const health = await healthcheck(DEFAULT_PORT);\n\n console.log(` Status: ✓ running`);\n console.log(` PID: ${pid || 'unknown'}`);\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` URL: http://127.0.0.1:${DEFAULT_PORT}`);\n console.log(` Health: ${health.ok ? '✓ healthy' : '✗ unhealthy'}`);\n\n if (health.ok && health.data?.data) {\n const data = health.data.data as Record<string, unknown>;\n if (data.uptime) {\n console.log(` Uptime: ${formatUptime(data.uptime as number)}`);\n }\n if (data.copilotConnected !== undefined) {\n console.log(` Copilot: ${data.copilotConnected ? '✓ connected' : '⊘ disconnected (mock mode)'}`);\n }\n }\n\n console.log(` PID file: ${PID_FILE}`);\n console.log(` Logs: ${LOG_FILE}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n}\n\nfunction formatUptime(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;\n const hours = Math.floor(seconds / 3600);\n const mins = Math.floor((seconds % 3600) / 60);\n return `${hours}h ${mins}m`;\n}\n","/**\n * CLI: logs command\n * Tail the DevMentorAI server log file.\n */\n\nimport fs from 'node:fs';\nimport { LOG_FILE } from '../lib/paths.js';\nimport type { CliOptions } from '../cli.js';\n\nexport async function logsCommand(options: CliOptions): Promise<void> {\n const lines = options.lines || 50;\n\n if (!fs.existsSync(LOG_FILE)) {\n console.log(`\\n⊘ No log file found at ${LOG_FILE}`);\n console.log(' The server may not have been started yet.\\n');\n return;\n }\n\n const content = fs.readFileSync(LOG_FILE, 'utf-8');\n const allLines = content.split('\\n');\n const tailLines = allLines.slice(-lines).join('\\n');\n\n console.log(`\\n📋 Last ${lines} lines of ${LOG_FILE}:\\n`);\n console.log(tailLines);\n console.log('');\n}\n","/**\n * CLI: doctor command\n * Verifies system requirements and dependencies for DevMentorAI server.\n */\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport { DATA_DIR, LOG_DIR, IMAGES_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\ninterface CheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n}\n\nexport async function doctorCommand(): Promise<void> {\n console.log('\\n🔍 DevMentorAI Server Doctor\\n');\n\n const checks: CheckResult[] = [];\n\n // 1. Node.js version\n checks.push(checkNodeVersion());\n\n // 2. Data directory\n checks.push(checkDataDirectory());\n\n // 3. Port availability\n checks.push(await checkPort(DEFAULT_PORT));\n\n // 4. Copilot CLI\n checks.push(checkCopilotCli());\n\n // 5. Native dependencies\n checks.push(checkNativeDep('better-sqlite3'));\n checks.push(checkNativeDep('sharp'));\n\n // Display results\n let hasFailure = false;\n for (const check of checks) {\n const icon = check.status === 'pass' ? '✓' : check.status === 'warn' ? '⚠' : '✗';\n console.log(` ${icon} ${check.name}: ${check.message}`);\n if (check.status === 'fail') hasFailure = true;\n }\n\n console.log('');\n if (hasFailure) {\n console.log(' Some checks failed. Please fix the issues above.\\n');\n process.exit(1);\n } else {\n console.log(' All checks passed! The server is ready to run.\\n');\n }\n}\n\nfunction checkNodeVersion(): CheckResult {\n const version = process.version;\n const major = parseInt(version.slice(1).split('.')[0], 10);\n\n if (major >= 20) {\n return { name: 'Node.js', status: 'pass', message: `${version} (>= 20 required)` };\n }\n return { name: 'Node.js', status: 'fail', message: `${version} — Node.js >= 20 is required` };\n}\n\nfunction checkDataDirectory(): CheckResult {\n const dirs = [DATA_DIR, LOG_DIR, IMAGES_DIR];\n const missing: string[] = [];\n\n for (const dir of dirs) {\n if (!fs.existsSync(dir)) {\n missing.push(dir);\n }\n }\n\n if (missing.length === 0) {\n return { name: 'Data directory', status: 'pass', message: DATA_DIR };\n }\n\n // Try to create missing dirs\n try {\n for (const dir of missing) {\n fs.mkdirSync(dir, { recursive: true });\n }\n return { name: 'Data directory', status: 'pass', message: `${DATA_DIR} (created)` };\n } catch (err) {\n return { name: 'Data directory', status: 'fail', message: `Cannot create ${DATA_DIR}: ${err}` };\n }\n}\n\nfunction checkPort(port: number): Promise<CheckResult> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve({ name: `Port ${port}`, status: 'warn', message: `Port ${port} is in use (server may already be running)` });\n } else {\n resolve({ name: `Port ${port}`, status: 'fail', message: `Cannot bind port ${port}: ${err.message}` });\n }\n });\n server.once('listening', () => {\n server.close(() => {\n resolve({ name: `Port ${port}`, status: 'pass', message: `Port ${port} is available` });\n });\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\nfunction checkCopilotCli(): CheckResult {\n try {\n const version = execSync('github-copilot --version 2>/dev/null || copilot --version 2>/dev/null', {\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n return { name: 'Copilot CLI', status: 'pass', message: version || 'installed' };\n } catch {\n return { name: 'Copilot CLI', status: 'warn', message: 'Not found — server will run in mock mode' };\n }\n}\n\nfunction checkNativeDep(name: string): CheckResult {\n try {\n require.resolve(name);\n return { name, status: 'pass', message: 'installed' };\n } catch {\n // In ESM context, require.resolve may not work; try dynamic import\n try {\n // Check if the module exists in node_modules\n const modulePath = `${process.cwd()}/node_modules/${name}`;\n if (fs.existsSync(modulePath)) {\n return { name, status: 'pass', message: 'installed' };\n }\n return { name, status: 'fail', message: 'not found — run npm install' };\n } catch {\n return { name, status: 'fail', message: 'not found — run npm install' };\n }\n }\n}\n","/**\n * DevMentorAI Server CLI\n *\n * Usage:\n * devmentorai-server [command]\n *\n * Commands:\n * start Start the server (default)\n * stop Stop the running server\n * status Show server status\n * logs Tail server logs\n * doctor Check system requirements\n */\n\nimport { startCommand } from './cli/start.js';\nimport { stopCommand } from './cli/stop.js';\nimport { statusCommand } from './cli/status.js';\nimport { logsCommand } from './cli/logs.js';\nimport { doctorCommand } from './cli/doctor.js';\n\nconst VERSION = '1.0.0';\n\nconst HELP = `\ndevmentorai-server v${VERSION}\n\nUsage:\n devmentorai-server [command] [options]\n\nCommands:\n start Start the server in background (default)\n stop Stop the running server\n status Show server status\n logs Tail server logs\n doctor Check system requirements and dependencies\n\nOptions:\n --port <port> Port to listen on (default: 3847)\n --foreground, -f Run in foreground (don't daemonize)\n --help, -h Show this help message\n --version, -v Show version\n\nExamples:\n npx devmentorai-server # Start server in background\n npx devmentorai-server status # Check if server is running\n npx devmentorai-server stop # Stop the server\n npx devmentorai-server doctor # Verify system setup\n`;\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0] || 'start';\n\n if (args.includes('--help') || args.includes('-h')) {\n console.log(HELP);\n process.exit(0);\n }\n\n if (args.includes('--version') || args.includes('-v')) {\n console.log(VERSION);\n process.exit(0);\n }\n\n const options = parseOptions(args.slice(command === 'start' || command === 'stop' || command === 'status' || command === 'logs' || command === 'doctor' ? 1 : 0));\n\n try {\n switch (command) {\n case 'start':\n await startCommand(options);\n break;\n case 'stop':\n await stopCommand();\n break;\n case 'status':\n await statusCommand();\n break;\n case 'logs':\n await logsCommand(options);\n break;\n case 'doctor':\n await doctorCommand();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n console.log(HELP);\n process.exit(1);\n }\n } catch (error) {\n console.error(`\\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n}\n\nexport interface CliOptions {\n port?: number;\n foreground?: boolean;\n lines?: number;\n}\n\nfunction parseOptions(args: string[]): CliOptions {\n const options: CliOptions = {};\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--port':\n options.port = parseInt(args[++i], 10);\n if (isNaN(options.port)) {\n console.error('Error: --port requires a valid number');\n process.exit(1);\n }\n break;\n case '--foreground':\n case '-f':\n options.foreground = true;\n break;\n case '--lines':\n case '-n':\n options.lines = parseInt(args[++i], 10);\n break;\n }\n }\n\n return options;\n}\n\nmain();\n"],"mappings":"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","names":["ok","DEFAULT_PORT","DEFAULT_PORT","DEFAULT_PORT","fs","fs","fs","DEFAULT_PORT","fs"]}
|