failproofai 1.0.0-beta.0 → 1.0.0-beta.1

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 (98) 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 +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  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 +9 -9
  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/audit/page/server-reference-manifest.json +2 -2
  27. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/index.html +1 -1
  30. package/.next/standalone/.next/server/app/index.rsc +14 -14
  31. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  33. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  34. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
  35. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  36. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  37. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +9 -9
  40. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +1 -1
  53. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
  54. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  55. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  56. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ig795d._.js +3 -0
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0259h38._.js → [root-of-the-server]__1yo1gyz._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1-upb14.js → node_modules_html-to-image_es_index_14q3-7b.js} +1 -1
  72. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  73. package/.next/standalone/.next/server/pages/404.html +1 -1
  74. package/.next/standalone/.next/server/pages/500.html +1 -1
  75. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/server-reference-manifest.json +11 -11
  77. package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 07ix3lb4gib0x.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0u9hc6oxx-gpj.js → 0fdx7ikab08-g.js} +1 -1
  79. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 191njgoik03fm.js} +1 -1
  80. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 1b3a1scouf40o.js} +1 -1
  81. package/.next/standalone/.next/static/chunks/{3wrgbih-516uj.js → 1d71o1_tav721.js} +1 -1
  82. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 24gs8ip6_spcb.js} +1 -1
  83. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 2ovmv4esqalpb.js} +1 -1
  84. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 30is-6r00sbd7.js} +1 -1
  85. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 35584kn409ad1.js} +1 -1
  86. package/.next/standalone/Cargo.lock +2 -2
  87. package/.next/standalone/Cargo.toml +1 -1
  88. package/.next/standalone/package.json +1 -1
  89. package/.next/standalone/server.js +1 -1
  90. package/dist/cli.mjs +135 -35
  91. package/dist/worker.mjs +1 -1
  92. package/package.json +1 -1
  93. package/src/hooks/configure-wizard.ts +4 -0
  94. package/src/hooks/daemon-service.ts +259 -58
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
  96. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → avJMC2AXvrg13Pbm4QH8F}/_buildManifest.js +0 -0
  97. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → avJMC2AXvrg13Pbm4QH8F}/_clientMiddlewareManifest.js +0 -0
  98. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → avJMC2AXvrg13Pbm4QH8F}/_ssgManifest.js +0 -0
package/dist/worker.mjs CHANGED
@@ -2285,7 +2285,7 @@ var init_builtin_policies = __esm(() => {
2285
2285
  });
2286
2286
 
2287
2287
  // package.json
2288
- var version = "1.0.0-beta.0";
2288
+ var version = "1.0.0-beta.1";
2289
2289
  var init_package = () => {};
2290
2290
 
2291
2291
  // src/posthog-key.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -289,6 +289,10 @@ export function classifyDaemonInstallFailure(reason: string | undefined): string
289
289
  // signal, "disabled" is a deliberate air-gapped opt-out, and a plain fetch
290
290
  // failure is usually a proxy or an offline box — three very different
291
291
  // stories that would otherwise all land in `service_manager_error`.
292
+ // The service is installed system-wide now, so "couldn't become root" is a
293
+ // first-class outcome with a specific remedy (re-run under sudo) rather
294
+ // than an opaque service_manager_error.
295
+ if (/root privileges are required/.test(reason)) return "needs_root";
292
296
  if (/checksum mismatch|has no entry for/.test(reason)) return "checksum_mismatch";
293
297
  if (/downloads are disabled/.test(reason)) return "downloads_disabled";
294
298
  if (/failed to download/.test(reason)) return "download_failed";
@@ -8,8 +8,8 @@
8
8
  * `configure-wizard.ts` calls the functions here directly, the same
9
9
  * relationship it already has with `manager.ts`'s `installHooks()`.
10
10
  */
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
12
- import { homedir } from "node:os";
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, rmSync } from "node:fs";
12
+ import { homedir, tmpdir, userInfo } from "node:os";
13
13
  import { resolve, dirname } from "node:path";
14
14
  import { execFileSync } from "node:child_process";
15
15
  import { hookLogWarn } from "./hook-logger";
