failproofai 0.0.13 → 0.0.14-beta.0

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 (283) hide show
  1. package/.next/standalone/.factory/hooks.json +112 -0
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  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.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  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/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  23. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/index.html +1 -1
  41. package/.next/standalone/.next/server/app/index.rsc +15 -15
  42. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  43. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  44. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  46. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  51. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -3
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +3 -0
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
  70. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +3 -0
  72. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jllheg._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +3 -0
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +3 -3
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/{_1z062h5._.js → _1vslkoe._.js} +2 -2
  93. package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +24 -12
  96. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07iom4f._.js → [root-of-the-server]__07qfsfa._.js} +2 -2
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ge8e7w._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h12me5._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11rtg6s._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +12 -9
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dky4g0._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__212nf49._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/{_0ul5rf_._.js → _1_ozgif._.js} +2 -2
  127. package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  129. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/lib_antigravity-projects_ts_0_6zf11._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/lib_devin-projects_ts_0a64qdy._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/lib_factory-projects_ts_1uyaahq._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/lib_goose-projects_ts_0zkscri._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_13p7zob.js → node_modules_html-to-image_es_index_0g5r64e.js} +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0-uvagv.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03c7gi5.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_09p-8om.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0v-kfiu.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0xrgzyz.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1806lsc.js +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1j6dd-e.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1sa65r-.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  148. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  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 +10 -10
  153. package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 003uz088wbmz7.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 0405htbxay9vd.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/0iinhn4417nk1.js +1 -0
  156. package/.next/standalone/.next/static/chunks/1374x9bv_s_vg.js +6 -0
  157. package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 17sdztgbcc0fi.js} +1 -1
  158. package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 2m_tbl39fcedn.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 2o3ql0n5owqsw.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{0wtagd8a5gwq-.js → 2ufuwhv03yvtb.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/35-90m9py64d4.js +1 -0
  162. package/.next/standalone/.next/static/chunks/3r--yyjkp9_7b.css +2 -0
  163. package/.next/standalone/.next/static/chunks/43weu8f59tlix.js +1 -0
  164. package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 454dyizqik6t9.js} +2 -2
  165. package/.next/standalone/app/api/audit/run/route.ts +2 -0
  166. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -1
  167. package/.next/standalone/app/globals.css +25 -1
  168. package/.next/standalone/app/policies/hooks-client.tsx +2 -4
  169. package/.next/standalone/app/project/[name]/page.tsx +30 -10
  170. package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +71 -21
  171. package/.next/standalone/assets/logos/antigravity.svg +1 -0
  172. package/.next/standalone/assets/logos/devin.svg +1 -0
  173. package/.next/standalone/assets/logos/factory-dark.png +0 -0
  174. package/.next/standalone/assets/logos/factory-light.png +0 -0
  175. package/.next/standalone/assets/logos/goose-dark.svg +1 -0
  176. package/.next/standalone/assets/logos/goose-light.svg +1 -0
  177. package/.next/standalone/assets/logos/openclaw.svg +22 -0
  178. package/.next/standalone/components/navbar.tsx +5 -11
  179. package/.next/standalone/components/reach-developers.tsx +1 -1
  180. package/.next/standalone/lib/antigravity-projects.ts +224 -0
  181. package/.next/standalone/lib/antigravity-sessions.ts +283 -0
  182. package/.next/standalone/lib/cli-registry.ts +26 -6
  183. package/.next/standalone/lib/devin-projects.ts +141 -0
  184. package/.next/standalone/lib/devin-sessions.ts +343 -0
  185. package/.next/standalone/lib/download-session.ts +56 -5
  186. package/.next/standalone/lib/factory-projects.ts +109 -0
  187. package/.next/standalone/lib/factory-sessions.ts +312 -0
  188. package/.next/standalone/lib/goose-projects.ts +148 -0
  189. package/.next/standalone/lib/goose-sessions.ts +296 -0
  190. package/.next/standalone/lib/openclaw-projects.ts +182 -0
  191. package/.next/standalone/lib/openclaw-sessions.ts +284 -0
  192. package/.next/standalone/lib/projects.ts +33 -9
  193. package/.next/standalone/lib/telemetry-sanitize.ts +43 -0
  194. package/.next/standalone/openclaw-plugin/index.js +223 -0
  195. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +6 -0
  196. package/.next/standalone/openclaw-plugin/package.json +12 -0
  197. package/.next/standalone/package.json +2 -1
  198. package/.next/standalone/server.js +1 -1
  199. package/README.md +105 -57
  200. package/bin/failproofai.mjs +87 -29
  201. package/dist/cli.mjs +12803 -9809
  202. package/lib/antigravity-projects.ts +224 -0
  203. package/lib/antigravity-sessions.ts +283 -0
  204. package/lib/cli-registry.ts +26 -6
  205. package/lib/devin-projects.ts +141 -0
  206. package/lib/devin-sessions.ts +343 -0
  207. package/lib/download-session.ts +56 -5
  208. package/lib/factory-projects.ts +109 -0
  209. package/lib/factory-sessions.ts +312 -0
  210. package/lib/goose-projects.ts +148 -0
  211. package/lib/goose-sessions.ts +296 -0
  212. package/lib/openclaw-projects.ts +182 -0
  213. package/lib/openclaw-sessions.ts +284 -0
  214. package/lib/projects.ts +33 -9
  215. package/lib/telemetry-sanitize.ts +43 -0
  216. package/openclaw-plugin/index.js +223 -0
  217. package/openclaw-plugin/openclaw.plugin.json +6 -0
  218. package/openclaw-plugin/package.json +12 -0
  219. package/package.json +2 -1
  220. package/scripts/launch.ts +4 -8
  221. package/scripts/postinstall.mjs +3 -4
  222. package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
  223. package/scripts/translate-docs/cli.ts +73 -52
  224. package/scripts/translate-docs/config.ts +57 -13
  225. package/scripts/translate-docs/mdx-translator.ts +51 -31
  226. package/scripts/translate-docs/mintlify-nav.ts +91 -10
  227. package/scripts/translate-docs/types.ts +3 -0
  228. package/src/audit/cli-adapters/antigravity.ts +87 -0
  229. package/src/audit/cli-adapters/devin.ts +53 -0
  230. package/src/audit/cli-adapters/factory.ts +75 -0
  231. package/src/audit/cli-adapters/goose.ts +53 -0
  232. package/src/audit/cli-adapters/index.ts +30 -6
  233. package/src/audit/cli-adapters/openclaw.ts +56 -0
  234. package/src/audit/cli.ts +44 -1
  235. package/src/audit/report.ts +1 -1
  236. package/src/audit/types.ts +1 -1
  237. package/src/hooks/builtin-policies.ts +5 -5
  238. package/src/hooks/configure-wizard.ts +356 -0
  239. package/src/hooks/handler.ts +283 -218
  240. package/src/hooks/hook-telemetry.ts +43 -3
  241. package/src/hooks/install-prompt.ts +92 -173
  242. package/src/hooks/integrations.ts +739 -138
  243. package/src/hooks/manager.ts +46 -2
  244. package/src/hooks/policy-evaluator.ts +301 -121
  245. package/src/hooks/policy-presets.ts +71 -0
  246. package/src/hooks/resolve-cwd.ts +1 -1
  247. package/src/hooks/resolve-permission-mode.ts +1 -5
  248. package/src/hooks/resolve-transcript-path.ts +25 -7
  249. package/src/hooks/tool-name-canonicalize.ts +36 -3
  250. package/src/hooks/tui.ts +578 -0
  251. package/src/hooks/types.ts +447 -109
  252. package/.next/standalone/.gemini/settings.json +0 -147
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cag8qd._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hgv_75._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +0 -3
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +0 -3
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +0 -3
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +0 -4
  264. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__108slqn._.js +0 -3
  265. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +0 -3
  266. package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +0 -3
  267. package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_1c7bgx-._.js +0 -3
  268. package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +0 -1
  269. package/.next/standalone/.next/static/chunks/0nrah-qv6smeg.js +0 -6
  270. package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +0 -1
  271. package/.next/standalone/.next/static/chunks/25qr0e6ez0ei0.js +0 -1
  272. package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +0 -2
  273. package/.next/standalone/assets/logos/gemini-dark.svg +0 -13
  274. package/.next/standalone/assets/logos/gemini-light.svg +0 -13
  275. package/.next/standalone/lib/gemini-projects.ts +0 -243
  276. package/.next/standalone/lib/gemini-sessions.ts +0 -365
  277. package/lib/gemini-projects.ts +0 -243
  278. package/lib/gemini-sessions.ts +0 -365
  279. package/src/audit/cli-adapters/gemini.ts +0 -51
  280. package/src/hooks/first-run-nudge.ts +0 -146
  281. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_buildManifest.js +0 -0
  282. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_clientMiddlewareManifest.js +0 -0
  283. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_ssgManifest.js +0 -0
