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.
Files changed (119) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/index.html +1 -1
  27. package/.next/standalone/.next/server/app/index.rsc +15 -15
  28. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  30. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  31. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  32. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  33. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  34. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  37. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__05akje6._.js → [root-of-the-server]__096k.db._.js} +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0i5kvry._.js → [root-of-the-server]__0kyh86x._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  64. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  65. package/.next/standalone/.next/server/pages/404.html +2 -2
  66. package/.next/standalone/.next/server/pages/500.html +1 -1
  67. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  69. package/.next/standalone/.next/static/chunks/{1052sguyd-.ka.js → 0-dm_9a6nsc2l.js} +1 -1
  70. package/.next/standalone/.next/static/chunks/{05j1px0r8yzh6.js → 01pmw1-asbek~.js} +2 -2
  71. package/.next/standalone/.next/static/chunks/{14cl9poem30dq.js → 051m32nx~n5yr.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/{0badv41uxa56..js → 0a-yctdwn368y.js} +1 -1
  73. package/.next/standalone/.next/static/chunks/{0xpl.oscrakvx.js → 0l-mu4okl-cj1.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{00j0rr7rh8ef8.js → 0mazj-p-~2kc6.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +6 -0
  76. package/.next/standalone/.next/static/chunks/{0npb~873.wvg3.js → 156zca6aewyr-.js} +1 -1
  77. package/.next/standalone/CHANGELOG.md +7 -0
  78. package/.next/standalone/bin/failproofai.mjs +91 -4
  79. package/.next/standalone/dist/cli.mjs +1155 -54
  80. package/.next/standalone/docs/ar/built-in-policies.mdx +118 -118
  81. package/.next/standalone/docs/built-in-policies.mdx +2 -2
  82. package/.next/standalone/docs/de/built-in-policies.mdx +48 -48
  83. package/.next/standalone/docs/es/built-in-policies.mdx +82 -82
  84. package/.next/standalone/docs/fr/built-in-policies.mdx +72 -72
  85. package/.next/standalone/docs/he/built-in-policies.mdx +129 -128
  86. package/.next/standalone/docs/hi/built-in-policies.mdx +178 -182
  87. package/.next/standalone/docs/it/built-in-policies.mdx +64 -64
  88. package/.next/standalone/docs/ja/built-in-policies.mdx +128 -128
  89. package/.next/standalone/docs/ko/built-in-policies.mdx +111 -111
  90. package/.next/standalone/docs/pt-br/built-in-policies.mdx +65 -65
  91. package/.next/standalone/docs/ru/built-in-policies.mdx +72 -72
  92. package/.next/standalone/docs/tr/built-in-policies.mdx +99 -99
  93. package/.next/standalone/docs/vi/built-in-policies.mdx +69 -72
  94. package/.next/standalone/docs/zh/built-in-policies.mdx +76 -78
  95. package/.next/standalone/package.json +1 -1
  96. package/.next/standalone/server.js +1 -1
  97. package/.next/standalone/src/auth/login.ts +104 -0
  98. package/.next/standalone/src/auth/logout.ts +50 -0
  99. package/.next/standalone/src/auth/token-store.ts +64 -0
  100. package/.next/standalone/src/hooks/builtin-policies.ts +22 -20
  101. package/.next/standalone/src/hooks/handler.ts +35 -15
  102. package/.next/standalone/src/relay/daemon.ts +362 -0
  103. package/.next/standalone/src/relay/pid.ts +76 -0
  104. package/.next/standalone/src/relay/queue.ts +225 -0
  105. package/bin/failproofai.mjs +91 -4
  106. package/dist/cli.mjs +1155 -54
  107. package/package.json +1 -1
  108. package/src/auth/login.ts +104 -0
  109. package/src/auth/logout.ts +50 -0
  110. package/src/auth/token-store.ts +64 -0
  111. package/src/hooks/builtin-policies.ts +22 -20
  112. package/src/hooks/handler.ts +35 -15
  113. package/src/relay/daemon.ts +362 -0
  114. package/src/relay/pid.ts +76 -0
  115. package/src/relay/queue.ts +225 -0
  116. package/.next/standalone/.next/static/chunks/0ijk_kek9_wyx.js +0 -6
  117. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_buildManifest.js +0 -0
  118. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_clientMiddlewareManifest.js +0 -0
  119. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -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.2";
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 readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
2644
- import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
2645
- import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
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(homedir6(), ".claude", "settings.json");
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 (!existsSync5(settingsPath)) {
3500
+ if (!existsSync10(settingsPath)) {
2669
3501
  return {};
2670
3502
  }
2671
- const raw = readFileSync3(settingsPath, "utf8");
3503
+ const raw = readFileSync6(settingsPath, "utf8");
2672
3504
  return JSON.parse(raw);
2673
3505
  }
2674
3506
  function writeSettings(settingsPath, settings) {
2675
- mkdirSync4(dirname3(settingsPath), { recursive: true });
2676
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + `
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 (!existsSync5(settingsPath))
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 (!existsSync5(settingsPath)) {
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 (!existsSync5(config.customPoliciesPath)) {
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(homedir6(), ".failproofai", "policies") }
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 homedir7 } from "os";
3179
- import { join as join4 } from "path";
4209
+ import { homedir as homedir13 } from "os";
4210
+ import { join as join10 } from "path";
3180
4211
  function getDefaultClaudeProjectsPath() {
3181
- return join4(homedir7(), ".claude", "projects");
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 existsSync6 } from "node:fs";
3255
- import { resolve as resolve7, dirname as dirname4 } from "node:path";
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(dirname4(realpathSync(fileURLToPath(import.meta.url))), "..");
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 (!existsSync6(serverJsPath)) {
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 = spawn(cmd, cmdArgs, {
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 dirname5, resolve as resolve8 } from "path";
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.2";
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(dirname5(realpathSync2(fileURLToPath2(import.meta.url))), "..");
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(dirname5(realpathSync2(fileURLToPath2(import.meta.url))), "..", "dist");
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 !== "policies");
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?