@aion0/forge 0.5.26 → 0.5.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
- package/RELEASE_NOTES.md +11 -28
- package/app/api/terminal-bell/route.ts +6 -2
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { getClaudeDir } from '@/lib/dirs';
|
|
7
|
+
|
|
8
|
+
const SKILLS_DIR = join(getClaudeDir(), 'skills');
|
|
9
|
+
const COMMANDS_DIR = join(getClaudeDir(), 'commands');
|
|
10
|
+
|
|
11
|
+
function md5(content: string): string {
|
|
12
|
+
return createHash('md5').update(content).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Recursively list files in a directory */
|
|
16
|
+
function listFiles(dir: string, prefix = ''): { path: string; size: number }[] {
|
|
17
|
+
const files: { path: string; size: number }[] = [];
|
|
18
|
+
if (!existsSync(dir)) return files;
|
|
19
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
20
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
21
|
+
const fullPath = join(dir, entry.name);
|
|
22
|
+
if (entry.isFile()) {
|
|
23
|
+
files.push({ path: relPath, size: statSync(fullPath).size });
|
|
24
|
+
} else if (entry.isDirectory()) {
|
|
25
|
+
files.push(...listFiles(fullPath, relPath));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return files;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveDir(name: string, type: string, projectPath?: string): string {
|
|
32
|
+
if (type === 'skill') {
|
|
33
|
+
return projectPath
|
|
34
|
+
? join(projectPath, '.claude', 'skills', name)
|
|
35
|
+
: join(SKILLS_DIR, name);
|
|
36
|
+
}
|
|
37
|
+
// Command: could be single file or directory
|
|
38
|
+
const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
|
|
39
|
+
const dirPath = join(base, name);
|
|
40
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) return dirPath;
|
|
41
|
+
// Single file — return parent dir
|
|
42
|
+
return base;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Scan a directory for all installed skills/commands */
|
|
46
|
+
function scanLocalItems(projectPath?: string): { name: string; type: 'skill' | 'command'; scope: string; fileCount: number; projectPath?: string }[] {
|
|
47
|
+
const items: { name: string; type: 'skill' | 'command'; scope: string; fileCount: number; projectPath?: string }[] = [];
|
|
48
|
+
|
|
49
|
+
// Scan skills directories
|
|
50
|
+
const skillsDirs = [
|
|
51
|
+
{ dir: SKILLS_DIR, scope: 'global' as const },
|
|
52
|
+
...(projectPath ? [{ dir: join(projectPath, '.claude', 'skills'), scope: 'project' as const }] : []),
|
|
53
|
+
];
|
|
54
|
+
for (const { dir, scope } of skillsDirs) {
|
|
55
|
+
if (!existsSync(dir)) continue;
|
|
56
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
57
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
58
|
+
const files = listFiles(join(dir, entry.name));
|
|
59
|
+
items.push({ name: entry.name, type: 'skill', scope, fileCount: files.length });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Scan commands directories
|
|
65
|
+
const cmdDirs = [
|
|
66
|
+
{ dir: COMMANDS_DIR, scope: 'global' as const },
|
|
67
|
+
...(projectPath ? [{ dir: join(projectPath, '.claude', 'commands'), scope: 'project' as const }] : []),
|
|
68
|
+
];
|
|
69
|
+
for (const { dir, scope } of cmdDirs) {
|
|
70
|
+
if (!existsSync(dir)) continue;
|
|
71
|
+
// Collect names, merge file + dir with same name
|
|
72
|
+
const seen = new Map<string, { name: string; type: 'command'; scope: typeof scope; fileCount: number }>();
|
|
73
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
74
|
+
if (entry.name.startsWith('.')) continue;
|
|
75
|
+
const cmdName = entry.isFile() ? entry.name.replace(/\.md$/, '') : entry.name;
|
|
76
|
+
if (entry.isFile() && !entry.name.endsWith('.md')) continue;
|
|
77
|
+
const existing = seen.get(cmdName);
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
const files = listFiles(join(dir, entry.name));
|
|
80
|
+
const count = files.length + (existing?.fileCount || 0);
|
|
81
|
+
seen.set(cmdName, { name: cmdName, type: 'command', scope, fileCount: count });
|
|
82
|
+
} else if (entry.isFile()) {
|
|
83
|
+
if (existing) {
|
|
84
|
+
existing.fileCount += 1;
|
|
85
|
+
} else {
|
|
86
|
+
seen.set(cmdName, { name: cmdName, type: 'command', scope, fileCount: 1 });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
items.push(...seen.values());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return items;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// GET /api/skills/local?name=X&type=skill|command&project=PATH
|
|
97
|
+
// action=scan → list ALL locally installed skills/commands
|
|
98
|
+
// action=files → list installed files for a specific item
|
|
99
|
+
// action=read&path=FILE → read file content + hash
|
|
100
|
+
export async function GET(req: Request) {
|
|
101
|
+
const { searchParams } = new URL(req.url);
|
|
102
|
+
const action = searchParams.get('action') || 'files';
|
|
103
|
+
const name = searchParams.get('name') || '';
|
|
104
|
+
const type = searchParams.get('type') || 'command';
|
|
105
|
+
const projectPath = searchParams.get('project') || '';
|
|
106
|
+
|
|
107
|
+
if (action === 'scan') {
|
|
108
|
+
const scanAll = searchParams.get('all') === '1';
|
|
109
|
+
if (scanAll) {
|
|
110
|
+
// Scan global + all configured projects
|
|
111
|
+
const { loadSettings } = require('@/lib/settings');
|
|
112
|
+
const settings = loadSettings();
|
|
113
|
+
const allItems: any[] = [];
|
|
114
|
+
// Global
|
|
115
|
+
allItems.push(...scanLocalItems());
|
|
116
|
+
// All projects
|
|
117
|
+
for (const root of (settings.projectRoots || [])) {
|
|
118
|
+
const resolvedRoot = root.replace(/^~/, homedir());
|
|
119
|
+
if (!existsSync(resolvedRoot)) continue;
|
|
120
|
+
for (const entry of readdirSync(resolvedRoot, { withFileTypes: true })) {
|
|
121
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
122
|
+
const pp = join(resolvedRoot, entry.name);
|
|
123
|
+
const projItems = scanLocalItems(pp)
|
|
124
|
+
.filter(i => i.scope === 'project')
|
|
125
|
+
.map(i => ({ ...i, projectPath: pp, scope: entry.name }));
|
|
126
|
+
allItems.push(...projItems);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return NextResponse.json({ items: allItems });
|
|
130
|
+
}
|
|
131
|
+
const items = scanLocalItems(projectPath || undefined);
|
|
132
|
+
return NextResponse.json({ items });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (action === 'files') {
|
|
136
|
+
if (type === 'skill') {
|
|
137
|
+
const dir = resolveDir(name, type, projectPath || undefined);
|
|
138
|
+
return NextResponse.json({ files: listFiles(dir) });
|
|
139
|
+
} else {
|
|
140
|
+
// Command: collect both single .md file and directory contents
|
|
141
|
+
const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
|
|
142
|
+
const singleFile = join(base, `${name}.md`);
|
|
143
|
+
const dirPath = join(base, name);
|
|
144
|
+
const files: { path: string; size: number }[] = [];
|
|
145
|
+
// Single .md file at root
|
|
146
|
+
if (existsSync(singleFile)) {
|
|
147
|
+
files.push({ path: `${name}.md`, size: statSync(singleFile).size });
|
|
148
|
+
}
|
|
149
|
+
// Directory contents
|
|
150
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
151
|
+
files.push(...listFiles(dirPath).map(f => ({ path: `${name}/${f.path}`, size: f.size })));
|
|
152
|
+
}
|
|
153
|
+
return NextResponse.json({ files });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (action === 'read') {
|
|
158
|
+
const filePath = searchParams.get('path') || '';
|
|
159
|
+
let fullPath: string;
|
|
160
|
+
if (type === 'skill') {
|
|
161
|
+
const dir = resolveDir(name, type, projectPath || undefined);
|
|
162
|
+
fullPath = join(dir, filePath);
|
|
163
|
+
} else {
|
|
164
|
+
// filePath from files action is like "name.md" or "name/sub/file.md"
|
|
165
|
+
// Resolve relative to commands base directory
|
|
166
|
+
const base = projectPath ? join(projectPath, '.claude', 'commands') : COMMANDS_DIR;
|
|
167
|
+
fullPath = join(base, filePath);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!existsSync(fullPath)) return NextResponse.json({ content: '', hash: '' });
|
|
171
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
172
|
+
return NextResponse.json({ content, hash: md5(content) });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// POST /api/skills/local — save, install-local, delete-local
|
|
179
|
+
export async function POST(req: Request) {
|
|
180
|
+
const body = await req.json();
|
|
181
|
+
const action = body.action || 'save';
|
|
182
|
+
|
|
183
|
+
// Install local skill/command to another project or global
|
|
184
|
+
if (action === 'install-local') {
|
|
185
|
+
const { installLocal } = require('@/lib/skills');
|
|
186
|
+
const { name, type, sourceProject, target, force } = body;
|
|
187
|
+
const result = installLocal(name, type, sourceProject || undefined, target, !!force);
|
|
188
|
+
return NextResponse.json(result, { status: result.ok ? 200 : 400 });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Delete local skill/command
|
|
192
|
+
if (action === 'delete-local') {
|
|
193
|
+
const { deleteLocal } = require('@/lib/skills');
|
|
194
|
+
const { name, type, project } = body;
|
|
195
|
+
const ok = deleteLocal(name, type, project || undefined);
|
|
196
|
+
return NextResponse.json({ ok });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Save edited file (default action)
|
|
200
|
+
const { name, type, project, path: filePath, content, expectedHash } = body;
|
|
201
|
+
|
|
202
|
+
let fullPath: string;
|
|
203
|
+
if (type === 'skill') {
|
|
204
|
+
const dir = project
|
|
205
|
+
? join(project, '.claude', 'skills', name)
|
|
206
|
+
: join(SKILLS_DIR, name);
|
|
207
|
+
fullPath = join(dir, filePath);
|
|
208
|
+
} else {
|
|
209
|
+
const base = project ? join(project, '.claude', 'commands') : COMMANDS_DIR;
|
|
210
|
+
const dirPath = join(base, name);
|
|
211
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
212
|
+
fullPath = join(dirPath, filePath);
|
|
213
|
+
} else {
|
|
214
|
+
fullPath = join(base, filePath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check for concurrent modification
|
|
219
|
+
if (expectedHash && existsSync(fullPath)) {
|
|
220
|
+
const current = readFileSync(fullPath, 'utf-8');
|
|
221
|
+
if (md5(current) !== expectedHash) {
|
|
222
|
+
return NextResponse.json({ ok: false, error: 'File was modified externally. Reload and try again.' }, { status: 409 });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
227
|
+
return NextResponse.json({ ok: true, hash: md5(content) });
|
|
228
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
syncSkills,
|
|
4
|
+
listSkills,
|
|
5
|
+
installGlobal,
|
|
6
|
+
installProject,
|
|
7
|
+
uninstallGlobal,
|
|
8
|
+
uninstallProject,
|
|
9
|
+
refreshInstallState,
|
|
10
|
+
checkLocalModified,
|
|
11
|
+
purgeDeletedSkill,
|
|
12
|
+
} from '@/lib/skills';
|
|
13
|
+
import { loadSettings } from '@/lib/settings';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
|
|
16
|
+
function getProjectPaths(): string[] {
|
|
17
|
+
const settings = loadSettings();
|
|
18
|
+
const roots = (settings.projectRoots || []).map(r => r.replace(/^~/, homedir()));
|
|
19
|
+
const paths: string[] = [];
|
|
20
|
+
for (const root of roots) {
|
|
21
|
+
try {
|
|
22
|
+
const { readdirSync, statSync } = require('node:fs');
|
|
23
|
+
const { join } = require('node:path');
|
|
24
|
+
for (const name of readdirSync(root)) {
|
|
25
|
+
const p = join(root, name);
|
|
26
|
+
try { if (statSync(p).isDirectory() && !name.startsWith('.')) paths.push(p); } catch {}
|
|
27
|
+
}
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
return paths;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// GET /api/skills — list skills, get file list, or get file content
|
|
34
|
+
export async function GET(req: Request) {
|
|
35
|
+
const { searchParams } = new URL(req.url);
|
|
36
|
+
const action = searchParams.get('action');
|
|
37
|
+
const name = searchParams.get('name');
|
|
38
|
+
|
|
39
|
+
// List files in a skill/command directory
|
|
40
|
+
if (action === 'files' && name) {
|
|
41
|
+
try {
|
|
42
|
+
const settings = loadSettings();
|
|
43
|
+
const repoUrl = settings.skillsRepoUrl || 'https://raw.githubusercontent.com/aiwatching/forge-skills/main';
|
|
44
|
+
const matchRepo = repoUrl.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
45
|
+
const repo = matchRepo ? matchRepo[1] : 'aiwatching/forge-skills';
|
|
46
|
+
|
|
47
|
+
// Try skills/ first, then commands/ (repo may not have commands/ dir)
|
|
48
|
+
let res = await fetch(`https://api.github.com/repos/${repo}/contents/skills/${name}`, {
|
|
49
|
+
headers: { 'Accept': 'application/vnd.github.v3+json' },
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
res = await fetch(`https://api.github.com/repos/${repo}/contents/commands/${name}`, {
|
|
53
|
+
headers: { 'Accept': 'application/vnd.github.v3+json' },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok) return NextResponse.json({ files: [] });
|
|
57
|
+
|
|
58
|
+
const items = await res.json();
|
|
59
|
+
const files: { name: string; path: string; type: string }[] = [];
|
|
60
|
+
|
|
61
|
+
const recurse = async (list: any[], prefix = '') => {
|
|
62
|
+
for (const item of list) {
|
|
63
|
+
if (item.type === 'file') {
|
|
64
|
+
files.push({ name: item.name, path: prefix + item.name, type: 'file' });
|
|
65
|
+
} else if (item.type === 'dir') {
|
|
66
|
+
files.push({ name: item.name + '/', path: prefix + item.name, type: 'dir' });
|
|
67
|
+
// Fetch subdirectory contents
|
|
68
|
+
try {
|
|
69
|
+
const subRes = await fetch(item.url, {
|
|
70
|
+
headers: { 'Accept': 'application/vnd.github.v3+json' },
|
|
71
|
+
});
|
|
72
|
+
if (subRes.ok) {
|
|
73
|
+
const subItems = await subRes.json();
|
|
74
|
+
if (Array.isArray(subItems)) {
|
|
75
|
+
await recurse(subItems, prefix + item.name + '/');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
await recurse(Array.isArray(items) ? items : []);
|
|
83
|
+
|
|
84
|
+
// Sort: dirs first, then files
|
|
85
|
+
files.sort((a, b) => {
|
|
86
|
+
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
|
|
87
|
+
return a.name.localeCompare(b.name);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return NextResponse.json({ files });
|
|
91
|
+
} catch {
|
|
92
|
+
return NextResponse.json({ files: [] });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get content of a specific file
|
|
97
|
+
if (action === 'file' && name) {
|
|
98
|
+
const filePath = searchParams.get('path') || 'skill.md';
|
|
99
|
+
try {
|
|
100
|
+
const settings = loadSettings();
|
|
101
|
+
const baseUrl = settings.skillsRepoUrl || 'https://raw.githubusercontent.com/aiwatching/forge-skills/main';
|
|
102
|
+
// Try skills/ first, then commands/
|
|
103
|
+
let res = await fetch(`${baseUrl}/skills/${name}/${filePath}`);
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
res = await fetch(`${baseUrl}/commands/${name}/${filePath}`);
|
|
106
|
+
}
|
|
107
|
+
if (!res.ok) return NextResponse.json({ content: '(Not found)' });
|
|
108
|
+
const content = await res.text();
|
|
109
|
+
return NextResponse.json({ content });
|
|
110
|
+
} catch {
|
|
111
|
+
return NextResponse.json({ content: '(Failed to load)' });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const skills = listSkills();
|
|
115
|
+
const projects = getProjectPaths().map(p => ({ path: p, name: p.split('/').pop() || p }));
|
|
116
|
+
return NextResponse.json({ skills, projects });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// POST /api/skills — sync, install, uninstall
|
|
120
|
+
export async function POST(req: Request) {
|
|
121
|
+
const body = await req.json();
|
|
122
|
+
|
|
123
|
+
if (body.action === 'sync') {
|
|
124
|
+
const result = await syncSkills();
|
|
125
|
+
if (result.synced > 0) {
|
|
126
|
+
refreshInstallState(getProjectPaths());
|
|
127
|
+
}
|
|
128
|
+
return NextResponse.json(result);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (body.action === 'check-modified') {
|
|
132
|
+
try {
|
|
133
|
+
const modified = await checkLocalModified(body.name);
|
|
134
|
+
return NextResponse.json({ modified });
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return NextResponse.json({ modified: false, error: String(e) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (body.action === 'install') {
|
|
141
|
+
const { name, target } = body; // target: 'global' | projectPath
|
|
142
|
+
if (!name || !target) return NextResponse.json({ ok: false, error: 'name and target required' }, { status: 400 });
|
|
143
|
+
try {
|
|
144
|
+
if (target === 'global') {
|
|
145
|
+
await installGlobal(name);
|
|
146
|
+
} else {
|
|
147
|
+
await installProject(name, target);
|
|
148
|
+
}
|
|
149
|
+
return NextResponse.json({ ok: true });
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (body.action === 'uninstall') {
|
|
156
|
+
const { name, target } = body;
|
|
157
|
+
if (!name || !target) return NextResponse.json({ ok: false, error: 'name and target required' }, { status: 400 });
|
|
158
|
+
try {
|
|
159
|
+
if (target === 'global') {
|
|
160
|
+
uninstallGlobal(name);
|
|
161
|
+
} else {
|
|
162
|
+
uninstallProject(name, target);
|
|
163
|
+
}
|
|
164
|
+
return NextResponse.json({ ok: true });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (body.action === 'purge-deleted') {
|
|
171
|
+
const { name } = body;
|
|
172
|
+
if (!name) return NextResponse.json({ ok: false, error: 'name required' }, { status: 400 });
|
|
173
|
+
try {
|
|
174
|
+
purgeDeletedSkill(name);
|
|
175
|
+
return NextResponse.json({ ok: true });
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
182
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getDataDir } from '@/lib/dirs';
|
|
5
|
+
|
|
6
|
+
function getTemplatesDir(): string {
|
|
7
|
+
const dir = join(getDataDir(), 'smith-templates');
|
|
8
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
9
|
+
return dir;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SmithTemplate {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
icon: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
config: Record<string, any>; // agent config without id/dependsOn/boundSessionId
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// List all smith templates
|
|
23
|
+
export async function GET() {
|
|
24
|
+
const dir = getTemplatesDir();
|
|
25
|
+
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
26
|
+
const templates: SmithTemplate[] = [];
|
|
27
|
+
for (const f of files) {
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
30
|
+
templates.push(data);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
templates.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
34
|
+
return NextResponse.json({ templates });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Save or delete a smith template
|
|
38
|
+
export async function POST(req: Request) {
|
|
39
|
+
const body = await req.json();
|
|
40
|
+
const { action } = body;
|
|
41
|
+
|
|
42
|
+
if (action === 'delete') {
|
|
43
|
+
const { id } = body;
|
|
44
|
+
if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 });
|
|
45
|
+
const fp = join(getTemplatesDir(), `${id}.json`);
|
|
46
|
+
if (existsSync(fp)) unlinkSync(fp);
|
|
47
|
+
return NextResponse.json({ ok: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Save (create or update)
|
|
51
|
+
const { config, name, icon, description } = body;
|
|
52
|
+
if (!config || !name) {
|
|
53
|
+
return NextResponse.json({ error: 'config and name required' }, { status: 400 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strip runtime/instance-specific fields
|
|
57
|
+
const cleanConfig = { ...config };
|
|
58
|
+
delete cleanConfig.id;
|
|
59
|
+
delete cleanConfig.dependsOn;
|
|
60
|
+
delete cleanConfig.boundSessionId;
|
|
61
|
+
delete cleanConfig.tmuxSession;
|
|
62
|
+
delete cleanConfig.content;
|
|
63
|
+
delete cleanConfig.entries;
|
|
64
|
+
delete cleanConfig.type;
|
|
65
|
+
|
|
66
|
+
const id = body.id || `smith-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
|
|
69
|
+
const template: SmithTemplate = {
|
|
70
|
+
id,
|
|
71
|
+
name: name.trim(),
|
|
72
|
+
icon: icon || cleanConfig.icon || '🤖',
|
|
73
|
+
description: description?.trim() || '',
|
|
74
|
+
config: cleanConfig,
|
|
75
|
+
createdAt: body.id ? (body.createdAt || now) : now,
|
|
76
|
+
updatedAt: now,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
writeFileSync(join(getTemplatesDir(), `${id}.json`), JSON.stringify(template, null, 2));
|
|
80
|
+
return NextResponse.json({ ok: true, template });
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getSessionManager } from '@/lib/session-manager';
|
|
3
|
+
import { listAvailableProviders } from '@/src/core/providers/registry';
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const manager = getSessionManager();
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
sessions: manager.list(),
|
|
9
|
+
providers: listAvailableProviders(),
|
|
10
|
+
usage: manager.getUsageSummary(),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDb } from '@/src/core/db/database';
|
|
3
|
+
import { getDbPath } from '@/src/config';
|
|
4
|
+
|
|
5
|
+
function db() { return getDb(getDbPath()); }
|
|
6
|
+
|
|
7
|
+
export async function GET(req: NextRequest) {
|
|
8
|
+
const type = req.nextUrl.searchParams.get('type') || 'projects';
|
|
9
|
+
try {
|
|
10
|
+
const row = db().prepare('SELECT data FROM tab_state WHERE type = ?').get(type) as any;
|
|
11
|
+
if (row?.data) return NextResponse.json(JSON.parse(row.data));
|
|
12
|
+
} catch {}
|
|
13
|
+
return NextResponse.json({ tabs: [], activeTabId: 0 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function POST(req: NextRequest) {
|
|
17
|
+
const type = req.nextUrl.searchParams.get('type') || 'projects';
|
|
18
|
+
try {
|
|
19
|
+
const body = await req.json();
|
|
20
|
+
db().prepare('INSERT OR REPLACE INTO tab_state (type, data) VALUES (?, ?)').run(type, JSON.stringify(body));
|
|
21
|
+
return NextResponse.json({ ok: true });
|
|
22
|
+
} catch (e: any) {
|
|
23
|
+
return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getTask, cancelTask, deleteTask, retryTask, updateTask } from '@/lib/task-manager';
|
|
3
|
+
import { getProjectInfo } from '@/lib/projects';
|
|
4
|
+
|
|
5
|
+
// Get task details (including full log)
|
|
6
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
const { id } = await params;
|
|
8
|
+
const task = getTask(id);
|
|
9
|
+
if (!task) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
10
|
+
return NextResponse.json(task);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Actions: cancel, retry
|
|
14
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
15
|
+
const { id } = await params;
|
|
16
|
+
const { action } = await req.json();
|
|
17
|
+
|
|
18
|
+
if (action === 'cancel') {
|
|
19
|
+
const ok = cancelTask(id);
|
|
20
|
+
return NextResponse.json({ ok });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (action === 'retry') {
|
|
24
|
+
const newTask = retryTask(id);
|
|
25
|
+
if (!newTask) return NextResponse.json({ error: 'Cannot retry this task' }, { status: 400 });
|
|
26
|
+
return NextResponse.json(newTask);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Edit a task
|
|
33
|
+
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
34
|
+
const { id } = await params;
|
|
35
|
+
const body = await req.json();
|
|
36
|
+
// Resolve projectName to projectPath if changed
|
|
37
|
+
if (body.projectName && !body.projectPath) {
|
|
38
|
+
const project = getProjectInfo(body.projectName);
|
|
39
|
+
if (project) body.projectPath = project.path;
|
|
40
|
+
}
|
|
41
|
+
const updated = updateTask(id, body);
|
|
42
|
+
if (!updated) return NextResponse.json({ error: 'Cannot edit this task' }, { status: 400 });
|
|
43
|
+
return NextResponse.json(updated);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Delete a task
|
|
47
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
48
|
+
const { id } = await params;
|
|
49
|
+
deleteTask(id);
|
|
50
|
+
return NextResponse.json({ ok: true });
|
|
51
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getTask } from '@/lib/task-manager';
|
|
2
|
+
import { onTaskEvent } from '@/lib/task-manager';
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic';
|
|
5
|
+
export const runtime = 'nodejs';
|
|
6
|
+
|
|
7
|
+
// SSE stream for real-time task log updates
|
|
8
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params;
|
|
10
|
+
|
|
11
|
+
const task = getTask(id);
|
|
12
|
+
if (!task) {
|
|
13
|
+
return new Response('Task not found', { status: 404 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const encoder = new TextEncoder();
|
|
17
|
+
let unsubscribe: (() => void) | null = null;
|
|
18
|
+
let heartbeat: ReturnType<typeof setInterval> | null = null;
|
|
19
|
+
let closed = false;
|
|
20
|
+
|
|
21
|
+
const stream = new ReadableStream({
|
|
22
|
+
start(controller) {
|
|
23
|
+
// Send existing log entries
|
|
24
|
+
for (const entry of task.log) {
|
|
25
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', entry })}\n\n`));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Send current status
|
|
29
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'status', status: task.status })}\n\n`));
|
|
30
|
+
|
|
31
|
+
// Heartbeat
|
|
32
|
+
heartbeat = setInterval(() => {
|
|
33
|
+
if (!closed) {
|
|
34
|
+
try { controller.enqueue(encoder.encode(': heartbeat\n\n')); } catch { cleanup(); }
|
|
35
|
+
}
|
|
36
|
+
}, 15000);
|
|
37
|
+
|
|
38
|
+
// Listen for new events
|
|
39
|
+
unsubscribe = onTaskEvent((taskId, event, data) => {
|
|
40
|
+
if (taskId !== id || closed) return;
|
|
41
|
+
try {
|
|
42
|
+
if (event === 'log') {
|
|
43
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', entry: data })}\n\n`));
|
|
44
|
+
} else if (event === 'status') {
|
|
45
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'status', status: data })}\n\n`));
|
|
46
|
+
// Close stream when task is done
|
|
47
|
+
if (data === 'done' || data === 'failed' || data === 'cancelled') {
|
|
48
|
+
// Send final task data
|
|
49
|
+
const finalTask = getTask(id);
|
|
50
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'complete', task: finalTask })}\n\n`));
|
|
51
|
+
cleanup();
|
|
52
|
+
controller.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch { cleanup(); }
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
cancel() {
|
|
59
|
+
cleanup();
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function cleanup() {
|
|
64
|
+
closed = true;
|
|
65
|
+
if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }
|
|
66
|
+
if (unsubscribe) { unsubscribe(); unsubscribe = null; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new Response(stream, {
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'text/event-stream',
|
|
72
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
73
|
+
Connection: 'keep-alive',
|
|
74
|
+
'X-Accel-Buffering': 'no',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|