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