failproofai 1.0.2-beta.0 → 1.0.2-beta.2
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 +5 -5
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- 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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- 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 +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07gm3zl._.js +14 -14
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__090sxxw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0wb8osu._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_12l4a_k._.js → _1oggjel._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- 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 +19 -19
- package/.next/standalone/.next/static/chunks/{3y54nw0i7dsjl.js → 0nnbtp0w4sxrr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0sa9v5mhc8861.js → 0ykd_go4oahan.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0eqky-7n7dgdm.js → 140ip21rqmelt.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2i2yt3hzjredm.js → 1da5a_a-3btas.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1oml7c7env4-g.js → 1fac8krph-zgp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2hs7ycvsav7v2.js → 1nzy5jc_vf9e0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1sl2xmn9inx3y.js → 1yw71gn8vqco1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1nuqytbta44af.js → 29eh4k77otcw6.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2aawsjld9ied_.js → 2axi5ehjbld9h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2gn7ut4svofm2.js → 2fzxl7n717pbc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{35xg6retpe6rp.js → 3jjmdqqzem21x.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3kjgdgwci4tqf.js → turbopack-2vqpa-dts1gy4.js} +1 -1
- package/.next/standalone/app/actions/pack-actions.ts +21 -11
- package/.next/standalone/app/policies/hooks-client.tsx +3 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
- package/.next/standalone/package.json +10 -11
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +455 -316
- package/dist/cli.mjs +2374 -1873
- package/dist/worker.mjs +3 -13
- package/package.json +10 -11
- package/src/hooks/configure-wizard.ts +67 -444
- package/src/hooks/fp-reset.ts +10 -19
- package/src/hooks/handler.ts +2 -2
- package/src/hooks/manager.ts +58 -24
- package/src/hooks/pack-cli.ts +488 -43
- package/src/hooks/pack-failclosed.ts +1 -1
- package/src/hooks/pack-manifest.ts +14 -6
- package/src/hooks/pack-store.ts +89 -128
- package/src/hooks/tui.ts +150 -48
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0zig5sv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_00e9tnf._.js +0 -23
- package/policy-pack/SHA256SUMS +0 -2
- package/policy-pack/failproofai-pack.json +0 -732
- package/policy-pack/failproofai-pack.mjs +0 -2017
- package/src/hooks/policy-presets.ts +0 -137
- /package/.next/standalone/.next/static/{nwbyWT06Ud0XdXOrVJyvS → A7czO1PZdTcNzLGa_H1sm}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{nwbyWT06Ud0XdXOrVJyvS → A7czO1PZdTcNzLGa_H1sm}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{nwbyWT06Ud0XdXOrVJyvS → A7czO1PZdTcNzLGa_H1sm}/_ssgManifest.js +0 -0
package/bin/failproofai.mjs
CHANGED
|
@@ -34,8 +34,35 @@ if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
|
34
34
|
|
|
35
35
|
const args = process.argv.slice(2);
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
|
|
37
|
+
// ── one noun for policies ──────────────────────────────────────────────────
|
|
38
|
+
// `policies`, `policy` and `pack` were three commands for one idea, two of them
|
|
39
|
+
// a single letter apart and doing unrelated things. They are now three
|
|
40
|
+
// spellings of the same command. Rewritten HERE, above SUBCOMMANDS and every
|
|
41
|
+
// dispatch below, so the rest of this file mentions only the canonical name and
|
|
42
|
+
// no branch has to remember the aliases.
|
|
43
|
+
//
|
|
44
|
+
// Nothing anybody has typed before stops working — the old spellings are
|
|
45
|
+
// translated, not rejected — which matters because they are printed in shipped
|
|
46
|
+
// help output, in this repo's docs, and in the release notes of every pack
|
|
47
|
+
// published so far.
|
|
48
|
+
if (args[0] === "p" || args[0] === "policy") args[0] = "policies";
|
|
49
|
+
if (args[0] === "pack") {
|
|
50
|
+
args[0] = "policies";
|
|
51
|
+
if (args[1] === "list") {
|
|
52
|
+
// `pack list` was two commands wearing one name: bare it listed what is
|
|
53
|
+
// installed here, with an argument it previewed a pack that is not. Those
|
|
54
|
+
// are different questions, so they are different words now — the bare form
|
|
55
|
+
// and `show`.
|
|
56
|
+
const hasSource = args[2] && !args[2].startsWith("-");
|
|
57
|
+
if (hasSource) args.splice(1, 1, "show");
|
|
58
|
+
else args.splice(1, 1);
|
|
59
|
+
} else if (args[1] === "build") {
|
|
60
|
+
// `pack build` produced the release assets and stopped. That is exactly
|
|
61
|
+
// `publish` with nowhere to publish to, so it IS publish — the local half
|
|
62
|
+
// of it. `publish` with no --repo does the same thing and says so.
|
|
63
|
+
args.splice(0, 2, "publish");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
39
66
|
// Normalize 'configure' / 'setup' → 'config' (aliases), so every later check
|
|
40
67
|
// (SUBCOMMANDS, dispatch) mentions only the canonical name.
|
|
41
68
|
if (args[0] === "configure" || args[0] === "setup") args[0] = "config";
|
|
@@ -271,168 +298,109 @@ if (hookIdx >= 0) {
|
|
|
271
298
|
*/
|
|
272
299
|
async function runCli() {
|
|
273
300
|
// --help / -h (only when not inside a subcommand that handles its own --help)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
301
|
+
// `update` and `migrate` were missing here, so `failproofai update --help`
|
|
302
|
+
// exited 1 with "Unexpected argument" — both commands had no reachable help
|
|
303
|
+
// at all, only the paragraph in the top-level dump that this rewrite moved.
|
|
304
|
+
// `help` and `publish` are new. `policy` and `pack` are canonicalized to
|
|
305
|
+
// `policies` above and never reach this list.
|
|
306
|
+
const SUBCOMMANDS = ["policies", "audit", "config", "uninstall", "backfill", "flush", "harness", "publish", "update", "migrate", "help"];
|
|
307
|
+
// ── help ─────────────────────────────────────────────────────────────────
|
|
308
|
+
//
|
|
309
|
+
// The index and the reference manual used to be the same document: 152 lines,
|
|
310
|
+
// six screens at 80x24, with every flag of every command inlined. That is a
|
|
311
|
+
// help tier collapse — the thing you read to find a command was the thing you
|
|
312
|
+
// read to use one — and the cost fell on the person who knew least.
|
|
313
|
+
//
|
|
314
|
+
// Now: ONE screen of what exists, and `help <command>` for everything else.
|
|
315
|
+
// `help <command>` is literally `<command> --help`, dispatched below, so there
|
|
316
|
+
// is exactly one copy of each command's documentation and the two spellings
|
|
317
|
+
// cannot drift.
|
|
318
|
+
const helpTopic = args[0] === "help" ? args[1] : undefined;
|
|
319
|
+
if (args[0] === "help" && helpTopic) {
|
|
320
|
+
// `--hook` is the entry point an agent CLI spawns per tool call. It is
|
|
321
|
+
// documented in NO help output — only a module docblock and one error
|
|
322
|
+
// string — so it gets a topic here rather than a line on the index, where a
|
|
323
|
+
// machine-facing flag would only take space from the human-facing commands.
|
|
324
|
+
if (helpTopic === "hook") {
|
|
325
|
+
console.log(`
|
|
326
|
+
failproofai --hook <event> [--cli <name>]
|
|
327
|
+
|
|
328
|
+
The entry point your agent CLI spawns, once per tool call. You do not run this;
|
|
329
|
+
\`failproofai config\` writes it into each CLI's hook configuration for you.
|
|
330
|
+
|
|
331
|
+
--hook <event> PreToolUse, PostToolUse, UserPromptSubmit, Stop,
|
|
332
|
+
SubagentStop, SessionStart, SessionEnd, PreCompact,
|
|
333
|
+
Notification, PermissionRequest
|
|
334
|
+
--cli <name> claude, codex, copilot, cursor, opencode, pi, hermes,
|
|
335
|
+
openclaw, factory, devin, antigravity, goose.
|
|
336
|
+
Defaults to claude. It selects which payload shape to
|
|
337
|
+
expect: each CLI names its events and tool arguments
|
|
338
|
+
differently, and failproofai canonicalizes them.
|
|
339
|
+
|
|
340
|
+
It reads the event as JSON on stdin and answers on stdout, in whatever shape
|
|
341
|
+
that CLI honours. Exit codes and response shapes differ per CLI by necessity —
|
|
342
|
+
see docs.befailproof.ai. Denials are reported to the agent, never to you.
|
|
343
|
+
`.trimStart());
|
|
344
|
+
process.exit(0);
|
|
345
|
+
}
|
|
346
|
+
// Canonicalize the topic the same way a typed command is canonicalized, so
|
|
347
|
+
// `help pack` and `help policy` answer instead of erroring.
|
|
348
|
+
const canonical =
|
|
349
|
+
helpTopic === "p" || helpTopic === "policy" || helpTopic === "pack"
|
|
350
|
+
? "policies"
|
|
351
|
+
: helpTopic === "configure" || helpTopic === "setup"
|
|
352
|
+
? "config"
|
|
353
|
+
: helpTopic;
|
|
354
|
+
if (!SUBCOMMANDS.includes(canonical)) {
|
|
355
|
+
throw new CliError(
|
|
356
|
+
`No help for: ${helpTopic}\n` +
|
|
357
|
+
`Run \`failproofai help\` to see every command.`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
// `policies add|remove|show` has its own help, distinct from the listing's.
|
|
361
|
+
args.splice(0, 2, canonical, ...(canonical === "policies" && !args[2] ? [] : []), "--help");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
args[0] === "help" ||
|
|
366
|
+
((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0]))
|
|
367
|
+
) {
|
|
368
|
+
const extraArgs = args.filter((a) => a !== "--help" && a !== "-h" && a !== "help");
|
|
277
369
|
if (extraArgs.length > 0) {
|
|
278
|
-
throw new CliError(`Unexpected argument: ${extraArgs[0]}\nRun \`failproofai
|
|
370
|
+
throw new CliError(`Unexpected argument: ${extraArgs[0]}\nRun \`failproofai help\` for usage.`);
|
|
279
371
|
}
|
|
280
372
|
console.log(`
|
|
281
|
-
failproofai v${version}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
--scope user|project|local Config scope to write to (default: user)
|
|
308
|
-
(Codex / Copilot / Cursor / OpenCode / Pi support user|project only)
|
|
309
|
-
--beta Include beta policies
|
|
310
|
-
--custom, -c <path> Custom policy file (repeat for multiple files)
|
|
311
|
-
|
|
312
|
-
policies --uninstall, -u Disable policies or remove hooks
|
|
313
|
-
[names...] Specific policy names to disable
|
|
314
|
-
--cli claude|codex|copilot|cursor|opencode|pi|hermes|openclaw|factory|devin|antigravity|goose
|
|
315
|
-
Agent CLI(s) to uninstall from
|
|
316
|
-
--scope user|project|local|all Config scope to remove from (default: user)
|
|
317
|
-
--beta Remove only beta policies
|
|
318
|
-
--custom, -c Clear all explicit custom policy paths
|
|
319
|
-
|
|
320
|
-
policies --help, -h Show this help for the policies command
|
|
321
|
-
|
|
322
|
-
pack add core Install the Failproof AI policies — from this
|
|
323
|
-
package, so it needs no network.
|
|
324
|
-
--policy <a,b> takes only those, --category
|
|
325
|
-
<x,y> takes whole categories, --all takes it all
|
|
326
|
-
pack list Show installed policy packs
|
|
327
|
-
pack list <source> Show what a pack CONTAINS before installing it.
|
|
328
|
-
Reads only its manifest — the code is never
|
|
329
|
-
downloaded, so looking is never running
|
|
330
|
-
pack add <source> Install someone else's pack from a GitHub release.
|
|
331
|
-
<source> is acme/pack, acme/pack@v1.2.0, or a
|
|
332
|
-
release URL; no tag takes the newest and pins it
|
|
333
|
-
pack remove <publisher/name> Deactivate an installed pack
|
|
334
|
-
pack build <entry.mjs> Turn your own policy file into the three assets
|
|
335
|
-
a GitHub release needs, so others can install it
|
|
336
|
-
pack --help, -h Show this help for the pack command
|
|
337
|
-
harness list Show extra capture paths per agent CLI
|
|
338
|
-
harness add-path <h> <path> Also capture sessions from <path> for harness
|
|
339
|
-
<h>. Accepts \`<label>=<path>\`; the label
|
|
340
|
-
namespaces agent ids so two copies of one
|
|
341
|
-
project stay distinct.
|
|
342
|
-
harness remove-path <h> <p> Stop capturing that path
|
|
343
|
-
harness --help, -h Show this help for the harness command
|
|
344
|
-
|
|
345
|
-
audit Audit your agent's behavior, then open the
|
|
346
|
-
dashboard at http://localhost:8020/audit
|
|
347
|
-
audit --schedule [days] Audit on a timer (default 7 days) and email you
|
|
348
|
-
what it finds. Signs you in the first time;
|
|
349
|
-
--email <address> skips that question
|
|
350
|
-
audit --no-schedule Stop auditing on a timer
|
|
351
|
-
audit --status Whether scheduling is on, where reports go, and
|
|
352
|
-
when the next scan is due
|
|
353
|
-
audit --help, -h Show this help for the audit command
|
|
354
|
-
|
|
355
|
-
backfill Re-send history the collector already read past
|
|
356
|
-
— after clearing the dashboard, re-enrolling a
|
|
357
|
-
machine, or connecting later than the work
|
|
358
|
-
--since <when> How far back: 30d, 6m, or YYYY-MM-DD
|
|
359
|
-
(default: 30 days)
|
|
360
|
-
--dry-run Report what would be re-read, change nothing
|
|
361
|
-
|
|
362
|
-
flush Deliver everything already spooled, now,
|
|
363
|
-
instead of waiting for the next sweep
|
|
364
|
-
--wait Block until the spool drains (or --timeout)
|
|
365
|
-
--timeout <secs> How long to wait with --wait (default: 60)
|
|
366
|
-
|
|
367
|
-
update Finish an upgrade npm cannot: migrate
|
|
368
|
-
~/.failproofai to this version's layout, put the
|
|
369
|
-
matching daemon binary in place, restart it.
|
|
370
|
-
Run after \`npm install -g failproofai@latest\`.
|
|
371
|
-
--no-daemon Migrate the home only
|
|
372
|
-
|
|
373
|
-
migrate Run pending layout migrations on their own,
|
|
374
|
-
keyed on the layout in ~/.failproofai/VERSION
|
|
375
|
-
(not the npm version, so skipping releases with
|
|
376
|
-
no layout change runs nothing)
|
|
377
|
-
--dry-run Print the steps and change nothing
|
|
378
|
-
|
|
379
|
-
uninstall Remove failproofai from this machine: hook
|
|
380
|
-
entries from every agent CLI, and the daemon
|
|
381
|
-
service. Run this BEFORE \`npm rm -g failproofai\`
|
|
382
|
-
— npm runs no uninstall script, so removing the
|
|
383
|
-
package alone leaves both behind.
|
|
384
|
-
--purge Also delete ~/.failproofai (settings,
|
|
385
|
-
credentials, audit history, daemon binary)
|
|
386
|
-
--dry-run Show what would be removed, change nothing
|
|
387
|
-
--yes, -y Skip the confirmation prompt
|
|
388
|
-
|
|
389
|
-
--version, -v Print version and exit
|
|
390
|
-
--help, -h Show this help message
|
|
391
|
-
|
|
392
|
-
CONVENTION POLICIES
|
|
393
|
-
Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
|
|
394
|
-
Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
|
|
395
|
-
No --custom flag or config changes needed — just drop files and they're picked up.
|
|
396
|
-
|
|
397
|
-
EXAMPLES
|
|
398
|
-
failproofai policies
|
|
399
|
-
failproofai policies --install
|
|
400
|
-
failproofai policies --install block-sudo sanitize-api-keys --scope project
|
|
401
|
-
failproofai policies --install --cli codex --scope project
|
|
402
|
-
failproofai policies --install --cli copilot --scope project
|
|
403
|
-
failproofai policies --install --cli cursor --scope project
|
|
404
|
-
failproofai policies --install --cli opencode --scope project
|
|
405
|
-
failproofai policies --install --cli pi --scope project
|
|
406
|
-
failproofai policies --install --cli factory --scope project
|
|
407
|
-
failproofai policies --install --cli devin --scope project
|
|
408
|
-
failproofai policies --install --cli claude codex copilot cursor opencode pi hermes openclaw factory devin antigravity goose
|
|
409
|
-
failproofai policies --install --custom ./my-policies.js
|
|
410
|
-
failproofai policies -i -c ./my-policies.js
|
|
411
|
-
failproofai policies --uninstall block-sudo
|
|
412
|
-
failproofai policies --uninstall --cli codex
|
|
413
|
-
failproofai policies --uninstall --cli copilot
|
|
414
|
-
failproofai policies --uninstall --cli cursor
|
|
415
|
-
failproofai policies --uninstall --cli opencode
|
|
416
|
-
failproofai policies --uninstall --cli pi
|
|
417
|
-
failproofai policies --uninstall --custom
|
|
418
|
-
failproofai pack add core
|
|
419
|
-
failproofai pack add core --category git,database
|
|
420
|
-
failproofai pack list acme/support-agent
|
|
421
|
-
failproofai pack build ./my-policies.mjs --id acme/support --version 1.0.0
|
|
422
|
-
failproofai backfill --since 6m
|
|
423
|
-
failproofai backfill --dry-run
|
|
424
|
-
failproofai flush --wait
|
|
425
|
-
failproofai config --status
|
|
426
|
-
|
|
427
|
-
LINKS
|
|
428
|
-
⭐ Star us: https://github.com/failproofai/failproofai
|
|
429
|
-
📖 Docs: https://docs.befailproof.ai/introduction
|
|
430
|
-
💬 Discord: https://discord.befailproof.ai/
|
|
431
|
-
👽 Reddit: https://www.reddit.com/r/failproofai/
|
|
373
|
+
failproofai v${version} -v version -h this screen
|
|
374
|
+
Usage failproofai <command> [options] Detail failproofai help <command>
|
|
375
|
+
━━ 1 GET IT RUNNING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
376
|
+
config, setup Interactive setup: agents, daemon, cloud
|
|
377
|
+
update Finish an npm upgrade: migrate home, match daemon
|
|
378
|
+
━━ 2 CHOOSE WHAT IT ENFORCES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
379
|
+
policies Every policy on this machine, and whether it is on
|
|
380
|
+
policies add Pick from a list, or name one: <policy> or <owner>/<repo>
|
|
381
|
+
policies remove Turn one policy off, or uninstall a whole pack
|
|
382
|
+
policies show What a pack contains, before you install it
|
|
383
|
+
policies -i / -u Wire failproofai into your agent CLIs
|
|
384
|
+
publish Ship your own policies as a pack anyone can install
|
|
385
|
+
━━ 3 SEE WHAT IT CAUGHT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
386
|
+
(no args) Open the policy dashboard on localhost:8020
|
|
387
|
+
audit Scan your agents' history, then open the audit view
|
|
388
|
+
config --status Cloud connection, daemon version, pause state
|
|
389
|
+
harness Extra paths to capture agent sessions from
|
|
390
|
+
━━ 4 PUT IT RIGHT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
391
|
+
config --pause Pause enforcement for one session (30m; max 8h)
|
|
392
|
+
flush Deliver everything already spooled, right now
|
|
393
|
+
backfill Re-send history the collector already read past
|
|
394
|
+
migrate Run pending ~/.failproofai layout migrations
|
|
395
|
+
uninstall Remove hooks and daemon, BEFORE npm rm -g failproofai
|
|
396
|
+
|
|
397
|
+
Docs docs.befailproof.ai Code github.com/failproofai/failproofai
|
|
398
|
+
Chat discord.befailproof.ai Forum reddit.com/r/failproofai
|
|
432
399
|
`.trimStart());
|
|
433
400
|
process.exit(0);
|
|
434
401
|
}
|
|
435
402
|
|
|
403
|
+
|
|
436
404
|
// --version / -v
|
|
437
405
|
if ((args.includes("--version") || args.includes("-v")) && !SUBCOMMANDS.includes(args[0])) {
|
|
438
406
|
const extraArgs = args.filter((a) => a !== "--version" && a !== "-v");
|
|
@@ -1217,6 +1185,328 @@ WHY THIS EXISTS
|
|
|
1217
1185
|
return;
|
|
1218
1186
|
}
|
|
1219
1187
|
|
|
1188
|
+
// publish — build the release assets AND put them on a GitHub release.
|
|
1189
|
+
//
|
|
1190
|
+
// Top level rather than under `policies`, because it is a PRODUCER verb: the
|
|
1191
|
+
// four consumer words are what every user reads, and almost none of them will
|
|
1192
|
+
// ever ship a pack. `pack build` is canonicalized into this above.
|
|
1193
|
+
if (args[0] === "publish") {
|
|
1194
|
+
const subArgs = args.slice(1);
|
|
1195
|
+
if (subArgs.includes("--help") || subArgs.includes("-h") || subArgs.length === 0) {
|
|
1196
|
+
console.log(`
|
|
1197
|
+
failproofai publish — ship your policies as a pack anyone can install
|
|
1198
|
+
|
|
1199
|
+
USAGE
|
|
1200
|
+
failproofai publish <entry.mjs> --repo <owner>/<repo> --version <version>
|
|
1201
|
+
|
|
1202
|
+
WHAT IT DOES
|
|
1203
|
+
Validates the file with the loader's own rules, writes the three release
|
|
1204
|
+
assets, creates (or reuses) the release, and attaches them. One command.
|
|
1205
|
+
|
|
1206
|
+
Only the release matters. Installs read
|
|
1207
|
+
releases/download/<tag>/<asset> and never touch your git tree — pushing the
|
|
1208
|
+
source is for humans reading it, not for the install to work.
|
|
1209
|
+
|
|
1210
|
+
OPTIONS
|
|
1211
|
+
--repo <owner>/<repo> Where to release it. Omit to build the assets only.
|
|
1212
|
+
--version <version> The pack version. Also the tag, unless --tag says otherwise.
|
|
1213
|
+
--id <publisher/name> The pack's id. Defaults to --repo.
|
|
1214
|
+
--tag <tag> Release tag. Defaults to --version; a leading v is fine.
|
|
1215
|
+
--notes <text> Release notes.
|
|
1216
|
+
--out <dir> Where to write the assets (default: dist-pack)
|
|
1217
|
+
--effect enforce|observe observe records and blocks nothing (default: enforce)
|
|
1218
|
+
--dry-run Build the assets, publish nothing.
|
|
1219
|
+
|
|
1220
|
+
CREDENTIAL
|
|
1221
|
+
GITHUB_TOKEN or GH_TOKEN, else whatever \`gh auth login\` stored. It needs
|
|
1222
|
+
write access to releases on that repository and nothing else. The token is
|
|
1223
|
+
never printed.
|
|
1224
|
+
|
|
1225
|
+
THE REPO MUST BE PUBLIC
|
|
1226
|
+
Installs are anonymous HTTPS with no credential to offer, so a private
|
|
1227
|
+
repository publishes to nobody. This warns you if it is.
|
|
1228
|
+
|
|
1229
|
+
YOUR ENTRY FILE
|
|
1230
|
+
One file, no relative imports — only the entry is digest-pinned, so a pack
|
|
1231
|
+
importing siblings could not honestly claim to be verified. Bundle first
|
|
1232
|
+
(esbuild, bun build, rollup) if yours is split. Each policy may carry
|
|
1233
|
+
\`category\` and \`defaultEnabled\` alongside the usual fields.
|
|
1234
|
+
|
|
1235
|
+
EXAMPLES
|
|
1236
|
+
failproofai publish ./deploy-guard.mjs --repo me/deploy-guard --version 1.0.0
|
|
1237
|
+
failproofai publish ./deploy-guard.mjs --version 1.0.1 --dry-run
|
|
1238
|
+
failproofai publish ./x.mjs --repo me/x --version 2.0.0 --effect observe
|
|
1239
|
+
`.trimStart());
|
|
1240
|
+
process.exit(0);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
lastSubcommand = "publish";
|
|
1244
|
+
const { runPublishCommand } = await import("../src/hooks/pack-cli");
|
|
1245
|
+
const result = await runPublishCommand(subArgs);
|
|
1246
|
+
for (const line of result.lines) {
|
|
1247
|
+
if (result.exitCode === 0) console.log(line);
|
|
1248
|
+
else console.error(line);
|
|
1249
|
+
}
|
|
1250
|
+
// Shape, never value: whether it published and whether it worked. Never the
|
|
1251
|
+
// repo, the pack id or the entry path — all three are things the user typed.
|
|
1252
|
+
await track("cli_publish", { ok: result.exitCode === 0, dry_run: subArgs.includes("--dry-run") });
|
|
1253
|
+
lastSubcommand = null;
|
|
1254
|
+
await exitAfterFlush(result.exitCode);
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// policies add | remove | show — one noun for policies.
|
|
1259
|
+
//
|
|
1260
|
+
// `policies`, `policy` and `pack` were three commands for one idea. They are
|
|
1261
|
+
// one now, and `add`/`remove` take EITHER a policy name or a pack source,
|
|
1262
|
+
// told apart by a SLASH: a policy name matches /^[A-Za-z0-9._-]+$/, so a
|
|
1263
|
+
// slash is already illegal in one and unambiguous in the other. That is the
|
|
1264
|
+
// rule npm and docker use, and it means nobody has to discover a flag before
|
|
1265
|
+
// they can install somebody else's policies.
|
|
1266
|
+
//
|
|
1267
|
+
// Honors the same --cli / --scope / --beta flags as `policies --install`.
|
|
1268
|
+
if (
|
|
1269
|
+
args[0] === "policies" &&
|
|
1270
|
+
(args[1] === "add" || args[1] === "remove" || args[1] === "show")
|
|
1271
|
+
) {
|
|
1272
|
+
lastSubcommand = "policy";
|
|
1273
|
+
const subArgs = args.slice(1);
|
|
1274
|
+
|
|
1275
|
+
if (subArgs.length === 0 || subArgs.includes("--help") || subArgs.includes("-h")) {
|
|
1276
|
+
console.log(`
|
|
1277
|
+
failproofai policies add|remove|show — choose what your agents may do
|
|
1278
|
+
|
|
1279
|
+
USAGE
|
|
1280
|
+
failproofai policies add Pick from what is installed here
|
|
1281
|
+
failproofai policies add <name> Turn one policy on
|
|
1282
|
+
failproofai policies add <owner>/<repo> Install someone's pack
|
|
1283
|
+
failproofai policies add core Our pack, from this package, offline
|
|
1284
|
+
failproofai policies remove <name> Turn one policy off
|
|
1285
|
+
failproofai policies remove <pack-id> Uninstall a pack
|
|
1286
|
+
failproofai policies show <owner>/<repo> What a pack contains, before you take it
|
|
1287
|
+
|
|
1288
|
+
A NAME OR A SOURCE
|
|
1289
|
+
Anything with a slash is a pack source; anything without is a policy name.
|
|
1290
|
+
Policy names cannot contain a slash, so there is nothing to guess at.
|
|
1291
|
+
|
|
1292
|
+
block-sudo a policy
|
|
1293
|
+
acme/deploy-guard a pack, newest release, pinned
|
|
1294
|
+
acme/deploy-guard@v2.1.0 that release
|
|
1295
|
+
github:acme/deploy-guard@v2.1.0 same, explicit
|
|
1296
|
+
https://github.com/acme/x/releases/tag/v2 the URL you copied
|
|
1297
|
+
|
|
1298
|
+
CHOOSING PART OF A PACK
|
|
1299
|
+
With no flags you get the pack's own defaults and are shown the rest.
|
|
1300
|
+
--policy a,b exactly these
|
|
1301
|
+
--category x,y whole categories (see: failproofai policies show <source>)
|
|
1302
|
+
--all everything it contains
|
|
1303
|
+
Re-adding at a newer version keeps what you chose.
|
|
1304
|
+
|
|
1305
|
+
OPTIONS (policy names only)
|
|
1306
|
+
--cli claude|codex|copilot|cursor|opencode|pi|hermes|openclaw|factory|devin|antigravity|goose
|
|
1307
|
+
Agent CLI(s) to apply to; space-separated or repeated.
|
|
1308
|
+
Omit to detect installed CLIs and prompt.
|
|
1309
|
+
--scope user|project|local Config scope (default: user)
|
|
1310
|
+
--beta Allow beta policies
|
|
1311
|
+
|
|
1312
|
+
EXAMPLES
|
|
1313
|
+
failproofai policies add
|
|
1314
|
+
failproofai policies add block-sudo
|
|
1315
|
+
failproofai policies add sanitize-api-keys --scope project
|
|
1316
|
+
failproofai policies add core --category sanitize,git
|
|
1317
|
+
failproofai policies add acme/deploy-guard --policy block-prod-deploy
|
|
1318
|
+
failproofai policies show acme/deploy-guard
|
|
1319
|
+
failproofai policies remove block-sudo
|
|
1320
|
+
|
|
1321
|
+
Publishing your own: failproofai publish --help
|
|
1322
|
+
Offline: FAILPROOFAI_NO_DOWNLOAD=1 refuses to fetch; packs already installed
|
|
1323
|
+
keep enforcing. FAILPROOFAI_PACK_BASE_URL points it all at a mirror.
|
|
1324
|
+
`.trimStart());
|
|
1325
|
+
process.exit(0);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const action = subArgs[0];
|
|
1329
|
+
const rest = subArgs.slice(1);
|
|
1330
|
+
|
|
1331
|
+
// A policy name matches /^[A-Za-z0-9._-]+$/ (pack-manifest.ts owns that
|
|
1332
|
+
// rule), so a slash can only ever mean a pack. `core` is the one name
|
|
1333
|
+
// without a slash that is still a source — it is the short spelling of our
|
|
1334
|
+
// own pack, and is checked by the pack lane itself rather than duplicated
|
|
1335
|
+
// here.
|
|
1336
|
+
const firstPositional = rest.find((a) => !a.startsWith("-"));
|
|
1337
|
+
let looksLikeSource =
|
|
1338
|
+
!!firstPositional &&
|
|
1339
|
+
(firstPositional.includes("/") || firstPositional.startsWith("github:"));
|
|
1340
|
+
if (!looksLikeSource && firstPositional) {
|
|
1341
|
+
// `core` is the one sourcey word with no slash in it. Read the alias set
|
|
1342
|
+
// from the layer that OWNS it rather than restating it here, where the
|
|
1343
|
+
// copy would drift the first time an alias is added — that exact drift
|
|
1344
|
+
// already shipped once, when the dashboard could not resolve a name the
|
|
1345
|
+
// CLI could.
|
|
1346
|
+
const { CORE_ALIASES } = await import("../src/hooks/pack-store");
|
|
1347
|
+
looksLikeSource = CORE_ALIASES.has(firstPositional.toLowerCase());
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
if (action === "show" || looksLikeSource) {
|
|
1351
|
+
if (action === "show" && !firstPositional) {
|
|
1352
|
+
throw new CliError(
|
|
1353
|
+
"Usage: failproofai policies show <owner>/<repo>\n" +
|
|
1354
|
+
"Run `failproofai policies` to see what is already installed here.",
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
// One lane, one implementation. `show` is the pack lane's remote preview,
|
|
1358
|
+
// and add/remove of a source are its add/remove — routed by translating
|
|
1359
|
+
// the words rather than by growing a second copy of either.
|
|
1360
|
+
const packArgs =
|
|
1361
|
+
action === "show" ? ["list", ...rest] : [action, ...rest];
|
|
1362
|
+
const { runPackCommand } = await import("../src/hooks/pack-cli");
|
|
1363
|
+
const result = await runPackCommand(packArgs);
|
|
1364
|
+
for (const line of result.lines) {
|
|
1365
|
+
if (result.exitCode === 0) console.log(line);
|
|
1366
|
+
else console.error(line);
|
|
1367
|
+
}
|
|
1368
|
+
await track("cli_pack", {
|
|
1369
|
+
ok: result.exitCode === 0,
|
|
1370
|
+
// The subcommand only — never the pack id, source or policy names. A
|
|
1371
|
+
// pack source is a value the user typed and a third-party pack name is
|
|
1372
|
+
// a publisher-controlled string; we send shape, never value.
|
|
1373
|
+
sub: action,
|
|
1374
|
+
});
|
|
1375
|
+
lastSubcommand = null;
|
|
1376
|
+
await exitAfterFlush(result.exitCode);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (action !== "add" && action !== "remove") {
|
|
1381
|
+
throw new CliError(
|
|
1382
|
+
`Unknown policies subcommand: ${action}\n` +
|
|
1383
|
+
`Run \`failproofai policies --help\` for usage.`,
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
const scopeIdx = rest.indexOf("--scope");
|
|
1388
|
+
const scope = scopeIdx >= 0 ? rest[scopeIdx + 1] : "user";
|
|
1389
|
+
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
1390
|
+
throw new CliError("Missing value for --scope. Valid values: user, project, local");
|
|
1391
|
+
}
|
|
1392
|
+
const validScopes = action === "remove"
|
|
1393
|
+
? ["user", "project", "local", "all"]
|
|
1394
|
+
: ["user", "project", "local"];
|
|
1395
|
+
if (scopeIdx >= 0 && !validScopes.includes(scope)) {
|
|
1396
|
+
throw new CliError(`Invalid scope: ${scope}. Valid values: ${validScopes.join(", ")}`);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// --cli accepts one or more space-separated values, optionally repeated.
|
|
1400
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "hermes", "openclaw", "factory", "devin", "antigravity", "goose"]);
|
|
1401
|
+
const cliFlagValues = [];
|
|
1402
|
+
const cliConsumedIdxs = new Set();
|
|
1403
|
+
const cliFlagIdxs = rest.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
|
|
1404
|
+
for (const idx of cliFlagIdxs) {
|
|
1405
|
+
let consumed = 0;
|
|
1406
|
+
for (let j = idx + 1; j < rest.length; j++) {
|
|
1407
|
+
const v = rest[j];
|
|
1408
|
+
if (v.startsWith("-")) break;
|
|
1409
|
+
if (!VALID_CLIS.has(v)) break;
|
|
1410
|
+
cliFlagValues.push(v);
|
|
1411
|
+
cliConsumedIdxs.add(j);
|
|
1412
|
+
consumed++;
|
|
1413
|
+
}
|
|
1414
|
+
if (consumed === 0) {
|
|
1415
|
+
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi hermes openclaw (or any subset)");
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const includeBeta = rest.includes("--beta");
|
|
1420
|
+
|
|
1421
|
+
// Reject unknown flags.
|
|
1422
|
+
const knownFlags = new Set(["--scope", "--cli", "--beta"]);
|
|
1423
|
+
const unknownFlag = rest.find((a) => a.startsWith("-") && !knownFlags.has(a));
|
|
1424
|
+
if (unknownFlag) {
|
|
1425
|
+
throw new CliError(`Unknown flag: ${unknownFlag}\nRun \`failproofai policy --help\` for usage.`);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Positional policy names = anything not consumed by --scope / --cli.
|
|
1429
|
+
const consumedIdxs = new Set();
|
|
1430
|
+
if (scopeIdx >= 0) consumedIdxs.add(scopeIdx + 1);
|
|
1431
|
+
for (const i of cliConsumedIdxs) consumedIdxs.add(i);
|
|
1432
|
+
const positional = rest.filter(
|
|
1433
|
+
(a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx),
|
|
1434
|
+
);
|
|
1435
|
+
|
|
1436
|
+
if (positional.length === 0) {
|
|
1437
|
+
// Naming nothing is a question, not a mistake. It used to be an error
|
|
1438
|
+
// telling you to go and read a list somewhere else and come back — so
|
|
1439
|
+
// the answer is to SHOW the list, here, with what is already on ticked.
|
|
1440
|
+
const { runPolicyPicker } = await import("../src/hooks/pack-cli");
|
|
1441
|
+
const result = await runPolicyPicker(action, { stdin: process.stdin, stdout: process.stdout });
|
|
1442
|
+
for (const line of result.lines) {
|
|
1443
|
+
if (result.exitCode === 0) console.log(line);
|
|
1444
|
+
else console.error(line);
|
|
1445
|
+
}
|
|
1446
|
+
await track("cli_policy_picker", { ok: result.exitCode === 0, action });
|
|
1447
|
+
lastSubcommand = null;
|
|
1448
|
+
await exitAfterFlush(result.exitCode);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (positional.length > 1) {
|
|
1452
|
+
throw new CliError(
|
|
1453
|
+
`\`policy ${action}\` takes exactly one policy name (got ${positional.length}).\n` +
|
|
1454
|
+
`For multiple policies use \`failproofai policies --${action === "add" ? "install" : "uninstall"} ${positional.join(" ")}\`.`,
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
const policyName = positional[0];
|
|
1458
|
+
|
|
1459
|
+
const { resolveTargetClis } = await import("../src/hooks/install-prompt");
|
|
1460
|
+
const cli = await resolveTargetClis(
|
|
1461
|
+
cliFlagValues.length > 0 ? cliFlagValues : undefined,
|
|
1462
|
+
action === "add" ? "install" : "uninstall",
|
|
1463
|
+
);
|
|
1464
|
+
|
|
1465
|
+
lastPolicyAction = action;
|
|
1466
|
+
if (action === "add") {
|
|
1467
|
+
const { installHooks } = await import("../src/hooks/manager");
|
|
1468
|
+
await installHooks(
|
|
1469
|
+
[policyName],
|
|
1470
|
+
scope,
|
|
1471
|
+
undefined,
|
|
1472
|
+
includeBeta,
|
|
1473
|
+
undefined,
|
|
1474
|
+
undefined,
|
|
1475
|
+
false,
|
|
1476
|
+
cli,
|
|
1477
|
+
);
|
|
1478
|
+
await track("cli_policy_add_success", {
|
|
1479
|
+
scope,
|
|
1480
|
+
cli,
|
|
1481
|
+
cli_count: cli.length,
|
|
1482
|
+
policy_name: policyName,
|
|
1483
|
+
include_beta: includeBeta,
|
|
1484
|
+
});
|
|
1485
|
+
} else {
|
|
1486
|
+
// `policy remove <name>` always removes the named policy regardless
|
|
1487
|
+
// of whether it's beta or not — passing `betaOnly: includeBeta`
|
|
1488
|
+
// here was a mislabel that only affected the telemetry field, not
|
|
1489
|
+
// the actual remove. Drop the `--beta` semantic for remove and
|
|
1490
|
+
// emit beta_only: false unconditionally so dashboards don't see
|
|
1491
|
+
// ghost "beta removal" events.
|
|
1492
|
+
const { removeHooks } = await import("../src/hooks/manager");
|
|
1493
|
+
await removeHooks(
|
|
1494
|
+
[policyName],
|
|
1495
|
+
scope,
|
|
1496
|
+
undefined,
|
|
1497
|
+
{ betaOnly: false, removeCustomHooks: false, cli },
|
|
1498
|
+
);
|
|
1499
|
+
await track("cli_policy_remove_success", {
|
|
1500
|
+
scope,
|
|
1501
|
+
cli,
|
|
1502
|
+
cli_count: cli.length,
|
|
1503
|
+
policy_name: policyName,
|
|
1504
|
+
beta_only: false,
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
lastPolicyAction = null;
|
|
1508
|
+
process.exit(0);
|
|
1509
|
+
}
|
|
1220
1510
|
// policies [--install|-i|--uninstall|-u|--help|-h] [names...] [--scope] [--beta] [--custom|-c <path>]
|
|
1221
1511
|
if (args[0] === "policies") {
|
|
1222
1512
|
const subArgs = args.slice(1);
|
|
@@ -1231,8 +1521,14 @@ failproofai policies — manage Failproof AI policies
|
|
|
1231
1521
|
|
|
1232
1522
|
USAGE
|
|
1233
1523
|
failproofai policies List all policies and their status
|
|
1234
|
-
failproofai policies
|
|
1235
|
-
failproofai policies
|
|
1524
|
+
failproofai policies add [what] Turn one on, take a pack, or pick
|
|
1525
|
+
failproofai policies remove <what> Turn one off, or uninstall a pack
|
|
1526
|
+
failproofai policies show <owner>/<repo> What a pack holds, before you take it
|
|
1527
|
+
failproofai policies --install, -i Wire policies into your agent CLIs
|
|
1528
|
+
failproofai policies --uninstall, -u Unwire them, or strip the hooks
|
|
1529
|
+
|
|
1530
|
+
policy, pack and p are all spellings of policies.
|
|
1531
|
+
Detail on the first three: failproofai policies add --help
|
|
1236
1532
|
|
|
1237
1533
|
OPTIONS (install)
|
|
1238
1534
|
[names...] Specific policy names to enable (omit for interactive)
|
|
@@ -1497,163 +1793,6 @@ EXAMPLES
|
|
|
1497
1793
|
return;
|
|
1498
1794
|
}
|
|
1499
1795
|
|
|
1500
|
-
// policy — single-policy shortcut over `policies --install <name>`.
|
|
1501
|
-
// failproofai policy add <name> enable one policy (defaults: claude/user)
|
|
1502
|
-
// failproofai policy remove <name> disable one policy
|
|
1503
|
-
// Honors the same --cli / --scope / --beta flags as `policies --install`.
|
|
1504
|
-
if (args[0] === "policy") {
|
|
1505
|
-
lastSubcommand = "policy";
|
|
1506
|
-
const subArgs = args.slice(1);
|
|
1507
|
-
|
|
1508
|
-
if (subArgs.length === 0 || subArgs.includes("--help") || subArgs.includes("-h")) {
|
|
1509
|
-
console.log(`
|
|
1510
|
-
failproofai policy — manage a single FailproofAI policy
|
|
1511
|
-
|
|
1512
|
-
USAGE
|
|
1513
|
-
failproofai policy add <name> Enable one policy
|
|
1514
|
-
failproofai policy remove <name> Disable one policy
|
|
1515
|
-
|
|
1516
|
-
OPTIONS
|
|
1517
|
-
--cli claude|codex|copilot|cursor|opencode|pi|hermes|openclaw|factory|devin|antigravity|goose
|
|
1518
|
-
Agent CLI(s) to apply to; space-separated or repeated.
|
|
1519
|
-
Omit to detect installed CLIs and prompt.
|
|
1520
|
-
--scope user|project|local Config scope (default: user)
|
|
1521
|
-
--beta Allow beta policies
|
|
1522
|
-
|
|
1523
|
-
EXAMPLES
|
|
1524
|
-
failproofai policy add block-sudo
|
|
1525
|
-
failproofai policy add sanitize-api-keys --scope project
|
|
1526
|
-
failproofai policy add block-force-push --cli claude codex
|
|
1527
|
-
failproofai policy remove block-sudo
|
|
1528
|
-
`.trimStart());
|
|
1529
|
-
process.exit(0);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
const action = subArgs[0];
|
|
1533
|
-
if (action !== "add" && action !== "remove") {
|
|
1534
|
-
throw new CliError(
|
|
1535
|
-
`Unknown policy subcommand: ${action}\n` +
|
|
1536
|
-
`Run \`failproofai policy --help\` for usage.`,
|
|
1537
|
-
);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
const rest = subArgs.slice(1);
|
|
1541
|
-
|
|
1542
|
-
const scopeIdx = rest.indexOf("--scope");
|
|
1543
|
-
const scope = scopeIdx >= 0 ? rest[scopeIdx + 1] : "user";
|
|
1544
|
-
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
1545
|
-
throw new CliError("Missing value for --scope. Valid values: user, project, local");
|
|
1546
|
-
}
|
|
1547
|
-
const validScopes = action === "remove"
|
|
1548
|
-
? ["user", "project", "local", "all"]
|
|
1549
|
-
: ["user", "project", "local"];
|
|
1550
|
-
if (scopeIdx >= 0 && !validScopes.includes(scope)) {
|
|
1551
|
-
throw new CliError(`Invalid scope: ${scope}. Valid values: ${validScopes.join(", ")}`);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// --cli accepts one or more space-separated values, optionally repeated.
|
|
1555
|
-
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "hermes", "openclaw", "factory", "devin", "antigravity", "goose"]);
|
|
1556
|
-
const cliFlagValues = [];
|
|
1557
|
-
const cliConsumedIdxs = new Set();
|
|
1558
|
-
const cliFlagIdxs = rest.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
|
|
1559
|
-
for (const idx of cliFlagIdxs) {
|
|
1560
|
-
let consumed = 0;
|
|
1561
|
-
for (let j = idx + 1; j < rest.length; j++) {
|
|
1562
|
-
const v = rest[j];
|
|
1563
|
-
if (v.startsWith("-")) break;
|
|
1564
|
-
if (!VALID_CLIS.has(v)) break;
|
|
1565
|
-
cliFlagValues.push(v);
|
|
1566
|
-
cliConsumedIdxs.add(j);
|
|
1567
|
-
consumed++;
|
|
1568
|
-
}
|
|
1569
|
-
if (consumed === 0) {
|
|
1570
|
-
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi hermes openclaw (or any subset)");
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
const includeBeta = rest.includes("--beta");
|
|
1575
|
-
|
|
1576
|
-
// Reject unknown flags.
|
|
1577
|
-
const knownFlags = new Set(["--scope", "--cli", "--beta"]);
|
|
1578
|
-
const unknownFlag = rest.find((a) => a.startsWith("-") && !knownFlags.has(a));
|
|
1579
|
-
if (unknownFlag) {
|
|
1580
|
-
throw new CliError(`Unknown flag: ${unknownFlag}\nRun \`failproofai policy --help\` for usage.`);
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
// Positional policy names = anything not consumed by --scope / --cli.
|
|
1584
|
-
const consumedIdxs = new Set();
|
|
1585
|
-
if (scopeIdx >= 0) consumedIdxs.add(scopeIdx + 1);
|
|
1586
|
-
for (const i of cliConsumedIdxs) consumedIdxs.add(i);
|
|
1587
|
-
const positional = rest.filter(
|
|
1588
|
-
(a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx),
|
|
1589
|
-
);
|
|
1590
|
-
|
|
1591
|
-
if (positional.length === 0) {
|
|
1592
|
-
throw new CliError(
|
|
1593
|
-
`Missing policy name.\n` +
|
|
1594
|
-
`Usage: failproofai policy ${action} <name>\n` +
|
|
1595
|
-
`Run \`failproofai policies\` to see available names.`,
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
if (positional.length > 1) {
|
|
1599
|
-
throw new CliError(
|
|
1600
|
-
`\`policy ${action}\` takes exactly one policy name (got ${positional.length}).\n` +
|
|
1601
|
-
`For multiple policies use \`failproofai policies --${action === "add" ? "install" : "uninstall"} ${positional.join(" ")}\`.`,
|
|
1602
|
-
);
|
|
1603
|
-
}
|
|
1604
|
-
const policyName = positional[0];
|
|
1605
|
-
|
|
1606
|
-
const { resolveTargetClis } = await import("../src/hooks/install-prompt");
|
|
1607
|
-
const cli = await resolveTargetClis(
|
|
1608
|
-
cliFlagValues.length > 0 ? cliFlagValues : undefined,
|
|
1609
|
-
action === "add" ? "install" : "uninstall",
|
|
1610
|
-
);
|
|
1611
|
-
|
|
1612
|
-
lastPolicyAction = action;
|
|
1613
|
-
if (action === "add") {
|
|
1614
|
-
const { installHooks } = await import("../src/hooks/manager");
|
|
1615
|
-
await installHooks(
|
|
1616
|
-
[policyName],
|
|
1617
|
-
scope,
|
|
1618
|
-
undefined,
|
|
1619
|
-
includeBeta,
|
|
1620
|
-
undefined,
|
|
1621
|
-
undefined,
|
|
1622
|
-
false,
|
|
1623
|
-
cli,
|
|
1624
|
-
);
|
|
1625
|
-
await track("cli_policy_add_success", {
|
|
1626
|
-
scope,
|
|
1627
|
-
cli,
|
|
1628
|
-
cli_count: cli.length,
|
|
1629
|
-
policy_name: policyName,
|
|
1630
|
-
include_beta: includeBeta,
|
|
1631
|
-
});
|
|
1632
|
-
} else {
|
|
1633
|
-
// `policy remove <name>` always removes the named policy regardless
|
|
1634
|
-
// of whether it's beta or not — passing `betaOnly: includeBeta`
|
|
1635
|
-
// here was a mislabel that only affected the telemetry field, not
|
|
1636
|
-
// the actual remove. Drop the `--beta` semantic for remove and
|
|
1637
|
-
// emit beta_only: false unconditionally so dashboards don't see
|
|
1638
|
-
// ghost "beta removal" events.
|
|
1639
|
-
const { removeHooks } = await import("../src/hooks/manager");
|
|
1640
|
-
await removeHooks(
|
|
1641
|
-
[policyName],
|
|
1642
|
-
scope,
|
|
1643
|
-
undefined,
|
|
1644
|
-
{ betaOnly: false, removeCustomHooks: false, cli },
|
|
1645
|
-
);
|
|
1646
|
-
await track("cli_policy_remove_success", {
|
|
1647
|
-
scope,
|
|
1648
|
-
cli,
|
|
1649
|
-
cli_count: cli.length,
|
|
1650
|
-
policy_name: policyName,
|
|
1651
|
-
beta_only: false,
|
|
1652
|
-
});
|
|
1653
|
-
}
|
|
1654
|
-
lastPolicyAction = null;
|
|
1655
|
-
process.exit(0);
|
|
1656
|
-
}
|
|
1657
1796
|
|
|
1658
1797
|
// config — the interactive setup launcher (scope, agents, policies).
|
|
1659
1798
|
// `configure` and `setup` are canonicalized to "config" up top. Running it
|