fleetlens 0.15.2 → 0.15.4

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 (222) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +3 -0
  3. package/app/apps/web/.next/build-manifest.json +3 -3
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +18 -0
  6. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
  14. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  17. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
  18. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
  19. package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
  21. package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/menubar/route/app-paths-manifest.json +3 -0
  23. package/app/apps/web/.next/server/app/api/menubar/route/build-manifest.json +9 -0
  24. package/app/apps/web/.next/server/app/api/menubar/route/server-reference-manifest.json +4 -0
  25. package/app/apps/web/.next/server/app/api/menubar/route.js +6 -0
  26. package/app/apps/web/.next/server/app/api/menubar/route.js.map +5 -0
  27. package/app/apps/web/.next/server/app/api/menubar/route.js.nft.json +1 -0
  28. package/app/apps/web/.next/server/app/api/menubar/route_client-reference-manifest.js +3 -0
  29. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +1 -1
  30. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
  31. package/app/apps/web/.next/server/app/api/projects/open-folder/route/app-paths-manifest.json +3 -0
  32. package/app/apps/web/.next/server/app/api/projects/open-folder/route/build-manifest.json +9 -0
  33. package/app/apps/web/.next/server/app/api/projects/open-folder/route/server-reference-manifest.json +4 -0
  34. package/app/apps/web/.next/server/app/api/projects/open-folder/route.js +7 -0
  35. package/app/apps/web/.next/server/app/api/projects/open-folder/route.js.map +5 -0
  36. package/app/apps/web/.next/server/app/api/projects/open-folder/route.js.nft.json +1 -0
  37. package/app/apps/web/.next/server/app/api/projects/open-folder/route_client-reference-manifest.js +3 -0
  38. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  39. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  40. package/app/apps/web/.next/server/app/api/settings/credentials/route/app-paths-manifest.json +3 -0
  41. package/app/apps/web/.next/server/app/api/settings/credentials/route/build-manifest.json +9 -0
  42. package/app/apps/web/.next/server/app/api/settings/credentials/route/server-reference-manifest.json +4 -0
  43. package/app/apps/web/.next/server/app/api/settings/credentials/route.js +10 -0
  44. package/app/apps/web/.next/server/app/api/settings/credentials/route.js.map +5 -0
  45. package/app/apps/web/.next/server/app/api/settings/credentials/route.js.nft.json +1 -0
  46. package/app/apps/web/.next/server/app/api/settings/credentials/route_client-reference-manifest.js +3 -0
  47. package/app/apps/web/.next/server/app/api/settings/route.js +4 -4
  48. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  49. package/app/apps/web/.next/server/app/changelog/page.js +1 -1
  50. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  51. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  52. package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
  53. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  54. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  55. package/app/apps/web/.next/server/app/day/page.js +1 -1
  56. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  57. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  58. package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
  59. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  60. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  61. package/app/apps/web/.next/server/app/digest/page.js +1 -1
  62. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  63. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
  65. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  66. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  67. package/app/apps/web/.next/server/app/insights/page.js +1 -1
  68. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  69. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  70. package/app/apps/web/.next/server/app/page.js +1 -1
  71. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  72. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  73. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  74. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  75. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
  77. package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
  78. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  79. package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -2
  80. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  81. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  82. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  83. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  84. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  85. package/app/apps/web/.next/server/app/runs/page.js +1 -1
  86. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  87. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  88. package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
  89. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  90. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  91. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  92. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  93. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  94. package/app/apps/web/.next/server/app/settings/page.js +2 -2
  95. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  96. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  97. package/app/apps/web/.next/server/app/team/onboarding/page.js +1 -1
  98. package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -1
  99. package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +1 -1
  100. package/app/apps/web/.next/server/app/team/page.js +1 -1
  101. package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
  102. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  103. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  104. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  105. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  106. package/app/apps/web/.next/server/app-paths-manifest.json +3 -0
  107. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_projects_open-folder_route_actions_0m~419c.js +3 -0
  108. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_settings_credentials_route_actions_04an_i2.js +3 -0
  109. package/app/apps/web/.next/server/chunks/[root-of-the-server]__05z0pcw._.js +3 -0
  110. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0_nle98._.js +3 -0
  111. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b5r~-1._.js +3 -0
  112. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0a0~7d6._.js → [root-of-the-server]__0t7i-hy._.js} +2 -2
  113. package/app/apps/web/.next/server/chunks/[root-of-the-server]__12iid50._.js +3 -0
  114. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_menubar_route_actions_0xep8_i.js +3 -0
  115. package/app/apps/web/.next/server/chunks/{node_modules_next_036my~r._.js → node_modules_next_04~_e52._.js} +5 -5
  116. package/app/apps/web/.next/server/chunks/packages_entries_dist_node_0bckr7-.js +14 -14
  117. package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
  118. package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
  119. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0.zquoa._.js +1 -1
  120. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__03svi~4._.js +8 -0
  121. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__04eq6z2._.js +1 -1
  122. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__04il6m2._.js +1 -1
  123. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08l86e-._.js +1 -1
  124. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09rjz7_._.js +1 -1
  125. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ar_8wr._.js +1 -1
  126. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__10~b4bf._.js → [root-of-the-server]__0blm9gz._.js} +3 -3
  127. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0e.gxk3._.js +1 -1
  128. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0g-tomj._.js +3 -0
  129. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0i.q-1h._.js +1 -1
  130. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0k1x-c9._.js +1 -1
  131. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0lz4a~g._.js +1 -1
  132. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0nr2~y1._.js +3 -0
  133. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0o1_g.~._.js +1 -1
  134. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0p1x6v2._.js +1 -1
  135. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
  136. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0rvzdnx._.js +1 -1
  137. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0stu~~r._.js +1 -1
  138. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0tejjy_._.js +1 -1
  139. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0u39afl._.js +1 -1
  140. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ufi3.w._.js +1 -1
  141. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0xy1qim._.js +1 -1
  142. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +3 -3
  143. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__10srpu0._.js +1 -1
  144. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__10xgshr._.js +1 -1
  145. package/app/apps/web/.next/server/chunks/ssr/_02r-a1a._.js +8 -0
  146. package/app/apps/web/.next/server/chunks/ssr/_0k62ejd._.js +8 -0
  147. package/app/apps/web/.next/server/chunks/ssr/{_0xu2pvc._.js → _0k8yioi._.js} +2 -2
  148. package/app/apps/web/.next/server/chunks/ssr/{_0wj750k._.js → _0t~mx7w._.js} +2 -2
  149. package/app/apps/web/.next/server/chunks/ssr/{_0kajk31._.js → _0whg~r-._.js} +2 -2
  150. package/app/apps/web/.next/server/chunks/ssr/_13jdm1g._.js +3 -0
  151. package/app/apps/web/.next/server/chunks/ssr/apps_web_0~aj0~u._.js +3 -0
  152. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_sessions_sessions-grid_tsx_0bri5x.._.js +1 -1
  153. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0x014hn._.js +16 -0
  154. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  155. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  156. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_12vc-v-._.js +3 -0
  157. package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
  158. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
  159. package/app/apps/web/.next/server/functions-config-manifest.json +3 -0
  160. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  161. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  162. package/app/apps/web/.next/server/pages/500.html +1 -1
  163. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  164. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  165. package/app/apps/web/.next/static/chunks/0-z49mtd-zwgv.js +1 -0
  166. package/app/apps/web/.next/static/chunks/043slkg3juwck.js +1 -0
  167. package/app/apps/web/.next/static/chunks/{0~qdw1b2uz._5.js → 044mtrzh7d7va.js} +3 -3
  168. package/app/apps/web/.next/static/chunks/05b0dn.r00hq1.js +1 -0
  169. package/app/apps/web/.next/static/chunks/{14vx702c32kn6.js → 0_0~~ipv90vm3.js} +1 -1
  170. package/app/apps/web/.next/static/chunks/0awkd5df28gvj.js +6 -0
  171. package/app/apps/web/.next/static/chunks/{0m7dd-2zd9vl2.js → 0eoc4yy0s0gpe.js} +1 -1
  172. package/app/apps/web/.next/static/chunks/0g0hv.dt-de9g.js +14 -0
  173. package/app/apps/web/.next/static/chunks/0rsp61c8wb.sx.js +6 -0
  174. package/app/apps/web/.next/static/chunks/{03rd.3cc_t~yf.js → 0s~0-8l~_kqnf.js} +1 -1
  175. package/app/apps/web/.next/static/chunks/0t2on1dhydyb2.css +1 -0
  176. package/app/apps/web/.next/static/chunks/0y3448zrint0k.js +6 -0
  177. package/app/apps/web/app/api/menubar/route.ts +66 -0
  178. package/app/apps/web/app/api/projects/open-folder/route.ts +60 -0
  179. package/app/apps/web/app/api/settings/credentials/route.ts +45 -0
  180. package/app/apps/web/app/layout.tsx +2 -0
  181. package/app/apps/web/app/projects/[slug]/local-folders-button.tsx +406 -0
  182. package/app/apps/web/app/projects/[slug]/page.tsx +155 -20
  183. package/app/apps/web/app/projects/page.tsx +2 -2
  184. package/app/apps/web/app/sessions/[id]/page.tsx +4 -1
  185. package/app/apps/web/app/sessions/[id]/session-view/header-stats.tsx +81 -0
  186. package/app/apps/web/app/sessions/[id]/session-view.tsx +11 -0
  187. package/app/apps/web/app/sessions/sessions-grid.tsx +56 -25
  188. package/app/apps/web/app/settings/page.tsx +26 -0
  189. package/app/apps/web/app/usage/page.tsx +80 -0
  190. package/app/apps/web/components/menubar-install-banner.tsx +117 -0
  191. package/app/apps/web/components/menubar-install.tsx +107 -0
  192. package/app/apps/web/components/zai-credentials-form.tsx +116 -0
  193. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  194. package/app/apps/web/lib/entries-index.ts +4 -4
  195. package/app/apps/web/lib/menubar-data.ts +50 -0
  196. package/app/apps/web/lib/usage-data.ts +4 -0
  197. package/app/apps/web/package.json +1 -1
  198. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  199. package/dist/daemon-worker.js +640 -240
  200. package/dist/index.js +938 -283
  201. package/package.json +3 -2
  202. package/app/apps/web/.next/server/chunks/[root-of-the-server]__065lib8._.js +0 -3
  203. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0a7n44-._.js +0 -3
  204. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0c00i76._.js +0 -3
  205. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0lhwp-k._.js +0 -8
  206. package/app/apps/web/.next/server/chunks/ssr/_0s3n5mz._.js +0 -8
  207. package/app/apps/web/.next/server/chunks/ssr/_12mbgs5._.js +0 -3
  208. package/app/apps/web/.next/server/chunks/ssr/apps_web_0q2x4a~._.js +0 -3
  209. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0b-4-4j._.js +0 -8
  210. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +0 -16
  211. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0cepfi8._.js +0 -3
  212. package/app/apps/web/.next/static/chunks/00a9oyw~h5i1p.js +0 -1
  213. package/app/apps/web/.next/static/chunks/03gy1evbn94qu.js +0 -6
  214. package/app/apps/web/.next/static/chunks/040s42ypidk7s.css +0 -1
  215. package/app/apps/web/.next/static/chunks/0d2x-wfu.m0-z.js +0 -6
  216. package/app/apps/web/.next/static/chunks/0i77er85v.rj..js +0 -1
  217. package/app/apps/web/.next/static/chunks/0nqklmbs5s3ax.js +0 -6
  218. package/app/apps/web/.next/static/chunks/0tirv_vo6q8uc.js +0 -1
  219. package/app/apps/web/.next/static/chunks/0u6gxpqhl0gc7.js +0 -14
  220. /package/app/apps/web/.next/static/{mTj8nKiazosAKJec15dLe → nmAGY0LiBgXuibWTKZ1RD}/_buildManifest.js +0 -0
  221. /package/app/apps/web/.next/static/{mTj8nKiazosAKJec15dLe → nmAGY0LiBgXuibWTKZ1RD}/_clientMiddlewareManifest.js +0 -0
  222. /package/app/apps/web/.next/static/{mTj8nKiazosAKJec15dLe → nmAGY0LiBgXuibWTKZ1RD}/_ssgManifest.js +0 -0
