dev3000 0.0.157 → 0.0.159

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 (516) hide show
  1. package/README.md +3 -1
  2. package/dist/cdp-monitor.d.ts +2 -3
  3. package/dist/cdp-monitor.d.ts.map +1 -1
  4. package/dist/cdp-monitor.js +5 -11
  5. package/dist/cdp-monitor.js.map +1 -1
  6. package/dist/cli.js +182 -161
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/cloud-check-pr.js +2 -1
  9. package/dist/commands/cloud-check-pr.js.map +1 -1
  10. package/dist/commands/cloud-fix.d.ts +1 -2
  11. package/dist/commands/cloud-fix.d.ts.map +1 -1
  12. package/dist/commands/cloud-fix.js +49 -685
  13. package/dist/commands/cloud-fix.js.map +1 -1
  14. package/dist/components/PackageSelector.d.ts +3 -2
  15. package/dist/components/PackageSelector.d.ts.map +1 -1
  16. package/dist/components/PackageSelector.js +9 -4
  17. package/dist/components/PackageSelector.js.map +1 -1
  18. package/dist/dev-environment.d.ts +9 -40
  19. package/dist/dev-environment.d.ts.map +1 -1
  20. package/dist/dev-environment.js +241 -544
  21. package/dist/dev-environment.js.map +1 -1
  22. package/dist/skills/d3k/SKILL.md +35 -2
  23. package/dist/src/tui-interface-impl.tsx +2 -12
  24. package/dist/tui-interface-impl.d.ts +0 -1
  25. package/dist/tui-interface-impl.d.ts.map +1 -1
  26. package/dist/tui-interface-impl.js +3 -7
  27. package/dist/tui-interface-impl.js.map +1 -1
  28. package/dist/tui-interface-opentui.d.ts +0 -1
  29. package/dist/tui-interface-opentui.d.ts.map +1 -1
  30. package/dist/tui-interface-opentui.js +26 -10
  31. package/dist/tui-interface-opentui.js.map +1 -1
  32. package/dist/tui-interface.d.ts +0 -1
  33. package/dist/tui-interface.d.ts.map +1 -1
  34. package/dist/tui-interface.js.map +1 -1
  35. package/dist/utils/agent-browser.d.ts.map +1 -1
  36. package/dist/utils/agent-browser.js +18 -13
  37. package/dist/utils/agent-browser.js.map +1 -1
  38. package/dist/utils/agent-selection.d.ts +1 -0
  39. package/dist/utils/agent-selection.d.ts.map +1 -1
  40. package/dist/utils/agent-selection.js +26 -1
  41. package/dist/utils/agent-selection.js.map +1 -1
  42. package/dist/utils/d3k-dir.d.ts +3 -0
  43. package/dist/utils/d3k-dir.d.ts.map +1 -0
  44. package/dist/utils/d3k-dir.js +12 -0
  45. package/dist/utils/d3k-dir.js.map +1 -0
  46. package/dist/utils/skill-installer.d.ts +7 -7
  47. package/dist/utils/skill-installer.d.ts.map +1 -1
  48. package/dist/utils/skill-installer.js +131 -18
  49. package/dist/utils/skill-installer.js.map +1 -1
  50. package/dist/utils/user-config.d.ts +0 -1
  51. package/dist/utils/user-config.d.ts.map +1 -1
  52. package/dist/utils/user-config.js +0 -13
  53. package/dist/utils/user-config.js.map +1 -1
  54. package/package.json +9 -29
  55. package/src/tui-interface-impl.tsx +2 -12
  56. package/dist/commands/cloud-fix-workflow.d.ts +0 -16
  57. package/dist/commands/cloud-fix-workflow.d.ts.map +0 -1
  58. package/dist/commands/cloud-fix-workflow.js +0 -153
  59. package/dist/commands/cloud-fix-workflow.js.map +0 -1
  60. package/dist/commands/restart.d.ts +0 -8
  61. package/dist/commands/restart.d.ts.map +0 -1
  62. package/dist/commands/restart.js +0 -92
  63. package/dist/commands/restart.js.map +0 -1
  64. package/dist/utils/mcp-configs.d.ts +0 -6
  65. package/dist/utils/mcp-configs.d.ts.map +0 -1
  66. package/dist/utils/mcp-configs.js +0 -54
  67. package/dist/utils/mcp-configs.js.map +0 -1
  68. package/mcp-server/.next/BUILD_ID +0 -1
  69. package/mcp-server/.next/app-path-routes-manifest.json +0 -22
  70. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js +0 -500
  71. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js.map +0 -11
  72. package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js +0 -441
  73. package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js.map +0 -7
  74. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js +0 -205
  75. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js.map +0 -8
  76. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js +0 -500
  77. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js.map +0 -11
  78. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_7988927e._.js +0 -13
  79. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_7988927e._.js.map +0 -5
  80. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_1efa112f._.js +0 -12
  81. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_1efa112f._.js.map +0 -5
  82. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js +0 -795
  83. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
  84. package/mcp-server/.next/build/chunks/node_modules__bun_19755e4f._.js +0 -6758
  85. package/mcp-server/.next/build/chunks/node_modules__bun_19755e4f._.js.map +0 -47
  86. package/mcp-server/.next/build/package.json +0 -1
  87. package/mcp-server/.next/build/postcss.js +0 -6
  88. package/mcp-server/.next/build/postcss.js.map +0 -5
  89. package/mcp-server/.next/build/webpack-loaders.js +0 -6
  90. package/mcp-server/.next/build/webpack-loaders.js.map +0 -5
  91. package/mcp-server/.next/build-manifest.json +0 -20
  92. package/mcp-server/.next/export-marker.json +0 -6
  93. package/mcp-server/.next/fallback-build-manifest.json +0 -12
  94. package/mcp-server/.next/images-manifest.json +0 -66
  95. package/mcp-server/.next/next-minimal-server.js.nft.json +0 -1
  96. package/mcp-server/.next/next-server.js.nft.json +0 -1
  97. package/mcp-server/.next/package.json +0 -1
  98. package/mcp-server/.next/prerender-manifest.json +0 -85
  99. package/mcp-server/.next/required-server-files.js +0 -164
  100. package/mcp-server/.next/required-server-files.json +0 -164
  101. package/mcp-server/.next/routes-manifest.json +0 -171
  102. package/mcp-server/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  103. package/mcp-server/.next/server/app/_global-error/page/build-manifest.json +0 -17
  104. package/mcp-server/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  105. package/mcp-server/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  106. package/mcp-server/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  107. package/mcp-server/.next/server/app/_global-error/page.js +0 -10
  108. package/mcp-server/.next/server/app/_global-error/page.js.map +0 -5
  109. package/mcp-server/.next/server/app/_global-error/page.js.nft.json +0 -1
  110. package/mcp-server/.next/server/app/_global-error/page_client-reference-manifest.js +0 -2
  111. package/mcp-server/.next/server/app/_global-error.html +0 -2
  112. package/mcp-server/.next/server/app/_global-error.meta +0 -15
  113. package/mcp-server/.next/server/app/_global-error.rsc +0 -12
  114. package/mcp-server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  115. package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +0 -12
  116. package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  117. package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +0 -4
  118. package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  119. package/mcp-server/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  120. package/mcp-server/.next/server/app/_not-found/page/build-manifest.json +0 -17
  121. package/mcp-server/.next/server/app/_not-found/page/next-font-manifest.json +0 -6
  122. package/mcp-server/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  123. package/mcp-server/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  124. package/mcp-server/.next/server/app/_not-found/page.js +0 -13
  125. package/mcp-server/.next/server/app/_not-found/page.js.map +0 -5
  126. package/mcp-server/.next/server/app/_not-found/page.js.nft.json +0 -1
  127. package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +0 -2
  128. package/mcp-server/.next/server/app/_not-found.html +0 -1
  129. package/mcp-server/.next/server/app/_not-found.meta +0 -16
  130. package/mcp-server/.next/server/app/_not-found.rsc +0 -14
  131. package/mcp-server/.next/server/app/_not-found.segments/_full.segment.rsc +0 -14
  132. package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +0 -5
  133. package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +0 -6
  134. package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  135. package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -4
  136. package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  137. package/mcp-server/.next/server/app/api/jank/[session]/route/app-paths-manifest.json +0 -3
  138. package/mcp-server/.next/server/app/api/jank/[session]/route/build-manifest.json +0 -11
  139. package/mcp-server/.next/server/app/api/jank/[session]/route/server-reference-manifest.json +0 -4
  140. package/mcp-server/.next/server/app/api/jank/[session]/route.js +0 -10
  141. package/mcp-server/.next/server/app/api/jank/[session]/route.js.map +0 -5
  142. package/mcp-server/.next/server/app/api/jank/[session]/route.js.nft.json +0 -1
  143. package/mcp-server/.next/server/app/api/jank/[session]/route_client-reference-manifest.js +0 -2
  144. package/mcp-server/.next/server/app/api/logs/append/route/app-paths-manifest.json +0 -3
  145. package/mcp-server/.next/server/app/api/logs/append/route/build-manifest.json +0 -11
  146. package/mcp-server/.next/server/app/api/logs/append/route/server-reference-manifest.json +0 -4
  147. package/mcp-server/.next/server/app/api/logs/append/route.js +0 -6
  148. package/mcp-server/.next/server/app/api/logs/append/route.js.map +0 -5
  149. package/mcp-server/.next/server/app/api/logs/append/route.js.nft.json +0 -1
  150. package/mcp-server/.next/server/app/api/logs/append/route_client-reference-manifest.js +0 -2
  151. package/mcp-server/.next/server/app/api/logs/head/route/app-paths-manifest.json +0 -3
  152. package/mcp-server/.next/server/app/api/logs/head/route/build-manifest.json +0 -11
  153. package/mcp-server/.next/server/app/api/logs/head/route/server-reference-manifest.json +0 -4
  154. package/mcp-server/.next/server/app/api/logs/head/route.js +0 -6
  155. package/mcp-server/.next/server/app/api/logs/head/route.js.map +0 -5
  156. package/mcp-server/.next/server/app/api/logs/head/route.js.nft.json +0 -1
  157. package/mcp-server/.next/server/app/api/logs/head/route_client-reference-manifest.js +0 -2
  158. package/mcp-server/.next/server/app/api/logs/list/route/app-paths-manifest.json +0 -3
  159. package/mcp-server/.next/server/app/api/logs/list/route/build-manifest.json +0 -11
  160. package/mcp-server/.next/server/app/api/logs/list/route/server-reference-manifest.json +0 -4
  161. package/mcp-server/.next/server/app/api/logs/list/route.js +0 -6
  162. package/mcp-server/.next/server/app/api/logs/list/route.js.map +0 -5
  163. package/mcp-server/.next/server/app/api/logs/list/route.js.nft.json +0 -1
  164. package/mcp-server/.next/server/app/api/logs/list/route_client-reference-manifest.js +0 -2
  165. package/mcp-server/.next/server/app/api/logs/rotate/route/app-paths-manifest.json +0 -3
  166. package/mcp-server/.next/server/app/api/logs/rotate/route/build-manifest.json +0 -11
  167. package/mcp-server/.next/server/app/api/logs/rotate/route/server-reference-manifest.json +0 -4
  168. package/mcp-server/.next/server/app/api/logs/rotate/route.js +0 -8
  169. package/mcp-server/.next/server/app/api/logs/rotate/route.js.map +0 -5
  170. package/mcp-server/.next/server/app/api/logs/rotate/route.js.nft.json +0 -1
  171. package/mcp-server/.next/server/app/api/logs/rotate/route_client-reference-manifest.js +0 -2
  172. package/mcp-server/.next/server/app/api/logs/stream/route/app-paths-manifest.json +0 -3
  173. package/mcp-server/.next/server/app/api/logs/stream/route/build-manifest.json +0 -11
  174. package/mcp-server/.next/server/app/api/logs/stream/route/server-reference-manifest.json +0 -4
  175. package/mcp-server/.next/server/app/api/logs/stream/route.js +0 -6
  176. package/mcp-server/.next/server/app/api/logs/stream/route.js.map +0 -5
  177. package/mcp-server/.next/server/app/api/logs/stream/route.js.nft.json +0 -1
  178. package/mcp-server/.next/server/app/api/logs/stream/route_client-reference-manifest.js +0 -2
  179. package/mcp-server/.next/server/app/api/logs/tail/route/app-paths-manifest.json +0 -3
  180. package/mcp-server/.next/server/app/api/logs/tail/route/build-manifest.json +0 -11
  181. package/mcp-server/.next/server/app/api/logs/tail/route/server-reference-manifest.json +0 -4
  182. package/mcp-server/.next/server/app/api/logs/tail/route.js +0 -6
  183. package/mcp-server/.next/server/app/api/logs/tail/route.js.map +0 -5
  184. package/mcp-server/.next/server/app/api/logs/tail/route.js.nft.json +0 -1
  185. package/mcp-server/.next/server/app/api/logs/tail/route_client-reference-manifest.js +0 -2
  186. package/mcp-server/.next/server/app/api/orchestrator/route/app-paths-manifest.json +0 -3
  187. package/mcp-server/.next/server/app/api/orchestrator/route/build-manifest.json +0 -11
  188. package/mcp-server/.next/server/app/api/orchestrator/route/server-reference-manifest.json +0 -4
  189. package/mcp-server/.next/server/app/api/orchestrator/route.js +0 -9
  190. package/mcp-server/.next/server/app/api/orchestrator/route.js.map +0 -5
  191. package/mcp-server/.next/server/app/api/orchestrator/route.js.nft.json +0 -1
  192. package/mcp-server/.next/server/app/api/orchestrator/route_client-reference-manifest.js +0 -2
  193. package/mcp-server/.next/server/app/api/screenshots/[filename]/route/app-paths-manifest.json +0 -3
  194. package/mcp-server/.next/server/app/api/screenshots/[filename]/route/build-manifest.json +0 -11
  195. package/mcp-server/.next/server/app/api/screenshots/[filename]/route/server-reference-manifest.json +0 -4
  196. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js +0 -8
  197. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.map +0 -5
  198. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.nft.json +0 -1
  199. package/mcp-server/.next/server/app/api/screenshots/[filename]/route_client-reference-manifest.js +0 -2
  200. package/mcp-server/.next/server/app/api/screenshots/capture/route/app-paths-manifest.json +0 -3
  201. package/mcp-server/.next/server/app/api/screenshots/capture/route/build-manifest.json +0 -11
  202. package/mcp-server/.next/server/app/api/screenshots/capture/route/server-reference-manifest.json +0 -4
  203. package/mcp-server/.next/server/app/api/screenshots/capture/route.js +0 -9
  204. package/mcp-server/.next/server/app/api/screenshots/capture/route.js.map +0 -5
  205. package/mcp-server/.next/server/app/api/screenshots/capture/route.js.nft.json +0 -1
  206. package/mcp-server/.next/server/app/api/screenshots/capture/route_client-reference-manifest.js +0 -2
  207. package/mcp-server/.next/server/app/api/screenshots/clear/route/app-paths-manifest.json +0 -3
  208. package/mcp-server/.next/server/app/api/screenshots/clear/route/build-manifest.json +0 -11
  209. package/mcp-server/.next/server/app/api/screenshots/clear/route/server-reference-manifest.json +0 -4
  210. package/mcp-server/.next/server/app/api/screenshots/clear/route.js +0 -8
  211. package/mcp-server/.next/server/app/api/screenshots/clear/route.js.map +0 -5
  212. package/mcp-server/.next/server/app/api/screenshots/clear/route.js.nft.json +0 -1
  213. package/mcp-server/.next/server/app/api/screenshots/clear/route_client-reference-manifest.js +0 -2
  214. package/mcp-server/.next/server/app/api/screenshots/list/route/app-paths-manifest.json +0 -3
  215. package/mcp-server/.next/server/app/api/screenshots/list/route/build-manifest.json +0 -11
  216. package/mcp-server/.next/server/app/api/screenshots/list/route/server-reference-manifest.json +0 -4
  217. package/mcp-server/.next/server/app/api/screenshots/list/route.js +0 -8
  218. package/mcp-server/.next/server/app/api/screenshots/list/route.js.map +0 -5
  219. package/mcp-server/.next/server/app/api/screenshots/list/route.js.nft.json +0 -1
  220. package/mcp-server/.next/server/app/api/screenshots/list/route_client-reference-manifest.js +0 -2
  221. package/mcp-server/.next/server/app/api/teams/route/app-paths-manifest.json +0 -3
  222. package/mcp-server/.next/server/app/api/teams/route/build-manifest.json +0 -11
  223. package/mcp-server/.next/server/app/api/teams/route/server-reference-manifest.json +0 -4
  224. package/mcp-server/.next/server/app/api/teams/route.js +0 -8
  225. package/mcp-server/.next/server/app/api/teams/route.js.map +0 -5
  226. package/mcp-server/.next/server/app/api/teams/route.js.nft.json +0 -1
  227. package/mcp-server/.next/server/app/api/teams/route_client-reference-manifest.js +0 -2
  228. package/mcp-server/.next/server/app/api/tools/route/app-paths-manifest.json +0 -3
  229. package/mcp-server/.next/server/app/api/tools/route/build-manifest.json +0 -11
  230. package/mcp-server/.next/server/app/api/tools/route/server-reference-manifest.json +0 -4
  231. package/mcp-server/.next/server/app/api/tools/route.js +0 -8
  232. package/mcp-server/.next/server/app/api/tools/route.js.map +0 -5
  233. package/mcp-server/.next/server/app/api/tools/route.js.nft.json +0 -1
  234. package/mcp-server/.next/server/app/api/tools/route_client-reference-manifest.js +0 -2
  235. package/mcp-server/.next/server/app/index.html +0 -1
  236. package/mcp-server/.next/server/app/index.meta +0 -14
  237. package/mcp-server/.next/server/app/index.rsc +0 -18
  238. package/mcp-server/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -9
  239. package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +0 -18
  240. package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +0 -5
  241. package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +0 -6
  242. package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +0 -2
  243. package/mcp-server/.next/server/app/logs/page/app-paths-manifest.json +0 -3
  244. package/mcp-server/.next/server/app/logs/page/build-manifest.json +0 -17
  245. package/mcp-server/.next/server/app/logs/page/next-font-manifest.json +0 -6
  246. package/mcp-server/.next/server/app/logs/page/react-loadable-manifest.json +0 -1
  247. package/mcp-server/.next/server/app/logs/page/server-reference-manifest.json +0 -4
  248. package/mcp-server/.next/server/app/logs/page.js +0 -16
  249. package/mcp-server/.next/server/app/logs/page.js.map +0 -5
  250. package/mcp-server/.next/server/app/logs/page.js.nft.json +0 -1
  251. package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +0 -2
  252. package/mcp-server/.next/server/app/mcp/route/app-paths-manifest.json +0 -3
  253. package/mcp-server/.next/server/app/mcp/route/build-manifest.json +0 -11
  254. package/mcp-server/.next/server/app/mcp/route/server-reference-manifest.json +0 -4
  255. package/mcp-server/.next/server/app/mcp/route.js +0 -11
  256. package/mcp-server/.next/server/app/mcp/route.js.map +0 -5
  257. package/mcp-server/.next/server/app/mcp/route.js.nft.json +0 -1
  258. package/mcp-server/.next/server/app/mcp/route_client-reference-manifest.js +0 -2
  259. package/mcp-server/.next/server/app/page/app-paths-manifest.json +0 -3
  260. package/mcp-server/.next/server/app/page/build-manifest.json +0 -17
  261. package/mcp-server/.next/server/app/page/next-font-manifest.json +0 -6
  262. package/mcp-server/.next/server/app/page/react-loadable-manifest.json +0 -1
  263. package/mcp-server/.next/server/app/page/server-reference-manifest.json +0 -4
  264. package/mcp-server/.next/server/app/page.js +0 -15
  265. package/mcp-server/.next/server/app/page.js.map +0 -5
  266. package/mcp-server/.next/server/app/page.js.nft.json +0 -1
  267. package/mcp-server/.next/server/app/page_client-reference-manifest.js +0 -2
  268. package/mcp-server/.next/server/app/video/[session]/page/app-paths-manifest.json +0 -3
  269. package/mcp-server/.next/server/app/video/[session]/page/build-manifest.json +0 -17
  270. package/mcp-server/.next/server/app/video/[session]/page/next-font-manifest.json +0 -6
  271. package/mcp-server/.next/server/app/video/[session]/page/react-loadable-manifest.json +0 -1
  272. package/mcp-server/.next/server/app/video/[session]/page/server-reference-manifest.json +0 -4
  273. package/mcp-server/.next/server/app/video/[session]/page.js +0 -15
  274. package/mcp-server/.next/server/app/video/[session]/page.js.map +0 -5
  275. package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +0 -1
  276. package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +0 -2
  277. package/mcp-server/.next/server/app-paths-manifest.json +0 -22
  278. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_[filename]_route_actions_4f8d6e37.js +0 -3
  279. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_[filename]_route_actions_4f8d6e37.js.map +0 -1
  280. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_capture_route_actions_4034f26c.js +0 -3
  281. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_capture_route_actions_4034f26c.js.map +0 -1
  282. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_clear_route_actions_e26206f4.js +0 -3
  283. package/mcp-server/.next/server/chunks/250ae__next-internal_server_app_api_screenshots_clear_route_actions_e26206f4.js.map +0 -1
  284. package/mcp-server/.next/server/chunks/[externals]__0e1fd2ca._.js +0 -3
  285. package/mcp-server/.next/server/chunks/[externals]__0e1fd2ca._.js.map +0 -1
  286. package/mcp-server/.next/server/chunks/[externals]_next_dist_2a398dc7._.js +0 -3
  287. package/mcp-server/.next/server/chunks/[externals]_next_dist_2a398dc7._.js.map +0 -1
  288. package/mcp-server/.next/server/chunks/[externals]_node:crypto_c20cce38._.js +0 -3
  289. package/mcp-server/.next/server/chunks/[externals]_node:crypto_c20cce38._.js.map +0 -1
  290. package/mcp-server/.next/server/chunks/[root-of-the-server]__1dca9894._.js +0 -3
  291. package/mcp-server/.next/server/chunks/[root-of-the-server]__1dca9894._.js.map +0 -1
  292. package/mcp-server/.next/server/chunks/[root-of-the-server]__21541e6b._.js +0 -11
  293. package/mcp-server/.next/server/chunks/[root-of-the-server]__21541e6b._.js.map +0 -1
  294. package/mcp-server/.next/server/chunks/[root-of-the-server]__2f95edf0._.js +0 -17
  295. package/mcp-server/.next/server/chunks/[root-of-the-server]__2f95edf0._.js.map +0 -1
  296. package/mcp-server/.next/server/chunks/[root-of-the-server]__377f76d7._.js +0 -7
  297. package/mcp-server/.next/server/chunks/[root-of-the-server]__377f76d7._.js.map +0 -1
  298. package/mcp-server/.next/server/chunks/[root-of-the-server]__454b0d3c._.js +0 -7
  299. package/mcp-server/.next/server/chunks/[root-of-the-server]__454b0d3c._.js.map +0 -1
  300. package/mcp-server/.next/server/chunks/[root-of-the-server]__69e6dfb7._.js +0 -3
  301. package/mcp-server/.next/server/chunks/[root-of-the-server]__69e6dfb7._.js.map +0 -1
  302. package/mcp-server/.next/server/chunks/[root-of-the-server]__6baff21e._.js +0 -4
  303. package/mcp-server/.next/server/chunks/[root-of-the-server]__6baff21e._.js.map +0 -1
  304. package/mcp-server/.next/server/chunks/[root-of-the-server]__6f790e1f._.js +0 -3
  305. package/mcp-server/.next/server/chunks/[root-of-the-server]__6f790e1f._.js.map +0 -1
  306. package/mcp-server/.next/server/chunks/[root-of-the-server]__7049acd5._.js +0 -3
  307. package/mcp-server/.next/server/chunks/[root-of-the-server]__7049acd5._.js.map +0 -1
  308. package/mcp-server/.next/server/chunks/[root-of-the-server]__73c9cc46._.js +0 -3
  309. package/mcp-server/.next/server/chunks/[root-of-the-server]__73c9cc46._.js.map +0 -1
  310. package/mcp-server/.next/server/chunks/[root-of-the-server]__78991125._.js +0 -3
  311. package/mcp-server/.next/server/chunks/[root-of-the-server]__78991125._.js.map +0 -1
  312. package/mcp-server/.next/server/chunks/[root-of-the-server]__7ae828c6._.js +0 -3
  313. package/mcp-server/.next/server/chunks/[root-of-the-server]__7ae828c6._.js.map +0 -1
  314. package/mcp-server/.next/server/chunks/[root-of-the-server]__94946101._.js +0 -3
  315. package/mcp-server/.next/server/chunks/[root-of-the-server]__94946101._.js.map +0 -1
  316. package/mcp-server/.next/server/chunks/[root-of-the-server]__99274dd8._.js +0 -4
  317. package/mcp-server/.next/server/chunks/[root-of-the-server]__99274dd8._.js.map +0 -1
  318. package/mcp-server/.next/server/chunks/[root-of-the-server]__9c4c7095._.js +0 -3
  319. package/mcp-server/.next/server/chunks/[root-of-the-server]__9c4c7095._.js.map +0 -1
  320. package/mcp-server/.next/server/chunks/[root-of-the-server]__b56464d6._.js +0 -7
  321. package/mcp-server/.next/server/chunks/[root-of-the-server]__b56464d6._.js.map +0 -1
  322. package/mcp-server/.next/server/chunks/[root-of-the-server]__b698502d._.js +0 -3
  323. package/mcp-server/.next/server/chunks/[root-of-the-server]__b698502d._.js.map +0 -1
  324. package/mcp-server/.next/server/chunks/[root-of-the-server]__b86e20b6._.js +0 -3
  325. package/mcp-server/.next/server/chunks/[root-of-the-server]__b86e20b6._.js.map +0 -1
  326. package/mcp-server/.next/server/chunks/[root-of-the-server]__c8cf5b23._.js +0 -3
  327. package/mcp-server/.next/server/chunks/[root-of-the-server]__c8cf5b23._.js.map +0 -1
  328. package/mcp-server/.next/server/chunks/[root-of-the-server]__e6a83e60._.js +0 -4
  329. package/mcp-server/.next/server/chunks/[root-of-the-server]__e6a83e60._.js.map +0 -1
  330. package/mcp-server/.next/server/chunks/[root-of-the-server]__ee139f8a._.js +0 -3
  331. package/mcp-server/.next/server/chunks/[root-of-the-server]__ee139f8a._.js.map +0 -1
  332. package/mcp-server/.next/server/chunks/[turbopack]_runtime.js +0 -795
  333. package/mcp-server/.next/server/chunks/[turbopack]_runtime.js.map +0 -10
  334. package/mcp-server/.next/server/chunks/edd96_next_80752ad3._.js +0 -3
  335. package/mcp-server/.next/server/chunks/edd96_next_80752ad3._.js.map +0 -1
  336. package/mcp-server/.next/server/chunks/edd96_next_dist_esm_build_templates_app-route_f51c5640.js +0 -3
  337. package/mcp-server/.next/server/chunks/edd96_next_dist_esm_build_templates_app-route_f51c5640.js.map +0 -1
  338. package/mcp-server/.next/server/chunks/edd96_next_dist_fa47e982._.js +0 -6
  339. package/mcp-server/.next/server/chunks/edd96_next_dist_fa47e982._.js.map +0 -1
  340. package/mcp-server/.next/server/chunks/edd96_next_ef93dda6._.js +0 -14
  341. package/mcp-server/.next/server/chunks/edd96_next_ef93dda6._.js.map +0 -1
  342. package/mcp-server/.next/server/chunks/mcp-server_70405c2f._.js +0 -3
  343. package/mcp-server/.next/server/chunks/mcp-server_70405c2f._.js.map +0 -1
  344. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_jank_[session]_route_actions_3b2b275b.js +0 -3
  345. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_jank_[session]_route_actions_3b2b275b.js.map +0 -1
  346. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_append_route_actions_bc66060f.js +0 -3
  347. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_append_route_actions_bc66060f.js.map +0 -1
  348. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_head_route_actions_1152480c.js +0 -3
  349. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_head_route_actions_1152480c.js.map +0 -1
  350. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_list_route_actions_b9e24400.js +0 -3
  351. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_list_route_actions_b9e24400.js.map +0 -1
  352. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_rotate_route_actions_76075d08.js +0 -3
  353. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_rotate_route_actions_76075d08.js.map +0 -1
  354. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_stream_route_actions_16e5c553.js +0 -3
  355. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_stream_route_actions_16e5c553.js.map +0 -1
  356. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_tail_route_actions_55440150.js +0 -3
  357. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_logs_tail_route_actions_55440150.js.map +0 -1
  358. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_orchestrator_route_actions_c6fba9ec.js +0 -3
  359. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_orchestrator_route_actions_c6fba9ec.js.map +0 -1
  360. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_screenshots_list_route_actions_acfa57bd.js +0 -3
  361. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_screenshots_list_route_actions_acfa57bd.js.map +0 -1
  362. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_teams_route_actions_aaa1c876.js +0 -3
  363. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_teams_route_actions_aaa1c876.js.map +0 -1
  364. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_tools_route_actions_007f3c7c.js +0 -3
  365. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_tools_route_actions_007f3c7c.js.map +0 -1
  366. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_mcp_route_actions_7f7b5be4.js +0 -3
  367. package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_mcp_route_actions_7f7b5be4.js.map +0 -1
  368. package/mcp-server/.next/server/chunks/mcp-server_app_mcp_tools_ts_faf6d7df._.js +0 -185
  369. package/mcp-server/.next/server/chunks/mcp-server_app_mcp_tools_ts_faf6d7df._.js.map +0 -1
  370. package/mcp-server/.next/server/chunks/mcp-server_instrumentation_node_ts_32271e34._.js +0 -3
  371. package/mcp-server/.next/server/chunks/mcp-server_instrumentation_node_ts_32271e34._.js.map +0 -1
  372. package/mcp-server/.next/server/chunks/src_utils_agent-browser_ts_cc00e0d8._.js +0 -3
  373. package/mcp-server/.next/server/chunks/src_utils_agent-browser_ts_cc00e0d8._.js.map +0 -1
  374. package/mcp-server/.next/server/chunks/src_utils_project-name_ts_1fab1dd5._.js +0 -3
  375. package/mcp-server/.next/server/chunks/src_utils_project-name_ts_1fab1dd5._.js.map +0 -1
  376. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__250a4cf3._.js +0 -3
  377. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__250a4cf3._.js.map +0 -1
  378. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__41e244ae._.js +0 -3
  379. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__41e244ae._.js.map +0 -1
  380. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__4a45fb1f._.js +0 -4
  381. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__4a45fb1f._.js.map +0 -1
  382. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__4feaccaf._.js +0 -3
  383. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__4feaccaf._.js.map +0 -1
  384. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__6d9fa861._.js +0 -3
  385. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__6d9fa861._.js.map +0 -1
  386. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__9913ce94._.js +0 -3
  387. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__9913ce94._.js.map +0 -1
  388. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__9923da5e._.js +0 -3
  389. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__9923da5e._.js.map +0 -1
  390. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__a8fcf205._.js +0 -3
  391. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__a8fcf205._.js.map +0 -1
  392. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__b17d4048._.js +0 -3
  393. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__b17d4048._.js.map +0 -1
  394. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__dcf84f77._.js +0 -3
  395. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__dcf84f77._.js.map +0 -1
  396. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__df4ed844._.js +0 -3
  397. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__df4ed844._.js.map +0 -1
  398. package/mcp-server/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -795
  399. package/mcp-server/.next/server/chunks/ssr/[turbopack]_runtime.js.map +0 -10
  400. package/mcp-server/.next/server/chunks/ssr/_213c874b._.js +0 -4
  401. package/mcp-server/.next/server/chunks/ssr/_213c874b._.js.map +0 -1
  402. package/mcp-server/.next/server/chunks/ssr/_69be9abe._.js +0 -4
  403. package/mcp-server/.next/server/chunks/ssr/_69be9abe._.js.map +0 -1
  404. package/mcp-server/.next/server/chunks/ssr/_b38781f1._.js +0 -4
  405. package/mcp-server/.next/server/chunks/ssr/_b38781f1._.js.map +0 -1
  406. package/mcp-server/.next/server/chunks/ssr/_edba94b0._.js +0 -8
  407. package/mcp-server/.next/server/chunks/ssr/_edba94b0._.js.map +0 -1
  408. package/mcp-server/.next/server/chunks/ssr/_f478416d._.js +0 -8
  409. package/mcp-server/.next/server/chunks/ssr/_f478416d._.js.map +0 -1
  410. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_22d4e869._.js +0 -4
  411. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_22d4e869._.js.map +0 -1
  412. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_329c4a9b._.js +0 -10
  413. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_329c4a9b._.js.map +0 -1
  414. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_6cceb2cd._.js +0 -3
  415. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_6cceb2cd._.js.map +0 -1
  416. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_861297ac._.js +0 -6
  417. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_861297ac._.js.map +0 -1
  418. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_85c7e922._.js +0 -3
  419. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_85c7e922._.js.map +0 -1
  420. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_forbidden_0eb1cacd.js +0 -3
  421. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_forbidden_0eb1cacd.js.map +0 -1
  422. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_global-error_e64e654b.js +0 -3
  423. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_global-error_e64e654b.js.map +0 -1
  424. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_unauthorized_06e7b5f5.js +0 -3
  425. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_client_components_builtin_unauthorized_06e7b5f5.js.map +0 -1
  426. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_esm_build_templates_app-page_f82c7ca4.js +0 -4
  427. package/mcp-server/.next/server/chunks/ssr/edd96_next_dist_esm_build_templates_app-page_f82c7ca4.js.map +0 -1
  428. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app__global-error_page_actions_404453e7.js +0 -3
  429. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app__global-error_page_actions_404453e7.js.map +0 -1
  430. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app__not-found_page_actions_c1864427.js +0 -3
  431. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app__not-found_page_actions_c1864427.js.map +0 -1
  432. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_logs_page_actions_71542ba9.js +0 -3
  433. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_logs_page_actions_71542ba9.js.map +0 -1
  434. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_page_actions_a5ee4758.js +0 -3
  435. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_page_actions_a5ee4758.js.map +0 -1
  436. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_video_[session]_page_actions_a6aab323.js +0 -3
  437. package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_video_[session]_page_actions_a6aab323.js.map +0 -1
  438. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js +0 -3
  439. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js.map +0 -1
  440. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_page_tsx_9fc46577._.js +0 -3
  441. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_page_tsx_9fc46577._.js.map +0 -1
  442. package/mcp-server/.next/server/chunks/ssr/mcp-server_components_dark-mode-toggle_tsx_f31dd15d._.js +0 -3
  443. package/mcp-server/.next/server/chunks/ssr/mcp-server_components_dark-mode-toggle_tsx_f31dd15d._.js.map +0 -1
  444. package/mcp-server/.next/server/chunks/ssr/node_modules__bun_39d5fbaf._.js +0 -3
  445. package/mcp-server/.next/server/chunks/ssr/node_modules__bun_39d5fbaf._.js.map +0 -1
  446. package/mcp-server/.next/server/chunks/ssr/node_modules__bun_d6d37386._.js +0 -5
  447. package/mcp-server/.next/server/chunks/ssr/node_modules__bun_d6d37386._.js.map +0 -1
  448. package/mcp-server/.next/server/functions-config-manifest.json +0 -4
  449. package/mcp-server/.next/server/instrumentation.js +0 -4
  450. package/mcp-server/.next/server/instrumentation.js.nft.json +0 -1
  451. package/mcp-server/.next/server/interception-route-rewrite-manifest.js +0 -1
  452. package/mcp-server/.next/server/middleware-build-manifest.js +0 -21
  453. package/mcp-server/.next/server/middleware-manifest.json +0 -6
  454. package/mcp-server/.next/server/next-font-manifest.js +0 -1
  455. package/mcp-server/.next/server/next-font-manifest.json +0 -6
  456. package/mcp-server/.next/server/pages/404.html +0 -1
  457. package/mcp-server/.next/server/pages/500.html +0 -2
  458. package/mcp-server/.next/server/pages-manifest.json +0 -4
  459. package/mcp-server/.next/server/server-reference-manifest.js +0 -1
  460. package/mcp-server/.next/server/server-reference-manifest.json +0 -5
  461. package/mcp-server/.next/static/chunks/10099c90a1ca89ea.js +0 -4
  462. package/mcp-server/.next/static/chunks/10c04cb580a9beec.js +0 -1
  463. package/mcp-server/.next/static/chunks/13606014ef33124c.js +0 -2
  464. package/mcp-server/.next/static/chunks/39e9bdcc541cc428.js +0 -1
  465. package/mcp-server/.next/static/chunks/3f3f8e7d16ba3bf4.js +0 -1
  466. package/mcp-server/.next/static/chunks/4785978304fb9e19.js +0 -1
  467. package/mcp-server/.next/static/chunks/59cdeaf92a780e96.css +0 -1
  468. package/mcp-server/.next/static/chunks/5df77c9395248155.js +0 -1
  469. package/mcp-server/.next/static/chunks/807aef58565dccb1.js +0 -1
  470. package/mcp-server/.next/static/chunks/a6dad97d9634a72d.js +0 -1
  471. package/mcp-server/.next/static/chunks/a6dad97d9634a72d.js.map +0 -1
  472. package/mcp-server/.next/static/chunks/b8eb42a9560f7980.js +0 -3
  473. package/mcp-server/.next/static/chunks/ea7b53054294e7bb.js +0 -3
  474. package/mcp-server/.next/static/chunks/ec58e1c556f5d0fa.js +0 -3
  475. package/mcp-server/.next/static/chunks/turbopack-b64e111cadf03885.js +0 -4
  476. package/mcp-server/.next/static/gTFARHqj7X4ZmcJWlUVID/_buildManifest.js +0 -11
  477. package/mcp-server/.next/static/gTFARHqj7X4ZmcJWlUVID/_clientMiddlewareManifest.json +0 -1
  478. package/mcp-server/.next/static/gTFARHqj7X4ZmcJWlUVID/_ssgManifest.js +0 -1
  479. package/mcp-server/app/api/jank/[session]/route.ts +0 -344
  480. package/mcp-server/app/api/logs/append/route.ts +0 -82
  481. package/mcp-server/app/api/logs/head/route.ts +0 -32
  482. package/mcp-server/app/api/logs/list/route.ts +0 -71
  483. package/mcp-server/app/api/logs/rotate/route.ts +0 -48
  484. package/mcp-server/app/api/logs/stream/route.ts +0 -61
  485. package/mcp-server/app/api/logs/tail/route.ts +0 -32
  486. package/mcp-server/app/api/orchestrator/route.ts +0 -73
  487. package/mcp-server/app/api/screenshots/[filename]/route.ts +0 -61
  488. package/mcp-server/app/api/screenshots/capture/route.ts +0 -137
  489. package/mcp-server/app/api/screenshots/clear/route.ts +0 -44
  490. package/mcp-server/app/api/screenshots/list/route.ts +0 -22
  491. package/mcp-server/app/api/teams/route.ts +0 -86
  492. package/mcp-server/app/api/tools/route.ts +0 -92
  493. package/mcp-server/app/globals.css +0 -124
  494. package/mcp-server/app/layout.tsx +0 -23
  495. package/mcp-server/app/logs/LogsClient.infinite-loop.test.tsx +0 -127
  496. package/mcp-server/app/logs/LogsClient.test.ts +0 -416
  497. package/mcp-server/app/logs/LogsClient.tsx +0 -1967
  498. package/mcp-server/app/logs/page.tsx +0 -150
  499. package/mcp-server/app/logs/utils.ts +0 -151
  500. package/mcp-server/app/mcp/client-manager.ts +0 -346
  501. package/mcp-server/app/mcp/route.ts +0 -765
  502. package/mcp-server/app/mcp/tools.ts +0 -4158
  503. package/mcp-server/app/page.tsx +0 -399
  504. package/mcp-server/app/video/[session]/page.tsx +0 -239
  505. package/mcp-server/next-env.d.ts +0 -6
  506. package/mcp-server/package.json +0 -48
  507. package/mcp-server/postcss.config.mjs +0 -5
  508. package/mcp-server/public/favicon-16.svg +0 -4
  509. package/mcp-server/public/favicon-180.png +0 -0
  510. package/mcp-server/public/favicon-64.svg +0 -4
  511. package/mcp-server/public/favicon-preview.html +0 -67
  512. package/mcp-server/public/favicon.ico +0 -0
  513. package/mcp-server/public/favicon.svg +0 -4
  514. package/mcp-server/public/screenshots/test.txt +0 -1
  515. package/mcp-server/start-production.mjs +0 -101
  516. package/mcp-server/tsconfig.json +0 -28
