vibepulse 0.2.2 → 0.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/fallback-build-manifest.json +2 -2
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +8 -0
  11. package/.next/server/app/_global-error/page.js +1 -1
  12. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  13. package/.next/server/app/_global-error.html +2 -2
  14. package/.next/server/app/_global-error.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/_not-found/page.js +1 -1
  21. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/_not-found.html +1 -1
  24. package/.next/server/app/_not-found.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  32. package/.next/server/app/api/node/sessions/route.js +5 -3
  33. package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  34. package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  35. package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  36. package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  37. package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  38. package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  39. package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  40. package/.next/server/app/api/profiles/route.js.nft.json +1 -1
  41. package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  42. package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  43. package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  44. package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  45. package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  46. package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  47. package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  48. package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  49. package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  50. package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  51. package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  52. package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  53. package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  54. package/.next/server/app/api/sessions/route.js +4 -2
  55. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  56. package/.next/server/app/index.html +1 -1
  57. package/.next/server/app/index.rsc +3 -3
  58. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app-paths-manifest.json +1 -0
  65. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  66. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
  67. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
  69. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  70. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
  71. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  72. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
  73. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  74. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
  75. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  76. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
  77. package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  78. package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
  79. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  80. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
  81. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  82. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
  83. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  84. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
  85. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  86. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
  87. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  88. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
  89. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  90. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
  91. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  92. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
  93. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  94. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
  95. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  96. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
  97. package/.next/server/pages/404.html +1 -1
  98. package/.next/server/pages/500.html +2 -2
  99. package/.next/server/server-reference-manifest.js +1 -1
  100. package/.next/server/server-reference-manifest.json +1 -1
  101. package/.next/standalone/.next/BUILD_ID +1 -1
  102. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  103. package/.next/standalone/.next/build-manifest.json +2 -2
  104. package/.next/standalone/.next/prerender-manifest.json +3 -3
  105. package/.next/standalone/.next/routes-manifest.json +8 -0
  106. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  107. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  108. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  109. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  110. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  111. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  112. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  113. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  114. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  115. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  116. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  117. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  118. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  119. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  120. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  121. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  122. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  123. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  124. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  125. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  126. package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
  128. package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  129. package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  131. package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  137. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  139. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  140. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  141. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  143. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  144. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  145. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  146. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  147. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  148. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  149. package/.next/standalone/.next/server/app/api/sessions/route.js +4 -2
  150. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/index.html +1 -1
  152. package/.next/standalone/.next/server/app/index.rsc +3 -3
  153. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  154. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  155. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  156. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  157. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  158. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  159. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  165. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  166. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  167. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  168. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  169. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  170. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  171. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  172. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  173. package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  175. package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  176. package/.next/standalone/.next/server/pages/404.html +1 -1
  177. package/.next/standalone/.next/server/pages/500.html +2 -2
  178. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  179. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  180. package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
  181. package/.next/standalone/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
  182. package/.next/standalone/README.md +29 -5
  183. package/.next/standalone/docs/session-status-detection.md +36 -0
  184. package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
  185. package/.next/standalone/package-lock.json +2 -2
  186. package/.next/standalone/package.json +1 -1
  187. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
  188. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
  189. package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
  190. package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
  191. package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
  192. package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
  193. package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
  194. package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
  195. package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
  196. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
  197. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
  198. package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
  199. package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
  200. package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
  201. package/.next/standalone/src/app/api/sessions/route.ts +365 -981
  202. package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
  203. package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
  204. package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
  205. package/.next/standalone/src/components/ProjectCard.tsx +238 -86
  206. package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
  207. package/.next/standalone/src/components/SessionCard.tsx +182 -76
  208. package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
  209. package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
  210. package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
  211. package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
  212. package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
  213. package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
  214. package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
  215. package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
  216. package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
  217. package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
  218. package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
  219. package/.next/standalone/src/lib/session-providers/types.ts +131 -0
  220. package/.next/standalone/src/lib/transform.test.ts +253 -0
  221. package/.next/standalone/src/lib/transform.ts +96 -37
  222. package/.next/standalone/src/types/index.ts +23 -17
  223. package/.next/static/chunks/b3bc362202331708.css +3 -0
  224. package/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
  225. package/.next/trace +1 -1
  226. package/.next/trace-build +1 -1
  227. package/.next/types/routes.d.ts +2 -1
  228. package/.next/types/validator.ts +9 -0
  229. package/README.md +29 -5
  230. package/package.json +1 -1
  231. package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  232. package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
  233. package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  234. package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
  235. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  236. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
  237. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  238. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
  239. package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  240. package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
  241. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  243. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  245. package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  246. package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
  247. package/.next/static/chunks/f42202943f6742e5.css +0 -3
  248. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  249. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  250. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
  251. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  252. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  253. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
