failproofai 0.0.1-beta.1 → 0.0.1-beta.2

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 (81) 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/server/app/_global-error/page/server-reference-manifest.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  9. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
  19. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  20. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  21. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  22. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/index.html +1 -1
  26. package/.next/standalone/.next/server/app/index.rsc +16 -16
  27. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  29. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  30. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
  31. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  33. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  36. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +1 -1
  49. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0138pgk._.js → [root-of-the-server]__09ienw0._.js} +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0osi8nq._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0nn6lb4._.js → [root-of-the-server]__0uf3j~z._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  63. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  64. package/.next/standalone/.next/server/pages/404.html +2 -2
  65. package/.next/standalone/.next/server/pages/500.html +1 -1
  66. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  68. package/.next/standalone/bin/failproofai.mjs +44 -0
  69. package/.next/standalone/components/reach-developers.tsx +37 -34
  70. package/.next/standalone/docs/cli-reference.md +23 -0
  71. package/.next/standalone/docs/dashboard.md +30 -0
  72. package/.next/standalone/examples/policies-advanced/index.js +2 -45
  73. package/.next/standalone/examples/policies-notification.js +59 -0
  74. package/.next/standalone/next.config.ts +5 -0
  75. package/.next/standalone/package.json +1 -1
  76. package/.next/standalone/scripts/launch.ts +8 -3
  77. package/.next/standalone/scripts/parse-script-args.ts +11 -1
  78. package/bin/failproofai.mjs +44 -0
  79. package/package.json +1 -1
  80. package/scripts/launch.ts +8 -3
  81. package/scripts/parse-script-args.ts +11 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * policies-notification.js — Notification event example
3
+ *
4
+ * Forwards Claude's idle notifications to Slack.
5
+ *
6
+ * Prerequisites:
7
+ * Set the SLACK_WEBHOOK_URL environment variable to your Slack incoming webhook URL.
8
+ *
9
+ * Install:
10
+ * failproofai --install-hooks custom ./examples/policies-notification.js
11
+ *
12
+ * Test by letting Claude finish a task and go idle — you should receive a Slack message.
13
+ */
14
+ import { customPolicies, allow } from "failproofai";
15
+
16
+ // Forward Claude idle notifications to Slack
17
+ customPolicies.add({
18
+ name: "slack-on-idle-notification",
19
+ description: "Forward Claude idle notifications to Slack (set SLACK_WEBHOOK_URL env var)",
20
+ match: { events: ["Notification"] },
21
+ fn: async (ctx) => {
22
+ const webhookUrl = process.env.SLACK_WEBHOOK_URL;
23
+ if (!webhookUrl) return allow(); // skip if not configured
24
+
25
+ const type = String(ctx.payload?.notification_type ?? "");
26
+ if (type !== "idle") return allow(); // only forward idle notifications
27
+
28
+ const message = String(ctx.payload?.message ?? "Claude is waiting for input");
29
+ const cwd = ctx.session?.cwd ?? "unknown";
30
+ const sessionId = ctx.session?.sessionId ?? "unknown";
31
+
32
+ // Fire-and-forget — never block Claude if Slack is unreachable
33
+ fetch(webhookUrl, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({
37
+ blocks: [
38
+ {
39
+ type: "header",
40
+ text: { type: "plain_text", text: "💬 Claude is waiting for you", emoji: true },
41
+ },
42
+ {
43
+ type: "section",
44
+ text: { type: "mrkdwn", text: message },
45
+ },
46
+ {
47
+ type: "section",
48
+ fields: [
49
+ { type: "mrkdwn", text: `*Project*\n\`${cwd}\`` },
50
+ { type: "mrkdwn", text: `*Session*\n\`${sessionId}\`` },
51
+ ],
52
+ },
53
+ ],
54
+ }),
55
+ }).catch(() => {});
56
+
57
+ return allow(); // Notification hooks must always return allow
58
+ },
59
+ });
@@ -1,6 +1,11 @@
1
1
  import type { NextConfig } from "next";
2
2
 