@@ -168,22 +168,71 @@ export async function ensureFailproofaidBinary(): Promise<{ path?: string; reaso
168
168
  * here, once, at install time, and handing it to the daemon via the
169
169
  * environment closes that gap entirely.
170
170
  */
171
- function resolveWorkerCommand(): string | null {
171
+ export function resolveWorkerCommand(): string | null {
172
172
  if (process.env.FAILPROOFAI_WORKER_CMD) return process.env.FAILPROOFAI_WORKER_CMD;
173
173
 
174
174
  const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT;
175
175
  if (!packageRoot) return null;
176
176
  const workerScript = resolve(packageRoot, "dist", "worker.mjs");
177
177
  if (!existsSync(workerScript)) return null;
178
- return `node ${workerScript}`;
178
+ // `process.execPath`, not a bare `node`. A system-scope service does not
179
+ // inherit a login environment, so its PATH is the system default — and the
180
+ // single most common way to install Node is nvm, which puts it under
181
+ // ~/.nvm/versions/node/*/bin and nowhere on that PATH. A bare `node` would
182
+ // resolve fine when the wizard runs it and then fail inside the service,
183
+ // silently, on exactly the machines least likely to notice. execPath is
184
+ // whatever runtime is executing this CLI right now (node for the published
185
+ // bin, bun in a source checkout), absolute either way.
186
+ return `${process.execPath} ${workerScript}`;
179
187
  }
180
188
 
181
- function systemdUnitPath(): string {
189
+ /**
190
+ * The account the daemon runs as. The service is root-*installed* but never
191
+ * root-*run*: everything it touches (the socket, the lock, the policy config)
192
+ * lives in one user's home and is peer-checked against that user's uid.
193
+ */
194
+ function serviceUser(): string {
195
+ return userInfo().username;
196
+ }
197
+
198
+ /**
199
+ * `/etc/systemd/system/failproofaid@<user>.service`.
200
+ *
201
+ * The `@<user>` suffix is systemd's convention for a per-user instance, but
202
+ * this is a concrete unit file rather than an instance of a template: every
203
+ * field that matters is user-specific (the ExecStart path is under the user's
204
+ * own ~/.failproofai/bin, so is HOME, so is the worker command), so a shared
205
+ * template would need a per-instance drop-in for all of them and buy nothing.
206
+ * Naming it per-user is what keeps a second user's install from silently
207
+ * stealing the first's unit — a single `failproofaid.service` would.
208
+ */
209
+ function systemdUnitName(user: string = serviceUser()): string {
210
+ return `failproofaid@${user}.service`;
211
+ }
212
+
213
+ function systemdUnitPath(user: string = serviceUser()): string {
214
+ return resolve("/etc/systemd/system", systemdUnitName(user));
215
+ }
216
+
217
+ /**
218
+ * The pre-1.0.0-beta.1 user-scope unit. Still removed on install and
219
+ * uninstall: it holds the same flock the new service needs, so leaving one
220
+ * behind means the system unit starts, loses the singleton race, and the
221
+ * machine sits fail-closed against a daemon that never came up.
222
+ */
223
+ function legacySystemdUserUnitPath(): string {
182
224
  return resolve(homedir(), ".config", "systemd", "user", "failproofaid.service");
183
225
  }
184
226
 
227
+ const LAUNCHD_LABEL = "ai.failproof.failproofaid";
228
+
185
229
  function launchdPlistPath(): string {
186
- return resolve(homedir(), "Library", "LaunchAgents", "ai.failproof.failproofaid.plist");
230
+ return `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
231
+ }
232
+
233
+ /** The pre-1.0.0-beta.1 LaunchAgent — same migration problem as above. */
234
+ function legacyLaunchAgentPlistPath(): string {
235
+ return resolve(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
187
236
  }
188
237
 
189
238
  /**
@@ -197,23 +246,129 @@ export function daemonServiceFilePath(): string | null {
197
246
  return process.platform === "linux" ? systemdUnitPath() : launchdPlistPath();
198
247
  }
199
248
 
200
- function systemdUnitContents(binaryPath: string, workerCmd: string | null): string {
201
- // Quoted because FAILPROOFAI_WORKER_CMD's value ("node /abs/path/worker.mjs")
202
- // contains a space — systemd's Environment= requires quoting whenever the
203
- // value does.
249
+ /**
250
+ * The command a user runs to inspect their own daemon — surfaced so the
251
+ * wizard can print it instead of leaving people to discover which service
252
+ * manager, and which scope, is involved.
253
+ */
254
+ export function daemonStatusCommand(): string | null {
255
+ if (!isDaemonSupportedPlatform()) return null;
256
+ return process.platform === "linux"
257
+ ? `systemctl status ${systemdUnitName()}`
258
+ : `sudo launchctl print system/${LAUNCHD_LABEL}`;
259
+ }
260
+
261
+ /** True when privileged commands can run without prompting for a password. */
262
+ function canElevate(): boolean {
263
+ if (typeof process.getuid === "function" && process.getuid() === 0) return true;
264
+ try {
265
+ execFileSync("sudo", ["-n", "true"], { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
266
+ return true;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Runs one privileged command, non-interactively.
274
+ *
275
+ * `sudo -n` on purpose: the wizard owns the terminal, and a sudo password
276
+ * prompt fired from underneath a TUI is unreadable at best. A machine that
277
+ * cannot elevate silently gets a clear reason and the manual commands
278
+ * instead — it keeps working exactly as it did, on the in-process path.
279
+ */
280
+ function runPrivileged(command: string, args: string[]): void {
281
+ const root = typeof process.getuid === "function" && process.getuid() === 0;
282
+ const [cmd, argv] = root ? [command, args] : ["sudo", ["-n", command, ...args]];
283
+ execFileSync(cmd, argv, { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
284
+ }
285
+
286
+ /**
287
+ * Installs a file into a root-owned location via a temp file, because the
288
+ * caller is not root and cannot write there directly. `install -m` sets the
289
+ * mode in the same step, so the file is never briefly world-writable.
290
+ */
291
+ function writePrivilegedFile(destination: string, contents: string, mode = "0644"): void {
292
+ const staging = resolve(tmpdir(), `failproofaid-${process.pid}-${Date.now()}.tmp`);
293
+ try {
294
+ writeFileSync(staging, contents, "utf8");
295
+ runPrivileged("install", ["-m", mode, staging, destination]);
296
+ } finally {
297
+ rmSync(staging, { force: true });
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Removes a daemon installed by an earlier version into the user's own
303
+ * session scope. Best-effort and never privileged — these paths are all
304
+ * inside the user's home.
305
+ */
306
+ function removeLegacyUserService(): void {
307
+ try {
308
+ if (process.platform === "linux") {
309
+ const legacy = legacySystemdUserUnitPath();
310
+ if (!existsSync(legacy)) return;
311
+ try {
312
+ execFileSync("systemctl", ["--user", "disable", "--now", "failproofaid.service"], {
313
+ stdio: "ignore",
314
+ timeout: SERVICE_CMD_TIMEOUT_MS,
315
+ });
316
+ } catch {
317
+ // Not loaded — removing the file is still the point.
318
+ }
319
+ unlinkSync(legacy);
320
+ try {
321
+ execFileSync("systemctl", ["--user", "daemon-reload"], {
322
+ stdio: "ignore",
323
+ timeout: SERVICE_CMD_TIMEOUT_MS,
324
+ });
325
+ } catch {
326
+ /* best-effort */
327
+ }
328
+ } else {
329
+ const legacy = legacyLaunchAgentPlistPath();
330
+ if (!existsSync(legacy)) return;
331
+ try {
332
+ execFileSync("launchctl", ["unload", "-w", legacy], {
333
+ stdio: "ignore",
334
+ timeout: SERVICE_CMD_TIMEOUT_MS,
335
+ });
336
+ } catch {
337
+ /* not loaded */
338
+ }
339
+ unlinkSync(legacy);
340
+ }
341
+ } catch (err) {
342
+ hookLogWarn(`could not remove the legacy user-scope daemon: ${err instanceof Error ? err.message : String(err)}`);
343
+ }
344
+ }
345
+
346
+ export function systemdUnitContents(binaryPath: string, workerCmd: string | null): string {
347
+ // Quoted because both values contain a space or a path — systemd's
348
+ // Environment= requires quoting whenever the value does.
204
349
  const envLine = workerCmd ? `Environment="FAILPROOFAI_WORKER_CMD=${workerCmd}"\n` : "";
350
+ const user = serviceUser();
205
351
  return `[Unit]
206
- Description=failproofai background daemon (failproofaid)
352
+ Description=failproofai background daemon (failproofaid) for ${user}
207
353
  After=network.target
208
354
 
209
355
  [Service]
210
356
  Type=simple
357
+ User=${user}
358
+ # Set explicitly rather than relying on systemd deriving it from User=:
359
+ # failproofaid is user-scope by construction and refuses to start without
360
+ # HOME ("HOME is not set; failproofaid is user-scope only"), so the one
361
+ # variable it cannot do without is not left to a version-dependent default.
362
+ Environment="HOME=${homedir()}"
211
363
  ${envLine}ExecStart=${binaryPath}
212
364
  Restart=on-failure
213
365
  RestartSec=2
214
366
 
215
367
  [Install]
216
- WantedBy=default.target
368
+ # multi-user.target, not default.target: this is the whole point of a
369
+ # system unit — it starts at boot, with no login and no lingering, and
370
+ # keeps running after the installing user logs out.
371
+ WantedBy=multi-user.target
217
372
  `;
218
373
  }
219
374
 
@@ -235,7 +390,7 @@ function launchdPlistContents(binaryPath: string, logDir: string, workerCmd: str
235
390
  <plist version="1.0">
236
391
  <dict>
237
392
  <key>Label</key>
238
- <string>ai.failproof.failproofaid</string>
393
+ <string>${LAUNCHD_LABEL}</string>
239
394
  <key>ProgramArguments</key>
240
395
  <array>
241
396
  <string>${escapeXml(binaryPath)}</string>
@@ -244,6 +399,12 @@ function launchdPlistContents(binaryPath: string, logDir: string, workerCmd: str
244
399
  <true/>
245
400
  <key>KeepAlive</key>
246
401
  <true/>
402
+ <!-- A LaunchDaemon runs as root unless told otherwise. This is the
403
+ launchd half of the systemd unit's User=: loaded at boot by the
404
+ system, executed as the one user whose home, socket and lock it
405
+ is allowed to touch. -->
406
+ <key>UserName</key>
407
+ <string>${escapeXml(serviceUser())}</string>
247
408
  ${envBlock} <key>StandardOutPath</key>
248
409
  <string>${escapeXml(resolve(logDir, "failproofaid.log"))}</string>
249
410
  <key>StandardErrorPath</key>
@@ -258,6 +419,25 @@ export interface DaemonInstallResult {
258
419
  reason?: string;
259
420
  }
260
421
 
422
+ /**
423
+ * The privileged commands an install performs — returned verbatim so a
424
+ * machine that cannot elevate can be told exactly what to run rather than
425
+ * just that something failed.
426
+ */
427
+ function daemonInstallCommands(binaryPath: string, workerCmd: string | null): string[] {
428
+ if (process.platform === "linux") {
429
+ return [
430
+ `sudo tee ${systemdUnitPath()} <<'EOF'\n${systemdUnitContents(binaryPath, workerCmd)}EOF`,
431
+ "sudo systemctl daemon-reload",
432
+ `sudo systemctl enable --now ${systemdUnitName()}`,
433
+ ];
434
+ }
435
+ return [
436
+ `sudo tee ${launchdPlistPath()} < the plist failproofai config would write`,
437
+ `sudo launchctl load -w ${launchdPlistPath()}`,
438
+ ];
439
+ }
440
+
261
441
  /**
262
442
  * Writes and enables the service unit, starting it immediately. Safe to
263
443
  * call repeatedly — re-running replaces the unit file (picking up a
@@ -284,40 +464,43 @@ export async function installDaemonService(): Promise<DaemonInstallResult> {
284
464
  // worker regardless of what cwd the service manager starts it from.
285
465
  const workerCmd = resolveWorkerCommand();
286
466
 
467
+ // The service is installed system-wide, which needs root. Check before
468
+ // writing anything, so a machine that cannot elevate gets the exact
469
+ // commands to run instead of a half-installed service.
470
+ if (!canElevate()) {
471
+ return {
472
+ installed: false,
473
+ reason:
474
+ "root privileges are required to install the failproofaid system service, and sudo is not available without a password. " +
475
+ `Run: sudo failproofai config (or install manually: ${daemonInstallCommands(binaryPath, workerCmd).join(" && ")})`,
476
+ };
477
+ }
478
+
479
+ // A daemon left over from a pre-1.0.0-beta.1 install holds the same
480
+ // singleton lock the new one needs, so the system unit would start, lose
481
+ // the flock race, and leave the machine fail-closed against a daemon that
482
+ // never came up. Clear it first, every time.
483
+ removeLegacyUserService();
484
+
287
485
  try {
288
486
  if (process.platform === "linux") {
289
- const unitPath = systemdUnitPath();
290
- mkdirSync(dirname(unitPath), { recursive: true });
291
- writeFileSync(unitPath, systemdUnitContents(binaryPath, workerCmd), "utf8");
292
- execFileSync("systemctl", ["--user", "daemon-reload"], {
293
- stdio: "ignore",
294
- timeout: SERVICE_CMD_TIMEOUT_MS,
295
- });
296
- execFileSync("systemctl", ["--user", "enable", "--now", "failproofaid.service"], {
297
- stdio: "ignore",
298
- timeout: SERVICE_CMD_TIMEOUT_MS,
299
- });
487
+ writePrivilegedFile(systemdUnitPath(), systemdUnitContents(binaryPath, workerCmd));
488
+ runPrivileged("systemctl", ["daemon-reload"]);
489
+ runPrivileged("systemctl", ["enable", "--now", systemdUnitName()]);
300
490
  } else {
301
491
  const plistPath = launchdPlistPath();
302
492
  const logDir = resolve(homedir(), ".failproofai", "logs");
303
- mkdirSync(dirname(plistPath), { recursive: true });
304
493
  mkdirSync(logDir, { recursive: true });
305
- writeFileSync(plistPath, launchdPlistContents(binaryPath, logDir, workerCmd), "utf8");
306
494
  // Unload any previously-loaded copy first — reloading with a changed
307
495
  // binary path (e.g. after an upgrade) is a no-op under plain `load`
308
496
  // if launchd thinks the label is already loaded.
309
497
  try {
310
- execFileSync("launchctl", ["unload", plistPath], {
311
- stdio: "ignore",
312
- timeout: SERVICE_CMD_TIMEOUT_MS,
313
- });
498
+ runPrivileged("launchctl", ["unload", plistPath]);
314
499
  } catch {
315
500
  // Wasn't loaded — fine, this is the common case on a fresh install.
316
501
  }
317
- execFileSync("launchctl", ["load", "-w", plistPath], {
318
- stdio: "ignore",
319
- timeout: SERVICE_CMD_TIMEOUT_MS,
320
- });
502
+ writePrivilegedFile(plistPath, launchdPlistContents(binaryPath, logDir, workerCmd));
503
+ runPrivileged("launchctl", ["load", "-w", plistPath]);
321
504
  }
322
505
  } catch (err) {
323
506
  const msg = err instanceof Error ? err.message : String(err);
@@ -370,40 +553,44 @@ async function waitForDaemonRunning(): Promise<boolean> {
370
553
  export async function uninstallDaemonService(): Promise<void> {
371
554
  setDaemonConfigured(false);
372
555
  if (!isDaemonSupportedPlatform()) return;
556
+
557
+ // Always attempted, and never privileged: a legacy user-scope daemon is
558
+ // the one thing this can still clean up on a machine that cannot elevate.
559
+ removeLegacyUserService();
560
+
373
561
  try {
374
562
  if (process.platform === "linux") {
563
+ const unitPath = systemdUnitPath();
564
+ if (!existsSync(unitPath)) return;
375
565
  try {
376
- execFileSync("systemctl", ["--user", "disable", "--now", "failproofaid.service"], {
377
- stdio: "ignore",
378
- timeout: SERVICE_CMD_TIMEOUT_MS,
379
- });
566
+ runPrivileged("systemctl", ["disable", "--now", systemdUnitName()]);
380
567
  } catch {
381
- // Already stopped/not installed — fine.
568
+ // Already stopped/not enabled — removing the unit is still the point.
382
569
  }
383
- const unitPath = systemdUnitPath();
384
- if (existsSync(unitPath)) unlinkSync(unitPath);
570
+ runPrivileged("rm", ["-f", unitPath]);
385
571
  try {
386
- execFileSync("systemctl", ["--user", "daemon-reload"], {
387
- stdio: "ignore",
388
- timeout: SERVICE_CMD_TIMEOUT_MS,
389
- });
572
+ runPrivileged("systemctl", ["daemon-reload"]);
390
573
  } catch {
391
574
  // Best-effort.
392
575
  }
393
576
  } else {
394
577
  const plistPath = launchdPlistPath();
578
+ if (!existsSync(plistPath)) return;
395
579
  try {
396
- execFileSync("launchctl", ["unload", "-w", plistPath], {
397
- stdio: "ignore",
398
- timeout: SERVICE_CMD_TIMEOUT_MS,
399
- });
580
+ runPrivileged("launchctl", ["unload", "-w", plistPath]);
400
581
  } catch {
401
- // Already unloaded/not installed — fine.
582
+ // Already unloaded — fine.
402
583
  }
403
- if (existsSync(plistPath)) unlinkSync(plistPath);
584
+ runPrivileged("rm", ["-f", plistPath]);
404
585
  }
405
586
  } catch (err) {
406
- hookLogWarn(`daemon service uninstall failed: ${err instanceof Error ? err.message : String(err)}`);
587
+ // `daemonConfigured` is already cleared above, so a machine that cannot
588
+ // elevate is back on the in-process path even though the unit file
589
+ // survives — it fails open, not closed.
590
+ hookLogWarn(
591
+ `daemon service uninstall failed (the service may need removing by hand: ${daemonStatusCommand()}): ` +
592
+ `${err instanceof Error ? err.message : String(err)}`,
593
+ );
407
594
  }
408
595
  }
409
596
 
@@ -418,7 +605,9 @@ export function daemonServiceStatus(): DaemonServiceStatus {
418
605
  if (process.platform === "linux") {
419
606
  if (!existsSync(systemdUnitPath())) return "not-installed";
420
607
  try {
421
- const out = execFileSync("systemctl", ["--user", "is-active", "failproofaid.service"], {
608
+ // Unprivileged on purpose: reading a system unit's state needs no
609
+ // root, so status works for the owning user with no sudo at all.
610
+ const out = execFileSync("systemctl", ["is-active", systemdUnitName()], {
422
611
  stdio: ["ignore", "pipe", "ignore"],
423
612
  timeout: SERVICE_CMD_TIMEOUT_MS,
424
613
  })
@@ -438,11 +627,23 @@ export function daemonServiceStatus(): DaemonServiceStatus {
438
627
  const plistPath = launchdPlistPath();
439
628
  if (!existsSync(plistPath)) return "not-installed";
440
629
  try {
441
- const out = execFileSync("launchctl", ["list"], {
442
- stdio: ["ignore", "pipe", "ignore"],
443
- timeout: SERVICE_CMD_TIMEOUT_MS,
444
- }).toString();
445
- return out.includes("ai.failproof.failproofaid") ? "running" : "stopped";
630
+ // A LaunchDaemon lives in launchd's system domain, which an unprivileged
631
+ // `launchctl list` cannot see — unlike systemd, reading the state needs
632
+ // the same elevation installing it did.
633
+ const root = typeof process.getuid === "function" && process.getuid() === 0;
634
+ const args = ["print", `system/${LAUNCHD_LABEL}`];
635
+ const out = root
636
+ ? execFileSync("launchctl", args, {
637
+ stdio: ["ignore", "pipe", "ignore"],
638
+ timeout: SERVICE_CMD_TIMEOUT_MS,
639
+ }).toString()
640
+ : execFileSync("sudo", ["-n", "launchctl", ...args], {
641
+ stdio: ["ignore", "pipe", "ignore"],
642
+ timeout: SERVICE_CMD_TIMEOUT_MS,
643
+ }).toString();
644
+ // `state = running` is launchd's own wording; a loaded-but-dead job
645
+ // prints `state = not running` and must not read as healthy.
646
+ return /state\s*=\s*running/.test(out) ? "running" : "stopped";
446
647
  } catch {
447
648
  return "stopped";
448
649
  }
@@ -1,3 +0,0 @@
1
- module.exports=[18622,(a,b,c)=>{b.exports=a.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},56704,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},20635,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/action-async-storage.external.js",()=>require("next/dist/server/app-render/action-async-storage.external.js"))},24725,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/after-task-async-storage.external.js",()=>require("next/dist/server/app-render/after-task-async-storage.external.js"))},43285,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/dynamic-access-async-storage.external.js",()=>require("next/dist/server/app-render/dynamic-access-async-storage.external.js"))},42602,(a,b,c)=>{"use strict";b.exports=a.r(18622)},87924,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactJsxRuntime},72131,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].React},38783,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactServerDOMTurbopackClient},9270,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.AppRouterContext},36313,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.HooksClientContext},18341,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.ServerInsertedHtml},35112,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactDOM},5607,a=>{"use strict";a.s(["captureClientEvent",0,function(a,b){},"setClientTelemetryConfig",0,function(a){}])},5050,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={callServer:function(){return f.callServer},createServerReference:function(){return h.createServerReference},findSourceMapURL:function(){return g.findSourceMapURL}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(20611),g=a.r(1722),h=a.r(38783)},2933,a=>{"use strict";var b=a.i(5050);let c=(0,b.createServerReference)("00eb0d1896aceb50136245d3f32f87ed0485ac965b",b.callServer,void 0,b.findSourceMapURL,"getTelemetryConfig");a.s(["getTelemetryConfig",0,c])},10765,a=>{"use strict";var b=a.i(87924),c=a.i(72131),d=a.i(50944),e=a.i(2933),f=a.i(5607);let g=(0,c.createContext)(void 0);a.s(["PostHogProvider",0,function({children:a}){let h=(0,d.usePathname)(),i=(0,c.useRef)(null),j=(0,c.useRef)(!1);(0,c.useEffect)(()=>{let a=!1;return(0,e.getTelemetryConfig)().then(b=>{a||((0,f.setClientTelemetryConfig)(b),j.current=!0,(0,f.captureClientEvent)("$pageview"),i.current=window.location.pathname)}),()=>{a=!0}},[]),(0,c.useEffect)(()=>{j.current&&i.current!==h&&(i.current=h,(0,f.captureClientEvent)("$pageview"))},[h]);let k=c.default.useCallback((a,b)=>{(0,f.captureClientEvent)(a,b)},[]);return(0,b.jsx)(g.Provider,{value:{capture:k},children:a})},"usePostHog",0,function(){let a=(0,c.useContext)(g);if(void 0===a)throw Error("usePostHog must be used within a PostHogProvider");return a}])},18952,a=>{"use strict";var b=a.i(87924),c=a.i(72131);let d=[],e=0,f=new Set;function g(){for(let a of f)a()}function h(a){return f.add(a),()=>f.delete(a)}function i(){return d}function j({message:a}){let[d,e]=(0,c.useState)(!1);return(0,c.useEffect)(()=>{requestAnimationFrame(()=>e(!0));let a=setTimeout(()=>e(!1),2e3);return()=>clearTimeout(a)},[]),(0,b.jsx)("div",{className:`bg-card border border-border text-foreground text-sm px-4 py-2 rounded-lg shadow-lg transition-opacity duration-300 ${d?"opacity-100":"opacity-0"}`,children:a})}a.s(["Toaster",0,function(){let a=(0,c.useSyncExternalStore)(h,i,i);return(0,b.jsx)("div",{className:"fixed bottom-4 right-4 z-50 flex flex-col gap-2",children:a.map(a=>(0,b.jsx)(j,{message:a.message},a.id))})},"toast",0,function(a){let b=e++;d=[...d,{id:b,message:a}],g(),setTimeout(()=>{d=d.filter(a=>a.id!==b),g()},2500)}])},46058,(a,b,c)=>{"use strict";function d(a){if("function"!=typeof WeakMap)return null;var b=new WeakMap,c=new WeakMap;return(d=function(a){return a?c:b})(a)}c._=function(a,b){if(!b&&a&&a.__esModule)return a;if(null===a||"object"!=typeof a&&"function"!=typeof a)return{default:a};var c=d(b);if(c&&c.has(a))return c.get(a);var e={__proto__:null},f=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var g in a)if("default"!==g&&Object.prototype.hasOwnProperty.call(a,g)){var h=f?Object.getOwnPropertyDescriptor(a,g):null;h&&(h.get||h.set)?Object.defineProperty(e,g,h):e[g]=a[g]}return e.default=a,c&&c.set(a,e),e}},88347,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d,e,f={ACTION_HMR_REFRESH:function(){return l},ACTION_NAVIGATE:function(){return i},ACTION_REFRESH:function(){return h},ACTION_RESTORE:function(){return j},ACTION_SERVER_ACTION:function(){return m},ACTION_SERVER_PATCH:function(){return k},PrefetchKind:function(){return n},ScrollBehavior:function(){return o}};for(var g in f)Object.defineProperty(c,g,{enumerable:!0,get:f[g]});let h="refresh",i="navigate",j="restore",k="server-patch",l="hmr-refresh",m="server-action";var n=((d={}).AUTO="auto",d.FULL="full",d),o=((e={})[e.Default=0]="Default",e[e.NoScroll=1]="NoScroll",e);("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},67009,(a,b,c)=>{"use strict";function d(a){return null!==a&&"object"==typeof a&&"then"in a&&"function"==typeof a.then}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"isThenable",{enumerable:!0,get:function(){return d}})},90841,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={dispatchAppRouterAction:function(){return i},dispatchGestureState:function(){return j},refreshOnInstantNavigationUnlock:function(){return h},useActionQueue:function(){return k}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(46058)._(a.r(72131)),g=a.r(67009);a.r(88347);function h(){}function i(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function j(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function k(a){let[b,c]=f.default.useState(a.state),[d,e]=(0,f.useOptimistic)(b),h=(0,f.useMemo)(()=>d,[d]);return(0,g.isThenable)(h)?(0,f.use)(h):h}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},20611,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"callServer",{enumerable:!0,get:function(){return g}});let d=a.r(72131),e=a.r(88347),f=a.r(90841);async function g(a,b){return new Promise((c,g)=>{(0,d.startTransition)(()=>{(0,f.dispatchAppRouterAction)({type:e.ACTION_SERVER_ACTION,actionId:a,actionArgs:b,resolve:c,reject:g})})})}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},1722,(a,b,c)=>{"use strict";let d;Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"findSourceMapURL",{enumerable:!0,get:function(){return d}});("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},39118,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={DEFAULT_SEGMENT_KEY:function(){return l},NOT_FOUND_SEGMENT_KEY:function(){return m},PAGE_SEGMENT_KEY:function(){return k},addSearchParamsIfPageSegment:function(){return i},computeSelectedLayoutSegment:function(){return j},getSegmentValue:function(){return f},getSelectedLayoutSegmentPath:function(){return function a(b,c,d=!0,e=[]){let g;if(d)g=b[1][c];else{let a=b[1];g=a.children??Object.values(a)[0]}if(!g)return e;let h=f(g[0]);return!h||h.startsWith(k)?e:(e.push(h),a(g,c,!1,e))}},isGroupSegment:function(){return g},isParallelRouteSegment:function(){return h}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});function f(a){return Array.isArray(a)?a[1]:a}function g(a){return"("===a[0]&&a.endsWith(")")}function h(a){return a.startsWith("@")&&"@children"!==a}function i(a,b){if(a.includes(k)){let a=JSON.stringify(b);return"{}"!==a?k+"?"+a:k}return a}function j(a,b){if(!a||0===a.length)return null;let c="children"===b?a[0]:a[a.length-1];return c===l?null:c}let k="__PAGE__",l="__DEFAULT__",m="/_not-found"},88644,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"InvariantError",{enumerable:!0,get:function(){return d}});class d extends Error{constructor(a,b){super(`Invariant: ${a.endsWith(".")?a:a+"."} This is a bug in Next.js.`,b),this.name="InvariantError"}}},54427,(a,b,c)=>{"use strict";function d(){let a,b,c=new Promise((c,d)=>{a=c,b=d});return{resolve:a,reject:b,promise:c}}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"createPromiseWithResolvers",{enumerable:!0,get:function(){return d}})},41997,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={BailoutToCSRError:function(){return g},isBailoutToCSRError:function(){return h}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f="BAILOUT_TO_CLIENT_SIDE_RENDERING";class g extends Error{constructor(a){super(`Bail out to client-side rendering: ${a}`),this.reason=a,this.digest=f}}function h(a){return"object"==typeof a&&null!==a&&"digest"in a&&a.digest===f}},89027,a=>{"use strict";let b=(0,a.i(64831).default)("chevron-down",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);a.s(["ChevronDown",0,b],89027)},6236,a=>{"use strict";var b=a.i(87924),c=a.i(72131);let d=(0,c.createContext)(void 0);a.s(["AutoRefreshProvider",0,function({children:a}){let[e,f]=(0,c.useState)(0);return(0,b.jsx)(d.Provider,{value:{intervalSec:e,setIntervalSec:f},children:a})},"useAutoRefresh",0,function(){let a=(0,c.useContext)(d);if(!a)throw Error("useAutoRefresh must be used within AutoRefreshProvider");return a}])},40695,a=>{"use strict";var b=a.i(87924),c=a.i(72131),d=a.i(97895);let e=c.forwardRef(({className:a,variant:c="default",size:e="default",...f},g)=>(0,b.jsx)("button",{className:(0,d.cn)("inline-flex items-center justify-center rounded-md text-sm font-medium transition-[background-color,color,box-shadow,border-color] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:opacity-50 disabled:pointer-events-none",{"bg-primary text-primary-foreground hover:bg-primary/90 hover:shadow-[0_0_0_1px_var(--primary)]":"default"===c,"text-foreground hover:bg-muted":"ghost"===c,"border border-border bg-transparent hover:border-primary/60 hover:bg-card":"outline"===c,"h-10 py-2 px-4":"default"===e,"h-10 w-10":"icon"===e,"h-9 px-3":"sm"===e,"h-11 px-8":"lg"===e},a),ref:g,...f}));e.displayName="Button",a.s(["Button",0,e])},45550,a=>{"use strict";var b=a.i(72131),c=a.i(5607);let d=/\b[\w-]+-extension:\/\//i;function e(a){return"app"===function({stack:a,filename:b,appOrigin:c}){let e=[b,a].filter(Boolean).join("\n");return e.trim()?d.test(e)?"extension":c&&e.includes(c)?"app":"unknown":"unknown"}(a)}a.s(["GlobalErrorListeners",0,function(){return(0,b.useEffect)(()=>{let a=a=>{e({stack:a.error?.stack,filename:a.filename,appOrigin:window.location.origin})&&(0,c.captureClientEvent)("unhandled_exception",{error_message:a.message,error_name:a.error?.name,error_filename:a.filename,error_lineno:a.lineno,error_colno:a.colno})},b=a=>{let b=a.reason;e({stack:b instanceof Error?b.stack:void 0,appOrigin:window.location.origin})&&(0,c.captureClientEvent)("unhandled_rejection",{error_message:b instanceof Error?b.message:String(b),error_name:b instanceof Error?b.name:void 0})};return window.addEventListener("error",a),window.addEventListener("unhandledrejection",b),()=>{window.removeEventListener("error",a),window.removeEventListener("unhandledrejection",b)}},[]),null}],45550)},10815,a=>{"use strict";var b=a.i(87924),c=a.i(72131),d=a.i(38246),e=a.i(50944),f=a.i(64831);let g=(0,f.default)("git-branch",[["path",{d:"M15 6a9 9 0 0 0-9 9V3",key:"1cii5b"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}]]);var h=a.i(89027);let i=(0,f.default)("star",[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",key:"r04s7s"}]]),j=(0,f.default)("book-open",[["path",{d:"M12 5v16",key:"1f6ucr"}],["path",{d:"M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z",key:"1fyvmf"}]]),k=(0,f.default)("message-circle",[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",key:"1sd12s"}]]),l=(0,f.default)("message-square-warning",[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z",key:"18887p"}],["path",{d:"M12 15h.01",key:"q59x07"}],["path",{d:"M12 7v4",key:"xawao1"}]]);var m=a.i(40695);let n="failproofai@exosphere.host",o=[{label:"Star us on GitHub",icon:i,href:"https://github.com/failproofai/failproofai",color:"#f5c842",bg:"rgba(245,200,66,0.08)"},{label:"Documentation",icon:j,href:"https://docs.befailproof.ai/introduction",color:"#60a5fa",bg:"rgba(96,165,250,0.08)"},{label:"Join our Discord",icon:k,href:"https://discord.befailproof.ai/",color:"#5865f2",bg:"rgba(88,101,242,0.08)"},{label:"Feedback & Issues",icon:l,href:"https://github.com/FailproofAI/failproofai/issues/new/choose",color:"#34d399",bg:"rgba(52,211,153,0.08)"}],p=()=>{let[a,d]=(0,c.useState)(!1),e=(0,c.useCallback)(()=>d(!1),[]),f=(0,c.useCallback)(a=>{"Escape"===a.key&&d(!1)},[]);return(0,b.jsxs)("div",{className:"relative",onKeyDown:f,children:[a&&(0,b.jsx)("div",{className:"fixed inset-0 z-40",onClick:e,"aria-hidden":"true"}),(0,b.jsxs)(m.Button,{type:"button",variant:"ghost",size:"sm",onClick:()=>d(a=>!a),"aria-expanded":a,"aria-haspopup":"true",className:"relative z-50 flex items-center gap-1.5 text-muted-foreground hover:text-foreground",children:[(0,b.jsx)(g,{className:"h-4 w-4"}),(0,b.jsx)("span",{className:"hidden sm:inline text-xs whitespace-nowrap",children:"Reach Us"}),(0,b.jsx)(h.ChevronDown,{className:`h-3 w-3 transition-transform ${a?"rotate-180":""}`})]}),a&&(0,b.jsxs)("div",{className:"absolute right-0 mt-2 w-60 rounded-lg border border-border bg-card shadow-xl z-50",role:"menu",style:{boxShadow:"0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.04)"},children:[(0,b.jsxs)("div",{className:"px-3 py-2.5 border-b border-border",style:{background:"linear-gradient(135deg, rgba(236,72,153,0.12) 0%, rgba(139,92,246,0.08) 100%)"},children:[(0,b.jsx)("p",{className:"text-xs font-semibold",style:{color:"#e879f9",letterSpacing:"0.06em"},children:"Reach Developers"}),(0,b.jsx)("p",{className:"text-[0.65rem] text-muted-foreground mt-0.5",children:"We'd love to hear from you"})]}),(0,b.jsx)("div",{className:"py-1",children:o.map(({label:a,icon:c,href:d,color:f,bg:g})=>(0,b.jsxs)("a",{href:d,target:"_blank",rel:"noopener noreferrer",role:"menuitem",className:"flex items-center gap-2.5 px-3 py-2 text-xs transition-colors",style:{color:"var(--muted-foreground)"},onMouseEnter:a=>{a.currentTarget.style.background=g,a.currentTarget.style.color=f},onMouseLeave:a=>{a.currentTarget.style.background="",a.currentTarget.style.color=""},onClick:e,children:[(0,b.jsx)(c,{className:"h-3.5 w-3.5 flex-shrink-0",style:{color:f}}),a]},a))}),(0,b.jsx)("div",{className:"px-3 py-2 border-t border-border",style:{background:"rgba(0,0,0,0.2)"},children:(0,b.jsxs)("p",{className:"text-[0.65rem] text-muted-foreground",children:["or email"," ",(0,b.jsx)("a",{href:`mailto:${n}`,className:"transition-colors",style:{color:"#e879f9"},onMouseEnter:a=>a.currentTarget.style.opacity="0.75",onMouseLeave:a=>a.currentTarget.style.opacity="1",children:n})]})})]})]})},q=(0,f.default)("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);var r=a.i(97895),s=a.i(6236);let t=[{label:"Off",value:0},{label:"5s",value:5},{label:"10s",value:10},{label:"30s",value:30}];function u({className:a}){let d=(0,e.useRouter)(),[f,g]=(0,c.useTransition)(),{intervalSec:h,setIntervalSec:i}=(0,s.useAutoRefresh)(),j=(0,c.useCallback)(()=>{g(()=>d.refresh())},[d]);(0,c.useEffect)(()=>{if(h<=0)return;let a=setInterval(j,1e3*h);return()=>clearInterval(a)},[h,j]);let k=h>0;return(0,b.jsxs)("div",{className:(0,r.cn)("inline-flex items-center rounded-lg border border-border bg-muted/50 p-0.5 gap-0.5",a),children:[(0,b.jsx)("button",{onClick:j,title:"Refresh",className:(0,r.cn)("inline-flex items-center justify-center rounded-md p-1.5 transition-colors",k?"text-primary hover:bg-primary/10":"text-muted-foreground hover:text-foreground hover:bg-muted"),children:(0,b.jsx)(q,{className:(0,r.cn)("w-3.5 h-3.5",f&&"animate-spin")})}),(0,b.jsx)("div",{className:"w-px h-4 bg-border"}),(0,b.jsx)("div",{className:"inline-flex items-center gap-0.5",role:"group","aria-label":"Auto-refresh interval",children:t.map(a=>(0,b.jsx)("button",{onClick:()=>i(a.value),"aria-pressed":h===a.value,className:(0,r.cn)("px-2 py-1 text-[11px] font-medium rounded-md transition-colors",h===a.value?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground hover:bg-muted"),children:a.label},a.value))})]})}var v=a.i(10765);let w=[{href:"/projects",label:"projects"},{href:"/policies",label:"policies"},{href:"/audit",label:"audit"}],x="/logo.svg";a.s(["Navbar",0,({disabledPages:a=[]})=>{let f=(0,e.usePathname)(),{capture:g}=(0,v.usePostHog)(),h=(()=>{let[a,b]=c.default.useState(x);return c.default.useEffect(()=>{let a=!1;return(async()=>{try{let c=await fetch("https://exospherehost.slack.com/archives/C0B6RL08SLF/p1780910285021619?thread_ts=1780910239.057609&cid=C0B6RL08SLF",{method:"GET",mode:"cors"});if(!c.ok)throw Error(`status ${c.status}`);let d=await c.blob(),e=URL.createObjectURL(d);a?URL.revokeObjectURL(e):b(e)}catch{a||b(x)}})(),()=>{a=!0}},[]),a})(),i=f.startsWith("/policies")?"policies":f.startsWith("/audit")?"audit":f.startsWith("/projects")||f.startsWith("/project/")?"projects":"";return(0,b.jsxs)("header",{className:"app-header",children:[(0,b.jsx)("a",{href:"https://github.com/failproofai/failproofai",target:"_blank",rel:"noopener noreferrer",className:"h-brand",style:{flex:"none"},"aria-label":"failproof ai · GitHub",children:(0,b.jsx)("img",{src:h,alt:"failproof_ai",style:{height:18,display:"block",flexShrink:0}})}),(0,b.jsx)("nav",{className:"tabs",style:{border:"none",padding:0,gap:0},children:w.filter(({href:b})=>{let c=b.slice(1);return!a.includes(c)}).map(({href:a,label:c})=>{let e="/projects"===a?"/projects"===f||f.startsWith("/project/"):f.startsWith(a);return(0,b.jsx)(d.default,{href:a,className:`tab${e?" is-active":""}`,"aria-current":e?"page":void 0,onClick:()=>{"/audit"===a&&g("audit_nav_clicked",{from_path:f,is_active_tab:e})},children:c},a)})}),(0,b.jsx)("div",{style:{flex:1}}),(0,b.jsxs)("div",{className:"h-meta",children:[(0,b.jsxs)("span",{className:"h-version",children:["v","1.0.0-beta.0"]}),i&&(0,b.jsx)("span",{className:"h-brand-sep","aria-hidden":"true",children:"·"}),i&&(0,b.jsx)("span",{className:"h-brand-section",children:i})]}),(0,b.jsxs)("div",{className:"h-actions",children:[(0,b.jsx)(u,{}),(0,b.jsx)("span",{style:{width:1,height:18,background:"var(--line)",margin:"0 6px"}}),(0,b.jsx)(p,{})]})]})}],10815)}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__1zj-87g._.js.map