@@ -0,0 +1,578 @@
1
+ /**
2
+ * Minimal clack-style TUI primitives for the `failproofai config` launcher,
3
+ * dressed in the befailproof.ai identity: the pixel logomark opens the flow, and
4
+ * the palette is pink-forward like the site — pink drives selection/enabled,
5
+ * teal stays the flower on the mark and the step spine.
6
+ *
7
+ * A single continuous flow with a left gutter (│) threading through step nodes:
8
+ * the active step shows as ◆, answered steps collapse to a persistent ◇ log
9
+ * line, and the run ends on a └ outro. Two interactive prompts — `selectOne`
10
+ * (radio) and `multiSelect` (checklist, windowed with a caret) — plus
11
+ * `intro` / `outro`.
12
+ *
13
+ * Each prompt owns only its own render region (cursor-up + clear-to-end
14
+ * repaint) and, on resolve, collapses that region to a one-line summary that
15
+ * stays on screen — so the next prompt simply appends below, building the log.
16
+ * No external dependencies. Honors NO_COLOR and non-TTY (returns the default
17
+ * without drawing), and uses 24-bit color where COLORTERM advertises it,
18
+ * falling back to the nearest basic ANSI hue otherwise.
19
+ */
20
+ import * as readline from "node:readline";
21
+
22
+ export type TTYIn = NodeJS.ReadableStream & {
23
+ isTTY?: boolean;
24
+ setRawMode?: (mode: boolean) => void;
25
+ isRaw?: boolean;
26
+ };
27
+ export type TTYOut = NodeJS.WritableStream & { isTTY?: boolean; columns?: number };
28
+
29
+ export interface SelectChoice<T> {
30
+ label: string;
31
+ value: T;
32
+ hint?: string;
33
+ section?: string;
34
+ }
35
+
36
+ export interface SelectOneOptions<T> {
37
+ message: string;
38
+ choices: SelectChoice<T>[];
39
+ /** Static info lines rendered under the question (e.g. a review summary). */
40
+ body?: string[];
41
+ stdin?: TTYIn;
42
+ stdout?: TTYOut;
43
+ }
44
+
45
+ export interface MultiChoice<T> {
46
+ label: string;
47
+ value: T;
48
+ hint?: string;
49
+ checked?: boolean;
50
+ section?: string;
51
+ }
52
+
53
+ export interface MultiSelectOptions<T> {
54
+ message: string;
55
+ choices: MultiChoice<T>[];
56
+ minSelected?: number;
57
+ hint?: string;
58
+ /** Noun used when collapsing many selections to a count (e.g. "assistants"). */
59
+ summaryNoun?: string;
60
+ stdin?: TTYIn;
61
+ stdout?: TTYOut;
62
+ }
63
+
64
+ const ESC = "\x1B";
65
+
66
+ // ── glyphs ────────────────────────────────────────────────────────────────
67
+ // Exported so the other branded prompts (install-prompt.ts) share the exact
68
+ // same set instead of hand-syncing copies.
69
+ export const BAR = "│";
70
+ const BAR_END = "└";
71
+ export const STEP_ACTIVE = "◆";
72
+ export const STEP_DONE = "◇";
73
+ const RADIO_ON = "●";
74
+ const RADIO_OFF = "○";
75
+ export const CHECK_ON = "◼";
76
+ export const CHECK_OFF = "◻";
77
+ export const CARET = "❯";
78
+ const FLOWER = "❋";
79
+
80
+ // ── color ─────────────────────────────────────────────────────────────────
81
+ // The single source of truth for the brand palette — exported (via `paint`)
82
+ // so the other branded prompts (install-prompt.ts) reuse it instead of
83
+ // re-deriving their own copies.
84
+ interface Hue {
85
+ rgb: [number, number, number];
86
+ basic: string;
87
+ }
88
+ const HUES = {
89
+ guide: { rgb: [102, 209, 181], basic: "36" }, // teal — the policy flower / step spine
90
+ pink: { rgb: [255, 46, 136], basic: "95" }, // hot pink — selection, enabled, the brand
91
+ logoPink: { rgb: [228, 88, 124], basic: "95" }, // the softer artwork pink of the logomark
92
+ warn: { rgb: [227, 179, 65], basic: "33" },
93
+ dim: { rgb: [107, 118, 132], basic: "2" },
94
+ } satisfies Record<string, Hue>;
95
+
96
+ export function colorsEnabled(out: TTYOut): boolean {
97
+ return !!out.isTTY && !process.env.NO_COLOR;
98
+ }
99
+ function truecolorEnabled(): boolean {
100
+ return /truecolor|24bit/i.test(process.env.COLORTERM || "");
101
+ }
102
+
103
+ /** Brand painter: role-named color functions, truecolor where advertised,
104
+ * basic-ANSI fallback otherwise, identity when `on` is false. */
105
+ export function paint(on: boolean) {
106
+ const tc = on && truecolorEnabled();
107
+ const mk =
108
+ (h: Hue, bold = false) =>
109
+ (s: string): string => {
110
+ if (!on) return s;
111
+ const code = tc ? `38;2;${h.rgb[0]};${h.rgb[1]};${h.rgb[2]}` : h.basic;
112
+ return `${ESC}[${bold ? "1;" : ""}${code}m${s}${ESC}[0m`;
113
+ };
114
+ return {
115
+ bold: (s: string) => (on ? `${ESC}[1m${s}${ESC}[0m` : s),
116
+ dim: mk(HUES.dim),
117
+ guide: mk(HUES.guide),
118
+ pink: mk(HUES.pink),
119
+ pinkBold: mk(HUES.pink, true),
120
+ softPink: mk(HUES.logoPink),
121
+ warn: mk(HUES.warn),
122
+ };
123
+ }
124
+
125
+ // ── brand logo (befailproof.ai logomark) ────────────────────────────────────
126
+ // Half-block rendition of the real site mark — the teal flower, the pink cross,
127
+ // and the tall bar joined at the base — downscaled from the actual artwork so it
128
+ // stays faithful at terminal size. Each character cell packs two vertical pixels
129
+ // (▀ top, ▄ bottom); `t` = teal, `p` = pink, `.` = transparent. Shown when
130
+ // there's room, else a compact one-liner.
131
+ const LOGO_MIN_COLS = 22;
132
+ const TAGLINE = "end-to-end failure layer for ai agents";
133
+
134
+ const LOGO_GRID = [
135
+ "...tt...........",
136
+ "..tttt..........",
137
+ ".tttttt.........",
138
+ ".tttttt.....ppp.",
139
+ "..tttt......ppp.",
140
+ "...tt.......ppp.",
141
+ "............ppp.",
142
+ "............ppp.",
143
+ "..pppp......ppp.",
144
+ "..pppp......ppp.",
145
+ "..pppp......ppp.",
146
+ ".pppppp.....ppp.",
147
+ ".pppppp.....ppp.",
148
+ ".pppppp.....ppp.",
149
+ ".pppppp.....ppp.",
150
+ "..pppp......ppp.",
151
+ "..pppp......ppp.",
152
+ "..pppp......ppp.",
153
+ "..ppppppppppppp.",
154
+ "..ppppppppppppp.",
155
+ "..ppppppppppppp.",
156
+ "..ppppppppppppp.",
157
+ ];
158
+ // Derived from the shared HUES table so a palette tweak needs one edit.
159
+ const LOGO_TEAL: [number, number, number] = HUES.guide.rgb;
160
+ const LOGO_PINK: [number, number, number] = HUES.logoPink.rgb;
161
+
162
+ /** Render the logomark as half-block art. When `colorize` is false (no truecolor
163
+ * / NO_COLOR) the shape still prints, just monochrome. */
164
+ function renderLogo(colorize: boolean): string[] {
165
+ const pad = ".".repeat(LOGO_GRID[0]?.length ?? 0);
166
+ const rgb = (ch: string): [number, number, number] | null =>
167
+ ch === "t" ? LOGO_TEAL : ch === "p" ? LOGO_PINK : null;
168
+ const lines: string[] = [];
169
+ for (let r = 0; r < LOGO_GRID.length; r += 2) {
170
+ const top = LOGO_GRID[r];
171
+ const bot = LOGO_GRID[r + 1] ?? pad;
172
+ let line = "";
173
+ for (let x = 0; x < top.length; x++) {
174
+ const t = rgb(top[x]);
175
+ const b = rgb(bot[x]);
176
+ if (!t && !b) {
177
+ line += " ";
178
+ } else if (!colorize) {
179
+ line += t && b ? "█" : t ? "▀" : "▄";
180
+ } else if (t && b) {
181
+ line +=
182
+ t === b
183
+ ? `${ESC}[38;2;${t.join(";")}m█${ESC}[0m`
184
+ : `${ESC}[38;2;${t.join(";")};48;2;${b.join(";")}m▀${ESC}[0m`;
185
+ } else if (t) {
186
+ line += `${ESC}[38;2;${t.join(";")}m▀${ESC}[0m`;
187
+ } else {
188
+ line += `${ESC}[38;2;${b!.join(";")}m▄${ESC}[0m`;
189
+ }
190
+ }
191
+ lines.push(line);
192
+ }
193
+ return lines;
194
+ }
195
+
196
+ // ── text helpers ─────────────────────────────────────────────────────────────
197
+
198
+ /** Truncate a line to `width` visual columns, skipping ANSI CSI sequences.
199
+ * Exported so install-prompt.ts shares it instead of keeping local copies. */
200
+ export function truncate(line: string, width: number): string {
201
+ let visual = 0;
202
+ let out = "";
203
+ let i = 0;
204
+ while (i < line.length) {
205
+ if (line[i] === ESC && line[i + 1] === "[") {
206
+ let j = i + 2;
207
+ while (j < line.length && !/[A-Za-z]/.test(line[j])) j++;
208
+ j++;
209
+ out += line.slice(i, j);
210
+ i = j;
211
+ } else {
212
+ if (visual >= width) break;
213
+ out += line[i];
214
+ visual++;
215
+ i++;
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+
221
+ /** Truncate PLAIN text to `width`, ending on a single ellipsis rather than a
222
+ * mid-word hard cut. Assumes no ANSI inside `text` (hints/labels are plain). */
223
+ export function ellipsize(text: string, width: number): string {
224
+ if (width <= 0) return "";
225
+ if (text.length <= width) return text;
226
+ return text.slice(0, width - 1).trimEnd() + "…";
227
+ }
228
+
229
+ /** Collapse many labels to a readable summary: a full join for a few, a
230
+ * `N noun · a, b, c +K` count for many. */
231
+ export function summarize(labels: string[], noun = "selected"): string {
232
+ if (labels.length === 0) return "none";
233
+ if (labels.length <= 3) return labels.join(", ");
234
+ const head = labels.slice(0, 3).join(", ");
235
+ return `${labels.length} ${noun} · ${head} +${labels.length - 3}`;
236
+ }
237
+
238
+ function writeLines(out: TTYOut, lines: string[]): void {
239
+ const cols = out.columns || 80;
240
+ out.write(lines.map((l) => (l === "" ? l : truncate(l, cols))).join("\n") + "\n");
241
+ }
242
+
243
+ // ── framing ─────────────────────────────────────────────────────────────────
244
+
245
+ /** The logomark + wordmark + tagline block (with a 2-space margin), color-aware.
246
+ * Shared by the wizard intro and the dashboard launch banner. On a too-narrow
247
+ * target it collapses to a single compact line. */
248
+ export function renderBrandLogo(stdout: TTYOut = process.stdout): string[] {
249
+ const c = paint(colorsEnabled(stdout));
250
+ const cols = stdout.columns || 80;
251
+ if (cols < LOGO_MIN_COLS) {
252
+ return [`${c.guide(FLOWER)} fa${c.pink("il")}proof ai ${c.dim("· " + TAGLINE)}`];
253
+ }
254
+ const tc = colorsEnabled(stdout) && truecolorEnabled();
255
+ const lines = renderLogo(tc).map((l) => ` ${l}`);
256
+ lines.push("");
257
+ lines.push(` fa${c.pink("il")}proof ai`);
258
+ lines.push(` ${c.dim(TAGLINE)}`);
259
+ return lines;
260
+ }
261
+
262
+ /** Print the flow header — the brand logo, tagline, and the opening step. */
263
+ export function intro(message: string, stdout: TTYOut = process.stdout): void {
264
+ if (!stdout.isTTY) return;
265
+ const c = paint(colorsEnabled(stdout));
266
+ const lines: string[] = ["", ...renderBrandLogo(stdout)];
267
+ lines.push(c.dim(BAR));
268
+ lines.push(`${c.guide(STEP_ACTIVE)} ${c.bold(message)}`);
269
+ writeLines(stdout, lines);
270
+ }
271
+
272
+ /** The branded splash the dashboard prints on launch — logomark, wordmark, and a
273
+ * tidy version/links column. Returned as ready-to-print lines (caller writes
274
+ * them). Degrades to plain text off a TTY so it stays clean in piped logs. */
275
+ export function renderLaunchBanner(version: string, stdout: TTYOut = process.stdout): string[] {
276
+ // paint() is identity when colors are off, so the link rows are built once
277
+ // and only the header block differs between TTY and piped output.
278
+ const c = paint(colorsEnabled(stdout));
279
+ const row = (label: string, value: string) => ` ${c.guide(label.padEnd(9))}${value}`;
280
+ const header = stdout.isTTY
281
+ ? renderBrandLogo(stdout)
282
+ : [" failproof ai", ` ${TAGLINE}`];
283
+ return [
284
+ "",
285
+ ...header,
286
+ "",
287
+ row("version", c.pink(version)),
288
+ row("star", c.dim("https://github.com/failproofai/failproofai")),
289
+ row("docs", c.dim("https://docs.befailproof.ai/introduction")),
290
+ row("discord", c.dim("https://discord.gg/2zjBZP7yQJ")),
291
+ "",
292
+ ];
293
+ }
294
+
295
+ /** Close the flow with a terminating └ line — pink on success, dim on cancel. */
296
+ export function outro(
297
+ message: string,
298
+ opts: { ok?: boolean } = {},
299
+ stdout: TTYOut = process.stdout,
300
+ ): void {
301
+ const c = paint(colorsEnabled(stdout));
302
+ const ok = opts.ok !== false;
303
+ if (!stdout.isTTY) {
304
+ stdout.write(message + "\n");
305
+ return;
306
+ }
307
+ const end = ok ? c.pink(BAR_END) : c.dim(BAR_END);
308
+ const text = ok ? c.pink(message) : c.dim(message);
309
+ writeLines(stdout, [c.dim(BAR), `${end} ${text}`]);
310
+ }
311
+
312
+ // ── shared render engine ─────────────────────────────────────────────────────
313
+
314
+ type Region = { lastCount: number };
315
+
316
+ const WINDOW = 8; // visible rows before the checklist scrolls
317
+
318
+ function repaint(out: TTYOut, region: Region, lines: string[]): void {
319
+ if (region.lastCount > 0) out.write(`${ESC}[${region.lastCount}A${ESC}[J`);
320
+ writeLines(out, lines);
321
+ region.lastCount = lines.length;
322
+ }
323
+
324
+ function hideCursor(out: TTYOut): void {
325
+ out.write(`${ESC}[?25l`);
326
+ }
327
+ function showCursor(out: TTYOut): void {
328
+ out.write(`${ESC}[?25h`);
329
+ }
330
+
331
+ /** Shared width for the label column so hints align into a second column. */
332
+ function nameWidth(labels: string[]): number {
333
+ return Math.min(24, Math.max(6, ...labels.map((l) => l.length)));
334
+ }
335
+
336
+ type DisplayRow = { kind: "header"; text: string } | { kind: "item"; index: number };
337
+
338
+ /** Flatten choices into section-header + item display rows. */
339
+ function displayRows(choices: Array<{ section?: string }>): DisplayRow[] {
340
+ const rows: DisplayRow[] = [];
341
+ let lastSection: string | undefined;
342
+ choices.forEach((choice, index) => {
343
+ if (choice.section && choice.section !== lastSection) {
344
+ lastSection = choice.section;
345
+ rows.push({ kind: "header", text: choice.section });
346
+ }
347
+ rows.push({ kind: "item", index });
348
+ });
349
+ return rows;
350
+ }
351
+
352
+ /** Compute a viewport window over display rows, centred on the cursor row. */
353
+ function viewport(rows: DisplayRow[], cursorRow: number, window: number) {
354
+ if (rows.length <= window) return { start: 0, end: rows.length };
355
+ let start = cursorRow - Math.floor(window / 2);
356
+ start = Math.max(0, Math.min(start, rows.length - window));
357
+ return { start, end: start + window };
358
+ }
359
+
360
+ // ── shared prompt engine ──────────────────────────────────────────────────────
361
+ // One copy of the raw-mode keypress loop, viewport frame, and ◇ collapse shared
362
+ // by selectOne and multiSelect — the two prompts differ only in row glyphs and
363
+ // non-navigation key handling.
364
+
365
+ interface PromptSpec<R> {
366
+ stdin: TTYIn;
367
+ stdout: TTYOut;
368
+ message: string;
369
+ c: ReturnType<typeof paint>;
370
+ /** Static info lines rendered under the question. */
371
+ body?: string[];
372
+ choices: Array<{ label: string; section?: string }>;
373
+ /** Row content after the gutter, e.g. "● Label hint". */
374
+ renderRow: (index: number, active: boolean, budget: number) => string;
375
+ /** Extra line(s) above the footer (e.g. a min-selected warning). */
376
+ warnLine?: () => string | null;
377
+ footer: string;
378
+ /** Handle non-navigation keys. `{done}` finishes, `"redraw"` repaints. */
379
+ onKey: (key: readline.Key, cursor: number) => { done: R } | "redraw" | undefined;
380
+ /** One-line ◇ summary for the collapsed log entry. */
381
+ summaryFor: (result: R | null) => string;
382
+ }
383
+
384
+ function runPrompt<R>(p: PromptSpec<R>): Promise<R | null> {
385
+ const { stdin, stdout, c, choices } = p;
386
+ const region: Region = { lastCount: 0 };
387
+ const nameCol = nameWidth(choices.map((ch) => ch.label));
388
+ let cursor = 0;
389
+
390
+ const build = (): string[] => {
391
+ const cols = stdout.columns || 80;
392
+ const lines: string[] = [c.dim(BAR), `${c.guide(STEP_ACTIVE)} ${c.bold(p.message)}`];
393
+ for (const b of p.body ?? []) lines.push(`${c.dim(BAR)} ${c.dim(b)}`);
394
+
395
+ const rows = displayRows(choices);
396
+ let cursorRow = 0;
397
+ rows.forEach((r, ri) => {
398
+ if (r.kind === "item" && r.index === cursor) cursorRow = ri;
399
+ });
400
+ const { start, end } = viewport(rows, cursorRow, WINDOW);
401
+ const above = rows.slice(0, start).filter((r) => r.kind === "item").length;
402
+ const below = rows.slice(end).filter((r) => r.kind === "item").length;
403
+ if (above > 0) lines.push(`${c.dim(BAR)} ${c.dim(`↑ ${above} more`)}`);
404
+
405
+ const budget = Math.max(6, cols - nameCol - 10);
406
+ for (let ri = start; ri < end; ri++) {
407
+ const row = rows[ri];
408
+ if (row.kind === "header") {
409
+ lines.push(`${c.dim(BAR)} ${c.dim(row.text)}`);
410
+ } else {
411
+ lines.push(`${c.dim(BAR)} ${p.renderRow(row.index, row.index === cursor, budget)}`);
412
+ }
413
+ }
414
+ if (below > 0) lines.push(`${c.dim(BAR)} ${c.dim(`↓ ${below} more`)}`);
415
+
416
+ const warn = p.warnLine?.();
417
+ if (warn) lines.push(`${c.dim(BAR)} ${warn}`);
418
+ lines.push(`${c.dim(BAR)} ${c.dim(p.footer)}`);
419
+ return lines;
420
+ };
421
+
422
+ const collapse = (result: R | null): void => {
423
+ repaint(stdout, region, [
424
+ c.dim(BAR),
425
+ `${c.dim(STEP_DONE)} ${p.message}`,
426
+ `${c.dim(BAR)} ${c.dim(p.summaryFor(result))}`,
427
+ ]);
428
+ };
429
+
430
+ return new Promise<R | null>((resolve) => {
431
+ hideCursor(stdout);
432
+ repaint(stdout, region, build());
433
+ readline.emitKeypressEvents(stdin);
434
+ const wasRaw = stdin.isRaw;
435
+ stdin.setRawMode?.(true);
436
+ stdin.resume();
437
+
438
+ const cleanup = () => {
439
+ stdin.removeListener("keypress", onKey);
440
+ stdin.setRawMode?.(wasRaw ?? false);
441
+ stdin.pause();
442
+ showCursor(stdout);
443
+ };
444
+ const finish = (result: R | null) => {
445
+ cleanup();
446
+ collapse(result);
447
+ resolve(result);
448
+ };
449
+
450
+ function onKey(_s: string | undefined, key: readline.Key): void {
451
+ if (!key) return;
452
+ if ((key.ctrl && (key.name === "c" || key.name === "d")) || key.name === "escape") {
453
+ finish(null);
454
+ } else if (key.name === "up") {
455
+ cursor = cursor > 0 ? cursor - 1 : choices.length - 1;
456
+ repaint(stdout, region, build());
457
+ } else if (key.name === "down") {
458
+ cursor = cursor < choices.length - 1 ? cursor + 1 : 0;
459
+ repaint(stdout, region, build());
460
+ } else {
461
+ const outcome = p.onKey(key, cursor);
462
+ if (outcome === "redraw") repaint(stdout, region, build());
463
+ else if (outcome) finish(outcome.done);
464
+ }
465
+ }
466
+
467
+ stdin.on("keypress", onKey);
468
+ });
469
+ }
470
+
471
+ // ── selectOne (radio) ─────────────────────────────────────────────────────────
472
+
473
+ export function selectOne<T>(opts: SelectOneOptions<T>): Promise<T | null> {
474
+ const stdin: TTYIn = opts.stdin ?? process.stdin;
475
+ const stdout: TTYOut = opts.stdout ?? process.stdout;
476
+ const choices = opts.choices;
477
+
478
+ // Guard empty choices before the TTY branch too — otherwise the Enter handler
479
+ // dereferences choices[0].value on a TTY. Matches the non-TTY null behavior.
480
+ if (!choices.length) return Promise.resolve(null);
481
+
482
+ if (!stdin.isTTY || !stdout.isTTY) {
483
+ return Promise.resolve(choices[0].value);
484
+ }
485
+
486
+ const c = paint(colorsEnabled(stdout));
487
+ const nameCol = nameWidth(choices.map((ch) => ch.label));
488
+
489
+ return runPrompt<T>({
490
+ stdin,
491
+ stdout,
492
+ message: opts.message,
493
+ c,
494
+ body: opts.body,
495
+ choices,
496
+ renderRow: (index, active, budget) => {
497
+ const choice = choices[index];
498
+ const dot = active ? c.pink(RADIO_ON) : c.dim(RADIO_OFF);
499
+ const rawLabel = choice.label.padEnd(nameCol);
500
+ const label = active ? c.pinkBold(rawLabel) : rawLabel;
501
+ const hint = choice.hint ? ` ${c.dim(ellipsize(choice.hint, budget))}` : "";
502
+ return `${dot} ${label}${hint}`;
503
+ },
504
+ footer: "↑/↓ navigate · enter to select · esc to cancel",
505
+ onKey: (key, cursor) =>
506
+ key.name === "return" ? { done: choices[cursor].value } : undefined,
507
+ summaryFor: (value) =>
508
+ value === null
509
+ ? "cancelled"
510
+ : (choices.find((ch) => ch.value === value)?.label ?? String(value)),
511
+ });
512
+ }
513
+
514
+ // ── multiSelect (checklist) ────────────────────────────────────────────────────
515
+
516
+ export function multiSelect<T>(opts: MultiSelectOptions<T>): Promise<T[] | null> {
517
+ const stdin: TTYIn = opts.stdin ?? process.stdin;
518
+ const stdout: TTYOut = opts.stdout ?? process.stdout;
519
+ const choices = opts.choices;
520
+ const minSelected = opts.minSelected ?? 0;
521
+ const noun = opts.summaryNoun ?? "selected";
522
+ const checked = choices.map((ch) => !!ch.checked);
523
+
524
+ if (!stdin.isTTY || !stdout.isTTY) {
525
+ return Promise.resolve(choices.filter((_, i) => checked[i]).map((ch) => ch.value));
526
+ }
527
+
528
+ const c = paint(colorsEnabled(stdout));
529
+ const nameCol = nameWidth(choices.map((ch) => ch.label));
530
+ let warn = false;
531
+
532
+ return runPrompt<T[]>({
533
+ stdin,
534
+ stdout,
535
+ message: opts.message,
536
+ c,
537
+ choices,
538
+ renderRow: (index, active, budget) => {
539
+ const choice = choices[index];
540
+ const caret = active ? c.pink(CARET) : " ";
541
+ const box = checked[index] ? c.pink(CHECK_ON) : c.dim(CHECK_OFF);
542
+ const rawLabel = choice.label.padEnd(nameCol);
543
+ const label = active ? c.pinkBold(rawLabel) : checked[index] ? rawLabel : c.dim(rawLabel);
544
+ const hint = choice.hint ? ` ${c.dim(ellipsize(choice.hint, budget))}` : "";
545
+ return `${caret} ${box} ${label}${hint}`;
546
+ },
547
+ warnLine: () => (warn ? c.warn(`Select at least ${minSelected}.`) : null),
548
+ footer: opts.hint ?? "↑/↓ move · space select · ctrl+a all · enter confirm",
549
+ onKey: (key, cursor) => {
550
+ if (key.name === "space") {
551
+ checked[cursor] = !checked[cursor];
552
+ warn = false;
553
+ return "redraw";
554
+ }
555
+ if (key.ctrl && key.name === "a") {
556
+ const allOn = checked.every(Boolean);
557
+ for (let i = 0; i < checked.length; i++) checked[i] = !allOn;
558
+ return "redraw";
559
+ }
560
+ if (key.name === "return") {
561
+ const selected = choices.filter((_, i) => checked[i]).map((ch) => ch.value);
562
+ if (selected.length < minSelected) {
563
+ warn = true;
564
+ return "redraw";
565
+ }
566
+ return { done: selected };
567
+ }
568
+ return undefined;
569
+ },
570
+ summaryFor: (values) =>
571
+ values === null
572
+ ? "cancelled"
573
+ : summarize(
574
+ choices.filter((ch) => values.includes(ch.value)).map((ch) => ch.label),
575
+ noun,
576
+ ),
577
+ });
578
+ }