3
+ const allowedDevOrigins = process.env.FAILPROOFAI_ALLOWED_DEV_ORIGINS
4
+ ? process.env.FAILPROOFAI_ALLOWED_DEV_ORIGINS.split(",").map((s) => s.trim()).filter(Boolean)
5
+ : undefined;
6
+
3
7
  const nextConfig: NextConfig = {
8
+ ...(allowedDevOrigins ? { allowedDevOrigins } : {}),
4
9
  output: "standalone",
5
10
  productionBrowserSourceMaps: false,
6
11
  turbopack: {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.2",
4
4
  "description": "Open-source hooks, policies, and project visualization for Claude Code & Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./bin/failproofai.mjs"
@@ -3,13 +3,14 @@
3
3
  */
4
4
  import { getDefaultClaudeProjectsPath } from "../lib/paths";
5
5
  import { spawn } from "child_process";
6
+ import { realpathSync } from "node:fs";
6
7
  import { resolve, dirname } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { parseScriptArgs } from "./parse-script-args";
9
10
  import { version } from "../package.json";
10
11
 
11
12
  export function launch(mode: "dev" | "start"): void {
12
- const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, remainingArgs } = parseScriptArgs(process.argv.slice(2));
13
+ const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
13
14
 
14
15
  console.log(`
15
16
  ______ _ __ ____ ___ ____
@@ -39,8 +40,11 @@ export function launch(mode: "dev" | "start"): void {
39
40
  process.env.PORT = port;
40
41
  process.env.HOSTNAME = "0.0.0.0";
41
42
  cmd = "node";
42
- // Absolute path required so the CLI works from any working directory after install
43
- cmdArgs = [resolve(dirname(fileURLToPath(import.meta.url)), "../.next/standalone/server.js")];
43
+ // Resolve the real package root via realpathSync so symlinked npm global binaries
44
+ // don't cause import.meta.url to point at the symlink dir instead of the package dir.
45
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT
46
+ ?? resolve(dirname(realpathSync(fileURLToPath(import.meta.url))), "..");
47
+ cmdArgs = [resolve(packageRoot, ".next/standalone/server.js")];
44
48
  } else {
45
49
  cmd = "bunx";
46
50
  cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
@@ -53,6 +57,7 @@ export function launch(mode: "dev" | "start"): void {
53
57
  CLAUDE_PROJECTS_PATH: claudeProjectsPath,
54
58
  ...(loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {}),
55
59
  ...(disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {}),
60
+ ...(allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}),
56
61
  },
57
62
  });
58
63
 
@@ -7,6 +7,7 @@ export interface ParsedScriptArgs {
7
7
  claudeProjectsPath: string | undefined;
8
8
  loggingLevel: string | undefined;
9
9
  disableTelemetry: boolean;
10
+ allowedDevOrigins: string[] | undefined;
10
11
  remainingArgs: string[];
11
12
  }
12
13
 
@@ -32,6 +33,7 @@ export function parseScriptArgs(argv: string[]): ParsedScriptArgs {
32
33
  let claudeProjectsPath: string | undefined;
33
34
  let loggingLevel: string | undefined;
34
35
  let disableTelemetry = false;
36
+ let allowedDevOrigins: string[] | undefined;
35
37
 
36
38
  for (let i = 0; i < args.length; i++) {
37
39
  const arg = args[i];
@@ -71,7 +73,15 @@ export function parseScriptArgs(argv: string[]): ParsedScriptArgs {
71
73
  i--;
72
74
  continue;
73
75
  }
76
+
77
+ if (flag === "--allowed-origins") {
78
+ const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
79
+ allowedDevOrigins = value.split(",").map(s => s.trim()).filter(Boolean);
80
+ args.splice(i, spliceCount);
81
+ i--;
82
+ continue;
83
+ }
74
84
  }
75
85
 
76
- return { claudeProjectsPath, loggingLevel, disableTelemetry, remainingArgs: args };
86
+ return { claudeProjectsPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
77
87
  }
@@ -10,8 +10,21 @@
10
10
  * --list-policies List available policies and their status
11
11
  * (default) Launch production dashboard
12
12
  */
13
+ import { realpathSync } from "node:fs";
14
+ import { dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
13
16
  import { version } from "../package.json";
14
17
 
18
+ // Resolve the real package root early (following any npm bin symlinks) so that
19
+ // scripts/launch.ts can locate .next/standalone/server.js correctly regardless
20
+ // of how bun resolves import.meta.url for dynamically-imported modules.
21
+ if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
22
+ process.env.FAILPROOFAI_PACKAGE_ROOT = resolve(
23
+ dirname(realpathSync(fileURLToPath(import.meta.url))),
24
+ ".."
25
+ );
26
+ }
27
+
15
28
  const args = process.argv.slice(2);
16
29
 
17
30
  // --version / -v
@@ -89,6 +102,37 @@ if (args.includes("--list-policies")) {
89
102
  process.exit(0);
90
103
  }
91
104
 
105
+ // Unknown flag guard — must appear after all known-flag branches
106
+ const knownFlags = ["--version", "-v", "--hook", "--install-policies",
107
+ "--remove-policies", "--list-policies"];
108
+ const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
109
+
110
+ if (unknownFlag) {
111
+ function levenshtein(a, b) {
112
+ const m = a.length, n = b.length;
113
+ const dp = Array.from({ length: m + 1 }, (_, i) =>
114
+ Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
115
+ );
116
+ for (let i = 1; i <= m; i++)
117
+ for (let j = 1; j <= n; j++)
118
+ dp[i][j] = a[i - 1] === b[j - 1]
119
+ ? dp[i - 1][j - 1]
120
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
121
+ return dp[m][n];
122
+ }
123
+
124
+ const primary = ["--version", "--hook", "--install-policies",
125
+ "--remove-policies", "--list-policies"];
126
+ const closest = primary.reduce((best, flag) => {
127
+ const dist = levenshtein(unknownFlag, flag);
128
+ return dist < best.dist ? { flag, dist } : best;
129
+ }, { flag: primary[0], dist: Infinity });
130
+
131
+ console.error(`Unknown flag: ${unknownFlag}`);
132
+ console.error(`Did you mean: ${closest.flag}?`);
133
+ process.exit(1);
134
+ }
135
+
92
136
  // Dashboard launch — always production mode
93
137
  const { launch } = await import("../scripts/launch");
94
138
  launch("start");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.2",
4
4
  "description": "Open-source hooks, policies, and project visualization for Claude Code & Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./bin/failproofai.mjs"
package/scripts/launch.ts CHANGED
@@ -3,13 +3,14 @@
3
3
  */
4
4
  import { getDefaultClaudeProjectsPath } from "../lib/paths";
5
5
  import { spawn } from "child_process";
6
+ import { realpathSync } from "node:fs";
6
7
  import { resolve, dirname } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { parseScriptArgs } from "./parse-script-args";
9
10
  import { version } from "../package.json";
10
11
 
11
12
  export function launch(mode: "dev" | "start"): void {
12
- const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, remainingArgs } = parseScriptArgs(process.argv.slice(2));
13
+ const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
13
14
 
14
15
  console.log(`
15
16
  ______ _ __ ____ ___ ____
@@ -39,8 +40,11 @@ export function launch(mode: "dev" | "start"): void {
39
40
  process.env.PORT = port;
40
41
  process.env.HOSTNAME = "0.0.0.0";
41
42
  cmd = "node";
42
- // Absolute path required so the CLI works from any working directory after install
43
- cmdArgs = [resolve(dirname(fileURLToPath(import.meta.url)), "../.next/standalone/server.js")];
43
+ // Resolve the real package root via realpathSync so symlinked npm global binaries
44
+ // don't cause import.meta.url to point at the symlink dir instead of the package dir.
45
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT
46
+ ?? resolve(dirname(realpathSync(fileURLToPath(import.meta.url))), "..");
47
+ cmdArgs = [resolve(packageRoot, ".next/standalone/server.js")];
44
48
  } else {
45
49
  cmd = "bunx";
46
50
  cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
@@ -53,6 +57,7 @@ export function launch(mode: "dev" | "start"): void {
53
57
  CLAUDE_PROJECTS_PATH: claudeProjectsPath,
54
58
  ...(loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {}),
55
59
  ...(disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {}),
60
+ ...(allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}),
56
61
  },
57
62
  });
58
63
 
@@ -7,6 +7,7 @@ export interface ParsedScriptArgs {
7
7
  claudeProjectsPath: string | undefined;
8
8
  loggingLevel: string | undefined;
9
9
  disableTelemetry: boolean;
10
+ allowedDevOrigins: string[] | undefined;
10
11
  remainingArgs: string[];
11
12
  }
12
13
 
@@ -32,6 +33,7 @@ export function parseScriptArgs(argv: string[]): ParsedScriptArgs {
32
33
  let claudeProjectsPath: string | undefined;
33
34
  let loggingLevel: string | undefined;
34
35
  let disableTelemetry = false;
36
+ let allowedDevOrigins: string[] | undefined;
35
37
 
36
38
  for (let i = 0; i < args.length; i++) {
37
39
  const arg = args[i];
@@ -71,7 +73,15 @@ export function parseScriptArgs(argv: string[]): ParsedScriptArgs {
71
73
  i--;
72
74
  continue;
73
75
  }
76
+
77
+ if (flag === "--allowed-origins") {
78
+ const { value, spliceCount } = parseStringFlag(flag, "a comma-separated list of origins", inlineValue, args, i);
79
+ allowedDevOrigins = value.split(",").map(s => s.trim()).filter(Boolean);
80
+ args.splice(i, spliceCount);
81
+ i--;
82
+ continue;
83
+ }
74
84
  }
75
85
 
76
- return { claudeProjectsPath, loggingLevel, disableTelemetry, remainingArgs: args };
86
+ return { claudeProjectsPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs: args };
77
87
  }