failproofai 1.0.0-beta.0 → 1.0.0-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 (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]__0259h38._.js → [root-of-the-server]__0nb0syg._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +3 -0
  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_0oy_-36.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 → 0fi645si-uiiq.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 18rvdw5v72364.js} +1 -1
  79. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 2j94uwkb4s3qr.js} +1 -1
  80. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 2sbelanzvxvv0.js} +1 -1
  81. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 31feojxhjyjpl.js} +1 -1
  82. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 3hp8ulqknybil.js} +1 -1
  83. package/.next/standalone/.next/static/chunks/{3wrgbih-516uj.js → 3s7myk_co0gbh.js} +1 -1
  84. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3vs4ay36tjds_.js} +1 -1
  85. package/.next/standalone/.next/static/chunks/{0u9hc6oxx-gpj.js → 40k1_v4os4cw6.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 +225 -42
  91. package/dist/worker.mjs +1 -1
  92. package/package.json +1 -1
  93. package/src/hooks/configure-wizard.ts +91 -13
  94. package/src/hooks/daemon-service.ts +360 -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 → NWAjs0tHFyzk97gRGG09r}/_buildManifest.js +0 -0
  97. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → NWAjs0tHFyzk97gRGG09r}/_clientMiddlewareManifest.js +0 -0
  98. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → NWAjs0tHFyzk97gRGG09r}/_ssgManifest.js +0 -0
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
9
9
  const hostname = process.env.HOSTNAME || '0.0.0.0'
10
10
 
11
11
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
12
- const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.0-beta.0"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
12
+ const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.0-beta.2"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
13
13
 
14
14
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
15
15
 
package/dist/cli.mjs CHANGED
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // package.json
22
- var version2 = "1.0.0-beta.0";
22
+ var version2 = "1.0.0-beta.2";
23
23
  var init_package = () => {};
24
24
 
25
25
  // src/posthog-key.ts
@@ -22763,8 +22763,16 @@ var init_daemon_download = __esm(() => {
22763
22763
  });
22764
22764
 
22765
22765
  // src/hooks/daemon-service.ts
22766
- import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "node:fs";
22767
- import { homedir as homedir32 } from "node:os";
22766
+ import {
22767
+ existsSync as existsSync21,
22768
+ mkdirSync as mkdirSync11,
22769
+ mkdtempSync,
22770
+ readFileSync as readFileSync16,
22771
+ writeFileSync as writeFileSync10,
22772
+ unlinkSync as unlinkSync3,
22773
+ rmSync as rmSync4
22774
+ } from "node:fs";
22775
+ import { homedir as homedir32, tmpdir, userInfo } from "node:os";
22768
22776
  import { resolve as resolve19, dirname as dirname9 } from "node:path";
22769
22777
  import { execFileSync as execFileSync4 } from "node:child_process";
