@aion0/forge 0.5.26 → 0.5.28

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 (255) hide show
  1. package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
  2. package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
  3. package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
  4. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
  5. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
  6. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
  7. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
  8. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
  9. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
  10. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
  11. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
  12. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
  13. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
  14. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
  15. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
  16. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
  17. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
  18. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
  19. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
  20. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
  21. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
  22. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
  23. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
  24. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
  25. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
  26. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
  27. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
  28. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
  29. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
  30. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
  31. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
  32. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
  33. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
  34. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
  35. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
  36. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
  37. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
  38. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
  39. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
  40. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
  41. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
  42. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
  43. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
  44. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
  45. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
  46. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
  47. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
  48. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
  49. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
  50. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
  51. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
  52. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
  53. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
  54. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
  55. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
  56. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
  57. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
  58. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
  59. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
  60. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
  61. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
  62. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
  63. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
  64. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
  65. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
  66. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
  67. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
  68. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
  69. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
  70. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
  71. package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
  72. package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
  73. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
  74. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
  75. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
  76. package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
  77. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
  78. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
  79. package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
  80. package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
  81. package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
  82. package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
  83. package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
  84. package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
  85. package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
  86. package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
  87. package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
  88. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
  89. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
  90. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
  91. package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
  92. package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
  93. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
  94. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
  95. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
  96. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
  97. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
  98. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
  99. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
  100. package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
  101. package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
  102. package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
  103. package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
  104. package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
  105. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
  106. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
  107. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
  108. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
  109. package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
  110. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
  111. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
  112. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
  113. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
  114. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
  115. package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
  116. package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
  117. package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
  118. package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
  119. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
  120. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
  121. package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
  122. package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
  123. package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
  124. package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
  125. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
  126. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
  127. package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
  128. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
  129. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
  130. package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
  131. package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
  132. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
  133. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
  134. package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
  135. package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
  136. package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
  137. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
  138. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
  139. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
  140. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
  141. package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
  142. package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
  143. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
  144. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
  145. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
  146. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
  147. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
  148. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
  149. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
  150. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
  151. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
  152. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
  153. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
  154. package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
  155. package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
  156. package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
  157. package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
  158. package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
  159. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
  160. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
  161. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
  162. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
  163. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
  164. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
  165. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
  166. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
  167. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
  168. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
  169. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
  170. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
  171. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
  172. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
  173. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
  174. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
  175. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
  176. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
  177. package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
  178. package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
  179. package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
  180. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
  181. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
  182. package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
  183. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
  184. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
  185. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
  186. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
  187. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
  188. package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
  189. package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
  190. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
  191. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
  192. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
  193. package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
  194. package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
  195. package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
  196. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
  197. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
  198. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
  199. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
  200. package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
  201. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
  202. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
  203. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
  204. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
  205. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
  206. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
  207. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
  208. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
  209. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
  210. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
  211. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
  212. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
  213. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
  214. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
  215. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
  216. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
  217. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
  218. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
  219. package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
  220. package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
  221. package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
  222. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
  223. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
  224. package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
  225. package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
  226. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
  227. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
  228. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
  229. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
  230. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
  231. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
  232. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
  233. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
  234. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
  235. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
  236. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
  237. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
  238. package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
  239. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
  240. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
  241. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
  242. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
  243. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
  244. package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
  245. package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
  246. package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
  247. package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
  248. package/RELEASE_NOTES.md +10 -29
  249. package/app/api/terminal-bell/route.ts +6 -2
  250. package/app/api/terminal-cwd/route.ts +7 -4
  251. package/components/CodeViewer.tsx +3 -31
  252. package/components/Dashboard.tsx +34 -20
  253. package/components/WebTerminal.tsx +36 -2
  254. package/lib/terminal-standalone.ts +19 -2
  255. package/package.json +1 -1
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Claude Code Process Manager
3
+ *
4
+ * Uses `claude -p --verbose --output-format stream-json` for structured output.
5
+ * Runs on your Claude Code subscription, not API key.
6
+ */
7
+
8
+ import { spawn, type ChildProcess } from 'node:child_process';
9
+ import { loadSettings } from './settings';
10
+
11
+ export interface ClaudeMessage {
12
+ type: 'system' | 'assistant' | 'result';
13
+ subtype?: string; // e.g. 'tool_use', 'text', 'init'
14
+ content: string;
15
+ tool?: string; // tool name if tool_use
16
+ costUSD?: number;
17
+ sessionId?: string;
18
+ timestamp: string;
19
+ }
20
+
21
+ export interface ClaudeProcess {
22
+ id: string;
23
+ projectName: string;
24
+ projectPath: string;
25
+ status: 'running' | 'idle' | 'exited';
26
+ createdAt: string;
27
+ messages: ClaudeMessage[];
28
+ conversationId?: string;
29
+ }
30
+
31
+ interface ManagedProcess {
32
+ info: ClaudeProcess;
33
+ child: ChildProcess | null;
34
+ listeners: Set<(msg: ClaudeMessage) => void>;
35
+ }
36
+
37
+ const processes = new Map<string, ManagedProcess>();
38
+
39
+ /**
40
+ * Create a new Claude Code session for a project.
41
+ */
42
+ export function createClaudeSession(projectName: string, projectPath: string): ClaudeProcess {
43
+ const id = `claude-${projectName}-${Date.now().toString(36)}`;
44
+
45
+ const info: ClaudeProcess = {
46
+ id,
47
+ projectName,
48
+ projectPath,
49
+ status: 'idle',
50
+ createdAt: new Date().toISOString(),
51
+ messages: [],
52
+ };
53
+
54
+ const managed: ManagedProcess = {
55
+ info,
56
+ child: null,
57
+ listeners: new Set(),
58
+ };
59
+
60
+ processes.set(id, managed);
61
+ return info;
62
+ }
63
+
64
+ /**
65
+ * Send a message to Claude Code and stream the response.
66
+ */
67
+ export function sendToClaudeSession(
68
+ id: string,
69
+ message: string,
70
+ conversationId?: string
71
+ ): boolean {
72
+ const managed = processes.get(id);
73
+ if (!managed) return false;
74
+ if (managed.info.status === 'running') return false; // Already processing
75
+
76
+ const settings = loadSettings();
77
+ const claudePath = settings.claudePath || process.env.CLAUDE_PATH || 'claude';
78
+
79
+ // Build command args — --verbose is required for stream-json
80
+ const args = ['-p', '--verbose', '--output-format', 'stream-json'];
81
+
82
+ // Continue conversation if we have a session ID
83
+ const continueId = conversationId || managed.info.conversationId;
84
+ if (continueId) {
85
+ args.push('--resume', continueId);
86
+ }
87
+
88
+ // Message passed as CLI arg (pushed above).
89
+ // Spawn without shell to prevent zsh from interpreting special chars
90
+ // in the message (${}, backticks, quotes in code diffs).
91
+
92
+ // Remove CLAUDECODE env var to avoid nesting detection
93
+ const env = { ...process.env };
94
+ delete env.CLAUDECODE;
95
+
96
+ // Resolve full path so spawn works without shell for PATH lookup
97
+ let resolvedClaude = claudePath;
98
+ if (!claudePath.startsWith('/')) {
99
+ try {
100
+ const { execSync: execS } = require('node:child_process');
101
+ resolvedClaude = execS(`which ${claudePath}`, { encoding: 'utf-8', env }).trim() || claudePath;
102
+ } catch {}
103
+ }
104
+
105
+ const child = spawn(resolvedClaude, args, {
106
+ cwd: managed.info.projectPath,
107
+ env,
108
+ stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin — prompt is in args, no stdin needed
109
+ });
110
+
111
+ managed.child = child;
112
+ managed.info.status = 'running';
113
+
114
+ // Add user message
115
+ const userMsg: ClaudeMessage = {
116
+ type: 'system',
117
+ subtype: 'user_input',
118
+ content: message,
119
+ timestamp: new Date().toISOString(),
120
+ };
121
+ managed.info.messages.push(userMsg);
122
+ broadcast(managed, userMsg);
123
+
124
+ let buffer = '';
125
+
126
+ child.stdout?.on('data', (data: Buffer) => {
127
+ buffer += data.toString();
128
+ // stream-json outputs one JSON object per line
129
+ const lines = buffer.split('\n');
130
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
131
+
132
+ for (const line of lines) {
133
+ if (!line.trim()) continue;
134
+ try {
135
+ const parsed = JSON.parse(line);
136
+ const messages = parseClaudeOutput(parsed);
137
+ for (const msg of messages) {
138
+ managed.info.messages.push(msg);
139
+
140
+ // Capture session ID for conversation continuity
141
+ if (parsed.session_id && !managed.info.conversationId) {
142
+ managed.info.conversationId = parsed.session_id;
143
+ }
144
+
145
+ broadcast(managed, msg);
146
+ }
147
+ } catch {
148
+ // Non-JSON line, treat as text
149
+ const msg: ClaudeMessage = {
150
+ type: 'assistant',
151
+ subtype: 'text',
152
+ content: line,
153
+ timestamp: new Date().toISOString(),
154
+ };
155
+ managed.info.messages.push(msg);
156
+ broadcast(managed, msg);
157
+ }
158
+ }
159
+ });
160
+
161
+ child.stderr?.on('data', (data: Buffer) => {
162
+ const text = data.toString().trim();
163
+ if (!text) return;
164
+ const msg: ClaudeMessage = {
165
+ type: 'system',
166
+ subtype: 'error',
167
+ content: text,
168
+ timestamp: new Date().toISOString(),
169
+ };
170
+ managed.info.messages.push(msg);
171
+ broadcast(managed, msg);
172
+ });
173
+
174
+ child.on('exit', (code) => {
175
+ // Process remaining buffer
176
+ if (buffer.trim()) {
177
+ try {
178
+ const parsed = JSON.parse(buffer);
179
+ const messages = parseClaudeOutput(parsed);
180
+ for (const msg of messages) {
181
+ managed.info.messages.push(msg);
182
+ broadcast(managed, msg);
183
+ }
184
+ } catch {}
185
+ buffer = '';
186
+ }
187
+
188
+ managed.info.status = 'idle';
189
+ managed.child = null;
190
+
191
+ const msg: ClaudeMessage = {
192
+ type: 'system',
193
+ subtype: 'complete',
194
+ content: `Done (exit ${code})`,
195
+ timestamp: new Date().toISOString(),
196
+ };
197
+ managed.info.messages.push(msg);
198
+ broadcast(managed, msg);
199
+ });
200
+
201
+ return true;
202
+ }
203
+
204
+ /**
205
+ * Parse actual claude stream-json output into ClaudeMessage(s).
206
+ *
207
+ * Real format examples:
208
+ * - {type: "system", subtype: "init", session_id: "...", model: "...", ...}
209
+ * - {type: "assistant", message: {content: [{type: "text", text: "..."}, {type: "tool_use", name: "...", input: {...}}]}, session_id: "..."}
210
+ * - {type: "result", subtype: "success", result: "...", total_cost_usd: 0.06, session_id: "..."}
211
+ */
212
+ function parseClaudeOutput(parsed: any): ClaudeMessage[] {
213
+ const msgs: ClaudeMessage[] = [];
214
+ const ts = new Date().toISOString();
215
+
216
+ // System init message
217
+ if (parsed.type === 'system' && parsed.subtype === 'init') {
218
+ msgs.push({
219
+ type: 'system',
220
+ subtype: 'init',
221
+ content: `Model: ${parsed.model || 'unknown'}`,
222
+ sessionId: parsed.session_id,
223
+ timestamp: ts,
224
+ });
225
+ return msgs;
226
+ }
227
+
228
+ // Assistant message — contains content array with text and tool_use blocks
229
+ if (parsed.type === 'assistant' && parsed.message?.content) {
230
+ const content = parsed.message.content;
231
+ if (Array.isArray(content)) {
232
+ for (const block of content) {
233
+ if (block.type === 'text' && block.text) {
234
+ msgs.push({
235
+ type: 'assistant',
236
+ subtype: 'text',
237
+ content: block.text,
238
+ sessionId: parsed.session_id,
239
+ timestamp: ts,
240
+ });
241
+ } else if (block.type === 'tool_use') {
242
+ msgs.push({
243
+ type: 'assistant',
244
+ subtype: 'tool_use',
245
+ content: typeof block.input === 'string'
246
+ ? block.input
247
+ : JSON.stringify(block.input || {}),
248
+ tool: block.name,
249
+ sessionId: parsed.session_id,
250
+ timestamp: ts,
251
+ });
252
+ } else if (block.type === 'tool_result') {
253
+ msgs.push({
254
+ type: 'assistant',
255
+ subtype: 'tool_result',
256
+ content: typeof block.content === 'string'
257
+ ? block.content
258
+ : JSON.stringify(block.content || ''),
259
+ tool: block.tool_use_id,
260
+ sessionId: parsed.session_id,
261
+ timestamp: ts,
262
+ });
263
+ }
264
+ }
265
+ }
266
+ return msgs;
267
+ }
268
+
269
+ // Result message
270
+ if (parsed.type === 'result') {
271
+ msgs.push({
272
+ type: 'result',
273
+ subtype: parsed.subtype || 'success',
274
+ content: typeof parsed.result === 'string'
275
+ ? parsed.result
276
+ : JSON.stringify(parsed.result || ''),
277
+ costUSD: parsed.total_cost_usd,
278
+ sessionId: parsed.session_id,
279
+ timestamp: ts,
280
+ });
281
+ return msgs;
282
+ }
283
+
284
+ // Skip rate_limit_event and other internal events silently
285
+ if (parsed.type === 'rate_limit_event') {
286
+ return msgs;
287
+ }
288
+
289
+ // Generic fallback — still show it
290
+ msgs.push({
291
+ type: 'assistant',
292
+ subtype: parsed.type || 'unknown',
293
+ content: JSON.stringify(parsed),
294
+ timestamp: ts,
295
+ });
296
+ return msgs;
297
+ }
298
+
299
+ function broadcast(managed: ManagedProcess, msg: ClaudeMessage) {
300
+ for (const listener of managed.listeners) {
301
+ try { listener(msg); } catch {}
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Attach a listener to receive messages from a Claude session.
307
+ */
308
+ export function attachToProcess(id: string, onMessage: (msg: ClaudeMessage) => void): (() => void) | null {
309
+ const managed = processes.get(id);
310
+ if (!managed) return null;
311
+
312
+ // Send history first
313
+ for (const msg of managed.info.messages) {
314
+ onMessage(msg);
315
+ }
316
+
317
+ managed.listeners.add(onMessage);
318
+ return () => { managed.listeners.delete(onMessage); };
319
+ }
320
+
321
+ /**
322
+ * Kill current running command (not the session).
323
+ */
324
+ export function killProcess(id: string): boolean {
325
+ const managed = processes.get(id);
326
+ if (!managed) return false;
327
+ if (managed.child) {
328
+ managed.child.kill('SIGTERM');
329
+ managed.info.status = 'idle';
330
+ }
331
+ return true;
332
+ }
333
+
334
+ /**
335
+ * Delete a session entirely.
336
+ */
337
+ export function deleteSession(id: string): boolean {
338
+ const managed = processes.get(id);
339
+ if (!managed) return false;
340
+ if (managed.child) managed.child.kill('SIGTERM');
341
+ processes.delete(id);
342
+ return true;
343
+ }
344
+
345
+ /**
346
+ * List all sessions.
347
+ */
348
+ export function listProcesses(): ClaudeProcess[] {
349
+ return Array.from(processes.values()).map(m => ({
350
+ ...m.info,
351
+ messages: [], // Don't send full history in list
352
+ }));
353
+ }
354
+
355
+ /**
356
+ * Get a single session.
357
+ */
358
+ export function getProcess(id: string): ClaudeProcess | null {
359
+ const managed = processes.get(id);
360
+ return managed?.info || null;
361
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Claude Sessions — read Claude Code's on-disk session JSONL files.
3
+ * Enables live-tailing of local CLI sessions from the web UI.
4
+ */
5
+
6
+ import { existsSync, readFileSync, statSync, readdirSync, watch, openSync, readSync, closeSync, unlinkSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { getClaudeDir } from './dirs';
9
+ import { getProjectInfo } from './projects';
10
+
11
+ export interface ClaudeSessionInfo {
12
+ sessionId: string;
13
+ summary?: string;
14
+ firstPrompt?: string;
15
+ messageCount?: number;
16
+ created?: string;
17
+ modified?: string;
18
+ gitBranch?: string;
19
+ fileSize: number;
20
+ }
21
+
22
+ export interface SessionEntry {
23
+ type: 'user' | 'assistant_text' | 'tool_use' | 'tool_result' | 'thinking' | 'system';
24
+ content: string;
25
+ toolName?: string;
26
+ model?: string;
27
+ timestamp?: string;
28
+ }
29
+
30
+ /**
31
+ * Convert a project path to the Claude projects directory.
32
+ * Claude uses: ~/.claude/projects/<path-with-slashes-replaced-by-dashes>/
33
+ */
34
+ export function projectPathToClaudeDir(projectPath: string): string {
35
+ // Claude Code encodes paths by replacing all non-alphanumeric chars with '-'
36
+ const hash = projectPath.replace(/[^a-zA-Z0-9]/g, '-');
37
+ return join(getClaudeDir(), 'projects', hash);
38
+ }
39
+
40
+ /**
41
+ * Get the Claude sessions directory for a project by name.
42
+ */
43
+ export function getClaudeDirForProject(projectName: string): string | null {
44
+ const project = getProjectInfo(projectName);
45
+ if (!project) return null;
46
+ const dir = projectPathToClaudeDir(project.path);
47
+ return existsSync(dir) ? dir : null;
48
+ }
49
+
50
+ /**
51
+ * List all sessions for a project.
52
+ */
53
+ export function listClaudeSessions(projectName: string): ClaudeSessionInfo[] {
54
+ const dir = getClaudeDirForProject(projectName);
55
+ if (!dir) return [];
56
+
57
+ // Scan .jsonl files on disk (authoritative source)
58
+ let diskSessions: ClaudeSessionInfo[] = [];
59
+ try {
60
+ const files = readdirSync(dir).filter(f => f.endsWith('.jsonl'));
61
+ diskSessions = files.map(f => {
62
+ const sessionId = f.replace('.jsonl', '');
63
+ const fp = join(dir, f);
64
+ const stat = statSync(fp);
65
+ return {
66
+ sessionId,
67
+ fileSize: stat.size,
68
+ modified: stat.mtime.toISOString(),
69
+ created: stat.birthtime.toISOString(),
70
+ };
71
+ }).sort((a, b) => (b.modified || '').localeCompare(a.modified || ''));
72
+ } catch {
73
+ return [];
74
+ }
75
+
76
+ // Enrich with metadata from sessions-index.json if available
77
+ const indexPath = join(dir, 'sessions-index.json');
78
+ if (existsSync(indexPath)) {
79
+ try {
80
+ const index = JSON.parse(readFileSync(indexPath, 'utf-8'));
81
+ const indexMap = new Map<string, any>();
82
+ for (const e of (index.entries || [])) {
83
+ if (e.sessionId) indexMap.set(e.sessionId, e);
84
+ }
85
+ for (const s of diskSessions) {
86
+ const meta = indexMap.get(s.sessionId);
87
+ if (meta) {
88
+ s.summary = meta.summary;
89
+ s.firstPrompt = meta.firstPrompt;
90
+ s.messageCount = meta.messageCount;
91
+ s.gitBranch = meta.gitBranch;
92
+ }
93
+ }
94
+ } catch {}
95
+ }
96
+
97
+ return diskSessions;
98
+ }
99
+
100
+ /**
101
+ * Parse a single JSONL line into displayable SessionEntry items.
102
+ * One JSONL line can produce multiple entries (e.g., assistant message with text + tool_use).
103
+ */
104
+ export function parseSessionLine(line: string): SessionEntry[] {
105
+ try {
106
+ const obj = JSON.parse(line);
107
+ const entries: SessionEntry[] = [];
108
+ const ts = obj.timestamp;
109
+
110
+ // Skip internal types
111
+ if (obj.type === 'queue-operation' || obj.type === 'last-prompt' || obj.type === 'rate_limit_event') {
112
+ return [];
113
+ }
114
+
115
+ // User message
116
+ if (obj.type === 'user' && obj.message) {
117
+ const content = typeof obj.message.content === 'string'
118
+ ? obj.message.content
119
+ : JSON.stringify(obj.message.content);
120
+ entries.push({ type: 'user', content, timestamp: ts });
121
+ return entries;
122
+ }
123
+
124
+ // Assistant message — can contain multiple content blocks
125
+ if (obj.type === 'assistant' && obj.message?.content) {
126
+ const model = obj.message.model;
127
+ for (const block of obj.message.content) {
128
+ if (block.type === 'thinking' && block.thinking) {
129
+ entries.push({ type: 'thinking', content: block.thinking, model, timestamp: ts });
130
+ } else if (block.type === 'text' && block.text) {
131
+ entries.push({ type: 'assistant_text', content: block.text, model, timestamp: ts });
132
+ } else if (block.type === 'tool_use') {
133
+ entries.push({
134
+ type: 'tool_use',
135
+ content: JSON.stringify(block.input || {}, null, 2),
136
+ toolName: block.name,
137
+ model,
138
+ timestamp: ts,
139
+ });
140
+ } else if (block.type === 'tool_result') {
141
+ const resultContent = typeof block.content === 'string'
142
+ ? block.content
143
+ : JSON.stringify(block.content);
144
+ entries.push({ type: 'tool_result', content: resultContent, timestamp: ts });
145
+ }
146
+ }
147
+ return entries;
148
+ }
149
+
150
+ // Tool result message (separate line)
151
+ if (obj.type === 'tool_result' && obj.message?.content) {
152
+ for (const block of obj.message.content) {
153
+ if (block.type === 'tool_result') {
154
+ const content = typeof block.content === 'string'
155
+ ? block.content
156
+ : JSON.stringify(block.content);
157
+ entries.push({ type: 'tool_result', content, timestamp: ts });
158
+ }
159
+ }
160
+ return entries;
161
+ }
162
+
163
+ // System messages
164
+ if (obj.type === 'system') {
165
+ entries.push({ type: 'system', content: obj.content || JSON.stringify(obj), timestamp: ts });
166
+ return entries;
167
+ }
168
+
169
+ return entries;
170
+ } catch {
171
+ return [];
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get the JSONL file path for a session.
177
+ */
178
+ export function getSessionFilePath(projectName: string, sessionId: string): string | null {
179
+ const dir = getClaudeDirForProject(projectName);
180
+ if (!dir) return null;
181
+ const fp = join(dir, `${sessionId}.jsonl`);
182
+ return existsSync(fp) ? fp : null;
183
+ }
184
+
185
+ /**
186
+ * Read all entries from a session file.
187
+ */
188
+ export function readSessionEntries(filePath: string): SessionEntry[] {
189
+ const content = readFileSync(filePath, 'utf-8');
190
+ const entries: SessionEntry[] = [];
191
+ for (const line of content.split('\n')) {
192
+ if (!line.trim()) continue;
193
+ entries.push(...parseSessionLine(line));
194
+ }
195
+ return entries;
196
+ }
197
+
198
+ /**
199
+ * Delete a session file and its cache entry.
200
+ */
201
+ export function deleteSession(projectName: string, sessionId: string): boolean {
202
+ const dir = getClaudeDirForProject(projectName);
203
+ if (!dir) return false;
204
+ const fp = join(dir, `${sessionId}.jsonl`);
205
+ if (!existsSync(fp)) return false;
206
+ unlinkSync(fp);
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * Tail a session file — calls onNewEntries when new lines are appended.
212
+ * Returns a cleanup function.
213
+ */
214
+ export function tailSessionFile(
215
+ filePath: string,
216
+ onNewEntries: (entries: SessionEntry[], raw: string) => void,
217
+ onError?: (err: Error) => void,
218
+ ): () => void {
219
+ let bytesRead = 0;
220
+
221
+ try {
222
+ bytesRead = statSync(filePath).size;
223
+ } catch {}
224
+
225
+ const readNewBytes = () => {
226
+ try {
227
+ const stat = statSync(filePath);
228
+ if (stat.size <= bytesRead) return;
229
+
230
+ const fd = openSync(filePath, 'r');
231
+ const buf = Buffer.alloc(stat.size - bytesRead);
232
+ readSync(fd, buf, 0, buf.length, bytesRead);
233
+ closeSync(fd);
234
+ bytesRead = stat.size;
235
+
236
+ const newText = buf.toString('utf-8');
237
+ const lines = newText.split('\n').filter(l => l.trim());
238
+ const entries: SessionEntry[] = [];
239
+ for (const line of lines) {
240
+ entries.push(...parseSessionLine(line));
241
+ }
242
+ if (entries.length > 0) {
243
+ onNewEntries(entries, newText);
244
+ }
245
+ } catch (err) {
246
+ onError?.(err as Error);
247
+ }
248
+ };
249
+
250
+ // Use both fs.watch AND polling as fallback (fs.watch is unreliable on macOS)
251
+ const watcher = watch(filePath, (eventType) => {
252
+ if (eventType === 'change') {
253
+ readNewBytes();
254
+ }
255
+ });
256
+
257
+ watcher.on('error', (err) => onError?.(err));
258
+
259
+ // Poll every 1 second as fallback (fs.watch is unreliable on macOS)
260
+ const pollTimer = setInterval(readNewBytes, 1000);
261
+
262
+ return () => {
263
+ watcher.close();
264
+ clearInterval(pollTimer);
265
+ };
266
+ }