failproofai 1.0.0-beta.2 → 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]__1psjybo._.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]__0nb0syg._.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_0oy_-36.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/{3s7myk_co0gbh.js → 01h6lfjz1_oie.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 0a7k_x4rp_97z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 0cvd6bcdwchc_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 13-obgukzg_ke.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 1h0_yaoq5f3yx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 29g8ykjk6sx_8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 2rqm-klet_gbl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.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 +2195 -770
- 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 +221 -5
- package/src/hooks/custom-hooks-loader.ts +69 -25
- package/src/hooks/daemon-client.ts +18 -0
- 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/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/chunks/40k1_v4os4cw6.js +0 -6
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → pM1veX5Llx4YKEaiFc8Sf}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → pM1veX5Llx4YKEaiFc8Sf}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → 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).` };
|
|
22397
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
|
+
}
|
|
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
|
}
|
|
@@ -22764,23 +23126,23 @@ var init_daemon_download = __esm(() => {
|
|
|
22764
23126
|
|
|
22765
23127
|
// src/hooks/daemon-service.ts
|
|
22766
23128
|
import {
|
|
22767
|
-
existsSync as
|
|
22768
|
-
mkdirSync as
|
|
23129
|
+
existsSync as existsSync24,
|
|
23130
|
+
mkdirSync as mkdirSync12,
|
|
22769
23131
|
mkdtempSync,
|
|
22770
|
-
readFileSync as
|
|
23132
|
+
readFileSync as readFileSync19,
|
|
22771
23133
|
writeFileSync as writeFileSync10,
|
|
22772
23134
|
unlinkSync as unlinkSync3,
|
|
22773
|
-
rmSync as
|
|
23135
|
+
rmSync as rmSync6
|
|
22774
23136
|
} from "node:fs";
|
|
22775
|
-
import { homedir as
|
|
22776
|
-
import { resolve as
|
|
23137
|
+
import { homedir as homedir34, tmpdir, userInfo } from "node:os";
|
|
23138
|
+
import { resolve as resolve20, dirname as dirname9 } from "node:path";
|
|
22777
23139
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
22778
23140
|
function setDaemonConfigured(value) {
|
|
22779
23141
|
const path3 = getConfigPathForScope("user");
|
|
22780
23142
|
let config = {};
|
|
22781
23143
|
try {
|
|
22782
|
-
if (
|
|
22783
|
-
config = JSON.parse(
|
|
23144
|
+
if (existsSync24(path3))
|
|
23145
|
+
config = JSON.parse(readFileSync19(path3, "utf8"));
|
|
22784
23146
|
} catch {
|
|
22785
23147
|
return;
|
|
22786
23148
|
}
|
|
@@ -22789,7 +23151,7 @@ function setDaemonConfigured(value) {
|
|
|
22789
23151
|
else
|
|
22790
23152
|
delete config.daemonConfigured;
|
|
22791
23153
|
try {
|
|
22792
|
-
|
|
23154
|
+
mkdirSync12(dirname9(path3), { recursive: true });
|
|
22793
23155
|
writeFileSync10(path3, JSON.stringify(config, null, 2) + `
|
|
22794
23156
|
`, "utf8");
|
|
22795
23157
|
} catch {}
|
|
@@ -22799,22 +23161,22 @@ function isDaemonSupportedPlatform() {
|
|
|
22799
23161
|
}
|
|
22800
23162
|
function platformKey() {
|
|
22801
23163
|
const os4 = process.platform === "linux" ? "linux" : process.platform === "darwin" ? "darwin" : null;
|
|
22802
|
-
const
|
|
22803
|
-
if (!os4 || !
|
|
23164
|
+
const arch4 = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : null;
|
|
23165
|
+
if (!os4 || !arch4)
|
|
22804
23166
|
return null;
|
|
22805
|
-
return `${os4}-${
|
|
23167
|
+
return `${os4}-${arch4}`;
|
|
22806
23168
|
}
|
|
22807
23169
|
function resolveFailproofaidBinaryPath() {
|
|
22808
23170
|
if (process.env.FAILPROOFAI_DAEMON_BINARY)
|
|
22809
23171
|
return process.env.FAILPROOFAI_DAEMON_BINARY;
|
|
22810
23172
|
const downloaded = installedBinaryPath();
|
|
22811
|
-
if (
|
|
23173
|
+
if (existsSync24(downloaded))
|
|
22812
23174
|
return downloaded;
|
|
22813
23175
|
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
22814
23176
|
if (packageRoot) {
|
|
22815
23177
|
for (const profile of ["release", "debug"]) {
|
|
22816
|
-
const candidate =
|
|
22817
|
-
if (
|
|
23178
|
+
const candidate = resolve20(packageRoot, "target", profile, "failproofaid");
|
|
23179
|
+
if (existsSync24(candidate))
|
|
22818
23180
|
return candidate;
|
|
22819
23181
|
}
|
|
22820
23182
|
}
|
|
@@ -22839,8 +23201,8 @@ function resolveWorkerCommand() {
|
|
|
22839
23201
|
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
22840
23202
|
if (!packageRoot)
|
|
22841
23203
|
return null;
|
|
22842
|
-
const workerScript =
|
|
22843
|
-
if (!
|
|
23204
|
+
const workerScript = resolve20(packageRoot, "dist", "worker.mjs");
|
|
23205
|
+
if (!existsSync24(workerScript))
|
|
22844
23206
|
return null;
|
|
22845
23207
|
return `${shellQuote(process.execPath)} ${shellQuote(workerScript)}`;
|
|
22846
23208
|
}
|
|
@@ -22854,10 +23216,10 @@ function systemdUnitName(user = serviceUser()) {
|
|
|
22854
23216
|
return `failproofaid@${user}.service`;
|
|
22855
23217
|
}
|
|
22856
23218
|
function systemdUnitPath(user = serviceUser()) {
|
|
22857
|
-
return
|
|
23219
|
+
return resolve20("/etc/systemd/system", systemdUnitName(user));
|
|
22858
23220
|
}
|
|
22859
23221
|
function legacySystemdUserUnitPath() {
|
|
22860
|
-
return
|
|
23222
|
+
return resolve20(homedir34(), ".config", "systemd", "user", "failproofaid.service");
|
|
22861
23223
|
}
|
|
22862
23224
|
function launchdLabel(user = serviceUser()) {
|
|
22863
23225
|
return `ai.failproof.failproofaid.${user}`;
|
|
@@ -22866,7 +23228,7 @@ function launchdPlistPath(user = serviceUser()) {
|
|
|
22866
23228
|
return `/Library/LaunchDaemons/${launchdLabel(user)}.plist`;
|
|
22867
23229
|
}
|
|
22868
23230
|
function legacyLaunchAgentPlistPath() {
|
|
22869
|
-
return
|
|
23231
|
+
return resolve20(homedir34(), "Library", "LaunchAgents", "ai.failproof.failproofaid.plist");
|
|
22870
23232
|
}
|
|
22871
23233
|
function legacySharedLaunchDaemonPath() {
|
|
22872
23234
|
return "/Library/LaunchDaemons/ai.failproof.failproofaid.plist";
|
|
@@ -22904,20 +23266,20 @@ function runPrivileged(command, args) {
|
|
|
22904
23266
|
execFileSync4(cmd, argv, { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
|
|
22905
23267
|
}
|
|
22906
23268
|
function writePrivilegedFile(destination, contents, mode = "0644") {
|
|
22907
|
-
const stagingDir = mkdtempSync(
|
|
22908
|
-
const staging =
|
|
23269
|
+
const stagingDir = mkdtempSync(resolve20(tmpdir(), "failproofaid-stage-"));
|
|
23270
|
+
const staging = resolve20(stagingDir, "service-definition");
|
|
22909
23271
|
try {
|
|
22910
23272
|
writeFileSync10(staging, contents, "utf8");
|
|
22911
23273
|
runPrivileged("install", ["-m", mode, staging, destination]);
|
|
22912
23274
|
} finally {
|
|
22913
|
-
|
|
23275
|
+
rmSync6(stagingDir, { recursive: true, force: true });
|
|
22914
23276
|
}
|
|
22915
23277
|
}
|
|
22916
23278
|
function removeLegacyUserService() {
|
|
22917
23279
|
try {
|
|
22918
23280
|
if (process.platform === "linux") {
|
|
22919
23281
|
const legacy = legacySystemdUserUnitPath();
|
|
22920
|
-
if (!
|
|
23282
|
+
if (!existsSync24(legacy))
|
|
22921
23283
|
return;
|
|
22922
23284
|
try {
|
|
22923
23285
|
execFileSync4("systemctl", ["--user", "disable", "--now", "failproofaid.service"], {
|
|
@@ -22934,7 +23296,7 @@ function removeLegacyUserService() {
|
|
|
22934
23296
|
} catch {}
|
|
22935
23297
|
} else {
|
|
22936
23298
|
const legacyAgent = legacyLaunchAgentPlistPath();
|
|
22937
|
-
if (
|
|
23299
|
+
if (existsSync24(legacyAgent)) {
|
|
22938
23300
|
try {
|
|
22939
23301
|
execFileSync4("launchctl", ["unload", "-w", legacyAgent], {
|
|
22940
23302
|
stdio: "ignore",
|
|
@@ -22944,7 +23306,7 @@ function removeLegacyUserService() {
|
|
|
22944
23306
|
unlinkSync3(legacyAgent);
|
|
22945
23307
|
}
|
|
22946
23308
|
const legacyDaemon = legacySharedLaunchDaemonPath();
|
|
22947
|
-
if (
|
|
23309
|
+
if (existsSync24(legacyDaemon)) {
|
|
22948
23310
|
try {
|
|
22949
23311
|
runPrivileged("launchctl", ["unload", "-w", legacyDaemon]);
|
|
22950
23312
|
} catch {}
|
|
@@ -22970,7 +23332,7 @@ User=${user}
|
|
|
22970
23332
|
# failproofaid is user-scope by construction and refuses to start without
|
|
22971
23333
|
# HOME ("HOME is not set; failproofaid is user-scope only"), so the one
|
|
22972
23334
|
# variable it cannot do without is not left to a version-dependent default.
|
|
22973
|
-
Environment="HOME=${
|
|
23335
|
+
Environment="HOME=${homedir34()}"
|
|
22974
23336
|
${envLine}ExecStart=${binaryPath}
|
|
22975
23337
|
Restart=on-failure
|
|
22976
23338
|
RestartSec=2
|
|
@@ -23013,9 +23375,9 @@ function launchdPlistContents(binaryPath, logDir2, workerCmd) {
|
|
|
23013
23375
|
<key>UserName</key>
|
|
23014
23376
|
<string>${escapeXml(serviceUser())}</string>
|
|
23015
23377
|
${envBlock} <key>StandardOutPath</key>
|
|
23016
|
-
<string>${escapeXml(
|
|
23378
|
+
<string>${escapeXml(resolve20(logDir2, "failproofaid.log"))}</string>
|
|
23017
23379
|
<key>StandardErrorPath</key>
|
|
23018
|
-
<string>${escapeXml(
|
|
23380
|
+
<string>${escapeXml(resolve20(logDir2, "failproofaid.err.log"))}</string>
|
|
23019
23381
|
</dict>
|
|
23020
23382
|
</plist>
|
|
23021
23383
|
`;
|
|
@@ -23057,77 +23419,874 @@ async function installDaemonService() {
|
|
|
23057
23419
|
runPrivileged("systemctl", ["enable", "--now", systemdUnitName()]);
|
|
23058
23420
|
} else {
|
|
23059
23421
|
const plistPath = launchdPlistPath();
|
|
23060
|
-
const logDir2 =
|
|
23061
|
-
|
|
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";
|
|
23062
24155
|
try {
|
|
23063
|
-
|
|
24156
|
+
await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
|
|
24157
|
+
cli: cliId,
|
|
24158
|
+
scope,
|
|
24159
|
+
error_type: errorType
|
|
24160
|
+
});
|
|
23064
24161
|
} catch {}
|
|
23065
|
-
|
|
23066
|
-
runPrivileged("launchctl", ["load", "-w", plistPath]);
|
|
24162
|
+
throw err;
|
|
23067
24163
|
}
|
|
23068
|
-
} catch (err) {
|
|
23069
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
23070
|
-
hookLogWarn(`daemon service install failed: ${msg}`);
|
|
23071
|
-
return { installed: false, reason: msg };
|
|
23072
24164
|
}
|
|
23073
|
-
|
|
23074
|
-
const
|
|
23075
|
-
|
|
23076
|
-
|
|
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}`);
|
|
23077
24206
|
}
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23082
|
-
|
|
23083
|
-
|
|
23084
|
-
break;
|
|
23085
|
-
if (Date.now() >= deadline)
|
|
23086
|
-
return false;
|
|
23087
|
-
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}`);
|
|
23088
24213
|
}
|
|
23089
|
-
|
|
23090
|
-
|
|
23091
|
-
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
|
|
23097
|
-
|
|
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.`);
|
|
23098
24223
|
try {
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
} catch {
|
|
23105
|
-
return "stopped";
|
|
23106
|
-
}
|
|
23107
|
-
}
|
|
23108
|
-
const plistPath = launchdPlistPath();
|
|
23109
|
-
if (!existsSync21(plistPath))
|
|
23110
|
-
return "not-installed";
|
|
23111
|
-
try {
|
|
23112
|
-
const root = typeof process.getuid === "function" && process.getuid() === 0;
|
|
23113
|
-
const args = ["print", `system/${launchdLabel()}`];
|
|
23114
|
-
const out = root ? execFileSync4("launchctl", args, {
|
|
23115
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
23116
|
-
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23117
|
-
}).toString() : execFileSync4("sudo", ["-n", "launchctl", ...args], {
|
|
23118
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
23119
|
-
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
23120
|
-
}).toString();
|
|
23121
|
-
return /state\s*=\s*running/.test(out) ? "running" : "stopped";
|
|
23122
|
-
} catch {
|
|
23123
|
-
return "stopped";
|
|
24224
|
+
await trackHookEvent2(getInstanceId2(), "multi_scope_warning_shown", {
|
|
24225
|
+
new_scope: scope,
|
|
24226
|
+
existing_scopes: duplicates,
|
|
24227
|
+
cli: selectedClis
|
|
24228
|
+
});
|
|
24229
|
+
} catch {}
|
|
23124
24230
|
}
|
|
23125
24231
|
}
|
|
23126
|
-
var
|
|
23127
|
-
var
|
|
23128
|
-
|
|
24232
|
+
var VALID_POLICY_NAMES2;
|
|
24233
|
+
var init_manager2 = __esm(() => {
|
|
24234
|
+
init_types();
|
|
24235
|
+
init_integrations();
|
|
24236
|
+
init_install_prompt();
|
|
23129
24237
|
init_hooks_config();
|
|
23130
|
-
|
|
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
|
+
];
|
|
23131
24290
|
});
|
|
23132
24291
|
|
|
23133
24292
|
// src/audit/cli.ts
|
|
@@ -23421,16 +24580,16 @@ __export(exports_configure_wizard, {
|
|
|
23421
24580
|
buildPresetChoices: () => buildPresetChoices,
|
|
23422
24581
|
buildAgentChoices: () => buildAgentChoices
|
|
23423
24582
|
});
|
|
23424
|
-
import { existsSync as
|
|
23425
|
-
import { homedir as
|
|
23426
|
-
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";
|
|
23427
24586
|
async function emit(event, props) {
|
|
23428
24587
|
try {
|
|
23429
24588
|
await trackHookEvent2(getInstanceId2(), event, props);
|
|
23430
24589
|
} catch {}
|
|
23431
24590
|
}
|
|
23432
24591
|
function homeify(p) {
|
|
23433
|
-
const home =
|
|
24592
|
+
const home = homedir38();
|
|
23434
24593
|
if (p === home)
|
|
23435
24594
|
return "~";
|
|
23436
24595
|
if (p.startsWith(home + sep5))
|
|
@@ -23544,8 +24703,8 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
|
|
|
23544
24703
|
const path3 = getConfigPathForScope(scope, cwd);
|
|
23545
24704
|
let config = {};
|
|
23546
24705
|
try {
|
|
23547
|
-
if (
|
|
23548
|
-
config = JSON.parse(
|
|
24706
|
+
if (existsSync28(path3))
|
|
24707
|
+
config = JSON.parse(readFileSync21(path3, "utf8"));
|
|
23549
24708
|
} catch {
|
|
23550
24709
|
return;
|
|
23551
24710
|
}
|
|
@@ -23554,7 +24713,7 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
|
|
|
23554
24713
|
else
|
|
23555
24714
|
config.customPoliciesEnabled = false;
|
|
23556
24715
|
try {
|
|
23557
|
-
|
|
24716
|
+
writeFileSync12(path3, JSON.stringify(config, null, 2) + `
|
|
23558
24717
|
`, "utf8");
|
|
23559
24718
|
} catch {}
|
|
23560
24719
|
}
|
|
@@ -23585,8 +24744,8 @@ function describeCustomPolicies(cwd) {
|
|
|
23585
24744
|
const scopes = [];
|
|
23586
24745
|
let fileCount = 0;
|
|
23587
24746
|
const dirs = [
|
|
23588
|
-
{ dir:
|
|
23589
|
-
{ dir:
|
|
24747
|
+
{ dir: resolve23(cwd, ".failproofai", "policies"), label: "project" },
|
|
24748
|
+
{ dir: resolve23(homedir38(), ".failproofai", "policies"), label: "global" }
|
|
23590
24749
|
];
|
|
23591
24750
|
for (const { dir, label } of dirs) {
|
|
23592
24751
|
const found = discoverPolicyFiles(dir);
|
|
@@ -23596,7 +24755,7 @@ function describeCustomPolicies(cwd) {
|
|
|
23596
24755
|
fileCount += found.length;
|
|
23597
24756
|
}
|
|
23598
24757
|
for (const name of findSkippedPolicyFiles(dir)) {
|
|
23599
|
-
warnings.push(`! ${homeify(
|
|
24758
|
+
warnings.push(`! ${homeify(resolve23(dir, name))} is NOT loaded — rename to ` + `${name.replace(/\.(js|mjs|ts)$/, "-policies.$1")}`);
|
|
23600
24759
|
}
|
|
23601
24760
|
}
|
|
23602
24761
|
return { active, warnings, fileCount, scopes };
|
|
@@ -23608,7 +24767,7 @@ function reviewLines(state) {
|
|
|
23608
24767
|
const assistantNames = clis.map((c3) => getIntegration(c3).displayName);
|
|
23609
24768
|
lines.push(` Where : ${where}`);
|
|
23610
24769
|
lines.push(` Assistants : ${assistantNames.length ? summarize(assistantNames, "assistants") : "(none)"}`);
|
|
23611
|
-
lines.push(` Policies : ${policies.length} enabled`);
|
|
24770
|
+
lines.push(policies.length === 0 ? " Policies : none enabled (add later: failproofai policies --install)" : ` Policies : ${policies.length} enabled`);
|
|
23612
24771
|
if (installDaemon && isDaemonSupportedPlatform()) {
|
|
23613
24772
|
lines.push(` Daemon : failproofaid, installed as a system service running as you`);
|
|
23614
24773
|
}
|
|
@@ -23635,15 +24794,15 @@ function reviewLines(state) {
|
|
|
23635
24794
|
return lines;
|
|
23636
24795
|
}
|
|
23637
24796
|
function firstRunMarkerPath() {
|
|
23638
|
-
return
|
|
24797
|
+
return resolve23(homedir38(), ".failproofai", ".launcher-configured");
|
|
23639
24798
|
}
|
|
23640
24799
|
function hasSeenLauncher() {
|
|
23641
|
-
return
|
|
24800
|
+
return existsSync28(firstRunMarkerPath());
|
|
23642
24801
|
}
|
|
23643
24802
|
function markLauncherSeen() {
|
|
23644
24803
|
try {
|
|
23645
|
-
|
|
23646
|
-
|
|
24804
|
+
mkdirSync14(resolve23(homedir38(), ".failproofai"), { recursive: true });
|
|
24805
|
+
writeFileSync12(firstRunMarkerPath(), "1", "utf8");
|
|
23647
24806
|
} catch {}
|
|
23648
24807
|
}
|
|
23649
24808
|
function anyHooksInstalledGlobally() {
|
|
@@ -23781,9 +24940,8 @@ Installing the failproofaid service needs root. Your password stays with sudo.
|
|
|
23781
24940
|
const presets = await multiSelect({
|
|
23782
24941
|
message: "What should we guard against?",
|
|
23783
24942
|
choices: presetChoices,
|
|
23784
|
-
minSelected: 1,
|
|
23785
24943
|
summaryNoun: "bundles",
|
|
23786
|
-
hint: "space toggles · combine presets · ↵ confirm",
|
|
24944
|
+
hint: "space toggles · combine presets · ↵ confirm · none is fine",
|
|
23787
24945
|
stdin,
|
|
23788
24946
|
stdout
|
|
23789
24947
|
});
|
|
@@ -23791,6 +24949,138 @@ Installing the failproofaid service needs root. Your password stays with sudo.
|
|
|
23791
24949
|
return cancel();
|
|
23792
24950
|
const policies = resolvePresetSelection(presets);
|
|
23793
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
|
+
}
|
|
23794
25084
|
const decision = await selectOne({
|
|
23795
25085
|
message: "Ready to apply?",
|
|
23796
25086
|
body: reviewLines({ scope, clis, policies, cwd, customEnabled, installDaemon: daemonWanted }),
|
|
@@ -23827,6 +25117,22 @@ Installing the failproofaid service needs root. Your password stays with sudo.
|
|
|
23827
25117
|
platform: process.platform
|
|
23828
25118
|
});
|
|
23829
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
|
+
}
|
|
23830
25136
|
await applied;
|
|
23831
25137
|
markLauncherSeen();
|
|
23832
25138
|
const customNote = customEnabled === true ? " + your custom policies" : customEnabled === false ? " · custom policies DISABLED" : "";
|
|
@@ -23838,6 +25144,7 @@ Installing the failproofaid service needs root. Your password stays with sudo.
|
|
|
23838
25144
|
var EVERYTHING = "__everything__", ALL_CLIS = "__all_clis__", CUSTOM = "__custom__", DIM_NOTE = "(auto-loaded)";
|
|
23839
25145
|
var init_configure_wizard = __esm(() => {
|
|
23840
25146
|
init_tui();
|
|
25147
|
+
init_collector_config();
|
|
23841
25148
|
init_integrations();
|
|
23842
25149
|
init_types();
|
|
23843
25150
|
init_manager2();
|
|
@@ -23848,6 +25155,8 @@ var init_configure_wizard = __esm(() => {
|
|
|
23848
25155
|
init_telemetry_id2();
|
|
23849
25156
|
init_daemon_service();
|
|
23850
25157
|
init_hook_logger();
|
|
25158
|
+
init_cloud_enrollment();
|
|
25159
|
+
init_cloud_connection();
|
|
23851
25160
|
});
|
|
23852
25161
|
|
|
23853
25162
|
// scripts/launch.ts
|
|
@@ -23856,8 +25165,8 @@ __export(exports_launch2, {
|
|
|
23856
25165
|
launch: () => launch2
|
|
23857
25166
|
});
|
|
23858
25167
|
import { spawn as spawn3 } from "child_process";
|
|
23859
|
-
import { realpathSync as
|
|
23860
|
-
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";
|
|
23861
25170
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23862
25171
|
import { createInterface as createInterface3 } from "node:readline";
|
|
23863
25172
|
function launch2(mode) {
|
|
@@ -23872,9 +25181,9 @@ function launch2(mode) {
|
|
|
23872
25181
|
process.env.PORT = port;
|
|
23873
25182
|
process.env.HOSTNAME = "0.0.0.0";
|
|
23874
25183
|
cmd = "node";
|
|
23875
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
23876
|
-
const serverJsPath =
|
|
23877
|
-
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)) {
|
|
23878
25187
|
let shadowMessage = null;
|
|
23879
25188
|
try {
|
|
23880
25189
|
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
@@ -23968,18 +25277,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
23968
25277
|
});
|
|
23969
25278
|
|
|
23970
25279
|
// bin/failproofai.mjs
|
|
23971
|
-
import { realpathSync as
|
|
23972
|
-
import { dirname as dirname11, resolve as
|
|
25280
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
25281
|
+
import { dirname as dirname11, resolve as resolve25 } from "path";
|
|
23973
25282
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23974
25283
|
// package.json
|
|
23975
|
-
var version = "1.0.0-beta.
|
|
25284
|
+
var version = "1.0.0-beta.3";
|
|
23976
25285
|
|
|
23977
25286
|
// bin/failproofai.mjs
|
|
23978
25287
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
23979
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
25288
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve25(dirname11(realpathSync5(fileURLToPath4(import.meta.url))), "..");
|
|
23980
25289
|
}
|
|
23981
25290
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
23982
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
25291
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve25(dirname11(realpathSync5(fileURLToPath4(import.meta.url))), "..", "dist");
|
|
23983
25292
|
}
|
|
23984
25293
|
var args = process.argv.slice(2);
|
|
23985
25294
|
if (args[0] === "p")
|
|
@@ -24516,11 +25825,127 @@ WHAT IT DOES
|
|
|
24516
25825
|
3. Policies \u2014 presets (combine any), Everything, or a custom pick
|
|
24517
25826
|
4. Review \u2014 confirms the exact files it will change, then applies
|
|
24518
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
|
+
|
|
24519
25859
|
Prefer flags? See \`failproofai policies --help\`.
|
|
24520
25860
|
`.trimStart());
|
|
24521
25861
|
process.exit(0);
|
|
24522
25862
|
}
|
|
24523
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
|
+
}
|
|
24524
25949
|
const { runConfigureWizard: runConfigureWizard2 } = await Promise.resolve().then(() => (init_configure_wizard(), exports_configure_wizard));
|
|
24525
25950
|
const result = await runConfigureWizard2();
|
|
24526
25951
|
await track("cli_configure_invoked", {
|