@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,35 @@
1
+ /**
2
+ * Next.js instrumentation — runs once when the server starts.
3
+ * Loads .env.local and prints login password.
4
+ */
5
+ export async function register() {
6
+ // Only run on server, not Edge
7
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
8
+ // Load ~/.forge/.env.local if it exists (works for both pnpm dev and forge-server)
9
+ const { existsSync, readFileSync } = await import('node:fs');
10
+ const { join } = await import('node:path');
11
+ const { getDataDir } = await import('./lib/dirs');
12
+ const dataDir = getDataDir();
13
+ const envFile = join(dataDir, '.env.local');
14
+ if (existsSync(envFile)) {
15
+ for (const line of readFileSync(envFile, 'utf-8').split('\n')) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed || trimmed.startsWith('#')) continue;
18
+ const eq = trimmed.indexOf('=');
19
+ if (eq === -1) continue;
20
+ const key = trimmed.slice(0, eq).trim();
21
+ const val = trimmed.slice(eq + 1).trim();
22
+ if (!process.env[key]) process.env[key] = val;
23
+ }
24
+ }
25
+
26
+ // Print password info
27
+ const { getAdminPassword } = await import('./lib/password');
28
+ const admin = getAdminPassword();
29
+ if (admin) {
30
+ console.log(`[init] Admin password: configured`);
31
+ } else {
32
+ console.log('[init] No admin password set — configure in Settings');
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Claude Code adapter — handles Claude CLI specifics.
3
+ */
4
+
5
+ import { execSync } from 'node:child_process';
6
+ import { realpathSync } from 'node:fs';
7
+ import type { AgentAdapter, AgentConfig, AgentSpawnOptions, AgentSpawnResult } from './types';
8
+
9
+ const CAPABILITIES = {
10
+ supportsResume: true,
11
+ supportsStreamJson: true,
12
+ supportsModel: true,
13
+ supportsSkipPermissions: true,
14
+ hasSessionFiles: true,
15
+ requiresTTY: false,
16
+ };
17
+
18
+ /** Resolve claude binary path (symlink → real .js → node) */
19
+ function resolveClaudePath(claudePath: string): { cmd: string; prefix: string[] } {
20
+ try {
21
+ let resolved = claudePath;
22
+ try {
23
+ const which = execSync(`which ${claudePath}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
24
+ resolved = realpathSync(which);
25
+ } catch {
26
+ resolved = realpathSync(claudePath);
27
+ }
28
+ if (resolved.endsWith('.js') || resolved.endsWith('.mjs')) {
29
+ return { cmd: process.execPath, prefix: [resolved] };
30
+ }
31
+ return { cmd: resolved, prefix: [] };
32
+ } catch {
33
+ return { cmd: process.execPath, prefix: [claudePath] };
34
+ }
35
+ }
36
+
37
+ export function createClaudeAdapter(config: AgentConfig): AgentAdapter {
38
+ return {
39
+ id: 'claude',
40
+ config: { ...config, capabilities: CAPABILITIES },
41
+
42
+ buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult {
43
+ const resolved = resolveClaudePath(config.path);
44
+ const args = [...resolved.prefix, '-p', '--verbose'];
45
+
46
+ if (opts.outputFormat === 'stream-json' || opts.outputFormat === undefined) {
47
+ args.push('--output-format', 'stream-json');
48
+ } else if (opts.outputFormat === 'json') {
49
+ args.push('--output-format', 'json');
50
+ }
51
+
52
+ if (opts.skipPermissions !== false) {
53
+ const flag = config.skipPermissionsFlag || '--dangerously-skip-permissions';
54
+ if (flag) args.push(...flag.split(/\s+/));
55
+ }
56
+
57
+ if (opts.model && opts.model !== 'default') {
58
+ args.push('--model', opts.model);
59
+ }
60
+
61
+ if (opts.conversationId) {
62
+ args.push('--resume', opts.conversationId);
63
+ }
64
+
65
+ if (opts.extraFlags) {
66
+ args.push(...opts.extraFlags);
67
+ }
68
+
69
+ args.push(opts.prompt);
70
+
71
+ return { cmd: resolved.cmd, args };
72
+ },
73
+
74
+ buildTerminalCommand(opts) {
75
+ const flag = config.skipPermissionsFlag || '--dangerously-skip-permissions';
76
+ const skipFlag = opts.skipPermissions && flag ? ` ${flag}` : '';
77
+ if (opts.sessionId) {
78
+ return `cd "${opts.projectPath}" && claude --resume ${opts.sessionId}${skipFlag}\n`;
79
+ }
80
+ const resumeFlag = opts.resume ? ' -c' : '';
81
+ return `cd "${opts.projectPath}" && claude${resumeFlag}${skipFlag}\n`;
82
+ },
83
+ };
84
+ }
85
+
86
+ /** Detect if claude is installed and return config */
87
+ export function detectClaude(customPath?: string): AgentConfig | null {
88
+ const paths = customPath ? [customPath] : ['claude'];
89
+ for (const p of paths) {
90
+ try {
91
+ execSync(`which ${p}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
92
+ return {
93
+ id: 'claude',
94
+ name: 'Claude Code',
95
+ path: p,
96
+ enabled: true,
97
+ type: 'claude-code',
98
+ capabilities: CAPABILITIES,
99
+ skipPermissionsFlag: '--dangerously-skip-permissions',
100
+ };
101
+ } catch {}
102
+ }
103
+ return null;
104
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Generic agent adapter — works with any CLI agent that takes a prompt via args or stdin.
3
+ * Supports: codex, aider, or any custom agent binary.
4
+ */
5
+
6
+ import { execSync } from 'node:child_process';
7
+ import type { AgentAdapter, AgentConfig, AgentSpawnOptions, AgentSpawnResult } from './types';
8
+
9
+ export function createGenericAdapter(config: AgentConfig): AgentAdapter {
10
+ return {
11
+ id: config.id,
12
+ config,
13
+
14
+ buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult {
15
+ const args: string[] = [];
16
+
17
+ // Add configured flags (e.g., ['--message'] for aider)
18
+ if (config.flags) {
19
+ args.push(...config.flags);
20
+ }
21
+
22
+ // Add skip permissions flag if configured
23
+ if (opts.skipPermissions !== false && config.skipPermissionsFlag) {
24
+ args.push(...config.skipPermissionsFlag.split(/\s+/));
25
+ }
26
+
27
+ // Add prompt
28
+ args.push(opts.prompt);
29
+
30
+ if (opts.extraFlags) {
31
+ args.push(...opts.extraFlags);
32
+ }
33
+
34
+ return { cmd: config.path, args };
35
+ },
36
+
37
+ buildTerminalCommand(opts) {
38
+ return `cd "${opts.projectPath}" && ${config.path}\n`;
39
+ },
40
+ };
41
+ }
42
+
43
+ /** Detect known agents */
44
+ export function detectAgent(id: string, name: string, binaryName: string, flags?: string[]): AgentConfig | null {
45
+ try {
46
+ execSync(`which ${binaryName}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
47
+ return {
48
+ id,
49
+ name,
50
+ path: binaryName,
51
+ enabled: true,
52
+ type: 'generic',
53
+ flags,
54
+ capabilities: {
55
+ supportsResume: false,
56
+ supportsStreamJson: false,
57
+ supportsModel: false,
58
+ supportsSkipPermissions: false,
59
+ hasSessionFiles: false,
60
+ requiresTTY: false,
61
+ },
62
+ };
63
+ } catch { return null; }
64
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Agent Registry — manages available agents and provides adapters.
3
+ * Agents coexist (not mutually exclusive). Each entry point can select any agent.
4
+ */
5
+
6
+ import { loadSettings } from '../settings';
7
+ import type { AgentAdapter, AgentConfig, AgentId } from './types';
8
+ import { createClaudeAdapter, detectClaude } from './claude-adapter';
9
+ import { createGenericAdapter, detectAgent } from './generic-adapter';
10
+
11
+ export type { AgentAdapter, AgentConfig, AgentId } from './types';
12
+
13
+ // Module-level cache
14
+ const adapterCache = new Map<AgentId, AgentAdapter>();
15
+
16
+ /** Get all configured agents */
17
+ export function listAgents(): AgentConfig[] {
18
+ const settings = loadSettings();
19
+ const agents: AgentConfig[] = [];
20
+
21
+ // Claude (always check — primary agent)
22
+ const claudeConfig = settings.agents?.claude;
23
+ const claude = detectClaude(claudeConfig?.path || settings.claudePath);
24
+ if (claude) {
25
+ agents.push({ ...claude, enabled: claudeConfig?.enabled !== false, detected: true, skipPermissionsFlag: claudeConfig?.skipPermissionsFlag || '--dangerously-skip-permissions', cliType: 'claude-code' } as any);
26
+ }
27
+
28
+ // Codex
29
+ const codexConfig = settings.agents?.codex;
30
+ const codex = detectAgent('codex', 'OpenAI Codex', codexConfig?.path || 'codex', ['exec']);
31
+ if (codex) {
32
+ codex.capabilities.requiresTTY = false; // exec subcommand is non-interactive
33
+ agents.push({ ...codex, enabled: codexConfig?.enabled !== false, detected: true, skipPermissionsFlag: codexConfig?.skipPermissionsFlag || '--full-auto', cliType: 'codex' } as any);
34
+ }
35
+
36
+ // Aider
37
+ const aiderConfig = settings.agents?.aider;
38
+ const aider = detectAgent('aider', 'Aider', aiderConfig?.path || 'aider', ['--message']);
39
+ if (aider) {
40
+ agents.push({ ...aider, enabled: aiderConfig?.enabled !== false, detected: true, skipPermissionsFlag: aiderConfig?.skipPermissionsFlag || '--yes', cliType: 'aider' } as any);
41
+ }
42
+
43
+ // Custom agents + profiles from settings
44
+ if (settings.agents) {
45
+ for (const [id, cfg] of Object.entries(settings.agents)) {
46
+ if (['claude', 'codex', 'aider'].includes(id)) continue;
47
+
48
+ // API profile — no CLI detection needed
49
+ if (cfg.type === 'api') {
50
+ agents.push({
51
+ id,
52
+ name: cfg.name || id,
53
+ path: '',
54
+ enabled: cfg.enabled !== false,
55
+ type: 'generic' as const,
56
+ capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false },
57
+ isProfile: true,
58
+ backendType: 'api',
59
+ provider: cfg.provider,
60
+ model: cfg.model,
61
+ apiKey: cfg.apiKey,
62
+ } as any);
63
+ continue;
64
+ }
65
+
66
+ // CLI profile (has base) — inherit from base agent
67
+ if (cfg.base) {
68
+ const baseAgent = agents.find(a => a.id === cfg.base);
69
+ agents.push({
70
+ ...(baseAgent || { type: 'generic' as const, capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false } }),
71
+ id,
72
+ name: cfg.name || id,
73
+ path: baseAgent?.path || '',
74
+ enabled: cfg.enabled !== false,
75
+ base: cfg.base,
76
+ isProfile: true,
77
+ backendType: 'cli',
78
+ model: cfg.model || cfg.models?.task,
79
+ skipPermissionsFlag: cfg.skipPermissionsFlag || baseAgent?.skipPermissionsFlag,
80
+ env: cfg.env,
81
+ cliType: cfg.cliType || (baseAgent as any)?.cliType || 'generic',
82
+ } as any);
83
+ continue;
84
+ }
85
+
86
+ // Custom agent (not a profile) — detect binary
87
+ if (!cfg.path) continue;
88
+ const flags = cfg.taskFlags ? cfg.taskFlags.split(/\s+/).filter(Boolean) : cfg.flags;
89
+ const detected = detectAgent(id, cfg.name || id, cfg.path, flags);
90
+ agents.push({
91
+ ...(detected || {
92
+ id, name: cfg.name || id, path: cfg.path, type: 'generic' as const, flags,
93
+ capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: !!cfg.requiresTTY },
94
+ }),
95
+ flags,
96
+ enabled: cfg.enabled !== false,
97
+ detected: !!detected,
98
+ cliType: cfg.cliType,
99
+ } as any);
100
+ }
101
+ }
102
+
103
+ return agents;
104
+ }
105
+
106
+ /** Get the default agent ID */
107
+ export function getDefaultAgentId(): AgentId {
108
+ const settings = loadSettings();
109
+ return settings.defaultAgent || 'claude';
110
+ }
111
+
112
+ /** Get an agent adapter by ID (falls back to default). For profiles, returns base agent's adapter. */
113
+ export function getAgent(id?: AgentId): AgentAdapter {
114
+ const agentId = id || getDefaultAgentId();
115
+
116
+ // Return cached adapter
117
+ if (adapterCache.has(agentId)) return adapterCache.get(agentId)!;
118
+
119
+ const agents = listAgents();
120
+ const config = agents.find(a => a.id === agentId && a.enabled);
121
+
122
+ // Profile with base → get base agent's adapter
123
+ if (config?.base) {
124
+ const baseAdapter = getAgent(config.base);
125
+ // Wrap adapter with profile's model override
126
+ const profileAdapter: AgentAdapter = {
127
+ ...baseAdapter,
128
+ id: agentId,
129
+ config: { ...baseAdapter.config, ...config, id: agentId },
130
+ };
131
+ adapterCache.set(agentId, profileAdapter);
132
+ return profileAdapter;
133
+ }
134
+
135
+ if (!config) {
136
+ // If specifically requested agent not found, only fallback for 'claude' (default)
137
+ if (agentId === 'claude' || agentId === getDefaultAgentId()) {
138
+ const fallback = detectClaude() || {
139
+ id: 'claude', name: 'Claude Code', path: 'claude', enabled: true,
140
+ type: 'claude-code' as const,
141
+ capabilities: { supportsResume: true, supportsStreamJson: true, supportsModel: true, supportsSkipPermissions: true, hasSessionFiles: true, requiresTTY: false },
142
+ };
143
+ const adapter = createClaudeAdapter(fallback);
144
+ adapterCache.set(agentId, adapter);
145
+ return adapter;
146
+ }
147
+ // Non-default agent not found — create generic with the ID as path (will fail if not installed)
148
+ const notFound: AgentConfig = {
149
+ id: agentId, name: agentId, path: agentId, enabled: true, type: 'generic',
150
+ capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false },
151
+ };
152
+ const adapter = createGenericAdapter(notFound);
153
+ adapterCache.set(agentId, adapter);
154
+ return adapter;
155
+ }
156
+
157
+ const adapter = config.type === 'claude-code'
158
+ ? createClaudeAdapter(config)
159
+ : createGenericAdapter(config);
160
+
161
+ adapterCache.set(agentId, adapter);
162
+ return adapter;
163
+ }
164
+
165
+ /** Clear adapter cache (call after settings change) */
166
+ export function clearAgentCache(): void {
167
+ adapterCache.clear();
168
+ }
169
+
170
+ /** Auto-detect all available agents (called on startup) */
171
+ export function autoDetectAgents(): AgentConfig[] {
172
+ const detected: AgentConfig[] = [];
173
+
174
+ const claude = detectClaude();
175
+ if (claude) detected.push(claude);
176
+
177
+ const codex = detectAgent('codex', 'OpenAI Codex', 'codex');
178
+ if (codex) detected.push(codex);
179
+
180
+ const aider = detectAgent('aider', 'Aider', 'aider', ['--message']);
181
+ if (aider) detected.push(aider);
182
+
183
+ if (detected.length > 0) {
184
+ console.log(`[agents] Detected: ${detected.map(a => a.name).join(', ')}`);
185
+ }
186
+
187
+ return detected;
188
+ }
189
+
190
+ /** Resolve terminal launch info for an agent — used by both VibeCoding and Workspace */
191
+ export interface TerminalLaunchInfo {
192
+ cliCmd: string; // actual binary: claude, codex, aider
193
+ cliType: string; // claude-code, codex, aider, generic
194
+ supportsSession: boolean; // has session files to resume
195
+ resumeFlag: string; // -c, --resume, etc.
196
+ env?: Record<string, string>; // profile env vars to export
197
+ model?: string; // profile model override (--model flag)
198
+ }
199
+
200
+ export function resolveTerminalLaunch(agentId?: string): TerminalLaunchInfo {
201
+ const settings = loadSettings();
202
+ const agentCfg = settings.agents?.[agentId || 'claude'] || {};
203
+ // Resolve cliType: own cliType → base agent's cliType → base agent name guessing → agentId name guessing
204
+ const baseId = agentCfg.base;
205
+ const baseCfg = baseId ? (settings.agents?.[baseId] || {}) : {};
206
+ const cliType = agentCfg.cliType || baseCfg.cliType
207
+ || (baseId === 'codex' ? 'codex' : baseId === 'aider' ? 'aider' : undefined)
208
+ || (agentId === 'codex' ? 'codex' : agentId === 'aider' ? 'aider' : 'claude-code');
209
+
210
+ // Determine CLI command and capabilities from cliType
211
+ const cliMap: Record<string, { cmd: string; session: boolean; resume: string }> = {
212
+ 'claude-code': { cmd: 'claude', session: true, resume: '-c' },
213
+ 'codex': { cmd: 'codex', session: false, resume: '' },
214
+ 'aider': { cmd: 'aider', session: false, resume: '' },
215
+ 'generic': { cmd: agentCfg.path || agentId || 'claude', session: false, resume: '' },
216
+ };
217
+ const cli = cliMap[cliType] || cliMap['claude-code'];
218
+
219
+ // Resolve env/model: either from this agent's own profile fields, or from linked profile
220
+ let env: Record<string, string> | undefined;
221
+ let model: string | undefined;
222
+ if (agentCfg.base || agentCfg.env || agentCfg.model || agentCfg.models) {
223
+ // This agent has profile-like config — read env/model directly
224
+ if (agentCfg.env) env = { ...agentCfg.env };
225
+ model = agentCfg.model || agentCfg.models?.terminal;
226
+ if (model === 'default') model = undefined; // 'default' means no override
227
+ } else if (agentCfg.profile) {
228
+ // Agent links to a separate profile — read from that
229
+ const profileCfg = settings.agents?.[agentCfg.profile];
230
+ if (profileCfg) {
231
+ if (profileCfg.env) env = { ...profileCfg.env };
232
+ model = profileCfg.model || profileCfg.models?.terminal;
233
+ if (model === 'default') model = undefined;
234
+ }
235
+ }
236
+
237
+ return {
238
+ cliCmd: cli.cmd,
239
+ cliType,
240
+ supportsSession: cli.session,
241
+ resumeFlag: agentCfg.resumeFlag || cli.resume,
242
+ env,
243
+ model,
244
+ };
245
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Agent types — shared interfaces for multi-agent support.
3
+ */
4
+
5
+ export type AgentId = string; // 'claude' | 'codex' | 'aider' | custom
6
+
7
+ export interface AgentCapabilities {
8
+ supportsResume: boolean; // -c / --resume (continue session)
9
+ supportsStreamJson: boolean; // structured output parsing
10
+ supportsModel: boolean; // --model flag
11
+ supportsSkipPermissions: boolean; // --dangerously-skip-permissions or equivalent
12
+ hasSessionFiles: boolean; // on-disk session files (JSONL etc.)
13
+ requiresTTY: boolean; // needs pseudo-terminal (e.g., codex)
14
+ }
15
+
16
+ export interface AgentConfig {
17
+ id: AgentId;
18
+ name: string; // display name: "Claude Code", "OpenAI Codex", "Aider"
19
+ path: string; // binary path
20
+ enabled: boolean;
21
+ type: 'claude-code' | 'generic'; // adapter type
22
+ flags?: string[]; // extra CLI flags
23
+ capabilities: AgentCapabilities;
24
+ version?: string;
25
+ skipPermissionsFlag?: string; // e.g., "--dangerously-skip-permissions", "--full-auto"
26
+ // Profile fields
27
+ base?: string; // base agent ID — makes this a profile
28
+ isProfile?: boolean; // true if this is a profile (not a base agent)
29
+ backendType?: 'cli' | 'api'; // 'api' for API profiles
30
+ provider?: string; // API provider (anthropic, google, openai, grok)
31
+ model?: string; // model override for profiles
32
+ apiKey?: string; // per-profile API key
33
+ env?: Record<string, string>; // env vars injected on spawn
34
+ cliType?: 'claude-code' | 'codex' | 'aider' | 'generic'; // CLI tool type
35
+ }
36
+
37
+ export interface AgentSpawnOptions {
38
+ projectPath: string;
39
+ prompt: string;
40
+ model?: string;
41
+ conversationId?: string; // for resume
42
+ skipPermissions?: boolean;
43
+ outputFormat?: 'stream-json' | 'json' | 'text';
44
+ extraFlags?: string[];
45
+ }
46
+
47
+ export interface AgentSpawnResult {
48
+ cmd: string;
49
+ args: string[];
50
+ env?: Record<string, string>;
51
+ }
52
+
53
+ export interface AgentAdapter {
54
+ id: AgentId;
55
+ config: AgentConfig;
56
+
57
+ /** Build spawn command + args for non-interactive task execution */
58
+ buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult;
59
+
60
+ /** Build the terminal command string (e.g., "cd /path && claude -c") */
61
+ buildTerminalCommand(opts: {
62
+ projectPath: string;
63
+ resume?: boolean;
64
+ sessionId?: string;
65
+ skipPermissions?: boolean;
66
+ }): string;
67
+
68
+ /** Parse a line of output into normalized events (for stream-json agents) */
69
+ parseOutputLine?(line: string): any[];
70
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Artifact system — structured data passing between delivery agents.
3
+ * Each artifact is a named document (requirements, architecture, test-plan, etc.)
4
+ * stored as a separate JSON file for lazy loading.
5
+ */
6
+
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { getDataDir } from './dirs';
11
+
12
+ export type ArtifactType = 'requirements' | 'architecture' | 'test-plan' | 'code-diff' | 'review-report' | 'custom';
13
+
14
+ export interface Artifact {
15
+ id: string;
16
+ deliveryId: string;
17
+ type: ArtifactType;
18
+ name: string; // e.g., "requirements.md"
19
+ content: string;
20
+ producedBy: string; // phase name or 'user'
21
+ consumedBy: string[]; // phases that consumed this
22
+ createdAt: string;
23
+ }
24
+
25
+ function artifactsDir(deliveryId: string): string {
26
+ const dir = join(getDataDir(), 'deliveries', deliveryId, 'artifacts');
27
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
28
+ return dir;
29
+ }
30
+
31
+ export function createArtifact(deliveryId: string, opts: {
32
+ type: ArtifactType;
33
+ name: string;
34
+ content: string;
35
+ producedBy: string;
36
+ }): Artifact {
37
+ const id = randomUUID().slice(0, 8);
38
+ const artifact: Artifact = {
39
+ id,
40
+ deliveryId,
41
+ type: opts.type,
42
+ name: opts.name,
43
+ content: opts.content,
44
+ producedBy: opts.producedBy,
45
+ consumedBy: [],
46
+ createdAt: new Date().toISOString(),
47
+ };
48
+ writeFileSync(join(artifactsDir(deliveryId), `${id}.json`), JSON.stringify(artifact, null, 2));
49
+ return artifact;
50
+ }
51
+
52
+ export function getArtifact(deliveryId: string, id: string): Artifact | null {
53
+ try {
54
+ return JSON.parse(readFileSync(join(artifactsDir(deliveryId), `${id}.json`), 'utf-8'));
55
+ } catch { return null; }
56
+ }
57
+
58
+ export function listArtifacts(deliveryId: string): Artifact[] {
59
+ const dir = artifactsDir(deliveryId);
60
+ if (!existsSync(dir)) return [];
61
+ return readdirSync(dir)
62
+ .filter(f => f.endsWith('.json'))
63
+ .map(f => {
64
+ try { return JSON.parse(readFileSync(join(dir, f), 'utf-8')) as Artifact; } catch { return null; }
65
+ })
66
+ .filter(Boolean) as Artifact[];
67
+ }
68
+
69
+ export function deleteArtifact(deliveryId: string, id: string): void {
70
+ try { unlinkSync(join(artifactsDir(deliveryId), `${id}.json`)); } catch {}
71
+ }
72
+
73
+ /** Extract artifacts from agent output using ===ARTIFACT:name=== markers */
74
+ export function extractArtifacts(output: string, deliveryId: string, producedBy: string): Artifact[] {
75
+ const artifacts: Artifact[] = [];
76
+ const regex = /===ARTIFACT:([\w.-]+)===\n([\s\S]*?)(?=\n===ARTIFACT:|$)/g;
77
+ let match;
78
+
79
+ while ((match = regex.exec(output)) !== null) {
80
+ const name = match[1];
81
+ const content = match[2].trim();
82
+ if (!content) continue;
83
+
84
+ // Infer type from name
85
+ let type: ArtifactType = 'custom';
86
+ if (name.includes('requirement')) type = 'requirements';
87
+ else if (name.includes('architect') || name.includes('design')) type = 'architecture';
88
+ else if (name.includes('test')) type = 'test-plan';
89
+ else if (name.includes('review')) type = 'review-report';
90
+ else if (name.includes('diff')) type = 'code-diff';
91
+
92
+ artifacts.push(createArtifact(deliveryId, { type, name, content, producedBy }));
93
+ }
94
+
95
+ // Fallback: if no markers found, don't create any artifact — let the engine decide
96
+ return artifacts;
97
+ }
98
+
99
+ /** Write artifact content to the project directory */
100
+ export function writeArtifactToProject(artifact: Artifact, projectPath: string, subDir = '.forge-delivery'): string {
101
+ const dir = join(projectPath, subDir);
102
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
103
+ const filePath = join(dir, artifact.name);
104
+ writeFileSync(filePath, artifact.content, 'utf-8');
105
+ return filePath;
106
+ }
@@ -0,0 +1,62 @@
1
+ import NextAuth from 'next-auth';
2
+ import Google from 'next-auth/providers/google';
3
+ import Credentials from 'next-auth/providers/credentials';
4
+ import { randomBytes } from 'node:crypto';
5
+
6
+ // Ensure AUTH_SECRET exists before NextAuth initializes
7
+ if (!process.env.AUTH_SECRET) {
8
+ process.env.AUTH_SECRET = randomBytes(32).toString('hex');
9
+ }
10
+
11
+ export const { handlers, signIn, signOut, auth } = NextAuth({
12
+ trustHost: true,
13
+ logger: {
14
+ error: () => {}, // Suppress noisy CredentialsSignin stack traces
15
+ },
16
+ providers: [
17
+ // Google OAuth — for production use
18
+ Google({
19
+ clientId: process.env.GOOGLE_CLIENT_ID,
20
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
21
+ }),
22
+ // Local: admin password only
23
+ // Remote (tunnel): admin password + session code (2FA)
24
+ Credentials({
25
+ name: 'Local',
26
+ credentials: {
27
+ password: { label: 'Password', type: 'password' },
28
+ sessionCode: { label: 'Session Code', type: 'text' },
29
+ isRemote: { label: 'Remote', type: 'text' },
30
+ },
31
+ async authorize(credentials) {
32
+ const { verifyLogin } = await import('./password');
33
+ const password = (credentials?.password ?? '') as string;
34
+ const sessionCode = (credentials?.sessionCode ?? '') as string;
35
+ const isRemote = String(credentials?.isRemote) === 'true';
36
+
37
+ if (verifyLogin(password, sessionCode, isRemote)) {
38
+ const { loadSettings } = await import('./settings');
39
+ const settings = loadSettings();
40
+ console.log(`[auth] Login success (${isRemote ? 'remote' : 'local'})`);
41
+ return { id: 'local', name: settings.displayName || 'Forge', email: settings.displayEmail || 'local@forge' };
42
+ }
43
+ console.warn(`[auth] Login failed (${isRemote ? 'remote' : 'local'})`);
44
+ return null;
45
+ },
46
+ }),
47
+ ],
48
+ pages: {
49
+ signIn: '/login',
50
+ },
51
+ callbacks: {
52
+ authorized({ auth }) {
53
+ return !!auth;
54
+ },
55
+ redirect({ url, baseUrl }) {
56
+ if (url.startsWith(baseUrl)) return url;
57
+ if (url.startsWith('/')) return `${baseUrl}${url}`;
58
+ if (url.includes('.trycloudflare.com')) return url;
59
+ return baseUrl;
60
+ },
61
+ },
62
+ });