@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,228 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import { createHash } from 'node:crypto';
6
+ import { getClaudeDir } from '@/lib/dirs';
7
+
8
+ const SKILLS_DIR = join(getClaudeDir(), 'skills');
9
+ const COMMANDS_DIR = join(getClaudeDir(), 'commands');
10
+
11
+ function md5(content: string): string {
12
+ return createHash('md5').update(content).digest('hex');
13
+ }
14
+
15
+ /** Recursively list files in a directory */
16
+ function listFiles(dir: string, prefix = ''): { path: string; size: number }[] {
17
+ const files: { path: string; size: number }[] = [];
18
+ if (!existsSync(dir)) return files;
19
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
20
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
21
+ const fullPath = join(dir, entry.name);
22
+ if (entry.isFile()) {
23
+ files.push({ path: relPath, size: statSync(fullPath).size });
24
+ } else if (entry.isDirectory()) {
25
+ files.push(...listFiles(fullPath, relPath));
26
+ }
27
+ }
28
+ return files;
29
+ }
30
+
31
+ function resolveDir(name: string, type: string, projectPath?: string): string {
32
+ if (type === 'skill') {
33
+ return projectPath
34
+ ? join(projectPath, '.claude', 'skills', name)
35
+ : join(SKILLS_DIR, name);
36
+ }
37
+ // Command: could be single file or directory
38
+ const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
39
+ const dirPath = join(base, name);
40
+ if (existsSync(dirPath) && statSync(dirPath).isDirectory()) return dirPath;
41
+ // Single file — return parent dir
42
+ return base;
43
+ }
44
+
45
+ /** Scan a directory for all installed skills/commands */
46
+ function scanLocalItems(projectPath?: string): { name: string; type: 'skill' | 'command'; scope: string; fileCount: number; projectPath?: string }[] {
47
+ const items: { name: string; type: 'skill' | 'command'; scope: string; fileCount: number; projectPath?: string }[] = [];
48
+
49
+ // Scan skills directories
50
+ const skillsDirs = [
51
+ { dir: SKILLS_DIR, scope: 'global' as const },
52
+ ...(projectPath ? [{ dir: join(projectPath, '.claude', 'skills'), scope: 'project' as const }] : []),
53
+ ];
54
+ for (const { dir, scope } of skillsDirs) {
55
+ if (!existsSync(dir)) continue;
56
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
57
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
58
+ const files = listFiles(join(dir, entry.name));
59
+ items.push({ name: entry.name, type: 'skill', scope, fileCount: files.length });
60
+ }
61
+ }
62
+ }
63
+
64
+ // Scan commands directories
65
+ const cmdDirs = [
66
+ { dir: COMMANDS_DIR, scope: 'global' as const },
67
+ ...(projectPath ? [{ dir: join(projectPath, '.claude', 'commands'), scope: 'project' as const }] : []),
68
+ ];
69
+ for (const { dir, scope } of cmdDirs) {
70
+ if (!existsSync(dir)) continue;
71
+ // Collect names, merge file + dir with same name
72
+ const seen = new Map<string, { name: string; type: 'command'; scope: typeof scope; fileCount: number }>();
73
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
74
+ if (entry.name.startsWith('.')) continue;
75
+ const cmdName = entry.isFile() ? entry.name.replace(/\.md$/, '') : entry.name;
76
+ if (entry.isFile() && !entry.name.endsWith('.md')) continue;
77
+ const existing = seen.get(cmdName);
78
+ if (entry.isDirectory()) {
79
+ const files = listFiles(join(dir, entry.name));
80
+ const count = files.length + (existing?.fileCount || 0);
81
+ seen.set(cmdName, { name: cmdName, type: 'command', scope, fileCount: count });
82
+ } else if (entry.isFile()) {
83
+ if (existing) {
84
+ existing.fileCount += 1;
85
+ } else {
86
+ seen.set(cmdName, { name: cmdName, type: 'command', scope, fileCount: 1 });
87
+ }
88
+ }
89
+ }
90
+ items.push(...seen.values());
91
+ }
92
+
93
+ return items;
94
+ }
95
+
96
+ // GET /api/skills/local?name=X&type=skill|command&project=PATH
97
+ // action=scan → list ALL locally installed skills/commands
98
+ // action=files → list installed files for a specific item
99
+ // action=read&path=FILE → read file content + hash
100
+ export async function GET(req: Request) {
101
+ const { searchParams } = new URL(req.url);
102
+ const action = searchParams.get('action') || 'files';
103
+ const name = searchParams.get('name') || '';
104
+ const type = searchParams.get('type') || 'command';
105
+ const projectPath = searchParams.get('project') || '';
106
+
107
+ if (action === 'scan') {
108
+ const scanAll = searchParams.get('all') === '1';
109
+ if (scanAll) {
110
+ // Scan global + all configured projects
111
+ const { loadSettings } = require('@/lib/settings');
112
+ const settings = loadSettings();
113
+ const allItems: any[] = [];
114
+ // Global
115
+ allItems.push(...scanLocalItems());
116
+ // All projects
117
+ for (const root of (settings.projectRoots || [])) {
118
+ const resolvedRoot = root.replace(/^~/, homedir());
119
+ if (!existsSync(resolvedRoot)) continue;
120
+ for (const entry of readdirSync(resolvedRoot, { withFileTypes: true })) {
121
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
122
+ const pp = join(resolvedRoot, entry.name);
123
+ const projItems = scanLocalItems(pp)
124
+ .filter(i => i.scope === 'project')
125
+ .map(i => ({ ...i, projectPath: pp, scope: entry.name }));
126
+ allItems.push(...projItems);
127
+ }
128
+ }
129
+ return NextResponse.json({ items: allItems });
130
+ }
131
+ const items = scanLocalItems(projectPath || undefined);
132
+ return NextResponse.json({ items });
133
+ }
134
+
135
+ if (action === 'files') {
136
+ if (type === 'skill') {
137
+ const dir = resolveDir(name, type, projectPath || undefined);
138
+ return NextResponse.json({ files: listFiles(dir) });
139
+ } else {
140
+ // Command: collect both single .md file and directory contents
141
+ const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
142
+ const singleFile = join(base, `${name}.md`);
143
+ const dirPath = join(base, name);
144
+ const files: { path: string; size: number }[] = [];
145
+ // Single .md file at root
146
+ if (existsSync(singleFile)) {
147
+ files.push({ path: `${name}.md`, size: statSync(singleFile).size });
148
+ }
149
+ // Directory contents
150
+ if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
151
+ files.push(...listFiles(dirPath).map(f => ({ path: `${name}/${f.path}`, size: f.size })));
152
+ }
153
+ return NextResponse.json({ files });
154
+ }
155
+ }
156
+
157
+ if (action === 'read') {
158
+ const filePath = searchParams.get('path') || '';
159
+ let fullPath: string;
160
+ if (type === 'skill') {
161
+ const dir = resolveDir(name, type, projectPath || undefined);
162
+ fullPath = join(dir, filePath);
163
+ } else {
164
+ // filePath from files action is like "name.md" or "name/sub/file.md"
165
+ // Resolve relative to commands base directory
166
+ const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
167
+ fullPath = join(base, filePath);
168
+ }
169
+
170
+ if (!existsSync(fullPath)) return NextResponse.json({ content: '', hash: '' });
171
+ const content = readFileSync(fullPath, 'utf-8');
172
+ return NextResponse.json({ content, hash: md5(content) });
173
+ }
174
+
175
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
176
+ }
177
+
178
+ // POST /api/skills/local — save, install-local, delete-local
179
+ export async function POST(req: Request) {
180
+ const body = await req.json();
181
+ const action = body.action || 'save';
182
+
183
+ // Install local skill/command to another project or global
184
+ if (action === 'install-local') {
185
+ const { installLocal } = require('@/lib/skills');
186
+ const { name, type, sourceProject, target, force } = body;
187
+ const result = installLocal(name, type, sourceProject || undefined, target, !!force);
188
+ return NextResponse.json(result, { status: result.ok ? 200 : 400 });
189
+ }
190
+
191
+ // Delete local skill/command
192
+ if (action === 'delete-local') {
193
+ const { deleteLocal } = require('@/lib/skills');
194
+ const { name, type, project } = body;
195
+ const ok = deleteLocal(name, type, project || undefined);
196
+ return NextResponse.json({ ok });
197
+ }
198
+
199
+ // Save edited file (default action)
200
+ const { name, type, project, path: filePath, content, expectedHash } = body;
201
+
202
+ let fullPath: string;
203
+ if (type === 'skill') {
204
+ const dir = project
205
+ ? join(project, '.claude', 'skills', name)
206
+ : join(SKILLS_DIR, name);
207
+ fullPath = join(dir, filePath);
208
+ } else {
209
+ const base = project ? join(project, '.claude', 'commands') : COMMANDS_DIR;
210
+ const dirPath = join(base, name);
211
+ if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
212
+ fullPath = join(dirPath, filePath);
213
+ } else {
214
+ fullPath = join(base, filePath);
215
+ }
216
+ }
217
+
218
+ // Check for concurrent modification
219
+ if (expectedHash && existsSync(fullPath)) {
220
+ const current = readFileSync(fullPath, 'utf-8');
221
+ if (md5(current) !== expectedHash) {
222
+ return NextResponse.json({ ok: false, error: 'File was modified externally. Reload and try again.' }, { status: 409 });
223
+ }
224
+ }
225
+
226
+ writeFileSync(fullPath, content, 'utf-8');
227
+ return NextResponse.json({ ok: true, hash: md5(content) });
228
+ }
@@ -0,0 +1,182 @@
1
+ import { NextResponse } from 'next/server';
2
+ import {
3
+ syncSkills,
4
+ listSkills,
5
+ installGlobal,
6
+ installProject,
7
+ uninstallGlobal,
8
+ uninstallProject,
9
+ refreshInstallState,
10
+ checkLocalModified,
11
+ purgeDeletedSkill,
12
+ } from '@/lib/skills';
13
+ import { loadSettings } from '@/lib/settings';
14
+ import { homedir } from 'node:os';
15
+
16
+ function getProjectPaths(): string[] {
17
+ const settings = loadSettings();
18
+ const roots = (settings.projectRoots || []).map(r => r.replace(/^~/, homedir()));
19
+ const paths: string[] = [];
20
+ for (const root of roots) {
21
+ try {
22
+ const { readdirSync, statSync } = require('node:fs');
23
+ const { join } = require('node:path');
24
+ for (const name of readdirSync(root)) {
25
+ const p = join(root, name);
26
+ try { if (statSync(p).isDirectory() && !name.startsWith('.')) paths.push(p); } catch {}
27
+ }
28
+ } catch {}
29
+ }
30
+ return paths;
31
+ }
32
+
33
+ // GET /api/skills — list skills, get file list, or get file content
34
+ export async function GET(req: Request) {
35
+ const { searchParams } = new URL(req.url);
36
+ const action = searchParams.get('action');
37
+ const name = searchParams.get('name');
38
+
39
+ // List files in a skill/command directory
40
+ if (action === 'files' && name) {
41
+ try {
42
+ const settings = loadSettings();
43
+ const repoUrl = settings.skillsRepoUrl || 'https://raw.githubusercontent.com/aiwatching/forge-skills/main';
44
+ const matchRepo = repoUrl.match(/github\.com\/([^/]+\/[^/]+)/);
45
+ const repo = matchRepo ? matchRepo[1] : 'aiwatching/forge-skills';
46
+
47
+ // Try skills/ first, then commands/ (repo may not have commands/ dir)
48
+ let res = await fetch(`https://api.github.com/repos/${repo}/contents/skills/${name}`, {
49
+ headers: { 'Accept': 'application/vnd.github.v3+json' },
50
+ });
51
+ if (!res.ok) {
52
+ res = await fetch(`https://api.github.com/repos/${repo}/contents/commands/${name}`, {
53
+ headers: { 'Accept': 'application/vnd.github.v3+json' },
54
+ });
55
+ }
56
+ if (!res.ok) return NextResponse.json({ files: [] });
57
+
58
+ const items = await res.json();
59
+ const files: { name: string; path: string; type: string }[] = [];
60
+
61
+ const recurse = async (list: any[], prefix = '') => {
62
+ for (const item of list) {
63
+ if (item.type === 'file') {
64
+ files.push({ name: item.name, path: prefix + item.name, type: 'file' });
65
+ } else if (item.type === 'dir') {
66
+ files.push({ name: item.name + '/', path: prefix + item.name, type: 'dir' });
67
+ // Fetch subdirectory contents
68
+ try {
69
+ const subRes = await fetch(item.url, {
70
+ headers: { 'Accept': 'application/vnd.github.v3+json' },
71
+ });
72
+ if (subRes.ok) {
73
+ const subItems = await subRes.json();
74
+ if (Array.isArray(subItems)) {
75
+ await recurse(subItems, prefix + item.name + '/');
76
+ }
77
+ }
78
+ } catch {}
79
+ }
80
+ }
81
+ };
82
+ await recurse(Array.isArray(items) ? items : []);
83
+
84
+ // Sort: dirs first, then files
85
+ files.sort((a, b) => {
86
+ if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
87
+ return a.name.localeCompare(b.name);
88
+ });
89
+
90
+ return NextResponse.json({ files });
91
+ } catch {
92
+ return NextResponse.json({ files: [] });
93
+ }
94
+ }
95
+
96
+ // Get content of a specific file
97
+ if (action === 'file' && name) {
98
+ const filePath = searchParams.get('path') || 'skill.md';
99
+ try {
100
+ const settings = loadSettings();
101
+ const baseUrl = settings.skillsRepoUrl || 'https://raw.githubusercontent.com/aiwatching/forge-skills/main';
102
+ // Try skills/ first, then commands/
103
+ let res = await fetch(`${baseUrl}/skills/${name}/${filePath}`);
104
+ if (!res.ok) {
105
+ res = await fetch(`${baseUrl}/commands/${name}/${filePath}`);
106
+ }
107
+ if (!res.ok) return NextResponse.json({ content: '(Not found)' });
108
+ const content = await res.text();
109
+ return NextResponse.json({ content });
110
+ } catch {
111
+ return NextResponse.json({ content: '(Failed to load)' });
112
+ }
113
+ }
114
+ const skills = listSkills();
115
+ const projects = getProjectPaths().map(p => ({ path: p, name: p.split('/').pop() || p }));
116
+ return NextResponse.json({ skills, projects });
117
+ }
118
+
119
+ // POST /api/skills — sync, install, uninstall
120
+ export async function POST(req: Request) {
121
+ const body = await req.json();
122
+
123
+ if (body.action === 'sync') {
124
+ const result = await syncSkills();
125
+ if (result.synced > 0) {
126
+ refreshInstallState(getProjectPaths());
127
+ }
128
+ return NextResponse.json(result);
129
+ }
130
+
131
+ if (body.action === 'check-modified') {
132
+ try {
133
+ const modified = await checkLocalModified(body.name);
134
+ return NextResponse.json({ modified });
135
+ } catch (e) {
136
+ return NextResponse.json({ modified: false, error: String(e) });
137
+ }
138
+ }
139
+
140
+ if (body.action === 'install') {
141
+ const { name, target } = body; // target: 'global' | projectPath
142
+ if (!name || !target) return NextResponse.json({ ok: false, error: 'name and target required' }, { status: 400 });
143
+ try {
144
+ if (target === 'global') {
145
+ await installGlobal(name);
146
+ } else {
147
+ await installProject(name, target);
148
+ }
149
+ return NextResponse.json({ ok: true });
150
+ } catch (e) {
151
+ return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
152
+ }
153
+ }
154
+
155
+ if (body.action === 'uninstall') {
156
+ const { name, target } = body;
157
+ if (!name || !target) return NextResponse.json({ ok: false, error: 'name and target required' }, { status: 400 });
158
+ try {
159
+ if (target === 'global') {
160
+ uninstallGlobal(name);
161
+ } else {
162
+ uninstallProject(name, target);
163
+ }
164
+ return NextResponse.json({ ok: true });
165
+ } catch (e) {
166
+ return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
167
+ }
168
+ }
169
+
170
+ if (body.action === 'purge-deleted') {
171
+ const { name } = body;
172
+ if (!name) return NextResponse.json({ ok: false, error: 'name required' }, { status: 400 });
173
+ try {
174
+ purgeDeletedSkill(name);
175
+ return NextResponse.json({ ok: true });
176
+ } catch (e) {
177
+ return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
178
+ }
179
+ }
180
+
181
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
182
+ }
@@ -0,0 +1,81 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { getDataDir } from '@/lib/dirs';
5
+
6
+ function getTemplatesDir(): string {
7
+ const dir = join(getDataDir(), 'smith-templates');
8
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
9
+ return dir;
10
+ }
11
+
12
+ export interface SmithTemplate {
13
+ id: string;
14
+ name: string;
15
+ icon: string;
16
+ description?: string;
17
+ config: Record<string, any>; // agent config without id/dependsOn/boundSessionId
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ }
21
+
22
+ // List all smith templates
23
+ export async function GET() {
24
+ const dir = getTemplatesDir();
25
+ const files = readdirSync(dir).filter(f => f.endsWith('.json'));
26
+ const templates: SmithTemplate[] = [];
27
+ for (const f of files) {
28
+ try {
29
+ const data = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
30
+ templates.push(data);
31
+ } catch {}
32
+ }
33
+ templates.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
34
+ return NextResponse.json({ templates });
35
+ }
36
+
37
+ // Save or delete a smith template
38
+ export async function POST(req: Request) {
39
+ const body = await req.json();
40
+ const { action } = body;
41
+
42
+ if (action === 'delete') {
43
+ const { id } = body;
44
+ if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 });
45
+ const fp = join(getTemplatesDir(), `${id}.json`);
46
+ if (existsSync(fp)) unlinkSync(fp);
47
+ return NextResponse.json({ ok: true });
48
+ }
49
+
50
+ // Save (create or update)
51
+ const { config, name, icon, description } = body;
52
+ if (!config || !name) {
53
+ return NextResponse.json({ error: 'config and name required' }, { status: 400 });
54
+ }
55
+
56
+ // Strip runtime/instance-specific fields
57
+ const cleanConfig = { ...config };
58
+ delete cleanConfig.id;
59
+ delete cleanConfig.dependsOn;
60
+ delete cleanConfig.boundSessionId;
61
+ delete cleanConfig.tmuxSession;
62
+ delete cleanConfig.content;
63
+ delete cleanConfig.entries;
64
+ delete cleanConfig.type;
65
+
66
+ const id = body.id || `smith-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`;
67
+ const now = Date.now();
68
+
69
+ const template: SmithTemplate = {
70
+ id,
71
+ name: name.trim(),
72
+ icon: icon || cleanConfig.icon || '🤖',
73
+ description: description?.trim() || '',
74
+ config: cleanConfig,
75
+ createdAt: body.id ? (body.createdAt || now) : now,
76
+ updatedAt: now,
77
+ };
78
+
79
+ writeFileSync(join(getTemplatesDir(), `${id}.json`), JSON.stringify(template, null, 2));
80
+ return NextResponse.json({ ok: true, template });
81
+ }
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getSessionManager } from '@/lib/session-manager';
3
+ import { listAvailableProviders } from '@/src/core/providers/registry';
4
+
5
+ export async function GET() {
6
+ const manager = getSessionManager();
7
+ return NextResponse.json({
8
+ sessions: manager.list(),
9
+ providers: listAvailableProviders(),
10
+ usage: manager.getUsageSummary(),
11
+ });
12
+ }
@@ -0,0 +1,25 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getDb } from '@/src/core/db/database';
3
+ import { getDbPath } from '@/src/config';
4
+
5
+ function db() { return getDb(getDbPath()); }
6
+
7
+ export async function GET(req: NextRequest) {
8
+ const type = req.nextUrl.searchParams.get('type') || 'projects';
9
+ try {
10
+ const row = db().prepare('SELECT data FROM tab_state WHERE type = ?').get(type) as any;
11
+ if (row?.data) return NextResponse.json(JSON.parse(row.data));
12
+ } catch {}
13
+ return NextResponse.json({ tabs: [], activeTabId: 0 });
14
+ }
15
+
16
+ export async function POST(req: NextRequest) {
17
+ const type = req.nextUrl.searchParams.get('type') || 'projects';
18
+ try {
19
+ const body = await req.json();
20
+ db().prepare('INSERT OR REPLACE INTO tab_state (type, data) VALUES (?, ?)').run(type, JSON.stringify(body));
21
+ return NextResponse.json({ ok: true });
22
+ } catch (e: any) {
23
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
24
+ }
25
+ }
@@ -0,0 +1,51 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getTask, cancelTask, deleteTask, retryTask, updateTask } from '@/lib/task-manager';
3
+ import { getProjectInfo } from '@/lib/projects';
4
+
5
+ // Get task details (including full log)
6
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
7
+ const { id } = await params;
8
+ const task = getTask(id);
9
+ if (!task) return NextResponse.json({ error: 'Not found' }, { status: 404 });
10
+ return NextResponse.json(task);
11
+ }
12
+
13
+ // Actions: cancel, retry
14
+ export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
15
+ const { id } = await params;
16
+ const { action } = await req.json();
17
+
18
+ if (action === 'cancel') {
19
+ const ok = cancelTask(id);
20
+ return NextResponse.json({ ok });
21
+ }
22
+
23
+ if (action === 'retry') {
24
+ const newTask = retryTask(id);
25
+ if (!newTask) return NextResponse.json({ error: 'Cannot retry this task' }, { status: 400 });
26
+ return NextResponse.json(newTask);
27
+ }
28
+
29
+ return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
30
+ }
31
+
32
+ // Edit a task
33
+ export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
34
+ const { id } = await params;
35
+ const body = await req.json();
36
+ // Resolve projectName to projectPath if changed
37
+ if (body.projectName && !body.projectPath) {
38
+ const project = getProjectInfo(body.projectName);
39
+ if (project) body.projectPath = project.path;
40
+ }
41
+ const updated = updateTask(id, body);
42
+ if (!updated) return NextResponse.json({ error: 'Cannot edit this task' }, { status: 400 });
43
+ return NextResponse.json(updated);
44
+ }
45
+
46
+ // Delete a task
47
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
48
+ const { id } = await params;
49
+ deleteTask(id);
50
+ return NextResponse.json({ ok: true });
51
+ }
@@ -0,0 +1,77 @@
1
+ import { getTask } from '@/lib/task-manager';
2
+ import { onTaskEvent } from '@/lib/task-manager';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+ export const runtime = 'nodejs';
6
+
7
+ // SSE stream for real-time task log updates
8
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
9
+ const { id } = await params;
10
+
11
+ const task = getTask(id);
12
+ if (!task) {
13
+ return new Response('Task not found', { status: 404 });
14
+ }
15
+
16
+ const encoder = new TextEncoder();
17
+ let unsubscribe: (() => void) | null = null;
18
+ let heartbeat: ReturnType<typeof setInterval> | null = null;
19
+ let closed = false;
20
+
21
+ const stream = new ReadableStream({
22
+ start(controller) {
23
+ // Send existing log entries
24
+ for (const entry of task.log) {
25
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', entry })}\n\n`));
26
+ }
27
+
28
+ // Send current status
29
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'status', status: task.status })}\n\n`));
30
+
31
+ // Heartbeat
32
+ heartbeat = setInterval(() => {
33
+ if (!closed) {
34
+ try { controller.enqueue(encoder.encode(': heartbeat\n\n')); } catch { cleanup(); }
35
+ }
36
+ }, 15000);
37
+
38
+ // Listen for new events
39
+ unsubscribe = onTaskEvent((taskId, event, data) => {
40
+ if (taskId !== id || closed) return;
41
+ try {
42
+ if (event === 'log') {
43
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', entry: data })}\n\n`));
44
+ } else if (event === 'status') {
45
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'status', status: data })}\n\n`));
46
+ // Close stream when task is done
47
+ if (data === 'done' || data === 'failed' || data === 'cancelled') {
48
+ // Send final task data
49
+ const finalTask = getTask(id);
50
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'complete', task: finalTask })}\n\n`));
51
+ cleanup();
52
+ controller.close();
53
+ }
54
+ }
55
+ } catch { cleanup(); }
56
+ });
57
+ },
58
+ cancel() {
59
+ cleanup();
60
+ },
61
+ });
62
+
63
+ function cleanup() {
64
+ closed = true;
65
+ if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }
66
+ if (unsubscribe) { unsubscribe(); unsubscribe = null; }
67
+ }
68
+
69
+ return new Response(stream, {
70
+ headers: {
71
+ 'Content-Type': 'text/event-stream',
72
+ 'Cache-Control': 'no-cache, no-transform',
73
+ Connection: 'keep-alive',
74
+ 'X-Accel-Buffering': 'no',
75
+ },
76
+ });
77
+ }