@aion0/forge 0.5.26 → 0.5.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
  2. package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
  3. package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
  4. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
  5. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
  6. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
  7. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
  8. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
  9. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
  10. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
  11. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
  12. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
  13. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
  14. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
  15. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
  16. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
  17. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
  18. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
  19. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
  20. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
  21. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
  22. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
  23. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
  24. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
  25. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
  26. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
  27. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
  28. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
  29. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
  30. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
  31. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
  32. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
  33. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
  34. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
  35. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
  36. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
  37. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
  38. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
  39. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
  40. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
  41. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
  42. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
  43. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
  44. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
  45. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
  46. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
  47. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
  48. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
  49. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
  50. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
  51. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
  52. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
  53. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
  54. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
  55. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
  56. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
  57. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
  58. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
  59. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
  60. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
  61. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
  62. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
  63. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
  64. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
  65. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
  66. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
  67. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
  68. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
  69. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
  70. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
  71. package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
  72. package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
  73. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
  74. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
  75. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
  76. package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
  77. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
  78. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
  79. package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
  80. package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
  81. package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
  82. package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
  83. package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
  84. package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
  85. package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
  86. package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
  87. package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
  88. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
  89. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
  90. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
  91. package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
  92. package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
  93. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
  94. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
  95. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
  96. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
  97. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
  98. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
  99. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
  100. package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
  101. package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
  102. package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
  103. package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
  104. package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
  105. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
  106. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
  107. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
  108. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
  109. package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
  110. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
  111. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
  112. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
  113. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
  114. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
  115. package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
  116. package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
  117. package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
  118. package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
  119. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
  120. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
  121. package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
  122. package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
  123. package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
  124. package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
  125. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
  126. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
  127. package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
  128. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
  129. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
  130. package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
  131. package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
  132. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
  133. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
  134. package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
  135. package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
  136. package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
  137. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
  138. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
  139. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
  140. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
  141. package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
  142. package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
  143. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
  144. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
  145. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
  146. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
  147. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
  148. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
  149. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
  150. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
  151. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
  152. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
  153. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
  154. package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
  155. package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
  156. package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
  157. package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
  158. package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
  159. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
  160. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
  161. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
  162. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
  163. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
  164. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
  165. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
  166. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
  167. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
  168. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
  169. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
  170. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
  171. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
  172. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
  173. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
  174. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
  175. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
  176. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
  177. package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
  178. package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
  179. package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
  180. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
  181. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
  182. package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
  183. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
  184. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
  185. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
  186. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
  187. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
  188. package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
  189. package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
  190. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
  191. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
  192. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
  193. package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
  194. package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
  195. package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
  196. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
  197. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
  198. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
  199. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
  200. package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
  201. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
  202. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
  203. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
  204. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
  205. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
  206. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
  207. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
  208. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
  209. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
  210. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
  211. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
  212. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
  213. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
  214. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
  215. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
  216. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
  217. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
  218. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
  219. package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
  220. package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
  221. package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
  222. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
  223. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
  224. package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
  225. package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
  226. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
  227. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
  228. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
  229. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
  230. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
  231. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
  232. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
  233. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
  234. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
  235. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
  236. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
  237. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
  238. package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
  239. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
  240. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
  241. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
  242. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
  243. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
  244. package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
  245. package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
  246. package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
  247. package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
  248. package/RELEASE_NOTES.md +10 -29
  249. package/app/api/terminal-bell/route.ts +6 -2
  250. package/app/api/terminal-cwd/route.ts +7 -4
  251. package/components/CodeViewer.tsx +3 -31
  252. package/components/Dashboard.tsx +34 -20
  253. package/components/WebTerminal.tsx +36 -2
  254. package/lib/terminal-standalone.ts +19 -2
  255. package/package.json +1 -1
