failproofai 0.0.1-beta.5 → 0.0.1-beta.7

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 (115) 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.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +1 -1
  49. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0unoyb1._.js → [root-of-the-server]__0cnhb7_._.js} +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0osi8nq._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +4 -4
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0g.-ow8._.js → [root-of-the-server]__0xmjv9e._.js} +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
  63. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  64. package/.next/standalone/.next/server/pages/404.html +2 -2
  65. package/.next/standalone/.next/server/pages/500.html +1 -1
  66. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  68. package/.next/standalone/.next/static/chunks/{0y-7a0dcrw047.js → 00qeuetfdsgjk.js} +1 -1
  69. package/.next/standalone/.next/static/chunks/{0ke6dphc4axsb.js → 07nvevyjq47qo.js} +1 -1
  70. package/.next/standalone/.next/static/chunks/{0s-.ej3z1p-ke.js → 0b.lzb_keexdb.js} +3 -3
  71. package/.next/standalone/.next/static/chunks/{0ca6y7bjo.ijt.js → 0iv-rbjm2j5nc.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/{10d1jgbx0dgle.js → 0jfhld9mk1zm4.js} +1 -1
  73. package/.next/standalone/.next/static/chunks/{128ogw.xok4x2.js → 0jzowkzl-dd6s.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{0d2zd7ikio.lc.js → 0w4.ug1d.ok15.js} +2 -2
  75. package/.next/standalone/.next/static/chunks/15jpradyu_531.css +1 -0
  76. package/.next/standalone/.next/static/chunks/{12tzy91t9mk_w.js → 16_nu40klq96n.js} +1 -1
  77. package/.next/standalone/README.md +2 -2
  78. package/.next/standalone/app/actions/get-hooks-config.ts +4 -4
  79. package/.next/standalone/app/policies/hooks-client.tsx +2 -2
  80. package/.next/standalone/bin/failproofai.mjs +162 -57
  81. package/.next/standalone/components/reach-developers.tsx +11 -1
  82. package/.next/standalone/docs/architecture.md +6 -6
  83. package/.next/standalone/docs/cli-reference.md +5 -5
  84. package/.next/standalone/docs/configuration.md +12 -12
  85. package/.next/standalone/docs/custom-hooks.md +7 -7
  86. package/.next/standalone/docs/dashboard.md +1 -1
  87. package/.next/standalone/docs/getting-started.md +3 -3
  88. package/.next/standalone/docs/index.md +1 -1
  89. package/.next/standalone/docs/testing.md +1 -1
  90. package/.next/standalone/package.json +1 -1
  91. package/.next/standalone/scripts/launch.ts +2 -0
  92. package/.next/standalone/scripts/postinstall.mjs +2 -2
  93. package/.next/standalone/src/hooks/custom-hooks-loader.ts +6 -6
  94. package/.next/standalone/src/hooks/handler.ts +2 -2
  95. package/.next/standalone/src/hooks/hooks-config.ts +13 -13
  96. package/.next/standalone/src/hooks/install-prompt.ts +2 -2
  97. package/.next/standalone/src/hooks/llm-client.ts +2 -2
  98. package/.next/standalone/src/hooks/manager.ts +34 -26
  99. package/.next/standalone/src/hooks/policy-types.ts +1 -1
  100. package/README.md +2 -2
  101. package/bin/failproofai.mjs +162 -57
  102. package/package.json +1 -1
  103. package/scripts/launch.ts +2 -0
  104. package/scripts/postinstall.mjs +2 -2
  105. package/src/hooks/custom-hooks-loader.ts +6 -6
  106. package/src/hooks/handler.ts +2 -2
  107. package/src/hooks/hooks-config.ts +13 -13
  108. package/src/hooks/install-prompt.ts +2 -2
  109. package/src/hooks/llm-client.ts +2 -2
  110. package/src/hooks/manager.ts +34 -26
  111. package/src/hooks/policy-types.ts +1 -1
  112. package/.next/standalone/.next/static/chunks/08f78tecvx61l.css +0 -1
  113. /package/.next/standalone/.next/static/{W2hv3TMvrp7iPePv6-jJA → H15OMlkQejMvLowzdBldp}/_buildManifest.js +0 -0
  114. /package/.next/standalone/.next/static/{W2hv3TMvrp7iPePv6-jJA → H15OMlkQejMvLowzdBldp}/_clientMiddlewareManifest.js +0 -0
  115. /package/.next/standalone/.next/static/{W2hv3TMvrp7iPePv6-jJA → H15OMlkQejMvLowzdBldp}/_ssgManifest.js +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Read/write the hooks configuration file at ~/.failproofai/hooks-config.json.
2
+ * Read/write the hooks configuration file at ~/.failproofai/policies-config.json.
3
3
  */
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
5
5
  import { resolve, dirname } from "node:path";
@@ -20,21 +20,21 @@ function readConfigAt(path: string): Partial<HooksConfig> {
20
20
 
21
21
  /**
22
22
  * Read and merge hooks config from three scopes in priority order:
23
- * 1. {cwd}/.failproofai/hooks-config.json (project)
24
- * 2. {cwd}/.failproofai/hooks-config.local.json (local)
25
- * 3. ~/.failproofai/hooks-config.json (global)
23
+ * 1. {cwd}/.failproofai/policies-config.json (project)
24
+ * 2. {cwd}/.failproofai/policies-config.local.json (local)
25
+ * 3. ~/.failproofai/policies-config.json (global)
26
26
  *
27
27
  * Merge rules:
28
28
  * enabledPolicies: union + dedup across all three
29
29
  * policyParams: per-policy key, first scope that defines it wins entirely
30
- * customHooksPath: first scope that defines it wins
30
+ * customPoliciesPath: first scope that defines it wins
31
31
  * llm: first scope that defines it wins
32
32
  */
33
33
  export function readMergedHooksConfig(cwd?: string): HooksConfig {
34
34
  const base = cwd ? resolve(cwd) : process.cwd();
35
- const projectPath = resolve(base, ".failproofai", "hooks-config.json");
36
- const localPath = resolve(base, ".failproofai", "hooks-config.local.json");
37
- const globalPath = resolve(homedir(), ".failproofai", "hooks-config.json");
35
+ const projectPath = resolve(base, ".failproofai", "policies-config.json");
36
+ const localPath = resolve(base, ".failproofai", "policies-config.local.json");
37
+ const globalPath = resolve(homedir(), ".failproofai", "policies-config.json");
38
38
 
39
39
  const project = readConfigAt(projectPath);
40
40
  const local = readConfigAt(localPath);
@@ -58,9 +58,9 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
58
58
  }
59
59
  }
60
60
 
61
- // customHooksPath: first scope wins
62
- const customHooksPath =
63
- project.customHooksPath ?? local.customHooksPath ?? global_.customHooksPath;
61
+ // customPoliciesPath: first scope wins
62
+ const customPoliciesPath =
63
+ project.customPoliciesPath ?? local.customPoliciesPath ?? global_.customPoliciesPath;
64
64
 
65
65
  // llm: first scope wins
66
66
  const llm = project.llm ?? local.llm ?? global_.llm;
@@ -68,13 +68,13 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
68
68
  return {
69
69
  enabledPolicies: [...enabledSet],
70
70
  ...(Object.keys(mergedParams).length > 0 ? { policyParams: mergedParams } : {}),
71
- ...(customHooksPath !== undefined ? { customHooksPath } : {}),
71
+ ...(customPoliciesPath !== undefined ? { customPoliciesPath } : {}),
72
72
  ...(llm !== undefined ? { llm } : {}),
73
73
  };
74
74
  }
75
75
 
76
76
  function getConfigPath(): string {
77
- return resolve(homedir(), ".failproofai", "hooks-config.json");
77
+ return resolve(homedir(), ".failproofai", "policies-config.json");
78
78
  }
79
79
 
80
80
  export function readHooksConfig(): HooksConfig {
@@ -263,11 +263,11 @@ export async function promptPolicySelection(
263
263
  );
264
264
  lines.push("");
265
265
  lines.push(
266
- " \x1B[2mTip: --list-policies for a flat list \u00b7 --install-policies <name\u2026> to skip prompt\x1B[0m",
266
+ " \x1B[2mTip: `policies` for a flat list \u00b7 `policies --install <name\u2026>` to skip prompt\x1B[0m",
267
267
  );
268
268
  if (!includeBeta) {
269
269
  lines.push(
270
- " \x1B[2mTip: --install-policies all --beta to include beta policies\x1B[0m",
270
+ " \x1B[2mTip: `policies --install --beta` to include beta policies\x1B[0m",
271
271
  );
272
272
  }
273
273
 
@@ -2,7 +2,7 @@
2
2
  * Shared OpenAI-compatible chat completions client.
3
3
  *
4
4
  * Uses raw `fetch` — no SDK dependency. Any policy can import this
5
- * to make LLM calls using the shared configuration from hooks-config.json
5
+ * to make LLM calls using the shared configuration from policies-config.json
6
6
  * or environment variables.
7
7
  */
8
8
  import { readLlmConfig } from "./hooks-config";
@@ -35,7 +35,7 @@ export async function chatCompletion(
35
35
  const config = readLlmConfig();
36
36
  if (!config) {
37
37
  throw new Error(
38
- "No LLM API key configured. Set FAILPROOFAI_LLM_API_KEY or configure llm.apiKey in hooks-config.json",
38
+ "No LLM API key configured. Set FAILPROOFAI_LLM_API_KEY or configure llm.apiKey in policies-config.json",
39
39
  );
40
40
  }
41
41
 
@@ -182,7 +182,7 @@ export async function installHooks(
182
182
  cwd?: string,
183
183
  includeBeta = false,
184
184
  source?: string,
185
- customHooksPath?: string,
185
+ customPoliciesPath?: string,
186
186
  removeCustomHooks = false,
187
187
  ): Promise<void> {
188
188
  const binaryPath = resolveFailproofaiBinary();
@@ -212,23 +212,23 @@ export async function installHooks(
212
212
  selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
213
213
  }
214
214
 
215
- // Preserve existing config fields (policyParams, customHooksPath, llm) when updating
215
+ // Preserve existing config fields (policyParams, customPoliciesPath, llm) when updating
216
216
  const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
217
217
  if (removeCustomHooks) {
218
- delete configToWrite.customHooksPath;
219
- } else if (customHooksPath) {
220
- configToWrite.customHooksPath = resolve(customHooksPath);
218
+ delete configToWrite.customPoliciesPath;
219
+ } else if (customPoliciesPath) {
220
+ configToWrite.customPoliciesPath = resolve(customPoliciesPath);
221
221
  // Validate the file before committing it to config
222
222
  let validatedHooks: Awaited<ReturnType<typeof loadCustomHooks>> = [];
223
223
  try {
224
- validatedHooks = await loadCustomHooks(configToWrite.customHooksPath, { strict: true });
224
+ validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
225
225
  } catch (err) {
226
226
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
227
227
  process.exit(1);
228
228
  }
229
229
  if (validatedHooks.length === 0) {
230
230
  console.error(
231
- `Error: no hooks registered in ${customHooksPath}. ` +
231
+ `Error: no hooks registered in ${customPoliciesPath}. ` +
232
232
  `Make sure your file calls customPolicies.add(...) at least once.`,
233
233
  );
234
234
  process.exit(1);
@@ -241,8 +241,8 @@ export async function installHooks(
241
241
  console.log(`\nEnabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
242
242
  if (removeCustomHooks) {
243
243
  console.log("Custom hooks path cleared.");
244
- } else if (configToWrite.customHooksPath) {
245
- console.log(`Custom hooks path: ${configToWrite.customHooksPath}`);
244
+ } else if (configToWrite.customPoliciesPath) {
245
+ console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
246
246
  }
247
247
 
248
248
  const settingsPath = getSettingsPath(scope, cwd);
@@ -306,7 +306,7 @@ export async function installHooks(
306
306
  arch: arch(),
307
307
  os_release: release(),
308
308
  hostname_hash: hashToId(hostname()),
309
- has_custom_hooks_path: !!(configToWrite.customHooksPath),
309
+ has_custom_hooks_path: !!(configToWrite.customPoliciesPath),
310
310
  has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
311
311
  param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
312
312
  });
@@ -326,8 +326,8 @@ export async function installHooks(
326
326
  console.log();
327
327
  console.log(`\x1B[33mWarning: Failproof AI hooks are also installed at ${scopeList}.\x1B[0m`);
328
328
  console.log(`Having hooks in multiple scopes may cause duplicate policy evaluation.`);
329
- console.log(`Use \`failproofai --remove-policies --scope ${duplicates[0]}\` to remove the other installation,`);
330
- console.log(`or \`failproofai --list-policies\` to see all scopes.`);
329
+ console.log(`Use \`failproofai policies --uninstall --scope ${duplicates[0]}\` to remove the other installation,`);
330
+ console.log(`or \`failproofai policies\` to see all scopes.`);
331
331
  }
332
332
  }
333
333
 
@@ -340,7 +340,15 @@ export async function installHooks(
340
340
  * @param scope — settings scope to remove from (default: "user"), or "all" to remove from all scopes
341
341
  * @param opts.betaOnly — set to true when removing only beta policies (adds beta_only flag to telemetry)
342
342
  */
343
- export async function removeHooks(policyNames?: string[], scope: HookScope | "all" = "user", cwd?: string, opts?: { betaOnly?: boolean; source?: string }): Promise<void> {
343
+ export async function removeHooks(policyNames?: string[], scope: HookScope | "all" = "user", cwd?: string, opts?: { betaOnly?: boolean; source?: string; removeCustomHooks?: boolean }): Promise<void> {
344
+ // Clear custom hooks path if requested
345
+ if (opts?.removeCustomHooks) {
346
+ const config = readHooksConfig();
347
+ delete config.customPoliciesPath;
348
+ writeHooksConfig(config);
349
+ console.log("Custom hooks path cleared.");
350
+ }
351
+
344
352
  // Remove specific policies from config (keep hooks installed)
345
353
  if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
346
354
  validatePolicyNames(policyNames);
@@ -452,7 +460,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
452
460
  // Clear policy config when removing from all scopes, or when no hooks remain in any scope
453
461
  if (scope === "all" || !HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
454
462
  const existingForClear = readHooksConfig();
455
- const { customHooksPath: _drop, policyParams: _dropParams, ...restClear } = existingForClear;
463
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...restClear } = existingForClear;
456
464
  writeHooksConfig({ ...restClear, enabledPolicies: [] });
457
465
  }
458
466
  }
@@ -467,7 +475,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
467
475
  * Also shows:
468
476
  * - Configured policyParams values beneath each policy
469
477
  * - Warnings for unknown policyParams keys
470
- * - Custom Hooks section if customHooksPath is set
478
+ * - Custom Hooks section if customPoliciesPath is set
471
479
  */
472
480
  export async function listHooks(cwd?: string): Promise<void> {
473
481
  const config = readMergedHooksConfig(cwd);
@@ -520,11 +528,11 @@ export async function listHooks(cwd?: string): Promise<void> {
520
528
  printBetaSection(printSimpleRow);
521
529
 
522
530
  if (config.enabledPolicies.length > 0) {
523
- console.log("\n Policies not installed. Run `failproofai --install-policies` to activate.");
531
+ console.log("\n Policies not installed. Run `failproofai policies --install` to activate.");
524
532
  } else {
525
- console.log("\n Run `failproofai --install-policies` to get started.");
533
+ console.log("\n Run `failproofai policies --install` to get started.");
526
534
  }
527
- console.log(" Config: ~/.failproofai/hooks-config.json\n");
535
+ console.log(" Config: ~/.failproofai/policies-config.json\n");
528
536
  } else if (installedScopes.length === 1) {
529
537
  // State B: Single scope — table with header row
530
538
  const scope = installedScopes[0];
@@ -536,7 +544,7 @@ export async function listHooks(cwd?: string): Promise<void> {
536
544
  for (const policy of regularPolicies) printSimpleRow(policy);
537
545
  printBetaSection(printSimpleRow);
538
546
 
539
- console.log("\n Config: ~/.failproofai/hooks-config.json\n");
547
+ console.log("\n Config: ~/.failproofai/policies-config.json\n");
540
548
  } else {
541
549
  // State C: Multiple scopes — column table
542
550
  const COL = 9;
@@ -580,13 +588,13 @@ export async function listHooks(cwd?: string): Promise<void> {
580
588
  for (const policy of betaPolicies) printMultiScopeRow(policy);
581
589
  }
582
590
 
583
- console.log("\n Config: ~/.failproofai/hooks-config.json");
591
+ console.log("\n Config: ~/.failproofai/policies-config.json");
584
592
 
585
593
  // Multi-scope warning
586
594
  const scopeNames = installedScopes.join(", ");
587
595
  console.log();
588
596
  console.log(`\x1B[33m\u26A0 Hooks in multiple scopes (${scopeNames}).\x1B[0m`);
589
- console.log(" Consider keeping one. Remove with: failproofai --remove-policies --scope <scope>\n");
597
+ console.log(" Consider keeping one. Remove with: failproofai policies --uninstall --scope <scope>\n");
590
598
  }
591
599
 
592
600
  // Warn about unknown policyParams keys
@@ -599,12 +607,12 @@ export async function listHooks(cwd?: string): Promise<void> {
599
607
  }
600
608
 
601
609
  // Custom Policies section
602
- if (config.customHooksPath) {
603
- console.log(`\n \u2500\u2500 Custom Policies (${config.customHooksPath}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
604
- if (!existsSync(config.customHooksPath)) {
605
- console.log(` \x1B[31m\u2717 File not found: ${config.customHooksPath}\x1B[0m`);
610
+ if (config.customPoliciesPath) {
611
+ console.log(`\n \u2500\u2500 Custom Policies (${config.customPoliciesPath}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
612
+ if (!existsSync(config.customPoliciesPath)) {
613
+ console.log(` \x1B[31m\u2717 File not found: ${config.customPoliciesPath}\x1B[0m`);
606
614
  } else {
607
- const hooks = await loadCustomHooks(config.customHooksPath);
615
+ const hooks = await loadCustomHooks(config.customPoliciesPath);
608
616
  if (hooks.length === 0) {
609
617
  console.log(` \x1B[31m\u2717 ERR failed to load (check ~/.failproofai/logs/hooks.log)\x1B[0m`);
610
618
  } else {
@@ -73,5 +73,5 @@ export interface HooksConfig {
73
73
  enabledPolicies: string[];
74
74
  llm?: LlmConfig;
75
75
  policyParams?: Record<string, Record<string, unknown>>;
76
- customHooksPath?: string;
76
+ customPoliciesPath?: string;
77
77
  }
package/README.md CHANGED
@@ -89,7 +89,7 @@ failproofai --remove-policies --scope project
89
89
 
90
90
  ## Configuration
91
91
 
92
- Policy configuration lives in `~/.failproofai/hooks-config.json` (global) or `.failproofai/hooks-config.json` in your project (per-project).
92
+ Policy configuration lives in `~/.failproofai/policies-config.json` (global) or `.failproofai/policies-config.json` in your project (per-project).
93
93
 
94
94
  ```json
95
95
  {
@@ -182,7 +182,7 @@ customPolicies.add({
182
182
  Install with:
183
183
 
184
184
  ```bash
185
- failproofai --install-policies --custom-hooks ./my-policies.js
185
+ failproofai --install-policies --custom ./my-policies.js
186
186
  ```
187
187
 
188
188
  ### Decision helpers
@@ -5,9 +5,8 @@
5
5
  * Handles:
6
6
  * --hook <event> Hook event from Claude Code (minimal startup latency)
7
7
  * --version / -v Print version and exit
8
- * --install-policies Install hooks + enable policies in Claude Code settings
9
- * --remove-policies Remove hooks or disable policies from Claude Code settings
10
- * --list-policies List available policies and their status
8
+ * --help / -h Show usage and exit
9
+ * policies Manage policies (list / install / uninstall)
11
10
  * (default) Launch production dashboard
12
11
  */
13
12
  import { realpathSync } from "node:fs";
@@ -27,6 +26,52 @@ if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
27
26
 
28
27
  const args = process.argv.slice(2);
29
28
 
29
+ // --help / -h (only when not inside a subcommand that handles its own --help)
30
+ const SUBCOMMANDS = ["policies"];
31
+ if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
32
+ console.log(`
33
+ failproofai v${version}
34
+
35
+ USAGE
36
+ failproofai [command] [options]
37
+
38
+ COMMANDS
39
+ (no args) Launch the policy dashboard
40
+
41
+ policies List all available policies and their status
42
+ policies --install, -i Enable policies in Claude Code settings
43
+ [names...] Specific policy names to enable
44
+ --scope user|project|local Config scope to write to (default: user)
45
+ --beta Include beta policies
46
+ --custom, -c <path> Path to a JS file of custom policies
47
+
48
+ policies --uninstall, -u Disable policies or remove hooks
49
+ [names...] Specific policy names to disable
50
+ --scope user|project|local|all Config scope to remove from (default: user)
51
+ --beta Remove only beta policies
52
+ --custom, -c Clear the customPoliciesPath from config
53
+
54
+ policies --help, -h Show this help for the policies command
55
+
56
+ --version, -v Print version and exit
57
+ --help, -h Show this help message
58
+
59
+ EXAMPLES
60
+ failproofai policies
61
+ failproofai policies --install
62
+ failproofai policies --install block-sudo sanitize-api-keys --scope project
63
+ failproofai policies --install --custom ./my-policies.js
64
+ failproofai policies -i -c ./my-policies.js
65
+ failproofai policies --uninstall block-sudo
66
+ failproofai policies --uninstall --custom
67
+
68
+ LINKS
69
+ ⭐ Star us: https://github.com/exospherehost/failproofai
70
+ 📖 Docs: https://befailproof.ai
71
+ `.trimStart());
72
+ process.exit(0);
73
+ }
74
+
30
75
  // --version / -v
31
76
  if (args.includes("--version") || args.includes("-v")) {
32
77
  console.log(version);
@@ -41,70 +86,121 @@ if (hookIdx >= 0 && args[hookIdx + 1]) {
41
86
  process.exit(exitCode);
42
87
  }
43
88
 
44
- // --install-policies [policyNames...] [--scope user|project|local] [--beta] [--custom-hooks <path>] [--remove-custom-hooks]
45
- if (args.includes("--install-policies")) {
46
- const { installHooks } = await import("../src/hooks/manager");
47
-
48
- const scopeIdx = args.indexOf("--scope");
49
- const scope = scopeIdx >= 0 ? args[scopeIdx + 1] : "user";
50
-
51
- const customHooksIdx = args.indexOf("--custom-hooks");
52
- const customHooksPath = customHooksIdx >= 0 ? args[customHooksIdx + 1] : undefined;
53
-
54
- const includeBeta = args.includes("--beta");
55
- const removeCustomHooks = args.includes("--remove-custom-hooks");
56
-
57
- // Collect positional policy names (args after --install-policies that don't start with --)
58
- const installIdx = args.indexOf("--install-policies");
59
- const policyNames = args
60
- .slice(installIdx + 1)
61
- .filter((a) => !a.startsWith("--") && a !== scope && a !== customHooksPath);
62
-
63
- await installHooks(
64
- policyNames.length > 0 ? policyNames : undefined,
65
- scope,
66
- undefined,
67
- includeBeta,
68
- undefined,
69
- customHooksPath,
70
- removeCustomHooks,
71
- );
72
- process.exit(0);
73
- }
89
+ // policies [--install|-i|--uninstall|-u|--help|-h] [names...] [--scope] [--beta] [--custom|-c <path>]
90
+ if (args[0] === "policies") {
91
+ const subArgs = args.slice(1);
92
+
93
+ const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
94
+ const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
95
+ const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
96
+
97
+ if (isHelp) {
98
+ console.log(`
99
+ failproofai policies manage Failproof AI policies
100
+
101
+ USAGE
102
+ failproofai policies List all policies and their status
103
+ failproofai policies --install, -i Enable policies
104
+ failproofai policies --uninstall, -u Disable policies or remove hooks
105
+
106
+ OPTIONS (install)
107
+ [names...] Specific policy names to enable (omit for interactive)
108
+ --scope user|project|local Config scope to write to (default: user)
109
+ --beta Include beta policies
110
+ --custom, -c <path> Path to a JS file of custom policies
111
+ (skips interactive prompt; validates file first)
112
+
113
+ OPTIONS (uninstall)
114
+ [names...] Specific policy names to disable (omit to remove hooks)
115
+ --scope user|project|local|all Config scope to remove from (default: user)
116
+ --beta Remove only beta policies
117
+ --custom, -c Clear the customPoliciesPath from config
118
+
119
+ EXAMPLES
120
+ failproofai policies
121
+ failproofai policies --install
122
+ failproofai policies --install block-sudo sanitize-api-keys
123
+ failproofai policies --install --custom ./my-policies.js
124
+ failproofai policies -i -c ./my-policies.js
125
+ failproofai policies --uninstall block-sudo
126
+ failproofai policies -u
127
+ failproofai policies --uninstall --custom
128
+ `.trimStart());
129
+ process.exit(0);
130
+ }
74
131
 
75
- // --remove-policies [policyNames...] [--scope user|project|local|all] [--beta-only]
76
- if (args.includes("--remove-policies")) {
77
- const { removeHooks } = await import("../src/hooks/manager");
132
+ if (isInstall) {
133
+ const { installHooks } = await import("../src/hooks/manager");
78
134
 
79
- const scopeIdx = args.indexOf("--scope");
80
- const scope = scopeIdx >= 0 ? args[scopeIdx + 1] : "user";
135
+ const scopeIdx = subArgs.indexOf("--scope");
136
+ const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
81
137
 
82
- const betaOnly = args.includes("--beta-only");
138
+ const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom")
139
+ : subArgs.includes("-c") ? subArgs.indexOf("-c")
140
+ : -1;
141
+ const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
83
142
 
84
- const removeIdx = args.indexOf("--remove-policies");
85
- const policyNames = args
86
- .slice(removeIdx + 1)
87
- .filter((a) => !a.startsWith("--") && a !== scope);
143
+ const includeBeta = subArgs.includes("--beta");
88
144
 
89
- await removeHooks(
90
- policyNames.length > 0 ? policyNames : undefined,
91
- scope,
92
- undefined,
93
- { betaOnly },
94
- );
95
- process.exit(0);
96
- }
145
+ // Collect positional policy names — args that don't start with - and aren't
146
+ // values consumed by --scope or --custom/-c.
147
+ const consumed = new Set([scope, customPoliciesPath].filter(Boolean));
148
+ const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
149
+ const explicitPolicyNames = subArgs.filter(
150
+ (a) => !a.startsWith("-") && !flags.has(a) && !consumed.has(a)
151
+ );
97
152
 
98
- // --list-policies
99
- if (args.includes("--list-policies")) {
153
+ // When --custom/-c is present but no explicit policy names, pass [] so
154
+ // installHooks uses the existing enabled policies and skips the interactive
155
+ // prompt — validation of the custom file happens inside installHooks.
156
+ const policyNames =
157
+ explicitPolicyNames.length > 0 ? explicitPolicyNames
158
+ : customPoliciesPath !== undefined ? []
159
+ : undefined;
160
+
161
+ await installHooks(
162
+ policyNames,
163
+ scope,
164
+ undefined,
165
+ includeBeta,
166
+ undefined,
167
+ customPoliciesPath,
168
+ );
169
+ process.exit(0);
170
+ }
171
+
172
+ if (isUninstall) {
173
+ const { removeHooks } = await import("../src/hooks/manager");
174
+
175
+ const scopeIdx = subArgs.indexOf("--scope");
176
+ const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
177
+
178
+ const betaOnly = subArgs.includes("--beta");
179
+ const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
180
+
181
+ const consumed = new Set([scope].filter(Boolean));
182
+ const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
183
+ const policyNames = subArgs.filter(
184
+ (a) => !a.startsWith("-") && !flags.has(a) && !consumed.has(a)
185
+ );
186
+
187
+ await removeHooks(
188
+ policyNames.length > 0 ? policyNames : undefined,
189
+ scope,
190
+ undefined,
191
+ { betaOnly, removeCustomHooks },
192
+ );
193
+ process.exit(0);
194
+ }
195
+
196
+ // Default: list policies
100
197
  const { listHooks } = await import("../src/hooks/manager");
101
198
  await listHooks();
102
199
  process.exit(0);
103
200
  }
104
201
 
105
202
  // Unknown flag guard — must appear after all known-flag branches
106
- const knownFlags = ["--version", "-v", "--hook", "--install-policies",
107
- "--remove-policies", "--list-policies"];
203
+ const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
108
204
  const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
109
205
 
110
206
  if (unknownFlag) {
@@ -121,8 +217,7 @@ if (unknownFlag) {
121
217
  return dp[m][n];
122
218
  }
123
219
 
124
- const primary = ["--version", "--hook", "--install-policies",
125
- "--remove-policies", "--list-policies"];
220
+ const primary = ["--version", "--help", "--hook", "policies"];
126
221
  const closest = primary.reduce((best, flag) => {
127
222
  const dist = levenshtein(unknownFlag, flag);
128
223
  return dist < best.dist ? { flag, dist } : best;
@@ -130,6 +225,16 @@ if (unknownFlag) {
130
225
 
131
226
  console.error(`Unknown flag: ${unknownFlag}`);
132
227
  console.error(`Did you mean: ${closest.flag}?`);
228
+ console.error(`Run \`failproofai --help\` for usage details.`);
229
+ process.exit(1);
230
+ }
231
+
232
+ // Unknown subcommand guard (non-flag args that aren't "policies")
233
+ const unknownSubcommand = args.find(a => !a.startsWith("-") && a !== "policies");
234
+ if (unknownSubcommand) {
235
+ console.error(`Unknown command: ${unknownSubcommand}`);
236
+ console.error(`Did you mean: failproofai policies?`);
237
+ console.error(`Run \`failproofai --help\` for usage details.`);
133
238
  process.exit(1);
134
239
  }
135
240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.1-beta.5",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "Open-source hooks, policies, and project visualization for Claude Code & Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./bin/failproofai.mjs"
package/scripts/launch.ts CHANGED
@@ -20,6 +20,8 @@ export function launch(mode: "dev" | "start"): void {
20
20
  /_/ \\__,_/_/_/ .___/_/ \\____/\\____/_/ /_/ |_/___/
21
21
  /_/ v${version}
22
22
  `);
23
+ console.log(` ⭐ Star us: https://github.com/exospherehost/failproofai`);
24
+ console.log(` 📖 Docs: https://befailproof.ai\n`);
23
25
 
24
26
  let claudeProjectsPath = parsedPath;
25
27
 
@@ -30,7 +30,7 @@ function hashToId(raw) {
30
30
  * @returns {{ configured: boolean, registered: boolean, policyCount: number }}
31
31
  */
32
32
  function checkHooks() {
33
- const hooksConfigPath = resolve(homedir(), ".failproofai", "hooks-config.json");
33
+ const hooksConfigPath = resolve(homedir(), ".failproofai", "policies-config.json");
34
34
  if (!existsSync(hooksConfigPath)) {
35
35
  return { configured: false, registered: false, policyCount: 0 };
36
36
  }
@@ -87,7 +87,7 @@ function printHooksWarning() {
87
87
  console.log(
88
88
  `\n[failproofai] Warning: hooks config exists with enabled policies, but hooks are not registered in Claude Code settings.\n` +
89
89
  ` To re-register hooks, run:\n` +
90
- ` failproofai --remove-policies && failproofai --install-policies\n`
90
+ ` failproofai policies --uninstall && failproofai policies --install\n`
91
91
  );
92
92
  }
93
93
 
@@ -16,18 +16,18 @@ import type { CustomHook } from "./policy-types";
16
16
  const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
17
17
 
18
18
  export async function loadCustomHooks(
19
- customHooksPath: string | undefined,
19
+ customPoliciesPath: string | undefined,
20
20
  opts?: { strict?: boolean },
21
21
  ): Promise<CustomHook[]> {
22
- if (!customHooksPath) return [];
22
+ if (!customPoliciesPath) return [];
23
23
 
24
- const absPath = isAbsolute(customHooksPath)
25
- ? customHooksPath
26
- : resolve(process.cwd(), customHooksPath);
24
+ const absPath = isAbsolute(customPoliciesPath)
25
+ ? customPoliciesPath
26
+ : resolve(process.cwd(), customPoliciesPath);
27
27
 
28
28
  if (!existsSync(absPath)) {
29
29
  if (opts?.strict) throw new Error(`Custom hooks file not found: ${absPath}`);
30
- hookLogWarn(`customHooksPath not found: ${absPath}`);
30
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
31
31
  return [];
32
32
  }
33
33
 
@@ -2,7 +2,7 @@
2
2
  * Hook event handler — invoked when Claude Code triggers a hook.
3
3
  *
4
4
  * Reads the JSON payload from stdin, loads enabled policies from
5
- * ~/.failproofai/hooks-config.json, evaluates matching policies, persists
5
+ * ~/.failproofai/policies-config.json, evaluates matching policies, persists
6
6
  * activity to disk, and returns the appropriate exit code + stdout response.
7
7
  */
8
8
  import type { HookEventType, SessionMetadata } from "./types";
@@ -71,7 +71,7 @@ export async function handleHookEvent(eventType: string): Promise<number> {
71
71
  registerBuiltinPolicies(config.enabledPolicies);
72
72
 
73
73
  // Load and register custom hooks (layer 2, after builtins)
74
- const customHooksList = await loadCustomHooks(config.customHooksPath);
74
+ const customHooksList = await loadCustomHooks(config.customPoliciesPath);
75
75
  for (const hook of customHooksList) {
76
76
  const hookName = hook.name;
77
77
  const fn: PolicyFunction = async (ctx): Promise<PolicyResult> => {