failproofai 1.0.10 → 1.0.11-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.meta +8 -0
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  19. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.meta +8 -0
  23. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
  40. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  44. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page.js +4 -4
  46. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/index.html +1 -1
  49. package/.next/standalone/.next/server/app/index.meta +8 -0
  50. package/.next/standalone/.next/server/app/index.rsc +14 -14
  51. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  52. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  53. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  56. package/.next/standalone/.next/server/app/page.js +4 -4
  57. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  61. package/.next/standalone/.next/server/app/policies/page.js +8 -8
  62. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
  67. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +5 -5
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
  81. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  82. package/.next/standalone/.next/server/app/settings/page.js +9 -9
  83. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
  86. package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0el6rg8._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0ol5pa3._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1rqmk27._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1w7tiq4._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  117. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.js} +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
  136. package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
  137. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  138. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  147. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  148. package/.next/standalone/.next/server/middleware.js +1 -1
  149. package/.next/standalone/.next/server/pages/404.html +1 -1
  150. package/.next/standalone/.next/server/pages/500.html +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  153. package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
  158. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  165. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  166. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  167. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  168. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  169. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  175. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  176. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  177. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  178. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  180. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  181. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  182. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  185. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  186. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  187. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  189. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  190. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  191. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  192. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  193. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  194. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  196. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  197. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  198. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  201. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  202. package/.next/standalone/node_modules/next/package.json +10 -10
  203. package/.next/standalone/package.json +14 -11
  204. package/.next/standalone/sdk/python/README.md +41 -0
  205. package/.next/standalone/sdk/python/docs/README.md +1 -0
  206. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  208. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  211. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  212. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  213. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  214. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  215. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  216. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  217. package/.next/standalone/sdk/python/uv.lock +258 -147
  218. package/.next/standalone/sdk/typescript/README.md +43 -0
  219. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  220. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  221. package/.next/standalone/sdk/typescript/package.json +2 -0
  222. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  223. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  224. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  225. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  226. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  227. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  228. package/.next/standalone/server.js +1 -1
  229. package/bin/failproofai.mjs +653 -1077
  230. package/dist/cli.mjs +8174 -3930
  231. package/dist/worker.mjs +170 -62
  232. package/hermes-plugin/plugin.yaml +1 -1
  233. package/package.json +14 -11
  234. package/scripts/launch-facts.ts +157 -0
  235. package/scripts/launch-ready.ts +228 -0
  236. package/scripts/launch-screen.ts +202 -0
  237. package/scripts/launch.ts +106 -49
  238. package/scripts/skew-log-filter.ts +42 -0
  239. package/src/audit/cli-login.ts +39 -59
  240. package/src/audit/cli.ts +308 -228
  241. package/src/audit/index.ts +45 -2
  242. package/src/audit/schedule-cli.ts +83 -120
  243. package/src/audit/types.ts +31 -0
  244. package/src/hooks/agent-selection.ts +155 -0
  245. package/src/hooks/backfill-cli.ts +151 -137
  246. package/src/hooks/backfill-request.ts +149 -0
  247. package/src/hooks/cloud-connection.ts +1 -1
  248. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  249. package/src/hooks/cloud-introspect.ts +3 -0
  250. package/src/hooks/collector-config.ts +10 -1
  251. package/src/hooks/config-status.ts +606 -0
  252. package/src/hooks/configure-wizard.ts +915 -989
  253. package/src/hooks/daemon-service.ts +16 -6
  254. package/src/hooks/did-you-mean.ts +185 -0
  255. package/src/hooks/flush-cli.ts +33 -7
  256. package/src/hooks/fp-config.ts +96 -0
  257. package/src/hooks/fp-home.ts +11 -0
  258. package/src/hooks/fp-reset.ts +102 -95
  259. package/src/hooks/handler.ts +4 -0
  260. package/src/hooks/harness-cli.ts +87 -138
  261. package/src/hooks/install-prompt.ts +38 -477
  262. package/src/hooks/integrations.ts +1 -1
  263. package/src/hooks/jev-cli.ts +183 -196
  264. package/src/hooks/manager.ts +554 -358
  265. package/src/hooks/onboarding-attempt.ts +11 -9
  266. package/src/hooks/otel-cli.ts +344 -0
  267. package/src/hooks/otel-output.ts +24 -0
  268. package/src/hooks/otel-writers.ts +225 -0
  269. package/src/hooks/pack-cli.ts +219 -204
  270. package/src/hooks/report-body.ts +50 -0
  271. package/src/hooks/safe-config-write.ts +4 -2
  272. package/src/hooks/session-pause-cli.ts +93 -73
  273. package/src/hooks/tui.ts +849 -389
  274. package/src/hooks/uninstall-cli.ts +136 -149
  275. package/src/hooks/untraced-note.ts +52 -0
  276. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  283. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  293. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  294. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
  297. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_ssgManifest.js +0 -0
@@ -43,6 +43,7 @@
43
43
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
44
44
  import { dirname } from "node:path";
45
45
  import { onboardingAttemptFile } from "./fp-home";
46
+ import { colorsEnabled, screenKit, type RenderOpts } from "./tui";
46
47
  import type { WizardAbort } from "./configure-wizard";
47
48
 
48
49
  /** Bumped only for a deliberate shape change; an unreadable record is ignored. */
