@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.
- package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
- package/RELEASE_NOTES.md +11 -28
- package/app/api/terminal-bell/route.ts +6 -2
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/next-env.d.ts +1 -1
- 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
|
+
}
|