crewos 0.1.1 ā 0.1.2
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/bin/crewos.js +104 -41
- package/package.json +1 -1
package/bin/crewos.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import os from 'os';
|
|
6
|
+
import net from 'net';
|
|
6
7
|
import crypto from 'crypto';
|
|
7
8
|
import readline from 'readline';
|
|
8
9
|
import { spawn } from 'child_process';
|
|
@@ -45,9 +46,34 @@ function validatePort(raw) {
|
|
|
45
46
|
return { valid: true, value: port };
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function checkPort(host, port) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const child = spawn('lsof', ['-ti', `:${port}`, '-sTCP:LISTEN'], {
|
|
52
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
|
+
});
|
|
54
|
+
let out = '';
|
|
55
|
+
child.stdout.on('data', (d) => (out += d));
|
|
56
|
+
child.stderr.on('data', (d) => (out += d));
|
|
57
|
+
child.on('close', (code) => {
|
|
58
|
+
resolve(code !== 0);
|
|
59
|
+
});
|
|
60
|
+
child.on('error', () => {
|
|
61
|
+
const server = net.createServer();
|
|
62
|
+
server.once('error', (err) => {
|
|
63
|
+
resolve(err.code !== 'EADDRINUSE');
|
|
64
|
+
});
|
|
65
|
+
server.once('listening', () => {
|
|
66
|
+
server.close();
|
|
67
|
+
resolve(true);
|
|
68
|
+
});
|
|
69
|
+
server.listen(port, host);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
function checkOpenCode() {
|
|
49
75
|
return new Promise((resolve) => {
|
|
50
|
-
const check = spawn('opencode
|
|
76
|
+
const check = spawn('opencode --version', {
|
|
51
77
|
stdio: 'pipe',
|
|
52
78
|
shell: true,
|
|
53
79
|
});
|
|
@@ -65,7 +91,7 @@ function checkOpenCode() {
|
|
|
65
91
|
|
|
66
92
|
function installOpenCode() {
|
|
67
93
|
return new Promise((resolve) => {
|
|
68
|
-
const install = spawn('npm
|
|
94
|
+
const install = spawn('npm install -g opencode-ai', {
|
|
69
95
|
stdio: 'inherit',
|
|
70
96
|
shell: true,
|
|
71
97
|
});
|
|
@@ -195,6 +221,30 @@ async function startCommand() {
|
|
|
195
221
|
|
|
196
222
|
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
197
223
|
|
|
224
|
+
const portChecks = [
|
|
225
|
+
{ name: 'Backend', port: config.backendPort },
|
|
226
|
+
{ name: 'Frontend', port: config.frontendPort },
|
|
227
|
+
{ name: 'OpenCode', port: config.opencodePort },
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
console.log('š Checking port availability...');
|
|
231
|
+
let conflict = false;
|
|
232
|
+
for (const { name, port } of portChecks) {
|
|
233
|
+
const available = await checkPort('127.0.0.1', port);
|
|
234
|
+
if (!available) {
|
|
235
|
+
console.log(` ${name} port ${port} is already in use`);
|
|
236
|
+
conflict = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (conflict) {
|
|
240
|
+
console.log('\nā Required ports are in use. Kill the old processes first:\n');
|
|
241
|
+
for (const { name, port } of portChecks) {
|
|
242
|
+
console.log(` lsof -ti :${port} -sTCP:LISTEN | xargs kill`);
|
|
243
|
+
}
|
|
244
|
+
console.log('');
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
198
248
|
console.log('šØ Building frontend...');
|
|
199
249
|
try {
|
|
200
250
|
await new Promise((resolve, reject) => {
|
|
@@ -202,7 +252,7 @@ async function startCommand() {
|
|
|
202
252
|
...process.env,
|
|
203
253
|
VITE_BACKEND_URL: `http://localhost:${config.backendPort}`,
|
|
204
254
|
};
|
|
205
|
-
const build = spawn('npx
|
|
255
|
+
const build = spawn('npx vite build', {
|
|
206
256
|
cwd: APP_DIR,
|
|
207
257
|
stdio: 'inherit',
|
|
208
258
|
env,
|
|
@@ -224,9 +274,25 @@ async function startCommand() {
|
|
|
224
274
|
console.log('\nš Starting crewOS...');
|
|
225
275
|
console.log(` Backend: http://localhost:${config.backendPort}`);
|
|
226
276
|
console.log(` Frontend: http://localhost:${config.frontendPort}`);
|
|
227
|
-
console.log(` OpenCode: http://localhost:${config.opencodePort}`);
|
|
277
|
+
console.log(` OpenCode: http://localhost:${config.opencodePort}\n`);
|
|
228
278
|
|
|
229
279
|
const children = [];
|
|
280
|
+
let shuttingDown = false;
|
|
281
|
+
|
|
282
|
+
function killAll() {
|
|
283
|
+
if (shuttingDown) return;
|
|
284
|
+
shuttingDown = true;
|
|
285
|
+
children.forEach((c) => {
|
|
286
|
+
try { c.kill('SIGTERM'); } catch {}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onChildExit(label, code) {
|
|
291
|
+
if (shuttingDown) return;
|
|
292
|
+
console.log(`\nā ${label} exited with code ${code}`);
|
|
293
|
+
killAll();
|
|
294
|
+
process.exit(code || 0);
|
|
295
|
+
}
|
|
230
296
|
|
|
231
297
|
const backend = spawn('node', ['src/index.js'], {
|
|
232
298
|
cwd: BACKEND_DIR,
|
|
@@ -236,51 +302,48 @@ async function startCommand() {
|
|
|
236
302
|
PORT: String(config.backendPort),
|
|
237
303
|
},
|
|
238
304
|
});
|
|
305
|
+
backend.on('error', (err) => {
|
|
306
|
+
console.log(`\nā Failed to start backend: ${err.message}`);
|
|
307
|
+
killAll();
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|
|
310
|
+
backend.on('close', (code) => onChildExit('Backend', code));
|
|
239
311
|
children.push(backend);
|
|
240
312
|
|
|
241
|
-
const frontend = spawn(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
{
|
|
245
|
-
|
|
246
|
-
stdio: 'inherit',
|
|
247
|
-
env: {
|
|
248
|
-
...process.env,
|
|
249
|
-
},
|
|
250
|
-
shell: true,
|
|
251
|
-
},
|
|
252
|
-
);
|
|
253
|
-
children.push(frontend);
|
|
254
|
-
|
|
255
|
-
const opencode = spawn(
|
|
256
|
-
'opencode',
|
|
257
|
-
['serve', '--port', String(config.opencodePort)],
|
|
258
|
-
{
|
|
259
|
-
stdio: 'inherit',
|
|
260
|
-
},
|
|
261
|
-
);
|
|
262
|
-
children.push(opencode);
|
|
263
|
-
|
|
264
|
-
backend.on('close', (code) => {
|
|
265
|
-
console.log(`Backend exited with code ${code}`);
|
|
266
|
-
children.forEach((c) => c.kill('SIGTERM'));
|
|
267
|
-
process.exit(code);
|
|
313
|
+
const frontend = spawn(`npx vite preview --port ${config.frontendPort} --strictPort`, {
|
|
314
|
+
cwd: APP_DIR,
|
|
315
|
+
stdio: 'inherit',
|
|
316
|
+
env: { ...process.env },
|
|
317
|
+
shell: true,
|
|
268
318
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
process.exit(code);
|
|
319
|
+
frontend.on('error', (err) => {
|
|
320
|
+
console.log(`\nā Failed to start frontend: ${err.message}`);
|
|
321
|
+
killAll();
|
|
322
|
+
process.exit(1);
|
|
274
323
|
});
|
|
324
|
+
frontend.on('close', (code) => onChildExit('Frontend', code));
|
|
325
|
+
children.push(frontend);
|
|
275
326
|
|
|
276
|
-
opencode
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
327
|
+
const opencode = spawn(`opencode serve --port ${config.opencodePort}`, {
|
|
328
|
+
stdio: 'inherit',
|
|
329
|
+
shell: true,
|
|
330
|
+
});
|
|
331
|
+
opencode.on('error', (err) => {
|
|
332
|
+
console.log(`\nā Failed to start opencode: ${err.message}`);
|
|
333
|
+
console.log(' Make sure opencode-ai is installed: npm install -g opencode-ai');
|
|
334
|
+
killAll();
|
|
335
|
+
process.exit(1);
|
|
280
336
|
});
|
|
337
|
+
opencode.on('close', (code) => onChildExit('OpenCode', code));
|
|
338
|
+
children.push(opencode);
|
|
281
339
|
|
|
282
340
|
const shutdown = () => {
|
|
283
|
-
|
|
341
|
+
if (shuttingDown) return;
|
|
342
|
+
shuttingDown = true;
|
|
343
|
+
console.log('\nš Shutting down...');
|
|
344
|
+
children.forEach((c) => {
|
|
345
|
+
try { c.kill('SIGTERM'); } catch {}
|
|
346
|
+
});
|
|
284
347
|
process.exit(0);
|
|
285
348
|
};
|
|
286
349
|
|