@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,100 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync, statSync, openSync, readSync, closeSync, writeFileSync, renameSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getDataDir } from '@/lib/dirs';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const LOG_FILE = join(getDataDir(), 'forge.log');
|
|
8
|
+
const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB — auto-rotate above this
|
|
9
|
+
|
|
10
|
+
/** Read last N bytes from file (efficient tail) */
|
|
11
|
+
function tailFile(filePath: string, maxBytes: number): string {
|
|
12
|
+
const stat = statSync(filePath);
|
|
13
|
+
const size = stat.size;
|
|
14
|
+
const readSize = Math.min(size, maxBytes);
|
|
15
|
+
const buf = Buffer.alloc(readSize);
|
|
16
|
+
const fd = openSync(filePath, 'r');
|
|
17
|
+
readSync(fd, buf, 0, readSize, size - readSize);
|
|
18
|
+
closeSync(fd);
|
|
19
|
+
// Skip partial first line
|
|
20
|
+
const str = buf.toString('utf-8');
|
|
21
|
+
const firstNewline = str.indexOf('\n');
|
|
22
|
+
return firstNewline > 0 ? str.slice(firstNewline + 1) : str;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Rotate log if too large: forge.log → forge.log.old, start fresh */
|
|
26
|
+
function rotateIfNeeded() {
|
|
27
|
+
if (!existsSync(LOG_FILE)) return;
|
|
28
|
+
const stat = statSync(LOG_FILE);
|
|
29
|
+
if (stat.size > MAX_LOG_SIZE) {
|
|
30
|
+
const oldFile = LOG_FILE + '.old';
|
|
31
|
+
try { renameSync(LOG_FILE, oldFile); } catch {}
|
|
32
|
+
writeFileSync(LOG_FILE, `[forge] Log rotated at ${new Date().toISOString()} (previous: ${(stat.size / 1024 / 1024).toFixed(1)}MB)\n`, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// GET /api/logs?lines=200&search=keyword
|
|
37
|
+
export async function GET(req: Request) {
|
|
38
|
+
const { searchParams } = new URL(req.url);
|
|
39
|
+
const lines = Math.min(parseInt(searchParams.get('lines') || '200'), 1000);
|
|
40
|
+
const search = searchParams.get('search') || '';
|
|
41
|
+
|
|
42
|
+
rotateIfNeeded();
|
|
43
|
+
|
|
44
|
+
if (!existsSync(LOG_FILE)) {
|
|
45
|
+
return NextResponse.json({ lines: [], total: 0, size: 0 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const stat = statSync(LOG_FILE);
|
|
50
|
+
// Read last 512KB max (enough for ~5000 lines)
|
|
51
|
+
const raw = tailFile(LOG_FILE, 512 * 1024);
|
|
52
|
+
let allLines = raw.split('\n').filter(Boolean);
|
|
53
|
+
|
|
54
|
+
if (search) {
|
|
55
|
+
allLines = allLines.filter(l => l.toLowerCase().includes(search.toLowerCase()));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = allLines.slice(-lines);
|
|
59
|
+
|
|
60
|
+
return NextResponse.json({
|
|
61
|
+
lines: result,
|
|
62
|
+
total: allLines.length,
|
|
63
|
+
size: stat.size,
|
|
64
|
+
file: LOG_FILE,
|
|
65
|
+
});
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// POST /api/logs — actions
|
|
72
|
+
export async function POST(req: Request) {
|
|
73
|
+
const body = await req.json();
|
|
74
|
+
|
|
75
|
+
if (body.action === 'clear') {
|
|
76
|
+
try {
|
|
77
|
+
writeFileSync(LOG_FILE, `[forge] Log cleared at ${new Date().toISOString()}\n`, 'utf-8');
|
|
78
|
+
return NextResponse.json({ ok: true });
|
|
79
|
+
} catch (e: any) {
|
|
80
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (body.action === 'processes') {
|
|
85
|
+
try {
|
|
86
|
+
const out = execSync("ps aux | grep -E 'next-server|telegram-standalone|terminal-standalone|cloudflared' | grep -v grep", {
|
|
87
|
+
encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
}).trim();
|
|
89
|
+
const processes = out.split('\n').filter(Boolean).map(line => {
|
|
90
|
+
const parts = line.trim().split(/\s+/);
|
|
91
|
+
return { pid: parts[1], cpu: parts[2], mem: parts[3], cmd: parts.slice(10).join(' ').slice(0, 80) };
|
|
92
|
+
});
|
|
93
|
+
return NextResponse.json({ processes });
|
|
94
|
+
} catch {
|
|
95
|
+
return NextResponse.json({ processes: [] });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
100
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { loadSettings } from '@/lib/settings';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
export const runtime = 'nodejs';
|
|
7
|
+
|
|
8
|
+
// POST /api/mobile-chat — send a message to claude and stream response
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const { message, projectPath, resume, agent: agentId } = await req.json() as {
|
|
11
|
+
message: string;
|
|
12
|
+
projectPath: string;
|
|
13
|
+
resume?: boolean;
|
|
14
|
+
agent?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
if (!message || !projectPath) {
|
|
18
|
+
return NextResponse.json({ error: 'message and projectPath required' }, { status: 400 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { getAgent } = require('@/lib/agents');
|
|
22
|
+
const adapter = getAgent(agentId);
|
|
23
|
+
const projectName = projectPath.split('/').pop() || projectPath;
|
|
24
|
+
|
|
25
|
+
const spawnOpts = adapter.buildTaskSpawn({
|
|
26
|
+
projectPath,
|
|
27
|
+
prompt: message,
|
|
28
|
+
skipPermissions: true,
|
|
29
|
+
outputFormat: adapter.config.capabilities?.supportsStreamJson ? 'json' : undefined,
|
|
30
|
+
conversationId: resume ? 'last' : undefined,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const child = spawn(spawnOpts.cmd, spawnOpts.args, {
|
|
34
|
+
cwd: projectPath,
|
|
35
|
+
env: { ...process.env, ...(spawnOpts.env || {}) },
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.stdin.write(message);
|
|
40
|
+
child.stdin.end();
|
|
41
|
+
|
|
42
|
+
const encoder = new TextEncoder();
|
|
43
|
+
let closed = false;
|
|
44
|
+
let fullOutput = '';
|
|
45
|
+
|
|
46
|
+
const stream = new ReadableStream({
|
|
47
|
+
start(controller) {
|
|
48
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
49
|
+
if (closed) return;
|
|
50
|
+
const text = chunk.toString();
|
|
51
|
+
fullOutput += text;
|
|
52
|
+
try {
|
|
53
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'chunk', text })}\n\n`));
|
|
54
|
+
} catch {}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.stderr.on('data', (chunk: Buffer) => {
|
|
58
|
+
if (closed) return;
|
|
59
|
+
const text = chunk.toString();
|
|
60
|
+
if (text.includes('npm update') || text.includes('WARN')) return;
|
|
61
|
+
try {
|
|
62
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'stderr', text })}\n\n`));
|
|
63
|
+
} catch {}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
child.on('exit', (code) => {
|
|
67
|
+
if (closed) return;
|
|
68
|
+
closed = true;
|
|
69
|
+
|
|
70
|
+
// Record usage from the JSON output
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(fullOutput);
|
|
73
|
+
if (parsed.session_id) {
|
|
74
|
+
const { recordUsage } = require('@/lib/usage-scanner');
|
|
75
|
+
recordUsage({
|
|
76
|
+
sessionId: parsed.session_id,
|
|
77
|
+
source: 'mobile',
|
|
78
|
+
projectPath,
|
|
79
|
+
projectName,
|
|
80
|
+
model: parsed.model || 'unknown',
|
|
81
|
+
inputTokens: parsed.usage?.input_tokens || parsed.total_input_tokens || 0,
|
|
82
|
+
outputTokens: parsed.usage?.output_tokens || parsed.total_output_tokens || 0,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', code })}\n\n`));
|
|
89
|
+
controller.close();
|
|
90
|
+
} catch {}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
child.on('error', (err) => {
|
|
94
|
+
if (closed) return;
|
|
95
|
+
closed = true;
|
|
96
|
+
try {
|
|
97
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', message: err.message })}\n\n`));
|
|
98
|
+
controller.close();
|
|
99
|
+
} catch {}
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
cancel() {
|
|
103
|
+
closed = true;
|
|
104
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return new Response(stream, {
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'text/event-stream',
|
|
111
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
112
|
+
Connection: 'keep-alive',
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
function run(cmd: string): string {
|
|
5
|
+
try {
|
|
6
|
+
return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
7
|
+
} catch { return ''; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function countProcess(pattern: string): { count: number; pid: string; startedAt: string } {
|
|
11
|
+
const out = run(`ps aux | grep '${pattern}' | grep -v grep | head -1`);
|
|
12
|
+
const pid = out ? out.split(/\s+/)[1] || '' : '';
|
|
13
|
+
const count = out ? run(`ps aux | grep '${pattern}' | grep -v grep | wc -l`).trim() : '0';
|
|
14
|
+
// Get start time from ps
|
|
15
|
+
const startedAt = pid ? run(`ps -o lstart= -p ${pid} 2>/dev/null`).trim() : '';
|
|
16
|
+
return { count: parseInt(count), pid, startedAt };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function GET() {
|
|
20
|
+
// Processes
|
|
21
|
+
const nextjs = countProcess('next-server');
|
|
22
|
+
const terminal = countProcess('terminal-standalone');
|
|
23
|
+
const telegram = countProcess('telegram-standalone');
|
|
24
|
+
const workspace = countProcess('workspace-standalone');
|
|
25
|
+
const tunnel = countProcess('cloudflared tunnel');
|
|
26
|
+
|
|
27
|
+
// MCP Server (runs inside workspace process, check /health endpoint)
|
|
28
|
+
let mcpStatus = { running: false, sessions: 0 };
|
|
29
|
+
try {
|
|
30
|
+
const mcpPort = Number(process.env.MCP_PORT) || 8406;
|
|
31
|
+
const mcpRes = run(`curl -s http://localhost:${mcpPort}/health 2>/dev/null`);
|
|
32
|
+
if (mcpRes.includes('"ok":true')) {
|
|
33
|
+
const data = JSON.parse(mcpRes);
|
|
34
|
+
mcpStatus = { running: true, sessions: data.sessions || 0 };
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
// Tunnel URL
|
|
39
|
+
let tunnelUrl = '';
|
|
40
|
+
try {
|
|
41
|
+
const { readFileSync } = require('fs');
|
|
42
|
+
const { join } = require('path');
|
|
43
|
+
const { getDataDir: _gdd } = require('@/lib/dirs');
|
|
44
|
+
const state = JSON.parse(readFileSync(join(_gdd(), 'tunnel-state.json'), 'utf-8'));
|
|
45
|
+
tunnelUrl = state.url || '';
|
|
46
|
+
} catch {}
|
|
47
|
+
|
|
48
|
+
// tmux sessions
|
|
49
|
+
let sessions: { name: string; created: string; attached: boolean; windows: number }[] = [];
|
|
50
|
+
try {
|
|
51
|
+
const out = run("tmux list-sessions -F '#{session_name}||#{session_created}||#{session_attached}||#{session_windows}' 2>/dev/null");
|
|
52
|
+
sessions = out.split('\n').filter(l => l.startsWith('mw-')).map(line => {
|
|
53
|
+
const [name, created, attached, windows] = line.split('||');
|
|
54
|
+
return { name, created: new Date(Number(created) * 1000).toISOString(), attached: attached !== '0', windows: Number(windows) || 1 };
|
|
55
|
+
});
|
|
56
|
+
} catch {}
|
|
57
|
+
|
|
58
|
+
// System info
|
|
59
|
+
const uptime = run('uptime');
|
|
60
|
+
const memory = run("ps -o rss= -p $$ 2>/dev/null || echo 0");
|
|
61
|
+
|
|
62
|
+
return NextResponse.json({
|
|
63
|
+
processes: {
|
|
64
|
+
nextjs: { running: nextjs.count > 0, pid: nextjs.pid, startedAt: nextjs.startedAt },
|
|
65
|
+
terminal: { running: terminal.count > 0, pid: terminal.pid, startedAt: terminal.startedAt },
|
|
66
|
+
telegram: { running: telegram.count > 0, pid: telegram.pid, startedAt: telegram.startedAt },
|
|
67
|
+
workspace: { running: workspace.count > 0, pid: workspace.pid, startedAt: workspace.startedAt },
|
|
68
|
+
tunnel: { running: tunnel.count > 0, pid: tunnel.pid, url: tunnelUrl, startedAt: tunnel.startedAt },
|
|
69
|
+
mcp: { running: mcpStatus.running, port: 8406, sessions: mcpStatus.sessions },
|
|
70
|
+
},
|
|
71
|
+
sessions,
|
|
72
|
+
uptime: uptime.replace(/.*up\s+/, '').replace(/,\s+\d+ user.*/, '').trim(),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getNotifications,
|
|
4
|
+
getUnreadCount,
|
|
5
|
+
markRead,
|
|
6
|
+
markAllRead,
|
|
7
|
+
deleteNotification,
|
|
8
|
+
} from '@/lib/notifications';
|
|
9
|
+
|
|
10
|
+
// GET /api/notifications — list notifications + unread count
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
const { searchParams } = new URL(req.url);
|
|
13
|
+
const limit = parseInt(searchParams.get('limit') || '50');
|
|
14
|
+
const offset = parseInt(searchParams.get('offset') || '0');
|
|
15
|
+
|
|
16
|
+
const notifications = getNotifications(limit, offset);
|
|
17
|
+
const unread = getUnreadCount();
|
|
18
|
+
|
|
19
|
+
return NextResponse.json({ notifications, unread });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// POST /api/notifications — actions: markRead, markAllRead, delete
|
|
23
|
+
export async function POST(req: Request) {
|
|
24
|
+
const body = await req.json();
|
|
25
|
+
|
|
26
|
+
if (body.action === 'markRead' && body.id) {
|
|
27
|
+
markRead(body.id);
|
|
28
|
+
return NextResponse.json({ ok: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (body.action === 'markAllRead') {
|
|
32
|
+
markAllRead();
|
|
33
|
+
return NextResponse.json({ ok: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (body.action === 'delete' && body.id) {
|
|
37
|
+
deleteNotification(body.id);
|
|
38
|
+
return NextResponse.json({ ok: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { loadSettings } from '@/lib/settings';
|
|
3
|
+
|
|
4
|
+
export async function POST() {
|
|
5
|
+
const settings = loadSettings();
|
|
6
|
+
const { telegramBotToken, telegramChatId } = settings;
|
|
7
|
+
|
|
8
|
+
if (!telegramBotToken || !telegramChatId) {
|
|
9
|
+
return NextResponse.json({ ok: false, error: 'Telegram bot token or chat ID not configured' });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const url = `https://api.telegram.org/bot${telegramBotToken}/sendMessage`;
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
chat_id: telegramChatId,
|
|
19
|
+
text: '✅ *Forge* — Test notification!\n\nTelegram notifications are working.',
|
|
20
|
+
parse_mode: 'Markdown',
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const body = await res.text();
|
|
26
|
+
return NextResponse.json({ ok: false, error: body });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return NextResponse.json({ ok: true });
|
|
30
|
+
} catch (err: any) {
|
|
31
|
+
return NextResponse.json({ ok: false, error: err.message });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
+
|
|
3
|
+
// Track active users: IP/identifier → last seen timestamp
|
|
4
|
+
const activeUsers = new Map<string, { lastSeen: number; isRemote: boolean }>();
|
|
5
|
+
const TIMEOUT = 30_000; // 30s — user is "offline" if no ping in 30s
|
|
6
|
+
|
|
7
|
+
function cleanup() {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
for (const [key, val] of activeUsers) {
|
|
10
|
+
if (now - val.lastSeen > TIMEOUT) activeUsers.delete(key);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// POST /api/online — heartbeat ping
|
|
15
|
+
export async function POST(req: NextRequest) {
|
|
16
|
+
cleanup();
|
|
17
|
+
|
|
18
|
+
const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
|
|
19
|
+
|| req.headers.get('x-real-ip')
|
|
20
|
+
|| 'local';
|
|
21
|
+
const host = req.headers.get('host') || '';
|
|
22
|
+
const isRemote = host.includes('.trycloudflare.com') || (ip !== 'local' && ip !== '127.0.0.1' && ip !== '::1');
|
|
23
|
+
|
|
24
|
+
activeUsers.set(ip, { lastSeen: Date.now(), isRemote });
|
|
25
|
+
|
|
26
|
+
const total = activeUsers.size;
|
|
27
|
+
const remote = [...activeUsers.values()].filter(v => v.isRemote).length;
|
|
28
|
+
|
|
29
|
+
return NextResponse.json({ total, remote });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// GET /api/online — just get counts
|
|
33
|
+
export async function GET() {
|
|
34
|
+
cleanup();
|
|
35
|
+
|
|
36
|
+
const total = activeUsers.size;
|
|
37
|
+
const remote = [...activeUsers.values()].filter(v => v.isRemote).length;
|
|
38
|
+
|
|
39
|
+
return NextResponse.json({ total, remote });
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getPipeline, cancelPipeline, deletePipeline, injectConversationMessage } from '@/lib/pipeline';
|
|
3
|
+
|
|
4
|
+
// GET /api/pipelines/:id
|
|
5
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params;
|
|
7
|
+
const pipeline = getPipeline(id);
|
|
8
|
+
if (!pipeline) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
9
|
+
return NextResponse.json(pipeline);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// POST /api/pipelines/:id — actions (cancel, delete, inject)
|
|
13
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
|
+
const { id } = await params;
|
|
15
|
+
const body = await req.json();
|
|
16
|
+
const { action } = body;
|
|
17
|
+
|
|
18
|
+
if (action === 'cancel') {
|
|
19
|
+
const ok = cancelPipeline(id);
|
|
20
|
+
return NextResponse.json({ ok });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (action === 'delete') {
|
|
24
|
+
const ok = deletePipeline(id);
|
|
25
|
+
return NextResponse.json({ ok });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Inject a message into a running conversation
|
|
29
|
+
if (action === 'inject') {
|
|
30
|
+
const { agentId, message } = body;
|
|
31
|
+
if (!agentId || !message) return NextResponse.json({ error: 'agentId and message required' }, { status: 400 });
|
|
32
|
+
try {
|
|
33
|
+
const ok = injectConversationMessage(id, agentId, message);
|
|
34
|
+
return NextResponse.json({ ok });
|
|
35
|
+
} catch (e: any) {
|
|
36
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
41
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { listPipelines, listWorkflows, startPipeline } from '@/lib/pipeline';
|
|
3
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import { getDataDir } from '@/lib/dirs';
|
|
7
|
+
|
|
8
|
+
const FLOWS_DIR = join(getDataDir(), 'flows');
|
|
9
|
+
|
|
10
|
+
// GET /api/pipelines — list all pipelines + available workflows
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
const { searchParams } = new URL(req.url);
|
|
13
|
+
const type = searchParams.get('type');
|
|
14
|
+
|
|
15
|
+
if (type === 'workflows') {
|
|
16
|
+
return NextResponse.json(listWorkflows());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (type === 'workflow-yaml') {
|
|
20
|
+
const name = searchParams.get('name');
|
|
21
|
+
if (!name) return NextResponse.json({ error: 'name required' }, { status: 400 });
|
|
22
|
+
try {
|
|
23
|
+
const { readFileSync, existsSync } = await import('node:fs');
|
|
24
|
+
const filePath = join(FLOWS_DIR, `${name}.yaml`);
|
|
25
|
+
const altPath = join(FLOWS_DIR, `${name}.yml`);
|
|
26
|
+
const path = existsSync(filePath) ? filePath : existsSync(altPath) ? altPath : null;
|
|
27
|
+
if (path) {
|
|
28
|
+
return NextResponse.json({ yaml: readFileSync(path, 'utf-8') });
|
|
29
|
+
}
|
|
30
|
+
// Check built-in workflows
|
|
31
|
+
const workflow = listWorkflows().find(w => w.name === name);
|
|
32
|
+
if (workflow?.builtin) {
|
|
33
|
+
const { BUILTIN_WORKFLOWS } = await import('@/lib/pipeline');
|
|
34
|
+
const yaml = BUILTIN_WORKFLOWS[name];
|
|
35
|
+
if (yaml) return NextResponse.json({ yaml: yaml.trim(), builtin: true });
|
|
36
|
+
}
|
|
37
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
38
|
+
} catch {
|
|
39
|
+
return NextResponse.json({ error: 'Failed to read' }, { status: 500 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return NextResponse.json(listPipelines().sort((a, b) => b.createdAt.localeCompare(a.createdAt)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// POST /api/pipelines — start a pipeline or save a workflow
|
|
47
|
+
export async function POST(req: Request) {
|
|
48
|
+
const body = await req.json();
|
|
49
|
+
|
|
50
|
+
// Save workflow YAML from visual editor
|
|
51
|
+
if (body.action === 'save-workflow' && body.yaml) {
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(FLOWS_DIR, { recursive: true });
|
|
54
|
+
const parsed = YAML.parse(body.yaml);
|
|
55
|
+
const name = parsed.name || 'unnamed';
|
|
56
|
+
const filePath = join(FLOWS_DIR, `${name}.yaml`);
|
|
57
|
+
writeFileSync(filePath, body.yaml, 'utf-8');
|
|
58
|
+
return NextResponse.json({ ok: true, name, path: filePath });
|
|
59
|
+
} catch (e: any) {
|
|
60
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Delete workflow
|
|
65
|
+
if (body.action === 'delete-workflow' && body.name) {
|
|
66
|
+
const { existsSync: ex, unlinkSync: ul } = await import('node:fs');
|
|
67
|
+
const filePath = join(FLOWS_DIR, `${body.name}.yaml`);
|
|
68
|
+
const altPath = join(FLOWS_DIR, `${body.name}.yml`);
|
|
69
|
+
const path = ex(filePath) ? filePath : ex(altPath) ? altPath : null;
|
|
70
|
+
if (!path) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
71
|
+
// Check if built-in
|
|
72
|
+
const w = listWorkflows().find(w => w.name === body.name);
|
|
73
|
+
if (w?.builtin) return NextResponse.json({ error: 'Cannot delete built-in workflow' }, { status: 400 });
|
|
74
|
+
ul(path);
|
|
75
|
+
return NextResponse.json({ ok: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Start pipeline
|
|
79
|
+
const { workflow, input } = body;
|
|
80
|
+
if (!workflow) {
|
|
81
|
+
return NextResponse.json({ error: 'workflow name required' }, { status: 400 });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const pipeline = startPipeline(workflow, input || {});
|
|
86
|
+
return NextResponse.json(pipeline);
|
|
87
|
+
} catch (e: any) {
|
|
88
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { listPlugins, getPlugin, installPlugin, uninstallPlugin, updatePluginConfig, listInstalledPlugins, getInstalledPlugin } from '@/lib/plugins/registry';
|
|
3
|
+
import { executePluginAction } from '@/lib/plugins/executor';
|
|
4
|
+
|
|
5
|
+
// GET: list plugins or get plugin details
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
const url = new URL(req.url);
|
|
8
|
+
const id = url.searchParams.get('id');
|
|
9
|
+
const installed = url.searchParams.get('installed');
|
|
10
|
+
|
|
11
|
+
if (id) {
|
|
12
|
+
// Try as plugin definition first, then as installed instance
|
|
13
|
+
const plugin = getPlugin(id);
|
|
14
|
+
const inst = getInstalledPlugin(id);
|
|
15
|
+
if (!plugin && !inst) return NextResponse.json({ error: 'Plugin not found' }, { status: 404 });
|
|
16
|
+
return NextResponse.json({
|
|
17
|
+
plugin: inst?.definition || plugin,
|
|
18
|
+
installed: !!inst,
|
|
19
|
+
config: inst?.config,
|
|
20
|
+
instanceName: inst?.instanceName,
|
|
21
|
+
source: inst?.source,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (installed === 'true') {
|
|
26
|
+
return NextResponse.json({ plugins: listInstalledPlugins() });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return NextResponse.json({ plugins: listPlugins() });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// POST: install, uninstall, update config, or test a plugin action
|
|
33
|
+
export async function POST(req: Request) {
|
|
34
|
+
const body = await req.json();
|
|
35
|
+
const { action, id, config, actionName, params } = body;
|
|
36
|
+
|
|
37
|
+
switch (action) {
|
|
38
|
+
case 'install': {
|
|
39
|
+
if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 });
|
|
40
|
+
const ok = installPlugin(id, config || {}, body.source ? { source: body.source, name: body.name } : undefined);
|
|
41
|
+
return NextResponse.json({ ok });
|
|
42
|
+
}
|
|
43
|
+
case 'create_instance': {
|
|
44
|
+
const { source, name, instanceId } = body;
|
|
45
|
+
if (!source || !name) return NextResponse.json({ error: 'source and name required' }, { status: 400 });
|
|
46
|
+
const iid = instanceId || name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
47
|
+
// Check for duplicate ID
|
|
48
|
+
const existing = getInstalledPlugin(iid);
|
|
49
|
+
if (existing) {
|
|
50
|
+
return NextResponse.json({ error: `Instance ID "${iid}" already exists (used by ${existing.instanceName || existing.definition.name}). Choose a different name.` }, { status: 409 });
|
|
51
|
+
}
|
|
52
|
+
const ok = installPlugin(iid, config || {}, { source, name });
|
|
53
|
+
return NextResponse.json({ ok, instanceId: iid });
|
|
54
|
+
}
|
|
55
|
+
case 'uninstall': {
|
|
56
|
+
if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 });
|
|
57
|
+
const ok = uninstallPlugin(id);
|
|
58
|
+
return NextResponse.json({ ok });
|
|
59
|
+
}
|
|
60
|
+
case 'update_config': {
|
|
61
|
+
if (!id || !config) return NextResponse.json({ error: 'id and config required' }, { status: 400 });
|
|
62
|
+
const ok = updatePluginConfig(id, config);
|
|
63
|
+
return NextResponse.json({ ok });
|
|
64
|
+
}
|
|
65
|
+
case 'test': {
|
|
66
|
+
if (!id || !actionName) return NextResponse.json({ error: 'id and actionName required' }, { status: 400 });
|
|
67
|
+
const inst = getInstalledPlugin(id);
|
|
68
|
+
if (!inst) return NextResponse.json({ error: 'Plugin not installed' }, { status: 400 });
|
|
69
|
+
const result = await executePluginAction(inst, actionName, params || {});
|
|
70
|
+
return NextResponse.json(result);
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getDataDir } from '@/lib/dirs';
|
|
5
|
+
|
|
6
|
+
const CONFIG_FILE = join(getDataDir(), 'preview.json');
|
|
7
|
+
|
|
8
|
+
function getPort(): number {
|
|
9
|
+
try {
|
|
10
|
+
const data = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
11
|
+
return data.port || 0;
|
|
12
|
+
} catch {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function proxy(req: NextRequest) {
|
|
18
|
+
const port = getPort();
|
|
19
|
+
if (!port) {
|
|
20
|
+
return NextResponse.json({ error: 'Preview not configured' }, { status: 503 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const url = new URL(req.url);
|
|
24
|
+
const path = url.pathname.replace(/^\/api\/preview/, '') || '/';
|
|
25
|
+
const target = `http://localhost:${port}${path}${url.search}`;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const headers: Record<string, string> = {};
|
|
29
|
+
req.headers.forEach((v, k) => {
|
|
30
|
+
if (!['host', 'connection', 'transfer-encoding'].includes(k.toLowerCase())) {
|
|
31
|
+
headers[k] = v;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const res = await fetch(target, {
|
|
36
|
+
method: req.method,
|
|
37
|
+
headers,
|
|
38
|
+
body: req.method !== 'GET' && req.method !== 'HEAD' ? await req.arrayBuffer() : undefined,
|
|
39
|
+
redirect: 'manual',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const responseHeaders = new Headers();
|
|
43
|
+
res.headers.forEach((v, k) => {
|
|
44
|
+
if (!['transfer-encoding', 'content-encoding'].includes(k.toLowerCase())) {
|
|
45
|
+
responseHeaders.set(k, v);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return new NextResponse(res.body, {
|
|
50
|
+
status: res.status,
|
|
51
|
+
headers: responseHeaders,
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
return NextResponse.json({ error: `Cannot connect to localhost:${port}` }, { status: 502 });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const GET = proxy;
|
|
59
|
+
export const POST = proxy;
|
|
60
|
+
export const PUT = proxy;
|
|
61
|
+
export const DELETE = proxy;
|
|
62
|
+
export const PATCH = proxy;
|
|
63
|
+
export const HEAD = proxy;
|
|
64
|
+
export const OPTIONS = proxy;
|