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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0259h38._.js → [root-of-the-server]__0nb0syg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- 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
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +11 -11
- package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 0fi645si-uiiq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 18rvdw5v72364.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 2j94uwkb4s3qr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 2sbelanzvxvv0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 31feojxhjyjpl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 3hp8ulqknybil.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3wrgbih-516uj.js → 3s7myk_co0gbh.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3vs4ay36tjds_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0u9hc6oxx-gpj.js → 40k1_v4os4cw6.js} +1 -1
- package/.next/standalone/Cargo.lock +2 -2
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +225 -42
- package/dist/worker.mjs +1 -1
- package/package.json +1 -1
- package/src/hooks/configure-wizard.ts +91 -13
- package/src/hooks/daemon-service.ts +360 -58
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → NWAjs0tHFyzk97gRGG09r}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → NWAjs0tHFyzk97gRGG09r}/_clientMiddlewareManifest.js +0 -0
- /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.
|
|
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.
|
|
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 {
|
|
22767
|
-
|
|
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
|
|
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
|
|
22859
|
+
function legacySystemdUserUnitPath() {
|
|
22840
22860
|
return resolve19(homedir32(), ".config", "systemd", "user", "failproofaid.service");
|
|
22841
22861
|
}
|
|
22842
|
-
function
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
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
|
-
|
|
22929
|
-
stdio: "ignore",
|
|
22930
|
-
timeout: SERVICE_CMD_TIMEOUT_MS
|
|
22931
|
-
});
|
|
23063
|
+
runPrivileged("launchctl", ["unload", plistPath]);
|
|
22932
23064
|
} catch {}
|
|
22933
|
-
|
|
22934
|
-
|
|
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", ["
|
|
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
|
|
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
|
|
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 (
|
|
23476
|
-
lines.push(` Daemon : failproofaid
|
|
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 (
|
|
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 (
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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 (
|
|
369
|
+
if (installDaemon && isDaemonSupportedPlatform()) {
|
|
359
370
|
lines.push(
|
|
360
|
-
` Daemon : failproofaid
|
|
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 (
|
|
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 —
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
//
|
|
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 (
|
|
699
|
+
if (daemonWanted) {
|
|
622
700
|
const daemonResult = await installDaemonService();
|
|
623
701
|
if (daemonResult.installed) {
|
|
624
702
|
setDaemonConfigured(true);
|