failproofai 1.0.0-beta.7 → 1.0.0-beta.9

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 (126) 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]__0c_qcgp._.js → [root-of-the-server]__0abann8._.js} +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f9901b._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +1 -1
  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/_0r-dsre._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/{_194yh9u._.js → _1t3vvw3._.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/{2p2kz4aj1tooj.js → 0jibhdbjojuq5.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{36pc3_8fpb6ic.js → 0qubxfmqjdk8-.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{0m-m7wgtxdoav.js → 159mxiauw9a8_.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{11l6oh9cyb35a.js → 1xxzquhtvg72x.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/270jpx60b2kuj.js +1 -0
  99. package/.next/standalone/.next/static/chunks/{2aej0vi1fm87m.js → 2f295j8d9ta-h.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/{2bxld6-rym3ah.js → 2g7p545cy5458.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{0gr76ps8_zm26.js → 2nsm1u-n43hfa.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/3940dugfmx1ai.js +6 -0
  103. package/.next/standalone/.next/static/chunks/{3t1wr_kx6brwc.js → 3dpc4c0l_hf32.js} +1 -1
  104. package/.next/standalone/package.json +5 -5
  105. package/.next/standalone/server.js +1 -1
  106. package/bin/failproofai.mjs +118 -1
  107. package/dist/cli.mjs +1199 -98
  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/collector-config.ts +18 -2
  113. package/src/hooks/configure-wizard.ts +65 -34
  114. package/src/hooks/daemon-client.ts +35 -0
  115. package/src/hooks/daemon-service.ts +227 -22
  116. package/src/hooks/first-run-gate.ts +9 -1
  117. package/src/hooks/fp-home.ts +23 -3
  118. package/src/hooks/fp-reset.ts +137 -5
  119. package/src/hooks/uninstall-cli.ts +345 -0
  120. package/src/hooks/worker-server.ts +12 -2
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kbc9bs._.js +0 -3
  122. package/.next/standalone/.next/static/chunks/0945ybz3fsvba.js +0 -1
  123. package/.next/standalone/.next/static/chunks/166f8qnh03jqc.js +0 -6
  124. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → biwmgAwO6vVswC0z-soDU}/_buildManifest.js +0 -0
  125. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → biwmgAwO6vVswC0z-soDU}/_clientMiddlewareManifest.js +0 -0
  126. /package/.next/standalone/.next/static/{LUaLkKg40nSkHrIajexIk → biwmgAwO6vVswC0z-soDU}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // package.json
22
- var version2 = "1.0.0-beta.7";
22
+ var version2 = "1.0.0-beta.9";
23
23
  var init_package = () => {};
24
24
 
25
25
  // src/posthog-key.ts
@@ -1007,7 +1007,8 @@ function resettablePaths() {
1007
1007
  legacy.launcherMarker(),
1008
1008
  legacy.lastVersion(),
1009
1009
  legacy.auditDashboard(),
1010
- legacy.cacheDir(),
1010
+ legacy.auditCacheDir(),
1011
+ legacy.codexSessionPaths(),
1011
1012
  legacy.spoolDir(),
1012
1013
  legacy.failedDir(),
1013
1014
  legacy.collectorHealth(),
@@ -1015,12 +1016,10 @@ function resettablePaths() {
1015
1016
  legacy.cloudManagedPolicies(),
1016
1017
  localPoliciesDir(),
1017
1018
  cloudPoliciesDir(),
1018
- at("cursors"),
1019
1019
  at("state"),
1020
1020
  configFile(),
1021
1021
  credentialsFile(),
1022
1022
  auditDir(),
1023
- hookActivityDir(),
1024
1023
  customAgentsDir(),
1025
1024
  logsDir()
1026
1025
  ];
@@ -1454,6 +1453,7 @@ __export(exports_daemon_client, {
1454
1453
  tryDaemonHook: () => tryDaemonHook,
1455
1454
  isDaemonConfigured: () => isDaemonConfigured,
1456
1455
  daemonSocketPresent: () => daemonSocketPresent,
1456
+ daemonAcceptsConnections: () => daemonAcceptsConnections,
1457
1457
  attemptDaemonHook: () => attemptDaemonHook
1458
1458
  });
1459
1459
  import { createConnection } from "node:net";
@@ -1465,6 +1465,27 @@ function daemonSocketPresent() {
1465
1465
  return false;
1466
1466
  }
1467
1467
  }
1468
+ async function daemonAcceptsConnections(timeoutMs = DAEMON_CONNECT_TIMEOUT_MS) {
1469
+ if (process.platform === "win32")
1470
+ return false;
1471
+ return new Promise((resolve2) => {
1472
+ let settled = false;
1473
+ const done = (v) => {
1474
+ if (settled)
1475
+ return;
1476
+ settled = true;
1477
+ clearTimeout(timer);
1478
+ socket.removeAllListeners();
1479
+ socket.destroy();
1480
+ resolve2(v);
1481
+ };
1482
+ const socket = createConnection({ path: socketPath() });
1483
+ const timer = setTimeout(() => done(false), timeoutMs);
1484
+ timer.unref?.();
1485
+ socket.on("connect", () => done(true));
1486
+ socket.on("error", () => done(false));
1487
+ });
1488
+ }
1468
1489
  function socketPath() {
1469
1490
  if (process.env.FAILPROOFAI_DAEMON_SOCKET)
1470
1491
  return process.env.FAILPROOFAI_DAEMON_SOCKET;
@@ -2751,11 +2772,82 @@ function blockEnvFiles(ctx) {
2751
2772
  }
2752
2773
  return allow();
2753
2774
  }
2775
+ function quoteAwareSegments(command) {
2776
+ const segments = [];
2777
+ let tokens = [];
2778
+ let token = "";
2779
+ let quote = null;
2780
+ const endToken = () => {
2781
+ if (token)
2782
+ tokens.push(token);
2783
+ token = "";
2784
+ };
2785
+ const endSegment = () => {
2786
+ endToken();
2787
+ if (tokens.length)
2788
+ segments.push(tokens);
2789
+ tokens = [];
2790
+ };
2791
+ for (let i = 0;i < command.length; i++) {
2792
+ const c = command[i];
2793
+ if (quote) {
2794
+ if (c === "\\" && quote === '"' && i + 1 < command.length) {
2795
+ token += command[++i];
2796
+ continue;
2797
+ }
2798
+ if (c === quote) {
2799
+ quote = null;
2800
+ continue;
2801
+ }
2802
+ token += c;
2803
+ continue;
2804
+ }
2805
+ if (c === "\\" && i + 1 < command.length) {
2806
+ token += command[++i];
2807
+ continue;
2808
+ }
2809
+ if (c === '"' || c === "'") {
2810
+ quote = c;
2811
+ continue;
2812
+ }
2813
+ if (/\s/.test(c)) {
2814
+ endToken();
2815
+ continue;
2816
+ }
2817
+ if (/[;&|\n\r(){}`]/.test(c)) {
2818
+ endSegment();
2819
+ continue;
2820
+ }
2821
+ token += c;
2822
+ }
2823
+ endSegment();
2824
+ return segments;
2825
+ }
2826
+ function namesElevation(command, depth = 0) {
2827
+ for (const tokens of quoteAwareSegments(command)) {
2828
+ let i = 0;
2829
+ while (i < tokens.length) {
2830
+ const token = tokens[i];
2831
+ const base = token.slice(token.lastIndexOf("/") + 1);
2832
+ if (base === "sudo" || base === "doas")
2833
+ return true;
2834
+ const isSkippable = ENV_ASSIGNMENT_RE.test(token) || token.startsWith("-") || token.startsWith(">") || token.startsWith("<") || RUNNER_OPERAND_RE.test(token) || COMMAND_PREFIX_TOKENS.has(base);
2835
+ if (!isSkippable)
2836
+ break;
2837
+ if (EVAL_FLAG_RE.test(token) && depth < 3 && i + 1 < tokens.length) {
2838
+ if (namesElevation(tokens[i + 1], depth + 1))
2839
+ return true;
2840
+ }
2841
+ i++;
2842
+ }
2843
+ }
2844
+ return false;
2845
+ }
2754
2846
  function blockSudo(ctx) {
2755
2847
  if (ctx.toolName !== "Bash")
2756
2848
  return allow();
2757
2849
  const cmd = getCommand(ctx).trimStart();
2758
- if (SUDO_RE.test(cmd) || cmd.startsWith("sudo ")) {
2850
+ if (namesElevation(cmd)) {
2759
2851
  const allowPatterns = ctx.params?.allowPatterns ?? [];
2760
2852
  if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p)))
2761
2853
  return allow();
@@ -3515,7 +3607,7 @@ function registerBuiltinPolicies(enabledNames) {
3515
3607
  }
3516
3608
  }
3517
3609
  }
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;
3610
+ 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
3611
  var init_builtin_policies = __esm(() => {
3520
3612
  init_hook_logger();
3521
3613
  isClaudeInternalPath = isAgentInternalPath;
@@ -3552,7 +3644,6 @@ var init_builtin_policies = __esm(() => {
3552
3644
  CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
3553
3645
  ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
3554
3646
  ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
3555
- SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
3556
3647
  PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
3557
3648
  RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
3558
3649
  CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
@@ -3638,6 +3729,7 @@ var init_builtin_policies = __esm(() => {
3638
3729
  HELM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*helm(?:\s|$)/;
3639
3730
  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
3731
  gitBranchCache = new Map;
3732
+ EVAL_FLAG_RE = /^-{1,2}c$/;
3641
3733
  HOME_PREFIX_RE = /^(?:~[A-Za-z0-9_.-]*|\$HOME|\$\{HOME\})(?=$|\/)/;
3642
3734
  RM_CMD_RE = /^(?:\/\S*\/)?rm$/;
3643
3735
  FIND_CMD_RE = /^(?:\/\S*\/)?find$/;
@@ -10739,6 +10831,7 @@ __export(exports_daemon_client2, {
10739
10831
  tryDaemonHook: () => tryDaemonHook2,
10740
10832
  isDaemonConfigured: () => isDaemonConfigured2,
10741
10833
  daemonSocketPresent: () => daemonSocketPresent2,
10834
+ daemonAcceptsConnections: () => daemonAcceptsConnections2,
10742
10835
  attemptDaemonHook: () => attemptDaemonHook2
10743
10836
  });
10744
10837
  import { createConnection as createConnection2 } from "node:net";
@@ -10750,6 +10843,27 @@ function daemonSocketPresent2() {
10750
10843
  return false;
10751
10844
  }
10752
10845
  }
10846
+ async function daemonAcceptsConnections2(timeoutMs = DAEMON_CONNECT_TIMEOUT_MS2) {
10847
+ if (process.platform === "win32")
10848
+ return false;
10849
+ return new Promise((resolve14) => {
10850
+ let settled = false;
10851
+ const done = (v) => {
10852
+ if (settled)
10853
+ return;
10854
+ settled = true;
10855
+ clearTimeout(timer);
10856
+ socket.removeAllListeners();
10857
+ socket.destroy();
10858
+ resolve14(v);
10859
+ };
10860
+ const socket = createConnection2({ path: socketPath2() });
10861
+ const timer = setTimeout(() => done(false), timeoutMs);
10862
+ timer.unref?.();
10863
+ socket.on("connect", () => done(true));
10864
+ socket.on("error", () => done(false));
10865
+ });
10866
+ }
10753
10867
  function socketPath2() {
10754
10868
  if (process.env.FAILPROOFAI_DAEMON_SOCKET)
10755
10869
  return process.env.FAILPROOFAI_DAEMON_SOCKET;
@@ -10947,6 +11061,15 @@ function resolveCliCommand() {
10947
11061
  function shellQuote(value) {
10948
11062
  return `'${value.replace(/'/g, `'\\''`)}'`;
10949
11063
  }
11064
+ function workerScriptPath() {
11065
+ if (process.env.FAILPROOFAI_WORKER_CMD)
11066
+ return null;
11067
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
11068
+ if (!packageRoot)
11069
+ return null;
11070
+ const workerScript = resolve14(packageRoot, "dist", "worker.mjs");
11071
+ return existsSync17(workerScript) ? workerScript : null;
11072
+ }
10950
11073
  function assertUnitSafe(value, field) {
10951
11074
  if (UNIT_UNSAFE_RE.test(value)) {
10952
11075
  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 +11208,13 @@ function systemdUnitContents(binaryPath, workerCmd, cliCmd = null) {
11085
11208
  const user = assertUnitSafe(serviceUser(), "User");
11086
11209
  assertUnitSafe(binaryPath, "ExecStart");
11087
11210
  assertUnitSafe(homedir19(), "HOME");
11211
+ const conditionPaths = [binaryPath, workerScriptPath()].filter((p) => typeof p === "string" && existsSync17(p));
11212
+ const conditionLines = conditionPaths.map((p) => `ConditionPathExists=${assertUnitSafe(p, "ConditionPathExists")}
11213
+ `).join("");
11088
11214
  return `[Unit]
11089
11215
  Description=failproofai background daemon (failproofaid) for ${user}
11090
11216
  After=network.target
11091
-
11217
+ ${conditionLines}
11092
11218
  [Service]
11093
11219
  Type=simple
11094
11220
  User=${user}
@@ -11209,19 +11335,37 @@ async function installDaemonService() {
11209
11335
  }
11210
11336
  return { installed: true };
11211
11337
  }
11212
- async function probeDaemonEndToEnd() {
11338
+ async function probeDaemon() {
11213
11339
  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";
11340
+ const { attemptDaemonHook: attemptDaemonHook3, daemonAcceptsConnections: daemonAcceptsConnections3 } = await Promise.resolve().then(() => (init_daemon_client2(), exports_daemon_client2));
11341
+ const deadline = Date.now() + DAEMON_PROBE_READY_TIMEOUT_MS;
11342
+ let everConnected = false;
11343
+ for (;; ) {
11344
+ if (await daemonAcceptsConnections3()) {
11345
+ everConnected = true;
11346
+ const attempt = await attemptDaemonHook3({
11347
+ hookEvent: "SessionStart",
11348
+ cli: "claude",
11349
+ stdin: JSON.stringify({
11350
+ hook_event_name: "SessionStart",
11351
+ source: "failproofai-health-probe"
11352
+ })
11353
+ }, { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS });
11354
+ if (attempt.ok || attempt.failure === "protocol-mismatch")
11355
+ return { ok: true };
11356
+ }
11357
+ if (Date.now() >= deadline)
11358
+ break;
11359
+ await new Promise((r) => setTimeout(r, DAEMON_PROBE_RETRY_MS));
11360
+ }
11361
+ return { ok: false, reason: everConnected ? "worker" : "unreachable" };
11221
11362
  } catch {
11222
- return false;
11363
+ return { ok: false, reason: "unreachable" };
11223
11364
  }
11224
11365
  }
11366
+ async function probeDaemonEndToEnd() {
11367
+ return (await probeDaemon()).ok;
11368
+ }
11225
11369
  async function waitForDaemonRunning() {
11226
11370
  const deadline = Date.now() + SERVICE_START_TIMEOUT_MS;
11227
11371
  for (;; ) {
@@ -11406,6 +11550,16 @@ function daemonVersionSkew() {
11406
11550
  return null;
11407
11551
  return recorded === version2 ? null : { installed: recorded, expected: version2 };
11408
11552
  }
11553
+ function inactiveLinuxStatus() {
11554
+ try {
11555
+ return interpretConditionResult(execFileSync3("systemctl", ["show", systemdUnitName(), "--property=ConditionResult", "--value"], { stdio: ["ignore", "pipe", "ignore"], timeout: SERVICE_CMD_TIMEOUT_MS }).toString());
11556
+ } catch {
11557
+ return "stopped";
11558
+ }
11559
+ }
11560
+ function interpretConditionResult(raw) {
11561
+ return raw.trim() === "no" ? "condition-failed" : "stopped";
11562
+ }
11409
11563
  function daemonServiceStatus() {
11410
11564
  if (!isDaemonSupportedPlatform())
11411
11565
  return "unsupported-platform";
@@ -11417,9 +11571,9 @@ function daemonServiceStatus() {
11417
11571
  stdio: ["ignore", "pipe", "ignore"],
11418
11572
  timeout: SERVICE_CMD_TIMEOUT_MS
11419
11573
  }).toString().trim();
11420
- return out === "active" ? "running" : "stopped";
11574
+ return out === "active" ? "running" : inactiveLinuxStatus();
11421
11575
  } catch {
11422
- return "stopped";
11576
+ return inactiveLinuxStatus();
11423
11577
  }
11424
11578
  }
11425
11579
  const plistPath = launchdPlistPath();
@@ -11440,7 +11594,7 @@ function daemonServiceStatus() {
11440
11594
  return "stopped";
11441
11595
  }
11442
11596
  }
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;
11597
+ 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
11598
  var init_daemon_service = __esm(() => {
11445
11599
  init_hook_logger();
11446
11600
  init_daemon_download();
@@ -11800,11 +11954,12 @@ var init_handler = __esm(() => {
11800
11954
  var exports_fp_reset = {};
11801
11955
  __export(exports_fp_reset, {
11802
11956
  resetHome: () => resetHome,
11957
+ migrateHookActivity: () => migrateHookActivity,
11803
11958
  migrateConventionPolicies: () => migrateConventionPolicies,
11804
11959
  layoutWarningForHook: () => layoutWarningForHook2,
11805
11960
  checkLayoutForCli: () => checkLayoutForCli
11806
11961
  });
11807
- import { existsSync as existsSync18, mkdirSync as mkdirSync10, readdirSync as readdirSync13, renameSync as renameSync5, rmSync as rmSync6 } from "node:fs";
11962
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync10, readdirSync as readdirSync13, renameSync as renameSync5, rmSync as rmSync6 } from "node:fs";
11808
11963
  import { resolve as resolve15 } from "node:path";
11809
11964
  function migrateConventionPolicies() {
11810
11965
  const from = policiesDir();
@@ -11829,8 +11984,45 @@ function migrateConventionPolicies() {
11829
11984
  } catch {}
11830
11985
  return moved.sort((a, b) => a.localeCompare(b));
11831
11986
  }
11987
+ function migrateHookActivity() {
11988
+ const from = legacy.hookActivityDir();
11989
+ if (!existsSync18(from))
11990
+ return [];
11991
+ const moved = [];
11992
+ try {
11993
+ const entries = readdirSync13(from, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
11994
+ if (entries.length === 0)
11995
+ return [];
11996
+ const to = hookActivityDir();
11997
+ mkdirSync10(to, { recursive: true });
11998
+ const stamp2 = Date.now();
11999
+ let seq = 0;
12000
+ for (const entry of entries) {
12001
+ const source = resolve15(from, entry.name);
12002
+ let name = entry.name === "current.jsonl" ? `page-${stamp2}-${seq++}.jsonl` : entry.name;
12003
+ let target = resolve15(to, name);
12004
+ while (existsSync18(target)) {
12005
+ name = `page-${stamp2}-${seq++}.jsonl`;
12006
+ target = resolve15(to, name);
12007
+ }
12008
+ try {
12009
+ renameSync5(source, target);
12010
+ moved.push(name);
12011
+ } catch (err) {
12012
+ if (err?.code === "EXDEV") {
12013
+ try {
12014
+ copyFileSync(source, target);
12015
+ moved.push(name);
12016
+ } catch {}
12017
+ }
12018
+ }
12019
+ }
12020
+ } catch {}
12021
+ return moved.sort((a, b) => a.localeCompare(b));
12022
+ }
11832
12023
  function resetHome(from) {
11833
12024
  const migrated = migrateConventionPolicies();
12025
+ const activity = migrateHookActivity();
11834
12026
  const removed = [];
11835
12027
  for (const path3 of resettablePaths()) {
11836
12028
  if (!existsSync18(path3))
@@ -11841,7 +12033,7 @@ function resetHome(from) {
11841
12033
  } catch {}
11842
12034
  }
11843
12035
  writeVersionFile();
11844
- return { removed, migrated, from };
12036
+ return { removed, migrated, activity, from };
11845
12037
  }
11846
12038
  async function checkLayoutForCli() {
11847
12039
  const state = detectLayout();
@@ -11860,21 +12052,25 @@ async function checkLayoutForCli() {
11860
12052
  };
11861
12053
  }
11862
12054
  if (state.kind === "stale") {
11863
- const { removed, migrated } = resetHome(state.found);
12055
+ const { removed, migrated, activity } = resetHome(state.found);
11864
12056
  return {
11865
12057
  state,
11866
12058
  fatal: false,
11867
12059
  didReset: true,
11868
12060
  lines: [
11869
12061
  `failproofai reorganised ${failproofaiHome()} in this version.`,
11870
- `Removed ${removed.length} item(s) from the old layout — policy config, activity`,
11871
- `history and audit cache. Your downloaded daemon binary was kept.`,
12062
+ `Removed ${removed.length} item(s) from the old layout — policy config and`,
12063
+ `audit cache. Your daemon binary and decision history were kept.`,
11872
12064
  ...migrated.length > 0 ? [
11873
12065
  ``,
11874
12066
  `Kept your own policy file(s) and moved them to where this version`,
11875
12067
  `loads them (${customPoliciesDir()}):`,
11876
12068
  ...migrated.map((name) => ` ${name}`)
11877
12069
  ] : [],
12070
+ ...activity.length > 0 ? [
12071
+ ``,
12072
+ `Carried ${activity.length} page(s) of decision history into ${hookActivityDir()}.`
12073
+ ] : [],
11878
12074
  ``,
11879
12075
  `Run \`failproofai config\` to set up again.`
11880
12076
  ]
@@ -11906,6 +12102,20 @@ async function healDaemonFlag() {
11906
12102
  ``
11907
12103
  ];
11908
12104
  }
12105
+ if (status === "condition-failed") {
12106
+ updateConfig({ daemon: { configured: false } });
12107
+ return [
12108
+ `failproofaid is installed but cannot start — a file its service requires is`,
12109
+ `gone (most often because failproofai was removed with \`npm rm -g\`, which`,
12110
+ `deletes the worker but leaves the service behind). This machine was`,
12111
+ `configured to require the daemon, which denies every tool call, so that flag`,
12112
+ `is cleared; policies now evaluate in-process.`,
12113
+ ``,
12114
+ `Run \`failproofai uninstall\` to remove the leftover service, or`,
12115
+ `\`failproofai config\` to rebuild it. \`${daemonStatusCommand()}\` names the missing path.`,
12116
+ ``
12117
+ ];
12118
+ }
11909
12119
  if (status === "running" && !await probeDaemonEndToEnd()) {
11910
12120
  updateConfig({ daemon: { configured: false } });
11911
12121
  return [
@@ -12215,7 +12425,8 @@ var init_first_run_gate = __esm(() => {
12215
12425
  FIRST_RUN_EXEMPT_SUBCOMMANDS = [
12216
12426
  "config",
12217
12427
  "policies",
12218
- "policy"
12428
+ "policy",
12429
+ "uninstall"
12219
12430
  ];
12220
12431
  });
12221
12432
 
@@ -12787,7 +12998,7 @@ async function validateIngestKey(cred, fetchImpl = fetch, timeoutMs = 1e4) {
12787
12998
  clearTimeout(timer);
12788
12999
  }
12789
13000
  }
12790
- var DEFAULT_INGEST_URL = "https://server.befailproof.ai/v1/events";
13001
+ var DEFAULT_INGEST_URL = "https://app.befailproof.ai/v1/events";
12791
13002
  var init_collector_config = __esm(() => {
12792
13003
  init_fp_home();
12793
13004
  init_fp_config();
@@ -21925,6 +22136,129 @@ This file can be committed to git — no machine-specific paths.`);
21925
22136
  } catch {}
21926
22137
  }
21927
22138
  }
22139
+ async function removeHooks(policyNames, scope = "user", cwd, opts) {
22140
+ const configScope = scope === "all" ? "user" : scope;
22141
+ const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
22142
+ if (opts?.removeCustomHooks) {
22143
+ const config = readScopedHooksConfig(configScope, cwd);
22144
+ delete config.customPoliciesPath;
22145
+ delete config.customPoliciesPaths;
22146
+ writeScopedHooksConfig(config, configScope, cwd);
22147
+ console.log("Custom hooks path cleared.");
22148
+ }
22149
+ if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
22150
+ validatePolicyNames(policyNames);
22151
+ const config = readScopedHooksConfig(configScope, cwd);
22152
+ const removeSet = new Set(policyNames);
22153
+ const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
22154
+ const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
22155
+ if (notEnabled.length > 0) {
22156
+ console.log(`Warning: policy(ies) not currently enabled: ${notEnabled.join(", ")}`);
22157
+ }
22158
+ const { policyParams: existingParams, ...baseConfig } = config;
22159
+ const filteredParams = existingParams ? Object.fromEntries(Object.entries(existingParams).filter(([k]) => !removeSet.has(k))) : null;
22160
+ const updatedConfig = {
22161
+ ...baseConfig,
22162
+ enabledPolicies: remaining,
22163
+ ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
22164
+ };
22165
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
22166
+ try {
22167
+ const distinctId = getInstanceId2();
22168
+ const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
22169
+ await trackHookEvent2(distinctId, "hooks_removed", {
22170
+ scope,
22171
+ cli: selectedClis,
22172
+ removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
22173
+ beta_only: opts?.betaOnly ?? false,
22174
+ policies_removed: actuallyRemoved,
22175
+ removed_count: actuallyRemoved.length,
22176
+ ...opts?.source ? { source: opts.source } : {},
22177
+ platform: platform3(),
22178
+ arch: arch3(),
22179
+ os_release: release2(),
22180
+ hostname_hash: hashToId2(hostname3())
22181
+ });
22182
+ } catch {}
22183
+ console.log(`Disabled ${policyNames.length - notEnabled.length} policy(ies).`);
22184
+ console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
22185
+ return;
22186
+ }
22187
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
22188
+ let totalRemoved = 0;
22189
+ let nothingToReport = false;
22190
+ for (const cliId of selectedClis) {
22191
+ const integration = getIntegration(cliId);
22192
+ const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
22193
+ for (const s of scopesToRemove) {
22194
+ const settingsPaths = settingsPathsFor(integration, s, cwd);
22195
+ const existing = settingsPaths.filter((p) => existsSync21(p));
22196
+ if (existing.length === 0) {
22197
+ if (scope !== "all" && selectedClis.length === 1) {
22198
+ console.log("No settings file found. Nothing to remove.");
22199
+ nothingToReport = true;
22200
+ }
22201
+ continue;
22202
+ }
22203
+ let removedHere = 0;
22204
+ for (const settingsPath of existing) {
22205
+ const removed = integration.removeHooksFromFile(settingsPath);
22206
+ removedHere += removed;
22207
+ if (removed > 0 && scope !== "all") {
22208
+ console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
22209
+ console.log(`Settings: ${settingsPath}`);
22210
+ }
22211
+ }
22212
+ if (removedHere === 0 && scope !== "all" && selectedClis.length === 1) {
22213
+ console.log("No hooks found in settings. Nothing to remove.");
22214
+ nothingToReport = true;
22215
+ continue;
22216
+ }
22217
+ totalRemoved += removedHere;
22218
+ }
22219
+ }
22220
+ if (nothingToReport && totalRemoved === 0)
22221
+ return;
22222
+ if (scope === "all") {
22223
+ console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
22224
+ for (const cliId of selectedClis) {
22225
+ const integration = getIntegration(cliId);
22226
+ for (const s of integration.scopes) {
22227
+ for (const p of settingsPathsFor(integration, s, cwd)) {
22228
+ console.log(` ${integration.displayName} / ${s}: ${p}`);
22229
+ }
22230
+ }
22231
+ }
22232
+ }
22233
+ try {
22234
+ const distinctId = getInstanceId2();
22235
+ await trackHookEvent2(distinctId, "hooks_removed", {
22236
+ scope,
22237
+ cli: selectedClis,
22238
+ removal_mode: "hooks",
22239
+ policies_removed: configBeforeRemoval.enabledPolicies,
22240
+ removed_count: totalRemoved,
22241
+ ...opts?.source ? { source: opts.source } : {},
22242
+ platform: platform3(),
22243
+ arch: arch3(),
22244
+ os_release: release2(),
22245
+ hostname_hash: hashToId2(hostname3())
22246
+ });
22247
+ } catch {}
22248
+ if (scope === "all") {
22249
+ for (const s of HOOK_SCOPES) {
22250
+ const existing = readScopedHooksConfig(s, cwd);
22251
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPaths?.length || existing.customPoliciesPath || existing.policyParams) {
22252
+ const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
22253
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
22254
+ }
22255
+ }
22256
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
22257
+ const existing = readScopedHooksConfig(configScope, cwd);
22258
+ const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
22259
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
22260
+ }
22261
+ }
21928
22262
  var VALID_POLICY_NAMES;
21929
22263
  var init_manager = __esm(() => {
21930
22264
  init_types();
@@ -25743,31 +26077,26 @@ refreshed. Re-run \`failproofai config\` once you can use sudo.
25743
26077
  }
25744
26078
  }
25745
26079
  if (url === null) {
25746
- const entered = await promptText({
25747
- message: "Failproof Cloud URL",
25748
- defaultValue: cloudBaseFor(DEFAULT_INGEST_URL),
25749
- hint: "Enter for the hosted endpoint",
25750
- validate: (v) => {
25751
- const check = validateCloudUrl(cloudBaseFor(v));
25752
- return check.ok ? null : check.reason;
25753
- },
25754
- stdin,
25755
- stdout
25756
- });
25757
- if (entered === null)
25758
- return cancel();
25759
- const validated = validateCloudUrl(cloudBaseFor(entered));
25760
- if (!validated.ok) {
26080
+ const override = process.env.FAILPROOFAI_CLOUD_URL?.trim();
26081
+ if (override) {
26082
+ const validated = validateCloudUrl(cloudBaseFor(override));
26083
+ if (!validated.ok) {
26084
+ stdout.write(`
26085
+ FAILPROOFAI_CLOUD_URL is set to "${override}", which cannot be used: ${validated.reason}
26086
+ `);
26087
+ return cancel();
26088
+ }
26089
+ url = validated.url;
25761
26090
  stdout.write(`
25762
- ${validated.reason}
26091
+ Using ${url} (from FAILPROOFAI_CLOUD_URL).
25763
26092
  `);
25764
- return cancel();
26093
+ } else {
26094
+ url = cloudBaseFor(DEFAULT_INGEST_URL);
25765
26095
  }
25766
- url = validated.url;
25767
26096
  }
25768
26097
  if (token === null) {
25769
26098
  token = await promptText({
25770
- message: "API key",
26099
+ message: `API key for ${new URL(url).host}`,
25771
26100
  hint: "needs events:add · policies:pull enables managed policy too",
25772
26101
  mask: true,
25773
26102
  validate: (v) => v.length >= 8 ? null : "that looks too short to be a key",
@@ -25862,18 +26191,22 @@ Setup stopped before changing anything. Once that is fixed, re-run:
25862
26191
  outro("Nothing was changed.", { ok: false }, stdout);
25863
26192
  return { applied: false, abort: "daemon_failed" };
25864
26193
  }
25865
- if (!await probeDaemonEndToEnd()) {
25866
- hookLogWarn("failproofaid was installed and running but did not answer a policy evaluation");
25867
- stdout.write(`
25868
- failproofaid started but cannot evaluate policies — its worker process
25869
- ` + `could not be run. Setup stopped before changing anything, because a machine
25870
- configured to require a daemon that cannot answer denies every tool call.
26194
+ const probe = await probeDaemon();
26195
+ if (!probe.ok) {
26196
+ hookLogWarn(`failproofaid was installed and running but did not answer a policy evaluation (${probe.reason})`);
26197
+ stdout.write((probe.reason === "worker" ? `
26198
+ failproofaid started but cannot evaluate policies — it is listening, and
26199
+ ` + `its worker process could not be run.
26200
+ ` : `
26201
+ failproofaid started but could not be reached on its socket.
26202
+ `) + `Setup stopped before changing anything, because a machine configured to
26203
+ require a daemon that cannot answer denies every tool call.
25871
26204
 
25872
26205
  Check it with: ${daemonStatusCommand() ?? "systemctl status failproofaid"}
25873
26206
  Then re-run: failproofai config
25874
26207
 
25875
26208
  `);
25876
- emit("configure_aborted", { reason: "daemon_not_answering" });
26209
+ emit("configure_aborted", { reason: `daemon_not_answering_${probe.reason}` });
25877
26210
  outro("Nothing was changed.", { ok: false }, stdout);
25878
26211
  return { applied: false, abort: "daemon_failed" };
25879
26212
  }
@@ -25998,21 +26331,701 @@ var init_configure_wizard = __esm(() => {
25998
26331
  init_onboarding_lock();
25999
26332
  });
26000
26333
 
26001
- // src/hooks/manager.ts
26002
- var exports_manager = {};
26003
- __export(exports_manager, {
26004
- removeHooks: () => removeHooks,
26005
- listHooks: () => listHooks,
26006
- installHooks: () => installHooks2,
26007
- hooksInstalledInSettings: () => hooksInstalledInSettings2,
26008
- getSettingsPath: () => getSettingsPath2
26334
+ // src/hooks/uninstall-cli.ts
26335
+ var exports_uninstall_cli = {};
26336
+ __export(exports_uninstall_cli, {
26337
+ serviceStateLabel: () => serviceStateLabel,
26338
+ runUninstallCommand: () => runUninstallCommand
26009
26339
  });
26010
- import { execSync as execSync7 } from "node:child_process";
26011
- import { existsSync as existsSync31 } from "node:fs";
26012
- import { resolve as resolve24, basename as basename6 } from "node:path";
26013
- import { platform as platform5, arch as arch4, release as release3, hostname as hostname5 } from "node:os";
26014
- function getSettingsPath2(scope, cwd) {
26015
- return claudeCode.getSettingsPath(scope, cwd);
26340
+ import { existsSync as existsSync31, rmSync as rmSync10 } from "node:fs";
26341
+ function survey(cwd) {
26342
+ const clis = [];
26343
+ for (const id of listInstallableIds()) {
26344
+ for (const scope of ["user", "project", "local"]) {
26345
+ try {
26346
+ if (getIntegration(id).hooksInstalledInSettings(scope, cwd)) {
26347
+ clis.push(id);
26348
+ break;
26349
+ }
26350
+ } catch {}
26351
+ }
26352
+ }
26353
+ let daemonConfigured = false;
26354
+ try {
26355
+ daemonConfigured = readConfig().daemon.configured;
26356
+ } catch {}
26357
+ const servicePath = isDaemonSupportedPlatform() ? daemonServiceFilePath() : null;
26358
+ return {
26359
+ clis,
26360
+ servicePath,
26361
+ serviceInstalled: !!servicePath && existsSync31(servicePath),
26362
+ daemonConfigured,
26363
+ homeExists: existsSync31(failproofaiHome())
26364
+ };
26365
+ }
26366
+ async function runUninstallCommand(opts = {}) {
26367
+ const found = survey(opts.cwd);
26368
+ const lines = [];
26369
+ const nothingToDo = found.clis.length === 0 && !found.serviceInstalled && !found.daemonConfigured && !(opts.purge && found.homeExists);
26370
+ if (nothingToDo) {
26371
+ return {
26372
+ exitCode: 0,
26373
+ planLines: 0,
26374
+ purged: false,
26375
+ lines: [
26376
+ "Nothing to uninstall — no hook entries, no daemon service, and nothing configured.",
26377
+ ...found.homeExists && !opts.purge ? [
26378
+ ``,
26379
+ `${failproofaiHome()} still holds settings and audit history.`,
26380
+ `Remove it with \`failproofai uninstall --purge\`.`
26381
+ ] : []
26382
+ ]
26383
+ };
26384
+ }
26385
+ const plan = [];
26386
+ if (found.clis.length > 0) {
26387
+ 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(", "));
26388
+ }
26389
+ if (found.daemonConfigured) {
26390
+ plan.push(` • stop requiring the daemon (policies go back to evaluating in-process)`);
26391
+ }
26392
+ if (found.serviceInstalled) {
26393
+ plan.push(` • stop, disable and delete the service at ${found.servicePath} — needs sudo`);
26394
+ }
26395
+ let purged = false;
26396
+ if (opts.purge && found.homeExists) {
26397
+ plan.push(` • delete ${failproofaiHome()} — settings, credentials, audit history and the daemon binary`);
26398
+ }
26399
+ lines.push("failproofai uninstall will:", ...plan);
26400
+ if (!opts.purge && found.homeExists) {
26401
+ lines.push(``, `${failproofaiHome()} is KEPT (settings and audit history survive a reinstall).`, `Add --purge to delete it too.`);
26402
+ }
26403
+ const planLines = lines.length;
26404
+ if (opts.dryRun) {
26405
+ lines.push(``, `--dry-run: nothing was changed.`);
26406
+ return { exitCode: 0, lines, planLines, purged: false };
26407
+ }
26408
+ if (!opts.yes) {
26409
+ if (!opts.confirm) {
26410
+ lines.push(``, `Refusing to uninstall without confirmation. Re-run with --yes.`);
26411
+ return { exitCode: 1, lines, planLines, purged: false };
26412
+ }
26413
+ if (!await opts.confirm(lines)) {
26414
+ return { exitCode: 1, planLines, purged: false, lines: [...lines, ``, `Cancelled — nothing was changed.`] };
26415
+ }
26416
+ }
26417
+ lines.push(``);
26418
+ const failures = [];
26419
+ if (found.daemonConfigured) {
26420
+ try {
26421
+ setDaemonConfigured(false);
26422
+ lines.push(`✓ policies now evaluate in-process (this machine no longer requires the daemon)`);
26423
+ } catch (err) {
26424
+ return {
26425
+ exitCode: 2,
26426
+ planLines,
26427
+ purged: false,
26428
+ lines: [
26429
+ ...lines,
26430
+ `✗ could not update ${failproofaiHome()}/config.toml: ${err instanceof Error ? err.message : String(err)}`,
26431
+ ``,
26432
+ `Stopped before touching the service. Removing it while this machine still`,
26433
+ `requires it would deny every tool call. Fix the file's permissions and re-run.`
26434
+ ]
26435
+ };
26436
+ }
26437
+ }
26438
+ if (found.clis.length > 0) {
26439
+ try {
26440
+ await removeHooks(undefined, "all", opts.cwd, {
26441
+ cli: found.clis,
26442
+ removeCustomHooks: true,
26443
+ source: "uninstall_command"
26444
+ });
26445
+ lines.push(`✓ removed hook entries from ${found.clis.length} agent CLI${found.clis.length === 1 ? "" : "s"}`);
26446
+ } catch (err) {
26447
+ failures.push(`hook entries: ${err instanceof Error ? err.message : String(err)} ` + `(finish with \`failproofai policies --uninstall --scope all\`)`);
26448
+ }
26449
+ }
26450
+ if (found.serviceInstalled) {
26451
+ try {
26452
+ await uninstallDaemonService();
26453
+ if (found.servicePath && existsSync31(found.servicePath)) {
26454
+ failures.push(`the service at ${found.servicePath} is still there (most often: no sudo). ` + `Remove it with the commands below.`);
26455
+ } else {
26456
+ lines.push(`✓ stopped and removed the daemon service`);
26457
+ }
26458
+ } catch (err) {
26459
+ failures.push(`daemon service: ${err instanceof Error ? err.message : String(err)}`);
26460
+ }
26461
+ }
26462
+ if (opts.purge && found.homeExists) {
26463
+ try {
26464
+ rmSync10(failproofaiHome(), { recursive: true, force: true });
26465
+ purged = true;
26466
+ lines.push(`✓ deleted ${failproofaiHome()}`);
26467
+ } catch (err) {
26468
+ failures.push(`${failproofaiHome()}: ${err instanceof Error ? err.message : String(err)}`);
26469
+ }
26470
+ }
26471
+ if (failures.length > 0) {
26472
+ lines.push(``, `Not everything could be removed:`);
26473
+ for (const f of failures)
26474
+ lines.push(` ✗ ${f}`);
26475
+ if (found.servicePath && existsSync31(found.servicePath)) {
26476
+ lines.push(``, `To remove the service by hand:`, ...manualServiceRemoval(found.servicePath), ``, `(\`${daemonStatusCommand()}\` shows its current state.)`);
26477
+ }
26478
+ return { exitCode: 1, lines, planLines, purged };
26479
+ }
26480
+ 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`);
26481
+ if (!opts.purge && found.homeExists) {
26482
+ lines.push(``, `${failproofaiHome()} was kept. Delete it with \`failproofai uninstall --purge\`.`);
26483
+ }
26484
+ return { exitCode: 0, lines, planLines, purged };
26485
+ }
26486
+ function manualServiceRemoval(servicePath) {
26487
+ if (process.platform === "darwin") {
26488
+ return [` sudo launchctl unload -w ${servicePath}`, ` sudo rm -f ${servicePath}`];
26489
+ }
26490
+ const unit = servicePath.split("/").pop() ?? servicePath;
26491
+ return [
26492
+ ` sudo systemctl disable --now ${unit}`,
26493
+ ` sudo rm -f ${servicePath}`,
26494
+ ` sudo systemctl daemon-reload`
26495
+ ];
26496
+ }
26497
+ function serviceStateLabel() {
26498
+ const status = daemonServiceStatus();
26499
+ return status === "condition-failed" ? "installed but skipped by systemd (a file it needs is missing)" : status;
26500
+ }
26501
+ var init_uninstall_cli = __esm(() => {
26502
+ init_fp_home();
26503
+ init_fp_config();
26504
+ init_daemon_service();
26505
+ init_integrations();
26506
+ init_manager();
26507
+ });
26508
+
26509
+ // src/hooks/tui.ts
26510
+ var exports_tui = {};
26511
+ __export(exports_tui, {
26512
+ truncate: () => truncate2,
26513
+ summarize: () => summarize2,
26514
+ selectOne: () => selectOne2,
26515
+ renderLaunchBanner: () => renderLaunchBanner2,
26516
+ renderBrandLogo: () => renderBrandLogo2,
26517
+ promptText: () => promptText2,
26518
+ paint: () => paint2,
26519
+ outro: () => outro2,
26520
+ multiSelect: () => multiSelect2,
26521
+ intro: () => intro2,
26522
+ ellipsize: () => ellipsize2,
26523
+ colorsEnabled: () => colorsEnabled2,
26524
+ brandAnsi: () => brandAnsi2,
26525
+ STEP_DONE: () => STEP_DONE2,
26526
+ STEP_ACTIVE: () => STEP_ACTIVE2,
26527
+ CHECK_ON: () => CHECK_ON2,
26528
+ CHECK_OFF: () => CHECK_OFF2,
26529
+ CARET: () => CARET2,
26530
+ BAR: () => BAR2,
26531
+ ANSI_RESET: () => ANSI_RESET2,
26532
+ ANSI_DIM: () => ANSI_DIM2,
26533
+ ANSI_BOLD: () => ANSI_BOLD2
26534
+ });
26535
+ import * as readline3 from "node:readline";
26536
+ function colorsEnabled2(out) {
26537
+ return !!out.isTTY && !process.env.NO_COLOR;
26538
+ }
26539
+ function brandAnsi2(role) {
26540
+ const h = HUES2[role];
26541
+ const code = truecolorEnabled2() ? `38;2;${h.rgb.join(";")}` : h.basic;
26542
+ return `${ESC2}[${code}m`;
26543
+ }
26544
+ function truecolorEnabled2() {
26545
+ return /truecolor|24bit/i.test(process.env.COLORTERM || "");
26546
+ }
26547
+ function paint2(on) {
26548
+ const tc = on && truecolorEnabled2();
26549
+ const mk = (h, bold = false) => (s) => {
26550
+ if (!on)
26551
+ return s;
26552
+ const code = tc ? `38;2;${h.rgb[0]};${h.rgb[1]};${h.rgb[2]}` : h.basic;
26553
+ return `${ESC2}[${bold ? "1;" : ""}${code}m${s}${ESC2}[0m`;
26554
+ };
26555
+ return {
26556
+ bold: (s) => on ? `${ESC2}[1m${s}${ESC2}[0m` : s,
26557
+ dim: mk(HUES2.dim),
26558
+ guide: mk(HUES2.guide),
26559
+ pink: mk(HUES2.pink),
26560
+ pinkBold: mk(HUES2.pink, true),
26561
+ softPink: mk(HUES2.logoPink),
26562
+ warn: mk(HUES2.warn)
26563
+ };
26564
+ }
26565
+ function renderLogo2(colorize) {
26566
+ const pad = ".".repeat(LOGO_GRID2[0]?.length ?? 0);
26567
+ const rgb = (ch) => ch === "t" ? LOGO_TEAL2 : ch === "p" ? LOGO_PINK2 : null;
26568
+ const lines = [];
26569
+ for (let r = 0;r < LOGO_GRID2.length; r += 2) {
26570
+ const top = LOGO_GRID2[r];
26571
+ const bot = LOGO_GRID2[r + 1] ?? pad;
26572
+ let line = "";
26573
+ for (let x = 0;x < top.length; x++) {
26574
+ const t = rgb(top[x]);
26575
+ const b = rgb(bot[x]);
26576
+ if (!t && !b) {
26577
+ line += " ";
26578
+ } else if (!colorize) {
26579
+ line += t && b ? "█" : t ? "▀" : "▄";
26580
+ } else if (t && b) {
26581
+ line += t === b ? `${ESC2}[38;2;${t.join(";")}m█${ESC2}[0m` : `${ESC2}[38;2;${t.join(";")};48;2;${b.join(";")}m▀${ESC2}[0m`;
26582
+ } else if (t) {
26583
+ line += `${ESC2}[38;2;${t.join(";")}m▀${ESC2}[0m`;
26584
+ } else {
26585
+ line += `${ESC2}[38;2;${b.join(";")}m▄${ESC2}[0m`;
26586
+ }
26587
+ }
26588
+ lines.push(line);
26589
+ }
26590
+ return lines;
26591
+ }
26592
+ function truncate2(line, width) {
26593
+ let visual = 0;
26594
+ let out = "";
26595
+ let i = 0;
26596
+ while (i < line.length) {
26597
+ if (line[i] === ESC2 && line[i + 1] === "[") {
26598
+ let j = i + 2;
26599
+ while (j < line.length && !/[A-Za-z]/.test(line[j]))
26600
+ j++;
26601
+ j++;
26602
+ out += line.slice(i, j);
26603
+ i = j;
26604
+ } else {
26605
+ if (visual >= width)
26606
+ break;
26607
+ out += line[i];
26608
+ visual++;
26609
+ i++;
26610
+ }
26611
+ }
26612
+ return out;
26613
+ }
26614
+ function ellipsize2(text, width) {
26615
+ if (width <= 0)
26616
+ return "";
26617
+ if (text.length <= width)
26618
+ return text;
26619
+ return text.slice(0, width - 1).trimEnd() + "…";
26620
+ }
26621
+ function summarize2(labels, noun = "selected") {
26622
+ if (labels.length === 0)
26623
+ return "none";
26624
+ if (labels.length <= 3)
26625
+ return labels.join(", ");
26626
+ const head = labels.slice(0, 3).join(", ");
26627
+ return `${labels.length} ${noun} · ${head} +${labels.length - 3}`;
26628
+ }
26629
+ function writeLines2(out, lines) {
26630
+ const cols = out.columns || 80;
26631
+ out.write(lines.map((l) => l === "" ? l : truncate2(l, cols)).join(`
26632
+ `) + `
26633
+ `);
26634
+ }
26635
+ function renderBrandLogo2(stdout = process.stdout) {
26636
+ const c2 = paint2(colorsEnabled2(stdout));
26637
+ const cols = stdout.columns || 80;
26638
+ if (cols < LOGO_MIN_COLS2) {
26639
+ return [`${c2.guide(FLOWER2)} fa${c2.pink("il")}proof ai ${c2.dim("· " + TAGLINE2)}`];
26640
+ }
26641
+ const tc = colorsEnabled2(stdout) && truecolorEnabled2();
26642
+ const lines = renderLogo2(tc).map((l) => ` ${l}`);
26643
+ lines.push("");
26644
+ lines.push(` fa${c2.pink("il")}proof ai`);
26645
+ lines.push(` ${c2.dim(TAGLINE2)}`);
26646
+ return lines;
26647
+ }
26648
+ function intro2(message, stdout = process.stdout) {
26649
+ if (!stdout.isTTY)
26650
+ return;
26651
+ const c2 = paint2(colorsEnabled2(stdout));
26652
+ const lines = ["", ...renderBrandLogo2(stdout)];
26653
+ lines.push(c2.dim(BAR2));
26654
+ lines.push(`${c2.guide(STEP_ACTIVE2)} ${c2.bold(message)}`);
26655
+ writeLines2(stdout, lines);
26656
+ }
26657
+ function renderLaunchBanner2(version3, stdout = process.stdout) {
26658
+ const c2 = paint2(colorsEnabled2(stdout));
26659
+ const row = (label, value) => ` ${c2.guide(label.padEnd(9))}${value}`;
26660
+ const header = stdout.isTTY ? renderBrandLogo2(stdout) : [" failproof ai", ` ${TAGLINE2}`];
26661
+ return [
26662
+ "",
26663
+ ...header,
26664
+ "",
26665
+ row("version", c2.pink(version3)),
26666
+ row("star", c2.dim("https://github.com/failproofai/failproofai")),
26667
+ row("docs", c2.dim("https://docs.befailproof.ai/introduction")),
26668
+ row("discord", c2.dim("https://discord.befailproof.ai/")),
26669
+ ""
26670
+ ];
26671
+ }
26672
+ function outro2(message, opts = {}, stdout = process.stdout) {
26673
+ const c2 = paint2(colorsEnabled2(stdout));
26674
+ const ok = opts.ok !== false;
26675
+ if (!stdout.isTTY) {
26676
+ stdout.write(message + `
26677
+ `);
26678
+ return;
26679
+ }
26680
+ const end = ok ? c2.pink(BAR_END2) : c2.dim(BAR_END2);
26681
+ const text = ok ? c2.pink(message) : c2.dim(message);
26682
+ writeLines2(stdout, [c2.dim(BAR2), `${end} ${text}`]);
26683
+ }
26684
+ function repaint2(out, region, lines) {
26685
+ if (region.lastCount > 0)
26686
+ out.write(`${ESC2}[${region.lastCount}A${ESC2}[J`);
26687
+ writeLines2(out, lines);
26688
+ region.lastCount = lines.length;
26689
+ }
26690
+ function hideCursor2(out) {
26691
+ out.write(`${ESC2}[?25l`);
26692
+ }
26693
+ function showCursor2(out) {
26694
+ out.write(`${ESC2}[?25h`);
26695
+ }
26696
+ function nameWidth2(labels) {
26697
+ return Math.min(24, Math.max(6, ...labels.map((l) => l.length)));
26698
+ }
26699
+ function displayRows2(choices) {
26700
+ const rows = [];
26701
+ let lastSection;
26702
+ choices.forEach((choice, index) => {
26703
+ if (choice.section && choice.section !== lastSection) {
26704
+ lastSection = choice.section;
26705
+ rows.push({ kind: "header", text: choice.section });
26706
+ }
26707
+ rows.push({ kind: "item", index });
26708
+ });
26709
+ return rows;
26710
+ }
26711
+ function viewport2(rows, cursorRow, window) {
26712
+ if (rows.length <= window)
26713
+ return { start: 0, end: rows.length };
26714
+ let start = cursorRow - Math.floor(window / 2);
26715
+ start = Math.max(0, Math.min(start, rows.length - window));
26716
+ return { start, end: start + window };
26717
+ }
26718
+ function runPrompt2(p) {
26719
+ const { stdin, stdout, c: c2, choices } = p;
26720
+ const region = { lastCount: 0 };
26721
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26722
+ let cursor2 = 0;
26723
+ const build = () => {
26724
+ const cols = stdout.columns || 80;
26725
+ const lines = [c2.dim(BAR2), `${c2.guide(STEP_ACTIVE2)} ${c2.bold(p.message)}`];
26726
+ for (const b of p.body ?? [])
26727
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(b)}`);
26728
+ const rows = displayRows2(choices);
26729
+ let cursorRow = 0;
26730
+ rows.forEach((r, ri) => {
26731
+ if (r.kind === "item" && r.index === cursor2)
26732
+ cursorRow = ri;
26733
+ });
26734
+ const { start, end } = viewport2(rows, cursorRow, WINDOW3);
26735
+ const above = rows.slice(0, start).filter((r) => r.kind === "item").length;
26736
+ const below = rows.slice(end).filter((r) => r.kind === "item").length;
26737
+ if (above > 0)
26738
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(`↑ ${above} more`)}`);
26739
+ const budget = Math.max(6, cols - nameCol - 10);
26740
+ for (let ri = start;ri < end; ri++) {
26741
+ const row = rows[ri];
26742
+ if (row.kind === "header") {
26743
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(row.text)}`);
26744
+ } else {
26745
+ lines.push(`${c2.dim(BAR2)} ${p.renderRow(row.index, row.index === cursor2, budget)}`);
26746
+ }
26747
+ }
26748
+ if (below > 0)
26749
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(`↓ ${below} more`)}`);
26750
+ const warn = p.warnLine?.();
26751
+ if (warn)
26752
+ lines.push(`${c2.dim(BAR2)} ${warn}`);
26753
+ lines.push(`${c2.dim(BAR2)} ${c2.dim(p.footer)}`);
26754
+ return lines;
26755
+ };
26756
+ const collapse = (result) => {
26757
+ repaint2(stdout, region, [
26758
+ c2.dim(BAR2),
26759
+ `${c2.dim(STEP_DONE2)} ${p.message}`,
26760
+ `${c2.dim(BAR2)} ${c2.dim(p.summaryFor(result))}`
26761
+ ]);
26762
+ };
26763
+ return new Promise((resolve24) => {
26764
+ hideCursor2(stdout);
26765
+ repaint2(stdout, region, build());
26766
+ readline3.emitKeypressEvents(stdin);
26767
+ const wasRaw = stdin.isRaw;
26768
+ stdin.setRawMode?.(true);
26769
+ stdin.resume();
26770
+ const cleanup = () => {
26771
+ stdin.removeListener("keypress", onKey);
26772
+ stdin.setRawMode?.(wasRaw ?? false);
26773
+ stdin.pause();
26774
+ showCursor2(stdout);
26775
+ };
26776
+ const finish = (result) => {
26777
+ cleanup();
26778
+ collapse(result);
26779
+ resolve24(result);
26780
+ };
26781
+ function onKey(_s, key) {
26782
+ if (!key)
26783
+ return;
26784
+ if (key.ctrl && (key.name === "c" || key.name === "d") || key.name === "escape") {
26785
+ finish(null);
26786
+ } else if (key.name === "up") {
26787
+ cursor2 = cursor2 > 0 ? cursor2 - 1 : choices.length - 1;
26788
+ repaint2(stdout, region, build());
26789
+ } else if (key.name === "down") {
26790
+ cursor2 = cursor2 < choices.length - 1 ? cursor2 + 1 : 0;
26791
+ repaint2(stdout, region, build());
26792
+ } else {
26793
+ const outcome = p.onKey(key, cursor2);
26794
+ if (outcome === "redraw")
26795
+ repaint2(stdout, region, build());
26796
+ else if (outcome)
26797
+ finish(outcome.done);
26798
+ }
26799
+ }
26800
+ stdin.on("keypress", onKey);
26801
+ });
26802
+ }
26803
+ function selectOne2(opts) {
26804
+ const stdin = opts.stdin ?? process.stdin;
26805
+ const stdout = opts.stdout ?? process.stdout;
26806
+ const choices = opts.choices;
26807
+ if (!choices.length)
26808
+ return Promise.resolve(null);
26809
+ if (!stdin.isTTY || !stdout.isTTY) {
26810
+ return Promise.resolve(choices[0].value);
26811
+ }
26812
+ const c2 = paint2(colorsEnabled2(stdout));
26813
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26814
+ return runPrompt2({
26815
+ stdin,
26816
+ stdout,
26817
+ message: opts.message,
26818
+ c: c2,
26819
+ body: opts.body,
26820
+ choices,
26821
+ renderRow: (index, active, budget) => {
26822
+ const choice = choices[index];
26823
+ const dot = active ? c2.pink(RADIO_ON2) : c2.dim(RADIO_OFF2);
26824
+ const rawLabel = choice.label.padEnd(nameCol);
26825
+ const label = active ? c2.pinkBold(rawLabel) : rawLabel;
26826
+ const hint = choice.hint ? ` ${c2.dim(ellipsize2(choice.hint, budget))}` : "";
26827
+ return `${dot} ${label}${hint}`;
26828
+ },
26829
+ footer: "↑/↓ navigate · enter to select · esc to cancel",
26830
+ onKey: (key, cursor2) => key.name === "return" ? { done: choices[cursor2].value } : undefined,
26831
+ summaryFor: (value) => value === null ? "cancelled" : choices.find((ch) => ch.value === value)?.label ?? String(value)
26832
+ });
26833
+ }
26834
+ function multiSelect2(opts) {
26835
+ const stdin = opts.stdin ?? process.stdin;
26836
+ const stdout = opts.stdout ?? process.stdout;
26837
+ const choices = opts.choices;
26838
+ const minSelected = opts.minSelected ?? 0;
26839
+ const noun = opts.summaryNoun ?? "selected";
26840
+ const checked = choices.map((ch) => ch.locked ? ch.checked ?? true : !!ch.checked);
26841
+ if (!stdin.isTTY || !stdout.isTTY) {
26842
+ return Promise.resolve(choices.filter((_, i) => checked[i]).map((ch) => ch.value));
26843
+ }
26844
+ const c2 = paint2(colorsEnabled2(stdout));
26845
+ const nameCol = nameWidth2(choices.map((ch) => ch.label));
26846
+ let warn = false;
26847
+ return runPrompt2({
26848
+ stdin,
26849
+ stdout,
26850
+ message: opts.message,
26851
+ c: c2,
26852
+ choices,
26853
+ renderRow: (index, active, budget) => {
26854
+ const choice = choices[index];
26855
+ const caret = active ? c2.pink(CARET2) : " ";
26856
+ const box = choice.locked ? checked[index] ? c2.guide(CHECK_ON2) : c2.dim(CHECK_OFF2) : checked[index] ? c2.pink(CHECK_ON2) : c2.dim(CHECK_OFF2);
26857
+ const rawLabel = choice.label.padEnd(nameCol);
26858
+ const label = active ? c2.pinkBold(rawLabel) : checked[index] ? rawLabel : c2.dim(rawLabel);
26859
+ const hint = choice.hint ? ` ${c2.dim(ellipsize2(choice.hint, budget))}` : "";
26860
+ return `${caret} ${box} ${label}${hint}`;
26861
+ },
26862
+ warnLine: () => warn ? c2.warn(`Select at least ${minSelected}.`) : null,
26863
+ footer: opts.hint ?? "↑/↓ move · space select · ctrl+a all · enter confirm",
26864
+ onKey: (key, cursor2) => {
26865
+ if (key.name === "space") {
26866
+ if (choices[cursor2]?.locked)
26867
+ return "redraw";
26868
+ checked[cursor2] = !checked[cursor2];
26869
+ warn = false;
26870
+ return "redraw";
26871
+ }
26872
+ if (key.ctrl && key.name === "a") {
26873
+ const allOn = choices.every((ch, i) => checked[i] || ch.locked);
26874
+ for (let i = 0;i < checked.length; i++) {
26875
+ checked[i] = choices[i]?.locked ? true : !allOn;
26876
+ }
26877
+ return "redraw";
26878
+ }
26879
+ if (key.name === "return") {
26880
+ const selected = choices.filter((_, i) => checked[i]).map((ch) => ch.value);
26881
+ const chosen = choices.filter((ch, i) => checked[i] && !ch.locked).length;
26882
+ if (chosen < minSelected) {
26883
+ warn = true;
26884
+ return "redraw";
26885
+ }
26886
+ return { done: selected };
26887
+ }
26888
+ return;
26889
+ },
26890
+ summaryFor: (values) => values === null ? "cancelled" : summarize2(choices.filter((ch) => values.includes(ch.value) && !ch.summaryExclude).map((ch) => ch.label), noun)
26891
+ });
26892
+ }
26893
+ function promptText2(opts) {
26894
+ const stdin = opts.stdin ?? process.stdin;
26895
+ const stdout = opts.stdout ?? process.stdout;
26896
+ const c2 = paint2(colorsEnabled2(stdout));
26897
+ const accept = (raw) => {
26898
+ const value = raw.trim() || opts.defaultValue || "";
26899
+ const error = opts.validate?.(value) ?? null;
26900
+ return error ? { ok: false, error } : { ok: true, value };
26901
+ };
26902
+ if (!stdin.isTTY) {
26903
+ return new Promise((resolve24) => {
26904
+ let buf = "";
26905
+ const onData = (chunk) => {
26906
+ buf += String(chunk);
26907
+ if (buf.includes(`
26908
+ `))
26909
+ done();
26910
+ };
26911
+ const done = () => {
26912
+ stdin.removeListener("data", onData);
26913
+ stdin.removeListener("end", done);
26914
+ const r = accept(buf.split(`
26915
+ `)[0] ?? "");
26916
+ resolve24(r.ok ? r.value : null);
26917
+ };
26918
+ stdin.on("data", onData);
26919
+ stdin.on("end", done);
26920
+ });
26921
+ }
26922
+ return new Promise((resolve24) => {
26923
+ let value = "";
26924
+ const draw = (error) => {
26925
+ const shown = opts.mask ? "•".repeat(value.length) : value;
26926
+ const hint = opts.hint ? ` ${c2.dim(opts.hint)}` : "";
26927
+ const err = error ? `
26928
+ ${c2.warn(error)}` : "";
26929
+ stdout.write(`\r\x1B[2K${c2.bold(opts.message)} ${shown}${hint}${err}`);
26930
+ if (err)
26931
+ stdout.write("\x1B[1A");
26932
+ };
26933
+ draw();
26934
+ readline3.emitKeypressEvents(stdin);
26935
+ const wasRaw = stdin.isRaw;
26936
+ stdin.setRawMode?.(true);
26937
+ stdin.resume();
26938
+ const cleanup = () => {
26939
+ stdin.removeListener("keypress", onKey);
26940
+ stdin.setRawMode?.(wasRaw ?? false);
26941
+ stdin.pause();
26942
+ stdout.write(`
26943
+ `);
26944
+ };
26945
+ function onKey(str2, key) {
26946
+ if (!key)
26947
+ return;
26948
+ if (key.ctrl && (key.name === "c" || key.name === "d") || key.name === "escape") {
26949
+ cleanup();
26950
+ resolve24(null);
26951
+ return;
26952
+ }
26953
+ if (key.name === "return" || key.name === "enter") {
26954
+ const r = accept(value);
26955
+ if (r.ok) {
26956
+ cleanup();
26957
+ resolve24(r.value);
26958
+ } else {
26959
+ draw(r.error);
26960
+ }
26961
+ return;
26962
+ }
26963
+ if (key.name === "backspace") {
26964
+ value = value.slice(0, -1);
26965
+ draw();
26966
+ return;
26967
+ }
26968
+ if (str2 && !key.ctrl && !key.meta) {
26969
+ value += str2;
26970
+ draw();
26971
+ }
26972
+ }
26973
+ stdin.on("keypress", onKey);
26974
+ });
26975
+ }
26976
+ 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;
26977
+ var init_tui2 = __esm(() => {
26978
+ HUES2 = {
26979
+ guide: { rgb: [102, 209, 181], basic: "36" },
26980
+ pink: { rgb: [255, 46, 136], basic: "95" },
26981
+ logoPink: { rgb: [228, 88, 124], basic: "95" },
26982
+ warn: { rgb: [227, 179, 65], basic: "33" },
26983
+ dim: { rgb: [107, 118, 132], basic: "2" }
26984
+ };
26985
+ ANSI_RESET2 = `${ESC2}[0m`;
26986
+ ANSI_BOLD2 = `${ESC2}[1m`;
26987
+ ANSI_DIM2 = `${ESC2}[2m`;
26988
+ LOGO_GRID2 = [
26989
+ "...tt........",
26990
+ "..tttt.......",
26991
+ ".tttttt..pppp",
26992
+ ".tttttt..pppp",
26993
+ "..tttt...pppp",
26994
+ "...tt....pppp",
26995
+ ".........pppp",
26996
+ ".........pppp",
26997
+ "..pppp...pppp",
26998
+ "..pppp...pppp",
26999
+ "..pppp...pppp",
27000
+ ".pppppp..pppp",
27001
+ ".pppppp..pppp",
27002
+ ".pppppp..pppp",
27003
+ ".pppppp..pppp",
27004
+ "..pppp...pppp",
27005
+ "..ppppppppppp",
27006
+ "..ppppppppppp",
27007
+ "..ppppppppppp",
27008
+ "..ppppppppppp"
27009
+ ];
27010
+ LOGO_TEAL2 = HUES2.guide.rgb;
27011
+ LOGO_PINK2 = HUES2.logoPink.rgb;
27012
+ });
27013
+
27014
+ // src/hooks/manager.ts
27015
+ var exports_manager = {};
27016
+ __export(exports_manager, {
27017
+ removeHooks: () => removeHooks2,
27018
+ listHooks: () => listHooks,
27019
+ installHooks: () => installHooks2,
27020
+ hooksInstalledInSettings: () => hooksInstalledInSettings2,
27021
+ getSettingsPath: () => getSettingsPath2
27022
+ });
27023
+ import { execSync as execSync7 } from "node:child_process";
27024
+ import { existsSync as existsSync32 } from "node:fs";
27025
+ import { resolve as resolve24, basename as basename6 } from "node:path";
27026
+ import { platform as platform5, arch as arch4, release as release3, hostname as hostname5 } from "node:os";
27027
+ function getSettingsPath2(scope, cwd) {
27028
+ return claudeCode.getSettingsPath(scope, cwd);
26016
27029
  }
26017
27030
  function scopeLabel2(scope) {
26018
27031
  switch (scope) {
@@ -26119,7 +27132,7 @@ async function installHooksImpl2(policyNames, scope = "user", cwd, includeBeta =
26119
27132
  } else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
26120
27133
  const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve24(path3));
26121
27134
  const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve24(path3)).filter((path3) => {
26122
- if (existsSync31(path3))
27135
+ if (existsSync32(path3))
26123
27136
  return true;
26124
27137
  console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
26125
27138
  return false;
@@ -26255,7 +27268,7 @@ This file can be committed to git — no machine-specific paths.`);
26255
27268
  } catch {}
26256
27269
  }
26257
27270
  }
26258
- async function removeHooks(policyNames, scope = "user", cwd, opts) {
27271
+ async function removeHooks2(policyNames, scope = "user", cwd, opts) {
26259
27272
  const configScope = scope === "all" ? "user" : scope;
26260
27273
  const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
26261
27274
  if (opts?.removeCustomHooks) {
@@ -26311,7 +27324,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
26311
27324
  const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
26312
27325
  for (const s of scopesToRemove) {
26313
27326
  const settingsPaths = settingsPathsFor(integration, s, cwd);
26314
- const existing = settingsPaths.filter((p) => existsSync31(p));
27327
+ const existing = settingsPaths.filter((p) => existsSync32(p));
26315
27328
  if (existing.length === 0) {
26316
27329
  if (scope !== "all" && selectedClis.length === 1) {
26317
27330
  console.log("No settings file found. Nothing to remove.");
@@ -26512,7 +27525,7 @@ Failproof AI Hook Policies
26512
27525
  for (const path3 of explicitPaths) {
26513
27526
  const absPath = resolve24(findProjectConfigDir(cwd ?? process.cwd()), path3);
26514
27527
  console.log(` ${absPath}`);
26515
- if (!existsSync31(absPath)) {
27528
+ if (!existsSync32(absPath)) {
26516
27529
  console.log(` \x1B[31m✗ File not found: ${absPath}\x1B[0m`);
26517
27530
  continue;
26518
27531
  }
@@ -26609,7 +27622,7 @@ __export(exports_install_prompt, {
26609
27622
  promptPolicySelection: () => promptPolicySelection2,
26610
27623
  buildCliMenuOptions: () => buildCliMenuOptions
26611
27624
  });
26612
- import * as readline3 from "node:readline";
27625
+ import * as readline4 from "node:readline";
26613
27626
  async function resolveTargetClis(explicit, action = "install") {
26614
27627
  const detected = explicit && explicit.length > 0 ? [] : detectInstalledClis();
26615
27628
  const stdinIsTty = !!process.stdin.isTTY;
@@ -26716,13 +27729,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26716
27729
  let cursor2 = 0;
26717
27730
  let lastLineCount = 0;
26718
27731
  let cursorHidden = false;
26719
- function hideCursor2() {
27732
+ function hideCursor3() {
26720
27733
  if (!cursorHidden) {
26721
27734
  process.stdout.write("\x1B[?25l");
26722
27735
  cursorHidden = true;
26723
27736
  }
26724
27737
  }
26725
- function showCursor2() {
27738
+ function showCursor3() {
26726
27739
  if (cursorHidden) {
26727
27740
  process.stdout.write("\x1B[?25h");
26728
27741
  cursorHidden = false;
@@ -26732,7 +27745,7 @@ async function promptCliTargetSelection(detected, action = "install") {
26732
27745
  const heading = action === "uninstall" ? "Remove failproofai hooks" : "Install failproofai hooks";
26733
27746
  function render() {
26734
27747
  const cols = process.stdout.columns || 100;
26735
- hideCursor2();
27748
+ hideCursor3();
26736
27749
  const g = dim(BAR);
26737
27750
  const lines = [g, `${teal(STEP_ACTIVE)} ${bold(heading)}`];
26738
27751
  for (const row of buildDisplayRows()) {
@@ -26765,13 +27778,13 @@ async function promptCliTargetSelection(detected, action = "install") {
26765
27778
  const itemCount = options.length;
26766
27779
  return new Promise((resolve25) => {
26767
27780
  render();
26768
- readline3.emitKeypressEvents(process.stdin);
27781
+ readline4.emitKeypressEvents(process.stdin);
26769
27782
  const wasRaw = process.stdin.isRaw;
26770
27783
  if (process.stdin.setRawMode)
26771
27784
  process.stdin.setRawMode(true);
26772
27785
  process.stdin.resume();
26773
27786
  function cleanup() {
26774
- showCursor2();
27787
+ showCursor3();
26775
27788
  process.stdin.removeListener("keypress", onKey);
26776
27789
  if (process.stdin.setRawMode)
26777
27790
  process.stdin.setRawMode(wasRaw ?? false);
@@ -26832,13 +27845,13 @@ async function promptPolicySelection2(preSelected, options = {}) {
26832
27845
  let search = "";
26833
27846
  let lastLineCount = 0;
26834
27847
  let cursorHidden = false;
26835
- function hideCursor2() {
27848
+ function hideCursor3() {
26836
27849
  if (!cursorHidden) {
26837
27850
  process.stdout.write("\x1B[?25l");
26838
27851
  cursorHidden = true;
26839
27852
  }
26840
27853
  }
26841
- function showCursor2() {
27854
+ function showCursor3() {
26842
27855
  if (cursorHidden) {
26843
27856
  process.stdout.write("\x1B[?25h");
26844
27857
  cursorHidden = false;
@@ -26885,7 +27898,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26885
27898
  }
26886
27899
  function render() {
26887
27900
  const cols = process.stdout.columns || 100;
26888
- hideCursor2();
27901
+ hideCursor3();
26889
27902
  const filtered = getFiltered();
26890
27903
  const shown = filtered.length;
26891
27904
  if (shown > 0 && cursor2 >= shown)
@@ -26904,10 +27917,10 @@ async function promptPolicySelection2(preSelected, options = {}) {
26904
27917
  for (let i = 0;i < WINDOW_SIZE + 1; i++)
26905
27918
  lines.push(g);
26906
27919
  } else {
26907
- const displayRows2 = buildDisplayRows(filtered);
27920
+ const displayRows3 = buildDisplayRows(filtered);
26908
27921
  let cursorDisplayRow = 0;
26909
- for (let i = 0;i < displayRows2.length; i++) {
26910
- const row = displayRows2[i];
27922
+ for (let i = 0;i < displayRows3.length; i++) {
27923
+ const row = displayRows3[i];
26911
27924
  if (row.kind === "item" && row.filteredIndex === cursor2) {
26912
27925
  cursorDisplayRow = i;
26913
27926
  break;
@@ -26915,12 +27928,12 @@ async function promptPolicySelection2(preSelected, options = {}) {
26915
27928
  }
26916
27929
  let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
26917
27930
  windowStart = Math.max(0, windowStart);
26918
- windowStart = Math.min(windowStart, Math.max(0, displayRows2.length - WINDOW_SIZE));
26919
- const windowEnd = Math.min(displayRows2.length, windowStart + WINDOW_SIZE);
26920
- const aboveItems = displayRows2.slice(0, windowStart).filter((r) => r.kind === "item").length;
27931
+ windowStart = Math.min(windowStart, Math.max(0, displayRows3.length - WINDOW_SIZE));
27932
+ const windowEnd = Math.min(displayRows3.length, windowStart + WINDOW_SIZE);
27933
+ const aboveItems = displayRows3.slice(0, windowStart).filter((r) => r.kind === "item").length;
26921
27934
  lines.push(aboveItems > 0 ? `${g} ${dim(`↑ ${aboveItems} more`)}` : g);
26922
27935
  for (let i = windowStart;i < windowEnd; i++) {
26923
- const row = displayRows2[i];
27936
+ const row = displayRows3[i];
26924
27937
  if (row.kind === "header") {
26925
27938
  lines.push(`${g} ${dim(row.category.toUpperCase())} ${dim(`${row.enabledCount}/${row.totalCount}`)}`);
26926
27939
  } else {
@@ -26937,7 +27950,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26937
27950
  }
26938
27951
  for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++)
26939
27952
  lines.push(g);
26940
- const belowItems = displayRows2.slice(windowEnd).filter((r) => r.kind === "item").length;
27953
+ const belowItems = displayRows3.slice(windowEnd).filter((r) => r.kind === "item").length;
26941
27954
  lines.push(belowItems > 0 ? `${g} ${dim(`↓ ${belowItems} more`)}` : g);
26942
27955
  }
26943
27956
  lines.push(g);
@@ -26953,7 +27966,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
26953
27966
  render();
26954
27967
  process.stdin.setRawMode(true);
26955
27968
  process.stdin.resume();
26956
- readline3.emitKeypressEvents(process.stdin);
27969
+ readline4.emitKeypressEvents(process.stdin);
26957
27970
  function keypressHandler(_str, key) {
26958
27971
  if (!key)
26959
27972
  return;
@@ -27009,7 +28022,7 @@ ${dim(BAR)} ${dim(`${selected.length} selected`)}
27009
28022
  }
27010
28023
  }
27011
28024
  function cleanup() {
27012
- showCursor2();
28025
+ showCursor3();
27013
28026
  process.stdin.removeListener("keypress", keypressHandler);
27014
28027
  process.stdin.setRawMode(false);
27015
28028
  process.stdin.pause();
@@ -27647,7 +28660,7 @@ __export(exports_launch2, {
27647
28660
  launch: () => launch2
27648
28661
  });
27649
28662
  import { spawn as spawn3 } from "child_process";
27650
- import { realpathSync as realpathSync5, existsSync as existsSync32 } from "node:fs";
28663
+ import { realpathSync as realpathSync5, existsSync as existsSync33 } from "node:fs";
27651
28664
  import { resolve as resolve25, dirname as dirname16 } from "node:path";
27652
28665
  import { fileURLToPath as fileURLToPath3 } from "node:url";
27653
28666
  import { createInterface as createInterface2 } from "node:readline";
@@ -27674,7 +28687,7 @@ function launch2(mode) {
27674
28687
  cmd = "node";
27675
28688
  const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve25(dirname16(realpathSync5(fileURLToPath3(import.meta.url))), "..");
27676
28689
  const serverJsPath = resolve25(packageRoot, ".next/standalone/server.js");
27677
- if (!existsSync32(serverJsPath)) {
28690
+ if (!existsSync33(serverJsPath)) {
27678
28691
  let shadowMessage = null;
27679
28692
  try {
27680
28693
  const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
@@ -27774,7 +28787,7 @@ import { realpathSync as realpathSync6 } from "fs";
27774
28787
  import { dirname as dirname17, resolve as resolve26 } from "path";
27775
28788
  import { fileURLToPath as fileURLToPath4 } from "url";
27776
28789
  // package.json
27777
- var version = "1.0.0-beta.7";
28790
+ var version = "1.0.0-beta.9";
27778
28791
 
27779
28792
  // bin/failproofai.mjs
27780
28793
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -27872,7 +28885,7 @@ if (hookIdx >= 0) {
27872
28885
  }
27873
28886
  }
27874
28887
  async function runCli() {
27875
- const SUBCOMMANDS = ["policies", "policy", "audit", "config"];
28888
+ const SUBCOMMANDS = ["policies", "policy", "audit", "config", "uninstall"];
27876
28889
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
27877
28890
  const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
27878
28891
  if (extraArgs.length > 0) {
@@ -27918,6 +28931,16 @@ COMMANDS
27918
28931
  dashboard at http://localhost:8020/audit
27919
28932
  audit --help, -h Show this help for the audit command
27920
28933
 
28934
+ uninstall Remove failproofai from this machine: hook
28935
+ entries from every agent CLI, and the daemon
28936
+ service. Run this BEFORE \`npm rm -g failproofai\`
28937
+ \u2014 npm runs no uninstall script, so removing the
28938
+ package alone leaves both behind.
28939
+ --purge Also delete ~/.failproofai (settings,
28940
+ credentials, audit history, daemon binary)
28941
+ --dry-run Show what would be removed, change nothing
28942
+ --yes, -y Skip the confirmation prompt
28943
+
27921
28944
  --version, -v Print version and exit
27922
28945
  --help, -h Show this help message
27923
28946
 
@@ -27995,6 +29018,84 @@ Run \`failproofai --help\` for usage.`);
27995
29018
  await maybeFirstRunConfigure2({}, { postSetupAudit: args[0] !== "audit", force: layoutWasReset });
27996
29019
  } catch {}
27997
29020
  }
29021
+ if (args[0] === "uninstall") {
29022
+ const subArgs = args.slice(1);
29023
+ if (subArgs.includes("--help") || subArgs.includes("-h")) {
29024
+ console.log(`
29025
+ failproofai uninstall \u2014 remove failproofai from this machine
29026
+
29027
+ USAGE
29028
+ failproofai uninstall [--purge] [--dry-run] [--yes]
29029
+
29030
+ WHAT IT REMOVES
29031
+ \u2022 failproofai hook entries from every agent CLI that has them
29032
+ \u2022 the failproofaid daemon service (needs sudo)
29033
+ \u2022 the "require the daemon" flag \u2014 cleared FIRST, so a partial uninstall can
29034
+ never leave this machine denying every tool call
29035
+
29036
+ OPTIONS
29037
+ --purge Also delete ~/.failproofai \u2014 settings, credentials, audit
29038
+ history and the downloaded daemon binary. Off by default so a
29039
+ reinstall keeps your history.
29040
+ --dry-run Print what would be removed and change nothing.
29041
+ --yes, -y Skip the confirmation prompt. Required when there is no TTY.
29042
+
29043
+ WHY THIS EXISTS
29044
+ npm runs no uninstall script, so \`npm rm -g failproofai\` removes the package
29045
+ and leaves the hook entries and the service behind. Run this first, then:
29046
+ npm rm -g failproofai
29047
+ `);
29048
+ process.exit(0);
29049
+ }
29050
+ const KNOWN = new Set(["--purge", "--dry-run", "--yes", "-y"]);
29051
+ const unknown = subArgs.find((a) => !KNOWN.has(a));
29052
+ if (unknown) {
29053
+ throw new CliError3(`Unexpected argument: ${unknown}
29054
+ Run \`failproofai uninstall --help\` for usage.`);
29055
+ }
29056
+ lastSubcommand = "uninstall_command";
29057
+ const { runUninstallCommand: runUninstallCommand2 } = await Promise.resolve().then(() => (init_uninstall_cli(), exports_uninstall_cli));
29058
+ const purge = subArgs.includes("--purge");
29059
+ const dryRun = subArgs.includes("--dry-run");
29060
+ const yes = subArgs.includes("--yes") || subArgs.includes("-y");
29061
+ let planWasShown = false;
29062
+ const result = await runUninstallCommand2({
29063
+ purge,
29064
+ dryRun,
29065
+ yes,
29066
+ cwd: process.cwd(),
29067
+ confirm: process.stdin.isTTY ? async (planLines) => {
29068
+ planWasShown = true;
29069
+ const { selectOne: selectOne3 } = await Promise.resolve().then(() => (init_tui2(), exports_tui));
29070
+ const answer = await selectOne3({
29071
+ message: purge ? "Remove failproofai and DELETE ~/.failproofai?" : "Remove failproofai from this machine?",
29072
+ body: planLines,
29073
+ choices: [
29074
+ { label: "No, cancel", value: false },
29075
+ { label: purge ? "Yes, remove and purge" : "Yes, remove it", value: true }
29076
+ ]
29077
+ });
29078
+ return answer === true;
29079
+ } : undefined
29080
+ });
29081
+ const skip = planWasShown ? result.planLines : 0;
29082
+ for (const line of result.lines.slice(skip)) {
29083
+ if (result.exitCode === 0)
29084
+ console.log(line);
29085
+ else
29086
+ console.error(line);
29087
+ }
29088
+ if (!result.purged) {
29089
+ await track("cli_uninstall_command", {
29090
+ ok: result.exitCode === 0,
29091
+ purge,
29092
+ dry_run: dryRun
29093
+ });
29094
+ }
29095
+ lastSubcommand = null;
29096
+ await exitAfterFlush(result.exitCode);
29097
+ return;
29098
+ }
27998
29099
  if (args[0] === "policies") {
27999
29100
  const subArgs = args.slice(1);
28000
29101
  const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
@@ -28124,7 +29225,7 @@ Run \`failproofai policies --help\` for usage.`);
28124
29225
  }
28125
29226
  if (isUninstall) {
28126
29227
  lastSubcommand = "uninstall";
28127
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
29228
+ const { removeHooks: removeHooks3 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28128
29229
  const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
28129
29230
  const scopeIdx = subArgs.indexOf("--scope");
28130
29231
  const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
@@ -28169,7 +29270,7 @@ Run \`failproofai policies --help\` for usage.`);
28169
29270
  }
28170
29271
  const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
28171
29272
  const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "uninstall");
28172
- await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
29273
+ await removeHooks3(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
28173
29274
  await track("cli_uninstall_success", {
28174
29275
  scope,
28175
29276
  cli,
@@ -28301,8 +29402,8 @@ For multiple policies use \`failproofai policies --${action === "add" ? "install
28301
29402
  include_beta: includeBeta
28302
29403
  });
28303
29404
  } else {
28304
- const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
28305
- await removeHooks2([policyName], scope, undefined, { betaOnly: false, removeCustomHooks: false, cli });
29405
+ const { removeHooks: removeHooks3 } = await Promise.resolve().then(() => (init_manager2(), exports_manager));
29406
+ await removeHooks3([policyName], scope, undefined, { betaOnly: false, removeCustomHooks: false, cli });
28306
29407
  await track("cli_policy_remove_success", {
28307
29408
  scope,
28308
29409
  cli,