22770
22778
  function setDaemonConfigured(value) {
@@ -22834,34 +22842,144 @@ function resolveWorkerCommand() {
22834
22842
  const workerScript = resolve19(packageRoot, "dist", "worker.mjs");
22835
22843
  if (!existsSync21(workerScript))
22836
22844
  return null;
22837
- return `node ${workerScript}`;
22845
+ return `${shellQuote(process.execPath)} ${shellQuote(workerScript)}`;
22846
+ }
22847
+ function shellQuote(value) {
22848
+ return `'${value.replace(/'/g, `'\\''`)}'`;
22849
+ }
22850
+ function serviceUser() {
22851
+ return userInfo().username;
22852
+ }
22853
+ function systemdUnitName(user = serviceUser()) {
22854
+ return `failproofaid@${user}.service`;
22855
+ }
22856
+ function systemdUnitPath(user = serviceUser()) {
22857
+ return resolve19("/etc/systemd/system", systemdUnitName(user));
22838
22858
  }
22839
- function systemdUnitPath() {
22859
+ function legacySystemdUserUnitPath() {
22840
22860
  return resolve19(homedir32(), ".config", "systemd", "user", "failproofaid.service");
22841
22861
  }
22842
- function launchdPlistPath() {
22862
+ function launchdLabel(user = serviceUser()) {
22863
+ return `ai.failproof.failproofaid.${user}`;
22864
+ }
22865
+ function launchdPlistPath(user = serviceUser()) {
22866
+ return `/Library/LaunchDaemons/${launchdLabel(user)}.plist`;
22867
+ }
22868
+ function legacyLaunchAgentPlistPath() {
22843
22869
  return resolve19(homedir32(), "Library", "LaunchAgents", "ai.failproof.failproofaid.plist");
22844
22870
  }
22871
+ function legacySharedLaunchDaemonPath() {
22872
+ return "/Library/LaunchDaemons/ai.failproof.failproofaid.plist";
22873
+ }
22845
22874
  function daemonServiceFilePath() {
22846
22875
  if (!isDaemonSupportedPlatform())
22847
22876
  return null;
22848
22877
  return process.platform === "linux" ? systemdUnitPath() : launchdPlistPath();
22849
22878
  }
22879
+ function primeElevation() {
22880
+ if (typeof process.getuid === "function" && process.getuid() === 0)
22881
+ return true;
22882
+ if (canElevate())
22883
+ return true;
22884
+ try {
22885
+ execFileSync4("sudo", ["-v"], { stdio: "inherit", timeout: SUDO_PROMPT_TIMEOUT_MS });
22886
+ return canElevate();
22887
+ } catch {
22888
+ return false;
22889
+ }
22890
+ }
22891
+ function canElevate() {
22892
+ if (typeof process.getuid === "function" && process.getuid() === 0)
22893
+ return true;
22894
+ try {
22895
+ execFileSync4("sudo", ["-n", "true"], { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
22896
+ return true;
22897
+ } catch {
22898
+ return false;
22899
+ }
22900
+ }
22901
+ function runPrivileged(command, args) {
22902
+ const root = typeof process.getuid === "function" && process.getuid() === 0;
22903
+ const [cmd, argv] = root ? [command, args] : ["sudo", ["-n", command, ...args]];
22904
+ execFileSync4(cmd, argv, { stdio: "ignore", timeout: SERVICE_CMD_TIMEOUT_MS });
22905
+ }
22906
+ function writePrivilegedFile(destination, contents, mode = "0644") {
22907
+ const stagingDir = mkdtempSync(resolve19(tmpdir(), "failproofaid-stage-"));
22908
+ const staging = resolve19(stagingDir, "service-definition");
22909
+ try {
22910
+ writeFileSync10(staging, contents, "utf8");
22911
+ runPrivileged("install", ["-m", mode, staging, destination]);
22912
+ } finally {
22913
+ rmSync4(stagingDir, { recursive: true, force: true });
22914
+ }
22915
+ }
22916
+ function removeLegacyUserService() {
22917
+ try {
22918
+ if (process.platform === "linux") {
22919
+ const legacy = legacySystemdUserUnitPath();
22920
+ if (!existsSync21(legacy))
22921
+ return;
22922
+ try {
22923
+ execFileSync4("systemctl", ["--user", "disable", "--now", "failproofaid.service"], {
22924
+ stdio: "ignore",
22925
+ timeout: SERVICE_CMD_TIMEOUT_MS
22926
+ });
22927
+ } catch {}
22928
+ unlinkSync3(legacy);
22929
+ try {
22930
+ execFileSync4("systemctl", ["--user", "daemon-reload"], {
22931
+ stdio: "ignore",
22932
+ timeout: SERVICE_CMD_TIMEOUT_MS
22933
+ });
22934
+ } catch {}
22935
+ } else {
22936
+ const legacyAgent = legacyLaunchAgentPlistPath();
22937
+ if (existsSync21(legacyAgent)) {
22938
+ try {
22939
+ execFileSync4("launchctl", ["unload", "-w", legacyAgent], {
22940
+ stdio: "ignore",
22941
+ timeout: SERVICE_CMD_TIMEOUT_MS
22942
+ });
22943
+ } catch {}
22944
+ unlinkSync3(legacyAgent);
22945
+ }
22946
+ const legacyDaemon = legacySharedLaunchDaemonPath();
22947
+ if (existsSync21(legacyDaemon)) {
22948
+ try {
22949
+ runPrivileged("launchctl", ["unload", "-w", legacyDaemon]);
22950
+ } catch {}
22951
+ runPrivileged("rm", ["-f", legacyDaemon]);
22952
+ }
22953
+ }
22954
+ } catch (err) {
22955
+ hookLogWarn(`could not remove the legacy user-scope daemon: ${err instanceof Error ? err.message : String(err)}`);
22956
+ }
22957
+ }
22850
22958
  function systemdUnitContents(binaryPath, workerCmd) {
22851
22959
  const envLine = workerCmd ? `Environment="FAILPROOFAI_WORKER_CMD=${workerCmd}"
22852
22960
  ` : "";
22961
+ const user = serviceUser();
22853
22962
  return `[Unit]
22854
- Description=failproofai background daemon (failproofaid)
22963
+ Description=failproofai background daemon (failproofaid) for ${user}
22855
22964
  After=network.target
22856
22965
 
22857
22966
  [Service]
22858
22967
  Type=simple
22968
+ User=${user}
22969
+ # Set explicitly rather than relying on systemd deriving it from User=:
22970
+ # failproofaid is user-scope by construction and refuses to start without
22971
+ # HOME ("HOME is not set; failproofaid is user-scope only"), so the one
22972
+ # variable it cannot do without is not left to a version-dependent default.
22973
+ Environment="HOME=${homedir32()}"
22859
22974
  ${envLine}ExecStart=${binaryPath}
22860
22975
  Restart=on-failure
22861
22976
  RestartSec=2
22862
22977
 
22863
22978
  [Install]
22864
- WantedBy=default.target
22979
+ # multi-user.target, not default.target: this is the whole point of a
22980
+ # system unit — it starts at boot, with no login and no lingering, and
22981
+ # keeps running after the installing user logs out.
22982
+ WantedBy=multi-user.target
22865
22983
  `;
22866
22984
  }
22867
22985
  function escapeXml(s) {
@@ -22879,7 +22997,7 @@ function launchdPlistContents(binaryPath, logDir2, workerCmd) {
22879
22997
  <plist version="1.0">
22880
22998
  <dict>
22881
22999
  <key>Label</key>
22882
- <string>ai.failproof.failproofaid</string>
23000
+ <string>${escapeXml(launchdLabel())}</string>
22883
23001
  <key>ProgramArguments</key>
22884
23002
  <array>
22885
23003
  <string>${escapeXml(binaryPath)}</string>
@@ -22888,6 +23006,12 @@ function launchdPlistContents(binaryPath, logDir2, workerCmd) {
22888
23006
  <true/>
22889
23007
  <key>KeepAlive</key>
22890
23008
  <true/>
23009
+ <!-- A LaunchDaemon runs as root unless told otherwise. This is the
23010
+ launchd half of the systemd unit's User=: loaded at boot by the
23011
+ system, executed as the one user whose home, socket and lock it
23012
+ is allowed to touch. -->
23013
+ <key>UserName</key>
23014
+ <string>${escapeXml(serviceUser())}</string>
22891
23015
  ${envBlock} <key>StandardOutPath</key>
22892
23016
  <string>${escapeXml(resolve19(logDir2, "failproofaid.log"))}</string>
22893
23017
  <key>StandardErrorPath</key>
@@ -22896,6 +23020,20 @@ ${envBlock} <key>StandardOutPath</key>
22896
23020
  </plist>
22897
23021
  `;
22898
23022
  }
23023
+ function daemonInstallCommands(binaryPath, workerCmd) {
23024
+ if (process.platform === "linux") {
23025
+ return [
23026
+ `sudo tee ${systemdUnitPath()} <<'EOF'
23027
+ ${systemdUnitContents(binaryPath, workerCmd)}EOF`,
23028
+ "sudo systemctl daemon-reload",
23029
+ `sudo systemctl enable --now ${systemdUnitName()}`
23030
+ ];
23031
+ }
23032
+ return [
23033
+ `sudo tee ${launchdPlistPath()} < the plist failproofai config would write`,
23034
+ `sudo launchctl load -w ${launchdPlistPath()}`
23035
+ ];
23036
+ }
22899
23037
  async function installDaemonService() {
22900
23038
  if (!isDaemonSupportedPlatform()) {
22901
23039
  return { installed: false, reason: `failproofaid is not supported on ${process.platform} yet` };
@@ -22905,35 +23043,27 @@ async function installDaemonService() {
22905
23043
  return { installed: false, reason: binaryReason ?? "failproofaid binary not found for this platform" };
22906
23044
  }
22907
23045
  const workerCmd = resolveWorkerCommand();
23046
+ if (!canElevate()) {
23047
+ return {
23048
+ installed: false,
23049
+ reason: "root privileges are required to install the failproofaid system service, and sudo credentials were not available. " + "Re-run `failproofai config` and approve the sudo prompt — do NOT run the CLI itself under sudo, which would " + "configure root's account instead of yours. To install by hand: " + daemonInstallCommands(binaryPath, workerCmd).join(" && ")
23050
+ };
23051
+ }
23052
+ removeLegacyUserService();
22908
23053
  try {
22909
23054
  if (process.platform === "linux") {
22910
- const unitPath = systemdUnitPath();
22911
- mkdirSync11(dirname9(unitPath), { recursive: true });
22912
- writeFileSync10(unitPath, systemdUnitContents(binaryPath, workerCmd), "utf8");
22913
- execFileSync4("systemctl", ["--user", "daemon-reload"], {
22914
- stdio: "ignore",
22915
- timeout: SERVICE_CMD_TIMEOUT_MS
22916
- });
22917
- execFileSync4("systemctl", ["--user", "enable", "--now", "failproofaid.service"], {
22918
- stdio: "ignore",
22919
- timeout: SERVICE_CMD_TIMEOUT_MS
22920
- });
23055
+ writePrivilegedFile(systemdUnitPath(), systemdUnitContents(binaryPath, workerCmd));
23056
+ runPrivileged("systemctl", ["daemon-reload"]);
23057
+ runPrivileged("systemctl", ["enable", "--now", systemdUnitName()]);
22921
23058
  } else {
22922
23059
  const plistPath = launchdPlistPath();
22923
23060
  const logDir2 = resolve19(homedir32(), ".failproofai", "logs");
22924
- mkdirSync11(dirname9(plistPath), { recursive: true });
22925
23061
  mkdirSync11(logDir2, { recursive: true });
22926
- writeFileSync10(plistPath, launchdPlistContents(binaryPath, logDir2, workerCmd), "utf8");
22927
23062
  try {
22928
- execFileSync4("launchctl", ["unload", plistPath], {
22929
- stdio: "ignore",
22930
- timeout: SERVICE_CMD_TIMEOUT_MS
22931
- });
23063
+ runPrivileged("launchctl", ["unload", plistPath]);
22932
23064
  } catch {}
22933
- execFileSync4("launchctl", ["load", "-w", plistPath], {
22934
- stdio: "ignore",
22935
- timeout: SERVICE_CMD_TIMEOUT_MS
22936
- });
23065
+ writePrivilegedFile(plistPath, launchdPlistContents(binaryPath, logDir2, workerCmd));
23066
+ runPrivileged("launchctl", ["load", "-w", plistPath]);
22937
23067
  }
22938
23068
  } catch (err) {
22939
23069
  const msg = err instanceof Error ? err.message : String(err);
@@ -22966,7 +23096,7 @@ function daemonServiceStatus() {
22966
23096
  if (!existsSync21(systemdUnitPath()))
22967
23097
  return "not-installed";
22968
23098
  try {
22969
- const out = execFileSync4("systemctl", ["--user", "is-active", "failproofaid.service"], {
23099
+ const out = execFileSync4("systemctl", ["is-active", systemdUnitName()], {
22970
23100
  stdio: ["ignore", "pipe", "ignore"],
22971
23101
  timeout: SERVICE_CMD_TIMEOUT_MS
22972
23102
  }).toString().trim();
@@ -22979,16 +23109,21 @@ function daemonServiceStatus() {
22979
23109
  if (!existsSync21(plistPath))
22980
23110
  return "not-installed";
22981
23111
  try {
22982
- const out = execFileSync4("launchctl", ["list"], {
23112
+ const root = typeof process.getuid === "function" && process.getuid() === 0;
23113
+ const args = ["print", `system/${launchdLabel()}`];
23114
+ const out = root ? execFileSync4("launchctl", args, {
23115
+ stdio: ["ignore", "pipe", "ignore"],
23116
+ timeout: SERVICE_CMD_TIMEOUT_MS
23117
+ }).toString() : execFileSync4("sudo", ["-n", "launchctl", ...args], {
22983
23118
  stdio: ["ignore", "pipe", "ignore"],
22984
23119
  timeout: SERVICE_CMD_TIMEOUT_MS
22985
23120
  }).toString();
22986
- return out.includes("ai.failproof.failproofaid") ? "running" : "stopped";
23121
+ return /state\s*=\s*running/.test(out) ? "running" : "stopped";
22987
23122
  } catch {
22988
23123
  return "stopped";
22989
23124
  }
22990
23125
  }
22991
- var SERVICE_CMD_TIMEOUT_MS = 1e4, SERVICE_START_TIMEOUT_MS = 5000, SERVICE_START_POLL_MS = 100, SERVICE_SETTLE_MS = 750;
23126
+ var SERVICE_CMD_TIMEOUT_MS = 1e4, SERVICE_START_TIMEOUT_MS = 5000, SERVICE_START_POLL_MS = 100, SERVICE_SETTLE_MS = 750, SUDO_PROMPT_TIMEOUT_MS = 120000;
22992
23127
  var init_daemon_service = __esm(() => {
22993
23128
  init_hook_logger();
22994
23129
  init_hooks_config();
@@ -23432,6 +23567,8 @@ function classifyDaemonInstallFailure(reason) {
23432
23567
  return "unsupported_platform";
23433
23568
  if (/binary not found/.test(reason))
23434
23569
  return "binary_not_found";
23570
+ if (/root privileges are required/.test(reason))
23571
+ return "needs_root";
23435
23572
  if (/checksum mismatch|has no entry for/.test(reason))
23436
23573
  return "checksum_mismatch";
23437
23574
  if (/downloads are disabled/.test(reason))
@@ -23465,15 +23602,15 @@ function describeCustomPolicies(cwd) {
23465
23602
  return { active, warnings, fileCount, scopes };
23466
23603
  }
23467
23604
  function reviewLines(state) {
23468
- const { scope, clis, policies, cwd, customEnabled } = state;
23605
+ const { scope, clis, policies, cwd, customEnabled, installDaemon } = state;
23469
23606
  const where = scope === "project" ? `This project (${homeify(cwd)})` : "Everywhere (global)";
23470
23607
  const lines = [];
23471
23608
  const assistantNames = clis.map((c3) => getIntegration(c3).displayName);
23472
23609
  lines.push(` Where : ${where}`);
23473
23610
  lines.push(` Assistants : ${assistantNames.length ? summarize(assistantNames, "assistants") : "(none)"}`);
23474
23611
  lines.push(` Policies : ${policies.length} enabled`);
23475
- if (scope === "user" && isDaemonSupportedPlatform()) {
23476
- lines.push(` Daemon : failproofaid will be installed/started as a background service`);
23612
+ if (installDaemon && isDaemonSupportedPlatform()) {
23613
+ lines.push(` Daemon : failproofaid, installed as a system service running as you`);
23477
23614
  }
23478
23615
  const custom = describeCustomPolicies(cwd);
23479
23616
  if (custom.active.length > 0) {
@@ -23490,10 +23627,10 @@ function reviewLines(state) {
23490
23627
  }
23491
23628
  }
23492
23629
  lines.push(` ${homeify(getConfigPathForScope(scope, cwd))} ${policies.length} policies`);
23493
- if (scope === "user" && isDaemonSupportedPlatform()) {
23630
+ if (installDaemon && isDaemonSupportedPlatform()) {
23494
23631
  const servicePath = daemonServiceFilePath();
23495
23632
  if (servicePath)
23496
- lines.push(` ${homeify(servicePath)} failproofaid service`);
23633
+ lines.push(` ${homeify(servicePath)} failproofaid service (needs root)`);
23497
23634
  }
23498
23635
  return lines;
23499
23636
  }
@@ -23554,6 +23691,15 @@ async function runConfigureWizard(io = {}) {
23554
23691
  stdout.write(`failproofai config needs an interactive terminal.
23555
23692
  Use the flag form instead, e.g.:
23556
23693
  failproofai policies --install --scope user --cli claude
23694
+ `);
23695
+ return { applied: false };
23696
+ }
23697
+ if (typeof process.getuid === "function" && process.getuid() === 0 && process.env.SUDO_USER) {
23698
+ stdout.write(`Run failproofai config as ${process.env.SUDO_USER}, not under sudo.
23699
+ ` + `Everything it configures is per-user — hooks, policies and the daemon's own
23700
+ ` + `account — so under sudo it would set all of that up for root instead of you.
23701
+ ` + `The one step that needs root (installing the service) asks for your password
23702
+ on its own.
23557
23703
  `);
23558
23704
  return { applied: false };
23559
23705
  }
@@ -23563,6 +23709,43 @@ Use the flag form instead, e.g.:
23563
23709
  outro("Cancelled — nothing was changed.", { ok: false }, stdout);
23564
23710
  return { applied: false };
23565
23711
  };
23712
+ let daemonWanted = false;
23713
+ let elevated = false;
23714
+ if (isDaemonSupportedPlatform()) {
23715
+ const choice = await selectOne({
23716
+ message: "Install the failproofaid background daemon?",
23717
+ choices: [
23718
+ {
23719
+ label: "Yes — keep policy evaluation warm",
23720
+ value: "install",
23721
+ hint: "a system service, started at boot, running as you · needs your password once"
23722
+ },
23723
+ {
23724
+ label: "Not now",
23725
+ value: "skip",
23726
+ hint: "policies still enforce, evaluated per hook call"
23727
+ }
23728
+ ],
23729
+ stdin,
23730
+ stdout
23731
+ });
23732
+ if (choice === null)
23733
+ return cancel();
23734
+ daemonWanted = choice === "install";
23735
+ if (daemonWanted) {
23736
+ stdout.write(`
23737
+ Installing the failproofaid service needs root. Your password stays with sudo.
23738
+ `);
23739
+ elevated = primeElevation();
23740
+ if (!elevated) {
23741
+ stdout.write(`Could not get sudo — carrying on without the daemon.
23742
+ ` + `Everything else still applies, and policies still enforce in-process.
23743
+
23744
+ `);
23745
+ daemonWanted = false;
23746
+ }
23747
+ }
23748
+ }
23566
23749
  const scope = await selectOne({
23567
23750
  message: "Where should this apply?",
23568
23751
  choices: buildScopeChoices(cwd),
@@ -23610,7 +23793,7 @@ Use the flag form instead, e.g.:
23610
23793
  const customEnabled = hasCustomFiles ? presets.includes(CUSTOM) : undefined;
23611
23794
  const decision = await selectOne({
23612
23795
  message: "Ready to apply?",
23613
- body: reviewLines({ scope, clis, policies, cwd, customEnabled }),
23796
+ body: reviewLines({ scope, clis, policies, cwd, customEnabled, installDaemon: daemonWanted }),
23614
23797
  choices: [
23615
23798
  { label: "Yes, apply now", value: "apply", hint: "write the config" },
23616
23799
  { label: "Cancel", value: "cancel", hint: "quit, no changes" }
@@ -23630,7 +23813,7 @@ Use the flag form instead, e.g.:
23630
23813
  await installHooks2(policies, scope, cwd, false, "configure-wizard", undefined, false, clis, { replace: true, quiet: true });
23631
23814
  setCustomPoliciesEnabled(scope, cwd, customEnabled);
23632
23815
  let daemonInstalled = false;
23633
- if (scope === "user" && isDaemonSupportedPlatform()) {
23816
+ if (daemonWanted) {
23634
23817
  const daemonResult = await installDaemonService();
23635
23818
  if (daemonResult.installed) {
23636
23819
  setDaemonConfigured(true);
@@ -23789,7 +23972,7 @@ import { realpathSync as realpathSync4 } from "fs";
23789
23972
  import { dirname as dirname11, resolve as resolve22 } from "path";
23790
23973
  import { fileURLToPath as fileURLToPath4 } from "url";
23791
23974
  // package.json
23792
- var version = "1.0.0-beta.0";
23975
+ var version = "1.0.0-beta.2";
23793
23976
 
23794
23977
  // bin/failproofai.mjs
23795
23978
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
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.2";
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.2",
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",
@@ -43,6 +43,7 @@ import {
43
43
  isDaemonSupportedPlatform,
44
44
  installDaemonService,
45
45
  daemonServiceFilePath,
46
+ primeElevation,
46
47
  setDaemonConfigured,
47
48
  } from "./daemon-service";
48
49
  import { hookLogWarn } from "./hook-logger";
@@ -289,6 +290,10 @@ export function classifyDaemonInstallFailure(reason: string | undefined): string
289
290
  // signal, "disabled" is a deliberate air-gapped opt-out, and a plain fetch
290
291
  // failure is usually a proxy or an offline box — three very different
291
292
  // stories that would otherwise all land in `service_manager_error`.
293
+ // The service is installed system-wide now, so "couldn't become root" is a
294
+ // first-class outcome with a specific remedy (re-run under sudo) rather
295
+ // than an opaque service_manager_error.
296
+ if (/root privileges are required/.test(reason)) return "needs_root";
292
297
  if (/checksum mismatch|has no entry for/.test(reason)) return "checksum_mismatch";
293
298
  if (/downloads are disabled/.test(reason)) return "downloads_disabled";
294
299
  if (/failed to download/.test(reason)) return "download_failed";
@@ -346,8 +351,14 @@ export function reviewLines(state: {
346
351
  cwd: string;
347
352
  /** The Custom checkbox. `undefined` = nothing to toggle, leave as-is. */
348
353
  customEnabled?: boolean;
354
+ /**
355
+ * Step 0's answer. The daemon is machine-level, so this is a decision the
356
+ * user made explicitly rather than something inferred from the scope —
357
+ * showing it any other way would promise a service the apply won't install.
358
+ */
359
+ installDaemon?: boolean;
349
360
  }): string[] {
350
- const { scope, clis, policies, cwd, customEnabled } = state;
361
+ const { scope, clis, policies, cwd, customEnabled, installDaemon } = state;
351
362
  const where =
352
363
  scope === "project" ? `This project (${homeify(cwd)})` : "Everywhere (global)";
353
364
  const lines: string[] = [];
@@ -355,9 +366,9 @@ export function reviewLines(state: {
355
366
  lines.push(` Where : ${where}`);
356
367
  lines.push(` Assistants : ${assistantNames.length ? summarize(assistantNames, "assistants") : "(none)"}`);
357
368
  lines.push(` Policies : ${policies.length} enabled`);
358
- if (scope === "user" && isDaemonSupportedPlatform()) {
369
+ if (installDaemon && isDaemonSupportedPlatform()) {
359
370
  lines.push(
360
- ` Daemon : failproofaid will be installed/started as a background service`,
371
+ ` Daemon : failproofaid, installed as a system service running as you`,
361
372
  );
362
373
  }
363
374
 
@@ -385,9 +396,9 @@ export function reviewLines(state: {
385
396
  }
386
397
  }
387
398
  lines.push(` ${homeify(getConfigPathForScope(scope, cwd))} ${policies.length} policies`);
388
- if (scope === "user" && isDaemonSupportedPlatform()) {
399
+ if (installDaemon && isDaemonSupportedPlatform()) {
389
400
  const servicePath = daemonServiceFilePath();
390
- if (servicePath) lines.push(` ${homeify(servicePath)} failproofaid service`);
401
+ if (servicePath) lines.push(` ${homeify(servicePath)} failproofaid service (needs root)`);
391
402
  }
392
403
  return lines;
393
404
  }
@@ -496,6 +507,24 @@ export async function runConfigureWizard(io: WizardIO = {}): Promise<WizardResul
496
507
  return { applied: false };
497
508
  }
498
509
 
510
+ // Running the wizard itself under sudo configures the WRONG ACCOUNT, and
511
+ // does it silently: homedir() becomes /root, so the hooks land in root's
512
+ // settings, `daemonConfigured` is set for root, the daemon binary downloads
513
+ // to /root/.failproofai/bin, and the unit is generated with User=root —
514
+ // exactly the elevation the design exists to avoid. SUDO_USER is set only
515
+ // when a real user sudo'd here, which distinguishes this mistake from a
516
+ // legitimately root-only environment (a container that has no other user).
517
+ if (typeof process.getuid === "function" && process.getuid() === 0 && process.env.SUDO_USER) {
518
+ stdout.write(
519
+ `Run failproofai config as ${process.env.SUDO_USER}, not under sudo.\n` +
520
+ "Everything it configures is per-user — hooks, policies and the daemon's own\n" +
521
+ "account — so under sudo it would set all of that up for root instead of you.\n" +
522
+ "The one step that needs root (installing the service) asks for your password\n" +
523
+ "on its own.\n",
524
+ );
525
+ return { applied: false };
526
+ }
527
+
499
528
  // Fire-and-forget: never block the wizard's first paint on telemetry.
500
529
  void emit("configure_started", {});
501
530
  intro("let's set up your safety net", stdout);
@@ -505,6 +534,55 @@ export async function runConfigureWizard(io: WizardIO = {}): Promise<WizardResul
505
534
  return { applied: false };
506
535
  };
507
536
 
537
+ // 0 — The background daemon, before anything else.
538
+ //
539
+ // First because it is the only step that needs a password: asking here
540
+ // means sudo prompts on a clean terminal, before any question has drawn a
541
+ // screen, instead of firing from underneath a rendered TUI where the prompt
542
+ // is invisible and the typed password lands in a redrawn frame. `sudo -v`
543
+ // caches the credential for the rest of the run, so the actual install at
544
+ // apply time stays non-interactive.
545
+ //
546
+ // Machine-level, so it is deliberately NOT gated on the scope chosen in the
547
+ // next step: one daemon serves every project on this machine.
548
+ let daemonWanted = false;
549
+ let elevated = false;
550
+ if (isDaemonSupportedPlatform()) {
551
+ const choice = await selectOne<"install" | "skip">({
552
+ message: "Install the failproofaid background daemon?",
553
+ choices: [
554
+ {
555
+ label: "Yes — keep policy evaluation warm",
556
+ value: "install",
557
+ hint: "a system service, started at boot, running as you · needs your password once",
558
+ },
559
+ {
560
+ label: "Not now",
561
+ value: "skip",
562
+ hint: "policies still enforce, evaluated per hook call",
563
+ },
564
+ ],
565
+ stdin,
566
+ stdout,
567
+ });
568
+ if (choice === null) return cancel();
569
+ daemonWanted = choice === "install";
570
+
571
+ if (daemonWanted) {
572
+ stdout.write("\nInstalling the failproofaid service needs root. Your password stays with sudo.\n");
573
+ elevated = primeElevation();
574
+ if (!elevated) {
575
+ // Never fatal: the rest of setup is worth applying, and a machine with
576
+ // no daemon is exactly the machine every release before this one had.
577
+ stdout.write(
578
+ "Could not get sudo — carrying on without the daemon.\n" +
579
+ "Everything else still applies, and policies still enforce in-process.\n\n",
580
+ );
581
+ daemonWanted = false;
582
+ }
583
+ }
584
+ }
585
+
508
586
  // 1 — Where?
509
587
  const scope = await selectOne<HookScope>({
510
588
  message: "Where should this apply?",
@@ -575,7 +653,7 @@ export async function runConfigureWizard(io: WizardIO = {}): Promise<WizardResul
575
653
  // 4 — Review & apply
576
654
  const decision = await selectOne<"apply" | "cancel">({
577
655
  message: "Ready to apply?",
578
- body: reviewLines({ scope, clis, policies, cwd, customEnabled }),
656
+ body: reviewLines({ scope, clis, policies, cwd, customEnabled, installDaemon: daemonWanted }),
579
657
  choices: [
580
658
  { label: "Yes, apply now", value: "apply", hint: "write the config" },
581
659
  { label: "Cancel", value: "cancel", hint: "quit, no changes" },
@@ -611,14 +689,14 @@ export async function runConfigureWizard(io: WizardIO = {}): Promise<WizardResul
611
689
  );
612
690
  setCustomPoliciesEnabled(scope, cwd, customEnabled);
613
691
 
614
- // Daemon setup — unconditional on Linux/macOS whenever the global scope
615
- // was chosen (a daemon is per-machine, not per-project), no separate
616
- // opt-in step or command. A failure here (binary not found, no service
617
- // manager, ...) never fails the wizard — the rest of setup already
618
- // applied, and this machine simply stays on the in-process path exactly
619
- // as before, since `daemonConfigured` is only set on success.
692
+ // Daemon setup — driven by step 0's answer, with the sudo credential it
693
+ // already primed, so nothing here can stop to ask a question. A failure
694
+ // (binary download blocked, no service manager, a daemon that won't stay
695
+ // up) never fails the wizard: the rest of setup already applied, and this
696
+ // machine simply stays on the in-process path, since `daemonConfigured` is
697
+ // only set on success.
620
698
  let daemonInstalled = false;
621
- if (scope === "user" && isDaemonSupportedPlatform()) {
699
+ if (daemonWanted) {
622
700
  const daemonResult = await installDaemonService();
623
701
  if (daemonResult.installed) {
624
702
  setDaemonConfigured(true);