nothumanallowed 16.0.62 → 16.0.64

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.62",
3
+ "version": "16.0.64",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '16.0.62';
8
+ export const VERSION = '16.0.64';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -473,20 +473,64 @@ export async function startServer({ port = 3847, host = '127.0.0.1', noBrowser =
473
473
 
474
474
  if (!noBrowser) openBrowser(`http://localhost:${port}`);
475
475
 
476
- // Cleanup sandbox processes on server shutdown
476
+ // ── Unified graceful shutdown ─────────────────────────────────────────────
477
+ // Stops, in this order: PAO daemon (Telegram bot + cron + Gmail/Calendar
478
+ // pollers), orphan WS listener on 3848, sandbox processes on 4000-4010, the
479
+ // HTTP server itself. Second Ctrl+C bails out hard. 5-second hard timeout
480
+ // guards against any step hanging.
481
+ let shuttingDown = false;
477
482
  const cleanup = async () => {
483
+ if (shuttingDown) {
484
+ process.stderr.write(`\n ${R}✗${NC} ${D}Force exit${NC}\n`);
485
+ process.exit(130);
486
+ }
487
+ shuttingDown = true;
488
+ process.stdout.write(`\n ${D}Shutting down...${NC}\n`);
489
+
490
+ const hardTimeout = setTimeout(() => {
491
+ process.stderr.write(` ${R}!${NC} ${D}Timed out, killing process${NC}\n`);
492
+ process.exit(1);
493
+ }, 5000);
494
+ hardTimeout.unref();
495
+
496
+ // 1. PAO daemon (Telegram, cron, Gmail/Calendar pollers, notifications)
497
+ try {
498
+ const { stopDaemon, isRunning: isDaemonRunning } = await import('../services/ops-daemon.mjs');
499
+ if (isDaemonRunning()) {
500
+ const r = stopDaemon();
501
+ if (r.ok) process.stdout.write(` ${G}✓${NC} ${D}PAO daemon stopped (PID ${r.pid})${NC}\n`);
502
+ }
503
+ } catch {}
504
+
505
+ // 2. Orphan WS listeners on port 3848 (stale daemon instances from
506
+ // previous sessions that never cleaned up)
507
+ try {
508
+ const { execSync } = await import('child_process');
509
+ const out = execSync('lsof -ti:3848 2>/dev/null', { encoding: 'utf-8', timeout: 1500 }).trim();
510
+ const pids = out.split('\n').filter(Boolean).filter((p) => p !== String(process.pid));
511
+ for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGTERM'); } catch {} }
512
+ if (pids.length) process.stdout.write(` ${G}✓${NC} ${D}Cleaned ${pids.length} orphan(s) on port 3848${NC}\n`);
513
+ } catch {}
514
+
515
+ // 3. WebCraft sandbox processes (4000-4010)
478
516
  try {
479
- const { exec } = await import('child_process');
480
- const { promisify } = await import('util');
481
- const execAsync = promisify(exec);
517
+ const { execSync } = await import('child_process');
518
+ let sandboxKilled = 0;
482
519
  for (let p = 4000; p <= 4010; p++) {
483
520
  try {
484
- const { stdout } = await execAsync(`lsof -ti:${p} 2>/dev/null || fuser ${p}/tcp 2>/dev/null`, { timeout: 2000 });
485
- const pids = stdout.trim().split(/\s+/).filter(Boolean);
486
- for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGKILL'); } catch {} }
521
+ const out = execSync(`lsof -ti:${p} 2>/dev/null`, { encoding: 'utf-8', timeout: 1500 }).trim();
522
+ const pids = out.split('\n').filter(Boolean);
523
+ for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGKILL'); sandboxKilled++; } catch {} }
487
524
  } catch {}
488
525
  }
526
+ if (sandboxKilled) process.stdout.write(` ${G}✓${NC} ${D}Stopped ${sandboxKilled} sandbox process(es)${NC}\n`);
489
527
  } catch {}
528
+
529
+ // 4. HTTP server — close listening socket so the port frees up immediately
530
+ try { server.close(); } catch {}
531
+
532
+ process.stdout.write(` ${G}✓${NC} ${D}Bye${NC}\n`);
533
+ clearTimeout(hardTimeout);
490
534
  process.exit(0);
491
535
  };
492
536
  process.on('SIGINT', cleanup);