crewos 0.1.0 → 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.
Files changed (2) hide show
  1. package/bin/crewos.js +104 -41
  2. 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', ['--version'], {
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', ['install', '-g', 'opencode-ai'], {
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', ['vite', 'build'], {
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
- 'npx',
243
- ['vite', 'preview', '--port', String(config.frontendPort), '--strictPort'],
244
- {
245
- cwd: APP_DIR,
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(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
- frontend.on('close', (code) => {
271
- console.log(`Frontend exited with code ${code}`);
272
- children.forEach((c) => c.kill('SIGTERM'));
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.on('close', (code) => {
277
- console.log(`OpenCode serve exited with code ${code}`);
278
- children.forEach((c) => c.kill('SIGTERM'));
279
- process.exit(code);
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
- children.forEach((c) => c.kill('SIGTERM'));
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewos",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "crewos": "./bin/crewos.js"