@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,807 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
4
+ import { signOut } from 'next-auth/react';
5
+ import TaskBoard from './TaskBoard';
6
+ import TaskDetail from './TaskDetail';
7
+ import TunnelToggle from './TunnelToggle';
8
+ import type { Task } from '@/src/types';
9
+ import type { WebTerminalHandle } from './WebTerminal';
10
+
11
+ const WebTerminal = lazy(() => import('./WebTerminal'));
12
+ const DocsViewer = lazy(() => import('./DocsViewer'));
13
+ const CodeViewer = lazy(() => import('./CodeViewer'));
14
+ const ProjectManager = lazy(() => import('./ProjectManager'));
15
+ const BrowserPanel = lazy(() => import('./BrowserPanel'));
16
+ const PipelineView = lazy(() => import('./PipelineView'));
17
+ const HelpDialog = lazy(() => import('./HelpDialog'));
18
+ const LogViewer = lazy(() => import('./LogViewer'));
19
+ const SkillsPanel = lazy(() => import('./SkillsPanel'));
20
+ const UsagePanel = lazy(() => import('./UsagePanel'));
21
+ const SessionView = lazy(() => import('./SessionView'));
22
+ const NewTaskModal = lazy(() => import('./NewTaskModal'));
23
+ const SettingsModal = lazy(() => import('./SettingsModal'));
24
+ const MonitorPanel = lazy(() => import('./MonitorPanel'));
25
+ const WorkspaceView = lazy(() => import('./WorkspaceView'));
26
+ // WorkspaceTree moved into ProjectDetail — no longer needed at Dashboard level
27
+
28
+ interface UsageSummary {
29
+ provider: string;
30
+ totalInput: number;
31
+ totalOutput: number;
32
+ totalCost: number;
33
+ }
34
+
35
+ interface ProviderInfo {
36
+ name: string;
37
+ displayName: string;
38
+ hasKey: boolean;
39
+ enabled: boolean;
40
+ }
41
+
42
+ interface ProjectInfo {
43
+ name: string;
44
+ path: string;
45
+ language: string | null;
46
+ }
47
+
48
+ function FloatingBrowser({ onClose }: { onClose: () => void }) {
49
+ const [pos, setPos] = useState({ x: 60, y: 60 });
50
+ const [size, setSize] = useState({ w: 700, h: 500 });
51
+ const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null);
52
+ const resizeRef = useRef<{ startX: number; startY: number; origW: number; origH: number } | null>(null);
53
+
54
+ return (
55
+ <div
56
+ className="fixed z-50 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-2xl flex flex-col overflow-hidden"
57
+ style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}
58
+ >
59
+ <div
60
+ className="flex items-center gap-2 px-3 py-1.5 bg-[var(--bg-tertiary)] border-b border-[var(--border)] cursor-move shrink-0 select-none"
61
+ onMouseDown={(e) => {
62
+ e.preventDefault();
63
+ dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y };
64
+ const onMove = (ev: MouseEvent) => {
65
+ if (!dragRef.current) return;
66
+ setPos({ x: Math.max(0, dragRef.current.origX + ev.clientX - dragRef.current.startX), y: Math.max(0, dragRef.current.origY + ev.clientY - dragRef.current.startY) });
67
+ };
68
+ const onUp = () => { dragRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
69
+ window.addEventListener('mousemove', onMove);
70
+ window.addEventListener('mouseup', onUp);
71
+ }}
72
+ >
73
+ <span className="text-[11px] font-semibold text-[var(--text-primary)]">Browser</span>
74
+ <button onClick={onClose} className="ml-auto text-[var(--text-secondary)] hover:text-[var(--red)] text-sm leading-none">✕</button>
75
+ </div>
76
+ <div className="flex-1 min-h-0 flex flex-col">
77
+ <BrowserPanel />
78
+ </div>
79
+ <div
80
+ onMouseDown={(e) => {
81
+ e.preventDefault();
82
+ e.stopPropagation();
83
+ resizeRef.current = { startX: e.clientX, startY: e.clientY, origW: size.w, origH: size.h };
84
+ const onMove = (ev: MouseEvent) => {
85
+ if (!resizeRef.current) return;
86
+ setSize({ w: Math.max(400, resizeRef.current.origW + ev.clientX - resizeRef.current.startX), h: Math.max(300, resizeRef.current.origH + ev.clientY - resizeRef.current.startY) });
87
+ };
88
+ const onUp = () => { resizeRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
89
+ window.addEventListener('mousemove', onMove);
90
+ window.addEventListener('mouseup', onUp);
91
+ }}
92
+ className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize"
93
+ style={{ background: 'linear-gradient(135deg, transparent 50%, var(--border) 50%)' }}
94
+ />
95
+ </div>
96
+ );
97
+ }
98
+
99
+ export default function Dashboard({ user }: { user: any }) {
100
+ const [viewMode, setViewMode] = useState<'tasks' | 'sessions' | 'terminal' | 'docs' | 'projects' | 'pipelines' | 'workspace' | 'skills' | 'logs' | 'usage'>('terminal');
101
+ // workspaceProject state kept for forge:open-terminal event compatibility
102
+ const [workspaceProject, setWorkspaceProject] = useState<{ name: string; path: string } | null>(null);
103
+ const [browserMode, setBrowserMode] = useState<'none' | 'float' | 'right' | 'left'>('none');
104
+ const [showBrowserMenu, setShowBrowserMenu] = useState(false);
105
+ const [browserWidth, setBrowserWidth] = useState(600);
106
+ const browserDragRef = useRef<{ startX: number; startW: number } | null>(null);
107
+ const [browserDragging, setBrowserDragging] = useState(false);
108
+ const [tasks, setTasks] = useState<Task[]>([]);
109
+ const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
110
+ const [showNewTask, setShowNewTask] = useState(false);
111
+ const [showSettings, setShowSettings] = useState(false);
112
+ const [showMonitor, setShowMonitor] = useState(false);
113
+ const [showHelp, setShowHelp] = useState(false);
114
+ const [usage, setUsage] = useState<UsageSummary[]>([]);
115
+ const [providers, setProviders] = useState<ProviderInfo[]>([]);
116
+ const [projects, setProjects] = useState<ProjectInfo[]>([]);
117
+ const [onlineCount, setOnlineCount] = useState<{ total: number; remote: number }>({ total: 0, remote: 0 });
118
+ const [versionInfo, setVersionInfo] = useState<{ current: string; latest: string; hasUpdate: boolean } | null>(null);
119
+ const [notifications, setNotifications] = useState<any[]>([]);
120
+ const [unreadCount, setUnreadCount] = useState(0);
121
+ const [showNotifications, setShowNotifications] = useState(false);
122
+ const [showUserMenu, setShowUserMenu] = useState(false);
123
+ const [theme, setTheme] = useState<'dark' | 'light'>('dark');
124
+ const [displayName, setDisplayName] = useState(user?.name || 'Forge');
125
+ const terminalRef = useRef<WebTerminalHandle>(null);
126
+
127
+ // Theme: load from localStorage + apply
128
+ useEffect(() => {
129
+ const saved = localStorage.getItem('forge-theme') as 'dark' | 'light' | null;
130
+ if (saved) {
131
+ setTheme(saved);
132
+ document.documentElement.setAttribute('data-theme', saved === 'light' ? 'light' : '');
133
+ }
134
+ }, []);
135
+
136
+ const toggleTheme = () => {
137
+ const next = theme === 'dark' ? 'light' : 'dark';
138
+ setTheme(next);
139
+ document.documentElement.setAttribute('data-theme', next === 'light' ? 'light' : '');
140
+ localStorage.setItem('forge-theme', next);
141
+ };
142
+
143
+ // Fetch display name from settings
144
+ const refreshDisplayName = useCallback(() => {
145
+ fetch('/api/settings').then(r => r.json())
146
+ .then((s: any) => { if (s.displayName) setDisplayName(s.displayName); })
147
+ .catch(() => {});
148
+ }, []);
149
+ useEffect(() => { refreshDisplayName(); }, [refreshDisplayName]);
150
+
151
+ // Listen for open-terminal events from ProjectManager
152
+ useEffect(() => {
153
+ const handler = (e: Event) => {
154
+ const { projectPath, projectName, agentId, resumeMode, sessionId, profileEnv } = (e as CustomEvent).detail;
155
+ setViewMode('terminal');
156
+ setTimeout(() => {
157
+ terminalRef.current?.openProjectTerminal?.(projectPath, projectName, agentId, resumeMode, sessionId, profileEnv);
158
+ }, 300);
159
+ };
160
+ window.addEventListener('forge:open-terminal', handler);
161
+ return () => window.removeEventListener('forge:open-terminal', handler);
162
+ }, []);
163
+
164
+ // Listen for navigation events (e.g. from ProjectDetail → Pipelines)
165
+ const [pendingPipelineId, setPendingPipelineId] = useState<string | null>(null);
166
+ useEffect(() => {
167
+ const handler = (e: Event) => {
168
+ const { view, pipelineId } = (e as CustomEvent).detail;
169
+ if (view) setViewMode(view);
170
+ if (pipelineId) setPendingPipelineId(pipelineId);
171
+ };
172
+ window.addEventListener('forge:navigate', handler);
173
+ return () => window.removeEventListener('forge:navigate', handler);
174
+ }, []);
175
+
176
+ // Version check (on mount + every 10 min)
177
+ useEffect(() => {
178
+ const check = () => fetch('/api/version').then(r => r.json()).then(setVersionInfo).catch(() => {});
179
+ check();
180
+ const id = setInterval(check, 10 * 60 * 1000);
181
+ return () => clearInterval(id);
182
+ }, []);
183
+
184
+ // Notification polling
185
+ const fetchNotifications = useCallback(() => {
186
+ fetch('/api/notifications').then(r => r.json()).then(data => {
187
+ setNotifications(data.notifications || []);
188
+ setUnreadCount(data.unread || 0);
189
+ }).catch(() => {});
190
+ }, []);
191
+
192
+ useEffect(() => {
193
+ fetchNotifications();
194
+ const id = setInterval(fetchNotifications, 10000);
195
+ return () => clearInterval(id);
196
+ }, [fetchNotifications]);
197
+
198
+ // Heartbeat for online user tracking
199
+ useEffect(() => {
200
+ const ping = () => {
201
+ fetch('/api/online', { method: 'POST' })
202
+ .then(r => r.json())
203
+ .then(setOnlineCount)
204
+ .catch(() => {});
205
+ };
206
+ ping();
207
+ const id = setInterval(ping, 15_000); // every 15s
208
+ return () => clearInterval(id);
209
+ }, []);
210
+
211
+ const fetchData = useCallback(async () => {
212
+ try {
213
+ const [tasksRes, statusRes, projectsRes] = await Promise.all([
214
+ fetch('/api/tasks'),
215
+ fetch('/api/status'),
216
+ fetch('/api/projects'),
217
+ ]);
218
+ if (tasksRes.ok) setTasks(await tasksRes.json());
219
+ if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
220
+ if (projectsRes.ok) setProjects(await projectsRes.json());
221
+ } catch {}
222
+ }, []);
223
+
224
+ useEffect(() => {
225
+ fetchData();
226
+ const interval = setInterval(fetchData, 5000);
227
+ return () => clearInterval(interval);
228
+ }, [fetchData]);
229
+
230
+ const activeTask = tasks.find(t => t.id === activeTaskId);
231
+ const running = tasks.filter(t => t.status === 'running');
232
+ const queued = tasks.filter(t => t.status === 'queued');
233
+
234
+ return (
235
+ <div className="h-screen flex">
236
+ {/* Browser — left side */}
237
+ {browserMode === 'left' && (
238
+ <>
239
+ <div style={{ width: browserWidth }} className="shrink-0 flex flex-col relative">
240
+ <Suspense fallback={null}><BrowserPanel onClose={() => setBrowserMode('none')} /></Suspense>
241
+ {browserDragging && <div className="absolute inset-0 z-10" />}
242
+ </div>
243
+ <div
244
+ onMouseDown={(e) => {
245
+ e.preventDefault();
246
+ browserDragRef.current = { startX: e.clientX, startW: browserWidth };
247
+ setBrowserDragging(true);
248
+ const onMove = (ev: MouseEvent) => {
249
+ if (!browserDragRef.current) return;
250
+ setBrowserWidth(Math.max(320, Math.min(1200, browserDragRef.current.startW + (ev.clientX - browserDragRef.current.startX))));
251
+ };
252
+ const onUp = () => { browserDragRef.current = null; setBrowserDragging(false); window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
253
+ window.addEventListener('mousemove', onMove);
254
+ window.addEventListener('mouseup', onUp);
255
+ }}
256
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50"
257
+ />
258
+ </>
259
+ )}
260
+
261
+ {/* Forge main area */}
262
+ <div className="flex-1 flex flex-col min-w-0 min-h-0 overflow-hidden">
263
+ {/* Top bar */}
264
+ <header className="h-12 border-b-2 border-[var(--border)] flex items-center justify-between px-4 shrink-0 bg-[var(--bg-secondary)]">
265
+ <div className="flex items-center gap-4">
266
+ <img src="/icon.png" alt="Forge" width={28} height={28} className="rounded" />
267
+ <span className="text-sm font-bold text-[var(--accent)]">Forge</span>
268
+ {versionInfo && (
269
+ <span className="flex items-center gap-1.5">
270
+ <span className="text-[10px] text-[var(--text-secondary)]">v{versionInfo.current}</span>
271
+ {versionInfo.hasUpdate && (
272
+ <span
273
+ className="text-[9px] px-1.5 py-0.5 bg-[var(--accent)]/15 text-[var(--accent)] rounded cursor-default"
274
+ title={`forge upgrade\nnpm install -g @aion0/forge@latest`}
275
+ >
276
+ v{versionInfo.latest} available
277
+ </span>
278
+ )}
279
+ <button
280
+ onClick={async () => {
281
+ const res = await fetch('/api/version?force=1');
282
+ const data = await res.json();
283
+ setVersionInfo(data);
284
+ if (data.hasUpdate) fetchNotifications();
285
+ }}
286
+ className="text-[9px] px-1 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
287
+ title="Check for updates"
288
+ >
289
+
290
+ </button>
291
+ </span>
292
+ )}
293
+
294
+ {/* View mode toggle */}
295
+ <div className="flex items-center bg-[var(--bg-tertiary)] rounded p-0.5">
296
+ {/* Workspace */}
297
+ {(['terminal', 'projects'] as const).map(mode => (
298
+ <button
299
+ key={mode}
300
+ onClick={() => setViewMode(mode)}
301
+ className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
302
+ viewMode === mode
303
+ ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
304
+ : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
305
+ }`}
306
+ >
307
+ {{ terminal: 'Vibe Coding', projects: 'Projects' }[mode]}
308
+ </button>
309
+ ))}
310
+ <span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
311
+ {/* Docs */}
312
+ <button
313
+ onClick={() => setViewMode('docs')}
314
+ className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
315
+ viewMode === 'docs'
316
+ ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
317
+ : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
318
+ }`}
319
+ >
320
+ Docs
321
+ </button>
322
+ <span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
323
+ {/* Automation */}
324
+ {(['tasks', 'pipelines'] as const).map(mode => (
325
+ <button
326
+ key={mode}
327
+ onClick={() => setViewMode(mode)}
328
+ className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
329
+ viewMode === mode
330
+ ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
331
+ : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
332
+ }`}
333
+ >
334
+ {{ tasks: 'Tasks', pipelines: 'Pipelines' }[mode]}
335
+ </button>
336
+ ))}
337
+ <span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
338
+ {/* Marketplace */}
339
+ <button
340
+ onClick={() => setViewMode('skills')}
341
+ className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
342
+ viewMode === 'skills'
343
+ ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
344
+ : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
345
+ }`}
346
+ >
347
+ Marketplace
348
+ </button>
349
+ </div>
350
+
351
+ {viewMode === 'tasks' && (
352
+ <span className="text-[10px] text-[var(--text-secondary)]">
353
+ {running.length} running · {queued.length} queued · {tasks.filter(t => t.status === 'done').length} done
354
+ </span>
355
+ )}
356
+ </div>
357
+ <div className="flex items-center gap-2.5">
358
+ {viewMode === 'tasks' && (
359
+ <button
360
+ onClick={() => setShowNewTask(true)}
361
+ className="text-[10px] px-2.5 py-1 bg-[var(--accent)] text-white rounded hover:opacity-90"
362
+ >
363
+ + New Task
364
+ </button>
365
+ )}
366
+ {/* Help */}
367
+ <button
368
+ onClick={() => setShowHelp(v => !v)}
369
+ className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
370
+ showHelp
371
+ ? 'border-[var(--accent)] text-[var(--accent)]'
372
+ : 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
373
+ }`}
374
+ >?</button>
375
+ <div className="relative">
376
+ <button
377
+ onClick={() => setShowBrowserMenu(v => !v)}
378
+ className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
379
+ browserMode !== 'none'
380
+ ? 'border-blue-500 text-blue-400'
381
+ : 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
382
+ }`}
383
+ >
384
+ Browser
385
+ </button>
386
+ {showBrowserMenu && (
387
+ <>
388
+ <div className="fixed inset-0 z-40" onClick={() => setShowBrowserMenu(false)} />
389
+ <div className="absolute top-full right-0 mt-1 z-50 bg-[var(--bg-secondary)] border border-[var(--border)] rounded shadow-lg py-1 min-w-[140px]">
390
+ {browserMode !== 'none' && (
391
+ <button onClick={() => { setBrowserMode('none'); setShowBrowserMenu(false); }} className="w-full text-left px-3 py-1.5 text-[10px] text-red-400 hover:bg-[var(--bg-tertiary)]">
392
+ Close Browser
393
+ </button>
394
+ )}
395
+ <button onClick={() => { setBrowserMode('float'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'float' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
396
+ Floating Window
397
+ </button>
398
+ <button onClick={() => { setBrowserMode('right'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'right' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
399
+ Right Side
400
+ </button>
401
+ <button onClick={() => { setBrowserMode('left'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'left' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
402
+ Left Side
403
+ </button>
404
+ <button onClick={() => {
405
+ const url = localStorage.getItem('forge-browser-url');
406
+ if (url) window.open(url, '_blank');
407
+ else { const u = prompt('Enter URL to open:'); if (u) window.open(u.trim(), '_blank'); }
408
+ setShowBrowserMenu(false);
409
+ }} className="w-full text-left px-3 py-1.5 text-[10px] text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]">
410
+ New Tab
411
+ </button>
412
+ </div>
413
+ </>
414
+ )}
415
+ </div>
416
+ <button
417
+ onClick={() => setViewMode('usage')}
418
+ className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
419
+ viewMode === 'usage'
420
+ ? 'border-[var(--accent)] text-[var(--accent)]'
421
+ : 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
422
+ }`}
423
+ >Usage</button>
424
+ <TunnelToggle />
425
+ {onlineCount.total > 0 && (
426
+ <span className="text-[10px] text-[var(--text-secondary)] flex items-center gap-1" title={`${onlineCount.total} online${onlineCount.remote > 0 ? `, ${onlineCount.remote} remote` : ''}`}>
427
+ <span className="text-green-500">●</span>
428
+ {onlineCount.total}
429
+ </span>
430
+ )}
431
+ <span className="w-[2px] h-4 bg-[var(--text-secondary)]/30" />
432
+ {/* Alerts */}
433
+ <div className="relative">
434
+ <button
435
+ onClick={() => { setShowNotifications(v => !v); setShowUserMenu(false); }}
436
+ className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] relative px-1"
437
+ >
438
+ Alerts
439
+ {unreadCount > 0 && (
440
+ <span className="absolute -top-1.5 -right-1.5 min-w-[14px] h-[14px] rounded-full bg-[var(--red)] text-[8px] text-white flex items-center justify-center px-1 font-bold">
441
+ {unreadCount > 99 ? '99+' : unreadCount}
442
+ </span>
443
+ )}
444
+ </button>
445
+ {showNotifications && (
446
+ <>
447
+ <div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
448
+ <div className="absolute right-0 top-8 w-[360px] max-h-[480px] bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-xl z-50 flex flex-col">
449
+ <div className="flex items-center justify-between px-3 py-2 border-b border-[var(--border)]">
450
+ <span className="text-xs font-bold text-[var(--text-primary)]">Notifications</span>
451
+ <div className="flex items-center gap-2">
452
+ {unreadCount > 0 && (
453
+ <button
454
+ onClick={async () => {
455
+ await fetch('/api/notifications', {
456
+ method: 'POST',
457
+ headers: { 'Content-Type': 'application/json' },
458
+ body: JSON.stringify({ action: 'markAllRead' }),
459
+ });
460
+ fetchNotifications();
461
+ }}
462
+ className="text-[9px] text-[var(--accent)] hover:underline"
463
+ >
464
+ Mark all read
465
+ </button>
466
+ )}
467
+ <button
468
+ onClick={() => setShowNotifications(false)}
469
+ className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
470
+ >
471
+ Close
472
+ </button>
473
+ </div>
474
+ </div>
475
+ <div className="flex-1 overflow-y-auto">
476
+ {notifications.length === 0 ? (
477
+ <div className="p-6 text-center text-xs text-[var(--text-secondary)]">No notifications</div>
478
+ ) : (
479
+ notifications.map((n: any) => (
480
+ <div
481
+ key={n.id}
482
+ className={`group px-3 py-2 border-b border-[var(--border)]/50 hover:bg-[var(--bg-tertiary)] ${!n.read ? 'bg-[var(--accent)]/5' : ''}`}
483
+ >
484
+ <div className="flex items-start gap-2">
485
+ <span className="text-[10px] mt-0.5 shrink-0">
486
+ {n.type === 'task_done' ? '✅' : n.type === 'task_failed' ? '❌' : n.type === 'pipeline_done' ? '🔗' : n.type === 'pipeline_failed' ? '💔' : n.type === 'tunnel' ? '🌐' : 'ℹ️'}
487
+ </span>
488
+ <div className="flex-1 min-w-0">
489
+ <div className="flex items-center gap-1">
490
+ <span className={`text-[11px] truncate ${!n.read ? 'font-semibold text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}`}>
491
+ {n.title}
492
+ </span>
493
+ {!n.read && <span className="w-1.5 h-1.5 rounded-full bg-[var(--accent)] shrink-0" />}
494
+ </div>
495
+ {n.body && (
496
+ <p className="text-[9px] text-[var(--text-secondary)] truncate mt-0.5">{n.body}</p>
497
+ )}
498
+ <span className="text-[8px] text-[var(--text-secondary)]">
499
+ {new Date(n.createdAt).toLocaleString()}
500
+ </span>
501
+ </div>
502
+ <div className="hidden group-hover:flex items-center gap-1 shrink-0">
503
+ {!n.read && (
504
+ <button
505
+ onClick={async (e) => {
506
+ e.stopPropagation();
507
+ await fetch('/api/notifications', {
508
+ method: 'POST',
509
+ headers: { 'Content-Type': 'application/json' },
510
+ body: JSON.stringify({ action: 'markRead', id: n.id }),
511
+ });
512
+ fetchNotifications();
513
+ }}
514
+ className="text-[8px] px-1 py-0.5 text-[var(--accent)] hover:underline"
515
+ >
516
+ read
517
+ </button>
518
+ )}
519
+ <button
520
+ onClick={async (e) => {
521
+ e.stopPropagation();
522
+ await fetch('/api/notifications', {
523
+ method: 'POST',
524
+ headers: { 'Content-Type': 'application/json' },
525
+ body: JSON.stringify({ action: 'delete', id: n.id }),
526
+ });
527
+ fetchNotifications();
528
+ }}
529
+ className="text-[8px] px-1 py-0.5 text-red-400 hover:underline"
530
+ >
531
+ del
532
+ </button>
533
+ </div>
534
+ </div>
535
+ </div>
536
+ ))
537
+ )}
538
+ </div>
539
+ </div>
540
+ </>
541
+ )}
542
+ </div>
543
+ {/* User menu */}
544
+ <div className="relative">
545
+ <button
546
+ onClick={() => { setShowUserMenu(v => !v); setShowNotifications(false); }}
547
+ className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] flex items-center gap-1 px-1"
548
+ >
549
+ {displayName} <span className="text-[8px]">▾</span>
550
+ </button>
551
+ {showUserMenu && (
552
+ <>
553
+ <div className="fixed inset-0 z-40" onClick={() => setShowUserMenu(false)} />
554
+ <div className="absolute right-0 top-8 w-[140px] bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-xl z-50 py-1">
555
+ <button
556
+ onClick={() => { setShowMonitor(true); setShowUserMenu(false); }}
557
+ className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
558
+ >
559
+ Monitor
560
+ </button>
561
+ <button
562
+ onClick={() => { setShowSettings(true); setShowUserMenu(false); }}
563
+ className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
564
+ >
565
+ Settings
566
+ </button>
567
+ <button
568
+ onClick={() => { setViewMode('logs'); setShowUserMenu(false); }}
569
+ className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
570
+ >
571
+ Logs
572
+ </button>
573
+ <a
574
+ href="/mobile"
575
+ className="block w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
576
+ >
577
+ Mobile View
578
+ </a>
579
+ <div className="border-t border-[var(--border)] my-1" />
580
+ <button
581
+ onClick={() => signOut({ callbackUrl: '/login' })}
582
+ className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--red)] hover:bg-[var(--bg-tertiary)]"
583
+ >
584
+ Logout
585
+ </button>
586
+ </div>
587
+ </>
588
+ )}
589
+ </div>
590
+ </div>
591
+ </header>
592
+
593
+ {/* Main content */}
594
+ <div className="flex-1 flex min-h-0">
595
+ {viewMode === 'tasks' ? (
596
+ <>
597
+ {/* Left — Task list */}
598
+ <aside className="w-72 border-r border-[var(--border)] flex flex-col shrink-0">
599
+ <TaskBoard tasks={tasks} activeId={activeTaskId} onSelect={setActiveTaskId} onRefresh={fetchData} />
600
+ </aside>
601
+
602
+ {/* Center — Task detail / empty state */}
603
+ <main className="flex-1 flex flex-col min-w-0">
604
+ {activeTask ? (
605
+ <TaskDetail
606
+ task={activeTask}
607
+ onRefresh={fetchData}
608
+ onFollowUp={async (data) => {
609
+ const res = await fetch('/api/tasks', {
610
+ method: 'POST',
611
+ headers: { 'Content-Type': 'application/json' },
612
+ body: JSON.stringify(data),
613
+ });
614
+ const newTask = await res.json();
615
+ setActiveTaskId(newTask.id);
616
+ fetchData();
617
+ }}
618
+ />
619
+ ) : (
620
+ <div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">
621
+ <div className="text-center space-y-2">
622
+ <p className="text-lg">Select a task or create a new one</p>
623
+ <p className="text-xs">Submit tasks for Claude Code to work on autonomously</p>
624
+ </div>
625
+ </div>
626
+ )}
627
+ </main>
628
+
629
+ {/* Right — Status panel */}
630
+ <aside className="w-56 border-l border-[var(--border)] flex flex-col shrink-0 p-3 space-y-4">
631
+ {/* Providers */}
632
+ <div>
633
+ <h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Providers</h3>
634
+ <div className="space-y-1">
635
+ {providers.map(p => (
636
+ <div key={p.name} className="flex items-center justify-between text-xs">
637
+ <span className={p.hasKey && p.enabled ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}>
638
+ {p.displayName}
639
+ </span>
640
+ <span className={`text-[10px] ${p.hasKey && p.enabled ? 'text-[var(--green)]' : 'text-[var(--red)]'}`}>
641
+ {p.hasKey && p.enabled ? '● active' : '○ off'}
642
+ </span>
643
+ </div>
644
+ ))}
645
+ </div>
646
+ </div>
647
+
648
+ {/* Usage */}
649
+ {usage.length > 0 && (
650
+ <div>
651
+ <h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Usage (30d)</h3>
652
+ <div className="space-y-1">
653
+ {usage.map((u, i) => (
654
+ <div key={i} className="text-xs">
655
+ <div className="flex justify-between">
656
+ <span className="text-[var(--text-secondary)]">{u.provider}</span>
657
+ <span className="text-[var(--text-primary)]">{((u.totalInput + u.totalOutput) / 1000).toFixed(0)}k tokens</span>
658
+ </div>
659
+ </div>
660
+ ))}
661
+ </div>
662
+ </div>
663
+ )}
664
+
665
+ {/* Running tasks */}
666
+ {running.length > 0 && (
667
+ <div>
668
+ <h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Running</h3>
669
+ <div className="space-y-1">
670
+ {running.map(t => (
671
+ <button
672
+ key={t.id}
673
+ onClick={() => { setViewMode('tasks'); setActiveTaskId(t.id); }}
674
+ className="w-full text-left px-2 py-1 rounded text-xs hover:bg-[var(--bg-tertiary)]"
675
+ >
676
+ <span className="text-[var(--green)] text-[10px]">● </span>
677
+ <span className="truncate">{t.projectName}</span>
678
+ </button>
679
+ ))}
680
+ </div>
681
+ </div>
682
+ )}
683
+ </aside>
684
+ </>
685
+ ) : null}
686
+
687
+ {/* Projects — keep alive to preserve state across tab switches */}
688
+ <div className={`flex-1 flex flex-col min-h-0 ${viewMode !== 'projects' ? 'hidden' : ''}`}>
689
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
690
+ <ProjectManager />
691
+ </Suspense>
692
+ </div>
693
+
694
+ {/* Pipelines */}
695
+ {viewMode === 'pipelines' && (
696
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
697
+ <PipelineView
698
+ onViewTask={(taskId) => { setViewMode('tasks'); setActiveTaskId(taskId); }}
699
+ focusPipelineId={pendingPipelineId}
700
+ onFocusHandled={() => setPendingPipelineId(null)}
701
+ />
702
+ </Suspense>
703
+ )}
704
+
705
+
706
+ {/* Skills */}
707
+ {viewMode === 'skills' && (
708
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
709
+ <SkillsPanel />
710
+ </Suspense>
711
+ )}
712
+
713
+
714
+ {/* Usage */}
715
+ {viewMode === 'usage' && (
716
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
717
+ <UsagePanel />
718
+ </Suspense>
719
+ )}
720
+
721
+ {/* Logs */}
722
+ {viewMode === 'logs' && (
723
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
724
+ <LogViewer />
725
+ </Suspense>
726
+ )}
727
+
728
+ {/* Docs — always mounted to keep terminal session alive */}
729
+ <div className={viewMode === 'docs' ? 'flex-1 min-h-0 flex' : 'hidden'}>
730
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
731
+ <DocsViewer />
732
+ </Suspense>
733
+ </div>
734
+
735
+ {/* Code — terminal + file browser, always mounted to keep terminal sessions alive */}
736
+ <div className={viewMode === 'terminal' ? 'flex-1 min-h-0 flex' : 'hidden'}>
737
+ <Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
738
+ <CodeViewer terminalRef={terminalRef} />
739
+ </Suspense>
740
+ </div>
741
+ </div>
742
+ </div>{/* close Forge main area */}
743
+
744
+ {/* Browser — right side */}
745
+ {browserMode === 'right' && (
746
+ <>
747
+ <div
748
+ onMouseDown={(e) => {
749
+ e.preventDefault();
750
+ browserDragRef.current = { startX: e.clientX, startW: browserWidth };
751
+ setBrowserDragging(true);
752
+ const onMove = (ev: MouseEvent) => {
753
+ if (!browserDragRef.current) return;
754
+ setBrowserWidth(Math.max(320, Math.min(1200, browserDragRef.current.startW - (ev.clientX - browserDragRef.current.startX))));
755
+ };
756
+ const onUp = () => { browserDragRef.current = null; setBrowserDragging(false); window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
757
+ window.addEventListener('mousemove', onMove);
758
+ window.addEventListener('mouseup', onUp);
759
+ }}
760
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50"
761
+ />
762
+ <div style={{ width: browserWidth }} className="shrink-0 flex flex-col relative">
763
+ <Suspense fallback={null}><BrowserPanel onClose={() => setBrowserMode('none')} /></Suspense>
764
+ {browserDragging && <div className="absolute inset-0 z-10" />}
765
+ </div>
766
+ </>
767
+ )}
768
+
769
+ {/* Browser — floating window */}
770
+ {browserMode === 'float' && (
771
+ <Suspense fallback={null}>
772
+ <FloatingBrowser onClose={() => setBrowserMode('none')} />
773
+ </Suspense>
774
+ )}
775
+
776
+ {showNewTask && (
777
+ <Suspense fallback={null}>
778
+ <NewTaskModal
779
+ onClose={() => setShowNewTask(false)}
780
+ onCreate={async (data) => {
781
+ await fetch('/api/tasks', {
782
+ method: 'POST',
783
+ headers: { 'Content-Type': 'application/json' },
784
+ body: JSON.stringify(data),
785
+ });
786
+ setShowNewTask(false);
787
+ fetchData();
788
+ }}
789
+ />
790
+ </Suspense>
791
+ )}
792
+
793
+ {showMonitor && <Suspense fallback={null}><MonitorPanel onClose={() => setShowMonitor(false)} /></Suspense>}
794
+
795
+ {showSettings && (
796
+ <Suspense fallback={null}>
797
+ <SettingsModal onClose={() => { setShowSettings(false); fetchData(); refreshDisplayName(); }} />
798
+ </Suspense>
799
+ )}
800
+ {showHelp && (
801
+ <Suspense fallback={null}>
802
+ <HelpDialog onClose={() => setShowHelp(false)} />
803
+ </Suspense>
804
+ )}
805
+ </div>
806
+ );
807
+ }