@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,60 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadConnectors, saveConnectors } from '@/lib/server/storage'
|
|
4
|
+
import type { Connector } from '@/types'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
const connectors = loadConnectors()
|
|
8
|
+
// Merge runtime status from manager
|
|
9
|
+
try {
|
|
10
|
+
const { getConnectorStatus, isConnectorAuthenticated, hasConnectorCredentials, getConnectorQR } = await import('@/lib/server/connectors/manager')
|
|
11
|
+
for (const c of Object.values(connectors) as Connector[]) {
|
|
12
|
+
c.status = getConnectorStatus(c.id)
|
|
13
|
+
if (c.platform === 'whatsapp') {
|
|
14
|
+
c.authenticated = isConnectorAuthenticated(c.id)
|
|
15
|
+
c.hasCredentials = hasConnectorCredentials(c.id)
|
|
16
|
+
const qr = getConnectorQR(c.id)
|
|
17
|
+
if (qr) c.qrDataUrl = qr
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
} catch { /* manager not loaded yet */ }
|
|
21
|
+
return NextResponse.json(connectors)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function POST(req: Request) {
|
|
25
|
+
const body = await req.json()
|
|
26
|
+
const connectors = loadConnectors()
|
|
27
|
+
const id = crypto.randomBytes(4).toString('hex')
|
|
28
|
+
|
|
29
|
+
const connector: Connector = {
|
|
30
|
+
id,
|
|
31
|
+
name: body.name || `${body.platform} Connector`,
|
|
32
|
+
platform: body.platform,
|
|
33
|
+
agentId: body.agentId,
|
|
34
|
+
credentialId: body.credentialId || null,
|
|
35
|
+
config: body.config || {},
|
|
36
|
+
isEnabled: false,
|
|
37
|
+
status: 'stopped',
|
|
38
|
+
lastError: null,
|
|
39
|
+
createdAt: Date.now(),
|
|
40
|
+
updatedAt: Date.now(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
connectors[id] = connector
|
|
44
|
+
saveConnectors(connectors)
|
|
45
|
+
|
|
46
|
+
// Auto-start if connector has credentials (or is WhatsApp which uses QR)
|
|
47
|
+
const hasCredentials = connector.platform === 'whatsapp' || connector.platform === 'openclaw' || !!connector.credentialId
|
|
48
|
+
if (hasCredentials && body.autoStart !== false) {
|
|
49
|
+
try {
|
|
50
|
+
const { startConnector } = await import('@/lib/server/connectors/manager')
|
|
51
|
+
await startConnector(id)
|
|
52
|
+
connector.isEnabled = true
|
|
53
|
+
connector.status = 'running'
|
|
54
|
+
connectors[id] = connector
|
|
55
|
+
saveConnectors(connectors)
|
|
56
|
+
} catch { /* auto-start is best-effort */ }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return NextResponse.json(connector)
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadCredentials, saveCredentials } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id: credId } = await params
|
|
6
|
+
const creds = loadCredentials()
|
|
7
|
+
if (!creds[credId]) {
|
|
8
|
+
return new NextResponse(null, { status: 404 })
|
|
9
|
+
}
|
|
10
|
+
delete creds[credId]
|
|
11
|
+
saveCredentials(creds)
|
|
12
|
+
console.log(`[credentials] deleted ${credId}`)
|
|
13
|
+
return new NextResponse('OK')
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadCredentials, saveCredentials, encryptKey } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const creds = loadCredentials()
|
|
7
|
+
const safe: Record<string, any> = {}
|
|
8
|
+
for (const [id, c] of Object.entries(creds) as [string, any][]) {
|
|
9
|
+
safe[id] = { id: c.id, provider: c.provider, name: c.name, createdAt: c.createdAt }
|
|
10
|
+
}
|
|
11
|
+
return NextResponse.json(safe)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function POST(req: Request) {
|
|
15
|
+
const { provider, name, apiKey } = await req.json()
|
|
16
|
+
if (!provider || !apiKey) {
|
|
17
|
+
return NextResponse.json({ error: 'provider and apiKey are required' }, { status: 400 })
|
|
18
|
+
}
|
|
19
|
+
const id = 'cred_' + crypto.randomBytes(6).toString('hex')
|
|
20
|
+
const creds = loadCredentials()
|
|
21
|
+
creds[id] = {
|
|
22
|
+
id,
|
|
23
|
+
provider,
|
|
24
|
+
name: name || `${provider} key`,
|
|
25
|
+
encryptedKey: encryptKey(apiKey),
|
|
26
|
+
createdAt: Date.now(),
|
|
27
|
+
}
|
|
28
|
+
saveCredentials(creds)
|
|
29
|
+
console.log(`[credentials] stored ${id} for ${provider}`)
|
|
30
|
+
return NextResponse.json({ id, provider, name: creds[id].name, createdAt: creds[id].createdAt })
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureDaemonStarted, getDaemonStatus, runDaemonHealthCheckNow } from '@/lib/server/daemon-state'
|
|
3
|
+
|
|
4
|
+
export async function POST() {
|
|
5
|
+
ensureDaemonStarted('api/daemon/health-check:post')
|
|
6
|
+
await runDaemonHealthCheckNow()
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
ok: true,
|
|
9
|
+
status: getDaemonStatus(),
|
|
10
|
+
})
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureDaemonStarted, getDaemonStatus, startDaemon, stopDaemon } from '@/lib/server/daemon-state'
|
|
3
|
+
|
|
4
|
+
export async function GET() {
|
|
5
|
+
ensureDaemonStarted('api/daemon:get')
|
|
6
|
+
return NextResponse.json(getDaemonStatus())
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const body = await req.json().catch(() => ({}))
|
|
11
|
+
const action = body.action
|
|
12
|
+
|
|
13
|
+
if (action === 'start') {
|
|
14
|
+
startDaemon({ source: 'api/daemon:post:start', manualStart: true })
|
|
15
|
+
return NextResponse.json({ ok: true, status: 'running' })
|
|
16
|
+
} else if (action === 'stop') {
|
|
17
|
+
stopDaemon({ source: 'api/daemon:post:stop', manualStop: true })
|
|
18
|
+
return NextResponse.json({ ok: true, status: 'stopped' })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return NextResponse.json({ error: 'Invalid action. Use "start" or "stop".' }, { status: 400 })
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
|
|
6
|
+
function pickMacOS(mode: 'file' | 'folder'): string | null {
|
|
7
|
+
const script = mode === 'folder'
|
|
8
|
+
? `osascript -e 'POSIX path of (choose folder with prompt "Select a directory")'`
|
|
9
|
+
: `osascript -e 'POSIX path of (choose file with prompt "Select a file")'`
|
|
10
|
+
try {
|
|
11
|
+
return execSync(script, { timeout: 60000, encoding: 'utf-8' }).trim().replace(/\/$/, '')
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pickLinux(mode: 'file' | 'folder'): string | null {
|
|
18
|
+
// Try zenity first (GTK), then kdialog (KDE)
|
|
19
|
+
const zenityFlag = mode === 'folder' ? '--file-selection --directory' : '--file-selection'
|
|
20
|
+
const kdialogFlag = mode === 'folder' ? '--getexistingdirectory ~' : '--getopenfilename ~'
|
|
21
|
+
for (const cmd of [
|
|
22
|
+
`zenity ${zenityFlag} --title="Select a ${mode}"`,
|
|
23
|
+
`kdialog ${kdialogFlag}`,
|
|
24
|
+
]) {
|
|
25
|
+
try {
|
|
26
|
+
return execSync(cmd, { timeout: 60000, encoding: 'utf-8' }).trim()
|
|
27
|
+
} catch { /* try next */ }
|
|
28
|
+
}
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pickWindows(mode: 'file' | 'folder'): string | null {
|
|
33
|
+
if (mode === 'folder') {
|
|
34
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.FolderBrowserDialog; if($d.ShowDialog() -eq 'OK'){$d.SelectedPath}`
|
|
35
|
+
try {
|
|
36
|
+
return execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 60000, encoding: 'utf-8' }).trim() || null
|
|
37
|
+
} catch { return null }
|
|
38
|
+
}
|
|
39
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.OpenFileDialog; if($d.ShowDialog() -eq 'OK'){$d.FileName}`
|
|
40
|
+
try {
|
|
41
|
+
return execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 60000, encoding: 'utf-8' }).trim() || null
|
|
42
|
+
} catch { return null }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function POST(req: NextRequest) {
|
|
46
|
+
const { mode = 'folder' } = (await req.json().catch(() => ({}))) as { mode?: 'file' | 'folder' }
|
|
47
|
+
const platform = os.platform()
|
|
48
|
+
|
|
49
|
+
let picked: string | null = null
|
|
50
|
+
if (platform === 'darwin') picked = pickMacOS(mode)
|
|
51
|
+
else if (platform === 'win32') picked = pickWindows(mode)
|
|
52
|
+
else picked = pickLinux(mode)
|
|
53
|
+
|
|
54
|
+
if (!picked) return NextResponse.json({ directory: null, file: null })
|
|
55
|
+
|
|
56
|
+
const directory = mode === 'folder' ? picked : path.dirname(picked)
|
|
57
|
+
const file = mode === 'file' ? picked : null
|
|
58
|
+
|
|
59
|
+
return NextResponse.json({ directory, file })
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
|
|
6
|
+
export async function GET(req: NextRequest) {
|
|
7
|
+
const rawPath = req.nextUrl.searchParams.get('path')
|
|
8
|
+
const targetDir = rawPath || path.join(os.homedir(), 'Dev')
|
|
9
|
+
|
|
10
|
+
// Resolve ~ to home dir
|
|
11
|
+
const resolved = targetDir.startsWith('~')
|
|
12
|
+
? path.join(os.homedir(), targetDir.slice(1))
|
|
13
|
+
: path.resolve(targetDir)
|
|
14
|
+
|
|
15
|
+
let dirs: Array<{ name: string; path: string }> = []
|
|
16
|
+
try {
|
|
17
|
+
dirs = fs.readdirSync(resolved)
|
|
18
|
+
.filter(d => {
|
|
19
|
+
if (d.startsWith('.')) return false
|
|
20
|
+
try { return fs.statSync(path.join(resolved, d)).isDirectory() } catch { return false }
|
|
21
|
+
})
|
|
22
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
23
|
+
.map(d => ({ name: d, path: path.join(resolved, d) }))
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
const parentPath = resolved === '/' ? null : path.dirname(resolved)
|
|
27
|
+
|
|
28
|
+
return NextResponse.json({ dirs, currentPath: resolved, parentPath })
|
|
29
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadDocuments, saveDocuments } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
function normalizeObject(value: unknown): Record<string, unknown> {
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
6
|
+
return value as Record<string, unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
|
+
const { id } = await params
|
|
11
|
+
const docs = loadDocuments()
|
|
12
|
+
const doc = docs[id]
|
|
13
|
+
if (!doc) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
|
|
14
|
+
return NextResponse.json(doc)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
18
|
+
const { id } = await params
|
|
19
|
+
const body = await req.json().catch(() => ({}))
|
|
20
|
+
const docs = loadDocuments()
|
|
21
|
+
const doc = docs[id]
|
|
22
|
+
if (!doc) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
|
|
23
|
+
|
|
24
|
+
if (body.title !== undefined) doc.title = body.title
|
|
25
|
+
if (body.fileName !== undefined) doc.fileName = body.fileName
|
|
26
|
+
if (body.sourcePath !== undefined) doc.sourcePath = body.sourcePath
|
|
27
|
+
if (body.content !== undefined) doc.content = body.content
|
|
28
|
+
if (body.method !== undefined) doc.method = body.method
|
|
29
|
+
if (body.metadata !== undefined) doc.metadata = normalizeObject(body.metadata)
|
|
30
|
+
doc.textLength = typeof body.textLength === 'number'
|
|
31
|
+
? body.textLength
|
|
32
|
+
: String(doc.content || '').length
|
|
33
|
+
doc.updatedAt = Date.now()
|
|
34
|
+
|
|
35
|
+
docs[id] = doc
|
|
36
|
+
saveDocuments(docs)
|
|
37
|
+
return NextResponse.json(doc)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
41
|
+
const { id } = await params
|
|
42
|
+
const docs = loadDocuments()
|
|
43
|
+
if (!docs[id]) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
|
|
44
|
+
delete docs[id]
|
|
45
|
+
saveDocuments(docs)
|
|
46
|
+
return NextResponse.json({ ok: true })
|
|
47
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { NextResponse } from 'next/server'
|
|
3
|
+
import { loadDocuments, saveDocuments } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
function normalizeLimit(raw: string | null, fallback = 10, max = 200): number {
|
|
6
|
+
if (!raw) return fallback
|
|
7
|
+
const parsed = Number.parseInt(raw, 10)
|
|
8
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback
|
|
9
|
+
return Math.min(parsed, max)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeObject(value: unknown): Record<string, unknown> {
|
|
13
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
14
|
+
return value as Record<string, unknown>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function GET(req: Request) {
|
|
18
|
+
const docs = loadDocuments()
|
|
19
|
+
const { searchParams } = new URL(req.url)
|
|
20
|
+
const q = (searchParams.get('q') || '').trim().toLowerCase()
|
|
21
|
+
|
|
22
|
+
if (!q) {
|
|
23
|
+
return NextResponse.json(docs)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const terms = q.split(/\s+/).filter(Boolean)
|
|
27
|
+
const limit = normalizeLimit(searchParams.get('limit'), 10, 100)
|
|
28
|
+
const rows = Object.values(docs)
|
|
29
|
+
.map((doc: any) => {
|
|
30
|
+
const title = String(doc.title || '')
|
|
31
|
+
const content = String(doc.content || '')
|
|
32
|
+
const hay = `${title}\n${content}`.toLowerCase()
|
|
33
|
+
if (!terms.every((term) => hay.includes(term))) return null
|
|
34
|
+
|
|
35
|
+
let score = hay.includes(q) ? 10 : 0
|
|
36
|
+
for (const term of terms) {
|
|
37
|
+
let idx = hay.indexOf(term)
|
|
38
|
+
while (idx !== -1) {
|
|
39
|
+
score += 1
|
|
40
|
+
idx = hay.indexOf(term, idx + term.length)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const first = terms[0] || q
|
|
45
|
+
const at = hay.indexOf(first)
|
|
46
|
+
const snippetStart = at >= 0 ? Math.max(0, at - 120) : 0
|
|
47
|
+
const snippetEnd = Math.min(content.length, snippetStart + 320)
|
|
48
|
+
const snippet = content.slice(snippetStart, snippetEnd).replace(/\s+/g, ' ').trim()
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
id: doc.id,
|
|
52
|
+
title: doc.title,
|
|
53
|
+
fileName: doc.fileName,
|
|
54
|
+
sourcePath: doc.sourcePath,
|
|
55
|
+
textLength: doc.textLength || content.length,
|
|
56
|
+
updatedAt: doc.updatedAt,
|
|
57
|
+
score,
|
|
58
|
+
snippet,
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.sort((a: any, b: any) => b.score - a.score)
|
|
63
|
+
.slice(0, limit)
|
|
64
|
+
|
|
65
|
+
return NextResponse.json(rows)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function POST(req: Request) {
|
|
69
|
+
const body = await req.json().catch(() => ({}))
|
|
70
|
+
const now = Date.now()
|
|
71
|
+
const docs = loadDocuments()
|
|
72
|
+
const id = body.id || crypto.randomBytes(6).toString('hex')
|
|
73
|
+
const fileName = body.fileName || body.filename || ''
|
|
74
|
+
const title = body.title || fileName || 'Untitled Document'
|
|
75
|
+
const content = typeof body.content === 'string' ? body.content : ''
|
|
76
|
+
const metadata = normalizeObject(body.metadata)
|
|
77
|
+
|
|
78
|
+
docs[id] = {
|
|
79
|
+
id,
|
|
80
|
+
title,
|
|
81
|
+
fileName,
|
|
82
|
+
sourcePath: body.sourcePath || body.path || '',
|
|
83
|
+
method: body.method || 'manual',
|
|
84
|
+
textLength: body.textLength || content.length,
|
|
85
|
+
content,
|
|
86
|
+
metadata,
|
|
87
|
+
createdAt: body.createdAt || now,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
saveDocuments(docs)
|
|
92
|
+
return NextResponse.json(docs[id])
|
|
93
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
const MIME_MAP: Record<string, string> = {
|
|
6
|
+
'.html': 'text/html',
|
|
7
|
+
'.htm': 'text/html',
|
|
8
|
+
'.css': 'text/css',
|
|
9
|
+
'.js': 'application/javascript',
|
|
10
|
+
'.json': 'application/json',
|
|
11
|
+
'.svg': 'image/svg+xml',
|
|
12
|
+
'.png': 'image/png',
|
|
13
|
+
'.jpg': 'image/jpeg',
|
|
14
|
+
'.jpeg': 'image/jpeg',
|
|
15
|
+
'.gif': 'image/gif',
|
|
16
|
+
'.webp': 'image/webp',
|
|
17
|
+
'.txt': 'text/plain',
|
|
18
|
+
'.md': 'text/markdown',
|
|
19
|
+
'.ts': 'text/plain',
|
|
20
|
+
'.tsx': 'text/plain',
|
|
21
|
+
'.jsx': 'text/plain',
|
|
22
|
+
'.py': 'text/plain',
|
|
23
|
+
'.sh': 'text/plain',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const MAX_SIZE = 10 * 1024 * 1024 // 10MB
|
|
27
|
+
|
|
28
|
+
export async function GET(req: Request) {
|
|
29
|
+
const url = new URL(req.url)
|
|
30
|
+
const filePath = url.searchParams.get('path')
|
|
31
|
+
|
|
32
|
+
if (!filePath) {
|
|
33
|
+
return NextResponse.json({ error: 'Missing path parameter' }, { status: 400 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Resolve and normalize the path
|
|
37
|
+
const resolved = path.resolve(filePath)
|
|
38
|
+
|
|
39
|
+
// Block access to sensitive paths
|
|
40
|
+
const blocked = ['.env', 'credentials', '.ssh', '.gnupg', '.aws']
|
|
41
|
+
if (blocked.some((b) => resolved.includes(b))) {
|
|
42
|
+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(resolved)) {
|
|
46
|
+
return NextResponse.json({ error: 'File not found' }, { status: 404 })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const stat = fs.statSync(resolved)
|
|
50
|
+
if (!stat.isFile()) {
|
|
51
|
+
return NextResponse.json({ error: 'Not a file' }, { status: 400 })
|
|
52
|
+
}
|
|
53
|
+
if (stat.size > MAX_SIZE) {
|
|
54
|
+
return NextResponse.json({ error: 'File too large' }, { status: 413 })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ext = path.extname(resolved).toLowerCase()
|
|
58
|
+
const contentType = MIME_MAP[ext] || 'application/octet-stream'
|
|
59
|
+
const content = fs.readFileSync(resolved)
|
|
60
|
+
|
|
61
|
+
return new NextResponse(content, {
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': contentType,
|
|
64
|
+
'Content-Disposition': contentType.startsWith('text/') || contentType.startsWith('image/')
|
|
65
|
+
? 'inline'
|
|
66
|
+
: `attachment; filename="${path.basename(resolved)}"`,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { buildLLM } from '@/lib/server/build-llm'
|
|
3
|
+
|
|
4
|
+
/** Returns which provider/model the generate endpoints will use */
|
|
5
|
+
export async function GET() {
|
|
6
|
+
try {
|
|
7
|
+
const { provider, model } = await buildLLM()
|
|
8
|
+
return NextResponse.json({ provider, model })
|
|
9
|
+
} catch (err: any) {
|
|
10
|
+
return NextResponse.json({ provider: 'none', model: '', error: err.message })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { buildLLM } from '@/lib/server/build-llm'
|
|
4
|
+
|
|
5
|
+
const scheduleSchema = z.object({
|
|
6
|
+
name: z.string().describe('Short descriptive name for the schedule'),
|
|
7
|
+
taskPrompt: z.string().describe('The prompt/instructions the agent should execute when the schedule fires'),
|
|
8
|
+
scheduleType: z.enum(['cron', 'interval', 'once']).describe('Type of schedule'),
|
|
9
|
+
cron: z.string().optional().describe('Cron expression if scheduleType is cron, e.g. "0 9 * * 1" for every Monday at 9am'),
|
|
10
|
+
intervalMs: z.number().optional().describe('Interval in milliseconds if scheduleType is interval'),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const taskSchema = z.object({
|
|
14
|
+
title: z.string().describe('Short descriptive title for the task'),
|
|
15
|
+
description: z.string().describe('Detailed description of what needs to be done'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const skillSchema = z.object({
|
|
19
|
+
name: z.string().describe('Short name for the skill'),
|
|
20
|
+
description: z.string().describe('One sentence describing what this skill does'),
|
|
21
|
+
content: z.string().describe('Full markdown content of the skill — detailed instructions, at least 3-4 paragraphs'),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const providerSchema = z.object({
|
|
25
|
+
name: z.string().describe('Display name for the provider, e.g. "Together AI", "Groq", "z.ai"'),
|
|
26
|
+
baseUrl: z.string().describe('The OpenAI-compatible API base URL, e.g. "https://api.together.xyz/v1" or "https://api.groq.com/openai/v1"'),
|
|
27
|
+
models: z.string().describe('Comma-separated list of available model IDs, e.g. "llama-3-70b,mixtral-8x7b"'),
|
|
28
|
+
requiresApiKey: z.boolean().describe('Whether this provider requires an API key'),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const SCHEMAS: Record<string, { schema: z.ZodObject<any>; prompt: string }> = {
|
|
32
|
+
schedule: {
|
|
33
|
+
schema: scheduleSchema,
|
|
34
|
+
prompt: `You are a schedule generator for SwarmClaw, an AI agent orchestration platform. The user will describe what they want scheduled. Generate a complete schedule definition.
|
|
35
|
+
|
|
36
|
+
Choose the appropriate scheduleType:
|
|
37
|
+
- "cron" for recurring schedules (provide a cron expression)
|
|
38
|
+
- "interval" for periodic execution (provide intervalMs in milliseconds)
|
|
39
|
+
- "once" for one-time execution
|
|
40
|
+
|
|
41
|
+
Make the taskPrompt detailed and specific — it should contain everything the agent needs to know to complete the task.`,
|
|
42
|
+
},
|
|
43
|
+
task: {
|
|
44
|
+
schema: taskSchema,
|
|
45
|
+
prompt: `You are a task generator for SwarmClaw, an AI agent orchestration platform. The user will describe a task they want to create. Generate a clear task definition.
|
|
46
|
+
|
|
47
|
+
Make the description thorough and actionable — include specific goals, acceptance criteria, and any relevant context. The description should give an AI agent enough information to complete the task autonomously.`,
|
|
48
|
+
},
|
|
49
|
+
skill: {
|
|
50
|
+
schema: skillSchema,
|
|
51
|
+
prompt: `You are a skill generator for SwarmClaw, an AI agent orchestration platform. Skills are reusable markdown instruction sets that get injected into agent system prompts.
|
|
52
|
+
|
|
53
|
+
The user will describe what skill they want. Generate a comprehensive skill definition with detailed markdown content. The content should be:
|
|
54
|
+
- Written as instructions/guidelines for an AI agent
|
|
55
|
+
- Thorough and specific (at least 3-4 paragraphs)
|
|
56
|
+
- Structured with markdown headings, lists, and examples
|
|
57
|
+
- Focused on actionable guidance the agent can follow`,
|
|
58
|
+
},
|
|
59
|
+
provider: {
|
|
60
|
+
schema: providerSchema,
|
|
61
|
+
prompt: `You are a provider configuration generator for SwarmClaw, an AI agent orchestration platform. The user will name an LLM provider they want to add.
|
|
62
|
+
|
|
63
|
+
Generate the correct OpenAI-compatible API configuration. You should know the base URLs and model names for popular providers:
|
|
64
|
+
- Together AI: https://api.together.xyz/v1
|
|
65
|
+
- Groq: https://api.groq.com/openai/v1
|
|
66
|
+
- Fireworks: https://api.fireworks.ai/inference/v1
|
|
67
|
+
- Perplexity: https://api.perplexity.ai
|
|
68
|
+
- Mistral: https://api.mistral.ai/v1
|
|
69
|
+
- DeepSeek: https://api.deepseek.com/v1
|
|
70
|
+
- OpenRouter: https://openrouter.ai/api/v1
|
|
71
|
+
- xAI/Grok: https://api.x.ai/v1
|
|
72
|
+
- z.ai: https://api.z.ai/v1
|
|
73
|
+
|
|
74
|
+
List the most popular/recommended models for the provider as comma-separated values. Most providers require an API key.
|
|
75
|
+
If you don't know the exact URL, make your best guess based on common patterns (provider domain + /v1).`,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function POST(req: Request) {
|
|
80
|
+
const { type, prompt } = await req.json()
|
|
81
|
+
if (!prompt?.trim()) {
|
|
82
|
+
return NextResponse.json({ error: 'prompt is required' }, { status: 400 })
|
|
83
|
+
}
|
|
84
|
+
const config = SCHEMAS[type]
|
|
85
|
+
if (!config) {
|
|
86
|
+
return NextResponse.json({ error: `Invalid type: ${type}. Must be one of: ${Object.keys(SCHEMAS).join(', ')}` }, { status: 400 })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const { llm, provider } = await buildLLM()
|
|
91
|
+
const structured = provider === 'anthropic'
|
|
92
|
+
? llm.withStructuredOutput(config.schema)
|
|
93
|
+
: (llm as any).withStructuredOutput(config.schema, {
|
|
94
|
+
name: `${type}_definition`,
|
|
95
|
+
method: 'functionCalling',
|
|
96
|
+
})
|
|
97
|
+
const result = await structured.invoke([
|
|
98
|
+
{ role: 'system' as const, content: config.prompt },
|
|
99
|
+
{ role: 'user' as const, content: prompt },
|
|
100
|
+
], { signal: AbortSignal.timeout(60_000) })
|
|
101
|
+
return NextResponse.json(result)
|
|
102
|
+
} catch (err: unknown) {
|
|
103
|
+
const message = err instanceof Error ? err.message : 'Generation failed'
|
|
104
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getMemoryDb } from '@/lib/server/memory-db'
|
|
3
|
+
|
|
4
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const db = getMemoryDb()
|
|
7
|
+
const entry = db.get(id)
|
|
8
|
+
if (!entry || entry.category !== 'knowledge') {
|
|
9
|
+
return new NextResponse(null, { status: 404 })
|
|
10
|
+
}
|
|
11
|
+
return NextResponse.json(entry)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
15
|
+
const { id } = await params
|
|
16
|
+
const db = getMemoryDb()
|
|
17
|
+
const existing = db.get(id)
|
|
18
|
+
if (!existing || existing.category !== 'knowledge') {
|
|
19
|
+
return new NextResponse(null, { status: 404 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = await req.json().catch(() => null)
|
|
23
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
24
|
+
return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { title, content, tags } = body as Record<string, unknown>
|
|
28
|
+
|
|
29
|
+
const updates: Record<string, unknown> = {}
|
|
30
|
+
if (typeof title === 'string' && title.trim()) {
|
|
31
|
+
updates.title = title.trim()
|
|
32
|
+
}
|
|
33
|
+
if (typeof content === 'string') {
|
|
34
|
+
updates.content = content
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const existingMeta = (existing.metadata || {}) as Record<string, unknown>
|
|
38
|
+
if (Array.isArray(tags)) {
|
|
39
|
+
const normalizedTags = (tags as unknown[]).filter(
|
|
40
|
+
(t): t is string => typeof t === 'string' && t.trim().length > 0,
|
|
41
|
+
)
|
|
42
|
+
updates.metadata = { ...existingMeta, tags: normalizedTags }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const updated = db.update(id, updates)
|
|
46
|
+
if (!updated) {
|
|
47
|
+
return new NextResponse(null, { status: 404 })
|
|
48
|
+
}
|
|
49
|
+
return NextResponse.json(updated)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
53
|
+
const { id } = await params
|
|
54
|
+
const db = getMemoryDb()
|
|
55
|
+
const existing = db.get(id)
|
|
56
|
+
if (!existing || existing.category !== 'knowledge') {
|
|
57
|
+
return new NextResponse(null, { status: 404 })
|
|
58
|
+
}
|
|
59
|
+
db.delete(id)
|
|
60
|
+
return NextResponse.json({ deleted: id })
|
|
61
|
+
}
|