failproofai 1.0.0-beta.6 → 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 (125) 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]__1e7-pa7._.js → [root-of-the-server]__0ommekk._.js} +2 -2
  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]__1_a_gwn._.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/{_0igtyj5._.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/02vvywmww22zz.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{34fa65k0cldkj.js → 0_-focvo4cuiv.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{3f_939wi3kfnx.js → 1fwh-2un_l-eo.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{3p1vpm2_-tfwm.js → 1zrdsxe91qq6i.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/{0_2n13i94rnw5.js → 2047vah_v4emu.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{35v07j9gc1pvr.js → 23hl78dzffun4.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/2bw0stk-s-1dd.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{3rbpw_waieafm.js → 2r_j5wrkbu5wm.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/{23557z4e6dtsr.js → 3m8onpqrjxzu5.js} +1 -1
  103. package/.next/standalone/.next/static/chunks/{28zkwzu50puki.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 +143 -27
  107. package/dist/cli.mjs +1288 -118
  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 +70 -6
  113. package/src/hooks/daemon-client.ts +35 -0
  114. package/src/hooks/daemon-service.ts +237 -24
  115. package/src/hooks/first-run-gate.ts +9 -1
  116. package/src/hooks/fp-home.ts +13 -0
  117. package/src/hooks/fp-reset.ts +25 -0
  118. package/src/hooks/onboarding-attempt.ts +179 -0
  119. package/src/hooks/uninstall-cli.ts +345 -0
  120. package/src/hooks/worker-server.ts +12 -2
  121. package/.next/standalone/.next/static/chunks/3081gykdubv8b.js +0 -1
  122. package/.next/standalone/.next/static/chunks/40ffnfpkeyvb0.js +0 -1
  123. /package/.next/standalone/.next/static/{Ptn8aBQl_cnNgOd0ebyv0 → gOR8i03AeQm1VnIayDHVV}/_buildManifest.js +0 -0
  124. /package/.next/standalone/.next/static/{Ptn8aBQl_cnNgOd0ebyv0 → gOR8i03AeQm1VnIayDHVV}/_clientMiddlewareManifest.js +0 -0
  125. /package/.next/standalone/.next/static/{Ptn8aBQl_cnNgOd0ebyv0 → 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.6";
22
+ var version2 = "1.0.0-beta.8";
23
23
  var init_package = () => {};
24
24
 
25
25
  // src/posthog-key.ts
@@ -1025,7 +1025,7 @@ function resettablePaths() {
1025
1025
  logsDir()
1026
1026
  ];
1027
1027
  }
1028
- var LAYOUT_VERSION = 2, at = (...parts) => resolve(failproofaiHome(), ...parts), atHome = (home, ...parts) => home ? resolve(failproofaiHome(home), ...parts) : at(...parts), versionFile = (home) => atHome(home, "VERSION"), configFile = (home) => atHome(home, "config.toml"), credentialsFile = (home) => atHome(home, "credentials.toml"), binDir = (home) => atHome(home, "bin"), policiesDir = (home) => atHome(home, "policies"), localPoliciesDir = (home) => atHome(home, "policies", "local-policies"), globalPolicyConfigFile = (home) => resolve(localPoliciesDir(home), "policies-config.json"), cloudPoliciesDir = (home) => resolve(policiesDir(home), "cloud-policies"), customPoliciesDir = (home) => resolve(policiesDir(home), "custom-policies"), customAgentsDir = (home) => atHome(home, "custom-agents"), auditDir = (home) => atHome(home, "audit"), auditDashboardFile = (home) => resolve(auditDir(home), "dashboard.json"), auditCacheDir = (home) => resolve(auditDir(home), "cache"), hookActivityDir = (home) => atHome(home, "hook-activity"), runDir = (home) => atHome(home, "run"), daemonSocket = () => process.env.FAILPROOFAI_DAEMON_SOCKET || resolve(runDir(), "failproofaid.sock"), auditLockFile = (home) => resolve(runDir(home), "audit.lock"), stateDir = (home) => atHome(home, "state"), shimsDir = (home) => resolve(stateDir(home), "shims"), sessionPauseDir = () => resolve(stateDir(), "sessions"), telemetryIdFile = (home) => resolve(stateDir(home), "telemetry-id"), launcherMarker = (home) => resolve(stateDir(home), "launcher-configured"), lastVersionFile = (home) => resolve(stateDir(home), "last-version"), onboardingLockFile = (home) => resolve(stateDir(home), "onboarding.lock"), codexSessionPathsFile = (home) => resolve(stateDir(home), "codex-session-paths.json"), logsDir = (home) => atHome(home, "logs"), legacy;
1028
+ var LAYOUT_VERSION = 2, at = (...parts) => resolve(failproofaiHome(), ...parts), atHome = (home, ...parts) => home ? resolve(failproofaiHome(home), ...parts) : at(...parts), versionFile = (home) => atHome(home, "VERSION"), configFile = (home) => atHome(home, "config.toml"), credentialsFile = (home) => atHome(home, "credentials.toml"), binDir = (home) => atHome(home, "bin"), policiesDir = (home) => atHome(home, "policies"), localPoliciesDir = (home) => atHome(home, "policies", "local-policies"), globalPolicyConfigFile = (home) => resolve(localPoliciesDir(home), "policies-config.json"), cloudPoliciesDir = (home) => resolve(policiesDir(home), "cloud-policies"), customPoliciesDir = (home) => resolve(policiesDir(home), "custom-policies"), customAgentsDir = (home) => atHome(home, "custom-agents"), auditDir = (home) => atHome(home, "audit"), auditDashboardFile = (home) => resolve(auditDir(home), "dashboard.json"), auditCacheDir = (home) => resolve(auditDir(home), "cache"), hookActivityDir = (home) => atHome(home, "hook-activity"), runDir = (home) => atHome(home, "run"), daemonSocket = () => process.env.FAILPROOFAI_DAEMON_SOCKET || resolve(runDir(), "failproofaid.sock"), auditLockFile = (home) => resolve(runDir(home), "audit.lock"), stateDir = (home) => atHome(home, "state"), shimsDir = (home) => resolve(stateDir(home), "shims"), sessionPauseDir = () => resolve(stateDir(), "sessions"), telemetryIdFile = (home) => resolve(stateDir(home), "telemetry-id"), launcherMarker = (home) => resolve(stateDir(home), "launcher-configured"), onboardingAttemptFile = (home) => resolve(stateDir(home), "onboarding-attempt.json"), lastVersionFile = (home) => resolve(stateDir(home), "last-version"), onboardingLockFile = (home) => resolve(stateDir(home), "onboarding.lock"), codexSessionPathsFile = (home) => resolve(stateDir(home), "codex-session-paths.json"), logsDir = (home) => atHome(home, "logs"), legacy;
1029
1029
  var init_fp_home = __esm(() => {
1030
1030
  legacy = {
1031
1031
  policyConfig: () => at("policies-config.json"),
@@ -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();
@@ -22438,8 +22731,87 @@ var init_setup_state = __esm(() => {
22438
22731
  init_fp_home();
22439
22732
  });
22440
22733
 
22734
+ // src/hooks/onboarding-attempt.ts
22735
+ import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
22736
+ import { dirname as dirname12 } from "node:path";
22737
+ function readOnboardingAttempt() {
22738
+ const path3 = onboardingAttemptFile();
22739
+ if (!existsSync24(path3))
22740
+ return null;
22741
+ try {
22742
+ const raw = JSON.parse(readFileSync19(path3, "utf8"));
22743
+ if (raw.schemaVersion !== SCHEMA_VERSION3)
22744
+ return null;
22745
+ if (typeof raw.reason !== "string" || typeof raw.cliVersion !== "string")
22746
+ return null;
22747
+ return {
22748
+ schemaVersion: SCHEMA_VERSION3,
22749
+ reason: raw.reason,
22750
+ cliVersion: raw.cliVersion,
22751
+ daemonStatus: typeof raw.daemonStatus === "string" ? raw.daemonStatus : "",
22752
+ at: typeof raw.at === "number" ? raw.at : 0
22753
+ };
22754
+ } catch {
22755
+ return null;
22756
+ }
22757
+ }
22758
+ function recordOnboardingAttempt(reason, cliVersion, daemonStatus, now = Date.now()) {
22759
+ try {
22760
+ const path3 = onboardingAttemptFile();
22761
+ mkdirSync14(dirname12(path3), { recursive: true });
22762
+ const record = {
22763
+ schemaVersion: SCHEMA_VERSION3,
22764
+ reason,
22765
+ cliVersion,
22766
+ daemonStatus,
22767
+ at: now
22768
+ };
22769
+ writeFileSync11(path3, JSON.stringify(record, null, 2), "utf8");
22770
+ } catch {}
22771
+ }
22772
+ function clearOnboardingAttempt() {
22773
+ try {
22774
+ rmSync8(onboardingAttemptFile(), { force: true });
22775
+ } catch {}
22776
+ }
22777
+ function blockerCleared(attempt, probe) {
22778
+ switch (attempt.reason) {
22779
+ case "needs_root":
22780
+ return probe.canElevate();
22781
+ case "daemon_failed":
22782
+ return probe.daemonStatus() !== attempt.daemonStatus || probe.canElevate();
22783
+ case "cancelled":
22784
+ return probe.cliVersion !== attempt.cliVersion;
22785
+ case "not_a_tty":
22786
+ case "running_as_sudo":
22787
+ return true;
22788
+ default:
22789
+ return true;
22790
+ }
22791
+ }
22792
+ function attemptHintLines(attempt) {
22793
+ const why = {
22794
+ needs_root: "it needs root to install the failproofaid service",
22795
+ daemon_failed: "the failproofaid service could not be started",
22796
+ cancelled: "it was cancelled",
22797
+ not_a_tty: "there was no terminal to ask in",
22798
+ running_as_sudo: "it was run under sudo"
22799
+ };
22800
+ const detail = why[attempt.reason] ?? "it did not finish";
22801
+ return [
22802
+ ``,
22803
+ `[failproofai] Setup is not finished — ${detail}.`,
22804
+ ` Run \`failproofai config\` when you are ready.`,
22805
+ ``
22806
+ ];
22807
+ }
22808
+ var SCHEMA_VERSION3 = 1;
22809
+ var init_onboarding_attempt = __esm(() => {
22810
+ init_fp_home();
22811
+ });
22812
+
22441
22813
  // src/hooks/onboarding-lock.ts
22442
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
22814
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync20, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
22443
22815
  import { homedir as homedir26 } from "node:os";
22444
22816
  function onboardingLockPath(home = homedir26()) {
22445
22817
  return onboardingLockFile(home);
@@ -22456,7 +22828,7 @@ function processAlive(pid) {
22456
22828
  }
22457
22829
  function readLock(path3) {
22458
22830
  try {
22459
- const parsed = JSON.parse(readFileSync19(path3, "utf8"));
22831
+ const parsed = JSON.parse(readFileSync20(path3, "utf8"));
22460
22832
  if (typeof parsed.pid !== "number")
22461
22833
  return null;
22462
22834
  return { pid: parsed.pid, startedAt: Number(parsed.startedAt) || 0 };
@@ -22470,9 +22842,9 @@ function acquireOnboardingLock(home = homedir26()) {
22470
22842
  const held = readLock(path3);
22471
22843
  if (held && held.pid !== process.pid && processAlive(held.pid))
22472
22844
  return null;
22473
- mkdirSync14(stateDir(home), { recursive: true });
22845
+ mkdirSync15(stateDir(home), { recursive: true });
22474
22846
  const body = { pid: process.pid, startedAt: Date.now() };
22475
- writeFileSync11(path3, JSON.stringify(body), "utf8");
22847
+ writeFileSync12(path3, JSON.stringify(body), "utf8");
22476
22848
  let released = false;
22477
22849
  return {
22478
22850
  release() {
@@ -22482,7 +22854,7 @@ function acquireOnboardingLock(home = homedir26()) {
22482
22854
  try {
22483
22855
  const current = readLock(path3);
22484
22856
  if (!current || current.pid === process.pid)
22485
- rmSync8(path3, { force: true });
22857
+ rmSync9(path3, { force: true });
22486
22858
  } catch {}
22487
22859
  }
22488
22860
  };
@@ -22495,7 +22867,7 @@ var init_onboarding_lock = __esm(() => {
22495
22867
  });
22496
22868
 
22497
22869
  // lib/claude-sessions.ts
22498
- import { readdirSync as readdirSync14, statSync as statSync16, existsSync as existsSync25 } from "node:fs";
22870
+ import { readdirSync as readdirSync14, statSync as statSync16, existsSync as existsSync26 } from "node:fs";
22499
22871
  import { join as join28, basename as basename5 } from "node:path";
22500
22872
  function getClaudeProjectsRoot() {
22501
22873
  return getClaudeProjectsPath();
@@ -22542,7 +22914,7 @@ function listClaudeTranscripts(project) {
22542
22914
  } catch {}
22543
22915
  } else if (entry.isDirectory() && UUID_RE2.test(entry.name)) {
22544
22916
  const subDir = join28(project.path, entry.name, "subagents");
22545
- if (!existsSync25(subDir))
22917
+ if (!existsSync26(subDir))
22546
22918
  continue;
22547
22919
  let subEntries;
22548
22920
  try {
@@ -23817,7 +24189,7 @@ var init_features = __esm(() => {
23817
24189
 
23818
24190
  // src/audit/cache.ts
23819
24191
  import { createHash as createHash6 } from "node:crypto";
23820
- import { existsSync as existsSync26, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync12, chmodSync as chmodSync4 } from "node:fs";
24192
+ import { existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync21, writeFileSync as writeFileSync13, chmodSync as chmodSync4 } from "node:fs";
23821
24193
  import { join as join29 } from "node:path";
23822
24194
  function getEngineVersion() {
23823
24195
  if (cachedEngineVersion)
@@ -23844,10 +24216,10 @@ function readCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes) {
23844
24216
  if (sizeBytes === 0)
23845
24217
  return null;
23846
24218
  const cachePath = getCachePathFor(transcriptPath);
23847
- if (!existsSync26(cachePath))
24219
+ if (!existsSync27(cachePath))
23848
24220
  return null;
23849
24221
  try {
23850
- const raw = readFileSync20(cachePath, "utf-8");
24222
+ const raw = readFileSync21(cachePath, "utf-8");
23851
24223
  const entry = JSON.parse(raw);
23852
24224
  if (entry.schemaVersion !== CACHE_SCHEMA_VERSION)
23853
24225
  return null;
@@ -23871,7 +24243,7 @@ function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result)
23871
24243
  return;
23872
24244
  const cachePath = getCachePathFor(transcriptPath);
23873
24245
  try {
23874
- mkdirSync15(auditCacheDir(), { recursive: true });
24246
+ mkdirSync16(auditCacheDir(), { recursive: true });
23875
24247
  const entry = {
23876
24248
  schemaVersion: CACHE_SCHEMA_VERSION,
23877
24249
  cachedAt: Date.now(),
@@ -23881,7 +24253,7 @@ function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result)
23881
24253
  detectorVersion: getDetectorVersion(),
23882
24254
  result
23883
24255
  };
23884
- writeFileSync12(cachePath, JSON.stringify(entry), { encoding: "utf-8", mode: 384 });
24256
+ writeFileSync13(cachePath, JSON.stringify(entry), { encoding: "utf-8", mode: 384 });
23885
24257
  try {
23886
24258
  chmodSync4(cachePath, 384);
23887
24259
  } catch {}
@@ -24247,11 +24619,11 @@ var init_audit = __esm(() => {
24247
24619
  import {
24248
24620
  closeSync,
24249
24621
  linkSync,
24250
- mkdirSync as mkdirSync16,
24622
+ mkdirSync as mkdirSync17,
24251
24623
  openSync,
24252
- readFileSync as readFileSync21,
24624
+ readFileSync as readFileSync22,
24253
24625
  unlinkSync as unlinkSync4,
24254
- writeFileSync as writeFileSync13,
24626
+ writeFileSync as writeFileSync14,
24255
24627
  writeSync
24256
24628
  } from "node:fs";
24257
24629
  function acquireAuditLock(source, now = Date.now()) {
@@ -24310,7 +24682,7 @@ function handleFor(path3, info) {
24310
24682
  }
24311
24683
  function readLock2(path3) {
24312
24684
  try {
24313
- const parsed = JSON.parse(readFileSync21(path3, "utf8"));
24685
+ const parsed = JSON.parse(readFileSync22(path3, "utf8"));
24314
24686
  if (typeof parsed?.pid !== "number" || typeof parsed?.startedAt !== "number")
24315
24687
  return null;
24316
24688
  return {
@@ -24326,7 +24698,7 @@ function writeLockExclusive(path3, info) {
24326
24698
  const body = JSON.stringify(info);
24327
24699
  const staging = `${path3}.${process.pid}.tmp`;
24328
24700
  try {
24329
- writeFileSync13(staging, body, { mode: 384 });
24701
+ writeFileSync14(staging, body, { mode: 384 });
24330
24702
  } catch {
24331
24703
  return false;
24332
24704
  }
@@ -24360,8 +24732,8 @@ function createThenWrite(path3, body) {
24360
24732
  }
24361
24733
  }
24362
24734
  function ensureRunDir() {
24363
- mkdirSync16(failproofaiHome(), { recursive: true });
24364
- mkdirSync16(runDir(), { recursive: true, mode: 448 });
24735
+ mkdirSync17(failproofaiHome(), { recursive: true });
24736
+ mkdirSync17(runDir(), { recursive: true, mode: 448 });
24365
24737
  }
24366
24738
  var AUDIT_LOCK_MAX_AGE_MS;
24367
24739
  var init_audit_lock = __esm(() => {
@@ -24525,26 +24897,26 @@ function parseScriptArgs(argv) {
24525
24897
  var init_parse_script_args = () => {};
24526
24898
 
24527
24899
  // scripts/install-diagnosis.mjs
24528
- import { existsSync as existsSync27, readFileSync as readFileSync22, realpathSync as realpathSync3 } from "node:fs";
24529
- import { dirname as dirname12, resolve as resolve21 } from "node:path";
24900
+ import { existsSync as existsSync28, readFileSync as readFileSync23, realpathSync as realpathSync3 } from "node:fs";
24901
+ import { dirname as dirname13, resolve as resolve21 } from "node:path";
24530
24902
  import { homedir as homedir27, platform as platform4 } from "node:os";
24531
24903
  import { spawnSync } from "node:child_process";
24532
24904
  function findPackageRoot(start) {
24533
24905
  try {
24534
24906
  let dir = realpathSync3(start);
24535
- if (existsSync27(dir) && !existsSync27(resolve21(dir, "package.json"))) {
24536
- dir = dirname12(dir);
24907
+ if (existsSync28(dir) && !existsSync28(resolve21(dir, "package.json"))) {
24908
+ dir = dirname13(dir);
24537
24909
  }
24538
24910
  while (true) {
24539
24911
  const pkgPath = resolve21(dir, "package.json");
24540
- if (existsSync27(pkgPath)) {
24912
+ if (existsSync28(pkgPath)) {
24541
24913
  try {
24542
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
24914
+ const pkg = JSON.parse(readFileSync23(pkgPath, "utf8"));
24543
24915
  if (pkg.name === PKG_NAME)
24544
24916
  return dir;
24545
24917
  } catch {}
24546
24918
  }
24547
- const parent = dirname12(dir);
24919
+ const parent = dirname13(dir);
24548
24920
  if (parent === dir)
24549
24921
  return null;
24550
24922
  dir = parent;
@@ -24557,7 +24929,7 @@ function readPackageVersion(packageRoot) {
24557
24929
  if (!packageRoot)
24558
24930
  return null;
24559
24931
  try {
24560
- const pkg = JSON.parse(readFileSync22(resolve21(packageRoot, "package.json"), "utf8"));
24932
+ const pkg = JSON.parse(readFileSync23(resolve21(packageRoot, "package.json"), "utf8"));
24561
24933
  return typeof pkg.version === "string" ? pkg.version : null;
24562
24934
  } catch {
24563
24935
  return null;
@@ -24584,7 +24956,7 @@ function locateNpmGlobal() {
24584
24956
  if (!root)
24585
24957
  return null;
24586
24958
  const candidate = resolve21(root, PKG_NAME);
24587
- return existsSync27(resolve21(candidate, "package.json")) ? candidate : null;
24959
+ return existsSync28(resolve21(candidate, "package.json")) ? candidate : null;
24588
24960
  } catch {
24589
24961
  return null;
24590
24962
  }
@@ -24592,7 +24964,7 @@ function locateNpmGlobal() {
24592
24964
  function locateBunGlobal() {
24593
24965
  try {
24594
24966
  const candidate = resolve21(homedir27(), ".bun", "install", "global", "node_modules", PKG_NAME);
24595
- return existsSync27(resolve21(candidate, "package.json")) ? candidate : null;
24967
+ return existsSync28(resolve21(candidate, "package.json")) ? candidate : null;
24596
24968
  } catch {
24597
24969
  return null;
24598
24970
  }
@@ -24699,8 +25071,8 @@ __export(exports_launch, {
24699
25071
  launch: () => launch
24700
25072
  });
24701
25073
  import { spawn as spawn2 } from "child_process";
24702
- import { realpathSync as realpathSync4, existsSync as existsSync28 } from "node:fs";
24703
- import { resolve as resolve22, dirname as dirname13 } from "node:path";
25074
+ import { realpathSync as realpathSync4, existsSync as existsSync29 } from "node:fs";
25075
+ import { resolve as resolve22, dirname as dirname14 } from "node:path";
24704
25076
  import { fileURLToPath as fileURLToPath2 } from "node:url";
24705
25077
  import { createInterface } from "node:readline";
24706
25078
  function launch(mode) {
@@ -24724,9 +25096,9 @@ function launch(mode) {
24724
25096
  process.env.PORT = port;
24725
25097
  process.env.HOSTNAME = bindHost;
24726
25098
  cmd = "node";
24727
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve22(dirname13(realpathSync4(fileURLToPath2(import.meta.url))), "..");
25099
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve22(dirname14(realpathSync4(fileURLToPath2(import.meta.url))), "..");
24728
25100
  const serverJsPath = resolve22(packageRoot, ".next/standalone/server.js");
24729
- if (!existsSync28(serverJsPath)) {
25101
+ if (!existsSync29(serverJsPath)) {
24730
25102
  let shadowMessage = null;
24731
25103
  try {
24732
25104
  const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
@@ -25171,9 +25543,9 @@ __export(exports_configure_wizard, {
25171
25543
  buildPresetChoices: () => buildPresetChoices,
25172
25544
  buildAgentChoices: () => buildAgentChoices
25173
25545
  });
25174
- import { existsSync as existsSync29, mkdirSync as mkdirSync17, writeFileSync as writeFileSync14, readFileSync as readFileSync23 } from "node:fs";
25546
+ import { existsSync as existsSync30, mkdirSync as mkdirSync18, writeFileSync as writeFileSync15, readFileSync as readFileSync24 } from "node:fs";
25175
25547
  import { homedir as homedir28 } from "node:os";
25176
- import { dirname as dirname14, resolve as resolve23, sep as sep5 } from "node:path";
25548
+ import { dirname as dirname15, resolve as resolve23, sep as sep5 } from "node:path";
25177
25549
  async function emit(event, props) {
25178
25550
  try {
25179
25551
  await trackHookEvent2(getInstanceId2(), event, props);
@@ -25280,8 +25652,8 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
25280
25652
  const path3 = getConfigPathForScope(scope, cwd);
25281
25653
  let config = {};
25282
25654
  try {
25283
- if (existsSync29(path3))
25284
- config = JSON.parse(readFileSync23(path3, "utf8"));
25655
+ if (existsSync30(path3))
25656
+ config = JSON.parse(readFileSync24(path3, "utf8"));
25285
25657
  } catch {
25286
25658
  return;
25287
25659
  }
@@ -25290,7 +25662,7 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
25290
25662
  else
25291
25663
  config.customPoliciesEnabled = false;
25292
25664
  try {
25293
- writeFileSync14(path3, JSON.stringify(config, null, 2) + `
25665
+ writeFileSync15(path3, JSON.stringify(config, null, 2) + `
25294
25666
  `, "utf8");
25295
25667
  } catch {}
25296
25668
  }
@@ -25390,14 +25762,33 @@ function firstRunMarkerPath() {
25390
25762
  return launcherMarker();
25391
25763
  }
25392
25764
  function hasSeenLauncher() {
25393
- return existsSync29(firstRunMarkerPath());
25765
+ return existsSync30(firstRunMarkerPath());
25394
25766
  }
25395
25767
  function markLauncherSeen() {
25396
25768
  try {
25397
- mkdirSync17(dirname14(firstRunMarkerPath()), { recursive: true });
25398
- writeFileSync14(firstRunMarkerPath(), "1", "utf8");
25769
+ mkdirSync18(dirname15(firstRunMarkerPath()), { recursive: true });
25770
+ writeFileSync15(firstRunMarkerPath(), "1", "utf8");
25399
25771
  } catch {}
25400
25772
  }
25773
+ function retryProbe() {
25774
+ return {
25775
+ canElevate: () => {
25776
+ try {
25777
+ return canElevate();
25778
+ } catch {
25779
+ return false;
25780
+ }
25781
+ },
25782
+ daemonStatus: () => {
25783
+ try {
25784
+ return daemonServiceStatus();
25785
+ } catch {
25786
+ return "";
25787
+ }
25788
+ },
25789
+ cliVersion: version2
25790
+ };
25791
+ }
25401
25792
  async function maybeFirstRunConfigure(io = {}, opts = {}) {
25402
25793
  if (process.env.FAILPROOFAI_NO_FIRST_RUN === "1")
25403
25794
  return false;
@@ -25409,6 +25800,13 @@ async function maybeFirstRunConfigure(io = {}, opts = {}) {
25409
25800
  markLauncherSeen();
25410
25801
  return false;
25411
25802
  }
25803
+ const attempt = opts.force ? null : readOnboardingAttempt();
25804
+ if (attempt && !blockerCleared(attempt, retryProbe())) {
25805
+ for (const line of attemptHintLines(attempt))
25806
+ stdout.write(`${line}
25807
+ `);
25808
+ return false;
25809
+ }
25412
25810
  if (!stdin.isTTY || !stdout.isTTY) {
25413
25811
  stdout.write(`
25414
25812
  [failproofai] Not set up yet — run \`failproofai config\` to get started.
@@ -25427,6 +25825,9 @@ async function maybeFirstRunConfigure(io = {}, opts = {}) {
25427
25825
  try {
25428
25826
  emit("first_run_configure_shown", {});
25429
25827
  const result = await runConfigureWizard(io);
25828
+ if (!result.applied && result.abort) {
25829
+ recordOnboardingAttempt(result.abort, version2, daemonServiceStatus());
25830
+ }
25430
25831
  if (result.applied && opts.postSetupAudit !== false) {
25431
25832
  try {
25432
25833
  const { runPostSetupAudit: runPostSetupAudit2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
@@ -25754,18 +26155,22 @@ Setup stopped before changing anything. Once that is fixed, re-run:
25754
26155
  outro("Nothing was changed.", { ok: false }, stdout);
25755
26156
  return { applied: false, abort: "daemon_failed" };
25756
26157
  }
25757
- if (!await probeDaemonEndToEnd()) {
25758
- hookLogWarn("failproofaid was installed and running but did not answer a policy evaluation");
25759
- stdout.write(`
25760
- failproofaid started but cannot evaluate policies — its worker process
25761
- ` + `could not be run. Setup stopped before changing anything, because a machine
25762
- 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.
25763
26168
 
25764
26169
  Check it with: ${daemonStatusCommand() ?? "systemctl status failproofaid"}
25765
26170
  Then re-run: failproofai config
25766
26171
 
25767
26172
  `);
25768
- emit("configure_aborted", { reason: "daemon_not_answering" });
26173
+ emit("configure_aborted", { reason: `daemon_not_answering_${probe.reason}` });
25769
26174
  outro("Nothing was changed.", { ok: false }, stdout);
25770
26175
  return { applied: false, abort: "daemon_failed" };
25771
26176
  }
@@ -25850,6 +26255,7 @@ Hooks keep enforcing; scheduled audits stay off until it is.
25850
26255
  }
25851
26256
  await applied;
25852
26257
  markLauncherSeen();
26258
+ clearOnboardingAttempt();
25853
26259
  const customNote = customEnabled === true ? " + your custom policies" : customEnabled === false ? " · custom policies DISABLED" : "";
25854
26260
  const daemonNote = daemonInstalled ? " · daemon on" : "";
25855
26261
  const cloudNote = connected ? " · reporting on" : "";
@@ -25885,20 +26291,701 @@ var init_configure_wizard = __esm(() => {
25885
26291
  init_fp_home();
25886
26292
  init_daemon_download();
25887
26293
  init_package();
26294
+ init_onboarding_attempt();
25888
26295
  init_onboarding_lock();
25889
26296
  });
25890
26297
 
25891
- // src/hooks/manager.ts
25892
- var exports_manager = {};
25893
- __export(exports_manager, {
25894
- removeHooks: () => removeHooks,
25895
- listHooks: () => listHooks,
25896
- installHooks: () => installHooks2,
25897
- hooksInstalledInSettings: () => hooksInstalledInSettings2,
26298
+ // src/hooks/uninstall-cli.ts
26299
+ var exports_uninstall_cli = {};
26300
+ __export(exports_uninstall_cli, {
26301
+ serviceStateLabel: () => serviceStateLabel,
26302
+ runUninstallCommand: () => runUninstallCommand
26303
+ });
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
+ }
26316
+ }
26317
+ let daemonConfigured = false;
26318
+ try {
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
+ };
26329
+ }
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,
25898
26985
  getSettingsPath: () => getSettingsPath2
25899
26986
  });
25900
26987
  import { execSync as execSync7 } from "node:child_process";
25901
- import { existsSync as existsSync30 } from "node:fs";
26988
+ import { existsSync as existsSync32 } from "node:fs";
25902
26989
  import { resolve as resolve24, basename as basename6 } from "node:path";
25903
26990
  import { platform as platform5, arch as arch4, release as release3, hostname as hostname5 } from "node:os";
25904
26991
  function getSettingsPath2(scope, cwd) {
@@ -26009,7 +27096,7 @@ async function installHooksImpl2(policyNames, scope = "user", cwd, includeBeta =
26009
27096
  } else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
26010
27097
  const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve24(path3));
26011
27098
  const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve24(path3)).filter((path3) => {
26012
- if (existsSync30(path3))
27099
+ if (existsSync32(path3))
26013
27100
  return true;
26014
27101
  console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
26015
27102
  return false;
@@ -26145,7 +27232,7 @@ This file can be committed to git — no machine-specific paths.`);
26145
27232
  } catch {}
26146
27233
  }
26147
27234
  }
26148
- async function removeHooks(policyNames, scope = "user", cwd, opts) {
27235
+ async function removeHooks2(policyNames, scope = "user", cwd, opts) {
26149
27236
  const configScope = scope === "all" ? "user" : scope;
26150
27237
  const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
26151
27238
  if (opts?.removeCustomHooks) {
@@ -26201,7 +27288,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
26201
27288
  const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
26202
27289
  for (const s of scopesToRemove) {
26203
27290
  const settingsPaths = settingsPathsFor(integration, s, cwd);
26204
- const existing = settingsPaths.filter((p) => existsSync30(p));
27291
+ const existing = settingsPaths.filter((p) => existsSync32(p));
26205
27292
  if (existing.length === 0) {
26206
27293
  if (scope !== "all" && selectedClis.length === 1) {
26207
27294
  console.log("No settings file found. Nothing to remove.");
@@ -26402,7 +27489,7 @@ Failproof AI Hook Policies
26402
27489
  for (const path3 of explicitPaths) {
26403
27490
  const absPath = resolve24(findProjectConfigDir(cwd ?? process.cwd()), path3);
26404
27491
  console.log(` ${absPath}`);
26405
- if (!existsSync30(absPath)) {
27492
+ if (!existsSync32(absPath)) {
26406
27493
  console.log(` \x1B[31m✗ File not found: ${absPath}\x1B[0m`);
26407
27494
  continue;
26408
27495
  }
@@ -26499,7 +27586,7 @@ __export(exports_install_prompt, {
26499
27586
  promptPolicySelection: () => promptPolicySelection2,
26500
27587
  buildCliMenuOptions: () => buildCliMenuOptions
26501
27588
  });
26502
- import * as readline3 from "node:readline";
27589
+ import * as readline4 from "node:readline";
26503
27590
  async function resolveTargetClis(explicit, action = "install") {
26504
27591
  const detected = explicit && explicit.length > 0 ? [] : detectInstalledClis();
26505
27592
  const stdinIsTty = !!process.stdin.isTTY;
@@ -26606,13 +27693,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26606
27693
  let cursor2 = 0;
26607
27694
  let lastLineCount = 0;
26608
27695
  let cursorHidden = false;
26609
- function hideCursor2() {
27696
+ function hideCursor3() {
26610
27697
  if (!cursorHidden) {
26611
27698
  process.stdout.write("\x1B[?25l");
26612
27699
  cursorHidden = true;
26613
27700
  }
26614
27701
  }
26615
- function showCursor2() {
27702
+ function showCursor3() {
26616
27703
  if (cursorHidden) {
26617
27704
  process.stdout.write("\x1B[?25h");
26618
27705
  cursorHidden = false;
@@ -26622,7 +27709,7 @@ async function promptCliTargetSelection(detected, action = "install") {
26622
27709
  const heading = action === "uninstall" ? "Remove failproofai hooks" : "Install failproofai hooks";
26623
27710
  function render() {
26624
27711
  const cols = process.stdout.columns || 100;
26625
- hideCursor2();
27712
+ hideCursor3();
26626
27713
  const g = dim(BAR);
26627
27714
  const lines = [g, `${teal(STEP_ACTIVE)} ${bold(heading)}`];
26628
27715
  for (const row of buildDisplayRows()) {
@@ -26655,13 +27742,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26655
27742
  const itemCount = options.length;
26656
27743
  return new Promise((resolve25) => {
26657
27744
  render();
26658
- readline3.emitKeypressEvents(process.stdin);
27745
+ readline4.emitKeypressEvents(process.stdin);
26659
27746
  const wasRaw = process.stdin.isRaw;
26660
27747
  if (process.stdin.setRawMode)
26661
27748
  process.stdin.setRawMode(true);
26662
27749
  process.stdin.resume();
26663
27750
  function cleanup() {
26664
- showCursor2();
27751
+ showCursor3();
26665
27752
  process.stdin.removeListener("keypress", onKey);
26666
27753
  if (process.stdin.setRawMode)
26667
27754
  process.stdin.setRawMode(wasRaw ?? false);
@@ -26722,13 +27809,13 @@ async function promptPolicySelection2(preSelected, options = {}) {
26722
27809
  let search = "";
26723
27810
  let lastLineCount = 0;
26724
27811
  let cursorHidden = false;
26725
- function hideCursor2() {
27812
+ function hideCursor3() {
26726
27813
  if (!cursorHidden) {
26727
27814
  process.stdout.write("\x1B[?25l");
26728
27815
  cursorHidden = true;
26729
27816
  }
26730
27817
  }
26731
- function showCursor2() {
27818
+ function showCursor3() {
26732
27819
  if (cursorHidden) {
26733
27820
  process.stdout.write("\x1B[?25h");
26734
27821
  cursorHidden = false;
@@ -26775,7 +27862,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26775
27862
  }
26776
27863
  function render() {
26777
27864
  const cols = process.stdout.columns || 100;
26778
- hideCursor2();
27865
+ hideCursor3();
26779
27866
  const filtered = getFiltered();
26780
27867
  const shown = filtered.length;
26781
27868
  if (shown > 0 && cursor2 >= shown)
@@ -26794,10 +27881,10 @@ async function promptPolicySelection2(preSelected, options = {}) {
26794
27881
  for (let i = 0;i < WINDOW_SIZE + 1; i++)
26795
27882
  lines.push(g);
26796
27883
  } else {
26797
- const displayRows2 = buildDisplayRows(filtered);
27884
+ const displayRows3 = buildDisplayRows(filtered);
26798
27885
  let cursorDisplayRow = 0;
26799
- for (let i = 0;i < displayRows2.length; i++) {
26800
- const row = displayRows2[i];
27886
+ for (let i = 0;i < displayRows3.length; i++) {
27887
+ const row = displayRows3[i];
26801
27888
  if (row.kind === "item" && row.filteredIndex === cursor2) {
26802
27889
  cursorDisplayRow = i;
26803
27890
  break;
@@ -26805,12 +27892,12 @@ async function promptPolicySelection2(preSelected, options = {}) {
26805
27892
  }
26806
27893
  let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
26807
27894
  windowStart = Math.max(0, windowStart);
26808
- windowStart = Math.min(windowStart, Math.max(0, displayRows2.length - WINDOW_SIZE));
26809
- const windowEnd = Math.min(displayRows2.length, windowStart + WINDOW_SIZE);
26810
- 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;
26811
27898
  lines.push(aboveItems > 0 ? `${g} ${dim(`↑ ${aboveItems} more`)}` : g);
26812
27899
  for (let i = windowStart;i < windowEnd; i++) {
26813
- const row = displayRows2[i];
27900
+ const row = displayRows3[i];
26814
27901
  if (row.kind === "header") {
26815
27902
  lines.push(`${g} ${dim(row.category.toUpperCase())} ${dim(`${row.enabledCount}/${row.totalCount}`)}`);
26816
27903
  } else {
@@ -26827,7 +27914,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26827
27914
  }
26828
27915
  for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++)
26829
27916
  lines.push(g);
26830
- const belowItems = displayRows2.slice(windowEnd).filter((r) => r.kind === "item").length;
27917
+ const belowItems = displayRows3.slice(windowEnd).filter((r) => r.kind === "item").length;
26831
27918
  lines.push(belowItems > 0 ? `${g} ${dim(`↓ ${belowItems} more`)}` : g);
26832
27919
  }
26833
27920
  lines.push(g);
@@ -26843,7 +27930,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26843
27930
  render();
26844
27931
  process.stdin.setRawMode(true);
26845
27932
  process.stdin.resume();
26846
- readline3.emitKeypressEvents(process.stdin);
27933
+ readline4.emitKeypressEvents(process.stdin);
26847
27934
  function keypressHandler(_str, key) {
26848
27935
  if (!key)
26849
27936
  return;
@@ -26899,7 +27986,7 @@ ${dim(BAR)} ${dim(`${selected.length} selected`)}
26899
27986
  }
26900
27987
  }
26901
27988
  function cleanup() {
26902
- showCursor2();
27989
+ showCursor3();
26903
27990
  process.stdin.removeListener("keypress", keypressHandler);
26904
27991
  process.stdin.setRawMode(false);
26905
27992
  process.stdin.pause();
@@ -27537,8 +28624,8 @@ __export(exports_launch2, {
27537
28624
  launch: () => launch2
27538
28625
  });
27539
28626
  import { spawn as spawn3 } from "child_process";
27540
- import { realpathSync as realpathSync5, existsSync as existsSync31 } from "node:fs";
27541
- import { resolve as resolve25, dirname as dirname15 } from "node:path";
28627
+ import { realpathSync as realpathSync5, existsSync as existsSync33 } from "node:fs";
28628
+ import { resolve as resolve25, dirname as dirname16 } from "node:path";
27542
28629
  import { fileURLToPath as fileURLToPath3 } from "node:url";
27543
28630
  import { createInterface as createInterface2 } from "node:readline";
27544
28631
  function launch2(mode) {
@@ -27562,9 +28649,9 @@ function launch2(mode) {
27562
28649
  process.env.PORT = port;
27563
28650
  process.env.HOSTNAME = bindHost;
27564
28651
  cmd = "node";
27565
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve25(dirname15(realpathSync5(fileURLToPath3(import.meta.url))), "..");
28652
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve25(dirname16(realpathSync5(fileURLToPath3(import.meta.url))), "..");
27566
28653
  const serverJsPath = resolve25(packageRoot, ".next/standalone/server.js");
27567
- if (!existsSync31(serverJsPath)) {
28654
+ if (!existsSync33(serverJsPath)) {
27568
28655
  let shadowMessage = null;
27569
28656
  try {
27570
28657
  const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
@@ -27661,17 +28748,17 @@ var init_cli_error2 = __esm(() => {
27661
28748
 
27662
28749
  // bin/failproofai.mjs
27663
28750
  import { realpathSync as realpathSync6 } from "fs";
27664
- import { dirname as dirname16, resolve as resolve26 } from "path";
28751
+ import { dirname as dirname17, resolve as resolve26 } from "path";
27665
28752
  import { fileURLToPath as fileURLToPath4 } from "url";
27666
28753
  // package.json
27667
- var version = "1.0.0-beta.6";
28754
+ var version = "1.0.0-beta.8";
27668
28755
 
27669
28756
  // bin/failproofai.mjs
27670
28757
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
27671
- process.env.FAILPROOFAI_PACKAGE_ROOT = resolve26(dirname16(realpathSync6(fileURLToPath4(import.meta.url))), "..");
28758
+ process.env.FAILPROOFAI_PACKAGE_ROOT = resolve26(dirname17(realpathSync6(fileURLToPath4(import.meta.url))), "..");
27672
28759
  }
27673
28760
  if (!process.env.FAILPROOFAI_DIST_PATH) {
27674
- process.env.FAILPROOFAI_DIST_PATH = resolve26(dirname16(realpathSync6(fileURLToPath4(import.meta.url))), "..", "dist");
28761
+ process.env.FAILPROOFAI_DIST_PATH = resolve26(dirname17(realpathSync6(fileURLToPath4(import.meta.url))), "..", "dist");
27675
28762
  }
27676
28763
  var args = process.argv.slice(2);
27677
28764
  if (args[0] === "p")
@@ -27731,15 +28818,10 @@ if (hookIdx >= 0) {
27731
28818
  let result;
27732
28819
  if (attempt.ok) {
27733
28820
  result = attempt.response;
27734
- } else if (attempt.failure === "protocol-mismatch") {
27735
- console.error("[failproofai] failproofaid speaks a different protocol version than this CLI \u2014 " + "evaluating in-process instead (slower, identical policies). Run `failproofai config` to update the daemon.");
27736
- result = await evaluateHookEvent2(eventType, cli, stdinRead.payload);
27737
28821
  } else {
28822
+ const reason = attempt.failure === "protocol-mismatch" ? "failproofaid is running a different protocol version than this CLI, so it cannot evaluate this call. Run `failproofai config` to update the daemon." : "failproofaid could not be reached. This machine is configured to run hooks through it " + "\u2014 check the daemon (see `failproofai config`) rather than retrying blindly.";
27738
28823
  result = await evaluateHookEvent2(eventType, cli, stdinRead.payload, {
27739
- forceDecision: {
27740
- decision: "deny",
27741
- reason: "failproofaid could not be reached. This machine is configured to run hooks through it " + "\u2014 check the daemon (see `failproofai config`) rather than retrying blindly."
27742
- }
28824
+ forceDecision: { decision: "deny", reason }
27743
28825
  });
27744
28826
  }
27745
28827
  if (result.stdout)
@@ -27767,7 +28849,7 @@ if (hookIdx >= 0) {
27767
28849
  }
27768
28850
  }
27769
28851
  async function runCli() {
27770
- const SUBCOMMANDS = ["policies", "policy", "audit", "config"];
28852
+ const SUBCOMMANDS = ["policies", "policy", "audit", "config", "uninstall"];
27771
28853
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
27772
28854
  const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
27773
28855
  if (extraArgs.length > 0) {
@@ -27813,6 +28895,16 @@ COMMANDS
27813
28895
  dashboard at http://localhost:8020/audit
27814
28896
  audit --help, -h Show this help for the audit command
27815
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
+
27816
28908
  --version, -v Print version and exit
27817
28909
  --help, -h Show this help message
27818
28910
 
@@ -27890,6 +28982,84 @@ Run \`failproofai --help\` for usage.`);
27890
28982
  await maybeFirstRunConfigure2({}, { postSetupAudit: args[0] !== "audit", force: layoutWasReset });
27891
28983
  } catch {}
27892
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
+ }
27893
29063
  if (args[0] === "policies") {
27894
29064
  const subArgs = args.slice(1);
27895
29065
  const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
@@ -28019,7 +29189,7 @@ Run \`failproofai policies --help\` for usage.`);
28019
29189
  }
28020
29190
  if (isUninstall) {
28021
29191
  lastSubcommand = "uninstall";
28022
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
29192
+ const { removeHooks: removeHooks3 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28023
29193
  const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
28024
29194
  const scopeIdx = subArgs.indexOf("--scope");
28025
29195
  const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
@@ -28064,7 +29234,7 @@ Run \`failproofai policies --help\` for usage.`);
28064
29234
  }
28065
29235
  const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
28066
29236
  const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "uninstall");
28067
- 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 });
28068
29238
  await track("cli_uninstall_success", {
28069
29239
  scope,
28070
29240
  cli,
@@ -28196,8 +29366,8 @@ For multiple policies use \`failproofai policies --${action === "add" ? "install
28196
29366
  include_beta: includeBeta
28197
29367
  });
28198
29368
  } else {
28199
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28200
- 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 });
28201
29371
  await track("cli_policy_remove_success", {
28202
29372
  scope,
28203
29373
  cli,