@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,46 @@
1
+ #!/usr/bin/env bash
2
+ # Validator for text utility task.
3
+ # Runs in harness_test project root. Exits 0 = pass, non-zero = fail.
4
+ set -e
5
+
6
+ PROJECT_ROOT="${1:-/Users/zliu/IdeaProjects/harness_test}"
7
+ cd "$PROJECT_ROOT/src" || { echo "FAIL: src/ directory not found"; exit 1; }
8
+
9
+ # 1. Check files exist
10
+ [ -f utils/text.js ] || { echo "FAIL: utils/text.js missing"; exit 1; }
11
+ [ -f utils/text.test.js ] || { echo "FAIL: utils/text.test.js missing"; exit 1; }
12
+
13
+ # 2. Check exports
14
+ grep -q "export.*capitalize" utils/text.js || { echo "FAIL: capitalize not exported"; exit 1; }
15
+ grep -q "export.*reverseWords" utils/text.js || { echo "FAIL: reverseWords not exported"; exit 1; }
16
+
17
+ # 3. Run tests
18
+ node --test utils/text.test.js 2>&1 | tee /tmp/text-test-output.txt
19
+ TEST_EXIT=${PIPESTATUS[0]}
20
+ if [ "$TEST_EXIT" != "0" ]; then
21
+ echo "FAIL: tests failed (exit=$TEST_EXIT)"
22
+ exit 1
23
+ fi
24
+
25
+ # 4. Additional smoke test — behavior verification independent of agent's tests
26
+ node -e "
27
+ import('./utils/text.js').then(m => {
28
+ const assert = require('node:assert/strict');
29
+ // capitalize
30
+ assert.equal(m.capitalize('hello'), 'Hello', 'capitalize basic');
31
+ assert.equal(m.capitalize('a'), 'A', 'capitalize single char');
32
+ try { m.capitalize(''); assert.fail('expected throw on empty'); } catch (e) { assert.ok(e instanceof TypeError); }
33
+ try { m.capitalize(null); assert.fail('expected throw on null'); } catch (e) { assert.ok(e instanceof TypeError); }
34
+ try { m.capitalize(123); assert.fail('expected throw on number'); } catch (e) { assert.ok(e instanceof TypeError); }
35
+ // reverseWords
36
+ assert.equal(m.reverseWords('hello world'), 'world hello');
37
+ assert.equal(m.reverseWords(' a b c '), 'c b a');
38
+ assert.equal(m.reverseWords(''), '');
39
+ assert.equal(m.reverseWords('single'), 'single');
40
+ try { m.reverseWords(null); assert.fail('expected throw'); } catch (e) { assert.ok(e instanceof TypeError); }
41
+ console.log('SMOKE_TEST_PASSED');
42
+ }).catch(err => { console.error('SMOKE_TEST_FAILED:', err.message); process.exit(1); });
43
+ " || { echo "FAIL: smoke test failed"; exit 1; }
44
+
45
+ echo "PASS"
46
+ exit 0
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # Create a basic user list module without pagination.
3
+ set -e
4
+ PROJECT="${1:-/Users/zliu/IdeaProjects/harness_test}"
5
+ mkdir -p "$PROJECT/src/api"
6
+
7
+ cat > "$PROJECT/src/api/users.js" <<'EOF'
8
+ const USERS = Array.from({ length: 127 }, (_, i) => ({
9
+ id: i + 1,
10
+ name: `User ${i + 1}`,
11
+ email: `user${i + 1}@example.com`,
12
+ }));
13
+
14
+ export function listUsers() {
15
+ return USERS;
16
+ }
17
+ EOF
18
+
19
+ echo "Setup complete: created src/api/users.js with 127 users and a listUsers() function."
@@ -0,0 +1,48 @@
1
+ # Task: Add Pagination to User List
2
+
3
+ The file `src/api/users.js` currently has a `listUsers()` function that returns all users. Add pagination support.
4
+
5
+ ## Requirements
6
+
7
+ Replace `listUsers()` (or add a new function) with a paginated version:
8
+
9
+ ```
10
+ listUsers({ page = 1, pageSize = 20 } = {})
11
+ ```
12
+
13
+ **Return format**:
14
+ ```js
15
+ {
16
+ items: [...], // users on the current page
17
+ total: 127, // total number of users
18
+ page: 1, // current page (1-indexed)
19
+ pageSize: 20, // page size (after validation)
20
+ totalPages: 7, // Math.ceil(total / pageSize)
21
+ hasNext: true, // true if more pages exist
22
+ hasPrev: false // true if page > 1
23
+ }
24
+ ```
25
+
26
+ ## Validation Rules
27
+
28
+ - `page` must be integer ≥ 1. If invalid (not a number, < 1, NaN, float), throw `RangeError`.
29
+ - `pageSize` must be integer in [1, 100]. If invalid, throw `RangeError`.
30
+ - If `page` exceeds available pages, return empty `items` array but still return correct `total`, `page`, `pageSize`, `totalPages`, `hasNext: false`, `hasPrev: true`.
31
+
32
+ ## Test File
33
+
34
+ Also create `src/api/users.test.js` using `node:test` and `node:assert/strict` covering:
35
+ - Default params return page 1 with 20 items
36
+ - Page 2 returns items 21-40
37
+ - Last page (page 7) returns items 121-127
38
+ - Page 8 returns empty items but correct metadata
39
+ - Custom pageSize (e.g., 50)
40
+ - Invalid page (0, -1, 'abc', 1.5, NaN) throws RangeError
41
+ - Invalid pageSize (0, 101, 'abc', 1.5) throws RangeError
42
+
43
+ ## Constraints
44
+
45
+ - Keep ES module syntax
46
+ - No external deps
47
+ - Preserve the existing USERS array
48
+ - Tests must pass via: `cd src && node --test api/users.test.js`
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ PROJECT="${1:-/Users/zliu/IdeaProjects/harness_test}"
4
+ cd "$PROJECT/src"
5
+
6
+ [ -f api/users.js ] || { echo "FAIL: api/users.js missing"; exit 1; }
7
+ [ -f api/users.test.js ] || { echo "FAIL: api/users.test.js missing"; exit 1; }
8
+ grep -q "export function listUsers\|export const listUsers\|export { listUsers" api/users.js || { echo "FAIL: listUsers not exported"; exit 1; }
9
+
10
+ # Run agent's tests
11
+ node --test api/users.test.js 2>&1 | tee /tmp/paginate-test-output.txt
12
+ TEST_EXIT=${PIPESTATUS[0]}
13
+ [ "$TEST_EXIT" = "0" ] || { echo "FAIL: agent tests failed"; exit 1; }
14
+
15
+ # Independent smoke test
16
+ node -e "
17
+ import('./api/users.js').then(m => {
18
+ const assert = require('node:assert/strict');
19
+ // Default: page 1, 20 items
20
+ let r = m.listUsers();
21
+ assert.equal(r.items.length, 20, 'default pageSize 20');
22
+ assert.equal(r.items[0].id, 1);
23
+ assert.equal(r.total, 127);
24
+ assert.equal(r.page, 1);
25
+ assert.equal(r.pageSize, 20);
26
+ assert.equal(r.totalPages, 7);
27
+ assert.equal(r.hasNext, true);
28
+ assert.equal(r.hasPrev, false);
29
+
30
+ // Page 2
31
+ r = m.listUsers({ page: 2 });
32
+ assert.equal(r.items[0].id, 21);
33
+ assert.equal(r.items.length, 20);
34
+ assert.equal(r.hasPrev, true);
35
+
36
+ // Last page (127 / 20 = 6.35 → 7 pages; page 7 has 7 items)
37
+ r = m.listUsers({ page: 7 });
38
+ assert.equal(r.items.length, 7, 'last page has 7 items');
39
+ assert.equal(r.items[0].id, 121);
40
+ assert.equal(r.hasNext, false);
41
+
42
+ // Page 8 (beyond) — empty but correct metadata
43
+ r = m.listUsers({ page: 8 });
44
+ assert.equal(r.items.length, 0, 'page beyond: empty items');
45
+ assert.equal(r.total, 127);
46
+ assert.equal(r.totalPages, 7);
47
+ assert.equal(r.hasNext, false);
48
+
49
+ // Custom pageSize
50
+ r = m.listUsers({ page: 1, pageSize: 50 });
51
+ assert.equal(r.items.length, 50);
52
+ assert.equal(r.totalPages, 3);
53
+
54
+ // Invalid page
55
+ for (const p of [0, -1, 'abc', 1.5, NaN]) {
56
+ try { m.listUsers({ page: p }); assert.fail('expected RangeError for page=' + p); }
57
+ catch (e) { assert.ok(e instanceof RangeError, 'page=' + p + ' should throw RangeError, got: ' + e.constructor.name); }
58
+ }
59
+ // Invalid pageSize
60
+ for (const ps of [0, 101, 'abc', 1.5]) {
61
+ try { m.listUsers({ page: 1, pageSize: ps }); assert.fail('expected RangeError for pageSize=' + ps); }
62
+ catch (e) { assert.ok(e instanceof RangeError, 'pageSize=' + ps + ' should throw RangeError'); }
63
+ }
64
+ console.log('SMOKE_TEST_PASSED');
65
+ }).catch(err => { console.error('SMOKE_TEST_FAILED:', err.message); process.exit(1); });
66
+ " || { echo "FAIL: smoke test failed"; exit 1; }
67
+
68
+ echo "PASS"
69
+ exit 0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # Create a date range calculator with 2 bugs.
3
+ set -e
4
+ PROJECT="${1:-/Users/zliu/IdeaProjects/harness_test}"
5
+ mkdir -p "$PROJECT/src/lib" "$PROJECT/src/lib/__tests__"
6
+
7
+ cat > "$PROJECT/src/lib/dateRange.js" <<'EOF'
8
+ // Compute the inclusive number of days between two YYYY-MM-DD dates.
9
+ // Returns a positive integer. If end is before start, throws RangeError.
10
+ export function daysBetween(startStr, endStr) {
11
+ if (typeof startStr !== 'string' || typeof endStr !== 'string') {
12
+ throw new TypeError('daysBetween expects two YYYY-MM-DD strings');
13
+ }
14
+ const start = new Date(startStr);
15
+ const end = new Date(endStr);
16
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
17
+ throw new TypeError('invalid date format');
18
+ }
19
+ if (end < start) throw new RangeError('end before start');
20
+ // BUG: missing +1 to be inclusive of both endpoints
21
+ return Math.floor((end - start) / (1000 * 60 * 60 * 24));
22
+ }
23
+
24
+ // Return array of YYYY-MM-DD strings from start to end (inclusive).
25
+ export function dateRange(startStr, endStr) {
26
+ const days = daysBetween(startStr, endStr);
27
+ const result = [];
28
+ const current = new Date(startStr);
29
+ // BUG: loop condition uses < instead of <=, excluding final day
30
+ for (let i = 0; i < days; i++) {
31
+ result.push(current.toISOString().slice(0, 10));
32
+ current.setDate(current.getDate() + 1);
33
+ }
34
+ return result;
35
+ }
36
+ EOF
37
+
38
+ cat > "$PROJECT/src/lib/__tests__/dateRange.test.js" <<'EOF'
39
+ import { test } from 'node:test';
40
+ import assert from 'node:assert/strict';
41
+ import { daysBetween, dateRange } from '../dateRange.js';
42
+
43
+ test('daysBetween: same day returns 1', () => {
44
+ assert.equal(daysBetween('2026-01-01', '2026-01-01'), 1);
45
+ });
46
+
47
+ test('daysBetween: one day apart returns 2', () => {
48
+ assert.equal(daysBetween('2026-01-01', '2026-01-02'), 2);
49
+ });
50
+
51
+ test('daysBetween: one week', () => {
52
+ assert.equal(daysBetween('2026-01-01', '2026-01-07'), 7);
53
+ });
54
+
55
+ test('daysBetween: end before start throws RangeError', () => {
56
+ assert.throws(() => daysBetween('2026-01-05', '2026-01-01'), RangeError);
57
+ });
58
+
59
+ test('daysBetween: non-string throws TypeError', () => {
60
+ assert.throws(() => daysBetween(20260101, '2026-01-02'), TypeError);
61
+ });
62
+
63
+ test('dateRange: single day returns array with one date', () => {
64
+ assert.deepEqual(dateRange('2026-01-01', '2026-01-01'), ['2026-01-01']);
65
+ });
66
+
67
+ test('dateRange: three days', () => {
68
+ assert.deepEqual(
69
+ dateRange('2026-01-01', '2026-01-03'),
70
+ ['2026-01-01', '2026-01-02', '2026-01-03']
71
+ );
72
+ });
73
+
74
+ test('dateRange: includes both endpoints', () => {
75
+ const r = dateRange('2026-03-30', '2026-04-02');
76
+ assert.equal(r.length, 4);
77
+ assert.equal(r[0], '2026-03-30');
78
+ assert.equal(r[r.length - 1], '2026-04-02');
79
+ });
80
+ EOF
81
+
82
+ echo "Setup complete: created src/lib/dateRange.js (with 2 bugs) and tests that currently fail."
@@ -0,0 +1,30 @@
1
+ # Task: Fix Bugs in dateRange Module
2
+
3
+ The file `src/lib/dateRange.js` has 2 bugs. The existing test file `src/lib/__tests__/dateRange.test.js` describes the expected behavior.
4
+
5
+ ## Your job
6
+
7
+ 1. Run the existing tests — several will fail. Identify what's wrong.
8
+ 2. Fix both bugs in `src/lib/dateRange.js`.
9
+ 3. Do NOT modify the test file. The tests correctly express the expected behavior.
10
+ 4. Do NOT change the function signatures or add new functions.
11
+ 5. After fixing, all tests must pass.
12
+
13
+ ## Verify
14
+
15
+ ```bash
16
+ cd src && node --test lib/__tests__/dateRange.test.js
17
+ ```
18
+
19
+ All tests should pass.
20
+
21
+ ## Hints
22
+
23
+ - `daysBetween('2026-01-01', '2026-01-01')` should return `1` (inclusive count)
24
+ - `dateRange('2026-01-01', '2026-01-03')` should return all 3 days including both endpoints
25
+
26
+ ## Constraints
27
+
28
+ - Minimal diff — fix only what's broken
29
+ - Keep the functions pure
30
+ - No new dependencies
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ PROJECT="${1:-/Users/zliu/IdeaProjects/harness_test}"
4
+ cd "$PROJECT/src"
5
+
6
+ [ -f lib/dateRange.js ] || { echo "FAIL: lib/dateRange.js missing (agent deleted it?)"; exit 1; }
7
+ [ -f lib/__tests__/dateRange.test.js ] || { echo "FAIL: test file missing (agent deleted it?)"; exit 1; }
8
+
9
+ # Run the existing (unmodified) tests
10
+ node --test lib/__tests__/dateRange.test.js 2>&1 | tee /tmp/bugfix-test-output.txt
11
+ TEST_EXIT=${PIPESTATUS[0]}
12
+ [ "$TEST_EXIT" = "0" ] || { echo "FAIL: tests still failing after fix"; exit 1; }
13
+
14
+ # Extra smoke: verify functions exist and behave
15
+ node -e "
16
+ import('./lib/dateRange.js').then(m => {
17
+ const assert = require('node:assert/strict');
18
+ assert.equal(m.daysBetween('2026-01-01', '2026-01-01'), 1);
19
+ assert.equal(m.daysBetween('2026-01-01', '2026-01-10'), 10);
20
+ const r = m.dateRange('2026-01-01', '2026-01-05');
21
+ assert.equal(r.length, 5, 'should include both endpoints');
22
+ assert.equal(r[0], '2026-01-01');
23
+ assert.equal(r[4], '2026-01-05');
24
+ console.log('SMOKE_TEST_PASSED');
25
+ }).catch(err => { console.error('SMOKE_TEST_FAILED:', err.message); process.exit(1); });
26
+ " || { echo "FAIL: smoke test failed"; exit 1; }
27
+
28
+ echo "PASS"
29
+ exit 0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Verification script — compares direct JSONL scanning with DB scanner results.
3
+ * Run: npx tsx scripts/verify-usage.ts
4
+ */
5
+
6
+ import { readdirSync, readFileSync, statSync } from 'fs';
7
+ import { join, basename } from 'path';
8
+ import { homedir } from 'os';
9
+
10
+ const CLAUDE_DIR = join(homedir(), '.claude', 'projects');
11
+
12
+ const PRICING: Record<string, { input: number; output: number }> = {
13
+ 'claude-opus-4': { input: 15, output: 75 },
14
+ 'claude-sonnet-4': { input: 3, output: 15 },
15
+ 'claude-haiku-4': { input: 0.80, output: 4 },
16
+ 'default': { input: 3, output: 15 },
17
+ };
18
+
19
+ function getModelFamily(model: string): string {
20
+ if (!model) return 'unknown';
21
+ if (model.includes('opus')) return 'claude-opus-4';
22
+ if (model.includes('haiku')) return 'claude-haiku-4';
23
+ if (model.includes('sonnet')) return 'claude-sonnet-4';
24
+ return 'unknown';
25
+ }
26
+
27
+ function calcCost(family: string, input: number, output: number, cacheRead: number, cacheCreate: number): number {
28
+ const p = PRICING[family] || PRICING['default'];
29
+ return (
30
+ (input * p.input / 1_000_000) +
31
+ (output * p.output / 1_000_000) +
32
+ (cacheRead * p.input * 0.1 / 1_000_000) +
33
+ (cacheCreate * p.input * 0.25 / 1_000_000)
34
+ );
35
+ }
36
+
37
+ interface ProjectStats {
38
+ input: number; output: number; cost: number; sessions: number; messages: number;
39
+ cacheRead: number; cacheCreate: number;
40
+ }
41
+
42
+ interface ModelStats {
43
+ input: number; output: number; cost: number; messages: number;
44
+ }
45
+
46
+ interface DayStats {
47
+ input: number; output: number; cost: number;
48
+ }
49
+
50
+ const byProject: Record<string, ProjectStats> = {};
51
+ const byModel: Record<string, ModelStats> = {};
52
+ const byDay: Record<string, DayStats> = {};
53
+ let totalInput = 0, totalOutput = 0, totalCost = 0, totalSessions = 0, totalMessages = 0;
54
+
55
+ console.log('Scanning JSONL files...\n');
56
+
57
+ const projectDirs = readdirSync(CLAUDE_DIR);
58
+ let fileCount = 0;
59
+
60
+ for (const projDir of projectDirs) {
61
+ const projPath = join(CLAUDE_DIR, projDir);
62
+ try { if (!statSync(projPath).isDirectory()) continue; } catch { continue; }
63
+
64
+ const projectName = projDir.replace(/^-/, '/').replace(/-/g, '/').split('/').pop() || projDir;
65
+ const files = readdirSync(projPath).filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
66
+
67
+ for (const file of files) {
68
+ const filePath = join(projPath, file);
69
+ fileCount++;
70
+ let sessionInput = 0, sessionOutput = 0, sessionCost = 0, sessionMsgs = 0;
71
+
72
+ try {
73
+ const content = readFileSync(filePath, 'utf-8');
74
+ for (const line of content.split('\n')) {
75
+ if (!line.trim()) continue;
76
+ try {
77
+ const obj = JSON.parse(line);
78
+ if (obj.type === 'assistant' && obj.message?.usage) {
79
+ const u = obj.message.usage;
80
+ const model = obj.message.model || '';
81
+ const family = getModelFamily(model);
82
+ const input = u.input_tokens || 0;
83
+ const output = u.output_tokens || 0;
84
+ const cacheRead = u.cache_read_input_tokens || 0;
85
+ const cacheCreate = u.cache_creation_input_tokens || 0;
86
+ const cost = calcCost(family, input, output, cacheRead, cacheCreate);
87
+
88
+ sessionInput += input;
89
+ sessionOutput += output;
90
+ sessionCost += cost;
91
+ sessionMsgs++;
92
+
93
+ if (!byModel[family]) byModel[family] = { input: 0, output: 0, cost: 0, messages: 0 };
94
+ byModel[family].input += input;
95
+ byModel[family].output += output;
96
+ byModel[family].cost += cost;
97
+ byModel[family].messages++;
98
+
99
+ const day = (obj.timestamp || '').slice(0, 10) || 'unknown';
100
+ if (!byDay[day]) byDay[day] = { input: 0, output: 0, cost: 0 };
101
+ byDay[day].input += input;
102
+ byDay[day].output += output;
103
+ byDay[day].cost += cost;
104
+ }
105
+ } catch {}
106
+ }
107
+ } catch { continue; }
108
+
109
+ if (sessionMsgs > 0) {
110
+ totalSessions++;
111
+ totalMessages += sessionMsgs;
112
+ totalInput += sessionInput;
113
+ totalOutput += sessionOutput;
114
+ totalCost += sessionCost;
115
+
116
+ if (!byProject[projectName]) byProject[projectName] = { input: 0, output: 0, cost: 0, sessions: 0, messages: 0, cacheRead: 0, cacheCreate: 0 };
117
+ byProject[projectName].input += sessionInput;
118
+ byProject[projectName].output += sessionOutput;
119
+ byProject[projectName].cost += sessionCost;
120
+ byProject[projectName].sessions++;
121
+ byProject[projectName].messages += sessionMsgs;
122
+ }
123
+ }
124
+ }
125
+
126
+ // Now run the DB scanner and compare
127
+ console.log('Running DB scanner...\n');
128
+
129
+ // Set up environment for the scanner
130
+ process.env.FORGE_DATA_DIR = process.env.FORGE_DATA_DIR || join(homedir(), '.forge', 'data');
131
+
132
+ // Dynamic import to use the actual scanner
133
+ const { scanUsage, queryUsage } = await import('../lib/usage-scanner');
134
+
135
+ const scanResult = scanUsage();
136
+ console.log(`Scan result: ${scanResult.scanned} files scanned, ${scanResult.updated} updated, ${scanResult.errors} errors\n`);
137
+
138
+ const dbData = queryUsage({});
139
+
140
+ // Compare
141
+ console.log('=== COMPARISON ===\n');
142
+
143
+ console.log('TOTAL:');
144
+ console.log(` Direct: ${(totalInput/1000).toFixed(0)}K in, ${(totalOutput/1000).toFixed(0)}K out, $${totalCost.toFixed(2)}, ${totalSessions} sessions, ${totalMessages} msgs`);
145
+ console.log(` DB: ${(dbData.total.input/1000).toFixed(0)}K in, ${(dbData.total.output/1000).toFixed(0)}K out, $${dbData.total.cost.toFixed(2)}, ${dbData.total.sessions} sessions, ${dbData.total.messages} msgs`);
146
+
147
+ const costDiff = Math.abs(totalCost - dbData.total.cost);
148
+ const costMatch = costDiff < 0.1;
149
+ console.log(` Match: ${costMatch ? '✅' : '❌'} (diff: $${costDiff.toFixed(2)})\n`);
150
+
151
+ console.log('BY MODEL:');
152
+ for (const [model, d] of Object.entries(byModel).sort((a, b) => b[1].cost - a[1].cost)) {
153
+ const dbModel = dbData.byModel.find(m => m.model === model);
154
+ const dbCost = dbModel?.cost || 0;
155
+ const match = Math.abs(d.cost - dbCost) < 0.1;
156
+ console.log(` ${model.padEnd(20)} Direct: $${d.cost.toFixed(2).padStart(8)} DB: $${dbCost.toFixed(2).padStart(8)} ${match ? '✅' : '❌'}`);
157
+ }
158
+
159
+ console.log('\nBY PROJECT (top 10):');
160
+ const sortedProjects = Object.entries(byProject).sort((a, b) => b[1].cost - a[1].cost).slice(0, 10);
161
+ for (const [name, d] of sortedProjects) {
162
+ const dbProj = dbData.byProject.find(p => p.name === name);
163
+ const dbCost = dbProj?.cost || 0;
164
+ const match = Math.abs(d.cost - dbCost) < 0.1;
165
+ console.log(` ${name.padEnd(25)} Direct: $${d.cost.toFixed(2).padStart(8)} DB: $${dbCost.toFixed(2).padStart(8)} ${match ? '✅' : '❌'}`);
166
+ }
167
+
168
+ console.log('\nBY DAY (last 7):');
169
+ const sortedDays = Object.entries(byDay).filter(([d]) => d !== 'unknown').sort((a, b) => b[0].localeCompare(a[0])).slice(0, 7);
170
+ for (const [day, d] of sortedDays) {
171
+ const dbDay = dbData.byDay.find(dd => dd.date === day);
172
+ const dbCost = dbDay?.cost || 0;
173
+ const match = Math.abs(d.cost - dbCost) < 0.1;
174
+ console.log(` ${day} Direct: $${d.cost.toFixed(2).padStart(8)} DB: $${dbCost.toFixed(2).padStart(8)} ${match ? '✅' : '❌'}`);
175
+ }
176
+
177
+ console.log(`\nFiles scanned: ${fileCount}`);
178
+ console.log('');
@@ -0,0 +1,129 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import YAML from 'yaml';
4
+ import type { AppConfig, ProviderName, SessionTemplate } from '@/src/types';
5
+ import { getConfigDir as _getConfigDir, getDataDir as _getDataDir } from '@/lib/dirs';
6
+
7
+ const CONFIG_DIR = _getConfigDir();
8
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');
9
+ const TEMPLATES_DIR = join(CONFIG_DIR, 'templates');
10
+
11
+ export function getConfigDir(): string {
12
+ return CONFIG_DIR;
13
+ }
14
+
15
+ export function getDataDir(): string {
16
+ return _getDataDir();
17
+ }
18
+
19
+ export function getDbPath(): string {
20
+ return join(getDataDir(), 'workflow.db');
21
+ }
22
+
23
+ export function ensureDirs() {
24
+ for (const dir of [CONFIG_DIR, getDataDir()]) {
25
+ if (!existsSync(dir)) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ }
29
+ }
30
+
31
+ export function loadConfig(): AppConfig {
32
+ ensureDirs();
33
+
34
+ if (!existsSync(CONFIG_FILE)) {
35
+ // Don't auto-create config.yaml — return defaults in memory only
36
+ return getDefaultConfig();
37
+ }
38
+
39
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
40
+ return YAML.parse(raw) as AppConfig;
41
+ }
42
+
43
+ export function saveConfig(config: AppConfig) {
44
+ ensureDirs();
45
+ writeFileSync(CONFIG_FILE, YAML.stringify(config), 'utf-8');
46
+ }
47
+
48
+ export function loadTemplate(templateId: string): SessionTemplate | null {
49
+ const filePath = join(TEMPLATES_DIR, `${templateId}.yaml`);
50
+ if (!existsSync(filePath)) return null;
51
+ const raw = readFileSync(filePath, 'utf-8');
52
+ return YAML.parse(raw) as SessionTemplate;
53
+ }
54
+
55
+ export function loadAllTemplates(): SessionTemplate[] {
56
+ ensureDirs();
57
+ const files = readdirSync(TEMPLATES_DIR).filter(f => f.endsWith('.yaml'));
58
+ return files.map(f => {
59
+ const raw = readFileSync(join(TEMPLATES_DIR, f), 'utf-8');
60
+ return YAML.parse(raw) as SessionTemplate;
61
+ });
62
+ }
63
+
64
+ export function saveTemplate(template: SessionTemplate) {
65
+ ensureDirs();
66
+ const filePath = join(TEMPLATES_DIR, `${template.id}.yaml`);
67
+ writeFileSync(filePath, YAML.stringify(template), 'utf-8');
68
+ }
69
+
70
+ function getDefaultConfig(): AppConfig {
71
+ return {
72
+ dataDir: getDataDir(),
73
+ providers: {
74
+ anthropic: {
75
+ name: 'anthropic',
76
+ displayName: 'Claude',
77
+ defaultModel: 'claude-sonnet-4-6',
78
+ models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
79
+ enabled: true,
80
+ },
81
+ google: {
82
+ name: 'google',
83
+ displayName: 'Gemini',
84
+ defaultModel: 'gemini-2.0-flash',
85
+ models: ['gemini-2.5-pro', 'gemini-2.0-flash', 'gemini-2.0-flash-lite'],
86
+ enabled: true,
87
+ },
88
+ openai: {
89
+ name: 'openai',
90
+ displayName: 'OpenAI',
91
+ defaultModel: 'gpt-4o-mini',
92
+ models: ['gpt-4o', 'gpt-4o-mini', 'o3-mini'],
93
+ enabled: false,
94
+ },
95
+ grok: {
96
+ name: 'grok',
97
+ displayName: 'Grok',
98
+ defaultModel: 'grok-3-mini-fast',
99
+ models: ['grok-3', 'grok-3-mini-fast'],
100
+ enabled: false,
101
+ },
102
+ },
103
+ server: {
104
+ host: '0.0.0.0',
105
+ port: 8403,
106
+ },
107
+ };
108
+ }
109
+
110
+ export function getProviderApiKey(provider: ProviderName, profileApiKey?: string): string | undefined {
111
+ // Priority: profile-level key > settings provider key > env var
112
+ if (profileApiKey) return profileApiKey;
113
+
114
+ try {
115
+ const { loadSettings } = require('@/lib/settings');
116
+ const settings = loadSettings();
117
+ if (settings.providers?.[provider]?.apiKey) {
118
+ return settings.providers[provider].apiKey;
119
+ }
120
+ } catch {}
121
+
122
+ const envMap: Record<ProviderName, string> = {
123
+ anthropic: 'ANTHROPIC_API_KEY',
124
+ google: 'GOOGLE_GENERATIVE_AI_API_KEY',
125
+ openai: 'OPENAI_API_KEY',
126
+ grok: 'XAI_API_KEY',
127
+ };
128
+ return process.env[envMap[provider]];
129
+ }