failproofai 0.0.9 → 0.0.10-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/.cursor/hooks.json +47 -0
- package/.next/standalone/.gemini/settings.json +147 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +1 -1
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.~nmr9._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0yspgjy._.js → [root-of-the-server]__010i6f5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08px0ym._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b57.gk._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dtn9lr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vlhtkc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wu7fr7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfq1yr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z4c5dj._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zso~62._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-2wr.c._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0.~m-w2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09icjsf._.js → [root-of-the-server]__0709m8.._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bz245.._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dl0kgt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gmhxyo._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0_b7pgn._.js → [root-of-the-server]__0lkkjl_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01g_w_e._.js → [root-of-the-server]__0mb9b9d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mup1hi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ohb3gc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qbpe_v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s~gy6y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t5l7a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +152 -6
- package/.next/standalone/.next/server/chunks/ssr/_03d7qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_07a1g.3._.js → _0zx~s__._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_codex-projects_ts_0eosib~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_copilot-projects_ts_0r8xkn8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_cursor-projects_ts_0qt1scg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_0sl~yqr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_opencode-projects_ts_0op9gyp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_pi-projects_ts_103tsh1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0n-_j_6fo6jex.js → 0-wd3kiz5wrsz.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0756i.7omnnl6.js → 0222q~_4u7p6h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0t~iusm_fxoao.js → 02y~6tp1j1wkh.js} +1 -1
- package/.next/standalone/.next/static/chunks/{09ose_165ra4d.js → 09qdljea8j.3~.js} +1 -1
- package/.next/standalone/.next/static/chunks/0bi2r.m~yokoo.js +1 -0
- package/.next/standalone/.next/static/chunks/{11kt_9zaooda3.js → 0pt38lwlsaxvs.js} +1 -1
- package/.next/standalone/.next/static/chunks/0q5bmqop--9yk.js +1 -0
- package/.next/standalone/.next/static/chunks/{0u-ys71jc4y68.js → 0vl201wjmz17m.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0pr7k36o_.du1.js → 0vl~p17i-4qt2.js} +1 -1
- package/.next/standalone/.next/static/chunks/0xkzmsj-sniqz.js +1 -0
- package/.next/standalone/.next/static/chunks/12po2vpc-4_c1.css +1 -0
- package/.next/standalone/.opencode/opencode.json +4 -0
- package/.next/standalone/.opencode/plugins/failproofai.mjs +131 -0
- package/.next/standalone/.pi/settings.json +5 -0
- package/.next/standalone/app/components/cli-badge.tsx +7 -11
- package/.next/standalone/app/components/project-list.tsx +32 -4
- package/.next/standalone/app/policies/hooks-client.tsx +31 -15
- package/.next/standalone/app/project/[name]/page.tsx +52 -16
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +92 -15
- package/.next/standalone/assets/logos/copilot-dark.svg +1 -0
- package/.next/standalone/assets/logos/copilot-light.svg +1 -0
- package/.next/standalone/assets/logos/cursor-dark.svg +1 -0
- package/.next/standalone/assets/logos/cursor-light.svg +1 -0
- package/.next/standalone/assets/logos/gemini-dark.svg +13 -0
- package/.next/standalone/assets/logos/gemini-light.svg +13 -0
- package/.next/standalone/assets/logos/opencode-dark.svg +1 -0
- package/.next/standalone/assets/logos/opencode-light.svg +1 -0
- package/.next/standalone/assets/logos/pi-dark.svg +7 -0
- package/.next/standalone/assets/logos/pi-light.svg +7 -0
- package/.next/standalone/lib/cli-registry.ts +107 -0
- package/.next/standalone/lib/codex-projects.ts +3 -3
- package/.next/standalone/lib/copilot-projects.ts +224 -0
- package/.next/standalone/lib/copilot-sessions.ts +395 -0
- package/.next/standalone/lib/cursor-projects.ts +312 -0
- package/.next/standalone/lib/cursor-sessions.ts +467 -0
- package/.next/standalone/lib/gemini-projects.ts +203 -0
- package/.next/standalone/lib/gemini-sessions.ts +365 -0
- package/.next/standalone/lib/opencode-projects.ts +232 -0
- package/.next/standalone/lib/opencode-sessions.ts +237 -0
- package/.next/standalone/lib/pi-projects.ts +230 -0
- package/.next/standalone/lib/pi-sessions.ts +325 -0
- package/.next/standalone/lib/projects.ts +67 -31
- package/.next/standalone/next.config.ts +5 -4
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/pi-extension/index.ts +373 -0
- package/.next/standalone/pi-extension/package.json +12 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +37 -3
- package/bin/failproofai.mjs +61 -21
- package/dist/cli.mjs +2405 -253
- package/lib/cli-registry.ts +107 -0
- package/lib/codex-projects.ts +3 -3
- package/lib/copilot-projects.ts +224 -0
- package/lib/copilot-sessions.ts +395 -0
- package/lib/cursor-projects.ts +312 -0
- package/lib/cursor-sessions.ts +467 -0
- package/lib/gemini-projects.ts +203 -0
- package/lib/gemini-sessions.ts +365 -0
- package/lib/opencode-projects.ts +232 -0
- package/lib/opencode-sessions.ts +237 -0
- package/lib/pi-projects.ts +230 -0
- package/lib/pi-sessions.ts +325 -0
- package/lib/projects.ts +67 -31
- package/package.json +2 -1
- package/pi-extension/index.ts +373 -0
- package/pi-extension/package.json +12 -0
- package/scripts/install-diagnosis.mjs +190 -0
- package/scripts/launch.ts +32 -0
- package/scripts/postinstall.mjs +25 -0
- package/scripts/translate-docs/mdx-translator.ts +56 -2
- package/scripts/translate-docs/translator.ts +1 -1
- package/src/hooks/builtin-policies.ts +84 -14
- package/src/hooks/handler.ts +67 -5
- package/src/hooks/install-prompt.ts +33 -10
- package/src/hooks/integrations.ts +1007 -6
- package/src/hooks/policy-evaluator.ts +299 -3
- package/src/hooks/resolve-permission-mode.ts +23 -0
- package/src/hooks/types.ts +307 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0su~k6f._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_codex-projects_ts_07qqk1g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01743wx._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gs6wz4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0it81ys._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u4a9jq._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12.h2mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_04w00cm._.js +0 -3
- package/.next/standalone/.next/static/chunks/0.rk1iwdt1d7c.css +0 -1
- package/.next/standalone/.next/static/chunks/06x4-d1~o-opr.js +0 -1
- package/.next/standalone/.next/static/chunks/095l4hc7-h.~~.js +0 -1
- package/.next/standalone/.next/static/chunks/0n~s0gafwnp2y.js +0 -1
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → w0GG7S5UEj1-p5g9hfsh2}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → w0GG7S5UEj1-p5g9hfsh2}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → w0GG7S5UEj1-p5g9hfsh2}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -16,10 +16,10 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
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__";
|
|
19
|
+
var HOOK_SCOPES, INTEGRATION_TYPES, CODEX_HOOK_SCOPES, CODEX_HOOK_EVENT_TYPES, CODEX_EVENT_MAP, COPILOT_HOOK_SCOPES, COPILOT_HOOK_EVENT_TYPES, CURSOR_HOOK_SCOPES, CURSOR_HOOK_EVENT_TYPES, CURSOR_EVENT_MAP, OPENCODE_HOOK_SCOPES, OPENCODE_HOOK_EVENT_TYPES, PI_HOOK_SCOPES, PI_HOOK_EVENT_TYPES, PI_EVENT_MAP, GEMINI_HOOK_SCOPES, GEMINI_HOOK_EVENT_TYPES, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
20
20
|
var init_types = __esm(() => {
|
|
21
21
|
HOOK_SCOPES = ["user", "project", "local"];
|
|
22
|
-
INTEGRATION_TYPES = ["claude", "codex"];
|
|
22
|
+
INTEGRATION_TYPES = ["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"];
|
|
23
23
|
CODEX_HOOK_SCOPES = ["user", "project"];
|
|
24
24
|
CODEX_HOOK_EVENT_TYPES = [
|
|
25
25
|
"session_start",
|
|
@@ -37,6 +37,103 @@ var init_types = __esm(() => {
|
|
|
37
37
|
user_prompt_submit: "UserPromptSubmit",
|
|
38
38
|
stop: "Stop"
|
|
39
39
|
};
|
|
40
|
+
COPILOT_HOOK_SCOPES = ["user", "project"];
|
|
41
|
+
COPILOT_HOOK_EVENT_TYPES = [
|
|
42
|
+
"SessionStart",
|
|
43
|
+
"SessionEnd",
|
|
44
|
+
"UserPromptSubmit",
|
|
45
|
+
"PreToolUse",
|
|
46
|
+
"PostToolUse",
|
|
47
|
+
"Stop"
|
|
48
|
+
];
|
|
49
|
+
CURSOR_HOOK_SCOPES = ["user", "project"];
|
|
50
|
+
CURSOR_HOOK_EVENT_TYPES = [
|
|
51
|
+
"sessionStart",
|
|
52
|
+
"sessionEnd",
|
|
53
|
+
"beforeSubmitPrompt",
|
|
54
|
+
"preToolUse",
|
|
55
|
+
"postToolUse",
|
|
56
|
+
"stop"
|
|
57
|
+
];
|
|
58
|
+
CURSOR_EVENT_MAP = {
|
|
59
|
+
sessionStart: "SessionStart",
|
|
60
|
+
sessionEnd: "SessionEnd",
|
|
61
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
62
|
+
preToolUse: "PreToolUse",
|
|
63
|
+
postToolUse: "PostToolUse",
|
|
64
|
+
stop: "Stop"
|
|
65
|
+
};
|
|
66
|
+
OPENCODE_HOOK_SCOPES = ["user", "project"];
|
|
67
|
+
OPENCODE_HOOK_EVENT_TYPES = [
|
|
68
|
+
"tool.execute.before",
|
|
69
|
+
"tool.execute.after",
|
|
70
|
+
"session.created",
|
|
71
|
+
"session.deleted",
|
|
72
|
+
"session.idle",
|
|
73
|
+
"message.updated",
|
|
74
|
+
"permission.ask"
|
|
75
|
+
];
|
|
76
|
+
PI_HOOK_SCOPES = ["user", "project"];
|
|
77
|
+
PI_HOOK_EVENT_TYPES = [
|
|
78
|
+
"session_start",
|
|
79
|
+
"session_shutdown",
|
|
80
|
+
"input",
|
|
81
|
+
"tool_call",
|
|
82
|
+
"user_bash",
|
|
83
|
+
"tool_result",
|
|
84
|
+
"agent_end"
|
|
85
|
+
];
|
|
86
|
+
PI_EVENT_MAP = {
|
|
87
|
+
session_start: "SessionStart",
|
|
88
|
+
session_shutdown: "SessionEnd",
|
|
89
|
+
input: "UserPromptSubmit",
|
|
90
|
+
tool_call: "PreToolUse",
|
|
91
|
+
user_bash: "PreToolUse",
|
|
92
|
+
tool_result: "PostToolUse",
|
|
93
|
+
agent_end: "Stop"
|
|
94
|
+
};
|
|
95
|
+
GEMINI_HOOK_SCOPES = ["user", "project"];
|
|
96
|
+
GEMINI_HOOK_EVENT_TYPES = [
|
|
97
|
+
"SessionStart",
|
|
98
|
+
"SessionEnd",
|
|
99
|
+
"BeforeAgent",
|
|
100
|
+
"AfterAgent",
|
|
101
|
+
"BeforeModel",
|
|
102
|
+
"AfterModel",
|
|
103
|
+
"BeforeToolSelection",
|
|
104
|
+
"BeforeTool",
|
|
105
|
+
"AfterTool",
|
|
106
|
+
"PreCompress",
|
|
107
|
+
"Notification"
|
|
108
|
+
];
|
|
109
|
+
GEMINI_EVENT_MAP = {
|
|
110
|
+
SessionStart: "SessionStart",
|
|
111
|
+
SessionEnd: "SessionEnd",
|
|
112
|
+
BeforeAgent: "UserPromptSubmit",
|
|
113
|
+
AfterAgent: "Stop",
|
|
114
|
+
BeforeTool: "PreToolUse",
|
|
115
|
+
AfterTool: "PostToolUse",
|
|
116
|
+
PreCompress: "PreCompact",
|
|
117
|
+
Notification: "Notification",
|
|
118
|
+
BeforeModel: "BeforeModel",
|
|
119
|
+
AfterModel: "AfterModel",
|
|
120
|
+
BeforeToolSelection: "BeforeToolSelection"
|
|
121
|
+
};
|
|
122
|
+
GEMINI_TOOL_MAP = {
|
|
123
|
+
run_shell_command: "Bash",
|
|
124
|
+
read_file: "Read",
|
|
125
|
+
read_many_files: "Read",
|
|
126
|
+
write_file: "Write",
|
|
127
|
+
replace: "Edit",
|
|
128
|
+
glob: "Glob",
|
|
129
|
+
grep_search: "Grep",
|
|
130
|
+
list_directory: "LS",
|
|
131
|
+
web_fetch: "WebFetch",
|
|
132
|
+
google_web_search: "WebSearch",
|
|
133
|
+
write_todos: "TodoWrite",
|
|
134
|
+
save_memory: "Memory",
|
|
135
|
+
ask_user: "AskUser"
|
|
136
|
+
};
|
|
40
137
|
HOOK_EVENT_TYPES = [
|
|
41
138
|
"SessionStart",
|
|
42
139
|
"SessionEnd",
|
|
@@ -332,9 +429,15 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
332
429
|
import { execSync, execFileSync } from "node:child_process";
|
|
333
430
|
import { homedir as homedir3 } from "node:os";
|
|
334
431
|
function isAgentInternalPath(resolved2) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
432
|
+
const normResolved = resolved2.replaceAll("\\", "/");
|
|
433
|
+
for (const dir of [".claude", ".codex", ".copilot", ".cursor", ".opencode", ".pi", ".gemini"]) {
|
|
434
|
+
const root = join2(homedir3(), dir).replaceAll("\\", "/");
|
|
435
|
+
if (normResolved === root || normResolved.startsWith(root + "/"))
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
for (const sub of [join2(".config", "opencode"), join2(".local", "share", "opencode")]) {
|
|
439
|
+
const root = join2(homedir3(), sub).replaceAll("\\", "/");
|
|
440
|
+
if (normResolved === root || normResolved.startsWith(root + "/"))
|
|
338
441
|
return true;
|
|
339
442
|
}
|
|
340
443
|
return false;
|
|
@@ -344,6 +447,30 @@ function isAgentSettingsFile(resolved2) {
|
|
|
344
447
|
return true;
|
|
345
448
|
if (/[\\/]\.codex[\\/]hooks\.json$/.test(resolved2))
|
|
346
449
|
return true;
|
|
450
|
+
if (/[\\/]\.copilot[\\/]hooks[\\/][^/\\]+\.json$/.test(resolved2))
|
|
451
|
+
return true;
|
|
452
|
+
if (/[\\/]\.github[\\/]hooks[\\/][^/\\]+\.json$/.test(resolved2))
|
|
453
|
+
return true;
|
|
454
|
+
if (/[\\/]\.cursor[\\/]hooks\.json$/.test(resolved2))
|
|
455
|
+
return true;
|
|
456
|
+
if (/[\\/]\.opencode[\\/]opencode\.jsonc?$/.test(resolved2))
|
|
457
|
+
return true;
|
|
458
|
+
if (/[\\/]\.opencode[\\/]plugins[\\/][^/\\]+\.(?:mjs|js|ts)$/.test(resolved2))
|
|
459
|
+
return true;
|
|
460
|
+
if (/[\\/]\.config[\\/]opencode[\\/]opencode\.jsonc?$/.test(resolved2))
|
|
461
|
+
return true;
|
|
462
|
+
if (/[\\/]\.config[\\/]opencode[\\/]config\.json$/.test(resolved2))
|
|
463
|
+
return true;
|
|
464
|
+
if (/[\\/]\.config[\\/]opencode[\\/]plugins[\\/][^/\\]+\.(?:mjs|js|ts)$/.test(resolved2))
|
|
465
|
+
return true;
|
|
466
|
+
if (/[\\/]\.pi[\\/](?:agent[\\/])?settings\.json$/.test(resolved2))
|
|
467
|
+
return true;
|
|
468
|
+
if (/[\\/]\.pi[\\/](?:agent[\\/])?extensions[\\/]/.test(resolved2))
|
|
469
|
+
return true;
|
|
470
|
+
if (/[\\/]\.gemini[\\/]settings\.json$/.test(resolved2))
|
|
471
|
+
return true;
|
|
472
|
+
if (/[\\/]\.gemini[\\/]hooks[\\/]/.test(resolved2))
|
|
473
|
+
return true;
|
|
347
474
|
return false;
|
|
348
475
|
}
|
|
349
476
|
function getCommand(ctx) {
|
|
@@ -780,7 +907,7 @@ function blockReadOutsideCwd(ctx) {
|
|
|
780
907
|
for (const p of paths) {
|
|
781
908
|
const resolved3 = resolve2(cwd, p);
|
|
782
909
|
if (isClaudeSettingsFile(resolved3)) {
|
|
783
|
-
return deny(`Reading
|
|
910
|
+
return deny(`Reading agent settings file blocked: ${resolved3}`);
|
|
784
911
|
}
|
|
785
912
|
if (isClaudeInternalPath(resolved3))
|
|
786
913
|
continue;
|
|
@@ -801,7 +928,7 @@ function blockReadOutsideCwd(ctx) {
|
|
|
801
928
|
return allow();
|
|
802
929
|
const resolved2 = resolve2(cwd, target);
|
|
803
930
|
if (isClaudeSettingsFile(resolved2)) {
|
|
804
|
-
return deny(`Reading
|
|
931
|
+
return deny(`Reading agent settings file blocked: ${resolved2}`);
|
|
805
932
|
}
|
|
806
933
|
if (isClaudeInternalPath(resolved2))
|
|
807
934
|
return allow();
|
|
@@ -1228,16 +1355,24 @@ function requireCiGreenBeforeStop(ctx) {
|
|
|
1228
1355
|
const branch = getCurrentBranch(cwd);
|
|
1229
1356
|
if (!branch || branch === "HEAD")
|
|
1230
1357
|
return allow("Detached HEAD, skipping CI check.");
|
|
1358
|
+
const sha = getHeadSha(cwd);
|
|
1231
1359
|
let workflowRuns = [];
|
|
1232
1360
|
try {
|
|
1233
|
-
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "
|
|
1361
|
+
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "20", "--json", "status,conclusion,name,headSha"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 }).trim();
|
|
1234
1362
|
if (runsJson && runsJson !== "[]") {
|
|
1235
|
-
|
|
1363
|
+
const allWorkflowRuns = JSON.parse(runsJson);
|
|
1364
|
+
const headRuns = sha ? allWorkflowRuns.filter((r) => r.headSha === sha) : allWorkflowRuns;
|
|
1365
|
+
const seen = new Set;
|
|
1366
|
+
workflowRuns = headRuns.filter((r) => {
|
|
1367
|
+
if (seen.has(r.name))
|
|
1368
|
+
return false;
|
|
1369
|
+
seen.add(r.name);
|
|
1370
|
+
return true;
|
|
1371
|
+
});
|
|
1236
1372
|
}
|
|
1237
1373
|
} catch {}
|
|
1238
1374
|
let thirdPartyChecks = [];
|
|
1239
1375
|
let commitStatuses = [];
|
|
1240
|
-
const sha = getHeadSha(cwd);
|
|
1241
1376
|
if (sha) {
|
|
1242
1377
|
thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
|
|
1243
1378
|
commitStatuses = getCommitStatuses(cwd, sha);
|
|
@@ -1803,7 +1938,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
1803
1938
|
},
|
|
1804
1939
|
{
|
|
1805
1940
|
name: "require-ci-green-before-stop",
|
|
1806
|
-
description: "Require CI checks to pass on the current
|
|
1941
|
+
description: "Require CI checks to pass on the current HEAD commit before Claude stops (ignores stale runs on prior commits)",
|
|
1807
1942
|
fn: requireCiGreenBeforeStop,
|
|
1808
1943
|
match: { events: ["Stop"] },
|
|
1809
1944
|
defaultEnabled: false,
|
|
@@ -1874,13 +2009,79 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1874
2009
|
if (result.decision === "deny") {
|
|
1875
2010
|
const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, getConfigParamsFor(config, policy.name)?.hint);
|
|
1876
2011
|
hookLogInfo(`deny by "${policy.name}": ${reason}`);
|
|
1877
|
-
|
|
2012
|
+
let displayTool;
|
|
2013
|
+
if (ctx.toolName) {
|
|
2014
|
+
displayTool = ctx.toolName;
|
|
2015
|
+
} else if (eventType === "UserPromptSubmit") {
|
|
2016
|
+
displayTool = "prompt";
|
|
2017
|
+
} else if (eventType === "SessionStart") {
|
|
2018
|
+
displayTool = "session start";
|
|
2019
|
+
} else if (eventType === "SessionEnd") {
|
|
2020
|
+
displayTool = "session end";
|
|
2021
|
+
} else if (eventType === "Stop") {
|
|
2022
|
+
displayTool = "stop";
|
|
2023
|
+
} else {
|
|
2024
|
+
displayTool = "operation";
|
|
2025
|
+
}
|
|
2026
|
+
const blockedMessage = `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`;
|
|
2027
|
+
if (session?.cli === "cursor") {
|
|
2028
|
+
const response = {
|
|
2029
|
+
permission: "deny",
|
|
2030
|
+
user_message: blockedMessage,
|
|
2031
|
+
agent_message: blockedMessage
|
|
2032
|
+
};
|
|
2033
|
+
return {
|
|
2034
|
+
exitCode: 0,
|
|
2035
|
+
stdout: JSON.stringify(response),
|
|
2036
|
+
stderr: "",
|
|
2037
|
+
policyName: policy.name,
|
|
2038
|
+
reason,
|
|
2039
|
+
decision: "deny"
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
if (session?.cli === "pi") {
|
|
2043
|
+
const response = {
|
|
2044
|
+
permission: "deny",
|
|
2045
|
+
reason: blockedMessage
|
|
2046
|
+
};
|
|
2047
|
+
return {
|
|
2048
|
+
exitCode: 0,
|
|
2049
|
+
stdout: JSON.stringify(response),
|
|
2050
|
+
stderr: "",
|
|
2051
|
+
policyName: policy.name,
|
|
2052
|
+
reason,
|
|
2053
|
+
decision: "deny"
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
if (session?.cli === "gemini") {
|
|
2057
|
+
if (eventType === "Stop") {
|
|
2058
|
+
const reasonText = `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}
|
|
2059
|
+
|
|
2060
|
+
You MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`;
|
|
2061
|
+
return {
|
|
2062
|
+
exitCode: 0,
|
|
2063
|
+
stdout: JSON.stringify({ decision: "block", reason: reasonText }),
|
|
2064
|
+
stderr: "",
|
|
2065
|
+
policyName: policy.name,
|
|
2066
|
+
reason,
|
|
2067
|
+
decision: "deny"
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
return {
|
|
2071
|
+
exitCode: 0,
|
|
2072
|
+
stdout: JSON.stringify({ decision: "deny", reason: blockedMessage }),
|
|
2073
|
+
stderr: "",
|
|
2074
|
+
policyName: policy.name,
|
|
2075
|
+
reason,
|
|
2076
|
+
decision: "deny"
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
1878
2079
|
if (eventType === "PreToolUse") {
|
|
1879
2080
|
const response = {
|
|
1880
2081
|
hookSpecificOutput: {
|
|
1881
2082
|
hookEventName: eventType,
|
|
1882
2083
|
permissionDecision: "deny",
|
|
1883
|
-
permissionDecisionReason:
|
|
2084
|
+
permissionDecisionReason: blockedMessage
|
|
1884
2085
|
}
|
|
1885
2086
|
};
|
|
1886
2087
|
return {
|
|
@@ -1961,6 +2162,98 @@ You MUST complete the above action NOW. Do NOT ask the user for confirmation —
|
|
|
1961
2162
|
const combined = instructEntries.map((e) => e.reason).join(`
|
|
1962
2163
|
`);
|
|
1963
2164
|
const policyNames = instructEntries.map((e) => e.policyName);
|
|
2165
|
+
if (session?.cli === "cursor") {
|
|
2166
|
+
if (eventType === "Stop") {
|
|
2167
|
+
const response3 = {
|
|
2168
|
+
followup_message: `Instruction from failproofai: ${combined}`
|
|
2169
|
+
};
|
|
2170
|
+
return {
|
|
2171
|
+
exitCode: 0,
|
|
2172
|
+
stdout: JSON.stringify(response3),
|
|
2173
|
+
stderr: "",
|
|
2174
|
+
policyName: policyNames[0],
|
|
2175
|
+
policyNames,
|
|
2176
|
+
reason: combined,
|
|
2177
|
+
decision: "instruct"
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
const response2 = {
|
|
2181
|
+
permission: "allow",
|
|
2182
|
+
additional_context: `Instruction from failproofai: ${combined}`
|
|
2183
|
+
};
|
|
2184
|
+
return {
|
|
2185
|
+
exitCode: 0,
|
|
2186
|
+
stdout: JSON.stringify(response2),
|
|
2187
|
+
stderr: "",
|
|
2188
|
+
policyName: policyNames[0],
|
|
2189
|
+
policyNames,
|
|
2190
|
+
reason: combined,
|
|
2191
|
+
decision: "instruct"
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
if (session?.cli === "pi") {
|
|
2195
|
+
const response2 = {
|
|
2196
|
+
permission: "allow",
|
|
2197
|
+
reason: `Instruction from failproofai: ${combined}`
|
|
2198
|
+
};
|
|
2199
|
+
return {
|
|
2200
|
+
exitCode: 0,
|
|
2201
|
+
stdout: JSON.stringify(response2),
|
|
2202
|
+
stderr: "",
|
|
2203
|
+
policyName: policyNames[0],
|
|
2204
|
+
policyNames,
|
|
2205
|
+
reason: combined,
|
|
2206
|
+
decision: "instruct"
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
if (session?.cli === "gemini") {
|
|
2210
|
+
if (eventType === "Stop") {
|
|
2211
|
+
const policyAttribution = policyNames.length === 1 ? `policy: ${policyNames[0]}` : `policies: ${policyNames.join(", ")}`;
|
|
2212
|
+
const reasonText = `MANDATORY ACTION REQUIRED from failproofai (${policyAttribution}): ${combined}
|
|
2213
|
+
|
|
2214
|
+
You MUST complete the above action(s) NOW. Do NOT ask the user for confirmation — execute the required action(s), then attempt to finish your task again.`;
|
|
2215
|
+
return {
|
|
2216
|
+
exitCode: 0,
|
|
2217
|
+
stdout: JSON.stringify({ decision: "block", reason: reasonText }),
|
|
2218
|
+
stderr: "",
|
|
2219
|
+
policyName: policyNames[0],
|
|
2220
|
+
policyNames,
|
|
2221
|
+
reason: combined,
|
|
2222
|
+
decision: "instruct"
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const supportsContext = eventType === "UserPromptSubmit" || eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "SessionStart";
|
|
2226
|
+
if (supportsContext) {
|
|
2227
|
+
const hookEventName = session?.hookEventName ?? session?.rawHookEventName ?? eventType;
|
|
2228
|
+
const response2 = {
|
|
2229
|
+
hookSpecificOutput: {
|
|
2230
|
+
hookEventName,
|
|
2231
|
+
additionalContext: `Instruction from failproofai: ${combined}`
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
return {
|
|
2235
|
+
exitCode: 0,
|
|
2236
|
+
stdout: JSON.stringify(response2),
|
|
2237
|
+
stderr: "",
|
|
2238
|
+
policyName: policyNames[0],
|
|
2239
|
+
policyNames,
|
|
2240
|
+
reason: combined,
|
|
2241
|
+
decision: "instruct"
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
const stderrMsg = instructEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
2245
|
+
`);
|
|
2246
|
+
return {
|
|
2247
|
+
exitCode: 0,
|
|
2248
|
+
stdout: "",
|
|
2249
|
+
stderr: stderrMsg + `
|
|
2250
|
+
`,
|
|
2251
|
+
policyName: policyNames[0],
|
|
2252
|
+
policyNames,
|
|
2253
|
+
reason: combined,
|
|
2254
|
+
decision: "instruct"
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
1964
2257
|
if (eventType === "Stop") {
|
|
1965
2258
|
const policyAttribution = policyNames.length === 1 ? `policy: ${policyNames[0]}` : `policies: ${policyNames.join(", ")}`;
|
|
1966
2259
|
return {
|
|
@@ -1995,6 +2288,76 @@ You MUST complete the above action(s) NOW. Do NOT ask the user for confirmation
|
|
|
1995
2288
|
const combined = allowEntries.map((e) => e.reason).join(`
|
|
1996
2289
|
`);
|
|
1997
2290
|
const policyNames = allowEntries.map((e) => e.policyName);
|
|
2291
|
+
if (session?.cli === "cursor") {
|
|
2292
|
+
const response2 = {
|
|
2293
|
+
permission: "allow",
|
|
2294
|
+
additional_context: `Note from failproofai: ${combined}`
|
|
2295
|
+
};
|
|
2296
|
+
const stderrMsg2 = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
2297
|
+
`);
|
|
2298
|
+
return {
|
|
2299
|
+
exitCode: 0,
|
|
2300
|
+
stdout: JSON.stringify(response2),
|
|
2301
|
+
stderr: stderrMsg2 + `
|
|
2302
|
+
`,
|
|
2303
|
+
policyName: policyNames[0],
|
|
2304
|
+
policyNames,
|
|
2305
|
+
reason: combined,
|
|
2306
|
+
decision: "allow"
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
if (session?.cli === "pi") {
|
|
2310
|
+
const response2 = {
|
|
2311
|
+
permission: "allow",
|
|
2312
|
+
reason: `Note from failproofai: ${combined}`
|
|
2313
|
+
};
|
|
2314
|
+
const stderrMsg2 = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
2315
|
+
`);
|
|
2316
|
+
return {
|
|
2317
|
+
exitCode: 0,
|
|
2318
|
+
stdout: JSON.stringify(response2),
|
|
2319
|
+
stderr: stderrMsg2 + `
|
|
2320
|
+
`,
|
|
2321
|
+
policyName: policyNames[0],
|
|
2322
|
+
policyNames,
|
|
2323
|
+
reason: combined,
|
|
2324
|
+
decision: "allow"
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
if (session?.cli === "gemini") {
|
|
2328
|
+
const supportsContext = eventType === "UserPromptSubmit" || eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "SessionStart";
|
|
2329
|
+
const stderrMsg2 = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
2330
|
+
`);
|
|
2331
|
+
if (supportsContext) {
|
|
2332
|
+
const hookEventName = session?.hookEventName ?? session?.rawHookEventName ?? eventType;
|
|
2333
|
+
const response2 = {
|
|
2334
|
+
hookSpecificOutput: {
|
|
2335
|
+
hookEventName,
|
|
2336
|
+
additionalContext: `Note from failproofai: ${combined}`
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
return {
|
|
2340
|
+
exitCode: 0,
|
|
2341
|
+
stdout: JSON.stringify(response2),
|
|
2342
|
+
stderr: stderrMsg2 + `
|
|
2343
|
+
`,
|
|
2344
|
+
policyName: policyNames[0],
|
|
2345
|
+
policyNames,
|
|
2346
|
+
reason: combined,
|
|
2347
|
+
decision: "allow"
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
return {
|
|
2351
|
+
exitCode: 0,
|
|
2352
|
+
stdout: "",
|
|
2353
|
+
stderr: stderrMsg2 + `
|
|
2354
|
+
`,
|
|
2355
|
+
policyName: policyNames[0],
|
|
2356
|
+
policyNames,
|
|
2357
|
+
reason: combined,
|
|
2358
|
+
decision: "allow"
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
1998
2361
|
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit" || eventType === "PermissionRequest";
|
|
1999
2362
|
const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
|
|
2000
2363
|
const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
@@ -2397,7 +2760,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2397
2760
|
});
|
|
2398
2761
|
|
|
2399
2762
|
// package.json
|
|
2400
|
-
var version2 = "0.0.
|
|
2763
|
+
var version2 = "0.0.10-beta.1";
|
|
2401
2764
|
var init_package = () => {};
|
|
2402
2765
|
|
|
2403
2766
|
// src/posthog-key.ts
|
|
@@ -2465,6 +2828,12 @@ import { join as join4 } from "path";
|
|
|
2465
2828
|
function getDefaultClaudeProjectsPath() {
|
|
2466
2829
|
return join4(homedir6(), ".claude", "projects");
|
|
2467
2830
|
}
|
|
2831
|
+
function decodeFolderName(name) {
|
|
2832
|
+
if (/^[A-Za-z]--/.test(name)) {
|
|
2833
|
+
return name[0] + ":/" + name.slice(3).replace(/-/g, "/");
|
|
2834
|
+
}
|
|
2835
|
+
return name.replace(/-/g, "/");
|
|
2836
|
+
}
|
|
2468
2837
|
function encodeFolderName(path) {
|
|
2469
2838
|
const driveMatch = /^([A-Za-z]):[\\/](.*)$/.exec(path);
|
|
2470
2839
|
if (driveMatch) {
|
|
@@ -2550,9 +2919,9 @@ __export(exports_codex_projects, {
|
|
|
2550
2919
|
getCachedCodexSessionsByEncodedName: () => getCachedCodexSessionsByEncodedName,
|
|
2551
2920
|
getCachedCodexProjects: () => getCachedCodexProjects
|
|
2552
2921
|
});
|
|
2553
|
-
import { open as open2, readdir } from "fs/promises";
|
|
2554
|
-
import { homedir as homedir7 } from "os";
|
|
2555
|
-
import { join as join5 } from "path";
|
|
2922
|
+
import { open as open2, readdir } from "node:fs/promises";
|
|
2923
|
+
import { homedir as homedir7 } from "node:os";
|
|
2924
|
+
import { join as join5 } from "node:path";
|
|
2556
2925
|
async function safeReaddir(dir) {
|
|
2557
2926
|
try {
|
|
2558
2927
|
return await readdir(dir, { withFileTypes: true });
|
|
@@ -2739,115 +3108,984 @@ var init_codex_projects = __esm(() => {
|
|
|
2739
3108
|
getCachedCodexSessionsByEncodedName = runtimeCache((name) => getCodexSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
2740
3109
|
});
|
|
2741
3110
|
|
|
2742
|
-
// lib/projects.ts
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3111
|
+
// lib/copilot-projects.ts
|
|
3112
|
+
var exports_copilot_projects = {};
|
|
3113
|
+
__export(exports_copilot_projects, {
|
|
3114
|
+
getCopilotSessionsForCwd: () => getCopilotSessionsForCwd,
|
|
3115
|
+
getCopilotSessionsByEncodedName: () => getCopilotSessionsByEncodedName,
|
|
3116
|
+
getCopilotProjects: () => getCopilotProjects,
|
|
3117
|
+
getCachedCopilotSessionsForCwd: () => getCachedCopilotSessionsForCwd,
|
|
3118
|
+
getCachedCopilotSessionsByEncodedName: () => getCachedCopilotSessionsByEncodedName,
|
|
3119
|
+
getCachedCopilotProjects: () => getCachedCopilotProjects
|
|
3120
|
+
});
|
|
3121
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
3122
|
+
import { homedir as homedir8 } from "node:os";
|
|
3123
|
+
import { join as join6 } from "node:path";
|
|
3124
|
+
function getCopilotSessionStateRoot() {
|
|
3125
|
+
return join6(process.env.COPILOT_HOME || join6(homedir8(), ".copilot"), "session-state");
|
|
3126
|
+
}
|
|
3127
|
+
async function safeReaddir2(dir) {
|
|
2746
3128
|
try {
|
|
2747
|
-
return
|
|
2748
|
-
} catch
|
|
2749
|
-
|
|
2750
|
-
return new Date(0);
|
|
3129
|
+
return await readdir2(dir, { withFileTypes: true });
|
|
3130
|
+
} catch {
|
|
3131
|
+
return null;
|
|
2751
3132
|
}
|
|
2752
3133
|
}
|
|
2753
|
-
|
|
3134
|
+
function parseCwdFromWorkspace(text) {
|
|
3135
|
+
const m = text.match(/^cwd\s*:\s*(.+?)\s*$/m);
|
|
3136
|
+
if (!m)
|
|
3137
|
+
return;
|
|
3138
|
+
return m[1].replace(/^['"]|['"]$/g, "");
|
|
3139
|
+
}
|
|
3140
|
+
async function statMtime(path) {
|
|
2754
3141
|
try {
|
|
2755
|
-
|
|
2756
|
-
if (!s.isDirectory())
|
|
2757
|
-
return null;
|
|
2758
|
-
return await readdir2(dirPath, { withFileTypes: true });
|
|
3142
|
+
return (await stat2(path)).mtime;
|
|
2759
3143
|
} catch {
|
|
2760
3144
|
return null;
|
|
2761
3145
|
}
|
|
2762
3146
|
}
|
|
2763
|
-
async function
|
|
3147
|
+
async function scanCopilotSessions() {
|
|
3148
|
+
const root = getCopilotSessionStateRoot();
|
|
3149
|
+
const entries = await safeReaddir2(root);
|
|
3150
|
+
if (!entries)
|
|
3151
|
+
return [];
|
|
3152
|
+
const candidates = entries.filter((e) => e.isDirectory()).map((e) => ({
|
|
3153
|
+
sessionId: e.name,
|
|
3154
|
+
workspacePath: join6(root, e.name, "workspace.yaml"),
|
|
3155
|
+
eventsPath: join6(root, e.name, "events.jsonl")
|
|
3156
|
+
}));
|
|
3157
|
+
const settled = await batchAll(candidates.map((c) => async () => {
|
|
3158
|
+
let workspaceText;
|
|
3159
|
+
try {
|
|
3160
|
+
workspaceText = await readFile3(c.workspacePath, "utf-8");
|
|
3161
|
+
} catch {
|
|
3162
|
+
return null;
|
|
3163
|
+
}
|
|
3164
|
+
const cwd = parseCwdFromWorkspace(workspaceText);
|
|
3165
|
+
if (!cwd)
|
|
3166
|
+
return null;
|
|
3167
|
+
const eventsMtime = await statMtime(c.eventsPath);
|
|
3168
|
+
const wsMtime = await statMtime(c.workspacePath);
|
|
3169
|
+
const hasTranscript = eventsMtime !== null;
|
|
3170
|
+
const fileMtime = eventsMtime && wsMtime ? new Date(Math.max(eventsMtime.getTime(), wsMtime.getTime())) : eventsMtime ?? wsMtime ?? new Date(0);
|
|
3171
|
+
return {
|
|
3172
|
+
workspacePath: c.workspacePath,
|
|
3173
|
+
eventsPath: c.eventsPath,
|
|
3174
|
+
sessionId: c.sessionId,
|
|
3175
|
+
cwd,
|
|
3176
|
+
fileMtime,
|
|
3177
|
+
hasTranscript
|
|
3178
|
+
};
|
|
3179
|
+
}), 16);
|
|
3180
|
+
return settled.filter((r) => r.status === "fulfilled").map((r) => r.value).filter((v) => v !== null);
|
|
3181
|
+
}
|
|
3182
|
+
async function getCopilotProjects() {
|
|
3183
|
+
let metas;
|
|
2764
3184
|
try {
|
|
2765
|
-
|
|
2766
|
-
const entries = await safeReaddir2(projectsPath);
|
|
2767
|
-
if (!entries)
|
|
2768
|
-
return [];
|
|
2769
|
-
const settled = await batchAll(entries.filter((entry) => entry.isDirectory()).map((entry) => async () => {
|
|
2770
|
-
const folderPath = join6(projectsPath, entry.name);
|
|
2771
|
-
const mtime = await getMtime(folderPath, entry.name);
|
|
2772
|
-
return {
|
|
2773
|
-
name: entry.name,
|
|
2774
|
-
path: folderPath,
|
|
2775
|
-
isDirectory: true,
|
|
2776
|
-
lastModified: mtime,
|
|
2777
|
-
lastModifiedFormatted: formatDate(mtime),
|
|
2778
|
-
cli: ["claude"]
|
|
2779
|
-
};
|
|
2780
|
-
}), 16);
|
|
2781
|
-
const folders = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
2782
|
-
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
2783
|
-
return folders;
|
|
3185
|
+
metas = await cachedScan2();
|
|
2784
3186
|
} catch (error) {
|
|
2785
|
-
|
|
3187
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
2786
3188
|
return [];
|
|
2787
3189
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
for (const f of claude)
|
|
2792
|
-
byName.set(f.name, { ...f, cli: [...f.cli] });
|
|
2793
|
-
for (const f of codex) {
|
|
2794
|
-
const existing = byName.get(f.name);
|
|
2795
|
-
if (!existing) {
|
|
2796
|
-
byName.set(f.name, { ...f, cli: [...f.cli] });
|
|
3190
|
+
const byCwd = new Map;
|
|
3191
|
+
for (const m of metas) {
|
|
3192
|
+
if (!m.hasTranscript)
|
|
2797
3193
|
continue;
|
|
3194
|
+
const existing = byCwd.get(m.cwd);
|
|
3195
|
+
if (!existing || m.fileMtime.getTime() > existing.latest.getTime()) {
|
|
3196
|
+
byCwd.set(m.cwd, { latest: m.fileMtime, cwd: m.cwd });
|
|
2798
3197
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3198
|
+
}
|
|
3199
|
+
const folders = [];
|
|
3200
|
+
for (const { cwd, latest } of byCwd.values()) {
|
|
3201
|
+
folders.push({
|
|
3202
|
+
name: encodeFolderName(cwd),
|
|
3203
|
+
path: cwd,
|
|
3204
|
+
isDirectory: true,
|
|
3205
|
+
lastModified: latest,
|
|
3206
|
+
lastModifiedFormatted: formatDate(latest),
|
|
3207
|
+
cli: ["copilot"]
|
|
2809
3208
|
});
|
|
2810
3209
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
return merged;
|
|
3210
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3211
|
+
return folders;
|
|
2814
3212
|
}
|
|
2815
|
-
|
|
2816
|
-
const
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3213
|
+
function metasToSessionFiles2(metas) {
|
|
3214
|
+
const files = metas.filter((m) => m.hasTranscript).map((m) => ({
|
|
3215
|
+
name: m.sessionId,
|
|
3216
|
+
path: m.eventsPath,
|
|
3217
|
+
lastModified: m.fileMtime,
|
|
3218
|
+
lastModifiedFormatted: formatDate(m.fileMtime),
|
|
3219
|
+
sessionId: m.sessionId,
|
|
3220
|
+
cli: "copilot"
|
|
3221
|
+
}));
|
|
3222
|
+
files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3223
|
+
return files;
|
|
2825
3224
|
}
|
|
2826
|
-
function
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
if (!candidate.startsWith(projectsPath + sep)) {
|
|
2834
|
-
throw new RangeError("Project path escapes root");
|
|
3225
|
+
async function getCopilotSessionsForCwd(cwd) {
|
|
3226
|
+
let metas;
|
|
3227
|
+
try {
|
|
3228
|
+
metas = await cachedScan2();
|
|
3229
|
+
} catch (error) {
|
|
3230
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
3231
|
+
return [];
|
|
2835
3232
|
}
|
|
2836
|
-
return
|
|
2837
|
-
}
|
|
2838
|
-
function
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3233
|
+
return metasToSessionFiles2(metas.filter((m) => m.cwd === cwd));
|
|
3234
|
+
}
|
|
3235
|
+
async function getCopilotSessionsByEncodedName(name) {
|
|
3236
|
+
let metas;
|
|
3237
|
+
try {
|
|
3238
|
+
metas = await cachedScan2();
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
3241
|
+
return { cwd: null, sessions: [] };
|
|
3242
|
+
}
|
|
3243
|
+
const matches = metas.filter((m) => m.hasTranscript && encodeFolderName(m.cwd) === name);
|
|
3244
|
+
return {
|
|
3245
|
+
cwd: matches[0]?.cwd ?? null,
|
|
3246
|
+
sessions: metasToSessionFiles2(matches)
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
var cachedScan2, getCachedCopilotProjects, getCachedCopilotSessionsForCwd, getCachedCopilotSessionsByEncodedName;
|
|
3250
|
+
var init_copilot_projects = __esm(() => {
|
|
3251
|
+
init_paths();
|
|
3252
|
+
init_logger();
|
|
3253
|
+
cachedScan2 = runtimeCache(scanCopilotSessions, 30);
|
|
3254
|
+
getCachedCopilotProjects = runtimeCache(getCopilotProjects, 30);
|
|
3255
|
+
getCachedCopilotSessionsForCwd = runtimeCache((cwd) => getCopilotSessionsForCwd(cwd), 30, { maxSize: 50 });
|
|
3256
|
+
getCachedCopilotSessionsByEncodedName = runtimeCache((name) => getCopilotSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
3257
|
+
});
|
|
3258
|
+
|
|
3259
|
+
// lib/cursor-projects.ts
|
|
3260
|
+
var exports_cursor_projects = {};
|
|
3261
|
+
__export(exports_cursor_projects, {
|
|
3262
|
+
getCursorSessionsForCwd: () => getCursorSessionsForCwd,
|
|
3263
|
+
getCursorSessionsByEncodedName: () => getCursorSessionsByEncodedName,
|
|
3264
|
+
getCursorProjects: () => getCursorProjects,
|
|
3265
|
+
getCachedCursorSessionsForCwd: () => getCachedCursorSessionsForCwd,
|
|
3266
|
+
getCachedCursorSessionsByEncodedName: () => getCachedCursorSessionsByEncodedName,
|
|
3267
|
+
getCachedCursorProjects: () => getCachedCursorProjects
|
|
3268
|
+
});
|
|
3269
|
+
import { readdir as readdir3, readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
3270
|
+
import { homedir as homedir9 } from "node:os";
|
|
3271
|
+
import { join as join7 } from "node:path";
|
|
3272
|
+
function getCursorHome() {
|
|
3273
|
+
return process.env.CURSOR_HOME || join7(homedir9(), ".cursor");
|
|
3274
|
+
}
|
|
3275
|
+
async function safeReaddir3(dir) {
|
|
3276
|
+
try {
|
|
3277
|
+
return await readdir3(dir, { withFileTypes: true });
|
|
3278
|
+
} catch {
|
|
3279
|
+
return null;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
async function statMtime2(path) {
|
|
3283
|
+
try {
|
|
3284
|
+
return (await stat3(path)).mtime;
|
|
3285
|
+
} catch {
|
|
3286
|
+
return null;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
function parseCwdFromMetaText(text) {
|
|
3290
|
+
try {
|
|
3291
|
+
const parsed = JSON.parse(text);
|
|
3292
|
+
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0)
|
|
3293
|
+
return parsed.cwd;
|
|
3294
|
+
} catch {}
|
|
3295
|
+
const yaml = text.match(/^\s*cwd\s*:\s*(.+?)\s*$/m);
|
|
3296
|
+
if (yaml) {
|
|
3297
|
+
const stripped = yaml[1].replace(/^['"]|['"]$/g, "");
|
|
3298
|
+
if (stripped.length > 0)
|
|
3299
|
+
return stripped;
|
|
3300
|
+
}
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
async function findFirstUsableMeta(dir, candidates) {
|
|
3304
|
+
for (const name of candidates) {
|
|
3305
|
+
const path = join7(dir, name);
|
|
3306
|
+
if (await statMtime2(path) === null)
|
|
3307
|
+
continue;
|
|
3308
|
+
let text;
|
|
3309
|
+
try {
|
|
3310
|
+
text = await readFile4(path, "utf-8");
|
|
3311
|
+
} catch {
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
const cwd = parseCwdFromMetaText(text);
|
|
3315
|
+
if (cwd)
|
|
3316
|
+
return { path, cwd };
|
|
3317
|
+
}
|
|
3318
|
+
return null;
|
|
3319
|
+
}
|
|
3320
|
+
async function findFirstExistingPath(dir, candidates) {
|
|
3321
|
+
for (const name of candidates) {
|
|
3322
|
+
const path = join7(dir, name);
|
|
3323
|
+
if (await statMtime2(path) !== null)
|
|
3324
|
+
return path;
|
|
3325
|
+
}
|
|
3326
|
+
return null;
|
|
3327
|
+
}
|
|
3328
|
+
async function scanCursorSessions() {
|
|
3329
|
+
const home = getCursorHome();
|
|
3330
|
+
const newCandidates = [];
|
|
3331
|
+
const legacyCandidates = [];
|
|
3332
|
+
const projectsRoot = join7(home, NEW_PROJECTS_DIR);
|
|
3333
|
+
const projectEntries = await safeReaddir3(projectsRoot);
|
|
3334
|
+
if (projectEntries) {
|
|
3335
|
+
for (const proj of projectEntries) {
|
|
3336
|
+
if (!proj.isDirectory())
|
|
3337
|
+
continue;
|
|
3338
|
+
const decoded = decodeFolderName(proj.name);
|
|
3339
|
+
const cwd = decoded.startsWith("/") || /^[A-Za-z]:\//.test(decoded) ? decoded : `/${decoded}`;
|
|
3340
|
+
const transcriptsRoot = join7(projectsRoot, proj.name, NEW_AGENT_TRANSCRIPTS_DIR);
|
|
3341
|
+
const sessionDirs = await safeReaddir3(transcriptsRoot);
|
|
3342
|
+
if (!sessionDirs)
|
|
3343
|
+
continue;
|
|
3344
|
+
for (const sd of sessionDirs) {
|
|
3345
|
+
if (!sd.isDirectory())
|
|
3346
|
+
continue;
|
|
3347
|
+
newCandidates.push({
|
|
3348
|
+
sessionId: sd.name,
|
|
3349
|
+
dir: join7(transcriptsRoot, sd.name),
|
|
3350
|
+
cwd
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
for (const sub of LEGACY_SESSION_ROOT_CANDIDATES) {
|
|
3356
|
+
const root = join7(home, sub);
|
|
3357
|
+
const entries = await safeReaddir3(root);
|
|
3358
|
+
if (!entries)
|
|
3359
|
+
continue;
|
|
3360
|
+
for (const e of entries) {
|
|
3361
|
+
if (!e.isDirectory())
|
|
3362
|
+
continue;
|
|
3363
|
+
legacyCandidates.push({ sessionId: e.name, dir: join7(root, e.name) });
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
if (newCandidates.length === 0 && legacyCandidates.length === 0)
|
|
3367
|
+
return [];
|
|
3368
|
+
const settled = await batchAll([
|
|
3369
|
+
...newCandidates.map((c) => async () => {
|
|
3370
|
+
const transcriptPath = join7(c.dir, `${c.sessionId}.jsonl`);
|
|
3371
|
+
const transcriptMtime = await statMtime2(transcriptPath);
|
|
3372
|
+
if (!transcriptMtime)
|
|
3373
|
+
return null;
|
|
3374
|
+
return {
|
|
3375
|
+
metaPath: c.dir,
|
|
3376
|
+
transcriptPath,
|
|
3377
|
+
sessionId: c.sessionId,
|
|
3378
|
+
cwd: c.cwd,
|
|
3379
|
+
fileMtime: transcriptMtime,
|
|
3380
|
+
hasTranscript: true
|
|
3381
|
+
};
|
|
3382
|
+
}),
|
|
3383
|
+
...legacyCandidates.map((c) => async () => {
|
|
3384
|
+
const meta = await findFirstUsableMeta(c.dir, META_FILE_CANDIDATES);
|
|
3385
|
+
if (!meta)
|
|
3386
|
+
return null;
|
|
3387
|
+
const transcriptPath = await findFirstExistingPath(c.dir, LEGACY_TRANSCRIPT_FILE_CANDIDATES);
|
|
3388
|
+
const transcriptMtime = transcriptPath ? await statMtime2(transcriptPath) : null;
|
|
3389
|
+
const metaMtime = await statMtime2(meta.path);
|
|
3390
|
+
const fileMtime = transcriptMtime && metaMtime ? new Date(Math.max(transcriptMtime.getTime(), metaMtime.getTime())) : transcriptMtime ?? metaMtime ?? new Date(0);
|
|
3391
|
+
return {
|
|
3392
|
+
metaPath: meta.path,
|
|
3393
|
+
transcriptPath,
|
|
3394
|
+
sessionId: c.sessionId,
|
|
3395
|
+
cwd: meta.cwd,
|
|
3396
|
+
fileMtime,
|
|
3397
|
+
hasTranscript: transcriptPath !== null
|
|
3398
|
+
};
|
|
3399
|
+
})
|
|
3400
|
+
], 16);
|
|
3401
|
+
return settled.filter((r) => r.status === "fulfilled").map((r) => r.value).filter((v) => v !== null);
|
|
3402
|
+
}
|
|
3403
|
+
async function getCursorProjects() {
|
|
3404
|
+
let metas;
|
|
3405
|
+
try {
|
|
3406
|
+
metas = await cachedScan3();
|
|
3407
|
+
} catch (error) {
|
|
3408
|
+
logWarn("Failed to scan Cursor sessions:", error);
|
|
3409
|
+
return [];
|
|
3410
|
+
}
|
|
3411
|
+
const byCwd = new Map;
|
|
3412
|
+
for (const m of metas) {
|
|
3413
|
+
if (!m.hasTranscript)
|
|
3414
|
+
continue;
|
|
3415
|
+
const existing = byCwd.get(m.cwd);
|
|
3416
|
+
if (!existing || m.fileMtime.getTime() > existing.latest.getTime()) {
|
|
3417
|
+
byCwd.set(m.cwd, { latest: m.fileMtime, cwd: m.cwd });
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
const folders = [];
|
|
3421
|
+
for (const { cwd, latest } of byCwd.values()) {
|
|
3422
|
+
folders.push({
|
|
3423
|
+
name: encodeFolderName(cwd),
|
|
3424
|
+
path: cwd,
|
|
3425
|
+
isDirectory: true,
|
|
3426
|
+
lastModified: latest,
|
|
3427
|
+
lastModifiedFormatted: formatDate(latest),
|
|
3428
|
+
cli: ["cursor"]
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3432
|
+
return folders;
|
|
3433
|
+
}
|
|
3434
|
+
function metasToSessionFiles3(metas) {
|
|
3435
|
+
const files = metas.filter((m) => m.hasTranscript && m.transcriptPath).map((m) => ({
|
|
3436
|
+
name: m.sessionId,
|
|
3437
|
+
path: m.transcriptPath,
|
|
3438
|
+
lastModified: m.fileMtime,
|
|
3439
|
+
lastModifiedFormatted: formatDate(m.fileMtime),
|
|
3440
|
+
sessionId: m.sessionId,
|
|
3441
|
+
cli: "cursor"
|
|
3442
|
+
}));
|
|
3443
|
+
files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3444
|
+
return files;
|
|
3445
|
+
}
|
|
3446
|
+
async function getCursorSessionsForCwd(cwd) {
|
|
3447
|
+
let metas;
|
|
3448
|
+
try {
|
|
3449
|
+
metas = await cachedScan3();
|
|
3450
|
+
} catch (error) {
|
|
3451
|
+
logWarn("Failed to scan Cursor sessions:", error);
|
|
3452
|
+
return [];
|
|
3453
|
+
}
|
|
3454
|
+
return metasToSessionFiles3(metas.filter((m) => m.cwd === cwd));
|
|
3455
|
+
}
|
|
3456
|
+
async function getCursorSessionsByEncodedName(name) {
|
|
3457
|
+
let metas;
|
|
3458
|
+
try {
|
|
3459
|
+
metas = await cachedScan3();
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
logWarn("Failed to scan Cursor sessions:", error);
|
|
3462
|
+
return { cwd: null, sessions: [] };
|
|
3463
|
+
}
|
|
3464
|
+
const matches = metas.filter((m) => m.hasTranscript && encodeFolderName(m.cwd) === name);
|
|
3465
|
+
return {
|
|
3466
|
+
cwd: matches[0]?.cwd ?? null,
|
|
3467
|
+
sessions: metasToSessionFiles3(matches)
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
var LEGACY_SESSION_ROOT_CANDIDATES, META_FILE_CANDIDATES, LEGACY_TRANSCRIPT_FILE_CANDIDATES, NEW_PROJECTS_DIR = "projects", NEW_AGENT_TRANSCRIPTS_DIR = "agent-transcripts", cachedScan3, getCachedCursorProjects, getCachedCursorSessionsForCwd, getCachedCursorSessionsByEncodedName;
|
|
3471
|
+
var init_cursor_projects = __esm(() => {
|
|
3472
|
+
init_paths();
|
|
3473
|
+
init_logger();
|
|
3474
|
+
LEGACY_SESSION_ROOT_CANDIDATES = ["agent-sessions", "conversations", "sessions"];
|
|
3475
|
+
META_FILE_CANDIDATES = ["meta.json", "session.json", "workspace.json", "workspace.yaml"];
|
|
3476
|
+
LEGACY_TRANSCRIPT_FILE_CANDIDATES = ["events.jsonl", "transcript.jsonl", "messages.jsonl"];
|
|
3477
|
+
cachedScan3 = runtimeCache(scanCursorSessions, 30);
|
|
3478
|
+
getCachedCursorProjects = runtimeCache(getCursorProjects, 30);
|
|
3479
|
+
getCachedCursorSessionsForCwd = runtimeCache((cwd) => getCursorSessionsForCwd(cwd), 30, { maxSize: 50 });
|
|
3480
|
+
getCachedCursorSessionsByEncodedName = runtimeCache((name) => getCursorSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
3481
|
+
});
|
|
3482
|
+
|
|
3483
|
+
// lib/opencode-projects.ts
|
|
3484
|
+
var exports_opencode_projects = {};
|
|
3485
|
+
__export(exports_opencode_projects, {
|
|
3486
|
+
getOpenCodeSessionsForCwd: () => getOpenCodeSessionsForCwd,
|
|
3487
|
+
getOpenCodeSessionsByEncodedName: () => getOpenCodeSessionsByEncodedName,
|
|
3488
|
+
getOpenCodeProjects: () => getOpenCodeProjects,
|
|
3489
|
+
getCachedOpenCodeSessionsForCwd: () => getCachedOpenCodeSessionsForCwd,
|
|
3490
|
+
getCachedOpenCodeSessionsByEncodedName: () => getCachedOpenCodeSessionsByEncodedName,
|
|
3491
|
+
getCachedOpenCodeProjects: () => getCachedOpenCodeProjects
|
|
3492
|
+
});
|
|
3493
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3494
|
+
import { basename as basename2 } from "node:path";
|
|
3495
|
+
function runOpenCodeDb(sql) {
|
|
3496
|
+
try {
|
|
3497
|
+
const stdout = execFileSync2("opencode", ["db", "--format", "json", sql], {
|
|
3498
|
+
encoding: "utf8",
|
|
3499
|
+
timeout: 5000,
|
|
3500
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3501
|
+
});
|
|
3502
|
+
if (!stdout.trim())
|
|
3503
|
+
return [];
|
|
3504
|
+
const parsed = JSON.parse(stdout);
|
|
3505
|
+
if (!Array.isArray(parsed))
|
|
3506
|
+
return null;
|
|
3507
|
+
return parsed;
|
|
3508
|
+
} catch {
|
|
3509
|
+
return null;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
function readSessionRows() {
|
|
3513
|
+
return runOpenCodeDb(`SELECT id, project_id, slug, directory, title, time_created, time_updated FROM session ORDER BY time_updated DESC LIMIT ${SESSION_LIMIT}`);
|
|
3514
|
+
}
|
|
3515
|
+
function readProjectRows() {
|
|
3516
|
+
return runOpenCodeDb(`SELECT id, worktree, vcs, name, time_created, time_updated FROM project`);
|
|
3517
|
+
}
|
|
3518
|
+
async function getOpenCodeProjects() {
|
|
3519
|
+
const sessions = readSessionRows();
|
|
3520
|
+
const projects = readProjectRows();
|
|
3521
|
+
if (sessions === null && projects === null) {
|
|
3522
|
+
return [];
|
|
3523
|
+
}
|
|
3524
|
+
const projectMap = new Map;
|
|
3525
|
+
for (const p of projects ?? [])
|
|
3526
|
+
projectMap.set(p.id, p);
|
|
3527
|
+
const groups = new Map;
|
|
3528
|
+
for (const s of sessions ?? []) {
|
|
3529
|
+
if (!s.project_id)
|
|
3530
|
+
continue;
|
|
3531
|
+
let g = groups.get(s.project_id);
|
|
3532
|
+
if (!g) {
|
|
3533
|
+
g = { rows: [], latest: 0 };
|
|
3534
|
+
groups.set(s.project_id, g);
|
|
3535
|
+
}
|
|
3536
|
+
g.rows.push(s);
|
|
3537
|
+
if (s.time_updated > g.latest)
|
|
3538
|
+
g.latest = s.time_updated;
|
|
3539
|
+
}
|
|
3540
|
+
const seen = new Set;
|
|
3541
|
+
const out = [];
|
|
3542
|
+
for (const [projectId, group] of groups) {
|
|
3543
|
+
seen.add(projectId);
|
|
3544
|
+
const proj = projectMap.get(projectId);
|
|
3545
|
+
const worktree = proj?.worktree ?? group.rows[0]?.directory ?? null;
|
|
3546
|
+
const name = proj?.name?.trim() || (worktree ? basename2(worktree) : projectId);
|
|
3547
|
+
const path = worktree ?? "";
|
|
3548
|
+
const lastModified = new Date(Math.max(group.latest, proj?.time_updated ?? 0));
|
|
3549
|
+
out.push({
|
|
3550
|
+
name,
|
|
3551
|
+
path,
|
|
3552
|
+
isDirectory: true,
|
|
3553
|
+
lastModified,
|
|
3554
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
3555
|
+
cli: ["opencode"]
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
for (const p of projects ?? []) {
|
|
3559
|
+
if (seen.has(p.id))
|
|
3560
|
+
continue;
|
|
3561
|
+
const worktree = p.worktree ?? "";
|
|
3562
|
+
const name = p.name?.trim() || (worktree ? basename2(worktree) : p.id);
|
|
3563
|
+
const lastModified = new Date(p.time_updated);
|
|
3564
|
+
out.push({
|
|
3565
|
+
name,
|
|
3566
|
+
path: worktree,
|
|
3567
|
+
isDirectory: true,
|
|
3568
|
+
lastModified,
|
|
3569
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
3570
|
+
cli: ["opencode"]
|
|
3571
|
+
});
|
|
3572
|
+
}
|
|
3573
|
+
out.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3574
|
+
return out;
|
|
3575
|
+
}
|
|
3576
|
+
async function getOpenCodeSessionsForCwd(cwd) {
|
|
3577
|
+
const sessions = readSessionRows();
|
|
3578
|
+
if (!sessions)
|
|
3579
|
+
return [];
|
|
3580
|
+
const matches = sessions.filter((s) => s.directory === cwd);
|
|
3581
|
+
return matches.map((s) => {
|
|
3582
|
+
const lastModified = new Date(s.time_updated);
|
|
3583
|
+
return {
|
|
3584
|
+
name: s.title ?? s.slug ?? s.id,
|
|
3585
|
+
path: `opencode://${s.id}`,
|
|
3586
|
+
lastModified,
|
|
3587
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
3588
|
+
sessionId: s.id,
|
|
3589
|
+
cli: "opencode"
|
|
3590
|
+
};
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
async function getOpenCodeSessionsByEncodedName(name) {
|
|
3594
|
+
let projects;
|
|
3595
|
+
let sessions;
|
|
3596
|
+
try {
|
|
3597
|
+
projects = readProjectRows();
|
|
3598
|
+
sessions = readSessionRows();
|
|
3599
|
+
} catch (error) {
|
|
3600
|
+
logWarn("Failed to read OpenCode DB:", error);
|
|
3601
|
+
return { cwd: null, sessions: [] };
|
|
3602
|
+
}
|
|
3603
|
+
if (!projects || !sessions)
|
|
3604
|
+
return { cwd: null, sessions: [] };
|
|
3605
|
+
const matchingProject = projects.find((p) => p.worktree && encodeFolderName(p.worktree) === name);
|
|
3606
|
+
if (!matchingProject || !matchingProject.worktree)
|
|
3607
|
+
return { cwd: null, sessions: [] };
|
|
3608
|
+
const matched = sessions.filter((s) => s.project_id === matchingProject.id);
|
|
3609
|
+
return {
|
|
3610
|
+
cwd: matchingProject.worktree,
|
|
3611
|
+
sessions: matched.map((s) => {
|
|
3612
|
+
const lastModified = new Date(s.time_updated);
|
|
3613
|
+
return {
|
|
3614
|
+
name: s.title ?? s.slug ?? s.id,
|
|
3615
|
+
path: `opencode://${s.id}`,
|
|
3616
|
+
lastModified,
|
|
3617
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
3618
|
+
sessionId: s.id,
|
|
3619
|
+
cli: "opencode"
|
|
3620
|
+
};
|
|
3621
|
+
})
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
var SESSION_LIMIT = 1000, getCachedOpenCodeProjects, getCachedOpenCodeSessionsForCwd, getCachedOpenCodeSessionsByEncodedName;
|
|
3625
|
+
var init_opencode_projects = __esm(() => {
|
|
3626
|
+
init_paths();
|
|
3627
|
+
init_logger();
|
|
3628
|
+
getCachedOpenCodeProjects = runtimeCache(getOpenCodeProjects, 30);
|
|
3629
|
+
getCachedOpenCodeSessionsForCwd = runtimeCache((cwd) => getOpenCodeSessionsForCwd(cwd), 30, { maxSize: 50 });
|
|
3630
|
+
getCachedOpenCodeSessionsByEncodedName = runtimeCache((name) => getOpenCodeSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
3631
|
+
});
|
|
3632
|
+
|
|
3633
|
+
// lib/pi-projects.ts
|
|
3634
|
+
var exports_pi_projects = {};
|
|
3635
|
+
__export(exports_pi_projects, {
|
|
3636
|
+
getPiSessionsForCwd: () => getPiSessionsForCwd,
|
|
3637
|
+
getPiSessionsByEncodedName: () => getPiSessionsByEncodedName,
|
|
3638
|
+
getPiProjects: () => getPiProjects,
|
|
3639
|
+
getCachedPiSessionsForCwd: () => getCachedPiSessionsForCwd,
|
|
3640
|
+
getCachedPiSessionsByEncodedName: () => getCachedPiSessionsByEncodedName,
|
|
3641
|
+
getCachedPiProjects: () => getCachedPiProjects
|
|
3642
|
+
});
|
|
3643
|
+
import { readdir as readdir4, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
3644
|
+
import { homedir as homedir10 } from "node:os";
|
|
3645
|
+
import { join as join8 } from "node:path";
|
|
3646
|
+
function getPiSessionsRoot() {
|
|
3647
|
+
return process.env.PI_SESSIONS_DIR || join8(homedir10(), ".pi", "agent", "sessions");
|
|
3648
|
+
}
|
|
3649
|
+
async function safeReaddir4(dir) {
|
|
3650
|
+
try {
|
|
3651
|
+
return await readdir4(dir, { withFileTypes: true });
|
|
3652
|
+
} catch {
|
|
3653
|
+
return null;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
async function statMtime3(path) {
|
|
3657
|
+
try {
|
|
3658
|
+
return (await stat4(path)).mtime;
|
|
3659
|
+
} catch {
|
|
3660
|
+
return null;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
async function readSessionCwd(filePath) {
|
|
3664
|
+
let text;
|
|
3665
|
+
try {
|
|
3666
|
+
text = await readFile5(filePath, "utf-8");
|
|
3667
|
+
} catch {
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
3670
|
+
const firstLine = text.indexOf(`
|
|
3671
|
+
`) >= 0 ? text.slice(0, text.indexOf(`
|
|
3672
|
+
`)) : text;
|
|
3673
|
+
if (!firstLine)
|
|
3674
|
+
return null;
|
|
3675
|
+
try {
|
|
3676
|
+
const parsed = JSON.parse(firstLine);
|
|
3677
|
+
if (parsed.type !== "session")
|
|
3678
|
+
return null;
|
|
3679
|
+
if (typeof parsed.cwd !== "string" || parsed.cwd.length === 0)
|
|
3680
|
+
return null;
|
|
3681
|
+
return parsed.cwd;
|
|
3682
|
+
} catch {
|
|
3683
|
+
return null;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
async function scanPiSessions() {
|
|
3687
|
+
const root = getPiSessionsRoot();
|
|
3688
|
+
const cwdDirs = await safeReaddir4(root);
|
|
3689
|
+
if (!cwdDirs)
|
|
3690
|
+
return [];
|
|
3691
|
+
const candidates = [];
|
|
3692
|
+
for (const cwdDir of cwdDirs) {
|
|
3693
|
+
if (!cwdDir.isDirectory())
|
|
3694
|
+
continue;
|
|
3695
|
+
const cwdPath = join8(root, cwdDir.name);
|
|
3696
|
+
const sessionFiles = await safeReaddir4(cwdPath);
|
|
3697
|
+
if (!sessionFiles)
|
|
3698
|
+
continue;
|
|
3699
|
+
for (const f of sessionFiles) {
|
|
3700
|
+
if (!f.isFile())
|
|
3701
|
+
continue;
|
|
3702
|
+
const m = SESSION_FILE_RE.exec(f.name);
|
|
3703
|
+
if (!m)
|
|
3704
|
+
continue;
|
|
3705
|
+
candidates.push({ sessionId: m[1], filePath: join8(cwdPath, f.name) });
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
if (candidates.length === 0)
|
|
3709
|
+
return [];
|
|
3710
|
+
const settled = await batchAll(candidates.map((c) => async () => {
|
|
3711
|
+
const cwd = await readSessionCwd(c.filePath);
|
|
3712
|
+
if (!cwd)
|
|
3713
|
+
return null;
|
|
3714
|
+
const mtime = await statMtime3(c.filePath);
|
|
3715
|
+
if (!mtime)
|
|
3716
|
+
return null;
|
|
3717
|
+
return {
|
|
3718
|
+
filePath: c.filePath,
|
|
3719
|
+
sessionId: c.sessionId,
|
|
3720
|
+
cwd,
|
|
3721
|
+
fileMtime: mtime
|
|
3722
|
+
};
|
|
3723
|
+
}), 16);
|
|
3724
|
+
return settled.filter((r) => r.status === "fulfilled").map((r) => r.value).filter((v) => v !== null);
|
|
3725
|
+
}
|
|
3726
|
+
async function getPiProjects() {
|
|
3727
|
+
let metas;
|
|
3728
|
+
try {
|
|
3729
|
+
metas = await cachedScan4();
|
|
3730
|
+
} catch (error) {
|
|
3731
|
+
logWarn("Failed to scan Pi sessions:", error);
|
|
3732
|
+
return [];
|
|
3733
|
+
}
|
|
3734
|
+
const byCwd = new Map;
|
|
3735
|
+
for (const m of metas) {
|
|
3736
|
+
const existing = byCwd.get(m.cwd);
|
|
3737
|
+
if (!existing || m.fileMtime.getTime() > existing.latest.getTime()) {
|
|
3738
|
+
byCwd.set(m.cwd, { latest: m.fileMtime, cwd: m.cwd });
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
const folders = [];
|
|
3742
|
+
for (const { cwd, latest } of byCwd.values()) {
|
|
3743
|
+
folders.push({
|
|
3744
|
+
name: encodeFolderName(cwd),
|
|
3745
|
+
path: cwd,
|
|
3746
|
+
isDirectory: true,
|
|
3747
|
+
lastModified: latest,
|
|
3748
|
+
lastModifiedFormatted: formatDate(latest),
|
|
3749
|
+
cli: ["pi"]
|
|
3750
|
+
});
|
|
3751
|
+
}
|
|
3752
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3753
|
+
return folders;
|
|
3754
|
+
}
|
|
3755
|
+
function metasToSessionFiles4(metas) {
|
|
3756
|
+
const files = metas.map((m) => ({
|
|
3757
|
+
name: m.sessionId,
|
|
3758
|
+
path: m.filePath,
|
|
3759
|
+
lastModified: m.fileMtime,
|
|
3760
|
+
lastModifiedFormatted: formatDate(m.fileMtime),
|
|
3761
|
+
sessionId: m.sessionId,
|
|
3762
|
+
cli: "pi"
|
|
3763
|
+
}));
|
|
3764
|
+
files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3765
|
+
return files;
|
|
3766
|
+
}
|
|
3767
|
+
async function getPiSessionsForCwd(cwd) {
|
|
3768
|
+
let metas;
|
|
3769
|
+
try {
|
|
3770
|
+
metas = await cachedScan4();
|
|
3771
|
+
} catch (error) {
|
|
3772
|
+
logWarn("Failed to scan Pi sessions:", error);
|
|
3773
|
+
return [];
|
|
3774
|
+
}
|
|
3775
|
+
return metasToSessionFiles4(metas.filter((m) => m.cwd === cwd));
|
|
3776
|
+
}
|
|
3777
|
+
async function getPiSessionsByEncodedName(name) {
|
|
3778
|
+
let metas;
|
|
3779
|
+
try {
|
|
3780
|
+
metas = await cachedScan4();
|
|
3781
|
+
} catch (error) {
|
|
3782
|
+
logWarn("Failed to scan Pi sessions:", error);
|
|
3783
|
+
return { cwd: null, sessions: [] };
|
|
3784
|
+
}
|
|
3785
|
+
const matches = metas.filter((m) => encodeFolderName(m.cwd) === name);
|
|
3786
|
+
const uniqueCwds = Array.from(new Set(matches.map((m) => m.cwd)));
|
|
3787
|
+
if (uniqueCwds.length !== 1) {
|
|
3788
|
+
return { cwd: null, sessions: [] };
|
|
3789
|
+
}
|
|
3790
|
+
return {
|
|
3791
|
+
cwd: uniqueCwds[0],
|
|
3792
|
+
sessions: metasToSessionFiles4(matches)
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
var SESSION_FILE_RE, cachedScan4, getCachedPiProjects, getCachedPiSessionsForCwd, getCachedPiSessionsByEncodedName;
|
|
3796
|
+
var init_pi_projects = __esm(() => {
|
|
3797
|
+
init_paths();
|
|
3798
|
+
init_logger();
|
|
3799
|
+
SESSION_FILE_RE = /^[\d-]+T[\d-]+Z_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
3800
|
+
cachedScan4 = runtimeCache(scanPiSessions, 30);
|
|
3801
|
+
getCachedPiProjects = runtimeCache(getPiProjects, 30);
|
|
3802
|
+
getCachedPiSessionsForCwd = runtimeCache((cwd) => getPiSessionsForCwd(cwd), 30, { maxSize: 50 });
|
|
3803
|
+
getCachedPiSessionsByEncodedName = runtimeCache((name) => getPiSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
3804
|
+
});
|
|
3805
|
+
|
|
3806
|
+
// lib/gemini-projects.ts
|
|
3807
|
+
var exports_gemini_projects = {};
|
|
3808
|
+
__export(exports_gemini_projects, {
|
|
3809
|
+
getGeminiSessionsForCwd: () => getGeminiSessionsForCwd,
|
|
3810
|
+
getGeminiSessionsByEncodedName: () => getGeminiSessionsByEncodedName,
|
|
3811
|
+
getGeminiProjects: () => getGeminiProjects,
|
|
3812
|
+
getCachedGeminiSessionsByEncodedName: () => getCachedGeminiSessionsByEncodedName,
|
|
3813
|
+
getCachedGeminiProjects: () => getCachedGeminiProjects
|
|
3814
|
+
});
|
|
3815
|
+
import { readdir as readdir5, readFile as readFile6, stat as stat5 } from "node:fs/promises";
|
|
3816
|
+
import { homedir as homedir11 } from "node:os";
|
|
3817
|
+
import { join as join9 } from "node:path";
|
|
3818
|
+
function getGeminiTmpRoot() {
|
|
3819
|
+
return process.env.GEMINI_SESSIONS_DIR || join9(homedir11(), ".gemini", "tmp");
|
|
3820
|
+
}
|
|
3821
|
+
async function safeReaddir5(dir) {
|
|
3822
|
+
try {
|
|
3823
|
+
return await readdir5(dir, { withFileTypes: true });
|
|
3824
|
+
} catch {
|
|
3825
|
+
return null;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
async function statMtime4(path) {
|
|
3829
|
+
try {
|
|
3830
|
+
return (await stat5(path)).mtime;
|
|
3831
|
+
} catch {
|
|
3832
|
+
return null;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
async function readProjectRoot(projectDir) {
|
|
3836
|
+
try {
|
|
3837
|
+
const text = await readFile6(join9(projectDir, ".project_root"), "utf-8");
|
|
3838
|
+
const trimmed = text.trim();
|
|
3839
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3840
|
+
} catch {
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
async function scanGeminiSessions() {
|
|
3845
|
+
const root = getGeminiTmpRoot();
|
|
3846
|
+
const projectDirs = await safeReaddir5(root);
|
|
3847
|
+
if (!projectDirs)
|
|
3848
|
+
return [];
|
|
3849
|
+
const out = [];
|
|
3850
|
+
await batchAll(projectDirs.filter((d) => d.isDirectory()).map((d) => async () => {
|
|
3851
|
+
const projectDir = join9(root, d.name);
|
|
3852
|
+
const cwd = await readProjectRoot(projectDir);
|
|
3853
|
+
if (!cwd)
|
|
3854
|
+
return;
|
|
3855
|
+
const chatsDir = join9(projectDir, "chats");
|
|
3856
|
+
const files = await safeReaddir5(chatsDir);
|
|
3857
|
+
if (!files)
|
|
3858
|
+
return;
|
|
3859
|
+
for (const f of files) {
|
|
3860
|
+
if (!f.isFile())
|
|
3861
|
+
continue;
|
|
3862
|
+
if (!SESSION_FILE_RE2.test(f.name))
|
|
3863
|
+
continue;
|
|
3864
|
+
const filePath = join9(chatsDir, f.name);
|
|
3865
|
+
const mtime = await statMtime4(filePath);
|
|
3866
|
+
if (!mtime)
|
|
3867
|
+
continue;
|
|
3868
|
+
out.push({ filePath, sessionFilename: f.name, cwd, fileMtime: mtime });
|
|
3869
|
+
}
|
|
3870
|
+
}), 16);
|
|
3871
|
+
return out;
|
|
3872
|
+
}
|
|
3873
|
+
async function getGeminiProjects() {
|
|
3874
|
+
const sessions = await scanGeminiSessions();
|
|
3875
|
+
const byCwd = new Map;
|
|
3876
|
+
for (const s of sessions) {
|
|
3877
|
+
const existing = byCwd.get(s.cwd);
|
|
3878
|
+
if (!existing || s.fileMtime.getTime() > existing.mtime.getTime()) {
|
|
3879
|
+
byCwd.set(s.cwd, { mtime: s.fileMtime });
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
const folders = [...byCwd.entries()].map(([cwd, { mtime }]) => ({
|
|
3883
|
+
name: encodeFolderName(cwd),
|
|
3884
|
+
path: cwd,
|
|
3885
|
+
isDirectory: true,
|
|
3886
|
+
lastModified: mtime,
|
|
3887
|
+
lastModifiedFormatted: formatDate(mtime),
|
|
3888
|
+
cli: ["gemini"]
|
|
3889
|
+
}));
|
|
3890
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3891
|
+
return folders;
|
|
3892
|
+
}
|
|
3893
|
+
async function getGeminiSessionsForCwd(cwd) {
|
|
3894
|
+
const sessions = await scanGeminiSessions();
|
|
3895
|
+
const matches = sessions.filter((s) => s.cwd === cwd);
|
|
3896
|
+
const files = matches.map((s) => {
|
|
3897
|
+
const m = s.sessionFilename.match(SESSION_FILE_RE2);
|
|
3898
|
+
return {
|
|
3899
|
+
name: s.sessionFilename,
|
|
3900
|
+
path: s.filePath,
|
|
3901
|
+
lastModified: s.fileMtime,
|
|
3902
|
+
lastModifiedFormatted: formatDate(s.fileMtime),
|
|
3903
|
+
sessionId: m ? m[2] : undefined,
|
|
3904
|
+
cli: "gemini"
|
|
3905
|
+
};
|
|
3906
|
+
});
|
|
3907
|
+
files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3908
|
+
return files;
|
|
3909
|
+
}
|
|
3910
|
+
async function getGeminiSessionsByEncodedName(name) {
|
|
3911
|
+
let metas;
|
|
3912
|
+
try {
|
|
3913
|
+
metas = await scanGeminiSessions();
|
|
3914
|
+
} catch (error) {
|
|
3915
|
+
logWarn("Failed to scan Gemini sessions:", error);
|
|
3916
|
+
return { cwd: null, sessions: [] };
|
|
3917
|
+
}
|
|
3918
|
+
const matches = metas.filter((m) => encodeFolderName(m.cwd) === name);
|
|
3919
|
+
const uniqueCwds = Array.from(new Set(matches.map((m) => m.cwd)));
|
|
3920
|
+
if (uniqueCwds.length !== 1) {
|
|
3921
|
+
return { cwd: null, sessions: [] };
|
|
3922
|
+
}
|
|
3923
|
+
const sessions = matches.map((s) => {
|
|
3924
|
+
const m = s.sessionFilename.match(SESSION_FILE_RE2);
|
|
3925
|
+
return {
|
|
3926
|
+
name: s.sessionFilename,
|
|
3927
|
+
path: s.filePath,
|
|
3928
|
+
lastModified: s.fileMtime,
|
|
3929
|
+
lastModifiedFormatted: formatDate(s.fileMtime),
|
|
3930
|
+
sessionId: m ? m[2] : undefined,
|
|
3931
|
+
cli: "gemini"
|
|
3932
|
+
};
|
|
3933
|
+
});
|
|
3934
|
+
sessions.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3935
|
+
return { cwd: uniqueCwds[0], sessions };
|
|
3936
|
+
}
|
|
3937
|
+
var SESSION_FILE_RE2, getCachedGeminiProjects, getCachedGeminiSessionsByEncodedName;
|
|
3938
|
+
var init_gemini_projects = __esm(() => {
|
|
3939
|
+
init_paths();
|
|
3940
|
+
init_logger();
|
|
3941
|
+
SESSION_FILE_RE2 = /^session-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2})-([0-9a-f]{8})\.jsonl$/i;
|
|
3942
|
+
getCachedGeminiProjects = runtimeCache(getGeminiProjects, 30);
|
|
3943
|
+
getCachedGeminiSessionsByEncodedName = runtimeCache((name) => getGeminiSessionsByEncodedName(name), 30, { maxSize: 50 });
|
|
3944
|
+
});
|
|
3945
|
+
|
|
3946
|
+
// lib/projects.ts
|
|
3947
|
+
import { readdir as readdir6, stat as stat6 } from "node:fs/promises";
|
|
3948
|
+
import { join as join10, resolve as resolve5, sep } from "node:path";
|
|
3949
|
+
async function getMtime(path, label) {
|
|
3950
|
+
try {
|
|
3951
|
+
return (await stat6(path)).mtime;
|
|
3952
|
+
} catch (error) {
|
|
3953
|
+
logWarn(`Failed to stat ${label}:`, error);
|
|
3954
|
+
return new Date(0);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
async function safeReaddir6(dirPath) {
|
|
3958
|
+
try {
|
|
3959
|
+
const s = await stat6(dirPath);
|
|
3960
|
+
if (!s.isDirectory())
|
|
3961
|
+
return null;
|
|
3962
|
+
return await readdir6(dirPath, { withFileTypes: true });
|
|
3963
|
+
} catch {
|
|
3964
|
+
return null;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
async function getClaudeProjectFolders() {
|
|
3968
|
+
try {
|
|
3969
|
+
const projectsPath = getClaudeProjectsPath();
|
|
3970
|
+
const entries = await safeReaddir6(projectsPath);
|
|
3971
|
+
if (!entries)
|
|
3972
|
+
return [];
|
|
3973
|
+
const settled = await batchAll(entries.filter((entry) => entry.isDirectory()).map((entry) => async () => {
|
|
3974
|
+
const folderPath = join10(projectsPath, entry.name);
|
|
3975
|
+
const mtime = await getMtime(folderPath, entry.name);
|
|
3976
|
+
return {
|
|
3977
|
+
name: entry.name,
|
|
3978
|
+
path: folderPath,
|
|
3979
|
+
isDirectory: true,
|
|
3980
|
+
lastModified: mtime,
|
|
3981
|
+
lastModifiedFormatted: formatDate(mtime),
|
|
3982
|
+
cli: ["claude"]
|
|
3983
|
+
};
|
|
3984
|
+
}), 16);
|
|
3985
|
+
const folders = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
3986
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
3987
|
+
return folders;
|
|
3988
|
+
} catch (error) {
|
|
3989
|
+
logError("Error reading Claude project folders:", error);
|
|
3990
|
+
return [];
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function mergeProjectFolders(...sources) {
|
|
3994
|
+
const byName = new Map;
|
|
3995
|
+
for (const list of sources) {
|
|
3996
|
+
for (const f of list) {
|
|
3997
|
+
const existing = byName.get(f.name);
|
|
3998
|
+
if (!existing) {
|
|
3999
|
+
byName.set(f.name, { ...f, cli: [...f.cli] });
|
|
4000
|
+
continue;
|
|
4001
|
+
}
|
|
4002
|
+
const mergedCli = [...existing.cli];
|
|
4003
|
+
for (const c of f.cli)
|
|
4004
|
+
if (!mergedCli.includes(c))
|
|
4005
|
+
mergedCli.push(c);
|
|
4006
|
+
const newer = f.lastModified.getTime() > existing.lastModified.getTime() ? f : existing;
|
|
4007
|
+
byName.set(f.name, {
|
|
4008
|
+
...existing,
|
|
4009
|
+
cli: mergedCli,
|
|
4010
|
+
lastModified: newer.lastModified,
|
|
4011
|
+
lastModifiedFormatted: newer.lastModifiedFormatted
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
const merged = Array.from(byName.values());
|
|
4016
|
+
merged.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
4017
|
+
return merged;
|
|
4018
|
+
}
|
|
4019
|
+
async function getProjectFolders() {
|
|
4020
|
+
const [
|
|
4021
|
+
{ getCodexProjects: getCodexProjects2 },
|
|
4022
|
+
{ getCopilotProjects: getCopilotProjects2 },
|
|
4023
|
+
{ getCursorProjects: getCursorProjects2 },
|
|
4024
|
+
{ getOpenCodeProjects: getOpenCodeProjects2 },
|
|
4025
|
+
{ getPiProjects: getPiProjects2 },
|
|
4026
|
+
{ getGeminiProjects: getGeminiProjects2 }
|
|
4027
|
+
] = await Promise.all([
|
|
4028
|
+
Promise.resolve().then(() => (init_codex_projects(), exports_codex_projects)),
|
|
4029
|
+
Promise.resolve().then(() => (init_copilot_projects(), exports_copilot_projects)),
|
|
4030
|
+
Promise.resolve().then(() => (init_cursor_projects(), exports_cursor_projects)),
|
|
4031
|
+
Promise.resolve().then(() => (init_opencode_projects(), exports_opencode_projects)),
|
|
4032
|
+
Promise.resolve().then(() => (init_pi_projects(), exports_pi_projects)),
|
|
4033
|
+
Promise.resolve().then(() => (init_gemini_projects(), exports_gemini_projects))
|
|
4034
|
+
]);
|
|
4035
|
+
const [claude, codex, copilot, cursor, opencode, pi, gemini] = await Promise.all([
|
|
4036
|
+
getClaudeProjectFolders(),
|
|
4037
|
+
getCodexProjects2().catch((error) => {
|
|
4038
|
+
logError("Error reading Codex projects:", error);
|
|
4039
|
+
return [];
|
|
4040
|
+
}),
|
|
4041
|
+
getCopilotProjects2().catch((error) => {
|
|
4042
|
+
logError("Error reading Copilot projects:", error);
|
|
4043
|
+
return [];
|
|
4044
|
+
}),
|
|
4045
|
+
getCursorProjects2().catch((error) => {
|
|
4046
|
+
logError("Error reading Cursor projects:", error);
|
|
4047
|
+
return [];
|
|
4048
|
+
}),
|
|
4049
|
+
getOpenCodeProjects2().catch((error) => {
|
|
4050
|
+
logError("Error reading OpenCode projects:", error);
|
|
4051
|
+
return [];
|
|
4052
|
+
}),
|
|
4053
|
+
getPiProjects2().catch((error) => {
|
|
4054
|
+
logError("Error reading Pi projects:", error);
|
|
4055
|
+
return [];
|
|
4056
|
+
}),
|
|
4057
|
+
getGeminiProjects2().catch((error) => {
|
|
4058
|
+
logError("Error reading Gemini projects:", error);
|
|
4059
|
+
return [];
|
|
4060
|
+
})
|
|
4061
|
+
]);
|
|
4062
|
+
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, gemini);
|
|
4063
|
+
}
|
|
4064
|
+
function resolveProjectPath(projectName) {
|
|
4065
|
+
if (!projectName)
|
|
4066
|
+
throw new RangeError("Empty project name");
|
|
4067
|
+
if (/^[/\\]/.test(projectName))
|
|
4068
|
+
throw new RangeError("Absolute project name");
|
|
4069
|
+
const projectsPath = resolve5(getClaudeProjectsPath());
|
|
4070
|
+
const candidate = resolve5(join10(projectsPath, projectName));
|
|
4071
|
+
if (!candidate.startsWith(projectsPath + sep)) {
|
|
4072
|
+
throw new RangeError("Project path escapes root");
|
|
4073
|
+
}
|
|
4074
|
+
return candidate;
|
|
4075
|
+
}
|
|
4076
|
+
function extractSessionId2(filename) {
|
|
4077
|
+
const uuidPattern = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
4078
|
+
const match = filename.match(uuidPattern);
|
|
4079
|
+
return match ? match[0] : undefined;
|
|
2842
4080
|
}
|
|
2843
4081
|
async function getSessionFiles(projectPath) {
|
|
2844
4082
|
try {
|
|
2845
|
-
const entries = await
|
|
4083
|
+
const entries = await safeReaddir6(projectPath);
|
|
2846
4084
|
if (!entries)
|
|
2847
4085
|
return [];
|
|
2848
4086
|
const jsonlEntries = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl") && extractSessionId2(entry.name));
|
|
2849
4087
|
const settled = await batchAll(jsonlEntries.map((entry) => async () => {
|
|
2850
|
-
const filePath =
|
|
4088
|
+
const filePath = join10(projectPath, entry.name);
|
|
2851
4089
|
const mtime = await getMtime(filePath, entry.name);
|
|
2852
4090
|
return {
|
|
2853
4091
|
name: entry.name,
|
|
@@ -2876,13 +4114,13 @@ var init_projects = __esm(() => {
|
|
|
2876
4114
|
|
|
2877
4115
|
// lib/resolve-subagent-path.ts
|
|
2878
4116
|
import { access as access2 } from "fs/promises";
|
|
2879
|
-
import { join as
|
|
4117
|
+
import { join as join11, relative as relative2 } from "path";
|
|
2880
4118
|
async function resolveSubagentPath(projectsPath, projectName, sessionId, agentId) {
|
|
2881
4119
|
const fileName = `agent-${agentId}.jsonl`;
|
|
2882
4120
|
const candidatePaths = [
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
4121
|
+
join11(projectsPath, projectName, fileName),
|
|
4122
|
+
join11(projectsPath, projectName, sessionId, fileName),
|
|
4123
|
+
join11(projectsPath, projectName, sessionId, "subagents", fileName)
|
|
2886
4124
|
];
|
|
2887
4125
|
for (const candidatePath of candidatePaths) {
|
|
2888
4126
|
if (relative2(projectsPath, candidatePath).startsWith("..")) {
|
|
@@ -2920,8 +4158,8 @@ function formatDuration(ms) {
|
|
|
2920
4158
|
}
|
|
2921
4159
|
|
|
2922
4160
|
// lib/log-entries.ts
|
|
2923
|
-
import { readFile as
|
|
2924
|
-
import { join as
|
|
4161
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4162
|
+
import { join as join12 } from "path";
|
|
2925
4163
|
function formatTimestamp(date) {
|
|
2926
4164
|
const base = formatDate(date);
|
|
2927
4165
|
const ms = date.getMilliseconds().toString().padStart(3, "0");
|
|
@@ -3102,8 +4340,8 @@ async function parseFileContent(fileContent, source) {
|
|
|
3102
4340
|
async function parseSessionLog(projectName, sessionId) {
|
|
3103
4341
|
const projectDir = resolveProjectPath(projectName);
|
|
3104
4342
|
const projectsPath = getClaudeProjectsPath();
|
|
3105
|
-
const filePath =
|
|
3106
|
-
const fileContent = await
|
|
4343
|
+
const filePath = join12(projectDir, `${sessionId}.jsonl`);
|
|
4344
|
+
const fileContent = await readFile7(filePath, "utf-8");
|
|
3107
4345
|
const { entries: sessionEntries, rawLines: sessionRawLines, subagentIds } = await parseFileContent(fileContent, "session");
|
|
3108
4346
|
if (subagentIds.length === 0) {
|
|
3109
4347
|
return { entries: sessionEntries, rawLines: sessionRawLines, subagentIds: [] };
|
|
@@ -3113,7 +4351,7 @@ async function parseSessionLog(projectName, sessionId) {
|
|
|
3113
4351
|
const agentPath = await resolveSubagentPath(projectsPath, projectName, sessionId, agentId);
|
|
3114
4352
|
if (!agentPath)
|
|
3115
4353
|
return null;
|
|
3116
|
-
const agentContent = await
|
|
4354
|
+
const agentContent = await readFile7(agentPath, "utf-8");
|
|
3117
4355
|
const { entries, rawLines } = await parseFileContent(agentContent, agentSource);
|
|
3118
4356
|
return { entries, rawLines };
|
|
3119
4357
|
}), SUBAGENT_CONCURRENCY);
|
|
@@ -3140,9 +4378,9 @@ var init_log_entries = __esm(() => {
|
|
|
3140
4378
|
|
|
3141
4379
|
// lib/codex-sessions.ts
|
|
3142
4380
|
import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, statSync as statSync4 } from "node:fs";
|
|
3143
|
-
import { readFile as
|
|
3144
|
-
import { dirname as dirname3, join as
|
|
3145
|
-
import { homedir as
|
|
4381
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
4382
|
+
import { dirname as dirname3, join as join13 } from "node:path";
|
|
4383
|
+
import { homedir as homedir12 } from "node:os";
|
|
3146
4384
|
function readCache() {
|
|
3147
4385
|
try {
|
|
3148
4386
|
if (!existsSync5(CACHE_PATH))
|
|
@@ -3164,7 +4402,7 @@ function dirSearch(dir, sessionId) {
|
|
|
3164
4402
|
try {
|
|
3165
4403
|
for (const f of readdirSync3(dir, { withFileTypes: true })) {
|
|
3166
4404
|
if (f.isFile() && f.name.includes(sessionId) && f.name.endsWith(".jsonl")) {
|
|
3167
|
-
return
|
|
4405
|
+
return join13(dir, f.name);
|
|
3168
4406
|
}
|
|
3169
4407
|
}
|
|
3170
4408
|
} catch {}
|
|
@@ -3175,14 +4413,14 @@ function findCodexTranscript(sessionId) {
|
|
|
3175
4413
|
const cached = cache[sessionId];
|
|
3176
4414
|
if (cached && existsSync5(cached))
|
|
3177
4415
|
return cached;
|
|
3178
|
-
const root =
|
|
4416
|
+
const root = join13(homedir12(), ".codex", "sessions");
|
|
3179
4417
|
const today = new Date;
|
|
3180
4418
|
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
|
3181
4419
|
const datedDirs = [today, yesterday].map((d) => {
|
|
3182
4420
|
const y = String(d.getUTCFullYear());
|
|
3183
4421
|
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
3184
4422
|
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
3185
|
-
return
|
|
4423
|
+
return join13(root, y, m, day);
|
|
3186
4424
|
});
|
|
3187
4425
|
for (const dir of datedDirs) {
|
|
3188
4426
|
const hit = dirSearch(dir, sessionId);
|
|
@@ -3195,13 +4433,13 @@ function findCodexTranscript(sessionId) {
|
|
|
3195
4433
|
for (const y of readdirSync3(root, { withFileTypes: true })) {
|
|
3196
4434
|
if (!y.isDirectory())
|
|
3197
4435
|
continue;
|
|
3198
|
-
for (const m of readdirSync3(
|
|
4436
|
+
for (const m of readdirSync3(join13(root, y.name), { withFileTypes: true })) {
|
|
3199
4437
|
if (!m.isDirectory())
|
|
3200
4438
|
continue;
|
|
3201
|
-
for (const d of readdirSync3(
|
|
4439
|
+
for (const d of readdirSync3(join13(root, y.name, m.name), { withFileTypes: true })) {
|
|
3202
4440
|
if (!d.isDirectory())
|
|
3203
4441
|
continue;
|
|
3204
|
-
const hit = dirSearch(
|
|
4442
|
+
const hit = dirSearch(join13(root, y.name, m.name, d.name), sessionId);
|
|
3205
4443
|
if (hit) {
|
|
3206
4444
|
writeCacheEntry(sessionId, hit);
|
|
3207
4445
|
return hit;
|
|
@@ -3414,14 +4652,14 @@ async function getCodexSessionLog(sessionId) {
|
|
|
3414
4652
|
const filePath = findCodexTranscript(sessionId);
|
|
3415
4653
|
if (!filePath)
|
|
3416
4654
|
return null;
|
|
3417
|
-
const fileContent = await
|
|
4655
|
+
const fileContent = await readFile8(filePath, "utf-8");
|
|
3418
4656
|
const { entries, rawLines, cwd } = await parseCodexLog(fileContent, "session");
|
|
3419
4657
|
return { entries, rawLines, cwd, filePath };
|
|
3420
4658
|
}
|
|
3421
4659
|
var CACHE_PATH, getCachedCodexSessionLog;
|
|
3422
4660
|
var init_codex_sessions = __esm(() => {
|
|
3423
4661
|
init_log_entries();
|
|
3424
|
-
CACHE_PATH =
|
|
4662
|
+
CACHE_PATH = join13(homedir12(), ".failproofai", "cache", "codex-session-paths.json");
|
|
3425
4663
|
getCachedCodexSessionLog = runtimeCache((sessionId) => getCodexSessionLog(sessionId), 60, { maxSize: 50 });
|
|
3426
4664
|
});
|
|
3427
4665
|
|
|
@@ -3557,8 +4795,8 @@ import {
|
|
|
3557
4795
|
openSync,
|
|
3558
4796
|
closeSync
|
|
3559
4797
|
} from "node:fs";
|
|
3560
|
-
import { join as
|
|
3561
|
-
import { homedir as
|
|
4798
|
+
import { join as join14 } from "node:path";
|
|
4799
|
+
import { homedir as homedir13 } from "node:os";
|
|
3562
4800
|
function ensureAuthDir() {
|
|
3563
4801
|
if (!existsSync6(AUTH_DIR))
|
|
3564
4802
|
mkdirSync5(AUTH_DIR, { recursive: true, mode: 448 });
|
|
@@ -3593,8 +4831,8 @@ function isLoggedIn() {
|
|
|
3593
4831
|
}
|
|
3594
4832
|
var AUTH_DIR, AUTH_FILE;
|
|
3595
4833
|
var init_token_store = __esm(() => {
|
|
3596
|
-
AUTH_DIR =
|
|
3597
|
-
AUTH_FILE =
|
|
4834
|
+
AUTH_DIR = join14(homedir13(), ".failproofai");
|
|
4835
|
+
AUTH_FILE = join14(AUTH_DIR, "auth.json");
|
|
3598
4836
|
});
|
|
3599
4837
|
|
|
3600
4838
|
// src/relay/queue.ts
|
|
@@ -3618,8 +4856,8 @@ import {
|
|
|
3618
4856
|
readdirSync as readdirSync4,
|
|
3619
4857
|
chmodSync
|
|
3620
4858
|
} from "node:fs";
|
|
3621
|
-
import { join as
|
|
3622
|
-
import { homedir as
|
|
4859
|
+
import { join as join15 } from "node:path";
|
|
4860
|
+
import { homedir as homedir14 } from "node:os";
|
|
3623
4861
|
import { createHash, randomUUID } from "node:crypto";
|
|
3624
4862
|
function hashCwd(cwd) {
|
|
3625
4863
|
if (!cwd)
|
|
@@ -3687,7 +4925,7 @@ function claimPendingBatch() {
|
|
|
3687
4925
|
return null;
|
|
3688
4926
|
}
|
|
3689
4927
|
const seq = `${Date.now()}-${process.pid}`;
|
|
3690
|
-
const processingFile =
|
|
4928
|
+
const processingFile = join15(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
|
|
3691
4929
|
try {
|
|
3692
4930
|
renameSync4(PENDING_FILE, processingFile);
|
|
3693
4931
|
try {
|
|
@@ -3704,7 +4942,7 @@ function claimPendingBatch() {
|
|
|
3704
4942
|
function findOrphanProcessingFiles() {
|
|
3705
4943
|
ensureDir2();
|
|
3706
4944
|
try {
|
|
3707
|
-
return readdirSync4(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) =>
|
|
4945
|
+
return readdirSync4(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) => join15(QUEUE_DIR, n)).sort();
|
|
3708
4946
|
} catch {
|
|
3709
4947
|
return [];
|
|
3710
4948
|
}
|
|
@@ -3733,15 +4971,15 @@ function deleteProcessingFile(path2) {
|
|
|
3733
4971
|
var QUEUE_DIR, PENDING_FILE, PROCESSING_PREFIX = "processing-", MAX_QUEUE_BYTES;
|
|
3734
4972
|
var init_queue = __esm(() => {
|
|
3735
4973
|
init_token_store();
|
|
3736
|
-
QUEUE_DIR =
|
|
3737
|
-
PENDING_FILE =
|
|
4974
|
+
QUEUE_DIR = join15(homedir14(), ".failproofai", "cache", "server-queue");
|
|
4975
|
+
PENDING_FILE = join15(QUEUE_DIR, "pending.jsonl");
|
|
3738
4976
|
MAX_QUEUE_BYTES = 50 * 1024 * 1024;
|
|
3739
4977
|
});
|
|
3740
4978
|
|
|
3741
4979
|
// src/relay/pid.ts
|
|
3742
4980
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8, unlinkSync as unlinkSync4, mkdirSync as mkdirSync7 } from "node:fs";
|
|
3743
|
-
import { join as
|
|
3744
|
-
import { homedir as
|
|
4981
|
+
import { join as join16, dirname as dirname4 } from "node:path";
|
|
4982
|
+
import { homedir as homedir15 } from "node:os";
|
|
3745
4983
|
function readPid() {
|
|
3746
4984
|
if (!existsSync8(PID_FILE))
|
|
3747
4985
|
return null;
|
|
@@ -3794,7 +5032,7 @@ function stopRelay() {
|
|
|
3794
5032
|
}
|
|
3795
5033
|
var PID_FILE;
|
|
3796
5034
|
var init_pid = __esm(() => {
|
|
3797
|
-
PID_FILE =
|
|
5035
|
+
PID_FILE = join16(homedir15(), ".failproofai", "relay.pid");
|
|
3798
5036
|
});
|
|
3799
5037
|
|
|
3800
5038
|
// src/relay/daemon.ts
|
|
@@ -3807,8 +5045,8 @@ __export(exports_daemon, {
|
|
|
3807
5045
|
});
|
|
3808
5046
|
import { spawn } from "node:child_process";
|
|
3809
5047
|
import { existsSync as existsSync9 } from "node:fs";
|
|
3810
|
-
import { join as
|
|
3811
|
-
import { homedir as
|
|
5048
|
+
import { join as join17 } from "node:path";
|
|
5049
|
+
import { homedir as homedir16 } from "node:os";
|
|
3812
5050
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3813
5051
|
function ensureRelayRunning() {
|
|
3814
5052
|
if (!isLoggedIn())
|
|
@@ -4075,7 +5313,7 @@ var init_daemon = __esm(() => {
|
|
|
4075
5313
|
init_token_store();
|
|
4076
5314
|
init_pid();
|
|
4077
5315
|
init_queue();
|
|
4078
|
-
QUEUE_DIR2 =
|
|
5316
|
+
QUEUE_DIR2 = join17(homedir16(), ".failproofai", "cache", "server-queue");
|
|
4079
5317
|
});
|
|
4080
5318
|
|
|
4081
5319
|
// src/hooks/handler.ts
|
|
@@ -4089,6 +5327,29 @@ function canonicalizeEventType(raw, cli) {
|
|
|
4089
5327
|
if (mapped)
|
|
4090
5328
|
return mapped;
|
|
4091
5329
|
}
|
|
5330
|
+
if (cli === "cursor") {
|
|
5331
|
+
const mapped = CURSOR_EVENT_MAP[raw];
|
|
5332
|
+
if (mapped)
|
|
5333
|
+
return mapped;
|
|
5334
|
+
}
|
|
5335
|
+
if (cli === "pi") {
|
|
5336
|
+
const mapped = PI_EVENT_MAP[raw];
|
|
5337
|
+
if (mapped)
|
|
5338
|
+
return mapped;
|
|
5339
|
+
}
|
|
5340
|
+
if (cli === "gemini") {
|
|
5341
|
+
const mapped = GEMINI_EVENT_MAP[raw];
|
|
5342
|
+
if (mapped)
|
|
5343
|
+
return mapped;
|
|
5344
|
+
}
|
|
5345
|
+
return raw;
|
|
5346
|
+
}
|
|
5347
|
+
function canonicalizeToolName(raw, cli) {
|
|
5348
|
+
if (!raw)
|
|
5349
|
+
return raw;
|
|
5350
|
+
if (cli === "gemini") {
|
|
5351
|
+
return GEMINI_TOOL_MAP[raw] ?? raw;
|
|
5352
|
+
}
|
|
4092
5353
|
return raw;
|
|
4093
5354
|
}
|
|
4094
5355
|
async function handleHookEvent(eventType, cli = "claude") {
|
|
@@ -4127,6 +5388,11 @@ async function handleHookEvent(eventType, cli = "claude") {
|
|
|
4127
5388
|
}
|
|
4128
5389
|
}
|
|
4129
5390
|
const canonicalEventType = canonicalizeEventType(eventType, cli);
|
|
5391
|
+
const rawToolName = parsed.tool_name;
|
|
5392
|
+
const canonicalToolName = canonicalizeToolName(rawToolName, cli);
|
|
5393
|
+
if (canonicalToolName !== rawToolName) {
|
|
5394
|
+
parsed.tool_name = canonicalToolName;
|
|
5395
|
+
}
|
|
4130
5396
|
const sessionId = parsed.session_id;
|
|
4131
5397
|
const session = {
|
|
4132
5398
|
sessionId,
|
|
@@ -4134,6 +5400,7 @@ async function handleHookEvent(eventType, cli = "claude") {
|
|
|
4134
5400
|
cwd: parsed.cwd,
|
|
4135
5401
|
permissionMode: resolvePermissionMode(cli, parsed, sessionId),
|
|
4136
5402
|
hookEventName: parsed.hook_event_name,
|
|
5403
|
+
rawHookEventName: eventType,
|
|
4137
5404
|
cli
|
|
4138
5405
|
};
|
|
4139
5406
|
const config = readMergedHooksConfig(session.cwd);
|
|
@@ -4275,8 +5542,8 @@ __export(exports_daemon2, {
|
|
|
4275
5542
|
});
|
|
4276
5543
|
import { spawn as spawn2 } from "node:child_process";
|
|
4277
5544
|
import { existsSync as existsSync10 } from "node:fs";
|
|
4278
|
-
import { join as
|
|
4279
|
-
import { homedir as
|
|
5545
|
+
import { join as join18 } from "node:path";
|
|
5546
|
+
import { homedir as homedir17 } from "node:os";
|
|
4280
5547
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4281
5548
|
function ensureRelayRunning2() {
|
|
4282
5549
|
if (!isLoggedIn())
|
|
@@ -4543,14 +5810,15 @@ var init_daemon2 = __esm(() => {
|
|
|
4543
5810
|
init_token_store();
|
|
4544
5811
|
init_pid();
|
|
4545
5812
|
init_queue();
|
|
4546
|
-
QUEUE_DIR3 =
|
|
5813
|
+
QUEUE_DIR3 = join18(homedir17(), ".failproofai", "cache", "server-queue");
|
|
4547
5814
|
});
|
|
4548
5815
|
|
|
4549
5816
|
// src/hooks/integrations.ts
|
|
4550
5817
|
import { execSync as execSync3 } from "node:child_process";
|
|
4551
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
|
|
5818
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync8, unlinkSync as unlinkSync5 } from "node:fs";
|
|
4552
5819
|
import { resolve as resolve6, dirname as dirname5 } from "node:path";
|
|
4553
|
-
import {
|
|
5820
|
+
import { fileURLToPath } from "node:url";
|
|
5821
|
+
import { homedir as homedir18 } from "node:os";
|
|
4554
5822
|
function readJsonFile(path2) {
|
|
4555
5823
|
if (!existsSync11(path2))
|
|
4556
5824
|
return {};
|
|
@@ -4563,9 +5831,12 @@ function writeJsonFile(path2, data) {
|
|
|
4563
5831
|
`, "utf8");
|
|
4564
5832
|
}
|
|
4565
5833
|
function isMarkedHook(hook) {
|
|
4566
|
-
if (hook
|
|
5834
|
+
if (!hook || typeof hook !== "object")
|
|
5835
|
+
return false;
|
|
5836
|
+
const h = hook;
|
|
5837
|
+
if (h[FAILPROOFAI_HOOK_MARKER] === true)
|
|
4567
5838
|
return true;
|
|
4568
|
-
const cmd = typeof
|
|
5839
|
+
const cmd = typeof h.command === "string" ? h.command : "";
|
|
4569
5840
|
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
4570
5841
|
}
|
|
4571
5842
|
function binaryExists(name) {
|
|
@@ -4577,6 +5848,194 @@ function binaryExists(name) {
|
|
|
4577
5848
|
return false;
|
|
4578
5849
|
}
|
|
4579
5850
|
}
|
|
5851
|
+
function isMarkedCopilotHook(hook) {
|
|
5852
|
+
if (hook[FAILPROOFAI_HOOK_MARKER] === true)
|
|
5853
|
+
return true;
|
|
5854
|
+
const bash = typeof hook.bash === "string" ? hook.bash : "";
|
|
5855
|
+
const ps = typeof hook.powershell === "string" ? hook.powershell : "";
|
|
5856
|
+
for (const cmd of [bash, ps]) {
|
|
5857
|
+
if (cmd.includes("failproofai") && cmd.includes("--hook"))
|
|
5858
|
+
return true;
|
|
5859
|
+
}
|
|
5860
|
+
return false;
|
|
5861
|
+
}
|
|
5862
|
+
function opencodePluginFilePath(settingsPath) {
|
|
5863
|
+
return resolve6(dirname5(settingsPath), "plugins", "failproofai.mjs");
|
|
5864
|
+
}
|
|
5865
|
+
function buildOpenCodePluginShim(binaryPath, scope) {
|
|
5866
|
+
const useNpx = scope === "project";
|
|
5867
|
+
const escapedBin = useNpx ? '""' : JSON.stringify(binaryPath);
|
|
5868
|
+
return `// AUTO-GENERATED by failproofai. ${FAILPROOFAI_HOOK_MARKER}
|
|
5869
|
+
// Re-generate via: failproofai policies --install --cli opencode
|
|
5870
|
+
// Plugin shim that bridges OpenCode's plugin API to the failproofai binary.
|
|
5871
|
+
// See: https://opencode.ai/docs/plugins/
|
|
5872
|
+
import { spawnSync } from "node:child_process";
|
|
5873
|
+
|
|
5874
|
+
// Map opencode bus-event types → canonical failproofai event names.
|
|
5875
|
+
// (The binary sees PascalCase — the binary's --cli=opencode flag is for
|
|
5876
|
+
// telemetry / activity tagging only; no opencode branch in handler.ts.)
|
|
5877
|
+
const BUS_EVENT_MAP = {
|
|
5878
|
+
"session.created": "SessionStart",
|
|
5879
|
+
"session.deleted": "SessionEnd",
|
|
5880
|
+
"session.idle": "Stop",
|
|
5881
|
+
// message.updated is handled separately (filter to role:user); see below.
|
|
5882
|
+
};
|
|
5883
|
+
|
|
5884
|
+
const FAILPROOFAI_BIN = ${escapedBin};
|
|
5885
|
+
const USE_NPX = ${useNpx};
|
|
5886
|
+
|
|
5887
|
+
function runFailproofai(eventName, payload, directory) {
|
|
5888
|
+
const cmd = USE_NPX ? "npx" : FAILPROOFAI_BIN;
|
|
5889
|
+
const args = USE_NPX
|
|
5890
|
+
? ["-y", "failproofai", "--hook", eventName, "--cli", "opencode"]
|
|
5891
|
+
: ["--hook", eventName, "--cli", "opencode"];
|
|
5892
|
+
const r = spawnSync(cmd, args, {
|
|
5893
|
+
input: JSON.stringify(payload),
|
|
5894
|
+
encoding: "utf8",
|
|
5895
|
+
timeout: 60_000,
|
|
5896
|
+
cwd: directory,
|
|
5897
|
+
});
|
|
5898
|
+
return { exitCode: r.status ?? 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
function applyDecision(result, ctx) {
|
|
5902
|
+
// Deny path 1: exit 2 (Claude Stop-style or any non-Pre/Post deny).
|
|
5903
|
+
if (result.exitCode === 2) {
|
|
5904
|
+
throw new Error((result.stderr || "").trim() || "Blocked by failproofai");
|
|
5905
|
+
}
|
|
5906
|
+
// Deny path 2: stdout JSON with hookSpecificOutput.permissionDecision === "deny".
|
|
5907
|
+
let parsed = null;
|
|
5908
|
+
try { parsed = JSON.parse(result.stdout); } catch { /* fail-open allow */ }
|
|
5909
|
+
if (!parsed) return;
|
|
5910
|
+
const out = parsed.hookSpecificOutput;
|
|
5911
|
+
if (out && out.permissionDecision === "deny") {
|
|
5912
|
+
throw new Error(out.permissionDecisionReason || "Blocked by failproofai");
|
|
5913
|
+
}
|
|
5914
|
+
// Codex-shape PermissionRequest deny: hookSpecificOutput.decision.behavior.
|
|
5915
|
+
if (out && out.decision && out.decision.behavior === "deny") {
|
|
5916
|
+
throw new Error((out.decision.message) || "Blocked by failproofai");
|
|
5917
|
+
}
|
|
5918
|
+
// Instruct: forward the additional context as a prompt to the session.
|
|
5919
|
+
const ctxText = out && out.additionalContext;
|
|
5920
|
+
if (ctxText && ctx && ctx.client && ctx.sessionID) {
|
|
5921
|
+
// Fire-and-forget: don't block the tool call on the SDK round-trip.
|
|
5922
|
+
Promise.resolve(ctx.client.session.prompt({
|
|
5923
|
+
path: { id: ctx.sessionID },
|
|
5924
|
+
body: { parts: [{ type: "text", text: ctxText }] },
|
|
5925
|
+
})).catch(() => {});
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
export default async function failproofaiPlugin({ client, directory }) {
|
|
5930
|
+
return {
|
|
5931
|
+
// Generic bus events: session lifecycle + user-prompt detection.
|
|
5932
|
+
event: async ({ event }) => {
|
|
5933
|
+
if (!event || !event.type) return;
|
|
5934
|
+
|
|
5935
|
+
// UserPromptSubmit — filter message.updated to user role only so we
|
|
5936
|
+
// don't fire on every assistant token. Forward the prompt text so
|
|
5937
|
+
// prompt-based policies (sanitize-* on input, content checks) see it.
|
|
5938
|
+
if (event.type === "message.updated") {
|
|
5939
|
+
const props = event.properties || {};
|
|
5940
|
+
const info = props.info || props.message || {};
|
|
5941
|
+
const role = info.role || props.role;
|
|
5942
|
+
if (role !== "user") return;
|
|
5943
|
+
const sessionID = info.sessionID || info.sessionId || info.session_id || props.sessionID;
|
|
5944
|
+
// OpenCode's message shape: parts is an array of {type, text, ...}.
|
|
5945
|
+
// Concatenate text parts to reconstruct the user-facing prompt.
|
|
5946
|
+
// Fall back to direct text/content fields if a future shape differs.
|
|
5947
|
+
let prompt = "";
|
|
5948
|
+
const parts = info.parts || props.parts || [];
|
|
5949
|
+
if (Array.isArray(parts)) {
|
|
5950
|
+
for (const p of parts) {
|
|
5951
|
+
if (p && typeof p === "object" && typeof p.text === "string") prompt += p.text;
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5954
|
+
if (!prompt) prompt = (info.text || info.content || props.text || "").toString();
|
|
5955
|
+
const r = runFailproofai("UserPromptSubmit", {
|
|
5956
|
+
session_id: sessionID, cwd: directory, hook_event_name: "UserPromptSubmit", prompt,
|
|
5957
|
+
}, directory);
|
|
5958
|
+
applyDecision(r, { client, sessionID });
|
|
5959
|
+
return;
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
const claudeEvent = BUS_EVENT_MAP[event.type];
|
|
5963
|
+
if (!claudeEvent) return;
|
|
5964
|
+
const props = event.properties || {};
|
|
5965
|
+
const sessionID = props.sessionID || (props.session && props.session.id) || props.id;
|
|
5966
|
+
const r = runFailproofai(claudeEvent, {
|
|
5967
|
+
session_id: sessionID, cwd: directory, hook_event_name: claudeEvent,
|
|
5968
|
+
}, directory);
|
|
5969
|
+
applyDecision(r, { client, sessionID });
|
|
5970
|
+
},
|
|
5971
|
+
|
|
5972
|
+
// First-class PreToolUse hook. Note: tool args live on output.args (mutable).
|
|
5973
|
+
"tool.execute.before": async (input, output) => {
|
|
5974
|
+
const r = runFailproofai("PreToolUse", {
|
|
5975
|
+
session_id: input.sessionID,
|
|
5976
|
+
cwd: directory,
|
|
5977
|
+
tool_name: input.tool,
|
|
5978
|
+
tool_input: output.args,
|
|
5979
|
+
hook_event_name: "PreToolUse",
|
|
5980
|
+
}, directory);
|
|
5981
|
+
applyDecision(r, { client, sessionID: input.sessionID });
|
|
5982
|
+
},
|
|
5983
|
+
|
|
5984
|
+
// First-class PostToolUse hook. Note: tool args live on input.args here.
|
|
5985
|
+
"tool.execute.after": async (input, output) => {
|
|
5986
|
+
const r = runFailproofai("PostToolUse", {
|
|
5987
|
+
session_id: input.sessionID,
|
|
5988
|
+
cwd: directory,
|
|
5989
|
+
tool_name: input.tool,
|
|
5990
|
+
tool_input: input.args,
|
|
5991
|
+
tool_response: { title: output.title, output: output.output, metadata: output.metadata },
|
|
5992
|
+
hook_event_name: "PostToolUse",
|
|
5993
|
+
}, directory);
|
|
5994
|
+
applyDecision(r, { client, sessionID: input.sessionID });
|
|
5995
|
+
},
|
|
5996
|
+
|
|
5997
|
+
// Cleaner deny UX for prompted tools — mutate output.status instead of throwing.
|
|
5998
|
+
"permission.ask": async (input, output) => {
|
|
5999
|
+
const r = runFailproofai("PermissionRequest", {
|
|
6000
|
+
session_id: input.sessionID,
|
|
6001
|
+
cwd: directory,
|
|
6002
|
+
tool_name: input.tool || input.command || "permission",
|
|
6003
|
+
tool_input: input,
|
|
6004
|
+
hook_event_name: "PermissionRequest",
|
|
6005
|
+
}, directory);
|
|
6006
|
+
try {
|
|
6007
|
+
applyDecision(r, { client, sessionID: input.sessionID });
|
|
6008
|
+
} catch {
|
|
6009
|
+
output.status = "deny";
|
|
6010
|
+
}
|
|
6011
|
+
},
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
`;
|
|
6015
|
+
}
|
|
6016
|
+
function getPiExtensionPath() {
|
|
6017
|
+
const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
6018
|
+
if (fromEnv)
|
|
6019
|
+
return resolve6(fromEnv, "pi-extension");
|
|
6020
|
+
return resolve6(fileURLToPath(import.meta.url), "..", "..", "..", "pi-extension");
|
|
6021
|
+
}
|
|
6022
|
+
function isFailproofaiPiEntry(source) {
|
|
6023
|
+
if (typeof source !== "string")
|
|
6024
|
+
return false;
|
|
6025
|
+
if (/(?:^|\/)pi-extension\/?$/.test(source))
|
|
6026
|
+
return true;
|
|
6027
|
+
return source.includes("pi-extension") && source.includes("failproofai");
|
|
6028
|
+
}
|
|
6029
|
+
function makePiProjectRelativeEntry(extPath) {
|
|
6030
|
+
const cwd = process.cwd();
|
|
6031
|
+
const cwdResolved = resolve6(cwd);
|
|
6032
|
+
const extResolved = resolve6(extPath);
|
|
6033
|
+
if (extResolved.startsWith(cwdResolved + "/") || extResolved === cwdResolved) {
|
|
6034
|
+
const fromSettingsDir = "../" + extResolved.slice(cwdResolved.length + 1);
|
|
6035
|
+
return fromSettingsDir;
|
|
6036
|
+
}
|
|
6037
|
+
return extResolved;
|
|
6038
|
+
}
|
|
4580
6039
|
function getIntegration(id) {
|
|
4581
6040
|
const integration = INTEGRATIONS[id];
|
|
4582
6041
|
if (!integration) {
|
|
@@ -4587,7 +6046,7 @@ function getIntegration(id) {
|
|
|
4587
6046
|
function detectInstalledClis() {
|
|
4588
6047
|
return INTEGRATION_TYPES.filter((id) => INTEGRATIONS[id].detectInstalled());
|
|
4589
6048
|
}
|
|
4590
|
-
var claudeCode, codex, INTEGRATIONS;
|
|
6049
|
+
var claudeCode, codex, copilot, cursor, OPENCODE_PLUGIN_REL_PATH = "./plugins/failproofai.mjs", opencode, pi, gemini, INTEGRATIONS;
|
|
4591
6050
|
var init_integrations = __esm(() => {
|
|
4592
6051
|
init_types();
|
|
4593
6052
|
claudeCode = {
|
|
@@ -4599,34 +6058,262 @@ var init_integrations = __esm(() => {
|
|
|
4599
6058
|
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
4600
6059
|
switch (scope) {
|
|
4601
6060
|
case "user":
|
|
4602
|
-
return resolve6(
|
|
6061
|
+
return resolve6(homedir18(), ".claude", "settings.json");
|
|
6062
|
+
case "project":
|
|
6063
|
+
return resolve6(base, ".claude", "settings.json");
|
|
6064
|
+
case "local":
|
|
6065
|
+
return resolve6(base, ".claude", "settings.local.json");
|
|
6066
|
+
}
|
|
6067
|
+
},
|
|
6068
|
+
readSettings(settingsPath) {
|
|
6069
|
+
return readJsonFile(settingsPath);
|
|
6070
|
+
},
|
|
6071
|
+
writeSettings(settingsPath, settings) {
|
|
6072
|
+
writeJsonFile(settingsPath, settings);
|
|
6073
|
+
},
|
|
6074
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
6075
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
|
|
6076
|
+
return {
|
|
6077
|
+
type: "command",
|
|
6078
|
+
command,
|
|
6079
|
+
timeout: 60000,
|
|
6080
|
+
[FAILPROOFAI_HOOK_MARKER]: true
|
|
6081
|
+
};
|
|
6082
|
+
},
|
|
6083
|
+
isFailproofaiHook: isMarkedHook,
|
|
6084
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
6085
|
+
const s = settings;
|
|
6086
|
+
if (!s.hooks)
|
|
6087
|
+
s.hooks = {};
|
|
6088
|
+
for (const eventType of HOOK_EVENT_TYPES) {
|
|
6089
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
6090
|
+
if (!s.hooks[eventType])
|
|
6091
|
+
s.hooks[eventType] = [];
|
|
6092
|
+
const matchers = s.hooks[eventType];
|
|
6093
|
+
let found = false;
|
|
6094
|
+
for (const matcher of matchers) {
|
|
6095
|
+
if (!matcher.hooks)
|
|
6096
|
+
continue;
|
|
6097
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h));
|
|
6098
|
+
if (idx >= 0) {
|
|
6099
|
+
matcher.hooks[idx] = hookEntry;
|
|
6100
|
+
found = true;
|
|
6101
|
+
break;
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
if (!found)
|
|
6105
|
+
matchers.push({ hooks: [hookEntry] });
|
|
6106
|
+
}
|
|
6107
|
+
},
|
|
6108
|
+
removeHooksFromFile(settingsPath) {
|
|
6109
|
+
const settings = this.readSettings(settingsPath);
|
|
6110
|
+
if (!settings.hooks)
|
|
6111
|
+
return 0;
|
|
6112
|
+
let removed = 0;
|
|
6113
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
6114
|
+
const matchers = settings.hooks[eventType];
|
|
6115
|
+
if (!Array.isArray(matchers))
|
|
6116
|
+
continue;
|
|
6117
|
+
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
6118
|
+
const matcher = matchers[i];
|
|
6119
|
+
if (!matcher.hooks)
|
|
6120
|
+
continue;
|
|
6121
|
+
const before = matcher.hooks.length;
|
|
6122
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h));
|
|
6123
|
+
removed += before - matcher.hooks.length;
|
|
6124
|
+
if (matcher.hooks.length === 0)
|
|
6125
|
+
matchers.splice(i, 1);
|
|
6126
|
+
}
|
|
6127
|
+
if (matchers.length === 0)
|
|
6128
|
+
delete settings.hooks[eventType];
|
|
6129
|
+
}
|
|
6130
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
6131
|
+
delete settings.hooks;
|
|
6132
|
+
this.writeSettings(settingsPath, settings);
|
|
6133
|
+
return removed;
|
|
6134
|
+
},
|
|
6135
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
6136
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
6137
|
+
if (!existsSync11(settingsPath))
|
|
6138
|
+
return false;
|
|
6139
|
+
try {
|
|
6140
|
+
const settings = this.readSettings(settingsPath);
|
|
6141
|
+
if (!settings.hooks)
|
|
6142
|
+
return false;
|
|
6143
|
+
for (const matchers of Object.values(settings.hooks)) {
|
|
6144
|
+
if (!Array.isArray(matchers))
|
|
6145
|
+
continue;
|
|
6146
|
+
for (const matcher of matchers) {
|
|
6147
|
+
if (!matcher.hooks)
|
|
6148
|
+
continue;
|
|
6149
|
+
if (matcher.hooks.some((h) => isMarkedHook(h)))
|
|
6150
|
+
return true;
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
} catch {}
|
|
6154
|
+
return false;
|
|
6155
|
+
},
|
|
6156
|
+
detectInstalled() {
|
|
6157
|
+
return binaryExists("claude") || binaryExists("claude-code");
|
|
6158
|
+
}
|
|
6159
|
+
};
|
|
6160
|
+
codex = {
|
|
6161
|
+
id: "codex",
|
|
6162
|
+
displayName: "OpenAI Codex",
|
|
6163
|
+
scopes: CODEX_HOOK_SCOPES,
|
|
6164
|
+
eventTypes: CODEX_HOOK_EVENT_TYPES,
|
|
6165
|
+
getSettingsPath(scope, cwd) {
|
|
6166
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
6167
|
+
switch (scope) {
|
|
6168
|
+
case "user":
|
|
6169
|
+
return resolve6(homedir18(), ".codex", "hooks.json");
|
|
6170
|
+
case "project":
|
|
6171
|
+
return resolve6(base, ".codex", "hooks.json");
|
|
6172
|
+
case "local":
|
|
6173
|
+
return resolve6(base, ".codex", "hooks.json");
|
|
6174
|
+
}
|
|
6175
|
+
},
|
|
6176
|
+
readSettings(settingsPath) {
|
|
6177
|
+
const raw = readJsonFile(settingsPath);
|
|
6178
|
+
if (raw.version === undefined)
|
|
6179
|
+
raw.version = 1;
|
|
6180
|
+
return raw;
|
|
6181
|
+
},
|
|
6182
|
+
writeSettings(settingsPath, settings) {
|
|
6183
|
+
writeJsonFile(settingsPath, settings);
|
|
6184
|
+
},
|
|
6185
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
6186
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli codex` : `"${binaryPath}" --hook ${eventType} --cli codex`;
|
|
6187
|
+
return {
|
|
6188
|
+
type: "command",
|
|
6189
|
+
command,
|
|
6190
|
+
timeout: 60000,
|
|
6191
|
+
[FAILPROOFAI_HOOK_MARKER]: true
|
|
6192
|
+
};
|
|
6193
|
+
},
|
|
6194
|
+
isFailproofaiHook: isMarkedHook,
|
|
6195
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
6196
|
+
const s = settings;
|
|
6197
|
+
if (s.version === undefined)
|
|
6198
|
+
s.version = 1;
|
|
6199
|
+
if (!s.hooks)
|
|
6200
|
+
s.hooks = {};
|
|
6201
|
+
for (const eventType of CODEX_HOOK_EVENT_TYPES) {
|
|
6202
|
+
const pascalKey = CODEX_EVENT_MAP[eventType];
|
|
6203
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
6204
|
+
if (!s.hooks[pascalKey])
|
|
6205
|
+
s.hooks[pascalKey] = [];
|
|
6206
|
+
const matchers = s.hooks[pascalKey];
|
|
6207
|
+
let found = false;
|
|
6208
|
+
for (const matcher of matchers) {
|
|
6209
|
+
if (!matcher.hooks)
|
|
6210
|
+
continue;
|
|
6211
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedHook(h));
|
|
6212
|
+
if (idx >= 0) {
|
|
6213
|
+
matcher.hooks[idx] = hookEntry;
|
|
6214
|
+
found = true;
|
|
6215
|
+
break;
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
if (!found)
|
|
6219
|
+
matchers.push({ hooks: [hookEntry] });
|
|
6220
|
+
}
|
|
6221
|
+
},
|
|
6222
|
+
removeHooksFromFile(settingsPath) {
|
|
6223
|
+
const settings = this.readSettings(settingsPath);
|
|
6224
|
+
if (!settings.hooks)
|
|
6225
|
+
return 0;
|
|
6226
|
+
let removed = 0;
|
|
6227
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
6228
|
+
const matchers = settings.hooks[eventType];
|
|
6229
|
+
if (!Array.isArray(matchers))
|
|
6230
|
+
continue;
|
|
6231
|
+
for (let i = matchers.length - 1;i >= 0; i--) {
|
|
6232
|
+
const matcher = matchers[i];
|
|
6233
|
+
if (!matcher.hooks)
|
|
6234
|
+
continue;
|
|
6235
|
+
const before = matcher.hooks.length;
|
|
6236
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h));
|
|
6237
|
+
removed += before - matcher.hooks.length;
|
|
6238
|
+
if (matcher.hooks.length === 0)
|
|
6239
|
+
matchers.splice(i, 1);
|
|
6240
|
+
}
|
|
6241
|
+
if (matchers.length === 0)
|
|
6242
|
+
delete settings.hooks[eventType];
|
|
6243
|
+
}
|
|
6244
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
6245
|
+
delete settings.hooks;
|
|
6246
|
+
this.writeSettings(settingsPath, settings);
|
|
6247
|
+
return removed;
|
|
6248
|
+
},
|
|
6249
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
6250
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
6251
|
+
if (!existsSync11(settingsPath))
|
|
6252
|
+
return false;
|
|
6253
|
+
try {
|
|
6254
|
+
const settings = this.readSettings(settingsPath);
|
|
6255
|
+
if (!settings.hooks)
|
|
6256
|
+
return false;
|
|
6257
|
+
for (const matchers of Object.values(settings.hooks)) {
|
|
6258
|
+
if (!Array.isArray(matchers))
|
|
6259
|
+
continue;
|
|
6260
|
+
for (const matcher of matchers) {
|
|
6261
|
+
if (!matcher.hooks)
|
|
6262
|
+
continue;
|
|
6263
|
+
if (matcher.hooks.some((h) => isMarkedHook(h)))
|
|
6264
|
+
return true;
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
} catch {}
|
|
6268
|
+
return false;
|
|
6269
|
+
},
|
|
6270
|
+
detectInstalled() {
|
|
6271
|
+
return binaryExists("codex");
|
|
6272
|
+
}
|
|
6273
|
+
};
|
|
6274
|
+
copilot = {
|
|
6275
|
+
id: "copilot",
|
|
6276
|
+
displayName: "GitHub Copilot",
|
|
6277
|
+
scopes: COPILOT_HOOK_SCOPES,
|
|
6278
|
+
eventTypes: COPILOT_HOOK_EVENT_TYPES,
|
|
6279
|
+
getSettingsPath(scope, cwd) {
|
|
6280
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
6281
|
+
switch (scope) {
|
|
6282
|
+
case "user":
|
|
6283
|
+
return resolve6(homedir18(), ".copilot", "hooks", "failproofai.json");
|
|
4603
6284
|
case "project":
|
|
4604
|
-
return resolve6(base, ".
|
|
6285
|
+
return resolve6(base, ".github", "hooks", "failproofai.json");
|
|
4605
6286
|
case "local":
|
|
4606
|
-
return resolve6(base, ".
|
|
6287
|
+
return resolve6(base, ".github", "hooks", "failproofai.json");
|
|
4607
6288
|
}
|
|
4608
6289
|
},
|
|
4609
6290
|
readSettings(settingsPath) {
|
|
4610
|
-
|
|
6291
|
+
const raw = readJsonFile(settingsPath);
|
|
6292
|
+
if (raw.version === undefined)
|
|
6293
|
+
raw.version = 1;
|
|
6294
|
+
return raw;
|
|
4611
6295
|
},
|
|
4612
6296
|
writeSettings(settingsPath, settings) {
|
|
4613
6297
|
writeJsonFile(settingsPath, settings);
|
|
4614
6298
|
},
|
|
4615
6299
|
buildHookEntry(binaryPath, eventType, scope) {
|
|
4616
|
-
const
|
|
6300
|
+
const cmd = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli copilot` : `"${binaryPath}" --hook ${eventType} --cli copilot`;
|
|
4617
6301
|
return {
|
|
4618
6302
|
type: "command",
|
|
4619
|
-
|
|
4620
|
-
|
|
6303
|
+
bash: cmd,
|
|
6304
|
+
powershell: cmd,
|
|
6305
|
+
timeoutSec: 60,
|
|
4621
6306
|
[FAILPROOFAI_HOOK_MARKER]: true
|
|
4622
6307
|
};
|
|
4623
6308
|
},
|
|
4624
|
-
isFailproofaiHook:
|
|
6309
|
+
isFailproofaiHook: isMarkedCopilotHook,
|
|
4625
6310
|
writeHookEntries(settings, binaryPath, scope) {
|
|
4626
6311
|
const s = settings;
|
|
6312
|
+
if (s.version === undefined)
|
|
6313
|
+
s.version = 1;
|
|
4627
6314
|
if (!s.hooks)
|
|
4628
6315
|
s.hooks = {};
|
|
4629
|
-
for (const eventType of
|
|
6316
|
+
for (const eventType of COPILOT_HOOK_EVENT_TYPES) {
|
|
4630
6317
|
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
4631
6318
|
if (!s.hooks[eventType])
|
|
4632
6319
|
s.hooks[eventType] = [];
|
|
@@ -4635,7 +6322,7 @@ var init_integrations = __esm(() => {
|
|
|
4635
6322
|
for (const matcher of matchers) {
|
|
4636
6323
|
if (!matcher.hooks)
|
|
4637
6324
|
continue;
|
|
4638
|
-
const idx = matcher.hooks.findIndex((h) =>
|
|
6325
|
+
const idx = matcher.hooks.findIndex((h) => isMarkedCopilotHook(h));
|
|
4639
6326
|
if (idx >= 0) {
|
|
4640
6327
|
matcher.hooks[idx] = hookEntry;
|
|
4641
6328
|
found = true;
|
|
@@ -4660,7 +6347,7 @@ var init_integrations = __esm(() => {
|
|
|
4660
6347
|
if (!matcher.hooks)
|
|
4661
6348
|
continue;
|
|
4662
6349
|
const before = matcher.hooks.length;
|
|
4663
|
-
matcher.hooks = matcher.hooks.filter((h) => !
|
|
6350
|
+
matcher.hooks = matcher.hooks.filter((h) => !isMarkedCopilotHook(h));
|
|
4664
6351
|
removed += before - matcher.hooks.length;
|
|
4665
6352
|
if (matcher.hooks.length === 0)
|
|
4666
6353
|
matchers.splice(i, 1);
|
|
@@ -4687,7 +6374,7 @@ var init_integrations = __esm(() => {
|
|
|
4687
6374
|
for (const matcher of matchers) {
|
|
4688
6375
|
if (!matcher.hooks)
|
|
4689
6376
|
continue;
|
|
4690
|
-
if (matcher.hooks.some((h) =>
|
|
6377
|
+
if (matcher.hooks.some((h) => isMarkedCopilotHook(h)))
|
|
4691
6378
|
return true;
|
|
4692
6379
|
}
|
|
4693
6380
|
}
|
|
@@ -4695,23 +6382,23 @@ var init_integrations = __esm(() => {
|
|
|
4695
6382
|
return false;
|
|
4696
6383
|
},
|
|
4697
6384
|
detectInstalled() {
|
|
4698
|
-
return binaryExists("
|
|
6385
|
+
return binaryExists("copilot");
|
|
4699
6386
|
}
|
|
4700
6387
|
};
|
|
4701
|
-
|
|
4702
|
-
id: "
|
|
4703
|
-
displayName: "
|
|
4704
|
-
scopes:
|
|
4705
|
-
eventTypes:
|
|
6388
|
+
cursor = {
|
|
6389
|
+
id: "cursor",
|
|
6390
|
+
displayName: "Cursor Agent",
|
|
6391
|
+
scopes: CURSOR_HOOK_SCOPES,
|
|
6392
|
+
eventTypes: CURSOR_HOOK_EVENT_TYPES,
|
|
4706
6393
|
getSettingsPath(scope, cwd) {
|
|
4707
6394
|
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
4708
6395
|
switch (scope) {
|
|
4709
6396
|
case "user":
|
|
4710
|
-
return resolve6(
|
|
6397
|
+
return resolve6(homedir18(), ".cursor", "hooks.json");
|
|
4711
6398
|
case "project":
|
|
4712
|
-
return resolve6(base, ".
|
|
6399
|
+
return resolve6(base, ".cursor", "hooks.json");
|
|
4713
6400
|
case "local":
|
|
4714
|
-
return resolve6(base, ".
|
|
6401
|
+
return resolve6(base, ".cursor", "hooks.json");
|
|
4715
6402
|
}
|
|
4716
6403
|
},
|
|
4717
6404
|
readSettings(settingsPath) {
|
|
@@ -4724,7 +6411,7 @@ var init_integrations = __esm(() => {
|
|
|
4724
6411
|
writeJsonFile(settingsPath, settings);
|
|
4725
6412
|
},
|
|
4726
6413
|
buildHookEntry(binaryPath, eventType, scope) {
|
|
4727
|
-
const command = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli
|
|
6414
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli cursor` : `"${binaryPath}" --hook ${eventType} --cli cursor`;
|
|
4728
6415
|
return {
|
|
4729
6416
|
type: "command",
|
|
4730
6417
|
command,
|
|
@@ -4739,12 +6426,291 @@ var init_integrations = __esm(() => {
|
|
|
4739
6426
|
s.version = 1;
|
|
4740
6427
|
if (!s.hooks)
|
|
4741
6428
|
s.hooks = {};
|
|
4742
|
-
for (const eventType of
|
|
4743
|
-
const pascalKey = CODEX_EVENT_MAP[eventType];
|
|
6429
|
+
for (const eventType of CURSOR_HOOK_EVENT_TYPES) {
|
|
4744
6430
|
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
6431
|
+
const existing = s.hooks[eventType];
|
|
6432
|
+
const entries = existing ?? [];
|
|
6433
|
+
if (!existing)
|
|
6434
|
+
s.hooks[eventType] = entries;
|
|
6435
|
+
const idx = entries.findIndex((h) => isMarkedHook(h));
|
|
6436
|
+
if (idx >= 0) {
|
|
6437
|
+
entries[idx] = hookEntry;
|
|
6438
|
+
} else {
|
|
6439
|
+
entries.push(hookEntry);
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
},
|
|
6443
|
+
removeHooksFromFile(settingsPath) {
|
|
6444
|
+
const settings = this.readSettings(settingsPath);
|
|
6445
|
+
if (!settings.hooks)
|
|
6446
|
+
return 0;
|
|
6447
|
+
let removed = 0;
|
|
6448
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
6449
|
+
const entries = settings.hooks[eventType];
|
|
6450
|
+
if (!Array.isArray(entries))
|
|
6451
|
+
continue;
|
|
6452
|
+
const before = entries.length;
|
|
6453
|
+
const filtered = entries.filter((h) => !isMarkedHook(h));
|
|
6454
|
+
removed += before - filtered.length;
|
|
6455
|
+
if (filtered.length === 0) {
|
|
6456
|
+
delete settings.hooks[eventType];
|
|
6457
|
+
} else {
|
|
6458
|
+
settings.hooks[eventType] = filtered;
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
6462
|
+
delete settings.hooks;
|
|
6463
|
+
this.writeSettings(settingsPath, settings);
|
|
6464
|
+
return removed;
|
|
6465
|
+
},
|
|
6466
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
6467
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
6468
|
+
if (!existsSync11(settingsPath))
|
|
6469
|
+
return false;
|
|
6470
|
+
try {
|
|
6471
|
+
const settings = this.readSettings(settingsPath);
|
|
6472
|
+
if (!settings.hooks)
|
|
6473
|
+
return false;
|
|
6474
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
6475
|
+
if (!Array.isArray(entries))
|
|
6476
|
+
continue;
|
|
6477
|
+
if (entries.some((h) => isMarkedHook(h)))
|
|
6478
|
+
return true;
|
|
6479
|
+
}
|
|
6480
|
+
} catch {}
|
|
6481
|
+
return false;
|
|
6482
|
+
},
|
|
6483
|
+
detectInstalled() {
|
|
6484
|
+
return binaryExists("cursor-agent") || binaryExists("agent");
|
|
6485
|
+
}
|
|
6486
|
+
};
|
|
6487
|
+
opencode = {
|
|
6488
|
+
id: "opencode",
|
|
6489
|
+
displayName: "OpenCode",
|
|
6490
|
+
scopes: OPENCODE_HOOK_SCOPES,
|
|
6491
|
+
eventTypes: OPENCODE_HOOK_EVENT_TYPES,
|
|
6492
|
+
getSettingsPath(scope, cwd) {
|
|
6493
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
6494
|
+
switch (scope) {
|
|
6495
|
+
case "user":
|
|
6496
|
+
return resolve6(homedir18(), ".config", "opencode", "opencode.json");
|
|
6497
|
+
case "project":
|
|
6498
|
+
return resolve6(base, ".opencode", "opencode.json");
|
|
6499
|
+
case "local":
|
|
6500
|
+
return resolve6(base, ".opencode", "opencode.json");
|
|
6501
|
+
}
|
|
6502
|
+
},
|
|
6503
|
+
readSettings(settingsPath) {
|
|
6504
|
+
return readJsonFile(settingsPath);
|
|
6505
|
+
},
|
|
6506
|
+
writeSettings(settingsPath, settings) {
|
|
6507
|
+
writeJsonFile(settingsPath, settings);
|
|
6508
|
+
},
|
|
6509
|
+
buildHookEntry(_binaryPath, _eventType, scope) {
|
|
6510
|
+
if (scope === "user") {
|
|
6511
|
+
const abs = resolve6(homedir18(), ".config", "opencode", "plugins", "failproofai.mjs");
|
|
6512
|
+
return { spec: `file://${abs}`, [FAILPROOFAI_HOOK_MARKER]: true };
|
|
6513
|
+
}
|
|
6514
|
+
return { spec: OPENCODE_PLUGIN_REL_PATH, [FAILPROOFAI_HOOK_MARKER]: true };
|
|
6515
|
+
},
|
|
6516
|
+
isFailproofaiHook(hook) {
|
|
6517
|
+
if (typeof hook === "string")
|
|
6518
|
+
return hook.includes("failproofai.mjs");
|
|
6519
|
+
if (Array.isArray(hook))
|
|
6520
|
+
return typeof hook[0] === "string" && hook[0].includes("failproofai.mjs");
|
|
6521
|
+
return false;
|
|
6522
|
+
},
|
|
6523
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
6524
|
+
const s = settings;
|
|
6525
|
+
const effectiveScope = scope ?? "project";
|
|
6526
|
+
const settingsPath = effectiveScope === "user" ? resolve6(homedir18(), ".config", "opencode", "opencode.json") : resolve6(process.cwd(), ".opencode", "opencode.json");
|
|
6527
|
+
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
6528
|
+
mkdirSync8(dirname5(pluginPath), { recursive: true });
|
|
6529
|
+
writeFileSync6(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope), "utf8");
|
|
6530
|
+
if (!Array.isArray(s.plugin))
|
|
6531
|
+
s.plugin = [];
|
|
6532
|
+
const desired = effectiveScope === "user" ? `file://${pluginPath}` : OPENCODE_PLUGIN_REL_PATH;
|
|
6533
|
+
const idx = s.plugin.findIndex((entry) => this.isFailproofaiHook(entry));
|
|
6534
|
+
if (idx >= 0) {
|
|
6535
|
+
s.plugin[idx] = desired;
|
|
6536
|
+
} else {
|
|
6537
|
+
s.plugin.push(desired);
|
|
6538
|
+
}
|
|
6539
|
+
},
|
|
6540
|
+
removeHooksFromFile(settingsPath) {
|
|
6541
|
+
let removed = 0;
|
|
6542
|
+
const settings = this.readSettings(settingsPath);
|
|
6543
|
+
if (Array.isArray(settings.plugin)) {
|
|
6544
|
+
const before = settings.plugin.length;
|
|
6545
|
+
settings.plugin = settings.plugin.filter((entry) => !this.isFailproofaiHook(entry));
|
|
6546
|
+
removed += before - settings.plugin.length;
|
|
6547
|
+
if (settings.plugin.length === 0)
|
|
6548
|
+
delete settings.plugin;
|
|
6549
|
+
}
|
|
6550
|
+
this.writeSettings(settingsPath, settings);
|
|
6551
|
+
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
6552
|
+
if (existsSync11(pluginPath)) {
|
|
6553
|
+
try {
|
|
6554
|
+
const content = readFileSync8(pluginPath, "utf8");
|
|
6555
|
+
if (content.includes(FAILPROOFAI_HOOK_MARKER)) {
|
|
6556
|
+
unlinkSync5(pluginPath);
|
|
6557
|
+
if (removed === 0)
|
|
6558
|
+
removed = 1;
|
|
6559
|
+
}
|
|
6560
|
+
} catch {}
|
|
6561
|
+
}
|
|
6562
|
+
return removed;
|
|
6563
|
+
},
|
|
6564
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
6565
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
6566
|
+
if (!existsSync11(settingsPath))
|
|
6567
|
+
return false;
|
|
6568
|
+
try {
|
|
6569
|
+
const settings = this.readSettings(settingsPath);
|
|
6570
|
+
if (!Array.isArray(settings.plugin))
|
|
6571
|
+
return false;
|
|
6572
|
+
const hasEntry = settings.plugin.some((entry) => this.isFailproofaiHook(entry));
|
|
6573
|
+
if (!hasEntry)
|
|
6574
|
+
return false;
|
|
6575
|
+
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
6576
|
+
if (!existsSync11(pluginPath))
|
|
6577
|
+
return false;
|
|
6578
|
+
const content = readFileSync8(pluginPath, "utf8");
|
|
6579
|
+
return content.includes(FAILPROOFAI_HOOK_MARKER);
|
|
6580
|
+
} catch {
|
|
6581
|
+
return false;
|
|
6582
|
+
}
|
|
6583
|
+
},
|
|
6584
|
+
detectInstalled() {
|
|
6585
|
+
return binaryExists("opencode");
|
|
6586
|
+
}
|
|
6587
|
+
};
|
|
6588
|
+
pi = {
|
|
6589
|
+
id: "pi",
|
|
6590
|
+
displayName: "Pi",
|
|
6591
|
+
scopes: PI_HOOK_SCOPES,
|
|
6592
|
+
eventTypes: PI_HOOK_EVENT_TYPES,
|
|
6593
|
+
getSettingsPath(scope, cwd) {
|
|
6594
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
6595
|
+
switch (scope) {
|
|
6596
|
+
case "user":
|
|
6597
|
+
return resolve6(homedir18(), ".pi", "agent", "settings.json");
|
|
6598
|
+
case "project":
|
|
6599
|
+
return resolve6(base, ".pi", "settings.json");
|
|
6600
|
+
case "local":
|
|
6601
|
+
return resolve6(base, ".pi", "settings.json");
|
|
6602
|
+
}
|
|
6603
|
+
},
|
|
6604
|
+
readSettings(settingsPath) {
|
|
6605
|
+
return readJsonFile(settingsPath);
|
|
6606
|
+
},
|
|
6607
|
+
writeSettings(settingsPath, settings) {
|
|
6608
|
+
writeJsonFile(settingsPath, settings);
|
|
6609
|
+
},
|
|
6610
|
+
buildHookEntry(_binaryPath, _eventType, scope) {
|
|
6611
|
+
return {
|
|
6612
|
+
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
6613
|
+
_piPackagePath: getPiExtensionPath(),
|
|
6614
|
+
_piScope: scope
|
|
6615
|
+
};
|
|
6616
|
+
},
|
|
6617
|
+
isFailproofaiHook(hook) {
|
|
6618
|
+
if (typeof hook === "string")
|
|
6619
|
+
return isFailproofaiPiEntry(hook);
|
|
6620
|
+
if (!hook || typeof hook !== "object")
|
|
6621
|
+
return false;
|
|
6622
|
+
const h = hook;
|
|
6623
|
+
if (h[FAILPROOFAI_HOOK_MARKER] === true)
|
|
6624
|
+
return true;
|
|
6625
|
+
if (typeof h.source === "string")
|
|
6626
|
+
return isFailproofaiPiEntry(h.source);
|
|
6627
|
+
return false;
|
|
6628
|
+
},
|
|
6629
|
+
writeHookEntries(settings, _binaryPath, scope) {
|
|
6630
|
+
const s = settings;
|
|
6631
|
+
if (!Array.isArray(s.packages))
|
|
6632
|
+
s.packages = [];
|
|
6633
|
+
const extPath = getPiExtensionPath();
|
|
6634
|
+
const entry = scope === "project" ? makePiProjectRelativeEntry(extPath) : extPath;
|
|
6635
|
+
const idx = s.packages.findIndex((p) => isFailproofaiPiEntry(p));
|
|
6636
|
+
if (idx >= 0) {
|
|
6637
|
+
s.packages[idx] = entry;
|
|
6638
|
+
} else {
|
|
6639
|
+
s.packages.push(entry);
|
|
6640
|
+
}
|
|
6641
|
+
},
|
|
6642
|
+
removeHooksFromFile(settingsPath) {
|
|
6643
|
+
if (!existsSync11(settingsPath))
|
|
6644
|
+
return 0;
|
|
6645
|
+
const settings = this.readSettings(settingsPath);
|
|
6646
|
+
if (!Array.isArray(settings.packages))
|
|
6647
|
+
return 0;
|
|
6648
|
+
const before = settings.packages.length;
|
|
6649
|
+
settings.packages = settings.packages.filter((p) => !isFailproofaiPiEntry(p));
|
|
6650
|
+
const removed = before - settings.packages.length;
|
|
6651
|
+
if (settings.packages.length === 0)
|
|
6652
|
+
delete settings.packages;
|
|
6653
|
+
this.writeSettings(settingsPath, settings);
|
|
6654
|
+
return removed;
|
|
6655
|
+
},
|
|
6656
|
+
hooksInstalledInSettings(scope, cwd) {
|
|
6657
|
+
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
6658
|
+
if (!existsSync11(settingsPath))
|
|
6659
|
+
return false;
|
|
6660
|
+
try {
|
|
6661
|
+
const settings = this.readSettings(settingsPath);
|
|
6662
|
+
if (!Array.isArray(settings.packages))
|
|
6663
|
+
return false;
|
|
6664
|
+
return settings.packages.some((p) => isFailproofaiPiEntry(p));
|
|
6665
|
+
} catch {
|
|
6666
|
+
return false;
|
|
6667
|
+
}
|
|
6668
|
+
},
|
|
6669
|
+
detectInstalled() {
|
|
6670
|
+
return binaryExists("pi");
|
|
6671
|
+
}
|
|
6672
|
+
};
|
|
6673
|
+
gemini = {
|
|
6674
|
+
id: "gemini",
|
|
6675
|
+
displayName: "Gemini CLI",
|
|
6676
|
+
scopes: GEMINI_HOOK_SCOPES,
|
|
6677
|
+
eventTypes: GEMINI_HOOK_EVENT_TYPES,
|
|
6678
|
+
getSettingsPath(scope, cwd) {
|
|
6679
|
+
const base = cwd ? resolve6(cwd) : process.cwd();
|
|
6680
|
+
switch (scope) {
|
|
6681
|
+
case "user":
|
|
6682
|
+
return resolve6(homedir18(), ".gemini", "settings.json");
|
|
6683
|
+
case "project":
|
|
6684
|
+
return resolve6(base, ".gemini", "settings.json");
|
|
6685
|
+
case "local":
|
|
6686
|
+
return resolve6(base, ".gemini", "settings.json");
|
|
6687
|
+
}
|
|
6688
|
+
},
|
|
6689
|
+
readSettings(settingsPath) {
|
|
6690
|
+
return readJsonFile(settingsPath);
|
|
6691
|
+
},
|
|
6692
|
+
writeSettings(settingsPath, settings) {
|
|
6693
|
+
writeJsonFile(settingsPath, settings);
|
|
6694
|
+
},
|
|
6695
|
+
buildHookEntry(binaryPath, eventType, scope) {
|
|
6696
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType} --cli gemini` : `"${binaryPath}" --hook ${eventType} --cli gemini`;
|
|
6697
|
+
return {
|
|
6698
|
+
type: "command",
|
|
6699
|
+
command,
|
|
6700
|
+
timeout: 60000,
|
|
6701
|
+
[FAILPROOFAI_HOOK_MARKER]: true
|
|
6702
|
+
};
|
|
6703
|
+
},
|
|
6704
|
+
isFailproofaiHook: isMarkedHook,
|
|
6705
|
+
writeHookEntries(settings, binaryPath, scope) {
|
|
6706
|
+
const s = settings;
|
|
6707
|
+
if (!s.hooks)
|
|
6708
|
+
s.hooks = {};
|
|
6709
|
+
for (const eventType of GEMINI_HOOK_EVENT_TYPES) {
|
|
6710
|
+
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope);
|
|
6711
|
+
if (!s.hooks[eventType])
|
|
6712
|
+
s.hooks[eventType] = [];
|
|
6713
|
+
const matchers = s.hooks[eventType];
|
|
4748
6714
|
let found = false;
|
|
4749
6715
|
for (const matcher of matchers) {
|
|
4750
6716
|
if (!matcher.hooks)
|
|
@@ -4757,7 +6723,7 @@ var init_integrations = __esm(() => {
|
|
|
4757
6723
|
}
|
|
4758
6724
|
}
|
|
4759
6725
|
if (!found)
|
|
4760
|
-
matchers.push({ hooks: [hookEntry] });
|
|
6726
|
+
matchers.push({ matcher: "*", hooks: [hookEntry] });
|
|
4761
6727
|
}
|
|
4762
6728
|
},
|
|
4763
6729
|
removeHooksFromFile(settingsPath) {
|
|
@@ -4809,12 +6775,17 @@ var init_integrations = __esm(() => {
|
|
|
4809
6775
|
return false;
|
|
4810
6776
|
},
|
|
4811
6777
|
detectInstalled() {
|
|
4812
|
-
return binaryExists("
|
|
6778
|
+
return binaryExists("gemini");
|
|
4813
6779
|
}
|
|
4814
6780
|
};
|
|
4815
6781
|
INTEGRATIONS = {
|
|
4816
6782
|
claude: claudeCode,
|
|
4817
|
-
codex
|
|
6783
|
+
codex,
|
|
6784
|
+
copilot,
|
|
6785
|
+
cursor,
|
|
6786
|
+
opencode,
|
|
6787
|
+
pi,
|
|
6788
|
+
gemini
|
|
4818
6789
|
};
|
|
4819
6790
|
});
|
|
4820
6791
|
|
|
@@ -4838,7 +6809,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
4838
6809
|
}));
|
|
4839
6810
|
const total = items.length;
|
|
4840
6811
|
const WINDOW_SIZE = 8;
|
|
4841
|
-
let
|
|
6812
|
+
let cursor2 = 0;
|
|
4842
6813
|
let search = "";
|
|
4843
6814
|
let lastLineCount = 0;
|
|
4844
6815
|
let cursorHidden = false;
|
|
@@ -4920,8 +6891,8 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
4920
6891
|
hideCursor();
|
|
4921
6892
|
const filtered = getFiltered();
|
|
4922
6893
|
const shown = filtered.length;
|
|
4923
|
-
if (shown > 0 &&
|
|
4924
|
-
|
|
6894
|
+
if (shown > 0 && cursor2 >= shown)
|
|
6895
|
+
cursor2 = shown - 1;
|
|
4925
6896
|
const lines = [];
|
|
4926
6897
|
lines.push(" Failproof AI — Policy Manager");
|
|
4927
6898
|
lines.push("");
|
|
@@ -4944,7 +6915,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
4944
6915
|
let cursorDisplayRow = 0;
|
|
4945
6916
|
for (let i = 0;i < displayRows.length; i++) {
|
|
4946
6917
|
const row = displayRows[i];
|
|
4947
|
-
if (row.kind === "item" && row.filteredIndex ===
|
|
6918
|
+
if (row.kind === "item" && row.filteredIndex === cursor2) {
|
|
4948
6919
|
cursorDisplayRow = i;
|
|
4949
6920
|
break;
|
|
4950
6921
|
}
|
|
@@ -4969,7 +6940,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
4969
6940
|
lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
|
|
4970
6941
|
} else {
|
|
4971
6942
|
const item = row.item;
|
|
4972
|
-
const isActive = row.filteredIndex ===
|
|
6943
|
+
const isActive = row.filteredIndex === cursor2;
|
|
4973
6944
|
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
4974
6945
|
const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
|
|
4975
6946
|
const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
|
|
@@ -5021,22 +6992,22 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
5021
6992
|
const filtered = getFiltered();
|
|
5022
6993
|
if (key.name === "up") {
|
|
5023
6994
|
if (filtered.length > 0) {
|
|
5024
|
-
|
|
6995
|
+
cursor2 = cursor2 > 0 ? cursor2 - 1 : filtered.length - 1;
|
|
5025
6996
|
}
|
|
5026
6997
|
render();
|
|
5027
6998
|
} else if (key.name === "down") {
|
|
5028
6999
|
if (filtered.length > 0) {
|
|
5029
|
-
|
|
7000
|
+
cursor2 = cursor2 < filtered.length - 1 ? cursor2 + 1 : 0;
|
|
5030
7001
|
}
|
|
5031
7002
|
render();
|
|
5032
7003
|
} else if (key.name === "return" || key.name === "space") {
|
|
5033
|
-
const item = filtered[
|
|
7004
|
+
const item = filtered[cursor2];
|
|
5034
7005
|
if (item)
|
|
5035
7006
|
item.selected = !item.selected;
|
|
5036
7007
|
render();
|
|
5037
7008
|
} else if (key.name === "escape") {
|
|
5038
7009
|
search = "";
|
|
5039
|
-
|
|
7010
|
+
cursor2 = 0;
|
|
5040
7011
|
render();
|
|
5041
7012
|
} else if (key.ctrl && key.name === "a") {
|
|
5042
7013
|
const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
|
|
@@ -5052,12 +7023,12 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
5052
7023
|
} else if (key.name === "backspace" || key.name === "delete") {
|
|
5053
7024
|
if (search.length > 0) {
|
|
5054
7025
|
search = search.slice(0, -1);
|
|
5055
|
-
|
|
7026
|
+
cursor2 = 0;
|
|
5056
7027
|
render();
|
|
5057
7028
|
}
|
|
5058
7029
|
} else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
|
|
5059
7030
|
search += _str;
|
|
5060
|
-
|
|
7031
|
+
cursor2 = 0;
|
|
5061
7032
|
render();
|
|
5062
7033
|
}
|
|
5063
7034
|
}
|
|
@@ -5099,8 +7070,8 @@ __export(exports_manager, {
|
|
|
5099
7070
|
});
|
|
5100
7071
|
import { execSync as execSync4 } from "node:child_process";
|
|
5101
7072
|
import { existsSync as existsSync12 } from "node:fs";
|
|
5102
|
-
import { resolve as resolve7, basename as
|
|
5103
|
-
import { homedir as
|
|
7073
|
+
import { resolve as resolve7, basename as basename3 } from "node:path";
|
|
7074
|
+
import { homedir as homedir19, platform, arch, release, hostname } from "node:os";
|
|
5104
7075
|
function getSettingsPath(scope, cwd) {
|
|
5105
7076
|
return claudeCode.getSettingsPath(scope, cwd);
|
|
5106
7077
|
}
|
|
@@ -5518,7 +7489,7 @@ Failproof AI Hook Policies
|
|
|
5518
7489
|
const base = cwd ? resolve7(cwd) : process.cwd();
|
|
5519
7490
|
const conventionDirs = [
|
|
5520
7491
|
{ label: "Project", dir: resolve7(base, ".failproofai", "policies") },
|
|
5521
|
-
{ label: "User", dir: resolve7(
|
|
7492
|
+
{ label: "User", dir: resolve7(homedir19(), ".failproofai", "policies") }
|
|
5522
7493
|
];
|
|
5523
7494
|
for (const { label, dir } of conventionDirs) {
|
|
5524
7495
|
const files = discoverPolicyFiles(dir);
|
|
@@ -5530,15 +7501,15 @@ Failproof AI Hook Policies
|
|
|
5530
7501
|
try {
|
|
5531
7502
|
const hooks = await loadCustomHooks(file);
|
|
5532
7503
|
if (hooks.length === 0) {
|
|
5533
|
-
const filename =
|
|
7504
|
+
const filename = basename3(file);
|
|
5534
7505
|
console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
5535
7506
|
} else {
|
|
5536
|
-
const filename =
|
|
7507
|
+
const filename = basename3(file);
|
|
5537
7508
|
const hookSummary = hooks.map((h) => h.name).join(", ");
|
|
5538
7509
|
console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
|
|
5539
7510
|
}
|
|
5540
7511
|
} catch {
|
|
5541
|
-
const filename =
|
|
7512
|
+
const filename = basename3(file);
|
|
5542
7513
|
console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
|
|
5543
7514
|
}
|
|
5544
7515
|
}
|
|
@@ -5566,34 +7537,40 @@ __export(exports_install_prompt, {
|
|
|
5566
7537
|
promptPolicySelection: () => promptPolicySelection2
|
|
5567
7538
|
});
|
|
5568
7539
|
import * as readline2 from "node:readline";
|
|
5569
|
-
async function resolveTargetClis(explicit) {
|
|
7540
|
+
async function resolveTargetClis(explicit, action = "install") {
|
|
5570
7541
|
if (explicit && explicit.length > 0)
|
|
5571
7542
|
return [...new Set(explicit)];
|
|
5572
7543
|
const detected = detectInstalledClis();
|
|
5573
7544
|
if (detected.length === 0) {
|
|
5574
|
-
|
|
7545
|
+
if (action === "uninstall") {
|
|
7546
|
+
console.log("\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex, copilot, cursor-agent, opencode, pi, gemini). " + "Defaulting to Claude Code; nothing will be removed if no settings file exists.\x1B[0m");
|
|
7547
|
+
return ["claude"];
|
|
7548
|
+
}
|
|
7549
|
+
console.log("\x1B[33mWarning: no agent CLI binary found in PATH (claude, codex, copilot, cursor-agent, opencode, pi, gemini). " + "Defaulting to Claude Code; hooks will activate when an agent is installed.\x1B[0m");
|
|
5575
7550
|
return ["claude"];
|
|
5576
7551
|
}
|
|
5577
7552
|
if (detected.length === 1) {
|
|
5578
7553
|
const integration = getIntegration(detected[0]);
|
|
5579
|
-
|
|
7554
|
+
const verb = action === "uninstall" ? "removing hooks from" : "installing hooks for";
|
|
7555
|
+
console.log(`Detected ${integration.displayName}; ${verb} it.`);
|
|
5580
7556
|
return detected;
|
|
5581
7557
|
}
|
|
5582
7558
|
if (!process.stdin.isTTY)
|
|
5583
7559
|
return detected;
|
|
5584
|
-
return promptCliTargetSelection(detected);
|
|
7560
|
+
return promptCliTargetSelection(detected, action);
|
|
5585
7561
|
}
|
|
5586
|
-
async function promptCliTargetSelection(detected) {
|
|
7562
|
+
async function promptCliTargetSelection(detected, action = "install") {
|
|
5587
7563
|
const labels = detected.map((id) => getIntegration(id).displayName).join(" + ");
|
|
7564
|
+
const allLabel = detected.length > 2 ? "All" : "Both";
|
|
5588
7565
|
const options = [
|
|
5589
|
-
{ label:
|
|
7566
|
+
{ label: allLabel, description: labels, value: detected },
|
|
5590
7567
|
...detected.map((id) => ({
|
|
5591
7568
|
label: `${getIntegration(id).displayName} only`,
|
|
5592
7569
|
description: "",
|
|
5593
7570
|
value: [id]
|
|
5594
7571
|
}))
|
|
5595
7572
|
];
|
|
5596
|
-
let
|
|
7573
|
+
let cursor2 = 0;
|
|
5597
7574
|
let lastLineCount = 0;
|
|
5598
7575
|
let cursorHidden = false;
|
|
5599
7576
|
function hideCursor() {
|
|
@@ -5630,17 +7607,19 @@ async function promptCliTargetSelection(detected) {
|
|
|
5630
7607
|
}
|
|
5631
7608
|
return result;
|
|
5632
7609
|
}
|
|
7610
|
+
const heading = action === "uninstall" ? "Remove Hooks" : "Install Hooks";
|
|
7611
|
+
const verb = action === "uninstall" ? "remove from" : "install";
|
|
5633
7612
|
function render() {
|
|
5634
7613
|
const cols = process.stdout.columns || 120;
|
|
5635
7614
|
hideCursor();
|
|
5636
7615
|
const lines = [];
|
|
5637
|
-
lines.push(
|
|
7616
|
+
lines.push(` Failproof AI — ${heading}`);
|
|
5638
7617
|
lines.push("");
|
|
5639
|
-
lines.push(` \x1B[2mDetected ${labels}. Choose where to
|
|
7618
|
+
lines.push(` \x1B[2mDetected ${labels}. Choose where to ${verb}:\x1B[0m`);
|
|
5640
7619
|
lines.push("");
|
|
5641
7620
|
for (let i = 0;i < options.length; i++) {
|
|
5642
7621
|
const opt = options[i];
|
|
5643
|
-
const isActive = i ===
|
|
7622
|
+
const isActive = i === cursor2;
|
|
5644
7623
|
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
5645
7624
|
const labelPart = isActive ? `\x1B[1;36m${opt.label}\x1B[0m` : opt.label;
|
|
5646
7625
|
const pad = opt.description ? " ".repeat(Math.max(2, 22 - opt.label.length)) : "";
|
|
@@ -5683,16 +7662,16 @@ async function promptCliTargetSelection(detected) {
|
|
|
5683
7662
|
process.exit(130);
|
|
5684
7663
|
}
|
|
5685
7664
|
if (key.name === "up") {
|
|
5686
|
-
|
|
7665
|
+
cursor2 = cursor2 > 0 ? cursor2 - 1 : options.length - 1;
|
|
5687
7666
|
render();
|
|
5688
7667
|
} else if (key.name === "down") {
|
|
5689
|
-
|
|
7668
|
+
cursor2 = cursor2 < options.length - 1 ? cursor2 + 1 : 0;
|
|
5690
7669
|
render();
|
|
5691
7670
|
} else if (key.name === "return" || key.name === "space") {
|
|
5692
7671
|
cleanup();
|
|
5693
7672
|
process.stdout.write(`
|
|
5694
7673
|
`);
|
|
5695
|
-
resolve8(options[
|
|
7674
|
+
resolve8(options[cursor2].value);
|
|
5696
7675
|
}
|
|
5697
7676
|
}
|
|
5698
7677
|
process.stdin.on("keypress", onKey);
|
|
@@ -5716,7 +7695,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5716
7695
|
}));
|
|
5717
7696
|
const total = items.length;
|
|
5718
7697
|
const WINDOW_SIZE = 8;
|
|
5719
|
-
let
|
|
7698
|
+
let cursor2 = 0;
|
|
5720
7699
|
let search = "";
|
|
5721
7700
|
let lastLineCount = 0;
|
|
5722
7701
|
let cursorHidden = false;
|
|
@@ -5798,8 +7777,8 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5798
7777
|
hideCursor();
|
|
5799
7778
|
const filtered = getFiltered();
|
|
5800
7779
|
const shown = filtered.length;
|
|
5801
|
-
if (shown > 0 &&
|
|
5802
|
-
|
|
7780
|
+
if (shown > 0 && cursor2 >= shown)
|
|
7781
|
+
cursor2 = shown - 1;
|
|
5803
7782
|
const lines = [];
|
|
5804
7783
|
lines.push(" Failproof AI — Policy Manager");
|
|
5805
7784
|
lines.push("");
|
|
@@ -5822,7 +7801,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5822
7801
|
let cursorDisplayRow = 0;
|
|
5823
7802
|
for (let i = 0;i < displayRows.length; i++) {
|
|
5824
7803
|
const row = displayRows[i];
|
|
5825
|
-
if (row.kind === "item" && row.filteredIndex ===
|
|
7804
|
+
if (row.kind === "item" && row.filteredIndex === cursor2) {
|
|
5826
7805
|
cursorDisplayRow = i;
|
|
5827
7806
|
break;
|
|
5828
7807
|
}
|
|
@@ -5847,7 +7826,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5847
7826
|
lines.push(` \x1B[2m${prefix}${label}${"─".repeat(dashLen)}\x1B[0m`);
|
|
5848
7827
|
} else {
|
|
5849
7828
|
const item = row.item;
|
|
5850
|
-
const isActive = row.filteredIndex ===
|
|
7829
|
+
const isActive = row.filteredIndex === cursor2;
|
|
5851
7830
|
const pointer = isActive ? "\x1B[36m❯\x1B[0m" : " ";
|
|
5852
7831
|
const check = item.selected ? "\x1B[32m[✓]\x1B[0m" : "[ ]";
|
|
5853
7832
|
const namePart = isActive ? `\x1B[1;36m${item.name}\x1B[0m` : item.name;
|
|
@@ -5899,22 +7878,22 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5899
7878
|
const filtered = getFiltered();
|
|
5900
7879
|
if (key.name === "up") {
|
|
5901
7880
|
if (filtered.length > 0) {
|
|
5902
|
-
|
|
7881
|
+
cursor2 = cursor2 > 0 ? cursor2 - 1 : filtered.length - 1;
|
|
5903
7882
|
}
|
|
5904
7883
|
render();
|
|
5905
7884
|
} else if (key.name === "down") {
|
|
5906
7885
|
if (filtered.length > 0) {
|
|
5907
|
-
|
|
7886
|
+
cursor2 = cursor2 < filtered.length - 1 ? cursor2 + 1 : 0;
|
|
5908
7887
|
}
|
|
5909
7888
|
render();
|
|
5910
7889
|
} else if (key.name === "return" || key.name === "space") {
|
|
5911
|
-
const item = filtered[
|
|
7890
|
+
const item = filtered[cursor2];
|
|
5912
7891
|
if (item)
|
|
5913
7892
|
item.selected = !item.selected;
|
|
5914
7893
|
render();
|
|
5915
7894
|
} else if (key.name === "escape") {
|
|
5916
7895
|
search = "";
|
|
5917
|
-
|
|
7896
|
+
cursor2 = 0;
|
|
5918
7897
|
render();
|
|
5919
7898
|
} else if (key.ctrl && key.name === "a") {
|
|
5920
7899
|
const allSelected = filtered.length > 0 && filtered.every((i) => i.selected);
|
|
@@ -5930,12 +7909,12 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
5930
7909
|
} else if (key.name === "backspace" || key.name === "delete") {
|
|
5931
7910
|
if (search.length > 0) {
|
|
5932
7911
|
search = search.slice(0, -1);
|
|
5933
|
-
|
|
7912
|
+
cursor2 = 0;
|
|
5934
7913
|
render();
|
|
5935
7914
|
}
|
|
5936
7915
|
} else if (_str && _str.length === 1 && !key.ctrl && !key.meta) {
|
|
5937
7916
|
search += _str;
|
|
5938
|
-
|
|
7917
|
+
cursor2 = 0;
|
|
5939
7918
|
render();
|
|
5940
7919
|
}
|
|
5941
7920
|
}
|
|
@@ -6088,9 +8067,9 @@ __export(exports_pid, {
|
|
|
6088
8067
|
isProcessAlive: () => isProcessAlive2,
|
|
6089
8068
|
clearPid: () => clearPid2
|
|
6090
8069
|
});
|
|
6091
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync13, unlinkSync as
|
|
6092
|
-
import { join as
|
|
6093
|
-
import { homedir as
|
|
8070
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync13, unlinkSync as unlinkSync6, mkdirSync as mkdirSync9 } from "node:fs";
|
|
8071
|
+
import { join as join19, dirname as dirname6 } from "node:path";
|
|
8072
|
+
import { homedir as homedir20 } from "node:os";
|
|
6094
8073
|
function readPid2() {
|
|
6095
8074
|
if (!existsSync13(PID_FILE2))
|
|
6096
8075
|
return null;
|
|
@@ -6112,7 +8091,7 @@ function writePid2(pid) {
|
|
|
6112
8091
|
}
|
|
6113
8092
|
function clearPid2() {
|
|
6114
8093
|
if (existsSync13(PID_FILE2))
|
|
6115
|
-
|
|
8094
|
+
unlinkSync6(PID_FILE2);
|
|
6116
8095
|
}
|
|
6117
8096
|
function isProcessAlive2(pid) {
|
|
6118
8097
|
try {
|
|
@@ -6149,7 +8128,7 @@ function relayStatus() {
|
|
|
6149
8128
|
}
|
|
6150
8129
|
var PID_FILE2;
|
|
6151
8130
|
var init_pid2 = __esm(() => {
|
|
6152
|
-
PID_FILE2 =
|
|
8131
|
+
PID_FILE2 = join19(homedir20(), ".failproofai", "relay.pid");
|
|
6153
8132
|
});
|
|
6154
8133
|
|
|
6155
8134
|
// scripts/parse-script-args.ts
|
|
@@ -6215,15 +8194,147 @@ function parseScriptArgs(argv) {
|
|
|
6215
8194
|
}
|
|
6216
8195
|
var init_parse_script_args = () => {};
|
|
6217
8196
|
|
|
8197
|
+
// scripts/install-diagnosis.mjs
|
|
8198
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
8199
|
+
import { dirname as dirname7, resolve as resolve9 } from "node:path";
|
|
8200
|
+
import { homedir as homedir21, platform as platform3 } from "node:os";
|
|
8201
|
+
import { spawnSync } from "node:child_process";
|
|
8202
|
+
function findPackageRoot(start) {
|
|
8203
|
+
try {
|
|
8204
|
+
let dir = realpathSync(start);
|
|
8205
|
+
if (existsSync14(dir) && !existsSync14(resolve9(dir, "package.json"))) {
|
|
8206
|
+
dir = dirname7(dir);
|
|
8207
|
+
}
|
|
8208
|
+
while (true) {
|
|
8209
|
+
const pkgPath = resolve9(dir, "package.json");
|
|
8210
|
+
if (existsSync14(pkgPath)) {
|
|
8211
|
+
try {
|
|
8212
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
8213
|
+
if (pkg.name === PKG_NAME)
|
|
8214
|
+
return dir;
|
|
8215
|
+
} catch {}
|
|
8216
|
+
}
|
|
8217
|
+
const parent = dirname7(dir);
|
|
8218
|
+
if (parent === dir)
|
|
8219
|
+
return null;
|
|
8220
|
+
dir = parent;
|
|
8221
|
+
}
|
|
8222
|
+
} catch {
|
|
8223
|
+
return null;
|
|
8224
|
+
}
|
|
8225
|
+
}
|
|
8226
|
+
function readPackageVersion(packageRoot) {
|
|
8227
|
+
if (!packageRoot)
|
|
8228
|
+
return null;
|
|
8229
|
+
try {
|
|
8230
|
+
const pkg = JSON.parse(readFileSync10(resolve9(packageRoot, "package.json"), "utf8"));
|
|
8231
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
8232
|
+
} catch {
|
|
8233
|
+
return null;
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
function resolvePathFirstBinary() {
|
|
8237
|
+
try {
|
|
8238
|
+
const isWin = platform3() === "win32";
|
|
8239
|
+
const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
|
|
8240
|
+
if (res.status !== 0)
|
|
8241
|
+
return null;
|
|
8242
|
+
const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
8243
|
+
return first ? first.trim() : null;
|
|
8244
|
+
} catch {
|
|
8245
|
+
return null;
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
function locateNpmGlobal() {
|
|
8249
|
+
try {
|
|
8250
|
+
const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
8251
|
+
if (res.status !== 0)
|
|
8252
|
+
return null;
|
|
8253
|
+
const root = (res.stdout || "").trim();
|
|
8254
|
+
if (!root)
|
|
8255
|
+
return null;
|
|
8256
|
+
const candidate = resolve9(root, PKG_NAME);
|
|
8257
|
+
return existsSync14(resolve9(candidate, "package.json")) ? candidate : null;
|
|
8258
|
+
} catch {
|
|
8259
|
+
return null;
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
function locateBunGlobal() {
|
|
8263
|
+
try {
|
|
8264
|
+
const candidate = resolve9(homedir21(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
8265
|
+
return existsSync14(resolve9(candidate, "package.json")) ? candidate : null;
|
|
8266
|
+
} catch {
|
|
8267
|
+
return null;
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
function buildRecommendation(pathFirstBin) {
|
|
8271
|
+
if (!pathFirstBin)
|
|
8272
|
+
return null;
|
|
8273
|
+
const bunBinPrefix = resolve9(homedir21(), ".bun", "bin") + "/";
|
|
8274
|
+
const bunGlobalPrefix = resolve9(homedir21(), ".bun", "install", "global") + "/";
|
|
8275
|
+
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
8276
|
+
if (isBun) {
|
|
8277
|
+
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
8278
|
+
}
|
|
8279
|
+
return `npm rm -g ${PKG_NAME}`;
|
|
8280
|
+
}
|
|
8281
|
+
function diagnoseShadow(self) {
|
|
8282
|
+
const selfPackageRoot = (() => {
|
|
8283
|
+
try {
|
|
8284
|
+
return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
|
|
8285
|
+
} catch {
|
|
8286
|
+
return self?.selfPackageRoot ?? null;
|
|
8287
|
+
}
|
|
8288
|
+
})();
|
|
8289
|
+
const selfVersion = self?.selfVersion ?? null;
|
|
8290
|
+
const pathFirstBin = resolvePathFirstBinary();
|
|
8291
|
+
const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
|
|
8292
|
+
const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
|
|
8293
|
+
const npmGlobalPath = locateNpmGlobal();
|
|
8294
|
+
const npmGlobalVersion = readPackageVersion(npmGlobalPath);
|
|
8295
|
+
const bunGlobalPath = locateBunGlobal();
|
|
8296
|
+
const bunGlobalVersion = readPackageVersion(bunGlobalPath);
|
|
8297
|
+
let shadowed = false;
|
|
8298
|
+
if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
|
|
8299
|
+
shadowed = true;
|
|
8300
|
+
} else if (pathFirstPackageRoot) {
|
|
8301
|
+
if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
|
|
8302
|
+
shadowed = true;
|
|
8303
|
+
else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
|
|
8304
|
+
shadowed = true;
|
|
8305
|
+
}
|
|
8306
|
+
const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
|
|
8307
|
+
let shadowDescription = null;
|
|
8308
|
+
if (shadowed) {
|
|
8309
|
+
shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
|
|
8310
|
+
}
|
|
8311
|
+
return {
|
|
8312
|
+
selfPackageRoot,
|
|
8313
|
+
selfVersion,
|
|
8314
|
+
pathFirstBin,
|
|
8315
|
+
pathFirstPath: pathFirstPackageRoot,
|
|
8316
|
+
pathFirstVersion,
|
|
8317
|
+
npmGlobalPath,
|
|
8318
|
+
npmGlobalVersion,
|
|
8319
|
+
bunGlobalPath,
|
|
8320
|
+
bunGlobalVersion,
|
|
8321
|
+
shadowed,
|
|
8322
|
+
shadowDescription,
|
|
8323
|
+
recommendation
|
|
8324
|
+
};
|
|
8325
|
+
}
|
|
8326
|
+
var PKG_NAME = "failproofai";
|
|
8327
|
+
var init_install_diagnosis = () => {};
|
|
8328
|
+
|
|
6218
8329
|
// scripts/launch.ts
|
|
6219
8330
|
var exports_launch = {};
|
|
6220
8331
|
__export(exports_launch, {
|
|
6221
8332
|
launch: () => launch
|
|
6222
8333
|
});
|
|
6223
8334
|
import { spawn as spawn4 } from "child_process";
|
|
6224
|
-
import { realpathSync, existsSync as
|
|
6225
|
-
import { resolve as
|
|
6226
|
-
import { fileURLToPath } from "node:url";
|
|
8335
|
+
import { realpathSync as realpathSync2, existsSync as existsSync15 } from "node:fs";
|
|
8336
|
+
import { resolve as resolve10, dirname as dirname8 } from "node:path";
|
|
8337
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6227
8338
|
function launch(mode) {
|
|
6228
8339
|
const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
6229
8340
|
console.log(`
|
|
@@ -6254,10 +8365,27 @@ function launch(mode) {
|
|
|
6254
8365
|
process.env.PORT = port;
|
|
6255
8366
|
process.env.HOSTNAME = "0.0.0.0";
|
|
6256
8367
|
cmd = "node";
|
|
6257
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
6258
|
-
const serverJsPath =
|
|
6259
|
-
if (!
|
|
6260
|
-
|
|
8368
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve10(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
8369
|
+
const serverJsPath = resolve10(packageRoot, ".next/standalone/server.js");
|
|
8370
|
+
if (!existsSync15(serverJsPath)) {
|
|
8371
|
+
let shadowMessage = null;
|
|
8372
|
+
try {
|
|
8373
|
+
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
8374
|
+
if (diag.shadowed) {
|
|
8375
|
+
const alt = (diag.npmGlobalPath && diag.npmGlobalPath !== diag.pathFirstPath ? { path: diag.npmGlobalPath, version: diag.npmGlobalVersion } : null) ?? (diag.bunGlobalPath && diag.bunGlobalPath !== diag.pathFirstPath ? { path: diag.bunGlobalPath, version: diag.bunGlobalVersion } : null);
|
|
8376
|
+
const newer = alt?.path ?? "(unknown)";
|
|
8377
|
+
const newerVer = alt?.version ?? "?";
|
|
8378
|
+
shadowMessage = `
|
|
8379
|
+
Error: failproofai on your PATH is a stale install that no longer has its build output.
|
|
8380
|
+
` + ` Running: ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `
|
|
8381
|
+
` + ` Newer copy: ${newer} (v${newerVer})
|
|
8382
|
+
|
|
8383
|
+
` + `Remove the shadow with:
|
|
8384
|
+
${diag.recommendation}
|
|
8385
|
+
`;
|
|
8386
|
+
}
|
|
8387
|
+
} catch {}
|
|
8388
|
+
console.error(shadowMessage ?? `
|
|
6261
8389
|
Error: Cannot find server.js at:
|
|
6262
8390
|
${serverJsPath}
|
|
6263
8391
|
|
|
@@ -6293,6 +8421,7 @@ Error: Cannot find server.js at:
|
|
|
6293
8421
|
var init_launch = __esm(() => {
|
|
6294
8422
|
init_paths();
|
|
6295
8423
|
init_parse_script_args();
|
|
8424
|
+
init_install_diagnosis();
|
|
6296
8425
|
init_package();
|
|
6297
8426
|
});
|
|
6298
8427
|
|
|
@@ -6314,18 +8443,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
6314
8443
|
});
|
|
6315
8444
|
|
|
6316
8445
|
// bin/failproofai.mjs
|
|
6317
|
-
import { realpathSync as
|
|
6318
|
-
import { dirname as
|
|
6319
|
-
import { fileURLToPath as
|
|
8446
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
8447
|
+
import { dirname as dirname9, resolve as resolve11 } from "path";
|
|
8448
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6320
8449
|
// package.json
|
|
6321
|
-
var version = "0.0.
|
|
8450
|
+
var version = "0.0.10-beta.1";
|
|
6322
8451
|
|
|
6323
8452
|
// bin/failproofai.mjs
|
|
6324
8453
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
6325
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
8454
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve11(dirname9(realpathSync3(fileURLToPath3(import.meta.url))), "..");
|
|
6326
8455
|
}
|
|
6327
8456
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
6328
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
8457
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve11(dirname9(realpathSync3(fileURLToPath3(import.meta.url))), "..", "dist");
|
|
6329
8458
|
}
|
|
6330
8459
|
var args = process.argv.slice(2);
|
|
6331
8460
|
if (args[0] === "p")
|
|
@@ -6334,13 +8463,13 @@ var hookIdx = args.indexOf("--hook");
|
|
|
6334
8463
|
if (hookIdx >= 0) {
|
|
6335
8464
|
if (!args[hookIdx + 1]) {
|
|
6336
8465
|
console.error("Error: Missing event type after --hook");
|
|
6337
|
-
console.error("Usage: failproofai --hook <event> [--cli <claude|codex>]");
|
|
8466
|
+
console.error("Usage: failproofai --hook <event> [--cli <claude|codex|copilot|cursor|opencode|pi|gemini>]");
|
|
6338
8467
|
process.exit(1);
|
|
6339
8468
|
}
|
|
6340
8469
|
const eventType = args[hookIdx + 1];
|
|
6341
8470
|
const cliIdx = args.indexOf("--cli");
|
|
6342
8471
|
const cliArg = cliIdx >= 0 ? args[cliIdx + 1] : undefined;
|
|
6343
|
-
const cli = cliArg && (cliArg === "claude" || cliArg === "codex") ? cliArg : "claude";
|
|
8472
|
+
const cli = cliArg && (cliArg === "claude" || cliArg === "codex" || cliArg === "copilot" || cliArg === "cursor" || cliArg === "opencode" || cliArg === "pi" || cliArg === "gemini") ? cliArg : "claude";
|
|
6344
8473
|
try {
|
|
6345
8474
|
const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
|
|
6346
8475
|
const exitCode = await handleHookEvent2(eventType, cli);
|
|
@@ -6382,17 +8511,19 @@ COMMANDS
|
|
|
6382
8511
|
policies, p List all available policies and their status
|
|
6383
8512
|
policies --install, -i Enable policies in agent CLI settings
|
|
6384
8513
|
[names...] Specific policy names to enable
|
|
6385
|
-
--cli claude|codex
|
|
6386
|
-
(
|
|
8514
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
8515
|
+
Agent CLI(s) to install for; space-separated
|
|
8516
|
+
(e.g. --cli claude codex copilot cursor opencode pi gemini) or repeated.
|
|
6387
8517
|
Default: detect installed CLIs and prompt.
|
|
6388
8518
|
--scope user|project|local Config scope to write to (default: user)
|
|
6389
|
-
(Codex
|
|
8519
|
+
(Codex / Copilot / Cursor / OpenCode / Pi / Gemini support user|project only)
|
|
6390
8520
|
--beta Include beta policies
|
|
6391
8521
|
--custom, -c <path> Path to a JS file of custom policies
|
|
6392
8522
|
|
|
6393
8523
|
policies --uninstall, -u Disable policies or remove hooks
|
|
6394
8524
|
[names...] Specific policy names to disable
|
|
6395
|
-
--cli claude|codex
|
|
8525
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
8526
|
+
Agent CLI(s) to uninstall from
|
|
6396
8527
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
6397
8528
|
--beta Remove only beta policies
|
|
6398
8529
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -6418,11 +8549,21 @@ EXAMPLES
|
|
|
6418
8549
|
failproofai policies --install
|
|
6419
8550
|
failproofai policies --install block-sudo sanitize-api-keys --scope project
|
|
6420
8551
|
failproofai policies --install --cli codex --scope project
|
|
6421
|
-
failproofai policies --install --cli
|
|
8552
|
+
failproofai policies --install --cli copilot --scope project
|
|
8553
|
+
failproofai policies --install --cli cursor --scope project
|
|
8554
|
+
failproofai policies --install --cli opencode --scope project
|
|
8555
|
+
failproofai policies --install --cli pi --scope project
|
|
8556
|
+
failproofai policies --install --cli gemini --scope project
|
|
8557
|
+
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini
|
|
6422
8558
|
failproofai policies --install --custom ./my-policies.js
|
|
6423
8559
|
failproofai policies -i -c ./my-policies.js
|
|
6424
8560
|
failproofai policies --uninstall block-sudo
|
|
6425
8561
|
failproofai policies --uninstall --cli codex
|
|
8562
|
+
failproofai policies --uninstall --cli copilot
|
|
8563
|
+
failproofai policies --uninstall --cli cursor
|
|
8564
|
+
failproofai policies --uninstall --cli opencode
|
|
8565
|
+
failproofai policies --uninstall --cli pi
|
|
8566
|
+
failproofai policies --uninstall --cli gemini
|
|
6426
8567
|
failproofai policies --uninstall --custom
|
|
6427
8568
|
|
|
6428
8569
|
LINKS
|
|
@@ -6457,19 +8598,21 @@ USAGE
|
|
|
6457
8598
|
|
|
6458
8599
|
OPTIONS (install)
|
|
6459
8600
|
[names...] Specific policy names to enable (omit for interactive)
|
|
6460
|
-
--cli claude|codex
|
|
6461
|
-
(
|
|
6462
|
-
|
|
6463
|
-
|
|
8601
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
8602
|
+
Agent CLI(s) to install for; space-separated
|
|
8603
|
+
(e.g. --cli claude codex copilot cursor opencode pi gemini) or repeated.
|
|
8604
|
+
Omit to detect installed CLIs and prompt (or
|
|
8605
|
+
auto-pick if only one is found).
|
|
6464
8606
|
--scope user|project|local Config scope to write to (default: user)
|
|
6465
|
-
(Codex
|
|
8607
|
+
(Codex / Copilot / Cursor / OpenCode / Pi / Gemini support user|project only)
|
|
6466
8608
|
--beta Include beta policies
|
|
6467
8609
|
--custom, -c <path> Path to a JS file of custom policies
|
|
6468
8610
|
(skips interactive prompt; validates file first)
|
|
6469
8611
|
|
|
6470
8612
|
OPTIONS (uninstall)
|
|
6471
8613
|
[names...] Specific policy names to disable (omit to remove hooks)
|
|
6472
|
-
--cli claude|codex
|
|
8614
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
8615
|
+
Agent CLI(s) to uninstall from
|
|
6473
8616
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
6474
8617
|
--beta Remove only beta policies
|
|
6475
8618
|
--custom, -c Clear the customPoliciesPath from config
|
|
@@ -6479,11 +8622,20 @@ EXAMPLES
|
|
|
6479
8622
|
failproofai policies --install
|
|
6480
8623
|
failproofai policies --install block-sudo sanitize-api-keys
|
|
6481
8624
|
failproofai policies --install --cli codex --scope project
|
|
6482
|
-
failproofai policies --install --cli
|
|
8625
|
+
failproofai policies --install --cli copilot --scope project
|
|
8626
|
+
failproofai policies --install --cli cursor --scope project
|
|
8627
|
+
failproofai policies --install --cli opencode --scope project
|
|
8628
|
+
failproofai policies --install --cli pi --scope project
|
|
8629
|
+
failproofai policies --install --cli gemini --scope project
|
|
8630
|
+
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini
|
|
6483
8631
|
failproofai policies --install --custom ./my-policies.js
|
|
6484
8632
|
failproofai policies -i -c ./my-policies.js
|
|
6485
8633
|
failproofai policies --uninstall block-sudo
|
|
6486
8634
|
failproofai policies --uninstall --cli codex
|
|
8635
|
+
failproofai policies --uninstall --cli copilot
|
|
8636
|
+
failproofai policies --uninstall --cli cursor
|
|
8637
|
+
failproofai policies --uninstall --cli opencode
|
|
8638
|
+
failproofai policies --uninstall --cli pi
|
|
6487
8639
|
failproofai policies -u
|
|
6488
8640
|
failproofai policies --uninstall --custom
|
|
6489
8641
|
`.trimStart());
|
|
@@ -6506,7 +8658,7 @@ EXAMPLES
|
|
|
6506
8658
|
throw new CliError3(`Missing path after --custom/-c
|
|
6507
8659
|
Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
6508
8660
|
}
|
|
6509
|
-
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
8661
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"]);
|
|
6510
8662
|
const cliFlagValues = [];
|
|
6511
8663
|
const cliConsumedIdxs = new Set;
|
|
6512
8664
|
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
@@ -6523,7 +8675,7 @@ Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
|
6523
8675
|
consumed++;
|
|
6524
8676
|
}
|
|
6525
8677
|
if (consumed === 0) {
|
|
6526
|
-
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
8678
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini (or any subset)");
|
|
6527
8679
|
}
|
|
6528
8680
|
}
|
|
6529
8681
|
const includeBeta = subArgs.includes("--beta");
|
|
@@ -6542,7 +8694,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
6542
8694
|
}
|
|
6543
8695
|
const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
6544
8696
|
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
|
|
6545
|
-
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
8697
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "install");
|
|
6546
8698
|
await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath, false, cli);
|
|
6547
8699
|
process.exit(0);
|
|
6548
8700
|
}
|
|
@@ -6557,7 +8709,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
6557
8709
|
if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
|
|
6558
8710
|
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
|
|
6559
8711
|
}
|
|
6560
|
-
const VALID_CLIS = new Set(["claude", "codex"]);
|
|
8712
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"]);
|
|
6561
8713
|
const cliFlagValues = [];
|
|
6562
8714
|
const cliConsumedIdxs = new Set;
|
|
6563
8715
|
const cliFlagIdxs = subArgs.map((a, i) => a === "--cli" ? i : -1).filter((i) => i >= 0);
|
|
@@ -6574,7 +8726,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
6574
8726
|
consumed++;
|
|
6575
8727
|
}
|
|
6576
8728
|
if (consumed === 0) {
|
|
6577
|
-
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex (or any subset)");
|
|
8729
|
+
throw new CliError3("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini (or any subset)");
|
|
6578
8730
|
}
|
|
6579
8731
|
}
|
|
6580
8732
|
const betaOnly = subArgs.includes("--beta");
|
|
@@ -6591,7 +8743,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
6591
8743
|
Run \`failproofai policies --help\` for usage.`);
|
|
6592
8744
|
}
|
|
6593
8745
|
const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
6594
|
-
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined);
|
|
8746
|
+
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "uninstall");
|
|
6595
8747
|
await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks, cli });
|
|
6596
8748
|
process.exit(0);
|
|
6597
8749
|
}
|