@aion0/forge 0.5.26 → 0.5.27

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/.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 +11 -28
  249. package/app/api/terminal-bell/route.ts +6 -2
  250. package/components/WebTerminal.tsx +36 -2
  251. package/lib/terminal-standalone.ts +19 -2
  252. package/next-env.d.ts +1 -1
  253. package/package.json +1 -1
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef, lazy, Suspense } from 'react';
4
+
5
+ interface DocItem {
6
+ name: string;
7
+ title: string;
8
+ }
9
+
10
+ const HelpTerminal = lazy(() => import('./HelpTerminal'));
11
+
12
+ export default function HelpDialog({ onClose }: { onClose: () => void }) {
13
+ const [docs, setDocs] = useState<DocItem[]>([]);
14
+ const [agent, setAgent] = useState<{ name: string } | null | undefined>(undefined); // undefined = loading
15
+ const [viewDoc, setViewDoc] = useState<string | null>(null);
16
+ const [docContent, setDocContent] = useState('');
17
+ const [search, setSearch] = useState('');
18
+ const [tab, setTab] = useState<'docs' | 'chat'>('docs');
19
+ const [position, setPosition] = useState({ x: Math.max(0, window.innerWidth - 520), y: 50 });
20
+ const [size, setSize] = useState({ w: 500, h: 560 });
21
+ const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null);
22
+ const resizeRef = useRef<{ startX: number; startY: number; origW: number; origH: number } | null>(null);
23
+
24
+ useEffect(() => {
25
+ fetch('/api/help?action=status').then(r => r.json())
26
+ .then(data => setAgent(data.agent || null)).catch(() => setAgent(null));
27
+ fetch('/api/help?action=docs').then(r => r.json())
28
+ .then(data => setDocs(data.docs || [])).catch(() => {});
29
+ }, []);
30
+
31
+ const loadDoc = async (name: string) => {
32
+ setViewDoc(name);
33
+ try {
34
+ const res = await fetch(`/api/help?action=doc&name=${encodeURIComponent(name)}`);
35
+ const data = await res.json();
36
+ setDocContent(data.content || '');
37
+ } catch { setDocContent('Failed to load'); }
38
+ };
39
+
40
+ // Drag
41
+ const onDragStart = (e: React.MouseEvent) => {
42
+ e.preventDefault();
43
+ dragRef.current = { startX: e.clientX, startY: e.clientY, origX: position.x, origY: position.y };
44
+ const onMove = (ev: MouseEvent) => {
45
+ if (!dragRef.current) return;
46
+ setPosition({
47
+ x: Math.max(0, dragRef.current.origX + ev.clientX - dragRef.current.startX),
48
+ y: Math.max(0, dragRef.current.origY + ev.clientY - dragRef.current.startY),
49
+ });
50
+ };
51
+ const onUp = () => { dragRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
52
+ window.addEventListener('mousemove', onMove);
53
+ window.addEventListener('mouseup', onUp);
54
+ };
55
+
56
+ // Resize
57
+ const onResizeStart = (e: React.MouseEvent) => {
58
+ e.preventDefault();
59
+ e.stopPropagation();
60
+ resizeRef.current = { startX: e.clientX, startY: e.clientY, origW: size.w, origH: size.h };
61
+ const onMove = (ev: MouseEvent) => {
62
+ if (!resizeRef.current) return;
63
+ setSize({
64
+ w: Math.max(350, resizeRef.current.origW + ev.clientX - resizeRef.current.startX),
65
+ h: Math.max(300, resizeRef.current.origH + ev.clientY - resizeRef.current.startY),
66
+ });
67
+ };
68
+ const onUp = () => { resizeRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
69
+ window.addEventListener('mousemove', onMove);
70
+ window.addEventListener('mouseup', onUp);
71
+ };
72
+
73
+ const filtered = search ? docs.filter(d => d.title.toLowerCase().includes(search.toLowerCase())) : docs;
74
+
75
+ return (
76
+ <div
77
+ className="fixed z-50 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-2xl flex flex-col overflow-hidden"
78
+ style={{ left: position.x, top: position.y, width: size.w, height: size.h }}
79
+ >
80
+ {/* Title bar */}
81
+ <div
82
+ className="flex items-center gap-2 px-3 py-2 bg-[var(--bg-tertiary)] border-b border-[var(--border)] cursor-move shrink-0 select-none"
83
+ onMouseDown={onDragStart}
84
+ >
85
+ <span className="text-[11px] font-semibold text-[var(--text-primary)]">Forge Help</span>
86
+ <div className="ml-auto flex items-center gap-1">
87
+ <button
88
+ onClick={() => { setTab('docs'); setViewDoc(null); }}
89
+ className={`text-[9px] px-2 py-0.5 rounded ${tab === 'docs' ? 'bg-[var(--accent)]/20 text-[var(--accent)]' : 'text-[var(--text-secondary)]'}`}
90
+ >Docs</button>
91
+ {agent && (
92
+ <button
93
+ onClick={() => setTab('chat')}
94
+ className={`text-[9px] px-2 py-0.5 rounded ${tab === 'chat' ? 'bg-[var(--accent)]/20 text-[var(--accent)]' : 'text-[var(--text-secondary)]'}`}
95
+ >AI Chat</button>
96
+ )}
97
+ <button onClick={onClose} className="text-[var(--text-secondary)] hover:text-[var(--red)] ml-1 text-sm leading-none">✕</button>
98
+ </div>
99
+ </div>
100
+
101
+ {tab === 'chat' ? (
102
+ /* Embedded terminal */
103
+ <div className="flex-1 min-h-0">
104
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-xs text-[var(--text-secondary)]">Loading terminal...</div>}>
105
+ <HelpTerminal />
106
+ </Suspense>
107
+ </div>
108
+ ) : viewDoc ? (
109
+ /* Doc view */
110
+ <>
111
+ <div className="px-3 py-1.5 border-b border-[var(--border)] flex items-center gap-2 shrink-0">
112
+ <button onClick={() => setViewDoc(null)} className="text-[10px] text-[var(--accent)]">← Back</button>
113
+ <span className="text-[10px] text-[var(--text-primary)] font-semibold truncate">
114
+ {docs.find(d => d.name === viewDoc)?.title || viewDoc}
115
+ </span>
116
+ </div>
117
+ <div className="flex-1 overflow-y-auto p-3">
118
+ <pre className="text-[11px] text-[var(--text-primary)] whitespace-pre-wrap break-words font-mono leading-relaxed">
119
+ {docContent}
120
+ </pre>
121
+ </div>
122
+ </>
123
+ ) : (
124
+ /* Doc list */
125
+ <>
126
+ <div className="px-3 py-2 border-b border-[var(--border)] shrink-0">
127
+ <input
128
+ type="text"
129
+ value={search}
130
+ onChange={e => setSearch(e.target.value)}
131
+ placeholder="Search help topics..."
132
+ className="w-full px-2 py-1 bg-[var(--bg-tertiary)] border border-[var(--border)] rounded text-[10px] text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)]"
133
+ autoFocus
134
+ />
135
+ </div>
136
+ {!agent && agent !== undefined && (
137
+ <div className="px-3 py-2 bg-[var(--yellow)]/10 border-b border-[var(--border)] shrink-0">
138
+ <p className="text-[9px] text-[var(--text-secondary)]">
139
+ Install Claude Code for AI help: <code className="text-[var(--accent)]">npm i -g @anthropic-ai/claude-code</code>
140
+ </p>
141
+ </div>
142
+ )}
143
+ <div className="flex-1 overflow-y-auto">
144
+ {filtered.map(doc => (
145
+ <button
146
+ key={doc.name}
147
+ onClick={() => loadDoc(doc.name)}
148
+ className="w-full text-left px-3 py-2.5 border-b border-[var(--border)]/30 hover:bg-[var(--bg-tertiary)] text-[11px] text-[var(--text-primary)] capitalize"
149
+ >
150
+ {doc.title}
151
+ </button>
152
+ ))}
153
+ </div>
154
+ <div className="px-3 py-2 border-t border-[var(--border)] shrink-0">
155
+ <a href="https://github.com/aiwatching/forge" target="_blank" rel="noopener noreferrer"
156
+ className="text-[9px] text-[var(--text-secondary)] hover:text-[var(--accent)]">GitHub →</a>
157
+ </div>
158
+ </>
159
+ )}
160
+
161
+ {/* Resize handle */}
162
+ <div
163
+ onMouseDown={onResizeStart}
164
+ className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize"
165
+ style={{ background: 'linear-gradient(135deg, transparent 50%, var(--border) 50%)' }}
166
+ />
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,141 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Terminal } from '@xterm/xterm';
5
+ import { FitAddon } from '@xterm/addon-fit';
6
+ import '@xterm/xterm/css/xterm.css';
7
+
8
+ const SESSION_NAME = 'mw-forge-help';
9
+
10
+ function getWsUrl() {
11
+ if (typeof window === 'undefined') return `ws://localhost:${parseInt(process.env.TERMINAL_PORT || '8404')}`;
12
+ const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
13
+ const wsHost = window.location.hostname;
14
+ if (wsHost !== 'localhost' && wsHost !== '127.0.0.1') {
15
+ return `${wsProtocol}//${window.location.host}/terminal-ws`;
16
+ }
17
+ const webPort = parseInt(window.location.port) || 8403;
18
+ return `${wsProtocol}//${wsHost}:${webPort + 1}`;
19
+ }
20
+
21
+ export default function HelpTerminal() {
22
+ const containerRef = useRef<HTMLDivElement>(null);
23
+ const [connected, setConnected] = useState(false);
24
+
25
+ useEffect(() => {
26
+ if (!containerRef.current) return;
27
+
28
+ let disposed = false;
29
+ let dataDir = '~/.forge/data';
30
+ let agentCmd = 'claude';
31
+
32
+ const cs = getComputedStyle(document.documentElement);
33
+ const tv = (name: string) => cs.getPropertyValue(name).trim();
34
+ const term = new Terminal({
35
+ cursorBlink: true,
36
+ fontSize: 12,
37
+ fontFamily: 'Menlo, Monaco, "Courier New", monospace',
38
+ scrollback: 3000,
39
+ logger: { trace: () => {}, debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
40
+ theme: {
41
+ background: tv('--term-bg') || '#1a1a2e',
42
+ foreground: tv('--term-fg') || '#e0e0e0',
43
+ cursor: tv('--term-cursor') || '#7c5bf0',
44
+ selectionBackground: (tv('--term-cursor') || '#7c5bf0') + '44',
45
+ },
46
+ });
47
+ const fit = new FitAddon();
48
+ term.loadAddon(fit);
49
+ term.open(containerRef.current);
50
+ try { fit.fit(); } catch {}
51
+
52
+ const wsUrl = getWsUrl();
53
+ let ws: WebSocket | null = null;
54
+ let reconnectTimer = 0;
55
+ let isNewSession = false;
56
+
57
+ function connect() {
58
+ if (disposed) return;
59
+ const socket = new WebSocket(wsUrl);
60
+ ws = socket;
61
+
62
+ socket.onopen = () => {
63
+ if (disposed) { socket.close(); return; }
64
+ socket.send(JSON.stringify({ type: 'attach', sessionName: SESSION_NAME, cols: term.cols, rows: term.rows }));
65
+ };
66
+
67
+ socket.onmessage = (event) => {
68
+ if (disposed) return;
69
+ try {
70
+ const msg = JSON.parse(event.data);
71
+ if (msg.type === 'output') {
72
+ try { term.write(msg.data); } catch {}
73
+ } else if (msg.type === 'connected') {
74
+ setConnected(true);
75
+ if (isNewSession) {
76
+ isNewSession = false;
77
+ setTimeout(() => {
78
+ if (socket.readyState === WebSocket.OPEN) {
79
+ socket.send(JSON.stringify({ type: 'input', data: `cd "${dataDir}" 2>/dev/null && ${agentCmd}\n` }));
80
+ }
81
+ }, 300);
82
+ }
83
+ } else if (msg.type === 'error') {
84
+ isNewSession = true;
85
+ if (socket.readyState === WebSocket.OPEN) {
86
+ socket.send(JSON.stringify({ type: 'create', cols: term.cols, rows: term.rows, sessionName: SESSION_NAME }));
87
+ }
88
+ }
89
+ } catch {}
90
+ };
91
+
92
+ socket.onclose = () => {
93
+ if (disposed) return;
94
+ setConnected(false);
95
+ reconnectTimer = window.setTimeout(connect, 3000);
96
+ };
97
+ socket.onerror = () => {};
98
+ }
99
+
100
+ // Fetch data dir + default agent then connect
101
+ Promise.all([
102
+ fetch('/api/help?action=status').then(r => r.json()).then(data => { if (data.dataDir) dataDir = data.dataDir; }).catch(() => {}),
103
+ fetch('/api/agents').then(r => r.json()).then(data => {
104
+ const defaultId = data.defaultAgent || 'claude';
105
+ const agent = (data.agents || []).find((a: any) => a.id === defaultId);
106
+ if (agent?.path) agentCmd = agent.path;
107
+ }).catch(() => {}),
108
+ ]).finally(() => { if (!disposed) connect(); });
109
+
110
+ term.onData((data) => {
111
+ if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'input', data }));
112
+ });
113
+
114
+ const resizeObserver = new ResizeObserver(() => {
115
+ const el = containerRef.current;
116
+ if (!el || el.offsetWidth < 50 || el.offsetHeight < 30) return;
117
+ try {
118
+ fit.fit();
119
+ if (term.cols < 2 || term.rows < 2) return;
120
+ if (ws?.readyState === WebSocket.OPEN) {
121
+ ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
122
+ }
123
+ } catch {}
124
+ });
125
+ resizeObserver.observe(containerRef.current);
126
+
127
+ return () => {
128
+ disposed = true;
129
+ clearTimeout(reconnectTimer);
130
+ ws?.close();
131
+ resizeObserver.disconnect();
132
+ term.dispose();
133
+ };
134
+ }, []);
135
+
136
+ return (
137
+ <div className="h-full flex flex-col">
138
+ <div ref={containerRef} className="flex-1" />
139
+ </div>
140
+ );
141
+ }
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect, useRef, lazy, Suspense } from 'react';
4
+ import type { TaskLogEntry } from '@/src/types';
5
+
6
+ const ConversationTerminalView = lazy(() => import('./ConversationTerminalView'));
7
+
8
+ // ─── Task stream hook ─────────────────────────────────────
9
+
10
+ function useTaskStreamInline(taskId: string | undefined, isRunning: boolean) {
11
+ const [log, setLog] = useState<TaskLogEntry[]>([]);
12
+ useEffect(() => {
13
+ if (!taskId || !isRunning) { setLog([]); return; }
14
+ const es = new EventSource(`/api/tasks/${taskId}/stream`);
15
+ es.onmessage = (event) => {
16
+ try {
17
+ const data = JSON.parse(event.data);
18
+ if (data.type === 'log') setLog(prev => [...prev, data.entry]);
19
+ else if (data.type === 'complete' && data.task) setLog(data.task.log);
20
+ } catch {}
21
+ };
22
+ es.onerror = () => es.close();
23
+ return () => es.close();
24
+ }, [taskId, isRunning]);
25
+ return log;
26
+ }
27
+
28
+ // ─── Live log renderer ────────────────────────────────────
29
+
30
+ function InlineLiveLog({ log }: { log: TaskLogEntry[] }) {
31
+ const endRef = useRef<HTMLDivElement>(null);
32
+ useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [log]);
33
+ if (log.length === 0) return <span className="text-[9px] text-[var(--text-secondary)] italic">Starting...</span>;
34
+ return (
35
+ <div className="max-h-[120px] overflow-y-auto text-[8px] font-mono leading-relaxed space-y-0.5">
36
+ {log.slice(-30).map((entry, i) => (
37
+ <div key={i} className={
38
+ entry.type === 'result' ? 'text-green-400' :
39
+ entry.subtype === 'error' ? 'text-red-400' :
40
+ entry.type === 'system' ? 'text-yellow-400/70' :
41
+ 'text-[var(--text-secondary)]'
42
+ }>
43
+ {entry.type === 'assistant' && entry.subtype === 'tool_use'
44
+ ? `⚙ ${entry.tool || 'tool'}: ${entry.content.slice(0, 60)}...`
45
+ : entry.content.slice(0, 120)}{entry.content.length > 120 ? '...' : ''}
46
+ </div>
47
+ ))}
48
+ <div ref={endRef} />
49
+ </div>
50
+ );
51
+ }
52
+
53
+ // ─── DAG node card ────────────────────────────────────────
54
+
55
+ function InlineDagNode({ nodeId, node }: { nodeId: string; node: any }) {
56
+ const isRunning = node.status === 'running';
57
+ const log = useTaskStreamInline(node.taskId, isRunning);
58
+ const statusIcon = node.status === 'done' ? '✅' : node.status === 'failed' ? '❌' : node.status === 'running' ? '🔄' : node.status === 'skipped' ? '⏭' : '⏳';
59
+ return (
60
+ <div className={`border rounded p-2 ${
61
+ isRunning ? 'border-yellow-500/40 bg-yellow-500/5' :
62
+ node.status === 'done' ? 'border-green-500/20 bg-green-500/5' :
63
+ node.status === 'failed' ? 'border-red-500/20 bg-red-500/5' :
64
+ 'border-[var(--border)]'
65
+ }`}>
66
+ <div className="flex items-center gap-1.5 text-[9px]">
67
+ <span>{statusIcon}</span>
68
+ <span className="font-semibold text-[var(--text-primary)]">{nodeId}</span>
69
+ {node.taskId && <span className="text-[7px] text-[var(--accent)] font-mono">task:{node.taskId}</span>}
70
+ <span className="text-[var(--text-secondary)] ml-auto">{node.status}</span>
71
+ </div>
72
+ {isRunning && <div className="mt-1.5"><InlineLiveLog log={log} /></div>}
73
+ {node.error && <div className="text-[8px] text-red-400 mt-1">{node.error}</div>}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ // ─── Main component ───────────────────────────────────────
79
+
80
+ export default function InlinePipelineView({ pipeline, onRefresh }: { pipeline: any; onRefresh: () => void }) {
81
+ useEffect(() => {
82
+ if (pipeline.status !== 'running') return;
83
+ const timer = setInterval(onRefresh, 3000);
84
+ return () => clearInterval(timer);
85
+ }, [pipeline.status, onRefresh]);
86
+
87
+ const isConversation = pipeline.type === 'conversation' && pipeline.conversation;
88
+
89
+ return (
90
+ <div className="bg-[var(--bg-tertiary)]/50">
91
+ {isConversation ? (
92
+ <div style={{ height: 450 }}>
93
+ <Suspense fallback={<div className="flex items-center justify-center h-full text-[9px] text-[var(--text-secondary)]">Loading...</div>}>
94
+ <ConversationTerminalView pipeline={pipeline} />
95
+ </Suspense>
96
+ </div>
97
+ ) : (
98
+ <div className="px-3 py-2 space-y-1.5">
99
+ {pipeline.nodeOrder.map((nodeId: string) => (
100
+ <InlineDagNode key={nodeId} nodeId={nodeId} node={pipeline.nodes[nodeId]} />
101
+ ))}
102
+ </div>
103
+ )}
104
+ {pipeline.status !== 'running' && (
105
+ <div className={`text-[8px] text-center py-1 ${pipeline.status === 'done' ? 'text-green-400' : 'text-red-400'}`}>
106
+ Pipeline {pipeline.status}
107
+ </div>
108
+ )}
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,194 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef, useCallback } from 'react';
4
+
5
+ export default function LogViewer() {
6
+ const [lines, setLines] = useState<string[]>([]);
7
+ const [total, setTotal] = useState(0);
8
+ const [fileSize, setFileSize] = useState(0);
9
+ const [filePath, setFilePath] = useState('');
10
+ const [search, setSearch] = useState('');
11
+ const [maxLines, setMaxLines] = useState(200);
12
+ const [autoRefresh, setAutoRefresh] = useState(true);
13
+ const [processes, setProcesses] = useState<{ pid: string; cpu: string; mem: string; cmd: string }[]>([]);
14
+ const [showProcesses, setShowProcesses] = useState(false);
15
+ const bottomRef = useRef<HTMLDivElement>(null);
16
+ const containerRef = useRef<HTMLDivElement>(null);
17
+ const [autoScroll, setAutoScroll] = useState(true);
18
+
19
+ const fetchLogs = useCallback(async () => {
20
+ try {
21
+ const res = await fetch(`/api/logs?lines=${maxLines}${search ? `&search=${encodeURIComponent(search)}` : ''}`);
22
+ const data = await res.json();
23
+ setLines(data.lines || []);
24
+ setTotal(data.total || 0);
25
+ setFileSize(data.size || 0);
26
+ if (data.file) setFilePath(data.file);
27
+ } catch {}
28
+ }, [maxLines, search]);
29
+
30
+ const fetchProcesses = async () => {
31
+ try {
32
+ const res = await fetch('/api/logs', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ action: 'processes' }),
36
+ });
37
+ const data = await res.json();
38
+ setProcesses(data.processes || []);
39
+ } catch {}
40
+ };
41
+
42
+ const clearLogs = async () => {
43
+ if (!confirm('Clear all logs?')) return;
44
+ await fetch('/api/logs', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({ action: 'clear' }),
48
+ });
49
+ fetchLogs();
50
+ };
51
+
52
+ // Initial + auto refresh
53
+ useEffect(() => { fetchLogs(); }, [fetchLogs]);
54
+ useEffect(() => {
55
+ if (!autoRefresh) return;
56
+ const id = setInterval(fetchLogs, 3000);
57
+ return () => clearInterval(id);
58
+ }, [autoRefresh, fetchLogs]);
59
+
60
+ // Auto scroll
61
+ useEffect(() => {
62
+ if (autoScroll) bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
63
+ }, [lines, autoScroll]);
64
+
65
+ // Detect manual scroll
66
+ const onScroll = () => {
67
+ const el = containerRef.current;
68
+ if (!el) return;
69
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 50;
70
+ setAutoScroll(atBottom);
71
+ };
72
+
73
+ const formatSize = (bytes: number) => {
74
+ if (bytes < 1024) return `${bytes} B`;
75
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
76
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
77
+ };
78
+
79
+ const getLineColor = (line: string) => {
80
+ if (line.includes('[error]') || line.includes('Error') || line.includes('FATAL')) return 'text-red-400';
81
+ if (line.includes('[warn]') || line.includes('Warning') || line.includes('WARN')) return 'text-yellow-400';
82
+ if (line.includes('[forge]') || line.includes('[init]')) return 'text-cyan-400';
83
+ if (line.includes('[task]') || line.includes('[pipeline]')) return 'text-green-400';
84
+ if (line.includes('[telegram]') || line.includes('[terminal]')) return 'text-purple-400';
85
+ if (line.includes('[issue-scanner]') || line.includes('[watcher]')) return 'text-orange-300';
86
+ return 'text-[var(--text-primary)]';
87
+ };
88
+
89
+ return (
90
+ <div className="flex-1 flex flex-col min-h-0">
91
+ {/* Toolbar */}
92
+ <div className="flex items-center gap-2 px-4 py-2 border-b border-[var(--border)] shrink-0 flex-wrap">
93
+ <span className="text-xs font-semibold text-[var(--text-primary)]">Logs</span>
94
+ <span className="text-[8px] text-[var(--text-secondary)]">{total} lines · {formatSize(fileSize)}</span>
95
+
96
+ {/* Search */}
97
+ <input
98
+ type="text"
99
+ value={search}
100
+ onChange={e => setSearch(e.target.value)}
101
+ placeholder="Filter..."
102
+ className="px-2 py-0.5 bg-[var(--bg-tertiary)] border border-[var(--border)] rounded text-[10px] text-[var(--text-primary)] w-32 focus:outline-none focus:border-[var(--accent)]"
103
+ />
104
+
105
+ {/* Max lines */}
106
+ <select
107
+ value={maxLines}
108
+ onChange={e => setMaxLines(Number(e.target.value))}
109
+ className="px-1 py-0.5 bg-[var(--bg-tertiary)] border border-[var(--border)] rounded text-[10px] text-[var(--text-primary)]"
110
+ >
111
+ <option value={100}>100 lines</option>
112
+ <option value={200}>200 lines</option>
113
+ <option value={500}>500 lines</option>
114
+ <option value={1000}>1000 lines</option>
115
+ </select>
116
+
117
+ <div className="ml-auto flex items-center gap-2">
118
+ {/* Auto refresh toggle */}
119
+ <label className="flex items-center gap-1 text-[9px] text-[var(--text-secondary)] cursor-pointer">
120
+ <input type="checkbox" checked={autoRefresh} onChange={e => setAutoRefresh(e.target.checked)} className="accent-[var(--accent)]" />
121
+ Auto (3s)
122
+ </label>
123
+
124
+ {/* Processes */}
125
+ <button
126
+ onClick={() => { setShowProcesses(v => !v); fetchProcesses(); }}
127
+ className={`text-[9px] px-2 py-0.5 rounded ${showProcesses ? 'bg-[var(--accent)]/20 text-[var(--accent)]' : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
128
+ >Processes</button>
129
+
130
+ {/* Refresh */}
131
+ <button onClick={fetchLogs} className="text-[9px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]">↻</button>
132
+
133
+ {/* Clear */}
134
+ <button onClick={clearLogs} className="text-[9px] text-[var(--red)] hover:underline">Clear</button>
135
+ </div>
136
+ </div>
137
+
138
+ {/* Processes panel */}
139
+ {showProcesses && processes.length > 0 && (
140
+ <div className="border-b border-[var(--border)] bg-[var(--bg-tertiary)] max-h-32 overflow-y-auto shrink-0">
141
+ <div className="px-4 py-1 text-[8px] text-[var(--text-secondary)] uppercase">Running Processes</div>
142
+ {processes.map(p => (
143
+ <div key={p.pid} className="px-4 py-0.5 text-[10px] font-mono flex gap-3">
144
+ <span className="text-[var(--accent)] w-12 shrink-0">{p.pid}</span>
145
+ <span className="text-green-400 w-10 shrink-0">{p.cpu}%</span>
146
+ <span className="text-yellow-400 w-10 shrink-0">{p.mem}%</span>
147
+ <span className="text-[var(--text-secondary)] truncate">{p.cmd}</span>
148
+ </div>
149
+ ))}
150
+ </div>
151
+ )}
152
+
153
+ {/* Log content */}
154
+ <div
155
+ ref={containerRef}
156
+ onScroll={onScroll}
157
+ className="flex-1 overflow-auto bg-[var(--bg-primary)] font-mono text-[11px] leading-[1.6]"
158
+ >
159
+ {lines.length === 0 ? (
160
+ <div className="flex items-center justify-center h-full text-[var(--text-secondary)] text-xs">
161
+ {filePath ? 'No log entries' : 'Log file not found — server running in foreground?'}
162
+ </div>
163
+ ) : (
164
+ <div className="p-3">
165
+ {lines.map((line, i) => (
166
+ <div key={i} className={`${getLineColor(line)} hover:bg-[var(--bg-tertiary)] px-1`}>
167
+ {search ? (
168
+ // Highlight search matches
169
+ line.split(new RegExp(`(${search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')).map((part, j) =>
170
+ part.toLowerCase() === search.toLowerCase()
171
+ ? <span key={j} className="bg-[var(--yellow)]/30 text-[var(--yellow)]">{part}</span>
172
+ : part
173
+ )
174
+ ) : line}
175
+ </div>
176
+ ))}
177
+ <div ref={bottomRef} />
178
+ </div>
179
+ )}
180
+ </div>
181
+
182
+ {/* Footer */}
183
+ <div className="px-4 py-1 border-t border-[var(--border)] shrink-0 flex items-center gap-2 text-[8px] text-[var(--text-secondary)]">
184
+ <span>{filePath}</span>
185
+ {!autoScroll && (
186
+ <button
187
+ onClick={() => { setAutoScroll(true); bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }}
188
+ className="ml-auto text-[var(--accent)] hover:underline"
189
+ >↓ Scroll to bottom</button>
190
+ )}
191
+ </div>
192
+ </div>
193
+ );
194
+ }