failproofai 1.0.0-beta.7 → 1.0.0-beta.8

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 (124) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/index.html +1 -1
  39. package/.next/standalone/.next/server/app/index.rsc +14 -14
  40. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  42. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  43. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
  44. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  49. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  62. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
  68. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ommekk._.js +3 -0
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0c_qcgp._.js → [root-of-the-server]__1tkyag1._.js} +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/{_194yh9u._.js → _0i4l3_6._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  89. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  90. package/.next/standalone/.next/server/pages/404.html +1 -1
  91. package/.next/standalone/.next/server/pages/500.html +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/server-reference-manifest.json +15 -15
  94. package/.next/standalone/.next/static/chunks/{2aej0vi1fm87m.js → 02vvywmww22zz.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{3t1wr_kx6brwc.js → 0_-focvo4cuiv.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{2bxld6-rym3ah.js → 1fwh-2un_l-eo.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{2p2kz4aj1tooj.js → 1zrdsxe91qq6i.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/{0m-m7wgtxdoav.js → 2047vah_v4emu.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/23hl78dzffun4.js +6 -0
  100. package/.next/standalone/.next/static/chunks/{11l6oh9cyb35a.js → 2bw0stk-s-1dd.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{0gr76ps8_zm26.js → 2r_j5wrkbu5wm.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/3m8onpqrjxzu5.js +1 -0
  103. package/.next/standalone/.next/static/chunks/{36pc3_8fpb6ic.js → 3yn1y6ckypqwp.js} +1 -1
  104. package/.next/standalone/package.json +5 -5
  105. package/.next/standalone/server.js +1 -1
  106. package/bin/failproofai.mjs +118 -1
  107. package/dist/cli.mjs +1166 -101
  108. package/dist/worker.mjs +77 -6
  109. package/package.json +5 -5
  110. package/scripts/repro-npm-install.sh +155 -0
  111. package/src/hooks/builtin-policies.ts +154 -2
  112. package/src/hooks/configure-wizard.ts +16 -6
  113. package/src/hooks/daemon-client.ts +35 -0
  114. package/src/hooks/daemon-service.ts +227 -22
  115. package/src/hooks/first-run-gate.ts +9 -1
  116. package/src/hooks/fp-reset.ts +25 -0
  117. package/src/hooks/uninstall-cli.ts +345 -0
  118. package/src/hooks/worker-server.ts +12 -2
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kbc9bs._.js +0 -3
  120. package/.next/standalone/.next/static/chunks/0945ybz3fsvba.js +0 -1
  121. package/.next/standalone/.next/static/chunks/166f8qnh03jqc.js +0 -6
  122. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → gOR8i03AeQm1VnIayDHVV}/_buildManifest.js +0 -0
  123. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → gOR8i03AeQm1VnIayDHVV}/_clientMiddlewareManifest.js +0 -0
  124. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → gOR8i03AeQm1VnIayDHVV}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // package.json
22
- var version2 = "1.0.0-beta.7";
22
+ var version2 = "1.0.0-beta.8";
23
23
  var init_package = () => {};
24
24
 
25
25
  // src/posthog-key.ts
@@ -1454,6 +1454,7 @@ __export(exports_daemon_client, {
1454
1454
  tryDaemonHook: () => tryDaemonHook,
1455
1455
  isDaemonConfigured: () => isDaemonConfigured,
1456
1456
  daemonSocketPresent: () => daemonSocketPresent,
1457
+ daemonAcceptsConnections: () => daemonAcceptsConnections,
1457
1458
  attemptDaemonHook: () => attemptDaemonHook
1458
1459
  });
1459
1460
  import { createConnection } from "node:net";
@@ -1465,6 +1466,27 @@ function daemonSocketPresent() {
1465
1466
  return false;
1466
1467
  }
1467
1468
  }
1469
+ async function daemonAcceptsConnections(timeoutMs = DAEMON_CONNECT_TIMEOUT_MS) {
1470
+ if (process.platform === "win32")
1471
+ return false;
1472
+ return new Promise((resolve2) => {
1473
+ let settled = false;
1474
+ const done = (v) => {
1475
+ if (settled)
1476
+ return;
1477
+ settled = true;
1478
+ clearTimeout(timer);
1479
+ socket.removeAllListeners();
1480
+ socket.destroy();
1481
+ resolve2(v);
1482
+ };
1483
+ const socket = createConnection({ path: socketPath() });
1484
+ const timer = setTimeout(() => done(false), timeoutMs);
1485
+ timer.unref?.();
1486
+ socket.on("connect", () => done(true));
1487
+ socket.on("error", () => done(false));
1488
+ });
1489
+ }
1468
1490
  function socketPath() {
1469
1491
  if (process.env.FAILPROOFAI_DAEMON_SOCKET)
1470
1492
  return process.env.FAILPROOFAI_DAEMON_SOCKET;
@@ -2751,11 +2773,82 @@ function blockEnvFiles(ctx) {
2751
2773
  }
2752
2774
  return allow();
2753
2775
  }
2776
+ function quoteAwareSegments(command) {
2777
+ const segments = [];
2778
+ let tokens = [];
2779
+ let token = "";
2780
+ let quote = null;
2781
+ const endToken = () => {
2782
+ if (token)
2783
+ tokens.push(token);
2784
+ token = "";
2785
+ };
2786
+ const endSegment = () => {
2787
+ endToken();
2788
+ if (tokens.length)
2789
+ segments.push(tokens);
2790
+ tokens = [];
2791
+ };
2792
+ for (let i = 0;i < command.length; i++) {
2793
+ const c = command[i];
2794
+ if (quote) {
2795
+ if (c === "\\" && quote === '"' && i + 1 < command.length) {
2796
+ token += command[++i];
2797
+ continue;
2798
+ }
2799
+ if (c === quote) {
2800
+ quote = null;
2801
+ continue;
2802
+ }
2803
+ token += c;
2804
+ continue;
2805
+ }
2806
+ if (c === "\\" && i + 1 < command.length) {
2807
+ token += command[++i];
2808
+ continue;
2809
+ }
2810
+ if (c === '"' || c === "'") {
2811
+ quote = c;
2812
+ continue;
2813
+ }
2814
+ if (/\s/.test(c)) {
2815
+ endToken();
2816
+ continue;
2817
+ }
2818
+ if (/[;&|\n\r(){}`]/.test(c)) {
2819
+ endSegment();
2820
+ continue;
2821
+ }
2822
+ token += c;
2823
+ }
2824
+ endSegment();
2825
+ return segments;
2826
+ }
2827
+ function namesElevation(command, depth = 0) {
2828
+ for (const tokens of quoteAwareSegments(command)) {
2829
+ let i = 0;
2830
+ while (i < tokens.length) {
2831
+ const token = tokens[i];
2832
+ const base = token.slice(token.lastIndexOf("/") + 1);
2833
+ if (base === "sudo" || base === "doas")
2834
+ return true;
2835
+ const isSkippable = ENV_ASSIGNMENT_RE.test(token) || token.startsWith("-") || token.startsWith(">") || token.startsWith("<") || RUNNER_OPERAND_RE.test(token) || COMMAND_PREFIX_TOKENS.has(base);
2836
+ if (!isSkippable)
2837
+ break;
2838
+ if (EVAL_FLAG_RE.test(token) && depth < 3 && i + 1 < tokens.length) {
2839
+ if (namesElevation(tokens[i + 1], depth + 1))
2840
+ return true;
2841
+ }
2842
+ i++;
2843
+ }
2844
+ }
2845
+ return false;
2846
+ }
2754
2847
  function blockSudo(ctx) {
2755
2848
  if (ctx.toolName !== "Bash")
2756
2849
  return allow();
2757
2850
  const cmd = getCommand(ctx).trimStart();
2758
- if (SUDO_RE.test(cmd) || cmd.startsWith("sudo ")) {
2851
+ if (namesElevation(cmd)) {
2759
2852
  const allowPatterns = ctx.params?.allowPatterns ?? [];
2760
2853
  if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p)))
2761
2854
  return allow();
@@ -3515,7 +3608,7 @@ function registerBuiltinPolicies(enabledNames) {
3515
3608
  }
3516
3609
  }
3517
3610
  }
3518
- var isClaudeInternalPath, isClaudeSettingsFile, SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, SEGMENT_SEPARATORS, COMMAND_PREFIX_TOKENS, SELF_BINARY_TOKEN_RE, CONFIG_SUBCOMMAND_RE, PAUSE_FLAG_RE, ENV_ASSIGNMENT_RE, RUNNER_OPERAND_RE, PS_WEB_PIPE_RE, SHORT_FLAG_BUNDLE_RE, SAFE_FORCE_PREFIXES, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, GIT_BRANCH_CACHE_MAX_ENTRIES = 500, HOME_PREFIX_RE, RM_CMD_RE, FIND_CMD_RE, FIND_EXEC_RE, FIND_GLOBAL_OPT_RE, FIND_EXPR_START_RE, SCRATCH_ROOTS, CATASTROPHIC_DEPTH = 2, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
3611
+ var isClaudeInternalPath, isClaudeSettingsFile, SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, SEGMENT_SEPARATORS, COMMAND_PREFIX_TOKENS, SELF_BINARY_TOKEN_RE, CONFIG_SUBCOMMAND_RE, PAUSE_FLAG_RE, ENV_ASSIGNMENT_RE, RUNNER_OPERAND_RE, PS_WEB_PIPE_RE, SHORT_FLAG_BUNDLE_RE, SAFE_FORCE_PREFIXES, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, GIT_BRANCH_CACHE_MAX_ENTRIES = 500, EVAL_FLAG_RE, HOME_PREFIX_RE, RM_CMD_RE, FIND_CMD_RE, FIND_EXEC_RE, FIND_GLOBAL_OPT_RE, FIND_EXPR_START_RE, SCRATCH_ROOTS, CATASTROPHIC_DEPTH = 2, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
3519
3612
  var init_builtin_policies = __esm(() => {
3520
3613
  init_hook_logger();
3521
3614
  isClaudeInternalPath = isAgentInternalPath;
@@ -3552,7 +3645,6 @@ var init_builtin_policies = __esm(() => {
3552
3645
  CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
3553
3646
  ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
3554
3647
  ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
3555
- SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
3556
3648
  PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
3557
3649
  RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
3558
3650
  CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
@@ -3638,6 +3730,7 @@ var init_builtin_policies = __esm(() => {
3638
3730
  HELM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*helm(?:\s|$)/;
3639
3731
  GH_PIPELINE_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*gh\s+(?:workflow\s+(?:run|enable|disable)|run\s+(?:rerun|cancel)|pr\s+merge|release\s+(?:create|delete)|cache\s+delete|secret\s+(?:set|delete))\b/;
3640
3732
  gitBranchCache = new Map;
3733
+ EVAL_FLAG_RE = /^-{1,2}c$/;
3641
3734
  HOME_PREFIX_RE = /^(?:~[A-Za-z0-9_.-]*|\$HOME|\$\{HOME\})(?=$|\/)/;
3642
3735
  RM_CMD_RE = /^(?:\/\S*\/)?rm$/;
3643
3736
  FIND_CMD_RE = /^(?:\/\S*\/)?find$/;
@@ -10739,6 +10832,7 @@ __export(exports_daemon_client2, {
10739
10832
  tryDaemonHook: () => tryDaemonHook2,
10740
10833
  isDaemonConfigured: () => isDaemonConfigured2,
10741
10834
  daemonSocketPresent: () => daemonSocketPresent2,
10835
+ daemonAcceptsConnections: () => daemonAcceptsConnections2,
10742
10836
  attemptDaemonHook: () => attemptDaemonHook2
10743
10837
  });
10744
10838
  import { createConnection as createConnection2 } from "node:net";
@@ -10750,6 +10844,27 @@ function daemonSocketPresent2() {
10750
10844
  return false;
10751
10845
  }
10752
10846
  }
10847
+ async function daemonAcceptsConnections2(timeoutMs = DAEMON_CONNECT_TIMEOUT_MS2) {
10848
+ if (process.platform === "win32")
10849
+ return false;
10850
+ return new Promise((resolve14) => {
10851
+ let settled = false;
10852
+ const done = (v) => {
10853
+ if (settled)
10854
+ return;
10855
+ settled = true;
10856
+ clearTimeout(timer);
10857
+ socket.removeAllListeners();
10858
+ socket.destroy();
10859
+ resolve14(v);
10860
+ };
10861
+ const socket = createConnection2({ path: socketPath2() });
10862
+ const timer = setTimeout(() => done(false), timeoutMs);
10863
+ timer.unref?.();
10864
+ socket.on("connect", () => done(true));
10865
+ socket.on("error", () => done(false));
10866
+ });
10867
+ }
10753
10868
  function socketPath2() {
10754
10869
  if (process.env.FAILPROOFAI_DAEMON_SOCKET)
10755
10870
  return process.env.FAILPROOFAI_DAEMON_SOCKET;
@@ -10947,6 +11062,15 @@ function resolveCliCommand() {
10947
11062
  function shellQuote(value) {
10948
11063
  return `'${value.replace(/'/g, `'\\''`)}'`;
10949
11064
  }
11065
+ function workerScriptPath() {
11066
+ if (process.env.FAILPROOFAI_WORKER_CMD)
11067
+ return null;
11068
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
11069
+ if (!packageRoot)
11070
+ return null;
11071
+ const workerScript = resolve14(packageRoot, "dist", "worker.mjs");
11072
+ return existsSync17(workerScript) ? workerScript : null;
11073
+ }
10950
11074
  function assertUnitSafe(value, field) {
10951
11075
  if (UNIT_UNSAFE_RE.test(value)) {
10952
11076
  throw new Error(`refusing to write a service definition: ${field} contains a quote, backslash or newline ` + `(${JSON.stringify(value)}), which would inject directives into a root-owned unit file`);
@@ -11085,10 +11209,13 @@ function systemdUnitContents(binaryPath, workerCmd, cliCmd = null) {
11085
11209
  const user = assertUnitSafe(serviceUser(), "User");
11086
11210
  assertUnitSafe(binaryPath, "ExecStart");
11087
11211
  assertUnitSafe(homedir19(), "HOME");
11212
+ const conditionPaths = [binaryPath, workerScriptPath()].filter((p) => typeof p === "string" && existsSync17(p));
11213
+ const conditionLines = conditionPaths.map((p) => `ConditionPathExists=${assertUnitSafe(p, "ConditionPathExists")}
11214
+ `).join("");
11088
11215
  return `[Unit]
11089
11216
  Description=failproofai background daemon (failproofaid) for ${user}
11090
11217
  After=network.target
11091
-
11218
+ ${conditionLines}
11092
11219
  [Service]
11093
11220
  Type=simple
11094
11221
  User=${user}
@@ -11209,19 +11336,37 @@ async function installDaemonService() {
11209
11336
  }
11210
11337
  return { installed: true };
11211
11338
  }
11212
- async function probeDaemonEndToEnd() {
11339
+ async function probeDaemon() {
11213
11340
  try {
11214
- const { attemptDaemonHook: attemptDaemonHook3 } = await Promise.resolve().then(() => (init_daemon_client2(), exports_daemon_client2));
11215
- const attempt = await attemptDaemonHook3({
11216
- hookEvent: "SessionStart",
11217
- cli: "claude",
11218
- stdin: JSON.stringify({ hook_event_name: "SessionStart", source: "failproofai-health-probe" })
11219
- }, { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS });
11220
- return attempt.ok || attempt.failure === "protocol-mismatch";
11341
+ const { attemptDaemonHook: attemptDaemonHook3, daemonAcceptsConnections: daemonAcceptsConnections3 } = await Promise.resolve().then(() => (init_daemon_client2(), exports_daemon_client2));
11342
+ const deadline = Date.now() + DAEMON_PROBE_READY_TIMEOUT_MS;
11343
+ let everConnected = false;
11344
+ for (;; ) {
11345
+ if (await daemonAcceptsConnections3()) {
11346
+ everConnected = true;
11347
+ const attempt = await attemptDaemonHook3({
11348
+ hookEvent: "SessionStart",
11349
+ cli: "claude",
11350
+ stdin: JSON.stringify({
11351
+ hook_event_name: "SessionStart",
11352
+ source: "failproofai-health-probe"
11353
+ })
11354
+ }, { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS });
11355
+ if (attempt.ok || attempt.failure === "protocol-mismatch")
11356
+ return { ok: true };
11357
+ }
11358
+ if (Date.now() >= deadline)
11359
+ break;
11360
+ await new Promise((r) => setTimeout(r, DAEMON_PROBE_RETRY_MS));
11361
+ }
11362
+ return { ok: false, reason: everConnected ? "worker" : "unreachable" };
11221
11363
  } catch {
11222
- return false;
11364
+ return { ok: false, reason: "unreachable" };
11223
11365
  }
11224
11366
  }
11367
+ async function probeDaemonEndToEnd() {
11368
+ return (await probeDaemon()).ok;
11369
+ }
11225
11370
  async function waitForDaemonRunning() {
11226
11371
  const deadline = Date.now() + SERVICE_START_TIMEOUT_MS;
11227
11372
  for (;; ) {
@@ -11406,6 +11551,16 @@ function daemonVersionSkew() {
11406
11551
  return null;
11407
11552
  return recorded === version2 ? null : { installed: recorded, expected: version2 };
11408
11553
  }
11554
+ function inactiveLinuxStatus() {
11555
+ try {
11556
+ return interpretConditionResult(execFileSync3("systemctl", ["show", systemdUnitName(), "--property=ConditionResult", "--value"], { stdio: ["ignore", "pipe", "ignore"], timeout: SERVICE_CMD_TIMEOUT_MS }).toString());
11557
+ } catch {
11558
+ return "stopped";
11559
+ }
11560
+ }
11561
+ function interpretConditionResult(raw) {
11562
+ return raw.trim() === "no" ? "condition-failed" : "stopped";
11563
+ }
11409
11564
  function daemonServiceStatus() {
11410
11565
  if (!isDaemonSupportedPlatform())
11411
11566
  return "unsupported-platform";
@@ -11417,9 +11572,9 @@ function daemonServiceStatus() {
11417
11572
  stdio: ["ignore", "pipe", "ignore"],
11418
11573
  timeout: SERVICE_CMD_TIMEOUT_MS
11419
11574
  }).toString().trim();
11420
- return out === "active" ? "running" : "stopped";
11575
+ return out === "active" ? "running" : inactiveLinuxStatus();
11421
11576
  } catch {
11422
- return "stopped";
11577
+ return inactiveLinuxStatus();
11423
11578
  }
11424
11579
  }
11425
11580
  const plistPath = launchdPlistPath();
@@ -11440,7 +11595,7 @@ function daemonServiceStatus() {
11440
11595
  return "stopped";
11441
11596
  }
11442
11597
  }
11443
- var SERVICE_CMD_TIMEOUT_MS = 1e4, SERVICE_START_TIMEOUT_MS = 5000, SERVICE_START_POLL_MS = 100, SERVICE_SETTLE_MS = 750, SUDO_PROMPT_TIMEOUT_MS = 120000, UNIT_UNSAFE_RE, DAEMON_PROBE_TIMEOUT_MS = 5000;
11598
+ var SERVICE_CMD_TIMEOUT_MS = 1e4, SERVICE_START_TIMEOUT_MS = 5000, SERVICE_START_POLL_MS = 100, SERVICE_SETTLE_MS = 750, SUDO_PROMPT_TIMEOUT_MS = 120000, UNIT_UNSAFE_RE, DAEMON_PROBE_TIMEOUT_MS = 5000, DAEMON_PROBE_READY_TIMEOUT_MS = 1e4, DAEMON_PROBE_RETRY_MS = 150;
11444
11599
  var init_daemon_service = __esm(() => {
11445
11600
  init_hook_logger();
11446
11601
  init_daemon_download();
@@ -11906,6 +12061,20 @@ async function healDaemonFlag() {
11906
12061
  ``
11907
12062
  ];
11908
12063
  }
12064
+ if (status === "condition-failed") {
12065
+ updateConfig({ daemon: { configured: false } });
12066
+ return [
12067
+ `failproofaid is installed but cannot start — a file its service requires is`,
12068
+ `gone (most often because failproofai was removed with \`npm rm -g\`, which`,
12069
+ `deletes the worker but leaves the service behind). This machine was`,
12070
+ `configured to require the daemon, which denies every tool call, so that flag`,
12071
+ `is cleared; policies now evaluate in-process.`,
12072
+ ``,
12073
+ `Run \`failproofai uninstall\` to remove the leftover service, or`,
12074
+ `\`failproofai config\` to rebuild it. \`${daemonStatusCommand()}\` names the missing path.`,
12075
+ ``
12076
+ ];
12077
+ }
11909
12078
  if (status === "running" && !await probeDaemonEndToEnd()) {
11910
12079
  updateConfig({ daemon: { configured: false } });
11911
12080
  return [
@@ -12215,7 +12384,8 @@ var init_first_run_gate = __esm(() => {
12215
12384
  FIRST_RUN_EXEMPT_SUBCOMMANDS = [
12216
12385
  "config",
12217
12386
  "policies",
12218
- "policy"
12387
+ "policy",
12388
+ "uninstall"
12219
12389
  ];
12220
12390
  });
12221
12391
 
@@ -21925,6 +22095,129 @@ This file can be committed to git — no machine-specific paths.`);
21925
22095
  } catch {}
21926
22096
  }
21927
22097
  }
22098
+ async function removeHooks(policyNames, scope = "user", cwd, opts) {
22099
+ const configScope = scope === "all" ? "user" : scope;
22100
+ const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
22101
+ if (opts?.removeCustomHooks) {
22102
+ const config = readScopedHooksConfig(configScope, cwd);
22103
+ delete config.customPoliciesPath;
22104
+ delete config.customPoliciesPaths;
22105
+ writeScopedHooksConfig(config, configScope, cwd);
22106
+ console.log("Custom hooks path cleared.");
22107
+ }
22108
+ if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
22109
+ validatePolicyNames(policyNames);
22110
+ const config = readScopedHooksConfig(configScope, cwd);
22111
+ const removeSet = new Set(policyNames);
22112
+ const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
22113
+ const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
22114
+ if (notEnabled.length > 0) {
22115
+ console.log(`Warning: policy(ies) not currently enabled: ${notEnabled.join(", ")}`);
22116
+ }
22117
+ const { policyParams: existingParams, ...baseConfig } = config;
22118
+ const filteredParams = existingParams ? Object.fromEntries(Object.entries(existingParams).filter(([k]) => !removeSet.has(k))) : null;
22119
+ const updatedConfig = {
22120
+ ...baseConfig,
22121
+ enabledPolicies: remaining,
22122
+ ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
22123
+ };
22124
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
22125
+ try {
22126
+ const distinctId = getInstanceId2();
22127
+ const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
22128
+ await trackHookEvent2(distinctId, "hooks_removed", {
22129
+ scope,
22130
+ cli: selectedClis,
22131
+ removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
22132
+ beta_only: opts?.betaOnly ?? false,
22133
+ policies_removed: actuallyRemoved,
22134
+ removed_count: actuallyRemoved.length,
22135
+ ...opts?.source ? { source: opts.source } : {},
22136
+ platform: platform3(),
22137
+ arch: arch3(),
22138
+ os_release: release2(),
22139
+ hostname_hash: hashToId2(hostname3())
22140
+ });
22141
+ } catch {}
22142
+ console.log(`Disabled ${policyNames.length - notEnabled.length} policy(ies).`);
22143
+ console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
22144
+ return;
22145
+ }
22146
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
22147
+ let totalRemoved = 0;
22148
+ let nothingToReport = false;
22149
+ for (const cliId of selectedClis) {
22150
+ const integration = getIntegration(cliId);
22151
+ const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
22152
+ for (const s of scopesToRemove) {
22153
+ const settingsPaths = settingsPathsFor(integration, s, cwd);
22154
+ const existing = settingsPaths.filter((p) => existsSync21(p));
22155
+ if (existing.length === 0) {
22156
+ if (scope !== "all" && selectedClis.length === 1) {
22157
+ console.log("No settings file found. Nothing to remove.");
22158
+ nothingToReport = true;
22159
+ }
22160
+ continue;
22161
+ }
22162
+ let removedHere = 0;
22163
+ for (const settingsPath of existing) {
22164
+ const removed = integration.removeHooksFromFile(settingsPath);
22165
+ removedHere += removed;
22166
+ if (removed > 0 && scope !== "all") {
22167
+ console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
22168
+ console.log(`Settings: ${settingsPath}`);
22169
+ }
22170
+ }
22171
+ if (removedHere === 0 && scope !== "all" && selectedClis.length === 1) {
22172
+ console.log("No hooks found in settings. Nothing to remove.");
22173
+ nothingToReport = true;
22174
+ continue;
22175
+ }
22176
+ totalRemoved += removedHere;
22177
+ }
22178
+ }
22179
+ if (nothingToReport && totalRemoved === 0)
22180
+ return;
22181
+ if (scope === "all") {
22182
+ console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
22183
+ for (const cliId of selectedClis) {
22184
+ const integration = getIntegration(cliId);
22185
+ for (const s of integration.scopes) {
22186
+ for (const p of settingsPathsFor(integration, s, cwd)) {
22187
+ console.log(` ${integration.displayName} / ${s}: ${p}`);
22188
+ }
22189
+ }
22190
+ }
22191
+ }
22192
+ try {
22193
+ const distinctId = getInstanceId2();
22194
+ await trackHookEvent2(distinctId, "hooks_removed", {
22195
+ scope,
22196
+ cli: selectedClis,
22197
+ removal_mode: "hooks",
22198
+ policies_removed: configBeforeRemoval.enabledPolicies,
22199
+ removed_count: totalRemoved,
22200
+ ...opts?.source ? { source: opts.source } : {},
22201
+ platform: platform3(),
22202
+ arch: arch3(),
22203
+ os_release: release2(),
22204
+ hostname_hash: hashToId2(hostname3())
22205
+ });
22206
+ } catch {}
22207
+ if (scope === "all") {
22208
+ for (const s of HOOK_SCOPES) {
22209
+ const existing = readScopedHooksConfig(s, cwd);
22210
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPaths?.length || existing.customPoliciesPath || existing.policyParams) {
22211
+ const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
22212
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
22213
+ }
22214
+ }
22215
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
22216
+ const existing = readScopedHooksConfig(configScope, cwd);
22217
+ const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
22218
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
22219
+ }
22220
+ }
21928
22221
  var VALID_POLICY_NAMES;
21929
22222
  var init_manager = __esm(() => {
21930
22223
  init_types();
@@ -25862,18 +26155,22 @@ Setup stopped before changing anything. Once that is fixed, re-run:
25862
26155
  outro("Nothing was changed.", { ok: false }, stdout);
25863
26156
  return { applied: false, abort: "daemon_failed" };
25864
26157
  }
25865
- if (!await probeDaemonEndToEnd()) {
25866
- hookLogWarn("failproofaid was installed and running but did not answer a policy evaluation");
25867
- stdout.write(`
25868
- failproofaid started but cannot evaluate policies — its worker process
25869
- ` + `could not be run. Setup stopped before changing anything, because a machine
25870
- configured to require a daemon that cannot answer denies every tool call.
26158
+ const probe = await probeDaemon();
26159
+ if (!probe.ok) {
26160
+ hookLogWarn(`failproofaid was installed and running but did not answer a policy evaluation (${probe.reason})`);
26161
+ stdout.write((probe.reason === "worker" ? `
26162
+ failproofaid started but cannot evaluate policies — it is listening, and
26163
+ ` + `its worker process could not be run.
26164
+ ` : `
26165
+ failproofaid started but could not be reached on its socket.
26166
+ `) + `Setup stopped before changing anything, because a machine configured to
26167
+ require a daemon that cannot answer denies every tool call.
25871
26168
 
25872
26169
  Check it with: ${daemonStatusCommand() ?? "systemctl status failproofaid"}
25873
26170
  Then re-run: failproofai config
25874
26171
 
25875
26172
  `);
25876
- emit("configure_aborted", { reason: "daemon_not_answering" });
26173
+ emit("configure_aborted", { reason: `daemon_not_answering_${probe.reason}` });
25877
26174
  outro("Nothing was changed.", { ok: false }, stdout);
25878
26175
  return { applied: false, abort: "daemon_failed" };
25879
26176
  }
@@ -25998,55 +26295,735 @@ var init_configure_wizard = __esm(() => {
25998
26295
  init_onboarding_lock();
25999
26296
  });
26000
26297
 
26001
- // src/hooks/manager.ts
26002
- var exports_manager = {};
26003
- __export(exports_manager, {
26004
- removeHooks: () => removeHooks,
26005
- listHooks: () => listHooks,
26006
- installHooks: () => installHooks2,
26007
- hooksInstalledInSettings: () => hooksInstalledInSettings2,
26008
- getSettingsPath: () => getSettingsPath2
26298
+ // src/hooks/uninstall-cli.ts
26299
+ var exports_uninstall_cli = {};
26300
+ __export(exports_uninstall_cli, {
26301
+ serviceStateLabel: () => serviceStateLabel,
26302
+ runUninstallCommand: () => runUninstallCommand
26009
26303
  });
26010
- import { execSync as execSync7 } from "node:child_process";
26011
- import { existsSync as existsSync31 } from "node:fs";
26012
- import { resolve as resolve24, basename as basename6 } from "node:path";
26013
- import { platform as platform5, arch as arch4, release as release3, hostname as hostname5 } from "node:os";
26014
- function getSettingsPath2(scope, cwd) {
26015
- return claudeCode.getSettingsPath(scope, cwd);
26016
- }
26017
- function scopeLabel2(scope) {
26018
- switch (scope) {
26019
- case "user":
26020
- return `~/.claude/settings.json`;
26021
- case "project":
26022
- return `{cwd}/.claude/settings.json`;
26023
- case "local":
26024
- return `{cwd}/.claude/settings.local.json`;
26304
+ import { existsSync as existsSync31, rmSync as rmSync10 } from "node:fs";
26305
+ function survey(cwd) {
26306
+ const clis = [];
26307
+ for (const id of listInstallableIds()) {
26308
+ for (const scope of ["user", "project", "local"]) {
26309
+ try {
26310
+ if (getIntegration(id).hooksInstalledInSettings(scope, cwd)) {
26311
+ clis.push(id);
26312
+ break;
26313
+ }
26314
+ } catch {}
26315
+ }
26025
26316
  }
26026
- }
26027
- function resolveFailproofaiBinary2() {
26028
- const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
26029
- if (override && override.trim())
26030
- return override.trim();
26317
+ let daemonConfigured = false;
26031
26318
  try {
26032
- const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
26033
- const result = execSync7(cmd, { encoding: "utf8" }).trim();
26034
- return result.split(`
26035
- `)[0].trim();
26036
- } catch {
26037
- throw new CliError(`failproofai binary not found in PATH.
26038
- ` + "Install it globally first: npm install -g failproofai");
26039
- }
26040
- }
26041
- function validatePolicyNames2(names) {
26042
- const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
26043
- if (invalid.length > 0) {
26044
- const validList = [...VALID_POLICY_NAMES2].join(", ");
26045
- throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
26046
- ` + `Valid policies: ${validList}`);
26047
- }
26319
+ daemonConfigured = readConfig().daemon.configured;
26320
+ } catch {}
26321
+ const servicePath = isDaemonSupportedPlatform() ? daemonServiceFilePath() : null;
26322
+ return {
26323
+ clis,
26324
+ servicePath,
26325
+ serviceInstalled: !!servicePath && existsSync31(servicePath),
26326
+ daemonConfigured,
26327
+ homeExists: existsSync31(failproofaiHome())
26328
+ };
26048
26329
  }
26049
- function deduplicateScopes2(scopes, cwd) {
26330
+ async function runUninstallCommand(opts = {}) {
26331
+ const found = survey(opts.cwd);
26332
+ const lines = [];
26333
+ const nothingToDo = found.clis.length === 0 && !found.serviceInstalled && !found.daemonConfigured && !(opts.purge && found.homeExists);
26334
+ if (nothingToDo) {
26335
+ return {
26336
+ exitCode: 0,
26337
+ planLines: 0,
26338
+ purged: false,
26339
+ lines: [
26340
+ "Nothing to uninstall — no hook entries, no daemon service, and nothing configured.",
26341
+ ...found.homeExists && !opts.purge ? [
26342
+ ``,
26343
+ `${failproofaiHome()} still holds settings and audit history.`,
26344
+ `Remove it with \`failproofai uninstall --purge\`.`
26345
+ ] : []
26346
+ ]
26347
+ };
26348
+ }
26349
+ const plan = [];
26350
+ if (found.clis.length > 0) {
26351
+ plan.push(` • remove failproofai hook entries from ${found.clis.length} agent CLI${found.clis.length === 1 ? "" : "s"}: ` + found.clis.map((id) => getIntegration(id).displayName ?? id).join(", "));
26352
+ }
26353
+ if (found.daemonConfigured) {
26354
+ plan.push(` • stop requiring the daemon (policies go back to evaluating in-process)`);
26355
+ }
26356
+ if (found.serviceInstalled) {
26357
+ plan.push(` • stop, disable and delete the service at ${found.servicePath} — needs sudo`);
26358
+ }
26359
+ let purged = false;
26360
+ if (opts.purge && found.homeExists) {
26361
+ plan.push(` • delete ${failproofaiHome()} — settings, credentials, audit history and the daemon binary`);
26362
+ }
26363
+ lines.push("failproofai uninstall will:", ...plan);
26364
+ if (!opts.purge && found.homeExists) {
26365
+ lines.push(``, `${failproofaiHome()} is KEPT (settings and audit history survive a reinstall).`, `Add --purge to delete it too.`);
26366
+ }
26367
+ const planLines = lines.length;
26368
+ if (opts.dryRun) {
26369
+ lines.push(``, `--dry-run: nothing was changed.`);
26370
+ return { exitCode: 0, lines, planLines, purged: false };
26371
+ }
26372
+ if (!opts.yes) {
26373
+ if (!opts.confirm) {
26374
+ lines.push(``, `Refusing to uninstall without confirmation. Re-run with --yes.`);
26375
+ return { exitCode: 1, lines, planLines, purged: false };
26376
+ }
26377
+ if (!await opts.confirm(lines)) {
26378
+ return { exitCode: 1, planLines, purged: false, lines: [...lines, ``, `Cancelled — nothing was changed.`] };
26379
+ }
26380
+ }
26381
+ lines.push(``);
26382
+ const failures = [];
26383
+ if (found.daemonConfigured) {
26384
+ try {
26385
+ setDaemonConfigured(false);
26386
+ lines.push(`✓ policies now evaluate in-process (this machine no longer requires the daemon)`);
26387
+ } catch (err) {
26388
+ return {
26389
+ exitCode: 2,
26390
+ planLines,
26391
+ purged: false,
26392
+ lines: [
26393
+ ...lines,
26394
+ `✗ could not update ${failproofaiHome()}/config.toml: ${err instanceof Error ? err.message : String(err)}`,
26395
+ ``,
26396
+ `Stopped before touching the service. Removing it while this machine still`,
26397
+ `requires it would deny every tool call. Fix the file's permissions and re-run.`
26398
+ ]
26399
+ };
26400
+ }
26401
+ }
26402
+ if (found.clis.length > 0) {
26403
+ try {
26404
+ await removeHooks(undefined, "all", opts.cwd, {
26405
+ cli: found.clis,
26406
+ removeCustomHooks: true,
26407
+ source: "uninstall_command"
26408
+ });
26409
+ lines.push(`✓ removed hook entries from ${found.clis.length} agent CLI${found.clis.length === 1 ? "" : "s"}`);
26410
+ } catch (err) {
26411
+ failures.push(`hook entries: ${err instanceof Error ? err.message : String(err)} ` + `(finish with \`failproofai policies --uninstall --scope all\`)`);
26412
+ }
26413
+ }
26414
+ if (found.serviceInstalled) {
26415
+ try {
26416
+ await uninstallDaemonService();
26417
+ if (found.servicePath && existsSync31(found.servicePath)) {
26418
+ failures.push(`the service at ${found.servicePath} is still there (most often: no sudo). ` + `Remove it with the commands below.`);
26419
+ } else {
26420
+ lines.push(`✓ stopped and removed the daemon service`);
26421
+ }
26422
+ } catch (err) {
26423
+ failures.push(`daemon service: ${err instanceof Error ? err.message : String(err)}`);
26424
+ }
26425
+ }
26426
+ if (opts.purge && found.homeExists) {
26427
+ try {
26428
+ rmSync10(failproofaiHome(), { recursive: true, force: true });
26429
+ purged = true;
26430
+ lines.push(`✓ deleted ${failproofaiHome()}`);
26431
+ } catch (err) {
26432
+ failures.push(`${failproofaiHome()}: ${err instanceof Error ? err.message : String(err)}`);
26433
+ }
26434
+ }
26435
+ if (failures.length > 0) {
26436
+ lines.push(``, `Not everything could be removed:`);
26437
+ for (const f of failures)
26438
+ lines.push(` ✗ ${f}`);
26439
+ if (found.servicePath && existsSync31(found.servicePath)) {
26440
+ lines.push(``, `To remove the service by hand:`, ...manualServiceRemoval(found.servicePath), ``, `(\`${daemonStatusCommand()}\` shows its current state.)`);
26441
+ }
26442
+ return { exitCode: 1, lines, planLines, purged };
26443
+ }
26444
+ lines.push(``, `Done. failproofai no longer enforces anything on this machine.`, ``, `The npm package itself is still installed — npm runs no uninstall script, which`, `is why this command exists. Remove it with:`, ` npm rm -g failproofai`);
26445
+ if (!opts.purge && found.homeExists) {
26446
+ lines.push(``, `${failproofaiHome()} was kept. Delete it with \`failproofai uninstall --purge\`.`);
26447
+ }
26448
+ return { exitCode: 0, lines, planLines, purged };
26449
+ }
26450
+ function manualServiceRemoval(servicePath) {
26451
+ if (process.platform === "darwin") {
26452
+ return [` sudo launchctl unload -w ${servicePath}`, ` sudo rm -f ${servicePath}`];
26453
+ }
26454
+ const unit = servicePath.split("/").pop() ?? servicePath;
26455
+ return [
26456
+ ` sudo systemctl disable --now ${unit}`,
26457
+ ` sudo rm -f ${servicePath}`,
26458
+ ` sudo systemctl daemon-reload`
26459
+ ];
26460
+ }
26461
+ function serviceStateLabel() {
26462
+ const status = daemonServiceStatus();
26463
+ return status === "condition-failed" ? "installed but skipped by systemd (a file it needs is missing)" : status;
26464
+ }
26465
+ var init_uninstall_cli = __esm(() => {
26466
+ init_fp_home();
26467
+ init_fp_config();
26468
+ init_daemon_service();
26469
+ init_integrations();
26470
+ init_manager();
26471
+ });
26472
+
26473
+ // src/hooks/tui.ts
26474
+ var exports_tui = {};
26475
+ __export(exports_tui, {
26476
+ truncate: () => truncate2,
26477
+ summarize: () => summarize2,
26478
+ selectOne: () => selectOne2,
26479
+ renderLaunchBanner: () => renderLaunchBanner2,
26480
+ renderBrandLogo: () => renderBrandLogo2,
26481
+ promptText: () => promptText2,
26482
+ paint: () => paint2,
26483
+ outro: () => outro2,
26484
+ multiSelect: () => multiSelect2,
26485
+ intro: () => intro2,
26486
+ ellipsize: () => ellipsize2,
26487
+ colorsEnabled: () => colorsEnabled2,
26488
+ brandAnsi: () => brandAnsi2,
26489
+ STEP_DONE: () => STEP_DONE2,
26490
+ STEP_ACTIVE: () => STEP_ACTIVE2,
26491
+ CHECK_ON: () => CHECK_ON2,
26492
+ CHECK_OFF: () => CHECK_OFF2,
26493
+ CARET: () => CARET2,
26494
+ BAR: () => BAR2,
26495
+ ANSI_RESET: () => ANSI_RESET2,
26496
+ ANSI_DIM: () => ANSI_DIM2,
26497
+ ANSI_BOLD: () => ANSI_BOLD2
26498
+ });
26499
+ import * as readline3 from "node:readline";
26500
+ function colorsEnabled2(out) {
26501
+ return !!out.isTTY && !process.env.NO_COLOR;
26502
+ }
26503
+ function brandAnsi2(role) {
26504
+ const h = HUES2[role];
26505
+ const code = truecolorEnabled2() ? `38;2;${h.rgb.join(";")}` : h.basic;
26506
+ return `${ESC2}[${code}m`;
26507
+ }
26508
+ function truecolorEnabled2() {
26509
+ return /truecolor|24bit/i.test(process.env.COLORTERM || "");
26510
+ }
26511
+ function paint2(on) {
26512
+ const tc = on && truecolorEnabled2();
26513
+ const mk = (h, bold = false) => (s) => {
26514
+ if (!on)
26515
+ return s;
26516
+ const code = tc ? `38;2;${h.rgb[0]};${h.rgb[1]};${h.rgb[2]}` : h.basic;
26517
+ return `${ESC2}[${bold ? "1;" : ""}${code}m${s}${ESC2}[0m`;
26518
+ };
26519
+ return {
26520
+ bold: (s) => on ? `${ESC2}[1m${s}${ESC2}[0m` : s,
26521
+ dim: mk(HUES2.dim),
26522
+ guide: mk(HUES2.guide),
26523
+ pink: mk(HUES2.pink),
26524
+ pinkBold: mk(HUES2.pink, true),
26525
+ softPink: mk(HUES2.logoPink),
26526
+ warn: mk(HUES2.warn)
26527
+ };
26528
+ }
26529
+ function renderLogo2(colorize) {
26530
+ const pad = ".".repeat(LOGO_GRID2[0]?.length ?? 0);
26531
+ const rgb = (ch) => ch === "t" ? LOGO_TEAL2 : ch === "p" ? LOGO_PINK2 : null;
26532
+ const lines = [];
26533
+ for (let r = 0;r < LOGO_GRID2.length; r += 2) {
26534
+ const top = LOGO_GRID2[r];
26535
+ const bot = LOGO_GRID2[r + 1] ?? pad;
26536
+ let line = "";
26537
+ for (let x = 0;x < top.length; x++) {
26538
+ const t = rgb(top[x]);
26539
+ const b = rgb(bot[x]);
26540
+ if (!t && !b) {
26541
+ line += " ";
26542
+ } else if (!colorize) {
26543
+ line += t && b ? "█" : t ? "▀" : "▄";
26544
+ } else if (t && b) {
26545
+ line += t === b ? `${ESC2}[38;2;${t.join(";")}m█${ESC2}[0m` : `${ESC2}[38;2;${t.join(";")};48;2;${b.join(";")}m▀${ESC2}[0m`;
26546
+ } else if (t) {
26547
+ line += `${ESC2}[38;2;${t.join(";")}m▀${ESC2}[0m`;
26548
+ } else {
26549
+ line += `${ESC2}[38;2;${b.join(";")}m▄${ESC2}[0m`;
26550
+ }
26551
+ }
26552
+ lines.push(line);
26553
+ }
26554
+ return lines;
26555
+ }
26556
+ function truncate2(line, width) {
26557
+ let visual = 0;
26558
+ let out = "";
26559
+ let i = 0;
26560
+ while (i < line.length) {
26561
+ if (line[i] === ESC2 && line[i + 1] === "[") {
26562
+ let j = i + 2;
26563
+ while (j < line.length && !/[A-Za-z]/.test(line[j]))
26564
+ j++;
26565
+ j++;
26566
+ out += line.slice(i, j);
26567
+ i = j;
26568
+ } else {
26569
+ if (visual >= width)
26570
+ break;
26571
+ out += line[i];
26572
+ visual++;
26573
+ i++;
26574
+ }
26575
+ }
26576
+ return out;
26577
+ }
26578
+ function ellipsize2(text, width) {
26579
+ if (width <= 0)
26580
+ return "";
26581
+ if (text.length <= width)
26582
+ return text;
26583
+ return text.slice(0, width - 1).trimEnd() + "…";
26584
+ }
26585
+ function summarize2(labels, noun = "selected") {
26586
+ if (labels.length === 0)
26587
+ return "none";
26588
+ if (labels.length <= 3)
26589
+ return labels.join(", ");
26590
+ const head = labels.slice(0, 3).join(", ");
26591
+ return `${labels.length} ${noun} · ${head} +${labels.length - 3}`;
26592
+ }
26593
+ function writeLines2(out, lines) {
26594
+ const cols = out.columns || 80;
26595
+ out.write(lines.map((l) => l === "" ? l : truncate2(l, cols)).join(`
26596
+ `) + `
26597
+ `);
26598
+ }
26599
+ function renderBrandLogo2(stdout = process.stdout) {
26600
+ const c2 = paint2(colorsEnabled2(stdout));
26601
+ const cols = stdout.columns || 80;
26602
+ if (cols < LOGO_MIN_COLS2) {
26603
+ return [`${c2.guide(FLOWER2)} fa${c2.pink("il")}proof ai ${c2.dim("· " + TAGLINE2)}`];
26604
+ }
26605
+ const tc = colorsEnabled2(stdout) && truecolorEnabled2();
26606
+ const lines = renderLogo2(tc).map((l) => ` ${l}`);
26607
+ lines.push("");
26608
+ lines.push(` fa${c2.pink("il")}proof ai`);
26609
+ lines.push(` ${c2.dim(TAGLINE2)}`);
26610
+ return lines;
26611
+ }
26612
+ function intro2(message, stdout = process.stdout) {
26613
+ if (!stdout.isTTY)
26614
+ return;
26615
+ const c2 = paint2(colorsEnabled2(stdout));
26616
+ const lines = ["", ...renderBrandLogo2(stdout)];
26617
+ lines.push(c2.dim(BAR2));
26618
+ lines.push(`${c2.guide(STEP_ACTIVE2)} ${c2.bold(message)}`);
26619
+ writeLines2(stdout, lines);
26620
+ }
26621
+ function renderLaunchBanner2(version3, stdout = process.stdout) {
26622
+ const c2 = paint2(colorsEnabled2(stdout));
26623
+ const row = (label, value) => ` ${c2.guide(label.padEnd(9))}${value}`;
26624
+ const header = stdout.isTTY ? renderBrandLogo2(stdout) : [" failproof ai", ` ${TAGLINE2}`];
26625
+ return [
26626
+ "",
26627
+ ...header,
26628
+ "",
26629
+ row("version", c2.pink(version3)),
26630
+ row("star", c2.dim("https://github.com/failproofai/failproofai")),
26631
+ row("docs", c2.dim("https://docs.befailproof.ai/introduction")),
26632
+ row("discord", c2.dim("https://discord.befailproof.ai/")),
26633
+ ""
26634
+ ];
26635
+ }
26636
+ function outro2(message, opts = {}, stdout = process.stdout) {
26637
+ const c2 = paint2(colorsEnabled2(stdout));
26638
+ const ok = opts.ok !== false;
26639
+ if (!stdout.isTTY) {
26640
+ stdout.write(message + `
26641
+ `);
26642
+ return;
26643
+ }
26644
+ const end = ok ? c2.pink(BAR_END2) : c2.dim(BAR_END2);
26645
+ const text = ok ? c2.pink(message) : c2.dim(message);
26646
+ writeLines2(stdout, [c2.dim(BAR2), `${end} ${text}`]);
26647
+ }
26648
+ function repaint2(out, region, lines) {
26649
+ if (region.lastCount > 0)
26650
+ out.write(`${ESC2}[${region.lastCount}A${ESC2}[J`);
26651
+ writeLines2(out, lines);
26652
+ region.lastCount = lines.length;
26653
+ }
26654
+ function hideCursor2(out) {
26655
+ out.write(`${ESC2}[?25l`);
26656
+ }
26657
+ function showCursor2(out) {
26658
+ out.write(`${ESC2}[?25h`);
26659
+ }
26660
+ function nameWidth2(labels) {
26661
+ return Math.min(24, Math.max(6, ...labels.map((l) => l.length)));
26662
+ }
26663
+ function displayRows2(choices) {
26664
+ const rows = [];
26665
+ let lastSection;
26666
+ choices.forEach((choice, index) => {
26667
+ if (choice.section && choice.section !== lastSection) {
26668
+ lastSection = choice.section;
26669
+ rows.push({ kind: "header", text: choice.section });
26670
+ }
26671
+ rows.push({ kind: "item", index });
26672
+ });
26673
+ return rows;
26674
+ }
26675
+ function viewport2(rows, cursorRow, window) {
26676
+ if (rows.length <= window)
26677
+ return { start: 0, end: rows.length };
26678
+ let start = cursorRow - Math.floor(window / 2);
26679
+ start = Math.max(0, Math.min(start, rows.length - window));
26680
+ return { start, end: start + window };
26681
+ }
26682
+ function runPrompt2(p) {
26683
+ const { stdin, stdout, c: c2, choices } = p;
26684
+ const region = { lastCount: 0 };
26685
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26686
+ let cursor2 = 0;
26687
+ const build = () => {
26688
+ const cols = stdout.columns || 80;
26689
+ const lines = [c2.dim(BAR2), `${c2.guide(STEP_ACTIVE2)} ${c2.bold(p.message)}`];
26690
+ for (const b of p.body ?? [])
26691
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(b)}`);
26692
+ const rows = displayRows2(choices);
26693
+ let cursorRow = 0;
26694
+ rows.forEach((r, ri) => {
26695
+ if (r.kind === "item" && r.index === cursor2)
26696
+ cursorRow = ri;
26697
+ });
26698
+ const { start, end } = viewport2(rows, cursorRow, WINDOW3);
26699
+ const above = rows.slice(0, start).filter((r) => r.kind === "item").length;
26700
+ const below = rows.slice(end).filter((r) => r.kind === "item").length;
26701
+ if (above > 0)
26702
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(`↑ ${above} more`)}`);
26703
+ const budget = Math.max(6, cols - nameCol - 10);
26704
+ for (let ri = start;ri < end; ri++) {
26705
+ const row = rows[ri];
26706
+ if (row.kind === "header") {
26707
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(row.text)}`);
26708
+ } else {
26709
+ lines.push(`${c2.dim(BAR2)} ${p.renderRow(row.index, row.index === cursor2, budget)}`);
26710
+ }
26711
+ }
26712
+ if (below > 0)
26713
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(`↓ ${below} more`)}`);
26714
+ const warn = p.warnLine?.();
26715
+ if (warn)
26716
+ lines.push(`${c2.dim(BAR2)} ${warn}`);
26717
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(p.footer)}`);
26718
+ return lines;
26719
+ };
26720
+ const collapse = (result) => {
26721
+ repaint2(stdout, region, [
26722
+ c2.dim(BAR2),
26723
+ `${c2.dim(STEP_DONE2)} ${p.message}`,
26724
+ `${c2.dim(BAR2)} ${c2.dim(p.summaryFor(result))}`
26725
+ ]);
26726
+ };
26727
+ return new Promise((resolve24) => {
26728
+ hideCursor2(stdout);
26729
+ repaint2(stdout, region, build());
26730
+ readline3.emitKeypressEvents(stdin);
26731
+ const wasRaw = stdin.isRaw;
26732
+ stdin.setRawMode?.(true);
26733
+ stdin.resume();
26734
+ const cleanup = () => {
26735
+ stdin.removeListener("keypress", onKey);
26736
+ stdin.setRawMode?.(wasRaw ?? false);
26737
+ stdin.pause();
26738
+ showCursor2(stdout);
26739
+ };
26740
+ const finish = (result) => {
26741
+ cleanup();
26742
+ collapse(result);
26743
+ resolve24(result);
26744
+ };
26745
+ function onKey(_s, key) {
26746
+ if (!key)
26747
+ return;
26748
+ if (key.ctrl && (key.name === "c" || key.name === "d") || key.name === "escape") {
26749
+ finish(null);
26750
+ } else if (key.name === "up") {
26751
+ cursor2 = cursor2 > 0 ? cursor2 - 1 : choices.length - 1;
26752
+ repaint2(stdout, region, build());
26753
+ } else if (key.name === "down") {
26754
+ cursor2 = cursor2 < choices.length - 1 ? cursor2 + 1 : 0;
26755
+ repaint2(stdout, region, build());
26756
+ } else {
26757
+ const outcome = p.onKey(key, cursor2);
26758
+ if (outcome === "redraw")
26759
+ repaint2(stdout, region, build());
26760
+ else if (outcome)
26761
+ finish(outcome.done);
26762
+ }
26763
+ }
26764
+ stdin.on("keypress", onKey);
26765
+ });
26766
+ }
26767
+ function selectOne2(opts) {
26768
+ const stdin = opts.stdin ?? process.stdin;
26769
+ const stdout = opts.stdout ?? process.stdout;
26770
+ const choices = opts.choices;
26771
+ if (!choices.length)
26772
+ return Promise.resolve(null);
26773
+ if (!stdin.isTTY || !stdout.isTTY) {
26774
+ return Promise.resolve(choices[0].value);
26775
+ }
26776
+ const c2 = paint2(colorsEnabled2(stdout));
26777
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26778
+ return runPrompt2({
26779
+ stdin,
26780
+ stdout,
26781
+ message: opts.message,
26782
+ c: c2,
26783
+ body: opts.body,
26784
+ choices,
26785
+ renderRow: (index, active, budget) => {
26786
+ const choice = choices[index];
26787
+ const dot = active ? c2.pink(RADIO_ON2) : c2.dim(RADIO_OFF2);
26788
+ const rawLabel = choice.label.padEnd(nameCol);
26789
+ const label = active ? c2.pinkBold(rawLabel) : rawLabel;
26790
+ const hint = choice.hint ? ` ${c2.dim(ellipsize2(choice.hint, budget))}` : "";
26791
+ return `${dot} ${label}${hint}`;
26792
+ },
26793
+ footer: "↑/↓ navigate · enter to select · esc to cancel",
26794
+ onKey: (key, cursor2) => key.name === "return" ? { done: choices[cursor2].value } : undefined,
26795
+ summaryFor: (value) => value === null ? "cancelled" : choices.find((ch) => ch.value === value)?.label ?? String(value)
26796
+ });
26797
+ }
26798
+ function multiSelect2(opts) {
26799
+ const stdin = opts.stdin ?? process.stdin;
26800
+ const stdout = opts.stdout ?? process.stdout;
26801
+ const choices = opts.choices;
26802
+ const minSelected = opts.minSelected ?? 0;
26803
+ const noun = opts.summaryNoun ?? "selected";
26804
+ const checked = choices.map((ch) => ch.locked ? ch.checked ?? true : !!ch.checked);
26805
+ if (!stdin.isTTY || !stdout.isTTY) {
26806
+ return Promise.resolve(choices.filter((_, i) => checked[i]).map((ch) => ch.value));
26807
+ }
26808
+ const c2 = paint2(colorsEnabled2(stdout));
26809
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26810
+ let warn = false;
26811
+ return runPrompt2({
26812
+ stdin,
26813
+ stdout,
26814
+ message: opts.message,
26815
+ c: c2,
26816
+ choices,
26817
+ renderRow: (index, active, budget) => {
26818
+ const choice = choices[index];
26819
+ const caret = active ? c2.pink(CARET2) : " ";
26820
+ const box = choice.locked ? checked[index] ? c2.guide(CHECK_ON2) : c2.dim(CHECK_OFF2) : checked[index] ? c2.pink(CHECK_ON2) : c2.dim(CHECK_OFF2);
26821
+ const rawLabel = choice.label.padEnd(nameCol);
26822
+ const label = active ? c2.pinkBold(rawLabel) : checked[index] ? rawLabel : c2.dim(rawLabel);
26823
+ const hint = choice.hint ? ` ${c2.dim(ellipsize2(choice.hint, budget))}` : "";
26824
+ return `${caret} ${box} ${label}${hint}`;
26825
+ },
26826
+ warnLine: () => warn ? c2.warn(`Select at least ${minSelected}.`) : null,
26827
+ footer: opts.hint ?? "↑/↓ move · space select · ctrl+a all · enter confirm",
26828
+ onKey: (key, cursor2) => {
26829
+ if (key.name === "space") {
26830
+ if (choices[cursor2]?.locked)
26831
+ return "redraw";
26832
+ checked[cursor2] = !checked[cursor2];
26833
+ warn = false;
26834
+ return "redraw";
26835
+ }
26836
+ if (key.ctrl && key.name === "a") {
26837
+ const allOn = choices.every((ch, i) => checked[i] || ch.locked);
26838
+ for (let i = 0;i < checked.length; i++) {
26839
+ checked[i] = choices[i]?.locked ? true : !allOn;
26840
+ }
26841
+ return "redraw";
26842
+ }
26843
+ if (key.name === "return") {
26844
+ const selected = choices.filter((_, i) => checked[i]).map((ch) => ch.value);
26845
+ const chosen = choices.filter((ch, i) => checked[i] && !ch.locked).length;
26846
+ if (chosen < minSelected) {
26847
+ warn = true;
26848
+ return "redraw";
26849
+ }
26850
+ return { done: selected };
26851
+ }
26852
+ return;
26853
+ },
26854
+ summaryFor: (values) => values === null ? "cancelled" : summarize2(choices.filter((ch) => values.includes(ch.value) && !ch.summaryExclude).map((ch) => ch.label), noun)
26855
+ });
26856
+ }
26857
+ function promptText2(opts) {
26858
+ const stdin = opts.stdin ?? process.stdin;
26859
+ const stdout = opts.stdout ?? process.stdout;
26860
+ const c2 = paint2(colorsEnabled2(stdout));
26861
+ const accept = (raw) => {
26862
+ const value = raw.trim() || opts.defaultValue || "";
26863
+ const error = opts.validate?.(value) ?? null;
26864
+ return error ? { ok: false, error } : { ok: true, value };
26865
+ };
26866
+ if (!stdin.isTTY) {
26867
+ return new Promise((resolve24) => {
26868
+ let buf = "";
26869
+ const onData = (chunk) => {
26870
+ buf += String(chunk);
26871
+ if (buf.includes(`
26872
+ `))
26873
+ done();
26874
+ };
26875
+ const done = () => {
26876
+ stdin.removeListener("data", onData);
26877
+ stdin.removeListener("end", done);
26878
+ const r = accept(buf.split(`
26879
+ `)[0] ?? "");
26880
+ resolve24(r.ok ? r.value : null);
26881
+ };
26882
+ stdin.on("data", onData);
26883
+ stdin.on("end", done);
26884
+ });
26885
+ }
26886
+ return new Promise((resolve24) => {
26887
+ let value = "";
26888
+ const draw = (error) => {
26889
+ const shown = opts.mask ? "•".repeat(value.length) : value;
26890
+ const hint = opts.hint ? ` ${c2.dim(opts.hint)}` : "";
26891
+ const err = error ? `
26892
+ ${c2.warn(error)}` : "";
26893
+ stdout.write(`\r\x1B[2K${c2.bold(opts.message)} ${shown}${hint}${err}`);
26894
+ if (err)
26895
+ stdout.write("\x1B[1A");
26896
+ };
26897
+ draw();
26898
+ readline3.emitKeypressEvents(stdin);
26899
+ const wasRaw = stdin.isRaw;
26900
+ stdin.setRawMode?.(true);
26901
+ stdin.resume();
26902
+ const cleanup = () => {
26903
+ stdin.removeListener("keypress", onKey);
26904
+ stdin.setRawMode?.(wasRaw ?? false);
26905
+ stdin.pause();
26906
+ stdout.write(`
26907
+ `);
26908
+ };
26909
+ function onKey(str2, key) {
26910
+ if (!key)
26911
+ return;
26912
+ if (key.ctrl && (key.name === "c" || key.name === "d") || key.name === "escape") {
26913
+ cleanup();
26914
+ resolve24(null);
26915
+ return;
26916
+ }
26917
+ if (key.name === "return" || key.name === "enter") {
26918
+ const r = accept(value);
26919
+ if (r.ok) {
26920
+ cleanup();
26921
+ resolve24(r.value);
26922
+ } else {
26923
+ draw(r.error);
26924
+ }
26925
+ return;
26926
+ }
26927
+ if (key.name === "backspace") {
26928
+ value = value.slice(0, -1);
26929
+ draw();
26930
+ return;
26931
+ }
26932
+ if (str2 && !key.ctrl && !key.meta) {
26933
+ value += str2;
26934
+ draw();
26935
+ }
26936
+ }
26937
+ stdin.on("keypress", onKey);
26938
+ });
26939
+ }
26940
+ var ESC2 = "\x1B", BAR2 = "│", BAR_END2 = "└", STEP_ACTIVE2 = "◆", STEP_DONE2 = "◇", RADIO_ON2 = "●", RADIO_OFF2 = "○", CHECK_ON2 = "◼", CHECK_OFF2 = "◻", CARET2 = "❯", FLOWER2 = "❋", HUES2, ANSI_RESET2, ANSI_BOLD2, ANSI_DIM2, LOGO_MIN_COLS2 = 22, TAGLINE2 = "end-to-end failure layer for ai agents", LOGO_GRID2, LOGO_TEAL2, LOGO_PINK2, WINDOW3 = 8;
26941
+ var init_tui2 = __esm(() => {
26942
+ HUES2 = {
26943
+ guide: { rgb: [102, 209, 181], basic: "36" },
26944
+ pink: { rgb: [255, 46, 136], basic: "95" },
26945
+ logoPink: { rgb: [228, 88, 124], basic: "95" },
26946
+ warn: { rgb: [227, 179, 65], basic: "33" },
26947
+ dim: { rgb: [107, 118, 132], basic: "2" }
26948
+ };
26949
+ ANSI_RESET2 = `${ESC2}[0m`;
26950
+ ANSI_BOLD2 = `${ESC2}[1m`;
26951
+ ANSI_DIM2 = `${ESC2}[2m`;
26952
+ LOGO_GRID2 = [
26953
+ "...tt........",
26954
+ "..tttt.......",
26955
+ ".tttttt..pppp",
26956
+ ".tttttt..pppp",
26957
+ "..tttt...pppp",
26958
+ "...tt....pppp",
26959
+ ".........pppp",
26960
+ ".........pppp",
26961
+ "..pppp...pppp",
26962
+ "..pppp...pppp",
26963
+ "..pppp...pppp",
26964
+ ".pppppp..pppp",
26965
+ ".pppppp..pppp",
26966
+ ".pppppp..pppp",
26967
+ ".pppppp..pppp",
26968
+ "..pppp...pppp",
26969
+ "..ppppppppppp",
26970
+ "..ppppppppppp",
26971
+ "..ppppppppppp",
26972
+ "..ppppppppppp"
26973
+ ];
26974
+ LOGO_TEAL2 = HUES2.guide.rgb;
26975
+ LOGO_PINK2 = HUES2.logoPink.rgb;
26976
+ });
26977
+
26978
+ // src/hooks/manager.ts
26979
+ var exports_manager = {};
26980
+ __export(exports_manager, {
26981
+ removeHooks: () => removeHooks2,
26982
+ listHooks: () => listHooks,
26983
+ installHooks: () => installHooks2,
26984
+ hooksInstalledInSettings: () => hooksInstalledInSettings2,
26985
+ getSettingsPath: () => getSettingsPath2
26986
+ });
26987
+ import { execSync as execSync7 } from "node:child_process";
26988
+ import { existsSync as existsSync32 } from "node:fs";
26989
+ import { resolve as resolve24, basename as basename6 } from "node:path";
26990
+ import { platform as platform5, arch as arch4, release as release3, hostname as hostname5 } from "node:os";
26991
+ function getSettingsPath2(scope, cwd) {
26992
+ return claudeCode.getSettingsPath(scope, cwd);
26993
+ }
26994
+ function scopeLabel2(scope) {
26995
+ switch (scope) {
26996
+ case "user":
26997
+ return `~/.claude/settings.json`;
26998
+ case "project":
26999
+ return `{cwd}/.claude/settings.json`;
27000
+ case "local":
27001
+ return `{cwd}/.claude/settings.local.json`;
27002
+ }
27003
+ }
27004
+ function resolveFailproofaiBinary2() {
27005
+ const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
27006
+ if (override && override.trim())
27007
+ return override.trim();
27008
+ try {
27009
+ const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
27010
+ const result = execSync7(cmd, { encoding: "utf8" }).trim();
27011
+ return result.split(`
27012
+ `)[0].trim();
27013
+ } catch {
27014
+ throw new CliError(`failproofai binary not found in PATH.
27015
+ ` + "Install it globally first: npm install -g failproofai");
27016
+ }
27017
+ }
27018
+ function validatePolicyNames2(names) {
27019
+ const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
27020
+ if (invalid.length > 0) {
27021
+ const validList = [...VALID_POLICY_NAMES2].join(", ");
27022
+ throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
27023
+ ` + `Valid policies: ${validList}`);
27024
+ }
27025
+ }
27026
+ function deduplicateScopes2(scopes, cwd) {
26050
27027
  const seen = new Set;
26051
27028
  return scopes.filter((s) => {
26052
27029
  const p = getSettingsPath2(s, cwd);
@@ -26119,7 +27096,7 @@ async function installHooksImpl2(policyNames, scope = "user", cwd, includeBeta =
26119
27096
  } else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
26120
27097
  const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve24(path3));
26121
27098
  const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve24(path3)).filter((path3) => {
26122
- if (existsSync31(path3))
27099
+ if (existsSync32(path3))
26123
27100
  return true;
26124
27101
  console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
26125
27102
  return false;
@@ -26255,7 +27232,7 @@ This file can be committed to git — no machine-specific paths.`);
26255
27232
  } catch {}
26256
27233
  }
26257
27234
  }
26258
- async function removeHooks(policyNames, scope = "user", cwd, opts) {
27235
+ async function removeHooks2(policyNames, scope = "user", cwd, opts) {
26259
27236
  const configScope = scope === "all" ? "user" : scope;
26260
27237
  const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
26261
27238
  if (opts?.removeCustomHooks) {
@@ -26311,7 +27288,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
26311
27288
  const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
26312
27289
  for (const s of scopesToRemove) {
26313
27290
  const settingsPaths = settingsPathsFor(integration, s, cwd);
26314
- const existing = settingsPaths.filter((p) => existsSync31(p));
27291
+ const existing = settingsPaths.filter((p) => existsSync32(p));
26315
27292
  if (existing.length === 0) {
26316
27293
  if (scope !== "all" && selectedClis.length === 1) {
26317
27294
  console.log("No settings file found. Nothing to remove.");
@@ -26512,7 +27489,7 @@ Failproof AI Hook Policies
26512
27489
  for (const path3 of explicitPaths) {
26513
27490
  const absPath = resolve24(findProjectConfigDir(cwd ?? process.cwd()), path3);
26514
27491
  console.log(` ${absPath}`);
26515
- if (!existsSync31(absPath)) {
27492
+ if (!existsSync32(absPath)) {
26516
27493
  console.log(` \x1B[31m✗ File not found: ${absPath}\x1B[0m`);
26517
27494
  continue;
26518
27495
  }
@@ -26609,7 +27586,7 @@ __export(exports_install_prompt, {
26609
27586
  promptPolicySelection: () => promptPolicySelection2,
26610
27587
  buildCliMenuOptions: () => buildCliMenuOptions
26611
27588
  });
26612
- import * as readline3 from "node:readline";
27589
+ import * as readline4 from "node:readline";
26613
27590
  async function resolveTargetClis(explicit, action = "install") {
26614
27591
  const detected = explicit && explicit.length > 0 ? [] : detectInstalledClis();
26615
27592
  const stdinIsTty = !!process.stdin.isTTY;
@@ -26716,13 +27693,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26716
27693
  let cursor2 = 0;
26717
27694
  let lastLineCount = 0;
26718
27695
  let cursorHidden = false;
26719
- function hideCursor2() {
27696
+ function hideCursor3() {
26720
27697
  if (!cursorHidden) {
26721
27698
  process.stdout.write("\x1B[?25l");
26722
27699
  cursorHidden = true;
26723
27700
  }
26724
27701
  }
26725
- function showCursor2() {
27702
+ function showCursor3() {
26726
27703
  if (cursorHidden) {
26727
27704
  process.stdout.write("\x1B[?25h");
26728
27705
  cursorHidden = false;
@@ -26732,7 +27709,7 @@ async function promptCliTargetSelection(detected, action = "install") {
26732
27709
  const heading = action === "uninstall" ? "Remove failproofai hooks" : "Install failproofai hooks";
26733
27710
  function render() {
26734
27711
  const cols = process.stdout.columns || 100;
26735
- hideCursor2();
27712
+ hideCursor3();
26736
27713
  const g = dim(BAR);
26737
27714
  const lines = [g, `${teal(STEP_ACTIVE)} ${bold(heading)}`];
26738
27715
  for (const row of buildDisplayRows()) {
@@ -26765,13 +27742,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26765
27742
  const itemCount = options.length;
26766
27743
  return new Promise((resolve25) => {
26767
27744
  render();
26768
- readline3.emitKeypressEvents(process.stdin);
27745
+ readline4.emitKeypressEvents(process.stdin);
26769
27746
  const wasRaw = process.stdin.isRaw;
26770
27747
  if (process.stdin.setRawMode)
26771
27748
  process.stdin.setRawMode(true);
26772
27749
  process.stdin.resume();
26773
27750
  function cleanup() {
26774
- showCursor2();
27751
+ showCursor3();
26775
27752
  process.stdin.removeListener("keypress", onKey);
26776
27753
  if (process.stdin.setRawMode)
26777
27754
  process.stdin.setRawMode(wasRaw ?? false);
@@ -26832,13 +27809,13 @@ async function promptPolicySelection2(preSelected, options = {}) {
26832
27809
  let search = "";
26833
27810
  let lastLineCount = 0;
26834
27811
  let cursorHidden = false;
26835
- function hideCursor2() {
27812
+ function hideCursor3() {
26836
27813
  if (!cursorHidden) {
26837
27814
  process.stdout.write("\x1B[?25l");
26838
27815
  cursorHidden = true;
26839
27816
  }
26840
27817
  }
26841
- function showCursor2() {
27818
+ function showCursor3() {
26842
27819
  if (cursorHidden) {
26843
27820
  process.stdout.write("\x1B[?25h");
26844
27821
  cursorHidden = false;
@@ -26885,7 +27862,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26885
27862
  }
26886
27863
  function render() {
26887
27864
  const cols = process.stdout.columns || 100;
26888
- hideCursor2();
27865
+ hideCursor3();
26889
27866
  const filtered = getFiltered();
26890
27867
  const shown = filtered.length;
26891
27868
  if (shown > 0 && cursor2 >= shown)
@@ -26904,10 +27881,10 @@ async function promptPolicySelection2(preSelected, options = {}) {
26904
27881
  for (let i = 0;i < WINDOW_SIZE + 1; i++)
26905
27882
  lines.push(g);
26906
27883
  } else {
26907
- const displayRows2 = buildDisplayRows(filtered);
27884
+ const displayRows3 = buildDisplayRows(filtered);
26908
27885
  let cursorDisplayRow = 0;
26909
- for (let i = 0;i < displayRows2.length; i++) {
26910
- const row = displayRows2[i];
27886
+ for (let i = 0;i < displayRows3.length; i++) {
27887
+ const row = displayRows3[i];
26911
27888
  if (row.kind === "item" && row.filteredIndex === cursor2) {
26912
27889
  cursorDisplayRow = i;
26913
27890
  break;
@@ -26915,12 +27892,12 @@ async function promptPolicySelection2(preSelected, options = {}) {
26915
27892
  }
26916
27893
  let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
26917
27894
  windowStart = Math.max(0, windowStart);
26918
- windowStart = Math.min(windowStart, Math.max(0, displayRows2.length - WINDOW_SIZE));
26919
- const windowEnd = Math.min(displayRows2.length, windowStart + WINDOW_SIZE);
26920
- const aboveItems = displayRows2.slice(0, windowStart).filter((r) => r.kind === "item").length;
27895
+ windowStart = Math.min(windowStart, Math.max(0, displayRows3.length - WINDOW_SIZE));
27896
+ const windowEnd = Math.min(displayRows3.length, windowStart + WINDOW_SIZE);
27897
+ const aboveItems = displayRows3.slice(0, windowStart).filter((r) => r.kind === "item").length;
26921
27898
  lines.push(aboveItems > 0 ? `${g} ${dim(`↑ ${aboveItems} more`)}` : g);
26922
27899
  for (let i = windowStart;i < windowEnd; i++) {
26923
- const row = displayRows2[i];
27900
+ const row = displayRows3[i];
26924
27901
  if (row.kind === "header") {
26925
27902
  lines.push(`${g} ${dim(row.category.toUpperCase())} ${dim(`${row.enabledCount}/${row.totalCount}`)}`);
26926
27903
  } else {
@@ -26937,7 +27914,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26937
27914
  }
26938
27915
  for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++)
26939
27916
  lines.push(g);
26940
- const belowItems = displayRows2.slice(windowEnd).filter((r) => r.kind === "item").length;
27917
+ const belowItems = displayRows3.slice(windowEnd).filter((r) => r.kind === "item").length;
26941
27918
  lines.push(belowItems > 0 ? `${g} ${dim(`↓ ${belowItems} more`)}` : g);
26942
27919
  }
26943
27920
  lines.push(g);
@@ -26953,7 +27930,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26953
27930
  render();
26954
27931
  process.stdin.setRawMode(true);
26955
27932
  process.stdin.resume();
26956
- readline3.emitKeypressEvents(process.stdin);
27933
+ readline4.emitKeypressEvents(process.stdin);
26957
27934
  function keypressHandler(_str, key) {
26958
27935
  if (!key)
26959
27936
  return;
@@ -27009,7 +27986,7 @@ ${dim(BAR)} ${dim(`${selected.length} selected`)}
27009
27986
  }
27010
27987
  }
27011
27988
  function cleanup() {
27012
- showCursor2();
27989
+ showCursor3();
27013
27990
  process.stdin.removeListener("keypress", keypressHandler);
27014
27991
  process.stdin.setRawMode(false);
27015
27992
  process.stdin.pause();
@@ -27647,7 +28624,7 @@ __export(exports_launch2, {
27647
28624
  launch: () => launch2
27648
28625
  });
27649
28626
  import { spawn as spawn3 } from "child_process";
27650
- import { realpathSync as realpathSync5, existsSync as existsSync32 } from "node:fs";
28627
+ import { realpathSync as realpathSync5, existsSync as existsSync33 } from "node:fs";
27651
28628
  import { resolve as resolve25, dirname as dirname16 } from "node:path";
27652
28629
  import { fileURLToPath as fileURLToPath3 } from "node:url";
27653
28630
  import { createInterface as createInterface2 } from "node:readline";
@@ -27674,7 +28651,7 @@ function launch2(mode) {
27674
28651
  cmd = "node";
27675
28652
  const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve25(dirname16(realpathSync5(fileURLToPath3(import.meta.url))), "..");
27676
28653
  const serverJsPath = resolve25(packageRoot, ".next/standalone/server.js");
27677
- if (!existsSync32(serverJsPath)) {
28654
+ if (!existsSync33(serverJsPath)) {
27678
28655
  let shadowMessage = null;
27679
28656
  try {
27680
28657
  const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
@@ -27774,7 +28751,7 @@ import { realpathSync as realpathSync6 } from "fs";
27774
28751
  import { dirname as dirname17, resolve as resolve26 } from "path";
27775
28752
  import { fileURLToPath as fileURLToPath4 } from "url";
27776
28753
  // package.json
27777
- var version = "1.0.0-beta.7";
28754
+ var version = "1.0.0-beta.8";
27778
28755
 
27779
28756
  // bin/failproofai.mjs
27780
28757
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -27872,7 +28849,7 @@ if (hookIdx >= 0) {
27872
28849
  }
27873
28850
  }
27874
28851
  async function runCli() {
27875
- const SUBCOMMANDS = ["policies", "policy", "audit", "config"];
28852
+ const SUBCOMMANDS = ["policies", "policy", "audit", "config", "uninstall"];
27876
28853
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
27877
28854
  const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
27878
28855
  if (extraArgs.length > 0) {
@@ -27918,6 +28895,16 @@ COMMANDS
27918
28895
  dashboard at http://localhost:8020/audit
27919
28896
  audit --help, -h Show this help for the audit command
27920
28897
 
28898
+ uninstall Remove failproofai from this machine: hook
28899
+ entries from every agent CLI, and the daemon
28900
+ service. Run this BEFORE \`npm rm -g failproofai\`
28901
+ \u2014 npm runs no uninstall script, so removing the
28902
+ package alone leaves both behind.
28903
+ --purge Also delete ~/.failproofai (settings,
28904
+ credentials, audit history, daemon binary)
28905
+ --dry-run Show what would be removed, change nothing
28906
+ --yes, -y Skip the confirmation prompt
28907
+
27921
28908
  --version, -v Print version and exit
27922
28909
  --help, -h Show this help message
27923
28910
 
@@ -27995,6 +28982,84 @@ Run \`failproofai --help\` for usage.`);
27995
28982
  await maybeFirstRunConfigure2({}, { postSetupAudit: args[0] !== "audit", force: layoutWasReset });
27996
28983
  } catch {}
27997
28984
  }
28985
+ if (args[0] === "uninstall") {
28986
+ const subArgs = args.slice(1);
28987
+ if (subArgs.includes("--help") || subArgs.includes("-h")) {
28988
+ console.log(`
28989
+ failproofai uninstall \u2014 remove failproofai from this machine
28990
+
28991
+ USAGE
28992
+ failproofai uninstall [--purge] [--dry-run] [--yes]
28993
+
28994
+ WHAT IT REMOVES
28995
+ \u2022 failproofai hook entries from every agent CLI that has them
28996
+ \u2022 the failproofaid daemon service (needs sudo)
28997
+ \u2022 the "require the daemon" flag \u2014 cleared FIRST, so a partial uninstall can
28998
+ never leave this machine denying every tool call
28999
+
29000
+ OPTIONS
29001
+ --purge Also delete ~/.failproofai \u2014 settings, credentials, audit
29002
+ history and the downloaded daemon binary. Off by default so a
29003
+ reinstall keeps your history.
29004
+ --dry-run Print what would be removed and change nothing.
29005
+ --yes, -y Skip the confirmation prompt. Required when there is no TTY.
29006
+
29007
+ WHY THIS EXISTS
29008
+ npm runs no uninstall script, so \`npm rm -g failproofai\` removes the package
29009
+ and leaves the hook entries and the service behind. Run this first, then:
29010
+ npm rm -g failproofai
29011
+ `);
29012
+ process.exit(0);
29013
+ }
29014
+ const KNOWN = new Set(["--purge", "--dry-run", "--yes", "-y"]);
29015
+ const unknown = subArgs.find((a) => !KNOWN.has(a));
29016
+ if (unknown) {
29017
+ throw new CliError3(`Unexpected argument: ${unknown}
29018
+ Run \`failproofai uninstall --help\` for usage.`);
29019
+ }
29020
+ lastSubcommand = "uninstall_command";
29021
+ const { runUninstallCommand: runUninstallCommand2 } = await Promise.resolve().then(() => (init_uninstall_cli(), exports_uninstall_cli));
29022
+ const purge = subArgs.includes("--purge");
29023
+ const dryRun = subArgs.includes("--dry-run");
29024
+ const yes = subArgs.includes("--yes") || subArgs.includes("-y");
29025
+ let planWasShown = false;
29026
+ const result = await runUninstallCommand2({
29027
+ purge,
29028
+ dryRun,
29029
+ yes,
29030
+ cwd: process.cwd(),
29031
+ confirm: process.stdin.isTTY ? async (planLines) => {
29032
+ planWasShown = true;
29033
+ const { selectOne: selectOne3 } = await Promise.resolve().then(() => (init_tui2(), exports_tui));
29034
+ const answer = await selectOne3({
29035
+ message: purge ? "Remove failproofai and DELETE ~/.failproofai?" : "Remove failproofai from this machine?",
29036
+ body: planLines,
29037
+ choices: [
29038
+ { label: "No, cancel", value: false },
29039
+ { label: purge ? "Yes, remove and purge" : "Yes, remove it", value: true }
29040
+ ]
29041
+ });
29042
+ return answer === true;
29043
+ } : undefined
29044
+ });
29045
+ const skip = planWasShown ? result.planLines : 0;
29046
+ for (const line of result.lines.slice(skip)) {
29047
+ if (result.exitCode === 0)
29048
+ console.log(line);
29049
+ else
29050
+ console.error(line);
29051
+ }
29052
+ if (!result.purged) {
29053
+ await track("cli_uninstall_command", {
29054
+ ok: result.exitCode === 0,
29055
+ purge,
29056
+ dry_run: dryRun
29057
+ });
29058
+ }
29059
+ lastSubcommand = null;
29060
+ await exitAfterFlush(result.exitCode);
29061
+ return;
29062
+ }
27998
29063
  if (args[0] === "policies") {
27999
29064
  const subArgs = args.slice(1);
28000
29065
  const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
@@ -28124,7 +29189,7 @@ Run \`failproofai policies --help\` for usage.`);
28124
29189
  }
28125
29190
  if (isUninstall) {
28126
29191
  lastSubcommand = "uninstall";
28127
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
29192
+ const { removeHooks: removeHooks3 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28128
29193
  const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
28129
29194
  const scopeIdx = subArgs.indexOf("--scope");
28130
29195
  const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
@@ -28169,7 +29234,7 @@ Run \`failproofai policies --help\` for usage.`);
28169
29234
  }
28170
29235
  const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
28171
29236
  const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "uninstall");
28172
- await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
29237
+ await removeHooks3(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
28173
29238
  await track("cli_uninstall_success", {
28174
29239
  scope,
28175
29240
  cli,
@@ -28301,8 +29366,8 @@ For multiple policies use \`failproofai policies --${action === "add" ? "install
28301
29366
  include_beta: includeBeta
28302
29367
  });
28303
29368
  } else {
28304
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28305
- await removeHooks2([policyName], scope, undefined, { betaOnly: false, removeCustomHooks: false, cli });
29369
+ const { removeHooks: removeHooks3 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
29370
+ await removeHooks3([policyName], scope, undefined, { betaOnly: false, removeCustomHooks: false, cli });
28306
29371
  await track("cli_policy_remove_success", {
28307
29372
  scope,
28308
29373
  cli,