@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,156 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { spawn, execSync, type ChildProcess } from 'node:child_process';
5
+ import { getDataDir, getConfigDir } from '@/lib/dirs';
6
+
7
+ const CONFIG_FILE = join(getDataDir(), 'preview.json');
8
+
9
+ interface PreviewEntry {
10
+ port: number;
11
+ url: string | null;
12
+ status: string;
13
+ label?: string;
14
+ }
15
+
16
+ // Persist state across hot-reloads
17
+ const stateKey = Symbol.for('mw-preview-state');
18
+ const g = globalThis as any;
19
+ if (!g[stateKey]) g[stateKey] = { entries: new Map<number, { process: ChildProcess | null; url: string | null; status: string; label: string }>() };
20
+ const state: { entries: Map<number, { process: ChildProcess | null; url: string | null; status: string; label: string }> } = g[stateKey];
21
+
22
+ function getConfig(): PreviewEntry[] {
23
+ try {
24
+ const data = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
25
+ return Array.isArray(data) ? data : data.port ? [data] : [];
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
31
+ function saveConfig(entries: PreviewEntry[]) {
32
+ const dir = dirname(CONFIG_FILE);
33
+ mkdirSync(dir, { recursive: true });
34
+ writeFileSync(CONFIG_FILE, JSON.stringify(entries, null, 2));
35
+ }
36
+
37
+ function getCloudflaredPath(): string | null {
38
+ const binPath = join(getConfigDir(), 'bin', 'cloudflared');
39
+ if (existsSync(binPath)) return binPath;
40
+ try {
41
+ return execSync('which cloudflared', { encoding: 'utf-8' }).trim();
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ // GET — list all previews
48
+ export async function GET() {
49
+ const entries: PreviewEntry[] = [];
50
+ for (const [port, s] of state.entries) {
51
+ entries.push({ port, url: s.url, status: s.status, label: s.label });
52
+ }
53
+ return NextResponse.json(entries);
54
+ }
55
+
56
+ // POST — start/stop/manage previews
57
+ export async function POST(req: Request) {
58
+ const body = await req.json();
59
+
60
+ // Stop a preview
61
+ if (body.action === 'stop' && body.port) {
62
+ const entry = state.entries.get(body.port);
63
+ if (entry?.process) {
64
+ entry.process.kill('SIGTERM');
65
+ } else {
66
+ // Process ref lost (hot-reload) — kill by port match
67
+ try {
68
+ const pids = execSync(`pgrep -f 'cloudflared tunnel.*localhost:${body.port}'`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
69
+ for (const pid of pids.split('\n').filter(Boolean)) {
70
+ try { process.kill(parseInt(pid), 'SIGTERM'); } catch {}
71
+ }
72
+ } catch {}
73
+ }
74
+ state.entries.delete(body.port);
75
+ syncConfig();
76
+ return NextResponse.json({ ok: true });
77
+ }
78
+
79
+ // Start a new preview
80
+ if (body.action === 'start' && body.port) {
81
+ const port = parseInt(body.port);
82
+ if (!port || port < 1 || port > 65535) {
83
+ return NextResponse.json({ error: 'Invalid port' }, { status: 400 });
84
+ }
85
+
86
+ // Already running?
87
+ const existing = state.entries.get(port);
88
+ if (existing && existing.status === 'running') {
89
+ return NextResponse.json({ port, url: existing.url, status: 'running', label: existing.label });
90
+ }
91
+
92
+ const binPath = getCloudflaredPath();
93
+ if (!binPath) {
94
+ return NextResponse.json({ error: 'cloudflared not installed. Start the main tunnel first.' }, { status: 500 });
95
+ }
96
+
97
+ const label = body.label || `localhost:${port}`;
98
+ state.entries.set(port, { process: null, url: null, status: 'starting', label });
99
+ syncConfig();
100
+
101
+ // Start tunnel
102
+ return new Promise<NextResponse>((resolve) => {
103
+ let resolved = false;
104
+ const child = spawn(binPath, ['tunnel', '--url', `http://localhost:${port}`], {
105
+ stdio: ['ignore', 'pipe', 'pipe'],
106
+ });
107
+
108
+ const entry = state.entries.get(port)!;
109
+ entry.process = child;
110
+
111
+ const handleOutput = (data: Buffer) => {
112
+ const urlMatch = data.toString().match(/(https:\/\/[a-z0-9-]+\.trycloudflare\.com)/);
113
+ if (urlMatch && !entry.url) {
114
+ entry.url = urlMatch[1];
115
+ entry.status = 'running';
116
+ syncConfig();
117
+ if (!resolved) {
118
+ resolved = true;
119
+ resolve(NextResponse.json({ port, url: entry.url, status: 'running', label }));
120
+ }
121
+ }
122
+ };
123
+
124
+ child.stdout?.on('data', handleOutput);
125
+ child.stderr?.on('data', handleOutput);
126
+
127
+ child.on('exit', () => {
128
+ entry.process = null;
129
+ entry.status = 'stopped';
130
+ entry.url = null;
131
+ syncConfig();
132
+ if (!resolved) {
133
+ resolved = true;
134
+ resolve(NextResponse.json({ port, url: null, status: 'stopped', error: 'Tunnel exited' }));
135
+ }
136
+ });
137
+
138
+ setTimeout(() => {
139
+ if (!resolved) {
140
+ resolved = true;
141
+ resolve(NextResponse.json({ port, url: null, status: entry.status, error: 'Timeout' }));
142
+ }
143
+ }, 30000);
144
+ });
145
+ }
146
+
147
+ return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
148
+ }
149
+
150
+ function syncConfig() {
151
+ const entries: PreviewEntry[] = [];
152
+ for (const [port, s] of state.entries) {
153
+ entries.push({ port, url: s.url, status: s.status, label: s.label });
154
+ }
155
+ saveConfig(entries);
156
+ }
@@ -0,0 +1,91 @@
1
+ import { NextResponse } from 'next/server';
2
+ import {
3
+ getBindings,
4
+ addBinding,
5
+ removeBinding,
6
+ updateBinding,
7
+ getRuns,
8
+ deleteRun,
9
+ triggerPipeline,
10
+ getNextRunTime,
11
+ scanAndTriggerIssues,
12
+ resetDedup,
13
+ } from '@/lib/pipeline-scheduler';
14
+ import { listWorkflows } from '@/lib/pipeline';
15
+
16
+ // GET /api/project-pipelines?project=PATH
17
+ export async function GET(req: Request) {
18
+ const { searchParams } = new URL(req.url);
19
+ const projectPath = searchParams.get('project');
20
+ if (!projectPath) return NextResponse.json({ error: 'project required' }, { status: 400 });
21
+
22
+ const bindings = getBindings(projectPath).map(b => ({
23
+ ...b,
24
+ nextRunAt: getNextRunTime(b),
25
+ }));
26
+ const runs = getRuns(projectPath);
27
+ const workflows = listWorkflows().map(w => ({ name: w.name, description: w.description, builtin: w.builtin }));
28
+
29
+ return NextResponse.json({ bindings, runs, workflows });
30
+ }
31
+
32
+ // POST /api/project-pipelines
33
+ export async function POST(req: Request) {
34
+ const body = await req.json();
35
+
36
+ if (body.action === 'add') {
37
+ const { projectPath, projectName, workflowName, config } = body;
38
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
39
+ addBinding(projectPath, projectName || projectPath.split('/').pop(), workflowName, config);
40
+ return NextResponse.json({ ok: true });
41
+ }
42
+
43
+ if (body.action === 'remove') {
44
+ removeBinding(body.projectPath, body.workflowName);
45
+ return NextResponse.json({ ok: true });
46
+ }
47
+
48
+ if (body.action === 'update') {
49
+ updateBinding(body.projectPath, body.workflowName, { enabled: body.enabled, config: body.config });
50
+ return NextResponse.json({ ok: true });
51
+ }
52
+
53
+ if (body.action === 'trigger') {
54
+ const { projectPath, projectName, workflowName, input } = body;
55
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
56
+ try {
57
+ const result = triggerPipeline(projectPath, projectName || projectPath.split('/').pop(), workflowName, input);
58
+ return NextResponse.json({ ok: true, ...result });
59
+ } catch (e: any) {
60
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
61
+ }
62
+ }
63
+
64
+ if (body.action === 'delete-run') {
65
+ deleteRun(body.id);
66
+ return NextResponse.json({ ok: true });
67
+ }
68
+
69
+ if (body.action === 'scan-now') {
70
+ const { projectPath, projectName, workflowName } = body;
71
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
72
+ const bindings = getBindings(projectPath);
73
+ const binding = bindings.find(b => b.workflowName === workflowName);
74
+ if (!binding) return NextResponse.json({ error: 'Binding not found' }, { status: 404 });
75
+ try {
76
+ const result = scanAndTriggerIssues(binding);
77
+ return NextResponse.json({ ok: true, ...result });
78
+ } catch (e: any) {
79
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
80
+ }
81
+ }
82
+
83
+ if (body.action === 'reset-dedup') {
84
+ const { projectPath, workflowName, dedupKey } = body;
85
+ if (!projectPath || !workflowName || !dedupKey) return NextResponse.json({ error: 'projectPath, workflowName, dedupKey required' }, { status: 400 });
86
+ resetDedup(projectPath, workflowName, dedupKey);
87
+ return NextResponse.json({ ok: true });
88
+ }
89
+
90
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
91
+ }
@@ -0,0 +1,61 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getFixedSession, setFixedSession, clearFixedSession, getAllFixedSessions } from '@/lib/project-sessions';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ // GET: get fixed session for a project, or all bindings
7
+ export async function GET(req: Request) {
8
+ const url = new URL(req.url);
9
+ const projectPath = url.searchParams.get('projectPath');
10
+ if (projectPath) {
11
+ // Also ensure mcp.json exists when querying
12
+ ensureMcpConfig(projectPath);
13
+ return NextResponse.json({ projectPath, fixedSessionId: getFixedSession(projectPath) || null });
14
+ }
15
+ return NextResponse.json(getAllFixedSessions());
16
+ }
17
+
18
+ // POST: set fixed session or ensure MCP config
19
+ export async function POST(req: Request) {
20
+ const body = await req.json();
21
+ const { projectPath, fixedSessionId, action } = body;
22
+ if (!projectPath) return NextResponse.json({ error: 'projectPath required' }, { status: 400 });
23
+
24
+ // Ensure MCP config action
25
+ if (action === 'ensure_mcp') {
26
+ ensureMcpConfig(projectPath);
27
+ return NextResponse.json({ ok: true });
28
+ }
29
+
30
+ if (!fixedSessionId) {
31
+ clearFixedSession(projectPath);
32
+ return NextResponse.json({ ok: true, cleared: true });
33
+ }
34
+ setFixedSession(projectPath, fixedSessionId);
35
+ return NextResponse.json({ ok: true, projectPath, fixedSessionId });
36
+ }
37
+
38
+ /** Generate .forge/mcp.json in the project directory with workspace context baked in */
39
+ function ensureMcpConfig(projectPath: string): void {
40
+ try {
41
+ const forgeDir = join(projectPath, '.forge');
42
+ const configPath = join(forgeDir, 'mcp.json');
43
+ const mcpPort = Number(process.env.MCP_PORT) || 8406;
44
+
45
+ // Resolve workspace + primary agent for this project
46
+ let wsParam = '';
47
+ try {
48
+ const { findWorkspaceByProject } = require('@/lib/workspace');
49
+ const ws = findWorkspaceByProject(projectPath);
50
+ if (ws) {
51
+ wsParam = `?workspaceId=${ws.id}`;
52
+ }
53
+ } catch {}
54
+
55
+ const config = { mcpServers: { forge: { type: 'sse', url: `http://localhost:${mcpPort}/sse${wsParam}` } } };
56
+
57
+ // Always rewrite (workspace context may have changed)
58
+ mkdirSync(forgeDir, { recursive: true });
59
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
60
+ } catch {}
61
+ }
@@ -0,0 +1,26 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { scanProjects } from '@/lib/projects';
3
+ import { applyDefaultTemplates, listTemplates } from '@/lib/claude-templates';
4
+
5
+ // Track known projects to detect new ones
6
+ const knownProjects = new Set<string>();
7
+
8
+ export async function GET() {
9
+ const projects = scanProjects();
10
+
11
+ // Auto-apply default templates to newly detected projects
12
+ const hasDefaults = listTemplates().some(t => t.isDefault);
13
+ if (hasDefaults) {
14
+ for (const p of projects) {
15
+ if (!knownProjects.has(p.path)) {
16
+ knownProjects.add(p.path);
17
+ try { applyDefaultTemplates(p.path); } catch {}
18
+ }
19
+ }
20
+ } else {
21
+ // Still track projects even without defaults
22
+ for (const p of projects) knownProjects.add(p.path);
23
+ }
24
+
25
+ return NextResponse.json(projects);
26
+ }
@@ -0,0 +1,64 @@
1
+ import { getSessionManager } from '@/lib/session-manager';
2
+ import { chatStream } from '@/src/core/providers/chat';
3
+ import type { ModelMessage } from 'ai';
4
+
5
+ export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params;
7
+ const { message } = await req.json();
8
+ const manager = getSessionManager();
9
+
10
+ const session = manager.get(id);
11
+ if (!session) {
12
+ return new Response(JSON.stringify({ error: 'Session not found' }), { status: 404 });
13
+ }
14
+
15
+ // Save user message
16
+ manager.addMessage(id, 'user', message, session.provider, session.model);
17
+
18
+ // Get memory-filtered messages
19
+ const memoryMessages = manager.getMemoryMessages(id);
20
+ const coreMessages: ModelMessage[] = memoryMessages.map(m => ({
21
+ role: m.role as 'user' | 'assistant',
22
+ content: m.content,
23
+ }));
24
+
25
+ manager.updateStatus(id, 'running');
26
+
27
+ // Stream response
28
+ const encoder = new TextEncoder();
29
+ const stream = new ReadableStream({
30
+ async start(controller) {
31
+ try {
32
+ const result = await chatStream({
33
+ provider: session.provider,
34
+ model: session.model || undefined,
35
+ systemPrompt: session.systemPrompt,
36
+ messages: coreMessages,
37
+ onToken(token) {
38
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ token })}\n\n`));
39
+ },
40
+ });
41
+
42
+ // Save assistant message
43
+ manager.addMessage(id, 'assistant', result.content, result.provider, result.model);
44
+ manager.recordUsage(id, result);
45
+ manager.updateStatus(id, 'idle');
46
+
47
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true, usage: { input: result.inputTokens, output: result.outputTokens } })}\n\n`));
48
+ controller.close();
49
+ } catch (err: any) {
50
+ manager.updateStatus(id, 'error');
51
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: err.message })}\n\n`));
52
+ controller.close();
53
+ }
54
+ },
55
+ });
56
+
57
+ return new Response(stream, {
58
+ headers: {
59
+ 'Content-Type': 'text/event-stream',
60
+ 'Cache-Control': 'no-cache',
61
+ Connection: 'keep-alive',
62
+ },
63
+ });
64
+ }
@@ -0,0 +1,9 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getSessionManager } from '@/lib/session-manager';
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params;
6
+ const manager = getSessionManager();
7
+ const messages = manager.getMessages(id);
8
+ return NextResponse.json(messages);
9
+ }
@@ -0,0 +1,17 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getSessionManager } from '@/lib/session-manager';
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params;
6
+ const manager = getSessionManager();
7
+ const session = manager.get(id) || manager.getByName(id);
8
+ if (!session) return NextResponse.json({ error: 'Not found' }, { status: 404 });
9
+ return NextResponse.json(session);
10
+ }
11
+
12
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
13
+ const { id } = await params;
14
+ const manager = getSessionManager();
15
+ manager.delete(id);
16
+ return NextResponse.json({ ok: true });
17
+ }
@@ -0,0 +1,20 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getSessionManager } from '@/lib/session-manager';
3
+ import { loadAllTemplates } from '@/src/config';
4
+
5
+ export async function GET() {
6
+ const manager = getSessionManager();
7
+ const sessions = manager.list();
8
+ return NextResponse.json(sessions);
9
+ }
10
+
11
+ export async function POST(req: Request) {
12
+ const body = await req.json();
13
+ const manager = getSessionManager();
14
+ try {
15
+ const session = manager.create(body);
16
+ return NextResponse.json(session);
17
+ } catch (err: any) {
18
+ return NextResponse.json({ error: err.message }, { status: 400 });
19
+ }
20
+ }
@@ -0,0 +1,64 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { loadSettings, loadSettingsMasked, saveSettings, type Settings } from '@/lib/settings';
3
+ import { restartTelegramBot } from '@/lib/init';
4
+ import { SECRET_FIELDS } from '@/lib/crypto';
5
+ import { verifyAdmin } from '@/lib/password';
6
+
7
+ export async function GET() {
8
+ return NextResponse.json(loadSettingsMasked());
9
+ }
10
+
11
+ export async function PUT(req: Request) {
12
+ const body = await req.json();
13
+
14
+ // Handle secret field updates separately
15
+ if (body._secretUpdate) {
16
+ const { field, adminPassword, newValue } = body._secretUpdate as {
17
+ field: string;
18
+ adminPassword: string;
19
+ newValue: string;
20
+ };
21
+
22
+ // Validate field name
23
+ if (!SECRET_FIELDS.includes(field as any)) {
24
+ return NextResponse.json({ ok: false, error: 'Invalid field' }, { status: 400 });
25
+ }
26
+
27
+ // Verify admin password
28
+ if (!verifyAdmin(adminPassword)) {
29
+ return NextResponse.json({ ok: false, error: 'Wrong password' }, { status: 403 });
30
+ }
31
+
32
+ // Update the specific field
33
+ const current = loadSettings();
34
+ (current as any)[field] = newValue;
35
+ saveSettings(current);
36
+
37
+ // Restart Telegram bot if token changed
38
+ if (field === 'telegramBotToken') {
39
+ restartTelegramBot();
40
+ }
41
+
42
+ return NextResponse.json({ ok: true });
43
+ }
44
+
45
+ // Normal settings update — strip masked secrets so we don't overwrite with placeholder
46
+ const settings = loadSettings();
47
+ const updated = body as Settings;
48
+
49
+ for (const field of SECRET_FIELDS) {
50
+ // Keep existing encrypted value if frontend sent masked placeholder
51
+ if (updated[field] === '••••••••' || updated[field] === '') {
52
+ updated[field] = settings[field];
53
+ }
54
+ }
55
+
56
+ // Remove internal fields
57
+ delete (updated as any)._secretStatus;
58
+
59
+ const changed = Object.keys(updated).filter(k => JSON.stringify((updated as any)[k]) !== JSON.stringify((settings as any)[k]) && !['telegramTunnelPassword', 'telegramBotToken'].includes(k));
60
+ if (changed.length > 0) console.log(`[settings] Updated: ${changed.join(', ')}`);
61
+ saveSettings(updated);
62
+ restartTelegramBot();
63
+ return NextResponse.json({ ok: true });
64
+ }