@swarmclawai/swarmclaw 0.2.0
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/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadAgents, loadTasks, saveTasks } from '@/lib/server/storage'
|
|
4
|
+
import { enqueueTask } from '@/lib/server/queue'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request) {
|
|
7
|
+
const { agentId, task } = await req.json()
|
|
8
|
+
if (!agentId || !task) {
|
|
9
|
+
return NextResponse.json({ error: 'agentId and task are required' }, { status: 400 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const agents = loadAgents()
|
|
13
|
+
const agent = agents[agentId]
|
|
14
|
+
if (!agent) {
|
|
15
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Create a board task and enqueue it
|
|
19
|
+
const taskId = crypto.randomBytes(4).toString('hex')
|
|
20
|
+
const now = Date.now()
|
|
21
|
+
const tasks = loadTasks()
|
|
22
|
+
tasks[taskId] = {
|
|
23
|
+
id: taskId,
|
|
24
|
+
title: task.slice(0, 80),
|
|
25
|
+
description: task,
|
|
26
|
+
status: 'backlog',
|
|
27
|
+
agentId,
|
|
28
|
+
sessionId: null,
|
|
29
|
+
result: null,
|
|
30
|
+
error: null,
|
|
31
|
+
createdAt: now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
queuedAt: null,
|
|
34
|
+
startedAt: null,
|
|
35
|
+
completedAt: null,
|
|
36
|
+
}
|
|
37
|
+
saveTasks(tasks)
|
|
38
|
+
|
|
39
|
+
// Enqueue — this sets status to queued and kicks the worker
|
|
40
|
+
enqueueTask(taskId)
|
|
41
|
+
|
|
42
|
+
return NextResponse.json({ ok: true, taskId })
|
|
43
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
const PLUGINS_DIR = path.join(process.cwd(), 'data', 'plugins')
|
|
6
|
+
|
|
7
|
+
export async function POST(req: Request) {
|
|
8
|
+
const body = await req.json()
|
|
9
|
+
const { url, filename } = body
|
|
10
|
+
|
|
11
|
+
// Validate URL
|
|
12
|
+
if (!url || typeof url !== 'string' || !url.startsWith('https://')) {
|
|
13
|
+
return NextResponse.json(
|
|
14
|
+
{ error: 'URL must be a valid HTTPS URL' },
|
|
15
|
+
{ status: 400 },
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate filename
|
|
20
|
+
if (!filename || typeof filename !== 'string' || !filename.endsWith('.js')) {
|
|
21
|
+
return NextResponse.json(
|
|
22
|
+
{ error: 'Filename must end in .js' },
|
|
23
|
+
{ status: 400 },
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Path traversal protection
|
|
28
|
+
const sanitized = path.basename(filename)
|
|
29
|
+
if (sanitized !== filename || filename.includes('..')) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: 'Invalid filename' },
|
|
32
|
+
{ status: 400 },
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url)
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw new Error(`Download failed: ${res.status}`)
|
|
40
|
+
}
|
|
41
|
+
const code = await res.text()
|
|
42
|
+
|
|
43
|
+
// Ensure plugins directory exists
|
|
44
|
+
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
45
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const dest = path.join(PLUGINS_DIR, sanitized)
|
|
49
|
+
fs.writeFileSync(dest, code, 'utf8')
|
|
50
|
+
|
|
51
|
+
return NextResponse.json({ ok: true, filename: sanitized })
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ error: 'Failed to install plugin', message: err.message },
|
|
55
|
+
{ status: 500 },
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
const REGISTRY_URL = 'https://swarmclaw.ai/registry/plugins.json'
|
|
4
|
+
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
|
|
5
|
+
|
|
6
|
+
let cache: { data: any; fetchedAt: number } | null = null
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const now = Date.now()
|
|
10
|
+
|
|
11
|
+
if (cache && now - cache.fetchedAt < CACHE_TTL) {
|
|
12
|
+
return NextResponse.json(cache.data)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(REGISTRY_URL, { cache: 'no-store' })
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new Error(`Registry returned ${res.status}`)
|
|
19
|
+
}
|
|
20
|
+
const data = await res.json()
|
|
21
|
+
cache = { data, fetchedAt: now }
|
|
22
|
+
return NextResponse.json(data)
|
|
23
|
+
} catch (err: any) {
|
|
24
|
+
// Return stale cache if available
|
|
25
|
+
if (cache) {
|
|
26
|
+
return NextResponse.json(cache.data)
|
|
27
|
+
}
|
|
28
|
+
return NextResponse.json(
|
|
29
|
+
{ error: 'Failed to fetch plugin registry', message: err.message },
|
|
30
|
+
{ status: 502 },
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
+
|
|
4
|
+
export async function GET() {
|
|
5
|
+
const manager = getPluginManager()
|
|
6
|
+
return NextResponse.json(manager.listPlugins())
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const body = await req.json()
|
|
11
|
+
const { filename, enabled } = body
|
|
12
|
+
|
|
13
|
+
if (!filename || typeof enabled !== 'boolean') {
|
|
14
|
+
return NextResponse.json({ error: 'filename and enabled required' }, { status: 400 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const manager = getPluginManager()
|
|
18
|
+
manager.setEnabled(filename, enabled)
|
|
19
|
+
|
|
20
|
+
return NextResponse.json({ ok: true })
|
|
21
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { spawn, type ChildProcess } from 'child_process'
|
|
3
|
+
import http from 'http'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { localIP } from '@/lib/server/storage'
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// MIME types for static server
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const MIME_MAP: Record<string, string> = {
|
|
13
|
+
'.html': 'text/html', '.htm': 'text/html', '.css': 'text/css',
|
|
14
|
+
'.js': 'application/javascript', '.mjs': 'application/javascript',
|
|
15
|
+
'.json': 'application/json', '.svg': 'image/svg+xml',
|
|
16
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
17
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon',
|
|
18
|
+
'.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf',
|
|
19
|
+
'.mp4': 'video/mp4', '.webm': 'video/webm',
|
|
20
|
+
'.txt': 'text/plain', '.md': 'text/plain',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Server tracking
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
interface PreviewServer {
|
|
28
|
+
type: 'static' | 'npm'
|
|
29
|
+
server?: http.Server // static server
|
|
30
|
+
proc?: ChildProcess // npm process
|
|
31
|
+
port: number
|
|
32
|
+
dir: string
|
|
33
|
+
startedAt: number
|
|
34
|
+
log: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const globalKey = '__swarmclaw_preview_servers__' as const
|
|
38
|
+
const servers: Map<string, PreviewServer> = (globalThis as unknown as Record<string, unknown>)[globalKey] as Map<string, PreviewServer>
|
|
39
|
+
?? ((globalThis as unknown as Record<string, unknown>)[globalKey] = new Map<string, PreviewServer>())
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function resolveServeDir(filePath: string): string {
|
|
46
|
+
const resolved = path.resolve(filePath)
|
|
47
|
+
try {
|
|
48
|
+
return fs.statSync(resolved).isDirectory() ? resolved : path.dirname(resolved)
|
|
49
|
+
} catch {
|
|
50
|
+
return path.dirname(resolved)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dirKey(dir: string): string {
|
|
55
|
+
return dir.replace(/\//g, '_')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function findFreePort(): Promise<number> {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const srv = http.createServer()
|
|
61
|
+
srv.listen(0, () => {
|
|
62
|
+
const addr = srv.address()
|
|
63
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0
|
|
64
|
+
srv.close(() => resolve(port))
|
|
65
|
+
})
|
|
66
|
+
srv.on('error', reject)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Project type detection
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
interface ProjectInfo {
|
|
75
|
+
type: 'npm' | 'static'
|
|
76
|
+
devCommand?: string[] // e.g. ['npm', 'run', 'dev']
|
|
77
|
+
framework?: string // e.g. 'vite', 'next', 'cra'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function detectProject(dir: string): ProjectInfo {
|
|
81
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
82
|
+
if (!fs.existsSync(pkgPath)) {
|
|
83
|
+
return { type: 'static' }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
88
|
+
const scripts = pkg.scripts || {}
|
|
89
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
90
|
+
|
|
91
|
+
// Detect framework
|
|
92
|
+
let framework = 'node'
|
|
93
|
+
if (deps.next) framework = 'next'
|
|
94
|
+
else if (deps.vite || deps['@vitejs/plugin-react']) framework = 'vite'
|
|
95
|
+
else if (deps['react-scripts']) framework = 'cra'
|
|
96
|
+
else if (deps.astro) framework = 'astro'
|
|
97
|
+
else if (deps.nuxt) framework = 'nuxt'
|
|
98
|
+
else if (deps.svelte || deps['@sveltejs/kit']) framework = 'svelte'
|
|
99
|
+
else if (deps.vue) framework = 'vue'
|
|
100
|
+
else if (deps.angular || deps['@angular/core']) framework = 'angular'
|
|
101
|
+
|
|
102
|
+
// Pick the best dev command
|
|
103
|
+
if (scripts.dev) {
|
|
104
|
+
return { type: 'npm', devCommand: ['npm', 'run', 'dev'], framework }
|
|
105
|
+
}
|
|
106
|
+
if (scripts.start) {
|
|
107
|
+
return { type: 'npm', devCommand: ['npm', 'start'], framework }
|
|
108
|
+
}
|
|
109
|
+
if (scripts.serve) {
|
|
110
|
+
return { type: 'npm', devCommand: ['npm', 'run', 'serve'], framework }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { type: 'static', framework }
|
|
114
|
+
} catch {
|
|
115
|
+
return { type: 'static' }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Static file server
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
function createStaticServer(dir: string): http.Server {
|
|
124
|
+
return http.createServer((req, res) => {
|
|
125
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
126
|
+
|
|
127
|
+
let reqPath = decodeURIComponent((req.url || '/').split('?')[0])
|
|
128
|
+
if (reqPath === '/') reqPath = '/index.html'
|
|
129
|
+
|
|
130
|
+
const filePath = path.join(dir, reqPath)
|
|
131
|
+
const normalizedFile = path.resolve(filePath)
|
|
132
|
+
|
|
133
|
+
if (!normalizedFile.startsWith(dir)) {
|
|
134
|
+
res.writeHead(403)
|
|
135
|
+
res.end('Forbidden')
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const candidates = [
|
|
140
|
+
normalizedFile,
|
|
141
|
+
normalizedFile + '.html',
|
|
142
|
+
path.join(normalizedFile, 'index.html'),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
147
|
+
const ext = path.extname(candidate).toLowerCase()
|
|
148
|
+
res.writeHead(200, { 'Content-Type': MIME_MAP[ext] || 'application/octet-stream' })
|
|
149
|
+
fs.createReadStream(candidate).pipe(res)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (fs.existsSync(normalizedFile) && fs.statSync(normalizedFile).isDirectory()) {
|
|
155
|
+
const files = fs.readdirSync(normalizedFile)
|
|
156
|
+
const links = files.map((f) => `<li><a href="${reqPath.replace(/\/$/, '')}/${f}">${f}</a></li>`).join('\n')
|
|
157
|
+
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
158
|
+
res.end(`<!DOCTYPE html><html><head><title>Index of ${reqPath}</title><style>body{font-family:monospace;padding:20px;background:#1a1a2e;color:#e0e0e0}a{color:#60a5fa}</style></head><body><h2>Index of ${reqPath}</h2><ul>${links}</ul></body></html>`)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
res.writeHead(404)
|
|
163
|
+
res.end('Not found')
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// npm dev server
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
async function startNpmServer(dir: string, command: string[], port: number): Promise<PreviewServer> {
|
|
172
|
+
// Install deps if node_modules missing
|
|
173
|
+
if (!fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
174
|
+
console.log(`[preview] Installing dependencies in ${dir}`)
|
|
175
|
+
await new Promise<void>((resolve, reject) => {
|
|
176
|
+
const install = spawn('npm', ['install'], { cwd: dir, stdio: 'pipe' })
|
|
177
|
+
install.on('close', (code) => code === 0 ? resolve() : reject(new Error(`npm install exited ${code}`)))
|
|
178
|
+
install.on('error', reject)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const env = {
|
|
183
|
+
...process.env,
|
|
184
|
+
PORT: String(port),
|
|
185
|
+
FORCE_COLOR: '0',
|
|
186
|
+
BROWSER: 'none', // CRA: don't open browser
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add --port flag for common frameworks
|
|
190
|
+
const args = [...command.slice(1)]
|
|
191
|
+
const cmdName = command[0]
|
|
192
|
+
|
|
193
|
+
const proc = spawn(cmdName, [...args, '--', '--port', String(port), '--host', '0.0.0.0'], {
|
|
194
|
+
cwd: dir,
|
|
195
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
|
+
env,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
let log = ''
|
|
200
|
+
let detectedPort = port
|
|
201
|
+
const urlRe = /https?:\/\/(?:localhost|0\.0\.0\.0|127\.0\.0\.1|[\d.]+):(\d+)/
|
|
202
|
+
|
|
203
|
+
const onData = (chunk: Buffer) => {
|
|
204
|
+
const text = chunk.toString()
|
|
205
|
+
log += text
|
|
206
|
+
if (log.length > 10000) log = log.slice(-5000)
|
|
207
|
+
const match = text.match(urlRe)
|
|
208
|
+
if (match) {
|
|
209
|
+
detectedPort = parseInt(match[1], 10)
|
|
210
|
+
const entry = servers.get(dirKey(dir))
|
|
211
|
+
if (entry) entry.port = detectedPort
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
proc.stdout?.on('data', onData)
|
|
216
|
+
proc.stderr?.on('data', onData)
|
|
217
|
+
|
|
218
|
+
const entry: PreviewServer = {
|
|
219
|
+
type: 'npm',
|
|
220
|
+
proc,
|
|
221
|
+
port,
|
|
222
|
+
dir,
|
|
223
|
+
startedAt: Date.now(),
|
|
224
|
+
log: '',
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
proc.on('close', () => {
|
|
228
|
+
servers.delete(dirKey(dir))
|
|
229
|
+
console.log(`[preview] npm server stopped for ${dir}`)
|
|
230
|
+
})
|
|
231
|
+
proc.on('error', () => servers.delete(dirKey(dir)))
|
|
232
|
+
|
|
233
|
+
servers.set(dirKey(dir), entry)
|
|
234
|
+
|
|
235
|
+
// Wait for the server to start and detect the actual port
|
|
236
|
+
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
237
|
+
entry.port = detectedPort
|
|
238
|
+
entry.log = log
|
|
239
|
+
|
|
240
|
+
return entry
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// API handler
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
function buildResponse(srv: PreviewServer) {
|
|
248
|
+
return {
|
|
249
|
+
running: true,
|
|
250
|
+
type: srv.type,
|
|
251
|
+
port: srv.port,
|
|
252
|
+
url: `http://localhost:${srv.port}`,
|
|
253
|
+
networkUrl: `http://${localIP()}:${srv.port}`,
|
|
254
|
+
dir: srv.dir,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function POST(req: Request) {
|
|
259
|
+
const { action, path: filePath } = await req.json()
|
|
260
|
+
|
|
261
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
262
|
+
return NextResponse.json({ error: 'Missing path' }, { status: 400 })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const dir = resolveServeDir(filePath)
|
|
266
|
+
const key = dirKey(dir)
|
|
267
|
+
|
|
268
|
+
if (action === 'start') {
|
|
269
|
+
if (servers.has(key)) {
|
|
270
|
+
return NextResponse.json(buildResponse(servers.get(key)!))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!fs.existsSync(dir)) {
|
|
274
|
+
return NextResponse.json({ error: 'Directory not found' }, { status: 404 })
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const project = detectProject(dir)
|
|
278
|
+
const port = await findFreePort()
|
|
279
|
+
|
|
280
|
+
if (project.type === 'npm' && project.devCommand) {
|
|
281
|
+
console.log(`[preview] Detected ${project.framework} project in ${dir}, running: ${project.devCommand.join(' ')}`)
|
|
282
|
+
try {
|
|
283
|
+
const entry = await startNpmServer(dir, project.devCommand, port)
|
|
284
|
+
return NextResponse.json({
|
|
285
|
+
...buildResponse(entry),
|
|
286
|
+
framework: project.framework,
|
|
287
|
+
})
|
|
288
|
+
} catch (err: unknown) {
|
|
289
|
+
console.error(`[preview] npm server failed, falling back to static:`, err)
|
|
290
|
+
// Fall through to static server
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Static file server
|
|
295
|
+
const server = createStaticServer(dir)
|
|
296
|
+
await new Promise<void>((resolve, reject) => {
|
|
297
|
+
server.listen(port, '0.0.0.0', () => resolve())
|
|
298
|
+
server.on('error', reject)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const entry: PreviewServer = { type: 'static', server, port, dir, startedAt: Date.now(), log: '' }
|
|
302
|
+
servers.set(key, entry)
|
|
303
|
+
console.log(`[preview] Started static server for ${dir} on port ${port}`)
|
|
304
|
+
|
|
305
|
+
return NextResponse.json(buildResponse(entry))
|
|
306
|
+
|
|
307
|
+
} else if (action === 'stop') {
|
|
308
|
+
if (servers.has(key)) {
|
|
309
|
+
const srv = servers.get(key)!
|
|
310
|
+
if (srv.type === 'npm' && srv.proc) {
|
|
311
|
+
try { srv.proc.kill('SIGTERM') } catch {}
|
|
312
|
+
try { if (srv.proc.pid) process.kill(-srv.proc.pid, 'SIGTERM') } catch {}
|
|
313
|
+
}
|
|
314
|
+
if (srv.server) srv.server.close()
|
|
315
|
+
servers.delete(key)
|
|
316
|
+
console.log(`[preview] Stopped server for ${dir}`)
|
|
317
|
+
}
|
|
318
|
+
return NextResponse.json({ running: false, dir })
|
|
319
|
+
|
|
320
|
+
} else if (action === 'status') {
|
|
321
|
+
if (servers.has(key)) {
|
|
322
|
+
return NextResponse.json(buildResponse(servers.get(key)!))
|
|
323
|
+
}
|
|
324
|
+
return NextResponse.json({ running: false, dir })
|
|
325
|
+
|
|
326
|
+
} else if (action === 'list') {
|
|
327
|
+
const list = Array.from(servers.values()).map((s) => ({
|
|
328
|
+
...buildResponse(s),
|
|
329
|
+
startedAt: s.startedAt,
|
|
330
|
+
}))
|
|
331
|
+
return NextResponse.json({ servers: list })
|
|
332
|
+
|
|
333
|
+
} else if (action === 'detect') {
|
|
334
|
+
const project = detectProject(dir)
|
|
335
|
+
return NextResponse.json({ dir, ...project })
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
339
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadModelOverrides, saveModelOverrides } from '@/lib/server/storage'
|
|
3
|
+
import { getProviderList } from '@/lib/providers'
|
|
4
|
+
|
|
5
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const overrides = loadModelOverrides()
|
|
8
|
+
const providers = getProviderList()
|
|
9
|
+
const provider = providers.find((p) => p.id === id)
|
|
10
|
+
if (!provider) return new NextResponse(null, { status: 404 })
|
|
11
|
+
return NextResponse.json({ models: provider.models, hasOverride: !!overrides[id] })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
15
|
+
const { id } = await params
|
|
16
|
+
const body = await req.json()
|
|
17
|
+
const overrides = loadModelOverrides()
|
|
18
|
+
overrides[id] = body.models || []
|
|
19
|
+
saveModelOverrides(overrides)
|
|
20
|
+
return NextResponse.json({ models: overrides[id] })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
24
|
+
const { id } = await params
|
|
25
|
+
const overrides = loadModelOverrides()
|
|
26
|
+
delete overrides[id]
|
|
27
|
+
saveModelOverrides(overrides)
|
|
28
|
+
return NextResponse.json({ ok: true })
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadProviderConfigs, saveProviderConfigs } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const configs = loadProviderConfigs()
|
|
7
|
+
const config = configs[id]
|
|
8
|
+
if (!config) return new NextResponse(null, { status: 404 })
|
|
9
|
+
return NextResponse.json(config)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
13
|
+
const { id } = await params
|
|
14
|
+
const body = await req.json()
|
|
15
|
+
const configs = loadProviderConfigs()
|
|
16
|
+
const existing = configs[id]
|
|
17
|
+
if (!existing) return new NextResponse(null, { status: 404 })
|
|
18
|
+
configs[id] = { ...existing, ...body, id, updatedAt: Date.now() }
|
|
19
|
+
saveProviderConfigs(configs)
|
|
20
|
+
return NextResponse.json(configs[id])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
24
|
+
const { id } = await params
|
|
25
|
+
const configs = loadProviderConfigs()
|
|
26
|
+
if (!configs[id]) return new NextResponse(null, { status: 404 })
|
|
27
|
+
// Only allow deleting custom providers
|
|
28
|
+
if (configs[id].type === 'builtin') {
|
|
29
|
+
return NextResponse.json({ error: 'Cannot delete built-in providers' }, { status: 400 })
|
|
30
|
+
}
|
|
31
|
+
delete configs[id]
|
|
32
|
+
saveProviderConfigs(configs)
|
|
33
|
+
return NextResponse.json({ ok: true })
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
/** Fetch locally installed models from Ollama API */
|
|
4
|
+
export async function GET(req: Request) {
|
|
5
|
+
const { searchParams } = new URL(req.url)
|
|
6
|
+
const endpoint = searchParams.get('endpoint') || 'http://localhost:11434'
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${endpoint.replace(/\/+$/, '')}/api/tags`, {
|
|
10
|
+
signal: AbortSignal.timeout(5000),
|
|
11
|
+
})
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
return NextResponse.json({ models: [], error: `Ollama returned ${res.status}` })
|
|
14
|
+
}
|
|
15
|
+
const data = await res.json()
|
|
16
|
+
const models = (data.models || []).map((m: any) => ({
|
|
17
|
+
name: m.name?.replace(/:latest$/, '') || m.name,
|
|
18
|
+
size: m.size,
|
|
19
|
+
modified: m.modified_at,
|
|
20
|
+
}))
|
|
21
|
+
return NextResponse.json({ models })
|
|
22
|
+
} catch (err: any) {
|
|
23
|
+
return NextResponse.json({
|
|
24
|
+
models: [],
|
|
25
|
+
error: err.code === 'ECONNREFUSED'
|
|
26
|
+
? 'Ollama is not running'
|
|
27
|
+
: err.message || 'Failed to connect',
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { probeOpenClawHealth } from '@/lib/server/openclaw-health'
|
|
3
|
+
|
|
4
|
+
function parseIntBounded(value: unknown, fallback: number, min: number, max: number): number {
|
|
5
|
+
const parsed = typeof value === 'number'
|
|
6
|
+
? value
|
|
7
|
+
: typeof value === 'string'
|
|
8
|
+
? Number.parseInt(value, 10)
|
|
9
|
+
: Number.NaN
|
|
10
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
11
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function GET(req: Request) {
|
|
15
|
+
const { searchParams } = new URL(req.url)
|
|
16
|
+
const endpoint = searchParams.get('endpoint')
|
|
17
|
+
const credentialId = searchParams.get('credentialId')
|
|
18
|
+
const token = searchParams.get('token')
|
|
19
|
+
const model = searchParams.get('model')
|
|
20
|
+
const timeoutMs = parseIntBounded(searchParams.get('timeoutMs'), 8000, 1000, 30000)
|
|
21
|
+
const result = await probeOpenClawHealth({ endpoint, credentialId, token, model, timeoutMs })
|
|
22
|
+
return NextResponse.json(result)
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { getProviderList } from '@/lib/providers'
|
|
4
|
+
import { loadProviderConfigs, saveProviderConfigs } from '@/lib/server/storage'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
return NextResponse.json(getProviderList())
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request) {
|
|
11
|
+
const body = await req.json()
|
|
12
|
+
const configs = loadProviderConfigs()
|
|
13
|
+
const id = body.id || `custom-${crypto.randomBytes(4).toString('hex')}`
|
|
14
|
+
configs[id] = {
|
|
15
|
+
id,
|
|
16
|
+
name: body.name || 'Custom Provider',
|
|
17
|
+
type: 'custom',
|
|
18
|
+
baseUrl: body.baseUrl || '',
|
|
19
|
+
models: body.models || [],
|
|
20
|
+
requiresApiKey: body.requiresApiKey ?? true,
|
|
21
|
+
credentialId: body.credentialId || null,
|
|
22
|
+
isEnabled: body.isEnabled ?? true,
|
|
23
|
+
createdAt: Date.now(),
|
|
24
|
+
updatedAt: Date.now(),
|
|
25
|
+
}
|
|
26
|
+
saveProviderConfigs(configs)
|
|
27
|
+
return NextResponse.json(configs[id])
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getRunById } from '@/lib/server/session-run-manager'
|
|
3
|
+
|
|
4
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const run = getRunById(id)
|
|
7
|
+
if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
8
|
+
return NextResponse.json(run)
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { listRuns, type SessionRunStatus } from '@/lib/server/session-run-manager'
|
|
3
|
+
|
|
4
|
+
export async function GET(req: Request) {
|
|
5
|
+
const { searchParams } = new URL(req.url)
|
|
6
|
+
const sessionId = searchParams.get('sessionId') || undefined
|
|
7
|
+
const status = (searchParams.get('status') || undefined) as SessionRunStatus | undefined
|
|
8
|
+
const limitRaw = searchParams.get('limit')
|
|
9
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined
|
|
10
|
+
|
|
11
|
+
const runs = listRuns({ sessionId, status, limit })
|
|
12
|
+
return NextResponse.json(runs)
|
|
13
|
+
}
|