failproofai 0.0.13 → 0.0.14-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 (310) hide show
  1. package/.next/standalone/.codex/hooks.json +6 -6
  2. package/.next/standalone/.cursor/hooks.json +6 -6
  3. package/.next/standalone/.factory/hooks.json +112 -0
  4. package/.next/standalone/.next/BUILD_ID +1 -1
  5. package/.next/standalone/.next/build-manifest.json +5 -5
  6. package/.next/standalone/.next/prerender-manifest.json +3 -3
  7. package/.next/standalone/.next/required-server-files.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  9. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  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.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/index.html +1 -1
  46. package/.next/standalone/.next/server/app/index.rsc +15 -15
  47. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  48. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  49. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  50. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  51. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  52. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  58. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
  64. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jllheg._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +3 -0
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{_1z062h5._.js → _1vslkoe._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +24 -12
  105. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__108slqn._.js → [root-of-the-server]__0brjczq._.js} +2 -2
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07iom4f._.js → [root-of-the-server]__0wp9w6i._.js} +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +12 -9
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +4 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
  129. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/{_0ul5rf_._.js → _1_ozgif._.js} +2 -2
  133. package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/app_087bt9w._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.js +1 -1
  137. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  138. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/lib_antigravity-projects_ts_0_6zf11._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/lib_devin-projects_ts_0a64qdy._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/lib_factory-projects_ts_1uyaahq._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/lib_goose-projects_ts_0zkscri._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +3 -0
  146. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_13p7zob.js → node_modules_html-to-image_es_index_12rjfea.js} +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  150. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  151. package/.next/standalone/.next/server/pages/404.html +1 -1
  152. package/.next/standalone/.next/server/pages/500.html +1 -1
  153. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  155. package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 0d5lypu1zs21r.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/0emg30_6svycu.js +1 -0
  158. package/.next/standalone/.next/static/chunks/0o9wpnze1ey8y.js +6 -0
  159. package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 1359o5785rmve.js} +2 -2
  160. package/.next/standalone/.next/static/chunks/20tuctw38jbgw.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 2ldhnaweu9ew3.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 2vav_x0_336ds.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/3-ogr79xqbw0h.js +1 -0
  164. package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
  165. package/.next/standalone/.next/static/chunks/3cg7x0joi2b98.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 3k87p-tijud-4.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 3kmaqcp7rldad.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
  169. package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
  170. package/.next/standalone/app/api/audit/run/route.ts +2 -0
  171. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -1
  172. package/.next/standalone/app/components/global-error-listeners.tsx +27 -0
  173. package/.next/standalone/app/globals.css +25 -1
  174. package/.next/standalone/app/policies/hooks-client.tsx +2 -4
  175. package/.next/standalone/app/project/[name]/page.tsx +30 -10
  176. package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +71 -21
  177. package/.next/standalone/assets/logos/antigravity.svg +1 -0
  178. package/.next/standalone/assets/logos/devin.svg +1 -0
  179. package/.next/standalone/assets/logos/factory-dark.png +0 -0
  180. package/.next/standalone/assets/logos/factory-light.png +0 -0
  181. package/.next/standalone/assets/logos/goose-dark.svg +1 -0
  182. package/.next/standalone/assets/logos/goose-light.svg +1 -0
  183. package/.next/standalone/assets/logos/openclaw.svg +22 -0
  184. package/.next/standalone/ci/cli-integration/Dockerfile +47 -0
  185. package/.next/standalone/ci/cli-integration/README.md +69 -0
  186. package/.next/standalone/ci/cli-integration/canary-policies.mjs +45 -0
  187. package/.next/standalone/ci/cli-integration/capture-tokens.sh +48 -0
  188. package/.next/standalone/ci/cli-integration/inject-tokens.sh +44 -0
  189. package/.next/standalone/ci/cli-integration/install-clis.sh +91 -0
  190. package/.next/standalone/ci/cli-integration/probe-cli.sh +208 -0
  191. package/.next/standalone/ci/cli-integration/report.js +74 -0
  192. package/.next/standalone/ci/cli-integration/run.sh +107 -0
  193. package/.next/standalone/components/navbar.tsx +5 -11
  194. package/.next/standalone/components/reach-developers.tsx +2 -2
  195. package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
  196. package/.next/standalone/lib/antigravity-projects.ts +224 -0
  197. package/.next/standalone/lib/antigravity-sessions.ts +283 -0
  198. package/.next/standalone/lib/cli-registry.ts +26 -6
  199. package/.next/standalone/lib/devin-projects.ts +141 -0
  200. package/.next/standalone/lib/devin-sessions.ts +343 -0
  201. package/.next/standalone/lib/download-session.ts +56 -5
  202. package/.next/standalone/lib/error-origin.ts +67 -0
  203. package/.next/standalone/lib/factory-projects.ts +109 -0
  204. package/.next/standalone/lib/factory-sessions.ts +312 -0
  205. package/.next/standalone/lib/goose-projects.ts +148 -0
  206. package/.next/standalone/lib/goose-sessions.ts +296 -0
  207. package/.next/standalone/lib/install-check.ts +231 -0
  208. package/.next/standalone/lib/openclaw-projects.ts +182 -0
  209. package/.next/standalone/lib/openclaw-sessions.ts +284 -0
  210. package/.next/standalone/lib/projects.ts +33 -9
  211. package/.next/standalone/lib/telemetry-sanitize.ts +43 -0
  212. package/.next/standalone/openclaw-plugin/index.js +223 -0
  213. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +6 -0
  214. package/.next/standalone/openclaw-plugin/package.json +12 -0
  215. package/.next/standalone/package.json +2 -3
  216. package/.next/standalone/server.js +1 -1
  217. package/README.md +94 -62
  218. package/bin/failproofai.mjs +98 -30
  219. package/dist/cli.mjs +12499 -9243
  220. package/lib/antigravity-projects.ts +224 -0
  221. package/lib/antigravity-sessions.ts +283 -0
  222. package/lib/cli-registry.ts +26 -6
  223. package/lib/devin-projects.ts +141 -0
  224. package/lib/devin-sessions.ts +343 -0
  225. package/lib/download-session.ts +56 -5
  226. package/lib/error-origin.ts +67 -0
  227. package/lib/factory-projects.ts +109 -0
  228. package/lib/factory-sessions.ts +312 -0
  229. package/lib/goose-projects.ts +148 -0
  230. package/lib/goose-sessions.ts +296 -0
  231. package/lib/install-check.ts +231 -0
  232. package/lib/openclaw-projects.ts +182 -0
  233. package/lib/openclaw-sessions.ts +284 -0
  234. package/lib/projects.ts +33 -9
  235. package/lib/telemetry-sanitize.ts +43 -0
  236. package/openclaw-plugin/index.js +223 -0
  237. package/openclaw-plugin/openclaw.plugin.json +6 -0
  238. package/openclaw-plugin/package.json +12 -0
  239. package/package.json +2 -3
  240. package/scripts/dev-hook.mjs +292 -0
  241. package/scripts/install-diagnosis.mjs +5 -8
  242. package/scripts/install-telemetry.mjs +12 -4
  243. package/scripts/launch.ts +4 -8
  244. package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
  245. package/scripts/translate-docs/cli.ts +120 -53
  246. package/scripts/translate-docs/config.ts +57 -13
  247. package/scripts/translate-docs/mdx-translator.ts +177 -28
  248. package/scripts/translate-docs/mintlify-nav.ts +91 -10
  249. package/scripts/translate-docs/readme-translator.ts +13 -5
  250. package/scripts/translate-docs/translator.ts +38 -2
  251. package/scripts/translate-docs/types.ts +3 -0
  252. package/scripts/validate-mdx.ts +12 -4
  253. package/src/audit/cli-adapters/antigravity.ts +87 -0
  254. package/src/audit/cli-adapters/devin.ts +53 -0
  255. package/src/audit/cli-adapters/factory.ts +75 -0
  256. package/src/audit/cli-adapters/goose.ts +53 -0
  257. package/src/audit/cli-adapters/index.ts +30 -6
  258. package/src/audit/cli-adapters/openclaw.ts +56 -0
  259. package/src/audit/cli.ts +83 -8
  260. package/src/audit/report.ts +1 -1
  261. package/src/audit/types.ts +1 -1
  262. package/src/hooks/builtin-policies.ts +5 -5
  263. package/src/hooks/configure-wizard.ts +356 -0
  264. package/src/hooks/handler.ts +283 -218
  265. package/src/hooks/hook-telemetry.ts +43 -3
  266. package/src/hooks/install-prompt.ts +92 -173
  267. package/src/hooks/integrations.ts +739 -138
  268. package/src/hooks/manager.ts +46 -2
  269. package/src/hooks/policy-evaluator.ts +301 -121
  270. package/src/hooks/policy-presets.ts +71 -0
  271. package/src/hooks/resolve-cwd.ts +1 -1
  272. package/src/hooks/resolve-permission-mode.ts +1 -5
  273. package/src/hooks/resolve-transcript-path.ts +25 -7
  274. package/src/hooks/tool-name-canonicalize.ts +36 -3
  275. package/src/hooks/tui.ts +578 -0
  276. package/src/hooks/types.ts +447 -109
  277. package/.next/standalone/.gemini/settings.json +0 -147
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cag8qd._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hgv_75._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +0 -4
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_1c7bgx-._.js +0 -3
  292. package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +0 -1
  293. package/.next/standalone/.next/static/chunks/0nrah-qv6smeg.js +0 -6
  294. package/.next/standalone/.next/static/chunks/0wtagd8a5gwq-.js +0 -1
  295. package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +0 -1
  296. package/.next/standalone/.next/static/chunks/25qr0e6ez0ei0.js +0 -1
  297. package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +0 -2
  298. package/.next/standalone/assets/logos/gemini-dark.svg +0 -13
  299. package/.next/standalone/assets/logos/gemini-light.svg +0 -13
  300. package/.next/standalone/lib/gemini-projects.ts +0 -243
  301. package/.next/standalone/lib/gemini-sessions.ts +0 -365
  302. package/lib/gemini-projects.ts +0 -243
  303. package/lib/gemini-sessions.ts +0 -365
  304. package/scripts/postinstall.mjs +0 -243
  305. package/scripts/preuninstall.mjs +0 -131
  306. package/src/audit/cli-adapters/gemini.ts +0 -51
  307. package/src/hooks/first-run-nudge.ts +0 -146
  308. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_buildManifest.js +0 -0
  309. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_clientMiddlewareManifest.js +0 -0
  310. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_ssgManifest.js +0 -0
