fullcourtdefense-cli 1.15.10 → 1.15.11

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.
@@ -53,6 +53,7 @@ const COLOR = {
53
53
  red: '\x1b[31m', yellow: '\x1b[33m', green: '\x1b[32m', cyan: '\x1b[36m', gray: '\x1b[90m',
54
54
  };
55
55
  const TASK_NAME = 'FullCourtDefense Daemon';
56
+ const WATCHDOG_TASK_NAME = 'FullCourtDefense Daemon Watchdog';
56
57
  const CRON_MARKER = '# FCD_DAEMON';
57
58
  const LAUNCHD_LABEL = 'ai.fullcourtdefense.daemon';
58
59
  const SYSTEMD_UNIT = 'fullcourtdefense-daemon.service';
@@ -262,6 +263,16 @@ async function runDaemon(args, config) {
262
263
  console.log(`${COLOR.yellow}Another FullCourtDefense daemon is already running (pid file: ${pidFile()}).${COLOR.reset}`);
263
264
  return;
264
265
  }
266
+ // The resident loop must never die silently from one bad tick (fs watcher
267
+ // callback, fetch, timer). Log and keep running — the loop is timer-driven,
268
+ // so surviving a failed tick is safe, and on Windows there is no supervisor
269
+ // that would restart a crashed process instantly.
270
+ process.on('uncaughtException', error => {
271
+ log(`Uncaught exception (daemon continues): ${error?.stack || String(error)}`);
272
+ });
273
+ process.on('unhandledRejection', reason => {
274
+ log(`Unhandled rejection (daemon continues): ${reason?.stack || String(reason)}`);
275
+ });
265
276
  const creds = (0, config_1.resolveCliCredentials)(config, {
266
277
  shieldId: args.shieldId,
267
278
  shieldKey: args.shieldKey,
@@ -696,12 +707,23 @@ function installWindows() {
696
707
  ], { stdio: 'ignore', windowsHide: true });
697
708
  ok = reg.status === 0;
698
709
  }
710
+ // Watchdog: logon triggers only START the daemon — nothing restarts it if it
711
+ // is killed or crashes mid-session. A time-based per-user task (no elevation
712
+ // needed, unlike ONLOGON) re-launches the daemon every 5 minutes;
713
+ // acquirePidLock() makes each tick a cheap no-op while the daemon is alive,
714
+ // and revives it within one tick when it is not. Best-effort: a missing
715
+ // watchdog never fails the install.
716
+ (0, child_process_1.spawnSync)('schtasks', [
717
+ '/Create', '/TN', WATCHDOG_TASK_NAME, '/TR', `wscript.exe "${vbs}"`,
718
+ '/SC', 'MINUTE', '/MO', '5', '/F',
719
+ ], { stdio: 'ignore', windowsHide: true });
699
720
  if (ok)
700
721
  startDaemonNowWindows(vbs, taskOk);
701
722
  return ok;
702
723
  }
703
724
  function uninstallWindows() {
704
725
  const task = (0, child_process_1.spawnSync)('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'ignore', windowsHide: true });
726
+ (0, child_process_1.spawnSync)('schtasks', ['/Delete', '/TN', WATCHDOG_TASK_NAME, '/F'], { stdio: 'ignore', windowsHide: true });
705
727
  const reg = (0, child_process_1.spawnSync)('reg', ['delete', WINDOWS_RUN_KEY, '/v', WINDOWS_RUN_VALUE, '/f'], { stdio: 'ignore', windowsHide: true });
706
728
  const vbs = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'FullCourtDefense', 'daemon.vbs');
707
729
  try {
@@ -790,11 +812,16 @@ WantedBy=default.target
790
812
  return true;
791
813
  }
792
814
  catch { /* fall back to cron */ }
793
- // Fallback: cron @reboot for boxes without a systemd user session.
815
+ // Fallback: cron for boxes without a systemd user session. @reboot starts
816
+ // the daemon at boot; the 5-min line is the watchdog (pid lock makes each
817
+ // tick a no-op while the daemon is alive, and revives it after a kill/crash).
794
818
  const command = `"${process.execPath}" "${cliEntry()}" daemon >/dev/null 2>&1`;
795
819
  const list = (0, child_process_1.spawnSync)('crontab', ['-l'], { encoding: 'utf8' });
796
820
  const existing = (list.status === 0 ? (list.stdout || '') : '').split('\n').filter(l => l && !l.includes(CRON_MARKER));
797
- const write = (0, child_process_1.spawnSync)('crontab', ['-'], { input: [...existing, `@reboot ${command} ${CRON_MARKER}`].join('\n') + '\n', encoding: 'utf8' });
821
+ const write = (0, child_process_1.spawnSync)('crontab', ['-'], {
822
+ input: [...existing, `@reboot ${command} ${CRON_MARKER}`, `*/5 * * * * ${command} ${CRON_MARKER}`].join('\n') + '\n',
823
+ encoding: 'utf8',
824
+ });
798
825
  return write.status === 0;
799
826
  }
800
827
  function uninstallLinux() {
@@ -854,6 +881,10 @@ function statusCommand() {
854
881
  else {
855
882
  console.log(' not installed — run: fullcourtdefense daemon --install true');
856
883
  }
884
+ const watchdog = (0, child_process_1.spawnSync)('schtasks', ['/Query', '/TN', WATCHDOG_TASK_NAME], { stdio: 'ignore', windowsHide: true, timeout: 5_000 });
885
+ console.log(watchdog.status === 0
886
+ ? ` Watchdog task "${WATCHDOG_TASK_NAME}" installed — revives a killed daemon within 5 min`
887
+ : ` ${COLOR.yellow}watchdog not installed${COLOR.reset} — a killed daemon stays down until next logon; re-run: fullcourtdefense daemon --install true`);
857
888
  }
858
889
  else if (process.platform === 'darwin') {
859
890
  console.log(fs.existsSync(launchdPlistPath()) ? ` launchd agent installed (${launchdPlistPath()})` : ' not installed');
@@ -897,7 +928,7 @@ async function daemonCommand(args, config) {
897
928
  : installLinux();
898
929
  if (ok) {
899
930
  console.log(`${COLOR.green}${COLOR.bold}Daemon autostart installed.${COLOR.reset}`);
900
- console.log(`${COLOR.gray}Trigger:${COLOR.reset} ${process.platform === 'win32' ? `at logon (Scheduled Task "${TASK_NAME}")` : process.platform === 'darwin' ? 'launchd (RunAtLoad + KeepAlive)' : 'systemd user unit (or @reboot cron)'}`);
931
+ console.log(`${COLOR.gray}Trigger:${COLOR.reset} ${process.platform === 'win32' ? `at logon (Scheduled Task "${TASK_NAME}") + 5-min watchdog ("${WATCHDOG_TASK_NAME}") that revives a killed daemon` : process.platform === 'darwin' ? 'launchd (RunAtLoad + KeepAlive)' : 'systemd user unit (or @reboot + 5-min watchdog cron)'}`);
901
932
  console.log(`${COLOR.gray}Start now:${COLOR.reset} fullcourtdefense daemon`);
902
933
  console.log(`${COLOR.gray}Status:${COLOR.reset} fullcourtdefense daemon --status true`);
903
934
  console.log(`${COLOR.gray}Remove:${COLOR.reset} fullcourtdefense daemon --uninstall true`);
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.15.10"
2
+ "version": "1.15.11"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.15.10",
3
+ "version": "1.15.11",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {