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
package/src/hooks/handler.ts
CHANGED
|
@@ -5,8 +5,16 @@
|
|
|
5
5
|
* ~/.failproofai/policies-config.json, evaluates matching policies, persists
|
|
6
6
|
* activity to disk, and returns the appropriate exit code + stdout response.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
9
|
-
|
|
8
|
+
import type {
|
|
9
|
+
HookEventType,
|
|
10
|
+
IntegrationType,
|
|
11
|
+
SessionMetadata,
|
|
12
|
+
CodexHookEventType,
|
|
13
|
+
CursorHookEventType,
|
|
14
|
+
PiHookEventType,
|
|
15
|
+
GeminiHookEventType,
|
|
16
|
+
} from "./types";
|
|
17
|
+
import { CODEX_EVENT_MAP, CURSOR_EVENT_MAP, PI_EVENT_MAP, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP } from "./types";
|
|
10
18
|
import type { PolicyFunction, PolicyResult } from "./policy-types";
|
|
11
19
|
import { readMergedHooksConfig } from "./hooks-config";
|
|
12
20
|
import { registerBuiltinPolicies } from "./builtin-policies";
|
|
@@ -22,18 +30,56 @@ import { hookLogInfo, hookLogWarn } from "./hook-logger";
|
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
32
|
* Canonicalize an event name to PascalCase. Codex sends snake_case event names
|
|
25
|
-
* on stdin and as the --hook arg;
|
|
26
|
-
*
|
|
33
|
+
* on stdin and as the --hook arg; Cursor sends camelCase (`preToolUse`,
|
|
34
|
+
* `beforeSubmitPrompt`); Pi sends underscore_lower_snake_case (`tool_call`,
|
|
35
|
+
* `session_start`); Claude Code sends PascalCase. Copilot CLI is installed
|
|
36
|
+
* in "VS Code compatible" PascalCase mode (see integrations.ts), so its events
|
|
37
|
+
* arrive PascalCase already. Gemini also sends PascalCase but with different
|
|
38
|
+
* names (`BeforeTool`, `BeforeAgent`, `AfterAgent`); we map via GEMINI_EVENT_MAP.
|
|
39
|
+
* The internal registry, builtin policies, and policy.match.events all key on
|
|
40
|
+
* PascalCase.
|
|
27
41
|
*/
|
|
28
42
|
function canonicalizeEventType(raw: string, cli: IntegrationType): HookEventType {
|
|
29
43
|
if (cli === "codex") {
|
|
30
44
|
const mapped = CODEX_EVENT_MAP[raw as CodexHookEventType];
|
|
31
45
|
if (mapped) return mapped;
|
|
32
46
|
}
|
|
33
|
-
|
|
47
|
+
if (cli === "cursor") {
|
|
48
|
+
const mapped = CURSOR_EVENT_MAP[raw as CursorHookEventType];
|
|
49
|
+
if (mapped) return mapped;
|
|
50
|
+
}
|
|
51
|
+
if (cli === "pi") {
|
|
52
|
+
const mapped = PI_EVENT_MAP[raw as PiHookEventType];
|
|
53
|
+
if (mapped) return mapped;
|
|
54
|
+
}
|
|
55
|
+
if (cli === "gemini") {
|
|
56
|
+
const mapped = GEMINI_EVENT_MAP[raw as GeminiHookEventType];
|
|
57
|
+
if (mapped) return mapped;
|
|
58
|
+
}
|
|
59
|
+
// claude / copilot / unknown — already PascalCase, pass through.
|
|
60
|
+
// HOOK_EVENT_TYPES type-checks downstream.
|
|
34
61
|
return raw as HookEventType;
|
|
35
62
|
}
|
|
36
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Canonicalize a per-CLI tool name to the Claude PascalCase form that builtin
|
|
66
|
+
* policies match on (e.g. `Bash`, `Read`, `Write`, `Edit`). Today only Gemini
|
|
67
|
+
* needs this — its tools are snake_case (`run_shell_command`, `read_file`,
|
|
68
|
+
* `write_file`, `replace`, …). Other CLIs pass through unchanged: Claude /
|
|
69
|
+
* Codex / Copilot already use PascalCase, and Cursor / Pi pre-canonicalize
|
|
70
|
+
* inside their own plugin shims before the payload reaches this binary.
|
|
71
|
+
*
|
|
72
|
+
* Unknown tool names (MCP `mcp_*`, third-party extensions, Skills) pass
|
|
73
|
+
* through unchanged so non-builtin tooling isn't lost.
|
|
74
|
+
*/
|
|
75
|
+
function canonicalizeToolName(raw: string | undefined, cli: IntegrationType): string | undefined {
|
|
76
|
+
if (!raw) return raw;
|
|
77
|
+
if (cli === "gemini") {
|
|
78
|
+
return GEMINI_TOOL_MAP[raw] ?? raw;
|
|
79
|
+
}
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
|
|
37
83
|
export async function handleHookEvent(
|
|
38
84
|
eventType: string,
|
|
39
85
|
cli: IntegrationType = "claude",
|
|
@@ -79,6 +125,17 @@ export async function handleHookEvent(
|
|
|
79
125
|
// Canonicalize event name (Codex sends snake_case; internals expect PascalCase)
|
|
80
126
|
const canonicalEventType = canonicalizeEventType(eventType, cli);
|
|
81
127
|
|
|
128
|
+
// Canonicalize tool name in place so both the policy-registry tool-name
|
|
129
|
+
// filter and policy bodies (`ctx.toolName === "Bash"`) see the canonical
|
|
130
|
+
// form. Today only Gemini's snake_case names need translation; other CLIs
|
|
131
|
+
// are no-ops here. Mutating `parsed.tool_name` keeps the activity store +
|
|
132
|
+
// telemetry tagging consistent (they read from `parsed.tool_name`).
|
|
133
|
+
const rawToolName = parsed.tool_name as string | undefined;
|
|
134
|
+
const canonicalToolName = canonicalizeToolName(rawToolName, cli);
|
|
135
|
+
if (canonicalToolName !== rawToolName) {
|
|
136
|
+
parsed.tool_name = canonicalToolName;
|
|
137
|
+
}
|
|
138
|
+
|
|
82
139
|
// Extract session metadata from payload
|
|
83
140
|
const sessionId = parsed.session_id as string | undefined;
|
|
84
141
|
const session: SessionMetadata = {
|
|
@@ -87,6 +144,11 @@ export async function handleHookEvent(
|
|
|
87
144
|
cwd: parsed.cwd as string | undefined,
|
|
88
145
|
permissionMode: resolvePermissionMode(cli, parsed, sessionId),
|
|
89
146
|
hookEventName: parsed.hook_event_name as string | undefined,
|
|
147
|
+
// Preserve the raw CLI-side event name (eventType arg) before
|
|
148
|
+
// canonicalization. Response shapes that round-trip the agent-emitted
|
|
149
|
+
// event name (e.g. Gemini's `hookSpecificOutput.hookEventName`) prefer
|
|
150
|
+
// this over the canonicalized form when stdin omits hook_event_name.
|
|
151
|
+
rawHookEventName: eventType,
|
|
90
152
|
cli,
|
|
91
153
|
};
|
|
92
154
|
|
|
@@ -30,8 +30,13 @@ export interface PromptOptions {
|
|
|
30
30
|
includeBeta?: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Whether the prompt is being shown for an install or an uninstall flow.
|
|
34
|
+
* Drives heading + hint text so `policies --uninstall` no longer says
|
|
35
|
+
* "Install Hooks". */
|
|
36
|
+
export type CliPromptAction = "install" | "uninstall";
|
|
37
|
+
|
|
33
38
|
/**
|
|
34
|
-
* Resolve which agent CLIs to install hooks for.
|
|
39
|
+
* Resolve which agent CLIs to install/uninstall hooks for.
|
|
35
40
|
*
|
|
36
41
|
* Rules:
|
|
37
42
|
* • If `explicit` is provided (from `--cli`), use it as-is.
|
|
@@ -42,14 +47,26 @@ export interface PromptOptions {
|
|
|
42
47
|
*
|
|
43
48
|
* Returns the selected IntegrationType[] (always non-empty).
|
|
44
49
|
*/
|
|
45
|
-
export async function resolveTargetClis(
|
|
50
|
+
export async function resolveTargetClis(
|
|
51
|
+
explicit?: IntegrationType[],
|
|
52
|
+
action: CliPromptAction = "install",
|
|
53
|
+
): Promise<IntegrationType[]> {
|
|
46
54
|
if (explicit && explicit.length > 0) return [...new Set(explicit)];
|
|
47
55
|
|
|
48
56
|
const detected = detectInstalledClis();
|
|
49
57
|
|
|
50
58
|
if (detected.length === 0) {
|
|
59
|
+
if (action === "uninstall") {
|
|
60
|
+
// Uninstall flow: no agent CLIs detected — nothing to remove from. Default to
|
|
61
|
+
// claude so removeHooks operates over Claude's scopes (no-op if no settings file).
|
|
62
|
+
console.log(
|
|
63
|
+
"\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex, copilot, cursor-agent, opencode, pi, gemini). " +
|
|
64
|
+
"Defaulting to Claude Code; nothing will be removed if no settings file exists.\x1B[0m",
|
|
65
|
+
);
|
|
66
|
+
return ["claude"];
|
|
67
|
+
}
|
|
51
68
|
console.log(
|
|
52
|
-
"\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex). " +
|
|
69
|
+
"\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex, copilot, cursor-agent, opencode, pi, gemini). " +
|
|
53
70
|
"Defaulting to Claude Code; hooks will activate when an agent is installed.\x1B[0m",
|
|
54
71
|
);
|
|
55
72
|
return ["claude"];
|
|
@@ -57,26 +74,29 @@ export async function resolveTargetClis(explicit?: IntegrationType[]): Promise<I
|
|
|
57
74
|
|
|
58
75
|
if (detected.length === 1) {
|
|
59
76
|
const integration = getIntegration(detected[0]);
|
|
60
|
-
|
|
77
|
+
const verb = action === "uninstall" ? "removing hooks from" : "installing hooks for";
|
|
78
|
+
console.log(`Detected ${integration.displayName}; ${verb} it.`);
|
|
61
79
|
return detected;
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
// Multiple detected. Prompt or default.
|
|
65
|
-
if (!process.stdin.isTTY) return detected; // non-interactive: install for all detected
|
|
83
|
+
if (!process.stdin.isTTY) return detected; // non-interactive: install/remove for all detected
|
|
66
84
|
|
|
67
|
-
return promptCliTargetSelection(detected);
|
|
85
|
+
return promptCliTargetSelection(detected, action);
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
/**
|
|
71
|
-
* Interactive arrow-key single-select for "install for which CLI?" when
|
|
89
|
+
* Interactive arrow-key single-select for "install/remove for which CLI?" when
|
|
72
90
|
* multiple agent CLIs are detected. Visual style mirrors promptPolicySelection.
|
|
73
91
|
*/
|
|
74
92
|
async function promptCliTargetSelection(
|
|
75
93
|
detected: IntegrationType[],
|
|
94
|
+
action: CliPromptAction = "install",
|
|
76
95
|
): Promise<IntegrationType[]> {
|
|
77
96
|
const labels = detected.map((id) => getIntegration(id).displayName).join(" + ");
|
|
97
|
+
const allLabel = detected.length > 2 ? "All" : "Both";
|
|
78
98
|
const options: Array<{ label: string; description: string; value: IntegrationType[] }> = [
|
|
79
|
-
{ label:
|
|
99
|
+
{ label: allLabel, description: labels, value: detected },
|
|
80
100
|
...detected.map((id) => ({
|
|
81
101
|
label: `${getIntegration(id).displayName} only`,
|
|
82
102
|
description: "",
|
|
@@ -122,14 +142,17 @@ async function promptCliTargetSelection(
|
|
|
122
142
|
return result;
|
|
123
143
|
}
|
|
124
144
|
|
|
145
|
+
const heading = action === "uninstall" ? "Remove Hooks" : "Install Hooks";
|
|
146
|
+
const verb = action === "uninstall" ? "remove from" : "install";
|
|
147
|
+
|
|
125
148
|
function render(): void {
|
|
126
149
|
const cols = process.stdout.columns || 120;
|
|
127
150
|
hideCursor();
|
|
128
151
|
|
|
129
152
|
const lines: string[] = [];
|
|
130
|
-
lines.push(
|
|
153
|
+
lines.push(` Failproof AI — ${heading}`);
|
|
131
154
|
lines.push("");
|
|
132
|
-
lines.push(` \x1B[2mDetected ${labels}. Choose where to
|
|
155
|
+
lines.push(` \x1B[2mDetected ${labels}. Choose where to ${verb}:\x1B[0m`);
|
|
133
156
|
lines.push("");
|
|
134
157
|
|
|
135
158
|
for (let i = 0; i < options.length; i++) {
|