failproofai 0.0.11-beta.9 → 0.0.12-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.codex/hooks.json +6 -7
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- 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 +2 -2
- 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 +2 -2
- 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 +1 -1
- 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 +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 +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 +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 +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 +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 +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 +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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/[externals]__1_g_b3t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- 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]__0808sha._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e4-6d8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0k65l27._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wprfyc._.js → [root-of-the-server]__12e2gos._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0e446gb._.js → [root-of-the-server]__1ex6lhp._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +7 -7
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +49 -21
- 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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0y4a-0q.js → node_modules_html-to-image_es_index_0jg3fuk.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- 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 +10 -10
- package/.next/standalone/.next/static/chunks/{2z42u62k-8-_q.js → 04y44gpe0698_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3nj6g3xu9uy78.js → 0_75be6zseuxl.js} +1 -1
- package/.next/standalone/.next/static/chunks/0jtj8f7njt8w3.js +69 -0
- package/.next/standalone/.next/static/chunks/0mdiwb8ozs8j5.js +1 -0
- package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +2 -0
- package/.next/standalone/.next/static/chunks/1ua5armwfph8o.js +31 -0
- package/.next/standalone/.next/static/chunks/{277oc363p56n6.js → 1xjrv35s71etn.js} +2 -2
- package/.next/standalone/.next/static/chunks/{3ty6dhcuogout.js → 21cumi0uqy4mk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{07_d165p5h5ys.js → 2611bbf4e_1cs.js} +1 -1
- package/.next/standalone/.next/static/chunks/2hc_0--4nm0at.js +1 -0
- package/.next/standalone/.next/static/chunks/{1kvadxkgnapyj.js → 2l78594jx17e1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{168k-8z6k7e8z.css → 3i27c3hcriawq.css} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3lrm4f20fz89b.js → turbopack-0vavqo2d-tlt3.js} +1 -1
- package/.next/standalone/app/api/audit/invite/route.ts +10 -1
- package/.next/standalone/app/api/audit/run/route.ts +35 -0
- package/.next/standalone/app/api/auth/login-request/route.ts +2 -2
- package/.next/standalone/app/api/auth/login-verify/route.ts +10 -2
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +9 -1
- package/.next/standalone/app/audit/_components/audit-poster.tsx +11 -7
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +6 -4
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +23 -3
- package/.next/standalone/app/audit/_components/invite-dialog.tsx +6 -3
- package/.next/standalone/app/audit/_components/share-templates.ts +58 -28
- package/.next/standalone/app/audit/audit-styles.css +17 -22
- package/.next/standalone/app/globals.css +27 -2
- package/.next/standalone/app/policies/hooks-client.tsx +33 -24
- package/.next/standalone/components/reach-developers.tsx +10 -25
- package/.next/standalone/lib/auth/api-server-client.ts +5 -2
- package/.next/standalone/lib/client-telemetry.ts +4 -0
- package/.next/standalone/node_modules/@next/env/package.json +7 -8
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -252
- package/.next/standalone/package.json +6 -4
- package/.next/standalone/server.js +1 -1
- package/README.md +2 -2
- package/bin/failproofai.mjs +24 -5
- package/dist/cli.mjs +2330 -374
- package/lib/auth/api-server-client.ts +5 -2
- package/lib/client-telemetry.ts +4 -0
- package/package.json +6 -4
- package/scripts/launch.ts +30 -4
- package/scripts/postinstall.mjs +10 -1
- package/scripts/skew-log-filter.ts +46 -0
- package/scripts/validate-mdx.ts +139 -0
- package/src/audit/cli.ts +330 -0
- package/src/audit/open-browser.ts +69 -0
- package/src/auth/cli.ts +16 -13
- package/src/hooks/integrations.ts +22 -9
- package/src/hooks/types.ts +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +0 -3
- package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +0 -1
- package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +0 -41
- package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +0 -2
- package/.next/standalone/.next/static/chunks/29tg7deqmq32l.js +0 -1
- package/.next/standalone/.next/static/chunks/2h0dkzyy0vocp.js +0 -31
- /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → DUw2FaxT-IF_URNqcba-m}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// package.json
|
|
19
|
-
var version2 = "0.0.
|
|
19
|
+
var version2 = "0.0.12-beta.0";
|
|
20
20
|
var init_package = () => {};
|
|
21
21
|
|
|
22
22
|
// src/posthog-key.ts
|
|
@@ -656,6 +656,14 @@ function clearPolicies() {
|
|
|
656
656
|
g[REGISTRY_KEY] = [];
|
|
657
657
|
setIndexCache(null);
|
|
658
658
|
}
|
|
659
|
+
function getAllPolicies() {
|
|
660
|
+
return [...getRegistry()];
|
|
661
|
+
}
|
|
662
|
+
function setAllPolicies(policies) {
|
|
663
|
+
const g = globalThis;
|
|
664
|
+
g[REGISTRY_KEY] = [...policies];
|
|
665
|
+
setIndexCache(null);
|
|
666
|
+
}
|
|
659
667
|
var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__", DEFAULT_POLICY_NAMESPACE = "failproofai";
|
|
660
668
|
|
|
661
669
|
// src/hooks/builtin-policies.ts
|
|
@@ -4883,6 +4891,10 @@ async function parseFileContent(fileContent, source) {
|
|
|
4883
4891
|
entries.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
4884
4892
|
return { entries, rawLines, subagentIds: Array.from(subagentIdSet) };
|
|
4885
4893
|
}
|
|
4894
|
+
async function parseLogContent(fileContent, source = "session") {
|
|
4895
|
+
const result = await parseFileContent(fileContent, source);
|
|
4896
|
+
return result.entries;
|
|
4897
|
+
}
|
|
4886
4898
|
async function parseSessionLog(projectName, sessionId) {
|
|
4887
4899
|
const projectDir = resolveProjectPath(projectName);
|
|
4888
4900
|
const projectsPath = getClaudeProjectsPath();
|
|
@@ -6425,6 +6437,13 @@ function isMarkedHook(hook) {
|
|
|
6425
6437
|
const cmd = typeof h.command === "string" ? h.command : "";
|
|
6426
6438
|
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
6427
6439
|
}
|
|
6440
|
+
function stripLegacyVersion(settings) {
|
|
6441
|
+
if ("version" in settings) {
|
|
6442
|
+
delete settings.version;
|
|
6443
|
+
return true;
|
|
6444
|
+
}
|
|
6445
|
+
return false;
|
|
6446
|
+
}
|
|
6428
6447
|
function binaryExists(name) {
|
|
6429
6448
|
try {
|
|
6430
6449
|
const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
|
|
@@ -6819,10 +6838,7 @@ var init_integrations = __esm(() => {
|
|
|
6819
6838
|
}
|
|
6820
6839
|
},
|
|
6821
6840
|
readSettings(settingsPath) {
|
|
6822
|
-
|
|
6823
|
-
if (raw.version === undefined)
|
|
6824
|
-
raw.version = 1;
|
|
6825
|
-
return raw;
|
|
6841
|
+
return readJsonFile(settingsPath);
|
|
6826
6842
|
},
|
|
6827
6843
|
writeSettings(settingsPath, settings) {
|
|
6828
6844
|
writeJsonFile(settingsPath, settings);
|
|
@@ -6832,15 +6848,14 @@ var init_integrations = __esm(() => {
|
|
|
6832
6848
|
return {
|
|
6833
6849
|
type: "command",
|
|
6834
6850
|
command,
|
|
6835
|
-
timeout:
|
|
6851
|
+
timeout: 60,
|
|
6836
6852
|
[FAILPROOFAI_HOOK_MARKER]: true
|
|
6837
6853
|
};
|
|
6838
6854
|
},
|
|
6839
6855
|
isFailproofaiHook: isMarkedHook,
|
|
6840
6856
|
writeHookEntries(settings, binaryPath, scope) {
|
|
6841
6857
|
const s = settings;
|
|
6842
|
-
|
|
6843
|
-
s.version = 1;
|
|
6858
|
+
stripLegacyVersion(s);
|
|
6844
6859
|
if (!s.hooks)
|
|
6845
6860
|
s.hooks = {};
|
|
6846
6861
|
for (const eventType of CODEX_HOOK_EVENT_TYPES) {
|
|
@@ -6866,8 +6881,12 @@ var init_integrations = __esm(() => {
|
|
|
6866
6881
|
},
|
|
6867
6882
|
removeHooksFromFile(settingsPath) {
|
|
6868
6883
|
const settings = this.readSettings(settingsPath);
|
|
6869
|
-
|
|
6884
|
+
const hadVersion = stripLegacyVersion(settings);
|
|
6885
|
+
if (!settings.hooks) {
|
|
6886
|
+
if (hadVersion)
|
|
6887
|
+
this.writeSettings(settingsPath, settings);
|
|
6870
6888
|
return 0;
|
|
6889
|
+
}
|
|
6871
6890
|
let removed = 0;
|
|
6872
6891
|
for (const eventType of Object.keys(settings.hooks)) {
|
|
6873
6892
|
const matchers = settings.hooks[eventType];
|
|
@@ -6888,7 +6907,9 @@ var init_integrations = __esm(() => {
|
|
|
6888
6907
|
}
|
|
6889
6908
|
if (Object.keys(settings.hooks).length === 0)
|
|
6890
6909
|
delete settings.hooks;
|
|
6891
|
-
|
|
6910
|
+
if (removed > 0 || hadVersion) {
|
|
6911
|
+
this.writeSettings(settingsPath, settings);
|
|
6912
|
+
}
|
|
6892
6913
|
return removed;
|
|
6893
6914
|
},
|
|
6894
6915
|
hooksInstalledInSettings(scope, cwd) {
|
|
@@ -9039,7 +9060,7 @@ async function runLogin() {
|
|
|
9039
9060
|
const nowSecs = Math.floor(Date.now() / 1000);
|
|
9040
9061
|
const refreshUsable = existing.refresh_expires_at > nowSecs;
|
|
9041
9062
|
if (refreshUsable) {
|
|
9042
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9063
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9043
9064
|
source: "cli",
|
|
9044
9065
|
status: "already_signed_in",
|
|
9045
9066
|
user_id: existing.user.id
|
|
@@ -9072,7 +9093,7 @@ async function runLogin() {
|
|
|
9072
9093
|
email = "";
|
|
9073
9094
|
}
|
|
9074
9095
|
if (!email) {
|
|
9075
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9096
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9076
9097
|
source: "cli",
|
|
9077
9098
|
status: "aborted_invalid_email"
|
|
9078
9099
|
});
|
|
@@ -9091,7 +9112,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
|
|
|
9091
9112
|
`);
|
|
9092
9113
|
} catch (err) {
|
|
9093
9114
|
const isApi = err instanceof AuthApiError;
|
|
9094
|
-
trackHookEvent2(getInstanceId2(), "audit_otp_requested", {
|
|
9115
|
+
await trackHookEvent2(getInstanceId2(), "audit_otp_requested", {
|
|
9095
9116
|
source: "cli",
|
|
9096
9117
|
status: "failed",
|
|
9097
9118
|
error_code: isApi ? err.code : "upstream_unreachable",
|
|
@@ -9118,7 +9139,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
|
|
|
9118
9139
|
break;
|
|
9119
9140
|
} catch (err) {
|
|
9120
9141
|
const isApi = err instanceof AuthApiError;
|
|
9121
|
-
trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
|
|
9142
|
+
await trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
|
|
9122
9143
|
source: "cli",
|
|
9123
9144
|
status: "failed",
|
|
9124
9145
|
attempt: verifyAttempts,
|
|
@@ -9137,7 +9158,7 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
|
|
|
9137
9158
|
}
|
|
9138
9159
|
}
|
|
9139
9160
|
if (!tokenResp) {
|
|
9140
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9161
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9141
9162
|
source: "cli",
|
|
9142
9163
|
status: "exhausted_attempts",
|
|
9143
9164
|
attempts: verifyAttempts
|
|
@@ -9145,21 +9166,21 @@ ${GREEN}code sent.${RESET} ${DIM}check ${email} — expires in ${r.expires_in}s.
|
|
|
9145
9166
|
throw new CliError("Too many bad codes — start over.");
|
|
9146
9167
|
}
|
|
9147
9168
|
writeAuth(authFromTokenResponse(tokenResp));
|
|
9148
|
-
trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
|
|
9169
|
+
await trackHookEvent2(getInstanceId2(), "audit_otp_verified", {
|
|
9149
9170
|
source: "cli",
|
|
9150
9171
|
status: "success",
|
|
9151
9172
|
attempt: verifyAttempts,
|
|
9152
9173
|
user_id: tokenResp.user.id,
|
|
9153
9174
|
email: tokenResp.user.email
|
|
9154
9175
|
});
|
|
9155
|
-
trackHookEvent2(getInstanceId2(), "audit_user_identity_linked", {
|
|
9176
|
+
await trackHookEvent2(getInstanceId2(), "audit_user_identity_linked", {
|
|
9156
9177
|
source: "cli",
|
|
9157
9178
|
user_id: tokenResp.user.id,
|
|
9158
9179
|
email: tokenResp.user.email,
|
|
9159
9180
|
local_random_id: getInstanceId2(),
|
|
9160
9181
|
$set: { email: tokenResp.user.email, user_id: tokenResp.user.id }
|
|
9161
9182
|
});
|
|
9162
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9183
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_login_completed", {
|
|
9163
9184
|
source: "cli",
|
|
9164
9185
|
status: "success",
|
|
9165
9186
|
attempts: verifyAttempts,
|
|
@@ -9173,7 +9194,7 @@ ${GREEN}✓ signed in as ${tokenResp.user.email}${RESET}
|
|
|
9173
9194
|
async function runLogout() {
|
|
9174
9195
|
const existing = readAuth();
|
|
9175
9196
|
if (!existing) {
|
|
9176
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
|
|
9197
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
|
|
9177
9198
|
source: "cli",
|
|
9178
9199
|
had_session: false,
|
|
9179
9200
|
upstream: "noop"
|
|
@@ -9191,7 +9212,7 @@ async function runLogout() {
|
|
|
9191
9212
|
}
|
|
9192
9213
|
}
|
|
9193
9214
|
deleteAuth();
|
|
9194
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
|
|
9215
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_logout_completed", {
|
|
9195
9216
|
source: "cli",
|
|
9196
9217
|
had_session: true,
|
|
9197
9218
|
upstream,
|
|
@@ -9200,10 +9221,10 @@ async function runLogout() {
|
|
|
9200
9221
|
process.stdout.write(`${GREEN}✓ signed out as ${existing.user.email}.${RESET}
|
|
9201
9222
|
`);
|
|
9202
9223
|
}
|
|
9203
|
-
function runWhoami() {
|
|
9224
|
+
async function runWhoami() {
|
|
9204
9225
|
const existing = readAuth();
|
|
9205
9226
|
if (!existing) {
|
|
9206
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
|
|
9227
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
|
|
9207
9228
|
source: "cli",
|
|
9208
9229
|
authenticated: false
|
|
9209
9230
|
});
|
|
@@ -9212,7 +9233,7 @@ function runWhoami() {
|
|
|
9212
9233
|
process.exitCode = 1;
|
|
9213
9234
|
return;
|
|
9214
9235
|
}
|
|
9215
|
-
trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
|
|
9236
|
+
await trackHookEvent2(getInstanceId2(), "audit_cli_auth_whoami", {
|
|
9216
9237
|
source: "cli",
|
|
9217
9238
|
authenticated: true,
|
|
9218
9239
|
user_id: existing.user.id
|
|
@@ -9267,163 +9288,2252 @@ EXAMPLES
|
|
|
9267
9288
|
SUBCOMMANDS = new Set(["login", "logout", "whoami", "help"]);
|
|
9268
9289
|
});
|
|
9269
9290
|
|
|
9270
|
-
//
|
|
9271
|
-
import {
|
|
9272
|
-
import {
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
case "user":
|
|
9280
|
-
return `~/.claude/settings.json`;
|
|
9281
|
-
case "project":
|
|
9282
|
-
return `{cwd}/.claude/settings.json`;
|
|
9283
|
-
case "local":
|
|
9284
|
-
return `{cwd}/.claude/settings.local.json`;
|
|
9285
|
-
}
|
|
9286
|
-
}
|
|
9287
|
-
function resolveFailproofaiBinary2() {
|
|
9288
|
-
const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
|
|
9289
|
-
if (override && override.trim())
|
|
9290
|
-
return override.trim();
|
|
9291
|
+
// lib/claude-sessions.ts
|
|
9292
|
+
import { readdirSync as readdirSync8, statSync as statSync10, existsSync as existsSync14 } from "node:fs";
|
|
9293
|
+
import { join as join19, basename as basename4 } from "node:path";
|
|
9294
|
+
function getClaudeProjectsRoot() {
|
|
9295
|
+
return getClaudeProjectsPath();
|
|
9296
|
+
}
|
|
9297
|
+
function listClaudeProjects() {
|
|
9298
|
+
const root = getClaudeProjectsRoot();
|
|
9299
|
+
let entries;
|
|
9291
9300
|
try {
|
|
9292
|
-
|
|
9293
|
-
const result = execSync6(cmd, { encoding: "utf8" }).trim();
|
|
9294
|
-
return result.split(`
|
|
9295
|
-
`)[0].trim();
|
|
9301
|
+
entries = readdirSync8(root, { withFileTypes: true });
|
|
9296
9302
|
} catch {
|
|
9297
|
-
|
|
9298
|
-
` + "Install it globally first: npm install -g failproofai");
|
|
9299
|
-
}
|
|
9300
|
-
}
|
|
9301
|
-
function validatePolicyNames2(names) {
|
|
9302
|
-
const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
|
|
9303
|
-
if (invalid.length > 0) {
|
|
9304
|
-
const validList = [...VALID_POLICY_NAMES2].join(", ");
|
|
9305
|
-
throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
|
|
9306
|
-
` + `Valid policies: ${validList}`);
|
|
9303
|
+
return [];
|
|
9307
9304
|
}
|
|
9305
|
+
return entries.filter((e) => e.isDirectory()).map((e) => ({
|
|
9306
|
+
name: e.name,
|
|
9307
|
+
cwd: decodeFolderName(e.name),
|
|
9308
|
+
path: join19(root, e.name)
|
|
9309
|
+
}));
|
|
9308
9310
|
}
|
|
9309
|
-
function
|
|
9310
|
-
const
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
return true;
|
|
9317
|
-
});
|
|
9318
|
-
}
|
|
9319
|
-
function hooksInstalledInSettings2(scope, cwd) {
|
|
9320
|
-
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
9321
|
-
}
|
|
9322
|
-
async function installHooks2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli) {
|
|
9323
|
-
if (policyNames !== undefined && policyNames.length > 0) {
|
|
9324
|
-
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
9325
|
-
if (nonAllNames.length > 0)
|
|
9326
|
-
validatePolicyNames2(nonAllNames);
|
|
9327
|
-
if (policyNames.includes("all") && nonAllNames.length > 0) {
|
|
9328
|
-
throw new CliError(`"all" cannot be combined with specific policy names.
|
|
9329
|
-
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
9330
|
-
}
|
|
9311
|
+
function listClaudeTranscripts(project) {
|
|
9312
|
+
const out = [];
|
|
9313
|
+
let entries;
|
|
9314
|
+
try {
|
|
9315
|
+
entries = readdirSync8(project.path, { withFileTypes: true });
|
|
9316
|
+
} catch {
|
|
9317
|
+
return out;
|
|
9331
9318
|
}
|
|
9332
|
-
const
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9319
|
+
for (const entry of entries) {
|
|
9320
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
9321
|
+
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
9322
|
+
if (!UUID_RE4.test(sessionId))
|
|
9323
|
+
continue;
|
|
9324
|
+
const transcriptPath = join19(project.path, entry.name);
|
|
9336
9325
|
try {
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9326
|
+
const s = statSync10(transcriptPath);
|
|
9327
|
+
out.push({
|
|
9328
|
+
projectName: project.name,
|
|
9329
|
+
cwd: project.cwd,
|
|
9330
|
+
sessionId,
|
|
9331
|
+
transcriptPath,
|
|
9332
|
+
mtimeMs: s.mtimeMs,
|
|
9333
|
+
sizeBytes: s.size,
|
|
9334
|
+
isSubagent: false
|
|
9341
9335
|
});
|
|
9342
9336
|
} catch {}
|
|
9343
|
-
|
|
9337
|
+
} else if (entry.isDirectory() && UUID_RE4.test(entry.name)) {
|
|
9338
|
+
const subDir = join19(project.path, entry.name, "subagents");
|
|
9339
|
+
if (!existsSync14(subDir))
|
|
9340
|
+
continue;
|
|
9341
|
+
let subEntries;
|
|
9342
|
+
try {
|
|
9343
|
+
subEntries = readdirSync8(subDir, { withFileTypes: true });
|
|
9344
|
+
} catch {
|
|
9345
|
+
continue;
|
|
9346
|
+
}
|
|
9347
|
+
for (const sub of subEntries) {
|
|
9348
|
+
if (!sub.isFile() || !sub.name.endsWith(".jsonl"))
|
|
9349
|
+
continue;
|
|
9350
|
+
const agentId = sub.name.slice(0, -".jsonl".length);
|
|
9351
|
+
const transcriptPath = join19(subDir, sub.name);
|
|
9352
|
+
try {
|
|
9353
|
+
const s = statSync10(transcriptPath);
|
|
9354
|
+
out.push({
|
|
9355
|
+
projectName: project.name,
|
|
9356
|
+
cwd: project.cwd,
|
|
9357
|
+
sessionId: agentId,
|
|
9358
|
+
transcriptPath,
|
|
9359
|
+
mtimeMs: s.mtimeMs,
|
|
9360
|
+
sizeBytes: s.size,
|
|
9361
|
+
isSubagent: true
|
|
9362
|
+
});
|
|
9363
|
+
} catch {}
|
|
9364
|
+
}
|
|
9344
9365
|
}
|
|
9345
9366
|
}
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9367
|
+
return out;
|
|
9368
|
+
}
|
|
9369
|
+
var UUID_RE4;
|
|
9370
|
+
var init_claude_sessions = __esm(() => {
|
|
9371
|
+
init_paths();
|
|
9372
|
+
UUID_RE4 = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
9373
|
+
});
|
|
9374
|
+
|
|
9375
|
+
// src/audit/types.ts
|
|
9376
|
+
var AUDIT_TOOL_RESULT_MAX_BYTES, AUDIT_EXAMPLE_MAX_CHARS = 80, AUDIT_MAX_EXAMPLES_PER_NAME = 3;
|
|
9377
|
+
var init_types2 = __esm(() => {
|
|
9378
|
+
AUDIT_TOOL_RESULT_MAX_BYTES = 64 * 1024;
|
|
9379
|
+
});
|
|
9380
|
+
|
|
9381
|
+
// src/audit/cli-adapters/shared.ts
|
|
9382
|
+
function truncateToUtf8Bytes(s, maxBytes) {
|
|
9383
|
+
const buf = Buffer.from(s, "utf-8");
|
|
9384
|
+
if (buf.byteLength <= maxBytes)
|
|
9385
|
+
return s;
|
|
9386
|
+
let end = maxBytes;
|
|
9387
|
+
while (end > 0 && (buf[end] & 192) === 128)
|
|
9388
|
+
end--;
|
|
9389
|
+
return buf.subarray(0, end).toString("utf-8");
|
|
9390
|
+
}
|
|
9391
|
+
function logEntriesToEvents(entries, ctx) {
|
|
9392
|
+
const events = [];
|
|
9393
|
+
for (const entry of entries) {
|
|
9394
|
+
if (entry.type !== "assistant")
|
|
9395
|
+
continue;
|
|
9396
|
+
for (const block of entry.message.content) {
|
|
9397
|
+
if (block.type !== "tool_use")
|
|
9398
|
+
continue;
|
|
9399
|
+
const rawName = block.name;
|
|
9400
|
+
const canonicalName = canonicalizeToolName(rawName, ctx.cli) ?? rawName;
|
|
9401
|
+
const canonicalInput = canonicalizeToolInput(canonicalName, block.input, ctx.cli);
|
|
9402
|
+
let toolResultText;
|
|
9403
|
+
if (block.result?.content) {
|
|
9404
|
+
toolResultText = truncateToUtf8Bytes(block.result.content, AUDIT_TOOL_RESULT_MAX_BYTES);
|
|
9405
|
+
}
|
|
9406
|
+
events.push({
|
|
9407
|
+
cli: ctx.cli,
|
|
9408
|
+
sessionId: ctx.sessionId,
|
|
9409
|
+
transcriptPath: ctx.transcriptPath,
|
|
9410
|
+
cwd: ctx.cwd,
|
|
9411
|
+
timestamp: entry.timestamp,
|
|
9412
|
+
toolName: canonicalName,
|
|
9413
|
+
rawToolName: rawName,
|
|
9414
|
+
toolInput: canonicalInput ?? {},
|
|
9415
|
+
toolResultText
|
|
9416
|
+
});
|
|
9356
9417
|
}
|
|
9357
|
-
selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
|
|
9358
|
-
} else {
|
|
9359
|
-
const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
|
|
9360
|
-
selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
|
|
9361
9418
|
}
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9419
|
+
return events;
|
|
9420
|
+
}
|
|
9421
|
+
var init_shared = __esm(() => {
|
|
9422
|
+
init_tool_name_canonicalize();
|
|
9423
|
+
init_types2();
|
|
9424
|
+
});
|
|
9425
|
+
|
|
9426
|
+
// src/audit/cli-adapters/claude.ts
|
|
9427
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
9428
|
+
async function listClaudeTranscriptMetadata(opts = {}) {
|
|
9429
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9430
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9431
|
+
const out = [];
|
|
9432
|
+
for (const project of listClaudeProjects()) {
|
|
9433
|
+
if (projectFilter && !projectFilter.has(project.cwd))
|
|
9434
|
+
continue;
|
|
9435
|
+
let transcripts;
|
|
9368
9436
|
try {
|
|
9369
|
-
|
|
9370
|
-
} catch
|
|
9371
|
-
|
|
9437
|
+
transcripts = listClaudeTranscripts(project);
|
|
9438
|
+
} catch {
|
|
9439
|
+
continue;
|
|
9440
|
+
}
|
|
9441
|
+
for (const t of transcripts) {
|
|
9442
|
+
if (t.mtimeMs < sinceMs)
|
|
9443
|
+
continue;
|
|
9444
|
+
out.push({
|
|
9445
|
+
cli: "claude",
|
|
9446
|
+
projectName: project.name,
|
|
9447
|
+
sessionId: t.sessionId,
|
|
9448
|
+
transcriptPath: t.transcriptPath,
|
|
9449
|
+
mtimeMs: t.mtimeMs,
|
|
9450
|
+
sizeBytes: t.sizeBytes
|
|
9451
|
+
});
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
return out;
|
|
9455
|
+
}
|
|
9456
|
+
async function streamClaudeEvents(meta) {
|
|
9457
|
+
let content;
|
|
9458
|
+
try {
|
|
9459
|
+
content = await readFile13(meta.transcriptPath, "utf-8");
|
|
9460
|
+
} catch {
|
|
9461
|
+
return [];
|
|
9462
|
+
}
|
|
9463
|
+
const source = "session";
|
|
9464
|
+
let entries;
|
|
9465
|
+
try {
|
|
9466
|
+
entries = await parseLogContent(content, source);
|
|
9467
|
+
} catch {
|
|
9468
|
+
return [];
|
|
9469
|
+
}
|
|
9470
|
+
let cwd = "";
|
|
9471
|
+
for (const line of content.split(`
|
|
9472
|
+
`, 50)) {
|
|
9473
|
+
if (!line.trim())
|
|
9474
|
+
continue;
|
|
9475
|
+
try {
|
|
9476
|
+
const parsed = JSON.parse(line);
|
|
9477
|
+
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
9478
|
+
cwd = parsed.cwd;
|
|
9479
|
+
break;
|
|
9480
|
+
}
|
|
9481
|
+
} catch {}
|
|
9482
|
+
}
|
|
9483
|
+
return logEntriesToEvents(entries, {
|
|
9484
|
+
cli: "claude",
|
|
9485
|
+
sessionId: meta.sessionId,
|
|
9486
|
+
transcriptPath: meta.transcriptPath,
|
|
9487
|
+
cwd
|
|
9488
|
+
});
|
|
9489
|
+
}
|
|
9490
|
+
var init_claude = __esm(() => {
|
|
9491
|
+
init_claude_sessions();
|
|
9492
|
+
init_log_entries();
|
|
9493
|
+
init_shared();
|
|
9494
|
+
});
|
|
9495
|
+
|
|
9496
|
+
// src/audit/cli-adapters/codex.ts
|
|
9497
|
+
import { statSync as statSync11 } from "node:fs";
|
|
9498
|
+
async function listCodexTranscriptMetadata(opts = {}) {
|
|
9499
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9500
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9501
|
+
const out = [];
|
|
9502
|
+
const projects = await getCodexProjects();
|
|
9503
|
+
for (const project of projects) {
|
|
9504
|
+
const { cwd, sessions } = await getCodexSessionsByEncodedName(project.name);
|
|
9505
|
+
const effectiveCwd = cwd ?? "";
|
|
9506
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9507
|
+
continue;
|
|
9508
|
+
for (const s of sessions) {
|
|
9509
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9510
|
+
if (mtimeMs < sinceMs)
|
|
9511
|
+
continue;
|
|
9512
|
+
let sizeBytes = 0;
|
|
9372
9513
|
try {
|
|
9373
|
-
|
|
9374
|
-
scope,
|
|
9375
|
-
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
9376
|
-
});
|
|
9514
|
+
sizeBytes = statSync11(s.path).size;
|
|
9377
9515
|
} catch {}
|
|
9378
|
-
|
|
9379
|
-
|
|
9516
|
+
if (!s.sessionId)
|
|
9517
|
+
continue;
|
|
9518
|
+
out.push({
|
|
9519
|
+
cli: "codex",
|
|
9520
|
+
projectName: project.name,
|
|
9521
|
+
sessionId: s.sessionId,
|
|
9522
|
+
transcriptPath: s.path,
|
|
9523
|
+
mtimeMs,
|
|
9524
|
+
sizeBytes
|
|
9525
|
+
});
|
|
9380
9526
|
}
|
|
9381
|
-
|
|
9527
|
+
}
|
|
9528
|
+
return out;
|
|
9529
|
+
}
|
|
9530
|
+
async function streamCodexEvents(meta) {
|
|
9531
|
+
const log = await getCodexSessionLog(meta.sessionId);
|
|
9532
|
+
if (!log)
|
|
9533
|
+
return [];
|
|
9534
|
+
return logEntriesToEvents(log.entries, {
|
|
9535
|
+
cli: "codex",
|
|
9536
|
+
sessionId: meta.sessionId,
|
|
9537
|
+
transcriptPath: meta.transcriptPath,
|
|
9538
|
+
cwd: log.cwd ?? ""
|
|
9539
|
+
});
|
|
9540
|
+
}
|
|
9541
|
+
var init_codex = __esm(() => {
|
|
9542
|
+
init_codex_projects();
|
|
9543
|
+
init_codex_sessions();
|
|
9544
|
+
init_shared();
|
|
9545
|
+
});
|
|
9546
|
+
|
|
9547
|
+
// src/audit/cli-adapters/copilot.ts
|
|
9548
|
+
import { statSync as statSync12 } from "node:fs";
|
|
9549
|
+
async function listCopilotTranscriptMetadata(opts = {}) {
|
|
9550
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9551
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9552
|
+
const out = [];
|
|
9553
|
+
const projects = await getCopilotProjects();
|
|
9554
|
+
for (const project of projects) {
|
|
9555
|
+
const { cwd, sessions } = await getCopilotSessionsByEncodedName(project.name);
|
|
9556
|
+
const effectiveCwd = cwd ?? "";
|
|
9557
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9558
|
+
continue;
|
|
9559
|
+
for (const s of sessions) {
|
|
9560
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9561
|
+
if (mtimeMs < sinceMs)
|
|
9562
|
+
continue;
|
|
9563
|
+
let sizeBytes = 0;
|
|
9382
9564
|
try {
|
|
9383
|
-
|
|
9384
|
-
scope,
|
|
9385
|
-
error_type: "no_hooks_registered"
|
|
9386
|
-
});
|
|
9565
|
+
sizeBytes = statSync12(s.path).size;
|
|
9387
9566
|
} catch {}
|
|
9388
|
-
|
|
9389
|
-
|
|
9567
|
+
if (!s.sessionId)
|
|
9568
|
+
continue;
|
|
9569
|
+
out.push({
|
|
9570
|
+
cli: "copilot",
|
|
9571
|
+
projectName: project.name,
|
|
9572
|
+
sessionId: s.sessionId,
|
|
9573
|
+
transcriptPath: s.path,
|
|
9574
|
+
mtimeMs,
|
|
9575
|
+
sizeBytes
|
|
9576
|
+
});
|
|
9390
9577
|
}
|
|
9391
|
-
console.log(`
|
|
9392
|
-
Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
9393
|
-
}
|
|
9394
|
-
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
9395
|
-
console.log(`
|
|
9396
|
-
Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
9397
|
-
`);
|
|
9398
|
-
if (removeCustomHooks) {
|
|
9399
|
-
console.log("Custom hooks path cleared.");
|
|
9400
|
-
} else if (configToWrite.customPoliciesPath) {
|
|
9401
|
-
console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
|
|
9402
9578
|
}
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9579
|
+
return out;
|
|
9580
|
+
}
|
|
9581
|
+
async function streamCopilotEvents(meta) {
|
|
9582
|
+
const log = await getCopilotSessionLog(meta.sessionId);
|
|
9583
|
+
if (!log)
|
|
9584
|
+
return [];
|
|
9585
|
+
return logEntriesToEvents(log.entries, {
|
|
9586
|
+
cli: "copilot",
|
|
9587
|
+
sessionId: meta.sessionId,
|
|
9588
|
+
transcriptPath: meta.transcriptPath,
|
|
9589
|
+
cwd: log.cwd ?? ""
|
|
9590
|
+
});
|
|
9591
|
+
}
|
|
9592
|
+
var init_copilot = __esm(() => {
|
|
9593
|
+
init_copilot_projects();
|
|
9594
|
+
init_copilot_sessions();
|
|
9595
|
+
init_shared();
|
|
9596
|
+
});
|
|
9597
|
+
|
|
9598
|
+
// src/audit/cli-adapters/cursor.ts
|
|
9599
|
+
import { statSync as statSync13 } from "node:fs";
|
|
9600
|
+
async function listCursorTranscriptMetadata(opts = {}) {
|
|
9601
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9602
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9603
|
+
const out = [];
|
|
9604
|
+
const projects = await getCursorProjects();
|
|
9605
|
+
for (const project of projects) {
|
|
9606
|
+
const { cwd, sessions } = await getCursorSessionsByEncodedName(project.name);
|
|
9607
|
+
const effectiveCwd = cwd ?? "";
|
|
9608
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9609
|
+
continue;
|
|
9610
|
+
for (const s of sessions) {
|
|
9611
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9612
|
+
if (mtimeMs < sinceMs)
|
|
9613
|
+
continue;
|
|
9614
|
+
let sizeBytes = 0;
|
|
9414
9615
|
try {
|
|
9415
|
-
|
|
9416
|
-
cli: cliId,
|
|
9417
|
-
scope,
|
|
9418
|
-
error_type: errorType
|
|
9419
|
-
});
|
|
9616
|
+
sizeBytes = statSync13(s.path).size;
|
|
9420
9617
|
} catch {}
|
|
9421
|
-
|
|
9618
|
+
if (!s.sessionId)
|
|
9619
|
+
continue;
|
|
9620
|
+
out.push({
|
|
9621
|
+
cli: "cursor",
|
|
9622
|
+
projectName: project.name,
|
|
9623
|
+
sessionId: s.sessionId,
|
|
9624
|
+
transcriptPath: s.path,
|
|
9625
|
+
mtimeMs,
|
|
9626
|
+
sizeBytes
|
|
9627
|
+
});
|
|
9422
9628
|
}
|
|
9423
9629
|
}
|
|
9630
|
+
return out;
|
|
9631
|
+
}
|
|
9632
|
+
async function streamCursorEvents(meta) {
|
|
9633
|
+
const log = await getCursorSessionLog(meta.sessionId);
|
|
9634
|
+
if (!log)
|
|
9635
|
+
return [];
|
|
9636
|
+
return logEntriesToEvents(log.entries, {
|
|
9637
|
+
cli: "cursor",
|
|
9638
|
+
sessionId: meta.sessionId,
|
|
9639
|
+
transcriptPath: meta.transcriptPath,
|
|
9640
|
+
cwd: log.cwd ?? ""
|
|
9641
|
+
});
|
|
9642
|
+
}
|
|
9643
|
+
var init_cursor = __esm(() => {
|
|
9644
|
+
init_cursor_projects();
|
|
9645
|
+
init_cursor_sessions();
|
|
9646
|
+
init_shared();
|
|
9647
|
+
});
|
|
9648
|
+
|
|
9649
|
+
// lib/opencode-sessions.ts
|
|
9650
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9651
|
+
function runOpenCodeDb2(sql) {
|
|
9424
9652
|
try {
|
|
9425
|
-
const
|
|
9426
|
-
|
|
9653
|
+
const stdout = execFileSync3("opencode", ["db", "--format", "json", sql], {
|
|
9654
|
+
encoding: "utf8",
|
|
9655
|
+
timeout: 5000,
|
|
9656
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9657
|
+
});
|
|
9658
|
+
if (!stdout.trim())
|
|
9659
|
+
return [];
|
|
9660
|
+
const parsed = JSON.parse(stdout);
|
|
9661
|
+
if (!Array.isArray(parsed))
|
|
9662
|
+
return null;
|
|
9663
|
+
return parsed;
|
|
9664
|
+
} catch {
|
|
9665
|
+
return null;
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9668
|
+
function isPlainObject(value) {
|
|
9669
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9670
|
+
}
|
|
9671
|
+
function parseDataColumn(raw) {
|
|
9672
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
9673
|
+
return null;
|
|
9674
|
+
try {
|
|
9675
|
+
const parsed = JSON.parse(raw);
|
|
9676
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
9677
|
+
} catch {
|
|
9678
|
+
return null;
|
|
9679
|
+
}
|
|
9680
|
+
}
|
|
9681
|
+
function readContentText(data) {
|
|
9682
|
+
if (typeof data.text === "string")
|
|
9683
|
+
return data.text;
|
|
9684
|
+
if (typeof data.content === "string")
|
|
9685
|
+
return data.content;
|
|
9686
|
+
return "";
|
|
9687
|
+
}
|
|
9688
|
+
function translateMessage(msgRow, partRows, source) {
|
|
9689
|
+
const msgData = parseDataColumn(msgRow.data) ?? {};
|
|
9690
|
+
const role = typeof msgData.role === "string" ? msgData.role : "system";
|
|
9691
|
+
const date = new Date(msgRow.time_created);
|
|
9692
|
+
const timestamp = date.toISOString();
|
|
9693
|
+
const raw = { uuid: msgRow.id, parentUuid: null };
|
|
9694
|
+
const base = baseEntry(raw, timestamp, date, source);
|
|
9695
|
+
const content = [];
|
|
9696
|
+
let userText = "";
|
|
9697
|
+
for (const p of partRows) {
|
|
9698
|
+
const data = parseDataColumn(p.data);
|
|
9699
|
+
if (!data)
|
|
9700
|
+
continue;
|
|
9701
|
+
const type = typeof data.type === "string" ? data.type : "unknown";
|
|
9702
|
+
if (type === "text") {
|
|
9703
|
+
const text = readContentText(data);
|
|
9704
|
+
if (text) {
|
|
9705
|
+
content.push({ type: "text", text });
|
|
9706
|
+
userText += (userText ? `
|
|
9707
|
+
` : "") + text;
|
|
9708
|
+
}
|
|
9709
|
+
continue;
|
|
9710
|
+
}
|
|
9711
|
+
if (type === "tool") {
|
|
9712
|
+
const toolName = typeof data.tool === "string" ? data.tool : typeof data.name === "string" ? data.name : "tool";
|
|
9713
|
+
const state = isPlainObject(data.state) ? data.state : null;
|
|
9714
|
+
const input = state && isPlainObject(state.input) ? state.input : isPlainObject(data.input) ? data.input : isPlainObject(data.args) ? data.args : {};
|
|
9715
|
+
const block = {
|
|
9716
|
+
type: "tool_use",
|
|
9717
|
+
id: p.id,
|
|
9718
|
+
name: toolName,
|
|
9719
|
+
input
|
|
9720
|
+
};
|
|
9721
|
+
const status = state && typeof state.status === "string" ? state.status : "";
|
|
9722
|
+
if (state && (status === "completed" || status === "error")) {
|
|
9723
|
+
const errorText = status === "error" && typeof state.error === "string" ? state.error : null;
|
|
9724
|
+
const rawOutput = errorText ?? state.output;
|
|
9725
|
+
const contentText = typeof rawOutput === "string" ? rawOutput : rawOutput != null ? JSON.stringify(rawOutput) : "";
|
|
9726
|
+
const time = isPlainObject(state.time) ? state.time : {};
|
|
9727
|
+
const startMs = typeof time.start === "number" ? time.start : p.time_created;
|
|
9728
|
+
const endMs = typeof time.end === "number" ? time.end : p.time_updated;
|
|
9729
|
+
const durationMs = Math.max(0, endMs - startMs);
|
|
9730
|
+
const date2 = new Date(endMs);
|
|
9731
|
+
block.result = {
|
|
9732
|
+
timestamp: date2.toISOString(),
|
|
9733
|
+
timestampFormatted: formatTimestamp(date2),
|
|
9734
|
+
content: contentText,
|
|
9735
|
+
durationMs,
|
|
9736
|
+
durationFormatted: formatDuration(durationMs)
|
|
9737
|
+
};
|
|
9738
|
+
}
|
|
9739
|
+
content.push(block);
|
|
9740
|
+
continue;
|
|
9741
|
+
}
|
|
9742
|
+
content.push({ type: "text", text: `[opencode ${type}]` });
|
|
9743
|
+
}
|
|
9744
|
+
if (role === "user") {
|
|
9745
|
+
const entry2 = {
|
|
9746
|
+
...base,
|
|
9747
|
+
type: "user",
|
|
9748
|
+
message: { role: "user", content: userText }
|
|
9749
|
+
};
|
|
9750
|
+
return entry2;
|
|
9751
|
+
}
|
|
9752
|
+
if (role === "assistant") {
|
|
9753
|
+
const modelInfo = isPlainObject(msgData.model) ? msgData.model : null;
|
|
9754
|
+
const modelStr = modelInfo && typeof modelInfo.modelID === "string" ? modelInfo.modelID : undefined;
|
|
9755
|
+
const entry2 = {
|
|
9756
|
+
...base,
|
|
9757
|
+
type: "assistant",
|
|
9758
|
+
message: { role: "assistant", content, model: modelStr }
|
|
9759
|
+
};
|
|
9760
|
+
return entry2;
|
|
9761
|
+
}
|
|
9762
|
+
const entry = {
|
|
9763
|
+
...base,
|
|
9764
|
+
type: "system",
|
|
9765
|
+
raw: { id: msgRow.id, role, parts: content }
|
|
9766
|
+
};
|
|
9767
|
+
return entry;
|
|
9768
|
+
}
|
|
9769
|
+
async function getOpenCodeSessionLog(sessionId) {
|
|
9770
|
+
if (!sessionId || !/^[A-Za-z0-9_-]+$/.test(sessionId))
|
|
9771
|
+
return null;
|
|
9772
|
+
const sessions = runOpenCodeDb2(`SELECT id, project_id, slug, directory, title, time_created, time_updated FROM session WHERE id = '${sessionId}'`);
|
|
9773
|
+
if (!sessions || sessions.length === 0)
|
|
9774
|
+
return null;
|
|
9775
|
+
const session = sessions[0];
|
|
9776
|
+
const messages = runOpenCodeDb2(`SELECT id, session_id, time_created, time_updated, data FROM message WHERE session_id = '${sessionId}' ORDER BY time_created ASC`);
|
|
9777
|
+
const parts = runOpenCodeDb2(`SELECT id, message_id, session_id, time_created, time_updated, data FROM part WHERE session_id = '${sessionId}' ORDER BY time_created ASC`);
|
|
9778
|
+
if (!messages)
|
|
9779
|
+
return { entries: [], rawLines: [], cwd: session.directory ?? undefined, filePath: `opencode://${sessionId}` };
|
|
9780
|
+
const partsByMessage = new Map;
|
|
9781
|
+
for (const p of parts ?? []) {
|
|
9782
|
+
let bucket = partsByMessage.get(p.message_id);
|
|
9783
|
+
if (!bucket) {
|
|
9784
|
+
bucket = [];
|
|
9785
|
+
partsByMessage.set(p.message_id, bucket);
|
|
9786
|
+
}
|
|
9787
|
+
bucket.push(p);
|
|
9788
|
+
}
|
|
9789
|
+
const entries = [];
|
|
9790
|
+
const rawLines = [];
|
|
9791
|
+
for (const msg of messages) {
|
|
9792
|
+
const partRows = partsByMessage.get(msg.id) ?? [];
|
|
9793
|
+
entries.push(translateMessage(msg, partRows, "session"));
|
|
9794
|
+
const data = parseDataColumn(msg.data);
|
|
9795
|
+
rawLines.push({
|
|
9796
|
+
id: msg.id,
|
|
9797
|
+
session_id: msg.session_id,
|
|
9798
|
+
time_created: msg.time_created,
|
|
9799
|
+
data: data ?? msg.data
|
|
9800
|
+
});
|
|
9801
|
+
}
|
|
9802
|
+
return {
|
|
9803
|
+
entries,
|
|
9804
|
+
rawLines,
|
|
9805
|
+
cwd: session.directory ?? undefined,
|
|
9806
|
+
filePath: `opencode://${sessionId}`
|
|
9807
|
+
};
|
|
9808
|
+
}
|
|
9809
|
+
var getCachedOpenCodeSessionLog;
|
|
9810
|
+
var init_opencode_sessions = __esm(() => {
|
|
9811
|
+
init_log_entries();
|
|
9812
|
+
getCachedOpenCodeSessionLog = runtimeCache((sessionId) => getOpenCodeSessionLog(sessionId), 30, { maxSize: 50 });
|
|
9813
|
+
});
|
|
9814
|
+
|
|
9815
|
+
// src/audit/cli-adapters/opencode.ts
|
|
9816
|
+
async function listOpenCodeTranscriptMetadata(opts = {}) {
|
|
9817
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9818
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9819
|
+
const out = [];
|
|
9820
|
+
const projects = await getOpenCodeProjects();
|
|
9821
|
+
for (const project of projects) {
|
|
9822
|
+
const { cwd, sessions } = await getOpenCodeSessionsByEncodedName(project.name);
|
|
9823
|
+
const effectiveCwd = cwd ?? "";
|
|
9824
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9825
|
+
continue;
|
|
9826
|
+
for (const s of sessions) {
|
|
9827
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9828
|
+
if (mtimeMs < sinceMs)
|
|
9829
|
+
continue;
|
|
9830
|
+
if (!s.sessionId)
|
|
9831
|
+
continue;
|
|
9832
|
+
out.push({
|
|
9833
|
+
cli: "opencode",
|
|
9834
|
+
projectName: project.name,
|
|
9835
|
+
sessionId: s.sessionId,
|
|
9836
|
+
transcriptPath: s.path,
|
|
9837
|
+
mtimeMs,
|
|
9838
|
+
sizeBytes: 0
|
|
9839
|
+
});
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
return out;
|
|
9843
|
+
}
|
|
9844
|
+
async function streamOpenCodeEvents(meta) {
|
|
9845
|
+
const log = await getOpenCodeSessionLog(meta.sessionId);
|
|
9846
|
+
if (!log)
|
|
9847
|
+
return [];
|
|
9848
|
+
return logEntriesToEvents(log.entries, {
|
|
9849
|
+
cli: "opencode",
|
|
9850
|
+
sessionId: meta.sessionId,
|
|
9851
|
+
transcriptPath: meta.transcriptPath,
|
|
9852
|
+
cwd: log.cwd ?? ""
|
|
9853
|
+
});
|
|
9854
|
+
}
|
|
9855
|
+
var init_opencode = __esm(() => {
|
|
9856
|
+
init_opencode_projects();
|
|
9857
|
+
init_opencode_sessions();
|
|
9858
|
+
init_shared();
|
|
9859
|
+
});
|
|
9860
|
+
|
|
9861
|
+
// src/audit/cli-adapters/pi.ts
|
|
9862
|
+
import { statSync as statSync14 } from "node:fs";
|
|
9863
|
+
async function listPiTranscriptMetadata(opts = {}) {
|
|
9864
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9865
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9866
|
+
const out = [];
|
|
9867
|
+
const projects = await getPiProjects();
|
|
9868
|
+
for (const project of projects) {
|
|
9869
|
+
const { cwd, sessions } = await getPiSessionsByEncodedName(project.name);
|
|
9870
|
+
const effectiveCwd = cwd ?? "";
|
|
9871
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9872
|
+
continue;
|
|
9873
|
+
for (const s of sessions) {
|
|
9874
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9875
|
+
if (mtimeMs < sinceMs)
|
|
9876
|
+
continue;
|
|
9877
|
+
let sizeBytes = 0;
|
|
9878
|
+
try {
|
|
9879
|
+
sizeBytes = statSync14(s.path).size;
|
|
9880
|
+
} catch {}
|
|
9881
|
+
if (!s.sessionId)
|
|
9882
|
+
continue;
|
|
9883
|
+
out.push({
|
|
9884
|
+
cli: "pi",
|
|
9885
|
+
projectName: project.name,
|
|
9886
|
+
sessionId: s.sessionId,
|
|
9887
|
+
transcriptPath: s.path,
|
|
9888
|
+
mtimeMs,
|
|
9889
|
+
sizeBytes
|
|
9890
|
+
});
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
return out;
|
|
9894
|
+
}
|
|
9895
|
+
async function streamPiEvents(meta) {
|
|
9896
|
+
const log = await getPiSessionLog(meta.sessionId);
|
|
9897
|
+
if (!log)
|
|
9898
|
+
return [];
|
|
9899
|
+
return logEntriesToEvents(log.entries, {
|
|
9900
|
+
cli: "pi",
|
|
9901
|
+
sessionId: meta.sessionId,
|
|
9902
|
+
transcriptPath: meta.transcriptPath,
|
|
9903
|
+
cwd: log.cwd ?? ""
|
|
9904
|
+
});
|
|
9905
|
+
}
|
|
9906
|
+
var init_pi = __esm(() => {
|
|
9907
|
+
init_pi_projects();
|
|
9908
|
+
init_pi_sessions();
|
|
9909
|
+
init_shared();
|
|
9910
|
+
});
|
|
9911
|
+
|
|
9912
|
+
// src/audit/cli-adapters/gemini.ts
|
|
9913
|
+
import { statSync as statSync15 } from "node:fs";
|
|
9914
|
+
async function listGeminiTranscriptMetadata(opts = {}) {
|
|
9915
|
+
const projectFilter = opts.projects ? new Set(opts.projects) : null;
|
|
9916
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
9917
|
+
const out = [];
|
|
9918
|
+
const projects = await getGeminiProjects();
|
|
9919
|
+
for (const project of projects) {
|
|
9920
|
+
const { cwd, sessions } = await getGeminiSessionsByEncodedName(project.name);
|
|
9921
|
+
const effectiveCwd = cwd ?? "";
|
|
9922
|
+
if (projectFilter && !projectFilter.has(effectiveCwd))
|
|
9923
|
+
continue;
|
|
9924
|
+
for (const s of sessions) {
|
|
9925
|
+
const mtimeMs = s.lastModified.getTime();
|
|
9926
|
+
if (mtimeMs < sinceMs)
|
|
9927
|
+
continue;
|
|
9928
|
+
let sizeBytes = 0;
|
|
9929
|
+
try {
|
|
9930
|
+
sizeBytes = statSync15(s.path).size;
|
|
9931
|
+
} catch {}
|
|
9932
|
+
if (!s.sessionId)
|
|
9933
|
+
continue;
|
|
9934
|
+
out.push({
|
|
9935
|
+
cli: "gemini",
|
|
9936
|
+
projectName: project.name,
|
|
9937
|
+
sessionId: s.sessionId,
|
|
9938
|
+
transcriptPath: s.path,
|
|
9939
|
+
mtimeMs,
|
|
9940
|
+
sizeBytes
|
|
9941
|
+
});
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
9944
|
+
return out;
|
|
9945
|
+
}
|
|
9946
|
+
async function streamGeminiEvents(meta) {
|
|
9947
|
+
const log = await getGeminiSessionLog(meta.sessionId);
|
|
9948
|
+
if (!log)
|
|
9949
|
+
return [];
|
|
9950
|
+
return logEntriesToEvents(log.entries, {
|
|
9951
|
+
cli: "gemini",
|
|
9952
|
+
sessionId: meta.sessionId,
|
|
9953
|
+
transcriptPath: meta.transcriptPath,
|
|
9954
|
+
cwd: log.cwd ?? ""
|
|
9955
|
+
});
|
|
9956
|
+
}
|
|
9957
|
+
var init_gemini = __esm(() => {
|
|
9958
|
+
init_gemini_projects();
|
|
9959
|
+
init_gemini_sessions();
|
|
9960
|
+
init_shared();
|
|
9961
|
+
});
|
|
9962
|
+
|
|
9963
|
+
// src/audit/cli-adapters/index.ts
|
|
9964
|
+
var ADAPTERS;
|
|
9965
|
+
var init_cli_adapters = __esm(() => {
|
|
9966
|
+
init_claude();
|
|
9967
|
+
init_codex();
|
|
9968
|
+
init_copilot();
|
|
9969
|
+
init_cursor();
|
|
9970
|
+
init_opencode();
|
|
9971
|
+
init_pi();
|
|
9972
|
+
init_gemini();
|
|
9973
|
+
ADAPTERS = {
|
|
9974
|
+
claude: {
|
|
9975
|
+
cli: "claude",
|
|
9976
|
+
listTranscripts: listClaudeTranscriptMetadata,
|
|
9977
|
+
streamEvents: streamClaudeEvents
|
|
9978
|
+
},
|
|
9979
|
+
codex: {
|
|
9980
|
+
cli: "codex",
|
|
9981
|
+
listTranscripts: listCodexTranscriptMetadata,
|
|
9982
|
+
streamEvents: streamCodexEvents
|
|
9983
|
+
},
|
|
9984
|
+
copilot: {
|
|
9985
|
+
cli: "copilot",
|
|
9986
|
+
listTranscripts: listCopilotTranscriptMetadata,
|
|
9987
|
+
streamEvents: streamCopilotEvents
|
|
9988
|
+
},
|
|
9989
|
+
cursor: {
|
|
9990
|
+
cli: "cursor",
|
|
9991
|
+
listTranscripts: listCursorTranscriptMetadata,
|
|
9992
|
+
streamEvents: streamCursorEvents
|
|
9993
|
+
},
|
|
9994
|
+
opencode: {
|
|
9995
|
+
cli: "opencode",
|
|
9996
|
+
listTranscripts: listOpenCodeTranscriptMetadata,
|
|
9997
|
+
streamEvents: streamOpenCodeEvents
|
|
9998
|
+
},
|
|
9999
|
+
pi: {
|
|
10000
|
+
cli: "pi",
|
|
10001
|
+
listTranscripts: listPiTranscriptMetadata,
|
|
10002
|
+
streamEvents: streamPiEvents
|
|
10003
|
+
},
|
|
10004
|
+
gemini: {
|
|
10005
|
+
cli: "gemini",
|
|
10006
|
+
listTranscripts: listGeminiTranscriptMetadata,
|
|
10007
|
+
streamEvents: streamGeminiEvents
|
|
10008
|
+
}
|
|
10009
|
+
};
|
|
10010
|
+
});
|
|
10011
|
+
|
|
10012
|
+
// src/audit/detectors/redundant-cd-cwd.ts
|
|
10013
|
+
var redundantCdCwd;
|
|
10014
|
+
var init_redundant_cd_cwd = __esm(() => {
|
|
10015
|
+
redundantCdCwd = {
|
|
10016
|
+
name: "redundant-cd-cwd",
|
|
10017
|
+
description: "Bash commands prefixed with `cd <cwd> && …` even though commands already run in cwd.",
|
|
10018
|
+
category: "Wasteful",
|
|
10019
|
+
severity: "info",
|
|
10020
|
+
displayTitle: "Prepended cd <cwd> before commands",
|
|
10021
|
+
impact: "Pure waste — your agent's shell already runs in `cwd`.",
|
|
10022
|
+
detect(event) {
|
|
10023
|
+
if (event.toolName !== "Bash")
|
|
10024
|
+
return null;
|
|
10025
|
+
const command = event.toolInput.command;
|
|
10026
|
+
if (typeof command !== "string" || !event.cwd)
|
|
10027
|
+
return null;
|
|
10028
|
+
const trimmed = command.trimStart();
|
|
10029
|
+
const match = /^cd\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*&&\s*([\s\S]+)$/.exec(trimmed);
|
|
10030
|
+
if (!match)
|
|
10031
|
+
return null;
|
|
10032
|
+
const path3 = (match[1] ?? match[2] ?? match[3] ?? "").replace(/\/+$/, "");
|
|
10033
|
+
const cwd = event.cwd.replace(/\/+$/, "");
|
|
10034
|
+
if (path3 !== cwd)
|
|
10035
|
+
return null;
|
|
10036
|
+
const rest = match[4].trim();
|
|
10037
|
+
return { example: `cd ${path3} && ${rest}` };
|
|
10038
|
+
}
|
|
10039
|
+
};
|
|
10040
|
+
});
|
|
10041
|
+
|
|
10042
|
+
// src/audit/detectors/prefer-edit-over-read-cat.ts
|
|
10043
|
+
var SOURCE_EXT_RE, preferEditOverReadCat;
|
|
10044
|
+
var init_prefer_edit_over_read_cat = __esm(() => {
|
|
10045
|
+
SOURCE_EXT_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|swift|rb|php|c|h|cc|cpp|hpp|cs|scala|sh|bash|zsh|json|yaml|yml|toml|md|txt|sql|html|css|scss|sass)$/i;
|
|
10046
|
+
preferEditOverReadCat = {
|
|
10047
|
+
name: "prefer-edit-over-read-cat",
|
|
10048
|
+
description: "Bash `cat`/`head`/`tail`/`less`/`more` on a single source file — use Read.",
|
|
10049
|
+
category: "Wasteful",
|
|
10050
|
+
severity: "info",
|
|
10051
|
+
displayTitle: "Used `cat`/`head`/`tail` on a source file",
|
|
10052
|
+
impact: "Burns tokens; the Read tool returns content directly without going through Bash output.",
|
|
10053
|
+
detect(event) {
|
|
10054
|
+
if (event.toolName !== "Bash")
|
|
10055
|
+
return null;
|
|
10056
|
+
const command = event.toolInput.command;
|
|
10057
|
+
if (typeof command !== "string")
|
|
10058
|
+
return null;
|
|
10059
|
+
const cmd = command.trim();
|
|
10060
|
+
if (/[|<>;&`$()]/.test(cmd))
|
|
10061
|
+
return null;
|
|
10062
|
+
const match = /^(cat|head|tail|less|more)\s+(?:-\S+\s+)*(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
10063
|
+
if (!match)
|
|
10064
|
+
return null;
|
|
10065
|
+
const path3 = match[2] ?? match[3] ?? match[4] ?? "";
|
|
10066
|
+
if (!path3)
|
|
10067
|
+
return null;
|
|
10068
|
+
if (/(?:^|\/)\.env(?:\..+)?$/.test(path3))
|
|
10069
|
+
return null;
|
|
10070
|
+
if (!SOURCE_EXT_RE.test(path3))
|
|
10071
|
+
return null;
|
|
10072
|
+
return { example: cmd };
|
|
10073
|
+
}
|
|
10074
|
+
};
|
|
10075
|
+
});
|
|
10076
|
+
|
|
10077
|
+
// src/audit/detectors/prefer-edit-over-sed-awk.ts
|
|
10078
|
+
var preferEditOverSedAwk;
|
|
10079
|
+
var init_prefer_edit_over_sed_awk = __esm(() => {
|
|
10080
|
+
preferEditOverSedAwk = {
|
|
10081
|
+
name: "prefer-edit-over-sed-awk",
|
|
10082
|
+
description: "Bash `sed -i`/`awk` in-place edits — use Edit.",
|
|
10083
|
+
category: "Wasteful",
|
|
10084
|
+
severity: "info",
|
|
10085
|
+
displayTitle: "Used sed -i or awk for an in-place edit",
|
|
10086
|
+
impact: "Edit tool is safer and produces a diff the agent can verify.",
|
|
10087
|
+
detect(event) {
|
|
10088
|
+
if (event.toolName !== "Bash")
|
|
10089
|
+
return null;
|
|
10090
|
+
const command = event.toolInput.command;
|
|
10091
|
+
if (typeof command !== "string")
|
|
10092
|
+
return null;
|
|
10093
|
+
const cmd = command.trim();
|
|
10094
|
+
if (/(?:^|\s|;|&&|\|\|)sed\b[^|]*\s-i(?=\b|['"])/.test(cmd)) {
|
|
10095
|
+
return { example: cmd };
|
|
10096
|
+
}
|
|
10097
|
+
if (/(?:^|\s|;|&&|\|\|)awk\b[^|]*\s>\s*\S+/.test(cmd) && !/\|/.test(cmd)) {
|
|
10098
|
+
return { example: cmd };
|
|
10099
|
+
}
|
|
10100
|
+
return null;
|
|
10101
|
+
}
|
|
10102
|
+
};
|
|
10103
|
+
});
|
|
10104
|
+
|
|
10105
|
+
// src/audit/detectors/prefer-write-over-heredoc.ts
|
|
10106
|
+
var preferWriteOverHeredoc;
|
|
10107
|
+
var init_prefer_write_over_heredoc = __esm(() => {
|
|
10108
|
+
preferWriteOverHeredoc = {
|
|
10109
|
+
name: "prefer-write-over-heredoc",
|
|
10110
|
+
description: "Bash heredoc / `echo > file` writing multi-line content — use Write.",
|
|
10111
|
+
category: "Wasteful",
|
|
10112
|
+
severity: "info",
|
|
10113
|
+
displayTitle: "Used heredoc / `echo > file` to write a multi-line file",
|
|
10114
|
+
impact: "Write tool handles escaping and is verifiable.",
|
|
10115
|
+
detect(event) {
|
|
10116
|
+
if (event.toolName !== "Bash")
|
|
10117
|
+
return null;
|
|
10118
|
+
const command = event.toolInput.command;
|
|
10119
|
+
if (typeof command !== "string")
|
|
10120
|
+
return null;
|
|
10121
|
+
const cmd = command;
|
|
10122
|
+
if (/<<-?\s*['"]?[A-Za-z_][A-Za-z0-9_]*['"]?\s*>\s*\S/.test(cmd)) {
|
|
10123
|
+
const summary = cmd.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
10124
|
+
return { example: summary };
|
|
10125
|
+
}
|
|
10126
|
+
if (/(?:^|\s|;|&&|\|\|)(?:echo|printf)\s+["'][^"']*\n[^"']*["']\s*>\s*\S/.test(cmd)) {
|
|
10127
|
+
const summary = cmd.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
10128
|
+
return { example: summary };
|
|
10129
|
+
}
|
|
10130
|
+
return null;
|
|
10131
|
+
}
|
|
10132
|
+
};
|
|
10133
|
+
});
|
|
10134
|
+
|
|
10135
|
+
// src/audit/detectors/sleep-polling-loop.ts
|
|
10136
|
+
var SLEEP_THRESHOLD_SECONDS = 30, sleepPollingLoop;
|
|
10137
|
+
var init_sleep_polling_loop = __esm(() => {
|
|
10138
|
+
sleepPollingLoop = {
|
|
10139
|
+
name: "sleep-polling-loop",
|
|
10140
|
+
description: "Bash long `sleep` or while-sleep polling loops.",
|
|
10141
|
+
category: "Wasteful",
|
|
10142
|
+
severity: "info",
|
|
10143
|
+
displayTitle: "Used a long sleep or while-sleep polling loop",
|
|
10144
|
+
impact: "Burns wall-clock; better to wait for an explicit signal.",
|
|
10145
|
+
detect(event) {
|
|
10146
|
+
if (event.toolName !== "Bash")
|
|
10147
|
+
return null;
|
|
10148
|
+
const command = event.toolInput.command;
|
|
10149
|
+
if (typeof command !== "string")
|
|
10150
|
+
return null;
|
|
10151
|
+
const cmd = command;
|
|
10152
|
+
if (/\bwhile\b[\s\S]*?\bsleep\b[\s\S]*?\bdone\b/.test(cmd)) {
|
|
10153
|
+
return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
|
|
10154
|
+
}
|
|
10155
|
+
const match = /\bsleep\s+(\d+(?:\.\d+)?)(m|h|d)?\b/.exec(cmd);
|
|
10156
|
+
if (match) {
|
|
10157
|
+
const n = parseFloat(match[1]);
|
|
10158
|
+
const unit = match[2] ?? "s";
|
|
10159
|
+
const seconds = unit === "m" ? n * 60 : unit === "h" ? n * 3600 : unit === "d" ? n * 86400 : n;
|
|
10160
|
+
if (seconds >= SLEEP_THRESHOLD_SECONDS) {
|
|
10161
|
+
return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
10164
|
+
return null;
|
|
10165
|
+
}
|
|
10166
|
+
};
|
|
10167
|
+
});
|
|
10168
|
+
|
|
10169
|
+
// src/audit/detectors/find-from-root.ts
|
|
10170
|
+
var RISKY_ROOTS, findFromRoot;
|
|
10171
|
+
var init_find_from_root = __esm(() => {
|
|
10172
|
+
RISKY_ROOTS = ["/", "/home", "/usr", "/etc", "/var", "/opt", "/Users"];
|
|
10173
|
+
findFromRoot = {
|
|
10174
|
+
name: "find-from-root",
|
|
10175
|
+
description: "Bash `find` against `/`, `/home`, `/usr`, etc. — scope to cwd instead.",
|
|
10176
|
+
category: "Risky",
|
|
10177
|
+
severity: "warn",
|
|
10178
|
+
displayTitle: "Ran find from /, /home, /usr, etc.",
|
|
10179
|
+
impact: "Filesystem-wide finds exhaust resources and rarely return useful results.",
|
|
10180
|
+
detect(event) {
|
|
10181
|
+
if (event.toolName !== "Bash")
|
|
10182
|
+
return null;
|
|
10183
|
+
const command = event.toolInput.command;
|
|
10184
|
+
if (typeof command !== "string")
|
|
10185
|
+
return null;
|
|
10186
|
+
const cmd = command.trim();
|
|
10187
|
+
const match = /(?:^|[\s;|&])find\s+(?:-\S+\s+)*("[^"]+"|'[^']+'|\S+)/.exec(cmd);
|
|
10188
|
+
if (!match)
|
|
10189
|
+
return null;
|
|
10190
|
+
const raw = match[1].replace(/^["']|["']$/g, "");
|
|
10191
|
+
const stripped = raw.replace(/\/+$/, "") || "/";
|
|
10192
|
+
if (!RISKY_ROOTS.includes(stripped))
|
|
10193
|
+
return null;
|
|
10194
|
+
return { example: cmd.slice(0, 160) };
|
|
10195
|
+
}
|
|
10196
|
+
};
|
|
10197
|
+
});
|
|
10198
|
+
|
|
10199
|
+
// src/audit/detectors/git-commit-no-verify.ts
|
|
10200
|
+
var gitCommitNoVerify;
|
|
10201
|
+
var init_git_commit_no_verify = __esm(() => {
|
|
10202
|
+
gitCommitNoVerify = {
|
|
10203
|
+
name: "git-commit-no-verify",
|
|
10204
|
+
description: "git commit invoked with --no-verify / -n, skipping hooks.",
|
|
10205
|
+
category: "Risky",
|
|
10206
|
+
severity: "warn",
|
|
10207
|
+
displayTitle: "Committed with --no-verify",
|
|
10208
|
+
impact: "Skips pre-commit hooks that exist to catch broken or unsafe code.",
|
|
10209
|
+
detect(event) {
|
|
10210
|
+
if (event.toolName !== "Bash")
|
|
10211
|
+
return null;
|
|
10212
|
+
const command = event.toolInput.command;
|
|
10213
|
+
if (typeof command !== "string")
|
|
10214
|
+
return null;
|
|
10215
|
+
const cmd = command;
|
|
10216
|
+
if (!/\bgit\s+commit\b/.test(cmd))
|
|
10217
|
+
return null;
|
|
10218
|
+
if (/\s--no-verify\b/.test(cmd) || /\s-n\b/.test(cmd)) {
|
|
10219
|
+
return { example: cmd.replace(/\s+/g, " ").trim().slice(0, 160) };
|
|
10220
|
+
}
|
|
10221
|
+
return null;
|
|
10222
|
+
}
|
|
10223
|
+
};
|
|
10224
|
+
});
|
|
10225
|
+
|
|
10226
|
+
// src/audit/detectors/reread-after-edit.ts
|
|
10227
|
+
function getState(state) {
|
|
10228
|
+
let s = state[STATE_KEY];
|
|
10229
|
+
if (!s) {
|
|
10230
|
+
s = { countdown: new Map };
|
|
10231
|
+
state[STATE_KEY] = s;
|
|
10232
|
+
}
|
|
10233
|
+
return s;
|
|
10234
|
+
}
|
|
10235
|
+
var STATE_KEY = "rereadAfterEdit", WINDOW = 5, rereadAfterEdit;
|
|
10236
|
+
var init_reread_after_edit = __esm(() => {
|
|
10237
|
+
rereadAfterEdit = {
|
|
10238
|
+
name: "reread-after-edit",
|
|
10239
|
+
description: "Read of a file that was just Edit'd or Write'n in the same session.",
|
|
10240
|
+
category: "Wasteful",
|
|
10241
|
+
severity: "info",
|
|
10242
|
+
displayTitle: "Re-read a file it just edited",
|
|
10243
|
+
impact: "Edit/Write already returned the updated content; the second Read is wasted tokens.",
|
|
10244
|
+
detect(event, sessionState) {
|
|
10245
|
+
const state = getState(sessionState);
|
|
10246
|
+
const filePath = event.toolInput.file_path;
|
|
10247
|
+
const pathStr = typeof filePath === "string" ? filePath : null;
|
|
10248
|
+
for (const [key, n] of state.countdown) {
|
|
10249
|
+
if (n <= 1)
|
|
10250
|
+
state.countdown.delete(key);
|
|
10251
|
+
else
|
|
10252
|
+
state.countdown.set(key, n - 1);
|
|
10253
|
+
}
|
|
10254
|
+
if (!pathStr)
|
|
10255
|
+
return null;
|
|
10256
|
+
if (event.toolName === "Edit" || event.toolName === "Write") {
|
|
10257
|
+
state.countdown.set(pathStr, WINDOW);
|
|
10258
|
+
return null;
|
|
10259
|
+
}
|
|
10260
|
+
if (event.toolName === "Read") {
|
|
10261
|
+
if (state.countdown.has(pathStr)) {
|
|
10262
|
+
state.countdown.delete(pathStr);
|
|
10263
|
+
return { example: `Read ${pathStr} immediately after Edit/Write` };
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10266
|
+
return null;
|
|
10267
|
+
}
|
|
10268
|
+
};
|
|
10269
|
+
});
|
|
10270
|
+
|
|
10271
|
+
// src/audit/detectors/index.ts
|
|
10272
|
+
var AUDIT_DETECTORS;
|
|
10273
|
+
var init_detectors = __esm(() => {
|
|
10274
|
+
init_redundant_cd_cwd();
|
|
10275
|
+
init_prefer_edit_over_read_cat();
|
|
10276
|
+
init_prefer_edit_over_sed_awk();
|
|
10277
|
+
init_prefer_write_over_heredoc();
|
|
10278
|
+
init_sleep_polling_loop();
|
|
10279
|
+
init_find_from_root();
|
|
10280
|
+
init_git_commit_no_verify();
|
|
10281
|
+
init_reread_after_edit();
|
|
10282
|
+
AUDIT_DETECTORS = [
|
|
10283
|
+
redundantCdCwd,
|
|
10284
|
+
preferEditOverReadCat,
|
|
10285
|
+
preferEditOverSedAwk,
|
|
10286
|
+
preferWriteOverHeredoc,
|
|
10287
|
+
sleepPollingLoop,
|
|
10288
|
+
findFromRoot,
|
|
10289
|
+
gitCommitNoVerify,
|
|
10290
|
+
rereadAfterEdit
|
|
10291
|
+
];
|
|
10292
|
+
});
|
|
10293
|
+
|
|
10294
|
+
// src/audit/features.ts
|
|
10295
|
+
function shortName(name) {
|
|
10296
|
+
const slash = name.indexOf("/");
|
|
10297
|
+
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
10298
|
+
}
|
|
10299
|
+
function severityForBuiltin(name) {
|
|
10300
|
+
const n = shortName(name);
|
|
10301
|
+
if (n.startsWith("sanitize-"))
|
|
10302
|
+
return "sanitize";
|
|
10303
|
+
if (n.startsWith("block-"))
|
|
10304
|
+
return "deny";
|
|
10305
|
+
if (n.startsWith("warn-") || n.startsWith("protect-") || n.startsWith("prefer-") || n.startsWith("require-"))
|
|
10306
|
+
return "warn";
|
|
10307
|
+
return "deny";
|
|
10308
|
+
}
|
|
10309
|
+
function emptyWeights() {
|
|
10310
|
+
return {
|
|
10311
|
+
optimist: 0,
|
|
10312
|
+
cowboy: 0,
|
|
10313
|
+
explorer: 0,
|
|
10314
|
+
goldfish: 0,
|
|
10315
|
+
architect: 0,
|
|
10316
|
+
precision: 0,
|
|
10317
|
+
hammer: 0,
|
|
10318
|
+
ghost: 0
|
|
10319
|
+
};
|
|
10320
|
+
}
|
|
10321
|
+
var MAPPABLE_KEYS, ARCHITECT_CAUTION_SIGNALS, MIN_BASELINE = 0.05, EMPIRICAL_FIRING_SHARE, BASELINE_SHARE;
|
|
10322
|
+
var init_features = __esm(() => {
|
|
10323
|
+
MAPPABLE_KEYS = [
|
|
10324
|
+
"cowboy",
|
|
10325
|
+
"explorer",
|
|
10326
|
+
"ghost",
|
|
10327
|
+
"optimist",
|
|
10328
|
+
"hammer",
|
|
10329
|
+
"architect"
|
|
10330
|
+
];
|
|
10331
|
+
ARCHITECT_CAUTION_SIGNALS = new Set(["reread-after-edit", "redundant-cd-cwd"]);
|
|
10332
|
+
EMPIRICAL_FIRING_SHARE = {
|
|
10333
|
+
explorer: 0.38,
|
|
10334
|
+
architect: 0.33,
|
|
10335
|
+
hammer: 0.11,
|
|
10336
|
+
cowboy: 0.11,
|
|
10337
|
+
optimist: 0.07,
|
|
10338
|
+
ghost: 0.01,
|
|
10339
|
+
precision: 0,
|
|
10340
|
+
goldfish: 0
|
|
10341
|
+
};
|
|
10342
|
+
BASELINE_SHARE = (() => {
|
|
10343
|
+
const out = emptyWeights();
|
|
10344
|
+
for (const k of MAPPABLE_KEYS)
|
|
10345
|
+
out[k] = Math.max(EMPIRICAL_FIRING_SHARE[k], MIN_BASELINE);
|
|
10346
|
+
return out;
|
|
10347
|
+
})();
|
|
10348
|
+
});
|
|
10349
|
+
|
|
10350
|
+
// src/audit/cache.ts
|
|
10351
|
+
import { createHash } from "node:crypto";
|
|
10352
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6, chmodSync as chmodSync2 } from "node:fs";
|
|
10353
|
+
import { join as join20 } from "node:path";
|
|
10354
|
+
import { homedir as homedir20 } from "node:os";
|
|
10355
|
+
function getEngineVersion() {
|
|
10356
|
+
if (cachedEngineVersion)
|
|
10357
|
+
return cachedEngineVersion;
|
|
10358
|
+
const blob = BUILTIN_POLICIES.map((p) => `${p.name}|${p.fn.toString()}`).sort().join(`
|
|
10359
|
+
`);
|
|
10360
|
+
cachedEngineVersion = createHash("sha1").update(blob).digest("hex").slice(0, 16);
|
|
10361
|
+
return cachedEngineVersion;
|
|
10362
|
+
}
|
|
10363
|
+
function getDetectorVersion() {
|
|
10364
|
+
if (cachedDetectorVersion)
|
|
10365
|
+
return cachedDetectorVersion;
|
|
10366
|
+
const blob = AUDIT_DETECTORS.map((d) => `${d.name}|${d.detect.toString()}`).sort().join(`
|
|
10367
|
+
`);
|
|
10368
|
+
cachedDetectorVersion = createHash("sha1").update(blob).digest("hex").slice(0, 16);
|
|
10369
|
+
return cachedDetectorVersion;
|
|
10370
|
+
}
|
|
10371
|
+
function getCachePathFor(transcriptPath) {
|
|
10372
|
+
const root = join20(homedir20(), ".failproofai", "cache", "audit");
|
|
10373
|
+
const key = createHash("sha1").update(transcriptPath).digest("hex");
|
|
10374
|
+
return join20(root, `${key}.json`);
|
|
10375
|
+
}
|
|
10376
|
+
function readCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes) {
|
|
10377
|
+
if (sizeBytes === 0)
|
|
10378
|
+
return null;
|
|
10379
|
+
const cachePath = getCachePathFor(transcriptPath);
|
|
10380
|
+
if (!existsSync15(cachePath))
|
|
10381
|
+
return null;
|
|
10382
|
+
try {
|
|
10383
|
+
const raw = readFileSync11(cachePath, "utf-8");
|
|
10384
|
+
const entry = JSON.parse(raw);
|
|
10385
|
+
if (entry.schemaVersion !== CACHE_SCHEMA_VERSION)
|
|
10386
|
+
return null;
|
|
10387
|
+
if (entry.mtimeMs !== mtimeMs)
|
|
10388
|
+
return null;
|
|
10389
|
+
if (entry.sizeBytes !== sizeBytes)
|
|
10390
|
+
return null;
|
|
10391
|
+
if (entry.engineVersion !== getEngineVersion())
|
|
10392
|
+
return null;
|
|
10393
|
+
if (entry.detectorVersion !== getDetectorVersion())
|
|
10394
|
+
return null;
|
|
10395
|
+
if (!Number.isFinite(entry.cachedAt) || Date.now() - entry.cachedAt > CACHE_TTL_MS)
|
|
10396
|
+
return null;
|
|
10397
|
+
return entry.result ?? null;
|
|
10398
|
+
} catch {
|
|
10399
|
+
return null;
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10402
|
+
function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result) {
|
|
10403
|
+
if (sizeBytes === 0)
|
|
10404
|
+
return;
|
|
10405
|
+
const cachePath = getCachePathFor(transcriptPath);
|
|
10406
|
+
try {
|
|
10407
|
+
mkdirSync7(join20(homedir20(), ".failproofai", "cache", "audit"), { recursive: true });
|
|
10408
|
+
const entry = {
|
|
10409
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
10410
|
+
cachedAt: Date.now(),
|
|
10411
|
+
mtimeMs,
|
|
10412
|
+
sizeBytes,
|
|
10413
|
+
engineVersion: getEngineVersion(),
|
|
10414
|
+
detectorVersion: getDetectorVersion(),
|
|
10415
|
+
result
|
|
10416
|
+
};
|
|
10417
|
+
writeFileSync6(cachePath, JSON.stringify(entry), { encoding: "utf-8", mode: 384 });
|
|
10418
|
+
try {
|
|
10419
|
+
chmodSync2(cachePath, 384);
|
|
10420
|
+
} catch {}
|
|
10421
|
+
} catch {}
|
|
10422
|
+
}
|
|
10423
|
+
var cachedEngineVersion = null, cachedDetectorVersion = null, CACHE_SCHEMA_VERSION = 3, CACHE_TTL_MS;
|
|
10424
|
+
var init_cache = __esm(() => {
|
|
10425
|
+
init_builtin_policies();
|
|
10426
|
+
init_detectors();
|
|
10427
|
+
CACHE_TTL_MS = 7 * 24 * 60 * 60000;
|
|
10428
|
+
});
|
|
10429
|
+
|
|
10430
|
+
// src/audit/replay.ts
|
|
10431
|
+
function initReplay() {
|
|
10432
|
+
if (initialized)
|
|
10433
|
+
return;
|
|
10434
|
+
savedSnapshot = getAllPolicies();
|
|
10435
|
+
clearPolicies();
|
|
10436
|
+
const enabled = BUILTIN_POLICIES.map((p) => p.name).filter((n) => !SKIP_POLICIES.has(normalizePolicyName(n)));
|
|
10437
|
+
registerBuiltinPolicies(enabled);
|
|
10438
|
+
initialized = true;
|
|
10439
|
+
}
|
|
10440
|
+
function restoreReplay() {
|
|
10441
|
+
if (!initialized)
|
|
10442
|
+
return;
|
|
10443
|
+
if (savedSnapshot !== null) {
|
|
10444
|
+
setAllPolicies(savedSnapshot);
|
|
10445
|
+
savedSnapshot = null;
|
|
10446
|
+
}
|
|
10447
|
+
initialized = false;
|
|
10448
|
+
}
|
|
10449
|
+
async function replayEvent(event) {
|
|
10450
|
+
if (!initialized)
|
|
10451
|
+
initReplay();
|
|
10452
|
+
const session = {
|
|
10453
|
+
sessionId: event.sessionId,
|
|
10454
|
+
transcriptPath: event.transcriptPath,
|
|
10455
|
+
cwd: event.cwd,
|
|
10456
|
+
cli: event.cli
|
|
10457
|
+
};
|
|
10458
|
+
const baseToolPayload = {
|
|
10459
|
+
tool_name: event.toolName,
|
|
10460
|
+
tool_input: event.toolInput,
|
|
10461
|
+
session_id: event.sessionId,
|
|
10462
|
+
cwd: event.cwd,
|
|
10463
|
+
transcript_path: event.transcriptPath
|
|
10464
|
+
};
|
|
10465
|
+
const out = [];
|
|
10466
|
+
const pre = await evaluatePolicies("PreToolUse", baseToolPayload, session);
|
|
10467
|
+
collectHits(pre, "PreToolUse", out);
|
|
10468
|
+
if (event.toolResultText !== undefined) {
|
|
10469
|
+
const postPayload = { ...baseToolPayload, tool_response: event.toolResultText };
|
|
10470
|
+
const post = await evaluatePolicies("PostToolUse", postPayload, session);
|
|
10471
|
+
collectHits(post, "PostToolUse", out);
|
|
10472
|
+
}
|
|
10473
|
+
return out;
|
|
10474
|
+
}
|
|
10475
|
+
function collectHits(result, eventType, out) {
|
|
10476
|
+
const names = result.policyNames && result.policyNames.length > 0 ? result.policyNames : result.policyName ? [result.policyName] : [];
|
|
10477
|
+
for (const name of names) {
|
|
10478
|
+
out.push({
|
|
10479
|
+
policyName: name,
|
|
10480
|
+
decision: result.decision,
|
|
10481
|
+
reason: result.reason,
|
|
10482
|
+
eventType
|
|
10483
|
+
});
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
var SKIP_POLICIES, initialized = false, savedSnapshot = null;
|
|
10487
|
+
var init_replay = __esm(() => {
|
|
10488
|
+
init_policy_evaluator();
|
|
10489
|
+
init_builtin_policies();
|
|
10490
|
+
SKIP_POLICIES = new Set(["warn-repeated-tool-calls"].map((n) => normalizePolicyName(n)));
|
|
10491
|
+
});
|
|
10492
|
+
|
|
10493
|
+
// src/audit/index.ts
|
|
10494
|
+
function shortPolicyName(name) {
|
|
10495
|
+
const slash = name.indexOf("/");
|
|
10496
|
+
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
10497
|
+
}
|
|
10498
|
+
function findBuiltin(name) {
|
|
10499
|
+
const short = shortPolicyName(name);
|
|
10500
|
+
for (const p of BUILTIN_POLICIES) {
|
|
10501
|
+
if (p.name === name || shortPolicyName(p.name) === short)
|
|
10502
|
+
return p;
|
|
10503
|
+
}
|
|
10504
|
+
return null;
|
|
10505
|
+
}
|
|
10506
|
+
function buildInstallHint(name, source, enabled) {
|
|
10507
|
+
if (source === "audit-detector") {
|
|
10508
|
+
return "Audit-only — `failproofai audit` will keep tracking these.";
|
|
10509
|
+
}
|
|
10510
|
+
if (enabled) {
|
|
10511
|
+
return "Already enforced — failproofai is blocking these in real time.";
|
|
10512
|
+
}
|
|
10513
|
+
return `Enable in one command: failproofai policies --install ${shortPolicyName(name)}`;
|
|
10514
|
+
}
|
|
10515
|
+
function truncateExample(s) {
|
|
10516
|
+
if (s.length <= AUDIT_EXAMPLE_MAX_CHARS)
|
|
10517
|
+
return s;
|
|
10518
|
+
return s.slice(0, AUDIT_EXAMPLE_MAX_CHARS - 1) + "…";
|
|
10519
|
+
}
|
|
10520
|
+
function parseSinceOpt(since) {
|
|
10521
|
+
if (!since)
|
|
10522
|
+
return;
|
|
10523
|
+
const m = /^(\d+)\s*([dhm])$/i.exec(since.trim());
|
|
10524
|
+
if (m) {
|
|
10525
|
+
const n = parseInt(m[1], 10);
|
|
10526
|
+
const unit = m[2].toLowerCase();
|
|
10527
|
+
const ms = unit === "d" ? 86400000 : unit === "h" ? 3600000 : 60000;
|
|
10528
|
+
return Date.now() - n * ms;
|
|
10529
|
+
}
|
|
10530
|
+
const t = Date.parse(since);
|
|
10531
|
+
if (!Number.isNaN(t))
|
|
10532
|
+
return t;
|
|
10533
|
+
throw new Error(`Invalid --since value: "${since}" (expected e.g. "7d", "30d", or "2026-04-01")`);
|
|
10534
|
+
}
|
|
10535
|
+
async function scanOneTranscript(meta) {
|
|
10536
|
+
const empty = {
|
|
10537
|
+
transcriptPath: meta.transcriptPath,
|
|
10538
|
+
cli: meta.cli,
|
|
10539
|
+
projectName: meta.projectName,
|
|
10540
|
+
sessionId: meta.sessionId,
|
|
10541
|
+
mtimeMs: meta.mtimeMs,
|
|
10542
|
+
sizeBytes: meta.sizeBytes,
|
|
10543
|
+
cwd: "",
|
|
10544
|
+
eventsScanned: 0,
|
|
10545
|
+
hitsByName: {},
|
|
10546
|
+
examplesByName: {},
|
|
10547
|
+
rangeByName: {}
|
|
10548
|
+
};
|
|
10549
|
+
const events = await ADAPTERS[meta.cli].streamEvents(meta);
|
|
10550
|
+
if (events.length === 0)
|
|
10551
|
+
return empty;
|
|
10552
|
+
const result = empty;
|
|
10553
|
+
result.eventsScanned = events.length;
|
|
10554
|
+
result.cwd = events[0].cwd || "";
|
|
10555
|
+
const sessionState = {};
|
|
10556
|
+
for (const event of events) {
|
|
10557
|
+
for (const detector of AUDIT_DETECTORS) {
|
|
10558
|
+
const hit = detector.detect(event, sessionState);
|
|
10559
|
+
if (!hit)
|
|
10560
|
+
continue;
|
|
10561
|
+
recordHit(result, detector.name, event.timestamp, event.cwd, truncateExample(hit.example));
|
|
10562
|
+
}
|
|
10563
|
+
let replayHits;
|
|
10564
|
+
try {
|
|
10565
|
+
replayHits = await replayEvent(event);
|
|
10566
|
+
} catch {
|
|
10567
|
+
continue;
|
|
10568
|
+
}
|
|
10569
|
+
for (const hit of replayHits) {
|
|
10570
|
+
const example = formatPolicyExample(hit.policyName, event);
|
|
10571
|
+
recordHit(result, hit.policyName, event.timestamp, event.cwd, truncateExample(example));
|
|
10572
|
+
}
|
|
10573
|
+
}
|
|
10574
|
+
return result;
|
|
10575
|
+
}
|
|
10576
|
+
function formatPolicyExample(_policyName, event) {
|
|
10577
|
+
if (event.toolName === "Bash") {
|
|
10578
|
+
const command = event.toolInput.command;
|
|
10579
|
+
if (typeof command === "string")
|
|
10580
|
+
return command.replace(/\s+/g, " ");
|
|
10581
|
+
}
|
|
10582
|
+
const filePath = event.toolInput.file_path;
|
|
10583
|
+
if (typeof filePath === "string")
|
|
10584
|
+
return `${event.toolName} ${filePath}`;
|
|
10585
|
+
return `${event.toolName}`;
|
|
10586
|
+
}
|
|
10587
|
+
function recordHit(result, name, timestamp, cwd, example) {
|
|
10588
|
+
result.hitsByName[name] = (result.hitsByName[name] ?? 0) + 1;
|
|
10589
|
+
const exs = result.examplesByName[name] ?? [];
|
|
10590
|
+
if (exs.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
|
|
10591
|
+
exs.push({ timestamp, cwd, example });
|
|
10592
|
+
result.examplesByName[name] = exs;
|
|
10593
|
+
}
|
|
10594
|
+
const range = result.rangeByName[name];
|
|
10595
|
+
if (!range) {
|
|
10596
|
+
result.rangeByName[name] = { first: timestamp, last: timestamp };
|
|
10597
|
+
} else {
|
|
10598
|
+
if (timestamp < range.first)
|
|
10599
|
+
range.first = timestamp;
|
|
10600
|
+
if (timestamp > range.last)
|
|
10601
|
+
range.last = timestamp;
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10604
|
+
function aggregateResults(perTranscript, enabledBuiltins) {
|
|
10605
|
+
const byName = new Map;
|
|
10606
|
+
for (const t of perTranscript) {
|
|
10607
|
+
for (const [name, count] of Object.entries(t.hitsByName)) {
|
|
10608
|
+
const bucket = byName.get(name) ?? {
|
|
10609
|
+
hits: 0,
|
|
10610
|
+
projects: new Set,
|
|
10611
|
+
examples: []
|
|
10612
|
+
};
|
|
10613
|
+
bucket.hits += count;
|
|
10614
|
+
bucket.projects.add(t.projectName);
|
|
10615
|
+
const tExs = t.examplesByName[name] ?? [];
|
|
10616
|
+
for (const e of tExs) {
|
|
10617
|
+
if (bucket.examples.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
|
|
10618
|
+
bucket.examples.push({ ...e, sessionId: t.sessionId });
|
|
10619
|
+
}
|
|
10620
|
+
}
|
|
10621
|
+
const range = t.rangeByName[name];
|
|
10622
|
+
if (range) {
|
|
10623
|
+
if (!bucket.first || range.first < bucket.first)
|
|
10624
|
+
bucket.first = range.first;
|
|
10625
|
+
if (!bucket.last || range.last > bucket.last)
|
|
10626
|
+
bucket.last = range.last;
|
|
10627
|
+
}
|
|
10628
|
+
byName.set(name, bucket);
|
|
10629
|
+
}
|
|
10630
|
+
}
|
|
10631
|
+
const detectorByName = new Map(AUDIT_DETECTORS.map((d) => [d.name, d]));
|
|
10632
|
+
const out = [];
|
|
10633
|
+
for (const [name, bucket] of byName) {
|
|
10634
|
+
const detector = detectorByName.get(name);
|
|
10635
|
+
const isDetector = !!detector;
|
|
10636
|
+
const builtin = isDetector ? null : findBuiltin(name);
|
|
10637
|
+
const source = isDetector ? "audit-detector" : "builtin";
|
|
10638
|
+
const enabled = isDetector ? false : enabledBuiltins.has(normalizePolicyName(name));
|
|
10639
|
+
const displayTitle = detector?.displayTitle ?? builtin?.displayTitle ?? detector?.description ?? builtin?.description ?? shortPolicyName(name);
|
|
10640
|
+
const impact = detector?.impact ?? builtin?.impact ?? "";
|
|
10641
|
+
out.push({
|
|
10642
|
+
name,
|
|
10643
|
+
source,
|
|
10644
|
+
category: detector?.category ?? builtin?.category ?? "Custom",
|
|
10645
|
+
severity: isDetector ? detector?.severity ?? "info" : severityForBuiltin(name),
|
|
10646
|
+
hits: bucket.hits,
|
|
10647
|
+
projects: bucket.projects.size,
|
|
10648
|
+
firstSeen: bucket.first,
|
|
10649
|
+
lastSeen: bucket.last,
|
|
10650
|
+
examples: bucket.examples,
|
|
10651
|
+
displayTitle,
|
|
10652
|
+
impact,
|
|
10653
|
+
enabledInConfig: enabled,
|
|
10654
|
+
installHint: buildInstallHint(name, source, enabled)
|
|
10655
|
+
});
|
|
10656
|
+
}
|
|
10657
|
+
out.sort((a, b) => b.hits - a.hits);
|
|
10658
|
+
return out;
|
|
10659
|
+
}
|
|
10660
|
+
async function runAudit(opts = {}) {
|
|
10661
|
+
const startedAt = Date.now();
|
|
10662
|
+
initReplay();
|
|
10663
|
+
try {
|
|
10664
|
+
return await runAuditInner(opts, startedAt);
|
|
10665
|
+
} finally {
|
|
10666
|
+
restoreReplay();
|
|
10667
|
+
}
|
|
10668
|
+
}
|
|
10669
|
+
async function runAuditInner(opts, startedAt) {
|
|
10670
|
+
const clis = opts.clis ?? Array.from(INTEGRATION_TYPES);
|
|
10671
|
+
const sinceMs = parseSinceOpt(opts.since);
|
|
10672
|
+
const userConfig = readMergedHooksConfig();
|
|
10673
|
+
const enabledBuiltins = new Set((userConfig.enabledPolicies ?? []).map((n) => normalizePolicyName(n)));
|
|
10674
|
+
const allTranscripts = [];
|
|
10675
|
+
for (const cli of clis) {
|
|
10676
|
+
const adapter = ADAPTERS[cli];
|
|
10677
|
+
let list;
|
|
10678
|
+
try {
|
|
10679
|
+
list = await adapter.listTranscripts({ projects: opts.projects, sinceMs });
|
|
10680
|
+
} catch {
|
|
10681
|
+
continue;
|
|
10682
|
+
}
|
|
10683
|
+
allTranscripts.push(...list);
|
|
10684
|
+
}
|
|
10685
|
+
let skipped = 0;
|
|
10686
|
+
let errors = 0;
|
|
10687
|
+
const tasks = allTranscripts.map((meta) => async () => {
|
|
10688
|
+
if (!opts.noCache) {
|
|
10689
|
+
const cached = readCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes);
|
|
10690
|
+
if (cached)
|
|
10691
|
+
return cached;
|
|
10692
|
+
}
|
|
10693
|
+
try {
|
|
10694
|
+
const fresh = await scanOneTranscript(meta);
|
|
10695
|
+
if (!opts.noCache) {
|
|
10696
|
+
writeCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes, fresh);
|
|
10697
|
+
}
|
|
10698
|
+
return fresh;
|
|
10699
|
+
} catch {
|
|
10700
|
+
errors++;
|
|
10701
|
+
return {
|
|
10702
|
+
transcriptPath: meta.transcriptPath,
|
|
10703
|
+
cli: meta.cli,
|
|
10704
|
+
projectName: meta.projectName,
|
|
10705
|
+
cwd: "",
|
|
10706
|
+
sessionId: meta.sessionId,
|
|
10707
|
+
mtimeMs: meta.mtimeMs,
|
|
10708
|
+
sizeBytes: meta.sizeBytes,
|
|
10709
|
+
eventsScanned: 0,
|
|
10710
|
+
hitsByName: {},
|
|
10711
|
+
examplesByName: {},
|
|
10712
|
+
rangeByName: {}
|
|
10713
|
+
};
|
|
10714
|
+
}
|
|
10715
|
+
});
|
|
10716
|
+
const settled = await batchAll(tasks, TRANSCRIPT_CONCURRENCY);
|
|
10717
|
+
const perTranscript = [];
|
|
10718
|
+
for (const s of settled) {
|
|
10719
|
+
if (s.status === "fulfilled")
|
|
10720
|
+
perTranscript.push(s.value);
|
|
10721
|
+
else
|
|
10722
|
+
skipped++;
|
|
10723
|
+
}
|
|
10724
|
+
let results = aggregateResults(perTranscript, enabledBuiltins);
|
|
10725
|
+
if (opts.policies?.length) {
|
|
10726
|
+
const wanted = new Set(opts.policies.map(shortPolicyName));
|
|
10727
|
+
results = results.filter((r) => wanted.has(shortPolicyName(r.name)));
|
|
10728
|
+
}
|
|
10729
|
+
const totalsHits = results.reduce((sum, r) => sum + r.hits, 0);
|
|
10730
|
+
const projectsWithHits = new Set;
|
|
10731
|
+
const projectsScannedSet = new Set;
|
|
10732
|
+
let eventsScanned = 0;
|
|
10733
|
+
for (const t of perTranscript) {
|
|
10734
|
+
if (Object.keys(t.hitsByName).length > 0)
|
|
10735
|
+
projectsWithHits.add(t.projectName);
|
|
10736
|
+
if (t.cwd)
|
|
10737
|
+
projectsScannedSet.add(t.cwd);
|
|
10738
|
+
eventsScanned += t.eventsScanned ?? 0;
|
|
10739
|
+
}
|
|
10740
|
+
const auditResult = {
|
|
10741
|
+
version: 2,
|
|
10742
|
+
scannedAt: new Date(startedAt).toISOString(),
|
|
10743
|
+
scope: {
|
|
10744
|
+
cli: clis,
|
|
10745
|
+
projects: opts.projects ?? "all",
|
|
10746
|
+
since: opts.since ?? null
|
|
10747
|
+
},
|
|
10748
|
+
transcripts: {
|
|
10749
|
+
scanned: allTranscripts.length,
|
|
10750
|
+
skipped,
|
|
10751
|
+
errors,
|
|
10752
|
+
durationMs: Date.now() - startedAt
|
|
10753
|
+
},
|
|
10754
|
+
results,
|
|
10755
|
+
totals: {
|
|
10756
|
+
hits: totalsHits,
|
|
10757
|
+
projectsWithHits: projectsWithHits.size
|
|
10758
|
+
},
|
|
10759
|
+
projectsScanned: [...projectsScannedSet].sort(),
|
|
10760
|
+
eventsScanned,
|
|
10761
|
+
enabledBuiltinNames: [...enabledBuiltins].map((n) => n.includes("/") ? n.slice(n.indexOf("/") + 1) : n)
|
|
10762
|
+
};
|
|
10763
|
+
return auditResult;
|
|
10764
|
+
}
|
|
10765
|
+
var TRANSCRIPT_CONCURRENCY = 8;
|
|
10766
|
+
var init_audit = __esm(() => {
|
|
10767
|
+
init_builtin_policies();
|
|
10768
|
+
init_hooks_config();
|
|
10769
|
+
init_types();
|
|
10770
|
+
init_cli_adapters();
|
|
10771
|
+
init_detectors();
|
|
10772
|
+
init_features();
|
|
10773
|
+
init_cache();
|
|
10774
|
+
init_replay();
|
|
10775
|
+
init_types2();
|
|
10776
|
+
});
|
|
10777
|
+
|
|
10778
|
+
// src/audit/dashboard-cache.ts
|
|
10779
|
+
import { join as join21 } from "node:path";
|
|
10780
|
+
import { homedir as homedir21 } from "node:os";
|
|
10781
|
+
function getCachePath() {
|
|
10782
|
+
return join21(homedir21(), ".failproofai", "audit-dashboard.json");
|
|
10783
|
+
}
|
|
10784
|
+
function writeDashboardCache(params, result) {
|
|
10785
|
+
try {
|
|
10786
|
+
const entry = {
|
|
10787
|
+
schemaVersion: DASHBOARD_CACHE_SCHEMA_VERSION,
|
|
10788
|
+
cachedAt: new Date().toISOString(),
|
|
10789
|
+
params,
|
|
10790
|
+
result
|
|
10791
|
+
};
|
|
10792
|
+
writeJsonAtomically(getCachePath(), entry);
|
|
10793
|
+
return true;
|
|
10794
|
+
} catch {
|
|
10795
|
+
return false;
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
10798
|
+
var DASHBOARD_CACHE_TTL_MINUTES, DASHBOARD_CACHE_SCHEMA_VERSION = 2;
|
|
10799
|
+
var init_dashboard_cache = __esm(() => {
|
|
10800
|
+
init_atomic_write();
|
|
10801
|
+
DASHBOARD_CACHE_TTL_MINUTES = 7 * 24 * 60;
|
|
10802
|
+
});
|
|
10803
|
+
|
|
10804
|
+
// src/audit/open-browser.ts
|
|
10805
|
+
import { spawn } from "node:child_process";
|
|
10806
|
+
function openUrl(url) {
|
|
10807
|
+
let cmd;
|
|
10808
|
+
let args;
|
|
10809
|
+
if (process.platform === "darwin") {
|
|
10810
|
+
cmd = "open";
|
|
10811
|
+
args = [url];
|
|
10812
|
+
} else if (process.platform === "win32") {
|
|
10813
|
+
cmd = "cmd";
|
|
10814
|
+
args = ["/c", "start", "", url];
|
|
10815
|
+
} else {
|
|
10816
|
+
cmd = "xdg-open";
|
|
10817
|
+
args = [url];
|
|
10818
|
+
}
|
|
10819
|
+
try {
|
|
10820
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
10821
|
+
child.on("error", () => {});
|
|
10822
|
+
child.unref();
|
|
10823
|
+
} catch {}
|
|
10824
|
+
}
|
|
10825
|
+
function openWhenReady(port, path3) {
|
|
10826
|
+
const base = `http://localhost:${port}`;
|
|
10827
|
+
const target = `${base}${path3}`;
|
|
10828
|
+
(async () => {
|
|
10829
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
10830
|
+
while (Date.now() < deadline) {
|
|
10831
|
+
try {
|
|
10832
|
+
await fetch(base, { method: "GET", redirect: "manual" });
|
|
10833
|
+
openUrl(target);
|
|
10834
|
+
return;
|
|
10835
|
+
} catch {
|
|
10836
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
10837
|
+
}
|
|
10838
|
+
}
|
|
10839
|
+
openUrl(target);
|
|
10840
|
+
})();
|
|
10841
|
+
}
|
|
10842
|
+
var POLL_INTERVAL_MS = 150, POLL_TIMEOUT_MS = 30000;
|
|
10843
|
+
var init_open_browser = () => {};
|
|
10844
|
+
|
|
10845
|
+
// scripts/parse-script-args.ts
|
|
10846
|
+
import { resolve as resolve12 } from "path";
|
|
10847
|
+
function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
|
|
10848
|
+
const raw = inlineValue ?? args[index + 1];
|
|
10849
|
+
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
10850
|
+
console.error(`Error: ${flagName} requires ${errorLabel}`);
|
|
10851
|
+
process.exit(1);
|
|
10852
|
+
}
|
|
10853
|
+
const value = options?.resolve ? resolve12(raw) : raw;
|
|
10854
|
+
return { value, spliceCount: inlineValue !== null ? 1 : 2 };
|
|
10855
|
+
}
|
|
10856
|
+
function parseScriptArgs(argv) {
|
|
10857
|
+
const args = [...argv];
|
|
10858
|
+
let loggingLevel;
|
|
10859
|
+
let disableTelemetry = false;
|
|
10860
|
+
let allowedDevOrigins;
|
|
10861
|
+
for (let i = 0;i < args.length; i++) {
|
|
10862
|
+
const arg = args[i];
|
|
10863
|
+
const eqIdx = arg.indexOf("=");
|
|
10864
|
+
const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
|
|
10865
|
+
const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
|
|
10866
|
+
if (flag === "--logging") {
|
|
10867
|
+
const raw = inlineValue ?? args[i + 1];
|
|
10868
|
+
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
10869
|
+
console.error("Error: --logging requires a level (info, warn, error)");
|
|
10870
|
+
process.exit(1);
|
|
10871
|
+
}
|
|
10872
|
+
const val = raw.toLowerCase();
|
|
10873
|
+
if (val !== "info" && val !== "warn" && val !== "error") {
|
|
10874
|
+
console.error("Error: --logging must be one of: info, warn, error");
|
|
10875
|
+
process.exit(1);
|
|
10876
|
+
}
|
|
10877
|
+
loggingLevel = val;
|
|
10878
|
+
args.splice(i, inlineValue !== null ? 1 : 2);
|
|
10879
|
+
i--;
|
|
10880
|
+
continue;
|
|
10881
|
+
}
|
|
10882
|
+
if (flag === "--disable-telemetry") {
|
|
10883
|
+
disableTelemetry = true;
|
|
10884
|
+
args.splice(i, 1);
|
|
10885
|
+
i--;
|
|
10886
|
+
continue;
|
|
10887
|
+
}
|
|
10888
|
+
if (flag === "--allowed-origins") {
|
|
10889
|
+
const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
|
|
10890
|
+
allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10891
|
+
args.splice(i, spliceCount);
|
|
10892
|
+
i--;
|
|
10893
|
+
continue;
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
return { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
|
|
10897
|
+
}
|
|
10898
|
+
var init_parse_script_args = () => {};
|
|
10899
|
+
|
|
10900
|
+
// scripts/install-diagnosis.mjs
|
|
10901
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12, realpathSync } from "node:fs";
|
|
10902
|
+
import { dirname as dirname6, resolve as resolve13 } from "node:path";
|
|
10903
|
+
import { homedir as homedir22, platform as platform2 } from "node:os";
|
|
10904
|
+
import { spawnSync } from "node:child_process";
|
|
10905
|
+
function findPackageRoot(start) {
|
|
10906
|
+
try {
|
|
10907
|
+
let dir = realpathSync(start);
|
|
10908
|
+
if (existsSync16(dir) && !existsSync16(resolve13(dir, "package.json"))) {
|
|
10909
|
+
dir = dirname6(dir);
|
|
10910
|
+
}
|
|
10911
|
+
while (true) {
|
|
10912
|
+
const pkgPath = resolve13(dir, "package.json");
|
|
10913
|
+
if (existsSync16(pkgPath)) {
|
|
10914
|
+
try {
|
|
10915
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
10916
|
+
if (pkg.name === PKG_NAME)
|
|
10917
|
+
return dir;
|
|
10918
|
+
} catch {}
|
|
10919
|
+
}
|
|
10920
|
+
const parent = dirname6(dir);
|
|
10921
|
+
if (parent === dir)
|
|
10922
|
+
return null;
|
|
10923
|
+
dir = parent;
|
|
10924
|
+
}
|
|
10925
|
+
} catch {
|
|
10926
|
+
return null;
|
|
10927
|
+
}
|
|
10928
|
+
}
|
|
10929
|
+
function readPackageVersion(packageRoot) {
|
|
10930
|
+
if (!packageRoot)
|
|
10931
|
+
return null;
|
|
10932
|
+
try {
|
|
10933
|
+
const pkg = JSON.parse(readFileSync12(resolve13(packageRoot, "package.json"), "utf8"));
|
|
10934
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
10935
|
+
} catch {
|
|
10936
|
+
return null;
|
|
10937
|
+
}
|
|
10938
|
+
}
|
|
10939
|
+
function resolvePathFirstBinary() {
|
|
10940
|
+
try {
|
|
10941
|
+
const isWin = platform2() === "win32";
|
|
10942
|
+
const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
|
|
10943
|
+
if (res.status !== 0)
|
|
10944
|
+
return null;
|
|
10945
|
+
const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
10946
|
+
return first ? first.trim() : null;
|
|
10947
|
+
} catch {
|
|
10948
|
+
return null;
|
|
10949
|
+
}
|
|
10950
|
+
}
|
|
10951
|
+
function locateNpmGlobal() {
|
|
10952
|
+
try {
|
|
10953
|
+
const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
10954
|
+
if (res.status !== 0)
|
|
10955
|
+
return null;
|
|
10956
|
+
const root = (res.stdout || "").trim();
|
|
10957
|
+
if (!root)
|
|
10958
|
+
return null;
|
|
10959
|
+
const candidate = resolve13(root, PKG_NAME);
|
|
10960
|
+
return existsSync16(resolve13(candidate, "package.json")) ? candidate : null;
|
|
10961
|
+
} catch {
|
|
10962
|
+
return null;
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
function locateBunGlobal() {
|
|
10966
|
+
try {
|
|
10967
|
+
const candidate = resolve13(homedir22(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
10968
|
+
return existsSync16(resolve13(candidate, "package.json")) ? candidate : null;
|
|
10969
|
+
} catch {
|
|
10970
|
+
return null;
|
|
10971
|
+
}
|
|
10972
|
+
}
|
|
10973
|
+
function buildRecommendation(pathFirstBin) {
|
|
10974
|
+
if (!pathFirstBin)
|
|
10975
|
+
return null;
|
|
10976
|
+
const bunBinPrefix = resolve13(homedir22(), ".bun", "bin") + "/";
|
|
10977
|
+
const bunGlobalPrefix = resolve13(homedir22(), ".bun", "install", "global") + "/";
|
|
10978
|
+
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
10979
|
+
if (isBun) {
|
|
10980
|
+
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
10981
|
+
}
|
|
10982
|
+
return `npm rm -g ${PKG_NAME}`;
|
|
10983
|
+
}
|
|
10984
|
+
function diagnoseShadow(self) {
|
|
10985
|
+
const selfPackageRoot = (() => {
|
|
10986
|
+
try {
|
|
10987
|
+
return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
|
|
10988
|
+
} catch {
|
|
10989
|
+
return self?.selfPackageRoot ?? null;
|
|
10990
|
+
}
|
|
10991
|
+
})();
|
|
10992
|
+
const selfVersion = self?.selfVersion ?? null;
|
|
10993
|
+
const pathFirstBin = resolvePathFirstBinary();
|
|
10994
|
+
const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
|
|
10995
|
+
const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
|
|
10996
|
+
const npmGlobalPath = locateNpmGlobal();
|
|
10997
|
+
const npmGlobalVersion = readPackageVersion(npmGlobalPath);
|
|
10998
|
+
const bunGlobalPath = locateBunGlobal();
|
|
10999
|
+
const bunGlobalVersion = readPackageVersion(bunGlobalPath);
|
|
11000
|
+
let shadowed = false;
|
|
11001
|
+
if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
|
|
11002
|
+
shadowed = true;
|
|
11003
|
+
} else if (pathFirstPackageRoot) {
|
|
11004
|
+
if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
|
|
11005
|
+
shadowed = true;
|
|
11006
|
+
else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
|
|
11007
|
+
shadowed = true;
|
|
11008
|
+
}
|
|
11009
|
+
const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
|
|
11010
|
+
let shadowDescription = null;
|
|
11011
|
+
if (shadowed) {
|
|
11012
|
+
shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
|
|
11013
|
+
}
|
|
11014
|
+
return {
|
|
11015
|
+
selfPackageRoot,
|
|
11016
|
+
selfVersion,
|
|
11017
|
+
pathFirstBin,
|
|
11018
|
+
pathFirstPath: pathFirstPackageRoot,
|
|
11019
|
+
pathFirstVersion,
|
|
11020
|
+
npmGlobalPath,
|
|
11021
|
+
npmGlobalVersion,
|
|
11022
|
+
bunGlobalPath,
|
|
11023
|
+
bunGlobalVersion,
|
|
11024
|
+
shadowed,
|
|
11025
|
+
shadowDescription,
|
|
11026
|
+
recommendation
|
|
11027
|
+
};
|
|
11028
|
+
}
|
|
11029
|
+
var PKG_NAME = "failproofai";
|
|
11030
|
+
var init_install_diagnosis = () => {};
|
|
11031
|
+
|
|
11032
|
+
// scripts/skew-log-filter.ts
|
|
11033
|
+
function makeSkewLogFilter() {
|
|
11034
|
+
let inSkewBlock = false;
|
|
11035
|
+
return (line) => {
|
|
11036
|
+
if (line.includes("Failed to find Server Action")) {
|
|
11037
|
+
inSkewBlock = true;
|
|
11038
|
+
return null;
|
|
11039
|
+
}
|
|
11040
|
+
if (inSkewBlock) {
|
|
11041
|
+
const trimmed = line.trimStart();
|
|
11042
|
+
if (trimmed.startsWith("Read more:") || line.includes("failed-to-find-server-action") || line.includes("ignore-listed frames") || /^\s+at\s/.test(line)) {
|
|
11043
|
+
return null;
|
|
11044
|
+
}
|
|
11045
|
+
inSkewBlock = false;
|
|
11046
|
+
}
|
|
11047
|
+
return line;
|
|
11048
|
+
};
|
|
11049
|
+
}
|
|
11050
|
+
|
|
11051
|
+
// scripts/launch.ts
|
|
11052
|
+
var exports_launch = {};
|
|
11053
|
+
__export(exports_launch, {
|
|
11054
|
+
launch: () => launch
|
|
11055
|
+
});
|
|
11056
|
+
import { spawn as spawn2 } from "child_process";
|
|
11057
|
+
import { realpathSync as realpathSync2, existsSync as existsSync17 } from "node:fs";
|
|
11058
|
+
import { resolve as resolve14, dirname as dirname7 } from "node:path";
|
|
11059
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11060
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
11061
|
+
function launch(mode) {
|
|
11062
|
+
const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
11063
|
+
console.log(`
|
|
11064
|
+
failproof ai
|
|
11065
|
+
`);
|
|
11066
|
+
console.log(` \uD83D\uDCE6 Version: ${version2}`);
|
|
11067
|
+
console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
|
|
11068
|
+
console.log(` \uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction`);
|
|
11069
|
+
console.log(` \uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
|
|
11070
|
+
`);
|
|
11071
|
+
let cmd;
|
|
11072
|
+
let cmdArgs;
|
|
11073
|
+
if (mode === "start") {
|
|
11074
|
+
const portIdx = remainingArgs.indexOf("--port");
|
|
11075
|
+
const port = portIdx >= 0 ? remainingArgs[portIdx + 1] : "8020";
|
|
11076
|
+
process.env.PORT = port;
|
|
11077
|
+
process.env.HOSTNAME = "0.0.0.0";
|
|
11078
|
+
cmd = "node";
|
|
11079
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve14(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
11080
|
+
const serverJsPath = resolve14(packageRoot, ".next/standalone/server.js");
|
|
11081
|
+
if (!existsSync17(serverJsPath)) {
|
|
11082
|
+
let shadowMessage = null;
|
|
11083
|
+
try {
|
|
11084
|
+
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
11085
|
+
if (diag.shadowed) {
|
|
11086
|
+
const alt = (diag.npmGlobalPath && diag.npmGlobalPath !== diag.pathFirstPath ? { path: diag.npmGlobalPath, version: diag.npmGlobalVersion } : null) ?? (diag.bunGlobalPath && diag.bunGlobalPath !== diag.pathFirstPath ? { path: diag.bunGlobalPath, version: diag.bunGlobalVersion } : null);
|
|
11087
|
+
const newer = alt?.path ?? "(unknown)";
|
|
11088
|
+
const newerVer = alt?.version ?? "?";
|
|
11089
|
+
shadowMessage = `
|
|
11090
|
+
Error: failproofai on your PATH is a stale install that no longer has its build output.
|
|
11091
|
+
` + ` Running: ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `
|
|
11092
|
+
` + ` Newer copy: ${newer} (v${newerVer})
|
|
11093
|
+
|
|
11094
|
+
` + `Remove the shadow with:
|
|
11095
|
+
${diag.recommendation}
|
|
11096
|
+
`;
|
|
11097
|
+
}
|
|
11098
|
+
} catch {}
|
|
11099
|
+
console.error(shadowMessage ?? `
|
|
11100
|
+
Error: Cannot find server.js at:
|
|
11101
|
+
${serverJsPath}
|
|
11102
|
+
|
|
11103
|
+
` + `The package may be missing its build output.
|
|
11104
|
+
` + `Try reinstalling:
|
|
11105
|
+
npm install -g failproofai@latest
|
|
11106
|
+
`);
|
|
11107
|
+
process.exit(1);
|
|
11108
|
+
}
|
|
11109
|
+
cmdArgs = [serverJsPath];
|
|
11110
|
+
} else {
|
|
11111
|
+
cmd = "bunx";
|
|
11112
|
+
cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
|
|
11113
|
+
}
|
|
11114
|
+
const filterLogs = mode === "start";
|
|
11115
|
+
const nextProcess = spawn2(cmd, cmdArgs, {
|
|
11116
|
+
stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
11117
|
+
env: {
|
|
11118
|
+
...process.env,
|
|
11119
|
+
...filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {},
|
|
11120
|
+
...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
|
|
11121
|
+
...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
|
|
11122
|
+
...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
|
|
11123
|
+
}
|
|
11124
|
+
});
|
|
11125
|
+
if (filterLogs) {
|
|
11126
|
+
for (const [src, dest] of [
|
|
11127
|
+
[nextProcess.stdout, process.stdout],
|
|
11128
|
+
[nextProcess.stderr, process.stderr]
|
|
11129
|
+
]) {
|
|
11130
|
+
if (!src)
|
|
11131
|
+
continue;
|
|
11132
|
+
const filter = makeSkewLogFilter();
|
|
11133
|
+
createInterface2({ input: src }).on("line", (line) => {
|
|
11134
|
+
const out = filter(line);
|
|
11135
|
+
if (out !== null)
|
|
11136
|
+
dest.write(out + `
|
|
11137
|
+
`);
|
|
11138
|
+
});
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
nextProcess.on("error", (error) => {
|
|
11142
|
+
console.error("Error starting Next.js:", error);
|
|
11143
|
+
process.exit(1);
|
|
11144
|
+
});
|
|
11145
|
+
nextProcess.on("exit", (code) => {
|
|
11146
|
+
process.exit(code || 0);
|
|
11147
|
+
});
|
|
11148
|
+
}
|
|
11149
|
+
var init_launch = __esm(() => {
|
|
11150
|
+
init_parse_script_args();
|
|
11151
|
+
init_install_diagnosis();
|
|
11152
|
+
init_package();
|
|
11153
|
+
});
|
|
11154
|
+
|
|
11155
|
+
// src/audit/cli.ts
|
|
11156
|
+
var exports_cli2 = {};
|
|
11157
|
+
__export(exports_cli2, {
|
|
11158
|
+
runAuditCli: () => runAuditCli,
|
|
11159
|
+
buildSummary: () => buildSummary,
|
|
11160
|
+
AUDIT_STAGES: () => AUDIT_STAGES
|
|
11161
|
+
});
|
|
11162
|
+
function colorOn() {
|
|
11163
|
+
if (process.env.NO_COLOR && process.env.NO_COLOR !== "")
|
|
11164
|
+
return false;
|
|
11165
|
+
if (process.env.FORCE_COLOR === "0")
|
|
11166
|
+
return false;
|
|
11167
|
+
if (process.env.FORCE_COLOR)
|
|
11168
|
+
return true;
|
|
11169
|
+
return !!process.stdout.isTTY;
|
|
11170
|
+
}
|
|
11171
|
+
function c(code, s) {
|
|
11172
|
+
return colorOn() ? `${code}${s}${RESET2}` : s;
|
|
11173
|
+
}
|
|
11174
|
+
function num(n) {
|
|
11175
|
+
return n.toLocaleString("en-US");
|
|
11176
|
+
}
|
|
11177
|
+
function die(message) {
|
|
11178
|
+
process.stderr.write(`Error: ${message}
|
|
11179
|
+
`);
|
|
11180
|
+
process.exit(1);
|
|
11181
|
+
}
|
|
11182
|
+
function startProgress() {
|
|
11183
|
+
const n = AUDIT_STAGES.length;
|
|
11184
|
+
let stage = 0;
|
|
11185
|
+
let tick = 0;
|
|
11186
|
+
let done = false;
|
|
11187
|
+
let printed = false;
|
|
11188
|
+
const lineFor = (i) => {
|
|
11189
|
+
const s = AUDIT_STAGES[i];
|
|
11190
|
+
if (done || i < stage)
|
|
11191
|
+
return ` ${c(GREEN2, "✓")} ${c(DIM2, s.label)}`;
|
|
11192
|
+
if (i === stage) {
|
|
11193
|
+
return ` ${c(PINK2, SPINNER[tick % SPINNER.length])} ${s.label} ${c(DIM2, s.detail)}`;
|
|
11194
|
+
}
|
|
11195
|
+
return ` ${c(DIM2, "○")} ${c(DIM2, s.label)}`;
|
|
11196
|
+
};
|
|
11197
|
+
const render = () => {
|
|
11198
|
+
const lines = Array.from({ length: n }, (_, i) => lineFor(i));
|
|
11199
|
+
if (printed)
|
|
11200
|
+
process.stdout.write(`\x1B[${n}A`);
|
|
11201
|
+
process.stdout.write(lines.map((l) => `\x1B[2K${l}`).join(`
|
|
11202
|
+
`) + `
|
|
11203
|
+
`);
|
|
11204
|
+
printed = true;
|
|
11205
|
+
};
|
|
11206
|
+
render();
|
|
11207
|
+
const spinTimer = setInterval(() => {
|
|
11208
|
+
tick++;
|
|
11209
|
+
render();
|
|
11210
|
+
}, 90);
|
|
11211
|
+
const stageTimer = setInterval(() => {
|
|
11212
|
+
if (stage < n - 1) {
|
|
11213
|
+
stage++;
|
|
11214
|
+
render();
|
|
11215
|
+
}
|
|
11216
|
+
}, 1100);
|
|
11217
|
+
const stop = () => {
|
|
11218
|
+
clearInterval(spinTimer);
|
|
11219
|
+
clearInterval(stageTimer);
|
|
11220
|
+
};
|
|
11221
|
+
return {
|
|
11222
|
+
finish() {
|
|
11223
|
+
stop();
|
|
11224
|
+
done = true;
|
|
11225
|
+
render();
|
|
11226
|
+
},
|
|
11227
|
+
fail() {
|
|
11228
|
+
stop();
|
|
11229
|
+
process.stdout.write(`
|
|
11230
|
+
`);
|
|
11231
|
+
}
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
async function runWithProgress(opts) {
|
|
11235
|
+
if (!process.stdout.isTTY) {
|
|
11236
|
+
process.stdout.write(` scanning your agent session history — this can take a moment…
|
|
11237
|
+
`);
|
|
11238
|
+
return runAudit(opts);
|
|
11239
|
+
}
|
|
11240
|
+
const progress = startProgress();
|
|
11241
|
+
try {
|
|
11242
|
+
const result = await runAudit(opts);
|
|
11243
|
+
progress.finish();
|
|
11244
|
+
return result;
|
|
11245
|
+
} catch (err) {
|
|
11246
|
+
progress.fail();
|
|
11247
|
+
throw err;
|
|
11248
|
+
}
|
|
11249
|
+
}
|
|
11250
|
+
function printHeader() {
|
|
11251
|
+
process.stdout.write(`
|
|
11252
|
+
${c(PINK2, "\uD83D\uDEE1 failproofai audit")} ${c(DIM2, "· beta")}
|
|
11253
|
+
|
|
11254
|
+
`);
|
|
11255
|
+
process.stdout.write(` ${c(DIM2, "starting audit…")}
|
|
11256
|
+
|
|
11257
|
+
`);
|
|
11258
|
+
}
|
|
11259
|
+
function buildSummary(result) {
|
|
11260
|
+
const sessions = result.transcripts.scanned;
|
|
11261
|
+
const events = result.eventsScanned;
|
|
11262
|
+
const projects = result.projectsScanned.length;
|
|
11263
|
+
const enabledRows = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig);
|
|
11264
|
+
const slippingRows = result.results.filter((r) => !(r.source === "builtin" && r.enabledInConfig));
|
|
11265
|
+
const lines = [];
|
|
11266
|
+
lines.push(`${c(GREEN2, "✓ audit complete")} ${c(DIM2, "·")} ` + `${c(BOLD, num(events))} tool call${events === 1 ? "" : "s"} across ` + `${num(sessions)} session${sessions === 1 ? "" : "s"}` + (projects > 0 ? ` ${c(DIM2, "·")} ${num(projects)} project${projects === 1 ? "" : "s"}` : ""));
|
|
11267
|
+
if (result.totals.hits === 0) {
|
|
11268
|
+
if (events > 0)
|
|
11269
|
+
lines.push(c(DIM2, "clean run — nothing flagged. nice."));
|
|
11270
|
+
return lines;
|
|
11271
|
+
}
|
|
11272
|
+
const parts = [];
|
|
11273
|
+
if (slippingRows.length > 0) {
|
|
11274
|
+
parts.push(`${c(PINK2, String(slippingRows.length))} ${slippingRows.length === 1 ? "pattern" : "patterns"} slipping through`);
|
|
11275
|
+
}
|
|
11276
|
+
if (enabledRows.length > 0) {
|
|
11277
|
+
parts.push(`${c(GREEN2, String(enabledRows.length))} already blocked by your policies`);
|
|
11278
|
+
}
|
|
11279
|
+
if (parts.length > 0)
|
|
11280
|
+
lines.push(parts.join(` ${c(DIM2, "·")} `));
|
|
11281
|
+
return lines;
|
|
11282
|
+
}
|
|
11283
|
+
function printSummary(result) {
|
|
11284
|
+
process.stdout.write(`
|
|
11285
|
+
`);
|
|
11286
|
+
for (const line of buildSummary(result))
|
|
11287
|
+
process.stdout.write(` ${line}
|
|
11288
|
+
`);
|
|
11289
|
+
}
|
|
11290
|
+
async function runAuditCli(args) {
|
|
11291
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
11292
|
+
process.stdout.write(HELP2);
|
|
11293
|
+
process.exit(0);
|
|
11294
|
+
}
|
|
11295
|
+
const stray = args.find((a) => a !== "--help" && a !== "-h");
|
|
11296
|
+
if (stray) {
|
|
11297
|
+
die(`\`audit\` takes no arguments yet (got: ${stray}).
|
|
11298
|
+
` + `Run \`failproofai audit\` to scan your history and open the dashboard.`);
|
|
11299
|
+
}
|
|
11300
|
+
const instanceId = getInstanceId2();
|
|
11301
|
+
trackHookEvent2(instanceId, "cli_audit_started", { source: "cli" });
|
|
11302
|
+
printHeader();
|
|
11303
|
+
const opts = {};
|
|
11304
|
+
let result;
|
|
11305
|
+
try {
|
|
11306
|
+
result = await runWithProgress(opts);
|
|
11307
|
+
} catch (err) {
|
|
11308
|
+
await trackHookEvent2(instanceId, "cli_audit_failed", {
|
|
11309
|
+
source: "cli",
|
|
11310
|
+
error_type: err instanceof Error ? err.name : "unknown"
|
|
11311
|
+
});
|
|
11312
|
+
die(`Audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11313
|
+
}
|
|
11314
|
+
printSummary(result);
|
|
11315
|
+
await trackHookEvent2(instanceId, "cli_audit_completed", {
|
|
11316
|
+
source: "cli",
|
|
11317
|
+
events_scanned: result.eventsScanned,
|
|
11318
|
+
sessions_scanned: result.transcripts.scanned,
|
|
11319
|
+
total_hits: result.totals.hits,
|
|
11320
|
+
findings: result.results.length
|
|
11321
|
+
});
|
|
11322
|
+
if (result.eventsScanned === 0) {
|
|
11323
|
+
process.stdout.write(`
|
|
11324
|
+
${c(DIM2, "no agent sessions found yet.")}
|
|
11325
|
+
` + ` install hooks with ${c(CYAN, "failproofai policies --install")} ` + `${c(DIM2, "and come back after using your agent.")}
|
|
11326
|
+
|
|
11327
|
+
`);
|
|
11328
|
+
process.exit(0);
|
|
11329
|
+
}
|
|
11330
|
+
const persisted = writeDashboardCache(opts, result);
|
|
11331
|
+
if (!persisted) {
|
|
11332
|
+
process.stdout.write(`
|
|
11333
|
+
${c(PINK2, "!")} ${c(DIM2, "couldn't save the audit cache; the dashboard may show an empty state.")}
|
|
11334
|
+
`);
|
|
11335
|
+
}
|
|
11336
|
+
const url = `http://localhost:${DASHBOARD_PORT}/audit`;
|
|
11337
|
+
process.stdout.write(`
|
|
11338
|
+
${c(DIM2, "starting the dashboard…")}
|
|
11339
|
+
` + ` ${c(PINK2, "✦")} ${c(BOLD, "here's your audit")} ${c(DIM2, "→")} ${c(CYAN, url)}
|
|
11340
|
+
` + ` ${c(DIM2, "(opening in your browser — press Ctrl+C to stop the server)")}
|
|
11341
|
+
|
|
11342
|
+
`);
|
|
11343
|
+
openWhenReady(DASHBOARD_PORT, "/audit");
|
|
11344
|
+
const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
|
|
11345
|
+
launch2("start");
|
|
11346
|
+
}
|
|
11347
|
+
var DASHBOARD_PORT = 8020, AUDIT_STAGES, HELP2, RESET2 = "\x1B[0m", DIM2 = "\x1B[2m", BOLD = "\x1B[1m", PINK2 = "\x1B[38;5;204m", GREEN2 = "\x1B[38;5;120m", CYAN = "\x1B[38;5;81m", SPINNER;
|
|
11348
|
+
var init_cli2 = __esm(() => {
|
|
11349
|
+
init_audit();
|
|
11350
|
+
init_dashboard_cache();
|
|
11351
|
+
init_hook_telemetry2();
|
|
11352
|
+
init_telemetry_id2();
|
|
11353
|
+
init_open_browser();
|
|
11354
|
+
AUDIT_STAGES = [
|
|
11355
|
+
{ label: "discovering transcripts", detail: "walking ~/.claude, ~/.codex, ~/.cursor, …" },
|
|
11356
|
+
{ label: "parsing session logs", detail: "reading JSONL + sqlite session stores" },
|
|
11357
|
+
{ label: "running policy checks", detail: "replaying through 30 builtin policies" },
|
|
11358
|
+
{ label: "aggregating results", detail: "counting hits, ranking by frequency" }
|
|
11359
|
+
];
|
|
11360
|
+
HELP2 = `
|
|
11361
|
+
failproofai audit — audit your AI agent's behavior, then open the dashboard
|
|
11362
|
+
|
|
11363
|
+
USAGE
|
|
11364
|
+
failproofai audit Scan your agent-CLI session history for risky and
|
|
11365
|
+
wasteful patterns, then open the audit dashboard.
|
|
11366
|
+
failproofai audit --help Show this help.
|
|
11367
|
+
|
|
11368
|
+
WHAT IT DOES
|
|
11369
|
+
1. Scans past sessions from every installed agent CLI (Claude, Codex, Cursor,
|
|
11370
|
+
Copilot, OpenCode, Pi, Gemini) — entirely on your machine.
|
|
11371
|
+
2. Starts the local dashboard and opens
|
|
11372
|
+
http://localhost:${DASHBOARD_PORT}/audit with your results.
|
|
11373
|
+
|
|
11374
|
+
Runs fully offline — no account or network required. Press Ctrl+C to stop the
|
|
11375
|
+
dashboard server when you're done.
|
|
11376
|
+
`.trimStart();
|
|
11377
|
+
SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
11378
|
+
});
|
|
11379
|
+
|
|
11380
|
+
// src/hooks/manager.ts
|
|
11381
|
+
import { execSync as execSync6 } from "node:child_process";
|
|
11382
|
+
import { resolve as resolve15, basename as basename5 } from "node:path";
|
|
11383
|
+
import { homedir as homedir23, platform as platform3, arch as arch2, release as release2, hostname as hostname2 } from "node:os";
|
|
11384
|
+
function getSettingsPath2(scope, cwd) {
|
|
11385
|
+
return claudeCode.getSettingsPath(scope, cwd);
|
|
11386
|
+
}
|
|
11387
|
+
function scopeLabel2(scope) {
|
|
11388
|
+
switch (scope) {
|
|
11389
|
+
case "user":
|
|
11390
|
+
return `~/.claude/settings.json`;
|
|
11391
|
+
case "project":
|
|
11392
|
+
return `{cwd}/.claude/settings.json`;
|
|
11393
|
+
case "local":
|
|
11394
|
+
return `{cwd}/.claude/settings.local.json`;
|
|
11395
|
+
}
|
|
11396
|
+
}
|
|
11397
|
+
function resolveFailproofaiBinary2() {
|
|
11398
|
+
const override = process.env.FAILPROOFAI_BINARY_OVERRIDE;
|
|
11399
|
+
if (override && override.trim())
|
|
11400
|
+
return override.trim();
|
|
11401
|
+
try {
|
|
11402
|
+
const cmd = process.platform === "win32" ? "where failproofai" : "which failproofai";
|
|
11403
|
+
const result = execSync6(cmd, { encoding: "utf8" }).trim();
|
|
11404
|
+
return result.split(`
|
|
11405
|
+
`)[0].trim();
|
|
11406
|
+
} catch {
|
|
11407
|
+
throw new CliError(`failproofai binary not found in PATH.
|
|
11408
|
+
` + "Install it globally first: npm install -g failproofai");
|
|
11409
|
+
}
|
|
11410
|
+
}
|
|
11411
|
+
function validatePolicyNames2(names) {
|
|
11412
|
+
const invalid = names.filter((n) => !VALID_POLICY_NAMES2.has(n));
|
|
11413
|
+
if (invalid.length > 0) {
|
|
11414
|
+
const validList = [...VALID_POLICY_NAMES2].join(", ");
|
|
11415
|
+
throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
|
|
11416
|
+
` + `Valid policies: ${validList}`);
|
|
11417
|
+
}
|
|
11418
|
+
}
|
|
11419
|
+
function deduplicateScopes2(scopes, cwd) {
|
|
11420
|
+
const seen = new Set;
|
|
11421
|
+
return scopes.filter((s) => {
|
|
11422
|
+
const p = getSettingsPath2(s, cwd);
|
|
11423
|
+
if (seen.has(p))
|
|
11424
|
+
return false;
|
|
11425
|
+
seen.add(p);
|
|
11426
|
+
return true;
|
|
11427
|
+
});
|
|
11428
|
+
}
|
|
11429
|
+
function hooksInstalledInSettings2(scope, cwd) {
|
|
11430
|
+
return claudeCode.hooksInstalledInSettings(scope, cwd);
|
|
11431
|
+
}
|
|
11432
|
+
async function installHooks2(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false, cli) {
|
|
11433
|
+
if (policyNames !== undefined && policyNames.length > 0) {
|
|
11434
|
+
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
11435
|
+
if (nonAllNames.length > 0)
|
|
11436
|
+
validatePolicyNames2(nonAllNames);
|
|
11437
|
+
if (policyNames.includes("all") && nonAllNames.length > 0) {
|
|
11438
|
+
throw new CliError(`"all" cannot be combined with specific policy names.
|
|
11439
|
+
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
11440
|
+
}
|
|
11441
|
+
}
|
|
11442
|
+
const selectedClis = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
|
|
11443
|
+
for (const cliId of selectedClis) {
|
|
11444
|
+
const integration = getIntegration(cliId);
|
|
11445
|
+
if (!integration.scopes.includes(scope)) {
|
|
11446
|
+
try {
|
|
11447
|
+
await trackHookEvent2(getInstanceId2(), "scope_validation_failed", {
|
|
11448
|
+
cli: cliId,
|
|
11449
|
+
scope,
|
|
11450
|
+
supported_scopes: integration.scopes
|
|
11451
|
+
});
|
|
11452
|
+
} catch {}
|
|
11453
|
+
throw new CliError(`Scope "${scope}" is not supported by ${integration.displayName}. ` + `Valid scopes: ${integration.scopes.join(", ")}`);
|
|
11454
|
+
}
|
|
11455
|
+
}
|
|
11456
|
+
const binaryPath = resolveFailproofaiBinary2();
|
|
11457
|
+
const previousConfig = readScopedHooksConfig(scope, cwd);
|
|
11458
|
+
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
11459
|
+
let selectedPolicies;
|
|
11460
|
+
if (policyNames !== undefined) {
|
|
11461
|
+
let incoming;
|
|
11462
|
+
if (policyNames.length === 1 && policyNames[0] === "all") {
|
|
11463
|
+
incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
|
|
11464
|
+
} else {
|
|
11465
|
+
incoming = policyNames;
|
|
11466
|
+
}
|
|
11467
|
+
selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
|
|
11468
|
+
} else {
|
|
11469
|
+
const preSelected = previousConfig.enabledPolicies.length > 0 ? previousConfig.enabledPolicies : undefined;
|
|
11470
|
+
selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
|
|
11471
|
+
}
|
|
11472
|
+
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
11473
|
+
if (removeCustomHooks) {
|
|
11474
|
+
delete configToWrite.customPoliciesPath;
|
|
11475
|
+
} else if (customPoliciesPath) {
|
|
11476
|
+
configToWrite.customPoliciesPath = resolve15(customPoliciesPath);
|
|
11477
|
+
let validatedHooks = [];
|
|
11478
|
+
try {
|
|
11479
|
+
validatedHooks = await loadCustomHooks(configToWrite.customPoliciesPath, { strict: true });
|
|
11480
|
+
} catch (err) {
|
|
11481
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11482
|
+
try {
|
|
11483
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
11484
|
+
scope,
|
|
11485
|
+
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
11486
|
+
});
|
|
11487
|
+
} catch {}
|
|
11488
|
+
console.error(`Error: ${msg}`);
|
|
11489
|
+
process.exit(1);
|
|
11490
|
+
}
|
|
11491
|
+
if (validatedHooks.length === 0) {
|
|
11492
|
+
try {
|
|
11493
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
11494
|
+
scope,
|
|
11495
|
+
error_type: "no_hooks_registered"
|
|
11496
|
+
});
|
|
11497
|
+
} catch {}
|
|
11498
|
+
console.error(`Error: no hooks registered in ${customPoliciesPath}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
|
|
11499
|
+
process.exit(1);
|
|
11500
|
+
}
|
|
11501
|
+
console.log(`
|
|
11502
|
+
Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
11503
|
+
}
|
|
11504
|
+
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
11505
|
+
console.log(`
|
|
11506
|
+
Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
11507
|
+
`);
|
|
11508
|
+
if (removeCustomHooks) {
|
|
11509
|
+
console.log("Custom hooks path cleared.");
|
|
11510
|
+
} else if (configToWrite.customPoliciesPath) {
|
|
11511
|
+
console.log(`Custom hooks path: ${configToWrite.customPoliciesPath}`);
|
|
11512
|
+
}
|
|
11513
|
+
const writtenSettingsPaths = [];
|
|
11514
|
+
for (const cliId of selectedClis) {
|
|
11515
|
+
const integration = getIntegration(cliId);
|
|
11516
|
+
const settingsPath = integration.getSettingsPath(scope, cwd);
|
|
11517
|
+
try {
|
|
11518
|
+
const settings = integration.readSettings(settingsPath);
|
|
11519
|
+
integration.writeHookEntries(settings, binaryPath, scope);
|
|
11520
|
+
integration.writeSettings(settingsPath, settings);
|
|
11521
|
+
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
11522
|
+
} catch (err) {
|
|
11523
|
+
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";
|
|
11524
|
+
try {
|
|
11525
|
+
await trackHookEvent2(getInstanceId2(), "hook_write_failed", {
|
|
11526
|
+
cli: cliId,
|
|
11527
|
+
scope,
|
|
11528
|
+
error_type: errorType
|
|
11529
|
+
});
|
|
11530
|
+
} catch {}
|
|
11531
|
+
throw err;
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
try {
|
|
11535
|
+
const newSet = new Set(selectedPolicies);
|
|
11536
|
+
const policiesAdded = selectedPolicies.filter((p) => !previousEnabled.has(p));
|
|
9427
11537
|
const policiesRemoved = [...previousEnabled].filter((p) => !newSet.has(p));
|
|
9428
11538
|
const distinctId = getInstanceId2();
|
|
9429
11539
|
await trackHookEvent2(distinctId, "hooks_installed", {
|
|
@@ -9435,7 +11545,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
|
9435
11545
|
policies_added: policiesAdded,
|
|
9436
11546
|
policies_removed: policiesRemoved,
|
|
9437
11547
|
...source ? { source } : {},
|
|
9438
|
-
platform:
|
|
11548
|
+
platform: platform3(),
|
|
9439
11549
|
arch: arch2(),
|
|
9440
11550
|
os_release: release2(),
|
|
9441
11551
|
hostname_hash: hashToId2(hostname2()),
|
|
@@ -9529,7 +11639,7 @@ function clisLabel(detected) {
|
|
|
9529
11639
|
return detected.map((id) => getIntegration(id).displayName).join(", ");
|
|
9530
11640
|
}
|
|
9531
11641
|
async function promptYesNo(stdin, stdout) {
|
|
9532
|
-
return new Promise((
|
|
11642
|
+
return new Promise((resolve16) => {
|
|
9533
11643
|
const rl = readline4.createInterface({ input: stdin, output: stdout });
|
|
9534
11644
|
let settled = false;
|
|
9535
11645
|
const finish = (answer) => {
|
|
@@ -9537,7 +11647,7 @@ async function promptYesNo(stdin, stdout) {
|
|
|
9537
11647
|
return;
|
|
9538
11648
|
settled = true;
|
|
9539
11649
|
rl.close();
|
|
9540
|
-
|
|
11650
|
+
resolve16(answer);
|
|
9541
11651
|
};
|
|
9542
11652
|
rl.on("SIGINT", () => finish("sigint"));
|
|
9543
11653
|
rl.question("Install policies now? [Y/n] ", (raw) => {
|
|
@@ -9609,211 +11719,25 @@ var init_first_run_nudge = __esm(() => {
|
|
|
9609
11719
|
init_telemetry_id2();
|
|
9610
11720
|
});
|
|
9611
11721
|
|
|
9612
|
-
// scripts/parse-script-args.ts
|
|
9613
|
-
import { resolve as resolve13 } from "path";
|
|
9614
|
-
function parseStringFlag(flagName, errorLabel, inlineValue, args, index, options) {
|
|
9615
|
-
const raw = inlineValue ?? args[index + 1];
|
|
9616
|
-
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
9617
|
-
console.error(`Error: ${flagName} requires ${errorLabel}`);
|
|
9618
|
-
process.exit(1);
|
|
9619
|
-
}
|
|
9620
|
-
const value = options?.resolve ? resolve13(raw) : raw;
|
|
9621
|
-
return { value, spliceCount: inlineValue !== null ? 1 : 2 };
|
|
9622
|
-
}
|
|
9623
|
-
function parseScriptArgs(argv) {
|
|
9624
|
-
const args = [...argv];
|
|
9625
|
-
let loggingLevel;
|
|
9626
|
-
let disableTelemetry = false;
|
|
9627
|
-
let allowedDevOrigins;
|
|
9628
|
-
for (let i = 0;i < args.length; i++) {
|
|
9629
|
-
const arg = args[i];
|
|
9630
|
-
const eqIdx = arg.indexOf("=");
|
|
9631
|
-
const flag = eqIdx >= 0 ? arg.slice(0, eqIdx) : arg;
|
|
9632
|
-
const inlineValue = eqIdx >= 0 ? arg.slice(eqIdx + 1) : null;
|
|
9633
|
-
if (flag === "--logging") {
|
|
9634
|
-
const raw = inlineValue ?? args[i + 1];
|
|
9635
|
-
if (raw === undefined || inlineValue === null && raw.startsWith("-")) {
|
|
9636
|
-
console.error("Error: --logging requires a level (info, warn, error)");
|
|
9637
|
-
process.exit(1);
|
|
9638
|
-
}
|
|
9639
|
-
const val = raw.toLowerCase();
|
|
9640
|
-
if (val !== "info" && val !== "warn" && val !== "error") {
|
|
9641
|
-
console.error("Error: --logging must be one of: info, warn, error");
|
|
9642
|
-
process.exit(1);
|
|
9643
|
-
}
|
|
9644
|
-
loggingLevel = val;
|
|
9645
|
-
args.splice(i, inlineValue !== null ? 1 : 2);
|
|
9646
|
-
i--;
|
|
9647
|
-
continue;
|
|
9648
|
-
}
|
|
9649
|
-
if (flag === "--disable-telemetry") {
|
|
9650
|
-
disableTelemetry = true;
|
|
9651
|
-
args.splice(i, 1);
|
|
9652
|
-
i--;
|
|
9653
|
-
continue;
|
|
9654
|
-
}
|
|
9655
|
-
if (flag === "--allowed-origins") {
|
|
9656
|
-
const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
|
|
9657
|
-
allowedDevOrigins = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
9658
|
-
args.splice(i, spliceCount);
|
|
9659
|
-
i--;
|
|
9660
|
-
continue;
|
|
9661
|
-
}
|
|
9662
|
-
}
|
|
9663
|
-
return { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
|
|
9664
|
-
}
|
|
9665
|
-
var init_parse_script_args = () => {};
|
|
9666
|
-
|
|
9667
|
-
// scripts/install-diagnosis.mjs
|
|
9668
|
-
import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync } from "node:fs";
|
|
9669
|
-
import { dirname as dirname6, resolve as resolve14 } from "node:path";
|
|
9670
|
-
import { homedir as homedir21, platform as platform3 } from "node:os";
|
|
9671
|
-
import { spawnSync } from "node:child_process";
|
|
9672
|
-
function findPackageRoot(start) {
|
|
9673
|
-
try {
|
|
9674
|
-
let dir = realpathSync(start);
|
|
9675
|
-
if (existsSync14(dir) && !existsSync14(resolve14(dir, "package.json"))) {
|
|
9676
|
-
dir = dirname6(dir);
|
|
9677
|
-
}
|
|
9678
|
-
while (true) {
|
|
9679
|
-
const pkgPath = resolve14(dir, "package.json");
|
|
9680
|
-
if (existsSync14(pkgPath)) {
|
|
9681
|
-
try {
|
|
9682
|
-
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
9683
|
-
if (pkg.name === PKG_NAME)
|
|
9684
|
-
return dir;
|
|
9685
|
-
} catch {}
|
|
9686
|
-
}
|
|
9687
|
-
const parent = dirname6(dir);
|
|
9688
|
-
if (parent === dir)
|
|
9689
|
-
return null;
|
|
9690
|
-
dir = parent;
|
|
9691
|
-
}
|
|
9692
|
-
} catch {
|
|
9693
|
-
return null;
|
|
9694
|
-
}
|
|
9695
|
-
}
|
|
9696
|
-
function readPackageVersion(packageRoot) {
|
|
9697
|
-
if (!packageRoot)
|
|
9698
|
-
return null;
|
|
9699
|
-
try {
|
|
9700
|
-
const pkg = JSON.parse(readFileSync11(resolve14(packageRoot, "package.json"), "utf8"));
|
|
9701
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
9702
|
-
} catch {
|
|
9703
|
-
return null;
|
|
9704
|
-
}
|
|
9705
|
-
}
|
|
9706
|
-
function resolvePathFirstBinary() {
|
|
9707
|
-
try {
|
|
9708
|
-
const isWin = platform3() === "win32";
|
|
9709
|
-
const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
|
|
9710
|
-
if (res.status !== 0)
|
|
9711
|
-
return null;
|
|
9712
|
-
const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
9713
|
-
return first ? first.trim() : null;
|
|
9714
|
-
} catch {
|
|
9715
|
-
return null;
|
|
9716
|
-
}
|
|
9717
|
-
}
|
|
9718
|
-
function locateNpmGlobal() {
|
|
9719
|
-
try {
|
|
9720
|
-
const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
9721
|
-
if (res.status !== 0)
|
|
9722
|
-
return null;
|
|
9723
|
-
const root = (res.stdout || "").trim();
|
|
9724
|
-
if (!root)
|
|
9725
|
-
return null;
|
|
9726
|
-
const candidate = resolve14(root, PKG_NAME);
|
|
9727
|
-
return existsSync14(resolve14(candidate, "package.json")) ? candidate : null;
|
|
9728
|
-
} catch {
|
|
9729
|
-
return null;
|
|
9730
|
-
}
|
|
9731
|
-
}
|
|
9732
|
-
function locateBunGlobal() {
|
|
9733
|
-
try {
|
|
9734
|
-
const candidate = resolve14(homedir21(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
9735
|
-
return existsSync14(resolve14(candidate, "package.json")) ? candidate : null;
|
|
9736
|
-
} catch {
|
|
9737
|
-
return null;
|
|
9738
|
-
}
|
|
9739
|
-
}
|
|
9740
|
-
function buildRecommendation(pathFirstBin) {
|
|
9741
|
-
if (!pathFirstBin)
|
|
9742
|
-
return null;
|
|
9743
|
-
const bunBinPrefix = resolve14(homedir21(), ".bun", "bin") + "/";
|
|
9744
|
-
const bunGlobalPrefix = resolve14(homedir21(), ".bun", "install", "global") + "/";
|
|
9745
|
-
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
9746
|
-
if (isBun) {
|
|
9747
|
-
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
9748
|
-
}
|
|
9749
|
-
return `npm rm -g ${PKG_NAME}`;
|
|
9750
|
-
}
|
|
9751
|
-
function diagnoseShadow(self) {
|
|
9752
|
-
const selfPackageRoot = (() => {
|
|
9753
|
-
try {
|
|
9754
|
-
return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
|
|
9755
|
-
} catch {
|
|
9756
|
-
return self?.selfPackageRoot ?? null;
|
|
9757
|
-
}
|
|
9758
|
-
})();
|
|
9759
|
-
const selfVersion = self?.selfVersion ?? null;
|
|
9760
|
-
const pathFirstBin = resolvePathFirstBinary();
|
|
9761
|
-
const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
|
|
9762
|
-
const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
|
|
9763
|
-
const npmGlobalPath = locateNpmGlobal();
|
|
9764
|
-
const npmGlobalVersion = readPackageVersion(npmGlobalPath);
|
|
9765
|
-
const bunGlobalPath = locateBunGlobal();
|
|
9766
|
-
const bunGlobalVersion = readPackageVersion(bunGlobalPath);
|
|
9767
|
-
let shadowed = false;
|
|
9768
|
-
if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
|
|
9769
|
-
shadowed = true;
|
|
9770
|
-
} else if (pathFirstPackageRoot) {
|
|
9771
|
-
if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
|
|
9772
|
-
shadowed = true;
|
|
9773
|
-
else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
|
|
9774
|
-
shadowed = true;
|
|
9775
|
-
}
|
|
9776
|
-
const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
|
|
9777
|
-
let shadowDescription = null;
|
|
9778
|
-
if (shadowed) {
|
|
9779
|
-
shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
|
|
9780
|
-
}
|
|
9781
|
-
return {
|
|
9782
|
-
selfPackageRoot,
|
|
9783
|
-
selfVersion,
|
|
9784
|
-
pathFirstBin,
|
|
9785
|
-
pathFirstPath: pathFirstPackageRoot,
|
|
9786
|
-
pathFirstVersion,
|
|
9787
|
-
npmGlobalPath,
|
|
9788
|
-
npmGlobalVersion,
|
|
9789
|
-
bunGlobalPath,
|
|
9790
|
-
bunGlobalVersion,
|
|
9791
|
-
shadowed,
|
|
9792
|
-
shadowDescription,
|
|
9793
|
-
recommendation
|
|
9794
|
-
};
|
|
9795
|
-
}
|
|
9796
|
-
var PKG_NAME = "failproofai";
|
|
9797
|
-
var init_install_diagnosis = () => {};
|
|
9798
|
-
|
|
9799
11722
|
// scripts/launch.ts
|
|
9800
|
-
var
|
|
9801
|
-
__export(
|
|
9802
|
-
launch: () =>
|
|
11723
|
+
var exports_launch2 = {};
|
|
11724
|
+
__export(exports_launch2, {
|
|
11725
|
+
launch: () => launch2
|
|
9803
11726
|
});
|
|
9804
|
-
import { spawn } from "child_process";
|
|
9805
|
-
import { realpathSync as
|
|
9806
|
-
import { resolve as
|
|
9807
|
-
import { fileURLToPath as
|
|
9808
|
-
|
|
11727
|
+
import { spawn as spawn3 } from "child_process";
|
|
11728
|
+
import { realpathSync as realpathSync3, existsSync as existsSync18 } from "node:fs";
|
|
11729
|
+
import { resolve as resolve16, dirname as dirname8 } from "node:path";
|
|
11730
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11731
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
11732
|
+
function launch2(mode) {
|
|
9809
11733
|
const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
9810
11734
|
console.log(`
|
|
9811
11735
|
failproof ai
|
|
9812
11736
|
`);
|
|
9813
11737
|
console.log(` \uD83D\uDCE6 Version: ${version2}`);
|
|
9814
11738
|
console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
|
|
9815
|
-
console.log(` \uD83D\uDCD6 Docs: https://befailproof.ai`);
|
|
9816
|
-
console.log(` \uD83D\uDCAC
|
|
11739
|
+
console.log(` \uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction`);
|
|
11740
|
+
console.log(` \uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
|
|
9817
11741
|
`);
|
|
9818
11742
|
let cmd;
|
|
9819
11743
|
let cmdArgs;
|
|
@@ -9823,9 +11747,9 @@ function launch(mode) {
|
|
|
9823
11747
|
process.env.PORT = port;
|
|
9824
11748
|
process.env.HOSTNAME = "0.0.0.0";
|
|
9825
11749
|
cmd = "node";
|
|
9826
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
9827
|
-
const serverJsPath =
|
|
9828
|
-
if (!
|
|
11750
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve16(dirname8(realpathSync3(fileURLToPath3(import.meta.url))), "..");
|
|
11751
|
+
const serverJsPath = resolve16(packageRoot, ".next/standalone/server.js");
|
|
11752
|
+
if (!existsSync18(serverJsPath)) {
|
|
9829
11753
|
let shadowMessage = null;
|
|
9830
11754
|
try {
|
|
9831
11755
|
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
@@ -9858,15 +11782,33 @@ Error: Cannot find server.js at:
|
|
|
9858
11782
|
cmd = "bunx";
|
|
9859
11783
|
cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
|
|
9860
11784
|
}
|
|
9861
|
-
const
|
|
9862
|
-
|
|
11785
|
+
const filterLogs = mode === "start";
|
|
11786
|
+
const nextProcess = spawn3(cmd, cmdArgs, {
|
|
11787
|
+
stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
9863
11788
|
env: {
|
|
9864
11789
|
...process.env,
|
|
11790
|
+
...filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {},
|
|
9865
11791
|
...loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {},
|
|
9866
11792
|
...disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {},
|
|
9867
11793
|
...allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}
|
|
9868
11794
|
}
|
|
9869
11795
|
});
|
|
11796
|
+
if (filterLogs) {
|
|
11797
|
+
for (const [src, dest] of [
|
|
11798
|
+
[nextProcess.stdout, process.stdout],
|
|
11799
|
+
[nextProcess.stderr, process.stderr]
|
|
11800
|
+
]) {
|
|
11801
|
+
if (!src)
|
|
11802
|
+
continue;
|
|
11803
|
+
const filter = makeSkewLogFilter();
|
|
11804
|
+
createInterface4({ input: src }).on("line", (line) => {
|
|
11805
|
+
const out = filter(line);
|
|
11806
|
+
if (out !== null)
|
|
11807
|
+
dest.write(out + `
|
|
11808
|
+
`);
|
|
11809
|
+
});
|
|
11810
|
+
}
|
|
11811
|
+
}
|
|
9870
11812
|
nextProcess.on("error", (error) => {
|
|
9871
11813
|
console.error("Error starting Next.js:", error);
|
|
9872
11814
|
process.exit(1);
|
|
@@ -9875,7 +11817,7 @@ Error: Cannot find server.js at:
|
|
|
9875
11817
|
process.exit(code || 0);
|
|
9876
11818
|
});
|
|
9877
11819
|
}
|
|
9878
|
-
var
|
|
11820
|
+
var init_launch2 = __esm(() => {
|
|
9879
11821
|
init_parse_script_args();
|
|
9880
11822
|
init_install_diagnosis();
|
|
9881
11823
|
init_package();
|
|
@@ -9899,18 +11841,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
9899
11841
|
});
|
|
9900
11842
|
|
|
9901
11843
|
// bin/failproofai.mjs
|
|
9902
|
-
import { realpathSync as
|
|
9903
|
-
import { dirname as
|
|
9904
|
-
import { fileURLToPath as
|
|
11844
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
11845
|
+
import { dirname as dirname9, resolve as resolve17 } from "path";
|
|
11846
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9905
11847
|
// package.json
|
|
9906
|
-
var version = "0.0.
|
|
11848
|
+
var version = "0.0.12-beta.0";
|
|
9907
11849
|
|
|
9908
11850
|
// bin/failproofai.mjs
|
|
9909
11851
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
9910
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
11852
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve17(dirname9(realpathSync4(fileURLToPath4(import.meta.url))), "..");
|
|
9911
11853
|
}
|
|
9912
11854
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
9913
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
11855
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve17(dirname9(realpathSync4(fileURLToPath4(import.meta.url))), "..", "dist");
|
|
9914
11856
|
}
|
|
9915
11857
|
var args = process.argv.slice(2);
|
|
9916
11858
|
if (args[0] === "p")
|
|
@@ -9957,7 +11899,7 @@ if (hookIdx >= 0) {
|
|
|
9957
11899
|
}
|
|
9958
11900
|
}
|
|
9959
11901
|
async function runCli() {
|
|
9960
|
-
const SUBCOMMANDS2 = ["policies", "policy", "auth"];
|
|
11902
|
+
const SUBCOMMANDS2 = ["policies", "policy", "auth", "audit"];
|
|
9961
11903
|
if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS2.includes(args[0])) {
|
|
9962
11904
|
const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
|
|
9963
11905
|
if (extraArgs.length > 0) {
|
|
@@ -10004,6 +11946,10 @@ COMMANDS
|
|
|
10004
11946
|
whoami Print the currently authenticated identity
|
|
10005
11947
|
auth --help, -h Show this help for the auth command
|
|
10006
11948
|
|
|
11949
|
+
audit Audit your agent's behavior, then open the
|
|
11950
|
+
dashboard at http://localhost:8020/audit
|
|
11951
|
+
audit --help, -h Show this help for the audit command
|
|
11952
|
+
|
|
10007
11953
|
--version, -v Print version and exit
|
|
10008
11954
|
--help, -h Show this help message
|
|
10009
11955
|
|
|
@@ -10036,8 +11982,8 @@ EXAMPLES
|
|
|
10036
11982
|
|
|
10037
11983
|
LINKS
|
|
10038
11984
|
\u2B50 Star us: https://github.com/failproofai/failproofai
|
|
10039
|
-
\uD83D\uDCD6 Docs: https://befailproof.ai
|
|
10040
|
-
\uD83D\uDCAC
|
|
11985
|
+
\uD83D\uDCD6 Docs: https://docs.befailproof.ai/introduction
|
|
11986
|
+
\uD83D\uDCAC Discord: https://discord.gg/2zjBZP7yQJ
|
|
10041
11987
|
`.trimStart());
|
|
10042
11988
|
process.exit(0);
|
|
10043
11989
|
}
|
|
@@ -10254,9 +12200,19 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
10254
12200
|
lastSubcommand = "auth";
|
|
10255
12201
|
const { runAuthCli: runAuthCli2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
|
|
10256
12202
|
await runAuthCli2(args.slice(1));
|
|
10257
|
-
await track("cli_auth_invoked", {
|
|
12203
|
+
await track("cli_auth_invoked", {
|
|
12204
|
+
args_count: args.length - 1,
|
|
12205
|
+
subcommand: args[1] ?? "help",
|
|
12206
|
+
exit_code: process.exitCode ?? 0
|
|
12207
|
+
});
|
|
10258
12208
|
process.exit(process.exitCode ?? 0);
|
|
10259
12209
|
}
|
|
12210
|
+
if (args[0] === "audit") {
|
|
12211
|
+
lastSubcommand = "audit";
|
|
12212
|
+
const { runAuditCli: runAuditCli2 } = await Promise.resolve().then(() => (init_cli2(), exports_cli2));
|
|
12213
|
+
await runAuditCli2(args.slice(1));
|
|
12214
|
+
return;
|
|
12215
|
+
}
|
|
10260
12216
|
if (args[0] === "policy") {
|
|
10261
12217
|
lastSubcommand = "policy";
|
|
10262
12218
|
const subArgs = args.slice(1);
|
|
@@ -10379,7 +12335,7 @@ For multiple policies use \`failproofai policies --${action === "add" ? "install
|
|
|
10379
12335
|
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
10380
12336
|
return dp[m][n];
|
|
10381
12337
|
};
|
|
10382
|
-
const primary = ["--version", "--help", "--hook", "policies", "policy", "auth"];
|
|
12338
|
+
const primary = ["--version", "--help", "--hook", "policies", "policy", "auth", "audit"];
|
|
10383
12339
|
const closest = primary.reduce((best, flag) => {
|
|
10384
12340
|
const dist = levenshtein(unknownFlag, flag);
|
|
10385
12341
|
return dist < best.dist ? { flag, dist } : best;
|
|
@@ -10400,8 +12356,8 @@ Run \`failproofai --help\` for usage details.`);
|
|
|
10400
12356
|
await maybeRunFirstRunNudge2();
|
|
10401
12357
|
} catch {}
|
|
10402
12358
|
}
|
|
10403
|
-
const { launch:
|
|
10404
|
-
|
|
12359
|
+
const { launch: launch3 } = await Promise.resolve().then(() => (init_launch2(), exports_launch2));
|
|
12360
|
+
launch3("start");
|
|
10405
12361
|
}
|
|
10406
12362
|
var { CliError: CliError3 } = await Promise.resolve().then(() => (init_cli_error2(), exports_cli_error));
|
|
10407
12363
|
try {
|