failproofai 0.0.6-beta.2 → 0.0.6-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +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 +2 -2
- 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 +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 +1 -1
- 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/[root-of-the-server]__0g72weg._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__05akje6._.js → [root-of-the-server]__096k.db._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0i5kvry._.js → [root-of-the-server]__0kyh86x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{1052sguyd-.ka.js → 0-dm_9a6nsc2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/{05j1px0r8yzh6.js → 01pmw1-asbek~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{14cl9poem30dq.js → 051m32nx~n5yr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0badv41uxa56..js → 0a-yctdwn368y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0xpl.oscrakvx.js → 0l-mu4okl-cj1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{00j0rr7rh8ef8.js → 0mazj-p-~2kc6.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +6 -0
- package/.next/standalone/.next/static/chunks/{0npb~873.wvg3.js → 156zca6aewyr-.js} +1 -1
- package/.next/standalone/CHANGELOG.md +7 -0
- package/.next/standalone/bin/failproofai.mjs +91 -4
- package/.next/standalone/dist/cli.mjs +1155 -54
- package/.next/standalone/docs/ar/built-in-policies.mdx +118 -118
- package/.next/standalone/docs/built-in-policies.mdx +2 -2
- package/.next/standalone/docs/de/built-in-policies.mdx +48 -48
- package/.next/standalone/docs/es/built-in-policies.mdx +82 -82
- package/.next/standalone/docs/fr/built-in-policies.mdx +72 -72
- package/.next/standalone/docs/he/built-in-policies.mdx +129 -128
- package/.next/standalone/docs/hi/built-in-policies.mdx +178 -182
- package/.next/standalone/docs/it/built-in-policies.mdx +64 -64
- package/.next/standalone/docs/ja/built-in-policies.mdx +128 -128
- package/.next/standalone/docs/ko/built-in-policies.mdx +111 -111
- package/.next/standalone/docs/pt-br/built-in-policies.mdx +65 -65
- package/.next/standalone/docs/ru/built-in-policies.mdx +72 -72
- package/.next/standalone/docs/tr/built-in-policies.mdx +99 -99
- package/.next/standalone/docs/vi/built-in-policies.mdx +69 -72
- package/.next/standalone/docs/zh/built-in-policies.mdx +76 -78
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/src/auth/login.ts +104 -0
- package/.next/standalone/src/auth/logout.ts +50 -0
- package/.next/standalone/src/auth/token-store.ts +64 -0
- package/.next/standalone/src/hooks/builtin-policies.ts +22 -20
- package/.next/standalone/src/hooks/handler.ts +35 -15
- package/.next/standalone/src/relay/daemon.ts +362 -0
- package/.next/standalone/src/relay/pid.ts +76 -0
- package/.next/standalone/src/relay/queue.ts +225 -0
- package/bin/failproofai.mjs +91 -4
- package/dist/cli.mjs +1155 -54
- package/package.json +1 -1
- package/src/auth/login.ts +104 -0
- package/src/auth/logout.ts +50 -0
- package/src/auth/token-store.ts +64 -0
- package/src/hooks/builtin-policies.ts +22 -20
- package/src/hooks/handler.ts +35 -15
- package/src/relay/daemon.ts +362 -0
- package/src/relay/pid.ts +76 -0
- package/src/relay/queue.ts +225 -0
- package/.next/standalone/.next/static/chunks/0ijk_kek9_wyx.js +0 -6
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_ssgManifest.js +0 -0
|
@@ -281,6 +281,7 @@ function getCurrentBranch(cwd) {
|
|
|
281
281
|
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
282
282
|
cwd,
|
|
283
283
|
encoding: "utf8",
|
|
284
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
284
285
|
timeout: 3000
|
|
285
286
|
}).trim();
|
|
286
287
|
gitBranchCache.set(cwd, branch);
|
|
@@ -295,6 +296,7 @@ function getHeadSha(cwd) {
|
|
|
295
296
|
const sha = execSync("git rev-parse HEAD", {
|
|
296
297
|
cwd,
|
|
297
298
|
encoding: "utf8",
|
|
299
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
298
300
|
timeout: 3000
|
|
299
301
|
}).trim();
|
|
300
302
|
return sha || null;
|
|
@@ -312,6 +314,7 @@ function getThirdPartyCheckRuns(cwd, sha) {
|
|
|
312
314
|
], {
|
|
313
315
|
cwd,
|
|
314
316
|
encoding: "utf8",
|
|
317
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
315
318
|
timeout: 15000
|
|
316
319
|
}).trim();
|
|
317
320
|
if (!json || json === "[]")
|
|
@@ -331,6 +334,7 @@ function getCommitStatuses(cwd, sha) {
|
|
|
331
334
|
], {
|
|
332
335
|
cwd,
|
|
333
336
|
encoding: "utf8",
|
|
337
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
334
338
|
timeout: 15000
|
|
335
339
|
}).trim();
|
|
336
340
|
if (!json || json === "[]")
|
|
@@ -682,7 +686,7 @@ function extractAbsolutePaths(command) {
|
|
|
682
686
|
return paths;
|
|
683
687
|
}
|
|
684
688
|
function blockReadOutsideCwd(ctx) {
|
|
685
|
-
const cwd = ctx.session?.cwd;
|
|
689
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || ctx.session?.cwd;
|
|
686
690
|
if (!cwd)
|
|
687
691
|
return allow();
|
|
688
692
|
const allowPaths = ctx.params?.allowPaths ?? [];
|
|
@@ -905,6 +909,7 @@ function requireCommitBeforeStop(ctx) {
|
|
|
905
909
|
const status = execSync("git status --porcelain", {
|
|
906
910
|
cwd,
|
|
907
911
|
encoding: "utf8",
|
|
912
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
908
913
|
timeout: 5000
|
|
909
914
|
}).trim();
|
|
910
915
|
if (status.length > 0) {
|
|
@@ -923,6 +928,7 @@ function requirePushBeforeStop(ctx) {
|
|
|
923
928
|
const remotes = execSync("git remote", {
|
|
924
929
|
cwd,
|
|
925
930
|
encoding: "utf8",
|
|
931
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
926
932
|
timeout: 3000
|
|
927
933
|
}).trim();
|
|
928
934
|
if (!remotes)
|
|
@@ -936,11 +942,11 @@ function requirePushBeforeStop(ctx) {
|
|
|
936
942
|
return allow(`On base branch "${baseBranch}", skipping push check.`);
|
|
937
943
|
}
|
|
938
944
|
try {
|
|
939
|
-
const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
945
|
+
const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
940
946
|
if (!ahead) {
|
|
941
947
|
return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
|
|
942
948
|
}
|
|
943
|
-
const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
949
|
+
const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
944
950
|
if (!diff) {
|
|
945
951
|
return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
|
|
946
952
|
}
|
|
@@ -950,6 +956,7 @@ function requirePushBeforeStop(ctx) {
|
|
|
950
956
|
execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
|
|
951
957
|
cwd,
|
|
952
958
|
encoding: "utf8",
|
|
959
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
953
960
|
timeout: 3000
|
|
954
961
|
});
|
|
955
962
|
hasTracking = true;
|
|
@@ -960,6 +967,7 @@ function requirePushBeforeStop(ctx) {
|
|
|
960
967
|
const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
|
|
961
968
|
cwd,
|
|
962
969
|
encoding: "utf8",
|
|
970
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
963
971
|
timeout: 5000
|
|
964
972
|
}).trim();
|
|
965
973
|
if (unpushed.length > 0) {
|
|
@@ -978,7 +986,7 @@ function requirePrBeforeStop(ctx) {
|
|
|
978
986
|
return allow("No working directory available, skipping PR check.");
|
|
979
987
|
try {
|
|
980
988
|
try {
|
|
981
|
-
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
989
|
+
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
982
990
|
} catch {
|
|
983
991
|
return allow("GitHub CLI (gh) not installed, skipping PR check.");
|
|
984
992
|
}
|
|
@@ -990,11 +998,11 @@ function requirePrBeforeStop(ctx) {
|
|
|
990
998
|
return allow(`On base branch "${baseBranch}", skipping PR check.`);
|
|
991
999
|
}
|
|
992
1000
|
try {
|
|
993
|
-
const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
1001
|
+
const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
994
1002
|
if (!ahead) {
|
|
995
1003
|
return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
|
|
996
1004
|
}
|
|
997
|
-
const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
1005
|
+
const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
998
1006
|
if (!diff) {
|
|
999
1007
|
return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
|
|
1000
1008
|
}
|
|
@@ -1004,6 +1012,7 @@ function requirePrBeforeStop(ctx) {
|
|
|
1004
1012
|
prJson = execSync("gh pr view --json number,url,state", {
|
|
1005
1013
|
cwd,
|
|
1006
1014
|
encoding: "utf8",
|
|
1015
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1007
1016
|
timeout: 15000
|
|
1008
1017
|
}).trim();
|
|
1009
1018
|
} catch {
|
|
@@ -1018,13 +1027,14 @@ function requirePrBeforeStop(ctx) {
|
|
|
1018
1027
|
execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
|
|
1019
1028
|
cwd,
|
|
1020
1029
|
encoding: "utf8",
|
|
1030
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1021
1031
|
timeout: 1e4
|
|
1022
1032
|
});
|
|
1023
|
-
const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
1033
|
+
const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1024
1034
|
if (!freshAhead) {
|
|
1025
1035
|
return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
|
|
1026
1036
|
}
|
|
1027
|
-
const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
1037
|
+
const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1028
1038
|
if (!freshDiff) {
|
|
1029
1039
|
return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
|
|
1030
1040
|
}
|
|
@@ -1041,7 +1051,7 @@ function requireCiGreenBeforeStop(ctx) {
|
|
|
1041
1051
|
return allow("No working directory available, skipping CI check.");
|
|
1042
1052
|
try {
|
|
1043
1053
|
try {
|
|
1044
|
-
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
1054
|
+
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1045
1055
|
} catch {
|
|
1046
1056
|
return allow("GitHub CLI (gh) not installed, skipping CI check.");
|
|
1047
1057
|
}
|
|
@@ -1050,7 +1060,7 @@ function requireCiGreenBeforeStop(ctx) {
|
|
|
1050
1060
|
return allow("Detached HEAD, skipping CI check.");
|
|
1051
1061
|
let workflowRuns = [];
|
|
1052
1062
|
try {
|
|
1053
|
-
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", timeout: 15000 }).trim();
|
|
1063
|
+
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 }).trim();
|
|
1054
1064
|
if (runsJson && runsJson !== "[]") {
|
|
1055
1065
|
workflowRuns = JSON.parse(runsJson);
|
|
1056
1066
|
}
|
|
@@ -2055,7 +2065,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2055
2065
|
});
|
|
2056
2066
|
|
|
2057
2067
|
// package.json
|
|
2058
|
-
var version2 = "0.0.6-beta.
|
|
2068
|
+
var version2 = "0.0.6-beta.3";
|
|
2059
2069
|
var init_package = () => {};
|
|
2060
2070
|
|
|
2061
2071
|
// src/posthog-key.ts
|
|
@@ -2167,6 +2177,538 @@ var init_telemetry_id = __esm(() => {
|
|
|
2167
2177
|
ID_FILE = path.join(ID_DIR, "instance-id");
|
|
2168
2178
|
});
|
|
2169
2179
|
|
|
2180
|
+
// src/auth/token-store.ts
|
|
2181
|
+
import {
|
|
2182
|
+
readFileSync as readFileSync3,
|
|
2183
|
+
writeFileSync as writeFileSync3,
|
|
2184
|
+
existsSync as existsSync5,
|
|
2185
|
+
mkdirSync as mkdirSync4,
|
|
2186
|
+
unlinkSync as unlinkSync2,
|
|
2187
|
+
renameSync as renameSync3,
|
|
2188
|
+
openSync,
|
|
2189
|
+
closeSync
|
|
2190
|
+
} from "node:fs";
|
|
2191
|
+
import { join as join4 } from "node:path";
|
|
2192
|
+
import { homedir as homedir6 } from "node:os";
|
|
2193
|
+
function ensureAuthDir() {
|
|
2194
|
+
if (!existsSync5(AUTH_DIR))
|
|
2195
|
+
mkdirSync4(AUTH_DIR, { recursive: true, mode: 448 });
|
|
2196
|
+
}
|
|
2197
|
+
function readTokens() {
|
|
2198
|
+
if (!existsSync5(AUTH_FILE))
|
|
2199
|
+
return null;
|
|
2200
|
+
try {
|
|
2201
|
+
const raw = readFileSync3(AUTH_FILE, "utf8");
|
|
2202
|
+
return JSON.parse(raw);
|
|
2203
|
+
} catch {
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function writeTokens(tokens) {
|
|
2208
|
+
ensureAuthDir();
|
|
2209
|
+
const tmpPath = `${AUTH_FILE}.tmp`;
|
|
2210
|
+
const fd = openSync(tmpPath, "w", 384);
|
|
2211
|
+
try {
|
|
2212
|
+
writeFileSync3(fd, JSON.stringify(tokens, null, 2));
|
|
2213
|
+
} finally {
|
|
2214
|
+
closeSync(fd);
|
|
2215
|
+
}
|
|
2216
|
+
renameSync3(tmpPath, AUTH_FILE);
|
|
2217
|
+
}
|
|
2218
|
+
function clearTokens() {
|
|
2219
|
+
if (existsSync5(AUTH_FILE))
|
|
2220
|
+
unlinkSync2(AUTH_FILE);
|
|
2221
|
+
}
|
|
2222
|
+
function isLoggedIn() {
|
|
2223
|
+
return existsSync5(AUTH_FILE);
|
|
2224
|
+
}
|
|
2225
|
+
var AUTH_DIR, AUTH_FILE;
|
|
2226
|
+
var init_token_store = __esm(() => {
|
|
2227
|
+
AUTH_DIR = join4(homedir6(), ".failproofai");
|
|
2228
|
+
AUTH_FILE = join4(AUTH_DIR, "auth.json");
|
|
2229
|
+
});
|
|
2230
|
+
|
|
2231
|
+
// src/relay/queue.ts
|
|
2232
|
+
var exports_queue = {};
|
|
2233
|
+
__export(exports_queue, {
|
|
2234
|
+
readProcessingFile: () => readProcessingFile,
|
|
2235
|
+
queueSizeBytes: () => queueSizeBytes,
|
|
2236
|
+
findOrphanProcessingFiles: () => findOrphanProcessingFiles,
|
|
2237
|
+
deleteProcessingFile: () => deleteProcessingFile,
|
|
2238
|
+
claimPendingBatch: () => claimPendingBatch,
|
|
2239
|
+
appendToServerQueue: () => appendToServerQueue
|
|
2240
|
+
});
|
|
2241
|
+
import {
|
|
2242
|
+
appendFileSync as appendFileSync3,
|
|
2243
|
+
mkdirSync as mkdirSync5,
|
|
2244
|
+
existsSync as existsSync6,
|
|
2245
|
+
readFileSync as readFileSync4,
|
|
2246
|
+
statSync as statSync3,
|
|
2247
|
+
renameSync as renameSync4,
|
|
2248
|
+
unlinkSync as unlinkSync3,
|
|
2249
|
+
readdirSync as readdirSync3,
|
|
2250
|
+
chmodSync
|
|
2251
|
+
} from "node:fs";
|
|
2252
|
+
import { join as join5 } from "node:path";
|
|
2253
|
+
import { homedir as homedir7 } from "node:os";
|
|
2254
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2255
|
+
function hashCwd(cwd) {
|
|
2256
|
+
if (!cwd)
|
|
2257
|
+
return null;
|
|
2258
|
+
return createHash("sha256").update(cwd).digest("hex");
|
|
2259
|
+
}
|
|
2260
|
+
function redactReason(reason) {
|
|
2261
|
+
if (!reason)
|
|
2262
|
+
return reason ?? null;
|
|
2263
|
+
return reason.replace(/AKIA[0-9A-Z]{16}/g, "[REDACTED-AWS-KEY]").replace(/eyJ[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+/g, "[REDACTED-JWT]").replace(/ghp_[A-Za-z0-9]{36,}/g, "[REDACTED-GH-TOKEN]").replace(/sk-[A-Za-z0-9]{20,}/g, "[REDACTED-API-KEY]").replace(/Bearer\s+[A-Za-z0-9_.=+-]+/gi, "Bearer [REDACTED]");
|
|
2264
|
+
}
|
|
2265
|
+
function sanitize(entry) {
|
|
2266
|
+
return {
|
|
2267
|
+
client_event_id: randomUUID(),
|
|
2268
|
+
timestamp: entry.timestamp,
|
|
2269
|
+
event_type: entry.eventType,
|
|
2270
|
+
tool_name: entry.toolName ?? null,
|
|
2271
|
+
policy_name: entry.policyName ?? null,
|
|
2272
|
+
policy_names: entry.policyNames ?? [],
|
|
2273
|
+
decision: entry.decision,
|
|
2274
|
+
reason: redactReason(entry.reason),
|
|
2275
|
+
duration_ms: entry.durationMs,
|
|
2276
|
+
session_id: entry.sessionId ?? null,
|
|
2277
|
+
cwd_hash: hashCwd(entry.cwd),
|
|
2278
|
+
permission_mode: entry.permissionMode ?? null,
|
|
2279
|
+
hook_event_name: entry.hookEventName ?? null
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
function ensureDir2() {
|
|
2283
|
+
if (!existsSync6(QUEUE_DIR)) {
|
|
2284
|
+
mkdirSync5(QUEUE_DIR, { recursive: true, mode: 448 });
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
function appendToServerQueue(entry) {
|
|
2288
|
+
if (!isLoggedIn())
|
|
2289
|
+
return;
|
|
2290
|
+
ensureDir2();
|
|
2291
|
+
try {
|
|
2292
|
+
if (existsSync6(PENDING_FILE) && statSync3(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
} catch {}
|
|
2296
|
+
const sanitized = sanitize(entry);
|
|
2297
|
+
appendFileSync3(PENDING_FILE, JSON.stringify(sanitized) + `
|
|
2298
|
+
`, { mode: 384 });
|
|
2299
|
+
try {
|
|
2300
|
+
chmodSync(PENDING_FILE, 384);
|
|
2301
|
+
} catch {}
|
|
2302
|
+
}
|
|
2303
|
+
function queueSizeBytes() {
|
|
2304
|
+
try {
|
|
2305
|
+
return statSync3(PENDING_FILE).size;
|
|
2306
|
+
} catch {
|
|
2307
|
+
return 0;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
function claimPendingBatch() {
|
|
2311
|
+
if (!existsSync6(PENDING_FILE))
|
|
2312
|
+
return null;
|
|
2313
|
+
try {
|
|
2314
|
+
const size = statSync3(PENDING_FILE).size;
|
|
2315
|
+
if (size === 0)
|
|
2316
|
+
return null;
|
|
2317
|
+
} catch {
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
const seq = `${Date.now()}-${process.pid}`;
|
|
2321
|
+
const processingFile = join5(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
|
|
2322
|
+
try {
|
|
2323
|
+
renameSync4(PENDING_FILE, processingFile);
|
|
2324
|
+
try {
|
|
2325
|
+
chmodSync(processingFile, 384);
|
|
2326
|
+
} catch {}
|
|
2327
|
+
return processingFile;
|
|
2328
|
+
} catch (err) {
|
|
2329
|
+
const e = err;
|
|
2330
|
+
if (e?.code === "ENOENT")
|
|
2331
|
+
return null;
|
|
2332
|
+
throw err;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
function findOrphanProcessingFiles() {
|
|
2336
|
+
ensureDir2();
|
|
2337
|
+
try {
|
|
2338
|
+
return readdirSync3(QUEUE_DIR).filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl")).map((n) => join5(QUEUE_DIR, n)).sort();
|
|
2339
|
+
} catch {
|
|
2340
|
+
return [];
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function readProcessingFile(path2) {
|
|
2344
|
+
if (!existsSync6(path2))
|
|
2345
|
+
return [];
|
|
2346
|
+
const content = readFileSync4(path2, "utf8");
|
|
2347
|
+
const out = [];
|
|
2348
|
+
for (const line of content.split(`
|
|
2349
|
+
`)) {
|
|
2350
|
+
const trimmed = line.trim();
|
|
2351
|
+
if (!trimmed)
|
|
2352
|
+
continue;
|
|
2353
|
+
try {
|
|
2354
|
+
out.push(JSON.parse(trimmed));
|
|
2355
|
+
} catch {}
|
|
2356
|
+
}
|
|
2357
|
+
return out;
|
|
2358
|
+
}
|
|
2359
|
+
function deleteProcessingFile(path2) {
|
|
2360
|
+
try {
|
|
2361
|
+
unlinkSync3(path2);
|
|
2362
|
+
} catch {}
|
|
2363
|
+
}
|
|
2364
|
+
var QUEUE_DIR, PENDING_FILE, PROCESSING_PREFIX = "processing-", MAX_QUEUE_BYTES;
|
|
2365
|
+
var init_queue = __esm(() => {
|
|
2366
|
+
init_token_store();
|
|
2367
|
+
QUEUE_DIR = join5(homedir7(), ".failproofai", "cache", "server-queue");
|
|
2368
|
+
PENDING_FILE = join5(QUEUE_DIR, "pending.jsonl");
|
|
2369
|
+
MAX_QUEUE_BYTES = 50 * 1024 * 1024;
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
// src/relay/pid.ts
|
|
2373
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2374
|
+
import { join as join6, dirname as dirname3 } from "node:path";
|
|
2375
|
+
import { homedir as homedir8 } from "node:os";
|
|
2376
|
+
function readPid() {
|
|
2377
|
+
if (!existsSync7(PID_FILE))
|
|
2378
|
+
return null;
|
|
2379
|
+
try {
|
|
2380
|
+
const raw = readFileSync5(PID_FILE, "utf8").trim();
|
|
2381
|
+
const pid = parseInt(raw, 10);
|
|
2382
|
+
if (Number.isNaN(pid) || pid <= 0)
|
|
2383
|
+
return null;
|
|
2384
|
+
return pid;
|
|
2385
|
+
} catch {
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
function writePid(pid) {
|
|
2390
|
+
const dir = dirname3(PID_FILE);
|
|
2391
|
+
if (!existsSync7(dir))
|
|
2392
|
+
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
2393
|
+
writeFileSync4(PID_FILE, String(pid));
|
|
2394
|
+
}
|
|
2395
|
+
function clearPid() {
|
|
2396
|
+
if (existsSync7(PID_FILE))
|
|
2397
|
+
unlinkSync4(PID_FILE);
|
|
2398
|
+
}
|
|
2399
|
+
function isProcessAlive(pid) {
|
|
2400
|
+
try {
|
|
2401
|
+
process.kill(pid, 0);
|
|
2402
|
+
return true;
|
|
2403
|
+
} catch (err) {
|
|
2404
|
+
const e = err;
|
|
2405
|
+
if (e?.code === "EPERM")
|
|
2406
|
+
return true;
|
|
2407
|
+
return false;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
function stopRelay() {
|
|
2411
|
+
const pid = readPid();
|
|
2412
|
+
if (pid === null)
|
|
2413
|
+
return false;
|
|
2414
|
+
if (!isProcessAlive(pid)) {
|
|
2415
|
+
clearPid();
|
|
2416
|
+
return false;
|
|
2417
|
+
}
|
|
2418
|
+
try {
|
|
2419
|
+
process.kill(pid, "SIGTERM");
|
|
2420
|
+
clearPid();
|
|
2421
|
+
return true;
|
|
2422
|
+
} catch {
|
|
2423
|
+
return false;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
var PID_FILE;
|
|
2427
|
+
var init_pid = __esm(() => {
|
|
2428
|
+
PID_FILE = join6(homedir8(), ".failproofai", "relay.pid");
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
// src/relay/daemon.ts
|
|
2432
|
+
var exports_daemon = {};
|
|
2433
|
+
__export(exports_daemon, {
|
|
2434
|
+
waitForRelayAlive: () => waitForRelayAlive,
|
|
2435
|
+
runOneShotSync: () => runOneShotSync,
|
|
2436
|
+
runDaemon: () => runDaemon,
|
|
2437
|
+
ensureRelayRunning: () => ensureRelayRunning
|
|
2438
|
+
});
|
|
2439
|
+
import { spawn } from "node:child_process";
|
|
2440
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2441
|
+
import { join as join7 } from "node:path";
|
|
2442
|
+
import { homedir as homedir9 } from "node:os";
|
|
2443
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2444
|
+
function ensureRelayRunning() {
|
|
2445
|
+
if (!isLoggedIn())
|
|
2446
|
+
return;
|
|
2447
|
+
const pid = readPid();
|
|
2448
|
+
if (pid !== null && isProcessAlive(pid))
|
|
2449
|
+
return;
|
|
2450
|
+
if (pid !== null)
|
|
2451
|
+
clearPid();
|
|
2452
|
+
spawnDaemon();
|
|
2453
|
+
}
|
|
2454
|
+
function spawnDaemon() {
|
|
2455
|
+
const entrypoint = process.env.FAILPROOFAI_RELAY_ENTRYPOINT ?? process.argv[1];
|
|
2456
|
+
if (!entrypoint)
|
|
2457
|
+
return;
|
|
2458
|
+
const child = spawn(process.execPath, [entrypoint, "--relay-daemon"], {
|
|
2459
|
+
detached: true,
|
|
2460
|
+
stdio: "ignore",
|
|
2461
|
+
env: { ...process.env, FAILPROOFAI_DAEMON: "1" }
|
|
2462
|
+
});
|
|
2463
|
+
child.unref();
|
|
2464
|
+
if (typeof child.pid === "number") {
|
|
2465
|
+
writePid(child.pid);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
async function waitForRelayAlive(timeoutMs = 2000) {
|
|
2469
|
+
const deadline = Date.now() + timeoutMs;
|
|
2470
|
+
while (Date.now() < deadline) {
|
|
2471
|
+
const pid = readPid();
|
|
2472
|
+
if (pid !== null && isProcessAlive(pid))
|
|
2473
|
+
return true;
|
|
2474
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2475
|
+
}
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
async function refreshTokenIfNeeded() {
|
|
2479
|
+
const tokens = readTokens();
|
|
2480
|
+
if (!tokens)
|
|
2481
|
+
return null;
|
|
2482
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
2483
|
+
if (tokens.expires_at - nowSec > 300) {
|
|
2484
|
+
return tokens.access_token;
|
|
2485
|
+
}
|
|
2486
|
+
try {
|
|
2487
|
+
const resp = await fetch(`${tokens.server_url}/api/v1/auth/refresh`, {
|
|
2488
|
+
method: "POST",
|
|
2489
|
+
headers: { "Content-Type": "application/json" },
|
|
2490
|
+
body: JSON.stringify({ refresh_token: tokens.refresh_token }),
|
|
2491
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
2492
|
+
});
|
|
2493
|
+
if (!resp.ok)
|
|
2494
|
+
return tokens.access_token;
|
|
2495
|
+
const refreshed = await resp.json();
|
|
2496
|
+
writeTokens({
|
|
2497
|
+
...tokens,
|
|
2498
|
+
access_token: refreshed.access_token,
|
|
2499
|
+
refresh_token: refreshed.refresh_token,
|
|
2500
|
+
expires_at: nowSec + refreshed.expires_in
|
|
2501
|
+
});
|
|
2502
|
+
return refreshed.access_token;
|
|
2503
|
+
} catch {
|
|
2504
|
+
return tokens.access_token;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
class Relay {
|
|
2509
|
+
ws;
|
|
2510
|
+
pendingAcks = new Map;
|
|
2511
|
+
closed = false;
|
|
2512
|
+
constructor(ws) {
|
|
2513
|
+
this.ws = ws;
|
|
2514
|
+
ws.onmessage = (ev) => this.handleMessage(ev.data);
|
|
2515
|
+
ws.onclose = () => this.handleClose();
|
|
2516
|
+
ws.onerror = () => this.handleClose();
|
|
2517
|
+
}
|
|
2518
|
+
handleMessage(data) {
|
|
2519
|
+
try {
|
|
2520
|
+
const msg = JSON.parse(data);
|
|
2521
|
+
if (msg.ack && this.pendingAcks.has(msg.ack)) {
|
|
2522
|
+
const resolve5 = this.pendingAcks.get(msg.ack);
|
|
2523
|
+
this.pendingAcks.delete(msg.ack);
|
|
2524
|
+
resolve5(true);
|
|
2525
|
+
}
|
|
2526
|
+
} catch {}
|
|
2527
|
+
}
|
|
2528
|
+
handleClose() {
|
|
2529
|
+
this.closed = true;
|
|
2530
|
+
for (const [, resolve5] of this.pendingAcks) {
|
|
2531
|
+
resolve5(false);
|
|
2532
|
+
}
|
|
2533
|
+
this.pendingAcks.clear();
|
|
2534
|
+
}
|
|
2535
|
+
isClosed() {
|
|
2536
|
+
return this.closed;
|
|
2537
|
+
}
|
|
2538
|
+
close() {
|
|
2539
|
+
try {
|
|
2540
|
+
this.ws.close();
|
|
2541
|
+
} catch {}
|
|
2542
|
+
}
|
|
2543
|
+
async sendBatchAndWaitAck(events) {
|
|
2544
|
+
if (this.closed)
|
|
2545
|
+
return false;
|
|
2546
|
+
const batchId = randomUUID2();
|
|
2547
|
+
const ackPromise = new Promise((resolve5) => {
|
|
2548
|
+
this.pendingAcks.set(batchId, resolve5);
|
|
2549
|
+
setTimeout(() => {
|
|
2550
|
+
if (this.pendingAcks.delete(batchId))
|
|
2551
|
+
resolve5(false);
|
|
2552
|
+
}, ACK_TIMEOUT_MS);
|
|
2553
|
+
});
|
|
2554
|
+
try {
|
|
2555
|
+
this.ws.send(JSON.stringify({ batch_id: batchId, events }));
|
|
2556
|
+
} catch {
|
|
2557
|
+
this.pendingAcks.delete(batchId);
|
|
2558
|
+
return false;
|
|
2559
|
+
}
|
|
2560
|
+
return ackPromise;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
async function connect(wsUrl, token) {
|
|
2564
|
+
const WSCtor = globalThis.WebSocket;
|
|
2565
|
+
if (!WSCtor) {
|
|
2566
|
+
throw new Error("WebSocket not available in this Node version. Requires Node 22+.");
|
|
2567
|
+
}
|
|
2568
|
+
const ws = new WSCtor(wsUrl);
|
|
2569
|
+
await new Promise((resolve5, reject) => {
|
|
2570
|
+
let settled = false;
|
|
2571
|
+
const timeout = setTimeout(() => {
|
|
2572
|
+
if (settled)
|
|
2573
|
+
return;
|
|
2574
|
+
settled = true;
|
|
2575
|
+
try {
|
|
2576
|
+
ws.close();
|
|
2577
|
+
} catch {}
|
|
2578
|
+
reject(new Error("WebSocket connect timeout"));
|
|
2579
|
+
}, WS_CONNECT_TIMEOUT_MS);
|
|
2580
|
+
ws.onopen = () => {
|
|
2581
|
+
if (settled)
|
|
2582
|
+
return;
|
|
2583
|
+
settled = true;
|
|
2584
|
+
clearTimeout(timeout);
|
|
2585
|
+
try {
|
|
2586
|
+
ws.send(token);
|
|
2587
|
+
resolve5();
|
|
2588
|
+
} catch (e) {
|
|
2589
|
+
reject(e);
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
ws.onerror = (e) => {
|
|
2593
|
+
if (settled)
|
|
2594
|
+
return;
|
|
2595
|
+
settled = true;
|
|
2596
|
+
clearTimeout(timeout);
|
|
2597
|
+
reject(e);
|
|
2598
|
+
};
|
|
2599
|
+
ws.onclose = () => {
|
|
2600
|
+
if (settled)
|
|
2601
|
+
return;
|
|
2602
|
+
settled = true;
|
|
2603
|
+
clearTimeout(timeout);
|
|
2604
|
+
reject(new Error("WebSocket closed before opening"));
|
|
2605
|
+
};
|
|
2606
|
+
});
|
|
2607
|
+
return ws;
|
|
2608
|
+
}
|
|
2609
|
+
async function sendProcessingFile(relay, path2) {
|
|
2610
|
+
const events = readProcessingFile(path2);
|
|
2611
|
+
if (events.length === 0)
|
|
2612
|
+
return true;
|
|
2613
|
+
for (let i = 0;i < events.length; i += BATCH_SIZE) {
|
|
2614
|
+
const batch = events.slice(i, i + BATCH_SIZE);
|
|
2615
|
+
const ok = await relay.sendBatchAndWaitAck(batch);
|
|
2616
|
+
if (!ok)
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
async function runDaemon() {
|
|
2622
|
+
let reconnectDelay = RECONNECT_BASE_MS;
|
|
2623
|
+
while (true) {
|
|
2624
|
+
const token = await refreshTokenIfNeeded();
|
|
2625
|
+
const tokens = readTokens();
|
|
2626
|
+
if (!token || !tokens) {
|
|
2627
|
+
await new Promise((r) => setTimeout(r, 30000));
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
const wsUrl = `${tokens.server_url.replace(/^http/, "ws")}/ws/events/ingest`;
|
|
2631
|
+
try {
|
|
2632
|
+
const ws = await connect(wsUrl, token);
|
|
2633
|
+
const relay = new Relay(ws);
|
|
2634
|
+
reconnectDelay = RECONNECT_BASE_MS;
|
|
2635
|
+
for (const orphan of findOrphanProcessingFiles()) {
|
|
2636
|
+
if (relay.isClosed())
|
|
2637
|
+
break;
|
|
2638
|
+
const ok = await sendProcessingFile(relay, orphan);
|
|
2639
|
+
if (ok)
|
|
2640
|
+
deleteProcessingFile(orphan);
|
|
2641
|
+
}
|
|
2642
|
+
while (!relay.isClosed()) {
|
|
2643
|
+
let processingFile = null;
|
|
2644
|
+
try {
|
|
2645
|
+
processingFile = claimPendingBatch();
|
|
2646
|
+
} catch {}
|
|
2647
|
+
if (processingFile) {
|
|
2648
|
+
const ok = await sendProcessingFile(relay, processingFile);
|
|
2649
|
+
if (ok) {
|
|
2650
|
+
deleteProcessingFile(processingFile);
|
|
2651
|
+
} else {
|
|
2652
|
+
break;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
await new Promise((r) => setTimeout(r, FLUSH_INTERVAL_MS));
|
|
2656
|
+
}
|
|
2657
|
+
relay.close();
|
|
2658
|
+
} catch {}
|
|
2659
|
+
if (existsSync8(QUEUE_DIR2)) {}
|
|
2660
|
+
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
2661
|
+
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
async function runOneShotSync() {
|
|
2665
|
+
const token = await refreshTokenIfNeeded();
|
|
2666
|
+
const tokens = readTokens();
|
|
2667
|
+
if (!token || !tokens) {
|
|
2668
|
+
throw new Error("Not logged in. Run `failproofai login` first.");
|
|
2669
|
+
}
|
|
2670
|
+
let total = 0;
|
|
2671
|
+
async function postBatch(events) {
|
|
2672
|
+
const resp = await fetch(`${tokens.server_url}/api/v1/events/batch`, {
|
|
2673
|
+
method: "POST",
|
|
2674
|
+
headers: {
|
|
2675
|
+
"Content-Type": "application/json",
|
|
2676
|
+
Authorization: `Bearer ${token}`
|
|
2677
|
+
},
|
|
2678
|
+
body: JSON.stringify({ events }),
|
|
2679
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
2680
|
+
});
|
|
2681
|
+
if (!resp.ok) {
|
|
2682
|
+
throw new Error(`Sync failed: ${resp.status} ${resp.statusText}`);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
for (const orphan of findOrphanProcessingFiles()) {
|
|
2686
|
+
const events = readProcessingFile(orphan);
|
|
2687
|
+
if (events.length > 0) {
|
|
2688
|
+
await postBatch(events);
|
|
2689
|
+
total += events.length;
|
|
2690
|
+
}
|
|
2691
|
+
deleteProcessingFile(orphan);
|
|
2692
|
+
}
|
|
2693
|
+
const processingFile = claimPendingBatch();
|
|
2694
|
+
if (processingFile) {
|
|
2695
|
+
const events = readProcessingFile(processingFile);
|
|
2696
|
+
if (events.length > 0) {
|
|
2697
|
+
await postBatch(events);
|
|
2698
|
+
total += events.length;
|
|
2699
|
+
}
|
|
2700
|
+
deleteProcessingFile(processingFile);
|
|
2701
|
+
}
|
|
2702
|
+
return total;
|
|
2703
|
+
}
|
|
2704
|
+
var QUEUE_DIR2, BATCH_SIZE = 100, FLUSH_INTERVAL_MS = 2000, RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 60000, HTTP_TIMEOUT_MS = 1e4, WS_CONNECT_TIMEOUT_MS = 15000, ACK_TIMEOUT_MS = 30000;
|
|
2705
|
+
var init_daemon = __esm(() => {
|
|
2706
|
+
init_token_store();
|
|
2707
|
+
init_pid();
|
|
2708
|
+
init_queue();
|
|
2709
|
+
QUEUE_DIR2 = join7(homedir9(), ".failproofai", "cache", "server-queue");
|
|
2710
|
+
});
|
|
2711
|
+
|
|
2170
2712
|
// src/hooks/handler.ts
|
|
2171
2713
|
var exports_handler = {};
|
|
2172
2714
|
__export(exports_handler, {
|
|
@@ -2274,25 +2816,34 @@ async function handleHookEvent(eventType) {
|
|
|
2274
2816
|
if (result.stderr) {
|
|
2275
2817
|
process.stderr.write(result.stderr);
|
|
2276
2818
|
}
|
|
2819
|
+
const activityEntry = {
|
|
2820
|
+
timestamp: Date.now(),
|
|
2821
|
+
eventType,
|
|
2822
|
+
toolName: parsed.tool_name ?? null,
|
|
2823
|
+
policyName: result.policyName,
|
|
2824
|
+
policyNames: result.policyNames,
|
|
2825
|
+
decision: result.decision,
|
|
2826
|
+
reason: result.reason,
|
|
2827
|
+
durationMs,
|
|
2828
|
+
sessionId: session.sessionId,
|
|
2829
|
+
transcriptPath: session.transcriptPath,
|
|
2830
|
+
cwd: session.cwd,
|
|
2831
|
+
permissionMode: session.permissionMode,
|
|
2832
|
+
hookEventName: session.hookEventName
|
|
2833
|
+
};
|
|
2277
2834
|
try {
|
|
2278
|
-
persistHookActivity(
|
|
2279
|
-
timestamp: Date.now(),
|
|
2280
|
-
eventType,
|
|
2281
|
-
toolName: parsed.tool_name ?? null,
|
|
2282
|
-
policyName: result.policyName,
|
|
2283
|
-
policyNames: result.policyNames,
|
|
2284
|
-
decision: result.decision,
|
|
2285
|
-
reason: result.reason,
|
|
2286
|
-
durationMs,
|
|
2287
|
-
sessionId: session.sessionId,
|
|
2288
|
-
transcriptPath: session.transcriptPath,
|
|
2289
|
-
cwd: session.cwd,
|
|
2290
|
-
permissionMode: session.permissionMode,
|
|
2291
|
-
hookEventName: session.hookEventName
|
|
2292
|
-
});
|
|
2835
|
+
persistHookActivity(activityEntry);
|
|
2293
2836
|
} catch {
|
|
2294
2837
|
hookLogWarn("activity persistence failed");
|
|
2295
2838
|
}
|
|
2839
|
+
try {
|
|
2840
|
+
const { appendToServerQueue: appendToServerQueue2 } = await Promise.resolve().then(() => (init_queue(), exports_queue));
|
|
2841
|
+
appendToServerQueue2(activityEntry);
|
|
2842
|
+
} catch {}
|
|
2843
|
+
try {
|
|
2844
|
+
const { ensureRelayRunning: ensureRelayRunning2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
|
|
2845
|
+
ensureRelayRunning2();
|
|
2846
|
+
} catch {}
|
|
2296
2847
|
if (result.decision === "deny" || result.decision === "instruct") {
|
|
2297
2848
|
try {
|
|
2298
2849
|
const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
|
|
@@ -2327,6 +2878,287 @@ var init_handler = __esm(() => {
|
|
|
2327
2878
|
init_hook_logger();
|
|
2328
2879
|
});
|
|
2329
2880
|
|
|
2881
|
+
// src/relay/daemon.ts
|
|
2882
|
+
var exports_daemon2 = {};
|
|
2883
|
+
__export(exports_daemon2, {
|
|
2884
|
+
waitForRelayAlive: () => waitForRelayAlive2,
|
|
2885
|
+
runOneShotSync: () => runOneShotSync2,
|
|
2886
|
+
runDaemon: () => runDaemon2,
|
|
2887
|
+
ensureRelayRunning: () => ensureRelayRunning2
|
|
2888
|
+
});
|
|
2889
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2890
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2891
|
+
import { join as join8 } from "node:path";
|
|
2892
|
+
import { homedir as homedir10 } from "node:os";
|
|
2893
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2894
|
+
function ensureRelayRunning2() {
|
|
2895
|
+
if (!isLoggedIn())
|
|
2896
|
+
return;
|
|
2897
|
+
const pid = readPid();
|
|
2898
|
+
if (pid !== null && isProcessAlive(pid))
|
|
2899
|
+
return;
|
|
2900
|
+
if (pid !== null)
|
|
2901
|
+
clearPid();
|
|
2902
|
+
spawnDaemon2();
|
|
2903
|
+
}
|
|
2904
|
+
function spawnDaemon2() {
|
|
2905
|
+
const entrypoint = process.env.FAILPROOFAI_RELAY_ENTRYPOINT ?? process.argv[1];
|
|
2906
|
+
if (!entrypoint)
|
|
2907
|
+
return;
|
|
2908
|
+
const child = spawn2(process.execPath, [entrypoint, "--relay-daemon"], {
|
|
2909
|
+
detached: true,
|
|
2910
|
+
stdio: "ignore",
|
|
2911
|
+
env: { ...process.env, FAILPROOFAI_DAEMON: "1" }
|
|
2912
|
+
});
|
|
2913
|
+
child.unref();
|
|
2914
|
+
if (typeof child.pid === "number") {
|
|
2915
|
+
writePid(child.pid);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
async function waitForRelayAlive2(timeoutMs = 2000) {
|
|
2919
|
+
const deadline = Date.now() + timeoutMs;
|
|
2920
|
+
while (Date.now() < deadline) {
|
|
2921
|
+
const pid = readPid();
|
|
2922
|
+
if (pid !== null && isProcessAlive(pid))
|
|
2923
|
+
return true;
|
|
2924
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2925
|
+
}
|
|
2926
|
+
return false;
|
|
2927
|
+
}
|
|
2928
|
+
async function refreshTokenIfNeeded2() {
|
|
2929
|
+
const tokens = readTokens();
|
|
2930
|
+
if (!tokens)
|
|
2931
|
+
return null;
|
|
2932
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
2933
|
+
if (tokens.expires_at - nowSec > 300) {
|
|
2934
|
+
return tokens.access_token;
|
|
2935
|
+
}
|
|
2936
|
+
try {
|
|
2937
|
+
const resp = await fetch(`${tokens.server_url}/api/v1/auth/refresh`, {
|
|
2938
|
+
method: "POST",
|
|
2939
|
+
headers: { "Content-Type": "application/json" },
|
|
2940
|
+
body: JSON.stringify({ refresh_token: tokens.refresh_token }),
|
|
2941
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS2)
|
|
2942
|
+
});
|
|
2943
|
+
if (!resp.ok)
|
|
2944
|
+
return tokens.access_token;
|
|
2945
|
+
const refreshed = await resp.json();
|
|
2946
|
+
writeTokens({
|
|
2947
|
+
...tokens,
|
|
2948
|
+
access_token: refreshed.access_token,
|
|
2949
|
+
refresh_token: refreshed.refresh_token,
|
|
2950
|
+
expires_at: nowSec + refreshed.expires_in
|
|
2951
|
+
});
|
|
2952
|
+
return refreshed.access_token;
|
|
2953
|
+
} catch {
|
|
2954
|
+
return tokens.access_token;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
class Relay2 {
|
|
2959
|
+
ws;
|
|
2960
|
+
pendingAcks = new Map;
|
|
2961
|
+
closed = false;
|
|
2962
|
+
constructor(ws) {
|
|
2963
|
+
this.ws = ws;
|
|
2964
|
+
ws.onmessage = (ev) => this.handleMessage(ev.data);
|
|
2965
|
+
ws.onclose = () => this.handleClose();
|
|
2966
|
+
ws.onerror = () => this.handleClose();
|
|
2967
|
+
}
|
|
2968
|
+
handleMessage(data) {
|
|
2969
|
+
try {
|
|
2970
|
+
const msg = JSON.parse(data);
|
|
2971
|
+
if (msg.ack && this.pendingAcks.has(msg.ack)) {
|
|
2972
|
+
const resolve5 = this.pendingAcks.get(msg.ack);
|
|
2973
|
+
this.pendingAcks.delete(msg.ack);
|
|
2974
|
+
resolve5(true);
|
|
2975
|
+
}
|
|
2976
|
+
} catch {}
|
|
2977
|
+
}
|
|
2978
|
+
handleClose() {
|
|
2979
|
+
this.closed = true;
|
|
2980
|
+
for (const [, resolve5] of this.pendingAcks) {
|
|
2981
|
+
resolve5(false);
|
|
2982
|
+
}
|
|
2983
|
+
this.pendingAcks.clear();
|
|
2984
|
+
}
|
|
2985
|
+
isClosed() {
|
|
2986
|
+
return this.closed;
|
|
2987
|
+
}
|
|
2988
|
+
close() {
|
|
2989
|
+
try {
|
|
2990
|
+
this.ws.close();
|
|
2991
|
+
} catch {}
|
|
2992
|
+
}
|
|
2993
|
+
async sendBatchAndWaitAck(events) {
|
|
2994
|
+
if (this.closed)
|
|
2995
|
+
return false;
|
|
2996
|
+
const batchId = randomUUID3();
|
|
2997
|
+
const ackPromise = new Promise((resolve5) => {
|
|
2998
|
+
this.pendingAcks.set(batchId, resolve5);
|
|
2999
|
+
setTimeout(() => {
|
|
3000
|
+
if (this.pendingAcks.delete(batchId))
|
|
3001
|
+
resolve5(false);
|
|
3002
|
+
}, ACK_TIMEOUT_MS2);
|
|
3003
|
+
});
|
|
3004
|
+
try {
|
|
3005
|
+
this.ws.send(JSON.stringify({ batch_id: batchId, events }));
|
|
3006
|
+
} catch {
|
|
3007
|
+
this.pendingAcks.delete(batchId);
|
|
3008
|
+
return false;
|
|
3009
|
+
}
|
|
3010
|
+
return ackPromise;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
async function connect2(wsUrl, token) {
|
|
3014
|
+
const WSCtor = globalThis.WebSocket;
|
|
3015
|
+
if (!WSCtor) {
|
|
3016
|
+
throw new Error("WebSocket not available in this Node version. Requires Node 22+.");
|
|
3017
|
+
}
|
|
3018
|
+
const ws = new WSCtor(wsUrl);
|
|
3019
|
+
await new Promise((resolve5, reject) => {
|
|
3020
|
+
let settled = false;
|
|
3021
|
+
const timeout = setTimeout(() => {
|
|
3022
|
+
if (settled)
|
|
3023
|
+
return;
|
|
3024
|
+
settled = true;
|
|
3025
|
+
try {
|
|
3026
|
+
ws.close();
|
|
3027
|
+
} catch {}
|
|
3028
|
+
reject(new Error("WebSocket connect timeout"));
|
|
3029
|
+
}, WS_CONNECT_TIMEOUT_MS2);
|
|
3030
|
+
ws.onopen = () => {
|
|
3031
|
+
if (settled)
|
|
3032
|
+
return;
|
|
3033
|
+
settled = true;
|
|
3034
|
+
clearTimeout(timeout);
|
|
3035
|
+
try {
|
|
3036
|
+
ws.send(token);
|
|
3037
|
+
resolve5();
|
|
3038
|
+
} catch (e) {
|
|
3039
|
+
reject(e);
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
ws.onerror = (e) => {
|
|
3043
|
+
if (settled)
|
|
3044
|
+
return;
|
|
3045
|
+
settled = true;
|
|
3046
|
+
clearTimeout(timeout);
|
|
3047
|
+
reject(e);
|
|
3048
|
+
};
|
|
3049
|
+
ws.onclose = () => {
|
|
3050
|
+
if (settled)
|
|
3051
|
+
return;
|
|
3052
|
+
settled = true;
|
|
3053
|
+
clearTimeout(timeout);
|
|
3054
|
+
reject(new Error("WebSocket closed before opening"));
|
|
3055
|
+
};
|
|
3056
|
+
});
|
|
3057
|
+
return ws;
|
|
3058
|
+
}
|
|
3059
|
+
async function sendProcessingFile2(relay, path2) {
|
|
3060
|
+
const events = readProcessingFile(path2);
|
|
3061
|
+
if (events.length === 0)
|
|
3062
|
+
return true;
|
|
3063
|
+
for (let i = 0;i < events.length; i += BATCH_SIZE2) {
|
|
3064
|
+
const batch = events.slice(i, i + BATCH_SIZE2);
|
|
3065
|
+
const ok = await relay.sendBatchAndWaitAck(batch);
|
|
3066
|
+
if (!ok)
|
|
3067
|
+
return false;
|
|
3068
|
+
}
|
|
3069
|
+
return true;
|
|
3070
|
+
}
|
|
3071
|
+
async function runDaemon2() {
|
|
3072
|
+
let reconnectDelay = RECONNECT_BASE_MS2;
|
|
3073
|
+
while (true) {
|
|
3074
|
+
const token = await refreshTokenIfNeeded2();
|
|
3075
|
+
const tokens = readTokens();
|
|
3076
|
+
if (!token || !tokens) {
|
|
3077
|
+
await new Promise((r) => setTimeout(r, 30000));
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
const wsUrl = `${tokens.server_url.replace(/^http/, "ws")}/ws/events/ingest`;
|
|
3081
|
+
try {
|
|
3082
|
+
const ws = await connect2(wsUrl, token);
|
|
3083
|
+
const relay = new Relay2(ws);
|
|
3084
|
+
reconnectDelay = RECONNECT_BASE_MS2;
|
|
3085
|
+
for (const orphan of findOrphanProcessingFiles()) {
|
|
3086
|
+
if (relay.isClosed())
|
|
3087
|
+
break;
|
|
3088
|
+
const ok = await sendProcessingFile2(relay, orphan);
|
|
3089
|
+
if (ok)
|
|
3090
|
+
deleteProcessingFile(orphan);
|
|
3091
|
+
}
|
|
3092
|
+
while (!relay.isClosed()) {
|
|
3093
|
+
let processingFile = null;
|
|
3094
|
+
try {
|
|
3095
|
+
processingFile = claimPendingBatch();
|
|
3096
|
+
} catch {}
|
|
3097
|
+
if (processingFile) {
|
|
3098
|
+
const ok = await sendProcessingFile2(relay, processingFile);
|
|
3099
|
+
if (ok) {
|
|
3100
|
+
deleteProcessingFile(processingFile);
|
|
3101
|
+
} else {
|
|
3102
|
+
break;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
await new Promise((r) => setTimeout(r, FLUSH_INTERVAL_MS2));
|
|
3106
|
+
}
|
|
3107
|
+
relay.close();
|
|
3108
|
+
} catch {}
|
|
3109
|
+
if (existsSync9(QUEUE_DIR3)) {}
|
|
3110
|
+
await new Promise((r) => setTimeout(r, reconnectDelay));
|
|
3111
|
+
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS2);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
async function runOneShotSync2() {
|
|
3115
|
+
const token = await refreshTokenIfNeeded2();
|
|
3116
|
+
const tokens = readTokens();
|
|
3117
|
+
if (!token || !tokens) {
|
|
3118
|
+
throw new Error("Not logged in. Run `failproofai login` first.");
|
|
3119
|
+
}
|
|
3120
|
+
let total = 0;
|
|
3121
|
+
async function postBatch(events) {
|
|
3122
|
+
const resp = await fetch(`${tokens.server_url}/api/v1/events/batch`, {
|
|
3123
|
+
method: "POST",
|
|
3124
|
+
headers: {
|
|
3125
|
+
"Content-Type": "application/json",
|
|
3126
|
+
Authorization: `Bearer ${token}`
|
|
3127
|
+
},
|
|
3128
|
+
body: JSON.stringify({ events }),
|
|
3129
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS2)
|
|
3130
|
+
});
|
|
3131
|
+
if (!resp.ok) {
|
|
3132
|
+
throw new Error(`Sync failed: ${resp.status} ${resp.statusText}`);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
for (const orphan of findOrphanProcessingFiles()) {
|
|
3136
|
+
const events = readProcessingFile(orphan);
|
|
3137
|
+
if (events.length > 0) {
|
|
3138
|
+
await postBatch(events);
|
|
3139
|
+
total += events.length;
|
|
3140
|
+
}
|
|
3141
|
+
deleteProcessingFile(orphan);
|
|
3142
|
+
}
|
|
3143
|
+
const processingFile = claimPendingBatch();
|
|
3144
|
+
if (processingFile) {
|
|
3145
|
+
const events = readProcessingFile(processingFile);
|
|
3146
|
+
if (events.length > 0) {
|
|
3147
|
+
await postBatch(events);
|
|
3148
|
+
total += events.length;
|
|
3149
|
+
}
|
|
3150
|
+
deleteProcessingFile(processingFile);
|
|
3151
|
+
}
|
|
3152
|
+
return total;
|
|
3153
|
+
}
|
|
3154
|
+
var QUEUE_DIR3, BATCH_SIZE2 = 100, FLUSH_INTERVAL_MS2 = 2000, RECONNECT_BASE_MS2 = 1000, RECONNECT_MAX_MS2 = 60000, HTTP_TIMEOUT_MS2 = 1e4, WS_CONNECT_TIMEOUT_MS2 = 15000, ACK_TIMEOUT_MS2 = 30000;
|
|
3155
|
+
var init_daemon2 = __esm(() => {
|
|
3156
|
+
init_token_store();
|
|
3157
|
+
init_pid();
|
|
3158
|
+
init_queue();
|
|
3159
|
+
QUEUE_DIR3 = join8(homedir10(), ".failproofai", "cache", "server-queue");
|
|
3160
|
+
});
|
|
3161
|
+
|
|
2330
3162
|
// src/hooks/types.ts
|
|
2331
3163
|
var HOOK_SCOPES, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
2332
3164
|
var init_types = __esm(() => {
|
|
@@ -2640,14 +3472,14 @@ __export(exports_manager, {
|
|
|
2640
3472
|
getSettingsPath: () => getSettingsPath
|
|
2641
3473
|
});
|
|
2642
3474
|
import { execSync as execSync3 } from "node:child_process";
|
|
2643
|
-
import { readFileSync as
|
|
2644
|
-
import { resolve as resolve5, dirname as
|
|
2645
|
-
import { homedir as
|
|
3475
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
|
|
3476
|
+
import { resolve as resolve5, dirname as dirname4, basename as basename2 } from "node:path";
|
|
3477
|
+
import { homedir as homedir11, platform, arch, release, hostname } from "node:os";
|
|
2646
3478
|
function getSettingsPath(scope, cwd) {
|
|
2647
3479
|
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
2648
3480
|
switch (scope) {
|
|
2649
3481
|
case "user":
|
|
2650
|
-
return resolve5(
|
|
3482
|
+
return resolve5(homedir11(), ".claude", "settings.json");
|
|
2651
3483
|
case "project":
|
|
2652
3484
|
return resolve5(base, ".claude", "settings.json");
|
|
2653
3485
|
case "local":
|
|
@@ -2665,15 +3497,15 @@ function scopeLabel(scope) {
|
|
|
2665
3497
|
}
|
|
2666
3498
|
}
|
|
2667
3499
|
function readSettings(settingsPath) {
|
|
2668
|
-
if (!
|
|
3500
|
+
if (!existsSync10(settingsPath)) {
|
|
2669
3501
|
return {};
|
|
2670
3502
|
}
|
|
2671
|
-
const raw =
|
|
3503
|
+
const raw = readFileSync6(settingsPath, "utf8");
|
|
2672
3504
|
return JSON.parse(raw);
|
|
2673
3505
|
}
|
|
2674
3506
|
function writeSettings(settingsPath, settings) {
|
|
2675
|
-
|
|
2676
|
-
|
|
3507
|
+
mkdirSync7(dirname4(settingsPath), { recursive: true });
|
|
3508
|
+
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
2677
3509
|
`, "utf8");
|
|
2678
3510
|
}
|
|
2679
3511
|
function resolveFailproofaiBinary() {
|
|
@@ -2713,7 +3545,7 @@ function deduplicateScopes(scopes, cwd) {
|
|
|
2713
3545
|
}
|
|
2714
3546
|
function hooksInstalledInSettings(scope, cwd) {
|
|
2715
3547
|
const settingsPath = getSettingsPath(scope, cwd);
|
|
2716
|
-
if (!
|
|
3548
|
+
if (!existsSync10(settingsPath))
|
|
2717
3549
|
return false;
|
|
2718
3550
|
try {
|
|
2719
3551
|
const settings = readSettings(settingsPath);
|
|
@@ -2941,7 +3773,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
2941
3773
|
let totalRemoved = 0;
|
|
2942
3774
|
for (const s of scopesToRemove) {
|
|
2943
3775
|
const settingsPath = getSettingsPath(s, cwd);
|
|
2944
|
-
if (!
|
|
3776
|
+
if (!existsSync10(settingsPath)) {
|
|
2945
3777
|
if (scope !== "all") {
|
|
2946
3778
|
console.log("No settings file found. Nothing to remove.");
|
|
2947
3779
|
return;
|
|
@@ -3116,7 +3948,7 @@ Failproof AI Hook Policies
|
|
|
3116
3948
|
if (config.customPoliciesPath) {
|
|
3117
3949
|
console.log(`
|
|
3118
3950
|
── Custom Policies (${config.customPoliciesPath}) ───────────────────────`);
|
|
3119
|
-
if (!
|
|
3951
|
+
if (!existsSync10(config.customPoliciesPath)) {
|
|
3120
3952
|
console.log(` \x1B[31m✗ File not found: ${config.customPoliciesPath}\x1B[0m`);
|
|
3121
3953
|
} else {
|
|
3122
3954
|
const hooks = await loadCustomHooks(config.customPoliciesPath);
|
|
@@ -3134,7 +3966,7 @@ Failproof AI Hook Policies
|
|
|
3134
3966
|
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
3135
3967
|
const conventionDirs = [
|
|
3136
3968
|
{ label: "Project", dir: resolve5(base, ".failproofai", "policies") },
|
|
3137
|
-
{ label: "User", dir: resolve5(
|
|
3969
|
+
{ label: "User", dir: resolve5(homedir11(), ".failproofai", "policies") }
|
|
3138
3970
|
];
|
|
3139
3971
|
for (const { label, dir } of conventionDirs) {
|
|
3140
3972
|
const files = discoverPolicyFiles(dir);
|
|
@@ -3174,11 +4006,210 @@ var init_manager = __esm(() => {
|
|
|
3174
4006
|
VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
3175
4007
|
});
|
|
3176
4008
|
|
|
4009
|
+
// src/auth/login.ts
|
|
4010
|
+
var exports_login = {};
|
|
4011
|
+
__export(exports_login, {
|
|
4012
|
+
login: () => login
|
|
4013
|
+
});
|
|
4014
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
4015
|
+
import { platform as platform2 } from "node:os";
|
|
4016
|
+
function openBrowser(url) {
|
|
4017
|
+
const os2 = platform2();
|
|
4018
|
+
try {
|
|
4019
|
+
if (os2 === "darwin") {
|
|
4020
|
+
spawn3("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
4021
|
+
} else if (os2 === "win32") {
|
|
4022
|
+
spawn3("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
|
|
4023
|
+
} else {
|
|
4024
|
+
spawn3("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
4025
|
+
}
|
|
4026
|
+
} catch {}
|
|
4027
|
+
}
|
|
4028
|
+
async function postJson(url, body, timeoutMs = HTTP_TIMEOUT_MS3) {
|
|
4029
|
+
const resp = await fetch(url, {
|
|
4030
|
+
method: "POST",
|
|
4031
|
+
headers: { "Content-Type": "application/json" },
|
|
4032
|
+
body: JSON.stringify(body),
|
|
4033
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
4034
|
+
});
|
|
4035
|
+
if (!resp.ok) {
|
|
4036
|
+
throw new Error(`${url} → ${resp.status} ${resp.statusText}`);
|
|
4037
|
+
}
|
|
4038
|
+
return await resp.json();
|
|
4039
|
+
}
|
|
4040
|
+
async function login() {
|
|
4041
|
+
const serverUrl = DEFAULT_SERVER_URL;
|
|
4042
|
+
console.log("Requesting device code...");
|
|
4043
|
+
const dc = await postJson(`${serverUrl}/api/v1/auth/device-code`, {});
|
|
4044
|
+
console.log(`
|
|
4045
|
+
Open this URL in your browser (will be opened automatically):`);
|
|
4046
|
+
console.log(` ${dc.verification_url}
|
|
4047
|
+
`);
|
|
4048
|
+
console.log(` Your code: ${dc.user_code}
|
|
4049
|
+
`);
|
|
4050
|
+
openBrowser(dc.verification_url);
|
|
4051
|
+
const deadline = Date.now() + dc.expires_in * 1000;
|
|
4052
|
+
const intervalMs = dc.interval * 1000;
|
|
4053
|
+
while (Date.now() < deadline) {
|
|
4054
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
4055
|
+
try {
|
|
4056
|
+
const result = await postJson(`${serverUrl}/api/v1/auth/device-token`, { device_code: dc.device_code });
|
|
4057
|
+
if ("access_token" in result) {
|
|
4058
|
+
const tokens = {
|
|
4059
|
+
access_token: result.access_token,
|
|
4060
|
+
refresh_token: result.refresh_token,
|
|
4061
|
+
expires_at: Math.floor(Date.now() / 1000) + result.expires_in,
|
|
4062
|
+
user_email: result.user.email,
|
|
4063
|
+
user_id: result.user.id,
|
|
4064
|
+
server_url: serverUrl
|
|
4065
|
+
};
|
|
4066
|
+
writeTokens(tokens);
|
|
4067
|
+
console.log(`Logged in as ${result.user.email}`);
|
|
4068
|
+
try {
|
|
4069
|
+
const { ensureRelayRunning: ensureRelayRunning3 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
|
|
4070
|
+
ensureRelayRunning3();
|
|
4071
|
+
console.log("Relay daemon started.");
|
|
4072
|
+
} catch (e) {
|
|
4073
|
+
console.warn("Failed to auto-start relay daemon:", e);
|
|
4074
|
+
}
|
|
4075
|
+
return;
|
|
4076
|
+
}
|
|
4077
|
+
} catch {}
|
|
4078
|
+
}
|
|
4079
|
+
throw new Error("Login timed out. Run `failproofai login` again.");
|
|
4080
|
+
}
|
|
4081
|
+
var DEFAULT_SERVER_URL, HTTP_TIMEOUT_MS3 = 1e4;
|
|
4082
|
+
var init_login = __esm(() => {
|
|
4083
|
+
init_token_store();
|
|
4084
|
+
DEFAULT_SERVER_URL = process.env.FAILPROOFAI_SERVER_URL ?? "https://api.befailproof.ai";
|
|
4085
|
+
});
|
|
4086
|
+
|
|
4087
|
+
// src/auth/logout.ts
|
|
4088
|
+
var exports_logout = {};
|
|
4089
|
+
__export(exports_logout, {
|
|
4090
|
+
whoami: () => whoami,
|
|
4091
|
+
logout: () => logout
|
|
4092
|
+
});
|
|
4093
|
+
async function logout() {
|
|
4094
|
+
const tokens = readTokens();
|
|
4095
|
+
if (!tokens) {
|
|
4096
|
+
console.log("Not logged in.");
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
try {
|
|
4100
|
+
await fetch(`${tokens.server_url}/api/v1/auth/logout`, {
|
|
4101
|
+
method: "POST",
|
|
4102
|
+
headers: { "Content-Type": "application/json" },
|
|
4103
|
+
body: JSON.stringify({ refresh_token: tokens.refresh_token }),
|
|
4104
|
+
signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS)
|
|
4105
|
+
});
|
|
4106
|
+
} catch {}
|
|
4107
|
+
try {
|
|
4108
|
+
stopRelay();
|
|
4109
|
+
} catch {}
|
|
4110
|
+
clearTokens();
|
|
4111
|
+
console.log("Logged out.");
|
|
4112
|
+
}
|
|
4113
|
+
function whoami() {
|
|
4114
|
+
const tokens = readTokens();
|
|
4115
|
+
if (!tokens) {
|
|
4116
|
+
console.log("Not logged in. Run `failproofai login` to authenticate.");
|
|
4117
|
+
process.exit(1);
|
|
4118
|
+
}
|
|
4119
|
+
console.log(`Logged in as ${tokens.user_email}`);
|
|
4120
|
+
console.log(`Server: ${tokens.server_url}`);
|
|
4121
|
+
const expiresIn = tokens.expires_at - Math.floor(Date.now() / 1000);
|
|
4122
|
+
if (expiresIn > 0) {
|
|
4123
|
+
console.log(`Access token expires in ${Math.floor(expiresIn / 60)} minutes`);
|
|
4124
|
+
} else {
|
|
4125
|
+
console.log(`Access token expired (will refresh on next use)`);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
var LOGOUT_TIMEOUT_MS = 3000;
|
|
4129
|
+
var init_logout = __esm(() => {
|
|
4130
|
+
init_token_store();
|
|
4131
|
+
init_pid();
|
|
4132
|
+
});
|
|
4133
|
+
|
|
4134
|
+
// src/relay/pid.ts
|
|
4135
|
+
var exports_pid = {};
|
|
4136
|
+
__export(exports_pid, {
|
|
4137
|
+
writePid: () => writePid2,
|
|
4138
|
+
stopRelay: () => stopRelay2,
|
|
4139
|
+
relayStatus: () => relayStatus,
|
|
4140
|
+
readPid: () => readPid2,
|
|
4141
|
+
isProcessAlive: () => isProcessAlive2,
|
|
4142
|
+
clearPid: () => clearPid2
|
|
4143
|
+
});
|
|
4144
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync11, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8 } from "node:fs";
|
|
4145
|
+
import { join as join9, dirname as dirname5 } from "node:path";
|
|
4146
|
+
import { homedir as homedir12 } from "node:os";
|
|
4147
|
+
function readPid2() {
|
|
4148
|
+
if (!existsSync11(PID_FILE2))
|
|
4149
|
+
return null;
|
|
4150
|
+
try {
|
|
4151
|
+
const raw = readFileSync7(PID_FILE2, "utf8").trim();
|
|
4152
|
+
const pid = parseInt(raw, 10);
|
|
4153
|
+
if (Number.isNaN(pid) || pid <= 0)
|
|
4154
|
+
return null;
|
|
4155
|
+
return pid;
|
|
4156
|
+
} catch {
|
|
4157
|
+
return null;
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
function writePid2(pid) {
|
|
4161
|
+
const dir = dirname5(PID_FILE2);
|
|
4162
|
+
if (!existsSync11(dir))
|
|
4163
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
4164
|
+
writeFileSync6(PID_FILE2, String(pid));
|
|
4165
|
+
}
|
|
4166
|
+
function clearPid2() {
|
|
4167
|
+
if (existsSync11(PID_FILE2))
|
|
4168
|
+
unlinkSync5(PID_FILE2);
|
|
4169
|
+
}
|
|
4170
|
+
function isProcessAlive2(pid) {
|
|
4171
|
+
try {
|
|
4172
|
+
process.kill(pid, 0);
|
|
4173
|
+
return true;
|
|
4174
|
+
} catch (err) {
|
|
4175
|
+
const e = err;
|
|
4176
|
+
if (e?.code === "EPERM")
|
|
4177
|
+
return true;
|
|
4178
|
+
return false;
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
function stopRelay2() {
|
|
4182
|
+
const pid = readPid2();
|
|
4183
|
+
if (pid === null)
|
|
4184
|
+
return false;
|
|
4185
|
+
if (!isProcessAlive2(pid)) {
|
|
4186
|
+
clearPid2();
|
|
4187
|
+
return false;
|
|
4188
|
+
}
|
|
4189
|
+
try {
|
|
4190
|
+
process.kill(pid, "SIGTERM");
|
|
4191
|
+
clearPid2();
|
|
4192
|
+
return true;
|
|
4193
|
+
} catch {
|
|
4194
|
+
return false;
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
function relayStatus() {
|
|
4198
|
+
const pid = readPid2();
|
|
4199
|
+
if (pid === null)
|
|
4200
|
+
return { running: false, pid: null };
|
|
4201
|
+
return { running: isProcessAlive2(pid), pid };
|
|
4202
|
+
}
|
|
4203
|
+
var PID_FILE2;
|
|
4204
|
+
var init_pid2 = __esm(() => {
|
|
4205
|
+
PID_FILE2 = join9(homedir12(), ".failproofai", "relay.pid");
|
|
4206
|
+
});
|
|
4207
|
+
|
|
3177
4208
|
// lib/paths.ts
|
|
3178
|
-
import { homedir as
|
|
3179
|
-
import { join as
|
|
4209
|
+
import { homedir as homedir13 } from "os";
|
|
4210
|
+
import { join as join10 } from "path";
|
|
3180
4211
|
function getDefaultClaudeProjectsPath() {
|
|
3181
|
-
return
|
|
4212
|
+
return join10(homedir13(), ".claude", "projects");
|
|
3182
4213
|
}
|
|
3183
4214
|
var init_paths = () => {};
|
|
3184
4215
|
|
|
@@ -3250,9 +4281,9 @@ var exports_launch = {};
|
|
|
3250
4281
|
__export(exports_launch, {
|
|
3251
4282
|
launch: () => launch
|
|
3252
4283
|
});
|
|
3253
|
-
import { spawn } from "child_process";
|
|
3254
|
-
import { realpathSync, existsSync as
|
|
3255
|
-
import { resolve as resolve7, dirname as
|
|
4284
|
+
import { spawn as spawn4 } from "child_process";
|
|
4285
|
+
import { realpathSync, existsSync as existsSync12 } from "node:fs";
|
|
4286
|
+
import { resolve as resolve7, dirname as dirname6 } from "node:path";
|
|
3256
4287
|
import { fileURLToPath } from "node:url";
|
|
3257
4288
|
function launch(mode) {
|
|
3258
4289
|
const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
@@ -3283,9 +4314,9 @@ function launch(mode) {
|
|
|
3283
4314
|
process.env.PORT = port;
|
|
3284
4315
|
process.env.HOSTNAME = "0.0.0.0";
|
|
3285
4316
|
cmd = "node";
|
|
3286
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve7(
|
|
4317
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve7(dirname6(realpathSync(fileURLToPath(import.meta.url))), "..");
|
|
3287
4318
|
const serverJsPath = resolve7(packageRoot, ".next/standalone/server.js");
|
|
3288
|
-
if (!
|
|
4319
|
+
if (!existsSync12(serverJsPath)) {
|
|
3289
4320
|
console.error(`
|
|
3290
4321
|
Error: Cannot find server.js at:
|
|
3291
4322
|
${serverJsPath}
|
|
@@ -3301,7 +4332,7 @@ Error: Cannot find server.js at:
|
|
|
3301
4332
|
cmd = "bunx";
|
|
3302
4333
|
cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
|
|
3303
4334
|
}
|
|
3304
|
-
const nextProcess =
|
|
4335
|
+
const nextProcess = spawn4(cmd, cmdArgs, {
|
|
3305
4336
|
stdio: "inherit",
|
|
3306
4337
|
env: {
|
|
3307
4338
|
...process.env,
|
|
@@ -3344,17 +4375,17 @@ var init_cli_error2 = __esm(() => {
|
|
|
3344
4375
|
|
|
3345
4376
|
// bin/failproofai.mjs
|
|
3346
4377
|
import { realpathSync as realpathSync2 } from "fs";
|
|
3347
|
-
import { dirname as
|
|
4378
|
+
import { dirname as dirname7, resolve as resolve8 } from "path";
|
|
3348
4379
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3349
4380
|
// package.json
|
|
3350
|
-
var version = "0.0.6-beta.
|
|
4381
|
+
var version = "0.0.6-beta.3";
|
|
3351
4382
|
|
|
3352
4383
|
// bin/failproofai.mjs
|
|
3353
4384
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
3354
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve8(
|
|
4385
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve8(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
3355
4386
|
}
|
|
3356
4387
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
3357
|
-
process.env.FAILPROOFAI_DIST_PATH = resolve8(
|
|
4388
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve8(dirname7(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
|
|
3358
4389
|
}
|
|
3359
4390
|
var args = process.argv.slice(2);
|
|
3360
4391
|
if (args[0] === "p")
|
|
@@ -3376,8 +4407,19 @@ if (hookIdx >= 0) {
|
|
|
3376
4407
|
process.exit(2);
|
|
3377
4408
|
}
|
|
3378
4409
|
}
|
|
4410
|
+
if (args.includes("--relay-daemon")) {
|
|
4411
|
+
try {
|
|
4412
|
+
const { runDaemon: runDaemon3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
|
|
4413
|
+
await runDaemon3();
|
|
4414
|
+
process.exit(0);
|
|
4415
|
+
} catch (err) {
|
|
4416
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4417
|
+
console.error(`Relay daemon error: ${msg}`);
|
|
4418
|
+
process.exit(1);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
3379
4421
|
async function runCli() {
|
|
3380
|
-
const SUBCOMMANDS = ["policies"];
|
|
4422
|
+
const SUBCOMMANDS = ["policies", "login", "logout", "whoami", "relay", "sync"];
|
|
3381
4423
|
if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
|
|
3382
4424
|
const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
|
|
3383
4425
|
if (extraArgs.length > 0) {
|
|
@@ -3408,6 +4450,12 @@ COMMANDS
|
|
|
3408
4450
|
|
|
3409
4451
|
policies --help, -h Show this help for the policies command
|
|
3410
4452
|
|
|
4453
|
+
login Authenticate with the failproofai cloud (Google OAuth)
|
|
4454
|
+
logout Clear local auth tokens and stop relay daemon
|
|
4455
|
+
whoami Print current logged-in user
|
|
4456
|
+
relay start|stop|status Manage the event relay daemon
|
|
4457
|
+
sync One-shot flush of pending events to the server
|
|
4458
|
+
|
|
3411
4459
|
--version, -v Print version and exit
|
|
3412
4460
|
--help, -h Show this help message
|
|
3413
4461
|
|
|
@@ -3552,6 +4600,59 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
3552
4600
|
await listHooks2();
|
|
3553
4601
|
process.exit(0);
|
|
3554
4602
|
}
|
|
4603
|
+
if (args[0] === "login") {
|
|
4604
|
+
const { login: login2 } = await Promise.resolve().then(() => (init_login(), exports_login));
|
|
4605
|
+
await login2();
|
|
4606
|
+
process.exit(0);
|
|
4607
|
+
}
|
|
4608
|
+
if (args[0] === "logout") {
|
|
4609
|
+
const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), exports_logout));
|
|
4610
|
+
await logout2();
|
|
4611
|
+
process.exit(0);
|
|
4612
|
+
}
|
|
4613
|
+
if (args[0] === "whoami") {
|
|
4614
|
+
const { whoami: whoami2 } = await Promise.resolve().then(() => (init_logout(), exports_logout));
|
|
4615
|
+
whoami2();
|
|
4616
|
+
process.exit(0);
|
|
4617
|
+
}
|
|
4618
|
+
if (args[0] === "relay") {
|
|
4619
|
+
const subcmd = args[1];
|
|
4620
|
+
const { relayStatus: relayStatus2, stopRelay: stopRelay3 } = await Promise.resolve().then(() => (init_pid2(), exports_pid));
|
|
4621
|
+
if (subcmd === "status") {
|
|
4622
|
+
const s = relayStatus2();
|
|
4623
|
+
if (s.running)
|
|
4624
|
+
console.log(`Relay daemon running (pid ${s.pid})`);
|
|
4625
|
+
else if (s.pid !== null)
|
|
4626
|
+
console.log(`Stale PID file (${s.pid}); daemon not running`);
|
|
4627
|
+
else
|
|
4628
|
+
console.log("Relay daemon not running");
|
|
4629
|
+
process.exit(0);
|
|
4630
|
+
}
|
|
4631
|
+
if (subcmd === "stop") {
|
|
4632
|
+
const stopped = stopRelay3();
|
|
4633
|
+
console.log(stopped ? "Relay daemon stopped" : "Relay daemon was not running");
|
|
4634
|
+
process.exit(0);
|
|
4635
|
+
}
|
|
4636
|
+
if (subcmd === "start") {
|
|
4637
|
+
const { ensureRelayRunning: ensureRelayRunning3, waitForRelayAlive: waitForRelayAlive3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
|
|
4638
|
+
ensureRelayRunning3();
|
|
4639
|
+
const alive = await waitForRelayAlive3();
|
|
4640
|
+
const s = relayStatus2();
|
|
4641
|
+
if (alive && s.running) {
|
|
4642
|
+
console.log(`Relay daemon started (pid ${s.pid})`);
|
|
4643
|
+
process.exit(0);
|
|
4644
|
+
}
|
|
4645
|
+
console.log("Failed to start daemon");
|
|
4646
|
+
process.exit(1);
|
|
4647
|
+
}
|
|
4648
|
+
throw new CliError3(`Usage: failproofai relay <start|stop|status>`);
|
|
4649
|
+
}
|
|
4650
|
+
if (args[0] === "sync") {
|
|
4651
|
+
const { runOneShotSync: runOneShotSync3 } = await Promise.resolve().then(() => (init_daemon2(), exports_daemon2));
|
|
4652
|
+
const count = await runOneShotSync3();
|
|
4653
|
+
console.log(`Synced ${count} event${count === 1 ? "" : "s"} to server`);
|
|
4654
|
+
process.exit(0);
|
|
4655
|
+
}
|
|
3555
4656
|
const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
|
|
3556
4657
|
const unknownFlag = args.find((a) => a.startsWith("-") && !knownFlags.includes(a));
|
|
3557
4658
|
if (unknownFlag) {
|
|
@@ -3563,7 +4664,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
3563
4664
|
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]);
|
|
3564
4665
|
return dp[m][n];
|
|
3565
4666
|
};
|
|
3566
|
-
const primary = ["--version", "--help", "--hook", "policies"];
|
|
4667
|
+
const primary = ["--version", "--help", "--hook", "policies", "login", "logout", "whoami", "relay", "sync"];
|
|
3567
4668
|
const closest = primary.reduce((best, flag) => {
|
|
3568
4669
|
const dist = levenshtein(unknownFlag, flag);
|
|
3569
4670
|
return dist < best.dist ? { flag, dist } : best;
|
|
@@ -3572,7 +4673,7 @@ Run \`failproofai policies --help\` for usage.`);
|
|
|
3572
4673
|
Did you mean: ${closest.flag}?
|
|
3573
4674
|
Run \`failproofai --help\` for usage details.`);
|
|
3574
4675
|
}
|
|
3575
|
-
const unknownSubcommand = args.find((a) => !a.startsWith("-") && a
|
|
4676
|
+
const unknownSubcommand = args.find((a) => !a.startsWith("-") && !SUBCOMMANDS.includes(a));
|
|
3576
4677
|
if (unknownSubcommand) {
|
|
3577
4678
|
throw new CliError3(`Unknown command: ${unknownSubcommand}
|
|
3578
4679
|
Did you mean: failproofai policies?
|