@@ -15,10 +15,91 @@
15
15
  * a component (no hook calls at module load).
16
16
  */
17
17
  import React, { useRef, useState } from "react";
18
+ import { Github, GitBranch } from "lucide-react";
18
19
  import type { SessionEvent } from "@claude-lens/parser";
20
+ import type { GitFolderInfo } from "@claude-lens/parser/fs";
19
21
  import { formatTokens } from "@/lib/format";
20
22
  import { TooltipRow } from "./tooltip";
21
23
 
24
+ const repoChip = {
25
+ fontFamily: "var(--font-mono)",
26
+ fontSize: 10.5,
27
+ padding: "1px 6px",
28
+ border: "1px solid var(--af-border-subtle)",
29
+ borderRadius: 999,
30
+ whiteSpace: "nowrap" as const,
31
+ };
32
+
33
+ /** The repo a session's cwd belongs to. `branch` is that folder's branch RIGHT
34
+ * NOW — git keeps no record of what it was when the session ran — so it is
35
+ * labelled as such rather than presented as the session's branch. */
36
+ export function RepoStat({
37
+ git,
38
+ worktreeName,
39
+ }: {
40
+ git: GitFolderInfo;
41
+ worktreeName?: string;
42
+ }) {
43
+ const { remote, branch, defaultBranch } = git;
44
+ return (
45
+ <span
46
+ style={{
47
+ display: "inline-flex",
48
+ alignItems: "center",
49
+ gap: 6,
50
+ fontSize: 12,
51
+ color: "var(--af-text-secondary)",
52
+ minWidth: 0,
53
+ }}
54
+ >
55
+ {remote && (
56
+ <a
57
+ href={remote.webUrl}
58
+ target="_blank"
59
+ rel="noreferrer"
60
+ title={remote.url}
61
+ style={{
62
+ display: "inline-flex",
63
+ alignItems: "center",
64
+ gap: 5,
65
+ color: "inherit",
66
+ textDecoration: "none",
67
+ overflow: "hidden",
68
+ textOverflow: "ellipsis",
69
+ whiteSpace: "nowrap",
70
+ }}
71
+ >
72
+ {remote.host === "github.com" ? (
73
+ <Github size={12} aria-label="GitHub" />
74
+ ) : (
75
+ <GitBranch size={12} aria-label={remote.host} />
76
+ )}
77
+ {remote.owner}/{remote.name}
78
+ </a>
79
+ )}
80
+ {worktreeName && (
81
+ <span style={{ ...repoChip, color: "var(--af-text-secondary)" }}>
82
+ worktree: {worktreeName}
83
+ </span>
84
+ )}
85
+ {branch && (
86
+ <span
87
+ title={`This folder is currently on "${branch}"${
88
+ defaultBranch ? ` (default: ${defaultBranch})` : ""
89
+ }. Not necessarily the branch this session ran on.`}
90
+ style={{
91
+ ...repoChip,
92
+ color:
93
+ branch === defaultBranch ? "var(--af-text-tertiary)" : "var(--af-text)",
94
+ }}
95
+ >
96
+ {branch}
97
+ </span>
98
+ )}
99
+ </span>
100
+ );
101
+ }
102
+
22
103
  export function InlineStat({
23
104
  icon,
24
105
  value,
@@ -55,10 +55,13 @@ import {
55
55
  InlineStatDivider,
56
56
  EntrypointBadge,
57
57
  InlineTokenStat,
58
+ RepoStat,
58
59
  useAnchoredTooltip,
59
60
  AnchoredTooltip,
60
61
  } from "./session-view/header-stats";
61
62
  import { Drawer } from "./session-view/drawer";
63
+ // type-only: erased at build, so node:fs never reaches the client bundle
64
+ import type { GitFolderInfo } from "@claude-lens/parser/fs";
62
65
  import {
63
66
  WorkflowsPanel,
64
67
  WorkflowAgentDrawer,
@@ -128,11 +131,13 @@ const VALID_TABS: TabId[] = ["transcript", "workflows", "team", "debug"];
128
131
 
129
132
  export function SessionView({
130
133
  session,
134
+ git,
131
135
  team,
132
136
  teamLead,
133
137
  entries = [],
134
138
  }: {
135
139
  session: SessionDetail;
140
+ git?: GitFolderInfo;
136
141
  team?: (TimelineData & { teamName: string }) | null;
137
142
  teamLead?: { leadSessionId: string; teamName: string; agentName: string } | null;
138
143
  entries?: Entry[];
@@ -1128,6 +1133,12 @@ export function SessionView({
1128
1133
  )}
1129
1134
  <InlineStatDivider />
1130
1135
  <InlineStat icon={<Folder size={12} />} value={projectName} truncate />
1136
+ {git && (git.remote || git.branch || session.worktreeName) && (
1137
+ <>
1138
+ <InlineStatDivider />
1139
+ <RepoStat git={git} worktreeName={session.worktreeName} />
1140
+ </>
1141
+ )}
1131
1142
  <InlineStatDivider />
1132
1143
  {airTimeMs !== undefined && (
1133
1144
  <InlineStat
@@ -4,7 +4,7 @@ import Link from "next/link";
4
4
  import { useCallback, useMemo } from "react";
5
5
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
6
6
  import type { AgentKind, SessionMeta } from "@claude-lens/parser";
7
- import { getAgentMetadata, isAgentKind } from "@claude-lens/parser";
7
+ import { buildProjectKeyResolver, getAgentMetadata, isAgentKind, projectKey } from "@claude-lens/parser";
8
8
  import type { DayOutcome, EntryEnrichmentStatus } from "@claude-lens/entries";
9
9
  import {
10
10
  formatDuration,
@@ -30,6 +30,10 @@ export type SessionRow = {
30
30
  latestLocalDay: string | null;
31
31
  };
32
32
 
33
+ type DisplaySessionRow = SessionRow & {
34
+ projectKey: string;
35
+ };
36
+
33
37
  type SortBy = "newest" | "longest" | "most-tokens" | "outcome";
34
38
 
35
39
  const SORT_VALUES: readonly SortBy[] = ["newest", "longest", "most-tokens", "outcome"] as const;
@@ -38,6 +42,10 @@ function isSortBy(s: string | null | undefined): s is SortBy {
38
42
  return s !== null && s !== undefined && (SORT_VALUES as readonly string[]).includes(s);
39
43
  }
40
44
 
45
+ function totalSessionTokens(s: SessionMeta): number {
46
+ return s.totalUsage.input + s.totalUsage.output + s.totalUsage.cacheRead + s.totalUsage.cacheWrite;
47
+ }
48
+
41
49
  export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
42
50
  const router = useRouter();
43
51
  const pathname = usePathname();
@@ -46,7 +54,22 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
46
54
  // Filter state lives in the URL — back/forward replays the same view, and
47
55
  // returning from a session detail page restores what the user had picked.
48
56
  const query = searchParams.get("q") ?? "";
49
- const project = searchParams.get("project") ?? "all";
57
+ const keyOf = useMemo(
58
+ () => buildProjectKeyResolver(rows.map((r) => r.session)),
59
+ [rows],
60
+ );
61
+ const displayRows = useMemo<DisplaySessionRow[]>(
62
+ () => rows.map((row) => ({ ...row, projectKey: keyOf(row.session) })),
63
+ [rows, keyOf],
64
+ );
65
+ // Older links carried the raw projectName path. Resolve those onto the
66
+ // project key so /sessions?project=… selects the same set as /projects/<key>.
67
+ const project = useMemo(() => {
68
+ const raw = searchParams.get("project") ?? "all";
69
+ if (raw === "all" || !raw.startsWith("/")) return raw;
70
+ const match = rows.find((r) => r.session.projectName === raw);
71
+ return match ? keyOf(match.session) : projectKey(raw);
72
+ }, [searchParams, rows, keyOf]);
50
73
  const agentParam = searchParams.get("agent");
51
74
  const agent: AgentKind | "all" = isAgentKind(agentParam) ? agentParam : "all";
52
75
  const sortBy: SortBy = isSortBy(searchParams.get("sort"))
@@ -72,9 +95,22 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
72
95
  const setSortBy = (v: SortBy) => updateParam("sort", v, "newest");
73
96
 
74
97
  const projects = useMemo(() => {
75
- const s = new Set(rows.map((r) => r.session.projectName));
76
- return ["all", ...Array.from(s).sort()];
77
- }, [rows]);
98
+ const totals = new Map<string, { tokens: number; sessions: number }>();
99
+ for (const r of displayRows) {
100
+ const cur = totals.get(r.projectKey) ?? { tokens: 0, sessions: 0 };
101
+ cur.tokens += totalSessionTokens(r.session);
102
+ cur.sessions += 1;
103
+ totals.set(r.projectKey, cur);
104
+ }
105
+ const ordered = Array.from(totals.entries())
106
+ .sort(([aKey, a], [bKey, b]) => {
107
+ if (b.tokens !== a.tokens) return b.tokens - a.tokens;
108
+ if (b.sessions !== a.sessions) return b.sessions - a.sessions;
109
+ return aKey.localeCompare(bKey);
110
+ })
111
+ .map(([key]) => key);
112
+ return ["all", ...ordered];
113
+ }, [displayRows]);
78
114
 
79
115
  const agentOptions = useMemo(() => {
80
116
  const counts = new Map<AgentKind, number>();
@@ -86,19 +122,20 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
86
122
  }, [rows]);
87
123
 
88
124
  const filtered = useMemo(() => {
89
- let items = rows.slice();
90
- if (project !== "all") items = items.filter((r) => r.session.projectName === project);
125
+ let items = displayRows.slice();
126
+ if (project !== "all") items = items.filter((r) => r.projectKey === project);
91
127
  if (agent !== "all")
92
128
  items = items.filter((r) => (r.session.agent ?? "claude-code") === agent);
93
129
  if (query) {
94
130
  const q = query.toLowerCase();
95
131
  items = items.filter(
96
- ({ session: s, briefSummary }) =>
132
+ ({ session: s, briefSummary, projectKey }) =>
97
133
  s.id.toLowerCase().includes(q) ||
98
134
  (briefSummary ?? "").toLowerCase().includes(q) ||
99
135
  (s.firstUserPreview ?? "").toLowerCase().includes(q) ||
100
136
  (s.lastAgentPreview ?? "").toLowerCase().includes(q) ||
101
- s.projectName.toLowerCase().includes(q),
137
+ s.projectName.toLowerCase().includes(q) ||
138
+ projectKey.toLowerCase().includes(q),
102
139
  );
103
140
  }
104
141
  if (sortBy === "newest")
@@ -115,18 +152,12 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
115
152
  );
116
153
  else if (sortBy === "most-tokens")
117
154
  items.sort(
118
- (a, b) =>
119
- b.session.totalUsage.input +
120
- b.session.totalUsage.output +
121
- b.session.totalUsage.cacheRead -
122
- (a.session.totalUsage.input +
123
- a.session.totalUsage.output +
124
- a.session.totalUsage.cacheRead),
155
+ (a, b) => totalSessionTokens(b.session) - totalSessionTokens(a.session),
125
156
  );
126
157
  else if (sortBy === "outcome")
127
158
  items.sort((a, b) => outcomePriority(b.outcome) - outcomePriority(a.outcome));
128
159
  return items;
129
- }, [rows, project, agent, query, sortBy]);
160
+ }, [displayRows, project, agent, query, sortBy]);
130
161
 
131
162
  return (
132
163
  <div>
@@ -199,7 +230,7 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
199
230
  {filtered.length === 0 ? (
200
231
  <div className="af-empty">No sessions found.</div>
201
232
  ) : viewMode === "table" ? (
202
- <DataTable<SessionRow>
233
+ <DataTable<DisplaySessionRow>
203
234
  rows={filtered}
204
235
  getRowKey={(r) => `${r.session.projectDir}/${r.session.id}`}
205
236
  onRowClick={(r) => router.push(`/sessions/${r.session.id}`)}
@@ -224,7 +255,7 @@ export function SessionsGrid({ rows }: { rows: SessionRow[] }) {
224
255
  );
225
256
  }
226
257
 
227
- const sessionTableColumns: Column<SessionRow>[] = [
258
+ const sessionTableColumns: Column<DisplaySessionRow>[] = [
228
259
  {
229
260
  key: "outcome",
230
261
  header: "Outcome",
@@ -234,7 +265,7 @@ const sessionTableColumns: Column<SessionRow>[] = [
234
265
  {
235
266
  key: "project",
236
267
  header: "Project",
237
- sortValue: (r) => r.session.projectName,
268
+ sortValue: (r) => r.projectKey,
238
269
  render: (r) => (
239
270
  <div
240
271
  style={{
@@ -243,7 +274,7 @@ const sessionTableColumns: Column<SessionRow>[] = [
243
274
  textOverflow: "ellipsis",
244
275
  whiteSpace: "nowrap",
245
276
  }}
246
- title={r.session.projectName}
277
+ title={`${r.projectKey} (${r.session.projectName})`}
247
278
  >
248
279
  <div style={{ fontWeight: 500, display: "flex", alignItems: "center", gap: 6 }}>
249
280
  <span
@@ -254,7 +285,7 @@ const sessionTableColumns: Column<SessionRow>[] = [
254
285
  minWidth: 0,
255
286
  }}
256
287
  >
257
- {prettyProjectName(r.session.projectName)}
288
+ {prettyProjectName(r.projectKey)}
258
289
  </span>
259
290
  <TeamBadge session={r.session} />
260
291
  <AgentBadge agent={r.session.agent} />
@@ -399,7 +430,7 @@ function OutcomeCell({ row }: { row: SessionRow }) {
399
430
  return <span style={{ color: "var(--af-text-tertiary)", fontSize: 10 }}>—</span>;
400
431
  }
401
432
 
402
- function SessionCard({ row }: { row: SessionRow }) {
433
+ function SessionCard({ row }: { row: DisplaySessionRow }) {
403
434
  const router = useRouter();
404
435
  const { session: s, briefSummary, outcome, latestLocalDay, enrichmentStatus } = row;
405
436
  const totalTokens =
@@ -440,13 +471,13 @@ function SessionCard({ row }: { row: SessionRow }) {
440
471
  gap: 6,
441
472
  minWidth: 0,
442
473
  }}
443
- title={s.projectName}
474
+ title={`${row.projectKey} (${s.projectName})`}
444
475
  >
445
476
  <LiveBadge mtimeIso={s.lastTimestamp} activityMs={s.lastActivityMs} />
446
477
  <span
447
478
  style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
448
479
  >
449
- {prettyProjectName(s.projectName)}
480
+ {prettyProjectName(row.projectKey)}
450
481
  </span>
451
482
  <TeamBadge session={s} linkable={false} />
452
483
  <AgentBadge agent={s.agent} />
@@ -2,10 +2,15 @@ import { readSettings } from "@claude-lens/entries/node";
2
2
  import { AiFeaturesForm } from "./ai-features-form";
3
3
  import { readAutostartState } from "@/lib/autostart-data";
4
4
  import { AutostartToggle } from "@/components/autostart-toggle";
5
+ import { readMenubarState } from "@/lib/menubar-data";
6
+ import { MenubarInstall } from "@/components/menubar-install";
7
+ import { readCredentialsMasked } from "@claude-lens/entries/node";
8
+ import { ZaiCredentialsForm } from "@/components/zai-credentials-form";
5
9
 
6
10
  // Layout mirrors /team: max-w-3xl, header + subtitle, card-style sections.
7
11
  export default function SettingsPage() {
8
12
  const s = readSettings();
13
+ const creds = readCredentialsMasked();
9
14
  return (
10
15
  <main className="mx-auto max-w-3xl p-6 space-y-8">
11
16
  <header>
@@ -24,6 +29,27 @@ export default function SettingsPage() {
24
29
  <AutostartToggle initial={readAutostartState()} />
25
30
  </section>
26
31
 
32
+ <section className="af-card space-y-3">
33
+ <h2 className="text-lg font-medium">Menu bar widget</h2>
34
+ <p className="text-sm text-gray-500">
35
+ Native macOS menu bar widget showing live Claude Code / Codex / Z.ai plan
36
+ utilization, reset countdowns, and burn-rate. Installs to ~/Applications
37
+ and updates as the daemon polls.
38
+ </p>
39
+ <MenubarInstall initial={readMenubarState()} />
40
+ </section>
41
+
42
+ <section className="af-card space-y-3">
43
+ <h2 className="text-lg font-medium">Z.ai API key</h2>
44
+ <p className="text-sm text-gray-500">
45
+ Optional. Add your Z.ai (GLM Coding Plan) API key so the daemon can track
46
+ your Z.ai usage alongside Claude Code and Codex. The key is stored in
47
+ ~/.cclens/credentials.json — nothing leaves your machine except the same
48
+ API calls Z.ai&apos;s own subscription UI makes.
49
+ </p>
50
+ <ZaiCredentialsForm initial={creds} />
51
+ </section>
52
+
27
53
  <section className="af-card space-y-3">
28
54
  <h2 className="text-lg font-medium">AI Features</h2>
29
55
  <p className="text-sm text-gray-500">
@@ -149,6 +149,26 @@ export default async function UsagePage({
149
149
  </span>
150
150
  </span>
151
151
  )}
152
+ {selected === "zai" && latest?.plan_type && (
153
+ <span
154
+ style={{
155
+ marginLeft: "auto",
156
+ display: "inline-flex",
157
+ alignItems: "center",
158
+ gap: 8,
159
+ fontSize: 12,
160
+ padding: "4px 10px",
161
+ border: "1px solid var(--af-border-subtle)",
162
+ borderRadius: 999,
163
+ fontFamily: "var(--font-mono)",
164
+ }}
165
+ >
166
+ <span style={{ color: "var(--af-text-tertiary)" }}>plan</span>
167
+ <span style={{ color: "var(--af-text)", fontWeight: 600 }}>
168
+ {latest.plan_type}
169
+ </span>
170
+ </span>
171
+ )}
152
172
  </header>
153
173
 
154
174
  {/* Agent tab strip — one Link per agent that has snapshots. */}
@@ -162,6 +182,9 @@ export default async function UsagePage({
162
182
  <PreviousCyclesTrend windowLabel="7d" cycles={cycles7d} />
163
183
  )}
164
184
  <UsageChartsDashboard snapshots={snapshots} predicted={predicted ?? undefined} />
185
+ {selected === "zai" && latest?.web_search_quota && (
186
+ <WebSearchQuotaCard quota={latest.web_search_quota} />
187
+ )}
165
188
  <div
166
189
  style={{
167
190
  fontSize: 11,
@@ -179,6 +202,63 @@ export default async function UsagePage({
179
202
  );
180
203
  }
181
204
 
205
+ function WebSearchQuotaCard({
206
+ quota,
207
+ }: {
208
+ quota: { used: number | null; limit: number | null };
209
+ }) {
210
+ // web_search_quota is a percentage meter (used = 0–100, limit = 100),
211
+ // matching Z.ai's portal display — not a raw usage/remaining split.
212
+ const pct = quota.used ?? 0;
213
+ return (
214
+ <div className="af-card" style={{ padding: "16px 18px" }}>
215
+ <div
216
+ style={{
217
+ fontSize: 11,
218
+ color: "var(--af-text-tertiary)",
219
+ textTransform: "uppercase",
220
+ letterSpacing: "0.05em",
221
+ fontWeight: 600,
222
+ }}
223
+ >
224
+ Monthly web search
225
+ </div>
226
+ <div
227
+ style={{
228
+ fontSize: 28,
229
+ fontWeight: 700,
230
+ marginTop: 8,
231
+ letterSpacing: "-0.02em",
232
+ lineHeight: 1.1,
233
+ fontVariantNumeric: "tabular-nums",
234
+ }}
235
+ >
236
+ {Math.round(pct)}%
237
+ </div>
238
+ <div
239
+ style={{
240
+ marginTop: 10,
241
+ height: 6,
242
+ width: "100%",
243
+ background: "var(--af-border-subtle)",
244
+ borderRadius: 999,
245
+ overflow: "hidden",
246
+ }}
247
+ >
248
+ <div
249
+ style={{
250
+ height: "100%",
251
+ width: `${Math.min(100, pct)}%`,
252
+ background: "rgb(59, 130, 246)",
253
+ borderRadius: 999,
254
+ transition: "width 0.24s ease",
255
+ }}
256
+ />
257
+ </div>
258
+ </div>
259
+ );
260
+ }
261
+
182
262
  function AgentTabs({ agents, selected }: { agents: AgentKind[]; selected: AgentKind }) {
183
263
  // Claude first, then everything else alphabetically.
184
264
  const sorted = [...agents].sort((a, b) => {
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ type MenubarState = {
6
+ supported: boolean;
7
+ installed: boolean;
8
+ running: boolean;
9
+ bundled: boolean;
10
+ };
11
+
12
+ const DISMISS_KEY = "fleetlens:menubar-banner-dismissed";
13
+
14
+ // Global, dismissible "install the menu bar widget" banner. Fetches state on
15
+ // mount and self-hides once installed (or when the user dismisses it). Mirrors
16
+ // components/team-welcome-banner.tsx styling + localStorage-dismiss pattern.
17
+ export function MenubarInstallBanner() {
18
+ const [hidden, setHidden] = useState(true); // start hidden to avoid SSR flash
19
+ const [state, setState] = useState<MenubarState | null>(null);
20
+ const [busy, setBusy] = useState(false);
21
+ const [error, setError] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ let cancelled = false;
25
+ fetch("/api/menubar", { cache: "no-store" })
26
+ .then((r) => r.json())
27
+ .then((s: MenubarState) => {
28
+ if (cancelled) return;
29
+ setState(s);
30
+ const dismissed = window.localStorage.getItem(DISMISS_KEY);
31
+ // Show only on macOS, when not installed, and not previously dismissed.
32
+ if (s.supported && !s.installed && !dismissed) setHidden(false);
33
+ })
34
+ .catch(() => {});
35
+ return () => {
36
+ cancelled = true;
37
+ };
38
+ }, []);
39
+
40
+ // Re-hide if a background refresh (SSE router.refresh) flips installed=true.
41
+ useEffect(() => {
42
+ if (state?.installed) setHidden(true);
43
+ }, [state?.installed]);
44
+
45
+ if (hidden) return null;
46
+
47
+ const dismiss = () => {
48
+ window.localStorage.setItem(DISMISS_KEY, "1");
49
+ setHidden(true);
50
+ };
51
+
52
+ async function install() {
53
+ setBusy(true);
54
+ setError(null);
55
+ const res = await fetch("/api/menubar", {
56
+ method: "POST",
57
+ headers: { "content-type": "application/json" },
58
+ body: JSON.stringify({ action: "install" }),
59
+ });
60
+ const body = await res.json().catch(() => null);
61
+ setBusy(false);
62
+ if (!res.ok) {
63
+ setError(body?.error ?? `Error ${res.status}`);
64
+ return;
65
+ }
66
+ setState(body);
67
+ }
68
+
69
+ return (
70
+ <div
71
+ role="status"
72
+ style={{
73
+ padding: "10px 24px",
74
+ background: "var(--af-accent-subtle)",
75
+ borderBottom: "1px solid var(--af-accent)",
76
+ color: "var(--af-text)",
77
+ fontSize: 13,
78
+ display: "flex",
79
+ gap: 12,
80
+ alignItems: "center",
81
+ }}
82
+ >
83
+ <span style={{ flex: 1 }}>
84
+ Get live Claude Code / Codex / Z.ai usage in your menu bar —{" "}
85
+ <strong>install the Fleetlens widget</strong>.
86
+ </span>
87
+ <button
88
+ type="button"
89
+ className="af-btn af-btn-primary"
90
+ disabled={busy}
91
+ onClick={() => void install()}
92
+ style={{ fontSize: 12, padding: "4px 12px" }}
93
+ >
94
+ {busy ? "Installing…" : "Install"}
95
+ </button>
96
+ <button
97
+ type="button"
98
+ onClick={dismiss}
99
+ aria-label="Dismiss"
100
+ style={{
101
+ background: "transparent",
102
+ border: "none",
103
+ color: "var(--af-text-tertiary)",
104
+ cursor: "pointer",
105
+ fontSize: 16,
106
+ lineHeight: 1,
107
+ padding: "2px 6px",
108
+ }}
109
+ >
110
+ ×
111
+ </button>
112
+ {error && (
113
+ <span style={{ color: "var(--af-danger, #dc2626)" }}>{error}</span>
114
+ )}
115
+ </div>
116
+ );
117
+ }