failproofai 0.0.8 → 0.0.9-beta.1
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 +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- 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/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- 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/build-manifest.json +3 -3
- 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/build-manifest.json +3 -3
- 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/build-manifest.json +3 -3
- 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]__0h3orxc._.js → [root-of-the-server]__0.f_cyx._.js} +2 -2
- 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]__0dub28-._.js +3 -0
- 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]__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 +6 -6
- 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/{01q52wg_amm60.js → 0_c_yox08g_44.js} +2 -2
- package/.next/standalone/.next/static/chunks/0bghqwo4iloy0.js +1 -0
- package/.next/standalone/.next/static/chunks/0fw2h.g66c0h3.js +1 -0
- package/.next/standalone/.next/static/chunks/0gu87mlr5ssnt.js +6 -0
- package/.next/standalone/.next/static/chunks/{0mbc8hyeqe2c4.js → 0igf3xbisp1lx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{175-vim0.ztb2.js → 0jryicwtm9z2g.js} +2 -2
- package/.next/standalone/.next/static/chunks/{12simlrcfk3g2.js → 0kzk5-mh1_x53.js} +1 -1
- package/.next/standalone/.next/static/chunks/0p5zh2diw90a1.js +1 -0
- package/.next/standalone/.next/static/chunks/{0eowehbf5egcz.js → 0ufq8smh~i7wc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0vlk_pv4somht.js → 0vwqucikost_q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0t3euwspxi_zg.js → 0w1f.k~gi-y6..js} +1 -1
- package/.next/standalone/.next/static/chunks/{151bdxm9n-pry.js → 0z-jh701rc~j8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-0o7k.hakttp4k.js → turbopack-0s36is87fc9r2.js} +1 -1
- 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 +1123 -281
- package/package.json +2 -2
- package/src/hooks/builtin-policies.ts +29 -6
- package/src/hooks/handler.ts +39 -10
- package/src/hooks/hook-activity-store.ts +2 -0
- package/src/hooks/install-prompt.ts +165 -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]__0_rr1ty._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dj-tbi._.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/096~b1zwv69ph.js +0 -1
- package/.next/standalone/.next/static/chunks/0bkir2pd22ski.js +0 -1
- package/.next/standalone/.next/static/chunks/0ksdlt_1hucdm.js +0 -1
- package/.next/standalone/.next/static/chunks/0lua3p__elu_..js +0 -6
- package/.next/standalone/.next/static/chunks/0mir9jdxn35~s.css +0 -1
- package/.next/standalone/.next/static/chunks/0s_18.dox44e9.js +0 -1
- /package/.next/standalone/.next/static/{RYld7TSCDXm2_WhJq20rD → CiVeb_yiVt-O2JYrzGzB7}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{RYld7TSCDXm2_WhJq20rD → CiVeb_yiVt-O2JYrzGzB7}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{RYld7TSCDXm2_WhJq20rD → CiVeb_yiVt-O2JYrzGzB7}/_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 ?? "";
|
|
@@ -1206,9 +1268,11 @@ function registerBuiltinPolicies(enabledNames) {
|
|
|
1206
1268
|
}
|
|
1207
1269
|
}
|
|
1208
1270
|
}
|
|
1209
|
-
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;
|
|
1210
1272
|
var init_builtin_policies = __esm(() => {
|
|
1211
1273
|
init_hook_logger();
|
|
1274
|
+
isClaudeInternalPath = isAgentInternalPath;
|
|
1275
|
+
isClaudeSettingsFile = isAgentSettingsFile;
|
|
1212
1276
|
SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
|
|
1213
1277
|
SHELL_METACHAR_RE = /[;&<>`$()\\]/;
|
|
1214
1278
|
JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
|
|
@@ -1372,7 +1436,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
1372
1436
|
name: "block-sudo",
|
|
1373
1437
|
description: "Block sudo commands",
|
|
1374
1438
|
fn: blockSudo,
|
|
1375
|
-
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1439
|
+
match: { events: ["PreToolUse", "PermissionRequest"], toolNames: ["Bash"] },
|
|
1376
1440
|
defaultEnabled: true,
|
|
1377
1441
|
category: "Dangerous Commands",
|
|
1378
1442
|
params: {
|
|
@@ -1782,7 +1846,8 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1782
1846
|
payload,
|
|
1783
1847
|
toolName,
|
|
1784
1848
|
toolInput,
|
|
1785
|
-
session
|
|
1849
|
+
session,
|
|
1850
|
+
cli: session?.cli
|
|
1786
1851
|
};
|
|
1787
1852
|
const instructEntries = [];
|
|
1788
1853
|
const allowEntries = [];
|
|
@@ -1827,6 +1892,25 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1827
1892
|
decision: "deny"
|
|
1828
1893
|
};
|
|
1829
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
|
+
}
|
|
1830
1914
|
if (eventType === "PostToolUse") {
|
|
1831
1915
|
const response = {
|
|
1832
1916
|
hookSpecificOutput: {
|
|
@@ -1911,7 +1995,7 @@ You MUST complete the above action(s) NOW. Do NOT ask the user for confirmation
|
|
|
1911
1995
|
const combined = allowEntries.map((e) => e.reason).join(`
|
|
1912
1996
|
`);
|
|
1913
1997
|
const policyNames = allowEntries.map((e) => e.policyName);
|
|
1914
|
-
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
|
|
1998
|
+
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit" || eventType === "PermissionRequest";
|
|
1915
1999
|
const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
|
|
1916
2000
|
const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
1917
2001
|
`);
|
|
@@ -2313,7 +2397,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2313
2397
|
});
|
|
2314
2398
|
|
|
2315
2399
|
// package.json
|
|
2316
|
-
var version2 = "0.0.
|
|
2400
|
+
var version2 = "0.0.9-beta.1";
|
|
2317
2401
|
var init_package = () => {};
|
|
2318
2402
|
|
|
2319
2403
|
// src/posthog-key.ts
|
|
@@ -2344,6 +2428,118 @@ var init_hook_telemetry = __esm(() => {
|
|
|
2344
2428
|
API_KEY = POSTHOG_API_KEY;
|
|
2345
2429
|
});
|
|
2346
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
|
+
|
|
2347
2543
|
// lib/telemetry-id.ts
|
|
2348
2544
|
import fs from "node:fs";
|
|
2349
2545
|
import path from "node:path";
|
|
@@ -2427,26 +2623,26 @@ var init_telemetry_id = __esm(() => {
|
|
|
2427
2623
|
|
|
2428
2624
|
// src/auth/token-store.ts
|
|
2429
2625
|
import {
|
|
2430
|
-
readFileSync as
|
|
2431
|
-
writeFileSync as
|
|
2432
|
-
existsSync as
|
|
2433
|
-
mkdirSync as
|
|
2626
|
+
readFileSync as readFileSync4,
|
|
2627
|
+
writeFileSync as writeFileSync4,
|
|
2628
|
+
existsSync as existsSync6,
|
|
2629
|
+
mkdirSync as mkdirSync5,
|
|
2434
2630
|
unlinkSync as unlinkSync2,
|
|
2435
2631
|
renameSync as renameSync3,
|
|
2436
2632
|
openSync,
|
|
2437
2633
|
closeSync
|
|
2438
2634
|
} from "node:fs";
|
|
2439
|
-
import { join as
|
|
2440
|
-
import { homedir as
|
|
2635
|
+
import { join as join5 } from "node:path";
|
|
2636
|
+
import { homedir as homedir7 } from "node:os";
|
|
2441
2637
|
function ensureAuthDir() {
|
|
2442
|
-
if (!
|
|
2443
|
-
|
|
2638
|
+
if (!existsSync6(AUTH_DIR))
|
|
2639
|
+
mkdirSync5(AUTH_DIR, { recursive: true, mode: 448 });
|
|
2444
2640
|
}
|
|
2445
2641
|
function readTokens() {
|
|
2446
|
-
if (!
|
|
2642
|
+
if (!existsSync6(AUTH_FILE))
|
|
2447
2643
|
return null;
|
|
2448
2644
|
try {
|
|
2449
|
-
const raw =
|
|
2645
|
+
const raw = readFileSync4(AUTH_FILE, "utf8");
|
|
2450
2646
|
return JSON.parse(raw);
|
|
2451
2647
|
} catch {
|
|
2452
2648
|
return null;
|
|
@@ -2457,23 +2653,23 @@ function writeTokens(tokens) {
|
|
|
2457
2653
|
const tmpPath = `${AUTH_FILE}.tmp`;
|
|
2458
2654
|
const fd = openSync(tmpPath, "w", 384);
|
|
2459
2655
|
try {
|
|
2460
|
-
|
|
2656
|
+
writeFileSync4(fd, JSON.stringify(tokens, null, 2));
|
|
2461
2657
|
} finally {
|
|
2462
2658
|
closeSync(fd);
|
|
2463
2659
|
}
|
|
2464
2660
|
renameSync3(tmpPath, AUTH_FILE);
|
|
2465
2661
|
}
|
|
2466
2662
|
function clearTokens() {
|
|
2467
|
-
if (
|
|
2663
|
+
if (existsSync6(AUTH_FILE))
|
|
2468
2664
|
unlinkSync2(AUTH_FILE);
|
|
2469
2665
|
}
|
|
2470
2666
|
function isLoggedIn() {
|
|
2471
|
-
return
|
|
2667
|
+
return existsSync6(AUTH_FILE);
|
|
2472
2668
|
}
|
|
2473
2669
|
var AUTH_DIR, AUTH_FILE;
|
|
2474
2670
|
var init_token_store = __esm(() => {
|
|
2475
|
-
AUTH_DIR =
|
|
2476
|
-
AUTH_FILE =
|
|
2671
|
+
AUTH_DIR = join5(homedir7(), ".failproofai");
|
|
2672
|
+
AUTH_FILE = join5(AUTH_DIR, "auth.json");
|
|
2477
2673
|
});
|
|
2478
2674
|
|
|
2479
2675
|
// src/relay/queue.ts
|
|
@@ -2488,17 +2684,17 @@ __export(exports_queue, {
|
|
|
2488
2684
|
});
|
|
2489
2685
|
import {
|
|
2490
2686
|
appendFileSync as appendFileSync3,
|
|
2491
|
-
mkdirSync as
|
|
2492
|
-
existsSync as
|
|
2493
|
-
readFileSync as
|
|
2687
|
+
mkdirSync as mkdirSync6,
|
|
2688
|
+
existsSync as existsSync7,
|
|
2689
|
+
readFileSync as readFileSync5,
|
|
2494
2690
|
statSync as statSync4,
|
|
2495
2691
|
renameSync as renameSync4,
|
|
2496
2692
|
unlinkSync as unlinkSync3,
|
|
2497
|
-
readdirSync as
|
|
2693
|
+
readdirSync as readdirSync4,
|
|
2498
2694
|
chmodSync
|
|
2499
2695
|
} from "node:fs";
|
|
2500
|
-
import { join as
|
|
2501
|
-
import { homedir as
|
|
2696
|
+
import { join as join6 } from "node:path";
|
|
2697
|
+
import { homedir as homedir8 } from "node:os";
|
|
2502
2698
|
import { createHash, randomUUID } from "node:crypto";
|
|
2503
2699
|
function hashCwd(cwd) {
|
|
2504
2700
|
if (!cwd)
|
|
@@ -2528,8 +2724,8 @@ function sanitize(entry) {
|
|
|
2528
2724
|
};
|
|
2529
2725
|
}
|
|
2530
2726
|
function ensureDir2() {
|
|
2531
|
-
if (!
|
|
2532
|
-
|
|
2727
|
+
if (!existsSync7(QUEUE_DIR)) {
|
|
2728
|
+
mkdirSync6(QUEUE_DIR, { recursive: true, mode: 448 });
|
|
2533
2729
|
}
|
|
2534
2730
|
}
|
|
2535
2731
|
function appendToServerQueue(entry) {
|
|
@@ -2537,7 +2733,7 @@ function appendToServerQueue(entry) {
|
|
|
2537
2733
|
return;
|
|
2538
2734
|
ensureDir2();
|
|
2539
2735
|
try {
|
|
2540
|
-
if (
|
|
2736
|
+
if (existsSync7(PENDING_FILE) && statSync4(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
2541
2737
|
return;
|
|
2542
2738
|
}
|
|
2543
2739
|
} catch {}
|
|
@@ -2556,7 +2752,7 @@ function queueSizeBytes() {
|
|
|
2556
2752
|
}
|
|
2557
2753
|
}
|
|
2558
2754
|
function claimPendingBatch() {
|
|
2559
|
-
if (!
|
|
2755
|
+
if (!existsSync7(PENDING_FILE))
|
|
2560
2756
|
return null;
|
|
2561
2757
|
try {
|
|
2562
2758
|
const size = statSync4(PENDING_FILE).size;
|
|
@@ -2566,7 +2762,7 @@ function claimPendingBatch() {
|
|
|
2566
2762
|
return null;
|
|
2567
2763
|
}
|
|
2568
2764
|
const seq = `${Date.now()}-${process.pid}`;
|
|
2569
|
-
const processingFile =
|
|
2765
|
+
const processingFile = join6(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
|
|
2570
2766
|
try {
|
|
2571
2767
|
renameSync4(PENDING_FILE, processingFile);
|
|
2572
2768
|
try {
|
|
@@ -2583,15 +2779,15 @@ function claimPendingBatch() {
|
|
|
2583
2779
|
function findOrphanProcessingFiles() {
|
|
2584
2780
|
ensureDir2();
|
|
2585
2781
|
try {
|
|
2586
|
-
return
|
|
2782
|
+
return readdirSync4(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) => join6(QUEUE_DIR, n)).sort();
|
|
2587
2783
|
} catch {
|
|
2588
2784
|
return [];
|
|
2589
2785
|
}
|
|
2590
2786
|
}
|
|
2591
2787
|
function readProcessingFile(path2) {
|
|
2592
|
-
if (!
|
|
2788
|
+
if (!existsSync7(path2))
|
|
2593
2789
|
return [];
|
|
2594
|
-
const content =
|
|
2790
|
+
const content = readFileSync5(path2, "utf8");
|
|
2595
2791
|
const out = [];
|
|
2596
2792
|
for (const line of content.split(`
|
|
2597
2793
|
`)) {
|
|
@@ -2612,20 +2808,20 @@ function deleteProcessingFile(path2) {
|
|
|
2612
2808
|
var QUEUE_DIR, PENDING_FILE, PROCESSING_PREFIX = "processing-", MAX_QUEUE_BYTES;
|
|
2613
2809
|
var init_queue = __esm(() => {
|
|
2614
2810
|
init_token_store();
|
|
2615
|
-
QUEUE_DIR =
|
|
2616
|
-
PENDING_FILE =
|
|
2811
|
+
QUEUE_DIR = join6(homedir8(), ".failproofai", "cache", "server-queue");
|
|
2812
|
+
PENDING_FILE = join6(QUEUE_DIR, "pending.jsonl");
|
|
2617
2813
|
MAX_QUEUE_BYTES = 50 * 1024 * 1024;
|
|
2618
2814
|
});
|
|
2619
2815
|
|
|
2620
2816
|
// src/relay/pid.ts
|
|
2621
|
-
import { readFileSync as
|
|
2622
|
-
import { join as
|
|
2623
|
-
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";
|
|
2624
2820
|
function readPid() {
|
|
2625
|
-
if (!
|
|
2821
|
+
if (!existsSync8(PID_FILE))
|
|
2626
2822
|
return null;
|
|
2627
2823
|
try {
|
|
2628
|
-
const raw =
|
|
2824
|
+
const raw = readFileSync6(PID_FILE, "utf8").trim();
|
|
2629
2825
|
const pid = parseInt(raw, 10);
|
|
2630
2826
|
if (Number.isNaN(pid) || pid <= 0)
|
|
2631
2827
|
return null;
|
|
@@ -2635,13 +2831,13 @@ function readPid() {
|
|
|
2635
2831
|
}
|
|
2636
2832
|
}
|
|
2637
2833
|
function writePid(pid) {
|
|
2638
|
-
const dir =
|
|
2639
|
-
if (!
|
|
2640
|
-
|
|
2641
|
-
|
|
2834
|
+
const dir = dirname4(PID_FILE);
|
|
2835
|
+
if (!existsSync8(dir))
|
|
2836
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2837
|
+
writeFileSync5(PID_FILE, String(pid));
|
|
2642
2838
|
}
|
|
2643
2839
|
function clearPid() {
|
|
2644
|
-
if (
|
|
2840
|
+
if (existsSync8(PID_FILE))
|
|
2645
2841
|
unlinkSync4(PID_FILE);
|
|
2646
2842
|
}
|
|
2647
2843
|
function isProcessAlive(pid) {
|
|
@@ -2673,7 +2869,7 @@ function stopRelay() {
|
|
|
2673
2869
|
}
|
|
2674
2870
|
var PID_FILE;
|
|
2675
2871
|
var init_pid = __esm(() => {
|
|
2676
|
-
PID_FILE =
|
|
2872
|
+
PID_FILE = join7(homedir9(), ".failproofai", "relay.pid");
|
|
2677
2873
|
});
|
|
2678
2874
|
|
|
2679
2875
|
// src/relay/daemon.ts
|
|
@@ -2685,9 +2881,9 @@ __export(exports_daemon, {
|
|
|
2685
2881
|
ensureRelayRunning: () => ensureRelayRunning
|
|
2686
2882
|
});
|
|
2687
2883
|
import { spawn } from "node:child_process";
|
|
2688
|
-
import { existsSync as
|
|
2689
|
-
import { join as
|
|
2690
|
-
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";
|
|
2691
2887
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2692
2888
|
function ensureRelayRunning() {
|
|
2693
2889
|
if (!isLoggedIn())
|
|
@@ -2904,7 +3100,7 @@ async function runDaemon() {
|
|
|
2904
3100
|
}
|
|
2905
3101
|
relay.close();
|
|
2906
3102
|
} catch {}
|
|
2907
|
-
if (
|
|
3103
|
+
if (existsSync9(QUEUE_DIR2)) {}
|
|
2908
3104
|
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
2909
3105
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
2910
3106
|
}
|
|
@@ -2954,7 +3150,7 @@ var init_daemon = __esm(() => {
|
|
|
2954
3150
|
init_token_store();
|
|
2955
3151
|
init_pid();
|
|
2956
3152
|
init_queue();
|
|
2957
|
-
QUEUE_DIR2 =
|
|
3153
|
+
QUEUE_DIR2 = join8(homedir10(), ".failproofai", "cache", "server-queue");
|
|
2958
3154
|
});
|
|
2959
3155
|
|
|
2960
3156
|
// src/hooks/handler.ts
|
|
@@ -2962,7 +3158,15 @@ var exports_handler = {};
|
|
|
2962
3158
|
__export(exports_handler, {
|
|
2963
3159
|
handleHookEvent: () => handleHookEvent
|
|
2964
3160
|
});
|
|
2965
|
-
|
|
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") {
|
|
2966
3170
|
const startTime = performance.now();
|
|
2967
3171
|
const MAX_STDIN_BYTES = 1048576;
|
|
2968
3172
|
let payload = "";
|
|
@@ -2997,12 +3201,15 @@ async function handleHookEvent(eventType) {
|
|
|
2997
3201
|
hookLogWarn(`payload parse failed for ${eventType} (${payload.length} bytes)`);
|
|
2998
3202
|
}
|
|
2999
3203
|
}
|
|
3204
|
+
const canonicalEventType = canonicalizeEventType(eventType, cli);
|
|
3205
|
+
const sessionId = parsed.session_id;
|
|
3000
3206
|
const session = {
|
|
3001
|
-
sessionId
|
|
3207
|
+
sessionId,
|
|
3002
3208
|
transcriptPath: parsed.transcript_path,
|
|
3003
3209
|
cwd: parsed.cwd,
|
|
3004
|
-
permissionMode: parsed
|
|
3005
|
-
hookEventName: parsed.hook_event_name
|
|
3210
|
+
permissionMode: resolvePermissionMode(cli, parsed, sessionId),
|
|
3211
|
+
hookEventName: parsed.hook_event_name,
|
|
3212
|
+
cli
|
|
3006
3213
|
};
|
|
3007
3214
|
const config = readMergedHooksConfig(session.cwd);
|
|
3008
3215
|
clearPolicies();
|
|
@@ -3030,6 +3237,7 @@ async function handleHookEvent(eventType) {
|
|
|
3030
3237
|
hook_name: hookName,
|
|
3031
3238
|
error_type: isTimeout ? "timeout" : "exception",
|
|
3032
3239
|
event_type: eventType,
|
|
3240
|
+
cli,
|
|
3033
3241
|
is_convention_policy: isConvention,
|
|
3034
3242
|
convention_scope: conventionScope ?? null
|
|
3035
3243
|
});
|
|
@@ -3040,6 +3248,7 @@ async function handleHookEvent(eventType) {
|
|
|
3040
3248
|
}
|
|
3041
3249
|
if (customHooksList.length > 0) {
|
|
3042
3250
|
trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
|
|
3251
|
+
cli,
|
|
3043
3252
|
custom_hooks_count: customHooksList.length,
|
|
3044
3253
|
custom_hook_names: customHooksList.map((h) => h.name),
|
|
3045
3254
|
event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
|
|
@@ -3047,15 +3256,16 @@ async function handleHookEvent(eventType) {
|
|
|
3047
3256
|
}
|
|
3048
3257
|
if (loadResult.conventionSources.length > 0) {
|
|
3049
3258
|
trackHookEvent(getInstanceId(), "convention_policies_loaded", {
|
|
3050
|
-
event_type:
|
|
3259
|
+
event_type: canonicalEventType,
|
|
3260
|
+
cli,
|
|
3051
3261
|
project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
|
|
3052
3262
|
user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
|
|
3053
3263
|
convention_hook_count: conventionHookNames.size,
|
|
3054
3264
|
convention_hook_names: [...conventionHookNames]
|
|
3055
3265
|
});
|
|
3056
3266
|
}
|
|
3057
|
-
hookLogInfo(`event=${
|
|
3058
|
-
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);
|
|
3059
3269
|
const durationMs = Math.round(performance.now() - startTime);
|
|
3060
3270
|
hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
|
|
3061
3271
|
if (result.stdout) {
|
|
@@ -3066,7 +3276,8 @@ async function handleHookEvent(eventType) {
|
|
|
3066
3276
|
}
|
|
3067
3277
|
const activityEntry = {
|
|
3068
3278
|
timestamp: Date.now(),
|
|
3069
|
-
eventType,
|
|
3279
|
+
eventType: canonicalEventType,
|
|
3280
|
+
integration: cli,
|
|
3070
3281
|
toolName: parsed.tool_name ?? null,
|
|
3071
3282
|
policyName: result.policyName,
|
|
3072
3283
|
policyNames: result.policyNames,
|
|
@@ -3101,7 +3312,8 @@ async function handleHookEvent(eventType) {
|
|
|
3101
3312
|
const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
|
|
3102
3313
|
const distinctId = getInstanceId();
|
|
3103
3314
|
await trackHookEvent(distinctId, "hook_policy_triggered", {
|
|
3104
|
-
event_type:
|
|
3315
|
+
event_type: canonicalEventType,
|
|
3316
|
+
cli,
|
|
3105
3317
|
tool_name: parsed.tool_name ?? null,
|
|
3106
3318
|
policy_name: result.policyName,
|
|
3107
3319
|
decision: result.decision,
|
|
@@ -3116,12 +3328,14 @@ async function handleHookEvent(eventType) {
|
|
|
3116
3328
|
return result.exitCode;
|
|
3117
3329
|
}
|
|
3118
3330
|
var init_handler = __esm(() => {
|
|
3331
|
+
init_types();
|
|
3119
3332
|
init_hooks_config();
|
|
3120
3333
|
init_builtin_policies();
|
|
3121
3334
|
init_policy_evaluator();
|
|
3122
3335
|
init_custom_hooks_loader();
|
|
3123
3336
|
init_hook_activity_store();
|
|
3124
3337
|
init_hook_telemetry();
|
|
3338
|
+
init_resolve_permission_mode();
|
|
3125
3339
|
init_telemetry_id();
|
|
3126
3340
|
init_hook_logger();
|
|
3127
3341
|
});
|
|
@@ -3135,9 +3349,9 @@ __export(exports_daemon2, {
|
|
|
3135
3349
|
ensureRelayRunning: () => ensureRelayRunning2
|
|
3136
3350
|
});
|
|
3137
3351
|
import { spawn as spawn2 } from "node:child_process";
|
|
3138
|
-
import { existsSync as
|
|
3139
|
-
import { join as
|
|
3140
|
-
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";
|
|
3141
3355
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3142
3356
|
function ensureRelayRunning2() {
|
|
3143
3357
|
if (!isLoggedIn())
|
|
@@ -3354,7 +3568,7 @@ async function runDaemon2() {
|
|
|
3354
3568
|
}
|
|
3355
3569
|
relay.close();
|
|
3356
3570
|
} catch {}
|
|
3357
|
-
if (
|
|
3571
|
+
if (existsSync10(QUEUE_DIR3)) {}
|
|
3358
3572
|
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
3359
3573
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS2);
|
|
3360
3574
|
}
|
|
@@ -3404,41 +3618,279 @@ var init_daemon2 = __esm(() => {
|
|
|
3404
3618
|
init_token_store();
|
|
3405
3619
|
init_pid();
|
|
3406
3620
|
init_queue();
|
|
3407
|
-
QUEUE_DIR3 =
|
|
3621
|
+
QUEUE_DIR3 = join9(homedir11(), ".failproofai", "cache", "server-queue");
|
|
3408
3622
|
});
|
|
3409
3623
|
|
|
3410
|
-
// src/hooks/
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
"
|
|
3435
|
-
"
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
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
|
+
};
|
|
3442
3894
|
});
|
|
3443
3895
|
|
|
3444
3896
|
// src/hooks/install-prompt.ts
|
|
@@ -3629,7 +4081,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3629
4081
|
process.stdout.write(output);
|
|
3630
4082
|
lastLineCount = lines.length;
|
|
3631
4083
|
}
|
|
3632
|
-
return new Promise((
|
|
4084
|
+
return new Promise((resolve6) => {
|
|
3633
4085
|
render();
|
|
3634
4086
|
process.stdin.setRawMode(true);
|
|
3635
4087
|
process.stdin.resume();
|
|
@@ -3671,7 +4123,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3671
4123
|
const selected = items.filter((i) => i.selected).map((i) => i.name);
|
|
3672
4124
|
process.stdout.write(`
|
|
3673
4125
|
`);
|
|
3674
|
-
|
|
4126
|
+
resolve6(selected);
|
|
3675
4127
|
} else if (key.name === "backspace" || key.name === "delete") {
|
|
3676
4128
|
if (search.length > 0) {
|
|
3677
4129
|
search = search.slice(0, -1);
|
|
@@ -3695,6 +4147,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
3695
4147
|
}
|
|
3696
4148
|
var init_install_prompt = __esm(() => {
|
|
3697
4149
|
init_builtin_policies();
|
|
4150
|
+
init_integrations();
|
|
3698
4151
|
});
|
|
3699
4152
|
|
|
3700
4153
|
// src/cli-error.ts
|
|
@@ -3719,20 +4172,12 @@ __export(exports_manager, {
|
|
|
3719
4172
|
hooksInstalledInSettings: () => hooksInstalledInSettings,
|
|
3720
4173
|
getSettingsPath: () => getSettingsPath
|
|
3721
4174
|
});
|
|
3722
|
-
import { execSync as
|
|
3723
|
-
import {
|
|
3724
|
-
import { resolve as
|
|
3725
|
-
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";
|
|
3726
4179
|
function getSettingsPath(scope, cwd) {
|
|
3727
|
-
|
|
3728
|
-
switch (scope) {
|
|
3729
|
-
case "user":
|
|
3730
|
-
return resolve5(homedir11(), ".claude", "settings.json");
|
|
3731
|
-
case "project":
|
|
3732
|
-
return resolve5(base, ".claude", "settings.json");
|
|
3733
|
-
case "local":
|
|
3734
|
-
return resolve5(base, ".claude", "settings.local.json");
|
|
3735
|
-
}
|
|
4180
|
+
return claudeCode.getSettingsPath(scope, cwd);
|
|
3736
4181
|
}
|
|
3737
4182
|
function scopeLabel(scope) {
|
|
3738
4183
|
switch (scope) {
|
|
@@ -3744,22 +4189,13 @@ function scopeLabel(scope) {
|
|
|
3744
4189
|
return `{cwd}/.claude/settings.local.json`;
|
|
3745
4190
|
}
|
|
3746
4191
|
}
|
|
3747
|
-
function readSettings(settingsPath) {
|
|
3748
|
-
if (!existsSync10(settingsPath)) {
|
|
3749
|
-
return {};
|
|
3750
|
-
}
|
|
3751
|
-
const raw = readFileSync6(settingsPath, "utf8");
|
|
3752
|
-
return JSON.parse(raw);
|
|
3753
|
-
}
|
|
3754
|
-
function writeSettings(settingsPath, settings) {
|
|
3755
|
-
mkdirSync7(dirname4(settingsPath), { recursive: true });
|
|
3756
|
-
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
3757
|
-
`, "utf8");
|
|
3758
|
-
}
|
|
3759
4192
|
function resolveFailproofaiBinary() {
|
|
4193
|
+
const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
|
|
4194
|
+
if (override && override.trim())
|
|
4195
|
+
return override.trim();
|
|
3760
4196
|
try {
|
|
3761
4197
|
const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
|
|
3762
|
-
const result =
|
|
4198
|
+
const result = execSync4(cmd, { encoding: "utf8" }).trim();
|
|
3763
4199
|
return result.split(`
|
|
3764
4200
|
`)[0].trim();
|
|
3765
4201
|
} catch {
|
|
@@ -3767,12 +4203,6 @@ function resolveFailproofaiBinary() {
|
|
|
3767
4203
|
` + "Install it globally first: npm install -g failproofai");
|
|
3768
4204
|
}
|
|
3769
4205
|
}
|
|
3770
|
-
function isFailproofaiHook(hook) {
|
|
3771
|
-
if (hook[FAILPROOFAI_HOOK_MARKER] === true)
|
|
3772
|
-
return true;
|
|
3773
|
-
const cmd = typeof hook.command === "string" ? hook.command : "";
|
|
3774
|
-
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
3775
|
-
}
|
|
3776
4206
|
function validatePolicyNames(names) {
|
|
3777
4207
|
const invalid = names.filter((n) => !VALID_POLICY_NAMES.has(n));
|
|
3778
4208
|
if (invalid.length > 0) {
|
|
@@ -3792,58 +4222,9 @@ function deduplicateScopes(scopes, cwd) {
|
|
|
3792
4222
|
});
|
|
3793
4223
|
}
|
|
3794
4224
|
function hooksInstalledInSettings(scope, cwd) {
|
|
3795
|
-
|
|
3796
|
-
if (!existsSync10(settingsPath))
|
|
3797
|
-
return false;
|
|
3798
|
-
try {
|
|
3799
|
-
const settings = readSettings(settingsPath);
|
|
3800
|
-
if (!settings.hooks)
|
|
3801
|
-
return false;
|
|
3802
|
-
for (const matchers of Object.values(settings.hooks)) {
|
|
3803
|
-
if (!Array.isArray(matchers))
|
|
3804
|
-
continue;
|
|
3805
|
-
for (const matcher of matchers) {
|
|
3806
|
-
if (!matcher.hooks)
|
|
3807
|
-
continue;
|
|
3808
|
-
if (matcher.hooks.some((h) => isFailproofaiHook(h))) {
|
|
3809
|
-
return true;
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
} catch {}
|
|
3814
|
-
return false;
|
|
3815
|
-
}
|
|
3816
|
-
function removeHooksFromSettingsFile(settingsPath) {
|
|
3817
|
-
const settings = readSettings(settingsPath);
|
|
3818
|
-
if (!settings.hooks)
|
|
3819
|
-
return 0;
|
|
3820
|
-
let removed = 0;
|
|
3821
|
-
for (const eventType of Object.keys(settings.hooks)) {
|
|
3822
|
-
const matchers = settings.hooks[eventType];
|
|
3823
|
-
if (!Array.isArray(matchers))
|
|
3824
|
-
continue;
|
|
3825
|
-
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
3826
|
-
const matcher = matchers[i];
|
|
3827
|
-
if (!matcher.hooks)
|
|
3828
|
-
continue;
|
|
3829
|
-
const before = matcher.hooks.length;
|
|
3830
|
-
matcher.hooks = matcher.hooks.filter((h) => !isFailproofaiHook(h));
|
|
3831
|
-
removed += before - matcher.hooks.length;
|
|
3832
|
-
if (matcher.hooks.length === 0) {
|
|
3833
|
-
matchers.splice(i, 1);
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
if (matchers.length === 0) {
|
|
3837
|
-
delete settings.hooks[eventType];
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
3841
|
-
delete settings.hooks;
|
|
3842
|
-
}
|
|
3843
|
-
writeSettings(settingsPath, settings);
|
|
3844
|
-
return removed;
|
|
4225
|
+
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
3845
4226
|
}
|
|
3846
|
-
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) {
|
|
3847
4228
|
if (policyNames !== undefined && policyNames.length > 0) {
|
|
3848
4229
|
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
3849
4230
|
if (nonAllNames.length > 0)
|
|
@@ -3853,6 +4234,13 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
3853
4234
|
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
3854
4235
|
}
|
|
3855
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
|
+
}
|
|
3856
4244
|
const binaryPath = resolveFailproofaiBinary();
|
|
3857
4245
|
const previousConfig = readScopedHooksConfig(scope, cwd);
|
|
3858
4246
|
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
@@ -3873,7 +4261,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
3873
4261
|
if (removeCustomHooks) {
|
|
3874
4262
|
delete configToWrite.customPoliciesPath;
|
|
3875
4263
|
} else if (customPoliciesPath) {
|
|
3876
|
-
configToWrite.customPoliciesPath =
|
|
4264
|
+
configToWrite.customPoliciesPath = resolve6(customPoliciesPath);
|
|
3877
4265
|
let validatedHooks = [];
|
|
3878
4266
|
try {
|
|
3879
4267
|
validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
|
|
@@ -3896,39 +4284,15 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3896
4284
|
} else if (configToWrite.customPoliciesPath) {
|
|
3897
4285
|
console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
|
|
3898
4286
|
}
|
|
3899
|
-
const
|
|
3900
|
-
const
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
type: "command",
|
|
3908
|
-
command,
|
|
3909
|
-
timeout: 60000,
|
|
3910
|
-
[FAILPROOFAI_HOOK_MARKER]: true
|
|
3911
|
-
};
|
|
3912
|
-
if (!settings.hooks[eventType]) {
|
|
3913
|
-
settings.hooks[eventType] = [];
|
|
3914
|
-
}
|
|
3915
|
-
const matchers = settings.hooks[eventType];
|
|
3916
|
-
let found = false;
|
|
3917
|
-
for (const matcher of matchers) {
|
|
3918
|
-
if (!matcher.hooks)
|
|
3919
|
-
continue;
|
|
3920
|
-
const failproofaiIdx = matcher.hooks.findIndex((h) => isFailproofaiHook(h));
|
|
3921
|
-
if (failproofaiIdx >= 0) {
|
|
3922
|
-
matcher.hooks[failproofaiIdx] = hookEntry;
|
|
3923
|
-
found = true;
|
|
3924
|
-
break;
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
if (!found) {
|
|
3928
|
-
matchers.push({ hooks: [hookEntry] });
|
|
3929
|
-
}
|
|
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 });
|
|
3930
4295
|
}
|
|
3931
|
-
writeSettings(settingsPath, settings);
|
|
3932
4296
|
try {
|
|
3933
4297
|
const newSet = new Set(selectedPolicies);
|
|
3934
4298
|
const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
|
|
@@ -3936,6 +4300,8 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3936
4300
|
const distinctId = getInstanceId();
|
|
3937
4301
|
await trackHookEvent(distinctId, "hooks_installed", {
|
|
3938
4302
|
scope,
|
|
4303
|
+
cli: selectedClis,
|
|
4304
|
+
cli_count: selectedClis.length,
|
|
3939
4305
|
policies: selectedPolicies,
|
|
3940
4306
|
policy_count: selectedPolicies.length,
|
|
3941
4307
|
policies_added: policiesAdded,
|
|
@@ -3951,8 +4317,11 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
3951
4317
|
command_format: scope === "project" ? "npx" : "absolute"
|
|
3952
4318
|
});
|
|
3953
4319
|
} catch {}
|
|
3954
|
-
|
|
3955
|
-
|
|
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
|
+
}
|
|
3956
4325
|
if (scope === "project") {
|
|
3957
4326
|
console.log(`Command: npx -y failproofai`);
|
|
3958
4327
|
console.log(`
|
|
@@ -3973,6 +4342,7 @@ This file can be committed to git — no machine-specific paths.`);
|
|
|
3973
4342
|
}
|
|
3974
4343
|
async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
3975
4344
|
const configScope = scope === "all" ? "user" : scope;
|
|
4345
|
+
const selectedClis = opts?.cli && opts.cli.length > 0 ? [...new Set(opts.cli)] : ["claude"];
|
|
3976
4346
|
if (opts?.removeCustomHooks) {
|
|
3977
4347
|
const config = readScopedHooksConfig(configScope, cwd);
|
|
3978
4348
|
delete config.customPoliciesPath;
|
|
@@ -4001,6 +4371,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
4001
4371
|
const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
|
|
4002
4372
|
await trackHookEvent(distinctId, "hooks_removed", {
|
|
4003
4373
|
scope,
|
|
4374
|
+
cli: selectedClis,
|
|
4004
4375
|
removal_mode: opts?.betaOnly ? "beta_policies" : "policies",
|
|
4005
4376
|
beta_only: opts?.betaOnly ?? false,
|
|
4006
4377
|
policies_removed: actuallyRemoved,
|
|
@@ -4017,42 +4388,49 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
4017
4388
|
return;
|
|
4018
4389
|
}
|
|
4019
4390
|
const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
|
|
4020
|
-
const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
|
|
4021
4391
|
let totalRemoved = 0;
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
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;
|
|
4028
4404
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
const settings = readSettings(settingsPath);
|
|
4032
|
-
if (!settings.hooks) {
|
|
4033
|
-
if (scope !== "all") {
|
|
4405
|
+
const removed = integration.removeHooksFromFile(settingsPath);
|
|
4406
|
+
if (removed === 0 && scope !== "all" && selectedClis.length === 1) {
|
|
4034
4407
|
console.log("No hooks found in settings. Nothing to remove.");
|
|
4035
|
-
|
|
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}`);
|
|
4036
4415
|
}
|
|
4037
|
-
continue;
|
|
4038
|
-
}
|
|
4039
|
-
const removed = removeHooksFromSettingsFile(settingsPath);
|
|
4040
|
-
totalRemoved += removed;
|
|
4041
|
-
if (scope !== "all") {
|
|
4042
|
-
console.log(`Removed ${removed} failproofai hook(s) from settings.`);
|
|
4043
|
-
console.log(`Settings: ${settingsPath}`);
|
|
4044
4416
|
}
|
|
4045
4417
|
}
|
|
4418
|
+
if (nothingToReport && totalRemoved === 0)
|
|
4419
|
+
return;
|
|
4046
4420
|
if (scope === "all") {
|
|
4047
4421
|
console.log(`Removed ${totalRemoved} failproofai hook(s) from all scopes.`);
|
|
4048
|
-
for (const
|
|
4049
|
-
|
|
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
|
+
}
|
|
4050
4427
|
}
|
|
4051
4428
|
}
|
|
4052
4429
|
try {
|
|
4053
4430
|
const distinctId = getInstanceId();
|
|
4054
4431
|
await trackHookEvent(distinctId, "hooks_removed", {
|
|
4055
4432
|
scope,
|
|
4433
|
+
cli: selectedClis,
|
|
4056
4434
|
removal_mode: "hooks",
|
|
4057
4435
|
policies_removed: configBeforeRemoval.enabledPolicies,
|
|
4058
4436
|
removed_count: totalRemoved,
|
|
@@ -4196,7 +4574,7 @@ Failproof AI Hook Policies
|
|
|
4196
4574
|
if (config.customPoliciesPath) {
|
|
4197
4575
|
console.log(`
|
|
4198
4576
|
── Custom Policies (${config.customPoliciesPath}) ───────────────────────`);
|
|
4199
|
-
if (!
|
|
4577
|
+
if (!existsSync12(config.customPoliciesPath)) {
|
|
4200
4578
|
console.log(` \x1B[31m✗ File not found: ${config.customPoliciesPath}\x1B[0m`);
|
|
4201
4579
|
} else {
|
|
4202
4580
|
const hooks = await loadCustomHooks(config.customPoliciesPath);
|
|
@@ -4211,10 +4589,10 @@ Failproof AI Hook Policies
|
|
|
4211
4589
|
}
|
|
4212
4590
|
console.log();
|
|
4213
4591
|
}
|
|
4214
|
-
const base = cwd ?
|
|
4592
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
4215
4593
|
const conventionDirs = [
|
|
4216
|
-
{ label: "Project", dir:
|
|
4217
|
-
{ label: "User", dir:
|
|
4594
|
+
{ label: "Project", dir: resolve6(base, ".failproofai", "policies") },
|
|
4595
|
+
{ label: "User", dir: resolve6(homedir13(), ".failproofai", "policies") }
|
|
4218
4596
|
];
|
|
4219
4597
|
for (const { label, dir } of conventionDirs) {
|
|
4220
4598
|
const files = discoverPolicyFiles(dir);
|
|
@@ -4244,6 +4622,7 @@ Failproof AI Hook Policies
|
|
|
4244
4622
|
var VALID_POLICY_NAMES;
|
|
4245
4623
|
var init_manager = __esm(() => {
|
|
4246
4624
|
init_types();
|
|
4625
|
+
init_integrations();
|
|
4247
4626
|
init_install_prompt();
|
|
4248
4627
|
init_hooks_config();
|
|
4249
4628
|
init_builtin_policies();
|
|
@@ -4254,6 +4633,400 @@ var init_manager = __esm(() => {
|
|
|
4254
4633
|
VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
4255
4634
|
});
|
|
4256
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
|
+
return promptCliTargetSelection(detected);
|
|
4659
|
+
}
|
|
4660
|
+
async function promptCliTargetSelection(detected) {
|
|
4661
|
+
const labels = detected.map((id) => getIntegration(id).displayName).join(" + ");
|
|
4662
|
+
const options = [
|
|
4663
|
+
{ label: "Both", description: labels, value: detected },
|
|
4664
|
+
...detected.map((id) => ({
|
|
4665
|
+
label: `${getIntegration(id).displayName} only`,
|
|
4666
|
+
description: "",
|
|
4667
|
+
value: [id]
|
|
4668
|
+
}))
|
|
4669
|
+
];
|
|
4670
|
+
let cursor = 0;
|
|
4671
|
+
let lastLineCount = 0;
|
|
4672
|
+
let cursorHidden = false;
|
|
4673
|
+
function hideCursor() {
|
|
4674
|
+
if (!cursorHidden) {
|
|
4675
|
+
process.stdout.write("\x1B[?25l");
|
|
4676
|
+
cursorHidden = true;
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
function showCursor() {
|
|
4680
|
+
if (cursorHidden) {
|
|
4681
|
+
process.stdout.write("\x1B[?25h");
|
|
4682
|
+
cursorHidden = false;
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
function truncateLine(line, width) {
|
|
4686
|
+
let visual = 0;
|
|
4687
|
+
let result = "";
|
|
4688
|
+
let i = 0;
|
|
4689
|
+
while (i < line.length) {
|
|
4690
|
+
if (line[i] === "\x1B" && line[i + 1] === "[") {
|
|
4691
|
+
let j = i + 2;
|
|
4692
|
+
while (j < line.length && !/[A-Za-z]/.test(line[j]))
|
|
4693
|
+
j++;
|
|
4694
|
+
j++;
|
|
4695
|
+
result += line.slice(i, j);
|
|
4696
|
+
i = j;
|
|
4697
|
+
} else {
|
|
4698
|
+
if (visual >= width)
|
|
4699
|
+
break;
|
|
4700
|
+
result += line[i];
|
|
4701
|
+
visual++;
|
|
4702
|
+
i++;
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
return result;
|
|
4706
|
+
}
|
|
4707
|
+
function render() {
|
|
4708
|
+
const cols = process.stdout.columns || 120;
|
|
4709
|
+
hideCursor();
|
|
4710
|
+
const lines = [];
|
|
4711
|
+
lines.push(" Failproof AI — Install Hooks");
|
|
4712
|
+
lines.push("");
|
|
4713
|
+
lines.push(` \x1B[2mDetected ${labels}. Choose where to install:\x1B[0m`);
|
|
4714
|
+
lines.push("");
|
|
4715
|
+
for (let i = 0;i < options.length; i++) {
|
|
4716
|
+
const opt = options[i];
|
|
4717
|
+
const isActive = i === cursor;
|
|
4718
|
+
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
4719
|
+
const labelPart = isActive ? `\x1B[1;36m${opt.label}\x1B[0m` : opt.label;
|
|
4720
|
+
const pad = opt.description ? " ".repeat(Math.max(2, 22 - opt.label.length)) : "";
|
|
4721
|
+
const desc = opt.description ? `\x1B[2m${opt.description}\x1B[0m` : "";
|
|
4722
|
+
lines.push(` ${pointer} ${labelPart}${pad}${desc}`);
|
|
4723
|
+
}
|
|
4724
|
+
lines.push("");
|
|
4725
|
+
lines.push(" \x1B[2m" + "─".repeat(Math.max(2, cols - 2)) + "\x1B[0m");
|
|
4726
|
+
lines.push(" [↑↓] Move [Enter] Select [^C] Quit");
|
|
4727
|
+
if (lastLineCount > 0) {
|
|
4728
|
+
process.stdout.write(`\x1B[${lastLineCount}A\x1B[J`);
|
|
4729
|
+
}
|
|
4730
|
+
const output = lines.map((l) => l === "" ? l : truncateLine(l, cols)).join(`
|
|
4731
|
+
`) + `
|
|
4732
|
+
`;
|
|
4733
|
+
process.stdout.write(output);
|
|
4734
|
+
lastLineCount = lines.length;
|
|
4735
|
+
}
|
|
4736
|
+
return new Promise((resolve7) => {
|
|
4737
|
+
render();
|
|
4738
|
+
readline2.emitKeypressEvents(process.stdin);
|
|
4739
|
+
const wasRaw = process.stdin.isRaw;
|
|
4740
|
+
if (process.stdin.setRawMode)
|
|
4741
|
+
process.stdin.setRawMode(true);
|
|
4742
|
+
process.stdin.resume();
|
|
4743
|
+
function cleanup() {
|
|
4744
|
+
showCursor();
|
|
4745
|
+
process.stdin.removeListener("keypress", onKey);
|
|
4746
|
+
if (process.stdin.setRawMode)
|
|
4747
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
4748
|
+
process.stdin.pause();
|
|
4749
|
+
}
|
|
4750
|
+
function onKey(_str, key) {
|
|
4751
|
+
if (!key)
|
|
4752
|
+
return;
|
|
4753
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
4754
|
+
cleanup();
|
|
4755
|
+
process.stdout.write(`
|
|
4756
|
+
`);
|
|
4757
|
+
process.exit(130);
|
|
4758
|
+
}
|
|
4759
|
+
if (key.name === "up") {
|
|
4760
|
+
cursor = cursor > 0 ? cursor - 1 : options.length - 1;
|
|
4761
|
+
render();
|
|
4762
|
+
} else if (key.name === "down") {
|
|
4763
|
+
cursor = cursor < options.length - 1 ? cursor + 1 : 0;
|
|
4764
|
+
render();
|
|
4765
|
+
} else if (key.name === "return" || key.name === "space") {
|
|
4766
|
+
cleanup();
|
|
4767
|
+
process.stdout.write(`
|
|
4768
|
+
`);
|
|
4769
|
+
resolve7(options[cursor].value);
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
process.stdin.on("keypress", onKey);
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
async function promptPolicySelection2(preSelected, options = {}) {
|
|
4776
|
+
const { includeBeta = false } = options;
|
|
4777
|
+
if (!process.stdin.isTTY) {
|
|
4778
|
+
const available = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta);
|
|
4779
|
+
if (preSelected)
|
|
4780
|
+
return preSelected.filter((name) => available.some((p) => p.name === name));
|
|
4781
|
+
return available.filter((p) => p.defaultEnabled).map((p) => p.name);
|
|
4782
|
+
}
|
|
4783
|
+
const preSelectedSet = preSelected ? new Set(preSelected) : null;
|
|
4784
|
+
const items = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => ({
|
|
4785
|
+
name: p.name,
|
|
4786
|
+
description: p.description,
|
|
4787
|
+
category: p.category,
|
|
4788
|
+
selected: preSelectedSet ? preSelectedSet.has(p.name) : p.defaultEnabled,
|
|
4789
|
+
beta: !!p.beta
|
|
4790
|
+
}));
|
|
4791
|
+
const total = items.length;
|
|
4792
|
+
const WINDOW_SIZE = 8;
|
|
4793
|
+
let cursor = 0;
|
|
4794
|
+
let search = "";
|
|
4795
|
+
let lastLineCount = 0;
|
|
4796
|
+
let cursorHidden = false;
|
|
4797
|
+
function hideCursor() {
|
|
4798
|
+
if (!cursorHidden) {
|
|
4799
|
+
process.stdout.write("\x1B[?25l");
|
|
4800
|
+
cursorHidden = true;
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
function showCursor() {
|
|
4804
|
+
if (cursorHidden) {
|
|
4805
|
+
process.stdout.write("\x1B[?25h");
|
|
4806
|
+
cursorHidden = false;
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
function truncateLine(line, width) {
|
|
4810
|
+
let visual = 0;
|
|
4811
|
+
let result = "";
|
|
4812
|
+
let i = 0;
|
|
4813
|
+
while (i < line.length) {
|
|
4814
|
+
if (line[i] === "\x1B" && line[i + 1] === "[") {
|
|
4815
|
+
let j = i + 2;
|
|
4816
|
+
while (j < line.length && !/[A-Za-z]/.test(line[j]))
|
|
4817
|
+
j++;
|
|
4818
|
+
j++;
|
|
4819
|
+
result += line.slice(i, j);
|
|
4820
|
+
i = j;
|
|
4821
|
+
} else {
|
|
4822
|
+
if (visual >= width)
|
|
4823
|
+
break;
|
|
4824
|
+
result += line[i];
|
|
4825
|
+
visual++;
|
|
4826
|
+
i++;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
return result;
|
|
4830
|
+
}
|
|
4831
|
+
function getFiltered() {
|
|
4832
|
+
if (!search)
|
|
4833
|
+
return items;
|
|
4834
|
+
const q = search.toLowerCase();
|
|
4835
|
+
return items.filter((i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
|
|
4836
|
+
}
|
|
4837
|
+
function buildDisplayRows(filtered) {
|
|
4838
|
+
const categoryOrder = [];
|
|
4839
|
+
const categoryEnabledCount = new Map;
|
|
4840
|
+
const categoryTotalCount = new Map;
|
|
4841
|
+
for (const p of items) {
|
|
4842
|
+
if (!categoryEnabledCount.has(p.category)) {
|
|
4843
|
+
categoryOrder.push(p.category);
|
|
4844
|
+
categoryEnabledCount.set(p.category, 0);
|
|
4845
|
+
categoryTotalCount.set(p.category, 0);
|
|
4846
|
+
}
|
|
4847
|
+
categoryTotalCount.set(p.category, categoryTotalCount.get(p.category) + 1);
|
|
4848
|
+
if (p.selected)
|
|
4849
|
+
categoryEnabledCount.set(p.category, categoryEnabledCount.get(p.category) + 1);
|
|
4850
|
+
}
|
|
4851
|
+
const filteredByCategory = new Map;
|
|
4852
|
+
for (const item of filtered) {
|
|
4853
|
+
const bucket = filteredByCategory.get(item.category) ?? [];
|
|
4854
|
+
bucket.push(item);
|
|
4855
|
+
filteredByCategory.set(item.category, bucket);
|
|
4856
|
+
}
|
|
4857
|
+
const rows = [];
|
|
4858
|
+
let idx = 0;
|
|
4859
|
+
for (const cat of categoryOrder) {
|
|
4860
|
+
const catFiltered = filteredByCategory.get(cat);
|
|
4861
|
+
if (!catFiltered || catFiltered.length === 0)
|
|
4862
|
+
continue;
|
|
4863
|
+
rows.push({ kind: "header", category: cat, enabledCount: categoryEnabledCount.get(cat), totalCount: categoryTotalCount.get(cat) });
|
|
4864
|
+
for (const item of catFiltered) {
|
|
4865
|
+
rows.push({ kind: "item", item, filteredIndex: idx++ });
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
return rows;
|
|
4869
|
+
}
|
|
4870
|
+
function render() {
|
|
4871
|
+
const cols = process.stdout.columns || 120;
|
|
4872
|
+
hideCursor();
|
|
4873
|
+
const filtered = getFiltered();
|
|
4874
|
+
const shown = filtered.length;
|
|
4875
|
+
if (shown > 0 && cursor >= shown)
|
|
4876
|
+
cursor = shown - 1;
|
|
4877
|
+
const lines = [];
|
|
4878
|
+
lines.push(" Failproof AI — Policy Manager");
|
|
4879
|
+
lines.push("");
|
|
4880
|
+
const innerWidth = Math.max(20, cols - 6);
|
|
4881
|
+
const topBorder = " ┌" + "─".repeat(innerWidth + 2) + "┐";
|
|
4882
|
+
const botBorder = " └" + "─".repeat(innerWidth + 2) + "┘";
|
|
4883
|
+
const cursorChar = "\x1B[7m \x1B[0m";
|
|
4884
|
+
const countPart = search ? ` \x1B[2m(${shown}/${total})\x1B[0m` : ` \x1B[2m(${total} policies)\x1B[0m`;
|
|
4885
|
+
const searchContent = `\x1B[1mSearch:\x1B[0m ${search}${cursorChar}${countPart}`;
|
|
4886
|
+
lines.push(topBorder);
|
|
4887
|
+
lines.push(` │ ${searchContent}`);
|
|
4888
|
+
lines.push(botBorder);
|
|
4889
|
+
lines.push("");
|
|
4890
|
+
if (shown === 0) {
|
|
4891
|
+
lines.push(" \x1B[2mNo policies match “" + search + "”\x1B[0m");
|
|
4892
|
+
for (let i = 0;i < WINDOW_SIZE + 1; i++)
|
|
4893
|
+
lines.push("");
|
|
4894
|
+
} else {
|
|
4895
|
+
const displayRows = buildDisplayRows(filtered);
|
|
4896
|
+
let cursorDisplayRow = 0;
|
|
4897
|
+
for (let i = 0;i < displayRows.length; i++) {
|
|
4898
|
+
const row = displayRows[i];
|
|
4899
|
+
if (row.kind === "item" && row.filteredIndex === cursor) {
|
|
4900
|
+
cursorDisplayRow = i;
|
|
4901
|
+
break;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
let windowStart = cursorDisplayRow - Math.floor(WINDOW_SIZE / 2);
|
|
4905
|
+
windowStart = Math.max(0, windowStart);
|
|
4906
|
+
windowStart = Math.min(windowStart, Math.max(0, displayRows.length - WINDOW_SIZE));
|
|
4907
|
+
const windowEnd = Math.min(displayRows.length, windowStart + WINDOW_SIZE);
|
|
4908
|
+
const aboveItems = displayRows.slice(0, windowStart).filter((r) => r.kind === "item").length;
|
|
4909
|
+
if (aboveItems > 0) {
|
|
4910
|
+
lines.push(` \x1B[2m ↑ ${aboveItems} more above\x1B[0m`);
|
|
4911
|
+
} else {
|
|
4912
|
+
lines.push("");
|
|
4913
|
+
}
|
|
4914
|
+
for (let i = windowStart;i < windowEnd; i++) {
|
|
4915
|
+
const row = displayRows[i];
|
|
4916
|
+
if (row.kind === "header") {
|
|
4917
|
+
const label = ` ${row.category.toUpperCase()} (${row.enabledCount}/${row.totalCount}) `;
|
|
4918
|
+
const prefix = "── ";
|
|
4919
|
+
const prefixLen = 3 + label.length;
|
|
4920
|
+
const dashLen = Math.max(2, cols - 2 - prefixLen);
|
|
4921
|
+
lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
|
|
4922
|
+
} else {
|
|
4923
|
+
const item = row.item;
|
|
4924
|
+
const isActive = row.filteredIndex === cursor;
|
|
4925
|
+
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
4926
|
+
const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
|
|
4927
|
+
const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
|
|
4928
|
+
const betaPart = item.beta ? " \x1B[35m[beta]\x1B[0m" : "";
|
|
4929
|
+
const pad = " ".repeat(Math.max(1, 28 - item.name.length));
|
|
4930
|
+
const desc = `\x1B[2m${item.description}\x1B[0m`;
|
|
4931
|
+
lines.push(` ${pointer} ${check} ${namePart}${betaPart}${pad}${desc}`);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
for (let i = windowEnd - windowStart;i < WINDOW_SIZE; i++) {
|
|
4935
|
+
lines.push("");
|
|
4936
|
+
}
|
|
4937
|
+
const belowItems = displayRows.slice(windowEnd).filter((r) => r.kind === "item").length;
|
|
4938
|
+
if (belowItems > 0) {
|
|
4939
|
+
lines.push(` \x1B[2m ↓ ${belowItems} more below\x1B[0m`);
|
|
4940
|
+
} else {
|
|
4941
|
+
lines.push("");
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
lines.push("");
|
|
4945
|
+
lines.push(" \x1B[2m" + "─".repeat(cols - 2) + "\x1B[0m");
|
|
4946
|
+
lines.push(" [↑↓] Move [Space] Toggle [Ctrl+A] All [Ctrl+S] Save [Esc] Clear [^C] Quit");
|
|
4947
|
+
lines.push("");
|
|
4948
|
+
lines.push(" \x1B[2mTip: `policies` for a flat list · `policies --install <name…>` to skip prompt\x1B[0m");
|
|
4949
|
+
if (!includeBeta) {
|
|
4950
|
+
lines.push(" \x1B[2mTip: `policies --install --beta` to include beta policies\x1B[0m");
|
|
4951
|
+
}
|
|
4952
|
+
if (lastLineCount > 0) {
|
|
4953
|
+
process.stdout.write(`\x1B[${lastLineCount}A\x1B[J`);
|
|
4954
|
+
}
|
|
4955
|
+
const output = lines.map((l) => l === "" ? l : truncateLine(l, cols)).join(`
|
|
4956
|
+
`) + `
|
|
4957
|
+
`;
|
|
4958
|
+
process.stdout.write(output);
|
|
4959
|
+
lastLineCount = lines.length;
|
|
4960
|
+
}
|
|
4961
|
+
return new Promise((resolve7) => {
|
|
4962
|
+
render();
|
|
4963
|
+
process.stdin.setRawMode(true);
|
|
4964
|
+
process.stdin.resume();
|
|
4965
|
+
readline2.emitKeypressEvents(process.stdin);
|
|
4966
|
+
function keypressHandler(_str, key) {
|
|
4967
|
+
if (!key)
|
|
4968
|
+
return;
|
|
4969
|
+
if (key.ctrl && key.name === "c") {
|
|
4970
|
+
cleanup();
|
|
4971
|
+
process.exit(0);
|
|
4972
|
+
}
|
|
4973
|
+
const filtered = getFiltered();
|
|
4974
|
+
if (key.name === "up") {
|
|
4975
|
+
if (filtered.length > 0) {
|
|
4976
|
+
cursor = cursor > 0 ? cursor - 1 : filtered.length - 1;
|
|
4977
|
+
}
|
|
4978
|
+
render();
|
|
4979
|
+
} else if (key.name === "down") {
|
|
4980
|
+
if (filtered.length > 0) {
|
|
4981
|
+
cursor = cursor < filtered.length - 1 ? cursor + 1 : 0;
|
|
4982
|
+
}
|
|
4983
|
+
render();
|
|
4984
|
+
} else if (key.name === "return" || key.name === "space") {
|
|
4985
|
+
const item = filtered[cursor];
|
|
4986
|
+
if (item)
|
|
4987
|
+
item.selected = !item.selected;
|
|
4988
|
+
render();
|
|
4989
|
+
} else if (key.name === "escape") {
|
|
4990
|
+
search = "";
|
|
4991
|
+
cursor = 0;
|
|
4992
|
+
render();
|
|
4993
|
+
} else if (key.ctrl && key.name === "a") {
|
|
4994
|
+
const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
|
|
4995
|
+
for (const item of filtered)
|
|
4996
|
+
item.selected = !allSelected;
|
|
4997
|
+
render();
|
|
4998
|
+
} else if (key.ctrl && key.name === "s") {
|
|
4999
|
+
cleanup();
|
|
5000
|
+
const selected = items.filter((i) => i.selected).map((i) => i.name);
|
|
5001
|
+
process.stdout.write(`
|
|
5002
|
+
`);
|
|
5003
|
+
resolve7(selected);
|
|
5004
|
+
} else if (key.name === "backspace" || key.name === "delete") {
|
|
5005
|
+
if (search.length > 0) {
|
|
5006
|
+
search = search.slice(0, -1);
|
|
5007
|
+
cursor = 0;
|
|
5008
|
+
render();
|
|
5009
|
+
}
|
|
5010
|
+
} else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
|
|
5011
|
+
search += _str;
|
|
5012
|
+
cursor = 0;
|
|
5013
|
+
render();
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
function cleanup() {
|
|
5017
|
+
showCursor();
|
|
5018
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
5019
|
+
process.stdin.setRawMode(false);
|
|
5020
|
+
process.stdin.pause();
|
|
5021
|
+
}
|
|
5022
|
+
process.stdin.on("keypress", keypressHandler);
|
|
5023
|
+
});
|
|
5024
|
+
}
|
|
5025
|
+
var init_install_prompt2 = __esm(() => {
|
|
5026
|
+
init_builtin_policies();
|
|
5027
|
+
init_integrations();
|
|
5028
|
+
});
|
|
5029
|
+
|
|
4257
5030
|
// src/auth/login.ts
|
|
4258
5031
|
var exports_login = {};
|
|
4259
5032
|
__export(exports_login, {
|
|
@@ -4389,14 +5162,14 @@ __export(exports_pid, {
|
|
|
4389
5162
|
isProcessAlive: () => isProcessAlive2,
|
|
4390
5163
|
clearPid: () => clearPid2
|
|
4391
5164
|
});
|
|
4392
|
-
import { readFileSync as
|
|
4393
|
-
import { join as
|
|
4394
|
-
import { homedir as
|
|
5165
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync13, unlinkSync as unlinkSync5, mkdirSync as mkdirSync9 } from "node:fs";
|
|
5166
|
+
import { join as join10, dirname as dirname6 } from "node:path";
|
|
5167
|
+
import { homedir as homedir14 } from "node:os";
|
|
4395
5168
|
function readPid2() {
|
|
4396
|
-
if (!
|
|
5169
|
+
if (!existsSync13(PID_FILE2))
|
|
4397
5170
|
return null;
|
|
4398
5171
|
try {
|
|
4399
|
-
const raw =
|
|
5172
|
+
const raw = readFileSync8(PID_FILE2, "utf8").trim();
|
|
4400
5173
|
const pid = parseInt(raw, 10);
|
|
4401
5174
|
if (Number.isNaN(pid) || pid <= 0)
|
|
4402
5175
|
return null;
|
|
@@ -4406,13 +5179,13 @@ function readPid2() {
|
|
|
4406
5179
|
}
|
|
4407
5180
|
}
|
|
4408
5181
|
function writePid2(pid) {
|
|
4409
|
-
const dir =
|
|
4410
|
-
if (!
|
|
4411
|
-
|
|
4412
|
-
|
|
5182
|
+
const dir = dirname6(PID_FILE2);
|
|
5183
|
+
if (!existsSync13(dir))
|
|
5184
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5185
|
+
writeFileSync7(PID_FILE2, String(pid));
|
|
4413
5186
|
}
|
|
4414
5187
|
function clearPid2() {
|
|
4415
|
-
if (
|
|
5188
|
+
if (existsSync13(PID_FILE2))
|
|
4416
5189
|
unlinkSync5(PID_FILE2);
|
|
4417
5190
|
}
|
|
4418
5191
|
function isProcessAlive2(pid) {
|
|
@@ -4450,26 +5223,26 @@ function relayStatus() {
|
|
|
4450
5223
|
}
|
|
4451
5224
|
var PID_FILE2;
|
|
4452
5225
|
var init_pid2 = __esm(() => {
|
|
4453
|
-
PID_FILE2 =
|
|
5226
|
+
PID_FILE2 = join10(homedir14(), ".failproofai", "relay.pid");
|
|
4454
5227
|
});
|
|
4455
5228
|
|
|
4456
5229
|
// lib/paths.ts
|
|
4457
|
-
import { homedir as
|
|
4458
|
-
import { join as
|
|
5230
|
+
import { homedir as homedir15 } from "os";
|
|
5231
|
+
import { join as join11 } from "path";
|
|
4459
5232
|
function getDefaultClaudeProjectsPath() {
|
|
4460
|
-
return
|
|
5233
|
+
return join11(homedir15(), ".claude", "projects");
|
|
4461
5234
|
}
|
|
4462
5235
|
var init_paths = () => {};
|
|
4463
5236
|
|
|
4464
5237
|
// scripts/parse-script-args.ts
|
|
4465
|
-
import { resolve as
|
|
5238
|
+
import { resolve as resolve7 } from "path";
|
|
4466
5239
|
function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
|
|
4467
5240
|
const raw = inlineValue ?? args[index + 1];
|
|
4468
5241
|
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
4469
5242
|
console.error(`Error: ${flagName} requires ${errorLabel}`);
|
|
4470
5243
|
process.exit(1);
|
|
4471
5244
|
}
|
|
4472
|
-
const value = options?.resolve ?
|
|
5245
|
+
const value = options?.resolve ? resolve7(raw) : raw;
|
|
4473
5246
|
return { value, spliceCount: inlineValue !== null ? 1 : 2 };
|
|
4474
5247
|
}
|
|
4475
5248
|
function parseScriptArgs(argv) {
|
|
@@ -4530,8 +5303,8 @@ __export(exports_launch, {
|
|
|
4530
5303
|
launch: () => launch
|
|
4531
5304
|
});
|
|
4532
5305
|
import { spawn as spawn4 } from "child_process";
|
|
4533
|
-
import { realpathSync, existsSync as
|
|
4534
|
-
import { resolve as
|
|
5306
|
+
import { realpathSync, existsSync as existsSync14 } from "node:fs";
|
|
5307
|
+
import { resolve as resolve8, dirname as dirname7 } from "node:path";
|
|
4535
5308
|
import { fileURLToPath } from "node:url";
|
|
4536
5309
|
function launch(mode) {
|
|
4537
5310
|
const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
@@ -4562,9 +5335,9 @@ function launch(mode) {
|
|
|
4562
5335
|
process.env.PORT = port;
|
|
4563
5336
|
process.env.HOSTNAME = "0.0.0.0";
|
|
4564
5337
|
cmd = "node";
|
|
4565
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
4566
|
-
const serverJsPath =
|
|
4567
|
-
if (!
|
|
5338
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve8(dirname7(realpathSync(fileURLToPath(import.meta.url))), "..");
|
|
5339
|
+
const serverJsPath = resolve8(packageRoot, ".next/standalone/server.js");
|
|
5340
|
+
if (!existsSync14(serverJsPath)) {
|
|
4568
5341
|
console.error(`
|
|
4569
5342
|
Error: Cannot find server.js at:
|
|
4570
5343
|
${serverJsPath}
|
|
@@ -4623,17 +5396,17 @@ var init_cli_error2 = __esm(() => {
|
|
|
4623
5396
|
|
|
4624
5397
|
// bin/failproofai.mjs
|
|
4625
5398
|
import { realpathSync as realpathSync2 } from "fs";
|
|
4626
|
-
import { dirname as
|
|
5399
|
+
import { dirname as dirname8, resolve as resolve9 } from "path";
|
|
4627
5400
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4628
5401
|
// package.json
|
|
4629
|
-
var version = "0.0.
|
|
5402
|
+
var version = "0.0.9-beta.1";
|
|
4630
5403
|
|
|
4631
5404
|
// bin/failproofai.mjs
|
|
4632
5405
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
4633
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
5406
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve9(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
4634
5407
|
}
|
|
4635
5408
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
4636
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
5409
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve9(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
|
|
4637
5410
|
}
|
|
4638
5411
|
var args = process.argv.slice(2);
|
|
4639
5412
|
if (args[0] === "p")
|
|
@@ -4642,12 +5415,16 @@ var hookIdx = args.indexOf("--hook");
|
|
|
4642
5415
|
if (hookIdx >= 0) {
|
|
4643
5416
|
if (!args[hookIdx + 1]) {
|
|
4644
5417
|
console.error("Error: Missing event type after --hook");
|
|
4645
|
-
console.error("Usage: failproofai --hook <event>
|
|
5418
|
+
console.error("Usage: failproofai --hook <event> [--cli <claude|codex>]");
|
|
4646
5419
|
process.exit(1);
|
|
4647
5420
|
}
|
|
5421
|
+
const eventType = args[hookIdx + 1];
|
|
5422
|
+
const cliIdx = args.indexOf("--cli");
|
|
5423
|
+
const cliArg = cliIdx >= 0 ? args[cliIdx + 1] : undefined;
|
|
5424
|
+
const cli = cliArg && (cliArg === "claude" || cliArg === "codex") ? cliArg : "claude";
|
|
4648
5425
|
try {
|
|
4649
5426
|
const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
|
|
4650
|
-
const exitCode = await handleHookEvent2(
|
|
5427
|
+
const exitCode = await handleHookEvent2(eventType, cli);
|
|
4651
5428
|
process.exit(exitCode);
|
|
4652
5429
|
} catch (err) {
|
|
4653
5430
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -4684,14 +5461,19 @@ COMMANDS
|
|
|
4684
5461
|
(no args) Launch the policy dashboard
|
|
4685
5462
|
|
|
4686
5463
|
policies, p List all available policies and their status
|
|
4687
|
-
policies --install, -i Enable policies in
|
|
5464
|
+
policies --install, -i Enable policies in agent CLI settings
|
|
4688
5465
|
[names...] Specific policy names to enable
|
|
5466
|
+
--cli claude|codex Agent CLI(s) to install for; space-separated
|
|
5467
|
+
(e.g. --cli claude codex) or repeated.
|
|
5468
|
+
Default: detect installed CLIs and prompt.
|
|
4689
5469
|
--scope user|project|local Config scope to write to (default: user)
|
|
5470
|
+
(Codex supports user|project only)
|
|
4690
5471
|
--beta Include beta policies
|
|
4691
5472
|
--custom, -c <path> Path to a JS file of custom policies
|
|
4692
5473
|
|
|
4693
5474
|
policies --uninstall, -u Disable policies or remove hooks
|
|
4694
5475
|
[names...] Specific policy names to disable
|
|
5476
|
+
--cli claude|codex Agent CLI(s) to uninstall from
|
|
4695
5477
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
4696
5478
|
--beta Remove only beta policies
|
|
4697
5479
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -4716,9 +5498,12 @@ EXAMPLES
|
|
|
4716
5498
|
failproofai policies
|
|
4717
5499
|
failproofai policies --install
|
|
4718
5500
|
failproofai policies --install block-sudo sanitize-api-keys --scope project
|
|
5501
|
+
failproofai policies --install --cli codex --scope project
|
|
5502
|
+
failproofai policies --install --cli claude codex
|
|
4719
5503
|
failproofai policies --install --custom ./my-policies.js
|
|
4720
5504
|
failproofai policies -i -c ./my-policies.js
|
|
4721
5505
|
failproofai policies --uninstall block-sudo
|
|
5506
|
+
failproofai policies --uninstall --cli codex
|
|
4722
5507
|
failproofai policies --uninstall --custom
|
|
4723
5508
|
|
|
4724
5509
|
LINKS
|
|
@@ -4752,13 +5537,19 @@ USAGE
|
|
|
4752
5537
|
|
|
4753
5538
|
OPTIONS (install)
|
|
4754
5539
|
[names...] Specific policy names to enable (omit for interactive)
|
|
5540
|
+
--cli claude|codex Agent CLI(s) to install for; space-separated
|
|
5541
|
+
(e.g. --cli claude codex) or repeated. Omit to
|
|
5542
|
+
detect installed CLIs and prompt (or auto-pick
|
|
5543
|
+
if only one is found).
|
|
4755
5544
|
--scope user|project|local Config scope to write to (default: user)
|
|
5545
|
+
(Codex supports user|project only)
|
|
4756
5546
|
--beta Include beta policies
|
|
4757
5547
|
--custom, -c <path> Path to a JS file of custom policies
|
|
4758
5548
|
(skips interactive prompt; validates file first)
|
|
4759
5549
|
|
|
4760
5550
|
OPTIONS (uninstall)
|
|
4761
5551
|
[names...] Specific policy names to disable (omit to remove hooks)
|
|
5552
|
+
--cli claude|codex Agent CLI(s) to uninstall from
|
|
4762
5553
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
4763
5554
|
--beta Remove only beta policies
|
|
4764
5555
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -4767,9 +5558,12 @@ EXAMPLES
|
|
|
4767
5558
|
failproofai policies
|
|
4768
5559
|
failproofai policies --install
|
|
4769
5560
|
failproofai policies --install block-sudo sanitize-api-keys
|
|
5561
|
+
failproofai policies --install --cli codex --scope project
|
|
5562
|
+
failproofai policies --install --cli claude codex
|
|
4770
5563
|
failproofai policies --install --custom ./my-policies.js
|
|
4771
5564
|
failproofai policies -i -c ./my-policies.js
|
|
4772
5565
|
failproofai policies --uninstall block-sudo
|
|
5566
|
+
failproofai policies --uninstall --cli codex
|
|
4773
5567
|
failproofai policies -u
|
|
4774
5568
|
failproofai policies --uninstall --custom
|
|
4775
5569
|
`.trimStart());
|
|
@@ -4777,6 +5571,7 @@ EXAMPLES
|
|
|
4777
5571
|
}
|
|
4778
5572
|
if (isInstall) {
|
|
4779
5573
|
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
5574
|
+
const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
|
|
4780
5575
|
const scopeIdx = subArgs.indexOf("--scope");
|
|
4781
5576
|
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
4782
5577
|
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
@@ -4791,13 +5586,35 @@ EXAMPLES
|
|
|
4791
5586
|
throw new CliError3(`Missing path after --custom/-c
|
|
4792
5587
|
Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
4793
5588
|
}
|
|
5589
|
+
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
5590
|
+
const cliFlagValues = [];
|
|
5591
|
+
const cliConsumedIdxs = new Set;
|
|
5592
|
+
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
5593
|
+
for (const idx of cliFlagIdxs) {
|
|
5594
|
+
let consumed = 0;
|
|
5595
|
+
for (let j = idx + 1;j < subArgs.length; j++) {
|
|
5596
|
+
const v = subArgs[j];
|
|
5597
|
+
if (v.startsWith("-"))
|
|
5598
|
+
break;
|
|
5599
|
+
if (!VALID_CLIS.has(v))
|
|
5600
|
+
break;
|
|
5601
|
+
cliFlagValues.push(v);
|
|
5602
|
+
cliConsumedIdxs.add(j);
|
|
5603
|
+
consumed++;
|
|
5604
|
+
}
|
|
5605
|
+
if (consumed === 0) {
|
|
5606
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
4794
5609
|
const includeBeta = subArgs.includes("--beta");
|
|
4795
5610
|
const consumedIdxs = new Set;
|
|
4796
5611
|
if (scopeIdx >= 0)
|
|
4797
5612
|
consumedIdxs.add(scopeIdx + 1);
|
|
4798
5613
|
if (customIdx >= 0)
|
|
4799
5614
|
consumedIdxs.add(customIdx + 1);
|
|
4800
|
-
|
|
5615
|
+
for (const i of cliConsumedIdxs)
|
|
5616
|
+
consumedIdxs.add(i);
|
|
5617
|
+
const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c", "--cli"]);
|
|
4801
5618
|
const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
4802
5619
|
if (unknownInstallFlag) {
|
|
4803
5620
|
throw new CliError3(`Unknown flag: ${unknownInstallFlag}
|
|
@@ -4805,11 +5622,13 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
4805
5622
|
}
|
|
4806
5623
|
const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
4807
5624
|
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
|
|
4808
|
-
await
|
|
5625
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
5626
|
+
await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath, false, cli);
|
|
4809
5627
|
process.exit(0);
|
|
4810
5628
|
}
|
|
4811
5629
|
if (isUninstall) {
|
|
4812
5630
|
const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
5631
|
+
const { resolveTargetClis: resolveTargetClis2 } = await Promise.resolve().then(() => (init_install_prompt2(), exports_install_prompt));
|
|
4813
5632
|
const scopeIdx = subArgs.indexOf("--scope");
|
|
4814
5633
|
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
4815
5634
|
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
@@ -4818,19 +5637,42 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
4818
5637
|
if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
|
|
4819
5638
|
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
|
|
4820
5639
|
}
|
|
5640
|
+
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
5641
|
+
const cliFlagValues = [];
|
|
5642
|
+
const cliConsumedIdxs = new Set;
|
|
5643
|
+
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
5644
|
+
for (const idx of cliFlagIdxs) {
|
|
5645
|
+
let consumed = 0;
|
|
5646
|
+
for (let j = idx + 1;j < subArgs.length; j++) {
|
|
5647
|
+
const v = subArgs[j];
|
|
5648
|
+
if (v.startsWith("-"))
|
|
5649
|
+
break;
|
|
5650
|
+
if (!VALID_CLIS.has(v))
|
|
5651
|
+
break;
|
|
5652
|
+
cliFlagValues.push(v);
|
|
5653
|
+
cliConsumedIdxs.add(j);
|
|
5654
|
+
consumed++;
|
|
5655
|
+
}
|
|
5656
|
+
if (consumed === 0) {
|
|
5657
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
4821
5660
|
const betaOnly = subArgs.includes("--beta");
|
|
4822
5661
|
const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
|
|
4823
5662
|
const consumedIdxs = new Set;
|
|
4824
5663
|
if (scopeIdx >= 0)
|
|
4825
5664
|
consumedIdxs.add(scopeIdx + 1);
|
|
4826
|
-
|
|
5665
|
+
for (const i of cliConsumedIdxs)
|
|
5666
|
+
consumedIdxs.add(i);
|
|
5667
|
+
const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c", "--cli"]);
|
|
4827
5668
|
const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
4828
5669
|
if (unknownUninstallFlag) {
|
|
4829
5670
|
throw new CliError3(`Unknown flag: ${unknownUninstallFlag}
|
|
4830
5671
|
Run \`failproofai policies --help\` for usage.`);
|
|
4831
5672
|
}
|
|
4832
5673
|
const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
4833
|
-
await
|
|
5674
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
5675
|
+
await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
|
|
4834
5676
|
process.exit(0);
|
|
4835
5677
|
}
|
|
4836
5678
|
const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
|