@@ -169,7 +170,11 @@ export function blockerCleared(attempt: OnboardingAttempt, probe: RetryProbe): b
169
170
  }
170
171
 
171
172
  /** The one line shown instead of relaunching the wizard. */
172
- export function attemptHintLines(attempt: OnboardingAttempt): string[] {
173
+ export function attemptHintLines(
174
+ attempt: OnboardingAttempt,
175
+ /** How to draw it. Defaults to what stdout, where the gate prints it, can show. */
176
+ render: RenderOpts = { color: colorsEnabled(process.stdout) },
177
+ ): string[] {
173
178
  const why: Record<string, string> = {
174
179
  needs_root: "it needs root to install the failproofaid service",
175
180
  daemon_failed: "the failproofaid service could not be started",
@@ -184,12 +189,9 @@ export function attemptHintLines(attempt: OnboardingAttempt): string[] {
184
189
  // hit the exact same hard-fail in the meantime.
185
190
  const action =
186
191
  attempt.reason === "unsupported_platform"
187
- ? "Check for a failproofai update — this platform may be supported by a newer release."
188
- : "Run `failproofai config` when you are ready.";
189
- return [
190
- ``,
191
- `[failproofai] Setup is not finished — ${detail}.`,
192
- ` ${action}`,
193
- ``,
194
- ];
192
+ ? "Check for an update to failproofai: a newer release may support this platform."
193
+ : "Run it when you are ready: `failproofai config`";
194
+ // Printed above the command that was typed: one `▲` line and one fix line,
195
+ // like every notice (`screenKit().notice`).
196
+ return ["", ...screenKit(render).notice("caution", `Setup is not finished: ${detail}.\n${action}`), ""];
195
197
  }
@@ -0,0 +1,344 @@
1
+ /** Opt-in agent setup; no OTLP translation or changes to the events uploader. */
2
+ import {
3
+ chmodSync, existsSync, mkdirSync, readFileSync,
4
+ statSync, unlinkSync, writeFileSync,
5
+ } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import { createHash } from "node:crypto";
8
+ import { homedir } from "node:os";
9
+ import { connect } from "node:net";
10
+ import { readConfig, readCredentials } from "./fp-config";
11
+ import { configFile, otelAgentStateFile, otlpHealthFile } from "./fp-home";
12
+ import { writeConfigFileAtomic } from "./safe-config-write";
13
+ import { agentSessionsEnabled } from "./collector-config";
14
+ import { introspectKey } from "./cloud-introspect";
15
+ import { readJsonFile } from "./integrations";
16
+ import { selectOne } from "./tui";
17
+ import {
18
+ AGENT_LABELS, OTEL_AGENTS, agentConfigPath, agentOtelWrites, editAgentConfig,
19
+ getSetting, parseAgentConfig, vscodeInstalled,
20
+ type JsonObject, type OtelAgent, type SettingWrite,
21
+ } from "./otel-writers";
22
+
23
+ export const OTEL_ORG_OFF = "OTEL ingest is off for your org. An admin can turn it on in Settings → OpenTelemetry.";
24
+ export interface OtelResult {
25
+ lines: string[];
26
+ exitCode: number;
27
+ events?: { name: "otel_enabled" | "otel_disabled"; agent: OtelAgent; local: boolean; content: boolean }[];
28
+ }
29
+ interface AgentRecord {
30
+ file: string;
31
+ local: boolean;
32
+ content: boolean;
33
+ destination: string;
34
+ keys: { path: string[]; hadPrevious: boolean; previous?: unknown; private?: boolean }[];
35
+ previousSessions?: boolean;
36
+ switchedSessions: boolean;
37
+ }
38
+ interface PrivateState {
39
+ original: string | null;
40
+ originalMode?: number;
41
+ writtenHash: string;
42
+ previous: SettingWrite[];
43
+ }
44
+ export interface OtelOptions {
45
+ home?: string;
46
+ fetchImpl?: typeof fetch;
47
+ confirm?: (question: string) => Promise<boolean>;
48
+ onEvent?: (name: string, properties: { agent: OtelAgent; local: boolean; content: boolean }) => void | Promise<void>;
49
+ }
50
+
51
+ function rawConfig(): JsonObject {
52
+ return readJsonFile(configFile());
53
+ }
54
+ function object(value: unknown): JsonObject {
55
+ if (value === undefined) return {};
56
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid OTEL configuration object.");
57
+ return value as JsonObject;
58
+ }
59
+ function records(raw: JsonObject): Partial<Record<OtelAgent, AgentRecord>> {
60
+ return object(object(raw.otel).agents) as Partial<Record<OtelAgent, AgentRecord>>;
61
+ }
62
+ function saveConfig(raw: JsonObject): void {
63
+ writeConfigFileAtomic(configFile(), `${JSON.stringify(raw, null, 2)}\n`, { backup: false });
64
+ }
65
+ function digest(text: string): string {
66
+ return createHash("sha256").update(text).digest("hex");
67
+ }
68
+ function privateState(agent: OtelAgent): PrivateState {
69
+ return JSON.parse(readFileSync(otelAgentStateFile(agent), "utf8")) as PrivateState;
70
+ }
71
+ function credential(): { key: string; origin: string } {
72
+ const ingest = readCredentials().ingest;
73
+ const key = process.env.FAILPROOFAI_INGEST_KEY || ingest?.key;
74
+ if (!key) throw new Error("No Cloud API key configured. Run `failproofai config` first.");
75
+ const url = new URL(process.env.FAILPROOFAI_INGEST_URL || ingest?.url || "https://app.befailproof.ai");
76
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) throw new Error("Invalid Cloud ingest URL.");
77
+ return { key, origin: url.origin };
78
+ }
79
+ function backupOnce(path: string, original: string | null): void {
80
+ if (existsSync(`${path}.failproofai-bak`)) return;
81
+ mkdirSync(dirname(path), { recursive: true });
82
+ writeFileSync(`${path}.failproofai-bak`, original ?? "", { flag: "wx", mode: 0o600 });
83
+ chmodSync(`${path}.failproofai-bak`, 0o600);
84
+ }
85
+ function savePrivate(agent: OtelAgent, state: PrivateState): void {
86
+ const path = otelAgentStateFile(agent);
87
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
88
+ writeConfigFileAtomic(path, JSON.stringify(state), { backup: false, mode: 0o600 });
89
+ chmodSync(path, 0o600);
90
+ }
91
+ function configureRelay(raw: JsonObject): void {
92
+ const collector = object(raw.collector);
93
+ raw.collector = collector;
94
+ const local = Object.values(records(raw)).some(record => record?.local);
95
+ if (local) collector.otlp = { ...object(collector.otlp), enabled: true };
96
+ else if (collector.otlp) collector.otlp = { ...object(collector.otlp), enabled: false };
97
+ }
98
+ function hasExternalOtel(agent: OtelAgent, config: JsonObject): boolean {
99
+ if (agent === "claude") return getSetting(config, ["env", "CLAUDE_CODE_ENABLE_TELEMETRY"]) === "1";
100
+ if (agent === "codex") {
101
+ return ["exporter", "trace_exporter", "metrics_exporter"].some(k => {
102
+ const value = getSetting(config, ["otel", k]);
103
+ return value !== undefined && value !== "none" && value !== "statsig";
104
+ });
105
+ }
106
+ return getSetting(config, agent === "gemini" ? ["telemetry", "enabled"] : ["github.copilot.chat.otel.enabled"]) === true;
107
+ }
108
+ async function defaultConfirm(question: string): Promise<boolean> {
109
+ return await selectOne({
110
+ message: question,
111
+ choices: [
112
+ { label: "No", value: false, hint: "keep transcript upload" },
113
+ { label: "Yes", value: true, hint: "switch this agent to OTEL" },
114
+ ],
115
+ }) === true;
116
+ }
117
+ function restoreAgent(agent: OtelAgent, raw: JsonObject): void {
118
+ const all = records(raw);
119
+ const record = all[agent];
120
+ if (!record) return;
121
+ const state = privateState(agent);
122
+ if (existsSync(record.file)) {
123
+ const current = readFileSync(record.file, "utf8");
124
+ if (digest(current) === state.writtenHash) {
125
+ if (state.original === null) unlinkSync(record.file);
126
+ else writeConfigFileAtomic(record.file, state.original, { backup: false, mode: state.originalMode });
127
+ } else {
128
+ const restored = editAgentConfig(current, agent, state.previous);
129
+ writeConfigFileAtomic(record.file, restored, { backup: false, mode: state.originalMode });
130
+ }
131
+ }
132
+ if (record.switchedSessions) {
133
+ const collector = object(raw.collector);
134
+ const agents = object(collector.agents);
135
+ const settings = object(agents[agent]);
136
+ if (record.previousSessions === undefined) delete settings.sessions;
137
+ else settings.sessions = record.previousSessions;
138
+ if (Object.keys(settings).length) agents[agent] = settings;
139
+ else delete agents[agent];
140
+ if (Object.keys(agents).length) collector.agents = agents;
141
+ else delete collector.agents;
142
+ }
143
+ delete all[agent];
144
+ object(raw.otel).agents = all;
145
+ }
146
+ async function enableAgent(agent: OtelAgent, yes: boolean, localFlag: boolean, content: boolean, options: OtelOptions): Promise<OtelResult> {
147
+ const { key, origin } = credential();
148
+ const raw = rawConfig();
149
+ const previousConfig = structuredClone(raw);
150
+ const file = agentConfigPath(agent, options.home ?? homedir());
151
+ if (agent === "copilot" && !vscodeInstalled(options.home ?? homedir())) {
152
+ return { lines: ["VS Code not found — skipping copilot"], exitCode: 0 };
153
+ }
154
+ const existing = records(raw)[agent];
155
+ // Gemini has no transcript source in this collector.
156
+ const transcriptsEnabled = agent !== "gemini" && agentSessionsEnabled(agent);
157
+ if (!existing && transcriptsEnabled && !yes) {
158
+ const question = `failproofai already sends ${AGENT_LABELS[agent]} sessions to Cloud. Send them with OTEL instead? Transcript upload for ${AGENT_LABELS[agent]} stops.`;
159
+ if (!await (options.confirm ?? defaultConfirm)(question)) return { lines: [`${AGENT_LABELS[agent]} unchanged (default No).`], exitCode: 0 };
160
+ }
161
+ const local = localFlag || agent === "gemini" || agent === "copilot";
162
+ const port = readConfig().collector.otlp?.port ?? 4318;
163
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("collector.otlp.port must be between 1 and 65535.");
164
+ const destination = local ? `http://127.0.0.1:${port}` : origin;
165
+ const original = existsSync(file) ? readFileSync(file, "utf8") : null;
166
+ const parsed = parseAgentConfig(original ?? "", agent);
167
+ const writes = agentOtelWrites(agent, destination, key, local, content);
168
+ // Switching from Cloud to local stops owning header keys. Restore any keys
169
+ // from an earlier enable before applying the new (header-free) write set.
170
+ const oldState = existing ? privateState(agent) : null;
171
+ const released = oldState?.previous.filter(previous =>
172
+ !writes.some(write => JSON.stringify(write.path) === JSON.stringify(previous.path))) ?? [];
173
+ const next = editAgentConfig(original ?? "", agent, [...released, ...writes]);
174
+ // On repeated enable keep the ORIGINAL previous values, not our last write.
175
+ const state = oldState ?? {
176
+ original,
177
+ originalMode: original === null ? undefined : statSync(file).mode & 0o777,
178
+ writtenHash: "",
179
+ previous: writes.map(w => ({ path: w.path, value: getSetting(parsed, w.path) })),
180
+ };
181
+ if (existing && original !== null && digest(original) !== state.writtenHash) {
182
+ // Re-enabling must not turn the fast byte-restore path into permission to
183
+ // discard changes made after the previous enable.
184
+ state.original = editAgentConfig(original, agent, state.previous);
185
+ }
186
+ if (existing) {
187
+ state.previous = writes.map(write => state.previous.find(previous =>
188
+ JSON.stringify(previous.path) === JSON.stringify(write.path)) ??
189
+ { path: write.path, value: getSetting(parsed, write.path) });
190
+ }
191
+ const collector = object(raw.collector);
192
+ const agents = object(collector.agents);
193
+ const previousSessions = object(agents[agent]).sessions as boolean | undefined;
194
+ const record: AgentRecord = {
195
+ file, local, content, destination,
196
+ keys: state.previous.map(w => ({
197
+ path: w.path,
198
+ hadPrevious: w.value !== undefined,
199
+ // Prior agent credentials belong in owner-only state, never config.json.
200
+ ...(w.path.some(k => /headers|exporter/i.test(k)) ? { private: true } : { previous: w.value }),
201
+ })),
202
+ previousSessions: existing ? existing.previousSessions : previousSessions,
203
+ switchedSessions: existing ? existing.switchedSessions : transcriptsEnabled,
204
+ };
205
+ if (record.switchedSessions) {
206
+ agents[agent] = { ...object(agents[agent]), sessions: false };
207
+ collector.agents = agents;
208
+ }
209
+ raw.collector = collector;
210
+ const otel = object(raw.otel);
211
+ otel.agents = { ...records(raw), [agent]: record };
212
+ raw.otel = otel;
213
+ configureRelay(raw);
214
+ // Recovery state and one-time backup precede the first agent-file write.
215
+ backupOnce(file, original);
216
+ savePrivate(agent, { ...state, writtenHash: digest(next) });
217
+ try {
218
+ // Publish ownership BEFORE the agent settings so a killed CLI can always
219
+ // disable/restore what it wrote, including the one-path transcript choice.
220
+ saveConfig(raw);
221
+ writeConfigFileAtomic(file, next, { backup: false, mode: 0o600 });
222
+ // Agent files now contain a credential, even if previously world-readable.
223
+ chmodSync(file, 0o600);
224
+ } catch (err) {
225
+ if (original !== null) writeConfigFileAtomic(file, original, { backup: false, mode: state.originalMode });
226
+ else if (existsSync(file)) unlinkSync(file);
227
+ saveConfig(previousConfig);
228
+ throw err;
229
+ }
230
+ const lines = [`${AGENT_LABELS[agent]} OTEL enabled → ${local ? "local relay" : "Cloud"} (${content ? "text capture on" : "no content"}).`];
231
+ if (local && !localFlag) lines.push(`${AGENT_LABELS[agent]} settings cannot carry an auth header; using the local relay automatically.`);
232
+ if (local) lines.push("The daemon picks this up within seconds — no restart. Use `failproofai otel status` to check the relay.");
233
+ const result = await introspectKey(origin, key, options.fetchImpl, 3000);
234
+ if (result.kind === "ok" && result.identity.otelIngest === false) lines.push(OTEL_ORG_OFF);
235
+ await options.onEvent?.("otel_enabled", { agent, local, content });
236
+ return { lines, exitCode: 0, events: [{ name: "otel_enabled", agent, local, content }] };
237
+ }
238
+ function relayListening(port: number): Promise<boolean> {
239
+ return new Promise(resolve => {
240
+ const socket = connect({ host: "127.0.0.1", port });
241
+ const done = (ok: boolean) => { socket.destroy(); resolve(ok); };
242
+ socket.setTimeout(200);
243
+ socket.once("connect", () => done(true));
244
+ socket.once("error", () => done(false));
245
+ socket.once("timeout", () => done(false));
246
+ });
247
+ }
248
+ async function status(options: OtelOptions): Promise<OtelResult> {
249
+ let org = "unknown";
250
+ try {
251
+ const { origin, key } = credential();
252
+ const result = await introspectKey(origin, key, options.fetchImpl, 3000);
253
+ if (result.kind === "ok" && result.identity.otelIngest !== undefined) org = result.identity.otelIngest ? "on" : "off";
254
+ } catch { /* Status works on an unconfigured machine too. */ }
255
+ const all = records(rawConfig());
256
+ const lines = [`Org OTEL ingest: ${org}`];
257
+ for (const agent of OTEL_AGENTS) {
258
+ const record = all[agent];
259
+ let parsed: JsonObject = {};
260
+ let unreadable = false;
261
+ try {
262
+ const file = record?.file ?? agentConfigPath(agent, options.home ?? homedir());
263
+ if (existsSync(file)) parsed = parseAgentConfig(readFileSync(file, "utf8"), agent);
264
+ } catch { unreadable = true; }
265
+ const on = hasExternalOtel(agent, parsed);
266
+ let managed = false;
267
+ if (record && on) {
268
+ try {
269
+ const state = privateState(agent);
270
+ const expected = parseAgentConfig(editAgentConfig(state.original ?? "", agent,
271
+ agentOtelWrites(agent, record.destination, credential().key, record.local, record.content)), agent);
272
+ managed = record.keys.every(k => JSON.stringify(getSetting(parsed, k.path)) === JSON.stringify(getSetting(expected, k.path)));
273
+ } catch { /* Missing recovery state or key. */ }
274
+ }
275
+ const dest = record?.destination ?? getSetting(parsed, agent === "claude" ? ["env", "OTEL_EXPORTER_OTLP_ENDPOINT"] :
276
+ agent === "gemini" ? ["telemetry", "otlpEndpoint"] :
277
+ agent === "copilot" ? ["github.copilot.chat.otel.otlpEndpoint"] : ["otel", "exporter", "otlp-http", "endpoint"]);
278
+ const destination = dest ? `${record?.local ? "local relay" : "Cloud"} (${dest})` : "none";
279
+ lines.push(`${AGENT_LABELS[agent]}: ${unreadable ? "unreadable settings" : on ? managed ? "on by failproofai" : "on, not managed" : "off"} · destination: ${destination} · org: ${org}`);
280
+ }
281
+ let health: JsonObject = {};
282
+ try { health = JSON.parse(readFileSync(otlpHealthFile(), "utf8")) as JsonObject; } catch { /* No relay yet. */ }
283
+ const requested = readConfig().collector.otlp?.enabled === true;
284
+ const live = requested && health.relay === "listening" && await relayListening(readConfig().collector.otlp?.port ?? 4318);
285
+ lines.push(`Relay: ${live ? "listening on 127.0.0.1" : requested ? health.relay === "port_busy" ? "off (port busy)" : "off (daemon not listening)" : "off"} · last upload: ${health.last_upload ?? "none"}`);
286
+ return { lines, exitCode: 0 };
287
+ }
288
+
289
+ export async function runOtelCommand(args: string[], options: OtelOptions = {}): Promise<OtelResult> {
290
+ try {
291
+ const [verb, ...rest] = args;
292
+ const flags = new Set(verb === "enable" ? ["--local", "--no-content", "--yes"] : verb === "env" ? ["--local", "--service"] : []);
293
+ const unexpected = rest.find((arg, i) => arg.startsWith("-") && !flags.has(arg) && rest[i - 1] !== "--service");
294
+ if (unexpected) throw new Error(`Unexpected argument: ${unexpected}`);
295
+ if (verb === "status") {
296
+ if (rest.length) throw new Error("status takes no arguments.");
297
+ return await status(options);
298
+ }
299
+ if (verb === "env") {
300
+ const { origin, key } = credential();
301
+ const index = rest.indexOf("--service");
302
+ const service = index >= 0 ? rest[index + 1] : "my-agent";
303
+ if (!service || service.startsWith("-")) throw new Error("Missing value after --service.");
304
+ const consumed = rest.filter((_, i) => i !== index && i !== index + 1 || index < 0).filter(x => x !== "--local");
305
+ if (consumed.length) throw new Error("env takes only --service <name> and --local.");
306
+ const local = rest.includes("--local");
307
+ const endpoint = local ? `http://127.0.0.1:${readConfig().collector.otlp?.port ?? 4318}` : origin;
308
+ const quote = (s: string) => `'${s.replace(/'/g, "'\\''")}'`;
309
+ const values = {
310
+ OTEL_EXPORTER_OTLP_ENDPOINT: endpoint, OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
311
+ ...(!local ? { OTEL_EXPORTER_OTLP_HEADERS: `Authorization=Bearer%20${encodeURIComponent(key)}` } : {}),
312
+ OTEL_SERVICE_NAME: service,
313
+ };
314
+ return { lines: Object.entries(values).map(([k, v]) => `export ${k}=${quote(v)}`), exitCode: 0 };
315
+ }
316
+ if (verb !== "enable" && verb !== "disable") throw new Error("Expected status, enable, disable or env. Run `failproofai otel --help`.");
317
+ const target = rest[0];
318
+ if (!target || (target !== "all" && !OTEL_AGENTS.includes(target as OtelAgent))) throw new Error(`Choose an agent: ${OTEL_AGENTS.join(", ")}, all.`);
319
+ if (rest.slice(1).some(x => !flags.has(x))) throw new Error("Unexpected positional argument.");
320
+ const agents = target === "all" ? [...OTEL_AGENTS] : [target as OtelAgent];
321
+ const result: OtelResult = { lines: [], exitCode: 0, events: [] };
322
+ for (const agent of agents) {
323
+ if (verb === "enable") {
324
+ const enabled = await enableAgent(agent, rest.includes("--yes"), rest.includes("--local"), !rest.includes("--no-content"), options);
325
+ result.lines.push(...enabled.lines);
326
+ result.events?.push(...enabled.events ?? []);
327
+ } else {
328
+ const raw = rawConfig();
329
+ const record = records(raw)[agent];
330
+ if (!record) { result.lines.push(`${AGENT_LABELS[agent]}: no managed OTEL settings.`); continue; }
331
+ restoreAgent(agent, raw);
332
+ configureRelay(raw);
333
+ saveConfig(raw);
334
+ unlinkSync(otelAgentStateFile(agent));
335
+ result.lines.push(`${AGENT_LABELS[agent]} OTEL disabled; previous settings restored.`);
336
+ await options.onEvent?.("otel_disabled", { agent, local: record.local, content: record.content });
337
+ result.events?.push({ name: "otel_disabled", agent, local: record.local, content: record.content });
338
+ }
339
+ }
340
+ return result;
341
+ } catch (err) {
342
+ return { lines: [err instanceof Error ? err.message : String(err)], exitCode: 1 };
343
+ }
344
+ }
@@ -0,0 +1,24 @@
1
+ import { OTEL_ORG_OFF, type OtelResult } from "./otel-cli";
2
+
3
+ /** Presentation only: writers and shell-ready `env` output keep their contract. */
4
+ export function otelReportLines(verb: string, result: OtelResult): string[] {
5
+ if (result.exitCode !== 0 || verb === "env") return result.lines;
6
+ return result.lines.flatMap(line => {
7
+ if (verb === "status") {
8
+ const glyph = line.includes("unreadable settings") ? "▲" :
9
+ /^[^:]+: (?:on\b|listening\b)/.test(line) ? "●" : "○";
10
+ return [`${glyph} ${line}`];
11
+ }
12
+ if (line === OTEL_ORG_OFF) return [
13
+ "▲ OTEL ingest is off for your org.",
14
+ " An admin can turn it on in Settings → OpenTelemetry.",
15
+ ];
16
+ if (line.startsWith("VS Code not found")) return [
17
+ `▲ ${line}`,
18
+ " Install VS Code or its Copilot extension, then run `failproofai otel enable copilot`.",
19
+ ];
20
+ if (/OTEL enabled|OTEL disabled/.test(line)) return [`✓ ${line}`];
21
+ if (/unchanged|no managed OTEL/.test(line)) return [`○ ${line}`];
22
+ return [` ${line}`];
23
+ });
24
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Agent-owned OTEL configuration. JSONC edits preserve comments; the TOML
3
+ * scanner identifies tables without mistaking a multiline string for one.
4
+ * smol-toml validates the complete document before any modification.
5
+ */
6
+ import { parse as parseToml } from "smol-toml";
7
+ import { applyEdits, modify, parse as parseJsonc, type ParseError } from "jsonc-parser/lib/esm/main.js";
8
+ import { homedir } from "node:os";
9
+ import { resolve } from "node:path";
10
+ import { accessSync, constants, readdirSync, statSync } from "node:fs";
11
+
12
+ export const OTEL_AGENTS = ["claude", "codex", "gemini", "copilot"] as const;
13
+ export type OtelAgent = typeof OTEL_AGENTS[number];
14
+ export const AGENT_LABELS: Record<OtelAgent, string> = {
15
+ claude: "Claude Code", codex: "Codex", gemini: "Gemini CLI", copilot: "Copilot",
16
+ };
17
+ export type JsonObject = Record<string, unknown>;
18
+ export interface SettingWrite { path: string[]; value?: unknown }
19
+
20
+ export function vscodeSettingsPath(home = homedir(), platform = process.platform): string {
21
+ if (platform === "darwin") return resolve(home, "Library/Application Support/Code/User/settings.json");
22
+ if (platform === "win32") return resolve(process.env.APPDATA ?? resolve(home, "AppData/Roaming"), "Code/User/settings.json");
23
+ return resolve(process.env.XDG_CONFIG_HOME ?? resolve(home, ".config"), "Code/User/settings.json");
24
+ }
25
+
26
+ export function agentConfigPath(agent: OtelAgent, home = homedir()): string {
27
+ return agent === "copilot" ? vscodeSettingsPath(home) :
28
+ resolve(home, `.${agent}`, agent === "codex" ? "config.toml" : "settings.json");
29
+ }
30
+
31
+ /**
32
+ * Fixed install locations checked after PATH. A mutable seam so tests can run
33
+ * on a machine that really has VS Code installed: these paths are system-wide,
34
+ * so no temp HOME or PATH hides them.
35
+ */
36
+ export const vscodeProbe = {
37
+ appPaths(home: string, platform: NodeJS.Platform): string[] {
38
+ return platform === "darwin" ? [
39
+ "/Applications/Visual Studio Code.app/Contents/MacOS/Electron",
40
+ "/Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron",
41
+ resolve(home, "Applications/Visual Studio Code.app/Contents/MacOS/Electron"),
42
+ ] : platform === "win32" ? [
43
+ resolve(process.env.LOCALAPPDATA ?? resolve(home, "AppData/Local"), "Programs/Microsoft VS Code/Code.exe"),
44
+ resolve(process.env.ProgramFiles ?? "C:/Program Files", "Microsoft VS Code/Code.exe"),
45
+ ] : ["/usr/share/code/code", "/opt/visual-studio-code/code"];
46
+ },
47
+ };
48
+
49
+ export function vscodeInstalled(home = homedir(), platform = process.platform): boolean {
50
+ const executable = (path: string) => {
51
+ try {
52
+ accessSync(path, platform === "win32" ? constants.F_OK : constants.X_OK);
53
+ return statSync(path).isFile();
54
+ } catch { return false; }
55
+ };
56
+ const names = platform === "win32" ? ["code.cmd", "code-insiders.cmd", "Code.exe", "Code - Insiders.exe"] : ["code", "code-insiders"];
57
+ if ((process.env.PATH ?? "").split(platform === "win32" ? ";" : ":")
58
+ .some(dir => names.some(name => executable(resolve(dir, name))))) return true;
59
+ if (vscodeProbe.appPaths(home, platform).some(executable)) return true;
60
+ return [".vscode", ".vscode-insiders"].some(folder => {
61
+ try {
62
+ return readdirSync(resolve(home, folder, "extensions"), { withFileTypes: true })
63
+ .some(entry => entry.isDirectory() && /^github\.copilot(?:-chat)?-\d/i.test(entry.name));
64
+ } catch { return false; }
65
+ });
66
+ }
67
+
68
+ export function parseAgentConfig(text: string, agent: OtelAgent): JsonObject {
69
+ if (agent === "codex") return parseToml(text) as JsonObject;
70
+ if (!text.trim()) return {};
71
+ const errors: ParseError[] = [];
72
+ const value: unknown = parseJsonc(text, errors, { allowTrailingComma: true });
73
+ if (errors.length || !value || typeof value !== "object" || Array.isArray(value)) {
74
+ throw new Error("Refusing to modify an invalid agent settings object.");
75
+ }
76
+ return value as JsonObject;
77
+ }
78
+
79
+ export function getSetting(object: JsonObject, path: string[]): unknown {
80
+ let value: unknown = object;
81
+ for (const key of path) {
82
+ if (!value || typeof value !== "object") return undefined;
83
+ value = (value as JsonObject)[key];
84
+ }
85
+ return value;
86
+ }
87
+
88
+ function setSetting(object: JsonObject, path: string[], value: unknown): void {
89
+ const [key, ...rest] = path;
90
+ if (!rest.length) {
91
+ if (value === undefined) delete object[key];
92
+ else object[key] = value;
93
+ return;
94
+ }
95
+ const child = object[key];
96
+ if (child !== undefined && (!child || typeof child !== "object" || Array.isArray(child))) {
97
+ throw new Error(`Refusing to replace a non-object ${key} setting.`);
98
+ }
99
+ const nested = (child ?? {}) as JsonObject;
100
+ setSetting(nested, rest, value);
101
+ if (Object.keys(nested).length) object[key] = nested;
102
+ else delete object[key];
103
+ }
104
+
105
+ function tomlValue(value: unknown): string {
106
+ if (typeof value === "string") return JSON.stringify(value);
107
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
108
+ if (value instanceof Date) return value.toISOString();
109
+ if (Array.isArray(value)) return `[${value.map(tomlValue).join(", ")}]`;
110
+ if (value && typeof value === "object") return `{ ${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)} = ${tomlValue(v)}`).join(", ")} }`;
111
+ throw new Error("Unsupported TOML value.");
112
+ }
113
+
114
+ function tables(text: string): { start: number; otel: boolean; rootHeader?: string }[] {
115
+ const result: { start: number; otel: boolean; rootHeader?: string }[] = [];
116
+ let multiline: string | null = null;
117
+ let offset = 0;
118
+ for (const line of text.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
119
+ if (!multiline) {
120
+ const header = /^\s*(\[\[?.*?\]\]?)\s*(?:#.*)?(?:\r?\n)?$/.exec(line);
121
+ if (header) {
122
+ const sample = parseToml(`${header[1]}\n__failproofai_probe = true\n`);
123
+ const root = Object.keys(sample).length === 1 && Object.hasOwn(sample, "otel") &&
124
+ Object.hasOwn(sample.otel as object, "__failproofai_probe");
125
+ result.push({ start: offset, otel: Object.hasOwn(sample, "otel"), rootHeader: root ? line.replace(/\r?\n$/, "") : undefined });
126
+ }
127
+ }
128
+ let quote: string | null = null;
129
+ for (let i = 0; i < line.length; i++) {
130
+ if (multiline) {
131
+ if (line.startsWith(multiline, i) && (multiline === "'''" || line[i - 1] !== "\\")) {
132
+ i += 2; multiline = null;
133
+ }
134
+ } else if (quote) {
135
+ if (quote === '"' && line[i] === "\\") i++;
136
+ else if (line[i] === quote) quote = null;
137
+ } else if (line[i] === "#") break;
138
+ else if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
139
+ multiline = line.slice(i, i + 3); i += 2;
140
+ } else if (line[i] === '"' || line[i] === "'") quote = line[i];
141
+ }
142
+ offset += line.length;
143
+ }
144
+ return result;
145
+ }
146
+
147
+ export function editAgentConfig(text: string, agent: OtelAgent, writes: SettingWrite[]): string {
148
+ const parsed = parseAgentConfig(text, agent);
149
+ if (agent !== "codex") {
150
+ let next = text.trim() ? text : "{}\n";
151
+ for (const write of writes) {
152
+ setSetting(parsed, write.path, write.value);
153
+ next = applyEdits(next, modify(next, write.path, write.value, {
154
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
155
+ }));
156
+ }
157
+ for (const root of ["env", "telemetry"]) {
158
+ if (getSetting(parsed, [root]) === undefined) {
159
+ next = applyEdits(next, modify(next, [root], undefined, {}));
160
+ }
161
+ }
162
+ return next;
163
+ }
164
+ for (const write of writes) setSetting(parsed, write.path, write.value);
165
+ const headers = tables(text);
166
+ if (Object.hasOwn(parseToml(text.slice(0, headers[0]?.start ?? text.length)), "otel")) {
167
+ throw new Error("Codex OTEL must use an [otel] table, not a root dotted or inline assignment.");
168
+ }
169
+ const newline = text.includes("\r\n") ? "\r\n" : "\n";
170
+ const otel = parsed.otel as JsonObject | undefined;
171
+ const rootHeader = headers.find(h => h.rootHeader)?.rootHeader ?? "[otel]";
172
+ const replacement = otel ? `${rootHeader}${newline}${Object.entries(otel).map(([k, v]) => `${JSON.stringify(k)} = ${tomlValue(v)}${newline}`).join("")}` : "";
173
+ let next = text;
174
+ const ranges = headers.map((h, i) => {
175
+ const end = headers[i + 1]?.start ?? text.length;
176
+ // Comments introducing the NEXT table are not part of the table we own.
177
+ const tail = /(?<=\n)(?:[ \t]*(?:#[^\r\n]*)?\r?\n)+$/.exec(text.slice(h.start, end));
178
+ return { ...h, end: tail ? h.start + tail.index : end };
179
+ }).filter(h => h.otel);
180
+ for (let i = ranges.length - 1; i >= 0; i--) {
181
+ const range = ranges[i];
182
+ next = next.slice(0, range.start) + (i === 0 ? replacement : "") + next.slice(range.end);
183
+ }
184
+ if (!ranges.length && replacement) next += `${next && !next.endsWith("\n") ? newline : ""}${replacement}`;
185
+ parseToml(next);
186
+ return next;
187
+ }
188
+
189
+ export function agentOtelWrites(agent: OtelAgent, endpoint: string, key: string, local: boolean, content: boolean): SettingWrite[] {
190
+ const auth = local ? {} : { Authorization: `Bearer ${key}` };
191
+ const env: JsonObject = {
192
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
193
+ OTEL_LOGS_EXPORTER: "otlp", OTEL_METRICS_EXPORTER: "otlp",
194
+ OTEL_EXPORTER_OTLP_ENDPOINT: endpoint, OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
195
+ ...(!local ? { OTEL_EXPORTER_OTLP_HEADERS: `Authorization=Bearer%20${encodeURIComponent(key)}` } : {}),
196
+ OTEL_LOG_USER_PROMPTS: content ? "1" : "0",
197
+ OTEL_LOG_TOOL_DETAILS: content ? "1" : "0",
198
+ // Tool output needs OTEL_LOG_TOOL_CONTENT; assistant responses follow OTEL_LOG_USER_PROMPTS, so omit OTEL_LOG_ASSISTANT_RESPONSES.
199
+ OTEL_LOG_TOOL_CONTENT: content ? "1" : "0",
200
+ };
201
+ if (agent === "claude") {
202
+ for (const signal of ["LOGS", "METRICS"]) {
203
+ env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] = `${endpoint}/v1/${signal.toLowerCase()}`;
204
+ env[`OTEL_EXPORTER_OTLP_${signal}_PROTOCOL`] = "http/protobuf";
205
+ if (!local) env[`OTEL_EXPORTER_OTLP_${signal}_HEADERS`] = env.OTEL_EXPORTER_OTLP_HEADERS;
206
+ }
207
+ return Object.entries(env).map(([k, value]) => ({ path: ["env", k], value }));
208
+ }
209
+ if (agent === "codex") {
210
+ const exporter = (signal: string) => ({ "otlp-http": {
211
+ endpoint: `${endpoint}/v1/${signal}`, protocol: "json", ...(!local ? { headers: auth } : {}),
212
+ } });
213
+ return Object.entries({
214
+ exporter: exporter("logs"), trace_exporter: exporter("traces"), metrics_exporter: exporter("metrics"),
215
+ log_user_prompt: content,
216
+ log_agent_responses: content,
217
+ }).map(([k, value]) => ({ path: ["otel", k], value }));
218
+ }
219
+ if (agent === "gemini") return Object.entries({
220
+ enabled: true, target: "local", otlpEndpoint: endpoint, otlpProtocol: "http", logPrompts: content, useCollector: true, outfile: undefined,
221
+ }).map(([k, value]) => ({ path: ["telemetry", k], value }));
222
+ return Object.entries({
223
+ enabled: true, exporterType: "otlp-http", otlpEndpoint: endpoint, captureContent: content,
224
+ }).map(([k, value]) => ({ path: [`github.copilot.chat.otel.${k}`], value }));
225
+ }