@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,398 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TerminalLauncher — unified terminal session picker and open utilities.
|
|
5
|
+
*
|
|
6
|
+
* Two main exports:
|
|
7
|
+
* 1. TerminalSessionPicker — dialog component for choosing how to open a terminal.
|
|
8
|
+
* Shows: "Current Session" (highlighted), "New Session", expandable list of other sessions.
|
|
9
|
+
*
|
|
10
|
+
* 2. openWorkspaceTerminal / buildProjectTerminalConfig — helpers to open a terminal
|
|
11
|
+
* correctly depending on context (workspace smith vs project/VibeCoding).
|
|
12
|
+
*
|
|
13
|
+
* Workspace smiths:
|
|
14
|
+
* - Need FORGE env vars injected via the forge launch script.
|
|
15
|
+
* - Must go through open_terminal API → daemon creates tmux → FloatingTerminal attaches.
|
|
16
|
+
*
|
|
17
|
+
* Project / VibeCoding:
|
|
18
|
+
* - Build profileEnv client-side from agent profile.
|
|
19
|
+
* - FloatingTerminal creates a new tmux session and runs the CLI.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useState, useEffect } from 'react';
|
|
23
|
+
|
|
24
|
+
// ─── Types ────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export interface SessionInfo {
|
|
27
|
+
id: string;
|
|
28
|
+
modified: string; // ISO string
|
|
29
|
+
size: number; // bytes
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Selection result from TerminalSessionPicker.
|
|
34
|
+
* mode='current' → open with currentSessionId (resume)
|
|
35
|
+
* mode='new' → open a fresh session (no --resume)
|
|
36
|
+
* mode='session' → open with a specific sessionId (resume)
|
|
37
|
+
*/
|
|
38
|
+
export type PickerSelection =
|
|
39
|
+
| { mode: 'current'; sessionId: string }
|
|
40
|
+
| { mode: 'new' }
|
|
41
|
+
| { mode: 'session'; sessionId: string };
|
|
42
|
+
|
|
43
|
+
// ─── Session Fetchers ─────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fetch sessions for a workspace agent (workDir-scoped, via workspace API).
|
|
47
|
+
* Used by workspace smith Open Terminal.
|
|
48
|
+
*/
|
|
49
|
+
export async function fetchAgentSessions(workspaceId: string, agentId: string): Promise<SessionInfo[]> {
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ action: 'sessions', agentId }),
|
|
55
|
+
});
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
return data.sessions || [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetch sessions for a project (project-level, via claude-sessions API).
|
|
65
|
+
* Used by ProjectDetail terminal button and VibeCoding / SessionView.
|
|
66
|
+
*/
|
|
67
|
+
export async function fetchProjectSessions(projectName: string): Promise<SessionInfo[]> {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(`/api/claude-sessions/${encodeURIComponent(projectName)}`);
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
if (!Array.isArray(data)) return [];
|
|
72
|
+
return data.map((s: any) => ({
|
|
73
|
+
id: s.sessionId || s.id || '',
|
|
74
|
+
modified: s.modified || '',
|
|
75
|
+
size: s.fileSize || s.size || 0,
|
|
76
|
+
}));
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Formatting helpers ───────────────────────────────────
|
|
83
|
+
|
|
84
|
+
function formatRelativeTime(iso: string): string {
|
|
85
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
86
|
+
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
87
|
+
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
88
|
+
return new Date(iso).toLocaleDateString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatSize(bytes: number): string {
|
|
92
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
93
|
+
if (bytes < 1_048_576) return `${(bytes / 1024).toFixed(0)}KB`;
|
|
94
|
+
return `${(bytes / 1_048_576).toFixed(1)}MB`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── SessionItem ──────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
function SessionItem({ session, onSelect }: {
|
|
100
|
+
session: SessionInfo;
|
|
101
|
+
onSelect: () => void;
|
|
102
|
+
}) {
|
|
103
|
+
const [expanded, setExpanded] = useState(false);
|
|
104
|
+
const [copied, setCopied] = useState(false);
|
|
105
|
+
|
|
106
|
+
const copyId = (e: React.MouseEvent) => {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
navigator.clipboard.writeText(session.id).then(() => {
|
|
109
|
+
setCopied(true);
|
|
110
|
+
setTimeout(() => setCopied(false), 1500);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="rounded border border-[#21262d] hover:border-[#30363d] hover:bg-[#161b22] transition-colors">
|
|
116
|
+
<div className="flex items-center gap-2 px-3 py-1.5 cursor-pointer" onClick={() => setExpanded(!expanded)}>
|
|
117
|
+
<span className="text-[8px] text-gray-600">{expanded ? '▼' : '▶'}</span>
|
|
118
|
+
<span className="text-[9px] text-gray-400 font-mono">{session.id.slice(0, 8)}</span>
|
|
119
|
+
<span className="text-[8px] text-gray-600">{formatRelativeTime(session.modified)}</span>
|
|
120
|
+
<span className="text-[8px] text-gray-600">{formatSize(session.size)}</span>
|
|
121
|
+
<button
|
|
122
|
+
onClick={e => { e.stopPropagation(); onSelect(); }}
|
|
123
|
+
className="ml-auto text-[8px] px-1.5 py-0.5 rounded bg-[#238636]/20 text-[#3fb950] hover:bg-[#238636]/40"
|
|
124
|
+
>
|
|
125
|
+
Resume
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
{expanded && (
|
|
129
|
+
<div className="px-3 pb-2 flex items-center gap-1.5">
|
|
130
|
+
<code className="text-[8px] text-gray-500 font-mono bg-[#161b22] px-1.5 py-0.5 rounded border border-[#21262d] select-all flex-1 overflow-hidden text-ellipsis">
|
|
131
|
+
{session.id}
|
|
132
|
+
</code>
|
|
133
|
+
<button
|
|
134
|
+
onClick={copyId}
|
|
135
|
+
className="text-[8px] px-1.5 py-0.5 rounded bg-[#30363d] text-gray-400 hover:text-white hover:bg-[#484f58] shrink-0"
|
|
136
|
+
>
|
|
137
|
+
{copied ? '✓' : 'Copy'}
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── TerminalSessionPicker ────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Unified dialog for choosing how to open a terminal session.
|
|
149
|
+
*
|
|
150
|
+
* Props:
|
|
151
|
+
* agentLabel — Display name for the agent / project (shown in title).
|
|
152
|
+
* currentSessionId — Bound/fixed session to show as "Current Session". null → no current.
|
|
153
|
+
* sessions — List of all available sessions (pre-fetched or lazy). If null, loading spinner shown.
|
|
154
|
+
* supportsSession — Whether the agent supports claude --resume. Default true.
|
|
155
|
+
* onSelect — Called with the picker result when user chooses an option.
|
|
156
|
+
* onCancel — Called when user dismisses without selecting.
|
|
157
|
+
*/
|
|
158
|
+
export function TerminalSessionPicker({
|
|
159
|
+
agentLabel,
|
|
160
|
+
currentSessionId,
|
|
161
|
+
sessions,
|
|
162
|
+
supportsSession = true,
|
|
163
|
+
onSelect,
|
|
164
|
+
onCancel,
|
|
165
|
+
}: {
|
|
166
|
+
agentLabel: string;
|
|
167
|
+
currentSessionId: string | null;
|
|
168
|
+
sessions: SessionInfo[] | null; // null = loading
|
|
169
|
+
supportsSession?: boolean;
|
|
170
|
+
onSelect: (selection: PickerSelection) => void;
|
|
171
|
+
onCancel: () => void;
|
|
172
|
+
}) {
|
|
173
|
+
const [showAll, setShowAll] = useState(false);
|
|
174
|
+
|
|
175
|
+
const isClaude = supportsSession !== false;
|
|
176
|
+
|
|
177
|
+
// Other sessions = all sessions except the current one
|
|
178
|
+
const otherSessions = sessions?.filter(s => s.id !== currentSessionId) ?? [];
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div
|
|
182
|
+
className="fixed inset-0 z-50 flex items-center justify-center"
|
|
183
|
+
style={{ background: 'rgba(0,0,0,0.75)' }}
|
|
184
|
+
onClick={e => { if (e.target === e.currentTarget) onCancel(); }}
|
|
185
|
+
>
|
|
186
|
+
<div
|
|
187
|
+
className="w-80 rounded-lg border border-[#30363d] p-4 shadow-xl"
|
|
188
|
+
style={{ background: '#0d1117' }}
|
|
189
|
+
>
|
|
190
|
+
<div className="text-sm font-bold text-white mb-3">⌨️ {agentLabel}</div>
|
|
191
|
+
|
|
192
|
+
<div className="space-y-2">
|
|
193
|
+
{/* Current Session — shown first, highlighted if exists */}
|
|
194
|
+
{isClaude && currentSessionId && (
|
|
195
|
+
<button
|
|
196
|
+
onClick={() => onSelect({ mode: 'current', sessionId: currentSessionId })}
|
|
197
|
+
className="w-full text-left px-3 py-2 rounded border border-[#3fb950]/60 hover:border-[#3fb950] hover:bg-[#161b22] transition-colors"
|
|
198
|
+
>
|
|
199
|
+
<div className="text-xs text-white font-semibold flex items-center gap-1.5">
|
|
200
|
+
<span className="text-[#3fb950]">●</span> Current Session
|
|
201
|
+
</div>
|
|
202
|
+
<div className="text-[9px] text-gray-500 font-mono mt-0.5">
|
|
203
|
+
{currentSessionId.slice(0, 16)}…
|
|
204
|
+
</div>
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{/* New Session */}
|
|
209
|
+
<button
|
|
210
|
+
onClick={() => onSelect({ mode: 'new' })}
|
|
211
|
+
className="w-full text-left px-3 py-2 rounded border border-[#30363d] hover:border-[#58a6ff] hover:bg-[#161b22] transition-colors"
|
|
212
|
+
>
|
|
213
|
+
<div className="text-xs text-white font-semibold">
|
|
214
|
+
{isClaude ? 'New Session' : 'Open Terminal'}
|
|
215
|
+
</div>
|
|
216
|
+
<div className="text-[9px] text-gray-500">
|
|
217
|
+
{isClaude ? 'Start fresh claude session' : 'Launch terminal'}
|
|
218
|
+
</div>
|
|
219
|
+
</button>
|
|
220
|
+
|
|
221
|
+
{/* Loading indicator */}
|
|
222
|
+
{isClaude && sessions === null && (
|
|
223
|
+
<div className="text-[9px] text-gray-600 text-center py-1">Loading sessions…</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{/* Toggle for other sessions */}
|
|
227
|
+
{isClaude && otherSessions.length > 0 && (
|
|
228
|
+
<button
|
|
229
|
+
onClick={() => setShowAll(!showAll)}
|
|
230
|
+
className="w-full text-[9px] text-gray-500 hover:text-white py-1"
|
|
231
|
+
>
|
|
232
|
+
{showAll ? '▼' : '▶'} Other sessions ({otherSessions.length})
|
|
233
|
+
</button>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{/* Other sessions list */}
|
|
237
|
+
{showAll && otherSessions.map(s => (
|
|
238
|
+
<SessionItem
|
|
239
|
+
key={s.id}
|
|
240
|
+
session={s}
|
|
241
|
+
onSelect={() => onSelect({ mode: 'session', sessionId: s.id })}
|
|
242
|
+
/>
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<button
|
|
247
|
+
onClick={onCancel}
|
|
248
|
+
className="w-full mt-3 text-[9px] text-gray-500 hover:text-white"
|
|
249
|
+
>
|
|
250
|
+
Cancel
|
|
251
|
+
</button>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── TerminalSessionPicker with lazy fetch ─────────────────
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Higher-level picker that fetches sessions automatically.
|
|
261
|
+
* Accepts a `fetchSessions` async function — result populates the session list.
|
|
262
|
+
*/
|
|
263
|
+
export function TerminalSessionPickerLazy({
|
|
264
|
+
agentLabel,
|
|
265
|
+
currentSessionId,
|
|
266
|
+
fetchSessions,
|
|
267
|
+
supportsSession = true,
|
|
268
|
+
onSelect,
|
|
269
|
+
onCancel,
|
|
270
|
+
}: {
|
|
271
|
+
agentLabel: string;
|
|
272
|
+
currentSessionId: string | null;
|
|
273
|
+
fetchSessions: () => Promise<SessionInfo[]>;
|
|
274
|
+
supportsSession?: boolean;
|
|
275
|
+
onSelect: (selection: PickerSelection) => void;
|
|
276
|
+
onCancel: () => void;
|
|
277
|
+
}) {
|
|
278
|
+
const [sessions, setSessions] = useState<SessionInfo[] | null>(null);
|
|
279
|
+
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
if (!supportsSession) {
|
|
282
|
+
setSessions([]);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
fetchSessions().then(setSessions).catch(() => setSessions([]));
|
|
286
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<TerminalSessionPicker
|
|
290
|
+
agentLabel={agentLabel}
|
|
291
|
+
currentSessionId={currentSessionId}
|
|
292
|
+
sessions={sessions}
|
|
293
|
+
supportsSession={supportsSession}
|
|
294
|
+
onSelect={onSelect}
|
|
295
|
+
onCancel={onCancel}
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ─── Workspace Terminal Open ──────────────────────────────
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Result from resolving how to open a workspace terminal.
|
|
304
|
+
* When tmuxSession is set → FloatingTerminal should attach (existingSession=tmuxSession).
|
|
305
|
+
* When tmuxSession is null → daemon couldn't create session; fall back to dialog or skip.
|
|
306
|
+
*/
|
|
307
|
+
export interface WorkspaceTerminalInfo {
|
|
308
|
+
tmuxSession: string | null;
|
|
309
|
+
cliCmd?: string;
|
|
310
|
+
cliType?: string;
|
|
311
|
+
supportsSession?: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Ask the orchestrator to create/find the tmux session for a workspace agent.
|
|
316
|
+
* Returns tmuxSession name that FloatingTerminal can attach to.
|
|
317
|
+
* This is the ONLY correct way to open a workspace terminal — ensures FORGE env vars
|
|
318
|
+
* are injected via the forge launch script (not client-side profileEnv).
|
|
319
|
+
*/
|
|
320
|
+
export async function resolveWorkspaceTerminal(
|
|
321
|
+
workspaceId: string,
|
|
322
|
+
agentId: string,
|
|
323
|
+
): Promise<WorkspaceTerminalInfo> {
|
|
324
|
+
try {
|
|
325
|
+
const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: { 'Content-Type': 'application/json' },
|
|
328
|
+
body: JSON.stringify({ action: 'open_terminal', agentId }),
|
|
329
|
+
});
|
|
330
|
+
const data = await res.json();
|
|
331
|
+
return {
|
|
332
|
+
tmuxSession: data.tmuxSession || null,
|
|
333
|
+
cliCmd: data.cliCmd,
|
|
334
|
+
cliType: data.cliType,
|
|
335
|
+
supportsSession: data.supportsSession ?? true,
|
|
336
|
+
};
|
|
337
|
+
} catch {
|
|
338
|
+
return { tmuxSession: null };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Resolve agent info for a workspace agent (resolveOnly — no session created).
|
|
344
|
+
* Used to get cliCmd, cliType, env, model, supportsSession without side effects.
|
|
345
|
+
*/
|
|
346
|
+
export async function resolveWorkspaceAgentInfo(
|
|
347
|
+
workspaceId: string,
|
|
348
|
+
agentId: string,
|
|
349
|
+
): Promise<{ cliCmd?: string; cliType?: string; env?: Record<string, string>; model?: string; supportsSession?: boolean }> {
|
|
350
|
+
try {
|
|
351
|
+
const res = await fetch(`/api/workspace/${workspaceId}/smith`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
354
|
+
body: JSON.stringify({ action: 'open_terminal', agentId, resolveOnly: true }),
|
|
355
|
+
});
|
|
356
|
+
return await res.json();
|
|
357
|
+
} catch {
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ─── Project Terminal Config ──────────────────────────────
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Result for opening a project terminal.
|
|
366
|
+
* FloatingTerminal uses profileEnv + resumeSessionId when creating a new tmux session.
|
|
367
|
+
*/
|
|
368
|
+
export interface ProjectTerminalConfig {
|
|
369
|
+
profileEnv: Record<string, string>;
|
|
370
|
+
resumeSessionId?: string;
|
|
371
|
+
cliCmd?: string;
|
|
372
|
+
cliType?: string;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Build config for opening a project terminal (VibeCoding / ProjectDetail).
|
|
377
|
+
* Agent env and model are resolved server-side via /api/agents?resolve=<agentId>.
|
|
378
|
+
* FORGE vars are NOT included here — project terminals don't use workspace context.
|
|
379
|
+
*/
|
|
380
|
+
export async function buildProjectTerminalConfig(
|
|
381
|
+
agentId: string,
|
|
382
|
+
resumeSessionId?: string,
|
|
383
|
+
): Promise<ProjectTerminalConfig> {
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch(`/api/agents?resolve=${encodeURIComponent(agentId)}`);
|
|
386
|
+
const info = await res.json();
|
|
387
|
+
const profileEnv: Record<string, string> = { ...(info.env || {}) };
|
|
388
|
+
if (info.model) profileEnv.CLAUDE_MODEL = info.model;
|
|
389
|
+
return {
|
|
390
|
+
profileEnv,
|
|
391
|
+
resumeSessionId,
|
|
392
|
+
cliCmd: info.cliCmd,
|
|
393
|
+
cliType: info.cliType,
|
|
394
|
+
};
|
|
395
|
+
} catch {
|
|
396
|
+
return { profileEnv: {}, resumeSessionId };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
interface TunnelStatus {
|
|
6
|
+
status: 'stopped' | 'starting' | 'running' | 'error';
|
|
7
|
+
url: string | null;
|
|
8
|
+
error: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function TunnelToggle() {
|
|
12
|
+
const [tunnel, setTunnel] = useState<TunnelStatus>({ status: 'stopped', url: null, error: null });
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
const [copied, setCopied] = useState(false);
|
|
15
|
+
const [isRemote, setIsRemote] = useState(false);
|
|
16
|
+
const [confirmStop, setConfirmStop] = useState(false);
|
|
17
|
+
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false);
|
|
18
|
+
const [password, setPassword] = useState('');
|
|
19
|
+
const [passwordError, setPasswordError] = useState('');
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setIsRemote(!['localhost', '127.0.0.1'].includes(window.location.hostname));
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const refresh = useCallback(() => {
|
|
26
|
+
fetch('/api/tunnel').then(r => r.json()).then(setTunnel).catch(() => {});
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
refresh();
|
|
31
|
+
const id = setInterval(refresh, 5000);
|
|
32
|
+
return () => clearInterval(id);
|
|
33
|
+
}, [refresh]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (tunnel.status !== 'starting') return;
|
|
37
|
+
const id = setInterval(refresh, 2000);
|
|
38
|
+
return () => clearInterval(id);
|
|
39
|
+
}, [tunnel.status, refresh]);
|
|
40
|
+
|
|
41
|
+
const doStop = async () => {
|
|
42
|
+
setLoading(true);
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch('/api/tunnel', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ action: 'stop' }),
|
|
48
|
+
});
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
setTunnel(data);
|
|
51
|
+
} catch {}
|
|
52
|
+
setLoading(false);
|
|
53
|
+
setConfirmStop(false);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const doStart = async (pw: string) => {
|
|
57
|
+
setLoading(true);
|
|
58
|
+
setPasswordError('');
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch('/api/tunnel', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ action: 'start', password: pw }),
|
|
64
|
+
});
|
|
65
|
+
const data = await res.json();
|
|
66
|
+
if (res.status === 403) {
|
|
67
|
+
setPasswordError('Wrong password');
|
|
68
|
+
setLoading(false);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setTunnel(data);
|
|
72
|
+
setShowPasswordPrompt(false);
|
|
73
|
+
setPassword('');
|
|
74
|
+
} catch {}
|
|
75
|
+
setLoading(false);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const copyUrl = () => {
|
|
79
|
+
if (tunnel.url) {
|
|
80
|
+
navigator.clipboard.writeText(tunnel.url);
|
|
81
|
+
setCopied(true);
|
|
82
|
+
setTimeout(() => setCopied(false), 2000);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Hide tunnel controls when accessing remotely
|
|
87
|
+
if (isRemote) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Password prompt
|
|
92
|
+
if (showPasswordPrompt) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="flex items-center gap-1.5">
|
|
95
|
+
<input
|
|
96
|
+
type="password"
|
|
97
|
+
value={password}
|
|
98
|
+
onChange={e => { setPassword(e.target.value); setPasswordError(''); }}
|
|
99
|
+
onKeyDown={e => e.key === 'Enter' && password && doStart(password)}
|
|
100
|
+
placeholder="Login password"
|
|
101
|
+
autoFocus
|
|
102
|
+
className={`w-[120px] text-[10px] px-2 py-0.5 bg-[var(--bg-tertiary)] border rounded font-mono focus:outline-none ${
|
|
103
|
+
passwordError ? 'border-[var(--red)]' : 'border-[var(--border)] focus:border-[var(--accent)]'
|
|
104
|
+
} text-[var(--text-primary)]`}
|
|
105
|
+
/>
|
|
106
|
+
<button
|
|
107
|
+
onClick={() => password && doStart(password)}
|
|
108
|
+
disabled={!password || loading}
|
|
109
|
+
className="text-[10px] px-2 py-0.5 bg-[var(--green)] text-black rounded hover:opacity-90 disabled:opacity-50"
|
|
110
|
+
>
|
|
111
|
+
{loading ? 'Starting...' : 'Start'}
|
|
112
|
+
</button>
|
|
113
|
+
<button
|
|
114
|
+
onClick={() => { setShowPasswordPrompt(false); setPassword(''); setPasswordError(''); }}
|
|
115
|
+
className="text-[10px] px-1.5 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
116
|
+
>
|
|
117
|
+
Cancel
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Stop confirmation dialog
|
|
124
|
+
if (confirmStop) {
|
|
125
|
+
return (
|
|
126
|
+
<div className="flex items-center gap-1.5">
|
|
127
|
+
<span className="text-[10px] text-[var(--text-secondary)]">Stop tunnel?</span>
|
|
128
|
+
<button
|
|
129
|
+
onClick={doStop}
|
|
130
|
+
disabled={loading}
|
|
131
|
+
className="text-[10px] px-2 py-0.5 bg-[var(--red)] text-white rounded hover:opacity-90"
|
|
132
|
+
>
|
|
133
|
+
Confirm
|
|
134
|
+
</button>
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => setConfirmStop(false)}
|
|
137
|
+
className="text-[10px] px-2 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
138
|
+
>
|
|
139
|
+
Cancel
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (tunnel.status === 'stopped' && !tunnel.error) {
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
onClick={() => setShowPasswordPrompt(true)}
|
|
149
|
+
disabled={loading}
|
|
150
|
+
className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)] transition-colors disabled:opacity-50"
|
|
151
|
+
title="Start Cloudflare Tunnel for remote access"
|
|
152
|
+
>
|
|
153
|
+
Tunnel
|
|
154
|
+
</button>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (tunnel.status === 'starting') {
|
|
159
|
+
return (
|
|
160
|
+
<span className="text-[10px] px-2 py-0.5 border border-[var(--yellow)] rounded text-[var(--yellow)]">
|
|
161
|
+
Tunnel starting...
|
|
162
|
+
</span>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (tunnel.status === 'running' && tunnel.url) {
|
|
167
|
+
return (
|
|
168
|
+
<div className="flex items-center gap-1.5">
|
|
169
|
+
<button
|
|
170
|
+
onClick={copyUrl}
|
|
171
|
+
className="text-[10px] px-2 py-0.5 border border-[var(--green)] rounded text-[var(--green)] hover:bg-[var(--green)] hover:text-black transition-colors truncate max-w-[200px]"
|
|
172
|
+
title={`Click to copy: ${tunnel.url}`}
|
|
173
|
+
>
|
|
174
|
+
{copied ? 'Copied!' : tunnel.url.replace('https://', '')}
|
|
175
|
+
</button>
|
|
176
|
+
<button
|
|
177
|
+
onClick={() => setConfirmStop(true)}
|
|
178
|
+
disabled={loading}
|
|
179
|
+
className="text-[10px] px-1.5 py-0.5 text-[var(--red)] hover:bg-[var(--red)] hover:text-white rounded transition-colors"
|
|
180
|
+
title="Stop tunnel"
|
|
181
|
+
>
|
|
182
|
+
Stop
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (tunnel.status === 'error') {
|
|
189
|
+
return (
|
|
190
|
+
<div className="flex items-center gap-1.5">
|
|
191
|
+
<span className="text-[10px] text-[var(--red)] truncate max-w-[200px]" title={tunnel.error || ''}>
|
|
192
|
+
Tunnel error
|
|
193
|
+
</span>
|
|
194
|
+
<button
|
|
195
|
+
onClick={() => setShowPasswordPrompt(true)}
|
|
196
|
+
disabled={loading}
|
|
197
|
+
className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors"
|
|
198
|
+
>
|
|
199
|
+
Retry
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|