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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0unoyb1._.js → [root-of-the-server]__0cnhb7_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0osi8nq._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +4 -4
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0g.-ow8._.js → [root-of-the-server]__0xmjv9e._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0y-7a0dcrw047.js → 00qeuetfdsgjk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ke6dphc4axsb.js → 07nvevyjq47qo.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0s-.ej3z1p-ke.js → 0b.lzb_keexdb.js} +3 -3
- package/.next/standalone/.next/static/chunks/{0ca6y7bjo.ijt.js → 0iv-rbjm2j5nc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{10d1jgbx0dgle.js → 0jfhld9mk1zm4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{128ogw.xok4x2.js → 0jzowkzl-dd6s.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0d2zd7ikio.lc.js → 0w4.ug1d.ok15.js} +2 -2
- package/.next/standalone/.next/static/chunks/15jpradyu_531.css +1 -0
- package/.next/standalone/.next/static/chunks/{12tzy91t9mk_w.js → 16_nu40klq96n.js} +1 -1
- package/.next/standalone/README.md +2 -2
- package/.next/standalone/app/actions/get-hooks-config.ts +4 -4
- package/.next/standalone/app/policies/hooks-client.tsx +2 -2
- package/.next/standalone/bin/failproofai.mjs +162 -57
- package/.next/standalone/components/reach-developers.tsx +11 -1
- package/.next/standalone/docs/architecture.md +6 -6
- package/.next/standalone/docs/cli-reference.md +5 -5
- package/.next/standalone/docs/configuration.md +12 -12
- package/.next/standalone/docs/custom-hooks.md +7 -7
- package/.next/standalone/docs/dashboard.md +1 -1
- package/.next/standalone/docs/getting-started.md +3 -3
- package/.next/standalone/docs/index.md +1 -1
- package/.next/standalone/docs/testing.md +1 -1
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/launch.ts +2 -0
- package/.next/standalone/scripts/postinstall.mjs +2 -2
- package/.next/standalone/src/hooks/custom-hooks-loader.ts +6 -6
- package/.next/standalone/src/hooks/handler.ts +2 -2
- package/.next/standalone/src/hooks/hooks-config.ts +13 -13
- package/.next/standalone/src/hooks/install-prompt.ts +2 -2
- package/.next/standalone/src/hooks/llm-client.ts +2 -2
- package/.next/standalone/src/hooks/manager.ts +34 -26
- package/.next/standalone/src/hooks/policy-types.ts +1 -1
- package/README.md +2 -2
- package/bin/failproofai.mjs +162 -57
- package/package.json +1 -1
- package/scripts/launch.ts +2 -0
- package/scripts/postinstall.mjs +2 -2
- package/src/hooks/custom-hooks-loader.ts +6 -6
- package/src/hooks/handler.ts +2 -2
- package/src/hooks/hooks-config.ts +13 -13
- package/src/hooks/install-prompt.ts +2 -2
- package/src/hooks/llm-client.ts +2 -2
- package/src/hooks/manager.ts +34 -26
- package/src/hooks/policy-types.ts +1 -1
- package/.next/standalone/.next/static/chunks/08f78tecvx61l.css +0 -1
- /package/.next/standalone/.next/static/{W2hv3TMvrp7iPePv6-jJA → H15OMlkQejMvLowzdBldp}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{W2hv3TMvrp7iPePv6-jJA → H15OMlkQejMvLowzdBldp}/_clientMiddlewareManifest.js +0 -0
- /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/
|
|
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/
|
|
24
|
-
* 2. {cwd}/.failproofai/
|
|
25
|
-
* 3. ~/.failproofai/
|
|
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
|
-
*
|
|
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", "
|
|
36
|
-
const localPath = resolve(base, ".failproofai", "
|
|
37
|
-
const globalPath = resolve(homedir(), ".failproofai", "
|
|
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
|
-
//
|
|
62
|
-
const
|
|
63
|
-
project.
|
|
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
|
-
...(
|
|
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", "
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
215
|
+
// Preserve existing config fields (policyParams, customPoliciesPath, llm) when updating
|
|
216
216
|
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
217
217
|
if (removeCustomHooks) {
|
|
218
|
-
delete configToWrite.
|
|
219
|
-
} else if (
|
|
220
|
-
configToWrite.
|
|
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.
|
|
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 ${
|
|
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.
|
|
245
|
-
console.log(`Custom hooks path: ${configToWrite.
|
|
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.
|
|
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
|
|
330
|
-
console.log(`or \`failproofai
|
|
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 {
|
|
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
|
|
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
|
|
531
|
+
console.log("\n Policies not installed. Run `failproofai policies --install` to activate.");
|
|
524
532
|
} else {
|
|
525
|
-
console.log("\n Run `failproofai --install
|
|
533
|
+
console.log("\n Run `failproofai policies --install` to get started.");
|
|
526
534
|
}
|
|
527
|
-
console.log(" Config: ~/.failproofai/
|
|
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/
|
|
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/
|
|
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
|
|
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.
|
|
603
|
-
console.log(`\n \u2500\u2500 Custom Policies (${config.
|
|
604
|
-
if (!existsSync(config.
|
|
605
|
-
console.log(` \x1B[31m\u2717 File not found: ${config.
|
|
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.
|
|
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 {
|
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/
|
|
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
|
|
185
|
+
failproofai --install-policies --custom ./my-policies.js
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
### Decision helpers
|
package/bin/failproofai.mjs
CHANGED
|
@@ -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
|
-
* --
|
|
9
|
-
*
|
|
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
|
|
45
|
-
if (args
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
const { removeHooks } = await import("../src/hooks/manager");
|
|
132
|
+
if (isInstall) {
|
|
133
|
+
const { installHooks } = await import("../src/hooks/manager");
|
|
78
134
|
|
|
79
|
-
|
|
80
|
-
|
|
135
|
+
const scopeIdx = subArgs.indexOf("--scope");
|
|
136
|
+
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
81
137
|
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
const policyNames = args
|
|
86
|
-
.slice(removeIdx + 1)
|
|
87
|
-
.filter((a) => !a.startsWith("--") && a !== scope);
|
|
143
|
+
const includeBeta = subArgs.includes("--beta");
|
|
88
144
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
scope,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
// --
|
|
99
|
-
|
|
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", "--
|
|
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", "
|
|
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
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
|
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -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", "
|
|
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
|
|
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
|
-
|
|
19
|
+
customPoliciesPath: string | undefined,
|
|
20
20
|
opts?: { strict?: boolean },
|
|
21
21
|
): Promise<CustomHook[]> {
|
|
22
|
-
if (!
|
|
22
|
+
if (!customPoliciesPath) return [];
|
|
23
23
|
|
|
24
|
-
const absPath = isAbsolute(
|
|
25
|
-
?
|
|
26
|
-
: resolve(process.cwd(),
|
|
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(`
|
|
30
|
+
hookLogWarn(`customPoliciesPath not found: ${absPath}`);
|
|
31
31
|
return [];
|
|
32
32
|
}
|
|
33
33
|
|
package/src/hooks/handler.ts
CHANGED
|
@@ -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/
|
|
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.
|
|
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> => {
|