@@ -8,23 +8,32 @@ A tiny dashboard that sits in your browser tab — tired of switching IDE tabs j
8
8
 
9
9
  ## What It Does
10
10
 
11
- - **Kanban board** — Auto-discovers OpenCode sessions, organizes them into Idle / Busy / Review / Done
11
+ - **Kanban board** — Auto-discovers OpenCode sessions and host-global Claude Code sessions, organizes them into Idle / Busy / Review / Done
12
12
  - **Remote Nodes** — Connect multiple VibePulse instances to a single hub for a unified view
13
13
  - **Audio alerts** — Makes a sound when sessions complete or need attention
14
14
  - **Zero setup** — No manual card creation; auto-scans ports and processes
15
15
  - **Profile switcher** — Flip between Oh My OpenAgent presets without touching config files
16
16
 
17
+ ## Claude Code Support
18
+ VibePulse includes experimental, capability-aware support for tracking local [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) sessions:
19
+ - **Host-Global Discovery:** Automatically detects all Claude Code sessions running on the machine, aggregating projects seamlessly.
20
+ - **Explicit Capabilities:** Supported actions are explicitly modeled per provider. Claude now supports VibePulse-managed `archive` and `delete`, while `openEditor` remains disabled until a provider-safe execution path is defined.
21
+ - **Visual Differentiation:** Mixed environments feature distinct visual indicators distinguishing OpenCode and Claude groups inside projects.
22
+ - **Robust Liveness Semantics:** Stale busy states and zombie parsing are prevented at the provider boundary using stricter liveness verification.
23
+ - **Polling Integration:** Claude status updates via polling only locally and remotely. Live SSE streams and real-time event parity are not supported.
24
+ - **Artifact-Backed Child Topology:** Child sessions are exposed only when verified by authoritative artifact-backed linkage. No transcript rendering is supported.
25
+
17
26
  ## Quick Start
18
27
 
19
28
  ### Hub Mode (Default)
20
- Run VibePulse locally to monitor your local OpenCode sessions and manage remote nodes.
29
+ Run VibePulse locally to monitor your local sessions and manage remote nodes.
21
30
  ```bash
22
31
  npx vibepulse
23
32
  ```
24
33
  Open http://localhost:3456
25
34
 
26
35
  ### Node Mode
27
- Run VibePulse on a remote server to expose its OpenCode sessions to a hub.
36
+ Run VibePulse on a remote server to expose its local OpenCode and Claude Code sessions to a hub.
28
37
  ```bash
29
38
  npx vibepulse --serve
30
39
  ```
