failproofai 0.0.7 → 0.0.9-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/.codex/hooks.json +77 -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.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +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 +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0m72uj7._.js → [root-of-the-server]__03rd.z8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0zn7uo6._.js → [root-of-the-server]__0ca1zru._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e74wa-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ea22pr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0okos0k._.js → [root-of-the-server]__0vu.o-3._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +7 -7
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0of~riu._.js → [root-of-the-server]__0zqcovi._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_07a1g.3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uy6m~m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0zaq1hm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_11rg2a_._.js +3 -0
- 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/node_modules_next_dist_0h9llsw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
- 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/0.rk1iwdt1d7c.css +1 -0
- package/.next/standalone/.next/static/chunks/00b5h4r1el.6f.js +1 -0
- package/.next/standalone/.next/static/chunks/{01l2mh88iy.ga.js → 03lsndql_yml5.js} +1 -1
- package/.next/standalone/.next/static/chunks/0amfi~vb_gfgo.js +1 -0
- package/.next/standalone/.next/static/chunks/0fw2h.g66c0h3.js +1 -0
- package/.next/standalone/.next/static/chunks/{0f_9854du76y2.js → 0jce49ygr4fdv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0388wpenm9-a4.js → 0mungg3~jpwe7.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0x0o8~u4jsatb.js → 0uq_5p-p7myfe.js} +2 -2
- package/.next/standalone/.next/static/chunks/0v.xuf4ynzp~~.js +6 -0
- package/.next/standalone/.next/static/chunks/{0kkzzoo.s-t3p.js → 0vb8xxj_v2tz8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0vlk_pv4somht.js → 0vwqucikost_q.js} +1 -1
- package/.next/standalone/.next/static/chunks/0~mroziiwl1m5.js +1 -0
- package/.next/standalone/app/actions/install-hooks-web.ts +21 -5
- package/.next/standalone/app/policies/hooks-client.tsx +23 -0
- package/.next/standalone/assets/logos/claude.svg +1 -0
- package/.next/standalone/assets/logos/openai-dark.svg +1 -0
- package/.next/standalone/assets/logos/openai-light.svg +1 -0
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/server.js +1 -1
- package/README.md +22 -3
- package/bin/failproofai.mjs +89 -9
- package/dist/cli.mjs +1040 -297
- package/package.json +2 -2
- package/src/hooks/builtin-policies.ts +39 -33
- package/src/hooks/handler.ts +39 -10
- package/src/hooks/hook-activity-store.ts +2 -0
- package/src/hooks/install-prompt.ts +69 -0
- package/src/hooks/integrations.ts +373 -0
- package/src/hooks/manager.ts +96 -171
- package/src/hooks/policy-evaluator.ts +28 -1
- package/src/hooks/policy-types.ts +3 -1
- package/src/hooks/resolve-permission-mode.ts +147 -0
- package/src/hooks/types.ts +30 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dj-tbi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0tjjyb9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0h21oar._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0i~.gk_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0q3h.2s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0x..fj-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0a0lh_a4f_xs-.js +0 -6
- package/.next/standalone/.next/static/chunks/0bkir2pd22ski.js +0 -1
- package/.next/standalone/.next/static/chunks/0j2o20pqkib~d.js +0 -1
- package/.next/standalone/.next/static/chunks/0ksdlt_1hucdm.js +0 -1
- package/.next/standalone/.next/static/chunks/0mir9jdxn35~s.css +0 -1
- package/.next/standalone/.next/static/chunks/12wu.28cbx4dl.js +0 -1
- /package/.next/standalone/.next/static/{9FNjQiktocMN-qDiGqDL5 → oUO8u4z9JvtTzS_2RJoGo}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{9FNjQiktocMN-qDiGqDL5 → oUO8u4z9JvtTzS_2RJoGo}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{9FNjQiktocMN-qDiGqDL5 → oUO8u4z9JvtTzS_2RJoGo}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -15,6 +15,60 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
|
+
// src/hooks/types.ts
|
|
19
|
+
var HOOK_SCOPES, INTEGRATION_TYPES, CODEX_HOOK_SCOPES, CODEX_HOOK_EVENT_TYPES, CODEX_EVENT_MAP, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
20
|
+
var init_types = __esm(() => {
|
|
21
|
+
HOOK_SCOPES = ["user", "project", "local"];
|
|
22
|
+
INTEGRATION_TYPES = ["claude", "codex"];
|
|
23
|
+
CODEX_HOOK_SCOPES = ["user", "project"];
|
|
24
|
+
CODEX_HOOK_EVENT_TYPES = [
|
|
25
|
+
"session_start",
|
|
26
|
+
"pre_tool_use",
|
|
27
|
+
"permission_request",
|
|
28
|
+
"post_tool_use",
|
|
29
|
+
"user_prompt_submit",
|
|
30
|
+
"stop"
|
|
31
|
+
];
|
|
32
|
+
CODEX_EVENT_MAP = {
|
|
33
|
+
session_start: "SessionStart",
|
|
34
|
+
pre_tool_use: "PreToolUse",
|
|
35
|
+
permission_request: "PermissionRequest",
|
|
36
|
+
post_tool_use: "PostToolUse",
|
|
37
|
+
user_prompt_submit: "UserPromptSubmit",
|
|
38
|
+
stop: "Stop"
|
|
39
|
+
};
|
|
40
|
+
HOOK_EVENT_TYPES = [
|
|
41
|
+
"SessionStart",
|
|
42
|
+
"SessionEnd",
|
|
43
|
+
"UserPromptSubmit",
|
|
44
|
+
"PreToolUse",
|
|
45
|
+
"PermissionRequest",
|
|
46
|
+
"PermissionDenied",
|
|
47
|
+
"PostToolUse",
|
|
48
|
+
"PostToolUseFailure",
|
|
49
|
+
"Notification",
|
|
50
|
+
"SubagentStart",
|
|
51
|
+
"SubagentStop",
|
|
52
|
+
"TaskCreated",
|
|
53
|
+
"TaskCompleted",
|
|
54
|
+
"Stop",
|
|
55
|
+
"StopFailure",
|
|
56
|
+
"TeammateIdle",
|
|
57
|
+
"InstructionsLoaded",
|
|
58
|
+
"ConfigChange",
|
|
59
|
+
"CwdChanged",
|
|
60
|
+
"FileChanged",
|
|
61
|
+
"WorktreeCreate",
|
|
62
|
+
"WorktreeRemove",
|
|
63
|
+
"PreCompact",
|
|
64
|
+
"PostCompact",
|
|
65
|
+
"Elicitation",
|
|
66
|
+
"ElicitationResult",
|
|
67
|
+
"UserPromptExpansion",
|
|
68
|
+
"PostToolBatch"
|
|
69
|
+
];
|
|
70
|
+
});
|
|
71
|
+
|
|
18
72
|
// src/hooks/hook-logger.ts
|
|
19
73
|
import {
|
|
20
74
|
appendFileSync,
|
|
@@ -277,12 +331,20 @@ import { resolve as resolve2, join as join2 } from "node:path";
|
|
|
277
331
|
import { readFile, writeFile } from "node:fs/promises";
|
|
278
332
|
import { execSync, execFileSync } from "node:child_process";
|
|
279
333
|
import { homedir as homedir3 } from "node:os";
|
|
280
|
-
function
|
|
281
|
-
const
|
|
282
|
-
|
|
334
|
+
function isAgentInternalPath(resolved2) {
|
|
335
|
+
for (const dir of [".claude", ".codex"]) {
|
|
336
|
+
const root = join2(homedir3(), dir);
|
|
337
|
+
if (resolved2 === root || resolved2.startsWith(root + "/"))
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
283
341
|
}
|
|
284
|
-
function
|
|
285
|
-
|
|
342
|
+
function isAgentSettingsFile(resolved2) {
|
|
343
|
+
if (/[\\/]\.claude[\\/]settings(?:\.[^/\\]+)?\.json$/.test(resolved2))
|
|
344
|
+
return true;
|
|
345
|
+
if (/[\\/]\.codex[\\/]hooks\.json$/.test(resolved2))
|
|
346
|
+
return true;
|
|
347
|
+
return false;
|
|
286
348
|
}
|
|
287
349
|
function getCommand(ctx) {
|
|
288
350
|
return ctx.toolInput?.command ?? "";
|
|
@@ -1074,22 +1136,7 @@ function requirePrBeforeStop(ctx) {
|
|
|
1074
1136
|
return allow(`PR #${pr.number} exists: ${pr.url}`);
|
|
1075
1137
|
}
|
|
1076
1138
|
if (pr.state === "MERGED") {
|
|
1077
|
-
|
|
1078
|
-
execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
|
|
1079
|
-
cwd,
|
|
1080
|
-
encoding: "utf8",
|
|
1081
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1082
|
-
timeout: 1e4
|
|
1083
|
-
});
|
|
1084
|
-
const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1085
|
-
if (!freshAhead) {
|
|
1086
|
-
return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
|
|
1087
|
-
}
|
|
1088
|
-
const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1089
|
-
if (!freshDiff) {
|
|
1090
|
-
return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
|
|
1091
|
-
}
|
|
1092
|
-
} catch {}
|
|
1139
|
+
return allow(`PR #${pr.number} was merged: ${pr.url}. ` + `Switch off this branch (e.g. 'git checkout ${baseBranch} && git pull') before stopping again.`);
|
|
1093
1140
|
}
|
|
1094
1141
|
return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`);
|
|
1095
1142
|
} catch {
|
|
@@ -1221,9 +1268,11 @@ function registerBuiltinPolicies(enabledNames) {
|
|
|
1221
1268
|
}
|
|
1222
1269
|
}
|
|
1223
1270
|
}
|
|
1224
|
-
var SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, FORCE_PUSH_RE, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
1271
|
+
var isClaudeInternalPath, isClaudeSettingsFile, SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, FORCE_PUSH_RE, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
1225
1272
|
var init_builtin_policies = __esm(() => {
|
|
1226
1273
|
init_hook_logger();
|
|
1274
|
+
isClaudeInternalPath = isAgentInternalPath;
|
|
1275
|
+
isClaudeSettingsFile = isAgentSettingsFile;
|
|
1227
1276
|
SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
|
|
1228
1277
|
SHELL_METACHAR_RE = /[;&<>`$()\\]/;
|
|
1229
1278
|
JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
|
|
@@ -1387,7 +1436,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
1387
1436
|
name: "block-sudo",
|
|
1388
1437
|
description: "Block sudo commands",
|
|
1389
1438
|
fn: blockSudo,
|
|
1390
|
-
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1439
|
+
match: { events: ["PreToolUse", "PermissionRequest"], toolNames: ["Bash"] },
|
|
1391
1440
|
defaultEnabled: true,
|
|
1392
1441
|
category: "Dangerous Commands",
|
|
1393
1442
|
params: {
|
|
@@ -1797,7 +1846,8 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1797
1846
|
payload,
|
|
1798
1847
|
toolName,
|
|
1799
1848
|
toolInput,
|
|
1800
|
-
session
|
|
1849
|
+
session,
|
|
1850
|
+
cli: session?.cli
|
|
1801
1851
|
};
|
|
1802
1852
|
const instructEntries = [];
|
|
1803
1853
|
const allowEntries = [];
|
|
@@ -1842,6 +1892,25 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1842
1892
|
decision: "deny"
|
|
1843
1893
|
};
|
|
1844
1894
|
}
|
|
1895
|
+
if (eventType === "PermissionRequest") {
|
|
1896
|
+
const response = {
|
|
1897
|
+
hookSpecificOutput: {
|
|
1898
|
+
hookEventName: eventType,
|
|
1899
|
+
decision: {
|
|
1900
|
+
behavior: "deny",
|
|
1901
|
+
message: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
return {
|
|
1906
|
+
exitCode: 0,
|
|
1907
|
+
stdout: JSON.stringify(response),
|
|
1908
|
+
stderr: "",
|
|
1909
|
+
policyName: policy.name,
|
|
1910
|
+
reason,
|
|
1911
|
+
decision: "deny"
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1845
1914
|
if (eventType === "PostToolUse") {
|
|
1846
1915
|
const response = {
|
|
1847
1916
|
hookSpecificOutput: {
|
|
@@ -1926,7 +1995,7 @@ You MUST complete the above action(s) NOW. Do NOT ask the user for confirmation
|
|
|
1926
1995
|
const combined = allowEntries.map((e) => e.reason).join(`
|
|
1927
1996
|
`);
|
|
1928
1997
|
const policyNames = allowEntries.map((e) => e.policyName);
|
|
1929
|
-
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
|
|
1998
|
+
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit" || eventType === "PermissionRequest";
|
|
1930
1999
|
const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
|
|
1931
2000
|
const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
1932
2001
|
`);
|
|
@@ -2328,7 +2397,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2328
2397
|
});
|
|
2329
2398
|
|
|
2330
2399
|
// package.json
|
|
2331
|
-
var version2 = "0.0.
|
|
2400
|
+
var version2 = "0.0.9-beta.0";
|
|
2332
2401
|
var init_package = () => {};
|
|
2333
2402
|
|
|
2334
2403
|
// src/posthog-key.ts
|
|
@@ -2359,6 +2428,118 @@ var init_hook_telemetry = __esm(() => {
|
|
|
2359
2428
|
API_KEY = POSTHOG_API_KEY;
|
|
2360
2429
|
});
|
|
2361
2430
|
|
|
2431
|
+
// src/hooks/resolve-permission-mode.ts
|
|
2432
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
2433
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
2434
|
+
import { homedir as homedir6 } from "node:os";
|
|
2435
|
+
function resolvePermissionMode(integration, parsed, sessionId) {
|
|
2436
|
+
if (integration === "claude") {
|
|
2437
|
+
return parsed.permission_mode ?? "default";
|
|
2438
|
+
}
|
|
2439
|
+
if (integration === "codex" && sessionId) {
|
|
2440
|
+
return resolveCodexMode(sessionId) ?? "default";
|
|
2441
|
+
}
|
|
2442
|
+
return "default";
|
|
2443
|
+
}
|
|
2444
|
+
function readCache() {
|
|
2445
|
+
try {
|
|
2446
|
+
if (!existsSync5(CACHE_PATH))
|
|
2447
|
+
return {};
|
|
2448
|
+
return JSON.parse(readFileSync3(CACHE_PATH, "utf-8"));
|
|
2449
|
+
} catch {
|
|
2450
|
+
return {};
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function writeCacheEntry(sessionId, path) {
|
|
2454
|
+
try {
|
|
2455
|
+
mkdirSync4(dirname3(CACHE_PATH), { recursive: true });
|
|
2456
|
+
const cache = readCache();
|
|
2457
|
+
cache[sessionId] = path;
|
|
2458
|
+
writeFileSync3(CACHE_PATH, JSON.stringify(cache), "utf-8");
|
|
2459
|
+
} catch {}
|
|
2460
|
+
}
|
|
2461
|
+
function dirSearch(dir, sessionId) {
|
|
2462
|
+
try {
|
|
2463
|
+
for (const f of readdirSync3(dir, { withFileTypes: true })) {
|
|
2464
|
+
if (f.isFile() && f.name.includes(sessionId) && f.name.endsWith(".jsonl")) {
|
|
2465
|
+
return join4(dir, f.name);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
} catch {}
|
|
2469
|
+
return null;
|
|
2470
|
+
}
|
|
2471
|
+
function findCodexTranscriptSync(sessionId) {
|
|
2472
|
+
const cache = readCache();
|
|
2473
|
+
const cached = cache[sessionId];
|
|
2474
|
+
if (cached && existsSync5(cached))
|
|
2475
|
+
return cached;
|
|
2476
|
+
const root = join4(homedir6(), ".codex", "sessions");
|
|
2477
|
+
const today = new Date;
|
|
2478
|
+
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
|
2479
|
+
const datedDirs = [today, yesterday].map((d) => {
|
|
2480
|
+
const y = String(d.getUTCFullYear());
|
|
2481
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2482
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
2483
|
+
return join4(root, y, m, day);
|
|
2484
|
+
});
|
|
2485
|
+
for (const dir of datedDirs) {
|
|
2486
|
+
const hit = dirSearch(dir, sessionId);
|
|
2487
|
+
if (hit) {
|
|
2488
|
+
writeCacheEntry(sessionId, hit);
|
|
2489
|
+
return hit;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
try {
|
|
2493
|
+
for (const y of readdirSync3(root, { withFileTypes: true })) {
|
|
2494
|
+
if (!y.isDirectory())
|
|
2495
|
+
continue;
|
|
2496
|
+
for (const m of readdirSync3(join4(root, y.name), { withFileTypes: true })) {
|
|
2497
|
+
if (!m.isDirectory())
|
|
2498
|
+
continue;
|
|
2499
|
+
for (const d of readdirSync3(join4(root, y.name, m.name), { withFileTypes: true })) {
|
|
2500
|
+
if (!d.isDirectory())
|
|
2501
|
+
continue;
|
|
2502
|
+
const hit = dirSearch(join4(root, y.name, m.name, d.name), sessionId);
|
|
2503
|
+
if (hit) {
|
|
2504
|
+
writeCacheEntry(sessionId, hit);
|
|
2505
|
+
return hit;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
} catch {}
|
|
2511
|
+
return null;
|
|
2512
|
+
}
|
|
2513
|
+
function resolveCodexMode(sessionId) {
|
|
2514
|
+
try {
|
|
2515
|
+
const path = findCodexTranscriptSync(sessionId);
|
|
2516
|
+
if (!path)
|
|
2517
|
+
return;
|
|
2518
|
+
for (const line of readFileSync3(path, "utf-8").split(`
|
|
2519
|
+
`)) {
|
|
2520
|
+
if (!line.includes("turn_context"))
|
|
2521
|
+
continue;
|
|
2522
|
+
try {
|
|
2523
|
+
const obj = JSON.parse(line);
|
|
2524
|
+
if (obj.type === "turn_context") {
|
|
2525
|
+
const policy = obj.payload?.approval_policy;
|
|
2526
|
+
if (policy === "never")
|
|
2527
|
+
return "full-auto";
|
|
2528
|
+
if (policy === "on-request")
|
|
2529
|
+
return "default";
|
|
2530
|
+
if (policy)
|
|
2531
|
+
return policy;
|
|
2532
|
+
}
|
|
2533
|
+
} catch {}
|
|
2534
|
+
}
|
|
2535
|
+
} catch {}
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
var CACHE_PATH;
|
|
2539
|
+
var init_resolve_permission_mode = __esm(() => {
|
|
2540
|
+
CACHE_PATH = join4(homedir6(), ".failproofai", "cache", "codex-session-paths.json");
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2362
2543
|
// lib/telemetry-id.ts
|
|
2363
2544
|
import fs from "node:fs";
|
|
2364
2545
|
import path from "node:path";
|
|
@@ -2442,26 +2623,26 @@ var init_telemetry_id = __esm(() => {
|
|
|
2442
2623
|
|
|
2443
2624
|
// src/auth/token-store.ts
|
|
2444
2625
|
import {
|
|
2445
|
-
readFileSync as
|
|
2446
|
-
writeFileSync as
|
|
2447
|
-
existsSync as
|
|
2448
|
-
mkdirSync as
|
|
2626
|
+
readFileSync as readFileSync4,
|
|
2627
|
+
writeFileSync as writeFileSync4,
|
|
2628
|
+
existsSync as existsSync6,
|
|
2629
|
+
mkdirSync as mkdirSync5,
|
|
2449
2630
|
unlinkSync as unlinkSync2,
|
|
2450
2631
|
renameSync as renameSync3,
|
|
2451
2632
|
openSync,
|
|
2452
2633
|
closeSync
|
|
2453
2634
|
} from "node:fs";
|
|
2454
|
-
import { join as
|
|
2455
|
-
import { homedir as
|
|
2635
|
+
import { join as join5 } from "node:path";
|
|
2636
|
+
import { homedir as homedir7 } from "node:os";
|
|
2456
2637
|
function ensureAuthDir() {
|
|
2457
|
-
if (!
|
|
2458
|
-
|
|
2638
|
+
if (!existsSync6(AUTH_DIR))
|
|
2639
|
+
mkdirSync5(AUTH_DIR, { recursive: true, mode: 448 });
|
|
2459
2640
|
}
|
|
2460
2641
|
function readTokens() {
|
|
2461
|
-
if (!
|
|
2642
|
+
if (!existsSync6(AUTH_FILE))
|
|
2462
2643
|
return null;
|
|
2463
2644
|
try {
|
|
2464
|
-
const raw =
|
|
2645
|
+
const raw = readFileSync4(AUTH_FILE, "utf8");
|
|
2465
2646
|
return JSON.parse(raw);
|
|
2466
2647
|
} catch {
|
|
2467
2648
|
return null;
|
|
@@ -2472,23 +2653,23 @@ function writeTokens(tokens) {
|
|
|
2472
2653
|
const tmpPath = `${AUTH_FILE}.tmp`;
|
|
2473
2654
|
const fd = openSync(tmpPath, "w", 384);
|
|
2474
2655
|
try {
|
|
2475
|
-
|
|
2656
|
+
writeFileSync4(fd, JSON.stringify(tokens, null, 2));
|
|
2476
2657
|
} finally {
|
|
2477
2658
|
closeSync(fd);
|
|
2478
2659
|
}
|
|
2479
2660
|
renameSync3(tmpPath, AUTH_FILE);
|
|
2480
2661
|
}
|
|
2481
2662
|
function clearTokens() {
|
|
2482
|
-
if (
|
|
2663
|
+
if (existsSync6(AUTH_FILE))
|
|
2483
2664
|
unlinkSync2(AUTH_FILE);
|
|
2484
2665
|
}
|
|
2485
2666
|
function isLoggedIn() {
|
|
2486
|
-
return
|
|
2667
|
+
return existsSync6(AUTH_FILE);
|
|
2487
2668
|
}
|
|
2488
2669
|
var AUTH_DIR, AUTH_FILE;
|
|
2489
2670
|
var init_token_store = __esm(() => {
|
|
2490
|
-
AUTH_DIR =
|
|
2491
|
-
AUTH_FILE =
|
|
2671
|
+
AUTH_DIR = join5(homedir7(), ".failproofai");
|
|
2672
|
+
AUTH_FILE = join5(AUTH_DIR, "auth.json");
|
|
2492
2673
|
});
|
|
2493
2674
|
|
|
2494
2675
|
// src/relay/queue.ts
|
|
@@ -2503,17 +2684,17 @@ __export(exports_queue, {
|
|
|
2503
2684
|
});
|
|
2504
2685
|
import {
|
|
2505
2686
|
appendFileSync as appendFileSync3,
|
|
2506
|
-
mkdirSync as
|
|
2507
|
-
existsSync as
|
|
2508
|
-
readFileSync as
|
|
2687
|
+
mkdirSync as mkdirSync6,
|
|
2688
|
+
existsSync as existsSync7,
|
|
2689
|
+
readFileSync as readFileSync5,
|
|
2509
2690
|
statSync as statSync4,
|
|
2510
2691
|
renameSync as renameSync4,
|
|
2511
2692
|
unlinkSync as unlinkSync3,
|
|
2512
|
-
readdirSync as
|
|
2693
|
+
readdirSync as readdirSync4,
|
|
2513
2694
|
chmodSync
|
|
2514
2695
|
} from "node:fs";
|
|
2515
|
-
import { join as
|
|
2516
|
-
import { homedir as
|
|
2696
|
+
import { join as join6 } from "node:path";
|
|
2697
|
+
import { homedir as homedir8 } from "node:os";
|
|
2517
2698
|
import { createHash, randomUUID } from "node:crypto";
|
|
2518
2699
|
function hashCwd(cwd) {
|
|
2519
2700
|
if (!cwd)
|
|
@@ -2543,8 +2724,8 @@ function sanitize(entry) {
|
|
|
2543
2724
|
};
|
|
2544
2725
|
}
|
|
2545
2726
|
function ensureDir2() {
|
|
2546
|
-
if (!
|
|
2547
|
-
|
|
2727
|
+
if (!existsSync7(QUEUE_DIR)) {
|
|
2728
|
+
mkdirSync6(QUEUE_DIR, { recursive: true, mode: 448 });
|
|
2548
2729
|
}
|
|
2549
2730
|
}
|
|
2550
2731
|
function appendToServerQueue(entry) {
|
|
@@ -2552,7 +2733,7 @@ function appendToServerQueue(entry) {
|
|
|
2552
2733
|
return;
|
|
2553
2734
|
ensureDir2();
|
|
2554
2735
|
try {
|
|
2555
|
-
if (
|
|
2736
|
+
if (existsSync7(PENDING_FILE) && statSync4(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
2556
2737
|
return;
|
|
2557
2738
|
}
|
|
2558
2739
|
} catch {}
|
|
@@ -2571,7 +2752,7 @@ function queueSizeBytes() {
|
|
|
2571
2752
|
}
|
|
2572
2753
|
}
|
|
2573
2754
|
function claimPendingBatch() {
|
|
2574
|
-
if (!
|
|
2755
|
+
if (!existsSync7(PENDING_FILE))
|
|
2575
2756
|
return null;
|
|
2576
2757
|
try {
|
|
2577
2758
|
const size = statSync4(PENDING_FILE).size;
|
|
@@ -2581,7 +2762,7 @@ function claimPendingBatch() {
|
|
|
2581
2762
|
return null;
|
|
2582
2763
|
}
|
|
2583
2764
|
const seq = `${Date.now()}-${process.pid}`;
|
|
2584
|
-
const processingFile =
|
|
2765
|
+
const processingFile = join6(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
|
|
2585
2766
|
try {
|
|
2586
2767
|
renameSync4(PENDING_FILE, processingFile);
|
|
2587
2768
|
try {
|
|
@@ -2598,15 +2779,15 @@ function claimPendingBatch() {
|
|
|
2598
2779
|
function findOrphanProcessingFiles() {
|
|
2599
2780
|
ensureDir2();
|
|
2600
2781
|
try {
|
|
2601
|
-
return
|
|
2782
|
+
return readdirSync4(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) => join6(QUEUE_DIR, n)).sort();
|
|
2602
2783
|
} catch {
|
|
2603
2784
|
return [];
|
|
2604
2785
|
}
|
|
2605
2786
|
}
|
|
2606
2787
|
function readProcessingFile(path2) {
|
|
2607
|
-
if (!
|
|
2788
|
+
if (!existsSync7(path2))
|
|
2608
2789
|
return [];
|
|
2609
|
-
const content =
|
|
2790
|
+
const content = readFileSync5(path2, "utf8");
|
|
2610
2791
|
const out = [];
|
|
2611
2792
|
for (const line of content.split(`
|
|
2612
2793
|
`)) {
|
|
@@ -2627,20 +2808,20 @@ function deleteProcessingFile(path2) {
|
|
|
2627
2808
|
var QUEUE_DIR, PENDING_FILE, PROCESSING_PREFIX = "processing-", MAX_QUEUE_BYTES;
|
|
2628
2809
|
var init_queue = __esm(() => {
|
|
2629
2810
|
init_token_store();
|
|
2630
|
-
QUEUE_DIR =
|
|
2631
|
-
PENDING_FILE =
|
|
2811
|
+
QUEUE_DIR = join6(homedir8(), ".failproofai", "cache", "server-queue");
|
|
2812
|
+
PENDING_FILE = join6(QUEUE_DIR, "pending.jsonl");
|
|
2632
2813
|
MAX_QUEUE_BYTES = 50 * 1024 * 1024;
|
|
2633
2814
|
});
|
|
2634
2815
|
|
|
2635
2816
|
// src/relay/pid.ts
|
|
2636
|
-
import { readFileSync as
|
|
2637
|
-
import { join as
|
|
2638
|
-
import { homedir as
|
|
2817
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, unlinkSync as unlinkSync4, mkdirSync as mkdirSync7 } from "node:fs";
|
|
2818
|
+
import { join as join7, dirname as dirname4 } from "node:path";
|
|
2819
|
+
import { homedir as homedir9 } from "node:os";
|
|
2639
2820
|
function readPid() {
|
|
2640
|
-
if (!
|
|
2821
|
+
if (!existsSync8(PID_FILE))
|
|
2641
2822
|
return null;
|
|
2642
2823
|
try {
|
|
2643
|
-
const raw =
|
|
2824
|
+
const raw = readFileSync6(PID_FILE, "utf8").trim();
|
|
2644
2825
|
const pid = parseInt(raw, 10);
|
|
2645
2826
|
if (Number.isNaN(pid) || pid <= 0)
|
|
2646
2827
|
return null;
|
|
@@ -2650,13 +2831,13 @@ function readPid() {
|
|
|
2650
2831
|
}
|
|
2651
2832
|
}
|
|
2652
2833
|
function writePid(pid) {
|
|
2653
|
-
const dir =
|
|
2654
|
-
if (!
|
|
2655
|
-
|
|
2656
|
-
|
|
2834
|
+
const dir = dirname4(PID_FILE);
|
|
2835
|
+
if (!existsSync8(dir))
|
|
2836
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2837
|
+
writeFileSync5(PID_FILE, String(pid));
|
|
2657
2838
|
}
|
|
2658
2839
|
function clearPid() {
|
|
2659
|
-
if (
|
|
2840
|
+
if (existsSync8(PID_FILE))
|
|
2660
2841
|
unlinkSync4(PID_FILE);
|
|
2661
2842
|
}
|
|
2662
2843
|
function isProcessAlive(pid) {
|
|
@@ -2688,7 +2869,7 @@ function stopRelay() {
|
|
|
2688
2869
|
}
|
|
2689
2870
|
var PID_FILE;
|
|
2690
2871
|
var init_pid = __esm(() => {
|
|
2691
|
-
PID_FILE =
|
|
2872
|
+
PID_FILE = join7(homedir9(), ".failproofai", "relay.pid");
|
|
2692
2873
|
});
|
|
2693
2874
|
|
|
2694
2875
|
// src/relay/daemon.ts
|
|
@@ -2700,9 +2881,9 @@ __export(exports_daemon, {
|
|
|
2700
2881
|
ensureRelayRunning: () => ensureRelayRunning
|
|
2701
2882
|
});
|
|
2702
2883
|
import { spawn } from "node:child_process";
|
|
2703
|
-
import { existsSync as
|
|
2704
|
-
import { join as
|
|
2705
|
-
import { homedir as
|
|
2884
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2885
|
+
import { join as join8 } from "node:path";
|
|
2886
|
+
import { homedir as homedir10 } from "node:os";
|
|
2706
2887
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2707
2888
|
function ensureRelayRunning() {
|
|
2708
2889
|
if (!isLoggedIn())
|
|
@@ -2919,7 +3100,7 @@ async function runDaemon() {
|
|
|
2919
3100
|
}
|
|
2920
3101
|
relay.close();
|
|
2921
3102
|
} catch {}
|
|
2922
|
-
if (
|
|
3103
|
+
if (existsSync9(QUEUE_DIR2)) {}
|
|
2923
3104
|
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
2924
3105
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
2925
3106
|
}
|
|
@@ -2969,7 +3150,7 @@ var init_daemon = __esm(() => {
|
|
|
2969
3150
|
init_token_store();
|
|
2970
3151
|
init_pid();
|
|
2971
3152
|
init_queue();
|
|
2972
|
-
QUEUE_DIR2 =
|
|
3153
|
+
QUEUE_DIR2 = join8(homedir10(), ".failproofai", "cache", "server-queue");
|
|
2973
3154
|
});
|
|
2974
3155
|
|
|
2975
3156
|
// src/hooks/handler.ts
|
|
@@ -2977,7 +3158,15 @@ var exports_handler = {};
|
|
|
2977
3158
|
__export(exports_handler, {
|
|
2978
3159
|
handleHookEvent: () => handleHookEvent
|
|
2979
3160
|
});
|
|
2980
|
-
|
|
3161
|
+
function canonicalizeEventType(raw, cli) {
|
|
3162
|
+
if (cli === "codex") {
|
|
3163
|
+
const mapped = CODEX_EVENT_MAP[raw];
|
|
3164
|
+
if (mapped)
|
|
3165
|
+
return mapped;
|
|
3166
|
+
}
|
|
3167
|
+
return raw;
|
|
3168
|
+
}
|
|
3169
|
+
async function handleHookEvent(eventType, cli = "claude") {
|
|
2981
3170
|
const startTime = performance.now();
|
|
2982
3171
|
const MAX_STDIN_BYTES = 1048576;
|
|
2983
3172
|
let payload = "";
|
|
@@ -3012,12 +3201,15 @@ async function handleHookEvent(eventType) {
|
|
|
3012
3201
|
hookLogWarn(`payload parse failed for ${eventType} (${payload.length} bytes)`);
|
|
3013
3202
|
}
|
|
3014
3203
|
}
|
|
3204
|
+
const canonicalEventType = canonicalizeEventType(eventType, cli);
|
|
3205
|
+
const sessionId = parsed.session_id;
|
|
3015
3206
|
const session = {
|
|
3016
|
-
sessionId
|
|
3207
|
+
sessionId,
|
|
3017
3208
|
transcriptPath: parsed.transcript_path,
|
|
3018
3209
|
cwd: parsed.cwd,
|
|
3019
|
-
permissionMode: parsed
|
|
3020
|
-
hookEventName: parsed.hook_event_name
|
|
3210
|
+
permissionMode: resolvePermissionMode(cli, parsed, sessionId),
|
|
3211
|
+
hookEventName: parsed.hook_event_name,
|
|
3212
|
+
cli
|
|
3021
3213
|
};
|
|
3022
3214
|
const config = readMergedHooksConfig(session.cwd);
|
|
3023
3215
|
clearPolicies();
|
|
@@ -3045,6 +3237,7 @@ async function handleHookEvent(eventType) {
|
|
|
3045
3237
|
hook_name: hookName,
|
|
3046
3238
|
error_type: isTimeout ? "timeout" : "exception",
|
|
3047
3239
|
event_type: eventType,
|
|
3240
|
+
cli,
|
|
3048
3241
|
is_convention_policy: isConvention,
|
|
3049
3242
|
convention_scope: conventionScope ?? null
|
|
3050
3243
|
});
|
|
@@ -3055,6 +3248,7 @@ async function handleHookEvent(eventType) {
|
|
|
3055
3248
|
}
|
|
3056
3249
|
if (customHooksList.length > 0) {
|
|
3057
3250
|
trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
|
|
3251
|
+
cli,
|
|
3058
3252
|
custom_hooks_count: customHooksList.length,
|
|
3059
3253
|
custom_hook_names: customHooksList.map((h) => h.name),
|
|
3060
3254
|
event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
|
|
@@ -3062,15 +3256,16 @@ async function handleHookEvent(eventType) {
|
|
|
3062
3256
|
}
|
|
3063
3257
|
if (loadResult.conventionSources.length > 0) {
|
|
3064
3258
|
trackHookEvent(getInstanceId(), "convention_policies_loaded", {
|
|
3065
|
-
event_type:
|
|
3259
|
+
event_type: canonicalEventType,
|
|
3260
|
+
cli,
|
|
3066
3261
|
project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
|
|
3067
3262
|
user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
|
|
3068
3263
|
convention_hook_count: conventionHookNames.size,
|
|
3069
3264
|
convention_hook_names: [...conventionHookNames]
|
|
3070
3265
|
});
|
|
3071
3266
|
}
|
|
3072
|
-
hookLogInfo(`event=${
|
|
3073
|
-
const result = await evaluatePolicies(
|
|
3267
|
+
hookLogInfo(`event=${canonicalEventType} cli=${cli} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
|
|
3268
|
+
const result = await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
3074
3269
|
const durationMs = Math.round(performance.now() - startTime);
|
|
3075
3270
|
hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
|
|
3076
3271
|
if (result.stdout) {
|
|
@@ -3081,7 +3276,8 @@ async function handleHookEvent(eventType) {
|
|
|
3081
3276
|
}
|
|
3082
3277
|
const activityEntry = {
|
|
3083
3278
|
timestamp: Date.now(),
|
|
3084
|
-
eventType,
|
|
3279
|
+
eventType: canonicalEventType,
|
|
3280
|
+
integration: cli,
|
|
3085
3281
|
toolName: parsed.tool_name ?? null,
|
|
3086
3282
|
policyName: result.policyName,
|
|
3087
3283
|
policyNames: result.policyNames,
|
|
@@ -3116,7 +3312,8 @@ async function handleHookEvent(eventType) {
|
|
|
3116
3312
|
const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
|
|
3117
3313
|
const distinctId = getInstanceId();
|
|
3118
3314
|
await trackHookEvent(distinctId, "hook_policy_triggered", {
|
|
3119
|
-
event_type:
|
|
3315
|
+
event_type: canonicalEventType,
|
|
3316
|
+
cli,
|
|
3120
3317
|
tool_name: parsed.tool_name ?? null,
|
|
3121
3318
|
policy_name: result.policyName,
|
|
3122
3319
|
decision: result.decision,
|
|
@@ -3131,12 +3328,14 @@ async function handleHookEvent(eventType) {
|
|
|
3131
3328
|
return result.exitCode;
|
|
3132
3329
|
}
|
|
3133
3330
|
var init_handler = __esm(() => {
|
|
3331
|
+
init_types();
|
|
3134
3332
|
init_hooks_config();
|
|
3135
3333
|
init_builtin_policies();
|
|
3136
3334
|
init_policy_evaluator();
|
|
3137
3335
|
init_custom_hooks_loader();
|
|
3138
3336
|
init_hook_activity_store();
|
|
3139
3337
|
init_hook_telemetry();
|
|
3338
|
+
init_resolve_permission_mode();
|
|
3140
3339
|
init_telemetry_id();
|
|
3141
3340
|
init_hook_logger();
|
|
3142
3341
|
});
|
|
@@ -3150,9 +3349,9 @@ __export(exports_daemon2, {
|
|
|
3150
3349
|
ensureRelayRunning: () => ensureRelayRunning2
|
|
3151
3350
|
});
|
|
3152
3351
|
import { spawn as spawn2 } from "node:child_process";
|
|
3153
|
-
import { existsSync as
|
|
3154
|
-
import { join as
|
|
3155
|
-
import { homedir as
|
|
3352
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
3353
|
+
import { join as join9 } from "node:path";
|
|
3354
|
+
import { homedir as homedir11 } from "node:os";
|
|
3156
3355
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3157
3356
|
function ensureRelayRunning2() {
|
|
3158
3357
|
if (!isLoggedIn())
|
|
@@ -3369,7 +3568,7 @@ async function runDaemon2() {
|
|
|
3369
3568
|
}
|
|
3370
3569
|
relay.close();
|
|
3371
3570
|
} catch {}
|
|
3372
|
-
if (
|
|
3571
|
+
if (existsSync10(QUEUE_DIR3)) {}
|
|
3373
3572
|
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
3374
3573
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS2);
|
|
3375
3574
|
}
|
|
@@ -3419,41 +3618,279 @@ var init_daemon2 = __esm(() => {
|
|
|
3419
3618
|
init_token_store();
|
|
3420
3619
|
init_pid();
|
|
3421
3620
|
init_queue();
|
|
3422
|
-
QUEUE_DIR3 =
|
|
3621
|
+
QUEUE_DIR3 = join9(homedir11(), ".failproofai", "cache", "server-queue");
|
|
3423
3622
|
});
|
|
3424
3623
|
|
|
3425
|
-
// src/hooks/
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
"
|
|
3450
|
-
"
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3624
|
+
// src/hooks/integrations.ts
|
|
3625
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
3626
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3627
|
+
import { resolve as resolve5, dirname as dirname5 } from "node:path";
|
|
3628
|
+
import { homedir as homedir12 } from "node:os";
|
|
3629
|
+
function readJsonFile(path2) {
|
|
3630
|
+
if (!existsSync11(path2))
|
|
3631
|
+
return {};
|
|
3632
|
+
const raw = readFileSync7(path2, "utf8");
|
|
3633
|
+
return JSON.parse(raw);
|
|
3634
|
+
}
|
|
3635
|
+
function writeJsonFile(path2, data) {
|
|
3636
|
+
mkdirSync8(dirname5(path2), { recursive: true });
|
|
3637
|
+
writeFileSync6(path2, JSON.stringify(data, null, 2) + `
|
|
3638
|
+
`, "utf8");
|
|
3639
|
+
}
|
|
3640
|
+
function isMarkedHook(hook) {
|
|
3641
|
+
if (hook[FAILPROOFAI_HOOK_MARKER] === true)
|
|
3642
|
+
return true;
|
|
3643
|
+
const cmd = typeof hook.command === "string" ? hook.command : "";
|
|
3644
|
+
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
3645
|
+
}
|
|
3646
|
+
function binaryExists(name) {
|
|
3647
|
+
try {
|
|
3648
|
+
const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
|
|
3649
|
+
execSync3(cmd, { encoding: "utf8", stdio: "pipe" });
|
|
3650
|
+
return true;
|
|
3651
|
+
} catch {
|
|
3652
|
+
return false;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
function getIntegration(id) {
|
|
3656
|
+
const integration = INTEGRATIONS[id];
|
|
3657
|
+
if (!integration) {
|
|
3658
|
+
throw new Error(`Unknown integration: ${id}. Valid: ${INTEGRATION_TYPES.join(", ")}`);
|
|
3659
|
+
}
|
|
3660
|
+
return integration;
|
|
3661
|
+
}
|
|
3662
|
+
function detectInstalledClis() {
|
|
3663
|
+
return INTEGRATION_TYPES.filter((id) => INTEGRATIONS[id].detectInstalled());
|
|
3664
|
+
}
|
|
3665
|
+
var claudeCode, codex, INTEGRATIONS;
|
|
3666
|
+
var init_integrations = __esm(() => {
|
|
3667
|
+
init_types();
|
|
3668
|
+
claudeCode = {
|
|
3669
|
+
id: "claude",
|
|
3670
|
+
displayName: "Claude Code",
|
|
3671
|
+
scopes: HOOK_SCOPES,
|
|
3672
|
+
eventTypes: HOOK_EVENT_TYPES,
|
|
3673
|
+
getSettingsPath(scope, cwd) {
|
|
3674
|
+
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
3675
|
+
switch (scope) {
|
|
3676
|
+
case "user":
|
|
3677
|
+
return resolve5(homedir12(), ".claude", "settings.json");
|
|
3678
|
+
case "project":
|
|
3679
|
+
return resolve5(base, ".claude", "settings.json");
|
|
3680
|
+
case "local":
|
|
3681
|
+
return resolve5(base, ".claude", "settings.local.json");
|
|
3682
|
+
}
|
|
3683
|
+
},
|
|
3684
|
+
readSettings(settingsPath) {
|
|
3685
|
+
return readJsonFile(settingsPath);
|
|
3686
|
+
},
|
|
3687
|
+
writeSettings(settingsPath, settings) {
|
|
3688
|
+
writeJsonFile(settingsPath, settings);
|
|
3689
|
+
},
|
|
3690
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
3691
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
|
|
3692
|
+
return {
|
|
3693
|
+
type: "command",
|
|
3694
|
+
command,
|
|
3695
|
+
timeout: 60000,
|
|
3696
|
+
[FAILPROOFAI_HOOK_MARKER]: true
|
|
3697
|
+
};
|
|
3698
|
+
},
|
|
3699
|
+
isFailproofaiHook: isMarkedHook,
|
|
3700
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
3701
|
+
const s = settings;
|
|
3702
|
+
if (!s.hooks)
|
|
3703
|
+
s.hooks = {};
|
|
3704
|
+
for (const eventType of HOOK_EVENT_TYPES) {
|
|
3705
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
3706
|
+
if (!s.hooks[eventType])
|
|
3707
|
+
s.hooks[eventType] = [];
|
|
3708
|
+
const matchers = s.hooks[eventType];
|
|
3709
|
+
let found = false;
|
|
3710
|
+
for (const matcher of matchers) {
|
|
3711
|
+
if (!matcher.hooks)
|
|
3712
|
+
continue;
|
|
3713
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h));
|
|
3714
|
+
if (idx >= 0) {
|
|
3715
|
+
matcher.hooks[idx] = hookEntry;
|
|
3716
|
+
found = true;
|
|
3717
|
+
break;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
if (!found)
|
|
3721
|
+
matchers.push({ hooks: [hookEntry] });
|
|
3722
|
+
}
|
|
3723
|
+
},
|
|
3724
|
+
removeHooksFromFile(settingsPath) {
|
|
3725
|
+
const settings = this.readSettings(settingsPath);
|
|
3726
|
+
if (!settings.hooks)
|
|
3727
|
+
return 0;
|
|
3728
|
+
let removed = 0;
|
|
3729
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
3730
|
+
const matchers = settings.hooks[eventType];
|
|
3731
|
+
if (!Array.isArray(matchers))
|
|
3732
|
+
continue;
|
|
3733
|
+
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
3734
|
+
const matcher = matchers[i];
|
|
3735
|
+
if (!matcher.hooks)
|
|
3736
|
+
continue;
|
|
3737
|
+
const before = matcher.hooks.length;
|
|
3738
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h));
|
|
3739
|
+
removed += before - matcher.hooks.length;
|
|
3740
|
+
if (matcher.hooks.length === 0)
|
|
3741
|
+
matchers.splice(i, 1);
|
|
3742
|
+
}
|
|
3743
|
+
if (matchers.length === 0)
|
|
3744
|
+
delete settings.hooks[eventType];
|
|
3745
|
+
}
|
|
3746
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
3747
|
+
delete settings.hooks;
|
|
3748
|
+
this.writeSettings(settingsPath, settings);
|
|
3749
|
+
return removed;
|
|
3750
|
+
},
|
|
3751
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
3752
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
3753
|
+
if (!existsSync11(settingsPath))
|
|
3754
|
+
return false;
|
|
3755
|
+
try {
|
|
3756
|
+
const settings = this.readSettings(settingsPath);
|
|
3757
|
+
if (!settings.hooks)
|
|
3758
|
+
return false;
|
|
3759
|
+
for (const matchers of Object.values(settings.hooks)) {
|
|
3760
|
+
if (!Array.isArray(matchers))
|
|
3761
|
+
continue;
|
|
3762
|
+
for (const matcher of matchers) {
|
|
3763
|
+
if (!matcher.hooks)
|
|
3764
|
+
continue;
|
|
3765
|
+
if (matcher.hooks.some((h) => isMarkedHook(h)))
|
|
3766
|
+
return true;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
} catch {}
|
|
3770
|
+
return false;
|
|
3771
|
+
},
|
|
3772
|
+
detectInstalled() {
|
|
3773
|
+
return binaryExists("claude") || binaryExists("claude-code");
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
codex = {
|
|
3777
|
+
id: "codex",
|
|
3778
|
+
displayName: "OpenAI Codex",
|
|
3779
|
+
scopes: CODEX_HOOK_SCOPES,
|
|
3780
|
+
eventTypes: CODEX_HOOK_EVENT_TYPES,
|
|
3781
|
+
getSettingsPath(scope, cwd) {
|
|
3782
|
+
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
3783
|
+
switch (scope) {
|
|
3784
|
+
case "user":
|
|
3785
|
+
return resolve5(homedir12(), ".codex", "hooks.json");
|
|
3786
|
+
case "project":
|
|
3787
|
+
return resolve5(base, ".codex", "hooks.json");
|
|
3788
|
+
case "local":
|
|
3789
|
+
return resolve5(base, ".codex", "hooks.json");
|
|
3790
|
+
}
|
|
3791
|
+
},
|
|
3792
|
+
readSettings(settingsPath) {
|
|
3793
|
+
const raw = readJsonFile(settingsPath);
|
|
3794
|
+
if (raw.version === undefined)
|
|
3795
|
+
raw.version = 1;
|
|
3796
|
+
return raw;
|
|
3797
|
+
},
|
|
3798
|
+
writeSettings(settingsPath, settings) {
|
|
3799
|
+
writeJsonFile(settingsPath, settings);
|
|
3800
|
+
},
|
|
3801
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
3802
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli codex` : `"${binaryPath}" --hook ${eventType} --cli codex`;
|
|
3803
|
+
return {
|
|
3804
|
+
type: "command",
|
|
3805
|
+
command,
|
|
3806
|
+
timeout: 60000,
|
|
3807
|
+
[FAILPROOFAI_HOOK_MARKER]: true
|
|
3808
|
+
};
|
|
3809
|
+
},
|
|
3810
|
+
isFailproofaiHook: isMarkedHook,
|
|
3811
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
3812
|
+
const s = settings;
|
|
3813
|
+
if (s.version === undefined)
|
|
3814
|
+
s.version = 1;
|
|
3815
|
+
if (!s.hooks)
|
|
3816
|
+
s.hooks = {};
|
|
3817
|
+
for (const eventType of CODEX_HOOK_EVENT_TYPES) {
|
|
3818
|
+
const pascalKey = CODEX_EVENT_MAP[eventType];
|
|
3819
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
3820
|
+
if (!s.hooks[pascalKey])
|
|
3821
|
+
s.hooks[pascalKey] = [];
|
|
3822
|
+
const matchers = s.hooks[pascalKey];
|
|
3823
|
+
let found = false;
|
|
3824
|
+
for (const matcher of matchers) {
|
|
3825
|
+
if (!matcher.hooks)
|
|
3826
|
+
continue;
|
|
3827
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h));
|
|
3828
|
+
if (idx >= 0) {
|
|
3829
|
+
matcher.hooks[idx] = hookEntry;
|
|
3830
|
+
found = true;
|
|
3831
|
+
break;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
if (!found)
|
|
3835
|
+
matchers.push({ hooks: [hookEntry] });
|
|
3836
|
+
}
|
|
3837
|
+
},
|
|
3838
|
+
removeHooksFromFile(settingsPath) {
|
|
3839
|
+
const settings = this.readSettings(settingsPath);
|
|
3840
|
+
if (!settings.hooks)
|
|
3841
|
+
return 0;
|
|
3842
|
+
let removed = 0;
|
|
3843
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
3844
|
+
const matchers = settings.hooks[eventType];
|
|
3845
|
+
if (!Array.isArray(matchers))
|
|
3846
|
+
continue;
|
|
3847
|
+
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
3848
|
+
const matcher = matchers[i];
|
|
3849
|
+
if (!matcher.hooks)
|
|
3850
|
+
continue;
|
|
3851
|
+
const before = matcher.hooks.length;
|
|
3852
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h));
|
|
3853
|
+
removed += before - matcher.hooks.length;
|
|
3854
|
+
if (matcher.hooks.length === 0)
|
|
3855
|
+
matchers.splice(i, 1);
|
|
3856
|
+
}
|
|
3857
|
+
if (matchers.length === 0)
|
|
3858
|
+
delete settings.hooks[eventType];
|
|
3859
|
+
}
|
|
3860
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
3861
|
+
delete settings.hooks;
|
|
3862
|
+
this.writeSettings(settingsPath, settings);
|
|
3863
|
+
return removed;
|
|
3864
|
+
},
|
|
3865
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
3866
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
3867
|
+
if (!existsSync11(settingsPath))
|
|
3868
|
+
return false;
|
|
3869
|
+
try {
|
|
3870
|
+
const settings = this.readSettings(settingsPath);
|
|
3871
|
+
if (!settings.hooks)
|
|
3872
|
+
return false;
|
|
3873
|
+
for (const matchers of Object.values(settings.hooks)) {
|
|
3874
|
+
if (!Array.isArray(matchers))
|
|
3875
|
+
continue;
|
|
3876
|
+
for (const matcher of matchers) {
|
|
3877
|
+
if (!matcher.hooks)
|
|
3878
|
+
continue;
|
|
3879
|
+
if (matcher.hooks.some((h) => isMarkedHook(h)))
|
|
3880
|
+
return true;
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
} catch {}
|
|
3884
|
+
return false;
|
|
3885
|
+
},
|
|
3886
|
+
detectInstalled() {
|
|
3887
|
+
return binaryExists("codex");
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
INTEGRATIONS = {
|
|
3891
|
+
claude: claudeCode,
|
|
3892
|
+
codex
|
|
3893
|
+
};
|
|
3457
3894
|
});
|
|
3458
3895
|
|
|
3459
3896
|
// src/hooks/install-prompt.ts
|
|
@@ -3644,7 +4081,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3644
4081
|
process.stdout.write(output);
|
|
3645
4082
|
lastLineCount = lines.length;
|
|
3646
4083
|
}
|
|
3647
|
-
return new Promise((
|
|
4084
|
+
return new Promise((resolve6) => {
|
|
3648
4085
|
render();
|
|
3649
4086
|
process.stdin.setRawMode(true);
|
|
3650
4087
|
process.stdin.resume();
|
|
@@ -3686,7 +4123,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3686
4123
|
const selected = items.filter((i) => i.selected).map((i) => i.name);
|
|
3687
4124
|
process.stdout.write(`
|
|
3688
4125
|
`);
|
|
3689
|
-
|
|
4126
|
+
resolve6(selected);
|
|
3690
4127
|
} else if (key.name === "backspace" || key.name === "delete") {
|
|
3691
4128
|
if (search.length > 0) {
|
|
3692
4129
|
search = search.slice(0, -1);
|
|
@@ -3710,6 +4147,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3710
4147
|
}
|
|
3711
4148
|
var init_install_prompt = __esm(() => {
|
|
3712
4149
|
init_builtin_policies();
|
|
4150
|
+
init_integrations();
|
|
3713
4151
|
});
|
|
3714
4152
|
|
|
3715
4153
|
// src/cli-error.ts
|
|
@@ -3734,20 +4172,12 @@ __export(exports_manager, {
|
|
|
3734
4172
|
hooksInstalledInSettings: () => hooksInstalledInSettings,
|
|
3735
4173
|
getSettingsPath: () => getSettingsPath
|
|
3736
4174
|
});
|
|
3737
|
-
import { execSync as
|
|
3738
|
-
import {
|
|
3739
|
-
import { resolve as
|
|
3740
|
-
import { homedir as
|
|
4175
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
4176
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
4177
|
+
import { resolve as resolve6, basename as basename2 } from "node:path";
|
|
4178
|
+
import { homedir as homedir13, platform, arch, release, hostname } from "node:os";
|
|
3741
4179
|
function getSettingsPath(scope, cwd) {
|
|
3742
|
-
|
|
3743
|
-
switch (scope) {
|
|
3744
|
-
case "user":
|
|
3745
|
-
return resolve5(homedir11(), ".claude", "settings.json");
|
|
3746
|
-
case "project":
|
|
3747
|
-
return resolve5(base, ".claude", "settings.json");
|
|
3748
|
-
case "local":
|
|
3749
|
-
return resolve5(base, ".claude", "settings.local.json");
|
|
3750
|
-
}
|
|
4180
|
+
return claudeCode.getSettingsPath(scope, cwd);
|
|
3751
4181
|
}
|
|
3752
4182
|
function scopeLabel(scope) {
|
|
3753
4183
|
switch (scope) {
|
|
@@ -3759,22 +4189,13 @@ function scopeLabel(scope) {
|
|
|
3759
4189
|
return `{cwd}/.claude/settings.local.json`;
|
|
3760
4190
|
}
|
|
3761
4191
|
}
|
|
3762
|
-
function readSettings(settingsPath) {
|
|
3763
|
-
if (!existsSync10(settingsPath)) {
|
|
3764
|
-
return {};
|
|
3765
|
-
}
|
|
3766
|
-
const raw = readFileSync6(settingsPath, "utf8");
|
|
3767
|
-
return JSON.parse(raw);
|
|
3768
|
-
}
|
|
3769
|
-
function writeSettings(settingsPath, settings) {
|
|
3770
|
-
mkdirSync7(dirname4(settingsPath), { recursive: true });
|
|
3771
|
-
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
3772
|
-
`, "utf8");
|
|
3773
|
-
}
|
|
3774
4192
|
function resolveFailproofaiBinary() {
|
|
4193
|
+
const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
|
|
4194
|
+
if (override && override.trim())
|
|
4195
|
+
return override.trim();
|
|
3775
4196
|
try {
|
|
3776
4197
|
const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
|
|
3777
|
-
const result =
|
|
4198
|
+
const result = execSync4(cmd, { encoding: "utf8" }).trim();
|
|
3778
4199
|
return result.split(`
|
|
3779
4200
|
`)[0].trim();
|
|
3780
4201
|
} catch {
|
|
@@ -3782,12 +4203,6 @@ function resolveFailproofaiBinary() {
|
|
|
3782
4203
|
` + "Install it globally first: npm install -g failproofai");
|
|
3783
4204
|
}
|
|
3784
4205
|
}
|
|
3785
|
-
function isFailproofaiHook(hook) {
|
|
3786
|
-
if (hook[FAILPROOFAI_HOOK_MARKER] === true)
|
|
3787
|
-
return true;
|
|
3788
|
-
const cmd = typeof hook.command === "string" ? hook.command : "";
|
|
3789
|
-
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
3790
|
-
}
|
|
3791
4206
|
function validatePolicyNames(names) {
|
|
3792
4207
|
const invalid = names.filter((n) => !VALID_POLICY_NAMES.has(n));
|
|
3793
4208
|
if (invalid.length > 0) {
|
|
@@ -3807,58 +4222,9 @@ function deduplicateScopes(scopes, cwd) {
|
|
|
3807
4222
|
});
|
|
3808
4223
|
}
|
|
3809
4224
|
function hooksInstalledInSettings(scope, cwd) {
|
|
3810
|
-
|
|
3811
|
-
if (!existsSync10(settingsPath))
|
|
3812
|
-
return false;
|
|
3813
|
-
try {
|
|
3814
|
-
const settings = readSettings(settingsPath);
|
|
3815
|
-
if (!settings.hooks)
|
|
3816
|
-
return false;
|
|
3817
|
-
for (const matchers of Object.values(settings.hooks)) {
|
|
3818
|
-
if (!Array.isArray(matchers))
|
|
3819
|
-
continue;
|
|
3820
|
-
for (const matcher of matchers) {
|
|
3821
|
-
if (!matcher.hooks)
|
|
3822
|
-
continue;
|
|
3823
|
-
if (matcher.hooks.some((h) => isFailproofaiHook(h))) {
|
|
3824
|
-
return true;
|
|
3825
|
-
}
|
|
3826
|
-
}
|
|
3827
|
-
}
|
|
3828
|
-
} catch {}
|
|
3829
|
-
return false;
|
|
3830
|
-
}
|
|
3831
|
-
function removeHooksFromSettingsFile(settingsPath) {
|
|
3832
|
-
const settings = readSettings(settingsPath);
|
|
3833
|
-
if (!settings.hooks)
|
|
3834
|
-
return 0;
|
|
3835
|
-
let removed = 0;
|
|
3836
|
-
for (const eventType of Object.keys(settings.hooks)) {
|
|
3837
|
-
const matchers = settings.hooks[eventType];
|
|
3838
|
-
if (!Array.isArray(matchers))
|
|
3839
|
-
continue;
|
|
3840
|
-
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
3841
|
-
const matcher = matchers[i];
|
|
3842
|
-
if (!matcher.hooks)
|
|
3843
|
-
continue;
|
|
3844
|
-
const before = matcher.hooks.length;
|
|
3845
|
-
matcher.hooks = matcher.hooks.filter((h) => !isFailproofaiHook(h));
|
|
3846
|
-
removed += before - matcher.hooks.length;
|
|
3847
|
-
if (matcher.hooks.length === 0) {
|
|
3848
|
-
matchers.splice(i, 1);
|
|
3849
|
-
}
|
|
3850
|
-
}
|
|
3851
|
-
if (matchers.length === 0) {
|
|
3852
|
-
delete settings.hooks[eventType];
|
|
3853
|
-
}
|
|
3854
|
-
}
|
|
3855
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
3856
|
-
delete settings.hooks;
|
|
3857
|
-
}
|
|
3858
|
-
writeSettings(settingsPath, settings);
|
|
3859
|
-
return removed;
|
|
4225
|
+
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
3860
4226
|
}
|
|
3861
|
-
async function installHooks(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false) {
|
|
4227
|
+
async function installHooks(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli) {
|
|
3862
4228
|
if (policyNames !== undefined && policyNames.length > 0) {
|
|
3863
4229
|
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
3864
4230
|
if (nonAllNames.length > 0)
|
|
@@ -3868,6 +4234,13 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
3868
4234
|
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
3869
4235
|
}
|
|
3870
4236
|
}
|
|
4237
|
+
const selectedClis = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
|
|
4238
|
+
for (const cliId of selectedClis) {
|
|
4239
|
+
const integration = getIntegration(cliId);
|
|
4240
|
+
if (!integration.scopes.includes(scope)) {
|
|
4241
|
+
throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
3871
4244
|
const binaryPath = resolveFailproofaiBinary();
|
|
3872
4245
|
const previousConfig = readScopedHooksConfig(scope, cwd);
|
|
3873
4246
|
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
@@ -3888,7 +4261,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
3888
4261
|
if (removeCustomHooks) {
|
|
3889
4262
|
delete configToWrite.customPoliciesPath;
|
|
3890
4263
|
} else if (customPoliciesPath) {
|
|
3891
|
-
configToWrite.customPoliciesPath =
|
|
4264
|
+
configToWrite.customPoliciesPath = resolve6(customPoliciesPath);
|
|
3892
4265
|
let validatedHooks = [];
|
|
3893
4266
|
try {
|
|
3894
4267
|
validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
|
|
@@ -3911,39 +4284,15 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3911
4284
|
} else if (configToWrite.customPoliciesPath) {
|
|
3912
4285
|
console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
|
|
3913
4286
|
}
|
|
3914
|
-
const
|
|
3915
|
-
const
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
type: "command",
|
|
3923
|
-
command,
|
|
3924
|
-
timeout: 60000,
|
|
3925
|
-
[FAILPROOFAI_HOOK_MARKER]: true
|
|
3926
|
-
};
|
|
3927
|
-
if (!settings.hooks[eventType]) {
|
|
3928
|
-
settings.hooks[eventType] = [];
|
|
3929
|
-
}
|
|
3930
|
-
const matchers = settings.hooks[eventType];
|
|
3931
|
-
let found = false;
|
|
3932
|
-
for (const matcher of matchers) {
|
|
3933
|
-
if (!matcher.hooks)
|
|
3934
|
-
continue;
|
|
3935
|
-
const failproofaiIdx = matcher.hooks.findIndex((h) => isFailproofaiHook(h));
|
|
3936
|
-
if (failproofaiIdx >= 0) {
|
|
3937
|
-
matcher.hooks[failproofaiIdx] = hookEntry;
|
|
3938
|
-
found = true;
|
|
3939
|
-
break;
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
3942
|
-
if (!found) {
|
|
3943
|
-
matchers.push({ hooks: [hookEntry] });
|
|
3944
|
-
}
|
|
4287
|
+
const writtenSettingsPaths = [];
|
|
4288
|
+
for (const cliId of selectedClis) {
|
|
4289
|
+
const integration = getIntegration(cliId);
|
|
4290
|
+
const settingsPath = integration.getSettingsPath(scope, cwd);
|
|
4291
|
+
const settings = integration.readSettings(settingsPath);
|
|
4292
|
+
integration.writeHookEntries(settings, binaryPath, scope);
|
|
4293
|
+
integration.writeSettings(settingsPath, settings);
|
|
4294
|
+
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
3945
4295
|
}
|
|
3946
|
-
writeSettings(settingsPath, settings);
|
|
3947
4296
|
try {
|
|
3948
4297
|
const newSet = new Set(selectedPolicies);
|
|
3949
4298
|
const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
|
|
@@ -3951,6 +4300,8 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3951
4300
|
const distinctId = getInstanceId();
|
|
3952
4301
|
await trackHookEvent(distinctId, "hooks_installed", {
|
|
3953
4302
|
scope,
|
|
4303
|
+
cli: selectedClis,
|
|
4304
|
+
cli_count: selectedClis.length,
|
|
3954
4305
|
policies: selectedPolicies,
|
|
3955
4306
|
policy_count: selectedPolicies.length,
|
|
3956
4307
|
policies_added: policiesAdded,
|
|
@@ -3966,8 +4317,11 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3966
4317
|
command_format: scope === "project" ? "npx" : "absolute"
|
|
3967
4318
|
});
|
|
3968
4319
|
} catch {}
|
|
3969
|
-
|
|
3970
|
-
|
|
4320
|
+
for (const { cli: cliId, path: path2 } of writtenSettingsPaths) {
|
|
4321
|
+
const integration = getIntegration(cliId);
|
|
4322
|
+
console.log(`Failproof AI hooks installed for ${integration.displayName} ` + `(${integration.eventTypes.length} event types, scope: ${scope}).`);
|
|
4323
|
+
console.log(`Settings: ${path2}`);
|
|
4324
|
+
}
|
|
3971
4325
|
if (scope === "project") {
|
|
3972
4326
|
console.log(`Command: npx -y failproofai`);
|
|
3973
4327
|
console.log(`
|
|
@@ -3988,6 +4342,7 @@ This file can be committed to git — no machine-specific paths.`);
|
|
|
3988
4342
|
}
|
|
3989
4343
|
async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
3990
4344
|
const configScope = scope === "all" ? "user" : scope;
|
|
4345
|
+
const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
|
|
3991
4346
|
if (opts?.removeCustomHooks) {
|
|
3992
4347
|
const config = readScopedHooksConfig(configScope, cwd);
|
|
3993
4348
|
delete config.customPoliciesPath;
|
|
@@ -4016,6 +4371,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
4016
4371
|
const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
|
|
4017
4372
|
await trackHookEvent(distinctId, "hooks_removed", {
|
|
4018
4373
|
scope,
|
|
4374
|
+
cli: selectedClis,
|
|
4019
4375
|
removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
|
|
4020
4376
|
beta_only: opts?.betaOnly ?? false,
|
|
4021
4377
|
policies_removed: actuallyRemoved,
|
|
@@ -4032,42 +4388,49 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
4032
4388
|
return;
|
|
4033
4389
|
}
|
|
4034
4390
|
const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
|
|
4035
|
-
const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
|
|
4036
4391
|
let totalRemoved = 0;
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4392
|
+
let nothingToReport = false;
|
|
4393
|
+
for (const cliId of selectedClis) {
|
|
4394
|
+
const integration = getIntegration(cliId);
|
|
4395
|
+
const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
|
|
4396
|
+
for (const s of scopesToRemove) {
|
|
4397
|
+
const settingsPath = integration.getSettingsPath(s, cwd);
|
|
4398
|
+
if (!existsSync12(settingsPath)) {
|
|
4399
|
+
if (scope !== "all" && selectedClis.length === 1) {
|
|
4400
|
+
console.log("No settings file found. Nothing to remove.");
|
|
4401
|
+
nothingToReport = true;
|
|
4402
|
+
}
|
|
4403
|
+
continue;
|
|
4043
4404
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
const settings = readSettings(settingsPath);
|
|
4047
|
-
if (!settings.hooks) {
|
|
4048
|
-
if (scope !== "all") {
|
|
4405
|
+
const removed = integration.removeHooksFromFile(settingsPath);
|
|
4406
|
+
if (removed === 0 && scope !== "all" && selectedClis.length === 1) {
|
|
4049
4407
|
console.log("No hooks found in settings. Nothing to remove.");
|
|
4050
|
-
|
|
4408
|
+
nothingToReport = true;
|
|
4409
|
+
continue;
|
|
4410
|
+
}
|
|
4411
|
+
totalRemoved += removed;
|
|
4412
|
+
if (scope !== "all") {
|
|
4413
|
+
console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
|
|
4414
|
+
console.log(`Settings: ${settingsPath}`);
|
|
4051
4415
|
}
|
|
4052
|
-
continue;
|
|
4053
|
-
}
|
|
4054
|
-
const removed = removeHooksFromSettingsFile(settingsPath);
|
|
4055
|
-
totalRemoved += removed;
|
|
4056
|
-
if (scope !== "all") {
|
|
4057
|
-
console.log(`Removed ${removed} failproofai hook(s) from settings.`);
|
|
4058
|
-
console.log(`Settings: ${settingsPath}`);
|
|
4059
4416
|
}
|
|
4060
4417
|
}
|
|
4418
|
+
if (nothingToReport && totalRemoved === 0)
|
|
4419
|
+
return;
|
|
4061
4420
|
if (scope === "all") {
|
|
4062
4421
|
console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
|
|
4063
|
-
for (const
|
|
4064
|
-
|
|
4422
|
+
for (const cliId of selectedClis) {
|
|
4423
|
+
const integration = getIntegration(cliId);
|
|
4424
|
+
for (const s of integration.scopes) {
|
|
4425
|
+
console.log(` ${integration.displayName} / ${s}: ${integration.getSettingsPath(s, cwd)}`);
|
|
4426
|
+
}
|
|
4065
4427
|
}
|
|
4066
4428
|
}
|
|
4067
4429
|
try {
|
|
4068
4430
|
const distinctId = getInstanceId();
|
|
4069
4431
|
await trackHookEvent(distinctId, "hooks_removed", {
|
|
4070
4432
|
scope,
|
|
4433
|
+
cli: selectedClis,
|
|
4071
4434
|
removal_mode: "hooks",
|
|
4072
4435
|
policies_removed: configBeforeRemoval.enabledPolicies,
|
|
4073
4436
|
removed_count: totalRemoved,
|
|
@@ -4211,7 +4574,7 @@ Failproof AI Hook Policies
|
|
|
4211
4574
|
if (config.customPoliciesPath) {
|
|
4212
4575
|
console.log(`
|
|
4213
4576
|
── Custom Policies (${config.customPoliciesPath}) ───────────────────────`);
|
|
4214
|
-
if (!
|
|
4577
|
+
if (!existsSync12(config.customPoliciesPath)) {
|
|
4215
4578
|
console.log(` \x1B[31m✗ File not found: ${config.customPoliciesPath}\x1B[0m`);
|
|
4216
4579
|
} else {
|
|
4217
4580
|
const hooks = await loadCustomHooks(config.customPoliciesPath);
|
|
@@ -4226,10 +4589,10 @@ Failproof AI Hook Policies
|
|
|
4226
4589
|
}
|
|
4227
4590
|
console.log();
|
|
4228
4591
|
}
|
|
4229
|
-
const base = cwd ?
|
|
4592
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
4230
4593
|
const conventionDirs = [
|
|
4231
|
-
{ label: "Project", dir:
|
|
4232
|
-
{ label: "User", dir:
|
|
4594
|
+
{ label: "Project", dir: resolve6(base, ".failproofai", "policies") },
|
|
4595
|
+
{ label: "User", dir: resolve6(homedir13(), ".failproofai", "policies") }
|
|
4233
4596
|
];
|
|
4234
4597
|
for (const { label, dir } of conventionDirs) {
|
|
4235
4598
|
const files = discoverPolicyFiles(dir);
|
|
@@ -4259,6 +4622,7 @@ Failproof AI Hook Policies
|
|
|
4259
4622
|
var VALID_POLICY_NAMES;
|
|
4260
4623
|
var init_manager = __esm(() => {
|
|
4261
4624
|
init_types();
|
|
4625
|
+
init_integrations();
|
|
4262
4626
|
init_install_prompt();
|
|
4263
4627
|
init_hooks_config();
|
|
4264
4628
|
init_builtin_policies();
|
|
@@ -4269,6 +4633,316 @@ var init_manager = __esm(() => {
|
|
|
4269
4633
|
VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
4270
4634
|
});
|
|
4271
4635
|
|
|
4636
|
+
// src/hooks/install-prompt.ts
|
|
4637
|
+
var exports_install_prompt = {};
|
|
4638
|
+
__export(exports_install_prompt, {
|
|
4639
|
+
resolveTargetClis: () => resolveTargetClis,
|
|
4640
|
+
promptPolicySelection: () => promptPolicySelection2
|
|
4641
|
+
});
|
|
4642
|
+
import * as readline2 from "node:readline";
|
|
4643
|
+
async function resolveTargetClis(explicit) {
|
|
4644
|
+
if (explicit && explicit.length > 0)
|
|
4645
|
+
return [...new Set(explicit)];
|
|
4646
|
+
const detected = detectInstalledClis();
|
|
4647
|
+
if (detected.length === 0) {
|
|
4648
|
+
console.log("\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex). " + "Defaulting to Claude Code; hooks will activate when an agent is installed.\x1B[0m");
|
|
4649
|
+
return ["claude"];
|
|
4650
|
+
}
|
|
4651
|
+
if (detected.length === 1) {
|
|
4652
|
+
const integration = getIntegration(detected[0]);
|
|
4653
|
+
console.log(`Detected ${integration.displayName}; installing hooks for it.`);
|
|
4654
|
+
return detected;
|
|
4655
|
+
}
|
|
4656
|
+
if (!process.stdin.isTTY)
|
|
4657
|
+
return detected;
|
|
4658
|
+
const labels = detected.map((id) => getIntegration(id).displayName).join(" + ");
|
|
4659
|
+
process.stdout.write(`Detected ${labels}. Install for [B]oth (default), [C]laude Code only, or co[D]ex only? `);
|
|
4660
|
+
return new Promise((resolve7) => {
|
|
4661
|
+
readline2.emitKeypressEvents(process.stdin);
|
|
4662
|
+
const wasRaw = process.stdin.isRaw;
|
|
4663
|
+
if (process.stdin.setRawMode)
|
|
4664
|
+
process.stdin.setRawMode(true);
|
|
4665
|
+
const restore = () => {
|
|
4666
|
+
if (process.stdin.setRawMode)
|
|
4667
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
4668
|
+
process.stdin.removeListener("keypress", onKey);
|
|
4669
|
+
};
|
|
4670
|
+
const onKey = (str, key) => {
|
|
4671
|
+
if (key && key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
4672
|
+
restore();
|
|
4673
|
+
process.stdout.write(`
|
|
4674
|
+
`);
|
|
4675
|
+
process.exit(130);
|
|
4676
|
+
}
|
|
4677
|
+
const ch = (str || "").toLowerCase();
|
|
4678
|
+
restore();
|
|
4679
|
+
process.stdout.write(`
|
|
4680
|
+
`);
|
|
4681
|
+
if (ch === "c")
|
|
4682
|
+
resolve7(["claude"]);
|
|
4683
|
+
else if (ch === "d")
|
|
4684
|
+
resolve7(["codex"]);
|
|
4685
|
+
else
|
|
4686
|
+
resolve7(detected);
|
|
4687
|
+
};
|
|
4688
|
+
process.stdin.on("keypress", onKey);
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
async function promptPolicySelection2(preSelected, options = {}) {
|
|
4692
|
+
const { includeBeta = false } = options;
|
|
4693
|
+
if (!process.stdin.isTTY) {
|
|
4694
|
+
const available = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta);
|
|
4695
|
+
if (preSelected)
|
|
4696
|
+
return preSelected.filter((name) => available.some((p) => p.name === name));
|
|
4697
|
+
return available.filter((p) => p.defaultEnabled).map((p) => p.name);
|
|
4698
|
+
}
|
|
4699
|
+
const preSelectedSet = preSelected ? new Set(preSelected) : null;
|
|
4700
|
+
const items = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => ({
|
|
4701
|
+
name: p.name,
|
|
4702
|
+
description: p.description,
|
|
4703
|
+
category: p.category,
|
|
4704
|
+
selected: preSelectedSet ? preSelectedSet.has(p.name) : p.defaultEnabled,
|
|
4705
|
+
beta: !!p.beta
|
|
4706
|
+
}));
|
|
4707
|
+
const total = items.length;
|
|
4708
|
+
const WINDOW_SIZE = 8;
|
|
4709
|
+
let cursor = 0;
|
|
4710
|
+
let search = "";
|
|
4711
|
+
let lastLineCount = 0;
|
|
4712
|
+
let cursorHidden = false;
|
|
4713
|
+
function hideCursor() {
|
|
4714
|
+
if (!cursorHidden) {
|
|
4715
|
+
process.stdout.write("\x1B[?25l");
|
|
4716
|
+
cursorHidden = true;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
function showCursor() {
|
|
4720
|
+
if (cursorHidden) {
|
|
4721
|
+
process.stdout.write("\x1B[?25h");
|
|
4722
|
+
cursorHidden = false;
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
function truncateLine(line, width) {
|
|
4726
|
+
let visual = 0;
|
|
4727
|
+
let result = "";
|
|
4728
|
+
let i = 0;
|
|
4729
|
+
while (i < line.length) {
|
|
4730
|
+
if (line[i] === "\x1B" && line[i + 1] === "[") {
|
|
4731
|
+
let j = i + 2;
|
|
4732
|
+
while (j < line.length && !/[A-Za-z]/.test(line[j]))
|
|
4733
|
+
j++;
|
|
4734
|
+
j++;
|
|
4735
|
+
result += line.slice(i, j);
|
|
4736
|
+
i = j;
|
|
4737
|
+
} else {
|
|
4738
|
+
if (visual >= width)
|
|
4739
|
+
break;
|
|
4740
|
+
result += line[i];
|
|
4741
|
+
visual++;
|
|
4742
|
+
i++;
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
return result;
|
|
4746
|
+
}
|
|
4747
|
+
function getFiltered() {
|
|
4748
|
+
if (!search)
|
|
4749
|
+
return items;
|
|
4750
|
+
const q = search.toLowerCase();
|
|
4751
|
+
return items.filter((i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
|
|
4752
|
+
}
|
|
4753
|
+
function buildDisplayRows(filtered) {
|
|
4754
|
+
const categoryOrder = [];
|
|
4755
|
+
const categoryEnabledCount = new Map;
|
|
4756
|
+
const categoryTotalCount = new Map;
|
|
4757
|
+
for (const p of items) {
|
|
4758
|
+
if (!categoryEnabledCount.has(p.category)) {
|
|
4759
|
+
categoryOrder.push(p.category);
|
|
4760
|
+
categoryEnabledCount.set(p.category, 0);
|
|
4761
|
+
categoryTotalCount.set(p.category, 0);
|
|
4762
|
+
}
|
|
4763
|
+
categoryTotalCount.set(p.category, categoryTotalCount.get(p.category) + 1);
|
|
4764
|
+
if (p.selected)
|
|
4765
|
+
categoryEnabledCount.set(p.category, categoryEnabledCount.get(p.category) + 1);
|
|
4766
|
+
}
|
|
4767
|
+
const filteredByCategory = new Map;
|
|
4768
|
+
for (const item of filtered) {
|
|
4769
|
+
const bucket = filteredByCategory.get(item.category) ?? [];
|
|
4770
|
+
bucket.push(item);
|
|
4771
|
+
filteredByCategory.set(item.category, bucket);
|
|
4772
|
+
}
|
|
4773
|
+
const rows = [];
|
|
4774
|
+
let idx = 0;
|
|
4775
|
+
for (const cat of categoryOrder) {
|
|
4776
|
+
const catFiltered = filteredByCategory.get(cat);
|
|
4777
|
+
if (!catFiltered || catFiltered.length === 0)
|
|
4778
|
+
continue;
|
|
4779
|
+
rows.push({ kind: "header", category: cat, enabledCount: categoryEnabledCount.get(cat), totalCount: categoryTotalCount.get(cat) });
|
|
4780
|
+
for (const item of catFiltered) {
|
|
4781
|
+
rows.push({ kind: "item", item, filteredIndex: idx++ });
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
return rows;
|
|
4785
|
+
}
|
|
4786
|
+
function render() {
|
|
4787
|
+
const cols = process.stdout.columns || 120;
|
|
4788
|
+
hideCursor();
|
|
4789
|
+
const filtered = getFiltered();
|
|
4790
|
+
const shown = filtered.length;
|
|
4791
|
+
if (shown > 0 && cursor >= shown)
|
|
4792
|
+
cursor = shown - 1;
|
|
4793
|
+
const lines = [];
|
|
4794
|
+
lines.push(" Failproof AI — Policy Manager");
|
|
4795
|
+
lines.push("");
|
|
4796
|
+
const innerWidth = Math.max(20, cols - 6);
|
|
4797
|
+
const topBorder = " ┌" + "─".repeat(innerWidth + 2) + "┐";
|
|
4798
|
+
const botBorder = " └" + "─".repeat(innerWidth + 2) + "┘";
|
|
4799
|
+
const cursorChar = "\x1B[7m \x1B[0m";
|
|
4800
|
+
const countPart = search ? ` \x1B[2m(${shown}/${total})\x1B[0m` : ` \x1B[2m(${total} policies)\x1B[0m`;
|
|
4801
|
+
const searchContent = `\x1B[1mSearch:\x1B[0m ${search}${cursorChar}${countPart}`;
|
|
4802
|
+
lines.push(topBorder);
|
|
4803
|
+
lines.push(` │ ${searchContent}`);
|
|
4804
|
+
lines.push(botBorder);
|
|
4805
|
+
lines.push("");
|
|
4806
|
+
if (shown === 0) {
|
|
4807
|
+
lines.push(" \x1B[2mNo policies match “" + search + "”\x1B[0m");
|
|
4808
|
+
for (let i = 0;i < WINDOW_SIZE + 1; i++)
|
|
4809
|
+
lines.push("");
|
|
4810
|
+
} else {
|
|
4811
|
+
const displayRows = buildDisplayRows(filtered);
|
|
4812
|
+
let cursorDisplayRow = 0;
|
|
4813
|
+
for (let i = 0;i < displayRows.length; i++) {
|
|
4814
|
+
const row = displayRows[i];
|
|
4815
|
+
if (row.kind === "item" && row.filteredIndex === cursor) {
|
|
4816
|
+
cursorDisplayRow = i;
|
|
4817
|
+
break;
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
|
|
4821
|
+
windowStart = Math.max(0, windowStart);
|
|
4822
|
+
windowStart = Math.min(windowStart, Math.max(0, displayRows.length - WINDOW_SIZE));
|
|
4823
|
+
const windowEnd = Math.min(displayRows.length, windowStart + WINDOW_SIZE);
|
|
4824
|
+
const aboveItems = displayRows.slice(0, windowStart).filter((r) => r.kind === "item").length;
|
|
4825
|
+
if (aboveItems > 0) {
|
|
4826
|
+
lines.push(` \x1B[2m ↑ ${aboveItems} more above\x1B[0m`);
|
|
4827
|
+
} else {
|
|
4828
|
+
lines.push("");
|
|
4829
|
+
}
|
|
4830
|
+
for (let i = windowStart;i < windowEnd; i++) {
|
|
4831
|
+
const row = displayRows[i];
|
|
4832
|
+
if (row.kind === "header") {
|
|
4833
|
+
const label = ` ${row.category.toUpperCase()} (${row.enabledCount}/${row.totalCount}) `;
|
|
4834
|
+
const prefix = "── ";
|
|
4835
|
+
const prefixLen = 3 + label.length;
|
|
4836
|
+
const dashLen = Math.max(2, cols - 2 - prefixLen);
|
|
4837
|
+
lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
|
|
4838
|
+
} else {
|
|
4839
|
+
const item = row.item;
|
|
4840
|
+
const isActive = row.filteredIndex === cursor;
|
|
4841
|
+
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
4842
|
+
const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
|
|
4843
|
+
const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
|
|
4844
|
+
const betaPart = item.beta ? " \x1B[35m[beta]\x1B[0m" : "";
|
|
4845
|
+
const pad = " ".repeat(Math.max(1, 28 - item.name.length));
|
|
4846
|
+
const desc = `\x1B[2m${item.description}\x1B[0m`;
|
|
4847
|
+
lines.push(` ${pointer} ${check} ${namePart}${betaPart}${pad}${desc}`);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++) {
|
|
4851
|
+
lines.push("");
|
|
4852
|
+
}
|
|
4853
|
+
const belowItems = displayRows.slice(windowEnd).filter((r) => r.kind === "item").length;
|
|
4854
|
+
if (belowItems > 0) {
|
|
4855
|
+
lines.push(` \x1B[2m ↓ ${belowItems} more below\x1B[0m`);
|
|
4856
|
+
} else {
|
|
4857
|
+
lines.push("");
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
lines.push("");
|
|
4861
|
+
lines.push(" \x1B[2m" + "─".repeat(cols - 2) + "\x1B[0m");
|
|
4862
|
+
lines.push(" [↑↓] Move [Space] Toggle [Ctrl+A] All [Ctrl+S] Save [Esc] Clear [^C] Quit");
|
|
4863
|
+
lines.push("");
|
|
4864
|
+
lines.push(" \x1B[2mTip: `policies` for a flat list · `policies --install <name…>` to skip prompt\x1B[0m");
|
|
4865
|
+
if (!includeBeta) {
|
|
4866
|
+
lines.push(" \x1B[2mTip: `policies --install --beta` to include beta policies\x1B[0m");
|
|
4867
|
+
}
|
|
4868
|
+
if (lastLineCount > 0) {
|
|
4869
|
+
process.stdout.write(`\x1B[${lastLineCount}A\x1B[J`);
|
|
4870
|
+
}
|
|
4871
|
+
const output = lines.map((l) => l === "" ? l : truncateLine(l, cols)).join(`
|
|
4872
|
+
`) + `
|
|
4873
|
+
`;
|
|
4874
|
+
process.stdout.write(output);
|
|
4875
|
+
lastLineCount = lines.length;
|
|
4876
|
+
}
|
|
4877
|
+
return new Promise((resolve7) => {
|
|
4878
|
+
render();
|
|
4879
|
+
process.stdin.setRawMode(true);
|
|
4880
|
+
process.stdin.resume();
|
|
4881
|
+
readline2.emitKeypressEvents(process.stdin);
|
|
4882
|
+
function keypressHandler(_str, key) {
|
|
4883
|
+
if (!key)
|
|
4884
|
+
return;
|
|
4885
|
+
if (key.ctrl && key.name === "c") {
|
|
4886
|
+
cleanup();
|
|
4887
|
+
process.exit(0);
|
|
4888
|
+
}
|
|
4889
|
+
const filtered = getFiltered();
|
|
4890
|
+
if (key.name === "up") {
|
|
4891
|
+
if (filtered.length > 0) {
|
|
4892
|
+
cursor = cursor > 0 ? cursor - 1 : filtered.length - 1;
|
|
4893
|
+
}
|
|
4894
|
+
render();
|
|
4895
|
+
} else if (key.name === "down") {
|
|
4896
|
+
if (filtered.length > 0) {
|
|
4897
|
+
cursor = cursor < filtered.length - 1 ? cursor + 1 : 0;
|
|
4898
|
+
}
|
|
4899
|
+
render();
|
|
4900
|
+
} else if (key.name === "return" || key.name === "space") {
|
|
4901
|
+
const item = filtered[cursor];
|
|
4902
|
+
if (item)
|
|
4903
|
+
item.selected = !item.selected;
|
|
4904
|
+
render();
|
|
4905
|
+
} else if (key.name === "escape") {
|
|
4906
|
+
search = "";
|
|
4907
|
+
cursor = 0;
|
|
4908
|
+
render();
|
|
4909
|
+
} else if (key.ctrl && key.name === "a") {
|
|
4910
|
+
const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
|
|
4911
|
+
for (const item of filtered)
|
|
4912
|
+
item.selected = !allSelected;
|
|
4913
|
+
render();
|
|
4914
|
+
} else if (key.ctrl && key.name === "s") {
|
|
4915
|
+
cleanup();
|
|
4916
|
+
const selected = items.filter((i) => i.selected).map((i) => i.name);
|
|
4917
|
+
process.stdout.write(`
|
|
4918
|
+
`);
|
|
4919
|
+
resolve7(selected);
|
|
4920
|
+
} else if (key.name === "backspace" || key.name === "delete") {
|
|
4921
|
+
if (search.length > 0) {
|
|
4922
|
+
search = search.slice(0, -1);
|
|
4923
|
+
cursor = 0;
|
|
4924
|
+
render();
|
|
4925
|
+
}
|
|
4926
|
+
} else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
|
|
4927
|
+
search += _str;
|
|
4928
|
+
cursor = 0;
|
|
4929
|
+
render();
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
function cleanup() {
|
|
4933
|
+
showCursor();
|
|
4934
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
4935
|
+
process.stdin.setRawMode(false);
|
|
4936
|
+
process.stdin.pause();
|
|
4937
|
+
}
|
|
4938
|
+
process.stdin.on("keypress", keypressHandler);
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
var init_install_prompt2 = __esm(() => {
|
|
4942
|
+
init_builtin_policies();
|
|
4943
|
+
init_integrations();
|
|
4944
|
+
});
|
|
4945
|
+
|
|
4272
4946
|
// src/auth/login.ts
|
|
4273
4947
|
var exports_login = {};
|
|
4274
4948
|
__export(exports_login, {
|
|
@@ -4404,14 +5078,14 @@ __export(exports_pid, {
|
|
|
4404
5078
|
isProcessAlive: () => isProcessAlive2,
|
|
4405
5079
|
clearPid: () => clearPid2
|
|
4406
5080
|
});
|
|
4407
|
-
import { readFileSync as
|
|
4408
|
-
import { join as
|
|
4409
|
-
import { homedir as
|
|
5081
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync13, unlinkSync as unlinkSync5, mkdirSync as mkdirSync9 } from "node:fs";
|
|
5082
|
+
import { join as join10, dirname as dirname6 } from "node:path";
|
|
5083
|
+
import { homedir as homedir14 } from "node:os";
|
|
4410
5084
|
function readPid2() {
|
|
4411
|
-
if (!
|
|
5085
|
+
if (!existsSync13(PID_FILE2))
|
|
4412
5086
|
return null;
|
|
4413
5087
|
try {
|
|
4414
|
-
const raw =
|
|
5088
|
+
const raw = readFileSync8(PID_FILE2, "utf8").trim();
|
|
4415
5089
|
const pid = parseInt(raw, 10);
|
|
4416
5090
|
if (Number.isNaN(pid) || pid <= 0)
|
|
4417
5091
|
return null;
|
|
@@ -4421,13 +5095,13 @@ function readPid2() {
|
|
|
4421
5095
|
}
|
|
4422
5096
|
}
|
|
4423
5097
|
function writePid2(pid) {
|
|
4424
|
-
const dir =
|
|
4425
|
-
if (!
|
|
4426
|
-
|
|
4427
|
-
|
|
5098
|
+
const dir = dirname6(PID_FILE2);
|
|
5099
|
+
if (!existsSync13(dir))
|
|
5100
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5101
|
+
writeFileSync7(PID_FILE2, String(pid));
|
|
4428
5102
|
}
|
|
4429
5103
|
function clearPid2() {
|
|
4430
|
-
if (
|
|
5104
|
+
if (existsSync13(PID_FILE2))
|
|
4431
5105
|
unlinkSync5(PID_FILE2);
|
|
4432
5106
|
}
|
|
4433
5107
|
function isProcessAlive2(pid) {
|
|
@@ -4465,26 +5139,26 @@ function relayStatus() {
|
|
|
4465
5139
|
}
|
|
4466
5140
|
var PID_FILE2;
|
|
4467
5141
|
var init_pid2 = __esm(() => {
|
|
4468
|
-
PID_FILE2 =
|
|
5142
|
+
PID_FILE2 = join10(homedir14(), ".failproofai", "relay.pid");
|
|
4469
5143
|
});
|
|
4470
5144
|
|
|
4471
5145
|
// lib/paths.ts
|
|
4472
|
-
import { homedir as
|
|
4473
|
-
import { join as
|
|
5146
|
+
import { homedir as homedir15 } from "os";
|
|
5147
|
+
import { join as join11 } from "path";
|
|
4474
5148
|
function getDefaultClaudeProjectsPath() {
|
|
4475
|
-
return
|
|
5149
|
+
return join11(homedir15(), ".claude", "projects");
|
|
4476
5150
|
}
|
|
4477
5151
|
var init_paths = () => {};
|
|
4478
5152
|
|
|
4479
5153
|
// scripts/parse-script-args.ts
|
|
4480
|
-
import { resolve as
|
|
5154
|
+
import { resolve as resolve7 } from "path";
|
|
4481
5155
|
function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
|
|
4482
5156
|
const raw = inlineValue ?? args[index + 1];
|
|
4483
5157
|
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
4484
5158
|
console.error(`Error: ${flagName} requires ${errorLabel}`);
|
|
4485
5159
|
process.exit(1);
|
|
4486
5160
|
}
|
|
4487
|
-
const value = options?.resolve ?
|
|
5161
|
+
const value = options?.resolve ? resolve7(raw) : raw;
|
|
4488
5162
|
return { value, spliceCount: inlineValue !== null ? 1 : 2 };
|
|
4489
5163
|
}
|
|
4490
5164
|
function parseScriptArgs(argv) {
|
|
@@ -4545,8 +5219,8 @@ __export(exports_launch, {
|
|
|
4545
5219
|
launch: () => launch
|
|
4546
5220
|
});
|
|
4547
5221
|
import { spawn as spawn4 } from "child_process";
|
|
4548
|
-
import { realpathSync, existsSync as
|
|
4549
|
-
import { resolve as
|
|
5222
|
+
import { realpathSync, existsSync as existsSync14 } from "node:fs";
|
|
5223
|
+
import { resolve as resolve8, dirname as dirname7 } from "node:path";
|
|
4550
5224
|
import { fileURLToPath } from "node:url";
|
|
4551
5225
|
function launch(mode) {
|
|
4552
5226
|
const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
@@ -4577,9 +5251,9 @@ function launch(mode) {
|
|
|
4577
5251
|
process.env.PORT = port;
|
|
4578
5252
|
process.env.HOSTNAME = "0.0.0.0";
|
|
4579
5253
|
cmd = "node";
|
|
4580
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
4581
|
-
const serverJsPath =
|
|
4582
|
-
if (!
|
|
5254
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve8(dirname7(realpathSync(fileURLToPath(import.meta.url))), "..");
|
|
5255
|
+
const serverJsPath = resolve8(packageRoot, ".next/standalone/server.js");
|
|
5256
|
+
if (!existsSync14(serverJsPath)) {
|
|
4583
5257
|
console.error(`
|
|
4584
5258
|
Error: Cannot find server.js at:
|
|
4585
5259
|
${serverJsPath}
|
|
@@ -4638,17 +5312,17 @@ var init_cli_error2 = __esm(() => {
|
|
|
4638
5312
|
|
|
4639
5313
|
// bin/failproofai.mjs
|
|
4640
5314
|
import { realpathSync as realpathSync2 } from "fs";
|
|
4641
|
-
import { dirname as
|
|
5315
|
+
import { dirname as dirname8, resolve as resolve9 } from "path";
|
|
4642
5316
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4643
5317
|
// package.json
|
|
4644
|
-
var version = "0.0.
|
|
5318
|
+
var version = "0.0.9-beta.0";
|
|
4645
5319
|
|
|
4646
5320
|
// bin/failproofai.mjs
|
|
4647
5321
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
4648
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
5322
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve9(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
4649
5323
|
}
|
|
4650
5324
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
4651
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
5325
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve9(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
|
|
4652
5326
|
}
|
|
4653
5327
|
var args = process.argv.slice(2);
|
|
4654
5328
|
if (args[0] === "p")
|
|
@@ -4657,12 +5331,16 @@ var hookIdx = args.indexOf("--hook");
|
|
|
4657
5331
|
if (hookIdx >= 0) {
|
|
4658
5332
|
if (!args[hookIdx + 1]) {
|
|
4659
5333
|
console.error("Error: Missing event type after --hook");
|
|
4660
|
-
console.error("Usage: failproofai --hook <event>
|
|
5334
|
+
console.error("Usage: failproofai --hook <event> [--cli <claude|codex>]");
|
|
4661
5335
|
process.exit(1);
|
|
4662
5336
|
}
|
|
5337
|
+
const eventType = args[hookIdx + 1];
|
|
5338
|
+
const cliIdx = args.indexOf("--cli");
|
|
5339
|
+
const cliArg = cliIdx >= 0 ? args[cliIdx + 1] : undefined;
|
|
5340
|
+
const cli = cliArg && (cliArg === "claude" || cliArg === "codex") ? cliArg : "claude";
|
|
4663
5341
|
try {
|
|
4664
5342
|
const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
|
|
4665
|
-
const exitCode = await handleHookEvent2(
|
|
5343
|
+
const exitCode = await handleHookEvent2(eventType, cli);
|
|
4666
5344
|
process.exit(exitCode);
|
|
4667
5345
|
} catch (err) {
|
|
4668
5346
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -4699,14 +5377,19 @@ COMMANDS
|
|
|
4699
5377
|
(no args) Launch the policy dashboard
|
|
4700
5378
|
|
|
4701
5379
|
policies, p List all available policies and their status
|
|
4702
|
-
policies --install, -i Enable policies in
|
|
5380
|
+
policies --install, -i Enable policies in agent CLI settings
|
|
4703
5381
|
[names...] Specific policy names to enable
|
|
5382
|
+
--cli claude|codex Agent CLI(s) to install for; space-separated
|
|
5383
|
+
(e.g. --cli claude codex) or repeated.
|
|
5384
|
+
Default: detect installed CLIs and prompt.
|
|
4704
5385
|
--scope user|project|local Config scope to write to (default: user)
|
|
5386
|
+
(Codex supports user|project only)
|
|
4705
5387
|
--beta Include beta policies
|
|
4706
5388
|
--custom, -c <path> Path to a JS file of custom policies
|
|
4707
5389
|
|
|
4708
5390
|
policies --uninstall, -u Disable policies or remove hooks
|
|
4709
5391
|
[names...] Specific policy names to disable
|
|
5392
|
+
--cli claude|codex Agent CLI(s) to uninstall from
|
|
4710
5393
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
4711
5394
|
--beta Remove only beta policies
|
|
4712
5395
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -4731,9 +5414,12 @@ EXAMPLES
|
|
|
4731
5414
|
failproofai policies
|
|
4732
5415
|
failproofai policies --install
|
|
4733
5416
|
failproofai policies --install block-sudo sanitize-api-keys --scope project
|
|
5417
|
+
failproofai policies --install --cli codex --scope project
|
|
5418
|
+
failproofai policies --install --cli claude codex
|
|
4734
5419
|
failproofai policies --install --custom ./my-policies.js
|
|
4735
5420
|
failproofai policies -i -c ./my-policies.js
|
|
4736
5421
|
failproofai policies --uninstall block-sudo
|
|
5422
|
+
failproofai policies --uninstall --cli codex
|
|
4737
5423
|
failproofai policies --uninstall --custom
|
|
4738
5424
|
|
|
4739
5425
|
LINKS
|
|
@@ -4767,13 +5453,19 @@ USAGE
|
|
|
4767
5453
|
|
|
4768
5454
|
OPTIONS (install)
|
|
4769
5455
|
[names...] Specific policy names to enable (omit for interactive)
|
|
5456
|
+
--cli claude|codex Agent CLI(s) to install for; space-separated
|
|
5457
|
+
(e.g. --cli claude codex) or repeated. Omit to
|
|
5458
|
+
detect installed CLIs and prompt (or auto-pick
|
|
5459
|
+
if only one is found).
|
|
4770
5460
|
--scope user|project|local Config scope to write to (default: user)
|
|
5461
|
+
(Codex supports user|project only)
|
|
4771
5462
|
--beta Include beta policies
|
|
4772
5463
|
--custom, -c <path> Path to a JS file of custom policies
|
|
4773
5464
|
(skips interactive prompt; validates file first)
|
|
4774
5465
|
|
|
4775
5466
|
OPTIONS (uninstall)
|
|
4776
5467
|
[names...] Specific policy names to disable (omit to remove hooks)
|
|
5468
|
+
--cli claude|codex Agent CLI(s) to uninstall from
|
|
4777
5469
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
4778
5470
|
--beta Remove only beta policies
|
|
4779
5471
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -4782,9 +5474,12 @@ EXAMPLES
|
|
|
4782
5474
|
failproofai policies
|
|
4783
5475
|
failproofai policies --install
|
|
4784
5476
|
failproofai policies --install block-sudo sanitize-api-keys
|
|
5477
|
+
failproofai policies --install --cli codex --scope project
|
|
5478
|
+
failproofai policies --install --cli claude codex
|
|
4785
5479
|
failproofai policies --install --custom ./my-policies.js
|
|
4786
5480
|
failproofai policies -i -c ./my-policies.js
|
|
4787
5481
|
failproofai policies --uninstall block-sudo
|
|
5482
|
+
failproofai policies --uninstall --cli codex
|
|
4788
5483
|
failproofai policies -u
|
|
4789
5484
|
failproofai policies --uninstall --custom
|
|
4790
5485
|
`.trimStart());
|
|
@@ -4792,6 +5487,7 @@ EXAMPLES
|
|
|
4792
5487
|
}
|
|
4793
5488
|
if (isInstall) {
|
|
4794
5489
|
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
5490
|
+
const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
|
|
4795
5491
|
const scopeIdx = subArgs.indexOf("--scope");
|
|
4796
5492
|
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
4797
5493
|
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
@@ -4806,13 +5502,35 @@ EXAMPLES
|
|
|
4806
5502
|
throw new CliError3(`Missing path after --custom/-c
|
|
4807
5503
|
Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
4808
5504
|
}
|
|
5505
|
+
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
5506
|
+
const cliFlagValues = [];
|
|
5507
|
+
const cliConsumedIdxs = new Set;
|
|
5508
|
+
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
5509
|
+
for (const idx of cliFlagIdxs) {
|
|
5510
|
+
let consumed = 0;
|
|
5511
|
+
for (let j = idx + 1;j < subArgs.length; j++) {
|
|
5512
|
+
const v = subArgs[j];
|
|
5513
|
+
if (v.startsWith("-"))
|
|
5514
|
+
break;
|
|
5515
|
+
if (!VALID_CLIS.has(v))
|
|
5516
|
+
break;
|
|
5517
|
+
cliFlagValues.push(v);
|
|
5518
|
+
cliConsumedIdxs.add(j);
|
|
5519
|
+
consumed++;
|
|
5520
|
+
}
|
|
5521
|
+
if (consumed === 0) {
|
|
5522
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
4809
5525
|
const includeBeta = subArgs.includes("--beta");
|
|
4810
5526
|
const consumedIdxs = new Set;
|
|
4811
5527
|
if (scopeIdx >= 0)
|
|
4812
5528
|
consumedIdxs.add(scopeIdx + 1);
|
|
4813
5529
|
if (customIdx >= 0)
|
|
4814
5530
|
consumedIdxs.add(customIdx + 1);
|
|
4815
|
-
|
|
5531
|
+
for (const i of cliConsumedIdxs)
|
|
5532
|
+
consumedIdxs.add(i);
|
|
5533
|
+
const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c", "--cli"]);
|
|
4816
5534
|
const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
4817
5535
|
if (unknownInstallFlag) {
|
|
4818
5536
|
throw new CliError3(`Unknown flag: ${unknownInstallFlag}
|
|
@@ -4820,11 +5538,13 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
4820
5538
|
}
|
|
4821
5539
|
const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
4822
5540
|
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
|
|
4823
|
-
await
|
|
5541
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
5542
|
+
await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath, false, cli);
|
|
4824
5543
|
process.exit(0);
|
|
4825
5544
|
}
|
|
4826
5545
|
if (isUninstall) {
|
|
4827
5546
|
const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
5547
|
+
const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
|
|
4828
5548
|
const scopeIdx = subArgs.indexOf("--scope");
|
|
4829
5549
|
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
4830
5550
|
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
@@ -4833,19 +5553,42 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
4833
5553
|
if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
|
|
4834
5554
|
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
|
|
4835
5555
|
}
|
|
5556
|
+
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
5557
|
+
const cliFlagValues = [];
|
|
5558
|
+
const cliConsumedIdxs = new Set;
|
|
5559
|
+
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
5560
|
+
for (const idx of cliFlagIdxs) {
|
|
5561
|
+
let consumed = 0;
|
|
5562
|
+
for (let j = idx + 1;j < subArgs.length; j++) {
|
|
5563
|
+
const v = subArgs[j];
|
|
5564
|
+
if (v.startsWith("-"))
|
|
5565
|
+
break;
|
|
5566
|
+
if (!VALID_CLIS.has(v))
|
|
5567
|
+
break;
|
|
5568
|
+
cliFlagValues.push(v);
|
|
5569
|
+
cliConsumedIdxs.add(j);
|
|
5570
|
+
consumed++;
|
|
5571
|
+
}
|
|
5572
|
+
if (consumed === 0) {
|
|
5573
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
4836
5576
|
const betaOnly = subArgs.includes("--beta");
|
|
4837
5577
|
const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
|
|
4838
5578
|
const consumedIdxs = new Set;
|
|
4839
5579
|
if (scopeIdx >= 0)
|
|
4840
5580
|
consumedIdxs.add(scopeIdx + 1);
|
|
4841
|
-
|
|
5581
|
+
for (const i of cliConsumedIdxs)
|
|
5582
|
+
consumedIdxs.add(i);
|
|
5583
|
+
const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c", "--cli"]);
|
|
4842
5584
|
const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
4843
5585
|
if (unknownUninstallFlag) {
|
|
4844
5586
|
throw new CliError3(`Unknown flag: ${unknownUninstallFlag}
|
|
4845
5587
|
Run \`failproofai policies --help\` for usage.`);
|
|
4846
5588
|
}
|
|
4847
5589
|
const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
4848
|
-
await
|
|
5590
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
5591
|
+
await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
|
|
4849
5592
|
process.exit(0);
|
|
4850
5593
|
}
|
|
4851
5594
|
const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
|