@aion0/forge 0.5.26 → 0.5.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +10 -29
- package/app/api/terminal-bell/route.ts +6 -2
- package/app/api/terminal-cwd/route.ts +7 -4
- package/components/CodeViewer.tsx +3 -31
- package/components/Dashboard.tsx +34 -20
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/package.json +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, relative, extname } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { loadSettings } from '@/lib/settings';
|
|
6
|
+
|
|
7
|
+
interface FileNode {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string; // relative to docRoot
|
|
10
|
+
type: 'file' | 'dir';
|
|
11
|
+
fileType?: 'md' | 'image' | 'other';
|
|
12
|
+
children?: FileNode[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp', '.ico', '.avif']);
|
|
16
|
+
|
|
17
|
+
function scanDir(dir: string, base: string, depth: number = 0): FileNode[] {
|
|
18
|
+
if (depth > 6) return [];
|
|
19
|
+
try {
|
|
20
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
21
|
+
const nodes: FileNode[] = [];
|
|
22
|
+
|
|
23
|
+
// Sort: dirs first, then files, alphabetical
|
|
24
|
+
const sorted = entries
|
|
25
|
+
.filter(e => !e.name.startsWith('.') && e.name !== 'node_modules')
|
|
26
|
+
.sort((a, b) => {
|
|
27
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
28
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
29
|
+
return a.name.localeCompare(b.name);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for (const entry of sorted) {
|
|
33
|
+
const fullPath = join(dir, entry.name);
|
|
34
|
+
const relPath = relative(base, fullPath);
|
|
35
|
+
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
const children = scanDir(fullPath, base, depth + 1);
|
|
38
|
+
if (children.length > 0) {
|
|
39
|
+
nodes.push({ name: entry.name, path: relPath, type: 'dir', children });
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
const ext = extname(entry.name).toLowerCase();
|
|
43
|
+
if (ext === '.md') {
|
|
44
|
+
nodes.push({ name: entry.name, path: relPath, type: 'file', fileType: 'md' });
|
|
45
|
+
} else if (IMAGE_EXTS.has(ext)) {
|
|
46
|
+
nodes.push({ name: entry.name, path: relPath, type: 'file', fileType: 'image' });
|
|
47
|
+
} else if (!entry.name.startsWith('.')) {
|
|
48
|
+
nodes.push({ name: entry.name, path: relPath, type: 'file', fileType: 'other' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return nodes;
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// GET /api/docs — list doc roots and their file trees
|
|
59
|
+
export async function GET(req: Request) {
|
|
60
|
+
const { searchParams } = new URL(req.url);
|
|
61
|
+
const filePath = searchParams.get('file');
|
|
62
|
+
const rootIdx = parseInt(searchParams.get('root') || '0');
|
|
63
|
+
|
|
64
|
+
const settings = loadSettings();
|
|
65
|
+
const docRoots = (settings.docRoots || []).map(r => r.replace(/^~/, homedir()));
|
|
66
|
+
|
|
67
|
+
if (docRoots.length === 0) {
|
|
68
|
+
return NextResponse.json({ roots: [], tree: [], content: null });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rootNames = docRoots.map(r => r.split('/').pop() || r);
|
|
72
|
+
|
|
73
|
+
// Serve image
|
|
74
|
+
const imagePath = searchParams.get('image');
|
|
75
|
+
if (imagePath && rootIdx < docRoots.length) {
|
|
76
|
+
const root = docRoots[rootIdx];
|
|
77
|
+
const fullPath = join(root, imagePath);
|
|
78
|
+
if (!fullPath.startsWith(root)) {
|
|
79
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const { readFileSync: readBin } = require('node:fs');
|
|
83
|
+
const data = readBin(fullPath);
|
|
84
|
+
const ext = extname(fullPath).toLowerCase();
|
|
85
|
+
const mimeMap: Record<string, string> = {
|
|
86
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
87
|
+
'.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp',
|
|
88
|
+
'.bmp': 'image/bmp', '.ico': 'image/x-icon', '.avif': 'image/avif',
|
|
89
|
+
};
|
|
90
|
+
return new Response(data, {
|
|
91
|
+
headers: { 'Content-Type': mimeMap[ext] || 'application/octet-stream', 'Cache-Control': 'public, max-age=3600' },
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
return NextResponse.json({ error: 'Image not found' }, { status: 404 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Read file content
|
|
99
|
+
if (filePath && rootIdx < docRoots.length) {
|
|
100
|
+
const root = docRoots[rootIdx];
|
|
101
|
+
const fullPath = join(root, filePath);
|
|
102
|
+
if (!fullPath.startsWith(root)) {
|
|
103
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const stat = statSync(fullPath);
|
|
107
|
+
const size = stat.size;
|
|
108
|
+
const ext = extname(fullPath).replace('.', '').toLowerCase();
|
|
109
|
+
const sizeKB = Math.round(size / 1024);
|
|
110
|
+
const sizeMB = (size / (1024 * 1024)).toFixed(1);
|
|
111
|
+
|
|
112
|
+
// Binary file types
|
|
113
|
+
const BINARY_EXTS = new Set([
|
|
114
|
+
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'avif',
|
|
115
|
+
'mp3', 'mp4', 'wav', 'ogg', 'webm', 'mov', 'avi',
|
|
116
|
+
'zip', 'gz', 'tar', 'bz2', 'xz', '7z', 'rar',
|
|
117
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
118
|
+
'exe', 'dll', 'so', 'dylib', 'bin', 'o', 'a',
|
|
119
|
+
'woff', 'woff2', 'ttf', 'eot', 'otf',
|
|
120
|
+
'sqlite', 'db', 'sqlite3', 'class', 'jar', 'pyc', 'wasm',
|
|
121
|
+
]);
|
|
122
|
+
if (BINARY_EXTS.has(ext)) {
|
|
123
|
+
return NextResponse.json({ binary: true, fileType: ext, size, sizeLabel: sizeKB > 1024 ? `${sizeMB} MB` : `${sizeKB} KB` });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (size > 2_000_000) {
|
|
127
|
+
return NextResponse.json({ tooLarge: true, size, sizeLabel: `${sizeMB} MB`, message: 'File exceeds 2 MB limit' });
|
|
128
|
+
}
|
|
129
|
+
if (size > 200_000) {
|
|
130
|
+
return NextResponse.json({ large: true, size, sizeLabel: `${sizeKB} KB` });
|
|
131
|
+
}
|
|
132
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
133
|
+
return NextResponse.json({ content, language: ext });
|
|
134
|
+
} catch {
|
|
135
|
+
return NextResponse.json({ error: 'File not found' }, { status: 404 });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Return tree for selected root
|
|
140
|
+
const idx = Math.min(rootIdx, docRoots.length - 1);
|
|
141
|
+
const root = docRoots[idx];
|
|
142
|
+
const tree = scanDir(root, root);
|
|
143
|
+
|
|
144
|
+
return NextResponse.json({ roots: rootNames, rootPaths: docRoots, tree });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// PUT /api/docs — save file content
|
|
148
|
+
export async function PUT(req: Request) {
|
|
149
|
+
const { root: rootIdx, file: filePath, content } = await req.json() as {
|
|
150
|
+
root: number;
|
|
151
|
+
file: string;
|
|
152
|
+
content: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const settings = loadSettings();
|
|
156
|
+
const docRoots = (settings.docRoots || []).map(r => r.replace(/^~/, homedir()));
|
|
157
|
+
|
|
158
|
+
if (rootIdx >= docRoots.length || !filePath) {
|
|
159
|
+
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const root = docRoots[rootIdx];
|
|
163
|
+
const fullPath = join(root, filePath);
|
|
164
|
+
|
|
165
|
+
// Security: ensure path is within root
|
|
166
|
+
if (!fullPath.startsWith(root)) {
|
|
167
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
172
|
+
return NextResponse.json({ ok: true });
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return NextResponse.json({ error: 'Failed to save' }, { status: 500 });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getClaudeDir } from '@/lib/dirs';
|
|
5
|
+
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
const { searchParams } = new URL(req.url);
|
|
8
|
+
const dir = searchParams.get('dir');
|
|
9
|
+
if (!dir) return NextResponse.json({ sessions: [] });
|
|
10
|
+
|
|
11
|
+
// Claude stores sessions at <claudeDir>/projects/<path-with-dashes>/
|
|
12
|
+
const hash = dir.replace(/\//g, '-');
|
|
13
|
+
const claudeDir = join(getClaudeDir(), 'projects', hash);
|
|
14
|
+
|
|
15
|
+
if (!existsSync(claudeDir)) {
|
|
16
|
+
return NextResponse.json({ sessions: [] });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const files = readdirSync(claudeDir).filter(f => f.endsWith('.jsonl'));
|
|
21
|
+
const sessions = files.map(f => {
|
|
22
|
+
const sessionId = f.replace('.jsonl', '');
|
|
23
|
+
const filePath = join(claudeDir, f);
|
|
24
|
+
const stat = statSync(filePath);
|
|
25
|
+
|
|
26
|
+
// Read first line to get first prompt
|
|
27
|
+
let firstPrompt = '';
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
30
|
+
const lines = content.split('\n').filter(Boolean);
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
try {
|
|
33
|
+
const entry = JSON.parse(line);
|
|
34
|
+
if (entry.type === 'human' || entry.role === 'user') {
|
|
35
|
+
const text = typeof entry.message === 'string' ? entry.message
|
|
36
|
+
: entry.message?.content?.[0]?.text || '';
|
|
37
|
+
if (text) { firstPrompt = text.slice(0, 80); break; }
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
sessionId,
|
|
45
|
+
firstPrompt,
|
|
46
|
+
modified: stat.mtime.toISOString(),
|
|
47
|
+
};
|
|
48
|
+
}).sort((a, b) => b.modified.localeCompare(a.modified));
|
|
49
|
+
|
|
50
|
+
return NextResponse.json({ sessions });
|
|
51
|
+
} catch {
|
|
52
|
+
return NextResponse.json({ sessions: [] });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getDb } from '@/src/core/db/database';
|
|
3
|
+
import { getDbPath } from '@/src/config';
|
|
4
|
+
|
|
5
|
+
function db() { return getDb(getDbPath()); }
|
|
6
|
+
|
|
7
|
+
// GET /api/favorites — list all favorites
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const rows = db().prepare('SELECT project_path FROM project_favorites ORDER BY created_at ASC').all() as any[];
|
|
10
|
+
return NextResponse.json(rows.map(r => r.project_path));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// POST /api/favorites — add or remove
|
|
14
|
+
export async function POST(req: Request) {
|
|
15
|
+
const { action, projectPath } = await req.json();
|
|
16
|
+
if (!projectPath) return NextResponse.json({ error: 'projectPath required' }, { status: 400 });
|
|
17
|
+
|
|
18
|
+
if (action === 'add') {
|
|
19
|
+
db().prepare('INSERT OR IGNORE INTO project_favorites (project_path) VALUES (?)').run(projectPath);
|
|
20
|
+
} else if (action === 'remove') {
|
|
21
|
+
db().prepare('DELETE FROM project_favorites WHERE project_path = ?').run(projectPath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const rows = db().prepare('SELECT project_path FROM project_favorites ORDER BY created_at ASC').all() as any[];
|
|
25
|
+
return NextResponse.json(rows.map(r => r.project_path));
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { runFlow } from '@/lib/flows';
|
|
3
|
+
|
|
4
|
+
export async function POST(req: Request) {
|
|
5
|
+
const { name } = await req.json();
|
|
6
|
+
if (!name) {
|
|
7
|
+
return NextResponse.json({ error: 'Flow name required' }, { status: 400 });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const result = runFlow(name);
|
|
12
|
+
return NextResponse.json({
|
|
13
|
+
flow: result.flow.name,
|
|
14
|
+
tasks: result.tasks.map(t => ({ id: t.id, projectName: t.projectName, prompt: t.prompt })),
|
|
15
|
+
});
|
|
16
|
+
} catch (err: any) {
|
|
17
|
+
return NextResponse.json({ error: err.message }, { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
+
import { execSync, exec } from 'node:child_process';
|
|
3
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { loadSettings } from '@/lib/settings';
|
|
8
|
+
|
|
9
|
+
function isUnderProjectRoot(dir: string): boolean {
|
|
10
|
+
const settings = loadSettings();
|
|
11
|
+
const roots = (settings.projectRoots || []).map(r => r.replace(/^~/, homedir()));
|
|
12
|
+
return roots.some(root => dir.startsWith(root) || dir === root);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
function gitSync(cmd: string, cwd: string): string {
|
|
18
|
+
return execSync(`git ${cmd}`, { cwd, encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function gitAsync(cmd: string, cwd: string): Promise<string> {
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await execAsync(`git ${cmd}`, { cwd, encoding: 'utf-8', timeout: 10000 });
|
|
24
|
+
return stdout.trim();
|
|
25
|
+
} catch { return ''; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// GET /api/git?dir=<path> — git status for a project
|
|
29
|
+
export async function GET(req: NextRequest) {
|
|
30
|
+
const dir = req.nextUrl.searchParams.get('dir');
|
|
31
|
+
if (!dir || !isUnderProjectRoot(dir)) {
|
|
32
|
+
return NextResponse.json({ error: 'Invalid directory' }, { status: 400 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Run all git commands in parallel
|
|
37
|
+
const [branchOut, statusOut, remoteOut, lastCommitOut, logOut, branchListOut] = await Promise.all([
|
|
38
|
+
gitAsync('rev-parse --abbrev-ref HEAD', dir),
|
|
39
|
+
gitAsync('status --porcelain -u', dir),
|
|
40
|
+
gitAsync('remote get-url origin', dir),
|
|
41
|
+
gitAsync('log -1 --format="%h %s"', dir),
|
|
42
|
+
gitAsync('log --format="%h||%s||%an||%ar" -20', dir),
|
|
43
|
+
gitAsync('branch --format="%(refname:short)||%(upstream:short)||%(objectname:short)"', dir),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const branch = branchOut;
|
|
47
|
+
const changes = statusOut ? statusOut.split('\n').filter(Boolean).map(line => ({
|
|
48
|
+
status: line.substring(0, 2).trim() || 'M',
|
|
49
|
+
path: line.substring(3).replace(/\/$/, ''),
|
|
50
|
+
})) : [];
|
|
51
|
+
|
|
52
|
+
let ahead = 0, behind = 0;
|
|
53
|
+
try {
|
|
54
|
+
const counts = await gitAsync(`rev-list --left-right --count HEAD...origin/${branch}`, dir);
|
|
55
|
+
if (counts) { const [a, b] = counts.split('\t'); ahead = parseInt(a) || 0; behind = parseInt(b) || 0; }
|
|
56
|
+
} catch {}
|
|
57
|
+
|
|
58
|
+
const log = logOut ? logOut.split('\n').filter(Boolean).map(line => {
|
|
59
|
+
const [hash, message, author, date] = line.split('||');
|
|
60
|
+
return { hash, message, author, date };
|
|
61
|
+
}) : [];
|
|
62
|
+
|
|
63
|
+
const branches = branchListOut ? branchListOut.split('\n').filter(Boolean).map(line => {
|
|
64
|
+
const [name, upstream, hash] = line.split('||');
|
|
65
|
+
return { name, upstream: upstream || '', hash: hash || '', current: name === branch };
|
|
66
|
+
}) : [];
|
|
67
|
+
|
|
68
|
+
return NextResponse.json({ branch, branches, changes, remote: remoteOut, ahead, behind, lastCommit: lastCommitOut, log });
|
|
69
|
+
} catch (e: any) {
|
|
70
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// POST /api/git — git operations (commit, push, pull, clone)
|
|
75
|
+
export async function POST(req: NextRequest) {
|
|
76
|
+
const body = await req.json();
|
|
77
|
+
const { action, dir, message, files, repoUrl, targetDir } = body;
|
|
78
|
+
|
|
79
|
+
if (action === 'clone') {
|
|
80
|
+
// Clone a repo into a project root
|
|
81
|
+
if (!repoUrl) return NextResponse.json({ error: 'repoUrl required' }, { status: 400 });
|
|
82
|
+
const settings = loadSettings();
|
|
83
|
+
const roots = (settings.projectRoots || []).map(r => r.replace(/^~/, homedir()));
|
|
84
|
+
const cloneTarget = targetDir || roots[0];
|
|
85
|
+
if (!cloneTarget) return NextResponse.json({ error: 'No project root configured' }, { status: 400 });
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const output = execSync(`git clone "${repoUrl}"`, {
|
|
89
|
+
cwd: cloneTarget,
|
|
90
|
+
encoding: 'utf-8',
|
|
91
|
+
timeout: 60000,
|
|
92
|
+
});
|
|
93
|
+
// Extract cloned dir name from URL
|
|
94
|
+
const repoName = repoUrl.split('/').pop()?.replace(/\.git$/, '') || 'repo';
|
|
95
|
+
return NextResponse.json({ ok: true, path: join(cloneTarget, repoName), output });
|
|
96
|
+
} catch (e: any) {
|
|
97
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!dir || !isUnderProjectRoot(dir)) {
|
|
102
|
+
return NextResponse.json({ error: 'Invalid directory' }, { status: 400 });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (action === 'commit') {
|
|
107
|
+
if (!message) return NextResponse.json({ error: 'message required' }, { status: 400 });
|
|
108
|
+
if (files && files.length > 0) {
|
|
109
|
+
for (const f of files) {
|
|
110
|
+
gitSync(`add "${f}"`, dir);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
gitSync('add -A', dir);
|
|
114
|
+
}
|
|
115
|
+
gitSync(`commit -m "${message.replace(/"/g, '\\"')}"`, dir);
|
|
116
|
+
return NextResponse.json({ ok: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (action === 'push') {
|
|
120
|
+
const output = gitSync('push', dir);
|
|
121
|
+
return NextResponse.json({ ok: true, output });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (action === 'pull') {
|
|
125
|
+
const output = gitSync('pull', dir);
|
|
126
|
+
return NextResponse.json({ ok: true, output });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (action === 'checkout') {
|
|
130
|
+
const branch = body.branch;
|
|
131
|
+
if (!branch) return NextResponse.json({ error: 'branch required' }, { status: 400 });
|
|
132
|
+
const out = gitSync(`checkout ${branch}`, dir);
|
|
133
|
+
return NextResponse.json({ ok: true, output: out });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (action === 'stage') {
|
|
137
|
+
if (files && files.length > 0) {
|
|
138
|
+
for (const f of files) gitSync(`add "${f}"`, dir);
|
|
139
|
+
} else {
|
|
140
|
+
gitSync('add -A', dir);
|
|
141
|
+
}
|
|
142
|
+
return NextResponse.json({ ok: true });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
146
|
+
} catch (e: any) {
|
|
147
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getConfigDir, getDataDir } from '@/lib/dirs';
|
|
5
|
+
import { loadSettings } from '@/lib/settings';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const HELP_DIR = join(getConfigDir(), 'help');
|
|
9
|
+
const SOURCE_HELP_DIR = join(process.cwd(), 'lib', 'help-docs');
|
|
10
|
+
|
|
11
|
+
/** Ensure help docs are copied to ~/.forge/help/ and CLAUDE.md to ~/.forge/data/ */
|
|
12
|
+
function ensureHelpDocs() {
|
|
13
|
+
if (!existsSync(HELP_DIR)) mkdirSync(HELP_DIR, { recursive: true });
|
|
14
|
+
if (existsSync(SOURCE_HELP_DIR)) {
|
|
15
|
+
for (const file of readdirSync(SOURCE_HELP_DIR)) {
|
|
16
|
+
if (!file.endsWith('.md')) continue;
|
|
17
|
+
const src = join(SOURCE_HELP_DIR, file);
|
|
18
|
+
const dest = join(HELP_DIR, file);
|
|
19
|
+
writeFileSync(dest, readFileSync(src));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Copy CLAUDE.md to data dir so Help AI (working in ~/.forge/data/) picks it up
|
|
23
|
+
const dataDir = getDataDir();
|
|
24
|
+
const claudeMdSrc = join(HELP_DIR, 'CLAUDE.md');
|
|
25
|
+
const claudeMdDest = join(dataDir, 'CLAUDE.md');
|
|
26
|
+
if (existsSync(claudeMdSrc)) {
|
|
27
|
+
writeFileSync(claudeMdDest, readFileSync(claudeMdSrc));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Check if any agent CLI is available */
|
|
32
|
+
function detectAgent(): { name: string; path: string } | null {
|
|
33
|
+
const settings = loadSettings();
|
|
34
|
+
if (settings.claudePath) {
|
|
35
|
+
try {
|
|
36
|
+
execSync(`"${settings.claudePath}" --version`, { timeout: 5000, stdio: 'pipe' });
|
|
37
|
+
return { name: 'claude', path: settings.claudePath };
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
for (const agent of ['claude', 'codex', 'aider']) {
|
|
41
|
+
try {
|
|
42
|
+
const path = execSync(`which ${agent}`, { encoding: 'utf-8', timeout: 3000, stdio: 'pipe' }).trim();
|
|
43
|
+
if (path) return { name: agent, path };
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// GET /api/help
|
|
50
|
+
export async function GET(req: Request) {
|
|
51
|
+
const { searchParams } = new URL(req.url);
|
|
52
|
+
const action = searchParams.get('action') || 'status';
|
|
53
|
+
|
|
54
|
+
if (action === 'status') {
|
|
55
|
+
const agent = detectAgent();
|
|
56
|
+
ensureHelpDocs();
|
|
57
|
+
const docs = existsSync(HELP_DIR)
|
|
58
|
+
? readdirSync(HELP_DIR).filter(f => f.endsWith('.md')).sort()
|
|
59
|
+
: [];
|
|
60
|
+
return NextResponse.json({ agent, docsCount: docs.length, helpDir: HELP_DIR, dataDir: getDataDir() });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (action === 'docs') {
|
|
64
|
+
ensureHelpDocs();
|
|
65
|
+
const docs = existsSync(HELP_DIR)
|
|
66
|
+
? readdirSync(HELP_DIR).filter(f => f.endsWith('.md')).sort().map(f => ({
|
|
67
|
+
name: f,
|
|
68
|
+
title: f.replace(/^\d+-/, '').replace(/\.md$/, '').replace(/-/g, ' '),
|
|
69
|
+
}))
|
|
70
|
+
: [];
|
|
71
|
+
return NextResponse.json({ docs });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (action === 'doc') {
|
|
75
|
+
const name = searchParams.get('name');
|
|
76
|
+
if (!name) return NextResponse.json({ error: 'name required' }, { status: 400 });
|
|
77
|
+
ensureHelpDocs();
|
|
78
|
+
const file = join(HELP_DIR, name);
|
|
79
|
+
if (!existsSync(file)) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
80
|
+
return NextResponse.json({ content: readFileSync(file, 'utf-8') });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
84
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getConfig,
|
|
4
|
+
saveConfig,
|
|
5
|
+
listConfigs,
|
|
6
|
+
scanAndTrigger,
|
|
7
|
+
restartScanner,
|
|
8
|
+
getProcessedIssues,
|
|
9
|
+
resetProcessedIssue,
|
|
10
|
+
getNextScanTime,
|
|
11
|
+
type IssueAutofixConfig,
|
|
12
|
+
} from '@/lib/issue-scanner';
|
|
13
|
+
|
|
14
|
+
// GET /api/issue-scanner?project=PATH — get config + processed issues
|
|
15
|
+
export async function GET(req: Request) {
|
|
16
|
+
const { searchParams } = new URL(req.url);
|
|
17
|
+
const projectPath = searchParams.get('project');
|
|
18
|
+
|
|
19
|
+
if (projectPath) {
|
|
20
|
+
const config = getConfig(projectPath);
|
|
21
|
+
const processed = getProcessedIssues(projectPath);
|
|
22
|
+
const scanTime = getNextScanTime(projectPath);
|
|
23
|
+
return NextResponse.json({ config, processed, ...scanTime });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// List all enabled configs
|
|
27
|
+
const configs = listConfigs();
|
|
28
|
+
return NextResponse.json({ configs });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// POST /api/issue-scanner
|
|
32
|
+
export async function POST(req: Request) {
|
|
33
|
+
const body = await req.json();
|
|
34
|
+
|
|
35
|
+
// Save config
|
|
36
|
+
if (body.action === 'save-config') {
|
|
37
|
+
const config: IssueAutofixConfig = {
|
|
38
|
+
projectPath: body.projectPath,
|
|
39
|
+
projectName: body.projectName,
|
|
40
|
+
enabled: !!body.enabled,
|
|
41
|
+
interval: body.interval || 30,
|
|
42
|
+
labels: body.labels || [],
|
|
43
|
+
baseBranch: body.baseBranch || '',
|
|
44
|
+
};
|
|
45
|
+
saveConfig(config);
|
|
46
|
+
restartScanner();
|
|
47
|
+
return NextResponse.json({ ok: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Manual scan & trigger
|
|
51
|
+
if (body.action === 'scan') {
|
|
52
|
+
const config = getConfig(body.projectPath);
|
|
53
|
+
if (!config) return NextResponse.json({ error: 'Not configured' }, { status: 400 });
|
|
54
|
+
const result = scanAndTrigger(config);
|
|
55
|
+
return NextResponse.json(result);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Manual trigger for a specific issue
|
|
59
|
+
if (body.action === 'trigger') {
|
|
60
|
+
const { startPipeline } = require('@/lib/pipeline');
|
|
61
|
+
const config = getConfig(body.projectPath);
|
|
62
|
+
const projectName = config?.projectName || body.projectName;
|
|
63
|
+
try {
|
|
64
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
65
|
+
issue_id: String(body.issueId),
|
|
66
|
+
project: projectName,
|
|
67
|
+
base_branch: config?.baseBranch || body.baseBranch || 'auto-detect',
|
|
68
|
+
});
|
|
69
|
+
// Track in processed issues
|
|
70
|
+
const { getDb } = require('@/src/core/db/database');
|
|
71
|
+
const { getDbPath } = require('@/src/config');
|
|
72
|
+
getDb(getDbPath()).prepare(`
|
|
73
|
+
INSERT OR REPLACE INTO issue_autofix_processed (project_path, issue_number, pipeline_id, status)
|
|
74
|
+
VALUES (?, ?, ?, 'processing')
|
|
75
|
+
`).run(body.projectPath, body.issueId, pipeline.id);
|
|
76
|
+
return NextResponse.json({ ok: true, pipelineId: pipeline.id });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Reset a processed issue (allow re-scan)
|
|
83
|
+
if (body.action === 'reset') {
|
|
84
|
+
resetProcessedIssue(body.projectPath, body.issueId);
|
|
85
|
+
return NextResponse.json({ ok: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Retry with additional context/instructions
|
|
89
|
+
if (body.action === 'retry') {
|
|
90
|
+
const { startPipeline } = require('@/lib/pipeline');
|
|
91
|
+
const config = getConfig(body.projectPath);
|
|
92
|
+
const projectName = config?.projectName || body.projectName;
|
|
93
|
+
// Reset the processed record first, then re-create with new pipeline
|
|
94
|
+
resetProcessedIssue(body.projectPath, body.issueId);
|
|
95
|
+
try {
|
|
96
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
97
|
+
issue_id: String(body.issueId),
|
|
98
|
+
project: projectName,
|
|
99
|
+
base_branch: config?.baseBranch || 'auto-detect',
|
|
100
|
+
extra_context: body.context || '',
|
|
101
|
+
});
|
|
102
|
+
// Re-mark as processed with new pipeline ID
|
|
103
|
+
const { getDb } = require('@/src/core/db/database');
|
|
104
|
+
const { getDbPath } = require('@/src/config');
|
|
105
|
+
getDb(getDbPath()).prepare(`
|
|
106
|
+
INSERT OR REPLACE INTO issue_autofix_processed (project_path, issue_number, pipeline_id, status)
|
|
107
|
+
VALUES (?, ?, ?, 'processing')
|
|
108
|
+
`).run(body.projectPath, body.issueId, pipeline.id);
|
|
109
|
+
return NextResponse.json({ ok: true, pipelineId: pipeline.id });
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
116
|
+
}
|