failproofai 1.0.0-beta.1 → 1.0.0-beta.3
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/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- 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 +10 -10
- 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/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +1 -1
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ath6v_._.js → [root-of-the-server]__0ls0gsr._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ig795d._.js → [root-of-the-server]__10s_z4l._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0114ewg._.js → [root-of-the-server]__1j4u3yd._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1yo1gyz._.js → [root-of-the-server]__1ky3im9._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_14q3-7b.js → node_modules_html-to-image_es_index_0jfts82.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/pages/404.html +1 -1
- 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 +32 -20
- package/.next/standalone/.next/static/chunks/{1d71o1_tav721.js → 01h6lfjz1_oie.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/{07ix3lb4gib0x.js → 0a7k_x4rp_97z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{35584kn409ad1.js → 0cvd6bcdwchc_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{191njgoik03fm.js → 13-obgukzg_ke.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/{2ovmv4esqalpb.js → 1h0_yaoq5f3yx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{30is-6r00sbd7.js → 29g8ykjk6sx_8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1b3a1scouf40o.js → 2rqm-klet_gbl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{24gs8ip6_spcb.js → 2uvyh8y0naofj.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/3p-n_j80zlzi3.js +6 -0
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/Cargo.lock +1992 -198
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/policies/hooks-client.tsx +81 -6
- package/.next/standalone/crates/CLOUD_POLICIES.md +136 -0
- package/.next/standalone/crates/failproofaid/Cargo.toml +7 -0
- package/.next/standalone/crates/failproofaid/src/cloud_client.rs +484 -0
- package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +921 -0
- package/.next/standalone/crates/failproofaid/src/main.rs +452 -3
- package/.next/standalone/crates/failproofaid/src/paths.rs +29 -0
- package/.next/standalone/crates/fpai-collect/Cargo.toml +43 -0
- package/.next/standalone/crates/fpai-collect/src/config.rs +412 -0
- package/.next/standalone/crates/fpai-collect/src/cursor.rs +520 -0
- package/.next/standalone/crates/fpai-collect/src/delivery.rs +472 -0
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +484 -0
- package/.next/standalone/crates/fpai-collect/src/health.rs +369 -0
- package/.next/standalone/crates/fpai-collect/src/lib.rs +34 -0
- package/.next/standalone/crates/fpai-collect/src/redact.rs +516 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +279 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +619 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +156 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +715 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +158 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +512 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +511 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +586 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +318 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +579 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +310 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +448 -0
- package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +23 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +176 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +500 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +315 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +493 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +201 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +533 -0
- package/.next/standalone/crates/fpai-collect/src/spool.rs +458 -0
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +387 -0
- package/.next/standalone/crates/fpai-collect/src/supervisor.rs +423 -0
- package/.next/standalone/crates/fpai-collect/src/uploader.rs +600 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +738 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +393 -0
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +975 -0
- package/.next/standalone/crates/fpai-collect/tests/config.rs +286 -0
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1019 -0
- package/.next/standalone/crates/fpai-collect/tests/delivery.rs +281 -0
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1008 -0
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1129 -0
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +621 -0
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +854 -0
- package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +1478 -0
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1191 -0
- package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +290 -0
- package/.next/standalone/crates/fpai-collect/tests/uploader.rs +506 -0
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +129 -0
- package/dist/cli.mjs +2294 -786
- package/dist/worker.mjs +365 -52
- package/package.json +2 -2
- package/src/hooks/builtin-policies.ts +139 -0
- package/src/hooks/cloud-connection.ts +205 -0
- package/src/hooks/cloud-enrollment-cli.ts +227 -0
- package/src/hooks/cloud-enrollment.ts +173 -0
- package/src/hooks/cloud-managed-policies.ts +138 -0
- package/src/hooks/collector-config.ts +226 -0
- package/src/hooks/configure-wizard.ts +308 -18
- package/src/hooks/custom-hooks-loader.ts +69 -25
- package/src/hooks/daemon-client.ts +18 -0
- package/src/hooks/daemon-service.ts +125 -24
- package/src/hooks/handler.ts +144 -6
- package/src/hooks/hook-activity-store.ts +50 -0
- package/src/hooks/loader-utils.ts +6 -4
- package/src/hooks/policy-types.ts +35 -0
- package/src/hooks/session-pause-cli.ts +157 -0
- package/src/hooks/session-pause.ts +236 -0
- package/src/hooks/tui.ts +115 -0
- package/.next/standalone/.next/static/chunks/0fdx7ikab08-g.js +0 -6
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- /package/.next/standalone/.next/static/{avJMC2AXvrg13Pbm4QH8F → pM1veX5Llx4YKEaiFc8Sf}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{avJMC2AXvrg13Pbm4QH8F → pM1veX5Llx4YKEaiFc8Sf}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{avJMC2AXvrg13Pbm4QH8F → pM1veX5Llx4YKEaiFc8Sf}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// package.json
|
|
22
|
-
var version2 = "1.0.0-beta.
|
|
22
|
+
var version2 = "1.0.0-beta.3";
|
|
23
23
|
var init_package = () => {};
|
|
24
24
|
|
|
25
25
|
// src/posthog-key.ts
|
|
@@ -332,6 +332,15 @@ function readHooksConfig() {
|
|
|
332
332
|
return { enabledPolicies: [] };
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
|
+
function writeHooksConfig(config) {
|
|
336
|
+
const configPath = getConfigPath();
|
|
337
|
+
const dir = dirname(configPath);
|
|
338
|
+
if (!existsSync2(dir)) {
|
|
339
|
+
mkdirSync2(dir, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + `
|
|
342
|
+
`, "utf8");
|
|
343
|
+
}
|
|
335
344
|
function getConfigPathForScope(scope, cwd) {
|
|
336
345
|
switch (scope) {
|
|
337
346
|
case "user":
|
|
@@ -397,11 +406,20 @@ var init_hooks_config = __esm(() => {
|
|
|
397
406
|
var exports_daemon_client = {};
|
|
398
407
|
__export(exports_daemon_client, {
|
|
399
408
|
tryDaemonHook: () => tryDaemonHook,
|
|
400
|
-
isDaemonConfigured: () => isDaemonConfigured
|
|
409
|
+
isDaemonConfigured: () => isDaemonConfigured,
|
|
410
|
+
daemonSocketPresent: () => daemonSocketPresent
|
|
401
411
|
});
|
|
402
412
|
import { createConnection } from "node:net";
|
|
403
413
|
import { resolve as resolve2 } from "node:path";
|
|
404
414
|
import { homedir as homedir3 } from "node:os";
|
|
415
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
416
|
+
function daemonSocketPresent() {
|
|
417
|
+
try {
|
|
418
|
+
return existsSync3(socketPath());
|
|
419
|
+
} catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
405
423
|
function socketPath() {
|
|
406
424
|
if (process.env.FAILPROOFAI_DAEMON_SOCKET)
|
|
407
425
|
return process.env.FAILPROOFAI_DAEMON_SOCKET;
|
|
@@ -1132,6 +1150,41 @@ function getFilePath(ctx) {
|
|
|
1132
1150
|
function parseArgvTokens(cmd) {
|
|
1133
1151
|
return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
1134
1152
|
}
|
|
1153
|
+
function decodeAnsiC(body) {
|
|
1154
|
+
return body.replace(/\\(x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{1,4}|U[0-9A-Fa-f]{1,8}|[0-7]{1,3}|.)/g, (_, seq) => {
|
|
1155
|
+
try {
|
|
1156
|
+
if (seq[0] === "x")
|
|
1157
|
+
return String.fromCharCode(parseInt(seq.slice(1), 16));
|
|
1158
|
+
if (seq[0] === "u" || seq[0] === "U")
|
|
1159
|
+
return String.fromCodePoint(parseInt(seq.slice(1), 16));
|
|
1160
|
+
if (/^[0-7]+$/.test(seq))
|
|
1161
|
+
return String.fromCharCode(parseInt(seq, 8));
|
|
1162
|
+
} catch {
|
|
1163
|
+
return seq;
|
|
1164
|
+
}
|
|
1165
|
+
const named = {
|
|
1166
|
+
a: "\x07",
|
|
1167
|
+
b: "\b",
|
|
1168
|
+
e: "\x1B",
|
|
1169
|
+
f: "\f",
|
|
1170
|
+
n: `
|
|
1171
|
+
`,
|
|
1172
|
+
r: "\r",
|
|
1173
|
+
t: "\t",
|
|
1174
|
+
v: "\v",
|
|
1175
|
+
"\\": "\\",
|
|
1176
|
+
"'": "'",
|
|
1177
|
+
'"': '"',
|
|
1178
|
+
"?": "?"
|
|
1179
|
+
};
|
|
1180
|
+
return named[seq] ?? seq;
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
function stripShellQuoting(command) {
|
|
1184
|
+
const joined = command.replace(/\\\r?\n/g, "");
|
|
1185
|
+
const ansiDecoded = joined.replace(/\$'((?:[^'\\]|\\.)*)'/g, (_, body) => decodeAnsiC(body));
|
|
1186
|
+
return ansiDecoded.replace(/\\(.)/g, "$1").replace(/['"]/g, "");
|
|
1187
|
+
}
|
|
1135
1188
|
function statGitHeadMtimeMs(cwd) {
|
|
1136
1189
|
try {
|
|
1137
1190
|
return statSync3(join2(cwd, ".git", "HEAD")).mtimeMs;
|
|
@@ -1394,6 +1447,15 @@ function blockSudo(ctx) {
|
|
|
1394
1447
|
}
|
|
1395
1448
|
return allow();
|
|
1396
1449
|
}
|
|
1450
|
+
function blockSelfPause(ctx) {
|
|
1451
|
+
if (ctx.toolName !== "Bash")
|
|
1452
|
+
return allow();
|
|
1453
|
+
const cmd = getCommand(ctx);
|
|
1454
|
+
if (SELF_PAUSE_RE.test(cmd) || SELF_PAUSE_RE.test(stripShellQuoting(cmd))) {
|
|
1455
|
+
return deny("Pausing failproofai enforcement is a human action, not an agent one. " + "If a policy is blocking legitimate work, say so and let the operator decide.");
|
|
1456
|
+
}
|
|
1457
|
+
return allow();
|
|
1458
|
+
}
|
|
1397
1459
|
function blockCurlPipeSh(ctx) {
|
|
1398
1460
|
if (ctx.toolName !== "Bash")
|
|
1399
1461
|
return allow();
|
|
@@ -2131,7 +2193,7 @@ function registerBuiltinPolicies(enabledNames) {
|
|
|
2131
2193
|
}
|
|
2132
2194
|
}
|
|
2133
2195
|
}
|
|
2134
|
-
var isClaudeInternalPath, isClaudeSettingsFile, SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, SHORT_FLAG_BUNDLE_RE, SAFE_FORCE_PREFIXES, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, GIT_BRANCH_CACHE_MAX_ENTRIES = 500, HOME_PREFIX_RE, RM_CMD_RE, FIND_CMD_RE, FIND_EXEC_RE, FIND_GLOBAL_OPT_RE, FIND_EXPR_START_RE, SCRATCH_ROOTS, CATASTROPHIC_DEPTH = 2, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
2196
|
+
var isClaudeInternalPath, isClaudeSettingsFile, SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, SELF_PAUSE_RE, PS_WEB_PIPE_RE, SHORT_FLAG_BUNDLE_RE, SAFE_FORCE_PREFIXES, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, GIT_BRANCH_CACHE_MAX_ENTRIES = 500, HOME_PREFIX_RE, RM_CMD_RE, FIND_CMD_RE, FIND_EXEC_RE, FIND_GLOBAL_OPT_RE, FIND_EXPR_START_RE, SCRATCH_ROOTS, CATASTROPHIC_DEPTH = 2, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
2135
2197
|
var init_builtin_policies = __esm(() => {
|
|
2136
2198
|
init_hook_logger();
|
|
2137
2199
|
isClaudeInternalPath = isAgentInternalPath;
|
|
@@ -2172,6 +2234,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
2172
2234
|
PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
|
|
2173
2235
|
RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
|
|
2174
2236
|
CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
|
|
2237
|
+
SELF_PAUSE_RE = /failproofai[^\s]*(?:\s+\S+)*?\s+(?:config|configure|setup)\b(?:\s+\S+)*?\s+--pause\b/;
|
|
2175
2238
|
PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
|
|
2176
2239
|
SHORT_FLAG_BUNDLE_RE = /^-[a-zA-Z]*f[a-zA-Z]*$/;
|
|
2177
2240
|
SAFE_FORCE_PREFIXES = ["--force-with-lease", "--force-if-includes"];
|
|
@@ -2319,6 +2382,16 @@ var init_builtin_policies = __esm(() => {
|
|
|
2319
2382
|
}
|
|
2320
2383
|
}
|
|
2321
2384
|
},
|
|
2385
|
+
{
|
|
2386
|
+
name: "block-self-pause",
|
|
2387
|
+
displayTitle: "Tried to pause failproofai enforcement",
|
|
2388
|
+
impact: "An agent that can pause enforcement can switch off every other policy.",
|
|
2389
|
+
description: "Block agents from pausing failproofai enforcement",
|
|
2390
|
+
fn: blockSelfPause,
|
|
2391
|
+
match: { events: ["PreToolUse", "PermissionRequest"], toolNames: ["Bash"] },
|
|
2392
|
+
defaultEnabled: true,
|
|
2393
|
+
category: "Dangerous Commands"
|
|
2394
|
+
},
|
|
2322
2395
|
{
|
|
2323
2396
|
name: "block-sudo",
|
|
2324
2397
|
displayTitle: "Tried to run a command with sudo",
|
|
@@ -3628,8 +3701,14 @@ function clearCustomHooks() {
|
|
|
3628
3701
|
const g = globalThis;
|
|
3629
3702
|
g[REGISTRY_KEY2] = [];
|
|
3630
3703
|
}
|
|
3631
|
-
var REGISTRY_KEY2 = "__failproofai_custom_hooks__";
|
|
3632
|
-
var init_custom_hooks_registry = () => {
|
|
3704
|
+
var REGISTRY_KEY2 = "__failproofai_custom_hooks__", customPolicies;
|
|
3705
|
+
var init_custom_hooks_registry = __esm(() => {
|
|
3706
|
+
customPolicies = {
|
|
3707
|
+
add(hook) {
|
|
3708
|
+
getRegistry2().push(hook);
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
});
|
|
3633
3712
|
|
|
3634
3713
|
// src/hooks/loader-utils.ts
|
|
3635
3714
|
import { readFile as readFile2, writeFile as writeFile2, unlink, access } from "fs/promises";
|
|
@@ -3670,8 +3749,8 @@ async function resolveLocalImport(fromDir, specifier) {
|
|
|
3670
3749
|
}
|
|
3671
3750
|
return null;
|
|
3672
3751
|
}
|
|
3673
|
-
async function createEsmShim(distIndex, distUrl) {
|
|
3674
|
-
const shimPath = distIndex
|
|
3752
|
+
async function createEsmShim(distIndex, distUrl, tmpSuffix = TMP_SUFFIX) {
|
|
3753
|
+
const shimPath = `${distIndex}${tmpSuffix}.shim.mjs`;
|
|
3675
3754
|
const shimCode = [
|
|
3676
3755
|
`import _cjs from '${distUrl}';`,
|
|
3677
3756
|
`export const customPolicies = _cjs.customPolicies;`,
|
|
@@ -3686,13 +3765,13 @@ async function createEsmShim(distIndex, distUrl) {
|
|
|
3686
3765
|
await writeFile2(shimPath, shimCode, "utf-8");
|
|
3687
3766
|
return { shimPath, shimUrl: pathToFileURL(shimPath).href };
|
|
3688
3767
|
}
|
|
3689
|
-
async function rewriteFileTree(entryPath, distUrl, distIndex) {
|
|
3768
|
+
async function rewriteFileTree(entryPath, distUrl, distIndex, tmpSuffix = TMP_SUFFIX) {
|
|
3690
3769
|
const queue = [entryPath];
|
|
3691
3770
|
const visited = new Set;
|
|
3692
3771
|
const tmpFiles = [];
|
|
3693
3772
|
let esmShimUrl = null;
|
|
3694
3773
|
if (distIndex && distUrl) {
|
|
3695
|
-
const shim = await createEsmShim(distIndex, distUrl);
|
|
3774
|
+
const shim = await createEsmShim(distIndex, distUrl, tmpSuffix);
|
|
3696
3775
|
tmpFiles.push(shim.shimPath);
|
|
3697
3776
|
esmShimUrl = shim.shimUrl;
|
|
3698
3777
|
}
|
|
@@ -3723,7 +3802,7 @@ async function rewriteFileTree(entryPath, distUrl, distIndex) {
|
|
|
3723
3802
|
if (!visited.has(resolved2) && !queue.includes(resolved2)) {
|
|
3724
3803
|
queue.push(resolved2);
|
|
3725
3804
|
}
|
|
3726
|
-
let relPath = relative(dir, resolved2 +
|
|
3805
|
+
let relPath = relative(dir, resolved2 + tmpSuffix).split("\\").join("/");
|
|
3727
3806
|
if (!relPath.startsWith("."))
|
|
3728
3807
|
relPath = "./" + relPath;
|
|
3729
3808
|
rewrites.set(specifier, relPath);
|
|
@@ -3736,7 +3815,7 @@ async function rewriteFileTree(entryPath, distUrl, distIndex) {
|
|
|
3736
3815
|
code = code.replace(new RegExp(`'${escaped}'`, "g"), `'${replacement}'`);
|
|
3737
3816
|
code = code.replace(new RegExp(`"${escaped}"`, "g"), `"${replacement}"`);
|
|
3738
3817
|
}
|
|
3739
|
-
const tmpPath = filePath +
|
|
3818
|
+
const tmpPath = filePath + tmpSuffix;
|
|
3740
3819
|
await writeFile2(tmpPath, code, "utf-8");
|
|
3741
3820
|
tmpFiles.push(tmpPath);
|
|
3742
3821
|
}
|
|
@@ -3757,11 +3836,13 @@ var init_loader_utils = __esm(() => {
|
|
|
3757
3836
|
|
|
3758
3837
|
// src/hooks/custom-hooks-loader.ts
|
|
3759
3838
|
import { resolve as resolve5, isAbsolute, basename } from "node:path";
|
|
3760
|
-
import { existsSync as
|
|
3839
|
+
import { existsSync as existsSync4, readdirSync } from "node:fs";
|
|
3840
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
3761
3841
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
3762
3842
|
import { homedir as homedir5 } from "node:os";
|
|
3843
|
+
import { createHash } from "node:crypto";
|
|
3763
3844
|
function discoverPolicyFiles(dir) {
|
|
3764
|
-
if (!
|
|
3845
|
+
if (!existsSync4(dir))
|
|
3765
3846
|
return [];
|
|
3766
3847
|
try {
|
|
3767
3848
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -3771,7 +3852,7 @@ function discoverPolicyFiles(dir) {
|
|
|
3771
3852
|
}
|
|
3772
3853
|
}
|
|
3773
3854
|
function findSkippedPolicyFiles(dir) {
|
|
3774
|
-
if (!
|
|
3855
|
+
if (!existsSync4(dir))
|
|
3775
3856
|
return [];
|
|
3776
3857
|
try {
|
|
3777
3858
|
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && LOADABLE_EXT_RE.test(e.name) && !CONVENTION_FILE_RE.test(e.name) && !e.name.endsWith(".d.ts")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
@@ -3786,10 +3867,24 @@ async function loadSingleFile(absPath, opts) {
|
|
|
3786
3867
|
try {
|
|
3787
3868
|
const distIndex = await findDistIndex();
|
|
3788
3869
|
const distUrl = distIndex ? pathToFileURL2(distIndex).href : null;
|
|
3789
|
-
|
|
3790
|
-
const
|
|
3791
|
-
|
|
3870
|
+
const sequence = ++loadSequence;
|
|
3871
|
+
const tmpSuffix = `${TMP_SUFFIX}.${process.pid}.${sequence}.mjs`;
|
|
3872
|
+
tmpFiles = await rewriteFileTree(absPath, distUrl, distIndex, tmpSuffix);
|
|
3873
|
+
const fingerprint = await fingerprintTemporaryTree(tmpFiles, tmpSuffix);
|
|
3874
|
+
const cached = policyModuleCache.get(absPath);
|
|
3875
|
+
if (cached?.fingerprint === fingerprint) {
|
|
3876
|
+
for (const hook of cached.hooks)
|
|
3877
|
+
customPolicies.add({ ...hook });
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const entryTmp = absPath + tmpSuffix;
|
|
3881
|
+
const fileUrl = pathToFileURL2(entryTmp).href;
|
|
3882
|
+
const hooksBefore = getCustomHooks().length;
|
|
3792
3883
|
await import(fileUrl);
|
|
3884
|
+
policyModuleCache.set(absPath, {
|
|
3885
|
+
fingerprint,
|
|
3886
|
+
hooks: getCustomHooks().slice(hooksBefore).map((hook) => ({ ...hook }))
|
|
3887
|
+
});
|
|
3793
3888
|
} catch (err) {
|
|
3794
3889
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3795
3890
|
const errorType = /Cannot find module|MODULE_NOT_FOUND|ENOENT/i.test(msg) ? "module_not_found" : /SyntaxError|Unexpected token/i.test(msg) ? "syntax_error" : "runtime_error";
|
|
@@ -3807,11 +3902,20 @@ async function loadSingleFile(absPath, opts) {
|
|
|
3807
3902
|
await cleanupTmpFiles(tmpFiles);
|
|
3808
3903
|
}
|
|
3809
3904
|
}
|
|
3905
|
+
async function fingerprintTemporaryTree(tmpFiles, tmpSuffix) {
|
|
3906
|
+
const hash = createHash("sha256");
|
|
3907
|
+
for (const tmpFile of [...tmpFiles].sort()) {
|
|
3908
|
+
hash.update(tmpFile.replaceAll(tmpSuffix, TMP_SUFFIX));
|
|
3909
|
+
const contents = await readFile3(tmpFile, "utf8");
|
|
3910
|
+
hash.update(contents.replaceAll(tmpSuffix, TMP_SUFFIX));
|
|
3911
|
+
}
|
|
3912
|
+
return hash.digest("hex");
|
|
3913
|
+
}
|
|
3810
3914
|
async function loadCustomHooks(customPoliciesPath, opts) {
|
|
3811
3915
|
if (!customPoliciesPath)
|
|
3812
3916
|
return [];
|
|
3813
3917
|
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve5(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
|
|
3814
|
-
if (!
|
|
3918
|
+
if (!existsSync4(absPath)) {
|
|
3815
3919
|
if (opts?.strict)
|
|
3816
3920
|
throw new Error(`Custom hooks file not found: ${absPath}`);
|
|
3817
3921
|
hookLogWarn(`customPoliciesPath not found: ${absPath}`);
|
|
@@ -3840,15 +3944,23 @@ async function loadAllCustomHooks(customPoliciesPaths, opts) {
|
|
|
3840
3944
|
const projectRoot = findProjectConfigDir(opts?.sessionCwd ?? process.cwd());
|
|
3841
3945
|
const conventionEnabled = opts?.customPoliciesEnabled !== false;
|
|
3842
3946
|
const loadedPaths = new Set;
|
|
3947
|
+
const cloudManagedByPath = new Map((opts?.cloudManagedPolicies ?? []).map((policy) => [resolve5(policy.path), policy]));
|
|
3843
3948
|
for (const customPoliciesPath of typeof customPoliciesPaths === "string" ? [customPoliciesPaths] : customPoliciesPaths ?? []) {
|
|
3844
3949
|
const absPath = resolve5(projectRoot, customPoliciesPath);
|
|
3845
|
-
if (
|
|
3950
|
+
if (existsSync4(absPath)) {
|
|
3846
3951
|
if (!loadedPaths.has(absPath)) {
|
|
3847
3952
|
loadedPaths.add(absPath);
|
|
3848
3953
|
const hooksBefore = getCustomHooks().length;
|
|
3849
3954
|
await loadSingleFile(absPath);
|
|
3850
3955
|
for (const hook of getCustomHooks().slice(hooksBefore)) {
|
|
3851
|
-
|
|
3956
|
+
const cloudManaged = cloudManagedByPath.get(absPath);
|
|
3957
|
+
const tagged = hook;
|
|
3958
|
+
if (cloudManaged) {
|
|
3959
|
+
tagged.__cloudManaged = cloudManaged;
|
|
3960
|
+
tagged.__policyId = `cloud:${cloudManaged.id}@${cloudManaged.revision}:${hook.name}`;
|
|
3961
|
+
} else {
|
|
3962
|
+
tagged.__policyId = customPolicyId(absPath, hook.name);
|
|
3963
|
+
}
|
|
3852
3964
|
}
|
|
3853
3965
|
}
|
|
3854
3966
|
} else {
|
|
@@ -3920,7 +4032,7 @@ async function loadAllCustomHooks(customPoliciesPaths, opts) {
|
|
|
3920
4032
|
}
|
|
3921
4033
|
return { hooks: allHooks, conventionSources };
|
|
3922
4034
|
}
|
|
3923
|
-
var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE, loadSequence = 0, LOADABLE_EXT_RE;
|
|
4035
|
+
var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE, loadSequence = 0, policyModuleCache, LOADABLE_EXT_RE;
|
|
3924
4036
|
var init_custom_hooks_loader = __esm(() => {
|
|
3925
4037
|
init_hook_logger();
|
|
3926
4038
|
init_custom_hooks_registry();
|
|
@@ -3929,6 +4041,7 @@ var init_custom_hooks_loader = __esm(() => {
|
|
|
3929
4041
|
init_hook_telemetry2();
|
|
3930
4042
|
init_telemetry_id2();
|
|
3931
4043
|
CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
|
|
4044
|
+
policyModuleCache = new Map;
|
|
3932
4045
|
LOADABLE_EXT_RE = /\.(js|mjs|ts)$/;
|
|
3933
4046
|
});
|
|
3934
4047
|
|
|
@@ -3940,14 +4053,14 @@ import {
|
|
|
3940
4053
|
renameSync as renameSync2,
|
|
3941
4054
|
readdirSync as readdirSync2,
|
|
3942
4055
|
mkdirSync as mkdirSync3,
|
|
3943
|
-
existsSync as
|
|
4056
|
+
existsSync as existsSync5,
|
|
3944
4057
|
statSync as statSync4,
|
|
3945
4058
|
unlinkSync
|
|
3946
4059
|
} from "node:fs";
|
|
3947
4060
|
import { join as join3 } from "node:path";
|
|
3948
4061
|
import { homedir as homedir6 } from "node:os";
|
|
3949
4062
|
function ensureDir() {
|
|
3950
|
-
if (!
|
|
4063
|
+
if (!existsSync5(storeDir)) {
|
|
3951
4064
|
mkdirSync3(storeDir, { recursive: true });
|
|
3952
4065
|
}
|
|
3953
4066
|
}
|
|
@@ -4019,6 +4132,13 @@ function writeCount(countPath, n) {
|
|
|
4019
4132
|
writeFileSync2(countPath, String(n), "utf-8");
|
|
4020
4133
|
} catch {}
|
|
4021
4134
|
}
|
|
4135
|
+
function readFileSafe(filePath) {
|
|
4136
|
+
try {
|
|
4137
|
+
return readFileSync2(filePath, "utf-8");
|
|
4138
|
+
} catch {
|
|
4139
|
+
return "";
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4022
4142
|
function readStoredStats() {
|
|
4023
4143
|
try {
|
|
4024
4144
|
return JSON.parse(readFileSync2(join3(storeDir, STATS_FILE), "utf-8"));
|
|
@@ -4048,6 +4168,49 @@ function updateStats(entry) {
|
|
|
4048
4168
|
} catch {}
|
|
4049
4169
|
}
|
|
4050
4170
|
}
|
|
4171
|
+
function getAllHookActivityEntries() {
|
|
4172
|
+
ensureDir();
|
|
4173
|
+
const currentPath = join3(storeDir, CURRENT_FILE);
|
|
4174
|
+
const currentEntries = readJsonlFile(currentPath).reverse();
|
|
4175
|
+
const archives = getArchiveFiles();
|
|
4176
|
+
const archiveEntries = [];
|
|
4177
|
+
for (const file of archives) {
|
|
4178
|
+
const entries = readJsonlFile(join3(storeDir, file));
|
|
4179
|
+
archiveEntries.push(...entries.reverse());
|
|
4180
|
+
}
|
|
4181
|
+
return [...currentEntries, ...archiveEntries];
|
|
4182
|
+
}
|
|
4183
|
+
function readJsonlFile(filePath) {
|
|
4184
|
+
const content = readFileSafe(filePath);
|
|
4185
|
+
if (!content.trim())
|
|
4186
|
+
return [];
|
|
4187
|
+
const entries = [];
|
|
4188
|
+
for (const line of content.trim().split(`
|
|
4189
|
+
`)) {
|
|
4190
|
+
try {
|
|
4191
|
+
entries.push(JSON.parse(line));
|
|
4192
|
+
} catch {}
|
|
4193
|
+
}
|
|
4194
|
+
return entries;
|
|
4195
|
+
}
|
|
4196
|
+
function getArchiveFiles() {
|
|
4197
|
+
try {
|
|
4198
|
+
const files = readdirSync2(storeDir);
|
|
4199
|
+
return files.filter((f) => f.startsWith("page-") && f.endsWith(".jsonl")).sort((a, b) => {
|
|
4200
|
+
const partsA = a.slice(5, -6).split("-");
|
|
4201
|
+
const partsB = b.slice(5, -6).split("-");
|
|
4202
|
+
const tsA = parseInt(partsA[0], 10);
|
|
4203
|
+
const tsB = parseInt(partsB[0], 10);
|
|
4204
|
+
if (tsA !== tsB)
|
|
4205
|
+
return tsB - tsA;
|
|
4206
|
+
const seqA = partsA.length > 1 ? parseInt(partsA[1], 10) : 0;
|
|
4207
|
+
const seqB = partsB.length > 1 ? parseInt(partsB[1], 10) : 0;
|
|
4208
|
+
return seqB - seqA;
|
|
4209
|
+
});
|
|
4210
|
+
} catch {
|
|
4211
|
+
return [];
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4051
4214
|
var PAGE_SIZE = 25, DEFAULT_STORE_DIR, CURRENT_FILE = "current.jsonl", COUNT_FILE = "current.count", STATS_FILE = "stats.json", LOCK_FILE = "current.lock", LOCK_STALE_MS = 2000, storeDir, rotateSeq = 0;
|
|
4052
4215
|
var init_hook_activity_store = __esm(() => {
|
|
4053
4216
|
DEFAULT_STORE_DIR = join3(homedir6(), ".failproofai", "cache", "hook-activity");
|
|
@@ -4395,7 +4558,7 @@ __export(exports_copilot_projects, {
|
|
|
4395
4558
|
getCachedCopilotSessionsByEncodedName: () => getCachedCopilotSessionsByEncodedName,
|
|
4396
4559
|
getCachedCopilotProjects: () => getCachedCopilotProjects
|
|
4397
4560
|
});
|
|
4398
|
-
import { readdir as readdir2, readFile as
|
|
4561
|
+
import { readdir as readdir2, readFile as readFile4, stat as stat2 } from "node:fs/promises";
|
|
4399
4562
|
import { homedir as homedir9 } from "node:os";
|
|
4400
4563
|
import { join as join6 } from "node:path";
|
|
4401
4564
|
function getCopilotSessionStateRoot() {
|
|
@@ -4434,7 +4597,7 @@ async function scanCopilotSessions() {
|
|
|
4434
4597
|
const settled = await batchAll(candidates.map((c) => async () => {
|
|
4435
4598
|
let workspaceText;
|
|
4436
4599
|
try {
|
|
4437
|
-
workspaceText = await
|
|
4600
|
+
workspaceText = await readFile4(c.workspacePath, "utf-8");
|
|
4438
4601
|
} catch {
|
|
4439
4602
|
return null;
|
|
4440
4603
|
}
|
|
@@ -4543,7 +4706,7 @@ __export(exports_cursor_projects, {
|
|
|
4543
4706
|
getCachedCursorSessionsByEncodedName: () => getCachedCursorSessionsByEncodedName,
|
|
4544
4707
|
getCachedCursorProjects: () => getCachedCursorProjects
|
|
4545
4708
|
});
|
|
4546
|
-
import { readdir as readdir3, readFile as
|
|
4709
|
+
import { readdir as readdir3, readFile as readFile5, stat as stat3 } from "node:fs/promises";
|
|
4547
4710
|
import { homedir as homedir10 } from "node:os";
|
|
4548
4711
|
import { join as join7 } from "node:path";
|
|
4549
4712
|
function getCursorHome() {
|
|
@@ -4584,7 +4747,7 @@ async function findFirstUsableMeta(dir, candidates) {
|
|
|
4584
4747
|
continue;
|
|
4585
4748
|
let text;
|
|
4586
4749
|
try {
|
|
4587
|
-
text = await
|
|
4750
|
+
text = await readFile5(path3, "utf-8");
|
|
4588
4751
|
} catch {
|
|
4589
4752
|
continue;
|
|
4590
4753
|
}
|
|
@@ -4916,7 +5079,7 @@ __export(exports_pi_projects, {
|
|
|
4916
5079
|
getCachedPiSessionsByEncodedName: () => getCachedPiSessionsByEncodedName,
|
|
4917
5080
|
getCachedPiProjects: () => getCachedPiProjects
|
|
4918
5081
|
});
|
|
4919
|
-
import { readdir as readdir4, readFile as
|
|
5082
|
+
import { readdir as readdir4, readFile as readFile6, stat as stat4 } from "node:fs/promises";
|
|
4920
5083
|
import { homedir as homedir11 } from "node:os";
|
|
4921
5084
|
import { join as join8 } from "node:path";
|
|
4922
5085
|
function getPiSessionsRoot() {
|
|
@@ -4939,7 +5102,7 @@ async function statMtime3(path3) {
|
|
|
4939
5102
|
async function readSessionCwd(filePath) {
|
|
4940
5103
|
let text;
|
|
4941
5104
|
try {
|
|
4942
|
-
text = await
|
|
5105
|
+
text = await readFile6(filePath, "utf-8");
|
|
4943
5106
|
} catch {
|
|
4944
5107
|
return null;
|
|
4945
5108
|
}
|
|
@@ -5080,7 +5243,7 @@ var init_pi_projects = __esm(() => {
|
|
|
5080
5243
|
});
|
|
5081
5244
|
|
|
5082
5245
|
// lib/sqlite-reader.ts
|
|
5083
|
-
import { existsSync as
|
|
5246
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
5084
5247
|
import initSqlJs from "sql.js/dist/sql-asm.js";
|
|
5085
5248
|
async function tryNodeSqlite(dbPath) {
|
|
5086
5249
|
try {
|
|
@@ -5131,7 +5294,7 @@ async function trySqlJs(dbPath) {
|
|
|
5131
5294
|
}
|
|
5132
5295
|
}
|
|
5133
5296
|
async function openSqliteReadonly(dbPath) {
|
|
5134
|
-
if (!
|
|
5297
|
+
if (!existsSync6(dbPath))
|
|
5135
5298
|
return null;
|
|
5136
5299
|
return await tryNodeSqlite(dbPath) ?? await trySqlJs(dbPath);
|
|
5137
5300
|
}
|
|
@@ -5521,7 +5684,7 @@ var init_hermes_projects = __esm(() => {
|
|
|
5521
5684
|
});
|
|
5522
5685
|
|
|
5523
5686
|
// lib/openclaw-sessions.ts
|
|
5524
|
-
import { readFile as
|
|
5687
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
5525
5688
|
import { readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
|
|
5526
5689
|
import { homedir as homedir13 } from "node:os";
|
|
5527
5690
|
import { join as join11 } from "node:path";
|
|
@@ -5693,7 +5856,7 @@ async function getOpenClawSessionLog(sessionId) {
|
|
|
5693
5856
|
return null;
|
|
5694
5857
|
let content;
|
|
5695
5858
|
try {
|
|
5696
|
-
content = await
|
|
5859
|
+
content = await readFile7(filePath, "utf-8");
|
|
5697
5860
|
} catch {
|
|
5698
5861
|
return null;
|
|
5699
5862
|
}
|
|
@@ -5890,7 +6053,7 @@ var init_openclaw_projects = __esm(() => {
|
|
|
5890
6053
|
});
|
|
5891
6054
|
|
|
5892
6055
|
// lib/factory-sessions.ts
|
|
5893
|
-
import { readFile as
|
|
6056
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
5894
6057
|
import { readdirSync as readdirSync5, statSync as statSync6 } from "node:fs";
|
|
5895
6058
|
import { homedir as homedir14 } from "node:os";
|
|
5896
6059
|
import { join as join13 } from "node:path";
|
|
@@ -6081,7 +6244,7 @@ async function getFactorySessionLog(sessionId) {
|
|
|
6081
6244
|
return null;
|
|
6082
6245
|
let content;
|
|
6083
6246
|
try {
|
|
6084
|
-
content = await
|
|
6247
|
+
content = await readFile8(filePath, "utf-8");
|
|
6085
6248
|
} catch {
|
|
6086
6249
|
return null;
|
|
6087
6250
|
}
|
|
@@ -6485,7 +6648,7 @@ var init_devin_projects = __esm(() => {
|
|
|
6485
6648
|
});
|
|
6486
6649
|
|
|
6487
6650
|
// lib/antigravity-sessions.ts
|
|
6488
|
-
import { readFile as
|
|
6651
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
6489
6652
|
import { readdirSync as readdirSync6, statSync as statSync7 } from "node:fs";
|
|
6490
6653
|
import { homedir as homedir16 } from "node:os";
|
|
6491
6654
|
import { join as join15 } from "node:path";
|
|
@@ -6645,7 +6808,7 @@ async function getAntigravitySessionLog(conversationId) {
|
|
|
6645
6808
|
return null;
|
|
6646
6809
|
let content;
|
|
6647
6810
|
try {
|
|
6648
|
-
content = await
|
|
6811
|
+
content = await readFile9(filePath, "utf-8");
|
|
6649
6812
|
} catch {
|
|
6650
6813
|
return null;
|
|
6651
6814
|
}
|
|
@@ -7321,7 +7484,7 @@ async function resolveSubagentPath(projectsPath, projectName, sessionId, agentId
|
|
|
7321
7484
|
var init_resolve_subagent_path = () => {};
|
|
7322
7485
|
|
|
7323
7486
|
// lib/log-entries.ts
|
|
7324
|
-
import { readFile as
|
|
7487
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
7325
7488
|
import { join as join20 } from "path";
|
|
7326
7489
|
function formatTimestamp(date) {
|
|
7327
7490
|
const base = formatDate(date);
|
|
@@ -7521,7 +7684,7 @@ async function parseSessionLog(projectName, sessionId) {
|
|
|
7521
7684
|
const projectDir = resolveProjectPath(projectName);
|
|
7522
7685
|
const projectsPath = getClaudeProjectsPath();
|
|
7523
7686
|
const filePath = join20(projectDir, `${sessionId}.jsonl`);
|
|
7524
|
-
const fileContent = await
|
|
7687
|
+
const fileContent = await readFile10(filePath, "utf-8");
|
|
7525
7688
|
const { entries: sessionEntries, rawLines: sessionRawLines, subagentIds } = await parseFileContent(fileContent, "session");
|
|
7526
7689
|
if (subagentIds.length === 0) {
|
|
7527
7690
|
return { entries: sessionEntries, rawLines: sessionRawLines, subagentIds: [] };
|
|
@@ -7531,7 +7694,7 @@ async function parseSessionLog(projectName, sessionId) {
|
|
|
7531
7694
|
const agentPath = await resolveSubagentPath(projectsPath, projectName, sessionId, agentId);
|
|
7532
7695
|
if (!agentPath)
|
|
7533
7696
|
return null;
|
|
7534
|
-
const agentContent = await
|
|
7697
|
+
const agentContent = await readFile10(agentPath, "utf-8");
|
|
7535
7698
|
const { entries, rawLines } = await parseFileContent(agentContent, agentSource);
|
|
7536
7699
|
return { entries, rawLines };
|
|
7537
7700
|
}), SUBAGENT_CONCURRENCY);
|
|
@@ -7557,13 +7720,13 @@ var init_log_entries = __esm(() => {
|
|
|
7557
7720
|
});
|
|
7558
7721
|
|
|
7559
7722
|
// lib/codex-sessions.ts
|
|
7560
|
-
import { readFileSync as readFileSync5, readdirSync as readdirSync7, existsSync as
|
|
7561
|
-
import { readFile as
|
|
7723
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync7, existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, statSync as statSync8 } from "node:fs";
|
|
7724
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
7562
7725
|
import { dirname as dirname4, join as join21 } from "node:path";
|
|
7563
7726
|
import { homedir as homedir18 } from "node:os";
|
|
7564
7727
|
function readCache() {
|
|
7565
7728
|
try {
|
|
7566
|
-
if (!
|
|
7729
|
+
if (!existsSync7(CACHE_PATH))
|
|
7567
7730
|
return {};
|
|
7568
7731
|
return JSON.parse(readFileSync5(CACHE_PATH, "utf-8"));
|
|
7569
7732
|
} catch {
|
|
@@ -7591,7 +7754,7 @@ function dirSearch(dir, sessionId) {
|
|
|
7591
7754
|
function findCodexTranscript(sessionId) {
|
|
7592
7755
|
const cache = readCache();
|
|
7593
7756
|
const cached = cache[sessionId];
|
|
7594
|
-
if (cached &&
|
|
7757
|
+
if (cached && existsSync7(cached))
|
|
7595
7758
|
return cached;
|
|
7596
7759
|
const root = join21(homedir18(), ".codex", "sessions");
|
|
7597
7760
|
const today = new Date;
|
|
@@ -7832,7 +7995,7 @@ async function getCodexSessionLog(sessionId) {
|
|
|
7832
7995
|
const filePath = findCodexTranscript(sessionId);
|
|
7833
7996
|
if (!filePath)
|
|
7834
7997
|
return null;
|
|
7835
|
-
const fileContent = await
|
|
7998
|
+
const fileContent = await readFile11(filePath, "utf-8");
|
|
7836
7999
|
const { entries, rawLines, cwd } = await parseCodexLog(fileContent, "session");
|
|
7837
8000
|
return { entries, rawLines, cwd, filePath };
|
|
7838
8001
|
}
|
|
@@ -7884,8 +8047,8 @@ var init_resolve_permission_mode = __esm(() => {
|
|
|
7884
8047
|
});
|
|
7885
8048
|
|
|
7886
8049
|
// lib/copilot-sessions.ts
|
|
7887
|
-
import { readFileSync as readFileSync7, readdirSync as readdirSync8, existsSync as
|
|
7888
|
-
import { readFile as
|
|
8050
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync8, existsSync as existsSync8, statSync as statSync9 } from "node:fs";
|
|
8051
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
7889
8052
|
import { join as join22, resolve as resolve8, sep as sep2 } from "node:path";
|
|
7890
8053
|
import { homedir as homedir19 } from "node:os";
|
|
7891
8054
|
function getCopilotHome() {
|
|
@@ -7913,13 +8076,13 @@ function findCopilotTranscript(sessionId) {
|
|
|
7913
8076
|
const candidate = resolveSessionFile(sessionId, "events.jsonl");
|
|
7914
8077
|
if (!candidate)
|
|
7915
8078
|
return null;
|
|
7916
|
-
return
|
|
8079
|
+
return existsSync8(candidate) ? candidate : null;
|
|
7917
8080
|
}
|
|
7918
8081
|
function findCopilotWorkspace(sessionId) {
|
|
7919
8082
|
const candidate = resolveSessionFile(sessionId, "workspace.yaml");
|
|
7920
8083
|
if (!candidate)
|
|
7921
8084
|
return null;
|
|
7922
|
-
return
|
|
8085
|
+
return existsSync8(candidate) ? candidate : null;
|
|
7923
8086
|
}
|
|
7924
8087
|
function readYamlScalar(path3, key) {
|
|
7925
8088
|
try {
|
|
@@ -8086,7 +8249,7 @@ async function getCopilotSessionLog(sessionId) {
|
|
|
8086
8249
|
return null;
|
|
8087
8250
|
let fileContent;
|
|
8088
8251
|
try {
|
|
8089
|
-
fileContent = await
|
|
8252
|
+
fileContent = await readFile12(filePath, "utf-8");
|
|
8090
8253
|
} catch {
|
|
8091
8254
|
return null;
|
|
8092
8255
|
}
|
|
@@ -8101,8 +8264,8 @@ var init_copilot_sessions = __esm(() => {
|
|
|
8101
8264
|
});
|
|
8102
8265
|
|
|
8103
8266
|
// lib/cursor-sessions.ts
|
|
8104
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync9, existsSync as
|
|
8105
|
-
import { readFile as
|
|
8267
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync9, existsSync as existsSync9, statSync as statSync10 } from "node:fs";
|
|
8268
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
8106
8269
|
import { basename as basename3, join as join23, resolve as resolve9, sep as sep3 } from "node:path";
|
|
8107
8270
|
import { homedir as homedir20 } from "node:os";
|
|
8108
8271
|
function getCursorHome2() {
|
|
@@ -8124,7 +8287,7 @@ function getCursorSessionDir(sessionId) {
|
|
|
8124
8287
|
const transcriptRoot = resolve9(projectsRoot, entry.name, NEW_AGENT_TRANSCRIPTS_DIR2);
|
|
8125
8288
|
if (candidate === transcriptRoot || !candidate.startsWith(`${transcriptRoot}${sep3}`))
|
|
8126
8289
|
continue;
|
|
8127
|
-
if (
|
|
8290
|
+
if (existsSync9(candidate))
|
|
8128
8291
|
return candidate;
|
|
8129
8292
|
}
|
|
8130
8293
|
for (const sub of LEGACY_SESSION_ROOT_CANDIDATES2) {
|
|
@@ -8132,7 +8295,7 @@ function getCursorSessionDir(sessionId) {
|
|
|
8132
8295
|
const candidate = resolve9(root, sessionId);
|
|
8133
8296
|
if (candidate === root || !candidate.startsWith(`${root}${sep3}`))
|
|
8134
8297
|
continue;
|
|
8135
|
-
if (
|
|
8298
|
+
if (existsSync9(candidate))
|
|
8136
8299
|
return candidate;
|
|
8137
8300
|
}
|
|
8138
8301
|
return null;
|
|
@@ -8142,11 +8305,11 @@ function findCursorTranscript(sessionId) {
|
|
|
8142
8305
|
if (!dir)
|
|
8143
8306
|
return null;
|
|
8144
8307
|
const newCandidate = join23(dir, `${basename3(dir)}.jsonl`);
|
|
8145
|
-
if (
|
|
8308
|
+
if (existsSync9(newCandidate))
|
|
8146
8309
|
return newCandidate;
|
|
8147
8310
|
for (const name of LEGACY_TRANSCRIPT_FILE_CANDIDATES2) {
|
|
8148
8311
|
const candidate = join23(dir, name);
|
|
8149
|
-
if (
|
|
8312
|
+
if (existsSync9(candidate))
|
|
8150
8313
|
return candidate;
|
|
8151
8314
|
}
|
|
8152
8315
|
return null;
|
|
@@ -8341,7 +8504,7 @@ async function getCursorSessionLog(sessionId) {
|
|
|
8341
8504
|
return null;
|
|
8342
8505
|
let fileContent;
|
|
8343
8506
|
try {
|
|
8344
|
-
fileContent = await
|
|
8507
|
+
fileContent = await readFile13(filePath, "utf-8");
|
|
8345
8508
|
} catch {
|
|
8346
8509
|
return null;
|
|
8347
8510
|
}
|
|
@@ -8357,8 +8520,8 @@ var init_cursor_sessions = __esm(() => {
|
|
|
8357
8520
|
});
|
|
8358
8521
|
|
|
8359
8522
|
// lib/pi-sessions.ts
|
|
8360
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync10, existsSync as
|
|
8361
|
-
import { readFile as
|
|
8523
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync10, existsSync as existsSync10, statSync as statSync11 } from "node:fs";
|
|
8524
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
8362
8525
|
import { join as join24, resolve as resolve10, sep as sep4 } from "node:path";
|
|
8363
8526
|
import { homedir as homedir21 } from "node:os";
|
|
8364
8527
|
function getPiSessionStateRoot() {
|
|
@@ -8394,7 +8557,7 @@ function findPiTranscript(sessionId) {
|
|
|
8394
8557
|
const candidate = resolve10(cwdPath, f);
|
|
8395
8558
|
if (!candidate.startsWith(`${cwdPath}${sep4}`))
|
|
8396
8559
|
continue;
|
|
8397
|
-
if (
|
|
8560
|
+
if (existsSync10(candidate))
|
|
8398
8561
|
return candidate;
|
|
8399
8562
|
}
|
|
8400
8563
|
}
|
|
@@ -8520,7 +8683,7 @@ async function getPiSessionLog(sessionId) {
|
|
|
8520
8683
|
return null;
|
|
8521
8684
|
let fileContent;
|
|
8522
8685
|
try {
|
|
8523
|
-
fileContent = await
|
|
8686
|
+
fileContent = await readFile14(filePath, "utf-8");
|
|
8524
8687
|
} catch {
|
|
8525
8688
|
return null;
|
|
8526
8689
|
}
|
|
@@ -8621,6 +8784,288 @@ async function readStdinPayload2(maxBytes = 1048576) {
|
|
|
8621
8784
|
return { payload, oversized, readError };
|
|
8622
8785
|
}
|
|
8623
8786
|
|
|
8787
|
+
// src/hooks/cloud-managed-policies.ts
|
|
8788
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8789
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
8790
|
+
import { homedir as homedir22 } from "node:os";
|
|
8791
|
+
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve11 } from "node:path";
|
|
8792
|
+
function cloudManagedPolicyRoot() {
|
|
8793
|
+
return process.env.FAILPROOFAI_CLOUD_POLICY_DIR ?? resolve11(homedir22(), ".failproofai", "policies", "cloud-managed");
|
|
8794
|
+
}
|
|
8795
|
+
function parseManifest(value) {
|
|
8796
|
+
if (!value || typeof value !== "object")
|
|
8797
|
+
throw new Error("active manifest is not an object");
|
|
8798
|
+
const manifest = value;
|
|
8799
|
+
if (manifest.schemaVersion !== ACTIVE_SCHEMA_VERSION) {
|
|
8800
|
+
throw new Error(`unsupported active manifest schema ${String(manifest.schemaVersion)}`);
|
|
8801
|
+
}
|
|
8802
|
+
if (!Number.isSafeInteger(manifest.generation) || (manifest.generation ?? -1) < 0) {
|
|
8803
|
+
throw new Error("active manifest generation is invalid");
|
|
8804
|
+
}
|
|
8805
|
+
if (!Array.isArray(manifest.policies))
|
|
8806
|
+
throw new Error("active manifest policies is not an array");
|
|
8807
|
+
return manifest;
|
|
8808
|
+
}
|
|
8809
|
+
function resolveManagedPath(root, candidate) {
|
|
8810
|
+
if (!candidate || isAbsolute2(candidate))
|
|
8811
|
+
throw new Error(`unsafe managed policy path ${JSON.stringify(candidate)}`);
|
|
8812
|
+
const absolute = resolve11(root, candidate);
|
|
8813
|
+
const lexicalRelative = relative3(root, absolute);
|
|
8814
|
+
if (lexicalRelative.startsWith("..") || isAbsolute2(lexicalRelative)) {
|
|
8815
|
+
throw new Error(`managed policy path escapes its root: ${JSON.stringify(candidate)}`);
|
|
8816
|
+
}
|
|
8817
|
+
const realRoot = realpathSync(root);
|
|
8818
|
+
const realFile = realpathSync(absolute);
|
|
8819
|
+
const physicalRelative = relative3(realRoot, realFile);
|
|
8820
|
+
if (physicalRelative.startsWith("..") || isAbsolute2(physicalRelative)) {
|
|
8821
|
+
throw new Error(`managed policy symlink escapes its root: ${JSON.stringify(candidate)}`);
|
|
8822
|
+
}
|
|
8823
|
+
return realFile;
|
|
8824
|
+
}
|
|
8825
|
+
function readActiveCloudManagedPolicies() {
|
|
8826
|
+
const root = cloudManagedPolicyRoot();
|
|
8827
|
+
const activePath = resolve11(root, "active.json");
|
|
8828
|
+
if (!existsSync11(activePath))
|
|
8829
|
+
return [];
|
|
8830
|
+
let raw;
|
|
8831
|
+
try {
|
|
8832
|
+
raw = JSON.parse(readFileSync10(activePath, "utf8"));
|
|
8833
|
+
} catch (err) {
|
|
8834
|
+
throw new Error(`failed to read cloud-managed active manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
8835
|
+
}
|
|
8836
|
+
const manifest = parseManifest(raw);
|
|
8837
|
+
const seen = new Set;
|
|
8838
|
+
return manifest.policies.map((policy) => {
|
|
8839
|
+
if (!POLICY_ID_RE.test(policy.id) || policy.id === "." || policy.id === "..") {
|
|
8840
|
+
throw new Error(`unsafe cloud-managed policy id ${JSON.stringify(policy.id)}`);
|
|
8841
|
+
}
|
|
8842
|
+
if (!Number.isSafeInteger(policy.revision) || policy.revision < 0) {
|
|
8843
|
+
throw new Error(`invalid revision for cloud-managed policy ${policy.id}`);
|
|
8844
|
+
}
|
|
8845
|
+
if (!SHA256_RE.test(policy.sha256)) {
|
|
8846
|
+
throw new Error(`invalid SHA-256 for cloud-managed policy ${policy.id}`);
|
|
8847
|
+
}
|
|
8848
|
+
if (seen.has(policy.id))
|
|
8849
|
+
throw new Error(`duplicate cloud-managed policy id ${policy.id}`);
|
|
8850
|
+
seen.add(policy.id);
|
|
8851
|
+
const path3 = resolveManagedPath(root, policy.path);
|
|
8852
|
+
const bytes = readFileSync10(path3);
|
|
8853
|
+
const actual = createHash2("sha256").update(bytes).digest("hex");
|
|
8854
|
+
if (actual !== policy.sha256) {
|
|
8855
|
+
throw new Error(`cloud-managed policy ${policy.id} failed integrity verification: expected ${policy.sha256}, got ${actual}`);
|
|
8856
|
+
}
|
|
8857
|
+
if (policy.effect !== undefined && policy.effect !== "enforce" && policy.effect !== "observe") {
|
|
8858
|
+
throw new Error(`unknown effect ${JSON.stringify(policy.effect)} for cloud-managed policy ${policy.id}`);
|
|
8859
|
+
}
|
|
8860
|
+
return {
|
|
8861
|
+
id: policy.id,
|
|
8862
|
+
revision: policy.revision,
|
|
8863
|
+
effect: policy.effect ?? "enforce",
|
|
8864
|
+
sha256: policy.sha256,
|
|
8865
|
+
path: path3,
|
|
8866
|
+
generation: manifest.generation
|
|
8867
|
+
};
|
|
8868
|
+
});
|
|
8869
|
+
}
|
|
8870
|
+
var ACTIVE_SCHEMA_VERSION = 1, SHA256_RE, POLICY_ID_RE;
|
|
8871
|
+
var init_cloud_managed_policies = __esm(() => {
|
|
8872
|
+
SHA256_RE = /^[a-f0-9]{64}$/;
|
|
8873
|
+
POLICY_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
8874
|
+
});
|
|
8875
|
+
|
|
8876
|
+
// lib/atomic-write.ts
|
|
8877
|
+
import {
|
|
8878
|
+
chmodSync,
|
|
8879
|
+
existsSync as existsSync12,
|
|
8880
|
+
mkdirSync as mkdirSync5,
|
|
8881
|
+
renameSync as renameSync3,
|
|
8882
|
+
rmSync,
|
|
8883
|
+
statSync as statSync12,
|
|
8884
|
+
writeFileSync as writeFileSync4
|
|
8885
|
+
} from "node:fs";
|
|
8886
|
+
import { dirname as dirname5 } from "node:path";
|
|
8887
|
+
import { randomBytes } from "node:crypto";
|
|
8888
|
+
function writeJsonAtomically(filePath, value, opts = {}) {
|
|
8889
|
+
const mode = opts.mode ?? 384;
|
|
8890
|
+
const dirMode = opts.dirMode ?? 448;
|
|
8891
|
+
const dir = dirname5(filePath);
|
|
8892
|
+
if (!existsSync12(dir))
|
|
8893
|
+
mkdirSync5(dir, { recursive: true, mode: dirMode });
|
|
8894
|
+
const tmp = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
8895
|
+
try {
|
|
8896
|
+
writeFileSync4(tmp, JSON.stringify(value, null, 2), { mode });
|
|
8897
|
+
try {
|
|
8898
|
+
if ((statSync12(tmp).mode & 63) !== 0)
|
|
8899
|
+
chmodSync(tmp, mode);
|
|
8900
|
+
} catch {}
|
|
8901
|
+
renameSync3(tmp, filePath);
|
|
8902
|
+
try {
|
|
8903
|
+
if ((statSync12(filePath).mode & 63) !== 0)
|
|
8904
|
+
chmodSync(filePath, mode);
|
|
8905
|
+
} catch {}
|
|
8906
|
+
} catch (err) {
|
|
8907
|
+
try {
|
|
8908
|
+
rmSync(tmp, { force: true });
|
|
8909
|
+
} catch {}
|
|
8910
|
+
throw err;
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
8913
|
+
var init_atomic_write = () => {};
|
|
8914
|
+
|
|
8915
|
+
// src/hooks/session-pause.ts
|
|
8916
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync11, readFileSync as readFileSync11, rmSync as rmSync2 } from "node:fs";
|
|
8917
|
+
import { homedir as homedir23 } from "node:os";
|
|
8918
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
8919
|
+
import { join as join25, resolve as resolve12 } from "node:path";
|
|
8920
|
+
function pauseStateDir() {
|
|
8921
|
+
const override = process.env.FAILPROOFAI_STATE_DIR;
|
|
8922
|
+
if (override)
|
|
8923
|
+
return resolve12(override, "sessions");
|
|
8924
|
+
return join25(homedir23(), ".failproofai", "state", "sessions");
|
|
8925
|
+
}
|
|
8926
|
+
function pauseFilePath(sessionId) {
|
|
8927
|
+
const digest = createHash3("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
8928
|
+
return join25(pauseStateDir(), `${digest}.json`);
|
|
8929
|
+
}
|
|
8930
|
+
function parsePauseDuration(input, ceilingMs = PAUSE_CEILING_MS) {
|
|
8931
|
+
if (input === undefined || input === "")
|
|
8932
|
+
return Math.min(PAUSE_DEFAULT_MS, ceilingMs);
|
|
8933
|
+
const match = /^(\d+)\s*(s|m|h)?$/i.exec(input.trim());
|
|
8934
|
+
if (!match) {
|
|
8935
|
+
throw new Error(`Invalid duration ${JSON.stringify(input)}. Use e.g. 30m, 2h, 90s (a bare number means minutes).`);
|
|
8936
|
+
}
|
|
8937
|
+
const value = Number(match[1]);
|
|
8938
|
+
const unit = (match[2] ?? "m").toLowerCase();
|
|
8939
|
+
const ms = unit === "s" ? value * 1000 : unit === "h" ? value * 3600000 : value * 60000;
|
|
8940
|
+
if (ms <= 0)
|
|
8941
|
+
throw new Error("Duration must be greater than zero.");
|
|
8942
|
+
if (ms > ceilingMs) {
|
|
8943
|
+
throw new Error(`Duration ${input} exceeds the maximum pause of ${formatDuration2(ceilingMs)}.`);
|
|
8944
|
+
}
|
|
8945
|
+
return ms;
|
|
8946
|
+
}
|
|
8947
|
+
function formatDuration2(ms) {
|
|
8948
|
+
if (ms < 60000)
|
|
8949
|
+
return `${Math.round(ms / 1000)}s`;
|
|
8950
|
+
const minutes = Math.round(ms / 60000);
|
|
8951
|
+
if (minutes < 60)
|
|
8952
|
+
return `${minutes}m`;
|
|
8953
|
+
const hours = Math.floor(minutes / 60);
|
|
8954
|
+
const rest = minutes % 60;
|
|
8955
|
+
return rest === 0 ? `${hours}h` : `${hours}h${rest}m`;
|
|
8956
|
+
}
|
|
8957
|
+
function parseStored(raw) {
|
|
8958
|
+
if (!raw || typeof raw !== "object")
|
|
8959
|
+
return null;
|
|
8960
|
+
const p = raw;
|
|
8961
|
+
if (p.schemaVersion !== SCHEMA_VERSION)
|
|
8962
|
+
return null;
|
|
8963
|
+
if (typeof p.sessionId !== "string" || p.sessionId.length === 0)
|
|
8964
|
+
return null;
|
|
8965
|
+
if (!Number.isFinite(p.pausedAt) || !Number.isFinite(p.expiresAt))
|
|
8966
|
+
return null;
|
|
8967
|
+
return {
|
|
8968
|
+
schemaVersion: SCHEMA_VERSION,
|
|
8969
|
+
sessionId: p.sessionId,
|
|
8970
|
+
pausedAt: p.pausedAt,
|
|
8971
|
+
expiresAt: p.expiresAt,
|
|
8972
|
+
setBy: typeof p.setBy === "string" ? p.setBy : "unknown",
|
|
8973
|
+
...typeof p.cwd === "string" ? { cwd: p.cwd } : {}
|
|
8974
|
+
};
|
|
8975
|
+
}
|
|
8976
|
+
function readActivePause(sessionId, now = Date.now()) {
|
|
8977
|
+
if (!sessionId)
|
|
8978
|
+
return null;
|
|
8979
|
+
try {
|
|
8980
|
+
const path3 = pauseFilePath(sessionId);
|
|
8981
|
+
if (!existsSync13(path3))
|
|
8982
|
+
return null;
|
|
8983
|
+
const stored = parseStored(JSON.parse(readFileSync11(path3, "utf8")));
|
|
8984
|
+
if (!stored)
|
|
8985
|
+
return null;
|
|
8986
|
+
if (stored.expiresAt <= now)
|
|
8987
|
+
return null;
|
|
8988
|
+
const { schemaVersion: _ignored, ...pause } = stored;
|
|
8989
|
+
return pause;
|
|
8990
|
+
} catch {
|
|
8991
|
+
return null;
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
function writePause(opts) {
|
|
8995
|
+
const now = opts.now ?? Date.now();
|
|
8996
|
+
const pause = {
|
|
8997
|
+
sessionId: opts.sessionId,
|
|
8998
|
+
pausedAt: now,
|
|
8999
|
+
expiresAt: now + opts.durationMs,
|
|
9000
|
+
setBy: opts.setBy ?? "cli",
|
|
9001
|
+
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
9002
|
+
};
|
|
9003
|
+
mkdirSync6(pauseStateDir(), { recursive: true, mode: 448 });
|
|
9004
|
+
writeJsonAtomically(pauseFilePath(opts.sessionId), { schemaVersion: SCHEMA_VERSION, ...pause });
|
|
9005
|
+
return pause;
|
|
9006
|
+
}
|
|
9007
|
+
function clearPause(sessionId) {
|
|
9008
|
+
const path3 = pauseFilePath(sessionId);
|
|
9009
|
+
if (!existsSync13(path3))
|
|
9010
|
+
return false;
|
|
9011
|
+
try {
|
|
9012
|
+
rmSync2(path3);
|
|
9013
|
+
return true;
|
|
9014
|
+
} catch {
|
|
9015
|
+
return false;
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
function listActivePauses(now = Date.now()) {
|
|
9019
|
+
const dir = pauseStateDir();
|
|
9020
|
+
if (!existsSync13(dir))
|
|
9021
|
+
return [];
|
|
9022
|
+
const out = [];
|
|
9023
|
+
let names;
|
|
9024
|
+
try {
|
|
9025
|
+
names = readdirSync11(dir);
|
|
9026
|
+
} catch {
|
|
9027
|
+
return [];
|
|
9028
|
+
}
|
|
9029
|
+
for (const name of names) {
|
|
9030
|
+
if (!name.endsWith(".json"))
|
|
9031
|
+
continue;
|
|
9032
|
+
try {
|
|
9033
|
+
const stored = parseStored(JSON.parse(readFileSync11(join25(dir, name), "utf8")));
|
|
9034
|
+
if (!stored || stored.expiresAt <= now)
|
|
9035
|
+
continue;
|
|
9036
|
+
const { schemaVersion: _ignored, ...pause } = stored;
|
|
9037
|
+
out.push(pause);
|
|
9038
|
+
} catch {}
|
|
9039
|
+
}
|
|
9040
|
+
return out.sort((a, b) => b.pausedAt - a.pausedAt);
|
|
9041
|
+
}
|
|
9042
|
+
function resolveSessionForCwd(cwd, maxAgeMs = 12 * 3600000, now = Date.now()) {
|
|
9043
|
+
let best = null;
|
|
9044
|
+
let entries;
|
|
9045
|
+
try {
|
|
9046
|
+
entries = getAllHookActivityEntries();
|
|
9047
|
+
} catch {
|
|
9048
|
+
return null;
|
|
9049
|
+
}
|
|
9050
|
+
for (const entry of entries) {
|
|
9051
|
+
if (!entry.sessionId || entry.cwd !== cwd)
|
|
9052
|
+
continue;
|
|
9053
|
+
if (now - entry.timestamp > maxAgeMs)
|
|
9054
|
+
continue;
|
|
9055
|
+
if (!best || entry.timestamp > best.timestamp) {
|
|
9056
|
+
best = { sessionId: entry.sessionId, timestamp: entry.timestamp };
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
9059
|
+
return best?.sessionId ?? null;
|
|
9060
|
+
}
|
|
9061
|
+
var SCHEMA_VERSION = 1, PAUSE_DEFAULT_MS, PAUSE_CEILING_MS;
|
|
9062
|
+
var init_session_pause = __esm(() => {
|
|
9063
|
+
init_atomic_write();
|
|
9064
|
+
init_hook_activity_store();
|
|
9065
|
+
PAUSE_DEFAULT_MS = 30 * 60000;
|
|
9066
|
+
PAUSE_CEILING_MS = 8 * 60 * 60000;
|
|
9067
|
+
});
|
|
9068
|
+
|
|
8624
9069
|
// src/hooks/handler.ts
|
|
8625
9070
|
var exports_handler = {};
|
|
8626
9071
|
__export(exports_handler, {
|
|
@@ -8661,6 +9106,24 @@ function canonicalizeEventType(raw, cli) {
|
|
|
8661
9106
|
}
|
|
8662
9107
|
return raw;
|
|
8663
9108
|
}
|
|
9109
|
+
async function runObserved(hook, ctx, hookName, eventType, cli) {
|
|
9110
|
+
try {
|
|
9111
|
+
return await Promise.race([
|
|
9112
|
+
hook.fn(ctx),
|
|
9113
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 1e4))
|
|
9114
|
+
]);
|
|
9115
|
+
} catch (err) {
|
|
9116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9117
|
+
trackHookEvent2(getInstanceId2(), "custom_hook_error", {
|
|
9118
|
+
hook_name: hookName,
|
|
9119
|
+
error_type: msg === "timeout" ? "timeout" : "exception",
|
|
9120
|
+
event_type: eventType,
|
|
9121
|
+
cli,
|
|
9122
|
+
is_observe_mode: true
|
|
9123
|
+
});
|
|
9124
|
+
return { decision: "allow" };
|
|
9125
|
+
}
|
|
9126
|
+
}
|
|
8664
9127
|
async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts) {
|
|
8665
9128
|
const startTime = performance.now();
|
|
8666
9129
|
try {
|
|
@@ -8702,6 +9165,10 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
8702
9165
|
let config;
|
|
8703
9166
|
let customHooksList = [];
|
|
8704
9167
|
let conventionHookNames = new Set;
|
|
9168
|
+
let activePause = null;
|
|
9169
|
+
const policyAttribution = new Map;
|
|
9170
|
+
let cloudGeneration;
|
|
9171
|
+
const observedResults = [];
|
|
8705
9172
|
if (opts?.forceDecision) {
|
|
8706
9173
|
config = { enabledPolicies: [] };
|
|
8707
9174
|
clearPolicies();
|
|
@@ -8710,23 +9177,49 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
8710
9177
|
} else {
|
|
8711
9178
|
config = readMergedHooksConfig(session.cwd);
|
|
8712
9179
|
clearPolicies();
|
|
8713
|
-
|
|
8714
|
-
|
|
9180
|
+
activePause = readActivePause(session.sessionId);
|
|
9181
|
+
registerBuiltinPolicies(activePause ? [] : config.enabledPolicies);
|
|
9182
|
+
const cloudManagedPolicies = readActiveCloudManagedPolicies();
|
|
9183
|
+
cloudGeneration = cloudManagedPolicies[0]?.generation;
|
|
9184
|
+
const configuredCustomPaths = config.customPoliciesPaths ?? config.customPoliciesPath;
|
|
9185
|
+
const allExplicitPaths = cloudManagedPolicies.length === 0 ? configuredCustomPaths : [
|
|
9186
|
+
...typeof configuredCustomPaths === "string" ? [configuredCustomPaths] : configuredCustomPaths ?? [],
|
|
9187
|
+
...cloudManagedPolicies.map((policy) => policy.path)
|
|
9188
|
+
];
|
|
9189
|
+
const loadResult = await loadAllCustomHooks(allExplicitPaths, {
|
|
8715
9190
|
sessionCwd: session.cwd,
|
|
8716
|
-
customPoliciesEnabled: config.customPoliciesEnabled
|
|
9191
|
+
customPoliciesEnabled: config.customPoliciesEnabled,
|
|
9192
|
+
...cloudManagedPolicies.length > 0 ? { cloudManagedPolicies } : {}
|
|
8717
9193
|
});
|
|
8718
9194
|
customHooksList = loadResult.hooks;
|
|
8719
9195
|
const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
|
|
8720
9196
|
conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
|
|
8721
9197
|
for (const hook of customHooksList) {
|
|
8722
|
-
const
|
|
8723
|
-
|
|
9198
|
+
const taggedHook = hook;
|
|
9199
|
+
const policyId = taggedHook.__policyId;
|
|
9200
|
+
const cloudManaged = taggedHook.__cloudManaged;
|
|
9201
|
+
if (!cloudManaged && policyId && disabledCustomPolicies.has(policyId))
|
|
9202
|
+
continue;
|
|
9203
|
+
if (!cloudManaged && activePause)
|
|
8724
9204
|
continue;
|
|
8725
9205
|
const hookName = hook.name;
|
|
8726
9206
|
const conventionScope = hook.__conventionScope;
|
|
8727
9207
|
const isConvention = !!conventionScope;
|
|
8728
|
-
const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
|
|
9208
|
+
const prefix = cloudManaged ? `cloud/${cloudManaged.id}@${cloudManaged.revision}` : isConvention ? `.failproofai-${conventionScope}` : "custom";
|
|
9209
|
+
const observeOnly = cloudManaged?.effect === "observe";
|
|
8729
9210
|
const fn = async (ctx) => {
|
|
9211
|
+
if (observeOnly) {
|
|
9212
|
+
const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
|
|
9213
|
+
if (shadow.decision !== "allow") {
|
|
9214
|
+
observedResults.push({
|
|
9215
|
+
policyId: cloudManaged.id,
|
|
9216
|
+
revision: cloudManaged.revision,
|
|
9217
|
+
decision: shadow.decision,
|
|
9218
|
+
reason: shadow.reason ?? null
|
|
9219
|
+
});
|
|
9220
|
+
}
|
|
9221
|
+
return { decision: "allow" };
|
|
9222
|
+
}
|
|
8730
9223
|
try {
|
|
8731
9224
|
const result2 = await Promise.race([
|
|
8732
9225
|
hook.fn(ctx),
|
|
@@ -8748,7 +9241,12 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
8748
9241
|
return { decision: "allow" };
|
|
8749
9242
|
}
|
|
8750
9243
|
};
|
|
8751
|
-
|
|
9244
|
+
const registeredName = `${prefix}/${hookName}`;
|
|
9245
|
+
policyAttribution.set(registeredName, {
|
|
9246
|
+
source: cloudManaged ? "cloud" : isConvention ? "convention" : "custom",
|
|
9247
|
+
...cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudRevision: cloudManaged.revision } : {}
|
|
9248
|
+
});
|
|
9249
|
+
registerPolicy(registeredName, hook.description ?? "", fn, hook.match ?? {}, -1);
|
|
8752
9250
|
}
|
|
8753
9251
|
if (customHooksList.length > 0) {
|
|
8754
9252
|
trackHookEvent2(getInstanceId2(), "custom_hooks_loaded", {
|
|
@@ -8789,7 +9287,18 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
8789
9287
|
transcriptPath: session.transcriptPath,
|
|
8790
9288
|
cwd: session.cwd,
|
|
8791
9289
|
permissionMode: session.permissionMode,
|
|
8792
|
-
hookEventName: session.hookEventName
|
|
9290
|
+
hookEventName: session.hookEventName,
|
|
9291
|
+
...result.policyName ? (() => {
|
|
9292
|
+
const attribution = policyAttribution.get(result.policyName);
|
|
9293
|
+
return {
|
|
9294
|
+
policySource: attribution?.source ?? "builtin",
|
|
9295
|
+
...attribution?.cloudPolicyId ? { cloudPolicyId: attribution.cloudPolicyId } : {},
|
|
9296
|
+
...attribution?.cloudRevision !== undefined ? { cloudRevision: attribution.cloudRevision } : {}
|
|
9297
|
+
};
|
|
9298
|
+
})() : {},
|
|
9299
|
+
...cloudGeneration !== undefined ? { cloudGeneration } : {},
|
|
9300
|
+
...observedResults.length > 0 ? { observed: observedResults } : {},
|
|
9301
|
+
...activePause ? { pausedBy: activePause.setBy, pauseExpiresAt: activePause.expiresAt } : {}
|
|
8793
9302
|
};
|
|
8794
9303
|
try {
|
|
8795
9304
|
persistHookActivity(activityEntry);
|
|
@@ -8869,22 +9378,24 @@ var init_handler = __esm(() => {
|
|
|
8869
9378
|
init_resolve_transcript_path();
|
|
8870
9379
|
init_telemetry_id2();
|
|
8871
9380
|
init_hook_logger();
|
|
9381
|
+
init_cloud_managed_policies();
|
|
9382
|
+
init_session_pause();
|
|
8872
9383
|
});
|
|
8873
9384
|
|
|
8874
9385
|
// scripts/install-telemetry.mjs
|
|
8875
9386
|
import { createHmac, randomUUID } from "node:crypto";
|
|
8876
9387
|
import { execSync as execSync4 } from "node:child_process";
|
|
8877
|
-
import { readFileSync as
|
|
8878
|
-
import { homedir as
|
|
8879
|
-
import { join as
|
|
9388
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "node:fs";
|
|
9389
|
+
import { homedir as homedir24, platform, arch, hostname, cpus } from "node:os";
|
|
9390
|
+
import { join as join26 } from "node:path";
|
|
8880
9391
|
function hashToId3(raw) {
|
|
8881
9392
|
return createHmac("sha256", NAMESPACE3).update(raw).digest("hex");
|
|
8882
9393
|
}
|
|
8883
9394
|
function getInstanceId3() {
|
|
8884
|
-
const idDir =
|
|
8885
|
-
const idFile =
|
|
9395
|
+
const idDir = join26(homedir24(), ".failproofai");
|
|
9396
|
+
const idFile = join26(idDir, "instance-id");
|
|
8886
9397
|
try {
|
|
8887
|
-
const existing =
|
|
9398
|
+
const existing = readFileSync12(idFile, "utf-8").trim();
|
|
8888
9399
|
if (existing)
|
|
8889
9400
|
return existing;
|
|
8890
9401
|
} catch {}
|
|
@@ -8893,7 +9404,7 @@ function getInstanceId3() {
|
|
|
8893
9404
|
if (p === "linux") {
|
|
8894
9405
|
for (const machineIdPath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
8895
9406
|
try {
|
|
8896
|
-
const id2 =
|
|
9407
|
+
const id2 = readFileSync12(machineIdPath, "utf-8").trim();
|
|
8897
9408
|
if (id2)
|
|
8898
9409
|
return hashToId3(id2);
|
|
8899
9410
|
} catch {}
|
|
@@ -8916,7 +9427,7 @@ function getInstanceId3() {
|
|
|
8916
9427
|
try {
|
|
8917
9428
|
const sysProps = [
|
|
8918
9429
|
hostname(),
|
|
8919
|
-
|
|
9430
|
+
homedir24(),
|
|
8920
9431
|
platform(),
|
|
8921
9432
|
arch(),
|
|
8922
9433
|
cpus()[0]?.model ?? ""
|
|
@@ -8925,8 +9436,8 @@ function getInstanceId3() {
|
|
|
8925
9436
|
} catch {}
|
|
8926
9437
|
const id = randomUUID();
|
|
8927
9438
|
try {
|
|
8928
|
-
|
|
8929
|
-
|
|
9439
|
+
mkdirSync7(idDir, { recursive: true });
|
|
9440
|
+
writeFileSync5(idFile, id, "utf-8");
|
|
8930
9441
|
} catch {}
|
|
8931
9442
|
return id;
|
|
8932
9443
|
}
|
|
@@ -8962,15 +9473,15 @@ __export(exports_install_check, {
|
|
|
8962
9473
|
compareSemver: () => compareSemver,
|
|
8963
9474
|
checkHooks: () => checkHooks
|
|
8964
9475
|
});
|
|
8965
|
-
import { existsSync as
|
|
8966
|
-
import { resolve as
|
|
8967
|
-
import { platform as platform2, arch as arch2, release, homedir as
|
|
9476
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
9477
|
+
import { resolve as resolve13 } from "node:path";
|
|
9478
|
+
import { platform as platform2, arch as arch2, release, homedir as homedir25, hostname as hostname2 } from "node:os";
|
|
8968
9479
|
import { createHmac as createHmac2 } from "node:crypto";
|
|
8969
9480
|
function hashToId4(raw) {
|
|
8970
9481
|
return createHmac2("sha256", NAMESPACE4).update(raw).digest("hex");
|
|
8971
9482
|
}
|
|
8972
9483
|
function lastVersionPath() {
|
|
8973
|
-
return
|
|
9484
|
+
return resolve13(homedir25(), ".failproofai", "last-version");
|
|
8974
9485
|
}
|
|
8975
9486
|
function compareSemver(a, b) {
|
|
8976
9487
|
const parse = (v) => {
|
|
@@ -9017,13 +9528,13 @@ function compareSemver(a, b) {
|
|
|
9017
9528
|
return 0;
|
|
9018
9529
|
}
|
|
9019
9530
|
function checkHooks() {
|
|
9020
|
-
const hooksConfigPath =
|
|
9021
|
-
if (!
|
|
9531
|
+
const hooksConfigPath = resolve13(homedir25(), ".failproofai", "policies-config.json");
|
|
9532
|
+
if (!existsSync14(hooksConfigPath)) {
|
|
9022
9533
|
return { configured: false, registered: false, policyCount: 0 };
|
|
9023
9534
|
}
|
|
9024
9535
|
let config;
|
|
9025
9536
|
try {
|
|
9026
|
-
config = JSON.parse(
|
|
9537
|
+
config = JSON.parse(readFileSync13(hooksConfigPath, "utf8"));
|
|
9027
9538
|
} catch {
|
|
9028
9539
|
return { configured: false, registered: false, policyCount: 0 };
|
|
9029
9540
|
}
|
|
@@ -9031,12 +9542,12 @@ function checkHooks() {
|
|
|
9031
9542
|
return { configured: false, registered: false, policyCount: 0 };
|
|
9032
9543
|
}
|
|
9033
9544
|
const policyCount = config.enabledPolicies.length;
|
|
9034
|
-
const settingsPath =
|
|
9035
|
-
if (!
|
|
9545
|
+
const settingsPath = resolve13(homedir25(), ".claude", "settings.json");
|
|
9546
|
+
if (!existsSync14(settingsPath))
|
|
9036
9547
|
return { configured: true, registered: false, policyCount };
|
|
9037
9548
|
let settings;
|
|
9038
9549
|
try {
|
|
9039
|
-
settings = JSON.parse(
|
|
9550
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
9040
9551
|
} catch {
|
|
9041
9552
|
return { configured: true, registered: false, policyCount };
|
|
9042
9553
|
}
|
|
@@ -9060,8 +9571,8 @@ async function maybeReportInstall(version3) {
|
|
|
9060
9571
|
const versionFile = lastVersionPath();
|
|
9061
9572
|
let previousVersion = null;
|
|
9062
9573
|
try {
|
|
9063
|
-
if (
|
|
9064
|
-
previousVersion =
|
|
9574
|
+
if (existsSync14(versionFile)) {
|
|
9575
|
+
previousVersion = readFileSync13(versionFile, "utf8").trim() || null;
|
|
9065
9576
|
}
|
|
9066
9577
|
} catch {}
|
|
9067
9578
|
if (previousVersion === version3)
|
|
@@ -9096,8 +9607,8 @@ async function maybeReportInstall(version3) {
|
|
|
9096
9607
|
enabled_policy_count: hooks.policyCount
|
|
9097
9608
|
}, { version: version3, timeoutMs: REPORT_TIMEOUT_MS }));
|
|
9098
9609
|
try {
|
|
9099
|
-
|
|
9100
|
-
|
|
9610
|
+
mkdirSync8(resolve13(homedir25(), ".failproofai"), { recursive: true });
|
|
9611
|
+
writeFileSync6(versionFile, version3, "utf8");
|
|
9101
9612
|
} catch {}
|
|
9102
9613
|
await Promise.all(events.map((p) => p.catch(() => {})));
|
|
9103
9614
|
} catch {}
|
|
@@ -16099,31 +16610,31 @@ var init_dist = __esm(() => {
|
|
|
16099
16610
|
|
|
16100
16611
|
// src/hooks/integrations.ts
|
|
16101
16612
|
import { execSync as execSync5 } from "node:child_process";
|
|
16102
|
-
import { readFileSync as
|
|
16103
|
-
import { resolve as
|
|
16613
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync15, mkdirSync as mkdirSync9, unlinkSync as unlinkSync2 } from "node:fs";
|
|
16614
|
+
import { resolve as resolve14, dirname as dirname6 } from "node:path";
|
|
16104
16615
|
import { fileURLToPath } from "node:url";
|
|
16105
|
-
import { homedir as
|
|
16616
|
+
import { homedir as homedir26 } from "node:os";
|
|
16106
16617
|
function readJsonFile(path3) {
|
|
16107
|
-
if (!
|
|
16618
|
+
if (!existsSync15(path3))
|
|
16108
16619
|
return {};
|
|
16109
|
-
const raw =
|
|
16620
|
+
const raw = readFileSync14(path3, "utf8");
|
|
16110
16621
|
return JSON.parse(raw);
|
|
16111
16622
|
}
|
|
16112
16623
|
function writeJsonFile(path3, data) {
|
|
16113
|
-
|
|
16114
|
-
|
|
16624
|
+
mkdirSync9(dirname6(path3), { recursive: true });
|
|
16625
|
+
writeFileSync7(path3, JSON.stringify(data, null, 2) + `
|
|
16115
16626
|
`, "utf8");
|
|
16116
16627
|
}
|
|
16117
16628
|
function readYamlDoc(path3) {
|
|
16118
16629
|
try {
|
|
16119
|
-
return
|
|
16630
|
+
return existsSync15(path3) ? $parseDocument(readFileSync14(path3, "utf8")) : $parseDocument("");
|
|
16120
16631
|
} catch {
|
|
16121
16632
|
return $parseDocument("");
|
|
16122
16633
|
}
|
|
16123
16634
|
}
|
|
16124
16635
|
function writeYamlDoc(path3, doc) {
|
|
16125
|
-
|
|
16126
|
-
|
|
16636
|
+
mkdirSync9(dirname6(path3), { recursive: true });
|
|
16637
|
+
writeFileSync7(path3, doc.toString(), "utf8");
|
|
16127
16638
|
}
|
|
16128
16639
|
function isMarkedHook(hook) {
|
|
16129
16640
|
if (!hook || typeof hook !== "object")
|
|
@@ -16166,7 +16677,7 @@ function isMarkedCopilotHook(hook) {
|
|
|
16166
16677
|
return false;
|
|
16167
16678
|
}
|
|
16168
16679
|
function opencodePluginFilePath(settingsPath) {
|
|
16169
|
-
return
|
|
16680
|
+
return resolve14(dirname6(settingsPath), "plugins", "failproofai.mjs");
|
|
16170
16681
|
}
|
|
16171
16682
|
function buildOpenCodePluginShim(binaryPath, scope) {
|
|
16172
16683
|
const useNpx = scope === "project";
|
|
@@ -16381,8 +16892,8 @@ export default async function failproofaiPlugin({ client, directory }) {
|
|
|
16381
16892
|
function getPiExtensionPath() {
|
|
16382
16893
|
const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
16383
16894
|
if (fromEnv)
|
|
16384
|
-
return
|
|
16385
|
-
return
|
|
16895
|
+
return resolve14(fromEnv, "pi-extension");
|
|
16896
|
+
return resolve14(fileURLToPath(import.meta.url), "..", "..", "..", "pi-extension");
|
|
16386
16897
|
}
|
|
16387
16898
|
function isFailproofaiPiEntry(source) {
|
|
16388
16899
|
if (typeof source !== "string")
|
|
@@ -16393,8 +16904,8 @@ function isFailproofaiPiEntry(source) {
|
|
|
16393
16904
|
}
|
|
16394
16905
|
function makePiProjectRelativeEntry(extPath) {
|
|
16395
16906
|
const cwd = process.cwd();
|
|
16396
|
-
const cwdResolved =
|
|
16397
|
-
const extResolved =
|
|
16907
|
+
const cwdResolved = resolve14(cwd);
|
|
16908
|
+
const extResolved = resolve14(extPath);
|
|
16398
16909
|
if (extResolved.startsWith(cwdResolved + "/") || extResolved === cwdResolved) {
|
|
16399
16910
|
const fromSettingsDir = "../" + extResolved.slice(cwdResolved.length + 1);
|
|
16400
16911
|
return fromSettingsDir;
|
|
@@ -16402,7 +16913,7 @@ function makePiProjectRelativeEntry(extPath) {
|
|
|
16402
16913
|
return extResolved;
|
|
16403
16914
|
}
|
|
16404
16915
|
function hermesConfigHasHooks(settingsPath) {
|
|
16405
|
-
if (!
|
|
16916
|
+
if (!existsSync15(settingsPath))
|
|
16406
16917
|
return false;
|
|
16407
16918
|
try {
|
|
16408
16919
|
const doc = readYamlDoc(settingsPath);
|
|
@@ -16418,13 +16929,13 @@ function hermesConfigHasHooks(settingsPath) {
|
|
|
16418
16929
|
return false;
|
|
16419
16930
|
}
|
|
16420
16931
|
function unhookedHermesProfiles() {
|
|
16421
|
-
return listHermesProfiles().filter((p) =>
|
|
16932
|
+
return listHermesProfiles().filter((p) => existsSync15(p.home)).filter((p) => !hermesConfigHasHooks(resolve14(p.home, "config.yaml"))).map((p) => p.name);
|
|
16422
16933
|
}
|
|
16423
16934
|
function getOpenClawPluginPath() {
|
|
16424
16935
|
const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
16425
16936
|
if (fromEnv)
|
|
16426
|
-
return
|
|
16427
|
-
return
|
|
16937
|
+
return resolve14(fromEnv, "openclaw-plugin");
|
|
16938
|
+
return resolve14(fileURLToPath(import.meta.url), "..", "..", "..", "openclaw-plugin");
|
|
16428
16939
|
}
|
|
16429
16940
|
function isFailproofaiOpenClawPath(p) {
|
|
16430
16941
|
if (typeof p !== "string")
|
|
@@ -16461,14 +16972,14 @@ var init_integrations = __esm(() => {
|
|
|
16461
16972
|
scopes: HOOK_SCOPES,
|
|
16462
16973
|
eventTypes: CLAUDE_INSTALL_EVENT_TYPES,
|
|
16463
16974
|
getSettingsPath(scope, cwd) {
|
|
16464
|
-
const base = cwd ?
|
|
16975
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
16465
16976
|
switch (scope) {
|
|
16466
16977
|
case "user":
|
|
16467
|
-
return
|
|
16978
|
+
return resolve14(homedir26(), ".claude", "settings.json");
|
|
16468
16979
|
case "project":
|
|
16469
|
-
return
|
|
16980
|
+
return resolve14(base, ".claude", "settings.json");
|
|
16470
16981
|
case "local":
|
|
16471
|
-
return
|
|
16982
|
+
return resolve14(base, ".claude", "settings.local.json");
|
|
16472
16983
|
}
|
|
16473
16984
|
},
|
|
16474
16985
|
readSettings(settingsPath) {
|
|
@@ -16564,7 +17075,7 @@ var init_integrations = __esm(() => {
|
|
|
16564
17075
|
},
|
|
16565
17076
|
hooksInstalledInSettings(scope, cwd) {
|
|
16566
17077
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
16567
|
-
if (!
|
|
17078
|
+
if (!existsSync15(settingsPath))
|
|
16568
17079
|
return false;
|
|
16569
17080
|
try {
|
|
16570
17081
|
const settings = this.readSettings(settingsPath);
|
|
@@ -16593,14 +17104,14 @@ var init_integrations = __esm(() => {
|
|
|
16593
17104
|
scopes: CODEX_HOOK_SCOPES,
|
|
16594
17105
|
eventTypes: CODEX_HOOK_EVENT_TYPES,
|
|
16595
17106
|
getSettingsPath(scope, cwd) {
|
|
16596
|
-
const base = cwd ?
|
|
17107
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
16597
17108
|
switch (scope) {
|
|
16598
17109
|
case "user":
|
|
16599
|
-
return
|
|
17110
|
+
return resolve14(homedir26(), ".codex", "hooks.json");
|
|
16600
17111
|
case "project":
|
|
16601
|
-
return
|
|
17112
|
+
return resolve14(base, ".codex", "hooks.json");
|
|
16602
17113
|
case "local":
|
|
16603
|
-
return
|
|
17114
|
+
return resolve14(base, ".codex", "hooks.json");
|
|
16604
17115
|
}
|
|
16605
17116
|
},
|
|
16606
17117
|
readSettings(settingsPath) {
|
|
@@ -16680,7 +17191,7 @@ var init_integrations = __esm(() => {
|
|
|
16680
17191
|
},
|
|
16681
17192
|
hooksInstalledInSettings(scope, cwd) {
|
|
16682
17193
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
16683
|
-
if (!
|
|
17194
|
+
if (!existsSync15(settingsPath))
|
|
16684
17195
|
return false;
|
|
16685
17196
|
try {
|
|
16686
17197
|
const settings = this.readSettings(settingsPath);
|
|
@@ -16709,14 +17220,14 @@ var init_integrations = __esm(() => {
|
|
|
16709
17220
|
scopes: COPILOT_HOOK_SCOPES,
|
|
16710
17221
|
eventTypes: COPILOT_HOOK_EVENT_TYPES,
|
|
16711
17222
|
getSettingsPath(scope, cwd) {
|
|
16712
|
-
const base = cwd ?
|
|
17223
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
16713
17224
|
switch (scope) {
|
|
16714
17225
|
case "user":
|
|
16715
|
-
return
|
|
17226
|
+
return resolve14(homedir26(), ".copilot", "hooks", "failproofai.json");
|
|
16716
17227
|
case "project":
|
|
16717
|
-
return
|
|
17228
|
+
return resolve14(base, ".github", "hooks", "failproofai.json");
|
|
16718
17229
|
case "local":
|
|
16719
|
-
return
|
|
17230
|
+
return resolve14(base, ".github", "hooks", "failproofai.json");
|
|
16720
17231
|
}
|
|
16721
17232
|
},
|
|
16722
17233
|
readSettings(settingsPath) {
|
|
@@ -16794,7 +17305,7 @@ var init_integrations = __esm(() => {
|
|
|
16794
17305
|
},
|
|
16795
17306
|
hooksInstalledInSettings(scope, cwd) {
|
|
16796
17307
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
16797
|
-
if (!
|
|
17308
|
+
if (!existsSync15(settingsPath))
|
|
16798
17309
|
return false;
|
|
16799
17310
|
try {
|
|
16800
17311
|
const settings = this.readSettings(settingsPath);
|
|
@@ -16823,14 +17334,14 @@ var init_integrations = __esm(() => {
|
|
|
16823
17334
|
scopes: CURSOR_HOOK_SCOPES,
|
|
16824
17335
|
eventTypes: CURSOR_HOOK_EVENT_TYPES,
|
|
16825
17336
|
getSettingsPath(scope, cwd) {
|
|
16826
|
-
const base = cwd ?
|
|
17337
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
16827
17338
|
switch (scope) {
|
|
16828
17339
|
case "user":
|
|
16829
|
-
return
|
|
17340
|
+
return resolve14(homedir26(), ".cursor", "hooks.json");
|
|
16830
17341
|
case "project":
|
|
16831
|
-
return
|
|
17342
|
+
return resolve14(base, ".cursor", "hooks.json");
|
|
16832
17343
|
case "local":
|
|
16833
|
-
return
|
|
17344
|
+
return resolve14(base, ".cursor", "hooks.json");
|
|
16834
17345
|
}
|
|
16835
17346
|
},
|
|
16836
17347
|
readSettings(settingsPath) {
|
|
@@ -16897,7 +17408,7 @@ var init_integrations = __esm(() => {
|
|
|
16897
17408
|
},
|
|
16898
17409
|
hooksInstalledInSettings(scope, cwd) {
|
|
16899
17410
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
16900
|
-
if (!
|
|
17411
|
+
if (!existsSync15(settingsPath))
|
|
16901
17412
|
return false;
|
|
16902
17413
|
try {
|
|
16903
17414
|
const settings = this.readSettings(settingsPath);
|
|
@@ -16922,14 +17433,14 @@ var init_integrations = __esm(() => {
|
|
|
16922
17433
|
scopes: OPENCODE_HOOK_SCOPES,
|
|
16923
17434
|
eventTypes: OPENCODE_HOOK_EVENT_TYPES,
|
|
16924
17435
|
getSettingsPath(scope, cwd) {
|
|
16925
|
-
const base = cwd ?
|
|
17436
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
16926
17437
|
switch (scope) {
|
|
16927
17438
|
case "user":
|
|
16928
|
-
return
|
|
17439
|
+
return resolve14(homedir26(), ".config", "opencode", "opencode.json");
|
|
16929
17440
|
case "project":
|
|
16930
|
-
return
|
|
17441
|
+
return resolve14(base, ".opencode", "opencode.json");
|
|
16931
17442
|
case "local":
|
|
16932
|
-
return
|
|
17443
|
+
return resolve14(base, ".opencode", "opencode.json");
|
|
16933
17444
|
}
|
|
16934
17445
|
},
|
|
16935
17446
|
readSettings(settingsPath) {
|
|
@@ -16940,7 +17451,7 @@ var init_integrations = __esm(() => {
|
|
|
16940
17451
|
},
|
|
16941
17452
|
buildHookEntry(_binaryPath, _eventType, scope) {
|
|
16942
17453
|
if (scope === "user") {
|
|
16943
|
-
const abs =
|
|
17454
|
+
const abs = resolve14(homedir26(), ".config", "opencode", "plugins", "failproofai.mjs");
|
|
16944
17455
|
return { spec: `file://${abs}`, [FAILPROOFAI_HOOK_MARKER]: true };
|
|
16945
17456
|
}
|
|
16946
17457
|
return { spec: OPENCODE_PLUGIN_REL_PATH, [FAILPROOFAI_HOOK_MARKER]: true };
|
|
@@ -16955,10 +17466,10 @@ var init_integrations = __esm(() => {
|
|
|
16955
17466
|
writeHookEntries(settings, binaryPath, scope) {
|
|
16956
17467
|
const s = settings;
|
|
16957
17468
|
const effectiveScope = scope ?? "project";
|
|
16958
|
-
const settingsPath = effectiveScope === "user" ?
|
|
17469
|
+
const settingsPath = effectiveScope === "user" ? resolve14(homedir26(), ".config", "opencode", "opencode.json") : resolve14(process.cwd(), ".opencode", "opencode.json");
|
|
16959
17470
|
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
16960
|
-
|
|
16961
|
-
|
|
17471
|
+
mkdirSync9(dirname6(pluginPath), { recursive: true });
|
|
17472
|
+
writeFileSync7(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope), "utf8");
|
|
16962
17473
|
if (!Array.isArray(s.plugin))
|
|
16963
17474
|
s.plugin = [];
|
|
16964
17475
|
const desired = effectiveScope === "user" ? `file://${pluginPath}` : OPENCODE_PLUGIN_REL_PATH;
|
|
@@ -16981,9 +17492,9 @@ var init_integrations = __esm(() => {
|
|
|
16981
17492
|
}
|
|
16982
17493
|
this.writeSettings(settingsPath, settings);
|
|
16983
17494
|
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
16984
|
-
if (
|
|
17495
|
+
if (existsSync15(pluginPath)) {
|
|
16985
17496
|
try {
|
|
16986
|
-
const content =
|
|
17497
|
+
const content = readFileSync14(pluginPath, "utf8");
|
|
16987
17498
|
if (content.includes(FAILPROOFAI_HOOK_MARKER)) {
|
|
16988
17499
|
unlinkSync2(pluginPath);
|
|
16989
17500
|
if (removed === 0)
|
|
@@ -16995,7 +17506,7 @@ var init_integrations = __esm(() => {
|
|
|
16995
17506
|
},
|
|
16996
17507
|
hooksInstalledInSettings(scope, cwd) {
|
|
16997
17508
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
16998
|
-
if (!
|
|
17509
|
+
if (!existsSync15(settingsPath))
|
|
16999
17510
|
return false;
|
|
17000
17511
|
try {
|
|
17001
17512
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17005,9 +17516,9 @@ var init_integrations = __esm(() => {
|
|
|
17005
17516
|
if (!hasEntry)
|
|
17006
17517
|
return false;
|
|
17007
17518
|
const pluginPath = opencodePluginFilePath(settingsPath);
|
|
17008
|
-
if (!
|
|
17519
|
+
if (!existsSync15(pluginPath))
|
|
17009
17520
|
return false;
|
|
17010
|
-
const content =
|
|
17521
|
+
const content = readFileSync14(pluginPath, "utf8");
|
|
17011
17522
|
return content.includes(FAILPROOFAI_HOOK_MARKER);
|
|
17012
17523
|
} catch {
|
|
17013
17524
|
return false;
|
|
@@ -17023,14 +17534,14 @@ var init_integrations = __esm(() => {
|
|
|
17023
17534
|
scopes: PI_HOOK_SCOPES,
|
|
17024
17535
|
eventTypes: PI_HOOK_EVENT_TYPES,
|
|
17025
17536
|
getSettingsPath(scope, cwd) {
|
|
17026
|
-
const base = cwd ?
|
|
17537
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
17027
17538
|
switch (scope) {
|
|
17028
17539
|
case "user":
|
|
17029
|
-
return
|
|
17540
|
+
return resolve14(homedir26(), ".pi", "agent", "settings.json");
|
|
17030
17541
|
case "project":
|
|
17031
|
-
return
|
|
17542
|
+
return resolve14(base, ".pi", "settings.json");
|
|
17032
17543
|
case "local":
|
|
17033
|
-
return
|
|
17544
|
+
return resolve14(base, ".pi", "settings.json");
|
|
17034
17545
|
}
|
|
17035
17546
|
},
|
|
17036
17547
|
readSettings(settingsPath) {
|
|
@@ -17072,7 +17583,7 @@ var init_integrations = __esm(() => {
|
|
|
17072
17583
|
}
|
|
17073
17584
|
},
|
|
17074
17585
|
removeHooksFromFile(settingsPath) {
|
|
17075
|
-
if (!
|
|
17586
|
+
if (!existsSync15(settingsPath))
|
|
17076
17587
|
return 0;
|
|
17077
17588
|
const settings = this.readSettings(settingsPath);
|
|
17078
17589
|
if (!Array.isArray(settings.packages))
|
|
@@ -17087,7 +17598,7 @@ var init_integrations = __esm(() => {
|
|
|
17087
17598
|
},
|
|
17088
17599
|
hooksInstalledInSettings(scope, cwd) {
|
|
17089
17600
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17090
|
-
if (!
|
|
17601
|
+
if (!existsSync15(settingsPath))
|
|
17091
17602
|
return false;
|
|
17092
17603
|
try {
|
|
17093
17604
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17108,10 +17619,10 @@ var init_integrations = __esm(() => {
|
|
|
17108
17619
|
scopes: HERMES_HOOK_SCOPES,
|
|
17109
17620
|
eventTypes: HERMES_HOOK_EVENT_TYPES,
|
|
17110
17621
|
getSettingsPath() {
|
|
17111
|
-
return
|
|
17622
|
+
return resolve14(hermesRoot(), "config.yaml");
|
|
17112
17623
|
},
|
|
17113
17624
|
getSettingsPaths() {
|
|
17114
|
-
return listHermesProfiles().map((p) =>
|
|
17625
|
+
return listHermesProfiles().map((p) => resolve14(p.home, "config.yaml"));
|
|
17115
17626
|
},
|
|
17116
17627
|
readSettings(settingsPath) {
|
|
17117
17628
|
return readYamlDoc(settingsPath);
|
|
@@ -17146,7 +17657,7 @@ var init_integrations = __esm(() => {
|
|
|
17146
17657
|
doc.set("hooks_auto_accept", true);
|
|
17147
17658
|
},
|
|
17148
17659
|
removeHooksFromFile(settingsPath) {
|
|
17149
|
-
if (!
|
|
17660
|
+
if (!existsSync15(settingsPath))
|
|
17150
17661
|
return 0;
|
|
17151
17662
|
const doc = readYamlDoc(settingsPath);
|
|
17152
17663
|
const js = doc.toJS() ?? {};
|
|
@@ -17191,7 +17702,7 @@ var init_integrations = __esm(() => {
|
|
|
17191
17702
|
scopes: OPENCLAW_HOOK_SCOPES,
|
|
17192
17703
|
eventTypes: OPENCLAW_HOOK_EVENT_TYPES,
|
|
17193
17704
|
getSettingsPath() {
|
|
17194
|
-
return
|
|
17705
|
+
return resolve14(homedir26(), ".openclaw", "openclaw.json");
|
|
17195
17706
|
},
|
|
17196
17707
|
readSettings(settingsPath) {
|
|
17197
17708
|
return readJsonFile(settingsPath);
|
|
@@ -17236,7 +17747,7 @@ var init_integrations = __esm(() => {
|
|
|
17236
17747
|
hooks.allowConversationAccess = true;
|
|
17237
17748
|
},
|
|
17238
17749
|
removeHooksFromFile(settingsPath) {
|
|
17239
|
-
if (!
|
|
17750
|
+
if (!existsSync15(settingsPath))
|
|
17240
17751
|
return 0;
|
|
17241
17752
|
const settings = this.readSettings(settingsPath);
|
|
17242
17753
|
const plugins = settings.plugins;
|
|
@@ -17265,7 +17776,7 @@ var init_integrations = __esm(() => {
|
|
|
17265
17776
|
},
|
|
17266
17777
|
hooksInstalledInSettings(scope, cwd) {
|
|
17267
17778
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17268
|
-
if (!
|
|
17779
|
+
if (!existsSync15(settingsPath))
|
|
17269
17780
|
return false;
|
|
17270
17781
|
try {
|
|
17271
17782
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17287,14 +17798,14 @@ var init_integrations = __esm(() => {
|
|
|
17287
17798
|
scopes: FACTORY_HOOK_SCOPES,
|
|
17288
17799
|
eventTypes: FACTORY_HOOK_EVENT_TYPES,
|
|
17289
17800
|
getSettingsPath(scope, cwd) {
|
|
17290
|
-
const base = cwd ?
|
|
17801
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
17291
17802
|
switch (scope) {
|
|
17292
17803
|
case "user":
|
|
17293
|
-
return
|
|
17804
|
+
return resolve14(homedir26(), ".factory", "hooks.json");
|
|
17294
17805
|
case "project":
|
|
17295
|
-
return
|
|
17806
|
+
return resolve14(base, ".factory", "hooks.json");
|
|
17296
17807
|
case "local":
|
|
17297
|
-
return
|
|
17808
|
+
return resolve14(base, ".factory", "hooks.json");
|
|
17298
17809
|
}
|
|
17299
17810
|
},
|
|
17300
17811
|
readSettings(settingsPath) {
|
|
@@ -17362,7 +17873,7 @@ var init_integrations = __esm(() => {
|
|
|
17362
17873
|
},
|
|
17363
17874
|
hooksInstalledInSettings(scope, cwd) {
|
|
17364
17875
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17365
|
-
if (!
|
|
17876
|
+
if (!existsSync15(settingsPath))
|
|
17366
17877
|
return false;
|
|
17367
17878
|
try {
|
|
17368
17879
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17389,14 +17900,14 @@ var init_integrations = __esm(() => {
|
|
|
17389
17900
|
scopes: DEVIN_HOOK_SCOPES,
|
|
17390
17901
|
eventTypes: DEVIN_HOOK_EVENT_TYPES,
|
|
17391
17902
|
getSettingsPath(scope, cwd) {
|
|
17392
|
-
const base = cwd ?
|
|
17903
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
17393
17904
|
switch (scope) {
|
|
17394
17905
|
case "user":
|
|
17395
|
-
return
|
|
17906
|
+
return resolve14(homedir26(), ".config", "devin", "config.json");
|
|
17396
17907
|
case "project":
|
|
17397
|
-
return
|
|
17908
|
+
return resolve14(base, ".devin", "config.json");
|
|
17398
17909
|
case "local":
|
|
17399
|
-
return
|
|
17910
|
+
return resolve14(base, ".devin", "config.json");
|
|
17400
17911
|
}
|
|
17401
17912
|
},
|
|
17402
17913
|
readSettings(settingsPath) {
|
|
@@ -17468,7 +17979,7 @@ var init_integrations = __esm(() => {
|
|
|
17468
17979
|
},
|
|
17469
17980
|
hooksInstalledInSettings(scope, cwd) {
|
|
17470
17981
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17471
|
-
if (!
|
|
17982
|
+
if (!existsSync15(settingsPath))
|
|
17472
17983
|
return false;
|
|
17473
17984
|
try {
|
|
17474
17985
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17498,14 +18009,14 @@ var init_integrations = __esm(() => {
|
|
|
17498
18009
|
scopes: ANTIGRAVITY_HOOK_SCOPES,
|
|
17499
18010
|
eventTypes: ANTIGRAVITY_HOOK_EVENT_TYPES,
|
|
17500
18011
|
getSettingsPath(scope, cwd) {
|
|
17501
|
-
const base = cwd ?
|
|
18012
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
17502
18013
|
switch (scope) {
|
|
17503
18014
|
case "user":
|
|
17504
|
-
return
|
|
18015
|
+
return resolve14(homedir26(), ".gemini", "config", "hooks.json");
|
|
17505
18016
|
case "project":
|
|
17506
|
-
return
|
|
18017
|
+
return resolve14(base, ".agents", "hooks.json");
|
|
17507
18018
|
case "local":
|
|
17508
|
-
return
|
|
18019
|
+
return resolve14(base, ".agents", "hooks.json");
|
|
17509
18020
|
}
|
|
17510
18021
|
},
|
|
17511
18022
|
readSettings(settingsPath) {
|
|
@@ -17607,7 +18118,7 @@ var init_integrations = __esm(() => {
|
|
|
17607
18118
|
},
|
|
17608
18119
|
hooksInstalledInSettings(scope, cwd) {
|
|
17609
18120
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17610
|
-
if (!
|
|
18121
|
+
if (!existsSync15(settingsPath))
|
|
17611
18122
|
return false;
|
|
17612
18123
|
try {
|
|
17613
18124
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17639,14 +18150,14 @@ var init_integrations = __esm(() => {
|
|
|
17639
18150
|
scopes: GOOSE_HOOK_SCOPES,
|
|
17640
18151
|
eventTypes: GOOSE_HOOK_EVENT_TYPES,
|
|
17641
18152
|
getSettingsPath(scope, cwd) {
|
|
17642
|
-
const base = cwd ?
|
|
18153
|
+
const base = cwd ? resolve14(cwd) : process.cwd();
|
|
17643
18154
|
switch (scope) {
|
|
17644
18155
|
case "user":
|
|
17645
|
-
return
|
|
18156
|
+
return resolve14(homedir26(), ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
17646
18157
|
case "project":
|
|
17647
|
-
return
|
|
18158
|
+
return resolve14(base, ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
17648
18159
|
case "local":
|
|
17649
|
-
return
|
|
18160
|
+
return resolve14(base, ".agents", "plugins", "failproofai", "hooks", "hooks.json");
|
|
17650
18161
|
}
|
|
17651
18162
|
},
|
|
17652
18163
|
readSettings(settingsPath) {
|
|
@@ -17713,7 +18224,7 @@ var init_integrations = __esm(() => {
|
|
|
17713
18224
|
},
|
|
17714
18225
|
hooksInstalledInSettings(scope, cwd) {
|
|
17715
18226
|
const settingsPath = this.getSettingsPath(scope, cwd);
|
|
17716
|
-
if (!
|
|
18227
|
+
if (!existsSync15(settingsPath))
|
|
17717
18228
|
return false;
|
|
17718
18229
|
try {
|
|
17719
18230
|
const settings = this.readSettings(settingsPath);
|
|
@@ -17981,7 +18492,7 @@ function runPrompt(p) {
|
|
|
17981
18492
|
`${c.dim(BAR)} ${c.dim(p.summaryFor(result))}`
|
|
17982
18493
|
]);
|
|
17983
18494
|
};
|
|
17984
|
-
return new Promise((
|
|
18495
|
+
return new Promise((resolve15) => {
|
|
17985
18496
|
hideCursor(stdout);
|
|
17986
18497
|
repaint(stdout, region, build());
|
|
17987
18498
|
readline.emitKeypressEvents(stdin);
|
|
@@ -17997,7 +18508,7 @@ function runPrompt(p) {
|
|
|
17997
18508
|
const finish = (result) => {
|
|
17998
18509
|
cleanup();
|
|
17999
18510
|
collapse(result);
|
|
18000
|
-
|
|
18511
|
+
resolve15(result);
|
|
18001
18512
|
};
|
|
18002
18513
|
function onKey(_s, key) {
|
|
18003
18514
|
if (!key)
|
|
@@ -18111,6 +18622,89 @@ function multiSelect(opts) {
|
|
|
18111
18622
|
summaryFor: (values) => values === null ? "cancelled" : summarize(choices.filter((ch) => values.includes(ch.value) && !ch.summaryExclude).map((ch) => ch.label), noun)
|
|
18112
18623
|
});
|
|
18113
18624
|
}
|
|
18625
|
+
function promptText(opts) {
|
|
18626
|
+
const stdin = opts.stdin ?? process.stdin;
|
|
18627
|
+
const stdout = opts.stdout ?? process.stdout;
|
|
18628
|
+
const c = paint(colorsEnabled(stdout));
|
|
18629
|
+
const accept = (raw) => {
|
|
18630
|
+
const value = raw.trim() || opts.defaultValue || "";
|
|
18631
|
+
const error = opts.validate?.(value) ?? null;
|
|
18632
|
+
return error ? { ok: false, error } : { ok: true, value };
|
|
18633
|
+
};
|
|
18634
|
+
if (!stdin.isTTY) {
|
|
18635
|
+
return new Promise((resolve15) => {
|
|
18636
|
+
let buf = "";
|
|
18637
|
+
const onData = (chunk) => {
|
|
18638
|
+
buf += String(chunk);
|
|
18639
|
+
if (buf.includes(`
|
|
18640
|
+
`))
|
|
18641
|
+
done();
|
|
18642
|
+
};
|
|
18643
|
+
const done = () => {
|
|
18644
|
+
stdin.removeListener("data", onData);
|
|
18645
|
+
stdin.removeListener("end", done);
|
|
18646
|
+
const r = accept(buf.split(`
|
|
18647
|
+
`)[0] ?? "");
|
|
18648
|
+
resolve15(r.ok ? r.value : null);
|
|
18649
|
+
};
|
|
18650
|
+
stdin.on("data", onData);
|
|
18651
|
+
stdin.on("end", done);
|
|
18652
|
+
});
|
|
18653
|
+
}
|
|
18654
|
+
return new Promise((resolve15) => {
|
|
18655
|
+
let value = "";
|
|
18656
|
+
const draw = (error) => {
|
|
18657
|
+
const shown = opts.mask ? "•".repeat(value.length) : value;
|
|
18658
|
+
const hint = opts.hint ? ` ${c.dim(opts.hint)}` : "";
|
|
18659
|
+
const err = error ? `
|
|
18660
|
+
${c.warn(error)}` : "";
|
|
18661
|
+
stdout.write(`\r\x1B[2K${c.bold(opts.message)} ${shown}${hint}${err}`);
|
|
18662
|
+
if (err)
|
|
18663
|
+
stdout.write("\x1B[1A");
|
|
18664
|
+
};
|
|
18665
|
+
draw();
|
|
18666
|
+
readline.emitKeypressEvents(stdin);
|
|
18667
|
+
const wasRaw = stdin.isRaw;
|
|
18668
|
+
stdin.setRawMode?.(true);
|
|
18669
|
+
stdin.resume();
|
|
18670
|
+
const cleanup = () => {
|
|
18671
|
+
stdin.removeListener("keypress", onKey);
|
|
18672
|
+
stdin.setRawMode?.(wasRaw ?? false);
|
|
18673
|
+
stdin.pause();
|
|
18674
|
+
stdout.write(`
|
|
18675
|
+
`);
|
|
18676
|
+
};
|
|
18677
|
+
function onKey(str2, key) {
|
|
18678
|
+
if (!key)
|
|
18679
|
+
return;
|
|
18680
|
+
if (key.ctrl && (key.name === "c" || key.name === "d") || key.name === "escape") {
|
|
18681
|
+
cleanup();
|
|
18682
|
+
resolve15(null);
|
|
18683
|
+
return;
|
|
18684
|
+
}
|
|
18685
|
+
if (key.name === "return" || key.name === "enter") {
|
|
18686
|
+
const r = accept(value);
|
|
18687
|
+
if (r.ok) {
|
|
18688
|
+
cleanup();
|
|
18689
|
+
resolve15(r.value);
|
|
18690
|
+
} else {
|
|
18691
|
+
draw(r.error);
|
|
18692
|
+
}
|
|
18693
|
+
return;
|
|
18694
|
+
}
|
|
18695
|
+
if (key.name === "backspace") {
|
|
18696
|
+
value = value.slice(0, -1);
|
|
18697
|
+
draw();
|
|
18698
|
+
return;
|
|
18699
|
+
}
|
|
18700
|
+
if (str2 && !key.ctrl && !key.meta) {
|
|
18701
|
+
value += str2;
|
|
18702
|
+
draw();
|
|
18703
|
+
}
|
|
18704
|
+
}
|
|
18705
|
+
stdin.on("keypress", onKey);
|
|
18706
|
+
});
|
|
18707
|
+
}
|
|
18114
18708
|
var ESC = "\x1B", BAR = "│", BAR_END = "└", STEP_ACTIVE = "◆", STEP_DONE = "◇", RADIO_ON = "●", RADIO_OFF = "○", CHECK_ON = "◼", CHECK_OFF = "◻", CARET = "❯", FLOWER = "❋", HUES, ANSI_RESET, ANSI_BOLD, ANSI_DIM, LOGO_MIN_COLS = 22, TAGLINE = "end-to-end failure layer for ai agents", LOGO_GRID, LOGO_TEAL, LOGO_PINK, WINDOW = 8;
|
|
18115
18709
|
var init_tui = __esm(() => {
|
|
18116
18710
|
HUES = {
|
|
@@ -18298,7 +18892,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
18298
18892
|
`);
|
|
18299
18893
|
lastLineCount = lines.length;
|
|
18300
18894
|
}
|
|
18301
|
-
return new Promise((
|
|
18895
|
+
return new Promise((resolve15) => {
|
|
18302
18896
|
render();
|
|
18303
18897
|
process.stdin.setRawMode(true);
|
|
18304
18898
|
process.stdin.resume();
|
|
@@ -18335,7 +18929,7 @@ async function promptPolicySelection(preSelected, options = {}) {
|
|
|
18335
18929
|
${dim(STEP_DONE)} Policies
|
|
18336
18930
|
${dim(BAR)} ${dim(`${selected.length} selected`)}
|
|
18337
18931
|
`);
|
|
18338
|
-
|
|
18932
|
+
resolve15(selected);
|
|
18339
18933
|
} else if (key.name === "escape") {
|
|
18340
18934
|
search = "";
|
|
18341
18935
|
cursor2 = 0;
|
|
@@ -18397,9 +18991,9 @@ __export(exports_manager, {
|
|
|
18397
18991
|
getSettingsPath: () => getSettingsPath
|
|
18398
18992
|
});
|
|
18399
18993
|
import { execSync as execSync6 } from "node:child_process";
|
|
18400
|
-
import { existsSync as
|
|
18401
|
-
import { resolve as
|
|
18402
|
-
import { homedir as
|
|
18994
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
18995
|
+
import { resolve as resolve15, basename as basename4 } from "node:path";
|
|
18996
|
+
import { homedir as homedir27, platform as platform3, arch as arch3, release as release2, hostname as hostname3 } from "node:os";
|
|
18403
18997
|
function getSettingsPath(scope, cwd) {
|
|
18404
18998
|
return claudeCode.getSettingsPath(scope, cwd);
|
|
18405
18999
|
}
|
|
@@ -18506,9 +19100,9 @@ async function installHooksImpl(policyNames, scope = "user", cwd, includeBeta =
|
|
|
18506
19100
|
delete configToWrite.customPoliciesPath;
|
|
18507
19101
|
delete configToWrite.customPoliciesPaths;
|
|
18508
19102
|
} else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
|
|
18509
|
-
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) =>
|
|
18510
|
-
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) =>
|
|
18511
|
-
if (
|
|
19103
|
+
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve15(path3));
|
|
19104
|
+
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve15(path3)).filter((path3) => {
|
|
19105
|
+
if (existsSync16(path3))
|
|
18512
19106
|
return true;
|
|
18513
19107
|
console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
|
|
18514
19108
|
return false;
|
|
@@ -18700,7 +19294,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
18700
19294
|
const scopesToRemove = scope === "all" ? [...integration.scopes] : integration.scopes.includes(scope) ? [scope] : [];
|
|
18701
19295
|
for (const s of scopesToRemove) {
|
|
18702
19296
|
const settingsPaths = settingsPathsFor(integration, s, cwd);
|
|
18703
|
-
const existing = settingsPaths.filter((p) =>
|
|
19297
|
+
const existing = settingsPaths.filter((p) => existsSync16(p));
|
|
18704
19298
|
if (existing.length === 0) {
|
|
18705
19299
|
if (scope !== "all" && selectedClis.length === 1) {
|
|
18706
19300
|
console.log("No settings file found. Nothing to remove.");
|
|
@@ -18899,9 +19493,9 @@ Failproof AI Hook Policies
|
|
|
18899
19493
|
console.log(`
|
|
18900
19494
|
── Custom Policies ───────────────────────`);
|
|
18901
19495
|
for (const path3 of explicitPaths) {
|
|
18902
|
-
const absPath =
|
|
19496
|
+
const absPath = resolve15(findProjectConfigDir(cwd ?? process.cwd()), path3);
|
|
18903
19497
|
console.log(` ${absPath}`);
|
|
18904
|
-
if (!
|
|
19498
|
+
if (!existsSync16(absPath)) {
|
|
18905
19499
|
console.log(` \x1B[31m✗ File not found: ${absPath}\x1B[0m`);
|
|
18906
19500
|
continue;
|
|
18907
19501
|
}
|
|
@@ -18920,8 +19514,8 @@ Failproof AI Hook Policies
|
|
|
18920
19514
|
console.log();
|
|
18921
19515
|
}
|
|
18922
19516
|
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
18923
|
-
const projectDir =
|
|
18924
|
-
const userDir =
|
|
19517
|
+
const projectDir = resolve15(base, ".failproofai", "policies");
|
|
19518
|
+
const userDir = resolve15(homedir27(), ".failproofai", "policies");
|
|
18925
19519
|
const sameDir = userDir === projectDir;
|
|
18926
19520
|
const conventionDirs = [
|
|
18927
19521
|
{ label: sameDir ? "Project + User" : "Project", dir: projectDir },
|
|
@@ -19151,7 +19745,7 @@ async function promptCliTargetSelection(detected, action = "install") {
|
|
|
19151
19745
|
lastLineCount = lines.length;
|
|
19152
19746
|
}
|
|
19153
19747
|
const itemCount = options.length;
|
|
19154
|
-
return new Promise((
|
|
19748
|
+
return new Promise((resolve16) => {
|
|
19155
19749
|
render();
|
|
19156
19750
|
readline3.emitKeypressEvents(process.stdin);
|
|
19157
19751
|
const wasRaw = process.stdin.isRaw;
|
|
@@ -19184,7 +19778,7 @@ async function promptCliTargetSelection(detected, action = "install") {
|
|
|
19184
19778
|
cleanup();
|
|
19185
19779
|
process.stdout.write(`
|
|
19186
19780
|
`);
|
|
19187
|
-
|
|
19781
|
+
resolve16(options[cursor2].value);
|
|
19188
19782
|
}
|
|
19189
19783
|
}
|
|
19190
19784
|
process.stdin.on("keypress", onKey);
|
|
@@ -19337,7 +19931,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
19337
19931
|
`);
|
|
19338
19932
|
lastLineCount = lines.length;
|
|
19339
19933
|
}
|
|
19340
|
-
return new Promise((
|
|
19934
|
+
return new Promise((resolve16) => {
|
|
19341
19935
|
render();
|
|
19342
19936
|
process.stdin.setRawMode(true);
|
|
19343
19937
|
process.stdin.resume();
|
|
@@ -19374,7 +19968,7 @@ async function promptPolicySelection2(preSelected, options = {}) {
|
|
|
19374
19968
|
${dim(STEP_DONE)} Policies
|
|
19375
19969
|
${dim(BAR)} ${dim(`${selected.length} selected`)}
|
|
19376
19970
|
`);
|
|
19377
|
-
|
|
19971
|
+
resolve16(selected);
|
|
19378
19972
|
} else if (key.name === "escape") {
|
|
19379
19973
|
search = "";
|
|
19380
19974
|
cursor2 = 0;
|
|
@@ -19435,12 +20029,18 @@ var init_telemetry = __esm(() => {
|
|
|
19435
20029
|
});
|
|
19436
20030
|
|
|
19437
20031
|
// lib/fetch-with-timeout.ts
|
|
20032
|
+
function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
20033
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
20034
|
+
const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
|
|
20035
|
+
return fetch(input, { ...init, signal });
|
|
20036
|
+
}
|
|
19438
20037
|
function isAbortError(err) {
|
|
19439
20038
|
if (err === null || typeof err !== "object")
|
|
19440
20039
|
return false;
|
|
19441
20040
|
const name = err.name;
|
|
19442
20041
|
return name === "AbortError" || name === "TimeoutError";
|
|
19443
20042
|
}
|
|
20043
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 15000;
|
|
19444
20044
|
|
|
19445
20045
|
// lib/auth/api-server-client.ts
|
|
19446
20046
|
function getApiBase() {
|
|
@@ -19494,7 +20094,7 @@ function pathFromUrl(url) {
|
|
|
19494
20094
|
return url;
|
|
19495
20095
|
}
|
|
19496
20096
|
}
|
|
19497
|
-
async function
|
|
20097
|
+
async function fetchWithTimeout2(url, init) {
|
|
19498
20098
|
try {
|
|
19499
20099
|
return await fetch(url, { ...init, signal: timeoutSignal(init.signal ?? undefined) });
|
|
19500
20100
|
} catch (err) {
|
|
@@ -19514,7 +20114,7 @@ async function postJson(path3, body, init) {
|
|
|
19514
20114
|
const headers = { "content-type": "application/json" };
|
|
19515
20115
|
if (init?.accessToken)
|
|
19516
20116
|
headers["authorization"] = `Bearer ${init.accessToken}`;
|
|
19517
|
-
const res = await
|
|
20117
|
+
const res = await fetchWithTimeout2(`${getApiBase()}${path3}`, {
|
|
19518
20118
|
method: "POST",
|
|
19519
20119
|
headers,
|
|
19520
20120
|
body: JSON.stringify(body)
|
|
@@ -19551,64 +20151,25 @@ var init_api_server_client = __esm(() => {
|
|
|
19551
20151
|
};
|
|
19552
20152
|
});
|
|
19553
20153
|
|
|
19554
|
-
// lib/atomic-write.ts
|
|
19555
|
-
import {
|
|
19556
|
-
chmodSync,
|
|
19557
|
-
existsSync as existsSync13,
|
|
19558
|
-
mkdirSync as mkdirSync8,
|
|
19559
|
-
renameSync as renameSync3,
|
|
19560
|
-
rmSync,
|
|
19561
|
-
statSync as statSync12,
|
|
19562
|
-
writeFileSync as writeFileSync7
|
|
19563
|
-
} from "node:fs";
|
|
19564
|
-
import { dirname as dirname6 } from "node:path";
|
|
19565
|
-
import { randomBytes } from "node:crypto";
|
|
19566
|
-
function writeJsonAtomically(filePath, value, opts = {}) {
|
|
19567
|
-
const mode = opts.mode ?? 384;
|
|
19568
|
-
const dirMode = opts.dirMode ?? 448;
|
|
19569
|
-
const dir = dirname6(filePath);
|
|
19570
|
-
if (!existsSync13(dir))
|
|
19571
|
-
mkdirSync8(dir, { recursive: true, mode: dirMode });
|
|
19572
|
-
const tmp = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
19573
|
-
try {
|
|
19574
|
-
writeFileSync7(tmp, JSON.stringify(value, null, 2), { mode });
|
|
19575
|
-
try {
|
|
19576
|
-
if ((statSync12(tmp).mode & 63) !== 0)
|
|
19577
|
-
chmodSync(tmp, mode);
|
|
19578
|
-
} catch {}
|
|
19579
|
-
renameSync3(tmp, filePath);
|
|
19580
|
-
try {
|
|
19581
|
-
if ((statSync12(filePath).mode & 63) !== 0)
|
|
19582
|
-
chmodSync(filePath, mode);
|
|
19583
|
-
} catch {}
|
|
19584
|
-
} catch (err) {
|
|
19585
|
-
try {
|
|
19586
|
-
rmSync(tmp, { force: true });
|
|
19587
|
-
} catch {}
|
|
19588
|
-
throw err;
|
|
19589
|
-
}
|
|
19590
|
-
}
|
|
19591
|
-
var init_atomic_write = () => {};
|
|
19592
|
-
|
|
19593
20154
|
// lib/auth/auth-store.ts
|
|
19594
|
-
import { existsSync as
|
|
19595
|
-
import { homedir as
|
|
19596
|
-
import { join as
|
|
20155
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, rmSync as rmSync3 } from "node:fs";
|
|
20156
|
+
import { homedir as homedir28 } from "node:os";
|
|
20157
|
+
import { join as join27 } from "node:path";
|
|
19597
20158
|
function getAuthDir() {
|
|
19598
20159
|
const override = process.env.FAILPROOFAI_AUTH_DIR;
|
|
19599
20160
|
if (override)
|
|
19600
20161
|
return override;
|
|
19601
|
-
return
|
|
20162
|
+
return join27(homedir28(), ".failproofai");
|
|
19602
20163
|
}
|
|
19603
20164
|
function getAuthFilePath() {
|
|
19604
|
-
return
|
|
20165
|
+
return join27(getAuthDir(), "auth.json");
|
|
19605
20166
|
}
|
|
19606
20167
|
function readAuth() {
|
|
19607
20168
|
const p = getAuthFilePath();
|
|
19608
|
-
if (!
|
|
20169
|
+
if (!existsSync17(p))
|
|
19609
20170
|
return null;
|
|
19610
20171
|
try {
|
|
19611
|
-
const raw =
|
|
20172
|
+
const raw = readFileSync15(p, "utf-8");
|
|
19612
20173
|
const parsed = JSON.parse(raw);
|
|
19613
20174
|
if (typeof parsed.access_token !== "string" || typeof parsed.refresh_token !== "string" || typeof parsed.access_expires_at !== "number" || typeof parsed.user !== "object" || !parsed.user || typeof parsed.user.id !== "string" || typeof parsed.user.email !== "string") {
|
|
19614
20175
|
return null;
|
|
@@ -19632,8 +20193,8 @@ function writeAuth(auth) {
|
|
|
19632
20193
|
}
|
|
19633
20194
|
function deleteAuth() {
|
|
19634
20195
|
const p = getAuthFilePath();
|
|
19635
|
-
if (
|
|
19636
|
-
|
|
20196
|
+
if (existsSync17(p))
|
|
20197
|
+
rmSync3(p, { force: true });
|
|
19637
20198
|
}
|
|
19638
20199
|
function authFromTokenResponse(token, existingUser) {
|
|
19639
20200
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -19709,13 +20270,13 @@ function prompt(question, opts = {}) {
|
|
|
19709
20270
|
orig(s);
|
|
19710
20271
|
};
|
|
19711
20272
|
}
|
|
19712
|
-
return new Promise((
|
|
20273
|
+
return new Promise((resolve16) => {
|
|
19713
20274
|
rl.question(question, (answer) => {
|
|
19714
20275
|
rl.close();
|
|
19715
20276
|
if (opts.hidden && process.stdin.isTTY)
|
|
19716
20277
|
process.stdout.write(`
|
|
19717
20278
|
`);
|
|
19718
|
-
|
|
20279
|
+
resolve16(answer.trim());
|
|
19719
20280
|
});
|
|
19720
20281
|
});
|
|
19721
20282
|
}
|
|
@@ -19964,8 +20525,8 @@ EXAMPLES
|
|
|
19964
20525
|
});
|
|
19965
20526
|
|
|
19966
20527
|
// lib/claude-sessions.ts
|
|
19967
|
-
import { readdirSync as
|
|
19968
|
-
import { join as
|
|
20528
|
+
import { readdirSync as readdirSync12, statSync as statSync13, existsSync as existsSync18 } from "node:fs";
|
|
20529
|
+
import { join as join28, basename as basename5 } from "node:path";
|
|
19969
20530
|
function getClaudeProjectsRoot() {
|
|
19970
20531
|
return getClaudeProjectsPath();
|
|
19971
20532
|
}
|
|
@@ -19973,21 +20534,21 @@ function listClaudeProjects() {
|
|
|
19973
20534
|
const root = getClaudeProjectsRoot();
|
|
19974
20535
|
let entries;
|
|
19975
20536
|
try {
|
|
19976
|
-
entries =
|
|
20537
|
+
entries = readdirSync12(root, { withFileTypes: true });
|
|
19977
20538
|
} catch {
|
|
19978
20539
|
return [];
|
|
19979
20540
|
}
|
|
19980
20541
|
return entries.filter((e) => e.isDirectory()).map((e) => ({
|
|
19981
20542
|
name: e.name,
|
|
19982
20543
|
cwd: decodeFolderName(e.name),
|
|
19983
|
-
path:
|
|
20544
|
+
path: join28(root, e.name)
|
|
19984
20545
|
}));
|
|
19985
20546
|
}
|
|
19986
20547
|
function listClaudeTranscripts(project) {
|
|
19987
20548
|
const out = [];
|
|
19988
20549
|
let entries;
|
|
19989
20550
|
try {
|
|
19990
|
-
entries =
|
|
20551
|
+
entries = readdirSync12(project.path, { withFileTypes: true });
|
|
19991
20552
|
} catch {
|
|
19992
20553
|
return out;
|
|
19993
20554
|
}
|
|
@@ -19996,7 +20557,7 @@ function listClaudeTranscripts(project) {
|
|
|
19996
20557
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
19997
20558
|
if (!UUID_RE2.test(sessionId))
|
|
19998
20559
|
continue;
|
|
19999
|
-
const transcriptPath =
|
|
20560
|
+
const transcriptPath = join28(project.path, entry.name);
|
|
20000
20561
|
try {
|
|
20001
20562
|
const s = statSync13(transcriptPath);
|
|
20002
20563
|
out.push({
|
|
@@ -20010,12 +20571,12 @@ function listClaudeTranscripts(project) {
|
|
|
20010
20571
|
});
|
|
20011
20572
|
} catch {}
|
|
20012
20573
|
} else if (entry.isDirectory() && UUID_RE2.test(entry.name)) {
|
|
20013
|
-
const subDir =
|
|
20014
|
-
if (!
|
|
20574
|
+
const subDir = join28(project.path, entry.name, "subagents");
|
|
20575
|
+
if (!existsSync18(subDir))
|
|
20015
20576
|
continue;
|
|
20016
20577
|
let subEntries;
|
|
20017
20578
|
try {
|
|
20018
|
-
subEntries =
|
|
20579
|
+
subEntries = readdirSync12(subDir, { withFileTypes: true });
|
|
20019
20580
|
} catch {
|
|
20020
20581
|
continue;
|
|
20021
20582
|
}
|
|
@@ -20023,7 +20584,7 @@ function listClaudeTranscripts(project) {
|
|
|
20023
20584
|
if (!sub.isFile() || !sub.name.endsWith(".jsonl"))
|
|
20024
20585
|
continue;
|
|
20025
20586
|
const agentId = sub.name.slice(0, -".jsonl".length);
|
|
20026
|
-
const transcriptPath =
|
|
20587
|
+
const transcriptPath = join28(subDir, sub.name);
|
|
20027
20588
|
try {
|
|
20028
20589
|
const s = statSync13(transcriptPath);
|
|
20029
20590
|
out.push({
|
|
@@ -20099,7 +20660,7 @@ var init_shared = __esm(() => {
|
|
|
20099
20660
|
});
|
|
20100
20661
|
|
|
20101
20662
|
// src/audit/cli-adapters/claude.ts
|
|
20102
|
-
import { readFile as
|
|
20663
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
20103
20664
|
async function listClaudeTranscriptMetadata(opts = {}) {
|
|
20104
20665
|
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
20105
20666
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -20131,7 +20692,7 @@ async function listClaudeTranscriptMetadata(opts = {}) {
|
|
|
20131
20692
|
async function streamClaudeEvents(meta) {
|
|
20132
20693
|
let content;
|
|
20133
20694
|
try {
|
|
20134
|
-
content = await
|
|
20695
|
+
content = await readFile15(meta.transcriptPath, "utf-8");
|
|
20135
20696
|
} catch {
|
|
20136
20697
|
return [];
|
|
20137
20698
|
}
|
|
@@ -20663,7 +21224,7 @@ var init_openclaw = __esm(() => {
|
|
|
20663
21224
|
});
|
|
20664
21225
|
|
|
20665
21226
|
// src/audit/cli-adapters/factory.ts
|
|
20666
|
-
import { readFile as
|
|
21227
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
20667
21228
|
async function listFactoryTranscriptMetadata(opts = {}) {
|
|
20668
21229
|
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
20669
21230
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -20687,7 +21248,7 @@ async function listFactoryTranscriptMetadata(opts = {}) {
|
|
|
20687
21248
|
async function streamFactoryEvents(meta) {
|
|
20688
21249
|
let content;
|
|
20689
21250
|
try {
|
|
20690
|
-
content = await
|
|
21251
|
+
content = await readFile16(meta.transcriptPath, "utf-8");
|
|
20691
21252
|
} catch {
|
|
20692
21253
|
return [];
|
|
20693
21254
|
}
|
|
@@ -20717,7 +21278,7 @@ var init_factory = __esm(() => {
|
|
|
20717
21278
|
});
|
|
20718
21279
|
|
|
20719
21280
|
// src/audit/cli-adapters/antigravity.ts
|
|
20720
|
-
import { readFile as
|
|
21281
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
20721
21282
|
async function listAntigravityTranscriptMetadata(opts = {}) {
|
|
20722
21283
|
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
20723
21284
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -20743,7 +21304,7 @@ async function listAntigravityTranscriptMetadata(opts = {}) {
|
|
|
20743
21304
|
async function streamAntigravityEvents(meta) {
|
|
20744
21305
|
let content;
|
|
20745
21306
|
try {
|
|
20746
|
-
content = await
|
|
21307
|
+
content = await readFile17(meta.transcriptPath, "utf-8");
|
|
20747
21308
|
} catch {
|
|
20748
21309
|
return [];
|
|
20749
21310
|
}
|
|
@@ -21278,16 +21839,16 @@ var init_features = __esm(() => {
|
|
|
21278
21839
|
});
|
|
21279
21840
|
|
|
21280
21841
|
// src/audit/cache.ts
|
|
21281
|
-
import { createHash } from "node:crypto";
|
|
21282
|
-
import { existsSync as
|
|
21283
|
-
import { join as
|
|
21284
|
-
import { homedir as
|
|
21842
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
21843
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync8, chmodSync as chmodSync2 } from "node:fs";
|
|
21844
|
+
import { join as join29 } from "node:path";
|
|
21845
|
+
import { homedir as homedir29 } from "node:os";
|
|
21285
21846
|
function getEngineVersion() {
|
|
21286
21847
|
if (cachedEngineVersion)
|
|
21287
21848
|
return cachedEngineVersion;
|
|
21288
21849
|
const blob = BUILTIN_POLICIES.map((p) => `${p.name}|${p.fn.toString()}`).sort().join(`
|
|
21289
21850
|
`);
|
|
21290
|
-
cachedEngineVersion =
|
|
21851
|
+
cachedEngineVersion = createHash4("sha1").update(blob).digest("hex").slice(0, 16);
|
|
21291
21852
|
return cachedEngineVersion;
|
|
21292
21853
|
}
|
|
21293
21854
|
function getDetectorVersion() {
|
|
@@ -21295,22 +21856,22 @@ function getDetectorVersion() {
|
|
|
21295
21856
|
return cachedDetectorVersion;
|
|
21296
21857
|
const blob = AUDIT_DETECTORS.map((d) => `${d.name}|${d.detect.toString()}`).sort().join(`
|
|
21297
21858
|
`);
|
|
21298
|
-
cachedDetectorVersion =
|
|
21859
|
+
cachedDetectorVersion = createHash4("sha1").update(blob).digest("hex").slice(0, 16);
|
|
21299
21860
|
return cachedDetectorVersion;
|
|
21300
21861
|
}
|
|
21301
21862
|
function getCachePathFor(transcriptPath) {
|
|
21302
|
-
const root =
|
|
21303
|
-
const key =
|
|
21304
|
-
return
|
|
21863
|
+
const root = join29(homedir29(), ".failproofai", "cache", "audit");
|
|
21864
|
+
const key = createHash4("sha1").update(transcriptPath).digest("hex");
|
|
21865
|
+
return join29(root, `${key}.json`);
|
|
21305
21866
|
}
|
|
21306
21867
|
function readCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes) {
|
|
21307
21868
|
if (sizeBytes === 0)
|
|
21308
21869
|
return null;
|
|
21309
21870
|
const cachePath = getCachePathFor(transcriptPath);
|
|
21310
|
-
if (!
|
|
21871
|
+
if (!existsSync19(cachePath))
|
|
21311
21872
|
return null;
|
|
21312
21873
|
try {
|
|
21313
|
-
const raw =
|
|
21874
|
+
const raw = readFileSync16(cachePath, "utf-8");
|
|
21314
21875
|
const entry = JSON.parse(raw);
|
|
21315
21876
|
if (entry.schemaVersion !== CACHE_SCHEMA_VERSION)
|
|
21316
21877
|
return null;
|
|
@@ -21334,7 +21895,7 @@ function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result)
|
|
|
21334
21895
|
return;
|
|
21335
21896
|
const cachePath = getCachePathFor(transcriptPath);
|
|
21336
21897
|
try {
|
|
21337
|
-
|
|
21898
|
+
mkdirSync10(join29(homedir29(), ".failproofai", "cache", "audit"), { recursive: true });
|
|
21338
21899
|
const entry = {
|
|
21339
21900
|
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
21340
21901
|
cachedAt: Date.now(),
|
|
@@ -21706,10 +22267,10 @@ var init_audit = __esm(() => {
|
|
|
21706
22267
|
});
|
|
21707
22268
|
|
|
21708
22269
|
// src/audit/dashboard-cache.ts
|
|
21709
|
-
import { join as
|
|
21710
|
-
import { homedir as
|
|
22270
|
+
import { join as join30 } from "node:path";
|
|
22271
|
+
import { homedir as homedir30 } from "node:os";
|
|
21711
22272
|
function getCachePath() {
|
|
21712
|
-
return
|
|
22273
|
+
return join30(homedir30(), ".failproofai", "audit-dashboard.json");
|
|
21713
22274
|
}
|
|
21714
22275
|
function writeDashboardCache(params, result) {
|
|
21715
22276
|
try {
|
|
@@ -21799,14 +22360,14 @@ var POLL_INTERVAL_MS = 150, POLL_TIMEOUT_MS = 30000;
|
|
|
21799
22360
|
var init_open_browser = () => {};
|
|
21800
22361
|
|
|
21801
22362
|
// scripts/parse-script-args.ts
|
|
21802
|
-
import { resolve as
|
|
22363
|
+
import { resolve as resolve16 } from "path";
|
|
21803
22364
|
function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
|
|
21804
22365
|
const raw = inlineValue ?? args[index + 1];
|
|
21805
22366
|
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
21806
22367
|
console.error(`Error: ${flagName} requires ${errorLabel}`);
|
|
21807
22368
|
process.exit(1);
|
|
21808
22369
|
}
|
|
21809
|
-
const value = options?.resolve ?
|
|
22370
|
+
const value = options?.resolve ? resolve16(raw) : raw;
|
|
21810
22371
|
return { value, spliceCount: inlineValue !== null ? 1 : 2 };
|
|
21811
22372
|
}
|
|
21812
22373
|
function parseScriptArgs(argv) {
|
|
@@ -21854,21 +22415,21 @@ function parseScriptArgs(argv) {
|
|
|
21854
22415
|
var init_parse_script_args = () => {};
|
|
21855
22416
|
|
|
21856
22417
|
// scripts/install-diagnosis.mjs
|
|
21857
|
-
import { existsSync as
|
|
21858
|
-
import { dirname as dirname7, resolve as
|
|
21859
|
-
import { homedir as
|
|
22418
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17, realpathSync as realpathSync2 } from "node:fs";
|
|
22419
|
+
import { dirname as dirname7, resolve as resolve17 } from "node:path";
|
|
22420
|
+
import { homedir as homedir31, platform as platform4 } from "node:os";
|
|
21860
22421
|
import { spawnSync } from "node:child_process";
|
|
21861
22422
|
function findPackageRoot(start) {
|
|
21862
22423
|
try {
|
|
21863
|
-
let dir =
|
|
21864
|
-
if (
|
|
22424
|
+
let dir = realpathSync2(start);
|
|
22425
|
+
if (existsSync20(dir) && !existsSync20(resolve17(dir, "package.json"))) {
|
|
21865
22426
|
dir = dirname7(dir);
|
|
21866
22427
|
}
|
|
21867
22428
|
while (true) {
|
|
21868
|
-
const pkgPath =
|
|
21869
|
-
if (
|
|
22429
|
+
const pkgPath = resolve17(dir, "package.json");
|
|
22430
|
+
if (existsSync20(pkgPath)) {
|
|
21870
22431
|
try {
|
|
21871
|
-
const pkg = JSON.parse(
|
|
22432
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
|
|
21872
22433
|
if (pkg.name === PKG_NAME)
|
|
21873
22434
|
return dir;
|
|
21874
22435
|
} catch {}
|
|
@@ -21886,7 +22447,7 @@ function readPackageVersion(packageRoot) {
|
|
|
21886
22447
|
if (!packageRoot)
|
|
21887
22448
|
return null;
|
|
21888
22449
|
try {
|
|
21889
|
-
const pkg = JSON.parse(
|
|
22450
|
+
const pkg = JSON.parse(readFileSync17(resolve17(packageRoot, "package.json"), "utf8"));
|
|
21890
22451
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
21891
22452
|
} catch {
|
|
21892
22453
|
return null;
|
|
@@ -21912,16 +22473,16 @@ function locateNpmGlobal() {
|
|
|
21912
22473
|
const root = (res.stdout || "").trim();
|
|
21913
22474
|
if (!root)
|
|
21914
22475
|
return null;
|
|
21915
|
-
const candidate =
|
|
21916
|
-
return
|
|
22476
|
+
const candidate = resolve17(root, PKG_NAME);
|
|
22477
|
+
return existsSync20(resolve17(candidate, "package.json")) ? candidate : null;
|
|
21917
22478
|
} catch {
|
|
21918
22479
|
return null;
|
|
21919
22480
|
}
|
|
21920
22481
|
}
|
|
21921
22482
|
function locateBunGlobal() {
|
|
21922
22483
|
try {
|
|
21923
|
-
const candidate =
|
|
21924
|
-
return
|
|
22484
|
+
const candidate = resolve17(homedir31(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
22485
|
+
return existsSync20(resolve17(candidate, "package.json")) ? candidate : null;
|
|
21925
22486
|
} catch {
|
|
21926
22487
|
return null;
|
|
21927
22488
|
}
|
|
@@ -21929,8 +22490,8 @@ function locateBunGlobal() {
|
|
|
21929
22490
|
function buildRecommendation(pathFirstBin) {
|
|
21930
22491
|
if (!pathFirstBin)
|
|
21931
22492
|
return null;
|
|
21932
|
-
const bunBinPrefix =
|
|
21933
|
-
const bunGlobalPrefix =
|
|
22493
|
+
const bunBinPrefix = resolve17(homedir31(), ".bun", "bin") + "/";
|
|
22494
|
+
const bunGlobalPrefix = resolve17(homedir31(), ".bun", "install", "global") + "/";
|
|
21934
22495
|
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
21935
22496
|
if (isBun) {
|
|
21936
22497
|
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
@@ -21940,7 +22501,7 @@ function buildRecommendation(pathFirstBin) {
|
|
|
21940
22501
|
function diagnoseShadow(self) {
|
|
21941
22502
|
const selfPackageRoot = (() => {
|
|
21942
22503
|
try {
|
|
21943
|
-
return self?.selfPackageRoot ?
|
|
22504
|
+
return self?.selfPackageRoot ? realpathSync2(self.selfPackageRoot) : null;
|
|
21944
22505
|
} catch {
|
|
21945
22506
|
return self?.selfPackageRoot ?? null;
|
|
21946
22507
|
}
|
|
@@ -22010,8 +22571,8 @@ __export(exports_launch, {
|
|
|
22010
22571
|
launch: () => launch
|
|
22011
22572
|
});
|
|
22012
22573
|
import { spawn as spawn2 } from "child_process";
|
|
22013
|
-
import { realpathSync as
|
|
22014
|
-
import { resolve as
|
|
22574
|
+
import { realpathSync as realpathSync3, existsSync as existsSync21 } from "node:fs";
|
|
22575
|
+
import { resolve as resolve18, dirname as dirname8 } from "node:path";
|
|
22015
22576
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
22016
22577
|
import { createInterface as createInterface2 } from "node:readline";
|
|
22017
22578
|
function launch(mode) {
|
|
@@ -22026,9 +22587,9 @@ function launch(mode) {
|
|
|
22026
22587
|
process.env.PORT = port;
|
|
22027
22588
|
process.env.HOSTNAME = "0.0.0.0";
|
|
22028
22589
|
cmd = "node";
|
|
22029
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
22030
|
-
const serverJsPath =
|
|
22031
|
-
if (!
|
|
22590
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve18(dirname8(realpathSync3(fileURLToPath2(import.meta.url))), "..");
|
|
22591
|
+
const serverJsPath = resolve18(packageRoot, ".next/standalone/server.js");
|
|
22592
|
+
if (!existsSync21(serverJsPath)) {
|
|
22032
22593
|
let shadowMessage = null;
|
|
22033
22594
|
try {
|
|
22034
22595
|
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
@@ -22378,381 +22939,182 @@ WHAT IT DOES
|
|
|
22378
22939
|
SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
22379
22940
|
});
|
|
22380
22941
|
|
|
22381
|
-
// src/hooks/
|
|
22382
|
-
import {
|
|
22383
|
-
import {
|
|
22384
|
-
import {
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22942
|
+
// src/hooks/cloud-enrollment.ts
|
|
22943
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, rmSync as rmSync4 } from "node:fs";
|
|
22944
|
+
import { homedir as homedir32 } from "node:os";
|
|
22945
|
+
import { join as join31 } from "node:path";
|
|
22946
|
+
function cloudCredentialPath() {
|
|
22947
|
+
const override = process.env.FAILPROOFAI_CLOUD_CREDENTIALS;
|
|
22948
|
+
if (override)
|
|
22949
|
+
return override;
|
|
22950
|
+
return join31(homedir32(), ".failproofai", "cloud.json");
|
|
22388
22951
|
}
|
|
22389
|
-
function
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
|
|
22952
|
+
function validateCloudUrl(raw) {
|
|
22953
|
+
let parsed;
|
|
22954
|
+
try {
|
|
22955
|
+
parsed = new URL(raw);
|
|
22956
|
+
} catch {
|
|
22957
|
+
return { ok: false, reason: `"${raw}" is not a valid URL. Expected something like https://be.failproof.ai` };
|
|
22958
|
+
}
|
|
22959
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
22960
|
+
return { ok: false, reason: `Unsupported scheme "${parsed.protocol}" — use https (or http for localhost).` };
|
|
22961
|
+
}
|
|
22962
|
+
if (parsed.protocol === "http:") {
|
|
22963
|
+
const host = parsed.hostname;
|
|
22964
|
+
const loopback = host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
22965
|
+
if (!loopback) {
|
|
22966
|
+
return {
|
|
22967
|
+
ok: false,
|
|
22968
|
+
reason: `Refusing to send the machine token to ${parsed.origin} over plain http. ` + `Use https, or http only for localhost during development.`
|
|
22969
|
+
};
|
|
22970
|
+
}
|
|
22397
22971
|
}
|
|
22972
|
+
return { ok: true, url: parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "")) };
|
|
22398
22973
|
}
|
|
22399
|
-
function
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22974
|
+
function maskToken(token) {
|
|
22975
|
+
if (token.length <= 4)
|
|
22976
|
+
return "****";
|
|
22977
|
+
return `****${token.slice(-4)}`;
|
|
22978
|
+
}
|
|
22979
|
+
function readCloudCredentials() {
|
|
22980
|
+
const path3 = cloudCredentialPath();
|
|
22981
|
+
if (!existsSync22(path3))
|
|
22982
|
+
return null;
|
|
22403
22983
|
try {
|
|
22404
|
-
const
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22984
|
+
const raw = JSON.parse(readFileSync18(path3, "utf8"));
|
|
22985
|
+
if (raw.schemaVersion !== SCHEMA_VERSION2)
|
|
22986
|
+
return null;
|
|
22987
|
+
if (typeof raw.url !== "string" || typeof raw.machineId !== "string" || typeof raw.token !== "string") {
|
|
22988
|
+
return null;
|
|
22989
|
+
}
|
|
22990
|
+
if (!raw.url || !raw.machineId || !raw.token)
|
|
22991
|
+
return null;
|
|
22992
|
+
return { url: raw.url, machineId: raw.machineId, token: raw.token };
|
|
22408
22993
|
} catch {
|
|
22409
|
-
|
|
22410
|
-
` + "Install it globally first: npm install -g failproofai");
|
|
22994
|
+
return null;
|
|
22411
22995
|
}
|
|
22412
22996
|
}
|
|
22413
|
-
function
|
|
22414
|
-
|
|
22415
|
-
if (invalid.length > 0) {
|
|
22416
|
-
const validList = [...VALID_POLICY_NAMES2].join(", ");
|
|
22417
|
-
throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
|
|
22418
|
-
` + `Valid policies: ${validList}`);
|
|
22419
|
-
}
|
|
22997
|
+
function writeCloudCredentials(creds) {
|
|
22998
|
+
writeJsonAtomically(cloudCredentialPath(), { schemaVersion: SCHEMA_VERSION2, ...creds });
|
|
22420
22999
|
}
|
|
22421
|
-
function
|
|
22422
|
-
const
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
seen.add(p);
|
|
23000
|
+
function clearCloudCredentials() {
|
|
23001
|
+
const path3 = cloudCredentialPath();
|
|
23002
|
+
if (!existsSync22(path3))
|
|
23003
|
+
return false;
|
|
23004
|
+
try {
|
|
23005
|
+
rmSync4(path3);
|
|
22428
23006
|
return true;
|
|
22429
|
-
}
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
23007
|
+
} catch {
|
|
23008
|
+
return false;
|
|
23009
|
+
}
|
|
22433
23010
|
}
|
|
22434
|
-
async function
|
|
22435
|
-
const
|
|
22436
|
-
|
|
22437
|
-
|
|
23011
|
+
async function verifyCloudCredentials(creds) {
|
|
23012
|
+
const url = `${creds.url}/enforcement/v1/desired-state?machineId=${encodeURIComponent(creds.machineId)}`;
|
|
23013
|
+
let response;
|
|
23014
|
+
try {
|
|
23015
|
+
response = await fetchWithTimeout(url, { headers: { Authorization: `Bearer ${creds.token}` } }, VERIFY_TIMEOUT_MS);
|
|
23016
|
+
} catch (err) {
|
|
23017
|
+
if (isAbortError(err))
|
|
23018
|
+
return { ok: false, reason: `No response from ${creds.url} within ${VERIFY_TIMEOUT_MS / 1000}s.` };
|
|
23019
|
+
return { ok: false, reason: `Could not reach ${creds.url}: ${err instanceof Error ? err.message : String(err)}` };
|
|
23020
|
+
}
|
|
23021
|
+
if (response.status === 401) {
|
|
23022
|
+
return { ok: false, reason: "The server rejected this token (401). Check the key was copied whole." };
|
|
23023
|
+
}
|
|
23024
|
+
if (response.status === 403) {
|
|
23025
|
+
return {
|
|
23026
|
+
ok: false,
|
|
23027
|
+
reason: "This key is valid but lacks the `policies:pull` permission (403). Create a machine key with that permission."
|
|
23028
|
+
};
|
|
23029
|
+
}
|
|
23030
|
+
if (!response.ok) {
|
|
23031
|
+
return { ok: false, reason: `The server answered ${response.status} ${response.statusText}.` };
|
|
22438
23032
|
}
|
|
22439
|
-
const origLog = console.log;
|
|
22440
|
-
console.log = () => {};
|
|
22441
23033
|
try {
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
23034
|
+
const body = await response.json();
|
|
23035
|
+
return {
|
|
23036
|
+
ok: true,
|
|
23037
|
+
policyCount: Array.isArray(body.policies) ? body.policies.length : 0,
|
|
23038
|
+
generation: typeof body.generation === "number" ? body.generation : 0
|
|
23039
|
+
};
|
|
23040
|
+
} catch {
|
|
23041
|
+
return {
|
|
23042
|
+
ok: false,
|
|
23043
|
+
reason: `${creds.url} answered, but not with a desired-state document. Is this a Failproof Cloud URL?`
|
|
23044
|
+
};
|
|
22445
23045
|
}
|
|
22446
23046
|
}
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
|
|
23047
|
+
var SCHEMA_VERSION2 = 1, VERIFY_TIMEOUT_MS = 1e4;
|
|
23048
|
+
var init_cloud_enrollment = __esm(() => {
|
|
23049
|
+
init_atomic_write();
|
|
23050
|
+
});
|
|
23051
|
+
|
|
23052
|
+
// src/hooks/daemon-download.ts
|
|
23053
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
23054
|
+
import { chmodSync as chmodSync3, existsSync as existsSync23, mkdirSync as mkdirSync11, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
23055
|
+
import { homedir as homedir33 } from "node:os";
|
|
23056
|
+
import { resolve as resolve19 } from "node:path";
|
|
23057
|
+
import { gunzipSync } from "node:zlib";
|
|
23058
|
+
function baseUrl() {
|
|
23059
|
+
return (process.env.FAILPROOFAI_DAEMON_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
23060
|
+
}
|
|
23061
|
+
function daemonBinaryDir() {
|
|
23062
|
+
return resolve19(homedir33(), ".failproofai", "bin");
|
|
23063
|
+
}
|
|
23064
|
+
function installedBinaryPath(ver = version2) {
|
|
23065
|
+
return resolve19(daemonBinaryDir(), `failproofaid-${ver}`);
|
|
23066
|
+
}
|
|
23067
|
+
function daemonAssetUrl(key, ver = version2) {
|
|
23068
|
+
return `${baseUrl()}/v${ver}/failproofaid-${key}.gz`;
|
|
23069
|
+
}
|
|
23070
|
+
function checksumsUrl(ver = version2) {
|
|
23071
|
+
return `${baseUrl()}/v${ver}/SHA256SUMS`;
|
|
23072
|
+
}
|
|
23073
|
+
async function fetchBytes(url) {
|
|
23074
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
23075
|
+
if (!response.ok)
|
|
23076
|
+
throw new Error(`GET ${url} returned ${response.status}`);
|
|
23077
|
+
return Buffer.from(await response.arrayBuffer());
|
|
23078
|
+
}
|
|
23079
|
+
function digestFor(manifest, assetName) {
|
|
23080
|
+
for (const line of manifest.split(`
|
|
23081
|
+
`)) {
|
|
23082
|
+
const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
|
|
23083
|
+
if (match && match[2].trim() === assetName)
|
|
23084
|
+
return match[1].toLowerCase();
|
|
22456
23085
|
}
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
|
|
22465
|
-
supported_scopes: integration.scopes
|
|
22466
|
-
});
|
|
22467
|
-
} catch {}
|
|
22468
|
-
throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
|
|
22469
|
-
}
|
|
23086
|
+
return null;
|
|
23087
|
+
}
|
|
23088
|
+
async function downloadFailproofaidBinary(key) {
|
|
23089
|
+
const target = installedBinaryPath();
|
|
23090
|
+
if (existsSync23(target))
|
|
23091
|
+
return { path: target };
|
|
23092
|
+
if (process.env.FAILPROOFAI_NO_DOWNLOAD) {
|
|
23093
|
+
return { error: "daemon downloads are disabled (FAILPROOFAI_NO_DOWNLOAD)" };
|
|
22470
23094
|
}
|
|
22471
|
-
const
|
|
22472
|
-
const
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
22480
|
-
|
|
23095
|
+
const assetName = `failproofaid-${key}.gz`;
|
|
23096
|
+
const tempPath = `${target}.${process.pid}.tmp`;
|
|
23097
|
+
try {
|
|
23098
|
+
const [asset, manifest] = await Promise.all([
|
|
23099
|
+
fetchBytes(daemonAssetUrl(key)),
|
|
23100
|
+
fetchBytes(checksumsUrl()).then((b) => b.toString("utf8"))
|
|
23101
|
+
]);
|
|
23102
|
+
const expected = digestFor(manifest, assetName);
|
|
23103
|
+
if (!expected) {
|
|
23104
|
+
return { error: `SHA256SUMS for v${version2} has no entry for ${assetName}` };
|
|
22481
23105
|
}
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
|
|
22486
|
-
}
|
|
22487
|
-
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
22488
|
-
if (removeCustomHooks) {
|
|
22489
|
-
delete configToWrite.customPoliciesPath;
|
|
22490
|
-
delete configToWrite.customPoliciesPaths;
|
|
22491
|
-
} else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
|
|
22492
|
-
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve17(path3));
|
|
22493
|
-
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve17(path3)).filter((path3) => {
|
|
22494
|
-
if (existsSync19(path3))
|
|
22495
|
-
return true;
|
|
22496
|
-
console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
|
|
22497
|
-
return false;
|
|
22498
|
-
});
|
|
22499
|
-
configToWrite.customPoliciesPaths = replace ? [...new Set(incoming)] : [...new Set([...carried, ...incoming])];
|
|
22500
|
-
delete configToWrite.customPoliciesPath;
|
|
22501
|
-
for (const path3 of incoming) {
|
|
22502
|
-
let validatedHooks = [];
|
|
22503
|
-
try {
|
|
22504
|
-
validatedHooks = await loadCustomHooks(path3, { strict: true });
|
|
22505
|
-
} catch (err) {
|
|
22506
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
22507
|
-
try {
|
|
22508
|
-
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
22509
|
-
scope,
|
|
22510
|
-
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
22511
|
-
});
|
|
22512
|
-
} catch {}
|
|
22513
|
-
console.error(`Error: ${msg}`);
|
|
22514
|
-
process.exit(1);
|
|
22515
|
-
}
|
|
22516
|
-
if (validatedHooks.length === 0) {
|
|
22517
|
-
try {
|
|
22518
|
-
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
22519
|
-
scope,
|
|
22520
|
-
error_type: "no_hooks_registered"
|
|
22521
|
-
});
|
|
22522
|
-
} catch {}
|
|
22523
|
-
console.error(`Error: no hooks registered in ${path3}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
|
|
22524
|
-
process.exit(1);
|
|
22525
|
-
}
|
|
22526
|
-
console.log(`
|
|
22527
|
-
Validated ${validatedHooks.length} custom hook(s) from ${path3}: ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
22528
|
-
}
|
|
22529
|
-
}
|
|
22530
|
-
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
22531
|
-
console.log(`
|
|
22532
|
-
Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
22533
|
-
`);
|
|
22534
|
-
if (removeCustomHooks) {
|
|
22535
|
-
console.log("Custom hooks path cleared.");
|
|
22536
|
-
} else if (configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath) {
|
|
22537
|
-
const paths = configToWrite.customPoliciesPaths ?? [configToWrite.customPoliciesPath];
|
|
22538
|
-
console.log(`Custom hooks paths: ${paths.join(", ")}`);
|
|
22539
|
-
}
|
|
22540
|
-
const writtenSettingsPaths = [];
|
|
22541
|
-
for (const cliId of selectedClis) {
|
|
22542
|
-
const integration = getIntegration(cliId);
|
|
22543
|
-
const settingsPaths = settingsPathsFor(integration, scope, cwd);
|
|
22544
|
-
try {
|
|
22545
|
-
for (const settingsPath of settingsPaths) {
|
|
22546
|
-
const settings = integration.readSettings(settingsPath);
|
|
22547
|
-
integration.writeHookEntries(settings, binaryPath, scope);
|
|
22548
|
-
integration.writeSettings(settingsPath, settings);
|
|
22549
|
-
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
22550
|
-
}
|
|
22551
|
-
} catch (err) {
|
|
22552
|
-
const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message) ? "permission_denied" : err instanceof Error && /ENOENT|ENOTDIR/.test(err.message) ? "path_not_found" : "write_error";
|
|
22553
|
-
try {
|
|
22554
|
-
await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
|
|
22555
|
-
cli: cliId,
|
|
22556
|
-
scope,
|
|
22557
|
-
error_type: errorType
|
|
22558
|
-
});
|
|
22559
|
-
} catch {}
|
|
22560
|
-
throw err;
|
|
22561
|
-
}
|
|
22562
|
-
}
|
|
22563
|
-
try {
|
|
22564
|
-
const newSet = new Set(selectedPolicies);
|
|
22565
|
-
const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
|
|
22566
|
-
const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
|
|
22567
|
-
const distinctId = getInstanceId2();
|
|
22568
|
-
await trackHookEvent2(distinctId, "hooks_installed", {
|
|
22569
|
-
scope,
|
|
22570
|
-
cli: selectedClis,
|
|
22571
|
-
cli_count: selectedClis.length,
|
|
22572
|
-
policies: selectedPolicies,
|
|
22573
|
-
policy_count: selectedPolicies.length,
|
|
22574
|
-
policies_added: policiesAdded,
|
|
22575
|
-
policies_removed: policiesRemoved,
|
|
22576
|
-
...source ? { source } : {},
|
|
22577
|
-
platform: platform5(),
|
|
22578
|
-
arch: arch4(),
|
|
22579
|
-
os_release: release3(),
|
|
22580
|
-
hostname_hash: hashToId2(hostname4()),
|
|
22581
|
-
has_custom_hooks_path: !!(configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath),
|
|
22582
|
-
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
22583
|
-
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
22584
|
-
command_format: scope === "project" ? "npx" : "absolute"
|
|
22585
|
-
});
|
|
22586
|
-
if (includeBeta) {
|
|
22587
|
-
const betaNames = new Set(BUILTIN_POLICIES.filter((p) => p.beta).map((p) => p.name));
|
|
22588
|
-
const installedBeta = selectedPolicies.filter((p) => betaNames.has(p));
|
|
22589
|
-
if (installedBeta.length > 0) {
|
|
22590
|
-
await trackHookEvent2(distinctId, "beta_policies_installed", {
|
|
22591
|
-
scope,
|
|
22592
|
-
cli: selectedClis,
|
|
22593
|
-
beta_count: installedBeta.length,
|
|
22594
|
-
beta_policy_names: installedBeta,
|
|
22595
|
-
...source ? { source } : {}
|
|
22596
|
-
});
|
|
22597
|
-
}
|
|
22598
|
-
}
|
|
22599
|
-
} catch {}
|
|
22600
|
-
for (const { cli: cliId, path: path3 } of writtenSettingsPaths) {
|
|
22601
|
-
const integration = getIntegration(cliId);
|
|
22602
|
-
console.log(`Failproof AI hooks installed for ${integration.displayName} ` + `(${integration.eventTypes.length} event types, scope: ${scope}).`);
|
|
22603
|
-
console.log(`Settings: ${path3}`);
|
|
22604
|
-
}
|
|
22605
|
-
if (scope === "project") {
|
|
22606
|
-
console.log(`Command: npx -y failproofai`);
|
|
22607
|
-
console.log(`
|
|
22608
|
-
This file can be committed to git — no machine-specific paths.`);
|
|
22609
|
-
} else {
|
|
22610
|
-
console.log(`Binary: ${binaryPath}`);
|
|
22611
|
-
}
|
|
22612
|
-
const otherScopes = deduplicateScopes2(HOOK_SCOPES, cwd).filter((s) => s !== scope);
|
|
22613
|
-
const duplicates = otherScopes.filter((s) => hooksInstalledInSettings2(s, cwd));
|
|
22614
|
-
if (duplicates.length > 0) {
|
|
22615
|
-
const scopeList = duplicates.map((s) => `${s} (${scopeLabel2(s)})`).join(", ");
|
|
22616
|
-
console.log();
|
|
22617
|
-
console.log(`\x1B[33mWarning: Failproof AI hooks are also installed at ${scopeList}.\x1B[0m`);
|
|
22618
|
-
console.log(`Having hooks in multiple scopes may cause duplicate policy evaluation.`);
|
|
22619
|
-
console.log(`Use \`failproofai policies --uninstall --scope ${duplicates[0]}\` to remove the other installation,`);
|
|
22620
|
-
console.log(`or \`failproofai policies\` to see all scopes.`);
|
|
22621
|
-
try {
|
|
22622
|
-
await trackHookEvent2(getInstanceId2(), "multi_scope_warning_shown", {
|
|
22623
|
-
new_scope: scope,
|
|
22624
|
-
existing_scopes: duplicates,
|
|
22625
|
-
cli: selectedClis
|
|
22626
|
-
});
|
|
22627
|
-
} catch {}
|
|
22628
|
-
}
|
|
22629
|
-
}
|
|
22630
|
-
var VALID_POLICY_NAMES2;
|
|
22631
|
-
var init_manager2 = __esm(() => {
|
|
22632
|
-
init_types();
|
|
22633
|
-
init_integrations();
|
|
22634
|
-
init_install_prompt();
|
|
22635
|
-
init_hooks_config();
|
|
22636
|
-
init_builtin_policies();
|
|
22637
|
-
init_custom_hooks_loader();
|
|
22638
|
-
init_hook_telemetry2();
|
|
22639
|
-
init_telemetry_id2();
|
|
22640
|
-
init_cli_error();
|
|
22641
|
-
init_hook_logger();
|
|
22642
|
-
VALID_POLICY_NAMES2 = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
22643
|
-
});
|
|
22644
|
-
|
|
22645
|
-
// src/hooks/policy-presets.ts
|
|
22646
|
-
function resolvePreset(id) {
|
|
22647
|
-
const preset = POLICY_PRESETS.find((p) => p.id === id);
|
|
22648
|
-
if (!preset)
|
|
22649
|
-
return [];
|
|
22650
|
-
const cats = new Set(preset.categories);
|
|
22651
|
-
const fromCategories = BUILTIN_POLICIES.filter((p) => !p.beta && cats.has(p.category)).map((p) => p.name);
|
|
22652
|
-
const extras = (preset.extra ?? []).filter((name) => BUILTIN_POLICIES.some((p) => p.name === name && !p.beta));
|
|
22653
|
-
return [...new Set([...fromCategories, ...extras])];
|
|
22654
|
-
}
|
|
22655
|
-
function resolveEverything() {
|
|
22656
|
-
return BUILTIN_POLICIES.filter((p) => !p.beta).map((p) => p.name);
|
|
22657
|
-
}
|
|
22658
|
-
var POLICY_PRESETS;
|
|
22659
|
-
var init_policy_presets = __esm(() => {
|
|
22660
|
-
init_builtin_policies();
|
|
22661
|
-
POLICY_PRESETS = [
|
|
22662
|
-
{
|
|
22663
|
-
id: "secrets",
|
|
22664
|
-
label: "Secrets & data",
|
|
22665
|
-
description: "Redact secrets in tool output, block .env & secret-file writes, keep reads inside the repo",
|
|
22666
|
-
categories: ["Sanitize", "Environment"],
|
|
22667
|
-
extra: ["block-secrets-write"]
|
|
22668
|
-
},
|
|
22669
|
-
{
|
|
22670
|
-
id: "git",
|
|
22671
|
-
label: "Git safety",
|
|
22672
|
-
description: "Block force-push & pushes to main, warn on history-rewriting git ops",
|
|
22673
|
-
categories: ["Git"]
|
|
22674
|
-
},
|
|
22675
|
-
{
|
|
22676
|
-
id: "ship",
|
|
22677
|
-
label: "Ship discipline",
|
|
22678
|
-
description: "Don't let the agent finish until changes are committed, pushed, PR'd and CI is green",
|
|
22679
|
-
categories: ["Workflow"]
|
|
22680
|
-
},
|
|
22681
|
-
{
|
|
22682
|
-
id: "infra",
|
|
22683
|
-
label: "Cloud & infra",
|
|
22684
|
-
description: "Block kubectl / terraform / aws / gcloud / az / helm / gh pipeline commands",
|
|
22685
|
-
categories: ["Infra Commands"]
|
|
22686
|
-
}
|
|
22687
|
-
];
|
|
22688
|
-
});
|
|
22689
|
-
|
|
22690
|
-
// src/hooks/daemon-download.ts
|
|
22691
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
22692
|
-
import { chmodSync as chmodSync3, existsSync as existsSync20, mkdirSync as mkdirSync10, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
22693
|
-
import { homedir as homedir31 } from "node:os";
|
|
22694
|
-
import { resolve as resolve18 } from "node:path";
|
|
22695
|
-
import { gunzipSync } from "node:zlib";
|
|
22696
|
-
function baseUrl() {
|
|
22697
|
-
return (process.env.FAILPROOFAI_DAEMON_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
22698
|
-
}
|
|
22699
|
-
function daemonBinaryDir() {
|
|
22700
|
-
return resolve18(homedir31(), ".failproofai", "bin");
|
|
22701
|
-
}
|
|
22702
|
-
function installedBinaryPath(ver = version2) {
|
|
22703
|
-
return resolve18(daemonBinaryDir(), `failproofaid-${ver}`);
|
|
22704
|
-
}
|
|
22705
|
-
function daemonAssetUrl(key, ver = version2) {
|
|
22706
|
-
return `${baseUrl()}/v${ver}/failproofaid-${key}.gz`;
|
|
22707
|
-
}
|
|
22708
|
-
function checksumsUrl(ver = version2) {
|
|
22709
|
-
return `${baseUrl()}/v${ver}/SHA256SUMS`;
|
|
22710
|
-
}
|
|
22711
|
-
async function fetchBytes(url) {
|
|
22712
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
22713
|
-
if (!response.ok)
|
|
22714
|
-
throw new Error(`GET ${url} returned ${response.status}`);
|
|
22715
|
-
return Buffer.from(await response.arrayBuffer());
|
|
22716
|
-
}
|
|
22717
|
-
function digestFor(manifest, assetName) {
|
|
22718
|
-
for (const line of manifest.split(`
|
|
22719
|
-
`)) {
|
|
22720
|
-
const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
|
|
22721
|
-
if (match && match[2].trim() === assetName)
|
|
22722
|
-
return match[1].toLowerCase();
|
|
22723
|
-
}
|
|
22724
|
-
return null;
|
|
22725
|
-
}
|
|
22726
|
-
async function downloadFailproofaidBinary(key) {
|
|
22727
|
-
const target = installedBinaryPath();
|
|
22728
|
-
if (existsSync20(target))
|
|
22729
|
-
return { path: target };
|
|
22730
|
-
if (process.env.FAILPROOFAI_NO_DOWNLOAD) {
|
|
22731
|
-
return { error: "daemon downloads are disabled (FAILPROOFAI_NO_DOWNLOAD)" };
|
|
22732
|
-
}
|
|
22733
|
-
const assetName = `failproofaid-${key}.gz`;
|
|
22734
|
-
const tempPath = `${target}.${process.pid}.tmp`;
|
|
22735
|
-
try {
|
|
22736
|
-
const [asset, manifest] = await Promise.all([
|
|
22737
|
-
fetchBytes(daemonAssetUrl(key)),
|
|
22738
|
-
fetchBytes(checksumsUrl()).then((b) => b.toString("utf8"))
|
|
22739
|
-
]);
|
|
22740
|
-
const expected = digestFor(manifest, assetName);
|
|
22741
|
-
if (!expected) {
|
|
22742
|
-
return { error: `SHA256SUMS for v${version2} has no entry for ${assetName}` };
|
|
22743
|
-
}
|
|
22744
|
-
const actual = createHash2("sha256").update(asset).digest("hex");
|
|
22745
|
-
if (actual !== expected) {
|
|
22746
|
-
return { error: `checksum mismatch for ${assetName} (expected ${expected}, got ${actual})` };
|
|
23106
|
+
const actual = createHash5("sha256").update(asset).digest("hex");
|
|
23107
|
+
if (actual !== expected) {
|
|
23108
|
+
return { error: `checksum mismatch for ${assetName} (expected ${expected}, got ${actual})` };
|
|
22747
23109
|
}
|
|
22748
23110
|
const binary = gunzipSync(asset);
|
|
22749
|
-
|
|
23111
|
+
mkdirSync11(daemonBinaryDir(), { recursive: true });
|
|
22750
23112
|
writeFileSync9(tempPath, binary);
|
|
22751
23113
|
chmodSync3(tempPath, 493);
|
|
22752
23114
|
renameSync4(tempPath, target);
|
|
22753
23115
|
return { path: target };
|
|
22754
23116
|
} catch (err) {
|
|
22755
|
-
|
|
23117
|
+
rmSync5(tempPath, { force: true });
|
|
22756
23118
|
const message = err instanceof Error ? err.message : String(err);
|
|
22757
23119
|
return { error: `failed to download failproofaid v${version2} for ${key}: ${message}` };
|
|
22758
23120
|
}
|
|
@@ -22763,16 +23125,24 @@ var init_daemon_download = __esm(() => {
|
|
|
22763
23125
|
});
|
|
22764
23126
|
|
|
22765
23127
|
// src/hooks/daemon-service.ts
|
|
22766
|
-
import {
|
|
22767
|
-
|
|
22768
|
-
|
|
23128
|
+
import {
|
|
23129
|
+
existsSync as existsSync24,
|
|
23130
|
+
mkdirSync as mkdirSync12,
|
|
23131
|
+
mkdtempSync,
|
|
23132
|
+
readFileSync as readFileSync19,
|
|
23133
|
+
writeFileSync as writeFileSync10,
|
|
23134
|
+
unlinkSync as unlinkSync3,
|
|
23135
|
+
rmSync as rmSync6
|
|
23136
|
+
} from "node:fs";
|
|
23137
|
+
import { homedir as homedir34, tmpdir, userInfo } from "node:os";
|
|
23138
|
+
import { resolve as resolve20, dirname as dirname9 } from "node:path";
|
|
22769
23139
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
22770
23140
|
function setDaemonConfigured(value) {
|
|
22771
23141
|
const path3 = getConfigPathForScope("user");
|
|
22772
23142
|
let config = {};
|
|
22773
23143
|
try {
|
|
22774
|
-
if (
|
|
22775
|
-
config = JSON.parse(
|
|
23144
|
+
if (existsSync24(path3))
|
|
23145
|
+
config = JSON.parse(readFileSync19(path3, "utf8"));
|
|
22776
23146
|
} catch {
|
|
22777
23147
|
return;
|
|
22778
23148
|
}
|
|
@@ -22781,7 +23151,7 @@ function setDaemonConfigured(value) {
|
|
|
22781
23151
|
else
|
|
22782
23152
|
delete config.daemonConfigured;
|
|
22783
23153
|
try {
|
|
22784
|
-
|
|
23154
|
+
mkdirSync12(dirname9(path3), { recursive: true });
|
|
22785
23155
|
writeFileSync10(path3, JSON.stringify(config, null, 2) + `
|
|
22786
23156
|
`, "utf8");
|
|
22787
23157
|
} catch {}
|
|
@@ -22791,22 +23161,22 @@ function isDaemonSupportedPlatform() {
|
|
|
22791
23161
|
}
|
|
22792
23162
|
function platformKey() {
|
|
22793
23163
|
const os4 = process.platform === "linux" ? "linux" : process.platform === "darwin" ? "darwin" : null;
|
|
22794
|
-
const
|
|
22795
|
-
if (!os4 || !
|
|
23164
|
+
const arch4 = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : null;
|
|
23165
|
+
if (!os4 || !arch4)
|
|
22796
23166
|
return null;
|
|
22797
|
-
return `${os4}-${
|
|
23167
|
+
return `${os4}-${arch4}`;
|
|
22798
23168
|
}
|
|
22799
23169
|
function resolveFailproofaidBinaryPath() {
|
|
22800
23170
|
if (process.env.FAILPROOFAI_DAEMON_BINARY)
|
|
22801
23171
|
return process.env.FAILPROOFAI_DAEMON_BINARY;
|
|
22802
23172
|
const downloaded = installedBinaryPath();
|
|
22803
|
-
if (
|
|
23173
|
+
if (existsSync24(downloaded))
|
|
22804
23174
|
return downloaded;
|
|
22805
23175
|
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
22806
23176
|
if (packageRoot) {
|
|
22807
23177
|
for (const profile of ["release", "debug"]) {
|
|
22808
|
-
const candidate =
|
|
22809
|
-
if (
|
|
23178
|
+
const candidate = resolve20(packageRoot, "target", profile, "failproofaid");
|
|
23179
|
+
if (existsSync24(candidate))
|
|
22810
23180
|
return candidate;
|
|
22811
23181
|
}
|
|
22812
23182
|
}
|
|
@@ -22831,10 +23201,13 @@ function resolveWorkerCommand() {
|
|
|
22831
23201
|
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
22832
23202
|
if (!packageRoot)
|
|
22833
23203
|
return null;
|
|
22834
|
-
const workerScript =
|
|
22835
|
-
if (!
|
|
23204
|
+
const workerScript = resolve20(packageRoot, "dist", "worker.mjs");
|
|
23205
|
+
if (!existsSync24(workerScript))
|
|
22836
23206
|
return null;
|
|
22837
|
-
return `${process.execPath} ${workerScript}`;
|
|
23207
|
+
return `${shellQuote(process.execPath)} ${shellQuote(workerScript)}`;
|
|
23208
|
+
}
|
|
23209
|
+
function shellQuote(value) {
|
|
23210
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
22838
23211
|
}
|
|
22839
23212
|
function serviceUser() {
|
|
22840
23213
|
return userInfo().username;
|
|
@@ -22843,22 +23216,40 @@ function systemdUnitName(user = serviceUser()) {
|
|
|
22843
23216
|
return `failproofaid@${user}.service`;
|
|
22844
23217
|
}
|
|
22845
23218
|
function systemdUnitPath(user = serviceUser()) {
|
|
22846
|
-
return
|
|
23219
|
+
return resolve20("/etc/systemd/system", systemdUnitName(user));
|
|
22847
23220
|
}
|
|
22848
23221
|
function legacySystemdUserUnitPath() {
|
|
22849
|
-
return
|
|
23222
|
+
return resolve20(homedir34(), ".config", "systemd", "user", "failproofaid.service");
|
|
22850
23223
|
}
|
|
22851
|
-
function
|
|
22852
|
-
return
|
|
23224
|
+
function launchdLabel(user = serviceUser()) {
|
|
23225
|
+
return `ai.failproof.failproofaid.${user}`;
|
|
23226
|
+
}
|
|
23227
|
+
function launchdPlistPath(user = serviceUser()) {
|
|
23228
|
+
return `/Library/LaunchDaemons/${launchdLabel(user)}.plist`;
|
|
22853
23229
|
}
|
|
22854
23230
|
function legacyLaunchAgentPlistPath() {
|
|
22855
|
-
return
|
|
23231
|
+
return resolve20(homedir34(), "Library", "LaunchAgents", "ai.failproof.failproofaid.plist");
|
|
23232
|
+
}
|
|
23233
|
+
function legacySharedLaunchDaemonPath() {
|
|
23234
|
+
return "/Library/LaunchDaemons/ai.failproof.failproofaid.plist";
|
|
22856
23235
|
}
|
|
22857
23236
|
function daemonServiceFilePath() {
|
|
22858
23237
|
if (!isDaemonSupportedPlatform())
|
|
22859
23238
|
return null;
|
|
22860
23239
|
return process.platform === "linux" ? systemdUnitPath() : launchdPlistPath();
|
|
22861
23240
|
}
|
|
23241
|
+
function primeElevation() {
|
|
23242
|
+
if (typeof process.getuid === "function" && process.getuid() === 0)
|
|
23243
|
+
return true;
|
|
23244
|
+
if (canElevate())
|
|
23245
|
+
return true;
|
|
23246
|
+
try {
|
|
23247
|
+
execFileSync4("sudo", ["-v"], { stdio: "inherit", timeout: SUDO_PROMPT_TIMEOUT_MS });
|
|
23248
|
+
return canElevate();
|
|
23249
|
+
} catch {
|
|
23250
|
+
return false;
|
|
23251
|
+
}
|
|
23252
|
+
}
|
|
22862
23253
|
function canElevate() {
|
|
22863
23254
|
if (typeof process.getuid === "function" && process.getuid() === 0)
|
|
22864
23255
|
return true;
|
|
@@ -22875,19 +23266,20 @@ function runPrivileged(command, args) {
|
|
|
22875
23266
|
execFileSync4(cmd, argv, { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
|
|
22876
23267
|
}
|
|
22877
23268
|
function writePrivilegedFile(destination, contents, mode = "0644") {
|
|
22878
|
-
const
|
|
23269
|
+
const stagingDir = mkdtempSync(resolve20(tmpdir(), "failproofaid-stage-"));
|
|
23270
|
+
const staging = resolve20(stagingDir, "service-definition");
|
|
22879
23271
|
try {
|
|
22880
23272
|
writeFileSync10(staging, contents, "utf8");
|
|
22881
23273
|
runPrivileged("install", ["-m", mode, staging, destination]);
|
|
22882
23274
|
} finally {
|
|
22883
|
-
|
|
23275
|
+
rmSync6(stagingDir, { recursive: true, force: true });
|
|
22884
23276
|
}
|
|
22885
23277
|
}
|
|
22886
23278
|
function removeLegacyUserService() {
|
|
22887
23279
|
try {
|
|
22888
23280
|
if (process.platform === "linux") {
|
|
22889
23281
|
const legacy = legacySystemdUserUnitPath();
|
|
22890
|
-
if (!
|
|
23282
|
+
if (!existsSync24(legacy))
|
|
22891
23283
|
return;
|
|
22892
23284
|
try {
|
|
22893
23285
|
execFileSync4("systemctl", ["--user", "disable", "--now", "failproofaid.service"], {
|
|
@@ -22903,16 +23295,23 @@ function removeLegacyUserService() {
|
|
|
22903
23295
|
});
|
|
22904
23296
|
} catch {}
|
|
22905
23297
|
} else {
|
|
22906
|
-
const
|
|
22907
|
-
if (
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
}
|
|
22914
|
-
|
|
22915
|
-
|
|
23298
|
+
const legacyAgent = legacyLaunchAgentPlistPath();
|
|
23299
|
+
if (existsSync24(legacyAgent)) {
|
|
23300
|
+
try {
|
|
23301
|
+
execFileSync4("launchctl", ["unload", "-w", legacyAgent], {
|
|
23302
|
+
stdio: "ignore",
|
|
23303
|
+
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23304
|
+
});
|
|
23305
|
+
} catch {}
|
|
23306
|
+
unlinkSync3(legacyAgent);
|
|
23307
|
+
}
|
|
23308
|
+
const legacyDaemon = legacySharedLaunchDaemonPath();
|
|
23309
|
+
if (existsSync24(legacyDaemon)) {
|
|
23310
|
+
try {
|
|
23311
|
+
runPrivileged("launchctl", ["unload", "-w", legacyDaemon]);
|
|
23312
|
+
} catch {}
|
|
23313
|
+
runPrivileged("rm", ["-f", legacyDaemon]);
|
|
23314
|
+
}
|
|
22916
23315
|
}
|
|
22917
23316
|
} catch (err) {
|
|
22918
23317
|
hookLogWarn(`could not remove the legacy user-scope daemon: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -22933,7 +23332,7 @@ User=${user}
|
|
|
22933
23332
|
# failproofaid is user-scope by construction and refuses to start without
|
|
22934
23333
|
# HOME ("HOME is not set; failproofaid is user-scope only"), so the one
|
|
22935
23334
|
# variable it cannot do without is not left to a version-dependent default.
|
|
22936
|
-
Environment="HOME=${
|
|
23335
|
+
Environment="HOME=${homedir34()}"
|
|
22937
23336
|
${envLine}ExecStart=${binaryPath}
|
|
22938
23337
|
Restart=on-failure
|
|
22939
23338
|
RestartSec=2
|
|
@@ -22960,7 +23359,7 @@ function launchdPlistContents(binaryPath, logDir2, workerCmd) {
|
|
|
22960
23359
|
<plist version="1.0">
|
|
22961
23360
|
<dict>
|
|
22962
23361
|
<key>Label</key>
|
|
22963
|
-
<string>${
|
|
23362
|
+
<string>${escapeXml(launchdLabel())}</string>
|
|
22964
23363
|
<key>ProgramArguments</key>
|
|
22965
23364
|
<array>
|
|
22966
23365
|
<string>${escapeXml(binaryPath)}</string>
|
|
@@ -22976,9 +23375,9 @@ function launchdPlistContents(binaryPath, logDir2, workerCmd) {
|
|
|
22976
23375
|
<key>UserName</key>
|
|
22977
23376
|
<string>${escapeXml(serviceUser())}</string>
|
|
22978
23377
|
${envBlock} <key>StandardOutPath</key>
|
|
22979
|
-
<string>${escapeXml(
|
|
23378
|
+
<string>${escapeXml(resolve20(logDir2, "failproofaid.log"))}</string>
|
|
22980
23379
|
<key>StandardErrorPath</key>
|
|
22981
|
-
<string>${escapeXml(
|
|
23380
|
+
<string>${escapeXml(resolve20(logDir2, "failproofaid.err.log"))}</string>
|
|
22982
23381
|
</dict>
|
|
22983
23382
|
</plist>
|
|
22984
23383
|
`;
|
|
@@ -23009,7 +23408,7 @@ async function installDaemonService() {
|
|
|
23009
23408
|
if (!canElevate()) {
|
|
23010
23409
|
return {
|
|
23011
23410
|
installed: false,
|
|
23012
|
-
reason: "root privileges are required to install the failproofaid system service, and sudo
|
|
23411
|
+
reason: "root privileges are required to install the failproofaid system service, and sudo credentials were not available. " + "Re-run `failproofai config` and approve the sudo prompt — do NOT run the CLI itself under sudo, which would " + "configure root's account instead of yours. To install by hand: " + daemonInstallCommands(binaryPath, workerCmd).join(" && ")
|
|
23013
23412
|
};
|
|
23014
23413
|
}
|
|
23015
23414
|
removeLegacyUserService();
|
|
@@ -23020,77 +23419,874 @@ async function installDaemonService() {
|
|
|
23020
23419
|
runPrivileged("systemctl", ["enable", "--now", systemdUnitName()]);
|
|
23021
23420
|
} else {
|
|
23022
23421
|
const plistPath = launchdPlistPath();
|
|
23023
|
-
const logDir2 =
|
|
23024
|
-
|
|
23422
|
+
const logDir2 = resolve20(homedir34(), ".failproofai", "logs");
|
|
23423
|
+
mkdirSync12(logDir2, { recursive: true });
|
|
23424
|
+
try {
|
|
23425
|
+
runPrivileged("launchctl", ["unload", plistPath]);
|
|
23426
|
+
} catch {}
|
|
23427
|
+
writePrivilegedFile(plistPath, launchdPlistContents(binaryPath, logDir2, workerCmd));
|
|
23428
|
+
runPrivileged("launchctl", ["load", "-w", plistPath]);
|
|
23429
|
+
}
|
|
23430
|
+
} catch (err) {
|
|
23431
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23432
|
+
hookLogWarn(`daemon service install failed: ${msg}`);
|
|
23433
|
+
return { installed: false, reason: msg };
|
|
23434
|
+
}
|
|
23435
|
+
if (!await waitForDaemonRunning()) {
|
|
23436
|
+
const reason = `failproofaid was installed but did not reach a running state within ${SERVICE_START_TIMEOUT_MS}ms (status: ${daemonServiceStatus()})`;
|
|
23437
|
+
hookLogWarn(`daemon service install failed: ${reason}`);
|
|
23438
|
+
return { installed: false, reason };
|
|
23439
|
+
}
|
|
23440
|
+
return { installed: true };
|
|
23441
|
+
}
|
|
23442
|
+
async function waitForDaemonRunning() {
|
|
23443
|
+
const deadline = Date.now() + SERVICE_START_TIMEOUT_MS;
|
|
23444
|
+
for (;; ) {
|
|
23445
|
+
if (daemonServiceStatus() === "running")
|
|
23446
|
+
break;
|
|
23447
|
+
if (Date.now() >= deadline)
|
|
23448
|
+
return false;
|
|
23449
|
+
await new Promise((r) => setTimeout(r, SERVICE_START_POLL_MS));
|
|
23450
|
+
}
|
|
23451
|
+
await new Promise((r) => setTimeout(r, SERVICE_SETTLE_MS));
|
|
23452
|
+
return daemonServiceStatus() === "running";
|
|
23453
|
+
}
|
|
23454
|
+
function daemonServiceStatus() {
|
|
23455
|
+
if (!isDaemonSupportedPlatform())
|
|
23456
|
+
return "unsupported-platform";
|
|
23457
|
+
if (process.platform === "linux") {
|
|
23458
|
+
if (!existsSync24(systemdUnitPath()))
|
|
23459
|
+
return "not-installed";
|
|
23460
|
+
try {
|
|
23461
|
+
const out = execFileSync4("systemctl", ["is-active", systemdUnitName()], {
|
|
23462
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23463
|
+
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23464
|
+
}).toString().trim();
|
|
23465
|
+
return out === "active" ? "running" : "stopped";
|
|
23466
|
+
} catch {
|
|
23467
|
+
return "stopped";
|
|
23468
|
+
}
|
|
23469
|
+
}
|
|
23470
|
+
const plistPath = launchdPlistPath();
|
|
23471
|
+
if (!existsSync24(plistPath))
|
|
23472
|
+
return "not-installed";
|
|
23473
|
+
try {
|
|
23474
|
+
const root = typeof process.getuid === "function" && process.getuid() === 0;
|
|
23475
|
+
const args = ["print", `system/${launchdLabel()}`];
|
|
23476
|
+
const out = root ? execFileSync4("launchctl", args, {
|
|
23477
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23478
|
+
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23479
|
+
}).toString() : execFileSync4("sudo", ["-n", "launchctl", ...args], {
|
|
23480
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23481
|
+
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23482
|
+
}).toString();
|
|
23483
|
+
return /state\s*=\s*running/.test(out) ? "running" : "stopped";
|
|
23484
|
+
} catch {
|
|
23485
|
+
return "stopped";
|
|
23486
|
+
}
|
|
23487
|
+
}
|
|
23488
|
+
var SERVICE_CMD_TIMEOUT_MS = 1e4, SERVICE_START_TIMEOUT_MS = 5000, SERVICE_START_POLL_MS = 100, SERVICE_SETTLE_MS = 750, SUDO_PROMPT_TIMEOUT_MS = 120000;
|
|
23489
|
+
var init_daemon_service = __esm(() => {
|
|
23490
|
+
init_hook_logger();
|
|
23491
|
+
init_hooks_config();
|
|
23492
|
+
init_daemon_download();
|
|
23493
|
+
});
|
|
23494
|
+
|
|
23495
|
+
// src/hooks/daemon-client.ts
|
|
23496
|
+
import { resolve as resolve21 } from "node:path";
|
|
23497
|
+
import { homedir as homedir35 } from "node:os";
|
|
23498
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
23499
|
+
function daemonSocketPresent2() {
|
|
23500
|
+
try {
|
|
23501
|
+
return existsSync25(socketPath2());
|
|
23502
|
+
} catch {
|
|
23503
|
+
return false;
|
|
23504
|
+
}
|
|
23505
|
+
}
|
|
23506
|
+
function socketPath2() {
|
|
23507
|
+
if (process.env.FAILPROOFAI_DAEMON_SOCKET)
|
|
23508
|
+
return process.env.FAILPROOFAI_DAEMON_SOCKET;
|
|
23509
|
+
return resolve21(homedir35(), ".failproofai", "run", "failproofaid.sock");
|
|
23510
|
+
}
|
|
23511
|
+
var MAX_FRAME_LEN2;
|
|
23512
|
+
var init_daemon_client2 = __esm(() => {
|
|
23513
|
+
init_hooks_config();
|
|
23514
|
+
MAX_FRAME_LEN2 = 16 * 1024 * 1024;
|
|
23515
|
+
});
|
|
23516
|
+
|
|
23517
|
+
// src/hooks/collector-config.ts
|
|
23518
|
+
import {
|
|
23519
|
+
writeFileSync as writeFileSync11,
|
|
23520
|
+
mkdirSync as mkdirSync13,
|
|
23521
|
+
existsSync as existsSync26,
|
|
23522
|
+
chmodSync as chmodSync4,
|
|
23523
|
+
statSync as statSync18,
|
|
23524
|
+
readFileSync as readFileSync20,
|
|
23525
|
+
rmSync as rmSync7
|
|
23526
|
+
} from "node:fs";
|
|
23527
|
+
import { join as join32 } from "node:path";
|
|
23528
|
+
import { homedir as homedir36 } from "node:os";
|
|
23529
|
+
function failproofaiHome() {
|
|
23530
|
+
return process.env.FAILPROOFAI_HOME || join32(homedir36(), ".failproofai");
|
|
23531
|
+
}
|
|
23532
|
+
function ingestPath() {
|
|
23533
|
+
return join32(failproofaiHome(), "ingest.json");
|
|
23534
|
+
}
|
|
23535
|
+
function hasIngestCredential() {
|
|
23536
|
+
return existsSync26(ingestPath());
|
|
23537
|
+
}
|
|
23538
|
+
function readIngestCredential() {
|
|
23539
|
+
try {
|
|
23540
|
+
const raw = JSON.parse(readFileSync20(ingestPath(), "utf8"));
|
|
23541
|
+
if (typeof raw.url !== "string" || typeof raw.key !== "string")
|
|
23542
|
+
return null;
|
|
23543
|
+
if (!raw.url || !raw.key)
|
|
23544
|
+
return null;
|
|
23545
|
+
return { url: raw.url, key: raw.key };
|
|
23546
|
+
} catch {
|
|
23547
|
+
return null;
|
|
23548
|
+
}
|
|
23549
|
+
}
|
|
23550
|
+
function clearIngestCredential() {
|
|
23551
|
+
try {
|
|
23552
|
+
rmSync7(ingestPath());
|
|
23553
|
+
return true;
|
|
23554
|
+
} catch {
|
|
23555
|
+
return false;
|
|
23556
|
+
}
|
|
23557
|
+
}
|
|
23558
|
+
function writeIngestCredential(cred) {
|
|
23559
|
+
const home = failproofaiHome();
|
|
23560
|
+
mkdirSync13(home, { recursive: true });
|
|
23561
|
+
try {
|
|
23562
|
+
const mode = statSync18(home).mode & 511;
|
|
23563
|
+
if (mode & 63)
|
|
23564
|
+
chmodSync4(home, 448);
|
|
23565
|
+
} catch {}
|
|
23566
|
+
const path3 = ingestPath();
|
|
23567
|
+
writeFileSync11(path3, JSON.stringify(cred, null, 2) + `
|
|
23568
|
+
`, { mode: 384 });
|
|
23569
|
+
try {
|
|
23570
|
+
chmodSync4(path3, 384);
|
|
23571
|
+
} catch {}
|
|
23572
|
+
return path3;
|
|
23573
|
+
}
|
|
23574
|
+
function writeCollectorSettings(settings) {
|
|
23575
|
+
const config = readHooksConfig();
|
|
23576
|
+
writeHooksConfig({
|
|
23577
|
+
...config,
|
|
23578
|
+
collector: {
|
|
23579
|
+
sessions: settings.sessions,
|
|
23580
|
+
hooks: settings.hooks,
|
|
23581
|
+
hooksVerbosity: settings.hooksVerbosity ?? "decisions",
|
|
23582
|
+
redact: settings.redact ?? "minimal",
|
|
23583
|
+
environment: settings.environment ?? "local"
|
|
23584
|
+
}
|
|
23585
|
+
});
|
|
23586
|
+
}
|
|
23587
|
+
async function validateIngestKey(cred, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
23588
|
+
const controller = new AbortController;
|
|
23589
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
23590
|
+
try {
|
|
23591
|
+
const res = await fetchImpl(cred.url, {
|
|
23592
|
+
method: "POST",
|
|
23593
|
+
headers: {
|
|
23594
|
+
Authorization: `Bearer ${cred.key}`,
|
|
23595
|
+
"Content-Type": "application/x-ndjson"
|
|
23596
|
+
},
|
|
23597
|
+
body: "",
|
|
23598
|
+
redirect: "manual",
|
|
23599
|
+
signal: controller.signal
|
|
23600
|
+
});
|
|
23601
|
+
if (res.status >= 300 && res.status < 400) {
|
|
23602
|
+
return {
|
|
23603
|
+
ok: false,
|
|
23604
|
+
reason: "that URL redirects rather than accepting events — it looks like the dashboard, not the ingest API"
|
|
23605
|
+
};
|
|
23606
|
+
}
|
|
23607
|
+
if (res.status === 401 || res.status === 403) {
|
|
23608
|
+
return { ok: false, reason: `the server rejected that key (${res.status})` };
|
|
23609
|
+
}
|
|
23610
|
+
if (res.status === 404) {
|
|
23611
|
+
return { ok: false, reason: `no ingest endpoint at that URL (404)` };
|
|
23612
|
+
}
|
|
23613
|
+
if (!res.ok) {
|
|
23614
|
+
return { ok: false, reason: `the server answered ${res.status}` };
|
|
23615
|
+
}
|
|
23616
|
+
let body;
|
|
23617
|
+
try {
|
|
23618
|
+
body = await res.json();
|
|
23619
|
+
} catch {
|
|
23620
|
+
return {
|
|
23621
|
+
ok: false,
|
|
23622
|
+
reason: "that URL answered without an ingest response — it is not the events endpoint"
|
|
23623
|
+
};
|
|
23624
|
+
}
|
|
23625
|
+
const shaped = typeof body === "object" && body !== null && typeof body.accepted === "number";
|
|
23626
|
+
if (!shaped) {
|
|
23627
|
+
return {
|
|
23628
|
+
ok: false,
|
|
23629
|
+
reason: "that URL answered, but not with an ingest response — it is not the events endpoint"
|
|
23630
|
+
};
|
|
23631
|
+
}
|
|
23632
|
+
return { ok: true };
|
|
23633
|
+
} catch (err) {
|
|
23634
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23635
|
+
return { ok: false, reason: `could not reach the server (${msg})` };
|
|
23636
|
+
} finally {
|
|
23637
|
+
clearTimeout(timer);
|
|
23638
|
+
}
|
|
23639
|
+
}
|
|
23640
|
+
var DEFAULT_INGEST_URL = "https://server.befailproof.ai/events";
|
|
23641
|
+
var init_collector_config = __esm(() => {
|
|
23642
|
+
init_hooks_config();
|
|
23643
|
+
});
|
|
23644
|
+
|
|
23645
|
+
// src/hooks/cloud-connection.ts
|
|
23646
|
+
function ingestUrlFor(baseUrl2) {
|
|
23647
|
+
return `${baseUrl2.replace(/\/$/, "")}${INGEST_PATH}`;
|
|
23648
|
+
}
|
|
23649
|
+
function cloudBaseFor(url) {
|
|
23650
|
+
const trimmed = url.replace(/\/$/, "");
|
|
23651
|
+
return trimmed.endsWith(INGEST_PATH) ? trimmed.slice(0, -INGEST_PATH.length) : trimmed;
|
|
23652
|
+
}
|
|
23653
|
+
async function connectToCloud(input) {
|
|
23654
|
+
const creds = {
|
|
23655
|
+
url: input.url,
|
|
23656
|
+
machineId: input.machineId,
|
|
23657
|
+
token: input.token
|
|
23658
|
+
};
|
|
23659
|
+
const verifyPolicy = input.verifyPolicy ?? verifyCloudCredentials;
|
|
23660
|
+
const verifyIngest = input.verifyIngest ?? validateIngestKey;
|
|
23661
|
+
const ingestUrl = ingestUrlFor(input.url);
|
|
23662
|
+
const [policyResult, ingestResult] = await Promise.all([
|
|
23663
|
+
verifyPolicy(creds),
|
|
23664
|
+
verifyIngest({ url: ingestUrl, key: input.token })
|
|
23665
|
+
]);
|
|
23666
|
+
const outcome = {
|
|
23667
|
+
policy: policyResult.ok ? { ok: true, policyCount: policyResult.policyCount, generation: policyResult.generation } : { ok: false, reason: policyResult.reason },
|
|
23668
|
+
ingest: ingestResult.ok ? { ok: true } : { ok: false, reason: ingestResult.reason },
|
|
23669
|
+
anyConfigured: false
|
|
23670
|
+
};
|
|
23671
|
+
if (policyResult.ok) {
|
|
23672
|
+
writeCloudCredentials(creds);
|
|
23673
|
+
outcome.anyConfigured = true;
|
|
23674
|
+
}
|
|
23675
|
+
if (ingestResult.ok) {
|
|
23676
|
+
writeIngestCredential({ url: ingestUrl, key: input.token });
|
|
23677
|
+
const settings = {
|
|
23678
|
+
hooks: true,
|
|
23679
|
+
sessions: input.sessions === true,
|
|
23680
|
+
environment: input.environment
|
|
23681
|
+
};
|
|
23682
|
+
writeCollectorSettings(settings);
|
|
23683
|
+
outcome.anyConfigured = true;
|
|
23684
|
+
}
|
|
23685
|
+
return outcome;
|
|
23686
|
+
}
|
|
23687
|
+
function describeOutcome(outcome, machineId, url) {
|
|
23688
|
+
const lines = [];
|
|
23689
|
+
if (outcome.policy.ok && outcome.ingest.ok) {
|
|
23690
|
+
lines.push(`Connected to ${url} as ${machineId}.`);
|
|
23691
|
+
const n = outcome.policy.policyCount ?? 0;
|
|
23692
|
+
lines.push(` Policy ${n} polic${n === 1 ? "y" : "ies"} assigned (generation ${outcome.policy.generation ?? 0}).`);
|
|
23693
|
+
lines.push(` Dashboard hook activity will be sent to ${ingestUrlFor(url)}.`);
|
|
23694
|
+
return lines;
|
|
23695
|
+
}
|
|
23696
|
+
if (outcome.policy.ok && !outcome.ingest.ok) {
|
|
23697
|
+
const n = outcome.policy.policyCount ?? 0;
|
|
23698
|
+
lines.push(`Connected to ${url} as ${machineId} for policy only.`);
|
|
23699
|
+
lines.push(` Policy ${n} polic${n === 1 ? "y" : "ies"} assigned (generation ${outcome.policy.generation ?? 0}).`);
|
|
23700
|
+
lines.push(` Dashboard NOT configured: ${outcome.ingest.reason}`);
|
|
23701
|
+
lines.push("");
|
|
23702
|
+
lines.push(" Enforcement works. Nothing will appear in the dashboard until this");
|
|
23703
|
+
lines.push(" key also carries `events:add`, or you re-run --connect with one that does.");
|
|
23704
|
+
return lines;
|
|
23705
|
+
}
|
|
23706
|
+
if (!outcome.policy.ok && outcome.ingest.ok) {
|
|
23707
|
+
lines.push(`Connected to ${url} as ${machineId} for dashboard reporting only.`);
|
|
23708
|
+
lines.push(` Dashboard hook activity will be sent to ${ingestUrlFor(url)}.`);
|
|
23709
|
+
lines.push(` Cloud policy NOT configured: ${outcome.policy.reason}`);
|
|
23710
|
+
lines.push("");
|
|
23711
|
+
lines.push(" This machine keeps enforcing its LOCAL policies and will report what");
|
|
23712
|
+
lines.push(" they decide, but will not receive centrally-managed ones.");
|
|
23713
|
+
return lines;
|
|
23714
|
+
}
|
|
23715
|
+
lines.push(`Could not connect to ${url}.`);
|
|
23716
|
+
lines.push(` Policy: ${outcome.policy.reason}`);
|
|
23717
|
+
lines.push(` Dashboard: ${outcome.ingest.reason}`);
|
|
23718
|
+
return lines;
|
|
23719
|
+
}
|
|
23720
|
+
function configuredPaths(outcome) {
|
|
23721
|
+
const paths = [];
|
|
23722
|
+
if (outcome.policy.ok)
|
|
23723
|
+
paths.push(cloudCredentialPath());
|
|
23724
|
+
if (outcome.ingest.ok)
|
|
23725
|
+
paths.push(ingestPath());
|
|
23726
|
+
return paths;
|
|
23727
|
+
}
|
|
23728
|
+
var INGEST_PATH = "/events";
|
|
23729
|
+
var init_cloud_connection = __esm(() => {
|
|
23730
|
+
init_cloud_enrollment();
|
|
23731
|
+
init_collector_config();
|
|
23732
|
+
});
|
|
23733
|
+
|
|
23734
|
+
// src/hooks/cloud-enrollment-cli.ts
|
|
23735
|
+
var exports_cloud_enrollment_cli = {};
|
|
23736
|
+
__export(exports_cloud_enrollment_cli, {
|
|
23737
|
+
runDisconnectCommand: () => runDisconnectCommand,
|
|
23738
|
+
runConnectCommand: () => runConnectCommand,
|
|
23739
|
+
connectionStatusLines: () => connectionStatusLines
|
|
23740
|
+
});
|
|
23741
|
+
function daemonWarning(status) {
|
|
23742
|
+
if (status === "running")
|
|
23743
|
+
return [];
|
|
23744
|
+
if (daemonSocketPresent2()) {
|
|
23745
|
+
return [
|
|
23746
|
+
"",
|
|
23747
|
+
" A daemon is running outside the service manager, so policy is being pulled.",
|
|
23748
|
+
" Install it as a service to survive reboot and logout: failproofai config"
|
|
23749
|
+
];
|
|
23750
|
+
}
|
|
23751
|
+
if (status === "unsupported-platform") {
|
|
23752
|
+
return [
|
|
23753
|
+
"",
|
|
23754
|
+
"! failproofaid does not run on this platform yet, so nothing will be pulled.",
|
|
23755
|
+
" The credentials are stored and will work on a supported machine."
|
|
23756
|
+
];
|
|
23757
|
+
}
|
|
23758
|
+
if (status === "not-installed") {
|
|
23759
|
+
return [
|
|
23760
|
+
"",
|
|
23761
|
+
"! failproofaid is not installed as a service, so nothing will be pulled yet.",
|
|
23762
|
+
" Install it with: failproofai config"
|
|
23763
|
+
];
|
|
23764
|
+
}
|
|
23765
|
+
return [
|
|
23766
|
+
"",
|
|
23767
|
+
"! failproofaid is installed but not running, so nothing will be pulled yet.",
|
|
23768
|
+
" Check it with: failproofai config --status"
|
|
23769
|
+
];
|
|
23770
|
+
}
|
|
23771
|
+
async function runConnectCommand(opts) {
|
|
23772
|
+
if (!opts.url)
|
|
23773
|
+
return { exitCode: 1, lines: ["--connect needs a URL, e.g. --connect https://be.failproof.ai"] };
|
|
23774
|
+
if (!opts.token) {
|
|
23775
|
+
return {
|
|
23776
|
+
exitCode: 1,
|
|
23777
|
+
lines: [
|
|
23778
|
+
"--connect needs a machine token: --token <key>",
|
|
23779
|
+
"",
|
|
23780
|
+
"Create an API key carrying only the `policies:pull` permission for this",
|
|
23781
|
+
"machine. Do not use an admin key."
|
|
23782
|
+
]
|
|
23783
|
+
};
|
|
23784
|
+
}
|
|
23785
|
+
const validated = validateCloudUrl(cloudBaseFor(opts.url));
|
|
23786
|
+
if (!validated.ok)
|
|
23787
|
+
return { exitCode: 1, lines: [validated.reason] };
|
|
23788
|
+
const machineId = (opts.machineId ?? opts.defaultMachineId ?? "").trim();
|
|
23789
|
+
if (!machineId) {
|
|
23790
|
+
return { exitCode: 1, lines: ["Could not determine a machine id — pass one with --machine-id <id>."] };
|
|
23791
|
+
}
|
|
23792
|
+
const outcome = await connectToCloud({
|
|
23793
|
+
url: validated.url,
|
|
23794
|
+
token: opts.token,
|
|
23795
|
+
machineId,
|
|
23796
|
+
sessions: opts.sessions,
|
|
23797
|
+
environment: opts.environment,
|
|
23798
|
+
verifyPolicy: opts.verify,
|
|
23799
|
+
verifyIngest: opts.verifyIngest
|
|
23800
|
+
});
|
|
23801
|
+
if (!outcome.anyConfigured) {
|
|
23802
|
+
return { exitCode: 1, lines: describeOutcome(outcome, machineId, validated.url) };
|
|
23803
|
+
}
|
|
23804
|
+
const exitCode = outcome.policy.ok ? 0 : 1;
|
|
23805
|
+
const status = (opts.daemonStatus ?? daemonServiceStatus)();
|
|
23806
|
+
const paths = configuredPaths(outcome);
|
|
23807
|
+
const lines = [
|
|
23808
|
+
...describeOutcome(outcome, machineId, validated.url),
|
|
23809
|
+
` Token ${maskToken(opts.token)} stored in ${paths.join(" and ")} (owner-only).`
|
|
23810
|
+
];
|
|
23811
|
+
if (outcome.ingest.ok && opts.sessions !== true) {
|
|
23812
|
+
lines.push(" Session transcripts are NOT being sent. Add --send-transcripts to include them.");
|
|
23813
|
+
}
|
|
23814
|
+
lines.push(...daemonWarning(status));
|
|
23815
|
+
return { exitCode, lines };
|
|
23816
|
+
}
|
|
23817
|
+
function runDisconnectCommand() {
|
|
23818
|
+
const existing = readCloudCredentials();
|
|
23819
|
+
const hadIngest = hasIngestCredential();
|
|
23820
|
+
const removed = clearCloudCredentials();
|
|
23821
|
+
const removedIngest = clearIngestCredential();
|
|
23822
|
+
if (!removed && !existing && !hadIngest) {
|
|
23823
|
+
return { exitCode: 0, lines: ["This machine is not connected to Failproof Cloud."] };
|
|
23824
|
+
}
|
|
23825
|
+
const lines = [
|
|
23826
|
+
"Disconnected from Failproof Cloud.",
|
|
23827
|
+
"",
|
|
23828
|
+
" Cloud-managed policies already on disk stop being refreshed. Local",
|
|
23829
|
+
" builtin, custom and convention policies are unaffected."
|
|
23830
|
+
];
|
|
23831
|
+
if (removedIngest) {
|
|
23832
|
+
lines.push(" Hook activity and transcripts stop being sent.");
|
|
23833
|
+
}
|
|
23834
|
+
return { exitCode: 0, lines };
|
|
23835
|
+
}
|
|
23836
|
+
function connectionStatusLines(daemonStatus = daemonServiceStatus) {
|
|
23837
|
+
const envUrl = process.env.FAILPROOFAI_CLOUD_URL;
|
|
23838
|
+
if (envUrl) {
|
|
23839
|
+
return [
|
|
23840
|
+
`Cloud: configured by environment (${envUrl}), which takes precedence over ${cloudCredentialPath()}.`
|
|
23841
|
+
];
|
|
23842
|
+
}
|
|
23843
|
+
const creds = readCloudCredentials();
|
|
23844
|
+
const ingest = readIngestCredential();
|
|
23845
|
+
if (!creds && !ingest)
|
|
23846
|
+
return ["Cloud: not connected."];
|
|
23847
|
+
const lines = [];
|
|
23848
|
+
if (creds) {
|
|
23849
|
+
lines.push(`Cloud: connected to ${creds.url} as ${creds.machineId} (token ${maskToken(creds.token)}).`);
|
|
23850
|
+
lines.push(` Policy pulling centrally-managed policies.`);
|
|
23851
|
+
} else {
|
|
23852
|
+
lines.push(`Cloud: connected to ${cloudBaseFor(ingest.url)} for reporting only.`);
|
|
23853
|
+
lines.push(` Policy NOT pulling — this machine enforces only its local policies.`);
|
|
23854
|
+
}
|
|
23855
|
+
if (ingest) {
|
|
23856
|
+
lines.push(` Dashboard sending hook activity to ${ingest.url}.`);
|
|
23857
|
+
} else {
|
|
23858
|
+
lines.push(` Dashboard NOT sending — nothing from this machine appears in the dashboard.`);
|
|
23859
|
+
lines.push(` Re-run: failproofai config --connect ${creds.url} --token <key>`);
|
|
23860
|
+
}
|
|
23861
|
+
lines.push(...daemonWarning(daemonStatus()).map((l) => l === "" ? "" : ` ${l.trim()}`));
|
|
23862
|
+
return lines;
|
|
23863
|
+
}
|
|
23864
|
+
var init_cloud_enrollment_cli = __esm(() => {
|
|
23865
|
+
init_cloud_enrollment();
|
|
23866
|
+
init_daemon_service();
|
|
23867
|
+
init_daemon_client2();
|
|
23868
|
+
init_cloud_connection();
|
|
23869
|
+
init_collector_config();
|
|
23870
|
+
});
|
|
23871
|
+
|
|
23872
|
+
// src/hooks/session-pause-cli.ts
|
|
23873
|
+
var exports_session_pause_cli = {};
|
|
23874
|
+
__export(exports_session_pause_cli, {
|
|
23875
|
+
runPauseCommand: () => runPauseCommand,
|
|
23876
|
+
effectiveCeilingMs: () => effectiveCeilingMs
|
|
23877
|
+
});
|
|
23878
|
+
function effectiveCeilingMs(cwd) {
|
|
23879
|
+
try {
|
|
23880
|
+
const configured = readMergedHooksConfig(cwd).maxPauseMs;
|
|
23881
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
|
|
23882
|
+
return Math.min(configured, PAUSE_CEILING_MS);
|
|
23883
|
+
}
|
|
23884
|
+
} catch {}
|
|
23885
|
+
return PAUSE_CEILING_MS;
|
|
23886
|
+
}
|
|
23887
|
+
function describe(pause, now) {
|
|
23888
|
+
const remaining = formatDuration2(Math.max(0, pause.expiresAt - now));
|
|
23889
|
+
const at = new Date(pause.expiresAt).toLocaleTimeString();
|
|
23890
|
+
const where = pause.cwd ? ` · ${pause.cwd}` : "";
|
|
23891
|
+
return ` ${pause.sessionId} ${remaining} left (until ${at})${where}`;
|
|
23892
|
+
}
|
|
23893
|
+
function runPauseCommand(opts) {
|
|
23894
|
+
const now = opts.now ?? Date.now();
|
|
23895
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
23896
|
+
if (opts.action === "status") {
|
|
23897
|
+
const active = listActivePauses(now);
|
|
23898
|
+
if (active.length === 0) {
|
|
23899
|
+
return { exitCode: 0, lines: ["Enforcement is active — nothing is paused."], affected: 0 };
|
|
23900
|
+
}
|
|
23901
|
+
return {
|
|
23902
|
+
exitCode: 0,
|
|
23903
|
+
lines: [
|
|
23904
|
+
`Enforcement paused for ${active.length} session${active.length === 1 ? "" : "s"}:`,
|
|
23905
|
+
...active.map((p) => describe(p, now)),
|
|
23906
|
+
"",
|
|
23907
|
+
"Cloud-managed policies keep enforcing regardless.",
|
|
23908
|
+
"Resume early with: failproofai config --resume"
|
|
23909
|
+
],
|
|
23910
|
+
affected: active.length
|
|
23911
|
+
};
|
|
23912
|
+
}
|
|
23913
|
+
if (opts.action === "resume") {
|
|
23914
|
+
if (opts.all) {
|
|
23915
|
+
const active = listActivePauses(now);
|
|
23916
|
+
let cleared2 = 0;
|
|
23917
|
+
for (const pause2 of active)
|
|
23918
|
+
if (clearPause(pause2.sessionId))
|
|
23919
|
+
cleared2++;
|
|
23920
|
+
return {
|
|
23921
|
+
exitCode: 0,
|
|
23922
|
+
lines: [cleared2 === 0 ? "Nothing was paused." : `Resumed enforcement for ${cleared2} session(s).`],
|
|
23923
|
+
affected: cleared2
|
|
23924
|
+
};
|
|
23925
|
+
}
|
|
23926
|
+
const sessionId2 = opts.sessionId ?? resolveSessionForCwd(cwd, undefined, now);
|
|
23927
|
+
if (!sessionId2) {
|
|
23928
|
+
return { exitCode: 0, lines: ["Nothing was paused for this directory."], affected: 0 };
|
|
23929
|
+
}
|
|
23930
|
+
const cleared = clearPause(sessionId2);
|
|
23931
|
+
return {
|
|
23932
|
+
exitCode: 0,
|
|
23933
|
+
lines: [cleared ? "Enforcement resumed." : "Nothing was paused for that session."],
|
|
23934
|
+
affected: cleared ? 1 : 0
|
|
23935
|
+
};
|
|
23936
|
+
}
|
|
23937
|
+
let durationMs;
|
|
23938
|
+
try {
|
|
23939
|
+
durationMs = parsePauseDuration(opts.duration, effectiveCeilingMs(cwd));
|
|
23940
|
+
} catch (err) {
|
|
23941
|
+
return { exitCode: 1, lines: [err instanceof Error ? err.message : String(err)], affected: 0 };
|
|
23942
|
+
}
|
|
23943
|
+
const sessionId = opts.sessionId ?? resolveSessionForCwd(cwd, undefined, now);
|
|
23944
|
+
if (!sessionId) {
|
|
23945
|
+
return {
|
|
23946
|
+
exitCode: 1,
|
|
23947
|
+
lines: [
|
|
23948
|
+
"No recent agent session found for this directory.",
|
|
23949
|
+
"",
|
|
23950
|
+
"A pause applies to one agent session, and the session id only appears once",
|
|
23951
|
+
"an agent has run at least one tool call here. Either run your agent first,",
|
|
23952
|
+
"or name the session explicitly:",
|
|
23953
|
+
"",
|
|
23954
|
+
" failproofai config --pause --session <id>",
|
|
23955
|
+
"",
|
|
23956
|
+
"Session ids are listed in the dashboard's activity view."
|
|
23957
|
+
],
|
|
23958
|
+
affected: 0
|
|
23959
|
+
};
|
|
23960
|
+
}
|
|
23961
|
+
const existing = readActivePause(sessionId, now);
|
|
23962
|
+
const pause = writePause({ sessionId, durationMs, cwd, setBy: "cli", now });
|
|
23963
|
+
const until = new Date(pause.expiresAt).toLocaleTimeString();
|
|
23964
|
+
return {
|
|
23965
|
+
exitCode: 0,
|
|
23966
|
+
lines: [
|
|
23967
|
+
existing ? `Enforcement pause extended · ${formatDuration2(durationMs)} · resumes at ${until}` : `Enforcement paused · ${formatDuration2(durationMs)} · resumes at ${until}`,
|
|
23968
|
+
"",
|
|
23969
|
+
" Builtin, custom and convention policies are suspended for this session.",
|
|
23970
|
+
" Cloud-managed policies keep enforcing.",
|
|
23971
|
+
"",
|
|
23972
|
+
"It lifts on its own — no action needed. To end it early:",
|
|
23973
|
+
" failproofai config --resume"
|
|
23974
|
+
],
|
|
23975
|
+
affected: 1
|
|
23976
|
+
};
|
|
23977
|
+
}
|
|
23978
|
+
var init_session_pause_cli = __esm(() => {
|
|
23979
|
+
init_session_pause();
|
|
23980
|
+
init_hooks_config();
|
|
23981
|
+
});
|
|
23982
|
+
|
|
23983
|
+
// src/hooks/manager.ts
|
|
23984
|
+
import { execSync as execSync7 } from "node:child_process";
|
|
23985
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
23986
|
+
import { resolve as resolve22, basename as basename6 } from "node:path";
|
|
23987
|
+
import { homedir as homedir37, platform as platform5, arch as arch4, release as release3, hostname as hostname4 } from "node:os";
|
|
23988
|
+
function getSettingsPath2(scope, cwd) {
|
|
23989
|
+
return claudeCode.getSettingsPath(scope, cwd);
|
|
23990
|
+
}
|
|
23991
|
+
function scopeLabel2(scope) {
|
|
23992
|
+
switch (scope) {
|
|
23993
|
+
case "user":
|
|
23994
|
+
return `~/.claude/settings.json`;
|
|
23995
|
+
case "project":
|
|
23996
|
+
return `{cwd}/.claude/settings.json`;
|
|
23997
|
+
case "local":
|
|
23998
|
+
return `{cwd}/.claude/settings.local.json`;
|
|
23999
|
+
}
|
|
24000
|
+
}
|
|
24001
|
+
function resolveFailproofaiBinary2() {
|
|
24002
|
+
const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
|
|
24003
|
+
if (override && override.trim())
|
|
24004
|
+
return override.trim();
|
|
24005
|
+
try {
|
|
24006
|
+
const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
|
|
24007
|
+
const result = execSync7(cmd, { encoding: "utf8" }).trim();
|
|
24008
|
+
return result.split(`
|
|
24009
|
+
`)[0].trim();
|
|
24010
|
+
} catch {
|
|
24011
|
+
throw new CliError(`failproofai binary not found in PATH.
|
|
24012
|
+
` + "Install it globally first: npm install -g failproofai");
|
|
24013
|
+
}
|
|
24014
|
+
}
|
|
24015
|
+
function validatePolicyNames2(names) {
|
|
24016
|
+
const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
|
|
24017
|
+
if (invalid.length > 0) {
|
|
24018
|
+
const validList = [...VALID_POLICY_NAMES2].join(", ");
|
|
24019
|
+
throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
|
|
24020
|
+
` + `Valid policies: ${validList}`);
|
|
24021
|
+
}
|
|
24022
|
+
}
|
|
24023
|
+
function deduplicateScopes2(scopes, cwd) {
|
|
24024
|
+
const seen = new Set;
|
|
24025
|
+
return scopes.filter((s) => {
|
|
24026
|
+
const p = getSettingsPath2(s, cwd);
|
|
24027
|
+
if (seen.has(p))
|
|
24028
|
+
return false;
|
|
24029
|
+
seen.add(p);
|
|
24030
|
+
return true;
|
|
24031
|
+
});
|
|
24032
|
+
}
|
|
24033
|
+
function hooksInstalledInSettings2(scope, cwd) {
|
|
24034
|
+
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
24035
|
+
}
|
|
24036
|
+
async function installHooks2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli, options = {}) {
|
|
24037
|
+
const { replace = false, quiet = false } = options;
|
|
24038
|
+
if (!quiet) {
|
|
24039
|
+
return installHooksImpl2(policyNames, scope, cwd, includeBeta, source, customPoliciesPath, removeCustomHooks, cli, replace);
|
|
24040
|
+
}
|
|
24041
|
+
const origLog = console.log;
|
|
24042
|
+
console.log = () => {};
|
|
24043
|
+
try {
|
|
24044
|
+
return await installHooksImpl2(policyNames, scope, cwd, includeBeta, source, customPoliciesPath, removeCustomHooks, cli, replace);
|
|
24045
|
+
} finally {
|
|
24046
|
+
console.log = origLog;
|
|
24047
|
+
}
|
|
24048
|
+
}
|
|
24049
|
+
async function installHooksImpl2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli, replace = false) {
|
|
24050
|
+
if (policyNames !== undefined && policyNames.length > 0) {
|
|
24051
|
+
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
24052
|
+
if (nonAllNames.length > 0)
|
|
24053
|
+
validatePolicyNames2(nonAllNames);
|
|
24054
|
+
if (policyNames.includes("all") && nonAllNames.length > 0) {
|
|
24055
|
+
throw new CliError(`"all" cannot be combined with specific policy names.
|
|
24056
|
+
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
24057
|
+
}
|
|
24058
|
+
}
|
|
24059
|
+
const selectedClis = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
|
|
24060
|
+
for (const cliId of selectedClis) {
|
|
24061
|
+
const integration = getIntegration(cliId);
|
|
24062
|
+
if (!integration.scopes.includes(scope)) {
|
|
24063
|
+
try {
|
|
24064
|
+
await trackHookEvent2(getInstanceId2(), "scope_validation_failed", {
|
|
24065
|
+
cli: cliId,
|
|
24066
|
+
scope,
|
|
24067
|
+
supported_scopes: integration.scopes
|
|
24068
|
+
});
|
|
24069
|
+
} catch {}
|
|
24070
|
+
throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
|
|
24071
|
+
}
|
|
24072
|
+
}
|
|
24073
|
+
const binaryPath = resolveFailproofaiBinary2();
|
|
24074
|
+
const previousConfig = readScopedHooksConfig(scope, cwd);
|
|
24075
|
+
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
24076
|
+
let selectedPolicies;
|
|
24077
|
+
if (policyNames !== undefined) {
|
|
24078
|
+
let incoming;
|
|
24079
|
+
if (policyNames.length === 1 && policyNames[0] === "all") {
|
|
24080
|
+
incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
|
|
24081
|
+
} else {
|
|
24082
|
+
incoming = policyNames;
|
|
24083
|
+
}
|
|
24084
|
+
selectedPolicies = replace ? [...new Set(incoming)] : [...new Set([...previousConfig.enabledPolicies, ...incoming])];
|
|
24085
|
+
} else {
|
|
24086
|
+
const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
|
|
24087
|
+
selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
|
|
24088
|
+
}
|
|
24089
|
+
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
24090
|
+
if (removeCustomHooks) {
|
|
24091
|
+
delete configToWrite.customPoliciesPath;
|
|
24092
|
+
delete configToWrite.customPoliciesPaths;
|
|
24093
|
+
} else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
|
|
24094
|
+
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve22(path3));
|
|
24095
|
+
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve22(path3)).filter((path3) => {
|
|
24096
|
+
if (existsSync27(path3))
|
|
24097
|
+
return true;
|
|
24098
|
+
console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
|
|
24099
|
+
return false;
|
|
24100
|
+
});
|
|
24101
|
+
configToWrite.customPoliciesPaths = replace ? [...new Set(incoming)] : [...new Set([...carried, ...incoming])];
|
|
24102
|
+
delete configToWrite.customPoliciesPath;
|
|
24103
|
+
for (const path3 of incoming) {
|
|
24104
|
+
let validatedHooks = [];
|
|
24105
|
+
try {
|
|
24106
|
+
validatedHooks = await loadCustomHooks(path3, { strict: true });
|
|
24107
|
+
} catch (err) {
|
|
24108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
24109
|
+
try {
|
|
24110
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
24111
|
+
scope,
|
|
24112
|
+
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
24113
|
+
});
|
|
24114
|
+
} catch {}
|
|
24115
|
+
console.error(`Error: ${msg}`);
|
|
24116
|
+
process.exit(1);
|
|
24117
|
+
}
|
|
24118
|
+
if (validatedHooks.length === 0) {
|
|
24119
|
+
try {
|
|
24120
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
24121
|
+
scope,
|
|
24122
|
+
error_type: "no_hooks_registered"
|
|
24123
|
+
});
|
|
24124
|
+
} catch {}
|
|
24125
|
+
console.error(`Error: no hooks registered in ${path3}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
|
|
24126
|
+
process.exit(1);
|
|
24127
|
+
}
|
|
24128
|
+
console.log(`
|
|
24129
|
+
Validated ${validatedHooks.length} custom hook(s) from ${path3}: ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
24130
|
+
}
|
|
24131
|
+
}
|
|
24132
|
+
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
24133
|
+
console.log(`
|
|
24134
|
+
Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
24135
|
+
`);
|
|
24136
|
+
if (removeCustomHooks) {
|
|
24137
|
+
console.log("Custom hooks path cleared.");
|
|
24138
|
+
} else if (configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath) {
|
|
24139
|
+
const paths = configToWrite.customPoliciesPaths ?? [configToWrite.customPoliciesPath];
|
|
24140
|
+
console.log(`Custom hooks paths: ${paths.join(", ")}`);
|
|
24141
|
+
}
|
|
24142
|
+
const writtenSettingsPaths = [];
|
|
24143
|
+
for (const cliId of selectedClis) {
|
|
24144
|
+
const integration = getIntegration(cliId);
|
|
24145
|
+
const settingsPaths = settingsPathsFor(integration, scope, cwd);
|
|
24146
|
+
try {
|
|
24147
|
+
for (const settingsPath of settingsPaths) {
|
|
24148
|
+
const settings = integration.readSettings(settingsPath);
|
|
24149
|
+
integration.writeHookEntries(settings, binaryPath, scope);
|
|
24150
|
+
integration.writeSettings(settingsPath, settings);
|
|
24151
|
+
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
24152
|
+
}
|
|
24153
|
+
} catch (err) {
|
|
24154
|
+
const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message) ? "permission_denied" : err instanceof Error && /ENOENT|ENOTDIR/.test(err.message) ? "path_not_found" : "write_error";
|
|
23025
24155
|
try {
|
|
23026
|
-
|
|
24156
|
+
await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
|
|
24157
|
+
cli: cliId,
|
|
24158
|
+
scope,
|
|
24159
|
+
error_type: errorType
|
|
24160
|
+
});
|
|
23027
24161
|
} catch {}
|
|
23028
|
-
|
|
23029
|
-
runPrivileged("launchctl", ["load", "-w", plistPath]);
|
|
24162
|
+
throw err;
|
|
23030
24163
|
}
|
|
23031
|
-
} catch (err) {
|
|
23032
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
23033
|
-
hookLogWarn(`daemon service install failed: ${msg}`);
|
|
23034
|
-
return { installed: false, reason: msg };
|
|
23035
24164
|
}
|
|
23036
|
-
|
|
23037
|
-
const
|
|
23038
|
-
|
|
23039
|
-
|
|
24165
|
+
try {
|
|
24166
|
+
const newSet = new Set(selectedPolicies);
|
|
24167
|
+
const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
|
|
24168
|
+
const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
|
|
24169
|
+
const distinctId = getInstanceId2();
|
|
24170
|
+
await trackHookEvent2(distinctId, "hooks_installed", {
|
|
24171
|
+
scope,
|
|
24172
|
+
cli: selectedClis,
|
|
24173
|
+
cli_count: selectedClis.length,
|
|
24174
|
+
policies: selectedPolicies,
|
|
24175
|
+
policy_count: selectedPolicies.length,
|
|
24176
|
+
policies_added: policiesAdded,
|
|
24177
|
+
policies_removed: policiesRemoved,
|
|
24178
|
+
...source ? { source } : {},
|
|
24179
|
+
platform: platform5(),
|
|
24180
|
+
arch: arch4(),
|
|
24181
|
+
os_release: release3(),
|
|
24182
|
+
hostname_hash: hashToId2(hostname4()),
|
|
24183
|
+
has_custom_hooks_path: !!(configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath),
|
|
24184
|
+
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
24185
|
+
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
24186
|
+
command_format: scope === "project" ? "npx" : "absolute"
|
|
24187
|
+
});
|
|
24188
|
+
if (includeBeta) {
|
|
24189
|
+
const betaNames = new Set(BUILTIN_POLICIES.filter((p) => p.beta).map((p) => p.name));
|
|
24190
|
+
const installedBeta = selectedPolicies.filter((p) => betaNames.has(p));
|
|
24191
|
+
if (installedBeta.length > 0) {
|
|
24192
|
+
await trackHookEvent2(distinctId, "beta_policies_installed", {
|
|
24193
|
+
scope,
|
|
24194
|
+
cli: selectedClis,
|
|
24195
|
+
beta_count: installedBeta.length,
|
|
24196
|
+
beta_policy_names: installedBeta,
|
|
24197
|
+
...source ? { source } : {}
|
|
24198
|
+
});
|
|
24199
|
+
}
|
|
24200
|
+
}
|
|
24201
|
+
} catch {}
|
|
24202
|
+
for (const { cli: cliId, path: path3 } of writtenSettingsPaths) {
|
|
24203
|
+
const integration = getIntegration(cliId);
|
|
24204
|
+
console.log(`Failproof AI hooks installed for ${integration.displayName} ` + `(${integration.eventTypes.length} event types, scope: ${scope}).`);
|
|
24205
|
+
console.log(`Settings: ${path3}`);
|
|
23040
24206
|
}
|
|
23041
|
-
|
|
23042
|
-
|
|
23043
|
-
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
|
|
23047
|
-
break;
|
|
23048
|
-
if (Date.now() >= deadline)
|
|
23049
|
-
return false;
|
|
23050
|
-
await new Promise((r) => setTimeout(r, SERVICE_START_POLL_MS));
|
|
24207
|
+
if (scope === "project") {
|
|
24208
|
+
console.log(`Command: npx -y failproofai`);
|
|
24209
|
+
console.log(`
|
|
24210
|
+
This file can be committed to git — no machine-specific paths.`);
|
|
24211
|
+
} else {
|
|
24212
|
+
console.log(`Binary: ${binaryPath}`);
|
|
23051
24213
|
}
|
|
23052
|
-
|
|
23053
|
-
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
|
|
23058
|
-
|
|
23059
|
-
|
|
23060
|
-
|
|
24214
|
+
const otherScopes = deduplicateScopes2(HOOK_SCOPES, cwd).filter((s) => s !== scope);
|
|
24215
|
+
const duplicates = otherScopes.filter((s) => hooksInstalledInSettings2(s, cwd));
|
|
24216
|
+
if (duplicates.length > 0) {
|
|
24217
|
+
const scopeList = duplicates.map((s) => `${s} (${scopeLabel2(s)})`).join(", ");
|
|
24218
|
+
console.log();
|
|
24219
|
+
console.log(`\x1B[33mWarning: Failproof AI hooks are also installed at ${scopeList}.\x1B[0m`);
|
|
24220
|
+
console.log(`Having hooks in multiple scopes may cause duplicate policy evaluation.`);
|
|
24221
|
+
console.log(`Use \`failproofai policies --uninstall --scope ${duplicates[0]}\` to remove the other installation,`);
|
|
24222
|
+
console.log(`or \`failproofai policies\` to see all scopes.`);
|
|
23061
24223
|
try {
|
|
23062
|
-
|
|
23063
|
-
|
|
23064
|
-
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
} catch {
|
|
23068
|
-
return "stopped";
|
|
23069
|
-
}
|
|
23070
|
-
}
|
|
23071
|
-
const plistPath = launchdPlistPath();
|
|
23072
|
-
if (!existsSync21(plistPath))
|
|
23073
|
-
return "not-installed";
|
|
23074
|
-
try {
|
|
23075
|
-
const root = typeof process.getuid === "function" && process.getuid() === 0;
|
|
23076
|
-
const args = ["print", `system/${LAUNCHD_LABEL}`];
|
|
23077
|
-
const out = root ? execFileSync4("launchctl", args, {
|
|
23078
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
23079
|
-
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23080
|
-
}).toString() : execFileSync4("sudo", ["-n", "launchctl", ...args], {
|
|
23081
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
23082
|
-
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23083
|
-
}).toString();
|
|
23084
|
-
return /state\s*=\s*running/.test(out) ? "running" : "stopped";
|
|
23085
|
-
} catch {
|
|
23086
|
-
return "stopped";
|
|
24224
|
+
await trackHookEvent2(getInstanceId2(), "multi_scope_warning_shown", {
|
|
24225
|
+
new_scope: scope,
|
|
24226
|
+
existing_scopes: duplicates,
|
|
24227
|
+
cli: selectedClis
|
|
24228
|
+
});
|
|
24229
|
+
} catch {}
|
|
23087
24230
|
}
|
|
23088
24231
|
}
|
|
23089
|
-
var
|
|
23090
|
-
var
|
|
23091
|
-
|
|
24232
|
+
var VALID_POLICY_NAMES2;
|
|
24233
|
+
var init_manager2 = __esm(() => {
|
|
24234
|
+
init_types();
|
|
24235
|
+
init_integrations();
|
|
24236
|
+
init_install_prompt();
|
|
23092
24237
|
init_hooks_config();
|
|
23093
|
-
|
|
24238
|
+
init_builtin_policies();
|
|
24239
|
+
init_custom_hooks_loader();
|
|
24240
|
+
init_hook_telemetry2();
|
|
24241
|
+
init_telemetry_id2();
|
|
24242
|
+
init_cli_error();
|
|
24243
|
+
init_hook_logger();
|
|
24244
|
+
VALID_POLICY_NAMES2 = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
24245
|
+
});
|
|
24246
|
+
|
|
24247
|
+
// src/hooks/policy-presets.ts
|
|
24248
|
+
function resolvePreset(id) {
|
|
24249
|
+
const preset = POLICY_PRESETS.find((p) => p.id === id);
|
|
24250
|
+
if (!preset)
|
|
24251
|
+
return [];
|
|
24252
|
+
const cats = new Set(preset.categories);
|
|
24253
|
+
const fromCategories = BUILTIN_POLICIES.filter((p) => !p.beta && cats.has(p.category)).map((p) => p.name);
|
|
24254
|
+
const extras = (preset.extra ?? []).filter((name) => BUILTIN_POLICIES.some((p) => p.name === name && !p.beta));
|
|
24255
|
+
return [...new Set([...fromCategories, ...extras])];
|
|
24256
|
+
}
|
|
24257
|
+
function resolveEverything() {
|
|
24258
|
+
return BUILTIN_POLICIES.filter((p) => !p.beta).map((p) => p.name);
|
|
24259
|
+
}
|
|
24260
|
+
var POLICY_PRESETS;
|
|
24261
|
+
var init_policy_presets = __esm(() => {
|
|
24262
|
+
init_builtin_policies();
|
|
24263
|
+
POLICY_PRESETS = [
|
|
24264
|
+
{
|
|
24265
|
+
id: "secrets",
|
|
24266
|
+
label: "Secrets & data",
|
|
24267
|
+
description: "Redact secrets in tool output, block .env & secret-file writes, keep reads inside the repo",
|
|
24268
|
+
categories: ["Sanitize", "Environment"],
|
|
24269
|
+
extra: ["block-secrets-write"]
|
|
24270
|
+
},
|
|
24271
|
+
{
|
|
24272
|
+
id: "git",
|
|
24273
|
+
label: "Git safety",
|
|
24274
|
+
description: "Block force-push & pushes to main, warn on history-rewriting git ops",
|
|
24275
|
+
categories: ["Git"]
|
|
24276
|
+
},
|
|
24277
|
+
{
|
|
24278
|
+
id: "ship",
|
|
24279
|
+
label: "Ship discipline",
|
|
24280
|
+
description: "Don't let the agent finish until changes are committed, pushed, PR'd and CI is green",
|
|
24281
|
+
categories: ["Workflow"]
|
|
24282
|
+
},
|
|
24283
|
+
{
|
|
24284
|
+
id: "infra",
|
|
24285
|
+
label: "Cloud & infra",
|
|
24286
|
+
description: "Block kubectl / terraform / aws / gcloud / az / helm / gh pipeline commands",
|
|
24287
|
+
categories: ["Infra Commands"]
|
|
24288
|
+
}
|
|
24289
|
+
];
|
|
23094
24290
|
});
|
|
23095
24291
|
|
|
23096
24292
|
// src/audit/cli.ts
|
|
@@ -23384,16 +24580,16 @@ __export(exports_configure_wizard, {
|
|
|
23384
24580
|
buildPresetChoices: () => buildPresetChoices,
|
|
23385
24581
|
buildAgentChoices: () => buildAgentChoices
|
|
23386
24582
|
});
|
|
23387
|
-
import { existsSync as
|
|
23388
|
-
import { homedir as
|
|
23389
|
-
import { resolve as
|
|
24583
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync21 } from "node:fs";
|
|
24584
|
+
import { homedir as homedir38, hostname as hostname5 } from "node:os";
|
|
24585
|
+
import { resolve as resolve23, sep as sep5 } from "node:path";
|
|
23390
24586
|
async function emit(event, props) {
|
|
23391
24587
|
try {
|
|
23392
24588
|
await trackHookEvent2(getInstanceId2(), event, props);
|
|
23393
24589
|
} catch {}
|
|
23394
24590
|
}
|
|
23395
24591
|
function homeify(p) {
|
|
23396
|
-
const home =
|
|
24592
|
+
const home = homedir38();
|
|
23397
24593
|
if (p === home)
|
|
23398
24594
|
return "~";
|
|
23399
24595
|
if (p.startsWith(home + sep5))
|
|
@@ -23507,8 +24703,8 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
|
|
|
23507
24703
|
const path3 = getConfigPathForScope(scope, cwd);
|
|
23508
24704
|
let config = {};
|
|
23509
24705
|
try {
|
|
23510
|
-
if (
|
|
23511
|
-
config = JSON.parse(
|
|
24706
|
+
if (existsSync28(path3))
|
|
24707
|
+
config = JSON.parse(readFileSync21(path3, "utf8"));
|
|
23512
24708
|
} catch {
|
|
23513
24709
|
return;
|
|
23514
24710
|
}
|
|
@@ -23517,7 +24713,7 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
|
|
|
23517
24713
|
else
|
|
23518
24714
|
config.customPoliciesEnabled = false;
|
|
23519
24715
|
try {
|
|
23520
|
-
|
|
24716
|
+
writeFileSync12(path3, JSON.stringify(config, null, 2) + `
|
|
23521
24717
|
`, "utf8");
|
|
23522
24718
|
} catch {}
|
|
23523
24719
|
}
|
|
@@ -23548,8 +24744,8 @@ function describeCustomPolicies(cwd) {
|
|
|
23548
24744
|
const scopes = [];
|
|
23549
24745
|
let fileCount = 0;
|
|
23550
24746
|
const dirs = [
|
|
23551
|
-
{ dir:
|
|
23552
|
-
{ dir:
|
|
24747
|
+
{ dir: resolve23(cwd, ".failproofai", "policies"), label: "project" },
|
|
24748
|
+
{ dir: resolve23(homedir38(), ".failproofai", "policies"), label: "global" }
|
|
23553
24749
|
];
|
|
23554
24750
|
for (const { dir, label } of dirs) {
|
|
23555
24751
|
const found = discoverPolicyFiles(dir);
|
|
@@ -23559,21 +24755,21 @@ function describeCustomPolicies(cwd) {
|
|
|
23559
24755
|
fileCount += found.length;
|
|
23560
24756
|
}
|
|
23561
24757
|
for (const name of findSkippedPolicyFiles(dir)) {
|
|
23562
|
-
warnings.push(`! ${homeify(
|
|
24758
|
+
warnings.push(`! ${homeify(resolve23(dir, name))} is NOT loaded — rename to ` + `${name.replace(/\.(js|mjs|ts)$/, "-policies.$1")}`);
|
|
23563
24759
|
}
|
|
23564
24760
|
}
|
|
23565
24761
|
return { active, warnings, fileCount, scopes };
|
|
23566
24762
|
}
|
|
23567
24763
|
function reviewLines(state) {
|
|
23568
|
-
const { scope, clis, policies, cwd, customEnabled } = state;
|
|
24764
|
+
const { scope, clis, policies, cwd, customEnabled, installDaemon } = state;
|
|
23569
24765
|
const where = scope === "project" ? `This project (${homeify(cwd)})` : "Everywhere (global)";
|
|
23570
24766
|
const lines = [];
|
|
23571
24767
|
const assistantNames = clis.map((c3) => getIntegration(c3).displayName);
|
|
23572
24768
|
lines.push(` Where : ${where}`);
|
|
23573
24769
|
lines.push(` Assistants : ${assistantNames.length ? summarize(assistantNames, "assistants") : "(none)"}`);
|
|
23574
|
-
lines.push(` Policies : ${policies.length} enabled`);
|
|
23575
|
-
if (
|
|
23576
|
-
lines.push(` Daemon : failproofaid
|
|
24770
|
+
lines.push(policies.length === 0 ? " Policies : none enabled (add later: failproofai policies --install)" : ` Policies : ${policies.length} enabled`);
|
|
24771
|
+
if (installDaemon && isDaemonSupportedPlatform()) {
|
|
24772
|
+
lines.push(` Daemon : failproofaid, installed as a system service running as you`);
|
|
23577
24773
|
}
|
|
23578
24774
|
const custom = describeCustomPolicies(cwd);
|
|
23579
24775
|
if (custom.active.length > 0) {
|
|
@@ -23590,23 +24786,23 @@ function reviewLines(state) {
|
|
|
23590
24786
|
}
|
|
23591
24787
|
}
|
|
23592
24788
|
lines.push(` ${homeify(getConfigPathForScope(scope, cwd))} ${policies.length} policies`);
|
|
23593
|
-
if (
|
|
24789
|
+
if (installDaemon && isDaemonSupportedPlatform()) {
|
|
23594
24790
|
const servicePath = daemonServiceFilePath();
|
|
23595
24791
|
if (servicePath)
|
|
23596
|
-
lines.push(` ${homeify(servicePath)} failproofaid service`);
|
|
24792
|
+
lines.push(` ${homeify(servicePath)} failproofaid service (needs root)`);
|
|
23597
24793
|
}
|
|
23598
24794
|
return lines;
|
|
23599
24795
|
}
|
|
23600
24796
|
function firstRunMarkerPath() {
|
|
23601
|
-
return
|
|
24797
|
+
return resolve23(homedir38(), ".failproofai", ".launcher-configured");
|
|
23602
24798
|
}
|
|
23603
24799
|
function hasSeenLauncher() {
|
|
23604
|
-
return
|
|
24800
|
+
return existsSync28(firstRunMarkerPath());
|
|
23605
24801
|
}
|
|
23606
24802
|
function markLauncherSeen() {
|
|
23607
24803
|
try {
|
|
23608
|
-
|
|
23609
|
-
|
|
24804
|
+
mkdirSync14(resolve23(homedir38(), ".failproofai"), { recursive: true });
|
|
24805
|
+
writeFileSync12(firstRunMarkerPath(), "1", "utf8");
|
|
23610
24806
|
} catch {}
|
|
23611
24807
|
}
|
|
23612
24808
|
function anyHooksInstalledGlobally() {
|
|
@@ -23654,6 +24850,15 @@ async function runConfigureWizard(io = {}) {
|
|
|
23654
24850
|
stdout.write(`failproofai config needs an interactive terminal.
|
|
23655
24851
|
Use the flag form instead, e.g.:
|
|
23656
24852
|
failproofai policies --install --scope user --cli claude
|
|
24853
|
+
`);
|
|
24854
|
+
return { applied: false };
|
|
24855
|
+
}
|
|
24856
|
+
if (typeof process.getuid === "function" && process.getuid() === 0 && process.env.SUDO_USER) {
|
|
24857
|
+
stdout.write(`Run failproofai config as ${process.env.SUDO_USER}, not under sudo.
|
|
24858
|
+
` + `Everything it configures is per-user — hooks, policies and the daemon's own
|
|
24859
|
+
` + `account — so under sudo it would set all of that up for root instead of you.
|
|
24860
|
+
` + `The one step that needs root (installing the service) asks for your password
|
|
24861
|
+
on its own.
|
|
23657
24862
|
`);
|
|
23658
24863
|
return { applied: false };
|
|
23659
24864
|
}
|
|
@@ -23663,6 +24868,43 @@ Use the flag form instead, e.g.:
|
|
|
23663
24868
|
outro("Cancelled — nothing was changed.", { ok: false }, stdout);
|
|
23664
24869
|
return { applied: false };
|
|
23665
24870
|
};
|
|
24871
|
+
let daemonWanted = false;
|
|
24872
|
+
let elevated = false;
|
|
24873
|
+
if (isDaemonSupportedPlatform()) {
|
|
24874
|
+
const choice = await selectOne({
|
|
24875
|
+
message: "Install the failproofaid background daemon?",
|
|
24876
|
+
choices: [
|
|
24877
|
+
{
|
|
24878
|
+
label: "Yes — keep policy evaluation warm",
|
|
24879
|
+
value: "install",
|
|
24880
|
+
hint: "a system service, started at boot, running as you · needs your password once"
|
|
24881
|
+
},
|
|
24882
|
+
{
|
|
24883
|
+
label: "Not now",
|
|
24884
|
+
value: "skip",
|
|
24885
|
+
hint: "policies still enforce, evaluated per hook call"
|
|
24886
|
+
}
|
|
24887
|
+
],
|
|
24888
|
+
stdin,
|
|
24889
|
+
stdout
|
|
24890
|
+
});
|
|
24891
|
+
if (choice === null)
|
|
24892
|
+
return cancel();
|
|
24893
|
+
daemonWanted = choice === "install";
|
|
24894
|
+
if (daemonWanted) {
|
|
24895
|
+
stdout.write(`
|
|
24896
|
+
Installing the failproofaid service needs root. Your password stays with sudo.
|
|
24897
|
+
`);
|
|
24898
|
+
elevated = primeElevation();
|
|
24899
|
+
if (!elevated) {
|
|
24900
|
+
stdout.write(`Could not get sudo — carrying on without the daemon.
|
|
24901
|
+
` + `Everything else still applies, and policies still enforce in-process.
|
|
24902
|
+
|
|
24903
|
+
`);
|
|
24904
|
+
daemonWanted = false;
|
|
24905
|
+
}
|
|
24906
|
+
}
|
|
24907
|
+
}
|
|
23666
24908
|
const scope = await selectOne({
|
|
23667
24909
|
message: "Where should this apply?",
|
|
23668
24910
|
choices: buildScopeChoices(cwd),
|
|
@@ -23698,9 +24940,8 @@ Use the flag form instead, e.g.:
|
|
|
23698
24940
|
const presets = await multiSelect({
|
|
23699
24941
|
message: "What should we guard against?",
|
|
23700
24942
|
choices: presetChoices,
|
|
23701
|
-
minSelected: 1,
|
|
23702
24943
|
summaryNoun: "bundles",
|
|
23703
|
-
hint: "space toggles · combine presets · ↵ confirm",
|
|
24944
|
+
hint: "space toggles · combine presets · ↵ confirm · none is fine",
|
|
23704
24945
|
stdin,
|
|
23705
24946
|
stdout
|
|
23706
24947
|
});
|
|
@@ -23708,9 +24949,141 @@ Use the flag form instead, e.g.:
|
|
|
23708
24949
|
return cancel();
|
|
23709
24950
|
const policies = resolvePresetSelection(presets);
|
|
23710
24951
|
const customEnabled = hasCustomFiles ? presets.includes(CUSTOM) : undefined;
|
|
24952
|
+
let collector = null;
|
|
24953
|
+
if (daemonWanted) {
|
|
24954
|
+
const send = await selectOne({
|
|
24955
|
+
message: "Send session and hook data to AgentEye?",
|
|
24956
|
+
choices: [
|
|
24957
|
+
{
|
|
24958
|
+
label: "Not now",
|
|
24959
|
+
value: "no",
|
|
24960
|
+
hint: "nothing leaves this machine"
|
|
24961
|
+
},
|
|
24962
|
+
{
|
|
24963
|
+
label: "Yes — connect to a dashboard",
|
|
24964
|
+
value: "yes",
|
|
24965
|
+
hint: "needs an events:add API key"
|
|
24966
|
+
}
|
|
24967
|
+
],
|
|
24968
|
+
stdin,
|
|
24969
|
+
stdout
|
|
24970
|
+
});
|
|
24971
|
+
if (send === null)
|
|
24972
|
+
return cancel();
|
|
24973
|
+
if (send === "yes") {
|
|
24974
|
+
const existing = readCloudCredentials();
|
|
24975
|
+
let url = null;
|
|
24976
|
+
let key = null;
|
|
24977
|
+
if (existing) {
|
|
24978
|
+
const reuse = await selectOne({
|
|
24979
|
+
message: `Use this machine's existing connection to ${existing.url}?`,
|
|
24980
|
+
choices: [
|
|
24981
|
+
{
|
|
24982
|
+
label: `Yes — reuse it`,
|
|
24983
|
+
value: "reuse",
|
|
24984
|
+
hint: `as ${existing.machineId}, same token`
|
|
24985
|
+
},
|
|
24986
|
+
{ label: "No — use a different endpoint and key", value: "other", hint: "" }
|
|
24987
|
+
],
|
|
24988
|
+
stdin,
|
|
24989
|
+
stdout
|
|
24990
|
+
});
|
|
24991
|
+
if (reuse === null)
|
|
24992
|
+
return cancel();
|
|
24993
|
+
if (reuse === "reuse") {
|
|
24994
|
+
url = ingestUrlFor(existing.url);
|
|
24995
|
+
key = existing.token;
|
|
24996
|
+
}
|
|
24997
|
+
}
|
|
24998
|
+
if (url === null) {
|
|
24999
|
+
const entered = await promptText({
|
|
25000
|
+
message: "Failproof Cloud URL",
|
|
25001
|
+
defaultValue: cloudBaseFor(DEFAULT_INGEST_URL),
|
|
25002
|
+
hint: "Enter for the hosted endpoint",
|
|
25003
|
+
validate: (v) => /^https?:\/\//.test(v) ? null : "must be an http(s) URL",
|
|
25004
|
+
stdin,
|
|
25005
|
+
stdout
|
|
25006
|
+
});
|
|
25007
|
+
if (entered === null)
|
|
25008
|
+
return cancel();
|
|
25009
|
+
url = ingestUrlFor(cloudBaseFor(entered));
|
|
25010
|
+
}
|
|
25011
|
+
if (key === null) {
|
|
25012
|
+
key = await promptText({
|
|
25013
|
+
message: "API key",
|
|
25014
|
+
hint: "needs events:add",
|
|
25015
|
+
mask: true,
|
|
25016
|
+
validate: (v) => v.length >= 8 ? null : "that looks too short to be a key",
|
|
25017
|
+
stdin,
|
|
25018
|
+
stdout
|
|
25019
|
+
});
|
|
25020
|
+
if (key === null)
|
|
25021
|
+
return cancel();
|
|
25022
|
+
}
|
|
25023
|
+
stdout.write(`
|
|
25024
|
+
Checking the key… `);
|
|
25025
|
+
const check = await validateIngestKey({ url, key });
|
|
25026
|
+
if (!check.ok) {
|
|
25027
|
+
stdout.write(`
|
|
25028
|
+
That did not work: ${check.reason}
|
|
25029
|
+
`);
|
|
25030
|
+
const retry = await selectOne({
|
|
25031
|
+
message: "Carry on without sending data?",
|
|
25032
|
+
choices: [
|
|
25033
|
+
{ label: "Yes, skip it", value: "skip", hint: "re-run failproofai config later" },
|
|
25034
|
+
{ label: "Save it anyway", value: "anyway", hint: "if you know the server is just down" }
|
|
25035
|
+
],
|
|
25036
|
+
stdin,
|
|
25037
|
+
stdout
|
|
25038
|
+
});
|
|
25039
|
+
if (retry === null)
|
|
25040
|
+
return cancel();
|
|
25041
|
+
if (retry === "skip") {
|
|
25042
|
+
stdout.write(`Skipping data collection. Everything else still applies.
|
|
25043
|
+
|
|
25044
|
+
`);
|
|
25045
|
+
} else {
|
|
25046
|
+
collector = { cred: { url, key }, sessions: false };
|
|
25047
|
+
}
|
|
25048
|
+
} else {
|
|
25049
|
+
stdout.write(`looks good.
|
|
25050
|
+
`);
|
|
25051
|
+
const sessions = await selectOne({
|
|
25052
|
+
message: "What should it send?",
|
|
25053
|
+
choices: [
|
|
25054
|
+
{
|
|
25055
|
+
label: "Policy decisions only",
|
|
25056
|
+
value: "hooks",
|
|
25057
|
+
hint: "which hooks fired and what they decided · no file contents"
|
|
25058
|
+
},
|
|
25059
|
+
{
|
|
25060
|
+
label: "Decisions and full session transcripts",
|
|
25061
|
+
value: "both",
|
|
25062
|
+
hint: "includes prompts, file contents and command output"
|
|
25063
|
+
}
|
|
25064
|
+
],
|
|
25065
|
+
stdin,
|
|
25066
|
+
stdout
|
|
25067
|
+
});
|
|
25068
|
+
if (sessions === null)
|
|
25069
|
+
return cancel();
|
|
25070
|
+
collector = { cred: { url, key }, sessions: sessions === "both" };
|
|
25071
|
+
if (!readCloudCredentials()) {
|
|
25072
|
+
const base = cloudBaseFor(url);
|
|
25073
|
+
const machineId = hostname5();
|
|
25074
|
+
const enrol = await verifyCloudCredentials({ url: base, machineId, token: key });
|
|
25075
|
+
if (enrol.ok) {
|
|
25076
|
+
writeCloudCredentials({ url: base, machineId, token: key });
|
|
25077
|
+
stdout.write(`Also connected for cloud-managed policy (${enrol.policyCount} assigned, generation ${enrol.generation}).
|
|
25078
|
+
`);
|
|
25079
|
+
}
|
|
25080
|
+
}
|
|
25081
|
+
}
|
|
25082
|
+
}
|
|
25083
|
+
}
|
|
23711
25084
|
const decision = await selectOne({
|
|
23712
25085
|
message: "Ready to apply?",
|
|
23713
|
-
body: reviewLines({ scope, clis, policies, cwd, customEnabled }),
|
|
25086
|
+
body: reviewLines({ scope, clis, policies, cwd, customEnabled, installDaemon: daemonWanted }),
|
|
23714
25087
|
choices: [
|
|
23715
25088
|
{ label: "Yes, apply now", value: "apply", hint: "write the config" },
|
|
23716
25089
|
{ label: "Cancel", value: "cancel", hint: "quit, no changes" }
|
|
@@ -23730,7 +25103,7 @@ Use the flag form instead, e.g.:
|
|
|
23730
25103
|
await installHooks2(policies, scope, cwd, false, "configure-wizard", undefined, false, clis, { replace: true, quiet: true });
|
|
23731
25104
|
setCustomPoliciesEnabled(scope, cwd, customEnabled);
|
|
23732
25105
|
let daemonInstalled = false;
|
|
23733
|
-
if (
|
|
25106
|
+
if (daemonWanted) {
|
|
23734
25107
|
const daemonResult = await installDaemonService();
|
|
23735
25108
|
if (daemonResult.installed) {
|
|
23736
25109
|
setDaemonConfigured(true);
|
|
@@ -23744,6 +25117,22 @@ Use the flag form instead, e.g.:
|
|
|
23744
25117
|
platform: process.platform
|
|
23745
25118
|
});
|
|
23746
25119
|
}
|
|
25120
|
+
if (collector && daemonInstalled) {
|
|
25121
|
+
try {
|
|
25122
|
+
writeIngestCredential(collector.cred);
|
|
25123
|
+
writeCollectorSettings({
|
|
25124
|
+
sessions: collector.sessions,
|
|
25125
|
+
hooks: true,
|
|
25126
|
+
hooksVerbosity: "decisions",
|
|
25127
|
+
redact: "minimal"
|
|
25128
|
+
});
|
|
25129
|
+
emit("configure_collector", { sessions: collector.sessions });
|
|
25130
|
+
} catch (err) {
|
|
25131
|
+
hookLogWarn(`collector configuration was not written: ${err instanceof Error ? err.message : String(err)}`);
|
|
25132
|
+
}
|
|
25133
|
+
} else if (collector && !daemonInstalled) {
|
|
25134
|
+
hookLogWarn("skipped collector configuration because the daemon was not installed");
|
|
25135
|
+
}
|
|
23747
25136
|
await applied;
|
|
23748
25137
|
markLauncherSeen();
|
|
23749
25138
|
const customNote = customEnabled === true ? " + your custom policies" : customEnabled === false ? " · custom policies DISABLED" : "";
|
|
@@ -23755,6 +25144,7 @@ Use the flag form instead, e.g.:
|
|
|
23755
25144
|
var EVERYTHING = "__everything__", ALL_CLIS = "__all_clis__", CUSTOM = "__custom__", DIM_NOTE = "(auto-loaded)";
|
|
23756
25145
|
var init_configure_wizard = __esm(() => {
|
|
23757
25146
|
init_tui();
|
|
25147
|
+
init_collector_config();
|
|
23758
25148
|
init_integrations();
|
|
23759
25149
|
init_types();
|
|
23760
25150
|
init_manager2();
|
|
@@ -23765,6 +25155,8 @@ var init_configure_wizard = __esm(() => {
|
|
|
23765
25155
|
init_telemetry_id2();
|
|
23766
25156
|
init_daemon_service();
|
|
23767
25157
|
init_hook_logger();
|
|
25158
|
+
init_cloud_enrollment();
|
|
25159
|
+
init_cloud_connection();
|
|
23768
25160
|
});
|
|
23769
25161
|
|
|
23770
25162
|
// scripts/launch.ts
|
|
@@ -23773,8 +25165,8 @@ __export(exports_launch2, {
|
|
|
23773
25165
|
launch: () => launch2
|
|
23774
25166
|
});
|
|
23775
25167
|
import { spawn as spawn3 } from "child_process";
|
|
23776
|
-
import { realpathSync as
|
|
23777
|
-
import { resolve as
|
|
25168
|
+
import { realpathSync as realpathSync4, existsSync as existsSync29 } from "node:fs";
|
|
25169
|
+
import { resolve as resolve24, dirname as dirname10 } from "node:path";
|
|
23778
25170
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23779
25171
|
import { createInterface as createInterface3 } from "node:readline";
|
|
23780
25172
|
function launch2(mode) {
|
|
@@ -23789,9 +25181,9 @@ function launch2(mode) {
|
|
|
23789
25181
|
process.env.PORT = port;
|
|
23790
25182
|
process.env.HOSTNAME = "0.0.0.0";
|
|
23791
25183
|
cmd = "node";
|
|
23792
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
23793
|
-
const serverJsPath =
|
|
23794
|
-
if (!
|
|
25184
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve24(dirname10(realpathSync4(fileURLToPath3(import.meta.url))), "..");
|
|
25185
|
+
const serverJsPath = resolve24(packageRoot, ".next/standalone/server.js");
|
|
25186
|
+
if (!existsSync29(serverJsPath)) {
|
|
23795
25187
|
let shadowMessage = null;
|
|
23796
25188
|
try {
|
|
23797
25189
|
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
@@ -23885,18 +25277,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
23885
25277
|
});
|
|
23886
25278
|
|
|
23887
25279
|
// bin/failproofai.mjs
|
|
23888
|
-
import { realpathSync as
|
|
23889
|
-
import { dirname as dirname11, resolve as
|
|
25280
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
25281
|
+
import { dirname as dirname11, resolve as resolve25 } from "path";
|
|
23890
25282
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23891
25283
|
// package.json
|
|
23892
|
-
var version = "1.0.0-beta.
|
|
25284
|
+
var version = "1.0.0-beta.3";
|
|
23893
25285
|
|
|
23894
25286
|
// bin/failproofai.mjs
|
|
23895
25287
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
23896
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
25288
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve25(dirname11(realpathSync5(fileURLToPath4(import.meta.url))), "..");
|
|
23897
25289
|
}
|
|
23898
25290
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
23899
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
25291
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve25(dirname11(realpathSync5(fileURLToPath4(import.meta.url))), "..", "dist");
|
|
23900
25292
|
}
|
|
23901
25293
|
var args = process.argv.slice(2);
|
|
23902
25294
|
if (args[0] === "p")
|
|
@@ -24433,11 +25825,127 @@ WHAT IT DOES
|
|
|
24433
25825
|
3. Policies \u2014 presets (combine any), Everything, or a custom pick
|
|
24434
25826
|
4. Review \u2014 confirms the exact files it will change, then applies
|
|
24435
25827
|
|
|
25828
|
+
FAILPROOF CLOUD
|
|
25829
|
+
failproofai config --connect <url> --token <key> [--machine-id <id>]
|
|
25830
|
+
Connect this machine to Failproof Cloud
|
|
25831
|
+
[--send-transcripts] also send full session transcripts
|
|
25832
|
+
failproofai config --disconnect Stop pulling policy and sending activity
|
|
25833
|
+
failproofai config --status Show connection and pause state
|
|
25834
|
+
|
|
25835
|
+
One connection, two capabilities: this machine PULLS centrally-managed
|
|
25836
|
+
policies and SENDS what its hooks decided, so the dashboard shows the fleet
|
|
25837
|
+
it is enforcing on. Both are checked against the server before anything is
|
|
25838
|
+
written, and reported separately \u2014 a key carrying policies:pull but not
|
|
25839
|
+
events:add connects for policy and says exactly why the dashboard is empty.
|
|
25840
|
+
|
|
25841
|
+
Tokens are stored owner-only in ~/.failproofai/, never in the service unit \u2014
|
|
25842
|
+
that file is world-readable. Connecting needs no sudo, and the machine id
|
|
25843
|
+
defaults to this host's name.
|
|
25844
|
+
|
|
25845
|
+
Session transcripts carry prompts, file contents and whatever was pasted into
|
|
25846
|
+
a terminal, so they are NEVER sent unless --send-transcripts is passed.
|
|
25847
|
+
|
|
25848
|
+
PAUSING ENFORCEMENT (one session, always time-boxed)
|
|
25849
|
+
failproofai config --pause Pause this directory's newest agent session (30m)
|
|
25850
|
+
failproofai config --pause 10m Pause for a given time (max 8h; s/m/h, bare = minutes)
|
|
25851
|
+
failproofai config --resume End the pause early
|
|
25852
|
+
failproofai config --status Show what is paused and when it lifts
|
|
25853
|
+
--session <id> Target a specific session
|
|
25854
|
+
--all With --resume, end every active pause
|
|
25855
|
+
|
|
25856
|
+
A pause suspends builtin, custom and convention policies for that session
|
|
25857
|
+
only, and always expires on its own. Cloud-managed policies keep enforcing.
|
|
25858
|
+
|
|
24436
25859
|
Prefer flags? See \`failproofai policies --help\`.
|
|
24437
25860
|
`.trimStart());
|
|
24438
25861
|
process.exit(0);
|
|
24439
25862
|
}
|
|
24440
25863
|
lastSubcommand = "config";
|
|
25864
|
+
const connectIdx = args.indexOf("--connect");
|
|
25865
|
+
const wantsDisconnect = args.includes("--disconnect");
|
|
25866
|
+
if (connectIdx >= 0 || wantsDisconnect) {
|
|
25867
|
+
if (connectIdx >= 0 && wantsDisconnect) {
|
|
25868
|
+
throw new CliError3("--connect and --disconnect cannot be combined.");
|
|
25869
|
+
}
|
|
25870
|
+
const valueAfter = (flag) => {
|
|
25871
|
+
const i = args.indexOf(flag);
|
|
25872
|
+
if (i < 0)
|
|
25873
|
+
return;
|
|
25874
|
+
const v = args[i + 1];
|
|
25875
|
+
if (!v || v.startsWith("-"))
|
|
25876
|
+
throw new CliError3(`Missing value after ${flag}.`);
|
|
25877
|
+
return v;
|
|
25878
|
+
};
|
|
25879
|
+
let result2;
|
|
25880
|
+
if (wantsDisconnect) {
|
|
25881
|
+
const { runDisconnectCommand: runDisconnectCommand2 } = await Promise.resolve().then(() => (init_cloud_enrollment_cli(), exports_cloud_enrollment_cli));
|
|
25882
|
+
result2 = runDisconnectCommand2();
|
|
25883
|
+
} else {
|
|
25884
|
+
const { hostname: hostname6 } = await import("os");
|
|
25885
|
+
const { runConnectCommand: runConnectCommand2 } = await Promise.resolve().then(() => (init_cloud_enrollment_cli(), exports_cloud_enrollment_cli));
|
|
25886
|
+
result2 = await runConnectCommand2({
|
|
25887
|
+
url: valueAfter("--connect"),
|
|
25888
|
+
token: valueAfter("--token"),
|
|
25889
|
+
machineId: valueAfter("--machine-id"),
|
|
25890
|
+
defaultMachineId: hostname6(),
|
|
25891
|
+
sessions: args.includes("--send-transcripts")
|
|
25892
|
+
});
|
|
25893
|
+
}
|
|
25894
|
+
for (const line of result2.lines) {
|
|
25895
|
+
if (result2.exitCode === 0)
|
|
25896
|
+
console.log(line);
|
|
25897
|
+
else
|
|
25898
|
+
console.error(line);
|
|
25899
|
+
}
|
|
25900
|
+
await track("cli_cloud_enrollment", {
|
|
25901
|
+
action: wantsDisconnect ? "disconnect" : "connect",
|
|
25902
|
+
ok: result2.exitCode === 0
|
|
25903
|
+
});
|
|
25904
|
+
await exitAfterFlush(result2.exitCode);
|
|
25905
|
+
return;
|
|
25906
|
+
}
|
|
25907
|
+
const pauseIdx = args.indexOf("--pause");
|
|
25908
|
+
const wantsResume = args.includes("--resume");
|
|
25909
|
+
const wantsStatus = args.includes("--status");
|
|
25910
|
+
if (pauseIdx >= 0 || wantsResume || wantsStatus) {
|
|
25911
|
+
const chosen = [pauseIdx >= 0 && "--pause", wantsResume && "--resume", wantsStatus && "--status"].filter(Boolean);
|
|
25912
|
+
if (chosen.length > 1) {
|
|
25913
|
+
throw new CliError3(`${chosen.join(" and ")} cannot be combined.`);
|
|
25914
|
+
}
|
|
25915
|
+
const sessionIdx = args.indexOf("--session");
|
|
25916
|
+
if (sessionIdx >= 0 && !args[sessionIdx + 1]) {
|
|
25917
|
+
throw new CliError3("Missing session id after --session.");
|
|
25918
|
+
}
|
|
25919
|
+
const next = pauseIdx >= 0 ? args[pauseIdx + 1] : undefined;
|
|
25920
|
+
const duration = next && !next.startsWith("-") ? next : undefined;
|
|
25921
|
+
const { runPauseCommand: runPauseCommand2 } = await Promise.resolve().then(() => (init_session_pause_cli(), exports_session_pause_cli));
|
|
25922
|
+
const result2 = runPauseCommand2({
|
|
25923
|
+
action: pauseIdx >= 0 ? "pause" : wantsResume ? "resume" : "status",
|
|
25924
|
+
duration,
|
|
25925
|
+
sessionId: sessionIdx >= 0 ? args[sessionIdx + 1] : undefined,
|
|
25926
|
+
all: args.includes("--all"),
|
|
25927
|
+
cwd: process.cwd()
|
|
25928
|
+
});
|
|
25929
|
+
if (wantsStatus) {
|
|
25930
|
+
const { connectionStatusLines: connectionStatusLines2 } = await Promise.resolve().then(() => (init_cloud_enrollment_cli(), exports_cloud_enrollment_cli));
|
|
25931
|
+
for (const line of connectionStatusLines2())
|
|
25932
|
+
console.log(line);
|
|
25933
|
+
console.log("");
|
|
25934
|
+
}
|
|
25935
|
+
for (const line of result2.lines) {
|
|
25936
|
+
if (result2.exitCode === 0)
|
|
25937
|
+
console.log(line);
|
|
25938
|
+
else
|
|
25939
|
+
console.error(line);
|
|
25940
|
+
}
|
|
25941
|
+
await track("cli_pause_invoked", {
|
|
25942
|
+
action: pauseIdx >= 0 ? "pause" : wantsResume ? "resume" : "status",
|
|
25943
|
+
ok: result2.exitCode === 0,
|
|
25944
|
+
affected: result2.affected
|
|
25945
|
+
});
|
|
25946
|
+
await exitAfterFlush(result2.exitCode);
|
|
25947
|
+
return;
|
|
25948
|
+
}
|
|
24441
25949
|
const { runConfigureWizard: runConfigureWizard2 } = await Promise.resolve().then(() => (init_configure_wizard(), exports_configure_wizard));
|
|
24442
25950
|
const result = await runConfigureWizard2();
|
|
24443
25951
|
await track("cli_configure_invoked", {
|