@@ -43,9 +52,9 @@ Node mode requires an access token for security. See [Architecture](#architectur
43
52
 
44
53
  ## Architecture
45
54
 
46
- VibePulse uses a Hub-and-Node architecture to aggregate OpenCode sessions across different machines.
55
+ VibePulse uses a Hub-and-Node architecture to aggregate OpenCode and Claude Code sessions across different machines.
47
56
 
48
- 1. **Node**: A VibePulse instance running with `--serve`. It interacts directly with the local OpenCode SDK and exposes an API.
57
+ 1. **Node**: A VibePulse instance running with `--serve`. It interacts directly with the local OpenCode SDK and Claude Code file-system artifacts and exposes an API.
49
58
  2. **Hub**: The primary VibePulse instance (default mode). It connects to one or more Nodes to collect session data.
50
59
 
51
60
  ### Connecting a Remote Node
@@ -64,6 +73,21 @@ npm install
64
73
  npm run dev
65
74
  ```
66
75
 
76
+ ### Claude Integration Verification
77
+ If modifying Claude Code integration, run the targeted regression matrix to ensure discovery order, bounded idle fallback, capability alignment, artifact-backed child topology, stronger liveness semantics, and mixed Claude/OpenCode visual behavior all stay intact:
78
+
79
+ | Area | Command |
80
+ |------|---------|
81
+ | Claude discovery + topology rules | `npm run test:run -- src/lib/session-providers/claudeCode.test.ts` |
82
+ | Capability alignment + rejection | `npm run test:run -- src/lib/session-providers/providerIds.test.ts src/app/api/sessions/route.test.ts` |
83
+ | Mixed project-group visual behavior | `npm run test:run -- src/components/ProjectCard.test.tsx src/components/SessionCard.test.tsx` |
84
+ | Local provider aggregation and route wiring | `npm run test:run -- src/lib/transform.test.ts src/hooks/useOpencodeSync.test.ts src/app/api/node/sessions/route.test.ts` |
85
+
86
+ ```bash
87
+ npm run test:run -- src/lib/session-providers/providerIds.test.ts src/lib/session-providers/claudeCode.test.ts src/components/ProjectCard.test.tsx src/components/SessionCard.test.tsx src/app/api/sessions/route.test.ts src/app/api/node/sessions/route.test.ts src/lib/transform.test.ts src/hooks/useOpencodeSync.test.ts
88
+ npm run lint && npm run build
89
+ ```
90
+
67
91
  ## Tech Stack
68
92
 
69
93
  - Next.js (App Router) + TypeScript
@@ -158,6 +158,42 @@ sequenceDiagram
158
158
 
159
159
  ---
160
160
 
161
+ ## Claude Code Support Boundary
162
+
163
+ VibePulse includes experimental host-global discovery for Claude Code sessions (`provider = 'claude-code'`). The detection mechanisms used for OpenCode differ for Claude Code due to its specific provider boundaries:
164
+
165
+ - **Host-Global Discovery:** Uses file-system artifacts (`~/.claude/projects/`, `~/.claude/sessions/`) across the entire host to automatically detect active projects without requiring current-repo constraints.
166
+ - **Artifact-Backed Child Topology:** Nested child sessions are fully supported via local polling and remote node-payload propagation. They roll up naturally into parent card logic. This topology relies entirely on explicit artifact-backed relationships—preventing "guessed" relationships.
167
+ - **Polling Isolation & No SSE Parity:** Claude status updates, including child topology, work via polling only. There is no Claude SSE stream, and therefore no event-level parity with OpenCode's real-time channels.
168
+ - **Robust Stale-Busy Mitigation:**
169
+ - Status inference uses explicit liveness checks (pid polling/verification) at the provider boundary to ensure zombie processes do not stall the UI in a "busy" state.
170
+ - Active pid mapping yields `realTimeStatus = 'busy'`.
171
+ - Artifact-only fallback with dead pids maps to `realTimeStatus = 'idle'`.
172
+ - Claude never emits `retry`, but it can emit `waitingForUser = true` when fresh transcript evidence shows either a direct assistant question or a pending `tool_use` approval with no later user/tool_result resolution.
173
+ - **Capability-Aware Contracts:** Claude sessions have discrete capability scopes. Claude now supports VibePulse-managed `archive` and `delete`, while `open-editor` remains explicitly disabled until a provider-safe execution path is available.
174
+
175
+ ### Claude "Waiting for User" Decision Rules (Detailed)
176
+
177
+ Claude detection is transcript-tail based and event-order sensitive:
178
+
179
+ 1. **Freshness gate**
180
+ - If the transcript artifact is older than the waiting window (currently 10 minutes), `waitingForUser = false`.
181
+ 2. **Scan from newest to oldest event**
182
+ - If the first relevant newest event is a **user** turn, `waitingForUser = false`.
183
+ - If the first relevant newest event is an **assistant** turn:
184
+ - `stop_reason === tool_use` or message content contains `tool_use` → `waitingForUser = true`
185
+ - `stop_reason === end_turn` and assistant text ends with `?` / `?` → `waitingForUser = true`
186
+ - otherwise → `waitingForUser = false`
187
+ 3. **Restore suppression**
188
+ - Immediately after restore, stale waiting signals can be suppressed until new transcript activity appears.
189
+
190
+ Practical implication for board status:
191
+
192
+ - Claude can be `realTimeStatus = idle` **and** `waitingForUser = true` at the same time.
193
+ - In that case, Kanban maps the session to `review` ("Needs Attention") by design, because the session is waiting for user input/approval rather than actively running.
194
+
195
+ ---
196
+
161
197
  ## Detection Limitations (Shortcomings)
162
198
 
163
199
  ### Root Cause: Unreliable Signal Source
@@ -0,0 +1,39 @@
1
+ # Claude Capability Alignment Design
2
+
3
+ ## Goal
4
+ Align Claude Code and OpenCode capabilities in VibePulse through an explicit capability matrix. We also need to fix stale busy status prediction and clearly differentiate providers in the UI.
5
+
6
+ ## Context
7
+ VibePulse recently added global Claude discovery alongside read-only Claude cards. The footer open state is now restored. We still have a known stale-busy problem rooted in weak process ID validation. Current documentation describes Claude as read-only with no action parity. This specification defines the next expansion iteration to replace blunt read-only gating with specific capability flags.
8
+
9
+ ## Non-goals
10
+ We won't build full feature parity for Claude if the underlying CLI lacks support. We also won't rewrite the core Next.js aggregation engine.
11
+
12
+ ## Architecture
13
+ The architecture relies on our existing separation of concerns. Provider contracts live in `src/lib/session-providers/*` and `src/types/index.ts`. Transformation logic resides in `src/lib/transform.ts`. UI action gating happens in `src/components/ProjectCard.tsx`, `src/components/SessionCard.tsx`, and `src/components/KanbanBoard.tsx`. Action routes remain in `src/app/api/sessions/[id]/*`. Implementation is strictly scoped to these boundaries.
14
+
15
+ ## Capability Model
16
+ We replace `readOnly` as our sole capability proxy with explicit action capabilities. A new capability matrix covers `openProject`, `openEditor`, `archive`, and `delete`. We keep the `readOnly` flag but narrow its meaning to indicate basic UI protection.
17
+
18
+ Current evidence shows Claude archive and delete official support is unclear. The capability matrix must allow staged enablement. We won't pretend parity exists. If Claude cannot safely archive a workspace, the matrix explicitly sets `archive: false`.
19
+
20
+ ## Status Model
21
+ The busy status fix lives at the Claude provider boundary instead of being patched in the UI. When liveness evidence is weak, we prefer false-idle over false-busy for Claude. This specific logic prevents sessions from getting stuck in a busy state when the underlying process might actually be dead or sleeping.
22
+
23
+ ## UI Design
24
+ Visual differentiation uses a mixed strategy. The interface will show a project-level primary provider identity. Individual session rows receive a lightweight indicator. Projects with sessions from multiple providers get explicit mixed-group treatment to avoid user confusion.
25
+
26
+ ## API and Route Enforcement
27
+ Action routes in `src/app/api/sessions/[id]/*` must check the capability matrix before executing destructive actions. If a provider lacks the `archive` capability, the API must reject the request and return a clear error code.
28
+
29
+ ## Testing Strategy
30
+ Tests will verify correct capability matrix enforcement in both UI components and API routes. We will add unit tests for the Claude provider status logic. This ensures the false-idle fallback works correctly under weak liveness conditions.
31
+
32
+ ## Rollout Order
33
+ 1. Provider contracts and status logic
34
+ 2. UI action gating and visual differentiation
35
+ 3. API action-route guards
36
+ 4. Tests and documentation
37
+
38
+ ## Risks
39
+ Staged capabilities might frustrate users if missing actions are not explained clearly in the UI. The strict idle fallback could mask active processes if PID checks fail due to obscure OS-level permission boundaries.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "vibepulse",
3
- "version": "0.2.2",
3
+ "version": "0.3.0-beta.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "vibepulse",
9
- "version": "0.2.2",
9
+ "version": "0.3.0-beta.0",
10
10
  "dependencies": {
11
11
  "@dnd-kit/core": "^6.3.1",
12
12
  "@dnd-kit/sortable": "^10.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibepulse",
3
- "version": "0.2.2",
3
+ "version": "0.3.0-beta.0",
4
4
  "private": false,
5
5
  "description": "Real-time dashboard for monitoring and managing OpenCode sessions",
6
6
  "repository": {
@@ -5,16 +5,28 @@ vi.mock('@/lib/opencodeDiscovery', () => ({
5
5
  }));
6
6
 
7
7
  vi.mock('@/lib/sessionArchiveOverrides', () => ({
8
+ clearSessionStickyStatusBlocked: vi.fn(),
8
9
  clearSessionForceUnarchived: vi.fn(),
10
+ markSessionForceUnarchived: vi.fn(),
9
11
  markSessionStickyStatusBlocked: vi.fn(),
10
12
  }));
11
13
 
12
14
  import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
13
15
  import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
16
+ import {
17
+ clearSessionStickyStatusBlocked,
18
+ clearSessionForceUnarchived,
19
+ markSessionForceUnarchived,
20
+ markSessionStickyStatusBlocked,
21
+ } from '@/lib/sessionArchiveOverrides';
14
22
 
15
- import { POST } from './route';
23
+ import { DELETE, POST } from './route';
16
24
 
17
25
  const mockDiscoverOpencodePortsWithMeta: any = discoverOpencodePortsWithMeta;
26
+ const mockClearSessionStickyStatusBlocked: any = clearSessionStickyStatusBlocked;
27
+ const mockClearSessionForceUnarchived: any = clearSessionForceUnarchived;
28
+ const mockMarkSessionForceUnarchived: any = markSessionForceUnarchived;
29
+ const mockMarkSessionStickyStatusBlocked: any = markSessionStickyStatusBlocked;
18
30
 
19
31
  describe('/api/node/sessions/[id]/archive', () => {
20
32
  const originalRuntimeRole = process.env.VIBEPULSE_RUNTIME_ROLE;
@@ -52,6 +64,53 @@ describe('/api/node/sessions/[id]/archive', () => {
52
64
  'http://localhost:7777/session/ses_123',
53
65
  expect.objectContaining({ method: 'PATCH' })
54
66
  );
67
+ expect(mockClearSessionForceUnarchived).toHaveBeenCalledWith('ses_123');
68
+ expect(mockMarkSessionStickyStatusBlocked).toHaveBeenCalledWith('ses_123');
69
+ });
70
+
71
+ it('restores a node-local session with valid auth', async () => {
72
+ const response = await DELETE(
73
+ new Request('http://localhost/api/node/sessions/ses_123/archive', {
74
+ method: 'DELETE',
75
+ headers: createNodeRequestHeaders('shared-secret'),
76
+ }),
77
+ { params: Promise.resolve({ id: 'ses_123' }) }
78
+ );
79
+ const data = await response.json();
80
+
81
+ expect(response.status).toBe(200);
82
+ expect(data).toEqual({ success: true });
83
+ expect(globalThis.fetch).toHaveBeenCalledWith(
84
+ 'http://localhost:7777/session/ses_123',
85
+ expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ time: { archived: null } }) })
86
+ );
87
+ expect(mockMarkSessionForceUnarchived).toHaveBeenCalledWith('ses_123');
88
+ expect(mockClearSessionStickyStatusBlocked).toHaveBeenCalledWith('ses_123');
89
+ });
90
+
91
+ it('does not mutate restore overrides when upstream restore fails', async () => {
92
+ Object.defineProperty(globalThis, 'fetch', {
93
+ value: vi.fn(async () => new Response(JSON.stringify({ error: 'boom' }), { status: 500 })),
94
+ configurable: true,
95
+ });
96
+
97
+ const response = await DELETE(
98
+ new Request('http://localhost/api/node/sessions/ses_123/archive', {
99
+ method: 'DELETE',
100
+ headers: createNodeRequestHeaders('shared-secret'),
101
+ }),
102
+ { params: Promise.resolve({ id: 'ses_123' }) }
103
+ );
104
+ const data = await response.json();
105
+
106
+ expect(response.status).toBe(500);
107
+ expect(data).toEqual({
108
+ error: 'Failed to restore session',
109
+ reason: 'node_request_failed_500',
110
+ message: JSON.stringify({ error: 'boom' }),
111
+ });
112
+ expect(mockMarkSessionForceUnarchived).not.toHaveBeenCalled();
113
+ expect(mockClearSessionStickyStatusBlocked).not.toHaveBeenCalled();
55
114
  });
56
115
 
57
116
  it('rejects invalid auth before mutating', async () => {
@@ -5,7 +5,9 @@ import {
5
5
  toNodeRequestGuardResponse,
6
6
  } from '@/lib/nodeProtocol';
7
7
  import {
8
+ clearSessionStickyStatusBlocked,
8
9
  clearSessionForceUnarchived,
10
+ markSessionForceUnarchived,
9
11
  markSessionStickyStatusBlocked,
10
12
  } from '@/lib/sessionArchiveOverrides';
11
13
 
@@ -20,28 +22,34 @@ function resolveNodeLocalSessionId(id: string): string | null {
20
22
 
21
23
  export const dynamic = 'force-dynamic';
22
24
 
23
- export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
24
- const guardResult = guardNodeRequest(request);
25
- if (!guardResult.ok) {
26
- return toNodeRequestGuardResponse(guardResult);
27
- }
28
-
29
- const { id } = await params;
30
- const sessionId = resolveNodeLocalSessionId(id);
25
+ function createInvalidNodeSessionIdResponse() {
26
+ return Response.json({ error: 'Invalid node session id' }, { status: 400 });
27
+ }
31
28
 
32
- if (!sessionId) {
33
- return Response.json({ error: 'Invalid node session id' }, { status: 400 });
34
- }
29
+ function createNodeUpstreamUnavailableResponse(timedOut: boolean) {
30
+ return createNodeFailureResponse(timedOut ? 'upstream_timeout' : 'upstream_unreachable', {
31
+ role: 'node',
32
+ upstream: {
33
+ kind: 'opencode',
34
+ reachable: false,
35
+ },
36
+ });
37
+ }
35
38
 
39
+ async function runArchiveMutation({
40
+ sessionId,
41
+ archived,
42
+ failureMessage,
43
+ onSuccess,
44
+ }: {
45
+ sessionId: string;
46
+ archived: number | null;
47
+ failureMessage: string;
48
+ onSuccess: () => void;
49
+ }): Promise<Response> {
36
50
  const { ports, timedOut } = discoverOpencodePortsWithMeta();
37
51
  if (!ports.length) {
38
- return createNodeFailureResponse(timedOut ? 'upstream_timeout' : 'upstream_unreachable', {
39
- role: 'node',
40
- upstream: {
41
- kind: 'opencode',
42
- reachable: false,
43
- },
44
- });
52
+ return createNodeUpstreamUnavailableResponse(timedOut);
45
53
  }
46
54
 
47
55
  let sawNotFound = false;
@@ -55,12 +63,11 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
55
63
  headers: {
56
64
  'Content-Type': 'application/json',
57
65
  },
58
- body: JSON.stringify({ time: { archived: Date.now() } }),
66
+ body: JSON.stringify({ time: { archived } }),
59
67
  });
60
68
 
61
69
  if (response.ok) {
62
- clearSessionForceUnarchived(sessionId);
63
- markSessionStickyStatusBlocked(sessionId);
70
+ onSuccess();
64
71
  return Response.json({ success: true });
65
72
  }
66
73
 
@@ -81,7 +88,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
81
88
  if (lastFailureStatus !== null) {
82
89
  return Response.json(
83
90
  {
84
- error: 'Failed to archive session',
91
+ error: failureMessage,
85
92
  reason: lastFailureStatus === 503 ? 'upstream_unreachable' : `node_request_failed_${lastFailureStatus}`,
86
93
  ...(lastFailureMessage ? { message: lastFailureMessage } : {}),
87
94
  },
@@ -95,3 +102,51 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
95
102
 
96
103
  return Response.json({ error: 'Session not found', reason: 'session_not_found' }, { status: 404 });
97
104
  }
105
+
106
+ export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
107
+ const guardResult = guardNodeRequest(request);
108
+ if (!guardResult.ok) {
109
+ return toNodeRequestGuardResponse(guardResult);
110
+ }
111
+
112
+ const { id } = await params;
113
+ const sessionId = resolveNodeLocalSessionId(id);
114
+
115
+ if (!sessionId) {
116
+ return createInvalidNodeSessionIdResponse();
117
+ }
118
+
119
+ return runArchiveMutation({
120
+ sessionId,
121
+ archived: Date.now(),
122
+ failureMessage: 'Failed to archive session',
123
+ onSuccess: () => {
124
+ clearSessionForceUnarchived(sessionId);
125
+ markSessionStickyStatusBlocked(sessionId);
126
+ },
127
+ });
128
+ }
129
+
130
+ export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
131
+ const guardResult = guardNodeRequest(request);
132
+ if (!guardResult.ok) {
133
+ return toNodeRequestGuardResponse(guardResult);
134
+ }
135
+
136
+ const { id } = await params;
137
+ const sessionId = resolveNodeLocalSessionId(id);
138
+
139
+ if (!sessionId) {
140
+ return createInvalidNodeSessionIdResponse();
141
+ }
142
+
143
+ return runArchiveMutation({
144
+ sessionId,
145
+ archived: null,
146
+ failureMessage: 'Failed to restore session',
147
+ onSuccess: () => {
148
+ markSessionForceUnarchived(sessionId);
149
+ clearSessionStickyStatusBlocked(sessionId);
150
+ },
151
+ });
152
+ }