@@ -0,0 +1,398 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * TerminalLauncher — unified terminal session picker and open utilities.
5
+ *
6
+ * Two main exports:
7
+ * 1. TerminalSessionPicker — dialog component for choosing how to open a terminal.
8
+ * Shows: "Current Session" (highlighted), "New Session", expandable list of other sessions.
9
+ *
10
+ * 2. openWorkspaceTerminal / buildProjectTerminalConfig — helpers to open a terminal
11
+ * correctly depending on context (workspace smith vs project/VibeCoding).
12
+ *
13
+ * Workspace smiths:
14
+ * - Need FORGE env vars injected via the forge launch script.
15
+ * - Must go through open_terminal API → daemon creates tmux → FloatingTerminal attaches.
16
+ *
17
+ * Project / VibeCoding:
18
+ * - Build profileEnv client-side from agent profile.
19
+ * - FloatingTerminal creates a new tmux session and runs the CLI.
20
+ */
21
+
22
+ import { useState, useEffect } from 'react';
23
+
24
+ // ─── Types ────────────────────────────────────────────────
25
+
26
+ export interface SessionInfo {
27
+ id: string;
28
+ modified: string; // ISO string
29
+ size: number; // bytes
30
+ }
31
+
32
+ /**
33
+ * Selection result from TerminalSessionPicker.
34
+ * mode='current' → open with currentSessionId (resume)
35
+ * mode='new' → open a fresh session (no --resume)
36
+ * mode='session' → open with a specific sessionId (resume)
37
+ */
38
+ export type PickerSelection =
39
+ | { mode: 'current'; sessionId: string }
40
+ | { mode: 'new' }
41
+ | { mode: 'session'; sessionId: string };
42
+
43
+ // ─── Session Fetchers ─────────────────────────────────────
44
+
45
+ /**
46
+ * Fetch sessions for a workspace agent (workDir-scoped, via workspace API).
47
+ * Used by workspace smith Open Terminal.
48
+ */
49
+ export async function fetchAgentSessions(workspaceId: string, agentId: string): Promise<SessionInfo[]> {
50
+ try {
51
+ const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ action: 'sessions', agentId }),
55
+ });
56
+ const data = await res.json();
57
+ return data.sessions || [];
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Fetch sessions for a project (project-level, via claude-sessions API).
65
+ * Used by ProjectDetail terminal button and VibeCoding / SessionView.
66
+ */
67
+ export async function fetchProjectSessions(projectName: string): Promise<SessionInfo[]> {
68
+ try {
69
+ const res = await fetch(`/api/claude-sessions/${encodeURIComponent(projectName)}`);
70
+ const data = await res.json();
71
+ if (!Array.isArray(data)) return [];
72
+ return data.map((s: any) => ({
73
+ id: s.sessionId || s.id || '',
74
+ modified: s.modified || '',
75
+ size: s.fileSize || s.size || 0,
76
+ }));
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+
82
+ // ─── Formatting helpers ───────────────────────────────────
83
+
84
+ function formatRelativeTime(iso: string): string {
85
+ const diff = Date.now() - new Date(iso).getTime();
86
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
87
+ if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
88
+ return new Date(iso).toLocaleDateString();
89
+ }
90
+
91
+ function formatSize(bytes: number): string {
92
+ if (bytes < 1024) return `${bytes}B`;
93
+ if (bytes < 1_048_576) return `${(bytes / 1024).toFixed(0)}KB`;
94
+ return `${(bytes / 1_048_576).toFixed(1)}MB`;
95
+ }
96
+
97
+ // ─── SessionItem ──────────────────────────────────────────
98
+
99
+ function SessionItem({ session, onSelect }: {
100
+ session: SessionInfo;
101
+ onSelect: () => void;
102
+ }) {
103
+ const [expanded, setExpanded] = useState(false);
104
+ const [copied, setCopied] = useState(false);
105
+
106
+ const copyId = (e: React.MouseEvent) => {
107
+ e.stopPropagation();
108
+ navigator.clipboard.writeText(session.id).then(() => {
109
+ setCopied(true);
110
+ setTimeout(() => setCopied(false), 1500);
111
+ });
112
+ };
113
+
114
+ return (
115
+ <div className="rounded border border-[#21262d] hover:border-[#30363d] hover:bg-[#161b22] transition-colors">
116
+ <div className="flex items-center gap-2 px-3 py-1.5 cursor-pointer" onClick={() => setExpanded(!expanded)}>
117
+ <span className="text-[8px] text-gray-600">{expanded ? '▼' : '▶'}</span>
118
+ <span className="text-[9px] text-gray-400 font-mono">{session.id.slice(0, 8)}</span>
119
+ <span className="text-[8px] text-gray-600">{formatRelativeTime(session.modified)}</span>
120
+ <span className="text-[8px] text-gray-600">{formatSize(session.size)}</span>
121
+ <button
122
+ onClick={e => { e.stopPropagation(); onSelect(); }}
123
+ className="ml-auto text-[8px] px-1.5 py-0.5 rounded bg-[#238636]/20 text-[#3fb950] hover:bg-[#238636]/40"
124
+ >
125
+ Resume
126
+ </button>
127
+ </div>
128
+ {expanded && (
129
+ <div className="px-3 pb-2 flex items-center gap-1.5">
130
+ <code className="text-[8px] text-gray-500 font-mono bg-[#161b22] px-1.5 py-0.5 rounded border border-[#21262d] select-all flex-1 overflow-hidden text-ellipsis">
131
+ {session.id}
132
+ </code>
133
+ <button
134
+ onClick={copyId}
135
+ className="text-[8px] px-1.5 py-0.5 rounded bg-[#30363d] text-gray-400 hover:text-white hover:bg-[#484f58] shrink-0"
136
+ >
137
+ {copied ? '✓' : 'Copy'}
138
+ </button>
139
+ </div>
140
+ )}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ // ─── TerminalSessionPicker ────────────────────────────────
146
+
147
+ /**
148
+ * Unified dialog for choosing how to open a terminal session.
149
+ *
150
+ * Props:
151
+ * agentLabel — Display name for the agent / project (shown in title).
152
+ * currentSessionId — Bound/fixed session to show as "Current Session". null → no current.
153
+ * sessions — List of all available sessions (pre-fetched or lazy). If null, loading spinner shown.
154
+ * supportsSession — Whether the agent supports claude --resume. Default true.
155
+ * onSelect — Called with the picker result when user chooses an option.
156
+ * onCancel — Called when user dismisses without selecting.
157
+ */
158
+ export function TerminalSessionPicker({
159
+ agentLabel,
160
+ currentSessionId,
161
+ sessions,
162
+ supportsSession = true,
163
+ onSelect,
164
+ onCancel,
165
+ }: {
166
+ agentLabel: string;
167
+ currentSessionId: string | null;
168
+ sessions: SessionInfo[] | null; // null = loading
169
+ supportsSession?: boolean;
170
+ onSelect: (selection: PickerSelection) => void;
171
+ onCancel: () => void;
172
+ }) {
173
+ const [showAll, setShowAll] = useState(false);
174
+
175
+ const isClaude = supportsSession !== false;
176
+
177
+ // Other sessions = all sessions except the current one
178
+ const otherSessions = sessions?.filter(s => s.id !== currentSessionId) ?? [];
179
+
180
+ return (
181
+ <div
182
+ className="fixed inset-0 z-50 flex items-center justify-center"
183
+ style={{ background: 'rgba(0,0,0,0.75)' }}
184
+ onClick={e => { if (e.target === e.currentTarget) onCancel(); }}
185
+ >
186
+ <div
187
+ className="w-80 rounded-lg border border-[#30363d] p-4 shadow-xl"
188
+ style={{ background: '#0d1117' }}
189
+ >
190
+ <div className="text-sm font-bold text-white mb-3">⌨️ {agentLabel}</div>
191
+
192
+ <div className="space-y-2">
193
+ {/* Current Session — shown first, highlighted if exists */}
194
+ {isClaude && currentSessionId && (
195
+ <button
196
+ onClick={() => onSelect({ mode: 'current', sessionId: currentSessionId })}
197
+ className="w-full text-left px-3 py-2 rounded border border-[#3fb950]/60 hover:border-[#3fb950] hover:bg-[#161b22] transition-colors"
198
+ >
199
+ <div className="text-xs text-white font-semibold flex items-center gap-1.5">
200
+ <span className="text-[#3fb950]">●</span> Current Session
201
+ </div>
202
+ <div className="text-[9px] text-gray-500 font-mono mt-0.5">
203
+ {currentSessionId.slice(0, 16)}…
204
+ </div>
205
+ </button>
206
+ )}
207
+
208
+ {/* New Session */}
209
+ <button
210
+ onClick={() => onSelect({ mode: 'new' })}
211
+ className="w-full text-left px-3 py-2 rounded border border-[#30363d] hover:border-[#58a6ff] hover:bg-[#161b22] transition-colors"
212
+ >
213
+ <div className="text-xs text-white font-semibold">
214
+ {isClaude ? 'New Session' : 'Open Terminal'}
215
+ </div>
216
+ <div className="text-[9px] text-gray-500">
217
+ {isClaude ? 'Start fresh claude session' : 'Launch terminal'}
218
+ </div>
219
+ </button>
220
+
221
+ {/* Loading indicator */}
222
+ {isClaude && sessions === null && (
223
+ <div className="text-[9px] text-gray-600 text-center py-1">Loading sessions…</div>
224
+ )}
225
+
226
+ {/* Toggle for other sessions */}
227
+ {isClaude && otherSessions.length > 0 && (
228
+ <button
229
+ onClick={() => setShowAll(!showAll)}
230
+ className="w-full text-[9px] text-gray-500 hover:text-white py-1"
231
+ >
232
+ {showAll ? '▼' : '▶'} Other sessions ({otherSessions.length})
233
+ </button>
234
+ )}
235
+
236
+ {/* Other sessions list */}
237
+ {showAll && otherSessions.map(s => (
238
+ <SessionItem
239
+ key={s.id}
240
+ session={s}
241
+ onSelect={() => onSelect({ mode: 'session', sessionId: s.id })}
242
+ />
243
+ ))}
244
+ </div>
245
+
246
+ <button
247
+ onClick={onCancel}
248
+ className="w-full mt-3 text-[9px] text-gray-500 hover:text-white"
249
+ >
250
+ Cancel
251
+ </button>
252
+ </div>
253
+ </div>
254
+ );
255
+ }
256
+
257
+ // ─── TerminalSessionPicker with lazy fetch ─────────────────
258
+
259
+ /**
260
+ * Higher-level picker that fetches sessions automatically.
261
+ * Accepts a `fetchSessions` async function — result populates the session list.
262
+ */
263
+ export function TerminalSessionPickerLazy({
264
+ agentLabel,
265
+ currentSessionId,
266
+ fetchSessions,
267
+ supportsSession = true,
268
+ onSelect,
269
+ onCancel,
270
+ }: {
271
+ agentLabel: string;
272
+ currentSessionId: string | null;
273
+ fetchSessions: () => Promise<SessionInfo[]>;
274
+ supportsSession?: boolean;
275
+ onSelect: (selection: PickerSelection) => void;
276
+ onCancel: () => void;
277
+ }) {
278
+ const [sessions, setSessions] = useState<SessionInfo[] | null>(null);
279
+
280
+ useEffect(() => {
281
+ if (!supportsSession) {
282
+ setSessions([]);
283
+ return;
284
+ }
285
+ fetchSessions().then(setSessions).catch(() => setSessions([]));
286
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
287
+
288
+ return (
289
+ <TerminalSessionPicker
290
+ agentLabel={agentLabel}
291
+ currentSessionId={currentSessionId}
292
+ sessions={sessions}
293
+ supportsSession={supportsSession}
294
+ onSelect={onSelect}
295
+ onCancel={onCancel}
296
+ />
297
+ );
298
+ }
299
+
300
+ // ─── Workspace Terminal Open ──────────────────────────────
301
+
302
+ /**
303
+ * Result from resolving how to open a workspace terminal.
304
+ * When tmuxSession is set → FloatingTerminal should attach (existingSession=tmuxSession).
305
+ * When tmuxSession is null → daemon couldn't create session; fall back to dialog or skip.
306
+ */
307
+ export interface WorkspaceTerminalInfo {
308
+ tmuxSession: string | null;
309
+ cliCmd?: string;
310
+ cliType?: string;
311
+ supportsSession?: boolean;
312
+ }
313
+
314
+ /**
315
+ * Ask the orchestrator to create/find the tmux session for a workspace agent.
316
+ * Returns tmuxSession name that FloatingTerminal can attach to.
317
+ * This is the ONLY correct way to open a workspace terminal — ensures FORGE env vars
318
+ * are injected via the forge launch script (not client-side profileEnv).
319
+ */
320
+ export async function resolveWorkspaceTerminal(
321
+ workspaceId: string,
322
+ agentId: string,
323
+ ): Promise<WorkspaceTerminalInfo> {
324
+ try {
325
+ const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
326
+ method: 'POST',
327
+ headers: { 'Content-Type': 'application/json' },
328
+ body: JSON.stringify({ action: 'open_terminal', agentId }),
329
+ });
330
+ const data = await res.json();
331
+ return {
332
+ tmuxSession: data.tmuxSession || null,
333
+ cliCmd: data.cliCmd,
334
+ cliType: data.cliType,
335
+ supportsSession: data.supportsSession ?? true,
336
+ };
337
+ } catch {
338
+ return { tmuxSession: null };
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Resolve agent info for a workspace agent (resolveOnly — no session created).
344
+ * Used to get cliCmd, cliType, env, model, supportsSession without side effects.
345
+ */
346
+ export async function resolveWorkspaceAgentInfo(
347
+ workspaceId: string,
348
+ agentId: string,
349
+ ): Promise<{ cliCmd?: string; cliType?: string; env?: Record<string, string>; model?: string; supportsSession?: boolean }> {
350
+ try {
351
+ const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
352
+ method: 'POST',
353
+ headers: { 'Content-Type': 'application/json' },
354
+ body: JSON.stringify({ action: 'open_terminal', agentId, resolveOnly: true }),
355
+ });
356
+ return await res.json();
357
+ } catch {
358
+ return {};
359
+ }
360
+ }
361
+
362
+ // ─── Project Terminal Config ──────────────────────────────
363
+
364
+ /**
365
+ * Result for opening a project terminal.
366
+ * FloatingTerminal uses profileEnv + resumeSessionId when creating a new tmux session.
367
+ */
368
+ export interface ProjectTerminalConfig {
369
+ profileEnv: Record<string, string>;
370
+ resumeSessionId?: string;
371
+ cliCmd?: string;
372
+ cliType?: string;
373
+ }
374
+
375
+ /**
376
+ * Build config for opening a project terminal (VibeCoding / ProjectDetail).
377
+ * Agent env and model are resolved server-side via /api/agents?resolve=<agentId>.
378
+ * FORGE vars are NOT included here — project terminals don't use workspace context.
379
+ */
380
+ export async function buildProjectTerminalConfig(
381
+ agentId: string,
382
+ resumeSessionId?: string,
383
+ ): Promise<ProjectTerminalConfig> {
384
+ try {
385
+ const res = await fetch(`/api/agents?resolve=${encodeURIComponent(agentId)}`);
386
+ const info = await res.json();
387
+ const profileEnv: Record<string, string> = { ...(info.env || {}) };
388
+ if (info.model) profileEnv.CLAUDE_MODEL = info.model;
389
+ return {
390
+ profileEnv,
391
+ resumeSessionId,
392
+ cliCmd: info.cliCmd,
393
+ cliType: info.cliType,
394
+ };
395
+ } catch {
396
+ return { profileEnv: {}, resumeSessionId };
397
+ }
398
+ }
@@ -0,0 +1,206 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+
5
+ interface TunnelStatus {
6
+ status: 'stopped' | 'starting' | 'running' | 'error';
7
+ url: string | null;
8
+ error: string | null;
9
+ }
10
+
11
+ export default function TunnelToggle() {
12
+ const [tunnel, setTunnel] = useState<TunnelStatus>({ status: 'stopped', url: null, error: null });
13
+ const [loading, setLoading] = useState(false);
14
+ const [copied, setCopied] = useState(false);
15
+ const [isRemote, setIsRemote] = useState(false);
16
+ const [confirmStop, setConfirmStop] = useState(false);
17
+ const [showPasswordPrompt, setShowPasswordPrompt] = useState(false);
18
+ const [password, setPassword] = useState('');
19
+ const [passwordError, setPasswordError] = useState('');
20
+
21
+ useEffect(() => {
22
+ setIsRemote(!['localhost', '127.0.0.1'].includes(window.location.hostname));
23
+ }, []);
24
+
25
+ const refresh = useCallback(() => {
26
+ fetch('/api/tunnel').then(r => r.json()).then(setTunnel).catch(() => {});
27
+ }, []);
28
+
29
+ useEffect(() => {
30
+ refresh();
31
+ const id = setInterval(refresh, 5000);
32
+ return () => clearInterval(id);
33
+ }, [refresh]);
34
+
35
+ useEffect(() => {
36
+ if (tunnel.status !== 'starting') return;
37
+ const id = setInterval(refresh, 2000);
38
+ return () => clearInterval(id);
39
+ }, [tunnel.status, refresh]);
40
+
41
+ const doStop = async () => {
42
+ setLoading(true);
43
+ try {
44
+ const res = await fetch('/api/tunnel', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({ action: 'stop' }),
48
+ });
49
+ const data = await res.json();
50
+ setTunnel(data);
51
+ } catch {}
52
+ setLoading(false);
53
+ setConfirmStop(false);
54
+ };
55
+
56
+ const doStart = async (pw: string) => {
57
+ setLoading(true);
58
+ setPasswordError('');
59
+ try {
60
+ const res = await fetch('/api/tunnel', {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({ action: 'start', password: pw }),
64
+ });
65
+ const data = await res.json();
66
+ if (res.status === 403) {
67
+ setPasswordError('Wrong password');
68
+ setLoading(false);
69
+ return;
70
+ }
71
+ setTunnel(data);
72
+ setShowPasswordPrompt(false);
73
+ setPassword('');
74
+ } catch {}
75
+ setLoading(false);
76
+ };
77
+
78
+ const copyUrl = () => {
79
+ if (tunnel.url) {
80
+ navigator.clipboard.writeText(tunnel.url);
81
+ setCopied(true);
82
+ setTimeout(() => setCopied(false), 2000);
83
+ }
84
+ };
85
+
86
+ // Hide tunnel controls when accessing remotely
87
+ if (isRemote) {
88
+ return null;
89
+ }
90
+
91
+ // Password prompt
92
+ if (showPasswordPrompt) {
93
+ return (
94
+ <div className="flex items-center gap-1.5">
95
+ <input
96
+ type="password"
97
+ value={password}
98
+ onChange={e => { setPassword(e.target.value); setPasswordError(''); }}
99
+ onKeyDown={e => e.key === 'Enter' && password && doStart(password)}
100
+ placeholder="Login password"
101
+ autoFocus
102
+ className={`w-[120px] text-[10px] px-2 py-0.5 bg-[var(--bg-tertiary)] border rounded font-mono focus:outline-none ${
103
+ passwordError ? 'border-[var(--red)]' : 'border-[var(--border)] focus:border-[var(--accent)]'
104
+ } text-[var(--text-primary)]`}
105
+ />
106
+ <button
107
+ onClick={() => password && doStart(password)}
108
+ disabled={!password || loading}
109
+ className="text-[10px] px-2 py-0.5 bg-[var(--green)] text-black rounded hover:opacity-90 disabled:opacity-50"
110
+ >
111
+ {loading ? 'Starting...' : 'Start'}
112
+ </button>
113
+ <button
114
+ onClick={() => { setShowPasswordPrompt(false); setPassword(''); setPasswordError(''); }}
115
+ className="text-[10px] px-1.5 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
116
+ >
117
+ Cancel
118
+ </button>
119
+ </div>
120
+ );
121
+ }
122
+
123
+ // Stop confirmation dialog
124
+ if (confirmStop) {
125
+ return (
126
+ <div className="flex items-center gap-1.5">
127
+ <span className="text-[10px] text-[var(--text-secondary)]">Stop tunnel?</span>
128
+ <button
129
+ onClick={doStop}
130
+ disabled={loading}
131
+ className="text-[10px] px-2 py-0.5 bg-[var(--red)] text-white rounded hover:opacity-90"
132
+ >
133
+ Confirm
134
+ </button>
135
+ <button
136
+ onClick={() => setConfirmStop(false)}
137
+ className="text-[10px] px-2 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
138
+ >
139
+ Cancel
140
+ </button>
141
+ </div>
142
+ );
143
+ }
144
+
145
+ if (tunnel.status === 'stopped' && !tunnel.error) {
146
+ return (
147
+ <button
148
+ onClick={() => setShowPasswordPrompt(true)}
149
+ disabled={loading}
150
+ className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)] transition-colors disabled:opacity-50"
151
+ title="Start Cloudflare Tunnel for remote access"
152
+ >
153
+ Tunnel
154
+ </button>
155
+ );
156
+ }
157
+
158
+ if (tunnel.status === 'starting') {
159
+ return (
160
+ <span className="text-[10px] px-2 py-0.5 border border-[var(--yellow)] rounded text-[var(--yellow)]">
161
+ Tunnel starting...
162
+ </span>
163
+ );
164
+ }
165
+
166
+ if (tunnel.status === 'running' && tunnel.url) {
167
+ return (
168
+ <div className="flex items-center gap-1.5">
169
+ <button
170
+ onClick={copyUrl}
171
+ className="text-[10px] px-2 py-0.5 border border-[var(--green)] rounded text-[var(--green)] hover:bg-[var(--green)] hover:text-black transition-colors truncate max-w-[200px]"
172
+ title={`Click to copy: ${tunnel.url}`}
173
+ >
174
+ {copied ? 'Copied!' : tunnel.url.replace('https://', '')}
175
+ </button>
176
+ <button
177
+ onClick={() => setConfirmStop(true)}
178
+ disabled={loading}
179
+ className="text-[10px] px-1.5 py-0.5 text-[var(--red)] hover:bg-[var(--red)] hover:text-white rounded transition-colors"
180
+ title="Stop tunnel"
181
+ >
182
+ Stop
183
+ </button>
184
+ </div>
185
+ );
186
+ }
187
+
188
+ if (tunnel.status === 'error') {
189
+ return (
190
+ <div className="flex items-center gap-1.5">
191
+ <span className="text-[10px] text-[var(--red)] truncate max-w-[200px]" title={tunnel.error || ''}>
192
+ Tunnel error
193
+ </span>
194
+ <button
195
+ onClick={() => setShowPasswordPrompt(true)}
196
+ disabled={loading}
197
+ className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors"
198
+ >
199
+ Retry
200
+ </button>
201
+ </div>
202
+ );
203
+ }
204
+
205
+ return null;
206
+ }