failproofai 0.0.2-beta.1 → 0.0.2-beta.3

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 (132) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  9. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
  19. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  20. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  21. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  22. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/index.html +1 -1
  26. package/.next/standalone/.next/server/app/index.rsc +16 -16
  27. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  29. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  30. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
  31. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  33. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  36. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  37. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0a3kr67._.js → [root-of-the-server]__07k6eu-._.js} +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rbuarm._.js → [root-of-the-server]__0kfv9fw._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0osi8nq._.js → [root-of-the-server]__0okos0k._.js} +3 -3
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +5 -4
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0rd0oc-._.js +1 -1
  65. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  66. package/.next/standalone/.next/server/pages/404.html +2 -2
  67. package/.next/standalone/.next/server/pages/500.html +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  70. package/.next/standalone/.next/static/chunks/{0a08gn8709y98.js → 0.jo.465b6_k..js} +1 -1
  71. package/.next/standalone/.next/static/chunks/{0jhw8ofx.5g_e.js → 01haq0a3zrx0v.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/08f78tecvx61l.css +1 -0
  73. package/.next/standalone/.next/static/chunks/{0mr-jhx402yci.js → 0a6xi1a8f_qlp.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{0qvj8bhl661lq.js → 0mq7ze1vkeo1p.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/{0gcz-jqgqz~9m.js → 0p_fpyfmmohnx.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{0kob_5.phc~sk.js → 0qwyj3m400l_g.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{0mjc3aq2wxvlt.js → 0t94r_mk0s7e4.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0q7z97izctgrw.js → 139~00zc9.u7s.js} +1 -1
  79. package/.next/standalone/Dockerfile.docs +12 -0
  80. package/.next/standalone/README.md +68 -55
  81. package/.next/standalone/bin/failproofai.mjs +221 -128
  82. package/.next/standalone/dist/cli.mjs +415 -106
  83. package/.next/standalone/dist/index.js +2 -2
  84. package/.next/standalone/docs/{architecture.md → architecture.mdx} +40 -23
  85. package/.next/standalone/docs/{built-in-policies.md → built-in-policies.mdx} +134 -12
  86. package/.next/standalone/docs/cli/dashboard.mdx +28 -0
  87. package/.next/standalone/docs/cli/environment-variables.mdx +34 -0
  88. package/.next/standalone/docs/cli/hook.mdx +30 -0
  89. package/.next/standalone/docs/cli/install-policies.mdx +48 -0
  90. package/.next/standalone/docs/cli/list-policies.mdx +31 -0
  91. package/.next/standalone/docs/cli/remove-policies.mdx +44 -0
  92. package/.next/standalone/docs/cli/version.mdx +12 -0
  93. package/.next/standalone/docs/{configuration.md → configuration.mdx} +16 -16
  94. package/.next/standalone/docs/{custom-hooks.md → custom-policies.mdx} +80 -42
  95. package/.next/standalone/docs/{dashboard.md → dashboard.mdx} +26 -29
  96. package/.next/standalone/docs/docs.json +31 -4
  97. package/.next/standalone/docs/examples.mdx +253 -0
  98. package/.next/standalone/docs/for-agents.mdx +38 -0
  99. package/.next/standalone/docs/getting-started.mdx +134 -0
  100. package/.next/standalone/docs/introduction.mdx +57 -0
  101. package/.next/standalone/docs/logo/dark.svg +21 -0
  102. package/.next/standalone/docs/logo/light.svg +21 -0
  103. package/.next/standalone/docs/{package-aliases.md → package-aliases.mdx} +5 -5
  104. package/.next/standalone/docs/{testing.md → testing.mdx} +11 -11
  105. package/.next/standalone/package.json +6 -9
  106. package/.next/standalone/scripts/publish-aliases.mjs +4 -2
  107. package/.next/standalone/skills-lock.json +10 -0
  108. package/.next/standalone/src/cli-error.ts +18 -0
  109. package/.next/standalone/src/hooks/builtin-policies.ts +259 -20
  110. package/.next/standalone/src/hooks/manager.ts +17 -3
  111. package/.next/standalone/src/hooks/policy-evaluator.ts +19 -1
  112. package/.next/standalone/src/hooks/policy-helpers.ts +2 -2
  113. package/.next/standalone/vitest.config.e2e.mts +3 -0
  114. package/.next/standalone/vitest.config.mts +3 -0
  115. package/README.md +68 -55
  116. package/bin/failproofai.mjs +221 -128
  117. package/dist/cli.mjs +415 -106
  118. package/dist/index.js +2 -2
  119. package/package.json +6 -9
  120. package/scripts/publish-aliases.mjs +4 -2
  121. package/src/cli-error.ts +18 -0
  122. package/src/hooks/builtin-policies.ts +259 -20
  123. package/src/hooks/manager.ts +17 -3
  124. package/src/hooks/policy-evaluator.ts +19 -1
  125. package/src/hooks/policy-helpers.ts +2 -2
  126. package/.next/standalone/.next/static/chunks/15jpradyu_531.css +0 -1
  127. package/.next/standalone/docs/cli-reference.md +0 -175
  128. package/.next/standalone/docs/getting-started.md +0 -128
  129. package/.next/standalone/docs/introduction.md +0 -47
  130. /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_buildManifest.js +0 -0
  131. /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_clientMiddlewareManifest.js +0 -0
  132. /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -15,21 +15,21 @@
15
15
  [![CI](https://img.shields.io/github/actions/workflow/status/exospherehost/failproofai/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/exospherehost/failproofai/actions)
16
16
  [![Discord](https://img.shields.io/discord/1234567890?style=flat-square&label=Discord&color=5865F2)](https://discord.com/invite/zT92CAgvkj)
17
17
 
18
- Open-source hooks, policies, and project visualization for **Claude Code** & the **Agents SDK**.
18
+ The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code** & the **Agents SDK**.
19
19
 
20
- - **Hooks & Policies** 35+ built-in security policies that run as Claude Code hooks. Block dangerous commands, sanitize secrets, restrict file access, and more.
21
- - **Custom Policies** Write your own policies in JavaScript. Same `allow`/`deny`/`instruct` API as built-in policies, with full async support.
22
- - **Policy Parameters** Tune built-in policies without writing code: configure allowlists, protected branches, thresholds, and custom patterns.
23
- - **Session Viewer** Browse Claude Code projects and sessions locally. Inspect tool calls, messages, and per-session hook activity side-by-side.
20
+ - **30 Built-in Policies** - Catch common agent failure modes out of the box. Block destructive commands, prevent secret leakage, keep agents inside project boundaries, detect loops, and more.
21
+ - **Custom Policies** - Write your own reliability rules in JavaScript. Use the `allow`/`deny`/`instruct` API to enforce conventions, prevent drift, gate operations, or integrate with external systems.
22
+ - **Easy Configuration** - Tune any policy without writing code. Set allowlists, protected branches, thresholds per-project or globally. Three-scope config merges automatically.
23
+ - **Agent Monitor** - See what your agents did while you were away. Browse sessions, inspect every tool call, and review exactly where policies fired.
24
24
 
25
- Everything runs locally no data leaves your machine.
25
+ Everything runs locally - no data leaves your machine.
26
26
 
27
27
  ---
28
28
 
29
29
  ## Requirements
30
30
 
31
31
  - Node.js >= 20.9.0
32
- - Bun >= 1.3.0 (optional only needed for development / building from source)
32
+ - Bun >= 1.3.0 (optional - only needed for development / building from source)
33
33
 
34
34
  ---
35
35
 
@@ -48,7 +48,7 @@ bun add -g failproofai
48
48
  ### 1. Enable policies globally
49
49
 
50
50
  ```bash
51
- failproofai --install-policies
51
+ failproofai policies --install
52
52
  ```
53
53
 
54
54
  Writes hook entries into `~/.claude/settings.json`. Claude Code will now invoke failproofai before and after each tool call.
@@ -59,12 +59,12 @@ Writes hook entries into `~/.claude/settings.json`. Claude Code will now invoke
59
59
  failproofai
60
60
  ```
61
61
 
62
- Opens `http://localhost:8020` browse sessions, inspect logs, manage policies.
62
+ Opens `http://localhost:8020` - browse sessions, inspect logs, manage policies.
63
63
 
64
64
  ### 3. Check what's active
65
65
 
66
66
  ```bash
67
- failproofai --list-policies
67
+ failproofai policies
68
68
  ```
69
69
 
70
70
  ---
@@ -75,22 +75,22 @@ failproofai --list-policies
75
75
 
76
76
  | Scope | Command | Where it writes |
77
77
  |-------|---------|-----------------|
78
- | Global (default) | `failproofai --install-policies` | `~/.claude/settings.json` |
79
- | Project | `failproofai --install-policies --scope project` | `.claude/settings.json` |
80
- | Local | `failproofai --install-policies --scope local` | `.claude/settings.local.json` |
78
+ | Global (default) | `failproofai policies --install` | `~/.claude/settings.json` |
79
+ | Project | `failproofai policies --install --scope project` | `.claude/settings.json` |
80
+ | Local | `failproofai policies --install --scope local` | `.claude/settings.local.json` |
81
81
 
82
82
  ### Install specific policies
83
83
 
84
84
  ```bash
85
- failproofai --install-policies block-sudo block-rm-rf sanitize-api-keys
85
+ failproofai policies --install block-sudo block-rm-rf sanitize-api-keys
86
86
  ```
87
87
 
88
88
  ### Remove policies
89
89
 
90
90
  ```bash
91
- failproofai --remove-policies
91
+ failproofai policies --uninstall
92
92
  # or for a specific scope:
93
- failproofai --remove-policies --scope project
93
+ failproofai policies --uninstall --scope project
94
94
  ```
95
95
 
96
96
  ---
@@ -128,7 +128,7 @@ Policy configuration lives in `~/.failproofai/policies-config.json` (global) or
128
128
  }
129
129
  ```
130
130
 
131
- **Three config scopes** are merged automatically (project → local → global). See [docs/configuration.md](docs/configuration.md) for full merge rules.
131
+ **Three config scopes** are merged automatically (project → local → global). See [docs/configuration.mdx](docs/configuration.mdx) for full merge rules.
132
132
 
133
133
  ---
134
134
 
@@ -136,40 +136,40 @@ Policy configuration lives in `~/.failproofai/policies-config.json` (global) or
136
136
 
137
137
  | Policy | Description | Configurable |
138
138
  |--------|-------------|:---:|
139
- | `block-sudo` | Block sudo commands | `allowPatterns` |
140
- | `block-rm-rf` | Block recursive deletions | `allowPaths` |
141
- | `block-curl-pipe-sh` | Block curl\|bash and wget\|bash | |
139
+ | `block-sudo` | Prevent agents from running privileged system commands | `allowPatterns` |
140
+ | `block-rm-rf` | Prevent accidental recursive file deletion | `allowPaths` |
141
+ | `block-curl-pipe-sh` | Prevent agents from piping untrusted scripts to shell | |
142
142
  | `block-failproofai-commands` | Prevent self-uninstallation | |
143
- | `sanitize-jwt` | Redact JWT tokens from tool output | |
144
- | `sanitize-api-keys` | Redact API keys from tool output | `additionalPatterns` |
145
- | `sanitize-connection-strings` | Redact database credentials from tool output | |
146
- | `sanitize-private-key-content` | Redact PEM private key blocks | |
147
- | `sanitize-bearer-tokens` | Redact Authorization Bearer tokens | |
148
- | `block-env-files` | Block access to .env files | |
149
- | `protect-env-vars` | Block commands that print environment variables | |
150
- | `block-read-outside-cwd` | Block reading files outside the project | `allowPaths` |
151
- | `block-secrets-write` | Block writes to private key and certificate files | `additionalPatterns` |
152
- | `block-push-master` | Block pushing to main/master | `protectedBranches` |
153
- | `block-work-on-main` | Block checking out main/master | `protectedBranches` |
154
- | `block-force-push` | Block `git push --force` | |
155
- | `warn-git-amend` | Warn on `git commit --amend` | |
156
- | `warn-git-stash-drop` | Warn on `git stash drop` | |
157
- | `warn-all-files-staged` | Warn on `git add -A` | |
158
- | `warn-destructive-sql` | Warn on DROP/DELETE SQL statements | |
159
- | `warn-schema-alteration` | Warn on ALTER TABLE statements | |
160
- | `warn-large-file-write` | Warn on large file writes | `thresholdKb` |
161
- | `warn-package-publish` | Warn on `npm publish` | |
162
- | `warn-background-process` | Warn on background process launches | |
163
- | `warn-global-package-install` | Warn on global package installs | |
143
+ | `sanitize-jwt` | Stop JWT tokens from leaking into agent context | |
144
+ | `sanitize-api-keys` | Stop API keys from leaking into agent context | `additionalPatterns` |
145
+ | `sanitize-connection-strings` | Stop database credentials from leaking into agent context | |
146
+ | `sanitize-private-key-content` | Redact PEM private key blocks from output | |
147
+ | `sanitize-bearer-tokens` | Redact Authorization Bearer tokens from output | |
148
+ | `block-env-files` | Keep agents from reading .env files | |
149
+ | `protect-env-vars` | Prevent agents from printing environment variables | |
150
+ | `block-read-outside-cwd` | Keep agents inside project boundaries | `allowPaths` |
151
+ | `block-secrets-write` | Prevent writes to private key and certificate files | `additionalPatterns` |
152
+ | `block-push-master` | Prevent accidental pushes to main/master | `protectedBranches` |
153
+ | `block-work-on-main` | Keep agents off protected branches | `protectedBranches` |
154
+ | `block-force-push` | Prevent `git push --force` | |
155
+ | `warn-git-amend` | Remind agents before amending commits | |
156
+ | `warn-git-stash-drop` | Remind agents before dropping stashes | |
157
+ | `warn-all-files-staged` | Catch accidental `git add -A` | |
158
+ | `warn-destructive-sql` | Catch DROP/DELETE SQL before execution | |
159
+ | `warn-schema-alteration` | Catch ALTER TABLE before execution | |
160
+ | `warn-large-file-write` | Catch unexpectedly large file writes | `thresholdKb` |
161
+ | `warn-package-publish` | Catch accidental `npm publish` | |
162
+ | `warn-background-process` | Catch unintended background process launches | |
163
+ | `warn-global-package-install` | Catch unintended global package installs | |
164
164
  | …and more | | |
165
165
 
166
- Full policy details and parameter reference: [docs/built-in-policies.md](docs/built-in-policies.md)
166
+ Full policy details and parameter reference: [docs/built-in-policies.mdx](docs/built-in-policies.mdx)
167
167
 
168
168
  ---
169
169
 
170
170
  ## Custom policies
171
171
 
172
- Create a `.js` file with your own policies:
172
+ Write your own policies to keep agents reliable and on-task:
173
173
 
174
174
  ```js
175
175
  import { customPolicies, allow, deny, instruct } from "failproofai";
@@ -190,15 +190,16 @@ customPolicies.add({
190
190
  Install with:
191
191
 
192
192
  ```bash
193
- failproofai --install-policies --custom ./my-policies.js
193
+ failproofai policies --install --custom ./my-policies.js
194
194
  ```
195
195
 
196
196
  ### Decision helpers
197
197
 
198
198
  | Function | Effect |
199
199
  |----------|--------|
200
- | `allow()` | Permit the tool call |
201
- | `deny(message)` | Block the tool call; message shown to Claude |
200
+ | `allow()` | Permit the operation |
201
+ | `allow(message)` | Permit and send informational context to Claude *(beta)* |
202
+ | `deny(message)` | Block the operation; message shown to Claude |
202
203
  | `instruct(message)` | Add context to Claude's prompt; does not block |
203
204
 
204
205
  ### Context object (`ctx`)
@@ -213,7 +214,7 @@ failproofai --install-policies --custom ./my-policies.js
213
214
  | `session.sessionId` | `string` | Session identifier |
214
215
  | `session.transcriptPath` | `string` | Path to the session transcript file |
215
216
 
216
- Custom hooks support transitive local imports, async/await, and access to `process.env`. Errors in custom hooks are fail-open (logged to `~/.failproofai/hook.log`, built-in policies continue). See [docs/custom-hooks.md](docs/custom-hooks.md) for the full guide.
217
+ Custom hooks support transitive local imports, async/await, and access to `process.env`. Errors are fail-open (logged to `~/.failproofai/hook.log`, built-in policies continue). See [docs/custom-hooks.mdx](docs/custom-hooks.mdx) for the full guide.
217
218
 
218
219
  ---
219
220
 
@@ -233,14 +234,26 @@ FAILPROOFAI_TELEMETRY_DISABLED=1 failproofai
233
234
 
234
235
  | Guide | Description |
235
236
  |-------|-------------|
236
- | [Getting Started](docs/getting-started.md) | Installation and first steps |
237
- | [CLI Reference](docs/cli-reference.md) | All commands and flags |
238
- | [Configuration](docs/configuration.md) | Config file format and scope merging |
239
- | [Built-in Policies](docs/built-in-policies.md) | All 35+ policies with parameters |
240
- | [Custom Hooks](docs/custom-hooks.md) | Write your own policies |
241
- | [Dashboard](docs/dashboard.md) | Session viewer and policy management |
242
- | [Architecture](docs/architecture.md) | How the hook system works |
243
- | [Testing](docs/testing.md) | Running tests and writing new ones |
237
+ | [Getting Started](docs/getting-started.mdx) | Installation and first steps |
238
+ | [Built-in Policies](docs/built-in-policies.mdx) | All 30 built-in policies with parameters |
239
+ | [Custom Policies](docs/custom-policies.mdx) | Write your own policies |
240
+ | [Configuration](docs/configuration.mdx) | Config file format and scope merging |
241
+ | [Dashboard](docs/dashboard.mdx) | Monitor sessions and review policy activity |
242
+ | [Architecture](docs/architecture.mdx) | How the hook system works |
243
+ | [Testing](docs/testing.mdx) | Running tests and writing new ones |
244
+
245
+ ### Run docs locally
246
+
247
+ ```bash
248
+ docker build -f Dockerfile.docs -t failproofai-docs .
249
+ docker run --rm -p 3000:3000 failproofai-docs
250
+ ```
251
+
252
+ Opens the Mintlify docs site at `http://localhost:3000`. The container watches for changes if you mount the docs directory:
253
+
254
+ ```bash
255
+ docker run --rm -p 3000:3000 -v $(pwd)/docs:/app/docs failproofai-docs
256
+ ```
244
257
 
245
258
  ---
246
259
 
@@ -37,10 +37,40 @@ const args = process.argv.slice(2);
37
37
  // Normalize 'p' → 'policies' (shorthand alias)
38
38
  if (args[0] === "p") args[0] = "policies";
39
39
 
40
- // --help / -h (only when not inside a subcommand that handles its own --help)
41
- const SUBCOMMANDS = ["policies"];
42
- if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
43
- console.log(`
40
+ // --hook <event> called by Claude Code hooks; fast path, outside runCli()
41
+ // because it has its own exit code contract with Claude Code.
42
+ const hookIdx = args.indexOf("--hook");
43
+ if (hookIdx >= 0) {
44
+ if (!args[hookIdx + 1]) {
45
+ console.error("Error: Missing event type after --hook");
46
+ console.error("Usage: failproofai --hook <event> (e.g. PreToolUse, PostToolUse)");
47
+ process.exit(1);
48
+ }
49
+ try {
50
+ const { handleHookEvent } = await import("../src/hooks/handler");
51
+ const exitCode = await handleHookEvent(args[hookIdx + 1]);
52
+ process.exit(exitCode);
53
+ } catch (err) {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ console.error(`Unexpected error: ${msg}`);
56
+ process.exit(2);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Centralised error handler for all CLI subcommands.
62
+ * CliError → clean message, no stack trace, exit exitCode (1 or 2)
63
+ * Error → unexpected; shows message only, exits 2
64
+ */
65
+ async function runCli() {
66
+ // --help / -h (only when not inside a subcommand that handles its own --help)
67
+ const SUBCOMMANDS = ["policies"];
68
+ if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
69
+ const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
70
+ if (extraArgs.length > 0) {
71
+ throw new CliError(`Unexpected argument: ${extraArgs[0]}\nRun \`failproofai --help\` for usage.`);
72
+ }
73
+ console.log(`
44
74
  failproofai v${version}
45
75
 
46
76
  USAGE
@@ -80,33 +110,29 @@ LINKS
80
110
  ⭐ Star us: https://github.com/exospherehost/failproofai
81
111
  📖 Docs: https://befailproof.ai
82
112
  `.trimStart());
83
- process.exit(0);
84
- }
85
-
86
- // --version / -v
87
- if (args.includes("--version") || args.includes("-v")) {
88
- console.log(version);
89
- process.exit(0);
90
- }
113
+ process.exit(0);
114
+ }
91
115
 
92
- // --hook <event> — called by Claude Code hooks; fast path, no dashboard startup
93
- const hookIdx = args.indexOf("--hook");
94
- if (hookIdx >= 0 && args[hookIdx + 1]) {
95
- const { handleHookEvent } = await import("../src/hooks/handler");
96
- const exitCode = await handleHookEvent(args[hookIdx + 1]);
97
- process.exit(exitCode);
98
- }
116
+ // --version / -v
117
+ if ((args.includes("--version") || args.includes("-v")) && !SUBCOMMANDS.includes(args[0])) {
118
+ const extraArgs = args.filter((a) => a !== "--version" && a !== "-v");
119
+ if (extraArgs.length > 0) {
120
+ throw new CliError(`Unexpected argument: ${extraArgs[0]}\nRun \`failproofai --help\` for usage.`);
121
+ }
122
+ console.log(version);
123
+ process.exit(0);
124
+ }
99
125
 
100
- // policies [--install|-i|--uninstall|-u|--help|-h] [names...] [--scope] [--beta] [--custom|-c <path>]
101
- if (args[0] === "policies") {
102
- const subArgs = args.slice(1);
126
+ // policies [--install|-i|--uninstall|-u|--help|-h] [names...] [--scope] [--beta] [--custom|-c <path>]
127
+ if (args[0] === "policies") {
128
+ const subArgs = args.slice(1);
103
129
 
104
- const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
105
- const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
106
- const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
130
+ const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
131
+ const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
132
+ const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
107
133
 
108
- if (isHelp) {
109
- console.log(`
134
+ if (isHelp) {
135
+ console.log(`
110
136
  failproofai policies — manage Failproof AI policies
111
137
 
112
138
  USAGE
@@ -137,118 +163,185 @@ EXAMPLES
137
163
  failproofai policies -u
138
164
  failproofai policies --uninstall --custom
139
165
  `.trimStart());
166
+ process.exit(0);
167
+ }
168
+
169
+ if (isInstall) {
170
+ const { installHooks } = await import("../src/hooks/manager");
171
+
172
+ const scopeIdx = subArgs.indexOf("--scope");
173
+ const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
174
+ if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
175
+ throw new CliError("Missing value for --scope. Valid values: user, project, local");
176
+ }
177
+ if (scopeIdx >= 0 && !["user", "project", "local"].includes(scope)) {
178
+ throw new CliError(`Invalid scope: ${scope}. Valid values: user, project, local`);
179
+ }
180
+
181
+ const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom")
182
+ : subArgs.includes("-c") ? subArgs.indexOf("-c")
183
+ : -1;
184
+ const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
185
+ if (customIdx >= 0 && (!customPoliciesPath || customPoliciesPath.startsWith("-"))) {
186
+ throw new CliError("Missing path after --custom/-c\nUsage: --custom <path> (e.g. --custom ./my-policies.js)");
187
+ }
188
+
189
+ const includeBeta = subArgs.includes("--beta");
190
+
191
+ // Collect positional policy names — args that don't start with - and aren't
192
+ // values consumed by --scope or --custom/-c (tracked by index, not value,
193
+ // so a policy named "user" isn't incorrectly dropped by the default scope).
194
+ const consumedIdxs = new Set();
195
+ if (scopeIdx >= 0) consumedIdxs.add(scopeIdx + 1);
196
+ if (customIdx >= 0) consumedIdxs.add(customIdx + 1);
197
+ const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
198
+ const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
199
+ if (unknownInstallFlag) {
200
+ throw new CliError(`Unknown flag: ${unknownInstallFlag}\nRun \`failproofai policies --help\` for usage.`);
201
+ }
202
+
203
+ const explicitPolicyNames = subArgs.filter(
204
+ (a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx)
205
+ );
206
+
207
+ // When --custom/-c is present but no explicit policy names, pass [] so
208
+ // installHooks uses the existing enabled policies and skips the interactive
209
+ // prompt — validation of the custom file happens inside installHooks.
210
+ const policyNames =
211
+ explicitPolicyNames.length > 0 ? explicitPolicyNames
212
+ : customPoliciesPath !== undefined ? []
213
+ : undefined;
214
+
215
+ await installHooks(
216
+ policyNames,
217
+ scope,
218
+ undefined,
219
+ includeBeta,
220
+ undefined,
221
+ customPoliciesPath,
222
+ );
223
+ process.exit(0);
224
+ }
225
+
226
+ if (isUninstall) {
227
+ const { removeHooks } = await import("../src/hooks/manager");
228
+
229
+ const scopeIdx = subArgs.indexOf("--scope");
230
+ const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
231
+ if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
232
+ throw new CliError("Missing value for --scope. Valid values: user, project, local, all");
233
+ }
234
+ if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
235
+ throw new CliError(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
236
+ }
237
+
238
+ const betaOnly = subArgs.includes("--beta");
239
+ const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
240
+
241
+ const consumedIdxs = new Set();
242
+ if (scopeIdx >= 0) consumedIdxs.add(scopeIdx + 1);
243
+ const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
244
+ const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
245
+ if (unknownUninstallFlag) {
246
+ throw new CliError(`Unknown flag: ${unknownUninstallFlag}\nRun \`failproofai policies --help\` for usage.`);
247
+ }
248
+
249
+ const policyNames = subArgs.filter(
250
+ (a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx)
251
+ );
252
+
253
+ await removeHooks(
254
+ policyNames.length > 0 ? policyNames : undefined,
255
+ scope,
256
+ undefined,
257
+ { betaOnly, removeCustomHooks },
258
+ );
259
+ process.exit(0);
260
+ }
261
+
262
+ // Default: list policies
263
+ // Accept --list as a no-op alias (common intuition), reject all other unknown flags
264
+ // and unexpected positional args (e.g. "hi").
265
+ const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
266
+ const unknownListArg = subArgs.find((a) => a.startsWith("-") && !knownListFlags.has(a));
267
+ if (unknownListArg) {
268
+ throw new CliError(
269
+ `Unknown flag: ${unknownListArg}\n` +
270
+ `Run \`failproofai policies --help\` for usage.`
271
+ );
272
+ }
273
+ const positionalArgs = subArgs.filter((a) => !a.startsWith("-"));
274
+ if (positionalArgs.length > 0) {
275
+ throw new CliError(
276
+ `Unexpected argument: ${positionalArgs[0]}\n` +
277
+ `Run \`failproofai policies --help\` for usage.`
278
+ );
279
+ }
280
+
281
+ const { listHooks } = await import("../src/hooks/manager");
282
+ await listHooks();
140
283
  process.exit(0);
141
284
  }
142
285
 
143
- if (isInstall) {
144
- const { installHooks } = await import("../src/hooks/manager");
145
-
146
- const scopeIdx = subArgs.indexOf("--scope");
147
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
148
-
149
- const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom")
150
- : subArgs.includes("-c") ? subArgs.indexOf("-c")
151
- : -1;
152
- const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
153
-
154
- const includeBeta = subArgs.includes("--beta");
155
-
156
- // Collect positional policy names — args that don't start with - and aren't
157
- // values consumed by --scope or --custom/-c.
158
- const consumed = new Set([scope, customPoliciesPath].filter(Boolean));
159
- const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
160
- const explicitPolicyNames = subArgs.filter(
161
- (a) => !a.startsWith("-") && !flags.has(a) && !consumed.has(a)
162
- );
163
-
164
- // When --custom/-c is present but no explicit policy names, pass [] so
165
- // installHooks uses the existing enabled policies and skips the interactive
166
- // prompt — validation of the custom file happens inside installHooks.
167
- const policyNames =
168
- explicitPolicyNames.length > 0 ? explicitPolicyNames
169
- : customPoliciesPath !== undefined ? []
170
- : undefined;
171
-
172
- await installHooks(
173
- policyNames,
174
- scope,
175
- undefined,
176
- includeBeta,
177
- undefined,
178
- customPoliciesPath,
286
+ // Unknown flag guard — must appear after all known-flag branches
287
+ const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
288
+ const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
289
+
290
+ if (unknownFlag) {
291
+ function levenshtein(a, b) {
292
+ const m = a.length, n = b.length;
293
+ const dp = Array.from({ length: m + 1 }, (_, i) =>
294
+ Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
295
+ );
296
+ for (let i = 1; i <= m; i++)
297
+ for (let j = 1; j <= n; j++)
298
+ dp[i][j] = a[i - 1] === b[j - 1]
299
+ ? dp[i - 1][j - 1]
300
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
301
+ return dp[m][n];
302
+ }
303
+
304
+ const primary = ["--version", "--help", "--hook", "policies"];
305
+ const closest = primary.reduce((best, flag) => {
306
+ const dist = levenshtein(unknownFlag, flag);
307
+ return dist < best.dist ? { flag, dist } : best;
308
+ }, { flag: primary[0], dist: Infinity });
309
+
310
+ throw new CliError(
311
+ `Unknown flag: ${unknownFlag}\n` +
312
+ `Did you mean: ${closest.flag}?\n` +
313
+ `Run \`failproofai --help\` for usage details.`
179
314
  );
180
- process.exit(0);
181
315
  }
182
316
 
183
- if (isUninstall) {
184
- const { removeHooks } = await import("../src/hooks/manager");
185
-
186
- const scopeIdx = subArgs.indexOf("--scope");
187
- const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
188
-
189
- const betaOnly = subArgs.includes("--beta");
190
- const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
191
-
192
- const consumed = new Set([scope].filter(Boolean));
193
- const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
194
- const policyNames = subArgs.filter(
195
- (a) => !a.startsWith("-") && !flags.has(a) && !consumed.has(a)
317
+ // Unknown subcommand guard (non-flag args that aren't "policies")
318
+ const unknownSubcommand = args.find(a => !a.startsWith("-") && a !== "policies");
319
+ if (unknownSubcommand) {
320
+ throw new CliError(
321
+ `Unknown command: ${unknownSubcommand}\n` +
322
+ `Did you mean: failproofai policies?\n` +
323
+ `Run \`failproofai --help\` for usage details.`
196
324
  );
197
-
198
- await removeHooks(
199
- policyNames.length > 0 ? policyNames : undefined,
200
- scope,
201
- undefined,
202
- { betaOnly, removeCustomHooks },
203
- );
204
- process.exit(0);
205
325
  }
206
326
 
207
- // Default: list policies
208
- const { listHooks } = await import("../src/hooks/manager");
209
- await listHooks();
210
- process.exit(0);
327
+ // Dashboard launch — always production mode
328
+ const { launch } = await import("../scripts/launch");
329
+ launch("start");
211
330
  }
212
331
 
213
- // Unknown flag guard must appear after all known-flag branches
214
- const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
215
- const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
332
+ // ── Import CliError for use in the guard above ────────────────────────────────
333
+ const { CliError } = await import("../src/cli-error");
216
334
 
217
- if (unknownFlag) {
218
- function levenshtein(a, b) {
219
- const m = a.length, n = b.length;
220
- const dp = Array.from({ length: m + 1 }, (_, i) =>
221
- Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
222
- );
223
- for (let i = 1; i <= m; i++)
224
- for (let j = 1; j <= n; j++)
225
- dp[i][j] = a[i - 1] === b[j - 1]
226
- ? dp[i - 1][j - 1]
227
- : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
228
- return dp[m][n];
335
+ // ── Run ───────────────────────────────────────────────────────────────────────
336
+ try {
337
+ await runCli();
338
+ } catch (err) {
339
+ if (err instanceof CliError) {
340
+ console.error(`Error: ${err.message}`);
341
+ process.exit(err.exitCode);
229
342
  }
230
-
231
- const primary = ["--version", "--help", "--hook", "policies"];
232
- const closest = primary.reduce((best, flag) => {
233
- const dist = levenshtein(unknownFlag, flag);
234
- return dist < best.dist ? { flag, dist } : best;
235
- }, { flag: primary[0], dist: Infinity });
236
-
237
- console.error(`Unknown flag: ${unknownFlag}`);
238
- console.error(`Did you mean: ${closest.flag}?`);
239
- console.error(`Run \`failproofai --help\` for usage details.`);
240
- process.exit(1);
241
- }
242
-
243
- // Unknown subcommand guard (non-flag args that aren't "policies")
244
- const unknownSubcommand = args.find(a => !a.startsWith("-") && a !== "policies");
245
- if (unknownSubcommand) {
246
- console.error(`Unknown command: ${unknownSubcommand}`);
247
- console.error(`Did you mean: failproofai policies?`);
248
- console.error(`Run \`failproofai --help\` for usage details.`);
249
- process.exit(1);
343
+ // Unexpected internal error — show message only, no stack trace
344
+ const msg = err instanceof Error ? err.message : String(err);
345
+ console.error(`Unexpected error: ${msg}`);
346
+ process.exit(2);
250
347
  }
251
-
252
- // Dashboard launch — always production mode
253
- const { launch } = await import("../scripts/launch");
254
- launch("start");