failproofai 0.0.14-beta.0 → 0.0.14-beta.1

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 (172) hide show
  1. package/.next/standalone/.codex/hooks.json +6 -6
  2. package/.next/standalone/.cursor/hooks.json +6 -6
  3. package/.next/standalone/.factory/hooks.json +9 -9
  4. package/.next/standalone/.next/BUILD_ID +1 -1
  5. package/.next/standalone/.next/build-manifest.json +5 -5
  6. package/.next/standalone/.next/prerender-manifest.json +3 -3
  7. package/.next/standalone/.next/required-server-files.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  9. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +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.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  40. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/index.html +1 -1
  44. package/.next/standalone/.next/server/app/index.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  47. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  48. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  49. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  50. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  56. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  69. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +3 -3
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0brjczq._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h12me5._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07qfsfa._.js → [root-of-the-server]__0wp9w6i._.js} +2 -2
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11rtg6s._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dky4g0._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__212nf49._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_087bt9w._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0g5r64e.js → node_modules_html-to-image_es_index_12rjfea.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0-uvagv.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03c7gi5.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_09p-8om.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0v-kfiu.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0xrgzyz.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1806lsc.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1j6dd-e.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1sa65r-.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  114. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  115. package/.next/standalone/.next/server/pages/404.html +1 -1
  116. package/.next/standalone/.next/server/pages/500.html +1 -1
  117. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  119. package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/{17sdztgbcc0fi.js → 0d5lypu1zs21r.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{2ufuwhv03yvtb.js → 0emg30_6svycu.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/{1374x9bv_s_vg.js → 0o9wpnze1ey8y.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/{454dyizqik6t9.js → 1359o5785rmve.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{43weu8f59tlix.js → 20tuctw38jbgw.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{0405htbxay9vd.js → 2ldhnaweu9ew3.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/{003uz088wbmz7.js → 2vav_x0_336ds.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/{0iinhn4417nk1.js → 3-ogr79xqbw0h.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
  129. package/.next/standalone/.next/static/chunks/{35-90m9py64d4.js → 3cg7x0joi2b98.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/{2o3ql0n5owqsw.js → 3k87p-tijud-4.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/{2m_tbl39fcedn.js → 3kmaqcp7rldad.js} +1 -1
  132. package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
  133. package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
  134. package/.next/standalone/app/components/global-error-listeners.tsx +27 -0
  135. package/.next/standalone/ci/cli-integration/Dockerfile +47 -0
  136. package/.next/standalone/ci/cli-integration/README.md +69 -0
  137. package/.next/standalone/ci/cli-integration/canary-policies.mjs +45 -0
  138. package/.next/standalone/ci/cli-integration/capture-tokens.sh +48 -0
  139. package/.next/standalone/ci/cli-integration/inject-tokens.sh +44 -0
  140. package/.next/standalone/ci/cli-integration/install-clis.sh +91 -0
  141. package/.next/standalone/ci/cli-integration/probe-cli.sh +208 -0
  142. package/.next/standalone/ci/cli-integration/report.js +74 -0
  143. package/.next/standalone/ci/cli-integration/run.sh +107 -0
  144. package/.next/standalone/components/reach-developers.tsx +1 -1
  145. package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
  146. package/.next/standalone/lib/error-origin.ts +67 -0
  147. package/.next/standalone/lib/install-check.ts +231 -0
  148. package/.next/standalone/package.json +1 -3
  149. package/.next/standalone/server.js +1 -1
  150. package/README.md +1 -17
  151. package/bin/failproofai.mjs +11 -1
  152. package/dist/cli.mjs +479 -217
  153. package/lib/error-origin.ts +67 -0
  154. package/lib/install-check.ts +231 -0
  155. package/package.json +1 -3
  156. package/scripts/dev-hook.mjs +292 -0
  157. package/scripts/install-diagnosis.mjs +5 -8
  158. package/scripts/install-telemetry.mjs +12 -4
  159. package/scripts/translate-docs/cli.ts +47 -1
  160. package/scripts/translate-docs/mdx-translator.ts +133 -4
  161. package/scripts/translate-docs/readme-translator.ts +13 -5
  162. package/scripts/translate-docs/translator.ts +38 -2
  163. package/scripts/validate-mdx.ts +12 -4
  164. package/src/audit/cli.ts +41 -9
  165. package/src/hooks/tui.ts +1 -1
  166. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ge8e7w._.js +0 -3
  167. package/.next/standalone/.next/static/chunks/3r--yyjkp9_7b.css +0 -2
  168. package/scripts/postinstall.mjs +0 -242
  169. package/scripts/preuninstall.mjs +0 -131
  170. /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → LkN-JjmhKGmL-WklH7WVi}/_buildManifest.js +0 -0
  171. /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → LkN-JjmhKGmL-WklH7WVi}/_clientMiddlewareManifest.js +0 -0
  172. /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → LkN-JjmhKGmL-WklH7WVi}/_ssgManifest.js +0 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # failproofai CLI Integration Tests — orchestrator (runs on the GitHub Actions runner).
4
+ # ./run.sh [cli ...] default: all 12
5
+ #
6
+ # Mirrors the box's run-canary.sh, but every path is env-parameterised (the
7
+ # runner is ephemeral) and the report is POSTed to a Slack Incoming Webhook
8
+ # instead of delivered via Hermes. Assumes the workflow has already:
9
+ # • checked out + built failproofai (dist/index.js + dist/cli.mjs) at $CANARY_REPO
10
+ # • built the sandbox image ($CANARY_IMAGE)
11
+ # • created the per-run volume ($CANARY_VOL) and INSTALLED the CLIs into it
12
+ # • injected OAuth token files into the volume (inject-tokens.sh)
13
+ # • assembled the gateway/PAT env-file ($CANARY_ENVFILE)
14
+ #
15
+ # VERSION-GATING (identical to the box): a CLI is re-probed only when its own
16
+ # binary version OR failproofai's HEAD changed since its last GREEN run — so
17
+ # daily runs don't burn LLM credits re-testing an unchanged (CLI, failproofai)
18
+ # pair. FAIL/INCONCLUSIVE/ERROR always re-probe until they recover. Override the
19
+ # gated set with CANARY_VERSION_GATED (comma-sep) or "" to force-probe all.
20
+ # ─────────────────────────────────────────────────────────────────────────────
21
+ set -u
22
+ HERE="$(cd "$(dirname "$0")" && pwd)"
23
+
24
+ REPO="${CANARY_REPO:?CANARY_REPO (built failproofai checkout) required}"
25
+ SANDBOX="${CANARY_SANDBOX:-$HERE}"
26
+ VOL="${CANARY_VOL:-canary-ci}"
27
+ IMAGE="${CANARY_IMAGE:-failproofai-canary:base}"
28
+ STATE="${CANARY_STATE:-$HERE/state.json}"
29
+ ENVFILE="${CANARY_ENVFILE:?CANARY_ENVFILE (docker --env-file with gateway creds) required}"
30
+ MODEL="${CANARY_LLM_MODEL:-deepseek-v4-pro}"
31
+ GATED="${CANARY_VERSION_GATED-all}"
32
+
33
+ CLIS=("$@"); [ ${#CLIS[@]} -eq 0 ] && CLIS=(claude codex copilot cursor factory devin antigravity goose opencode pi hermes openclaw)
34
+
35
+ # failproofai main HEAD — the second gate dimension. Prefer the value the workflow
36
+ # computed; fall back to git in the checkout.
37
+ FP_SHA="${CANARY_FP_SHA:-$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo unknown)}"
38
+
39
+ # Installed versions from the install step, keyed by cli.
40
+ VERSIONS_JSON="$(docker run --rm -v "$VOL:/home/canary" "$IMAGE" cat /home/canary/canary-tier0.json 2>/dev/null || echo '[]')"
41
+
42
+ run_probe() {
43
+ docker run --rm --env-file "$ENVFILE" \
44
+ -v "$REPO:/repo:ro" -v "$SANDBOX:/opt/canary:ro" -v "$VOL:/home/canary" \
45
+ "$IMAGE" bash /opt/canary/probe-cli.sh "$1" 2>&1
46
+ }
47
+
48
+ results="[]"
49
+ for cli in "${CLIS[@]}"; do
50
+ cur_ver="$(node -e 'const a=JSON.parse(process.argv[1]);const x=a.find(y=>y.cli===process.argv[2]);process.stdout.write(x&&x.version?x.version:"")' "$VERSIONS_JSON" "$cli")"
51
+
52
+ gate="$(node -e '
53
+ const [statePath, cli, curVer, curSha, gated] = process.argv.slice(1);
54
+ let st={clis:{}}; try{ st=JSON.parse(require("fs").readFileSync(statePath,"utf8")); }catch{}
55
+ const isGated = gated === "all" || gated.split(",").filter(Boolean).includes(cli);
56
+ const prev = st.clis && st.clis[cli];
57
+ let skip = false;
58
+ if (isGated && prev && prev.version && curVer && prev.version===curVer
59
+ && prev.fpSha && curSha && prev.fpSha===curSha) {
60
+ const vals=Object.values((prev.probes||prev)||{});
61
+ // Only carry forward a genuinely GREEN result — FAIL/INCONCLUSIVE/ERROR
62
+ // must keep re-probing so they recover (e.g. an expired token clears once
63
+ // the secret is refreshed) instead of being frozen.
64
+ const green = vals.length>0 && !vals.includes("FAIL") && !vals.includes("INCONCLUSIVE") && !vals.includes("ERROR");
65
+ if (green) skip = true;
66
+ }
67
+ process.stdout.write(skip ? "skip" : "probe");
68
+ ' "$STATE" "$cli" "$cur_ver" "$FP_SHA" "$GATED")"
69
+
70
+ if [ "$gate" = skip ]; then
71
+ echo ">> $cli: CLI $cur_ver + failproofai $FP_SHA both unchanged & last green → gated-skip (protecting quota)" >&2
72
+ vj="$(node -e 'const st=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));const p=st.clis[process.argv[2]];process.stdout.write(JSON.stringify({cli:process.argv[2],probes:p.probes||p,version:p.version,fpSha:p.fpSha,gated:true}))' "$STATE" "$cli")"
73
+ else
74
+ echo ">> probing $cli ..." >&2
75
+ vj="$(run_probe "$cli" | sed -n 's/^VERDICT_JSON //p' | tail -1)"
76
+ [ -z "$vj" ] && vj="{\"cli\":\"$cli\",\"probes\":{}}"
77
+ vj="$(node -e 'const v=JSON.parse(process.argv[1]);v.version=process.argv[2]||null;v.fpSha=process.argv[3]||null;process.stdout.write(JSON.stringify(v))' "$vj" "$cur_ver" "$FP_SHA")"
78
+ fi
79
+ results="$(node -e 'const a=JSON.parse(process.argv[1]);a.push(JSON.parse(process.argv[2]));process.stdout.write(JSON.stringify(a))' "$results" "$vj")"
80
+ done
81
+
82
+ report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL")"
83
+ echo "════ report ════" >&2; printf '%s\n' "$report" >&2; echo "════════════════" >&2
84
+ printf '%s\n' "$report" # also to stdout for the workflow log / artifact
85
+
86
+ if [ -n "${CANARY_SLACK_WEBHOOK:-}" ]; then
87
+ payload="$(node -e 'const t=require("fs").readFileSync(0,"utf8");process.stdout.write(JSON.stringify({text:t}))' <<<"$report")"
88
+ code="$(curl -sS --connect-timeout 10 --max-time 30 \
89
+ -o /dev/null -w '%{http_code}' -X POST -H 'Content-type: application/json' \
90
+ --data "$payload" "$CANARY_SLACK_WEBHOOK" 2>/dev/null || echo 000)"
91
+ if [ "$code" = 200 ]; then echo "✓ posted to Slack webhook" >&2
92
+ else echo "⚠️ Slack webhook POST returned HTTP $code" >&2; fi
93
+ else
94
+ echo "(no CANARY_SLACK_WEBHOOK set — report not posted)" >&2
95
+ fi
96
+
97
+ # Fail the job when any probe reported a hard FAIL (broken enforcement) so this
98
+ # scheduled check actually blocks regressions. ERROR (vendor quota/auth) and
99
+ # INCONCLUSIVE (model didn't attempt the tool) are NOT failures — they mean
100
+ # "couldn't confirm", not "enforcement broke". The report is already emitted +
101
+ # posted above regardless, so the signal is never lost.
102
+ if node -e 'const r=JSON.parse(process.argv[1]);process.exit(r.some(x=>Object.values(x.probes||{}).includes("FAIL"))?1:0)' "$results"; then
103
+ exit 0
104
+ else
105
+ echo "✗ FAIL verdict(s) present — failing the job" >&2
106
+ exit 1
107
+ fi
@@ -25,7 +25,7 @@ const options = [
25
25
  {
26
26
  label: "Join our Discord",
27
27
  icon: MessageCircle,
28
- href: "https://discord.gg/2zjBZP7yQJ",
28
+ href: "https://discord.befailproof.ai/",
29
29
  color: "#5865f2",
30
30
  bg: "rgba(88,101,242,0.08)",
31
31
  },
@@ -5,10 +5,14 @@
5
5
  # harness we integrate with and this repo, and opens ONE auto-PR with the fix.
6
6
  #
7
7
  # Bundles:
8
- # - Node 20 + npm
9
- # - bun (REQUIRED: the cloned repo's .claude/settings.json fires hooks via
10
- # `bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook <Event>`, so the
11
- # dogfooded require-*-before-stop policies enforce the commit/push/PR)
8
+ # - Node 20 + npm (npm is a fallback bun installer for scripts/dev-hook.mjs;
9
+ # node runs the launcher itself)
10
+ # - bun (STILL REQUIRED: the cloned repo's .claude/settings.json fires hooks via
11
+ # `node "$CLAUDE_PROJECT_DIR/scripts/dev-hook.mjs" --hook <Event>`, and that
12
+ # launcher only *locates* bun — it cannot conjure one. Without this layer the
13
+ # dogfooded require-*-before-stop policies never fire and the auto-PR is
14
+ # never committed/pushed. We install to BUN_INSTALL=/home/appuser/.bun, which
15
+ # the launcher probes explicitly, so enforcement survives a broken PATH.)
12
16
  # - Claude Code CLI (`claude`) — run headless with --effort ultracode
13
17
  # - failproofai@latest on PATH (belt-and-suspenders; the dogfood hooks actually
14
18
  # run the clone's SOURCE via bun, not this global)
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Works out whether a browser error actually came from the dashboard.
3
+ *
4
+ * `window`'s "error" and "unhandledrejection" events are page-global: browser
5
+ * extensions inject content scripts into the same page and share the same
6
+ * window, so their failures reach our listeners and get reported as ours. The
7
+ * observed case was MetaMask's "Failed to connect to MetaMask" arriving as a
8
+ * failproofai `unhandled_rejection`. Extensions are user-installed and
9
+ * open-ended, so the noise is unbounded and unrelated to our code.
10
+ *
11
+ * We therefore attribute positively — report an error only when it can be
12
+ * traced to our own origin — rather than maintaining a denylist of extensions
13
+ * we happen to know about. React render errors don't depend on this: the error
14
+ * boundaries (`app/global-error.tsx`, `app/components/error-fallback.tsx`)
15
+ * report `client_error` directly, and React only invokes those for errors thrown
16
+ * inside our own tree.
17
+ */
18
+
19
+ /**
20
+ * Any `<vendor>-extension://` URL — `chrome-extension://`, `moz-extension://`,
21
+ * `safari-web-extension://`, `ms-browser-extension://`. Matching the shared
22
+ * suffix rather than an explicit vendor list means a new browser's scheme is
23
+ * filtered the day it ships.
24
+ */
25
+ const EXTENSION_URL = /\b[\w-]+-extension:\/\//i;
26
+
27
+ export type ErrorOrigin =
28
+ /** Traceable to our own origin — a real dashboard failure. */
29
+ | "app"
30
+ /** Came from a browser extension sharing the page. */
31
+ | "extension"
32
+ /** Not attributable: no stack, or a cross-origin "Script error.". */
33
+ | "unknown";
34
+
35
+ export interface ErrorOriginInput {
36
+ /** `error.stack` / `reason.stack`, when the thrown value was an Error. */
37
+ stack?: string | null;
38
+ /** `ErrorEvent.filename` — the script URL. Absent on rejections. */
39
+ filename?: string | null;
40
+ /** Typically `window.location.origin`. */
41
+ appOrigin: string;
42
+ }
43
+
44
+ /**
45
+ * Classify where an error came from, using whatever location information the
46
+ * browser gave us.
47
+ *
48
+ * Extension frames are checked first: an error whose stack passes through an
49
+ * extension is that extension's problem even if one of our frames appears
50
+ * further down.
51
+ */
52
+ export function classifyErrorOrigin({ stack, filename, appOrigin }: ErrorOriginInput): ErrorOrigin {
53
+ const text = [filename, stack].filter(Boolean).join("\n");
54
+ if (!text.trim()) return "unknown";
55
+ if (EXTENSION_URL.test(text)) return "extension";
56
+ if (appOrigin && text.includes(appOrigin)) return "app";
57
+ return "unknown";
58
+ }
59
+
60
+ /**
61
+ * Whether an error is ours to report. Only positively-attributed errors qualify:
62
+ * `unknown` covers cross-origin "Script error." and stack-less rejections, which
63
+ * are unactionable anyway — there's nothing in them to debug.
64
+ */
65
+ export function isAppError(input: ErrorOriginInput): boolean {
66
+ return classifyErrorOrigin(input) === "app";
67
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Install reporting, driven from the CLI instead of an npm lifecycle script.
3
+ *
4
+ * This used to live in `scripts/postinstall.mjs`. Every package manager now
5
+ * blocks dependency install scripts by default — npm 12 (`allowScripts`), pnpm,
6
+ * bun, and Yarn ≥4.14 — and a publisher cannot opt its own package in, so a
7
+ * postinstall hook is no longer a channel that reaches users. The first non-hook
8
+ * CLI invocation is.
9
+ *
10
+ * Two constraints shape this module:
11
+ *
12
+ * 1. It must never run on the `--hook` fast path in bin/failproofai.mjs. That
13
+ * path fires on every tool call of every agent session; the check belongs
14
+ * in runCli() only.
15
+ * 2. It must be free on the steady-state path. When the recorded version
16
+ * matches, this returns before touching anything else.
17
+ *
18
+ * Everything here is best-effort: telemetry must never break, delay, or fail a
19
+ * real command.
20
+ */
21
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
22
+ import { resolve } from "node:path";
23
+ import { platform, arch, release, homedir, hostname } from "node:os";
24
+ import { createHmac } from "node:crypto";
25
+ import { trackInstallEvent } from "../scripts/install-telemetry.mjs";
26
+
27
+ const FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
28
+ const NAMESPACE = "failproofai-telemetry-v1";
29
+
30
+ /**
31
+ * Cap on the once-per-version report so a hanging network never stalls a command.
32
+ *
33
+ * A ceiling, not a cost: delivery resolves as soon as PostHog answers, measured
34
+ * at ~1.1s from a cold process (these events are the first thing a fresh process
35
+ * sends, so they always pay the DNS + TLS handshake). Raising this only matters
36
+ * when the network is slow — precisely when a tight budget would silently drop
37
+ * the report instead.
38
+ *
39
+ * 5s matches hook-telemetry and the postinstall script this replaces. An earlier
40
+ * 2s left barely any headroom over a 1.85s worst case seen in testing, for a
41
+ * saving nobody experiences on a healthy network.
42
+ */
43
+ const REPORT_TIMEOUT_MS = 5000;
44
+
45
+ function hashToId(raw: string): string {
46
+ return createHmac("sha256", NAMESPACE).update(raw).digest("hex");
47
+ }
48
+
49
+ function lastVersionPath(): string {
50
+ return resolve(homedir(), ".failproofai", "last-version");
51
+ }
52
+
53
+ /**
54
+ * Semver ordering. A release outranks the same version carrying a prerelease
55
+ * tag (semver §11); within a prerelease, numeric identifiers rank below
56
+ * non-numeric ones.
57
+ *
58
+ * Prerelease identifiers are separated by dots only (semver §9). A hyphen is a
59
+ * legal character *inside* an identifier, so `beta-2` is one non-numeric
60
+ * identifier compared lexically — not `beta` and `2`.
61
+ */
62
+ export function compareSemver(a: string, b: string): number {
63
+ const parse = (v: string) => {
64
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(v);
65
+ if (!m) return null;
66
+ return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
67
+ };
68
+ const pa = parse(a);
69
+ const pb = parse(b);
70
+ if (!pa || !pb) return a < b ? -1 : a > b ? 1 : 0;
71
+ for (let i = 0; i < 3; i++) {
72
+ if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1;
73
+ }
74
+ if (pa.pre === null && pb.pre === null) return 0;
75
+ if (pa.pre === null) return 1;
76
+ if (pb.pre === null) return -1;
77
+ const ax = pa.pre.split(".");
78
+ const bx = pb.pre.split(".");
79
+ for (let i = 0; i < Math.max(ax.length, bx.length); i++) {
80
+ const ai = ax[i], bi = bx[i];
81
+ if (ai === undefined) return -1;
82
+ if (bi === undefined) return 1;
83
+ const aNum = /^\d+$/.test(ai), bNum = /^\d+$/.test(bi);
84
+ if (aNum && bNum) {
85
+ const d = Number(ai) - Number(bi);
86
+ if (d !== 0) return d < 0 ? -1 : 1;
87
+ } else if (aNum) {
88
+ return -1;
89
+ } else if (bNum) {
90
+ return 1;
91
+ } else if (ai !== bi) {
92
+ return ai < bi ? -1 : 1;
93
+ }
94
+ }
95
+ return 0;
96
+ }
97
+
98
+ export interface HooksState {
99
+ configured: boolean;
100
+ registered: boolean;
101
+ policyCount: number;
102
+ }
103
+
104
+ /**
105
+ * Snapshot of the user's hook configuration, carried as properties on
106
+ * package_installed.
107
+ *
108
+ * Only inspects Claude's settings, which understates `registered` for someone
109
+ * running failproofai against another CLI. Preserved as-is from the postinstall
110
+ * script so the event keeps its historical meaning.
111
+ */
112
+ export function checkHooks(): HooksState {
113
+ const hooksConfigPath = resolve(homedir(), ".failproofai", "policies-config.json");
114
+ if (!existsSync(hooksConfigPath)) {
115
+ return { configured: false, registered: false, policyCount: 0 };
116
+ }
117
+
118
+ let config;
119
+ try {
120
+ config = JSON.parse(readFileSync(hooksConfigPath, "utf8"));
121
+ } catch {
122
+ return { configured: false, registered: false, policyCount: 0 };
123
+ }
124
+
125
+ if (!Array.isArray(config.enabledPolicies) || config.enabledPolicies.length === 0) {
126
+ return { configured: false, registered: false, policyCount: 0 };
127
+ }
128
+
129
+ const policyCount = config.enabledPolicies.length;
130
+ const settingsPath = resolve(homedir(), ".claude", "settings.json");
131
+ if (!existsSync(settingsPath)) return { configured: true, registered: false, policyCount };
132
+
133
+ let settings;
134
+ try {
135
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
136
+ } catch {
137
+ return { configured: true, registered: false, policyCount };
138
+ }
139
+
140
+ if (!settings?.hooks) return { configured: true, registered: false, policyCount };
141
+
142
+ for (const matchers of Object.values(settings.hooks)) {
143
+ if (!Array.isArray(matchers)) continue;
144
+ for (const matcher of matchers) {
145
+ if (!matcher?.hooks) continue;
146
+ if (matcher.hooks.some((h: Record<string, unknown>) => h[FAILPROOFAI_HOOK_MARKER] === true)) {
147
+ return { configured: true, registered: true, policyCount };
148
+ }
149
+ }
150
+ }
151
+
152
+ return { configured: true, registered: false, policyCount };
153
+ }
154
+
155
+ /**
156
+ * Report an install or upgrade, at most once per version.
157
+ *
158
+ * Fires first_install (nothing recorded yet) or version_changed (recorded
159
+ * version differs), then package_installed, then records `version`. A same-version
160
+ * run is a no-op — unlike the postinstall script this replaces, the CLI has no
161
+ * signal that would distinguish a reinstall from an ordinary invocation, so
162
+ * `direction: "reinstall"` is no longer reported.
163
+ */
164
+ export async function maybeReportInstall(version: string): Promise<void> {
165
+ try {
166
+ const versionFile = lastVersionPath();
167
+
168
+ let previousVersion: string | null = null;
169
+ try {
170
+ if (existsSync(versionFile)) {
171
+ previousVersion = readFileSync(versionFile, "utf8").trim() || null;
172
+ }
173
+ } catch {
174
+ // Unreadable marker — treat as first run rather than give up.
175
+ }
176
+
177
+ // Steady state: same version already reported. Costs one stat() per command.
178
+ if (previousVersion === version) return;
179
+
180
+ const hooks = checkHooks();
181
+
182
+ const events: Promise<void>[] = [];
183
+ if (previousVersion === null) {
184
+ events.push(
185
+ trackInstallEvent("first_install", {
186
+ platform: platform(),
187
+ arch: arch(),
188
+ os_release: release(),
189
+ node_version: process.versions.node,
190
+ }, { version, timeoutMs: REPORT_TIMEOUT_MS }),
191
+ );
192
+ } else {
193
+ const cmp = compareSemver(previousVersion, version);
194
+ events.push(
195
+ trackInstallEvent("version_changed", {
196
+ from_version: previousVersion,
197
+ to_version: version,
198
+ direction: cmp < 0 ? "upgrade" : "downgrade",
199
+ platform: platform(),
200
+ arch: arch(),
201
+ }, { version, timeoutMs: REPORT_TIMEOUT_MS }),
202
+ );
203
+ }
204
+
205
+ events.push(
206
+ trackInstallEvent("package_installed", {
207
+ platform: platform(),
208
+ arch: arch(),
209
+ os_release: release(),
210
+ node_version: process.versions.node,
211
+ hostname_hash: hashToId(hostname()),
212
+ hooks_configured: hooks.configured,
213
+ hooks_registered: hooks.registered,
214
+ enabled_policy_count: hooks.policyCount,
215
+ }, { version, timeoutMs: REPORT_TIMEOUT_MS }),
216
+ );
217
+
218
+ // Record before awaiting: a dropped event is cheaper than re-reporting the
219
+ // same version on every command because delivery kept failing.
220
+ try {
221
+ mkdirSync(resolve(homedir(), ".failproofai"), { recursive: true });
222
+ writeFileSync(versionFile, version, "utf8");
223
+ } catch {
224
+ // best-effort
225
+ }
226
+
227
+ await Promise.all(events.map((p) => p.catch(() => {})));
228
+ } catch {
229
+ // Reporting must never break a command.
230
+ }
231
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.14-beta.0",
3
+ "version": "0.0.14-beta.1",
4
4
  "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs"
@@ -30,8 +30,6 @@
30
30
  "test": "vitest",
31
31
  "test:run": "vitest run",
32
32
  "lint": "eslint . --config eslint.config.mjs",
33
- "postinstall": "node scripts/postinstall.mjs",
34
- "preuninstall": "node scripts/preuninstall.mjs",
35
33
  "prepare": "bun run build",
36
34
  "test:e2e": "vitest run --config vitest.config.e2e.mts",
37
35
  "test:e2e:watch": "vitest --config vitest.config.e2e.mts",
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
9
9
  const hostname = process.env.HOSTNAME || '0.0.0.0'
10
10
 
11
11
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
12
- const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.14-beta.0"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
12
+ const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.14-beta.1"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
13
13
 
14
14
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
15
15
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm](https://img.shields.io/npm/v/failproofai?style=flat-square&color=CB3837)](https://www.npmjs.com/package/failproofai)
6
6
  [![CI](https://img.shields.io/github/actions/workflow/status/failproofai/failproofai/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/failproofai/failproofai/actions)
7
7
  [![Supply Chain](https://img.shields.io/badge/supply%20chain-secure-brightgreen?style=flat-square)](https://github.com/failproofai/failproofai/actions/workflows/osv-scanner.yml)
8
- [![Discord](https://img.shields.io/badge/Discord-join%20us-5865F2?style=flat-square&logo=discord)](https://discord.gg/2zjBZP7yQJ)
8
+ [![Discord](https://img.shields.io/badge/Discord-join%20us-5865F2?style=flat-square&logo=discord)](https://discord.befailproof.ai/)
9
9
  [![Docs](https://img.shields.io/badge/docs-befailproof.ai-002CA7?style=flat-square)](https://docs.befailproof.ai/introduction)
10
10
  [![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-blue?style=flat-square)](./LICENSE)
11
11
 
@@ -119,22 +119,6 @@ before they become incidents. Zero latency. Runs locally.
119
119
  </tr>
120
120
  </table>
121
121
 
122
- > Install hooks for one or any combination: `failproofai policies --install --cli opencode pi` (or `--cli claude codex copilot cursor opencode pi hermes openclaw factory devin antigravity goose`). Omit `--cli` to auto-detect installed CLIs and prompt.
123
- >
124
- > **Hermes** (hermes-agent, a Slack/Telegram gateway) is supported for both **live-hook enforcement** (`--cli hermes` — one install intercepts tool calls from every platform and subagent) and offline **audit** replay of its gateway sessions from the single `~/.hermes/state.db`.
125
- >
126
- > **OpenClaw** (openclaw gateway, a self-hosted multi-channel assistant) is supported for both **live-hook enforcement** (`--cli openclaw`, user-scope) and offline **audit** replay of its JSONL sessions (`~/.openclaw/agents/<id>/sessions/*.jsonl`). Enforcement uses OpenClaw's **in-process plugin hooks** (a shipped `openclaw-plugin/` that async-spawns failproofai — its file-based internal hooks are observation-only and can't block): `before_tool_call` blocks a tool, and `before_agent_finalize` is a real turn-end gate, so the `require-*-before-stop` builtins enforce.
127
- >
128
- > **Factory Droid** (`droid`) is supported for both **live-hook enforcement** (`--cli factory`, user + project scope) and offline **audit** replay of its on-disk JSONL sessions. droid blocks tool calls off hook **exit code 2** (not a JSON decision) and honors `{decision:"block"}` only on the turn-end `Stop` event — failproofai emits the right shape per event automatically.
129
- >
130
- > **Devin CLI** (`devin`, Cognition) is supported for both **live-hook enforcement** (`--cli devin`, user + project scope) and offline **audit** replay of its SQLite sessions (`~/.local/share/devin/cli/sessions.db`). Devin is a **pure Claude-clone** — same event names, same snake_case payload, same `"hooks"`-wrapper config (`~/.config/devin/config.json` / `<cwd>/.devin/config.json`) — blocking via `{decision:"block"}` JSON on every event.
131
- >
132
- > **Antigravity CLI** (`agy`) is supported for both **live-hook enforcement** (`--cli antigravity`, user + project scope) and offline **audit** replay of its plain-JSONL sessions (`~/.gemini/antigravity-cli/brain/<id>/…/transcript_full.jsonl`). Antigravity has its **own** contract (not a Claude-clone): a **named-hook** `hooks.json` schema (`~/.gemini/config/hooks.json` / `<cwd>/.agents/hooks.json`), a camelCase stdin payload that failproofai normalizes, and its own response shapes — `{decision:"deny"}` to block a tool, `{decision:"continue"}` to force another turn at `Stop`, `{injectSteps}` to inject a reminder before the model runs.
133
- >
134
- > **Goose** (codename goose, Block) is supported for both **live-hook enforcement** (`--cli goose`, user + project scope) and offline **audit** replay of its SQLite sessions (`~/.local/share/goose/sessions/sessions.db`). Enforcement uses Goose's **hooks** system (the cross-agent **Open Plugins** spec) — the installer just drops a plugin dir at `~/.agents/plugins/failproofai/` and Goose auto-discovers it. Blocking is `{"decision":"block"}` JSON on the `PreToolUse` event (which fires for the shell tool and inside delegated subagents), verified live against goose v1.43.0; Goose has no turn-end `Stop` event, so the `require-*-before-stop` builtins don't apply (as with Hermes).
135
-
136
- ---
137
-
138
122
  ## Install
139
123
 
140
124
  ```sh
@@ -124,6 +124,16 @@ if (hookIdx >= 0) {
124
124
  * Error → unexpected; shows message only, exits 2
125
125
  */
126
126
  async function runCli() {
127
+ // Report a fresh install / upgrade. Deliberately here rather than at module
128
+ // scope: everything above this point is the --hook fast path, which runs on
129
+ // every tool call. No-ops after the first run on a given version.
130
+ try {
131
+ const { maybeReportInstall } = await import("../lib/install-check");
132
+ await maybeReportInstall(version);
133
+ } catch {
134
+ // never block a command on reporting
135
+ }
136
+
127
137
  // --help / -h (only when not inside a subcommand that handles its own --help)
128
138
  const SUBCOMMANDS = ["policies", "policy", "auth", "audit", "config"];
129
139
  if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
@@ -209,7 +219,7 @@ EXAMPLES
209
219
  LINKS
210
220
  ⭐ Star us: https://github.com/failproofai/failproofai
211
221
  📖 Docs: https://docs.befailproof.ai/introduction
212
- 💬 Discord: https://discord.gg/2zjBZP7yQJ
222
+ 💬 Discord: https://discord.befailproof.ai/
213
223
  `.trimStart());
214
224
  process.exit(0);
215
225
  }