failproofai 0.0.11-beta.9 → 0.0.12-beta.0

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 (180) hide show
  1. package/.next/standalone/.codex/hooks.json +6 -7
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/build-manifest.json +5 -5
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  25. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  28. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/index.html +1 -1
  49. package/.next/standalone/.next/server/app/index.rsc +15 -15
  50. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  51. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  52. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  53. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  54. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  55. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  61. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  64. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/chunks/[externals]__1_g_b3t._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
  84. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e4-6d8._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +2 -2
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +2 -2
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0k65l27._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +2 -2
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wprfyc._.js → [root-of-the-server]__12e2gos._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0e446gb._.js → [root-of-the-server]__1ex6lhp._.js} +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +7 -7
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +49 -21
  101. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0y4a-0q.js → node_modules_html-to-image_es_index_0jg3fuk.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  105. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  106. package/.next/standalone/.next/server/pages/404.html +1 -1
  107. package/.next/standalone/.next/server/pages/500.html +1 -1
  108. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  109. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  110. package/.next/standalone/.next/static/chunks/{2z42u62k-8-_q.js → 04y44gpe0698_.js} +1 -1
  111. package/.next/standalone/.next/static/chunks/{3nj6g3xu9uy78.js → 0_75be6zseuxl.js} +1 -1
  112. package/.next/standalone/.next/static/chunks/0jtj8f7njt8w3.js +69 -0
  113. package/.next/standalone/.next/static/chunks/0mdiwb8ozs8j5.js +1 -0
  114. package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +2 -0
  115. package/.next/standalone/.next/static/chunks/1ua5armwfph8o.js +31 -0
  116. package/.next/standalone/.next/static/chunks/{277oc363p56n6.js → 1xjrv35s71etn.js} +2 -2
  117. package/.next/standalone/.next/static/chunks/{3ty6dhcuogout.js → 21cumi0uqy4mk.js} +1 -1
  118. package/.next/standalone/.next/static/chunks/{07_d165p5h5ys.js → 2611bbf4e_1cs.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/2hc_0--4nm0at.js +1 -0
  120. package/.next/standalone/.next/static/chunks/{1kvadxkgnapyj.js → 2l78594jx17e1.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{168k-8z6k7e8z.css → 3i27c3hcriawq.css} +1 -1
  122. package/.next/standalone/.next/static/chunks/{turbopack-3lrm4f20fz89b.js → turbopack-0vavqo2d-tlt3.js} +1 -1
  123. package/.next/standalone/app/api/audit/invite/route.ts +10 -1
  124. package/.next/standalone/app/api/audit/run/route.ts +35 -0
  125. package/.next/standalone/app/api/auth/login-request/route.ts +2 -2
  126. package/.next/standalone/app/api/auth/login-verify/route.ts +10 -2
  127. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +9 -1
  128. package/.next/standalone/app/audit/_components/audit-poster.tsx +11 -7
  129. package/.next/standalone/app/audit/_components/auth-dialog.tsx +6 -4
  130. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +23 -3
  131. package/.next/standalone/app/audit/_components/invite-dialog.tsx +6 -3
  132. package/.next/standalone/app/audit/_components/share-templates.ts +58 -28
  133. package/.next/standalone/app/audit/audit-styles.css +17 -22
  134. package/.next/standalone/app/globals.css +27 -2
  135. package/.next/standalone/app/policies/hooks-client.tsx +33 -24
  136. package/.next/standalone/components/reach-developers.tsx +10 -25
  137. package/.next/standalone/lib/auth/api-server-client.ts +5 -2
  138. package/.next/standalone/lib/client-telemetry.ts +4 -0
  139. package/.next/standalone/node_modules/@next/env/package.json +7 -8
  140. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  141. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  142. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  143. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  144. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  145. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  146. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  147. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  148. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  149. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  150. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  151. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  152. package/.next/standalone/node_modules/next/package.json +10 -252
  153. package/.next/standalone/package.json +6 -4
  154. package/.next/standalone/server.js +1 -1
  155. package/README.md +2 -2
  156. package/bin/failproofai.mjs +24 -5
  157. package/dist/cli.mjs +2330 -374
  158. package/lib/auth/api-server-client.ts +5 -2
  159. package/lib/client-telemetry.ts +4 -0
  160. package/package.json +6 -4
  161. package/scripts/launch.ts +30 -4
  162. package/scripts/postinstall.mjs +10 -1
  163. package/scripts/skew-log-filter.ts +46 -0
  164. package/scripts/validate-mdx.ts +139 -0
  165. package/src/audit/cli.ts +330 -0
  166. package/src/audit/open-browser.ts +69 -0
  167. package/src/auth/cli.ts +16 -13
  168. package/src/hooks/integrations.ts +22 -9
  169. package/src/hooks/types.ts +2 -2
  170. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +0 -3
  171. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +0 -3
  172. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +0 -3
  173. package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +0 -1
  174. package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +0 -41
  175. package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +0 -2
  176. package/.next/standalone/.next/static/chunks/29tg7deqmq32l.js +0 -1
  177. package/.next/standalone/.next/static/chunks/2h0dkzyy0vocp.js +0 -31
  178. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_buildManifest.js +0 -0
  179. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_clientMiddlewareManifest.js +0 -0
  180. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -16,7 +16,7 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // package.json
19
- var version2 = "0.0.11-beta.9";
19
+ var version2 = "0.0.12-beta.0";
20
20
  var init_package = () => {};
21
21
 
22
22
  // src/posthog-key.ts
@@ -656,6 +656,14 @@ function clearPolicies() {
656
656
  g[REGISTRY_KEY] = [];
657
657
  setIndexCache(null);
658
658
  }
659
+ function getAllPolicies() {
660
+ return [...getRegistry()];
661
+ }
662
+ function setAllPolicies(policies) {
663
+ const g = globalThis;
664
+ g[REGISTRY_KEY] = [...policies];
665
+ setIndexCache(null);
666
+ }
659
667
  var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__", DEFAULT_POLICY_NAMESPACE = "failproofai";
660
668
 
661
669
  // src/hooks/builtin-policies.ts
@@ -4883,6 +4891,10 @@ async function parseFileContent(fileContent, source) {
4883
4891
  entries.sort((a, b) => a.timestampMs - b.timestampMs);
4884
4892
  return { entries, rawLines, subagentIds: Array.from(subagentIdSet) };
4885
4893
  }
4894
+ async function parseLogContent(fileContent, source = "session") {
4895
+ const result = await parseFileContent(fileContent, source);
4896
+ return result.entries;
4897
+ }
4886
4898
  async function parseSessionLog(projectName, sessionId) {
4887
4899
  const projectDir = resolveProjectPath(projectName);
4888
4900
  const projectsPath = getClaudeProjectsPath();
@@ -6425,6 +6437,13 @@ function isMarkedHook(hook) {
6425
6437
  const cmd = typeof h.command === "string" ? h.command : "";
6426
6438
  return cmd.includes("failproofai") && cmd.includes("--hook");
6427
6439
  }
6440
+ function stripLegacyVersion(settings) {
6441
+ if ("version" in settings) {
6442
+ delete settings.version;
6443
+ return true;
6444
+ }
6445
+ return false;
6446
+ }
6428
6447
  function binaryExists(name) {
6429
6448
  try {
6430
6449
  const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
@@ -6819,10 +6838,7 @@ var init_integrations = __esm(() => {
6819
6838
  }
6820
6839
  },
6821
6840
  readSettings(settingsPath) {
6822
- const raw = readJsonFile(settingsPath);
6823
- if (raw.version === undefined)
6824
- raw.version = 1;
6825
- return raw;
6841
+ return readJsonFile(settingsPath);
6826
6842
  },
6827
6843
  writeSettings(settingsPath, settings) {
6828
6844
  writeJsonFile(settingsPath, settings);
@@ -6832,15 +6848,14 @@ var init_integrations = __esm(() => {
6832
6848
  return {
6833
6849
  type: "command",
6834
6850
  command,
6835
- timeout: 60000,
6851
+ timeout: 60,
6836
6852
  [FAILPROOFAI_HOOK_MARKER]: true
6837
6853
  };
6838
6854
  },
6839
6855
  isFailproofaiHook: isMarkedHook,
6840
6856
  writeHookEntries(settings, binaryPath, scope) {
6841
6857
  const s = settings;
6842
- if (s.version === undefined)
6843
- s.version = 1;
6858
+ stripLegacyVersion(s);
6844
6859
  if (!s.hooks)
6845
6860
  s.hooks = {};
6846
6861
  for (const eventType of CODEX_HOOK_EVENT_TYPES) {
@@ -6866,8 +6881,12 @@ var init_integrations = __esm(() => {
6866
6881
  },
6867
6882
  removeHooksFromFile(settingsPath) {
6868
6883
  const settings = this.readSettings(settingsPath);
6869
- if (!settings.hooks)
6884
+ const hadVersion = stripLegacyVersion(settings);
6885
+ if (!settings.hooks) {
6886
+ if (hadVersion)
6887
+ this.writeSettings(settingsPath, settings);
6870
6888
  return 0;
6889
+ }
6871
6890
  let removed = 0;
6872
6891
  for (const eventType of Object.keys(settings.hooks)) {
6873
6892
  const matchers = settings.hooks[eventType];
@@ -6888,7 +6907,9 @@ var init_integrations = __esm(() => {
6888
6907
  }
6889
6908
  if (Object.keys(settings.hooks).length === 0)
6890
6909
  delete settings.hooks;
6891
- this.writeSettings(settingsPath, settings);
6910
+ if (removed > 0 || hadVersion) {
6911
+ this.writeSettings(settingsPath, settings);
6912
+ }
6892
6913
  return removed;
6893
6914
  },
6894
6915
  hooksInstalledInSettings(scope, cwd) {
@@ -9039,7 +9060,7 @@ async function runLogin() {
9039
9060
  const nowSecs = Math.floor(Date.now() / 1000);
9040
9061
  const refreshUsable = existing.refresh_expires_at > nowSecs;
9041
9062
  if (refreshUsable) {
9042
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9063
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9043
9064
  source: "cli",
9044
9065
  status: "already_signed_in",
9045
9066
  user_id: existing.user.id
@@ -9072,7 +9093,7 @@ async function runLogin() {
9072
9093
  email = "";
9073
9094
  }
9074
9095
  if (!email) {
9075
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9096
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9076
9097
  source: "cli",
9077
9098
  status: "aborted_invalid_email"
9078
9099
  });
@@ -9091,7 +9112,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
9091
9112
  `);
9092
9113
  } catch (err) {
9093
9114
  const isApi = err instanceof AuthApiError;
9094
- trackHookEvent2(getInstanceId2(), "audit_otp_requested", {
9115
+ await trackHookEvent2(getInstanceId2(), "audit_otp_requested", {
9095
9116
  source: "cli",
9096
9117
  status: "failed",
9097
9118
  error_code: isApi ? err.code : "upstream_unreachable",
@@ -9118,7 +9139,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
9118
9139
  break;
9119
9140
  } catch (err) {
9120
9141
  const isApi = err instanceof AuthApiError;
9121
- trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
9142
+ await trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
9122
9143
  source: "cli",
9123
9144
  status: "failed",
9124
9145
  attempt: verifyAttempts,
@@ -9137,7 +9158,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
9137
9158
  }
9138
9159
  }
9139
9160
  if (!tokenResp) {
9140
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9161
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9141
9162
  source: "cli",
9142
9163
  status: "exhausted_attempts",
9143
9164
  attempts: verifyAttempts
@@ -9145,21 +9166,21 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
9145
9166
  throw new CliError("Too many bad codes — start over.");
9146
9167
  }
9147
9168
  writeAuth(authFromTokenResponse(tokenResp));
9148
- trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
9169
+ await trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
9149
9170
  source: "cli",
9150
9171
  status: "success",
9151
9172
  attempt: verifyAttempts,
9152
9173
  user_id: tokenResp.user.id,
9153
9174
  email: tokenResp.user.email
9154
9175
  });
9155
- trackHookEvent2(getInstanceId2(), "audit_user_identity_linked", {
9176
+ await trackHookEvent2(getInstanceId2(), "audit_user_identity_linked", {
9156
9177
  source: "cli",
9157
9178
  user_id: tokenResp.user.id,
9158
9179
  email: tokenResp.user.email,
9159
9180
  local_random_id: getInstanceId2(),
9160
9181
  $set: { email: tokenResp.user.email, user_id: tokenResp.user.id }
9161
9182
  });
9162
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9183
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
9163
9184
  source: "cli",
9164
9185
  status: "success",
9165
9186
  attempts: verifyAttempts,
@@ -9173,7 +9194,7 @@ ${GREEN}✓ signed in as ${tokenResp.user.email}${RESET}
9173
9194
  async function runLogout() {
9174
9195
  const existing = readAuth();
9175
9196
  if (!existing) {
9176
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
9197
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
9177
9198
  source: "cli",
9178
9199
  had_session: false,
9179
9200
  upstream: "noop"
@@ -9191,7 +9212,7 @@ async function runLogout() {
9191
9212
  }
9192
9213
  }
9193
9214
  deleteAuth();
9194
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
9215
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
9195
9216
  source: "cli",
9196
9217
  had_session: true,
9197
9218
  upstream,
@@ -9200,10 +9221,10 @@ async function runLogout() {
9200
9221
  process.stdout.write(`${GREEN}✓ signed out as ${existing.user.email}.${RESET}
9201
9222
  `);
9202
9223
  }
9203
- function runWhoami() {
9224
+ async function runWhoami() {
9204
9225
  const existing = readAuth();
9205
9226
  if (!existing) {
9206
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
9227
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
9207
9228
  source: "cli",
9208
9229
  authenticated: false
9209
9230
  });
@@ -9212,7 +9233,7 @@ function runWhoami() {
9212
9233
  process.exitCode = 1;
9213
9234
  return;
9214
9235
  }
9215
- trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
9236
+ await trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
9216
9237
  source: "cli",
9217
9238
  authenticated: true,
9218
9239
  user_id: existing.user.id
@@ -9267,163 +9288,2252 @@ EXAMPLES
9267
9288
  SUBCOMMANDS = new Set(["login", "logout", "whoami", "help"]);
9268
9289
  });
9269
9290
 
9270
- // src/hooks/manager.ts
9271
- import { execSync as execSync6 } from "node:child_process";
9272
- import { resolve as resolve12, basename as basename4 } from "node:path";
9273
- import { homedir as homedir20, platform as platform2, arch as arch2, release as release2, hostname as hostname2 } from "node:os";
9274
- function getSettingsPath2(scope, cwd) {
9275
- return claudeCode.getSettingsPath(scope, cwd);
9276
- }
9277
- function scopeLabel2(scope) {
9278
- switch (scope) {
9279
- case "user":
9280
- return `~/.claude/settings.json`;
9281
- case "project":
9282
- return `{cwd}/.claude/settings.json`;
9283
- case "local":
9284
- return `{cwd}/.claude/settings.local.json`;
9285
- }
9286
- }
9287
- function resolveFailproofaiBinary2() {
9288
- const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
9289
- if (override && override.trim())
9290
- return override.trim();
9291
+ // lib/claude-sessions.ts
9292
+ import { readdirSync as readdirSync8, statSync as statSync10, existsSync as existsSync14 } from "node:fs";
9293
+ import { join as join19, basename as basename4 } from "node:path";
9294
+ function getClaudeProjectsRoot() {
9295
+ return getClaudeProjectsPath();
9296
+ }
9297
+ function listClaudeProjects() {
9298
+ const root = getClaudeProjectsRoot();
9299
+ let entries;
9291
9300
  try {
9292
- const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
9293
- const result = execSync6(cmd, { encoding: "utf8" }).trim();
9294
- return result.split(`
9295
- `)[0].trim();
9301
+ entries = readdirSync8(root, { withFileTypes: true });
9296
9302
  } catch {
9297
- throw new CliError(`failproofai binary not found in PATH.
9298
- ` + "Install it globally first: npm install -g failproofai");
9299
- }
9300
- }
9301
- function validatePolicyNames2(names) {
9302
- const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
9303
- if (invalid.length > 0) {
9304
- const validList = [...VALID_POLICY_NAMES2].join(", ");
9305
- throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
9306
- ` + `Valid policies: ${validList}`);
9303
+ return [];
9307
9304
  }
9305
+ return entries.filter((e) => e.isDirectory()).map((e) => ({
9306
+ name: e.name,
9307
+ cwd: decodeFolderName(e.name),
9308
+ path: join19(root, e.name)
9309
+ }));
9308
9310
  }
9309
- function deduplicateScopes2(scopes, cwd) {
9310
- const seen = new Set;
9311
- return scopes.filter((s) => {
9312
- const p = getSettingsPath2(s, cwd);
9313
- if (seen.has(p))
9314
- return false;
9315
- seen.add(p);
9316
- return true;
9317
- });
9318
- }
9319
- function hooksInstalledInSettings2(scope, cwd) {
9320
- return claudeCode.hooksInstalledInSettings(scope, cwd);
9321
- }
9322
- async function installHooks2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli) {
9323
- if (policyNames !== undefined && policyNames.length > 0) {
9324
- const nonAllNames = policyNames.filter((n) => n !== "all");
9325
- if (nonAllNames.length > 0)
9326
- validatePolicyNames2(nonAllNames);
9327
- if (policyNames.includes("all") && nonAllNames.length > 0) {
9328
- throw new CliError(`"all" cannot be combined with specific policy names.
9329
- ` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
9330
- }
9311
+ function listClaudeTranscripts(project) {
9312
+ const out = [];
9313
+ let entries;
9314
+ try {
9315
+ entries = readdirSync8(project.path, { withFileTypes: true });
9316
+ } catch {
9317
+ return out;
9331
9318
  }
9332
- const selectedClis = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
9333
- for (const cliId of selectedClis) {
9334
- const integration = getIntegration(cliId);
9335
- if (!integration.scopes.includes(scope)) {
9319
+ for (const entry of entries) {
9320
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
9321
+ const sessionId = entry.name.slice(0, -".jsonl".length);
9322
+ if (!UUID_RE4.test(sessionId))
9323
+ continue;
9324
+ const transcriptPath = join19(project.path, entry.name);
9336
9325
  try {
9337
- await trackHookEvent2(getInstanceId2(), "scope_validation_failed", {
9338
- cli: cliId,
9339
- scope,
9340
- supported_scopes: integration.scopes
9326
+ const s = statSync10(transcriptPath);
9327
+ out.push({
9328
+ projectName: project.name,
9329
+ cwd: project.cwd,
9330
+ sessionId,
9331
+ transcriptPath,
9332
+ mtimeMs: s.mtimeMs,
9333
+ sizeBytes: s.size,
9334
+ isSubagent: false
9341
9335
  });
9342
9336
  } catch {}
9343
- throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
9337
+ } else if (entry.isDirectory() && UUID_RE4.test(entry.name)) {
9338
+ const subDir = join19(project.path, entry.name, "subagents");
9339
+ if (!existsSync14(subDir))
9340
+ continue;
9341
+ let subEntries;
9342
+ try {
9343
+ subEntries = readdirSync8(subDir, { withFileTypes: true });
9344
+ } catch {
9345
+ continue;
9346
+ }
9347
+ for (const sub of subEntries) {
9348
+ if (!sub.isFile() || !sub.name.endsWith(".jsonl"))
9349
+ continue;
9350
+ const agentId = sub.name.slice(0, -".jsonl".length);
9351
+ const transcriptPath = join19(subDir, sub.name);
9352
+ try {
9353
+ const s = statSync10(transcriptPath);
9354
+ out.push({
9355
+ projectName: project.name,
9356
+ cwd: project.cwd,
9357
+ sessionId: agentId,
9358
+ transcriptPath,
9359
+ mtimeMs: s.mtimeMs,
9360
+ sizeBytes: s.size,
9361
+ isSubagent: true
9362
+ });
9363
+ } catch {}
9364
+ }
9344
9365
  }
9345
9366
  }
9346
- const binaryPath = resolveFailproofaiBinary2();
9347
- const previousConfig = readScopedHooksConfig(scope, cwd);
9348
- const previousEnabled = new Set(previousConfig.enabledPolicies);
9349
- let selectedPolicies;
9350
- if (policyNames !== undefined) {
9351
- let incoming;
9352
- if (policyNames.length === 1 && policyNames[0] === "all") {
9353
- incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
9354
- } else {
9355
- incoming = policyNames;
9367
+ return out;
9368
+ }
9369
+ var UUID_RE4;
9370
+ var init_claude_sessions = __esm(() => {
9371
+ init_paths();
9372
+ UUID_RE4 = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
9373
+ });
9374
+
9375
+ // src/audit/types.ts
9376
+ var AUDIT_TOOL_RESULT_MAX_BYTES, AUDIT_EXAMPLE_MAX_CHARS = 80, AUDIT_MAX_EXAMPLES_PER_NAME = 3;
9377
+ var init_types2 = __esm(() => {
9378
+ AUDIT_TOOL_RESULT_MAX_BYTES = 64 * 1024;
9379
+ });
9380
+
9381
+ // src/audit/cli-adapters/shared.ts
9382
+ function truncateToUtf8Bytes(s, maxBytes) {
9383
+ const buf = Buffer.from(s, "utf-8");
9384
+ if (buf.byteLength <= maxBytes)
9385
+ return s;
9386
+ let end = maxBytes;
9387
+ while (end > 0 && (buf[end] & 192) === 128)
9388
+ end--;
9389
+ return buf.subarray(0, end).toString("utf-8");
9390
+ }
9391
+ function logEntriesToEvents(entries, ctx) {
9392
+ const events = [];
9393
+ for (const entry of entries) {
9394
+ if (entry.type !== "assistant")
9395
+ continue;
9396
+ for (const block of entry.message.content) {
9397
+ if (block.type !== "tool_use")
9398
+ continue;
9399
+ const rawName = block.name;
9400
+ const canonicalName = canonicalizeToolName(rawName, ctx.cli) ?? rawName;
9401
+ const canonicalInput = canonicalizeToolInput(canonicalName, block.input, ctx.cli);
9402
+ let toolResultText;
9403
+ if (block.result?.content) {
9404
+ toolResultText = truncateToUtf8Bytes(block.result.content, AUDIT_TOOL_RESULT_MAX_BYTES);
9405
+ }
9406
+ events.push({
9407
+ cli: ctx.cli,
9408
+ sessionId: ctx.sessionId,
9409
+ transcriptPath: ctx.transcriptPath,
9410
+ cwd: ctx.cwd,
9411
+ timestamp: entry.timestamp,
9412
+ toolName: canonicalName,
9413
+ rawToolName: rawName,
9414
+ toolInput: canonicalInput ?? {},
9415
+ toolResultText
9416
+ });
9356
9417
  }
9357
- selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
9358
- } else {
9359
- const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
9360
- selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
9361
9418
  }
9362
- const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
9363
- if (removeCustomHooks) {
9364
- delete configToWrite.customPoliciesPath;
9365
- } else if (customPoliciesPath) {
9366
- configToWrite.customPoliciesPath = resolve12(customPoliciesPath);
9367
- let validatedHooks = [];
9419
+ return events;
9420
+ }
9421
+ var init_shared = __esm(() => {
9422
+ init_tool_name_canonicalize();
9423
+ init_types2();
9424
+ });
9425
+
9426
+ // src/audit/cli-adapters/claude.ts
9427
+ import { readFile as readFile13 } from "node:fs/promises";
9428
+ async function listClaudeTranscriptMetadata(opts = {}) {
9429
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9430
+ const sinceMs = opts.sinceMs ?? 0;
9431
+ const out = [];
9432
+ for (const project of listClaudeProjects()) {
9433
+ if (projectFilter && !projectFilter.has(project.cwd))
9434
+ continue;
9435
+ let transcripts;
9368
9436
  try {
9369
- validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
9370
- } catch (err) {
9371
- const msg = err instanceof Error ? err.message : String(err);
9437
+ transcripts = listClaudeTranscripts(project);
9438
+ } catch {
9439
+ continue;
9440
+ }
9441
+ for (const t of transcripts) {
9442
+ if (t.mtimeMs < sinceMs)
9443
+ continue;
9444
+ out.push({
9445
+ cli: "claude",
9446
+ projectName: project.name,
9447
+ sessionId: t.sessionId,
9448
+ transcriptPath: t.transcriptPath,
9449
+ mtimeMs: t.mtimeMs,
9450
+ sizeBytes: t.sizeBytes
9451
+ });
9452
+ }
9453
+ }
9454
+ return out;
9455
+ }
9456
+ async function streamClaudeEvents(meta) {
9457
+ let content;
9458
+ try {
9459
+ content = await readFile13(meta.transcriptPath, "utf-8");
9460
+ } catch {
9461
+ return [];
9462
+ }
9463
+ const source = "session";
9464
+ let entries;
9465
+ try {
9466
+ entries = await parseLogContent(content, source);
9467
+ } catch {
9468
+ return [];
9469
+ }
9470
+ let cwd = "";
9471
+ for (const line of content.split(`
9472
+ `, 50)) {
9473
+ if (!line.trim())
9474
+ continue;
9475
+ try {
9476
+ const parsed = JSON.parse(line);
9477
+ if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
9478
+ cwd = parsed.cwd;
9479
+ break;
9480
+ }
9481
+ } catch {}
9482
+ }
9483
+ return logEntriesToEvents(entries, {
9484
+ cli: "claude",
9485
+ sessionId: meta.sessionId,
9486
+ transcriptPath: meta.transcriptPath,
9487
+ cwd
9488
+ });
9489
+ }
9490
+ var init_claude = __esm(() => {
9491
+ init_claude_sessions();
9492
+ init_log_entries();
9493
+ init_shared();
9494
+ });
9495
+
9496
+ // src/audit/cli-adapters/codex.ts
9497
+ import { statSync as statSync11 } from "node:fs";
9498
+ async function listCodexTranscriptMetadata(opts = {}) {
9499
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9500
+ const sinceMs = opts.sinceMs ?? 0;
9501
+ const out = [];
9502
+ const projects = await getCodexProjects();
9503
+ for (const project of projects) {
9504
+ const { cwd, sessions } = await getCodexSessionsByEncodedName(project.name);
9505
+ const effectiveCwd = cwd ?? "";
9506
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9507
+ continue;
9508
+ for (const s of sessions) {
9509
+ const mtimeMs = s.lastModified.getTime();
9510
+ if (mtimeMs < sinceMs)
9511
+ continue;
9512
+ let sizeBytes = 0;
9372
9513
  try {
9373
- await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
9374
- scope,
9375
- error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
9376
- });
9514
+ sizeBytes = statSync11(s.path).size;
9377
9515
  } catch {}
9378
- console.error(`Error: ${msg}`);
9379
- process.exit(1);
9516
+ if (!s.sessionId)
9517
+ continue;
9518
+ out.push({
9519
+ cli: "codex",
9520
+ projectName: project.name,
9521
+ sessionId: s.sessionId,
9522
+ transcriptPath: s.path,
9523
+ mtimeMs,
9524
+ sizeBytes
9525
+ });
9380
9526
  }
9381
- if (validatedHooks.length === 0) {
9527
+ }
9528
+ return out;
9529
+ }
9530
+ async function streamCodexEvents(meta) {
9531
+ const log = await getCodexSessionLog(meta.sessionId);
9532
+ if (!log)
9533
+ return [];
9534
+ return logEntriesToEvents(log.entries, {
9535
+ cli: "codex",
9536
+ sessionId: meta.sessionId,
9537
+ transcriptPath: meta.transcriptPath,
9538
+ cwd: log.cwd ?? ""
9539
+ });
9540
+ }
9541
+ var init_codex = __esm(() => {
9542
+ init_codex_projects();
9543
+ init_codex_sessions();
9544
+ init_shared();
9545
+ });
9546
+
9547
+ // src/audit/cli-adapters/copilot.ts
9548
+ import { statSync as statSync12 } from "node:fs";
9549
+ async function listCopilotTranscriptMetadata(opts = {}) {
9550
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9551
+ const sinceMs = opts.sinceMs ?? 0;
9552
+ const out = [];
9553
+ const projects = await getCopilotProjects();
9554
+ for (const project of projects) {
9555
+ const { cwd, sessions } = await getCopilotSessionsByEncodedName(project.name);
9556
+ const effectiveCwd = cwd ?? "";
9557
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9558
+ continue;
9559
+ for (const s of sessions) {
9560
+ const mtimeMs = s.lastModified.getTime();
9561
+ if (mtimeMs < sinceMs)
9562
+ continue;
9563
+ let sizeBytes = 0;
9382
9564
  try {
9383
- await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
9384
- scope,
9385
- error_type: "no_hooks_registered"
9386
- });
9565
+ sizeBytes = statSync12(s.path).size;
9387
9566
  } catch {}
9388
- console.error(`Error: no hooks registered in ${customPoliciesPath}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
9389
- process.exit(1);
9567
+ if (!s.sessionId)
9568
+ continue;
9569
+ out.push({
9570
+ cli: "copilot",
9571
+ projectName: project.name,
9572
+ sessionId: s.sessionId,
9573
+ transcriptPath: s.path,
9574
+ mtimeMs,
9575
+ sizeBytes
9576
+ });
9390
9577
  }
9391
- console.log(`
9392
- Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
9393
- }
9394
- writeScopedHooksConfig(configToWrite, scope, cwd);
9395
- console.log(`
9396
- Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
9397
- `);
9398
- if (removeCustomHooks) {
9399
- console.log("Custom hooks path cleared.");
9400
- } else if (configToWrite.customPoliciesPath) {
9401
- console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
9402
9578
  }
9403
- const writtenSettingsPaths = [];
9404
- for (const cliId of selectedClis) {
9405
- const integration = getIntegration(cliId);
9406
- const settingsPath = integration.getSettingsPath(scope, cwd);
9407
- try {
9408
- const settings = integration.readSettings(settingsPath);
9409
- integration.writeHookEntries(settings, binaryPath, scope);
9410
- integration.writeSettings(settingsPath, settings);
9411
- writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
9412
- } catch (err) {
9413
- const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message) ? "permission_denied" : err instanceof Error && /ENOENT|ENOTDIR/.test(err.message) ? "path_not_found" : "write_error";
9579
+ return out;
9580
+ }
9581
+ async function streamCopilotEvents(meta) {
9582
+ const log = await getCopilotSessionLog(meta.sessionId);
9583
+ if (!log)
9584
+ return [];
9585
+ return logEntriesToEvents(log.entries, {
9586
+ cli: "copilot",
9587
+ sessionId: meta.sessionId,
9588
+ transcriptPath: meta.transcriptPath,
9589
+ cwd: log.cwd ?? ""
9590
+ });
9591
+ }
9592
+ var init_copilot = __esm(() => {
9593
+ init_copilot_projects();
9594
+ init_copilot_sessions();
9595
+ init_shared();
9596
+ });
9597
+
9598
+ // src/audit/cli-adapters/cursor.ts
9599
+ import { statSync as statSync13 } from "node:fs";
9600
+ async function listCursorTranscriptMetadata(opts = {}) {
9601
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9602
+ const sinceMs = opts.sinceMs ?? 0;
9603
+ const out = [];
9604
+ const projects = await getCursorProjects();
9605
+ for (const project of projects) {
9606
+ const { cwd, sessions } = await getCursorSessionsByEncodedName(project.name);
9607
+ const effectiveCwd = cwd ?? "";
9608
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9609
+ continue;
9610
+ for (const s of sessions) {
9611
+ const mtimeMs = s.lastModified.getTime();
9612
+ if (mtimeMs < sinceMs)
9613
+ continue;
9614
+ let sizeBytes = 0;
9414
9615
  try {
9415
- await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
9416
- cli: cliId,
9417
- scope,
9418
- error_type: errorType
9419
- });
9616
+ sizeBytes = statSync13(s.path).size;
9420
9617
  } catch {}
9421
- throw err;
9618
+ if (!s.sessionId)
9619
+ continue;
9620
+ out.push({
9621
+ cli: "cursor",
9622
+ projectName: project.name,
9623
+ sessionId: s.sessionId,
9624
+ transcriptPath: s.path,
9625
+ mtimeMs,
9626
+ sizeBytes
9627
+ });
9422
9628
  }
9423
9629
  }
9630
+ return out;
9631
+ }
9632
+ async function streamCursorEvents(meta) {
9633
+ const log = await getCursorSessionLog(meta.sessionId);
9634
+ if (!log)
9635
+ return [];
9636
+ return logEntriesToEvents(log.entries, {
9637
+ cli: "cursor",
9638
+ sessionId: meta.sessionId,
9639
+ transcriptPath: meta.transcriptPath,
9640
+ cwd: log.cwd ?? ""
9641
+ });
9642
+ }
9643
+ var init_cursor = __esm(() => {
9644
+ init_cursor_projects();
9645
+ init_cursor_sessions();
9646
+ init_shared();
9647
+ });
9648
+
9649
+ // lib/opencode-sessions.ts
9650
+ import { execFileSync as execFileSync3 } from "node:child_process";
9651
+ function runOpenCodeDb2(sql) {
9424
9652
  try {
9425
- const newSet = new Set(selectedPolicies);
9426
- const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
9653
+ const stdout = execFileSync3("opencode", ["db", "--format", "json", sql], {
9654
+ encoding: "utf8",
9655
+ timeout: 5000,
9656
+ stdio: ["ignore", "pipe", "pipe"]
9657
+ });
9658
+ if (!stdout.trim())
9659
+ return [];
9660
+ const parsed = JSON.parse(stdout);
9661
+ if (!Array.isArray(parsed))
9662
+ return null;
9663
+ return parsed;
9664
+ } catch {
9665
+ return null;
9666
+ }
9667
+ }
9668
+ function isPlainObject(value) {
9669
+ return !!value && typeof value === "object" && !Array.isArray(value);
9670
+ }
9671
+ function parseDataColumn(raw) {
9672
+ if (typeof raw !== "string" || raw.length === 0)
9673
+ return null;
9674
+ try {
9675
+ const parsed = JSON.parse(raw);
9676
+ return isPlainObject(parsed) ? parsed : null;
9677
+ } catch {
9678
+ return null;
9679
+ }
9680
+ }
9681
+ function readContentText(data) {
9682
+ if (typeof data.text === "string")
9683
+ return data.text;
9684
+ if (typeof data.content === "string")
9685
+ return data.content;
9686
+ return "";
9687
+ }
9688
+ function translateMessage(msgRow, partRows, source) {
9689
+ const msgData = parseDataColumn(msgRow.data) ?? {};
9690
+ const role = typeof msgData.role === "string" ? msgData.role : "system";
9691
+ const date = new Date(msgRow.time_created);
9692
+ const timestamp = date.toISOString();
9693
+ const raw = { uuid: msgRow.id, parentUuid: null };
9694
+ const base = baseEntry(raw, timestamp, date, source);
9695
+ const content = [];
9696
+ let userText = "";
9697
+ for (const p of partRows) {
9698
+ const data = parseDataColumn(p.data);
9699
+ if (!data)
9700
+ continue;
9701
+ const type = typeof data.type === "string" ? data.type : "unknown";
9702
+ if (type === "text") {
9703
+ const text = readContentText(data);
9704
+ if (text) {
9705
+ content.push({ type: "text", text });
9706
+ userText += (userText ? `
9707
+ ` : "") + text;
9708
+ }
9709
+ continue;
9710
+ }
9711
+ if (type === "tool") {
9712
+ const toolName = typeof data.tool === "string" ? data.tool : typeof data.name === "string" ? data.name : "tool";
9713
+ const state = isPlainObject(data.state) ? data.state : null;
9714
+ const input = state && isPlainObject(state.input) ? state.input : isPlainObject(data.input) ? data.input : isPlainObject(data.args) ? data.args : {};
9715
+ const block = {
9716
+ type: "tool_use",
9717
+ id: p.id,
9718
+ name: toolName,
9719
+ input
9720
+ };
9721
+ const status = state && typeof state.status === "string" ? state.status : "";
9722
+ if (state && (status === "completed" || status === "error")) {
9723
+ const errorText = status === "error" && typeof state.error === "string" ? state.error : null;
9724
+ const rawOutput = errorText ?? state.output;
9725
+ const contentText = typeof rawOutput === "string" ? rawOutput : rawOutput != null ? JSON.stringify(rawOutput) : "";
9726
+ const time = isPlainObject(state.time) ? state.time : {};
9727
+ const startMs = typeof time.start === "number" ? time.start : p.time_created;
9728
+ const endMs = typeof time.end === "number" ? time.end : p.time_updated;
9729
+ const durationMs = Math.max(0, endMs - startMs);
9730
+ const date2 = new Date(endMs);
9731
+ block.result = {
9732
+ timestamp: date2.toISOString(),
9733
+ timestampFormatted: formatTimestamp(date2),
9734
+ content: contentText,
9735
+ durationMs,
9736
+ durationFormatted: formatDuration(durationMs)
9737
+ };
9738
+ }
9739
+ content.push(block);
9740
+ continue;
9741
+ }
9742
+ content.push({ type: "text", text: `[opencode ${type}]` });
9743
+ }
9744
+ if (role === "user") {
9745
+ const entry2 = {
9746
+ ...base,
9747
+ type: "user",
9748
+ message: { role: "user", content: userText }
9749
+ };
9750
+ return entry2;
9751
+ }
9752
+ if (role === "assistant") {
9753
+ const modelInfo = isPlainObject(msgData.model) ? msgData.model : null;
9754
+ const modelStr = modelInfo && typeof modelInfo.modelID === "string" ? modelInfo.modelID : undefined;
9755
+ const entry2 = {
9756
+ ...base,
9757
+ type: "assistant",
9758
+ message: { role: "assistant", content, model: modelStr }
9759
+ };
9760
+ return entry2;
9761
+ }
9762
+ const entry = {
9763
+ ...base,
9764
+ type: "system",
9765
+ raw: { id: msgRow.id, role, parts: content }
9766
+ };
9767
+ return entry;
9768
+ }
9769
+ async function getOpenCodeSessionLog(sessionId) {
9770
+ if (!sessionId || !/^[A-Za-z0-9_-]+$/.test(sessionId))
9771
+ return null;
9772
+ const sessions = runOpenCodeDb2(`SELECT id, project_id, slug, directory, title, time_created, time_updated FROM session WHERE id = '${sessionId}'`);
9773
+ if (!sessions || sessions.length === 0)
9774
+ return null;
9775
+ const session = sessions[0];
9776
+ const messages = runOpenCodeDb2(`SELECT id, session_id, time_created, time_updated, data FROM message WHERE session_id = '${sessionId}' ORDER BY time_created ASC`);
9777
+ const parts = runOpenCodeDb2(`SELECT id, message_id, session_id, time_created, time_updated, data FROM part WHERE session_id = '${sessionId}' ORDER BY time_created ASC`);
9778
+ if (!messages)
9779
+ return { entries: [], rawLines: [], cwd: session.directory ?? undefined, filePath: `opencode://${sessionId}` };
9780
+ const partsByMessage = new Map;
9781
+ for (const p of parts ?? []) {
9782
+ let bucket = partsByMessage.get(p.message_id);
9783
+ if (!bucket) {
9784
+ bucket = [];
9785
+ partsByMessage.set(p.message_id, bucket);
9786
+ }
9787
+ bucket.push(p);
9788
+ }
9789
+ const entries = [];
9790
+ const rawLines = [];
9791
+ for (const msg of messages) {
9792
+ const partRows = partsByMessage.get(msg.id) ?? [];
9793
+ entries.push(translateMessage(msg, partRows, "session"));
9794
+ const data = parseDataColumn(msg.data);
9795
+ rawLines.push({
9796
+ id: msg.id,
9797
+ session_id: msg.session_id,
9798
+ time_created: msg.time_created,
9799
+ data: data ?? msg.data
9800
+ });
9801
+ }
9802
+ return {
9803
+ entries,
9804
+ rawLines,
9805
+ cwd: session.directory ?? undefined,
9806
+ filePath: `opencode://${sessionId}`
9807
+ };
9808
+ }
9809
+ var getCachedOpenCodeSessionLog;
9810
+ var init_opencode_sessions = __esm(() => {
9811
+ init_log_entries();
9812
+ getCachedOpenCodeSessionLog = runtimeCache((sessionId) => getOpenCodeSessionLog(sessionId), 30, { maxSize: 50 });
9813
+ });
9814
+
9815
+ // src/audit/cli-adapters/opencode.ts
9816
+ async function listOpenCodeTranscriptMetadata(opts = {}) {
9817
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9818
+ const sinceMs = opts.sinceMs ?? 0;
9819
+ const out = [];
9820
+ const projects = await getOpenCodeProjects();
9821
+ for (const project of projects) {
9822
+ const { cwd, sessions } = await getOpenCodeSessionsByEncodedName(project.name);
9823
+ const effectiveCwd = cwd ?? "";
9824
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9825
+ continue;
9826
+ for (const s of sessions) {
9827
+ const mtimeMs = s.lastModified.getTime();
9828
+ if (mtimeMs < sinceMs)
9829
+ continue;
9830
+ if (!s.sessionId)
9831
+ continue;
9832
+ out.push({
9833
+ cli: "opencode",
9834
+ projectName: project.name,
9835
+ sessionId: s.sessionId,
9836
+ transcriptPath: s.path,
9837
+ mtimeMs,
9838
+ sizeBytes: 0
9839
+ });
9840
+ }
9841
+ }
9842
+ return out;
9843
+ }
9844
+ async function streamOpenCodeEvents(meta) {
9845
+ const log = await getOpenCodeSessionLog(meta.sessionId);
9846
+ if (!log)
9847
+ return [];
9848
+ return logEntriesToEvents(log.entries, {
9849
+ cli: "opencode",
9850
+ sessionId: meta.sessionId,
9851
+ transcriptPath: meta.transcriptPath,
9852
+ cwd: log.cwd ?? ""
9853
+ });
9854
+ }
9855
+ var init_opencode = __esm(() => {
9856
+ init_opencode_projects();
9857
+ init_opencode_sessions();
9858
+ init_shared();
9859
+ });
9860
+
9861
+ // src/audit/cli-adapters/pi.ts
9862
+ import { statSync as statSync14 } from "node:fs";
9863
+ async function listPiTranscriptMetadata(opts = {}) {
9864
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9865
+ const sinceMs = opts.sinceMs ?? 0;
9866
+ const out = [];
9867
+ const projects = await getPiProjects();
9868
+ for (const project of projects) {
9869
+ const { cwd, sessions } = await getPiSessionsByEncodedName(project.name);
9870
+ const effectiveCwd = cwd ?? "";
9871
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9872
+ continue;
9873
+ for (const s of sessions) {
9874
+ const mtimeMs = s.lastModified.getTime();
9875
+ if (mtimeMs < sinceMs)
9876
+ continue;
9877
+ let sizeBytes = 0;
9878
+ try {
9879
+ sizeBytes = statSync14(s.path).size;
9880
+ } catch {}
9881
+ if (!s.sessionId)
9882
+ continue;
9883
+ out.push({
9884
+ cli: "pi",
9885
+ projectName: project.name,
9886
+ sessionId: s.sessionId,
9887
+ transcriptPath: s.path,
9888
+ mtimeMs,
9889
+ sizeBytes
9890
+ });
9891
+ }
9892
+ }
9893
+ return out;
9894
+ }
9895
+ async function streamPiEvents(meta) {
9896
+ const log = await getPiSessionLog(meta.sessionId);
9897
+ if (!log)
9898
+ return [];
9899
+ return logEntriesToEvents(log.entries, {
9900
+ cli: "pi",
9901
+ sessionId: meta.sessionId,
9902
+ transcriptPath: meta.transcriptPath,
9903
+ cwd: log.cwd ?? ""
9904
+ });
9905
+ }
9906
+ var init_pi = __esm(() => {
9907
+ init_pi_projects();
9908
+ init_pi_sessions();
9909
+ init_shared();
9910
+ });
9911
+
9912
+ // src/audit/cli-adapters/gemini.ts
9913
+ import { statSync as statSync15 } from "node:fs";
9914
+ async function listGeminiTranscriptMetadata(opts = {}) {
9915
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
9916
+ const sinceMs = opts.sinceMs ?? 0;
9917
+ const out = [];
9918
+ const projects = await getGeminiProjects();
9919
+ for (const project of projects) {
9920
+ const { cwd, sessions } = await getGeminiSessionsByEncodedName(project.name);
9921
+ const effectiveCwd = cwd ?? "";
9922
+ if (projectFilter && !projectFilter.has(effectiveCwd))
9923
+ continue;
9924
+ for (const s of sessions) {
9925
+ const mtimeMs = s.lastModified.getTime();
9926
+ if (mtimeMs < sinceMs)
9927
+ continue;
9928
+ let sizeBytes = 0;
9929
+ try {
9930
+ sizeBytes = statSync15(s.path).size;
9931
+ } catch {}
9932
+ if (!s.sessionId)
9933
+ continue;
9934
+ out.push({
9935
+ cli: "gemini",
9936
+ projectName: project.name,
9937
+ sessionId: s.sessionId,
9938
+ transcriptPath: s.path,
9939
+ mtimeMs,
9940
+ sizeBytes
9941
+ });
9942
+ }
9943
+ }
9944
+ return out;
9945
+ }
9946
+ async function streamGeminiEvents(meta) {
9947
+ const log = await getGeminiSessionLog(meta.sessionId);
9948
+ if (!log)
9949
+ return [];
9950
+ return logEntriesToEvents(log.entries, {
9951
+ cli: "gemini",
9952
+ sessionId: meta.sessionId,
9953
+ transcriptPath: meta.transcriptPath,
9954
+ cwd: log.cwd ?? ""
9955
+ });
9956
+ }
9957
+ var init_gemini = __esm(() => {
9958
+ init_gemini_projects();
9959
+ init_gemini_sessions();
9960
+ init_shared();
9961
+ });
9962
+
9963
+ // src/audit/cli-adapters/index.ts
9964
+ var ADAPTERS;
9965
+ var init_cli_adapters = __esm(() => {
9966
+ init_claude();
9967
+ init_codex();
9968
+ init_copilot();
9969
+ init_cursor();
9970
+ init_opencode();
9971
+ init_pi();
9972
+ init_gemini();
9973
+ ADAPTERS = {
9974
+ claude: {
9975
+ cli: "claude",
9976
+ listTranscripts: listClaudeTranscriptMetadata,
9977
+ streamEvents: streamClaudeEvents
9978
+ },
9979
+ codex: {
9980
+ cli: "codex",
9981
+ listTranscripts: listCodexTranscriptMetadata,
9982
+ streamEvents: streamCodexEvents
9983
+ },
9984
+ copilot: {
9985
+ cli: "copilot",
9986
+ listTranscripts: listCopilotTranscriptMetadata,
9987
+ streamEvents: streamCopilotEvents
9988
+ },
9989
+ cursor: {
9990
+ cli: "cursor",
9991
+ listTranscripts: listCursorTranscriptMetadata,
9992
+ streamEvents: streamCursorEvents
9993
+ },
9994
+ opencode: {
9995
+ cli: "opencode",
9996
+ listTranscripts: listOpenCodeTranscriptMetadata,
9997
+ streamEvents: streamOpenCodeEvents
9998
+ },
9999
+ pi: {
10000
+ cli: "pi",
10001
+ listTranscripts: listPiTranscriptMetadata,
10002
+ streamEvents: streamPiEvents
10003
+ },
10004
+ gemini: {
10005
+ cli: "gemini",
10006
+ listTranscripts: listGeminiTranscriptMetadata,
10007
+ streamEvents: streamGeminiEvents
10008
+ }
10009
+ };
10010
+ });
10011
+
10012
+ // src/audit/detectors/redundant-cd-cwd.ts
10013
+ var redundantCdCwd;
10014
+ var init_redundant_cd_cwd = __esm(() => {
10015
+ redundantCdCwd = {
10016
+ name: "redundant-cd-cwd",
10017
+ description: "Bash commands prefixed with `cd <cwd> && …` even though commands already run in cwd.",
10018
+ category: "Wasteful",
10019
+ severity: "info",
10020
+ displayTitle: "Prepended cd <cwd> before commands",
10021
+ impact: "Pure waste — your agent's shell already runs in `cwd`.",
10022
+ detect(event) {
10023
+ if (event.toolName !== "Bash")
10024
+ return null;
10025
+ const command = event.toolInput.command;
10026
+ if (typeof command !== "string" || !event.cwd)
10027
+ return null;
10028
+ const trimmed = command.trimStart();
10029
+ const match = /^cd\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*&&\s*([\s\S]+)$/.exec(trimmed);
10030
+ if (!match)
10031
+ return null;
10032
+ const path3 = (match[1] ?? match[2] ?? match[3] ?? "").replace(/\/+$/, "");
10033
+ const cwd = event.cwd.replace(/\/+$/, "");
10034
+ if (path3 !== cwd)
10035
+ return null;
10036
+ const rest = match[4].trim();
10037
+ return { example: `cd ${path3} && ${rest}` };
10038
+ }
10039
+ };
10040
+ });
10041
+
10042
+ // src/audit/detectors/prefer-edit-over-read-cat.ts
10043
+ var SOURCE_EXT_RE, preferEditOverReadCat;
10044
+ var init_prefer_edit_over_read_cat = __esm(() => {
10045
+ SOURCE_EXT_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|swift|rb|php|c|h|cc|cpp|hpp|cs|scala|sh|bash|zsh|json|yaml|yml|toml|md|txt|sql|html|css|scss|sass)$/i;
10046
+ preferEditOverReadCat = {
10047
+ name: "prefer-edit-over-read-cat",
10048
+ description: "Bash `cat`/`head`/`tail`/`less`/`more` on a single source file — use Read.",
10049
+ category: "Wasteful",
10050
+ severity: "info",
10051
+ displayTitle: "Used `cat`/`head`/`tail` on a source file",
10052
+ impact: "Burns tokens; the Read tool returns content directly without going through Bash output.",
10053
+ detect(event) {
10054
+ if (event.toolName !== "Bash")
10055
+ return null;
10056
+ const command = event.toolInput.command;
10057
+ if (typeof command !== "string")
10058
+ return null;
10059
+ const cmd = command.trim();
10060
+ if (/[|<>;&`$()]/.test(cmd))
10061
+ return null;
10062
+ const match = /^(cat|head|tail|less|more)\s+(?:-\S+\s+)*(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
10063
+ if (!match)
10064
+ return null;
10065
+ const path3 = match[2] ?? match[3] ?? match[4] ?? "";
10066
+ if (!path3)
10067
+ return null;
10068
+ if (/(?:^|\/)\.env(?:\..+)?$/.test(path3))
10069
+ return null;
10070
+ if (!SOURCE_EXT_RE.test(path3))
10071
+ return null;
10072
+ return { example: cmd };
10073
+ }
10074
+ };
10075
+ });
10076
+
10077
+ // src/audit/detectors/prefer-edit-over-sed-awk.ts
10078
+ var preferEditOverSedAwk;
10079
+ var init_prefer_edit_over_sed_awk = __esm(() => {
10080
+ preferEditOverSedAwk = {
10081
+ name: "prefer-edit-over-sed-awk",
10082
+ description: "Bash `sed -i`/`awk` in-place edits — use Edit.",
10083
+ category: "Wasteful",
10084
+ severity: "info",
10085
+ displayTitle: "Used sed -i or awk for an in-place edit",
10086
+ impact: "Edit tool is safer and produces a diff the agent can verify.",
10087
+ detect(event) {
10088
+ if (event.toolName !== "Bash")
10089
+ return null;
10090
+ const command = event.toolInput.command;
10091
+ if (typeof command !== "string")
10092
+ return null;
10093
+ const cmd = command.trim();
10094
+ if (/(?:^|\s|;|&&|\|\|)sed\b[^|]*\s-i(?=\b|['"])/.test(cmd)) {
10095
+ return { example: cmd };
10096
+ }
10097
+ if (/(?:^|\s|;|&&|\|\|)awk\b[^|]*\s>\s*\S+/.test(cmd) && !/\|/.test(cmd)) {
10098
+ return { example: cmd };
10099
+ }
10100
+ return null;
10101
+ }
10102
+ };
10103
+ });
10104
+
10105
+ // src/audit/detectors/prefer-write-over-heredoc.ts
10106
+ var preferWriteOverHeredoc;
10107
+ var init_prefer_write_over_heredoc = __esm(() => {
10108
+ preferWriteOverHeredoc = {
10109
+ name: "prefer-write-over-heredoc",
10110
+ description: "Bash heredoc / `echo > file` writing multi-line content — use Write.",
10111
+ category: "Wasteful",
10112
+ severity: "info",
10113
+ displayTitle: "Used heredoc / `echo > file` to write a multi-line file",
10114
+ impact: "Write tool handles escaping and is verifiable.",
10115
+ detect(event) {
10116
+ if (event.toolName !== "Bash")
10117
+ return null;
10118
+ const command = event.toolInput.command;
10119
+ if (typeof command !== "string")
10120
+ return null;
10121
+ const cmd = command;
10122
+ if (/<<-?\s*['"]?[A-Za-z_][A-Za-z0-9_]*['"]?\s*>\s*\S/.test(cmd)) {
10123
+ const summary = cmd.replace(/\s+/g, " ").trim().slice(0, 160);
10124
+ return { example: summary };
10125
+ }
10126
+ if (/(?:^|\s|;|&&|\|\|)(?:echo|printf)\s+["'][^"']*\n[^"']*["']\s*>\s*\S/.test(cmd)) {
10127
+ const summary = cmd.replace(/\s+/g, " ").trim().slice(0, 160);
10128
+ return { example: summary };
10129
+ }
10130
+ return null;
10131
+ }
10132
+ };
10133
+ });
10134
+
10135
+ // src/audit/detectors/sleep-polling-loop.ts
10136
+ var SLEEP_THRESHOLD_SECONDS = 30, sleepPollingLoop;
10137
+ var init_sleep_polling_loop = __esm(() => {
10138
+ sleepPollingLoop = {
10139
+ name: "sleep-polling-loop",
10140
+ description: "Bash long `sleep` or while-sleep polling loops.",
10141
+ category: "Wasteful",
10142
+ severity: "info",
10143
+ displayTitle: "Used a long sleep or while-sleep polling loop",
10144
+ impact: "Burns wall-clock; better to wait for an explicit signal.",
10145
+ detect(event) {
10146
+ if (event.toolName !== "Bash")
10147
+ return null;
10148
+ const command = event.toolInput.command;
10149
+ if (typeof command !== "string")
10150
+ return null;
10151
+ const cmd = command;
10152
+ if (/\bwhile\b[\s\S]*?\bsleep\b[\s\S]*?\bdone\b/.test(cmd)) {
10153
+ return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
10154
+ }
10155
+ const match = /\bsleep\s+(\d+(?:\.\d+)?)(m|h|d)?\b/.exec(cmd);
10156
+ if (match) {
10157
+ const n = parseFloat(match[1]);
10158
+ const unit = match[2] ?? "s";
10159
+ const seconds = unit === "m" ? n * 60 : unit === "h" ? n * 3600 : unit === "d" ? n * 86400 : n;
10160
+ if (seconds >= SLEEP_THRESHOLD_SECONDS) {
10161
+ return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
10162
+ }
10163
+ }
10164
+ return null;
10165
+ }
10166
+ };
10167
+ });
10168
+
10169
+ // src/audit/detectors/find-from-root.ts
10170
+ var RISKY_ROOTS, findFromRoot;
10171
+ var init_find_from_root = __esm(() => {
10172
+ RISKY_ROOTS = ["/", "/home", "/usr", "/etc", "/var", "/opt", "/Users"];
10173
+ findFromRoot = {
10174
+ name: "find-from-root",
10175
+ description: "Bash `find` against `/`, `/home`, `/usr`, etc. — scope to cwd instead.",
10176
+ category: "Risky",
10177
+ severity: "warn",
10178
+ displayTitle: "Ran find from /, /home, /usr, etc.",
10179
+ impact: "Filesystem-wide finds exhaust resources and rarely return useful results.",
10180
+ detect(event) {
10181
+ if (event.toolName !== "Bash")
10182
+ return null;
10183
+ const command = event.toolInput.command;
10184
+ if (typeof command !== "string")
10185
+ return null;
10186
+ const cmd = command.trim();
10187
+ const match = /(?:^|[\s;|&])find\s+(?:-\S+\s+)*("[^"]+"|'[^']+'|\S+)/.exec(cmd);
10188
+ if (!match)
10189
+ return null;
10190
+ const raw = match[1].replace(/^["']|["']$/g, "");
10191
+ const stripped = raw.replace(/\/+$/, "") || "/";
10192
+ if (!RISKY_ROOTS.includes(stripped))
10193
+ return null;
10194
+ return { example: cmd.slice(0, 160) };
10195
+ }
10196
+ };
10197
+ });
10198
+
10199
+ // src/audit/detectors/git-commit-no-verify.ts
10200
+ var gitCommitNoVerify;
10201
+ var init_git_commit_no_verify = __esm(() => {
10202
+ gitCommitNoVerify = {
10203
+ name: "git-commit-no-verify",
10204
+ description: "git commit invoked with --no-verify / -n, skipping hooks.",
10205
+ category: "Risky",
10206
+ severity: "warn",
10207
+ displayTitle: "Committed with --no-verify",
10208
+ impact: "Skips pre-commit hooks that exist to catch broken or unsafe code.",
10209
+ detect(event) {
10210
+ if (event.toolName !== "Bash")
10211
+ return null;
10212
+ const command = event.toolInput.command;
10213
+ if (typeof command !== "string")
10214
+ return null;
10215
+ const cmd = command;
10216
+ if (!/\bgit\s+commit\b/.test(cmd))
10217
+ return null;
10218
+ if (/\s--no-verify\b/.test(cmd) || /\s-n\b/.test(cmd)) {
10219
+ return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
10220
+ }
10221
+ return null;
10222
+ }
10223
+ };
10224
+ });
10225
+
10226
+ // src/audit/detectors/reread-after-edit.ts
10227
+ function getState(state) {
10228
+ let s = state[STATE_KEY];
10229
+ if (!s) {
10230
+ s = { countdown: new Map };
10231
+ state[STATE_KEY] = s;
10232
+ }
10233
+ return s;
10234
+ }
10235
+ var STATE_KEY = "rereadAfterEdit", WINDOW = 5, rereadAfterEdit;
10236
+ var init_reread_after_edit = __esm(() => {
10237
+ rereadAfterEdit = {
10238
+ name: "reread-after-edit",
10239
+ description: "Read of a file that was just Edit'd or Write'n in the same session.",
10240
+ category: "Wasteful",
10241
+ severity: "info",
10242
+ displayTitle: "Re-read a file it just edited",
10243
+ impact: "Edit/Write already returned the updated content; the second Read is wasted tokens.",
10244
+ detect(event, sessionState) {
10245
+ const state = getState(sessionState);
10246
+ const filePath = event.toolInput.file_path;
10247
+ const pathStr = typeof filePath === "string" ? filePath : null;
10248
+ for (const [key, n] of state.countdown) {
10249
+ if (n <= 1)
10250
+ state.countdown.delete(key);
10251
+ else
10252
+ state.countdown.set(key, n - 1);
10253
+ }
10254
+ if (!pathStr)
10255
+ return null;
10256
+ if (event.toolName === "Edit" || event.toolName === "Write") {
10257
+ state.countdown.set(pathStr, WINDOW);
10258
+ return null;
10259
+ }
10260
+ if (event.toolName === "Read") {
10261
+ if (state.countdown.has(pathStr)) {
10262
+ state.countdown.delete(pathStr);
10263
+ return { example: `Read ${pathStr} immediately after Edit/Write` };
10264
+ }
10265
+ }
10266
+ return null;
10267
+ }
10268
+ };
10269
+ });
10270
+
10271
+ // src/audit/detectors/index.ts
10272
+ var AUDIT_DETECTORS;
10273
+ var init_detectors = __esm(() => {
10274
+ init_redundant_cd_cwd();
10275
+ init_prefer_edit_over_read_cat();
10276
+ init_prefer_edit_over_sed_awk();
10277
+ init_prefer_write_over_heredoc();
10278
+ init_sleep_polling_loop();
10279
+ init_find_from_root();
10280
+ init_git_commit_no_verify();
10281
+ init_reread_after_edit();
10282
+ AUDIT_DETECTORS = [
10283
+ redundantCdCwd,
10284
+ preferEditOverReadCat,
10285
+ preferEditOverSedAwk,
10286
+ preferWriteOverHeredoc,
10287
+ sleepPollingLoop,
10288
+ findFromRoot,
10289
+ gitCommitNoVerify,
10290
+ rereadAfterEdit
10291
+ ];
10292
+ });
10293
+
10294
+ // src/audit/features.ts
10295
+ function shortName(name) {
10296
+ const slash = name.indexOf("/");
10297
+ return slash >= 0 ? name.slice(slash + 1) : name;
10298
+ }
10299
+ function severityForBuiltin(name) {
10300
+ const n = shortName(name);
10301
+ if (n.startsWith("sanitize-"))
10302
+ return "sanitize";
10303
+ if (n.startsWith("block-"))
10304
+ return "deny";
10305
+ if (n.startsWith("warn-") || n.startsWith("protect-") || n.startsWith("prefer-") || n.startsWith("require-"))
10306
+ return "warn";
10307
+ return "deny";
10308
+ }
10309
+ function emptyWeights() {
10310
+ return {
10311
+ optimist: 0,
10312
+ cowboy: 0,
10313
+ explorer: 0,
10314
+ goldfish: 0,
10315
+ architect: 0,
10316
+ precision: 0,
10317
+ hammer: 0,
10318
+ ghost: 0
10319
+ };
10320
+ }
10321
+ var MAPPABLE_KEYS, ARCHITECT_CAUTION_SIGNALS, MIN_BASELINE = 0.05, EMPIRICAL_FIRING_SHARE, BASELINE_SHARE;
10322
+ var init_features = __esm(() => {
10323
+ MAPPABLE_KEYS = [
10324
+ "cowboy",
10325
+ "explorer",
10326
+ "ghost",
10327
+ "optimist",
10328
+ "hammer",
10329
+ "architect"
10330
+ ];
10331
+ ARCHITECT_CAUTION_SIGNALS = new Set(["reread-after-edit", "redundant-cd-cwd"]);
10332
+ EMPIRICAL_FIRING_SHARE = {
10333
+ explorer: 0.38,
10334
+ architect: 0.33,
10335
+ hammer: 0.11,
10336
+ cowboy: 0.11,
10337
+ optimist: 0.07,
10338
+ ghost: 0.01,
10339
+ precision: 0,
10340
+ goldfish: 0
10341
+ };
10342
+ BASELINE_SHARE = (() => {
10343
+ const out = emptyWeights();
10344
+ for (const k of MAPPABLE_KEYS)
10345
+ out[k] = Math.max(EMPIRICAL_FIRING_SHARE[k], MIN_BASELINE);
10346
+ return out;
10347
+ })();
10348
+ });
10349
+
10350
+ // src/audit/cache.ts
10351
+ import { createHash } from "node:crypto";
10352
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6, chmodSync as chmodSync2 } from "node:fs";
10353
+ import { join as join20 } from "node:path";
10354
+ import { homedir as homedir20 } from "node:os";
10355
+ function getEngineVersion() {
10356
+ if (cachedEngineVersion)
10357
+ return cachedEngineVersion;
10358
+ const blob = BUILTIN_POLICIES.map((p) => `${p.name}|${p.fn.toString()}`).sort().join(`
10359
+ `);
10360
+ cachedEngineVersion = createHash("sha1").update(blob).digest("hex").slice(0, 16);
10361
+ return cachedEngineVersion;
10362
+ }
10363
+ function getDetectorVersion() {
10364
+ if (cachedDetectorVersion)
10365
+ return cachedDetectorVersion;
10366
+ const blob = AUDIT_DETECTORS.map((d) => `${d.name}|${d.detect.toString()}`).sort().join(`
10367
+ `);
10368
+ cachedDetectorVersion = createHash("sha1").update(blob).digest("hex").slice(0, 16);
10369
+ return cachedDetectorVersion;
10370
+ }
10371
+ function getCachePathFor(transcriptPath) {
10372
+ const root = join20(homedir20(), ".failproofai", "cache", "audit");
10373
+ const key = createHash("sha1").update(transcriptPath).digest("hex");
10374
+ return join20(root, `${key}.json`);
10375
+ }
10376
+ function readCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes) {
10377
+ if (sizeBytes === 0)
10378
+ return null;
10379
+ const cachePath = getCachePathFor(transcriptPath);
10380
+ if (!existsSync15(cachePath))
10381
+ return null;
10382
+ try {
10383
+ const raw = readFileSync11(cachePath, "utf-8");
10384
+ const entry = JSON.parse(raw);
10385
+ if (entry.schemaVersion !== CACHE_SCHEMA_VERSION)
10386
+ return null;
10387
+ if (entry.mtimeMs !== mtimeMs)
10388
+ return null;
10389
+ if (entry.sizeBytes !== sizeBytes)
10390
+ return null;
10391
+ if (entry.engineVersion !== getEngineVersion())
10392
+ return null;
10393
+ if (entry.detectorVersion !== getDetectorVersion())
10394
+ return null;
10395
+ if (!Number.isFinite(entry.cachedAt) || Date.now() - entry.cachedAt > CACHE_TTL_MS)
10396
+ return null;
10397
+ return entry.result ?? null;
10398
+ } catch {
10399
+ return null;
10400
+ }
10401
+ }
10402
+ function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result) {
10403
+ if (sizeBytes === 0)
10404
+ return;
10405
+ const cachePath = getCachePathFor(transcriptPath);
10406
+ try {
10407
+ mkdirSync7(join20(homedir20(), ".failproofai", "cache", "audit"), { recursive: true });
10408
+ const entry = {
10409
+ schemaVersion: CACHE_SCHEMA_VERSION,
10410
+ cachedAt: Date.now(),
10411
+ mtimeMs,
10412
+ sizeBytes,
10413
+ engineVersion: getEngineVersion(),
10414
+ detectorVersion: getDetectorVersion(),
10415
+ result
10416
+ };
10417
+ writeFileSync6(cachePath, JSON.stringify(entry), { encoding: "utf-8", mode: 384 });
10418
+ try {
10419
+ chmodSync2(cachePath, 384);
10420
+ } catch {}
10421
+ } catch {}
10422
+ }
10423
+ var cachedEngineVersion = null, cachedDetectorVersion = null, CACHE_SCHEMA_VERSION = 3, CACHE_TTL_MS;
10424
+ var init_cache = __esm(() => {
10425
+ init_builtin_policies();
10426
+ init_detectors();
10427
+ CACHE_TTL_MS = 7 * 24 * 60 * 60000;
10428
+ });
10429
+
10430
+ // src/audit/replay.ts
10431
+ function initReplay() {
10432
+ if (initialized)
10433
+ return;
10434
+ savedSnapshot = getAllPolicies();
10435
+ clearPolicies();
10436
+ const enabled = BUILTIN_POLICIES.map((p) => p.name).filter((n) => !SKIP_POLICIES.has(normalizePolicyName(n)));
10437
+ registerBuiltinPolicies(enabled);
10438
+ initialized = true;
10439
+ }
10440
+ function restoreReplay() {
10441
+ if (!initialized)
10442
+ return;
10443
+ if (savedSnapshot !== null) {
10444
+ setAllPolicies(savedSnapshot);
10445
+ savedSnapshot = null;
10446
+ }
10447
+ initialized = false;
10448
+ }
10449
+ async function replayEvent(event) {
10450
+ if (!initialized)
10451
+ initReplay();
10452
+ const session = {
10453
+ sessionId: event.sessionId,
10454
+ transcriptPath: event.transcriptPath,
10455
+ cwd: event.cwd,
10456
+ cli: event.cli
10457
+ };
10458
+ const baseToolPayload = {
10459
+ tool_name: event.toolName,
10460
+ tool_input: event.toolInput,
10461
+ session_id: event.sessionId,
10462
+ cwd: event.cwd,
10463
+ transcript_path: event.transcriptPath
10464
+ };
10465
+ const out = [];
10466
+ const pre = await evaluatePolicies("PreToolUse", baseToolPayload, session);
10467
+ collectHits(pre, "PreToolUse", out);
10468
+ if (event.toolResultText !== undefined) {
10469
+ const postPayload = { ...baseToolPayload, tool_response: event.toolResultText };
10470
+ const post = await evaluatePolicies("PostToolUse", postPayload, session);
10471
+ collectHits(post, "PostToolUse", out);
10472
+ }
10473
+ return out;
10474
+ }
10475
+ function collectHits(result, eventType, out) {
10476
+ const names = result.policyNames && result.policyNames.length > 0 ? result.policyNames : result.policyName ? [result.policyName] : [];
10477
+ for (const name of names) {
10478
+ out.push({
10479
+ policyName: name,
10480
+ decision: result.decision,
10481
+ reason: result.reason,
10482
+ eventType
10483
+ });
10484
+ }
10485
+ }
10486
+ var SKIP_POLICIES, initialized = false, savedSnapshot = null;
10487
+ var init_replay = __esm(() => {
10488
+ init_policy_evaluator();
10489
+ init_builtin_policies();
10490
+ SKIP_POLICIES = new Set(["warn-repeated-tool-calls"].map((n) => normalizePolicyName(n)));
10491
+ });
10492
+
10493
+ // src/audit/index.ts
10494
+ function shortPolicyName(name) {
10495
+ const slash = name.indexOf("/");
10496
+ return slash >= 0 ? name.slice(slash + 1) : name;
10497
+ }
10498
+ function findBuiltin(name) {
10499
+ const short = shortPolicyName(name);
10500
+ for (const p of BUILTIN_POLICIES) {
10501
+ if (p.name === name || shortPolicyName(p.name) === short)
10502
+ return p;
10503
+ }
10504
+ return null;
10505
+ }
10506
+ function buildInstallHint(name, source, enabled) {
10507
+ if (source === "audit-detector") {
10508
+ return "Audit-only — `failproofai audit` will keep tracking these.";
10509
+ }
10510
+ if (enabled) {
10511
+ return "Already enforced — failproofai is blocking these in real time.";
10512
+ }
10513
+ return `Enable in one command: failproofai policies --install ${shortPolicyName(name)}`;
10514
+ }
10515
+ function truncateExample(s) {
10516
+ if (s.length <= AUDIT_EXAMPLE_MAX_CHARS)
10517
+ return s;
10518
+ return s.slice(0, AUDIT_EXAMPLE_MAX_CHARS - 1) + "…";
10519
+ }
10520
+ function parseSinceOpt(since) {
10521
+ if (!since)
10522
+ return;
10523
+ const m = /^(\d+)\s*([dhm])$/i.exec(since.trim());
10524
+ if (m) {
10525
+ const n = parseInt(m[1], 10);
10526
+ const unit = m[2].toLowerCase();
10527
+ const ms = unit === "d" ? 86400000 : unit === "h" ? 3600000 : 60000;
10528
+ return Date.now() - n * ms;
10529
+ }
10530
+ const t = Date.parse(since);
10531
+ if (!Number.isNaN(t))
10532
+ return t;
10533
+ throw new Error(`Invalid --since value: "${since}" (expected e.g. "7d", "30d", or "2026-04-01")`);
10534
+ }
10535
+ async function scanOneTranscript(meta) {
10536
+ const empty = {
10537
+ transcriptPath: meta.transcriptPath,
10538
+ cli: meta.cli,
10539
+ projectName: meta.projectName,
10540
+ sessionId: meta.sessionId,
10541
+ mtimeMs: meta.mtimeMs,
10542
+ sizeBytes: meta.sizeBytes,
10543
+ cwd: "",
10544
+ eventsScanned: 0,
10545
+ hitsByName: {},
10546
+ examplesByName: {},
10547
+ rangeByName: {}
10548
+ };
10549
+ const events = await ADAPTERS[meta.cli].streamEvents(meta);
10550
+ if (events.length === 0)
10551
+ return empty;
10552
+ const result = empty;
10553
+ result.eventsScanned = events.length;
10554
+ result.cwd = events[0].cwd || "";
10555
+ const sessionState = {};
10556
+ for (const event of events) {
10557
+ for (const detector of AUDIT_DETECTORS) {
10558
+ const hit = detector.detect(event, sessionState);
10559
+ if (!hit)
10560
+ continue;
10561
+ recordHit(result, detector.name, event.timestamp, event.cwd, truncateExample(hit.example));
10562
+ }
10563
+ let replayHits;
10564
+ try {
10565
+ replayHits = await replayEvent(event);
10566
+ } catch {
10567
+ continue;
10568
+ }
10569
+ for (const hit of replayHits) {
10570
+ const example = formatPolicyExample(hit.policyName, event);
10571
+ recordHit(result, hit.policyName, event.timestamp, event.cwd, truncateExample(example));
10572
+ }
10573
+ }
10574
+ return result;
10575
+ }
10576
+ function formatPolicyExample(_policyName, event) {
10577
+ if (event.toolName === "Bash") {
10578
+ const command = event.toolInput.command;
10579
+ if (typeof command === "string")
10580
+ return command.replace(/\s+/g, " ");
10581
+ }
10582
+ const filePath = event.toolInput.file_path;
10583
+ if (typeof filePath === "string")
10584
+ return `${event.toolName} ${filePath}`;
10585
+ return `${event.toolName}`;
10586
+ }
10587
+ function recordHit(result, name, timestamp, cwd, example) {
10588
+ result.hitsByName[name] = (result.hitsByName[name] ?? 0) + 1;
10589
+ const exs = result.examplesByName[name] ?? [];
10590
+ if (exs.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
10591
+ exs.push({ timestamp, cwd, example });
10592
+ result.examplesByName[name] = exs;
10593
+ }
10594
+ const range = result.rangeByName[name];
10595
+ if (!range) {
10596
+ result.rangeByName[name] = { first: timestamp, last: timestamp };
10597
+ } else {
10598
+ if (timestamp < range.first)
10599
+ range.first = timestamp;
10600
+ if (timestamp > range.last)
10601
+ range.last = timestamp;
10602
+ }
10603
+ }
10604
+ function aggregateResults(perTranscript, enabledBuiltins) {
10605
+ const byName = new Map;
10606
+ for (const t of perTranscript) {
10607
+ for (const [name, count] of Object.entries(t.hitsByName)) {
10608
+ const bucket = byName.get(name) ?? {
10609
+ hits: 0,
10610
+ projects: new Set,
10611
+ examples: []
10612
+ };
10613
+ bucket.hits += count;
10614
+ bucket.projects.add(t.projectName);
10615
+ const tExs = t.examplesByName[name] ?? [];
10616
+ for (const e of tExs) {
10617
+ if (bucket.examples.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
10618
+ bucket.examples.push({ ...e, sessionId: t.sessionId });
10619
+ }
10620
+ }
10621
+ const range = t.rangeByName[name];
10622
+ if (range) {
10623
+ if (!bucket.first || range.first < bucket.first)
10624
+ bucket.first = range.first;
10625
+ if (!bucket.last || range.last > bucket.last)
10626
+ bucket.last = range.last;
10627
+ }
10628
+ byName.set(name, bucket);
10629
+ }
10630
+ }
10631
+ const detectorByName = new Map(AUDIT_DETECTORS.map((d) => [d.name, d]));
10632
+ const out = [];
10633
+ for (const [name, bucket] of byName) {
10634
+ const detector = detectorByName.get(name);
10635
+ const isDetector = !!detector;
10636
+ const builtin = isDetector ? null : findBuiltin(name);
10637
+ const source = isDetector ? "audit-detector" : "builtin";
10638
+ const enabled = isDetector ? false : enabledBuiltins.has(normalizePolicyName(name));
10639
+ const displayTitle = detector?.displayTitle ?? builtin?.displayTitle ?? detector?.description ?? builtin?.description ?? shortPolicyName(name);
10640
+ const impact = detector?.impact ?? builtin?.impact ?? "";
10641
+ out.push({
10642
+ name,
10643
+ source,
10644
+ category: detector?.category ?? builtin?.category ?? "Custom",
10645
+ severity: isDetector ? detector?.severity ?? "info" : severityForBuiltin(name),
10646
+ hits: bucket.hits,
10647
+ projects: bucket.projects.size,
10648
+ firstSeen: bucket.first,
10649
+ lastSeen: bucket.last,
10650
+ examples: bucket.examples,
10651
+ displayTitle,
10652
+ impact,
10653
+ enabledInConfig: enabled,
10654
+ installHint: buildInstallHint(name, source, enabled)
10655
+ });
10656
+ }
10657
+ out.sort((a, b) => b.hits - a.hits);
10658
+ return out;
10659
+ }
10660
+ async function runAudit(opts = {}) {
10661
+ const startedAt = Date.now();
10662
+ initReplay();
10663
+ try {
10664
+ return await runAuditInner(opts, startedAt);
10665
+ } finally {
10666
+ restoreReplay();
10667
+ }
10668
+ }
10669
+ async function runAuditInner(opts, startedAt) {
10670
+ const clis = opts.clis ?? Array.from(INTEGRATION_TYPES);
10671
+ const sinceMs = parseSinceOpt(opts.since);
10672
+ const userConfig = readMergedHooksConfig();
10673
+ const enabledBuiltins = new Set((userConfig.enabledPolicies ?? []).map((n) => normalizePolicyName(n)));
10674
+ const allTranscripts = [];
10675
+ for (const cli of clis) {
10676
+ const adapter = ADAPTERS[cli];
10677
+ let list;
10678
+ try {
10679
+ list = await adapter.listTranscripts({ projects: opts.projects, sinceMs });
10680
+ } catch {
10681
+ continue;
10682
+ }
10683
+ allTranscripts.push(...list);
10684
+ }
10685
+ let skipped = 0;
10686
+ let errors = 0;
10687
+ const tasks = allTranscripts.map((meta) => async () => {
10688
+ if (!opts.noCache) {
10689
+ const cached = readCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes);
10690
+ if (cached)
10691
+ return cached;
10692
+ }
10693
+ try {
10694
+ const fresh = await scanOneTranscript(meta);
10695
+ if (!opts.noCache) {
10696
+ writeCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes, fresh);
10697
+ }
10698
+ return fresh;
10699
+ } catch {
10700
+ errors++;
10701
+ return {
10702
+ transcriptPath: meta.transcriptPath,
10703
+ cli: meta.cli,
10704
+ projectName: meta.projectName,
10705
+ cwd: "",
10706
+ sessionId: meta.sessionId,
10707
+ mtimeMs: meta.mtimeMs,
10708
+ sizeBytes: meta.sizeBytes,
10709
+ eventsScanned: 0,
10710
+ hitsByName: {},
10711
+ examplesByName: {},
10712
+ rangeByName: {}
10713
+ };
10714
+ }
10715
+ });
10716
+ const settled = await batchAll(tasks, TRANSCRIPT_CONCURRENCY);
10717
+ const perTranscript = [];
10718
+ for (const s of settled) {
10719
+ if (s.status === "fulfilled")
10720
+ perTranscript.push(s.value);
10721
+ else
10722
+ skipped++;
10723
+ }
10724
+ let results = aggregateResults(perTranscript, enabledBuiltins);
10725
+ if (opts.policies?.length) {
10726
+ const wanted = new Set(opts.policies.map(shortPolicyName));
10727
+ results = results.filter((r) => wanted.has(shortPolicyName(r.name)));
10728
+ }
10729
+ const totalsHits = results.reduce((sum, r) => sum + r.hits, 0);
10730
+ const projectsWithHits = new Set;
10731
+ const projectsScannedSet = new Set;
10732
+ let eventsScanned = 0;
10733
+ for (const t of perTranscript) {
10734
+ if (Object.keys(t.hitsByName).length > 0)
10735
+ projectsWithHits.add(t.projectName);
10736
+ if (t.cwd)
10737
+ projectsScannedSet.add(t.cwd);
10738
+ eventsScanned += t.eventsScanned ?? 0;
10739
+ }
10740
+ const auditResult = {
10741
+ version: 2,
10742
+ scannedAt: new Date(startedAt).toISOString(),
10743
+ scope: {
10744
+ cli: clis,
10745
+ projects: opts.projects ?? "all",
10746
+ since: opts.since ?? null
10747
+ },
10748
+ transcripts: {
10749
+ scanned: allTranscripts.length,
10750
+ skipped,
10751
+ errors,
10752
+ durationMs: Date.now() - startedAt
10753
+ },
10754
+ results,
10755
+ totals: {
10756
+ hits: totalsHits,
10757
+ projectsWithHits: projectsWithHits.size
10758
+ },
10759
+ projectsScanned: [...projectsScannedSet].sort(),
10760
+ eventsScanned,
10761
+ enabledBuiltinNames: [...enabledBuiltins].map((n) => n.includes("/") ? n.slice(n.indexOf("/") + 1) : n)
10762
+ };
10763
+ return auditResult;
10764
+ }
10765
+ var TRANSCRIPT_CONCURRENCY = 8;
10766
+ var init_audit = __esm(() => {
10767
+ init_builtin_policies();
10768
+ init_hooks_config();
10769
+ init_types();
10770
+ init_cli_adapters();
10771
+ init_detectors();
10772
+ init_features();
10773
+ init_cache();
10774
+ init_replay();
10775
+ init_types2();
10776
+ });
10777
+
10778
+ // src/audit/dashboard-cache.ts
10779
+ import { join as join21 } from "node:path";
10780
+ import { homedir as homedir21 } from "node:os";
10781
+ function getCachePath() {
10782
+ return join21(homedir21(), ".failproofai", "audit-dashboard.json");
10783
+ }
10784
+ function writeDashboardCache(params, result) {
10785
+ try {
10786
+ const entry = {
10787
+ schemaVersion: DASHBOARD_CACHE_SCHEMA_VERSION,
10788
+ cachedAt: new Date().toISOString(),
10789
+ params,
10790
+ result
10791
+ };
10792
+ writeJsonAtomically(getCachePath(), entry);
10793
+ return true;
10794
+ } catch {
10795
+ return false;
10796
+ }
10797
+ }
10798
+ var DASHBOARD_CACHE_TTL_MINUTES, DASHBOARD_CACHE_SCHEMA_VERSION = 2;
10799
+ var init_dashboard_cache = __esm(() => {
10800
+ init_atomic_write();
10801
+ DASHBOARD_CACHE_TTL_MINUTES = 7 * 24 * 60;
10802
+ });
10803
+
10804
+ // src/audit/open-browser.ts
10805
+ import { spawn } from "node:child_process";
10806
+ function openUrl(url) {
10807
+ let cmd;
10808
+ let args;
10809
+ if (process.platform === "darwin") {
10810
+ cmd = "open";
10811
+ args = [url];
10812
+ } else if (process.platform === "win32") {
10813
+ cmd = "cmd";
10814
+ args = ["/c", "start", "", url];
10815
+ } else {
10816
+ cmd = "xdg-open";
10817
+ args = [url];
10818
+ }
10819
+ try {
10820
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
10821
+ child.on("error", () => {});
10822
+ child.unref();
10823
+ } catch {}
10824
+ }
10825
+ function openWhenReady(port, path3) {
10826
+ const base = `http://localhost:${port}`;
10827
+ const target = `${base}${path3}`;
10828
+ (async () => {
10829
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
10830
+ while (Date.now() < deadline) {
10831
+ try {
10832
+ await fetch(base, { method: "GET", redirect: "manual" });
10833
+ openUrl(target);
10834
+ return;
10835
+ } catch {
10836
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
10837
+ }
10838
+ }
10839
+ openUrl(target);
10840
+ })();
10841
+ }
10842
+ var POLL_INTERVAL_MS = 150, POLL_TIMEOUT_MS = 30000;
10843
+ var init_open_browser = () => {};
10844
+
10845
+ // scripts/parse-script-args.ts
10846
+ import { resolve as resolve12 } from "path";
10847
+ function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
10848
+ const raw = inlineValue ?? args[index + 1];
10849
+ if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
10850
+ console.error(`Error: ${flagName} requires ${errorLabel}`);
10851
+ process.exit(1);
10852
+ }
10853
+ const value = options?.resolve ? resolve12(raw) : raw;
10854
+ return { value, spliceCount: inlineValue !== null ? 1 : 2 };
10855
+ }
10856
+ function parseScriptArgs(argv) {
10857
+ const args = [...argv];
10858
+ let loggingLevel;
10859
+ let disableTelemetry = false;
10860
+ let allowedDevOrigins;
10861
+ for (let i = 0;i < args.length; i++) {
10862
+ const arg = args[i];
10863
+ const eqIdx = arg.indexOf("=");
10864
+ const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
10865
+ const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
10866
+ if (flag === "--logging") {
10867
+ const raw = inlineValue ?? args[i + 1];
10868
+ if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
10869
+ console.error("Error: --logging requires a level (info, warn, error)");
10870
+ process.exit(1);
10871
+ }
10872
+ const val = raw.toLowerCase();
10873
+ if (val !== "info" && val !== "warn" && val !== "error") {
10874
+ console.error("Error: --logging must be one of: info, warn, error");
10875
+ process.exit(1);
10876
+ }
10877
+ loggingLevel = val;
10878
+ args.splice(i, inlineValue !== null ? 1 : 2);
10879
+ i--;
10880
+ continue;
10881
+ }
10882
+ if (flag === "--disable-telemetry") {
10883
+ disableTelemetry = true;
10884
+ args.splice(i, 1);
10885
+ i--;
10886
+ continue;
10887
+ }
10888
+ if (flag === "--allowed-origins") {
10889
+ const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
10890
+ allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
10891
+ args.splice(i, spliceCount);
10892
+ i--;
10893
+ continue;
10894
+ }
10895
+ }
10896
+ return { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
10897
+ }
10898
+ var init_parse_script_args = () => {};
10899
+
10900
+ // scripts/install-diagnosis.mjs
10901
+ import { existsSync as existsSync16, readFileSync as readFileSync12, realpathSync } from "node:fs";
10902
+ import { dirname as dirname6, resolve as resolve13 } from "node:path";
10903
+ import { homedir as homedir22, platform as platform2 } from "node:os";
10904
+ import { spawnSync } from "node:child_process";
10905
+ function findPackageRoot(start) {
10906
+ try {
10907
+ let dir = realpathSync(start);
10908
+ if (existsSync16(dir) && !existsSync16(resolve13(dir, "package.json"))) {
10909
+ dir = dirname6(dir);
10910
+ }
10911
+ while (true) {
10912
+ const pkgPath = resolve13(dir, "package.json");
10913
+ if (existsSync16(pkgPath)) {
10914
+ try {
10915
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
10916
+ if (pkg.name === PKG_NAME)
10917
+ return dir;
10918
+ } catch {}
10919
+ }
10920
+ const parent = dirname6(dir);
10921
+ if (parent === dir)
10922
+ return null;
10923
+ dir = parent;
10924
+ }
10925
+ } catch {
10926
+ return null;
10927
+ }
10928
+ }
10929
+ function readPackageVersion(packageRoot) {
10930
+ if (!packageRoot)
10931
+ return null;
10932
+ try {
10933
+ const pkg = JSON.parse(readFileSync12(resolve13(packageRoot, "package.json"), "utf8"));
10934
+ return typeof pkg.version === "string" ? pkg.version : null;
10935
+ } catch {
10936
+ return null;
10937
+ }
10938
+ }
10939
+ function resolvePathFirstBinary() {
10940
+ try {
10941
+ const isWin = platform2() === "win32";
10942
+ const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
10943
+ if (res.status !== 0)
10944
+ return null;
10945
+ const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
10946
+ return first ? first.trim() : null;
10947
+ } catch {
10948
+ return null;
10949
+ }
10950
+ }
10951
+ function locateNpmGlobal() {
10952
+ try {
10953
+ const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
10954
+ if (res.status !== 0)
10955
+ return null;
10956
+ const root = (res.stdout || "").trim();
10957
+ if (!root)
10958
+ return null;
10959
+ const candidate = resolve13(root, PKG_NAME);
10960
+ return existsSync16(resolve13(candidate, "package.json")) ? candidate : null;
10961
+ } catch {
10962
+ return null;
10963
+ }
10964
+ }
10965
+ function locateBunGlobal() {
10966
+ try {
10967
+ const candidate = resolve13(homedir22(), ".bun", "install", "global", "node_modules", PKG_NAME);
10968
+ return existsSync16(resolve13(candidate, "package.json")) ? candidate : null;
10969
+ } catch {
10970
+ return null;
10971
+ }
10972
+ }
10973
+ function buildRecommendation(pathFirstBin) {
10974
+ if (!pathFirstBin)
10975
+ return null;
10976
+ const bunBinPrefix = resolve13(homedir22(), ".bun", "bin") + "/";
10977
+ const bunGlobalPrefix = resolve13(homedir22(), ".bun", "install", "global") + "/";
10978
+ const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
10979
+ if (isBun) {
10980
+ return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
10981
+ }
10982
+ return `npm rm -g ${PKG_NAME}`;
10983
+ }
10984
+ function diagnoseShadow(self) {
10985
+ const selfPackageRoot = (() => {
10986
+ try {
10987
+ return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
10988
+ } catch {
10989
+ return self?.selfPackageRoot ?? null;
10990
+ }
10991
+ })();
10992
+ const selfVersion = self?.selfVersion ?? null;
10993
+ const pathFirstBin = resolvePathFirstBinary();
10994
+ const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
10995
+ const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
10996
+ const npmGlobalPath = locateNpmGlobal();
10997
+ const npmGlobalVersion = readPackageVersion(npmGlobalPath);
10998
+ const bunGlobalPath = locateBunGlobal();
10999
+ const bunGlobalVersion = readPackageVersion(bunGlobalPath);
11000
+ let shadowed = false;
11001
+ if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
11002
+ shadowed = true;
11003
+ } else if (pathFirstPackageRoot) {
11004
+ if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
11005
+ shadowed = true;
11006
+ else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
11007
+ shadowed = true;
11008
+ }
11009
+ const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
11010
+ let shadowDescription = null;
11011
+ if (shadowed) {
11012
+ shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
11013
+ }
11014
+ return {
11015
+ selfPackageRoot,
11016
+ selfVersion,
11017
+ pathFirstBin,
11018
+ pathFirstPath: pathFirstPackageRoot,
11019
+ pathFirstVersion,
11020
+ npmGlobalPath,
11021
+ npmGlobalVersion,
11022
+ bunGlobalPath,
11023
+ bunGlobalVersion,
11024
+ shadowed,
11025
+ shadowDescription,
11026
+ recommendation
11027
+ };
11028
+ }
11029
+ var PKG_NAME = "failproofai";
11030
+ var init_install_diagnosis = () => {};
11031
+
11032
+ // scripts/skew-log-filter.ts
11033
+ function makeSkewLogFilter() {
11034
+ let inSkewBlock = false;
11035
+ return (line) => {
11036
+ if (line.includes("Failed to find Server Action")) {
11037
+ inSkewBlock = true;
11038
+ return null;
11039
+ }
11040
+ if (inSkewBlock) {
11041
+ const trimmed = line.trimStart();
11042
+ if (trimmed.startsWith("Read more:") || line.includes("failed-to-find-server-action") || line.includes("ignore-listed frames") || /^\s+at\s/.test(line)) {
11043
+ return null;
11044
+ }
11045
+ inSkewBlock = false;
11046
+ }
11047
+ return line;
11048
+ };
11049
+ }
11050
+
11051
+ // scripts/launch.ts
11052
+ var exports_launch = {};
11053
+ __export(exports_launch, {
11054
+ launch: () => launch
11055
+ });
11056
+ import { spawn as spawn2 } from "child_process";
11057
+ import { realpathSync as realpathSync2, existsSync as existsSync17 } from "node:fs";
11058
+ import { resolve as resolve14, dirname as dirname7 } from "node:path";
11059
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
11060
+ import { createInterface as createInterface2 } from "node:readline";
11061
+ function launch(mode) {
11062
+ const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
11063
+ console.log(`
11064
+ failproof ai
11065
+ `);
11066
+ console.log(` \uD83D\uDCE6 Version: ${version2}`);
11067
+ console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
11068
+ console.log(` \uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction`);
11069
+ console.log(` \uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
11070
+ `);
11071
+ let cmd;
11072
+ let cmdArgs;
11073
+ if (mode === "start") {
11074
+ const portIdx = remainingArgs.indexOf("--port");
11075
+ const port = portIdx >= 0 ? remainingArgs[portIdx + 1] : "8020";
11076
+ process.env.PORT = port;
11077
+ process.env.HOSTNAME = "0.0.0.0";
11078
+ cmd = "node";
11079
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve14(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..");
11080
+ const serverJsPath = resolve14(packageRoot, ".next/standalone/server.js");
11081
+ if (!existsSync17(serverJsPath)) {
11082
+ let shadowMessage = null;
11083
+ try {
11084
+ const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
11085
+ if (diag.shadowed) {
11086
+ const alt = (diag.npmGlobalPath && diag.npmGlobalPath !== diag.pathFirstPath ? { path: diag.npmGlobalPath, version: diag.npmGlobalVersion } : null) ?? (diag.bunGlobalPath && diag.bunGlobalPath !== diag.pathFirstPath ? { path: diag.bunGlobalPath, version: diag.bunGlobalVersion } : null);
11087
+ const newer = alt?.path ?? "(unknown)";
11088
+ const newerVer = alt?.version ?? "?";
11089
+ shadowMessage = `
11090
+ Error: failproofai on your PATH is a stale install that no longer has its build output.
11091
+ ` + ` Running: ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `
11092
+ ` + ` Newer copy: ${newer} (v${newerVer})
11093
+
11094
+ ` + `Remove the shadow with:
11095
+ ${diag.recommendation}
11096
+ `;
11097
+ }
11098
+ } catch {}
11099
+ console.error(shadowMessage ?? `
11100
+ Error: Cannot find server.js at:
11101
+ ${serverJsPath}
11102
+
11103
+ ` + `The package may be missing its build output.
11104
+ ` + `Try reinstalling:
11105
+ npm install -g failproofai@latest
11106
+ `);
11107
+ process.exit(1);
11108
+ }
11109
+ cmdArgs = [serverJsPath];
11110
+ } else {
11111
+ cmd = "bunx";
11112
+ cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
11113
+ }
11114
+ const filterLogs = mode === "start";
11115
+ const nextProcess = spawn2(cmd, cmdArgs, {
11116
+ stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
11117
+ env: {
11118
+ ...process.env,
11119
+ ...filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {},
11120
+ ...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
11121
+ ...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
11122
+ ...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
11123
+ }
11124
+ });
11125
+ if (filterLogs) {
11126
+ for (const [src, dest] of [
11127
+ [nextProcess.stdout, process.stdout],
11128
+ [nextProcess.stderr, process.stderr]
11129
+ ]) {
11130
+ if (!src)
11131
+ continue;
11132
+ const filter = makeSkewLogFilter();
11133
+ createInterface2({ input: src }).on("line", (line) => {
11134
+ const out = filter(line);
11135
+ if (out !== null)
11136
+ dest.write(out + `
11137
+ `);
11138
+ });
11139
+ }
11140
+ }
11141
+ nextProcess.on("error", (error) => {
11142
+ console.error("Error starting Next.js:", error);
11143
+ process.exit(1);
11144
+ });
11145
+ nextProcess.on("exit", (code) => {
11146
+ process.exit(code || 0);
11147
+ });
11148
+ }
11149
+ var init_launch = __esm(() => {
11150
+ init_parse_script_args();
11151
+ init_install_diagnosis();
11152
+ init_package();
11153
+ });
11154
+
11155
+ // src/audit/cli.ts
11156
+ var exports_cli2 = {};
11157
+ __export(exports_cli2, {
11158
+ runAuditCli: () => runAuditCli,
11159
+ buildSummary: () => buildSummary,
11160
+ AUDIT_STAGES: () => AUDIT_STAGES
11161
+ });
11162
+ function colorOn() {
11163
+ if (process.env.NO_COLOR && process.env.NO_COLOR !== "")
11164
+ return false;
11165
+ if (process.env.FORCE_COLOR === "0")
11166
+ return false;
11167
+ if (process.env.FORCE_COLOR)
11168
+ return true;
11169
+ return !!process.stdout.isTTY;
11170
+ }
11171
+ function c(code, s) {
11172
+ return colorOn() ? `${code}${s}${RESET2}` : s;
11173
+ }
11174
+ function num(n) {
11175
+ return n.toLocaleString("en-US");
11176
+ }
11177
+ function die(message) {
11178
+ process.stderr.write(`Error: ${message}
11179
+ `);
11180
+ process.exit(1);
11181
+ }
11182
+ function startProgress() {
11183
+ const n = AUDIT_STAGES.length;
11184
+ let stage = 0;
11185
+ let tick = 0;
11186
+ let done = false;
11187
+ let printed = false;
11188
+ const lineFor = (i) => {
11189
+ const s = AUDIT_STAGES[i];
11190
+ if (done || i < stage)
11191
+ return ` ${c(GREEN2, "✓")} ${c(DIM2, s.label)}`;
11192
+ if (i === stage) {
11193
+ return ` ${c(PINK2, SPINNER[tick % SPINNER.length])} ${s.label} ${c(DIM2, s.detail)}`;
11194
+ }
11195
+ return ` ${c(DIM2, "○")} ${c(DIM2, s.label)}`;
11196
+ };
11197
+ const render = () => {
11198
+ const lines = Array.from({ length: n }, (_, i) => lineFor(i));
11199
+ if (printed)
11200
+ process.stdout.write(`\x1B[${n}A`);
11201
+ process.stdout.write(lines.map((l) => `\x1B[2K${l}`).join(`
11202
+ `) + `
11203
+ `);
11204
+ printed = true;
11205
+ };
11206
+ render();
11207
+ const spinTimer = setInterval(() => {
11208
+ tick++;
11209
+ render();
11210
+ }, 90);
11211
+ const stageTimer = setInterval(() => {
11212
+ if (stage < n - 1) {
11213
+ stage++;
11214
+ render();
11215
+ }
11216
+ }, 1100);
11217
+ const stop = () => {
11218
+ clearInterval(spinTimer);
11219
+ clearInterval(stageTimer);
11220
+ };
11221
+ return {
11222
+ finish() {
11223
+ stop();
11224
+ done = true;
11225
+ render();
11226
+ },
11227
+ fail() {
11228
+ stop();
11229
+ process.stdout.write(`
11230
+ `);
11231
+ }
11232
+ };
11233
+ }
11234
+ async function runWithProgress(opts) {
11235
+ if (!process.stdout.isTTY) {
11236
+ process.stdout.write(` scanning your agent session history — this can take a moment…
11237
+ `);
11238
+ return runAudit(opts);
11239
+ }
11240
+ const progress = startProgress();
11241
+ try {
11242
+ const result = await runAudit(opts);
11243
+ progress.finish();
11244
+ return result;
11245
+ } catch (err) {
11246
+ progress.fail();
11247
+ throw err;
11248
+ }
11249
+ }
11250
+ function printHeader() {
11251
+ process.stdout.write(`
11252
+ ${c(PINK2, "\uD83D\uDEE1 failproofai audit")} ${c(DIM2, "· beta")}
11253
+
11254
+ `);
11255
+ process.stdout.write(` ${c(DIM2, "starting audit…")}
11256
+
11257
+ `);
11258
+ }
11259
+ function buildSummary(result) {
11260
+ const sessions = result.transcripts.scanned;
11261
+ const events = result.eventsScanned;
11262
+ const projects = result.projectsScanned.length;
11263
+ const enabledRows = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig);
11264
+ const slippingRows = result.results.filter((r) => !(r.source === "builtin" && r.enabledInConfig));
11265
+ const lines = [];
11266
+ lines.push(`${c(GREEN2, "✓ audit complete")} ${c(DIM2, "·")} ` + `${c(BOLD, num(events))} tool call${events === 1 ? "" : "s"} across ` + `${num(sessions)} session${sessions === 1 ? "" : "s"}` + (projects > 0 ? ` ${c(DIM2, "·")} ${num(projects)} project${projects === 1 ? "" : "s"}` : ""));
11267
+ if (result.totals.hits === 0) {
11268
+ if (events > 0)
11269
+ lines.push(c(DIM2, "clean run — nothing flagged. nice."));
11270
+ return lines;
11271
+ }
11272
+ const parts = [];
11273
+ if (slippingRows.length > 0) {
11274
+ parts.push(`${c(PINK2, String(slippingRows.length))} ${slippingRows.length === 1 ? "pattern" : "patterns"} slipping through`);
11275
+ }
11276
+ if (enabledRows.length > 0) {
11277
+ parts.push(`${c(GREEN2, String(enabledRows.length))} already blocked by your policies`);
11278
+ }
11279
+ if (parts.length > 0)
11280
+ lines.push(parts.join(` ${c(DIM2, "·")} `));
11281
+ return lines;
11282
+ }
11283
+ function printSummary(result) {
11284
+ process.stdout.write(`
11285
+ `);
11286
+ for (const line of buildSummary(result))
11287
+ process.stdout.write(` ${line}
11288
+ `);
11289
+ }
11290
+ async function runAuditCli(args) {
11291
+ if (args.includes("--help") || args.includes("-h")) {
11292
+ process.stdout.write(HELP2);
11293
+ process.exit(0);
11294
+ }
11295
+ const stray = args.find((a) => a !== "--help" && a !== "-h");
11296
+ if (stray) {
11297
+ die(`\`audit\` takes no arguments yet (got: ${stray}).
11298
+ ` + `Run \`failproofai audit\` to scan your history and open the dashboard.`);
11299
+ }
11300
+ const instanceId = getInstanceId2();
11301
+ trackHookEvent2(instanceId, "cli_audit_started", { source: "cli" });
11302
+ printHeader();
11303
+ const opts = {};
11304
+ let result;
11305
+ try {
11306
+ result = await runWithProgress(opts);
11307
+ } catch (err) {
11308
+ await trackHookEvent2(instanceId, "cli_audit_failed", {
11309
+ source: "cli",
11310
+ error_type: err instanceof Error ? err.name : "unknown"
11311
+ });
11312
+ die(`Audit failed: ${err instanceof Error ? err.message : String(err)}`);
11313
+ }
11314
+ printSummary(result);
11315
+ await trackHookEvent2(instanceId, "cli_audit_completed", {
11316
+ source: "cli",
11317
+ events_scanned: result.eventsScanned,
11318
+ sessions_scanned: result.transcripts.scanned,
11319
+ total_hits: result.totals.hits,
11320
+ findings: result.results.length
11321
+ });
11322
+ if (result.eventsScanned === 0) {
11323
+ process.stdout.write(`
11324
+ ${c(DIM2, "no agent sessions found yet.")}
11325
+ ` + ` install hooks with ${c(CYAN, "failproofai policies --install")} ` + `${c(DIM2, "and come back after using your agent.")}
11326
+
11327
+ `);
11328
+ process.exit(0);
11329
+ }
11330
+ const persisted = writeDashboardCache(opts, result);
11331
+ if (!persisted) {
11332
+ process.stdout.write(`
11333
+ ${c(PINK2, "!")} ${c(DIM2, "couldn't save the audit cache; the dashboard may show an empty state.")}
11334
+ `);
11335
+ }
11336
+ const url = `http://localhost:${DASHBOARD_PORT}/audit`;
11337
+ process.stdout.write(`
11338
+ ${c(DIM2, "starting the dashboard…")}
11339
+ ` + ` ${c(PINK2, "✦")} ${c(BOLD, "here's your audit")} ${c(DIM2, "→")} ${c(CYAN, url)}
11340
+ ` + ` ${c(DIM2, "(opening in your browser — press Ctrl+C to stop the server)")}
11341
+
11342
+ `);
11343
+ openWhenReady(DASHBOARD_PORT, "/audit");
11344
+ const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
11345
+ launch2("start");
11346
+ }
11347
+ var DASHBOARD_PORT = 8020, AUDIT_STAGES, HELP2, RESET2 = "\x1B[0m", DIM2 = "\x1B[2m", BOLD = "\x1B[1m", PINK2 = "\x1B[38;5;204m", GREEN2 = "\x1B[38;5;120m", CYAN = "\x1B[38;5;81m", SPINNER;
11348
+ var init_cli2 = __esm(() => {
11349
+ init_audit();
11350
+ init_dashboard_cache();
11351
+ init_hook_telemetry2();
11352
+ init_telemetry_id2();
11353
+ init_open_browser();
11354
+ AUDIT_STAGES = [
11355
+ { label: "discovering transcripts", detail: "walking ~/.claude, ~/.codex, ~/.cursor, …" },
11356
+ { label: "parsing session logs", detail: "reading JSONL + sqlite session stores" },
11357
+ { label: "running policy checks", detail: "replaying through 30 builtin policies" },
11358
+ { label: "aggregating results", detail: "counting hits, ranking by frequency" }
11359
+ ];
11360
+ HELP2 = `
11361
+ failproofai audit — audit your AI agent's behavior, then open the dashboard
11362
+
11363
+ USAGE
11364
+ failproofai audit Scan your agent-CLI session history for risky and
11365
+ wasteful patterns, then open the audit dashboard.
11366
+ failproofai audit --help Show this help.
11367
+
11368
+ WHAT IT DOES
11369
+ 1. Scans past sessions from every installed agent CLI (Claude, Codex, Cursor,
11370
+ Copilot, OpenCode, Pi, Gemini) — entirely on your machine.
11371
+ 2. Starts the local dashboard and opens
11372
+ http://localhost:${DASHBOARD_PORT}/audit with your results.
11373
+
11374
+ Runs fully offline — no account or network required. Press Ctrl+C to stop the
11375
+ dashboard server when you're done.
11376
+ `.trimStart();
11377
+ SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
11378
+ });
11379
+
11380
+ // src/hooks/manager.ts
11381
+ import { execSync as execSync6 } from "node:child_process";
11382
+ import { resolve as resolve15, basename as basename5 } from "node:path";
11383
+ import { homedir as homedir23, platform as platform3, arch as arch2, release as release2, hostname as hostname2 } from "node:os";
11384
+ function getSettingsPath2(scope, cwd) {
11385
+ return claudeCode.getSettingsPath(scope, cwd);
11386
+ }
11387
+ function scopeLabel2(scope) {
11388
+ switch (scope) {
11389
+ case "user":
11390
+ return `~/.claude/settings.json`;
11391
+ case "project":
11392
+ return `{cwd}/.claude/settings.json`;
11393
+ case "local":
11394
+ return `{cwd}/.claude/settings.local.json`;
11395
+ }
11396
+ }
11397
+ function resolveFailproofaiBinary2() {
11398
+ const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
11399
+ if (override && override.trim())
11400
+ return override.trim();
11401
+ try {
11402
+ const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
11403
+ const result = execSync6(cmd, { encoding: "utf8" }).trim();
11404
+ return result.split(`
11405
+ `)[0].trim();
11406
+ } catch {
11407
+ throw new CliError(`failproofai binary not found in PATH.
11408
+ ` + "Install it globally first: npm install -g failproofai");
11409
+ }
11410
+ }
11411
+ function validatePolicyNames2(names) {
11412
+ const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
11413
+ if (invalid.length > 0) {
11414
+ const validList = [...VALID_POLICY_NAMES2].join(", ");
11415
+ throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
11416
+ ` + `Valid policies: ${validList}`);
11417
+ }
11418
+ }
11419
+ function deduplicateScopes2(scopes, cwd) {
11420
+ const seen = new Set;
11421
+ return scopes.filter((s) => {
11422
+ const p = getSettingsPath2(s, cwd);
11423
+ if (seen.has(p))
11424
+ return false;
11425
+ seen.add(p);
11426
+ return true;
11427
+ });
11428
+ }
11429
+ function hooksInstalledInSettings2(scope, cwd) {
11430
+ return claudeCode.hooksInstalledInSettings(scope, cwd);
11431
+ }
11432
+ async function installHooks2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli) {
11433
+ if (policyNames !== undefined && policyNames.length > 0) {
11434
+ const nonAllNames = policyNames.filter((n) => n !== "all");
11435
+ if (nonAllNames.length > 0)
11436
+ validatePolicyNames2(nonAllNames);
11437
+ if (policyNames.includes("all") && nonAllNames.length > 0) {
11438
+ throw new CliError(`"all" cannot be combined with specific policy names.
11439
+ ` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
11440
+ }
11441
+ }
11442
+ const selectedClis = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
11443
+ for (const cliId of selectedClis) {
11444
+ const integration = getIntegration(cliId);
11445
+ if (!integration.scopes.includes(scope)) {
11446
+ try {
11447
+ await trackHookEvent2(getInstanceId2(), "scope_validation_failed", {
11448
+ cli: cliId,
11449
+ scope,
11450
+ supported_scopes: integration.scopes
11451
+ });
11452
+ } catch {}
11453
+ throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
11454
+ }
11455
+ }
11456
+ const binaryPath = resolveFailproofaiBinary2();
11457
+ const previousConfig = readScopedHooksConfig(scope, cwd);
11458
+ const previousEnabled = new Set(previousConfig.enabledPolicies);
11459
+ let selectedPolicies;
11460
+ if (policyNames !== undefined) {
11461
+ let incoming;
11462
+ if (policyNames.length === 1 && policyNames[0] === "all") {
11463
+ incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
11464
+ } else {
11465
+ incoming = policyNames;
11466
+ }
11467
+ selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
11468
+ } else {
11469
+ const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
11470
+ selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
11471
+ }
11472
+ const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
11473
+ if (removeCustomHooks) {
11474
+ delete configToWrite.customPoliciesPath;
11475
+ } else if (customPoliciesPath) {
11476
+ configToWrite.customPoliciesPath = resolve15(customPoliciesPath);
11477
+ let validatedHooks = [];
11478
+ try {
11479
+ validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
11480
+ } catch (err) {
11481
+ const msg = err instanceof Error ? err.message : String(err);
11482
+ try {
11483
+ await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
11484
+ scope,
11485
+ error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
11486
+ });
11487
+ } catch {}
11488
+ console.error(`Error: ${msg}`);
11489
+ process.exit(1);
11490
+ }
11491
+ if (validatedHooks.length === 0) {
11492
+ try {
11493
+ await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
11494
+ scope,
11495
+ error_type: "no_hooks_registered"
11496
+ });
11497
+ } catch {}
11498
+ console.error(`Error: no hooks registered in ${customPoliciesPath}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
11499
+ process.exit(1);
11500
+ }
11501
+ console.log(`
11502
+ Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
11503
+ }
11504
+ writeScopedHooksConfig(configToWrite, scope, cwd);
11505
+ console.log(`
11506
+ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
11507
+ `);
11508
+ if (removeCustomHooks) {
11509
+ console.log("Custom hooks path cleared.");
11510
+ } else if (configToWrite.customPoliciesPath) {
11511
+ console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
11512
+ }
11513
+ const writtenSettingsPaths = [];
11514
+ for (const cliId of selectedClis) {
11515
+ const integration = getIntegration(cliId);
11516
+ const settingsPath = integration.getSettingsPath(scope, cwd);
11517
+ try {
11518
+ const settings = integration.readSettings(settingsPath);
11519
+ integration.writeHookEntries(settings, binaryPath, scope);
11520
+ integration.writeSettings(settingsPath, settings);
11521
+ writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
11522
+ } catch (err) {
11523
+ const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message) ? "permission_denied" : err instanceof Error && /ENOENT|ENOTDIR/.test(err.message) ? "path_not_found" : "write_error";
11524
+ try {
11525
+ await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
11526
+ cli: cliId,
11527
+ scope,
11528
+ error_type: errorType
11529
+ });
11530
+ } catch {}
11531
+ throw err;
11532
+ }
11533
+ }
11534
+ try {
11535
+ const newSet = new Set(selectedPolicies);
11536
+ const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
9427
11537
  const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
9428
11538
  const distinctId = getInstanceId2();
9429
11539
  await trackHookEvent2(distinctId, "hooks_installed", {
@@ -9435,7 +11545,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
9435
11545
  policies_added: policiesAdded,
9436
11546
  policies_removed: policiesRemoved,
9437
11547
  ...source ? { source } : {},
9438
- platform: platform2(),
11548
+ platform: platform3(),
9439
11549
  arch: arch2(),
9440
11550
  os_release: release2(),
9441
11551
  hostname_hash: hashToId2(hostname2()),
@@ -9529,7 +11639,7 @@ function clisLabel(detected) {
9529
11639
  return detected.map((id) => getIntegration(id).displayName).join(", ");
9530
11640
  }
9531
11641
  async function promptYesNo(stdin, stdout) {
9532
- return new Promise((resolve13) => {
11642
+ return new Promise((resolve16) => {
9533
11643
  const rl = readline4.createInterface({ input: stdin, output: stdout });
9534
11644
  let settled = false;
9535
11645
  const finish = (answer) => {
@@ -9537,7 +11647,7 @@ async function promptYesNo(stdin, stdout) {
9537
11647
  return;
9538
11648
  settled = true;
9539
11649
  rl.close();
9540
- resolve13(answer);
11650
+ resolve16(answer);
9541
11651
  };
9542
11652
  rl.on("SIGINT", () => finish("sigint"));
9543
11653
  rl.question("Install policies now? [Y/n] ", (raw) => {
@@ -9609,211 +11719,25 @@ var init_first_run_nudge = __esm(() => {
9609
11719
  init_telemetry_id2();
9610
11720
  });
9611
11721
 
9612
- // scripts/parse-script-args.ts
9613
- import { resolve as resolve13 } from "path";
9614
- function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
9615
- const raw = inlineValue ?? args[index + 1];
9616
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
9617
- console.error(`Error: ${flagName} requires ${errorLabel}`);
9618
- process.exit(1);
9619
- }
9620
- const value = options?.resolve ? resolve13(raw) : raw;
9621
- return { value, spliceCount: inlineValue !== null ? 1 : 2 };
9622
- }
9623
- function parseScriptArgs(argv) {
9624
- const args = [...argv];
9625
- let loggingLevel;
9626
- let disableTelemetry = false;
9627
- let allowedDevOrigins;
9628
- for (let i = 0;i < args.length; i++) {
9629
- const arg = args[i];
9630
- const eqIdx = arg.indexOf("=");
9631
- const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
9632
- const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
9633
- if (flag === "--logging") {
9634
- const raw = inlineValue ?? args[i + 1];
9635
- if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
9636
- console.error("Error: --logging requires a level (info, warn, error)");
9637
- process.exit(1);
9638
- }
9639
- const val = raw.toLowerCase();
9640
- if (val !== "info" && val !== "warn" && val !== "error") {
9641
- console.error("Error: --logging must be one of: info, warn, error");
9642
- process.exit(1);
9643
- }
9644
- loggingLevel = val;
9645
- args.splice(i, inlineValue !== null ? 1 : 2);
9646
- i--;
9647
- continue;
9648
- }
9649
- if (flag === "--disable-telemetry") {
9650
- disableTelemetry = true;
9651
- args.splice(i, 1);
9652
- i--;
9653
- continue;
9654
- }
9655
- if (flag === "--allowed-origins") {
9656
- const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
9657
- allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
9658
- args.splice(i, spliceCount);
9659
- i--;
9660
- continue;
9661
- }
9662
- }
9663
- return { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
9664
- }
9665
- var init_parse_script_args = () => {};
9666
-
9667
- // scripts/install-diagnosis.mjs
9668
- import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync } from "node:fs";
9669
- import { dirname as dirname6, resolve as resolve14 } from "node:path";
9670
- import { homedir as homedir21, platform as platform3 } from "node:os";
9671
- import { spawnSync } from "node:child_process";
9672
- function findPackageRoot(start) {
9673
- try {
9674
- let dir = realpathSync(start);
9675
- if (existsSync14(dir) && !existsSync14(resolve14(dir, "package.json"))) {
9676
- dir = dirname6(dir);
9677
- }
9678
- while (true) {
9679
- const pkgPath = resolve14(dir, "package.json");
9680
- if (existsSync14(pkgPath)) {
9681
- try {
9682
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
9683
- if (pkg.name === PKG_NAME)
9684
- return dir;
9685
- } catch {}
9686
- }
9687
- const parent = dirname6(dir);
9688
- if (parent === dir)
9689
- return null;
9690
- dir = parent;
9691
- }
9692
- } catch {
9693
- return null;
9694
- }
9695
- }
9696
- function readPackageVersion(packageRoot) {
9697
- if (!packageRoot)
9698
- return null;
9699
- try {
9700
- const pkg = JSON.parse(readFileSync11(resolve14(packageRoot, "package.json"), "utf8"));
9701
- return typeof pkg.version === "string" ? pkg.version : null;
9702
- } catch {
9703
- return null;
9704
- }
9705
- }
9706
- function resolvePathFirstBinary() {
9707
- try {
9708
- const isWin = platform3() === "win32";
9709
- const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
9710
- if (res.status !== 0)
9711
- return null;
9712
- const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
9713
- return first ? first.trim() : null;
9714
- } catch {
9715
- return null;
9716
- }
9717
- }
9718
- function locateNpmGlobal() {
9719
- try {
9720
- const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
9721
- if (res.status !== 0)
9722
- return null;
9723
- const root = (res.stdout || "").trim();
9724
- if (!root)
9725
- return null;
9726
- const candidate = resolve14(root, PKG_NAME);
9727
- return existsSync14(resolve14(candidate, "package.json")) ? candidate : null;
9728
- } catch {
9729
- return null;
9730
- }
9731
- }
9732
- function locateBunGlobal() {
9733
- try {
9734
- const candidate = resolve14(homedir21(), ".bun", "install", "global", "node_modules", PKG_NAME);
9735
- return existsSync14(resolve14(candidate, "package.json")) ? candidate : null;
9736
- } catch {
9737
- return null;
9738
- }
9739
- }
9740
- function buildRecommendation(pathFirstBin) {
9741
- if (!pathFirstBin)
9742
- return null;
9743
- const bunBinPrefix = resolve14(homedir21(), ".bun", "bin") + "/";
9744
- const bunGlobalPrefix = resolve14(homedir21(), ".bun", "install", "global") + "/";
9745
- const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
9746
- if (isBun) {
9747
- return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
9748
- }
9749
- return `npm rm -g ${PKG_NAME}`;
9750
- }
9751
- function diagnoseShadow(self) {
9752
- const selfPackageRoot = (() => {
9753
- try {
9754
- return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
9755
- } catch {
9756
- return self?.selfPackageRoot ?? null;
9757
- }
9758
- })();
9759
- const selfVersion = self?.selfVersion ?? null;
9760
- const pathFirstBin = resolvePathFirstBinary();
9761
- const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
9762
- const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
9763
- const npmGlobalPath = locateNpmGlobal();
9764
- const npmGlobalVersion = readPackageVersion(npmGlobalPath);
9765
- const bunGlobalPath = locateBunGlobal();
9766
- const bunGlobalVersion = readPackageVersion(bunGlobalPath);
9767
- let shadowed = false;
9768
- if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
9769
- shadowed = true;
9770
- } else if (pathFirstPackageRoot) {
9771
- if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
9772
- shadowed = true;
9773
- else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
9774
- shadowed = true;
9775
- }
9776
- const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
9777
- let shadowDescription = null;
9778
- if (shadowed) {
9779
- shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
9780
- }
9781
- return {
9782
- selfPackageRoot,
9783
- selfVersion,
9784
- pathFirstBin,
9785
- pathFirstPath: pathFirstPackageRoot,
9786
- pathFirstVersion,
9787
- npmGlobalPath,
9788
- npmGlobalVersion,
9789
- bunGlobalPath,
9790
- bunGlobalVersion,
9791
- shadowed,
9792
- shadowDescription,
9793
- recommendation
9794
- };
9795
- }
9796
- var PKG_NAME = "failproofai";
9797
- var init_install_diagnosis = () => {};
9798
-
9799
11722
  // scripts/launch.ts
9800
- var exports_launch = {};
9801
- __export(exports_launch, {
9802
- launch: () => launch
11723
+ var exports_launch2 = {};
11724
+ __export(exports_launch2, {
11725
+ launch: () => launch2
9803
11726
  });
9804
- import { spawn } from "child_process";
9805
- import { realpathSync as realpathSync2, existsSync as existsSync15 } from "node:fs";
9806
- import { resolve as resolve15, dirname as dirname7 } from "node:path";
9807
- import { fileURLToPath as fileURLToPath2 } from "node:url";
9808
- function launch(mode) {
11727
+ import { spawn as spawn3 } from "child_process";
11728
+ import { realpathSync as realpathSync3, existsSync as existsSync18 } from "node:fs";
11729
+ import { resolve as resolve16, dirname as dirname8 } from "node:path";
11730
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
11731
+ import { createInterface as createInterface4 } from "node:readline";
11732
+ function launch2(mode) {
9809
11733
  const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
9810
11734
  console.log(`
9811
11735
  failproof ai
9812
11736
  `);
9813
11737
  console.log(` \uD83D\uDCE6 Version: ${version2}`);
9814
11738
  console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
9815
- console.log(` \uD83D\uDCD6 Docs: https://befailproof.ai`);
9816
- console.log(` \uD83D\uDCAC Slack: https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ
11739
+ console.log(` \uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction`);
11740
+ console.log(` \uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
9817
11741
  `);
9818
11742
  let cmd;
9819
11743
  let cmdArgs;
@@ -9823,9 +11747,9 @@ function launch(mode) {
9823
11747
  process.env.PORT = port;
9824
11748
  process.env.HOSTNAME = "0.0.0.0";
9825
11749
  cmd = "node";
9826
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve15(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..");
9827
- const serverJsPath = resolve15(packageRoot, ".next/standalone/server.js");
9828
- if (!existsSync15(serverJsPath)) {
11750
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve16(dirname8(realpathSync3(fileURLToPath3(import.meta.url))), "..");
11751
+ const serverJsPath = resolve16(packageRoot, ".next/standalone/server.js");
11752
+ if (!existsSync18(serverJsPath)) {
9829
11753
  let shadowMessage = null;
9830
11754
  try {
9831
11755
  const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
@@ -9858,15 +11782,33 @@ Error: Cannot find server.js at:
9858
11782
  cmd = "bunx";
9859
11783
  cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
9860
11784
  }
9861
- const nextProcess = spawn(cmd, cmdArgs, {
9862
- stdio: "inherit",
11785
+ const filterLogs = mode === "start";
11786
+ const nextProcess = spawn3(cmd, cmdArgs, {
11787
+ stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
9863
11788
  env: {
9864
11789
  ...process.env,
11790
+ ...filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {},
9865
11791
  ...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
9866
11792
  ...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
9867
11793
  ...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
9868
11794
  }
9869
11795
  });
11796
+ if (filterLogs) {
11797
+ for (const [src, dest] of [
11798
+ [nextProcess.stdout, process.stdout],
11799
+ [nextProcess.stderr, process.stderr]
11800
+ ]) {
11801
+ if (!src)
11802
+ continue;
11803
+ const filter = makeSkewLogFilter();
11804
+ createInterface4({ input: src }).on("line", (line) => {
11805
+ const out = filter(line);
11806
+ if (out !== null)
11807
+ dest.write(out + `
11808
+ `);
11809
+ });
11810
+ }
11811
+ }
9870
11812
  nextProcess.on("error", (error) => {
9871
11813
  console.error("Error starting Next.js:", error);
9872
11814
  process.exit(1);
@@ -9875,7 +11817,7 @@ Error: Cannot find server.js at:
9875
11817
  process.exit(code || 0);
9876
11818
  });
9877
11819
  }
9878
- var init_launch = __esm(() => {
11820
+ var init_launch2 = __esm(() => {
9879
11821
  init_parse_script_args();
9880
11822
  init_install_diagnosis();
9881
11823
  init_package();
@@ -9899,18 +11841,18 @@ var init_cli_error2 = __esm(() => {
9899
11841
  });
9900
11842
 
9901
11843
  // bin/failproofai.mjs
9902
- import { realpathSync as realpathSync3 } from "fs";
9903
- import { dirname as dirname8, resolve as resolve16 } from "path";
9904
- import { fileURLToPath as fileURLToPath3 } from "url";
11844
+ import { realpathSync as realpathSync4 } from "fs";
11845
+ import { dirname as dirname9, resolve as resolve17 } from "path";
11846
+ import { fileURLToPath as fileURLToPath4 } from "url";
9905
11847
  // package.json
9906
- var version = "0.0.11-beta.9";
11848
+ var version = "0.0.12-beta.0";
9907
11849
 
9908
11850
  // bin/failproofai.mjs
9909
11851
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
9910
- process.env.FAILPROOFAI_PACKAGE_ROOT = resolve16(dirname8(realpathSync3(fileURLToPath3(import.meta.url))), "..");
11852
+ process.env.FAILPROOFAI_PACKAGE_ROOT = resolve17(dirname9(realpathSync4(fileURLToPath4(import.meta.url))), "..");
9911
11853
  }
9912
11854
  if (!process.env.FAILPROOFAI_DIST_PATH) {
9913
- process.env.FAILPROOFAI_DIST_PATH = resolve16(dirname8(realpathSync3(fileURLToPath3(import.meta.url))), "..", "dist");
11855
+ process.env.FAILPROOFAI_DIST_PATH = resolve17(dirname9(realpathSync4(fileURLToPath4(import.meta.url))), "..", "dist");
9914
11856
  }
9915
11857
  var args = process.argv.slice(2);
9916
11858
  if (args[0] === "p")
@@ -9957,7 +11899,7 @@ if (hookIdx >= 0) {
9957
11899
  }
9958
11900
  }
9959
11901
  async function runCli() {
9960
- const SUBCOMMANDS2 = ["policies", "policy", "auth"];
11902
+ const SUBCOMMANDS2 = ["policies", "policy", "auth", "audit"];
9961
11903
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS2.includes(args[0])) {
9962
11904
  const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
9963
11905
  if (extraArgs.length > 0) {
@@ -10004,6 +11946,10 @@ COMMANDS
10004
11946
  whoami Print the currently authenticated identity
10005
11947
  auth --help, -h Show this help for the auth command
10006
11948
 
11949
+ audit Audit your agent's behavior, then open the
11950
+ dashboard at http://localhost:8020/audit
11951
+ audit --help, -h Show this help for the audit command
11952
+
10007
11953
  --version, -v Print version and exit
10008
11954
  --help, -h Show this help message
10009
11955
 
@@ -10036,8 +11982,8 @@ EXAMPLES
10036
11982
 
10037
11983
  LINKS
10038
11984
  \u2B50 Star us: https://github.com/failproofai/failproofai
10039
- \uD83D\uDCD6 Docs: https://befailproof.ai
10040
- \uD83D\uDCAC Slack: https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ
11985
+ \uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction
11986
+ \uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
10041
11987
  `.trimStart());
10042
11988
  process.exit(0);
10043
11989
  }
@@ -10254,9 +12200,19 @@ Run \`failproofai policies --help\` for usage.`);
10254
12200
  lastSubcommand = "auth";
10255
12201
  const { runAuthCli: runAuthCli2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
10256
12202
  await runAuthCli2(args.slice(1));
10257
- await track("cli_auth_invoked", { args_count: args.length - 1 });
12203
+ await track("cli_auth_invoked", {
12204
+ args_count: args.length - 1,
12205
+ subcommand: args[1] ?? "help",
12206
+ exit_code: process.exitCode ?? 0
12207
+ });
10258
12208
  process.exit(process.exitCode ?? 0);
10259
12209
  }
12210
+ if (args[0] === "audit") {
12211
+ lastSubcommand = "audit";
12212
+ const { runAuditCli: runAuditCli2 } = await Promise.resolve().then(() => (init_cli2(), exports_cli2));
12213
+ await runAuditCli2(args.slice(1));
12214
+ return;
12215
+ }
10260
12216
  if (args[0] === "policy") {
10261
12217
  lastSubcommand = "policy";
10262
12218
  const subArgs = args.slice(1);
@@ -10379,7 +12335,7 @@ For multiple policies use \`failproofai policies --${action === "add" ? "install
10379
12335
  dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
10380
12336
  return dp[m][n];
10381
12337
  };
10382
- const primary = ["--version", "--help", "--hook", "policies", "policy", "auth"];
12338
+ const primary = ["--version", "--help", "--hook", "policies", "policy", "auth", "audit"];
10383
12339
  const closest = primary.reduce((best, flag) => {
10384
12340
  const dist = levenshtein(unknownFlag, flag);
10385
12341
  return dist < best.dist ? { flag, dist } : best;
@@ -10400,8 +12356,8 @@ Run \`failproofai --help\` for usage details.`);
10400
12356
  await maybeRunFirstRunNudge2();
10401
12357
  } catch {}
10402
12358
  }
10403
- const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
10404
- launch2("start");
12359
+ const { launch: launch3 } = await Promise.resolve().then(() => (init_launch2(), exports_launch2));
12360
+ launch3("start");
10405
12361
  }
10406
12362
  var { CliError: CliError3 } = await Promise.resolve().then(() => (init_cli_error2(), exports_cli_error));
10407
12363
  try {