@@ -1,1967 +0,0 @@
1
- "use client"
2
-
3
- import Image from "next/image"
4
- import { useRouter, useSearchParams } from "next/navigation"
5
- import { useCallback, useEffect, useMemo, useRef, useState } from "react"
6
- import { DarkModeToggle } from "@/components/dark-mode-toggle"
7
- import { useDarkMode } from "@/hooks/use-dark-mode"
8
- import type { LogEntry, LogFile, LogListResponse, LogsApiResponse } from "@/types"
9
- import { getTextColor, LOG_COLORS } from "../../../src/constants/log-colors"
10
-
11
- // Define interfaces for object property rendering
12
- interface PropertyData {
13
- name: string
14
- value: string | number | boolean | null
15
- type: string
16
- subtype?: string
17
- }
18
-
19
- interface ReplayEvent {
20
- timestamp: string
21
- event: string
22
- details: string
23
- type?: string
24
- x?: number
25
- y?: number
26
- target?: string
27
- direction?: string
28
- distance?: number
29
- key?: string
30
- }
31
-
32
- import { parseLogEntries } from "./utils"
33
-
34
- // Keep this for backwards compatibility, but it's not used anymore
35
- function _parseLogLine(line: string): LogEntry | null {
36
- const match = line.match(/^\[([^\]]+)\] \[([^\]]+)\] (.*)$/s)
37
- if (!match) return null
38
-
39
- const [, timestamp, source, message] = match
40
- const screenshot = message.match(/\[SCREENSHOT\] (.+)/)?.[1]
41
-
42
- return {
43
- timestamp,
44
- source,
45
- message,
46
- screenshot,
47
- original: line
48
- }
49
- }
50
-
51
- // Component to render truncated URLs with click-to-expand
52
- function URLRenderer({ url, maxLength = 60 }: { url: string; maxLength?: number }) {
53
- const [isExpanded, setIsExpanded] = useState(false)
54
-
55
- if (url.length <= maxLength) {
56
- return (
57
- <a
58
- href={url}
59
- target="_blank"
60
- rel="noopener noreferrer"
61
- className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline"
62
- >
63
- {url}
64
- </a>
65
- )
66
- }
67
-
68
- const truncated = `${url.substring(0, maxLength)}...`
69
-
70
- return (
71
- <span className="inline-block">
72
- {isExpanded ? (
73
- <span>
74
- <a
75
- href={url}
76
- target="_blank"
77
- rel="noopener noreferrer"
78
- className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline"
79
- >
80
- {url}
81
- </a>
82
- <button
83
- type="button"
84
- onClick={() => setIsExpanded(false)}
85
- className="ml-2 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
86
- >
87
- [collapse]
88
- </button>
89
- </span>
90
- ) : (
91
- <span>
92
- <a
93
- href={url}
94
- target="_blank"
95
- rel="noopener noreferrer"
96
- className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline"
97
- >
98
- {truncated}
99
- </a>
100
- <button
101
- type="button"
102
- onClick={() => setIsExpanded(true)}
103
- className="ml-1 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
104
- >
105
- [expand]
106
- </button>
107
- </span>
108
- )}
109
- </span>
110
- )
111
- }
112
-
113
- // Component to render Chrome DevTools-style collapsible objects
114
- function ObjectRenderer({ content }: { content: string }) {
115
- const [isExpanded, setIsExpanded] = useState(false)
116
-
117
- try {
118
- const obj = JSON.parse(content)
119
-
120
- // Check if it's a Chrome DevTools object representation
121
- if (obj && typeof obj === "object" && obj.type === "object" && obj.properties) {
122
- const properties = obj.properties
123
- const description = obj.description || "Object"
124
- const overflow = obj.overflow
125
-
126
- return (
127
- <div className="inline-block">
128
- <button
129
- type="button"
130
- onClick={() => setIsExpanded(!isExpanded)}
131
- className="inline-flex items-center gap-1 text-blue-600 hover:text-blue-800 font-mono text-sm"
132
- >
133
- <svg
134
- className={`w-3 h-3 transition-transform ${isExpanded ? "rotate-90" : ""}`}
135
- fill="currentColor"
136
- viewBox="0 0 20 20"
137
- >
138
- <path
139
- fillRule="evenodd"
140
- d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
141
- clipRule="evenodd"
142
- />
143
- </svg>
144
- <span className="text-purple-600">{description}</span>
145
- {!isExpanded && (
146
- <span className="text-gray-500">
147
- {overflow ? "..." : ""} {"{"}
148
- {properties.slice(0, 3).map((prop: PropertyData, idx: number) => (
149
- <span key={`${prop.name}-${idx}`}>
150
- {idx > 0 && ", "}
151
- <span className="text-red-600">{prop.name}</span>:
152
- <span className="text-blue-600">
153
- {prop.type === "string"
154
- ? `"${prop.value}"`
155
- : prop.type === "number"
156
- ? prop.value
157
- : prop.type === "object"
158
- ? prop.subtype === "array"
159
- ? prop.value
160
- : "{...}"
161
- : prop.value}
162
- </span>
163
- </span>
164
- ))}
165
- {properties.length > 3 && ", ..."}
166
- {"}"}
167
- </span>
168
- )}
169
- </button>
170
-
171
- {isExpanded && (
172
- <div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
173
- <div className="font-mono text-sm">
174
- <div className="text-gray-600">
175
- {description} {"{"}
176
- <div className="ml-4">
177
- {properties.map((prop: PropertyData, idx: number) => (
178
- <div key={`${prop.name}-${idx}`} className="py-0.5">
179
- <span className="text-red-600">{prop.name}</span>
180
- <span className="text-gray-500">: </span>
181
- <span
182
- className={
183
- prop.type === "string"
184
- ? "text-green-600"
185
- : prop.type === "number"
186
- ? "text-blue-600"
187
- : prop.type === "object"
188
- ? "text-purple-600"
189
- : "text-orange-600"
190
- }
191
- >
192
- {prop.type === "string"
193
- ? `"${String(prop.value)}"`
194
- : prop.type === "number"
195
- ? String(prop.value)
196
- : prop.type === "object"
197
- ? prop.subtype === "array"
198
- ? String(prop.value)
199
- : "{...}"
200
- : String(prop.value)}
201
- </span>
202
- {idx < properties.length - 1 && <span className="text-gray-500">,</span>}
203
- </div>
204
- ))}
205
- {overflow && <div className="text-gray-500 italic">... and more properties</div>}
206
- </div>
207
- <div className="text-gray-600">{"}"}</div>
208
- </div>
209
- </div>
210
- </div>
211
- )}
212
- </div>
213
- )
214
- }
215
-
216
- // For regular JSON objects, render them nicely too
217
- return (
218
- <div className="inline-block">
219
- <button
220
- type="button"
221
- onClick={() => setIsExpanded(!isExpanded)}
222
- className="inline-flex items-center gap-1 text-blue-600 hover:text-blue-800 font-mono text-sm"
223
- >
224
- <svg
225
- className={`w-3 h-3 transition-transform ${isExpanded ? "rotate-90" : ""}`}
226
- fill="currentColor"
227
- viewBox="0 0 20 20"
228
- >
229
- <path
230
- fillRule="evenodd"
231
- d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
232
- clipRule="evenodd"
233
- />
234
- </svg>
235
- <span className="text-purple-600">Object</span>
236
- {!isExpanded && (
237
- <span className="text-gray-500">
238
- {"{"}...{"}"}
239
- </span>
240
- )}
241
- </button>
242
-
243
- {isExpanded && (
244
- <div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
245
- <pre className="font-mono text-sm text-foreground whitespace-pre-wrap">{JSON.stringify(obj, null, 2)}</pre>
246
- </div>
247
- )}
248
- </div>
249
- )
250
- } catch (_e) {
251
- // If it's not valid JSON, just return the original content
252
- return <span>{content}</span>
253
- }
254
- }
255
-
256
- function LogEntryComponent({ entry, darkMode }: { entry: LogEntry; darkMode: boolean }) {
257
- // Parse log type from message patterns - using shared TUI colors
258
- const parseLogType = (message: string) => {
259
- if (message.includes("[INTERACTION]"))
260
- return {
261
- type: "INTERACTION",
262
- backgroundColor: LOG_COLORS.INTERACTION,
263
- textColor: getTextColor(LOG_COLORS.INTERACTION)
264
- }
265
- if (message.includes("[ERROR]"))
266
- return {
267
- type: "ERROR",
268
- backgroundColor: LOG_COLORS.ERROR,
269
- textColor: getTextColor(LOG_COLORS.ERROR)
270
- }
271
- if (message.includes("[WARNING]"))
272
- return {
273
- type: "WARNING",
274
- backgroundColor: LOG_COLORS.WARNING,
275
- textColor: getTextColor(LOG_COLORS.WARNING)
276
- }
277
- if (message.includes("[INFO]"))
278
- return {
279
- type: "INFO",
280
- backgroundColor: LOG_COLORS.INFO,
281
- textColor: getTextColor(LOG_COLORS.INFO)
282
- }
283
- if (message.includes("[LOG]"))
284
- return {
285
- type: "LOG",
286
- backgroundColor: LOG_COLORS.LOG,
287
- textColor: getTextColor(LOG_COLORS.LOG)
288
- }
289
- if (message.includes("[DEBUG]"))
290
- return {
291
- type: "DEBUG",
292
- backgroundColor: LOG_COLORS.DEBUG,
293
- textColor: getTextColor(LOG_COLORS.DEBUG)
294
- }
295
- if (message.includes("[SCREENSHOT]"))
296
- return {
297
- type: "SCREENSHOT",
298
- backgroundColor: LOG_COLORS.SCREENSHOT,
299
- textColor: getTextColor(LOG_COLORS.SCREENSHOT)
300
- }
301
- if (message.includes("[NAVIGATION]") || message.includes("[PAGE]") || message.includes("[DOM]"))
302
- return {
303
- type: "DOM",
304
- backgroundColor: LOG_COLORS.DOM,
305
- textColor: getTextColor(LOG_COLORS.DOM)
306
- }
307
- if (message.includes("[CDP]"))
308
- return {
309
- type: "CDP",
310
- backgroundColor: LOG_COLORS.CDP,
311
- textColor: getTextColor(LOG_COLORS.CDP)
312
- }
313
- if (message.includes("[NETWORK]"))
314
- return {
315
- type: "NETWORK",
316
- backgroundColor: LOG_COLORS.NETWORK,
317
- textColor: getTextColor(LOG_COLORS.NETWORK)
318
- }
319
- if (message.includes("[CRASH]"))
320
- return {
321
- type: "CRASH",
322
- backgroundColor: LOG_COLORS.CRASH,
323
- textColor: getTextColor(LOG_COLORS.CRASH)
324
- }
325
- if (message.includes("[EXIT]"))
326
- return {
327
- type: "EXIT",
328
- backgroundColor: LOG_COLORS.EXIT,
329
- textColor: getTextColor(LOG_COLORS.EXIT)
330
- }
331
- if (message.includes("[CHROME]"))
332
- return {
333
- type: "CHROME",
334
- backgroundColor: LOG_COLORS.CHROME,
335
- textColor: getTextColor(LOG_COLORS.CHROME)
336
- }
337
- if (message.includes("[REPLAY]"))
338
- return {
339
- type: "REPLAY",
340
- backgroundColor: LOG_COLORS.REPLAY,
341
- textColor: getTextColor(LOG_COLORS.REPLAY)
342
- }
343
- return {
344
- type: "DEFAULT",
345
- color: "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
346
- backgroundColor: LOG_COLORS.DEFAULT,
347
- textColor: getTextColor(LOG_COLORS.DEFAULT)
348
- }
349
- }
350
-
351
- const logTypeInfo = parseLogType(entry.message)
352
-
353
- // Extract and highlight type tags, detect JSON objects and URLs
354
- const renderMessage = (message: string) => {
355
- const typeTagRegex = /\[([A-Z\s]+)\]/g
356
- const jsonRegex = /\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g
357
- const urlRegex = /(https?:\/\/[^\s]+)/g
358
-
359
- const parts = []
360
- let lastIndex = 0
361
- let match: RegExpExecArray | null
362
-
363
- // First, handle type tags
364
- match = typeTagRegex.exec(message)
365
- while (match !== null) {
366
- // Add text before the tag
367
- if (match.index > lastIndex) {
368
- parts.push(message.slice(lastIndex, match.index))
369
- }
370
-
371
- // Add the tag with styling using TUI colors
372
- parts.push(
373
- <span
374
- key={match.index}
375
- className="inline-block px-1.5 py-0.5 rounded text-xs font-medium mr-1"
376
- style={{
377
- backgroundColor: logTypeInfo.backgroundColor,
378
- color: logTypeInfo.textColor
379
- }}
380
- >
381
- {match[1]}
382
- </span>
383
- )
384
-
385
- lastIndex = match.index + match[0].length
386
- match = typeTagRegex.exec(message)
387
- }
388
-
389
- // Add remaining text
390
- const remainingText = message.slice(lastIndex)
391
-
392
- // Process remaining text for JSON objects and URLs
393
- const processTextForObjects = (text: string, keyPrefix: string) => {
394
- const jsonMatches = [...text.matchAll(jsonRegex)]
395
- const urlMatches = [...text.matchAll(urlRegex)]
396
- const allMatches = [
397
- ...jsonMatches.map((m) => ({ ...m, type: "json" })),
398
- ...urlMatches.map((m) => ({ ...m, type: "url" }))
399
- ]
400
-
401
- // Sort matches by index
402
- allMatches.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
403
-
404
- if (allMatches.length === 0) {
405
- return [text]
406
- }
407
-
408
- const finalParts = []
409
- let textLastIndex = 0
410
-
411
- allMatches.forEach((objMatch, _idx) => {
412
- // Add text before match
413
- if ((objMatch.index ?? 0) > textLastIndex) {
414
- finalParts.push(text.slice(textLastIndex, objMatch.index ?? 0))
415
- }
416
-
417
- // Add appropriate renderer
418
- if (objMatch.type === "json") {
419
- finalParts.push(
420
- <ObjectRenderer
421
- key={`${keyPrefix}-json-${objMatch.index}-${objMatch[0].slice(0, 20)}`}
422
- content={objMatch[0]}
423
- />
424
- )
425
- } else if (objMatch.type === "url") {
426
- finalParts.push(<URLRenderer key={`${keyPrefix}-url-${objMatch.index}-${objMatch[0]}`} url={objMatch[0]} />)
427
- }
428
-
429
- textLastIndex = (objMatch.index ?? 0) + objMatch[0].length
430
- })
431
-
432
- // Add any text after the last match
433
- if (textLastIndex < text.length) {
434
- finalParts.push(text.slice(textLastIndex))
435
- }
436
-
437
- return finalParts
438
- }
439
-
440
- const processedRemaining = processTextForObjects(remainingText, "main")
441
- parts.push(...processedRemaining)
442
-
443
- return parts.length > 0 ? parts : message
444
- }
445
-
446
- // Determine the background color based on log type and dark mode
447
- const getBackgroundStyle = () => {
448
- const type = logTypeInfo.type
449
- if (darkMode) {
450
- // Dark mode colors
451
- switch (type) {
452
- case "INTERACTION":
453
- case "CONSOLE DEBUG":
454
- case "DOM":
455
- return { backgroundColor: "rgba(88, 28, 135, 0.3)" } // purple-950/30
456
- case "CONSOLE ERROR":
457
- case "ERROR":
458
- case "CRITICAL ERROR":
459
- return { backgroundColor: "rgba(127, 29, 29, 0.3)" } // red-950/30
460
- case "CONSOLE WARN":
461
- case "CDP":
462
- case "NETWORK":
463
- return { backgroundColor: "rgba(133, 77, 14, 0.3)" } // yellow-950/30
464
- case "CONSOLE INFO":
465
- case "SCREENSHOT":
466
- return { backgroundColor: "rgba(30, 58, 138, 0.3)" } // blue-950/30
467
- case "PAGE":
468
- return { backgroundColor: "rgba(20, 83, 45, 0.3)" } // green-950/30
469
- default:
470
- return { backgroundColor: "rgba(17, 24, 39, 0.3)" } // gray-900/30
471
- }
472
- } else {
473
- // Light mode colors
474
- switch (type) {
475
- case "INTERACTION":
476
- case "CONSOLE DEBUG":
477
- case "DOM":
478
- return { backgroundColor: "rgba(250, 245, 255, 0.5)" } // purple-50/50
479
- case "CONSOLE ERROR":
480
- case "ERROR":
481
- case "CRITICAL ERROR":
482
- return { backgroundColor: "rgba(254, 242, 242, 0.5)" } // red-50/50
483
- case "CONSOLE WARN":
484
- case "CDP":
485
- case "NETWORK":
486
- return { backgroundColor: "rgba(254, 252, 232, 0.5)" } // yellow-50/50
487
- case "CONSOLE INFO":
488
- case "SCREENSHOT":
489
- return { backgroundColor: "rgba(239, 246, 255, 0.5)" } // blue-50/50
490
- case "PAGE":
491
- return { backgroundColor: "rgba(240, 253, 244, 0.5)" } // green-50/50
492
- default:
493
- return { backgroundColor: "transparent" }
494
- }
495
- }
496
- }
497
-
498
- // Determine the border class based on log type
499
- const getBorderClass = () => {
500
- const type = logTypeInfo.type
501
- switch (type) {
502
- case "INTERACTION":
503
- case "CONSOLE DEBUG":
504
- case "DOM":
505
- return "border-purple-200 dark:border-purple-800"
506
- case "CONSOLE ERROR":
507
- case "ERROR":
508
- case "CRITICAL ERROR":
509
- return "border-red-200 dark:border-red-800"
510
- case "CONSOLE WARN":
511
- case "CDP":
512
- case "NETWORK":
513
- return "border-yellow-200 dark:border-yellow-800"
514
- case "CONSOLE INFO":
515
- case "SCREENSHOT":
516
- return "border-blue-200 dark:border-blue-800"
517
- case "PAGE":
518
- return "border-green-200 dark:border-green-800"
519
- default:
520
- return "border-gray-200 dark:border-gray-700"
521
- }
522
- }
523
-
524
- return (
525
- <div className={`border-l-4 ${getBorderClass()} pl-4 py-2`} style={getBackgroundStyle()}>
526
- {/* Table-like layout using CSS Grid */}
527
- <div className="grid grid-cols-[auto_auto_1fr] gap-3 items-start">
528
- {/* Column 1: Timestamp */}
529
- <div className="text-xs text-muted-foreground font-mono whitespace-nowrap pt-1">
530
- {new Date(entry.timestamp).toLocaleTimeString()}
531
- </div>
532
-
533
- {/* Column 2: Source */}
534
- <div
535
- className="px-2 py-1 rounded text-xs font-medium whitespace-nowrap"
536
- style={{
537
- backgroundColor: entry.source === "SERVER" ? LOG_COLORS.SERVER : LOG_COLORS.BROWSER,
538
- color: "#000"
539
- }}
540
- >
541
- {entry.source}
542
- </div>
543
-
544
- {/* Column 3: Message content with user agent info */}
545
- <div className="font-mono text-sm min-w-0 text-foreground">
546
- <div className="flex flex-wrap items-start gap-2">
547
- <div className="flex-1 min-w-0">{renderMessage(entry.message)}</div>
548
- {/* User Agent and Tab Identifier Pills */}
549
- <div className="flex items-center gap-1 flex-shrink-0">
550
- {entry.tabIdentifier && (
551
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-muted text-muted-foreground">
552
- {entry.tabIdentifier}
553
- </span>
554
- )}
555
- {entry.userAgent && (
556
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200">
557
- {entry.userAgent.includes("Chrome")
558
- ? "Chrome"
559
- : entry.userAgent.includes("Firefox")
560
- ? "Firefox"
561
- : entry.userAgent.includes("Safari")
562
- ? "Safari"
563
- : entry.userAgent.includes("Edge")
564
- ? "Edge"
565
- : "Browser"}
566
- </span>
567
- )}
568
- {/* Browser Type Pill (Chrome Extension vs Playwright) - only show if browser source */}
569
- {entry.source === "BROWSER" &&
570
- (() => {
571
- const isPlaywright = entry.message.includes("[PLAYWRIGHT]")
572
- const isChromeExtension = entry.message.includes("[CHROME_EXTENSION]") || entry.tabIdentifier // Tab identifier indicates Chrome Extension
573
-
574
- if (isPlaywright) {
575
- return (
576
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 dark:bg-purple-800 text-purple-700 dark:text-purple-200">
577
- 🎭 Playwright
578
- </span>
579
- )
580
- } else if (isChromeExtension) {
581
- return (
582
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-200">
583
- 🧩 Extension
584
- </span>
585
- )
586
- }
587
- return null
588
- })()}
589
- {/* Server Framework Pill - only show if server source */}
590
- {entry.source === "SERVER" &&
591
- (() => {
592
- const message = entry.message.toLowerCase()
593
-
594
- // Next.js - look for Next.js specific patterns
595
- if (
596
- message.includes("ready on http://localhost") ||
597
- message.includes("ready in ") ||
598
- message.includes("next.js") ||
599
- message.includes("compiled client and server")
600
- ) {
601
- return (
602
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-black text-white">
603
- ▲ Next.js
604
- </span>
605
- )
606
- }
607
-
608
- // Nuxt.js - look for Nuxt specific patterns
609
- else if (
610
- message.includes("nuxt") ||
611
- (message.includes("nitro") && message.includes("http://localhost")) ||
612
- message.includes("universal mode") ||
613
- message.includes("spa mode")
614
- ) {
615
- return (
616
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-500 text-white">
617
- 💚 Nuxt
618
- </span>
619
- )
620
- }
621
-
622
- // Vue CLI/Vite + Vue - look for Vue specific patterns
623
- else if (
624
- message.includes("vue-cli-service") ||
625
- (message.includes("vue") && (message.includes("dev server") || message.includes("local:"))) ||
626
- message.includes("@vue/cli-service")
627
- ) {
628
- return (
629
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-600 text-white">
630
- 🟢 Vue
631
- </span>
632
- )
633
- }
634
-
635
- // Vite (generic) - look for Vite patterns
636
- else if (
637
- (message.includes("local:") && message.includes("vite")) ||
638
- message.includes("dev server running") ||
639
- message.includes("vite v")
640
- ) {
641
- return (
642
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-500 text-white">
643
- ⚡ Vite
644
- </span>
645
- )
646
- }
647
-
648
- // Ruby on Rails - look for Rails patterns
649
- else if (
650
- message.includes("rails server") ||
651
- message.includes("puma starting") ||
652
- message.includes("use ctrl-c to stop") ||
653
- (message.includes("listening on tcp://") && message.includes("3000"))
654
- ) {
655
- return (
656
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-600 text-white">
657
- 💎 Rails
658
- </span>
659
- )
660
- }
661
-
662
- // Laravel - look for Laravel/Artisan patterns
663
- else if (
664
- message.includes("laravel development server") ||
665
- message.includes("artisan serve") ||
666
- (message.includes("laravel") && message.includes("127.0.0.1:8000"))
667
- ) {
668
- return (
669
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-500 text-white">
670
- 🅻 Laravel
671
- </span>
672
- )
673
- }
674
-
675
- // Express.js/Node.js - look for Express patterns
676
- else if (
677
- message.includes("express server") ||
678
- message.includes("server listening on") ||
679
- message.includes("app listening on port") ||
680
- message.includes("node server")
681
- ) {
682
- return (
683
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-700 text-white">
684
- 🟩 Express
685
- </span>
686
- )
687
- }
688
-
689
- // Django - look for Django patterns
690
- else if (
691
- message.includes("django") ||
692
- (message.includes("development server") && message.includes("127.0.0.1:8000")) ||
693
- message.includes("runserver")
694
- ) {
695
- return (
696
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-800 text-white">
697
- 🐍 Django
698
- </span>
699
- )
700
- }
701
-
702
- // Flask - look for Flask patterns
703
- else if (
704
- message.includes("flask") ||
705
- message.includes("running on http://127.0.0.1:5000") ||
706
- message.includes("debug mode: on")
707
- ) {
708
- return (
709
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-800 text-white">
710
- 🌶️ Flask
711
- </span>
712
- )
713
- }
714
-
715
- // Svelte/SvelteKit - look for Svelte patterns
716
- else if (
717
- message.includes("sveltekit") ||
718
- (message.includes("svelte") && message.includes("dev")) ||
719
- message.includes("@sveltejs")
720
- ) {
721
- return (
722
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-orange-500 text-white">
723
- 🔥 Svelte
724
- </span>
725
- )
726
- }
727
-
728
- // Remix - look for Remix patterns
729
- else if (message.includes("remix") || message.includes("@remix-run")) {
730
- return (
731
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-400 text-white">
732
- 💿 Remix
733
- </span>
734
- )
735
- }
736
-
737
- // Astro - look for Astro patterns
738
- else if (message.includes("astro") || message.includes("@astrojs")) {
739
- return (
740
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-600 text-white">
741
- 🚀 Astro
742
- </span>
743
- )
744
- }
745
-
746
- // Spring Boot - look for Spring patterns
747
- else if (
748
- message.includes("spring boot") ||
749
- message.includes("tomcat started on port") ||
750
- (message.includes("started application") && message.includes("seconds"))
751
- ) {
752
- return (
753
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-500 text-white">
754
- 🍃 Spring
755
- </span>
756
- )
757
- }
758
-
759
- // Generic React (CRA, webpack-dev-server) - fallback for React patterns
760
- else if (
761
- message.includes("webpack compiled") ||
762
- message.includes("compiled successfully") ||
763
- message.includes("webpack-dev-server")
764
- ) {
765
- return (
766
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-600 text-white">
767
- ⚛️ React
768
- </span>
769
- )
770
- }
771
-
772
- return null
773
- })()}
774
- </div>
775
- </div>
776
- </div>
777
- </div>
778
-
779
- {entry.screenshot && (
780
- <div className="mt-2">
781
- <Image
782
- src={entry.screenshot.startsWith("http") ? entry.screenshot : `/api/screenshots/${entry.screenshot}`}
783
- alt="Screenshot"
784
- width={800}
785
- height={600}
786
- className="w-full h-auto max-w-2xl border rounded shadow-sm"
787
- style={{ maxHeight: "600px", objectFit: "contain" }}
788
- unoptimized
789
- />
790
- </div>
791
- )}
792
-
793
- {entry.screencast && (
794
- <div className="mt-2">
795
- <a
796
- href={entry.screencast}
797
- target="_blank"
798
- rel="noopener noreferrer"
799
- className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
800
- >
801
- <svg
802
- className="w-5 h-5"
803
- fill="none"
804
- stroke="currentColor"
805
- viewBox="0 0 24 24"
806
- xmlns="http://www.w3.org/2000/svg"
807
- >
808
- <path
809
- strokeLinecap="round"
810
- strokeLinejoin="round"
811
- strokeWidth={2}
812
- d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
813
- />
814
- <path
815
- strokeLinecap="round"
816
- strokeLinejoin="round"
817
- strokeWidth={2}
818
- d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
819
- />
820
- </svg>
821
- View Jank Analysis Video
822
- </a>
823
- </div>
824
- )}
825
- </div>
826
- )
827
- }
828
-
829
- interface LogsClientProps {
830
- version: string
831
- initialData?: {
832
- logs: LogEntry[]
833
- logFiles: LogFile[]
834
- currentLogFile: string
835
- mode: "head" | "tail"
836
- }
837
- }
838
-
839
- export default function LogsClient({ version, initialData }: LogsClientProps) {
840
- const router = useRouter()
841
- const searchParams = useSearchParams()
842
- const [darkMode, setDarkMode] = useDarkMode()
843
- const [logs, setLogs] = useState<LogEntry[]>(initialData?.logs || [])
844
- const [mode, setMode] = useState<"head" | "tail">(initialData?.mode || "tail")
845
-
846
- // Update mode when URL parameters change
847
- useEffect(() => {
848
- const urlMode = searchParams.get("mode") as "head" | "tail" | null
849
- if (urlMode && urlMode !== mode) {
850
- setMode(urlMode)
851
- }
852
- }, [searchParams, mode])
853
- const [isAtBottom, setIsAtBottom] = useState(true)
854
- const [isInitialLoading, setIsInitialLoading] = useState(!initialData)
855
- const [lastLogCount, setLastLogCount] = useState(initialData?.logs.length || 0)
856
- const [lastFetched, setLastFetched] = useState<Date | null>(null)
857
- const [availableLogs, setAvailableLogs] = useState<LogFile[]>(initialData?.logFiles || [])
858
- const [currentLogFile, setCurrentLogFile] = useState<string>(initialData?.currentLogFile || "")
859
- const [projectName, setProjectName] = useState<string>("")
860
- const [showLogSelector, setShowLogSelector] = useState(false)
861
- const [isReplaying, setIsReplaying] = useState(false)
862
- const [showFilters, setShowFilters] = useState(false)
863
- const [showReplayPreview, setShowReplayPreview] = useState(false)
864
- const [replayEvents, setReplayEvents] = useState<ReplayEvent[]>([])
865
- const [isRotatingLog, setIsRotatingLog] = useState(false)
866
- const [retryCount, setRetryCount] = useState(0)
867
- const [maxRetries] = useState(5)
868
- const [lastFailedUrl, setLastFailedUrl] = useState<string | null>(null)
869
- const [hasLoadedInitial, setHasLoadedInitial] = useState(false)
870
- const [filters, setFilters] = useState({
871
- browser: true,
872
- server: true,
873
- interaction: true,
874
- screenshot: true
875
- })
876
- const [userAgentFilters, setUserAgentFilters] = useState<Record<string, boolean>>({})
877
- const [logBuffer, setLogBuffer] = useState<LogEntry[]>([]) // Buffer logs when not in live mode
878
- const bottomRef = useRef<HTMLDivElement>(null)
879
- const containerRef = useRef<HTMLDivElement>(null)
880
- const pollIntervalRef = useRef<NodeJS.Timeout | null>(null)
881
- const dropdownRef = useRef<HTMLDivElement>(null)
882
- const filterDropdownRef = useRef<HTMLDivElement>(null)
883
- const userScrolledManually = useRef<boolean>(false) // Track if user manually scrolled away from live mode
884
-
885
- const loadAvailableLogs = useCallback(async () => {
886
- try {
887
- const response = await fetch("/api/logs/list")
888
- if (response.ok) {
889
- const data: LogListResponse = await response.json()
890
- setAvailableLogs(data.files)
891
- setCurrentLogFile(data.currentFile)
892
- setProjectName(data.projectName)
893
- return true
894
- }
895
- return false
896
- } catch (error) {
897
- console.error("Error loading available logs:", error)
898
- return false
899
- }
900
- }, [])
901
-
902
- const pollForNewLogs = useCallback(async () => {
903
- if (mode !== "tail") return
904
-
905
- try {
906
- // Determine which log file to poll
907
- const requestedFile = searchParams.get("file")
908
- let logPath = ""
909
- let isCurrentFile = true
910
-
911
- if (requestedFile && availableLogs.length > 0) {
912
- // Find the specific log file requested
913
- const foundFile = availableLogs.find((f) => f.name === requestedFile)
914
- logPath = foundFile?.path || currentLogFile
915
- isCurrentFile = foundFile?.isCurrent !== false
916
- } else {
917
- // Use current log file
918
- logPath = currentLogFile
919
- isCurrentFile = true
920
- }
921
-
922
- // Only poll for new logs if viewing the current (active) log file
923
- if (!isCurrentFile) return
924
-
925
- // Build API URL with logPath parameter if needed
926
- // For polling, only fetch a small number of new lines to reduce server load
927
- const apiUrl = logPath
928
- ? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
929
- : `/api/logs/tail?lines=50`
930
-
931
- // Check if we've exceeded retry limit for this specific URL
932
- if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
933
- console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Stopping polling.`)
934
- // Clear the polling interval to stop further attempts
935
- if (pollIntervalRef.current) {
936
- clearInterval(pollIntervalRef.current)
937
- pollIntervalRef.current = null
938
- }
939
- return
940
- }
941
-
942
- const response = await fetch(apiUrl)
943
- if (!response.ok) {
944
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
945
- }
946
-
947
- // Reset retry count on successful fetch
948
- if (lastFailedUrl === apiUrl || retryCount > 0) {
949
- setRetryCount(0)
950
- setLastFailedUrl(null)
951
- }
952
-
953
- const data: LogsApiResponse = await response.json()
954
-
955
- if (!data.logs) {
956
- console.warn("No logs data in response")
957
- return
958
- }
959
-
960
- const entries = parseLogEntries(data.logs)
961
-
962
- if (entries.length > lastLogCount) {
963
- if (isAtBottom) {
964
- // In live mode - apply logs immediately and flush buffer
965
- setLastFetched(new Date()) // Only update "Last updated" when actually showing new logs
966
-
967
- if (logBuffer.length > 0) {
968
- // Merge buffer with latest entries
969
- setLogs([...logs, ...logBuffer, ...entries.slice(lastLogCount)])
970
- setLogBuffer([]) // Clear buffer
971
- } else {
972
- setLogs(entries)
973
- }
974
- setLastLogCount(entries.length)
975
-
976
- // Auto-scroll to bottom - but only if user hasn't manually scrolled away
977
- if (!userScrolledManually.current) {
978
- setTimeout(() => {
979
- bottomRef.current?.scrollIntoView({ behavior: "smooth" })
980
- }, 50)
981
- }
982
- } else {
983
- // Not in live mode - buffer the new entries (don't update "Last updated")
984
- const newEntries = entries.slice(lastLogCount)
985
- if (newEntries.length > 0) {
986
- setLogBuffer((prev) => [...prev, ...newEntries])
987
- setLastLogCount(entries.length)
988
- }
989
- }
990
- }
991
- } catch (error) {
992
- console.error("Error polling logs:", error)
993
-
994
- // Build the failed URL for tracking
995
- const requestedFile = searchParams.get("file")
996
- let logPath = ""
997
- if (requestedFile && availableLogs.length > 0) {
998
- const foundFile = availableLogs.find((f) => f.name === requestedFile)
999
- logPath = foundFile?.path || currentLogFile
1000
- } else {
1001
- logPath = currentLogFile
1002
- }
1003
- const failedUrl = logPath
1004
- ? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
1005
- : `/api/logs/tail?lines=50`
1006
-
1007
- // Increment retry count if it's the same URL, otherwise reset
1008
- if (lastFailedUrl === failedUrl) {
1009
- const newRetryCount = retryCount + 1
1010
- setRetryCount(newRetryCount)
1011
-
1012
- if (newRetryCount >= maxRetries) {
1013
- console.error(`Maximum retry attempts (${maxRetries}) reached for ${failedUrl}. Stopping polling.`)
1014
- // Clear the polling interval to stop further attempts
1015
- if (pollIntervalRef.current) {
1016
- clearInterval(pollIntervalRef.current)
1017
- pollIntervalRef.current = null
1018
- }
1019
- }
1020
- } else {
1021
- setLastFailedUrl(failedUrl)
1022
- setRetryCount(1)
1023
- }
1024
- }
1025
- }, [
1026
- mode,
1027
- isAtBottom,
1028
- searchParams,
1029
- availableLogs,
1030
- currentLogFile,
1031
- lastLogCount,
1032
- logBuffer,
1033
- logs,
1034
- retryCount,
1035
- lastFailedUrl,
1036
- maxRetries
1037
- ])
1038
-
1039
- // Start/stop polling based on mode (always poll in tail mode, but buffer when not at bottom)
1040
- useEffect(() => {
1041
- if (mode === "tail" && retryCount < maxRetries) {
1042
- pollIntervalRef.current = setInterval(pollForNewLogs, 3000) // Poll every 3 seconds
1043
- return () => {
1044
- if (pollIntervalRef.current) {
1045
- clearInterval(pollIntervalRef.current)
1046
- }
1047
- }
1048
- } else {
1049
- if (pollIntervalRef.current) {
1050
- clearInterval(pollIntervalRef.current)
1051
- }
1052
- }
1053
- }, [mode, pollForNewLogs, retryCount, maxRetries]) // Removed isAtBottom - now always poll in tail mode
1054
-
1055
- // Handle returning to live mode - ONLY flush buffer when user explicitly clicks "Live" button
1056
- // This effect is removed to prevent race conditions - buffer flushing now happens only on explicit user action
1057
-
1058
- const loadInitialLogs = useCallback(async () => {
1059
- if (retryCount >= maxRetries) {
1060
- console.log("Max retries reached, not attempting to load")
1061
- return
1062
- }
1063
-
1064
- setIsInitialLoading(true)
1065
-
1066
- // Load available logs list first
1067
- const logsListLoaded = await loadAvailableLogs()
1068
- if (!logsListLoaded) {
1069
- setIsInitialLoading(false)
1070
- return
1071
- }
1072
-
1073
- try {
1074
- // Determine which log file to load
1075
- const requestedFile = searchParams.get("file")
1076
- let logPath = ""
1077
-
1078
- if (requestedFile && availableLogs.length > 0) {
1079
- // Find the specific log file requested
1080
- const foundFile = availableLogs.find((f) => f.name === requestedFile)
1081
- logPath = foundFile?.path || currentLogFile
1082
- } else {
1083
- // Use current log file
1084
- logPath = currentLogFile
1085
- }
1086
-
1087
- // Build API URL with logPath parameter if needed
1088
- const apiUrl = logPath
1089
- ? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
1090
- : `/api/logs/${mode}?lines=1000`
1091
-
1092
- // Check if we've exceeded retry limit for this specific URL
1093
- if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
1094
- console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Not attempting to load.`)
1095
- setLogs([])
1096
- setIsInitialLoading(false)
1097
- return
1098
- }
1099
-
1100
- const response = await fetch(apiUrl)
1101
- if (!response.ok) {
1102
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
1103
- }
1104
-
1105
- // Reset retry count on successful fetch
1106
- if (lastFailedUrl === apiUrl || retryCount > 0) {
1107
- setRetryCount(0)
1108
- setLastFailedUrl(null)
1109
- }
1110
-
1111
- const data: LogsApiResponse = await response.json()
1112
-
1113
- if (!data.logs) {
1114
- console.warn("No logs data in response")
1115
- setLogs([])
1116
- setIsInitialLoading(false)
1117
- return
1118
- }
1119
-
1120
- const entries = parseLogEntries(data.logs)
1121
-
1122
- setLogs(entries)
1123
- setLastLogCount(entries.length)
1124
- setLastFetched(new Date())
1125
- setIsInitialLoading(false)
1126
-
1127
- // Auto-scroll to bottom for tail mode - only if user hasn't manually scrolled away
1128
- if (mode === "tail" && !userScrolledManually.current) {
1129
- setIsAtBottom(true)
1130
- const scrollToBottom = () => {
1131
- if (bottomRef.current) {
1132
- bottomRef.current.scrollIntoView({ behavior: "auto" })
1133
- } else if (containerRef.current) {
1134
- containerRef.current.scrollTop = containerRef.current.scrollHeight
1135
- }
1136
- }
1137
- // Try multiple times to ensure it works
1138
- setTimeout(scrollToBottom, 0)
1139
- setTimeout(scrollToBottom, 100)
1140
- }
1141
- } catch (error) {
1142
- console.error("Error loading logs:", error)
1143
-
1144
- // Build the failed URL for tracking
1145
- const requestedFile = searchParams.get("file")
1146
- let logPath = ""
1147
- if (requestedFile && availableLogs.length > 0) {
1148
- const foundFile = availableLogs.find((f) => f.name === requestedFile)
1149
- logPath = foundFile?.path || currentLogFile
1150
- } else {
1151
- logPath = currentLogFile
1152
- }
1153
- const failedUrl = logPath
1154
- ? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
1155
- : `/api/logs/${mode}?lines=1000`
1156
-
1157
- // Track retry attempts
1158
- if (lastFailedUrl === failedUrl) {
1159
- const newRetryCount = retryCount + 1
1160
- setRetryCount(newRetryCount)
1161
-
1162
- if (newRetryCount >= maxRetries) {
1163
- console.error(`Maximum retry attempts (${maxRetries}) reached for initial load of ${failedUrl}.`)
1164
- }
1165
- } else {
1166
- setLastFailedUrl(failedUrl)
1167
- setRetryCount(1)
1168
- }
1169
-
1170
- setLogs([])
1171
- } finally {
1172
- setIsInitialLoading(false)
1173
- }
1174
- }, [loadAvailableLogs, searchParams, availableLogs, currentLogFile, mode, retryCount, lastFailedUrl, maxRetries])
1175
-
1176
- useEffect(() => {
1177
- // Only load logs if we don't have initial data and haven't tried loading yet
1178
- const hasInitialData = initialData?.logs && initialData?.logs.length > 0
1179
-
1180
- if (!hasLoadedInitial && !hasInitialData && !logs.length) {
1181
- // No server-side data and no client data - load fresh
1182
- setHasLoadedInitial(true)
1183
- loadInitialLogs()
1184
- } else if (hasInitialData && logs.length === 0) {
1185
- // We have server-side data but client state is empty - use server data
1186
- setLogs(initialData.logs)
1187
- setIsInitialLoading(false)
1188
- if (mode === "tail" && !userScrolledManually.current) {
1189
- // Scroll to bottom for tail mode with server data - only if user hasn't manually scrolled away
1190
- setIsAtBottom(true)
1191
- const scrollToBottom = () => {
1192
- if (bottomRef.current) {
1193
- bottomRef.current.scrollIntoView({ behavior: "auto" })
1194
- } else if (containerRef.current) {
1195
- containerRef.current.scrollTop = containerRef.current.scrollHeight
1196
- }
1197
- }
1198
- // Try multiple times to ensure it works
1199
- setTimeout(scrollToBottom, 0)
1200
- setTimeout(scrollToBottom, 100)
1201
- setTimeout(scrollToBottom, 300)
1202
- }
1203
- }
1204
- }, [mode, initialData, logs.length, hasLoadedInitial, loadInitialLogs]) // eslint-disable-line react-hooks/exhaustive-deps
1205
- // CRITICAL: loadInitialLogs must NEVER be added to dependencies - it will cause infinite loops
1206
- // The linter will complain but this is intentional. DO NOT "FIX" THIS.
1207
-
1208
- // Separate effect to handle scrolling after logs are rendered
1209
- useEffect(() => {
1210
- if (logs.length > 0 && mode === "tail" && isAtBottom && !userScrolledManually.current) {
1211
- const scrollToBottom = () => {
1212
- if (bottomRef.current) {
1213
- bottomRef.current.scrollIntoView({ behavior: "auto" })
1214
- } else if (containerRef.current) {
1215
- containerRef.current.scrollTop = containerRef.current.scrollHeight
1216
- }
1217
- }
1218
- // Scroll after DOM updates
1219
- setTimeout(scrollToBottom, 0)
1220
- }
1221
- }, [logs.length, mode, isAtBottom]) // Trigger when logs are actually rendered
1222
-
1223
- useEffect(() => {
1224
- return () => {
1225
- if (pollIntervalRef.current) {
1226
- clearInterval(pollIntervalRef.current)
1227
- }
1228
- }
1229
- }, [])
1230
-
1231
- // Close dropdowns when clicking outside
1232
- useEffect(() => {
1233
- const handleClickOutside = (event: MouseEvent) => {
1234
- if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
1235
- setShowLogSelector(false)
1236
- }
1237
- if (filterDropdownRef.current && !filterDropdownRef.current.contains(event.target as Node)) {
1238
- setShowFilters(false)
1239
- }
1240
- }
1241
-
1242
- if (showLogSelector || showFilters) {
1243
- document.addEventListener("mousedown", handleClickOutside)
1244
- return () => document.removeEventListener("mousedown", handleClickOutside)
1245
- }
1246
- }, [showLogSelector, showFilters])
1247
-
1248
- const handleScroll = () => {
1249
- if (containerRef.current) {
1250
- const { scrollTop, scrollHeight, clientHeight } = containerRef.current
1251
- const atBottom = scrollTop + clientHeight >= scrollHeight - 10
1252
-
1253
- // If user scrolled up from the bottom, mark as manual scroll
1254
- if (isAtBottom && !atBottom) {
1255
- userScrolledManually.current = true
1256
- }
1257
-
1258
- // If user scrolled back to bottom, clear manual scroll flag only via explicit action
1259
- // (not automatically - they need to click the "Live" button)
1260
-
1261
- setIsAtBottom(atBottom)
1262
- }
1263
- }
1264
-
1265
- const handleReplay = async () => {
1266
- if (isReplaying) return
1267
-
1268
- setIsReplaying(true)
1269
-
1270
- try {
1271
- // Get replay data from logs
1272
- const response = await fetch("/api/replay?action=parse")
1273
- if (!response.ok) {
1274
- throw new Error("Failed to parse replay data")
1275
- }
1276
-
1277
- const replayData = await response.json()
1278
-
1279
- if (replayData.interactions.length === 0) {
1280
- alert("No user interactions found in logs to replay")
1281
- return
1282
- }
1283
-
1284
- // Generate CDP commands for replay
1285
- const response2 = await fetch("/api/replay", {
1286
- method: "POST",
1287
- headers: { "Content-Type": "application/json" },
1288
- body: JSON.stringify({
1289
- action: "execute",
1290
- replayData: replayData,
1291
- speed: 2
1292
- })
1293
- })
1294
-
1295
- const result = await response2.json()
1296
-
1297
- if (result.success) {
1298
- console.log("Replay executed successfully:", result)
1299
- alert(`Replay completed! Executed ${result.totalCommands} commands.`)
1300
- } else {
1301
- console.log("CDP execution failed, showing commands:", result)
1302
- alert(
1303
- `CDP execution not available. Generated ${result.commands?.length || 0} commands. Check console for details.`
1304
- )
1305
- }
1306
- } catch (error) {
1307
- console.error("Replay error:", error)
1308
- alert(`Failed to start replay: ${error instanceof Error ? error.message : "Unknown error"}`)
1309
- } finally {
1310
- setIsReplaying(false)
1311
- }
1312
- }
1313
-
1314
- const loadReplayPreview = () => {
1315
- // Extract interactions from current logs instead of making API call
1316
- const interactions = logs
1317
- .filter((log) => log.message.includes("[INTERACTION]"))
1318
- .map((log) => {
1319
- const match = log.message.match(/\[INTERACTION\] (.+)/)
1320
- if (match) {
1321
- try {
1322
- // Try parsing as JSON (new format)
1323
- const data = JSON.parse(match[1])
1324
- return {
1325
- timestamp: log.timestamp,
1326
- event: data.type || "unknown",
1327
- details: JSON.stringify(data),
1328
- type: data.type,
1329
- x: data.x,
1330
- y: data.y,
1331
- target: data.target,
1332
- direction: data.direction,
1333
- distance: data.distance,
1334
- key: data.key
1335
- } as ReplayEvent
1336
- } catch {
1337
- // Fallback to old format parsing
1338
- const oldMatch = match[1].match(/(CLICK|TAP|SCROLL|KEY) (.+)/)
1339
- if (oldMatch) {
1340
- return {
1341
- timestamp: log.timestamp,
1342
- event: oldMatch[1],
1343
- details: oldMatch[2],
1344
- type: oldMatch[1]
1345
- } as ReplayEvent
1346
- }
1347
- }
1348
- }
1349
- return null
1350
- })
1351
- .filter((item): item is ReplayEvent => item !== null)
1352
-
1353
- setReplayEvents(interactions)
1354
- }
1355
-
1356
- const handleRotateLog = async () => {
1357
- if (!currentLogFile || isRotatingLog) return
1358
-
1359
- const confirmed = window.confirm(
1360
- "Clear logs and start fresh?\n\n" +
1361
- "This will:\n" +
1362
- "• Archive the current log file\n" +
1363
- "• Start a new empty log file\n" +
1364
- "• Clear the current view\n\n" +
1365
- "The archived logs will still be available in the dropdown."
1366
- )
1367
-
1368
- if (!confirmed) return
1369
-
1370
- setIsRotatingLog(true)
1371
- try {
1372
- const response = await fetch("/api/logs/rotate", {
1373
- method: "POST",
1374
- headers: {
1375
- "Content-Type": "application/json"
1376
- },
1377
- body: JSON.stringify({ currentLogPath: currentLogFile })
1378
- })
1379
-
1380
- if (response.ok) {
1381
- // Clear current logs from UI
1382
- setLogs([])
1383
- setLastLogCount(0)
1384
- setLastFetched(null)
1385
-
1386
- // Reload available logs to show the new archived file
1387
- await loadAvailableLogs()
1388
-
1389
- // Start fresh polling
1390
- await loadInitialLogs()
1391
- } else {
1392
- const error = await response.json()
1393
- console.error("Failed to rotate log:", error)
1394
- alert(`Failed to rotate log: ${error.error}`)
1395
- }
1396
- } catch (error) {
1397
- console.error("Error rotating log:", error)
1398
- alert("Error rotating log")
1399
- } finally {
1400
- setIsRotatingLog(false)
1401
- }
1402
- }
1403
-
1404
- // Compute available user agents from browser logs
1405
- const availableUserAgents = useMemo(() => {
1406
- const userAgents = new Set<string>()
1407
- logs.forEach((entry) => {
1408
- if (entry.source === "BROWSER" && entry.userAgent) {
1409
- userAgents.add(entry.userAgent)
1410
- }
1411
- })
1412
- return Array.from(userAgents).sort()
1413
- }, [logs])
1414
-
1415
- // Update user agent filters when available user agents change
1416
- useEffect(() => {
1417
- if (availableUserAgents.length > 0) {
1418
- setUserAgentFilters((prev) => {
1419
- const newFilters = { ...prev }
1420
- // Enable all user agents by default if not already set
1421
- availableUserAgents.forEach((ua) => {
1422
- if (!(ua in newFilters)) {
1423
- newFilters[ua] = true
1424
- }
1425
- })
1426
- return newFilters
1427
- })
1428
- }
1429
- }, [availableUserAgents])
1430
-
1431
- const filteredLogs = useMemo(() => {
1432
- return logs.filter((entry) => {
1433
- // Check specific message types first (these override source filtering)
1434
- const isInteraction = entry.message.includes("[INTERACTION]")
1435
- const isScreenshot = entry.message.includes("[SCREENSHOT]")
1436
-
1437
- if (isInteraction) return filters.interaction
1438
- if (isScreenshot) return filters.screenshot
1439
-
1440
- // For other logs, filter by source
1441
- if (entry.source === "SERVER") return filters.server
1442
- if (entry.source === "BROWSER") {
1443
- // First check if browser logs are enabled at all
1444
- if (!filters.browser) return false
1445
-
1446
- // If there are user agent filters and this entry has a user agent, apply UA filtering
1447
- if (availableUserAgents.length > 0 && entry.userAgent) {
1448
- return userAgentFilters[entry.userAgent] !== false
1449
- }
1450
-
1451
- // Otherwise, just show if browser is enabled
1452
- return true
1453
- }
1454
-
1455
- return true
1456
- })
1457
- }, [logs, filters, userAgentFilters, availableUserAgents])
1458
-
1459
- return (
1460
- <div className="h-screen bg-background text-foreground flex flex-col transition-colors">
1461
- {/* Header - Fixed */}
1462
- <div className="bg-card shadow-sm border-b border-border flex-none z-10">
1463
- <div className="max-w-7xl mx-auto px-4 py-3">
1464
- <div className="flex items-center justify-between">
1465
- <div className="flex items-center gap-2 sm:gap-4">
1466
- <div className="flex items-center gap-1">
1467
- <h1 className="text-xl sm:text-2xl font-bold whitespace-nowrap">dev3000</h1>
1468
- <span className="text-xs text-muted-foreground whitespace-nowrap">(v{version})</span>
1469
- </div>
1470
- {/* Log File Selector */}
1471
- {availableLogs.length > 1 ? (
1472
- <div className="relative" ref={dropdownRef}>
1473
- <button
1474
- type="button"
1475
- onClick={() => setShowLogSelector(!showLogSelector)}
1476
- className="flex items-center gap-2 px-3 py-1 text-sm text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
1477
- >
1478
- <span className="font-mono text-xs whitespace-nowrap">
1479
- {isInitialLoading && !currentLogFile ? (
1480
- <div className="h-4 bg-muted rounded animate-pulse" style={{ width: "220px" }} />
1481
- ) : currentLogFile ? (
1482
- "Current"
1483
- ) : (
1484
- "No log file"
1485
- )}
1486
- </span>
1487
- <svg
1488
- className={`w-4 h-4 transition-transform ${showLogSelector ? "rotate-180" : ""}`}
1489
- fill="none"
1490
- stroke="currentColor"
1491
- viewBox="0 0 24 24"
1492
- >
1493
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1494
- </svg>
1495
- </button>
1496
- {/* Dropdown */}
1497
- {showLogSelector && availableLogs.length > 1 && (
1498
- <div className="absolute top-full left-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-80">
1499
- <div className="py-1 max-h-60 overflow-y-auto">
1500
- <div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
1501
- {projectName} logs ({availableLogs.length})
1502
- </div>
1503
- {availableLogs.map((logFile) => (
1504
- <button
1505
- type="button"
1506
- key={logFile.path}
1507
- onClick={() => {
1508
- setShowLogSelector(false)
1509
- router.push(`/logs?file=${encodeURIComponent(logFile.name)}&mode=${mode}`)
1510
- }}
1511
- className={`w-full text-left px-3 py-2 text-sm hover:bg-accent flex items-center justify-between ${
1512
- logFile.isCurrent ? "bg-primary/10 text-primary" : "text-foreground"
1513
- }`}
1514
- >
1515
- <div className="flex flex-col">
1516
- <span className="font-mono text-xs">{logFile.name}</span>
1517
- <span className="text-xs text-muted-foreground">
1518
- {new Date(logFile.mtime).toLocaleString()} • {Math.round(logFile.size / 1024)}KB
1519
- </span>
1520
- </div>
1521
- {logFile.isCurrent && <span className="text-xs text-blue-600 font-medium">current</span>}
1522
- </button>
1523
- ))}
1524
- </div>
1525
- </div>
1526
- )}
1527
- </div>
1528
- ) : (
1529
- <div className="flex items-center gap-2">
1530
- <span className="font-mono text-xs text-gray-600 px-3 py-1 whitespace-nowrap">
1531
- {isInitialLoading && !currentLogFile ? (
1532
- <div className="h-4 bg-gray-200 rounded animate-pulse" style={{ width: "220px" }} />
1533
- ) : currentLogFile ? (
1534
- // Show basename for all files
1535
- currentLogFile
1536
- .split("/")
1537
- .pop()
1538
- ) : (
1539
- "No log file"
1540
- )}
1541
- </span>
1542
- </div>
1543
- )}
1544
-
1545
- {/* Entries count with clear button */}
1546
- {logs.length > 0 && (
1547
- <div className="flex items-center gap-1 hidden sm:flex">
1548
- <span className="text-sm text-muted-foreground">{logs.length} entries</span>
1549
- {currentLogFile && !isInitialLoading && (
1550
- <button
1551
- type="button"
1552
- onClick={handleRotateLog}
1553
- disabled={isRotatingLog}
1554
- className="p-0.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
1555
- title="Clear logs (rotate current log to archive and start fresh)"
1556
- >
1557
- {isRotatingLog ? (
1558
- <div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
1559
- ) : (
1560
- <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1561
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1562
- </svg>
1563
- )}
1564
- </button>
1565
- )}
1566
- </div>
1567
- )}
1568
-
1569
- {/* Buffered logs indicator */}
1570
- {logBuffer.length > 0 && !isAtBottom && (
1571
- <span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded-full">
1572
- +{logBuffer.length} buffered
1573
- </span>
1574
- )}
1575
- </div>
1576
-
1577
- {/* Mode Toggle */}
1578
- <div className="flex items-center gap-2">
1579
- {/* Replay Button - Hidden until functionality is complete */}
1580
- {false && (
1581
- <div className="relative">
1582
- <button
1583
- type="button"
1584
- onClick={handleReplay}
1585
- disabled={isReplaying}
1586
- onMouseEnter={() => {
1587
- if (!isReplaying) {
1588
- loadReplayPreview()
1589
- setShowReplayPreview(true)
1590
- }
1591
- }}
1592
- onMouseLeave={() => setShowReplayPreview(false)}
1593
- className={`flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap ${
1594
- isReplaying
1595
- ? "bg-gray-100 text-gray-400 cursor-not-allowed"
1596
- : "bg-purple-100 text-purple-800 hover:bg-purple-200"
1597
- }`}
1598
- >
1599
- {isReplaying ? (
1600
- <>
1601
- <div className="w-3 h-3 border border-purple-300 border-t-purple-600 rounded-full animate-spin"></div>
1602
- Replaying...
1603
- </>
1604
- ) : (
1605
- <>
1606
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1607
- <path
1608
- strokeLinecap="round"
1609
- strokeLinejoin="round"
1610
- strokeWidth={2}
1611
- d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h1m4 0h1m6-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M7 7V4a1 1 0 011-1h4a1 1 0 011 1v3m-9 4h16m-5 4v1a1 1 0 01-1 1H8a1 1 0 01-1-1v-1m8 0V9a1 1 0 00-1-1H8a1 1 0 00-1 1v8.001"
1612
- />
1613
- </svg>
1614
- Replay
1615
- </>
1616
- )}
1617
- </button>
1618
- {/* Replay Preview Dropdown */}
1619
- {showReplayPreview && !isReplaying && (
1620
- <div className="absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-30 w-80">
1621
- <div className="py-2">
1622
- <div className="px-3 py-2 text-xs font-medium text-gray-500 border-b">
1623
- Replay Events ({replayEvents.length})
1624
- </div>
1625
- <div className="max-h-60 overflow-y-auto">
1626
- {replayEvents.length === 0 ? (
1627
- <div className="px-3 py-4 text-sm text-gray-500 text-center">No interactions to replay</div>
1628
- ) : (
1629
- replayEvents.map((event, index) => (
1630
- <div
1631
- key={`${event.timestamp}-${index}`}
1632
- className="px-3 py-2 text-sm hover:bg-gray-50 border-b border-gray-100 last:border-b-0"
1633
- >
1634
- <div className="flex items-center gap-2">
1635
- <span
1636
- className={`px-1.5 py-0.5 rounded text-xs font-medium ${
1637
- event.type === "CLICK"
1638
- ? "bg-blue-100 text-blue-800"
1639
- : event.type === "SCROLL"
1640
- ? "bg-green-100 text-green-800"
1641
- : "bg-muted text-muted-foreground"
1642
- }`}
1643
- >
1644
- {event.type}
1645
- </span>
1646
- <span className="text-xs text-gray-500 font-mono">
1647
- {new Date(event.timestamp).toLocaleTimeString()}
1648
- </span>
1649
- </div>
1650
- <div className="mt-1 text-xs text-gray-600 font-mono truncate">
1651
- {event.type === "CLICK" && `(${event.x}, ${event.y}) on ${event.target}`}
1652
- {event.type === "SCROLL" &&
1653
- `${event.direction} ${event.distance}px to (${event.x}, ${event.y})`}
1654
- {event.type === "KEY" && `${event.key} in ${event.target}`}
1655
- </div>
1656
- </div>
1657
- ))
1658
- )}
1659
- </div>
1660
- </div>
1661
- </div>
1662
- )}
1663
- </div>
1664
- )}
1665
- {/* Filter Button */}
1666
- <div className="relative" ref={filterDropdownRef}>
1667
- <button
1668
- type="button"
1669
- onClick={() => setShowFilters(!showFilters)}
1670
- className="flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap bg-muted text-muted-foreground hover:bg-muted/80"
1671
- >
1672
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1673
- <path
1674
- strokeLinecap="round"
1675
- strokeLinejoin="round"
1676
- strokeWidth={2}
1677
- d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
1678
- />
1679
- </svg>
1680
- Filter
1681
- </button>
1682
- {/* Filter Dropdown */}
1683
- {showFilters && (
1684
- <div className="absolute top-full right-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-48">
1685
- <div className="py-2">
1686
- <div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
1687
- Log Types
1688
- </div>
1689
-
1690
- {/* Server Logs */}
1691
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1692
- <div className="flex items-center gap-2">
1693
- <input
1694
- type="checkbox"
1695
- checked={filters.server}
1696
- onChange={(e) =>
1697
- setFilters((prev) => ({
1698
- ...prev,
1699
- server: e.target.checked
1700
- }))
1701
- }
1702
- className="rounded border-input text-primary focus:ring-ring"
1703
- />
1704
- <span className="text-foreground">Server</span>
1705
- </div>
1706
- <span className="text-xs text-muted-foreground">
1707
- {logs.filter((l) => l.source === "SERVER").length}
1708
- </span>
1709
- </label>
1710
-
1711
- {/* Browser Logs */}
1712
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1713
- <div className="flex items-center gap-2">
1714
- <input
1715
- type="checkbox"
1716
- checked={filters.browser}
1717
- onChange={(e) =>
1718
- setFilters((prev) => ({
1719
- ...prev,
1720
- browser: e.target.checked
1721
- }))
1722
- }
1723
- className="rounded border-input text-primary focus:ring-ring"
1724
- />
1725
- <span className="text-foreground">Browser</span>
1726
- </div>
1727
- <span className="text-xs text-muted-foreground">
1728
- {logs.filter((l) => l.source === "BROWSER").length}
1729
- </span>
1730
- </label>
1731
-
1732
- {/* User Agent Sub-filters */}
1733
- {availableUserAgents.length > 1 && filters.browser && (
1734
- <div className="ml-6 border-l border-border pl-2">
1735
- {availableUserAgents.map((ua) => {
1736
- const shortUA = ua.includes("Chrome")
1737
- ? "Chrome"
1738
- : ua.includes("Firefox")
1739
- ? "Firefox"
1740
- : ua.includes("Safari")
1741
- ? "Safari"
1742
- : ua.includes("Edge")
1743
- ? "Edge"
1744
- : "Browser"
1745
- return (
1746
- <label
1747
- key={ua}
1748
- className="flex items-center justify-between px-2 py-1 text-xs hover:bg-accent cursor-pointer"
1749
- >
1750
- <div className="flex items-center gap-2">
1751
- <input
1752
- type="checkbox"
1753
- checked={userAgentFilters[ua] !== false}
1754
- onChange={(e) =>
1755
- setUserAgentFilters((prev) => ({
1756
- ...prev,
1757
- [ua]: e.target.checked
1758
- }))
1759
- }
1760
- className="rounded border-input text-primary focus:ring-ring w-3 h-3"
1761
- />
1762
- <span className="text-foreground">{shortUA}</span>
1763
- </div>
1764
- <span className="text-xs text-muted-foreground">
1765
- {logs.filter((l) => l.source === "BROWSER" && l.userAgent === ua).length}
1766
- </span>
1767
- </label>
1768
- )
1769
- })}
1770
- </div>
1771
- )}
1772
-
1773
- {/* Interaction Logs */}
1774
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1775
- <div className="flex items-center gap-2">
1776
- <input
1777
- type="checkbox"
1778
- checked={filters.interaction}
1779
- onChange={(e) =>
1780
- setFilters((prev) => ({
1781
- ...prev,
1782
- interaction: e.target.checked
1783
- }))
1784
- }
1785
- className="rounded border-input text-primary focus:ring-ring"
1786
- />
1787
- <span className="text-foreground">Interaction</span>
1788
- </div>
1789
- <span className="text-xs text-muted-foreground">
1790
- {logs.filter((l) => l.message.includes("[INTERACTION]")).length}
1791
- </span>
1792
- </label>
1793
-
1794
- {/* Screenshot Logs */}
1795
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1796
- <div className="flex items-center gap-2">
1797
- <input
1798
- type="checkbox"
1799
- checked={filters.screenshot}
1800
- onChange={(e) =>
1801
- setFilters((prev) => ({
1802
- ...prev,
1803
- screenshot: e.target.checked
1804
- }))
1805
- }
1806
- className="rounded border-input text-primary focus:ring-ring"
1807
- />
1808
- <span className="text-foreground">Screenshot</span>
1809
- </div>
1810
- <span className="text-xs text-muted-foreground">
1811
- {logs.filter((l) => l.message.includes("[SCREENSHOT]")).length}
1812
- </span>
1813
- </label>
1814
- </div>
1815
- </div>
1816
- )}
1817
- </div>
1818
- {/* Head/Tail toggle - commented out for now as it's too noisy */}
1819
- {/* <div className="flex items-center bg-muted rounded-md p-1">
1820
- <button
1821
- type="button"
1822
- onClick={() => {
1823
- const currentFile = searchParams.get("file")
1824
- if (currentFile) {
1825
- router.push(`/logs?file=${encodeURIComponent(currentFile)}&mode=head`)
1826
- } else {
1827
- router.push("/logs?mode=head")
1828
- }
1829
- }}
1830
- className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
1831
- mode === "head"
1832
- ? "bg-background text-foreground shadow-sm"
1833
- : "text-muted-foreground hover:text-foreground"
1834
- }`}
1835
- >
1836
- Head
1837
- </button>
1838
- <button
1839
- type="button"
1840
- onClick={() => {
1841
- const currentFile = searchParams.get("file")
1842
- if (currentFile) {
1843
- router.push(`/logs?file=${encodeURIComponent(currentFile)}&mode=tail`)
1844
- } else {
1845
- router.push("/logs?mode=tail")
1846
- }
1847
- }}
1848
- className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
1849
- mode === "tail"
1850
- ? "bg-background text-foreground shadow-sm"
1851
- : "text-muted-foreground hover:text-foreground"
1852
- }`}
1853
- >
1854
- Tail
1855
- </button>
1856
- </div> */}
1857
- {/* Dark Mode Toggle - moved to last item */}
1858
- <DarkModeToggle darkMode={darkMode} setDarkMode={setDarkMode} className="ml-2" />
1859
- </div>
1860
- </div>
1861
- </div>
1862
- </div>
1863
-
1864
- {/* Content Area - Fills remaining space */}
1865
- <div className="flex-1 overflow-hidden">
1866
- <div ref={containerRef} className="max-w-7xl mx-auto px-4 py-6 h-full overflow-y-auto" onScroll={handleScroll}>
1867
- {isInitialLoading ? (
1868
- <div className="text-center py-12">
1869
- <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
1870
- <div className="text-muted-foreground text-sm mt-4">Loading logs...</div>
1871
- </div>
1872
- ) : logs.length === 0 ? (
1873
- <div className="text-center py-12">
1874
- <div className="text-muted-foreground text-lg">📝 No logs yet</div>
1875
- <div className="text-muted-foreground text-sm mt-2">
1876
- {retryCount >= maxRetries ? (
1877
- <>
1878
- <p className="text-red-500 mb-2">Failed to load logs after {maxRetries} attempts.</p>
1879
- <button
1880
- type="button"
1881
- onClick={() => {
1882
- setRetryCount(0)
1883
- setLastFailedUrl(null)
1884
- setHasLoadedInitial(false)
1885
- loadInitialLogs()
1886
- }}
1887
- className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
1888
- >
1889
- Retry
1890
- </button>
1891
- </>
1892
- ) : (
1893
- "Logs will appear here as your development server runs"
1894
- )}
1895
- </div>
1896
- </div>
1897
- ) : filteredLogs.length === 0 ? (
1898
- <div className="text-center py-12">
1899
- <div className="text-muted-foreground text-lg">🔍 No logs match current filters</div>
1900
- <div className="text-muted-foreground text-sm mt-2">
1901
- Try adjusting your filter settings to see more logs
1902
- </div>
1903
- </div>
1904
- ) : (
1905
- <div className="space-y-1 pb-4">
1906
- {filteredLogs.map((entry, index) => (
1907
- <LogEntryComponent key={`${entry.timestamp}-${index}`} entry={entry} darkMode={darkMode} />
1908
- ))}
1909
- <div ref={bottomRef} />
1910
- </div>
1911
- )}
1912
- </div>
1913
- </div>
1914
-
1915
- {/* Footer - Fixed */}
1916
- <div className="border-t border-border bg-muted/50 flex-none">
1917
- <div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
1918
- <div className="flex items-center gap-3">
1919
- {lastFetched && (
1920
- <span className="text-xs text-muted-foreground font-mono">
1921
- Last updated {lastFetched.toLocaleTimeString()}
1922
- </span>
1923
- )}
1924
- {currentLogFile && (
1925
- <span className="text-xs text-muted-foreground font-mono break-all">{currentLogFile}</span>
1926
- )}
1927
- </div>
1928
-
1929
- {/* Live indicator or scroll to bottom button - positioned on the right */}
1930
- <div className="relative">
1931
- {/* Live indicator when at bottom */}
1932
- <div
1933
- className={`flex items-center gap-1 text-green-600 ${
1934
- mode === "tail" && isAtBottom ? "visible" : "invisible"
1935
- }`}
1936
- >
1937
- <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
1938
- <span className="text-xs">Live</span>
1939
- </div>
1940
-
1941
- {/* Scroll to bottom button when not at bottom */}
1942
- <button
1943
- type="button"
1944
- onClick={() => {
1945
- userScrolledManually.current = false // Clear manual scroll flag
1946
-
1947
- // Flush any buffered logs when explicitly returning to live mode
1948
- if (logBuffer.length > 0) {
1949
- setLogs((prevLogs) => [...prevLogs, ...logBuffer])
1950
- setLogBuffer([])
1951
- setLastFetched(new Date()) // Update "Last updated" when showing buffered logs
1952
- }
1953
-
1954
- bottomRef.current?.scrollIntoView({ behavior: "smooth" })
1955
- }}
1956
- className={`absolute top-0 right-0 flex items-center gap-1 px-2 py-0.5 bg-blue-600 text-white rounded text-xs hover:bg-blue-700 whitespace-nowrap ${
1957
- mode === "tail" && !isAtBottom ? "visible" : "invisible"
1958
- }`}
1959
- >
1960
- ↓ Live
1961
- </button>
1962
- </div>
1963
- </div>
1964
- </div>
1965
- </div>
1966
- )
1967
- }