@@ -1,243 +0,0 @@
1
- /**
2
- * Gemini CLI project discovery.
3
- *
4
- * Empirically verified against gemini-cli v0.40.1:
5
- *
6
- * • Session-state root: `~/.gemini/tmp/<project-basename>/`. Each subdir
7
- * corresponds to one project. The basename is the cwd's last path segment
8
- * (lossy when two projects share a basename — but every dir carries a
9
- * `.project_root` text file with the absolute cwd to disambiguate).
10
- * • Project list registry: `~/.gemini/projects.json` maps absolute cwd →
11
- * basename. Authoritative when present, but we read each `.project_root`
12
- * anyway so the dashboard tolerates partially-pruned registries.
13
- * • Per-session file: `~/.gemini/tmp/<project>/chats/session-<ISO-timestamp>-<uuid-prefix>.jsonl`.
14
- * A sidecar `<file>.jsonl.tool-calls.json` may sit alongside.
15
- * • File format: JSONL. First line is metadata
16
- * `{sessionId, projectHash, startTime, lastUpdated, kind}`; subsequent
17
- * lines are message records `{id, timestamp, type, content: [{text}]}`
18
- * and `{$set: {...}}` partial updates.
19
- *
20
- * As with Cursor / Pi / OpenCode, this module is intentionally permissive — a
21
- * missing `~/.gemini/` returns `[]`, malformed JSONL falls open without
22
- * surfacing the session.
23
- */
24
- import { open, readdir, readFile, stat } from "node:fs/promises";
25
- import { homedir } from "node:os";
26
- import { join } from "node:path";
27
- import type { ProjectFolder, SessionFile } from "./projects";
28
- import { runtimeCache } from "./runtime-cache";
29
- import { batchAll } from "./concurrency";
30
- import { formatDate } from "./format-date";
31
- import { encodeFolderName } from "./paths";
32
- import { logWarn } from "./logger";
33
-
34
- /** Filename pattern for a Gemini session JSONL:
35
- * `session-<ISO-timestamp-with-dashes>-<8-hex-uuid-prefix>.jsonl`. */
36
- const SESSION_FILE_RE = /^session-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2})-([0-9a-f]{8})\.jsonl$/i;
37
- /** Full UUID — the filename only embeds the first 8 hex chars; the rest is on
38
- * the JSONL metadata header line. The session detail route requires a full
39
- * UUID, so links built from the truncated filename prefix 404. */
40
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
- /** Metadata header sits on line 1 and is well under 1 KB; 4 KB covers it
42
- * comfortably without slurping a multi-MB transcript. */
43
- const FIRST_LINE_CHUNK_BYTES = 4 * 1024;
44
-
45
- /** Override for tests. Defaults to the live Gemini session-state root. */
46
- function getGeminiTmpRoot(): string {
47
- return process.env.GEMINI_SESSIONS_DIR
48
- || join(homedir(), ".gemini", "tmp");
49
- }
50
-
51
- interface GeminiSessionMeta {
52
- filePath: string;
53
- sessionFilename: string;
54
- cwd: string;
55
- fileMtime: Date;
56
- /** Full UUID parsed from the JSONL metadata header (line 1). Undefined when
57
- * the header is missing, malformed, or carries a non-UUID `sessionId`;
58
- * callers fall through to rendering an un-linked row. */
59
- sessionId?: string;
60
- }
61
-
62
- async function safeReaddir(dir: string) {
63
- try {
64
- return await readdir(dir, { withFileTypes: true });
65
- } catch {
66
- return null;
67
- }
68
- }
69
-
70
- async function statMtime(path: string): Promise<Date | null> {
71
- try {
72
- return (await stat(path)).mtime;
73
- } catch {
74
- return null;
75
- }
76
- }
77
-
78
- /** Read the first newline-delimited line of `filePath` without slurping the
79
- * rest. Mirrors `lib/codex-projects.ts`'s readFirstLine; the Gemini metadata
80
- * header is always on line 1. */
81
- async function readFirstLine(filePath: string): Promise<string | null> {
82
- let fh: Awaited<ReturnType<typeof open>> | null = null;
83
- try {
84
- fh = await open(filePath, "r");
85
- const buf = Buffer.alloc(FIRST_LINE_CHUNK_BYTES);
86
- const { bytesRead } = await fh.read(buf, 0, FIRST_LINE_CHUNK_BYTES, 0);
87
- if (bytesRead === 0) return null;
88
- const slice = buf.subarray(0, bytesRead);
89
- const nl = slice.indexOf(0x0a); // '\n'
90
- const end = nl === -1 ? bytesRead : nl;
91
- return slice.subarray(0, end).toString("utf-8");
92
- } catch {
93
- return null;
94
- } finally {
95
- if (fh) await fh.close().catch(() => {});
96
- }
97
- }
98
-
99
- /** Extract a full-UUID `sessionId` from a JSONL metadata header line. Returns
100
- * undefined on parse failure, missing field, or a non-UUID value. */
101
- function extractFullSessionId(line: string | null): string | undefined {
102
- if (!line) return undefined;
103
- try {
104
- const meta = JSON.parse(line) as { sessionId?: unknown };
105
- if (typeof meta.sessionId !== "string") return undefined;
106
- return UUID_RE.test(meta.sessionId) ? meta.sessionId : undefined;
107
- } catch {
108
- return undefined;
109
- }
110
- }
111
-
112
- /** Read `.project_root` to recover the absolute cwd for a basename folder.
113
- * Returns null if missing or empty (caller treats the folder as un-mappable). */
114
- async function readProjectRoot(projectDir: string): Promise<string | null> {
115
- try {
116
- const text = await readFile(join(projectDir, ".project_root"), "utf-8");
117
- const trimmed = text.trim();
118
- return trimmed.length > 0 ? trimmed : null;
119
- } catch {
120
- return null;
121
- }
122
- }
123
-
124
- async function scanGeminiSessions(): Promise<GeminiSessionMeta[]> {
125
- const root = getGeminiTmpRoot();
126
- const projectDirs = await safeReaddir(root);
127
- if (!projectDirs) return [];
128
-
129
- const out: GeminiSessionMeta[] = [];
130
-
131
- await batchAll(
132
- projectDirs
133
- .filter((d) => d.isDirectory())
134
- .map((d) => async () => {
135
- const projectDir = join(root, d.name);
136
- const cwd = await readProjectRoot(projectDir);
137
- if (!cwd) return;
138
-
139
- const chatsDir = join(projectDir, "chats");
140
- const files = await safeReaddir(chatsDir);
141
- if (!files) return;
142
-
143
- for (const f of files) {
144
- if (!f.isFile()) continue;
145
- if (!SESSION_FILE_RE.test(f.name)) continue;
146
- const filePath = join(chatsDir, f.name);
147
- const mtime = await statMtime(filePath);
148
- if (!mtime) continue;
149
- const sessionId = extractFullSessionId(await readFirstLine(filePath));
150
- out.push({ filePath, sessionFilename: f.name, cwd, fileMtime: mtime, sessionId });
151
- }
152
- }),
153
- 16,
154
- );
155
-
156
- return out;
157
- }
158
-
159
- /** Returns one ProjectFolder per unique cwd that has at least one session file.
160
- * `name` is the encoded full-cwd slug (`encodeFolderName(cwd)`), matching the
161
- * routing scheme used by the dashboard's project URL — `mergeProjectFolders`
162
- * unions by `name`, and `getGeminiSessionsByEncodedName` looks up by the same
163
- * slug, so every cross-CLI merge and Gemini-only project link round-trips. */
164
- export async function getGeminiProjects(): Promise<ProjectFolder[]> {
165
- const sessions = await scanGeminiSessions();
166
- const byCwd = new Map<string, { mtime: Date }>();
167
- for (const s of sessions) {
168
- const existing = byCwd.get(s.cwd);
169
- if (!existing || s.fileMtime.getTime() > existing.mtime.getTime()) {
170
- byCwd.set(s.cwd, { mtime: s.fileMtime });
171
- }
172
- }
173
- const folders: ProjectFolder[] = [...byCwd.entries()].map(([cwd, { mtime }]) => ({
174
- name: encodeFolderName(cwd),
175
- path: cwd,
176
- isDirectory: true,
177
- lastModified: mtime,
178
- lastModifiedFormatted: formatDate(mtime),
179
- cli: ["gemini"],
180
- }));
181
- folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
182
- return folders;
183
- }
184
-
185
- /** Returns SessionFile entries for the given absolute cwd. Empty if none. */
186
- export async function getGeminiSessionsForCwd(cwd: string): Promise<SessionFile[]> {
187
- const sessions = await scanGeminiSessions();
188
- const matches = sessions.filter((s) => s.cwd === cwd);
189
- const files: SessionFile[] = matches.map((s) => ({
190
- name: s.sessionFilename,
191
- path: s.filePath,
192
- lastModified: s.fileMtime,
193
- lastModifiedFormatted: formatDate(s.fileMtime),
194
- sessionId: s.sessionId,
195
- cli: "gemini",
196
- }));
197
- files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
198
- return files;
199
- }
200
-
201
- export interface GeminiProjectByName {
202
- cwd: string | null;
203
- sessions: SessionFile[];
204
- }
205
-
206
- /**
207
- * Looks up Gemini sessions for a project URL slug. `decodeFolderName` is lossy
208
- * on cwds containing `-`, so we re-encode each session's cwd via
209
- * `encodeFolderName` and match in that direction. Returns both the canonical
210
- * cwd and the matching sessions. When two distinct cwds collapse to the same
211
- * encoded slug we return null to avoid mis-labeling the project.
212
- */
213
- export async function getGeminiSessionsByEncodedName(name: string): Promise<GeminiProjectByName> {
214
- let metas: GeminiSessionMeta[];
215
- try {
216
- metas = await scanGeminiSessions();
217
- } catch (error) {
218
- logWarn("Failed to scan Gemini sessions:", error);
219
- return { cwd: null, sessions: [] };
220
- }
221
- const matches = metas.filter((m) => encodeFolderName(m.cwd) === name);
222
- const uniqueCwds = Array.from(new Set(matches.map((m) => m.cwd)));
223
- if (uniqueCwds.length !== 1) {
224
- return { cwd: null, sessions: [] };
225
- }
226
- const sessions = matches.map((s) => ({
227
- name: s.sessionFilename,
228
- path: s.filePath,
229
- lastModified: s.fileMtime,
230
- lastModifiedFormatted: formatDate(s.fileMtime),
231
- sessionId: s.sessionId,
232
- cli: "gemini" as const,
233
- }));
234
- sessions.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
235
- return { cwd: uniqueCwds[0], sessions };
236
- }
237
-
238
- export const getCachedGeminiProjects = runtimeCache(getGeminiProjects, 30);
239
- export const getCachedGeminiSessionsByEncodedName = runtimeCache(
240
- (name: string) => getGeminiSessionsByEncodedName(name),
241
- 30,
242
- { maxSize: 50 },
243
- );
@@ -1,365 +0,0 @@
1
- /**
2
- * Gemini CLI session transcript discovery + JSONL parser.
3
- *
4
- * Empirically verified against gemini-cli v0.40.1:
5
- *
6
- * Session files live at
7
- * `~/.gemini/tmp/<project-basename>/chats/session-<ISO-timestamp-with-dashes>-<8-hex-uuid-prefix>.jsonl`
8
- * with a sidecar `<file>.jsonl.tool-calls.json` of tool-call records.
9
- * The basename component is just the cwd's last path segment; the canonical
10
- * cwd lives at `~/.gemini/tmp/<project-basename>/.project_root`.
11
- *
12
- * JSONL record schema (observed):
13
- * • Line 1 (metadata): `{sessionId, projectHash, startTime, lastUpdated, kind}`
14
- * • Subsequent lines:
15
- * `{id, timestamp, type: "user" | "assistant" | ..., content: [{text}]}` (messages)
16
- * `{$set: {lastUpdated: "..."}}` (partial update)
17
- *
18
- * Parser is intentionally permissive — unknown record types degrade to
19
- * "system" entries; malformed lines are skipped without aborting; and the
20
- * loader fall-opens (returns null) on any I/O or parse failure.
21
- */
22
- import { closeSync, openSync, readFileSync, readSync, readdirSync, existsSync, statSync } from "node:fs";
23
- import { readFile } from "node:fs/promises";
24
- import { join, resolve, sep } from "node:path";
25
- import { homedir } from "node:os";
26
- import { runtimeCache } from "./runtime-cache";
27
- import {
28
- baseEntry,
29
- type LogEntry,
30
- type UserEntry,
31
- type AssistantEntry,
32
- type GenericEntry,
33
- type QueueOperationEntry,
34
- type ContentBlock,
35
- type LogSource,
36
- } from "./log-entries";
37
-
38
- // ── Paths ──
39
-
40
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
- /** Matches `session-<timestamp-of-arbitrary-shape>-<8-hex-uuid-prefix>.jsonl`.
42
- * Empirically gemini-cli v0.40.1 emits minute-precision (`YYYY-MM-DDTHH-mm`),
43
- * but the documented format includes seconds (`YYYY-MM-DDTHH-mm:ss`) and may
44
- * evolve further. The first-line `sessionId` validation in findGeminiTranscript
45
- * is the load-bearing safety check, so we accept any timestamp shape. */
46
- const SESSION_FILE_RE = /^session-(.+)-([0-9a-f]{8})\.jsonl$/i;
47
-
48
- /** Root directory for Gemini session state, honoring GEMINI_SESSIONS_DIR. */
49
- export function getGeminiSessionStateRoot(): string {
50
- return process.env.GEMINI_SESSIONS_DIR
51
- || join(homedir(), ".gemini", "tmp");
52
- }
53
-
54
- /** Reject a sessionId that isn't a UUID — defends against path traversal. */
55
- function isSafeSessionId(sessionId: string): boolean {
56
- return UUID_RE.test(sessionId);
57
- }
58
-
59
- /**
60
- * Read up to the first newline from `path` without loading the whole file.
61
- * Used by `findGeminiTranscript` to inspect the JSONL metadata header on
62
- * candidate matches; the typical header is well under 1KB and we cap at 4KB
63
- * for safety so a degenerate single-line file can't blow memory.
64
- *
65
- * Returns the first line as a UTF-8 string, or null on read failure / empty
66
- * file. If the first 4KB contain no newline, returns whatever was read so the
67
- * JSON.parse caller can still attempt — JSON.parse will fail for a truncated
68
- * object, which findGeminiTranscript treats as a malformed-header miss.
69
- */
70
- function readFirstLineSync(path: string): string | null {
71
- let fd: number;
72
- try {
73
- fd = openSync(path, "r");
74
- } catch {
75
- return null;
76
- }
77
- try {
78
- const buf = Buffer.alloc(4096);
79
- const bytesRead = readSync(fd, buf, 0, buf.length, 0);
80
- if (bytesRead === 0) return null;
81
- const text = buf.subarray(0, bytesRead).toString("utf-8");
82
- const nl = text.indexOf("\n");
83
- return nl >= 0 ? text.slice(0, nl) : text;
84
- } catch {
85
- return null;
86
- } finally {
87
- try { closeSync(fd); } catch { /* best-effort */ }
88
- }
89
- }
90
-
91
- /**
92
- * Find the JSONL transcript for `sessionId`. Walks every per-project subdir's
93
- * `chats/` directory, matches the 8-hex prefix, and verifies the first record's
94
- * full `sessionId` field matches. Rejects path-traversal sessionIds and
95
- * verifies resolved paths stay under the root. Returns null on miss.
96
- */
97
- export function findGeminiTranscript(sessionId: string): string | null {
98
- if (!isSafeSessionId(sessionId)) return null;
99
- const root = resolve(getGeminiSessionStateRoot());
100
- const wantPrefix = sessionId.slice(0, 8).toLowerCase();
101
-
102
- let projectDirs: string[];
103
- try {
104
- projectDirs = readdirSync(root);
105
- } catch {
106
- return null;
107
- }
108
-
109
- for (const projectDir of projectDirs) {
110
- const projectPath = resolve(root, projectDir);
111
- if (!projectPath.startsWith(`${root}${sep}`)) continue;
112
-
113
- const chatsDir = resolve(projectPath, "chats");
114
- let files: string[];
115
- try {
116
- files = readdirSync(chatsDir);
117
- } catch {
118
- continue;
119
- }
120
-
121
- for (const f of files) {
122
- const m = SESSION_FILE_RE.exec(f);
123
- if (!m || m[2].toLowerCase() !== wantPrefix) continue;
124
- const candidate = resolve(chatsDir, f);
125
- if (!candidate.startsWith(`${chatsDir}${sep}`)) continue;
126
- if (!existsSync(candidate)) continue;
127
- // Confirm the full sessionId — the 8-hex prefix isn't unique on its own.
128
- // Read only the first ~4KB so large transcripts don't bloat memory; the
129
- // metadata header sits on line 1 well within that bound. The full file
130
- // is re-read in getGeminiSessionLog() once we've matched.
131
- const firstLine = readFirstLineSync(candidate);
132
- if (!firstLine) continue;
133
- try {
134
- const meta = JSON.parse(firstLine) as { sessionId?: unknown };
135
- if (typeof meta.sessionId === "string" && meta.sessionId.toLowerCase() === sessionId.toLowerCase()) {
136
- return candidate;
137
- }
138
- } catch {
139
- // Malformed first record — try next file.
140
- continue;
141
- }
142
- }
143
- }
144
- return null;
145
- }
146
-
147
- // ── Parser ──
148
-
149
- interface GeminiSessionRecord {
150
- // Metadata-line fields (line 1)
151
- sessionId?: string;
152
- projectHash?: string;
153
- startTime?: string;
154
- lastUpdated?: string;
155
- kind?: string;
156
- // Message-line fields
157
- id?: string;
158
- timestamp?: string;
159
- type?: string;
160
- content?: Array<Record<string, unknown>>;
161
- // Partial-update line: `{$set: {...}}`
162
- $set?: Record<string, unknown>;
163
- }
164
-
165
- interface GeminiParseResult {
166
- entries: LogEntry[];
167
- rawLines: Record<string, unknown>[];
168
- /** Working directory pulled from the sidecar `.project_root` if available. */
169
- cwd?: string;
170
- }
171
-
172
- function extractMessageText(content: Array<Record<string, unknown>> | undefined): string {
173
- if (!Array.isArray(content)) return "";
174
- const parts: string[] = [];
175
- for (const block of content) {
176
- if (typeof block?.text === "string") parts.push(block.text);
177
- }
178
- return parts.join("\n\n");
179
- }
180
-
181
- function buildAssistantContent(content: Array<Record<string, unknown>> | undefined): ContentBlock[] {
182
- if (!Array.isArray(content)) return [];
183
- const blocks: ContentBlock[] = [];
184
- for (const block of content) {
185
- if (typeof block?.text === "string" && block.text.length > 0) {
186
- blocks.push({ type: "text", text: block.text });
187
- }
188
- }
189
- return blocks;
190
- }
191
-
192
- /**
193
- * Parse a Gemini JSONL transcript into `LogEntry[]` plus the raw lines.
194
- * Yields to the event loop every 200 lines so big transcripts don't block
195
- * the request.
196
- */
197
- export async function parseGeminiLog(
198
- fileContent: string,
199
- source: LogSource = "session",
200
- cwdHint?: string,
201
- ): Promise<GeminiParseResult> {
202
- const lines = fileContent.split("\n").filter((line) => line.trim() !== "");
203
- const entries: LogEntry[] = [];
204
- const rawLines: Record<string, unknown>[] = [];
205
- let cwd: string | undefined = cwdHint;
206
- let seenStart = false;
207
-
208
- for (let i = 0; i < lines.length; i++) {
209
- if (i > 0 && i % 200 === 0) await new Promise<void>((r) => setImmediate(r));
210
-
211
- const line = lines[i];
212
- let raw: GeminiSessionRecord;
213
- try {
214
- raw = JSON.parse(line) as GeminiSessionRecord;
215
- } catch {
216
- continue;
217
- }
218
-
219
- const rawCopy = { ...(raw as Record<string, unknown>), _source: source };
220
- rawLines.push(rawCopy);
221
-
222
- // Metadata line — derive a synthetic "Session Started" entry from startTime.
223
- if (typeof raw.sessionId === "string" && typeof raw.startTime === "string") {
224
- const date = new Date(raw.startTime);
225
- if (!Number.isNaN(date.getTime())) {
226
- const label: QueueOperationEntry["label"] = seenStart ? "Session Resumed" : "Session Started";
227
- seenStart = true;
228
- entries.push({
229
- type: "queue-operation",
230
- ...baseEntry(rawCopy, date.toISOString(), date, source),
231
- label,
232
- } satisfies QueueOperationEntry);
233
- }
234
- continue;
235
- }
236
-
237
- // Partial-update line — preserve in raw but skip rendering.
238
- if (raw.$set) continue;
239
-
240
- const timestampStr = raw.timestamp;
241
- if (!timestampStr) continue;
242
- const date = new Date(timestampStr);
243
- if (Number.isNaN(date.getTime())) continue;
244
- const timestamp = date.toISOString();
245
-
246
- const recType = raw.type;
247
-
248
- if (recType === "user") {
249
- const text = extractMessageText(raw.content);
250
- if (!text) continue;
251
- entries.push({
252
- type: "user",
253
- ...baseEntry(rawCopy, timestamp, date, source),
254
- message: { role: "user", content: text },
255
- } satisfies UserEntry);
256
- continue;
257
- }
258
-
259
- if (recType === "assistant" || recType === "model") {
260
- const blocks = buildAssistantContent(raw.content);
261
- if (blocks.length === 0) {
262
- entries.push({
263
- type: "system",
264
- ...baseEntry(rawCopy, timestamp, date, source),
265
- raw: rawCopy,
266
- } satisfies GenericEntry);
267
- continue;
268
- }
269
- entries.push({
270
- type: "assistant",
271
- ...baseEntry(rawCopy, timestamp, date, source),
272
- message: { role: "assistant", content: blocks },
273
- } satisfies AssistantEntry);
274
- continue;
275
- }
276
-
277
- // Unknown type — preserve raw.
278
- entries.push({
279
- type: "system",
280
- ...baseEntry(rawCopy, timestamp, date, source),
281
- raw: rawCopy,
282
- } satisfies GenericEntry);
283
- }
284
-
285
- if (entries.length > 500) await new Promise<void>((r) => setImmediate(r));
286
- entries.sort((a, b) => a.timestampMs - b.timestampMs);
287
-
288
- return { entries, rawLines, cwd };
289
- }
290
-
291
- // ── Public loader ──
292
-
293
- export interface GeminiSessionLogData {
294
- entries: LogEntry[];
295
- rawLines: Record<string, unknown>[];
296
- cwd?: string;
297
- filePath: string;
298
- }
299
-
300
- /** Read `.project_root` next to the chats dir to recover the absolute cwd. */
301
- function readSiblingProjectRoot(transcriptPath: string): string | undefined {
302
- // transcriptPath = .../<basename>/chats/<file>.jsonl
303
- const chatsDir = resolve(transcriptPath, "..");
304
- const projectDir = resolve(chatsDir, "..");
305
- const rootFile = join(projectDir, ".project_root");
306
- try {
307
- const text = readFileSync(rootFile, "utf-8").trim();
308
- return text.length > 0 ? text : undefined;
309
- } catch {
310
- return undefined;
311
- }
312
- }
313
-
314
- export async function getGeminiSessionLog(sessionId: string): Promise<GeminiSessionLogData | null> {
315
- const filePath = findGeminiTranscript(sessionId);
316
- if (!filePath) return null;
317
- let fileContent: string;
318
- try {
319
- fileContent = await readFile(filePath, "utf-8");
320
- } catch {
321
- return null;
322
- }
323
- const cwdHint = readSiblingProjectRoot(filePath);
324
- let parsed: GeminiParseResult;
325
- try {
326
- parsed = await parseGeminiLog(fileContent, "session", cwdHint);
327
- } catch {
328
- return null;
329
- }
330
- return {
331
- entries: parsed.entries,
332
- rawLines: parsed.rawLines,
333
- cwd: parsed.cwd,
334
- filePath,
335
- };
336
- }
337
-
338
- export const getCachedGeminiSessionLog = runtimeCache(
339
- (sessionId: string) => getGeminiSessionLog(sessionId),
340
- 60,
341
- { maxSize: 50 },
342
- );
343
-
344
- // ── Test helpers ──
345
-
346
- export function _statGeminiTranscript(sessionId: string): { mtimeMs: number } | null {
347
- const path = findGeminiTranscript(sessionId);
348
- if (!path) return null;
349
- try {
350
- const s = statSync(path);
351
- return { mtimeMs: s.mtimeMs };
352
- } catch {
353
- return null;
354
- }
355
- }
356
-
357
- export function readGeminiTranscriptSync(sessionId: string): string | null {
358
- const path = findGeminiTranscript(sessionId);
359
- if (!path) return null;
360
- try {
361
- return readFileSync(path, "utf-8");
362
- } catch {
363
- return null;
364
- }
365
- }