failproofai 0.0.9 → 0.0.10-beta.0
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/.cursor/hooks.json +47 -0
- package/.next/standalone/.gemini/settings.json +147 -0
- 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/required-server-files.json +1 -1
- 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 +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 +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/api/download/[project]/[session]/route.js +2 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- 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 +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 +1 -1
- 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 +2 -2
- 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 +5 -5
- 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 +2 -2
- 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]__0.~nmr9._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0yspgjy._.js → [root-of-the-server]__010i6f5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08px0ym._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b57.gk._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dtn9lr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vlhtkc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wu7fr7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfq1yr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z4c5dj._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zso~62._.js +3 -0
- 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]__0-2wr.c._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0.~m-w2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09icjsf._.js → [root-of-the-server]__0709m8.._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bz245.._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dl0kgt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gmhxyo._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mup1hi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ohb3gc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qbpe_v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s~gy6y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t5l7a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +152 -6
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0_b7pgn._.js → [root-of-the-server]__0ymn496._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01g_w_e._.js → [root-of-the-server]__10h.ggz._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_03d7qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_07a1g.3._.js → _0zx~s__._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
- 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/chunks/ssr/lib_codex-projects_ts_0eosib~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_copilot-projects_ts_0r8xkn8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_cursor-projects_ts_0qt1scg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_0sl~yqr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_opencode-projects_ts_0op9gyp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_pi-projects_ts_103tsh1._.js +3 -0
- 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/{0n-_j_6fo6jex.js → 00ay03h8bq4b~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{11kt_9zaooda3.js → 0agmlhk5ml7x5.js} +1 -1
- package/.next/standalone/.next/static/chunks/0bi2r.m~yokoo.js +1 -0
- package/.next/standalone/.next/static/chunks/{095l4hc7-h.~~.js → 0en4v5k2nnxks.js} +1 -1
- package/.next/standalone/.next/static/chunks/0q5bmqop--9yk.js +1 -0
- package/.next/standalone/.next/static/chunks/{0756i.7omnnl6.js → 0s6nux54y~l~r.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0t~iusm_fxoao.js → 0tpse0wu2wwo0.js} +1 -1
- package/.next/standalone/.next/static/chunks/12po2vpc-4_c1.css +1 -0
- package/.next/standalone/.next/static/chunks/{0u-ys71jc4y68.js → 1400rtd5ywbt..js} +2 -2
- package/.next/standalone/.next/static/chunks/{09ose_165ra4d.js → 14lmf8boay-zu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0pr7k36o_.du1.js → 17htukxga7bil.js} +1 -1
- package/.next/standalone/.opencode/opencode.json +4 -0
- package/.next/standalone/.opencode/plugins/failproofai.mjs +131 -0
- package/.next/standalone/.pi/settings.json +5 -0
- package/.next/standalone/app/components/cli-badge.tsx +7 -11
- package/.next/standalone/app/components/project-list.tsx +32 -4
- package/.next/standalone/app/policies/hooks-client.tsx +31 -15
- package/.next/standalone/app/project/[name]/page.tsx +52 -16
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +92 -15
- package/.next/standalone/assets/logos/copilot-dark.svg +1 -0
- package/.next/standalone/assets/logos/copilot-light.svg +1 -0
- package/.next/standalone/assets/logos/cursor-dark.svg +1 -0
- package/.next/standalone/assets/logos/cursor-light.svg +1 -0
- package/.next/standalone/assets/logos/gemini-dark.svg +13 -0
- package/.next/standalone/assets/logos/gemini-light.svg +13 -0
- package/.next/standalone/assets/logos/opencode-dark.svg +1 -0
- package/.next/standalone/assets/logos/opencode-light.svg +1 -0
- package/.next/standalone/assets/logos/pi-dark.svg +7 -0
- package/.next/standalone/assets/logos/pi-light.svg +7 -0
- package/.next/standalone/lib/cli-registry.ts +107 -0
- package/.next/standalone/lib/codex-projects.ts +3 -3
- package/.next/standalone/lib/copilot-projects.ts +224 -0
- package/.next/standalone/lib/copilot-sessions.ts +395 -0
- package/.next/standalone/lib/cursor-projects.ts +312 -0
- package/.next/standalone/lib/cursor-sessions.ts +467 -0
- package/.next/standalone/lib/gemini-projects.ts +203 -0
- package/.next/standalone/lib/gemini-sessions.ts +365 -0
- package/.next/standalone/lib/opencode-projects.ts +232 -0
- package/.next/standalone/lib/opencode-sessions.ts +237 -0
- package/.next/standalone/lib/pi-projects.ts +230 -0
- package/.next/standalone/lib/pi-sessions.ts +325 -0
- package/.next/standalone/lib/projects.ts +67 -31
- package/.next/standalone/next.config.ts +5 -4
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/pi-extension/index.ts +373 -0
- package/.next/standalone/pi-extension/package.json +12 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +37 -3
- package/bin/failproofai.mjs +61 -21
- package/dist/cli.mjs +2248 -246
- package/lib/cli-registry.ts +107 -0
- package/lib/codex-projects.ts +3 -3
- package/lib/copilot-projects.ts +224 -0
- package/lib/copilot-sessions.ts +395 -0
- package/lib/cursor-projects.ts +312 -0
- package/lib/cursor-sessions.ts +467 -0
- package/lib/gemini-projects.ts +203 -0
- package/lib/gemini-sessions.ts +365 -0
- package/lib/opencode-projects.ts +232 -0
- package/lib/opencode-sessions.ts +237 -0
- package/lib/pi-projects.ts +230 -0
- package/lib/pi-sessions.ts +325 -0
- package/lib/projects.ts +67 -31
- package/package.json +2 -1
- package/pi-extension/index.ts +373 -0
- package/pi-extension/package.json +12 -0
- package/scripts/translate-docs/mdx-translator.ts +56 -2
- package/scripts/translate-docs/translator.ts +1 -1
- package/src/hooks/builtin-policies.ts +84 -14
- package/src/hooks/handler.ts +67 -5
- package/src/hooks/install-prompt.ts +33 -10
- package/src/hooks/integrations.ts +1007 -6
- package/src/hooks/policy-evaluator.ts +299 -3
- package/src/hooks/resolve-permission-mode.ts +23 -0
- package/src/hooks/types.ts +307 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0su~k6f._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_codex-projects_ts_07qqk1g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01743wx._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gs6wz4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0it81ys._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u4a9jq._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12.h2mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_04w00cm._.js +0 -3
- package/.next/standalone/.next/static/chunks/0.rk1iwdt1d7c.css +0 -1
- package/.next/standalone/.next/static/chunks/06x4-d1~o-opr.js +0 -1
- package/.next/standalone/.next/static/chunks/0n~s0gafwnp2y.js +0 -1
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* failproofai policy bridge for Pi (pi-coding-agent).
|
|
3
|
+
*
|
|
4
|
+
* This extension is loaded by Pi at startup and registered via
|
|
5
|
+
* `pi install <abs-path-to-this-dir> [-l]` (or by hand-authoring an entry in
|
|
6
|
+
* `<scope>/.pi/settings.json`). It subscribes to Pi's `tool_call`, `user_bash`,
|
|
7
|
+
* `input`, and `session_start` events and forwards them to the failproofai
|
|
8
|
+
* binary as `failproofai --hook <Event> --cli pi`. failproofai prints a
|
|
9
|
+
* decision JSON to stdout; this shim parses it and translates into Pi's
|
|
10
|
+
* `{ block: true, reason }` return shape so policy `deny` decisions cancel
|
|
11
|
+
* tool execution.
|
|
12
|
+
*
|
|
13
|
+
* Marker comment for failproofai's installer detection (do not remove):
|
|
14
|
+
* __failproofai_hook__: true
|
|
15
|
+
*
|
|
16
|
+
* Binary resolution. failproofai ships two entrypoints:
|
|
17
|
+
* • dist/cli.mjs — bundled, node-compatible (production npm install)
|
|
18
|
+
* • bin/failproofai.mjs — source, requires `bun` (dev / monorepo)
|
|
19
|
+
*
|
|
20
|
+
* dist/cli.mjs is preferred because spawning `node bin/failproofai.mjs`
|
|
21
|
+
* fails with ERR_IMPORT_ATTRIBUTE_MISSING (the source `import package.json`
|
|
22
|
+
* needs `with { type: "json" }` under node, which bun handles transparently
|
|
23
|
+
* but the build:cli step transpiles away in dist/cli.mjs). When dist/cli.mjs
|
|
24
|
+
* isn't present, fall back to running bin/failproofai.mjs with `bun`. Pi
|
|
25
|
+
* spawns extensions with an undefined cwd contract, so paths are resolved
|
|
26
|
+
* relative to this file via `import.meta.url`, NOT process.cwd().
|
|
27
|
+
*/
|
|
28
|
+
import { spawnSync } from "node:child_process";
|
|
29
|
+
import { resolve, dirname, join } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
|
|
34
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const DIST_BIN = resolve(HERE, "..", "dist", "cli.mjs");
|
|
36
|
+
const SRC_BIN = resolve(HERE, "..", "bin", "failproofai.mjs");
|
|
37
|
+
// Prefer the bundled dist/cli.mjs (node-compatible); fall back to source +
|
|
38
|
+
// bun for dev workflows where dist/ hasn't been built yet.
|
|
39
|
+
function resolveSpawn(): { cmd: string; args: string[] } {
|
|
40
|
+
if (process.env.FAILPROOFAI_BINARY_OVERRIDE) {
|
|
41
|
+
return { cmd: "node", args: [process.env.FAILPROOFAI_BINARY_OVERRIDE] };
|
|
42
|
+
}
|
|
43
|
+
if (existsSync(DIST_BIN)) {
|
|
44
|
+
return { cmd: "node", args: [DIST_BIN] };
|
|
45
|
+
}
|
|
46
|
+
return { cmd: "bun", args: [SRC_BIN] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface PolicyDecision {
|
|
50
|
+
permission?: "allow" | "deny";
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Spawn `failproofai --hook <eventName> --cli pi`, write the JSON payload to
|
|
56
|
+
* stdin, and parse the flat `{permission, reason}` JSON we expect failproofai
|
|
57
|
+
* to print on stdout. Fail-open on any subprocess / parse error.
|
|
58
|
+
*/
|
|
59
|
+
/** Optional stderr trace for debugging the shim. Enabled with
|
|
60
|
+
* FAILPROOFAI_PI_DEBUG=1; silent otherwise. */
|
|
61
|
+
function debug(msg: string): void {
|
|
62
|
+
if (process.env.FAILPROOFAI_PI_DEBUG === "1") {
|
|
63
|
+
process.stderr.write(`[failproofai-pi-shim] ${msg}\n`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function callPolicy(eventName: string, payload: unknown): { block: boolean; reason: string } {
|
|
68
|
+
const { cmd, args } = resolveSpawn();
|
|
69
|
+
debug(`callPolicy event=${eventName} cmd=${cmd}`);
|
|
70
|
+
try {
|
|
71
|
+
const result = spawnSync(
|
|
72
|
+
cmd,
|
|
73
|
+
[...args, "--hook", eventName, "--cli", "pi"],
|
|
74
|
+
{
|
|
75
|
+
input: JSON.stringify(payload),
|
|
76
|
+
encoding: "utf8",
|
|
77
|
+
timeout: 60_000,
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
if (result.status !== 0) return { block: false, reason: "" };
|
|
81
|
+
const stdout = (result.stdout || "").trim();
|
|
82
|
+
if (!stdout) return { block: false, reason: "" };
|
|
83
|
+
const parsed = JSON.parse(stdout) as PolicyDecision;
|
|
84
|
+
if (parsed.permission === "deny") {
|
|
85
|
+
debug(`DENY reason=${parsed.reason}`);
|
|
86
|
+
return { block: true, reason: parsed.reason ?? "Blocked by failproofai" };
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
debug(`EXCEPTION ${err instanceof Error ? err.message : String(err)}`);
|
|
90
|
+
// Fail-open: never block tool execution because of an infra failure.
|
|
91
|
+
}
|
|
92
|
+
return { block: false, reason: "" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface PiToolCallEvent {
|
|
96
|
+
type?: string;
|
|
97
|
+
toolName?: string;
|
|
98
|
+
toolCallId?: string;
|
|
99
|
+
input?: Record<string, unknown>;
|
|
100
|
+
cwd?: string;
|
|
101
|
+
sessionId?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Pi emits tool names in lowercase (`bash`, `read`, `edit`, `write`).
|
|
106
|
+
* failproofai's builtin policies match on Claude-shaped capitalized names
|
|
107
|
+
* (`Bash`, `Read`, `Edit`, `Write`). Map between the two so existing
|
|
108
|
+
* tool-name match clauses fire on Pi sessions.
|
|
109
|
+
*/
|
|
110
|
+
function canonicalizeToolName(piToolName: string | undefined): string | undefined {
|
|
111
|
+
if (!piToolName) return undefined;
|
|
112
|
+
return piToolName.charAt(0).toUpperCase() + piToolName.slice(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Resolve the cwd for the policy payload. Pi events don't include cwd, so
|
|
116
|
+
* fall back to the extension's process.cwd() — which is where Pi was
|
|
117
|
+
* launched and where `.failproofai/` config lives. */
|
|
118
|
+
function resolveCwd(eventCwd: string | undefined): string {
|
|
119
|
+
return eventCwd ?? process.cwd();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Pi (verified empirically against pi-coding-agent v0.71.1) does NOT
|
|
124
|
+
* populate `event.sessionId` on any of its events — `session_start`,
|
|
125
|
+
* `tool_call`, `user_bash`, `input`, `tool_result`, `agent_end`,
|
|
126
|
+
* `session_shutdown` all leave it undefined. Without help the shim can't
|
|
127
|
+
* tag activity records with a session id, so the dashboard renders
|
|
128
|
+
* `Session ID: —` for every Pi row.
|
|
129
|
+
*
|
|
130
|
+
* What Pi DOES do: at session start it creates a JSONL transcript at
|
|
131
|
+
* `~/.pi/agent/sessions/<encodedCwd>/<isoTimestamp>_<uuid>.jsonl` where
|
|
132
|
+
* the filename encodes the sessionId. We discover ours by scanning the
|
|
133
|
+
* encoded-cwd directory for the most-recently-modified matching file.
|
|
134
|
+
*
|
|
135
|
+
* Strategy: scan once and cache. Pi runs one session per process so the
|
|
136
|
+
* cache is per-process and lives for the session's lifetime. If Pi ever
|
|
137
|
+
* multiplexes, we'd need a keyed map.
|
|
138
|
+
*/
|
|
139
|
+
const PI_FILE_RE = /^[\d-]+T[\d-]+Z_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
140
|
+
|
|
141
|
+
/** Encode a cwd into Pi's on-disk session-dir name. Pi strips the leading
|
|
142
|
+
* `/` before replacing remaining slashes with `-`, e.g.
|
|
143
|
+
* `/home/u/repo` → `--home-u-repo--`. */
|
|
144
|
+
function piEncodeCwd(cwd: string): string {
|
|
145
|
+
const inner = cwd.replace(/^\/+/, "").replace(/\//g, "-");
|
|
146
|
+
return `--${inner}--`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Process start boundary — files older than this aren't from the current
|
|
150
|
+
* Pi session. Captured at module load so cold-start in a cwd with stale
|
|
151
|
+
* transcripts doesn't pin a previous session's UUID. We allow a small
|
|
152
|
+
* tolerance below `processStartMs` because mtime resolution and clock
|
|
153
|
+
* skew can put a "current" file's mtime a few hundred ms before module
|
|
154
|
+
* load on slow startup. */
|
|
155
|
+
const PROCESS_START_MS = Date.now();
|
|
156
|
+
const STALE_TOLERANCE_MS = 2_000;
|
|
157
|
+
|
|
158
|
+
/** Find the newest `<ts>_<uuid>.jsonl` file under `~/.pi/agent/sessions/<encodedCwd>/`
|
|
159
|
+
* whose mtime indicates it belongs to the CURRENT Pi process (≥ process
|
|
160
|
+
* start, with a small tolerance). Files older than that are stale
|
|
161
|
+
* transcripts from prior sessions in the same cwd — caching their UUID
|
|
162
|
+
* would cross-attribute every event of the new session.
|
|
163
|
+
* Returns undefined when the dir doesn't exist, has no matching file, or
|
|
164
|
+
* every matching file is stale. */
|
|
165
|
+
function discoverPiSessionId(cwd: string): string | undefined {
|
|
166
|
+
const root = process.env.PI_SESSIONS_DIR || join(homedir(), ".pi", "agent", "sessions");
|
|
167
|
+
const dir = join(root, piEncodeCwd(cwd));
|
|
168
|
+
let entries: string[];
|
|
169
|
+
try { entries = readdirSync(dir); } catch { return undefined; }
|
|
170
|
+
const boundary = PROCESS_START_MS - STALE_TOLERANCE_MS;
|
|
171
|
+
let best: { sessionId: string; mtime: number } | undefined;
|
|
172
|
+
for (const name of entries) {
|
|
173
|
+
const m = PI_FILE_RE.exec(name);
|
|
174
|
+
if (!m) continue;
|
|
175
|
+
let mtime: number;
|
|
176
|
+
try { mtime = statSync(join(dir, name)).mtimeMs; } catch { continue; }
|
|
177
|
+
if (mtime < boundary) continue;
|
|
178
|
+
if (!best || mtime > best.mtime) best = { sessionId: m[1], mtime };
|
|
179
|
+
}
|
|
180
|
+
return best?.sessionId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** sessionId cache, keyed by cwd. Per-cwd so a multi-cwd Pi (extension running
|
|
184
|
+
* across multiple workspace roots) can't cross-attribute. Cleared on
|
|
185
|
+
* session_shutdown reasons `new`/`resume`/`fork` (Pi reuses the process). */
|
|
186
|
+
const cachedSessionIdByCwd = new Map<string, string>();
|
|
187
|
+
function resolveSessionId(eventSessionId: string | undefined, cwd: string): string | undefined {
|
|
188
|
+
if (eventSessionId) {
|
|
189
|
+
cachedSessionIdByCwd.set(cwd, eventSessionId);
|
|
190
|
+
return eventSessionId;
|
|
191
|
+
}
|
|
192
|
+
const cached = cachedSessionIdByCwd.get(cwd);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
// Pi v0.71.1 never sets sessionId — discover from disk.
|
|
195
|
+
const discovered = discoverPiSessionId(cwd);
|
|
196
|
+
if (discovered) cachedSessionIdByCwd.set(cwd, discovered);
|
|
197
|
+
return discovered;
|
|
198
|
+
}
|
|
199
|
+
/** Clear the cached sessionId for a cwd. Called on session_shutdown reasons
|
|
200
|
+
* that indicate a new session is starting in the same process (`new`,
|
|
201
|
+
* `resume`, `fork`). Without this, the next session would inherit the prior
|
|
202
|
+
* sessionId until disk discovery refreshed it. */
|
|
203
|
+
function resetSessionIdCache(cwd: string): void {
|
|
204
|
+
cachedSessionIdByCwd.delete(cwd);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface PiUserBashEvent {
|
|
208
|
+
type?: string;
|
|
209
|
+
command?: string;
|
|
210
|
+
cwd?: string;
|
|
211
|
+
sessionId?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface PiInputEvent {
|
|
215
|
+
type?: string;
|
|
216
|
+
text?: string;
|
|
217
|
+
source?: string;
|
|
218
|
+
cwd?: string;
|
|
219
|
+
sessionId?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface PiSessionStartEvent {
|
|
223
|
+
type?: string;
|
|
224
|
+
reason?: string;
|
|
225
|
+
cwd?: string;
|
|
226
|
+
sessionId?: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface PiSessionShutdownEvent {
|
|
230
|
+
type?: string;
|
|
231
|
+
/** "quit" | "reload" | "new" | "resume" | "fork" per pi-coding-agent v0.72.1 */
|
|
232
|
+
reason?: string;
|
|
233
|
+
targetSessionFile?: string;
|
|
234
|
+
cwd?: string;
|
|
235
|
+
sessionId?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface PiToolResultEvent {
|
|
239
|
+
type?: string;
|
|
240
|
+
toolCallId?: string;
|
|
241
|
+
toolName?: string;
|
|
242
|
+
input?: Record<string, unknown>;
|
|
243
|
+
/** TextContent | ImageContent — opaque to us; forwarded as-is. */
|
|
244
|
+
content?: unknown[];
|
|
245
|
+
isError?: boolean;
|
|
246
|
+
cwd?: string;
|
|
247
|
+
sessionId?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface PiAgentEndEvent {
|
|
251
|
+
type?: string;
|
|
252
|
+
/** AgentMessage[] — opaque; not forwarded (Stop policies don't need it). */
|
|
253
|
+
messages?: unknown[];
|
|
254
|
+
cwd?: string;
|
|
255
|
+
sessionId?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
interface PiExtensionApi {
|
|
259
|
+
on(event: string, handler: (event: unknown) => unknown): void;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
263
|
+
// tool_call → PreToolUse. Block tool execution when failproofai denies.
|
|
264
|
+
pi.on("tool_call", (event: unknown): unknown => {
|
|
265
|
+
const e = event as PiToolCallEvent;
|
|
266
|
+
const decision = callPolicy("tool_call", {
|
|
267
|
+
tool_name: canonicalizeToolName(e.toolName),
|
|
268
|
+
tool_input: e.input,
|
|
269
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
270
|
+
cwd: resolveCwd(e.cwd),
|
|
271
|
+
hook_event_name: "PreToolUse",
|
|
272
|
+
});
|
|
273
|
+
if (decision.block) return { block: true, reason: decision.reason };
|
|
274
|
+
return undefined;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// user_bash → PreToolUse with synthesized toolName=Bash.
|
|
278
|
+
pi.on("user_bash", (event: unknown): unknown => {
|
|
279
|
+
const e = event as PiUserBashEvent;
|
|
280
|
+
const decision = callPolicy("user_bash", {
|
|
281
|
+
tool_name: "Bash",
|
|
282
|
+
tool_input: { command: e.command },
|
|
283
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
284
|
+
cwd: resolveCwd(e.cwd),
|
|
285
|
+
hook_event_name: "PreToolUse",
|
|
286
|
+
});
|
|
287
|
+
if (decision.block) return { block: true, reason: decision.reason };
|
|
288
|
+
return undefined;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// input → UserPromptSubmit. Honor block decisions if Pi accepts them
|
|
292
|
+
// (Pi's docs describe block on input but it's not exhaustively tested).
|
|
293
|
+
pi.on("input", (event: unknown): unknown => {
|
|
294
|
+
const e = event as PiInputEvent;
|
|
295
|
+
const decision = callPolicy("input", {
|
|
296
|
+
prompt: e.text,
|
|
297
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
298
|
+
cwd: resolveCwd(e.cwd),
|
|
299
|
+
hook_event_name: "UserPromptSubmit",
|
|
300
|
+
});
|
|
301
|
+
if (decision.block) return { block: true, reason: decision.reason };
|
|
302
|
+
return undefined;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// session_start → SessionStart. Observe-only; we still forward so the
|
|
306
|
+
// activity feed records the session and any UserPromptSubmit policies that
|
|
307
|
+
// need session_id continuity see the metadata.
|
|
308
|
+
pi.on("session_start", (event: unknown): unknown => {
|
|
309
|
+
const e = event as PiSessionStartEvent;
|
|
310
|
+
callPolicy("session_start", {
|
|
311
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
312
|
+
cwd: resolveCwd(e.cwd),
|
|
313
|
+
reason: e.reason,
|
|
314
|
+
hook_event_name: "SessionStart",
|
|
315
|
+
});
|
|
316
|
+
return undefined;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// tool_result → PostToolUse. Observation-only on Pi: ToolResultEventResult
|
|
320
|
+
// exposes {content, details, isError} for mutation but no `block`. We
|
|
321
|
+
// forward to the failproofai binary so PostToolUse builtins (sanitize-jwt,
|
|
322
|
+
// sanitize-api-keys, sanitize-connection-strings, sanitize-private-key-
|
|
323
|
+
// content, sanitize-bearer-tokens) run and get their decisions logged to
|
|
324
|
+
// the activity store + stderr — but Pi keeps the original tool result.
|
|
325
|
+
pi.on("tool_result", (event: unknown): unknown => {
|
|
326
|
+
const e = event as PiToolResultEvent;
|
|
327
|
+
callPolicy("tool_result", {
|
|
328
|
+
tool_name: canonicalizeToolName(e.toolName),
|
|
329
|
+
tool_input: e.input ?? {},
|
|
330
|
+
tool_response: { content: e.content, isError: e.isError },
|
|
331
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
332
|
+
cwd: resolveCwd(e.cwd),
|
|
333
|
+
hook_event_name: "PostToolUse",
|
|
334
|
+
});
|
|
335
|
+
return undefined;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// agent_end → Stop. Observation-only on Pi: the agent loop has already
|
|
339
|
+
// exited when this fires, so a deny decision cannot keep Pi running the
|
|
340
|
+
// way Claude's exit-2-from-Stop can. We still forward so the 5
|
|
341
|
+
// require-*-before-stop builtins run and log their findings (visible in
|
|
342
|
+
// the dashboard's activity feed and stderr) — best-effort visibility.
|
|
343
|
+
pi.on("agent_end", (event: unknown): unknown => {
|
|
344
|
+
const e = event as PiAgentEndEvent;
|
|
345
|
+
callPolicy("agent_end", {
|
|
346
|
+
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
347
|
+
cwd: resolveCwd(e.cwd),
|
|
348
|
+
hook_event_name: "Stop",
|
|
349
|
+
});
|
|
350
|
+
return undefined;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// session_shutdown → SessionEnd. Observation-only; emits a SessionEnd
|
|
354
|
+
// record so per-session telemetry has a clean close. Reset the per-cwd
|
|
355
|
+
// sessionId cache for shutdown reasons that mean "Pi is starting a new
|
|
356
|
+
// session in the same process" — without the reset, the next session's
|
|
357
|
+
// events would inherit the prior session's id until disk discovery
|
|
358
|
+
// refreshed it.
|
|
359
|
+
pi.on("session_shutdown", (event: unknown): unknown => {
|
|
360
|
+
const e = event as PiSessionShutdownEvent;
|
|
361
|
+
const cwd = resolveCwd(e.cwd);
|
|
362
|
+
callPolicy("session_shutdown", {
|
|
363
|
+
session_id: resolveSessionId(e.sessionId, cwd),
|
|
364
|
+
cwd,
|
|
365
|
+
reason: e.reason,
|
|
366
|
+
hook_event_name: "SessionEnd",
|
|
367
|
+
});
|
|
368
|
+
if (e.reason === "new" || e.reason === "resume" || e.reason === "fork") {
|
|
369
|
+
resetSessionIdCache(cwd);
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@failproofai/pi-extension",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "failproofai policy bridge for Pi (pi-coding-agent)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"private": true,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"failproofai"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.10-beta.0"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
**Translations**: [简体中文](docs/i18n/README.zh.md) | [日本語](docs/i18n/README.ja.md) | [한국어](docs/i18n/README.ko.md) | [Español](docs/i18n/README.es.md) | [Português](docs/i18n/README.pt-br.md) | [Deutsch](docs/i18n/README.de.md) | [Français](docs/i18n/README.fr.md) | [Русский](docs/i18n/README.ru.md) | [हिन्दी](docs/i18n/README.hi.md) | [Türkçe](docs/i18n/README.tr.md) | [Tiếng Việt](docs/i18n/README.vi.md) | [Italiano](docs/i18n/README.it.md) | [العربية](docs/i18n/README.ar.md) | [עברית](docs/i18n/README.he.md)
|
|
19
19
|
|
|
20
|
-
The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code**, **OpenAI Codex** & the **Agents SDK**.
|
|
20
|
+
The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code**, **OpenAI Codex**, **GitHub Copilot CLI** _(beta)_, **Cursor Agent** _(beta)_, **OpenCode** _(beta)_, **Pi** _(beta)_, **Gemini CLI** _(beta)_ & the **Agents SDK**.
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
23
23
|
<img src="failproofai-hq.gif" alt="Failproof AI in action" width="800" />
|
|
@@ -37,10 +37,44 @@ The easiest way to manage policies that keep your AI agents reliable, on-task, a
|
|
|
37
37
|
</picture>
|
|
38
38
|
</a>
|
|
39
39
|
|
|
40
|
-
<
|
|
40
|
+
<a href="https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/use-hooks" title="GitHub Copilot CLI">
|
|
41
|
+
<picture>
|
|
42
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/copilot-dark.svg" />
|
|
43
|
+
<img src="assets/logos/copilot-light.svg" alt="GitHub Copilot" width="64" height="64" />
|
|
44
|
+
</picture>
|
|
45
|
+
</a>
|
|
46
|
+
|
|
47
|
+
<a href="https://cursor.com/docs/hooks" title="Cursor Agent CLI">
|
|
48
|
+
<picture>
|
|
49
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/cursor-dark.svg" />
|
|
50
|
+
<img src="assets/logos/cursor-light.svg" alt="Cursor Agent" width="64" height="64" />
|
|
51
|
+
</picture>
|
|
52
|
+
</a>
|
|
53
|
+
</p>
|
|
54
|
+
<p align="center">
|
|
55
|
+
<a href="https://opencode.ai/docs/plugins/" title="OpenCode">
|
|
56
|
+
<picture>
|
|
57
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/opencode-dark.svg" />
|
|
58
|
+
<img src="assets/logos/opencode-light.svg" alt="OpenCode" width="64" height="64" />
|
|
59
|
+
</picture>
|
|
60
|
+
</a>
|
|
61
|
+
|
|
62
|
+
<a href="https://pi.dev" title="Pi (pi-coding-agent)">
|
|
63
|
+
<picture>
|
|
64
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/pi-dark.svg" />
|
|
65
|
+
<img src="assets/logos/pi-light.svg" alt="Pi" width="64" height="64" />
|
|
66
|
+
</picture>
|
|
67
|
+
</a>
|
|
68
|
+
|
|
69
|
+
<a href="https://geminicli.com/" title="Gemini CLI">
|
|
70
|
+
<picture>
|
|
71
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/gemini-dark.svg" />
|
|
72
|
+
<img src="assets/logos/gemini-light.svg" alt="Gemini CLI" width="64" height="64" />
|
|
73
|
+
</picture>
|
|
74
|
+
</a>
|
|
41
75
|
</p>
|
|
42
76
|
|
|
43
|
-
> Install hooks for one or
|
|
77
|
+
> Install hooks for one or any combination: `failproofai policies --install --cli opencode pi gemini` (or `--cli claude codex copilot cursor opencode pi gemini`). Omit `--cli` to auto-detect installed CLIs and prompt. **GitHub Copilot CLI, Cursor Agent, OpenCode, Pi, and Gemini CLI support are in beta — testing is ongoing.**
|
|
44
78
|
|
|
45
79
|
- **39 Built-in Policies** - Catch common agent failure modes out of the box. Block destructive commands, prevent secret leakage, keep agents inside project boundaries, detect loops, and more.
|
|
46
80
|
- **Custom Policies** - Write your own reliability rules in JavaScript. Use the `allow`/`deny`/`instruct` API to enforce conventions, prevent drift, gate operations, or integrate with external systems.
|