@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,1504 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import process from 'node:process'
|
|
7
|
+
|
|
8
|
+
const DEFAULT_BASE_URL =
|
|
9
|
+
process.env.SWARMCLAW_URL
|
|
10
|
+
|| process.env.SWARMCLAW_BASE_URL
|
|
11
|
+
|| 'http://localhost:3456'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ACCESS_KEY = process.env.SWARMCLAW_ACCESS_KEY || ''
|
|
14
|
+
|
|
15
|
+
const GLOBAL_FLAG_KEYS = new Set([
|
|
16
|
+
'url',
|
|
17
|
+
'key',
|
|
18
|
+
'raw',
|
|
19
|
+
'json',
|
|
20
|
+
'file',
|
|
21
|
+
'help',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const SHORT_FLAG_ALIASES = {
|
|
25
|
+
h: 'help',
|
|
26
|
+
u: 'url',
|
|
27
|
+
k: 'key',
|
|
28
|
+
r: 'raw',
|
|
29
|
+
j: 'json',
|
|
30
|
+
f: 'file',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const GROUP_ALIASES = {
|
|
34
|
+
agent: 'agents',
|
|
35
|
+
session: 'sessions',
|
|
36
|
+
task: 'tasks',
|
|
37
|
+
schedule: 'schedules',
|
|
38
|
+
connector: 'connectors',
|
|
39
|
+
provider: 'providers',
|
|
40
|
+
credential: 'credentials',
|
|
41
|
+
secret: 'secrets',
|
|
42
|
+
skill: 'skills',
|
|
43
|
+
run: 'runs',
|
|
44
|
+
log: 'logs',
|
|
45
|
+
plugin: 'plugins',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DEFAULT_ACTION_ALIASES = {
|
|
49
|
+
ls: 'list',
|
|
50
|
+
del: 'delete',
|
|
51
|
+
rm: 'delete',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function appendFlagValue(flags, key, value) {
|
|
55
|
+
if (flags[key] === undefined) {
|
|
56
|
+
flags[key] = value
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(flags[key])) {
|
|
60
|
+
flags[key].push(value)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
flags[key] = [flags[key], value]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseArgv(argv) {
|
|
67
|
+
const flags = {}
|
|
68
|
+
const positionals = []
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
71
|
+
const arg = argv[i]
|
|
72
|
+
if (arg === '--') {
|
|
73
|
+
positionals.push(...argv.slice(i + 1))
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (arg.startsWith('--')) {
|
|
78
|
+
const eq = arg.indexOf('=')
|
|
79
|
+
if (eq >= 0) {
|
|
80
|
+
appendFlagValue(flags, arg.slice(2, eq), arg.slice(eq + 1))
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
const key = arg.slice(2)
|
|
84
|
+
const next = argv[i + 1]
|
|
85
|
+
if (next !== undefined && !next.startsWith('-')) {
|
|
86
|
+
appendFlagValue(flags, key, next)
|
|
87
|
+
i += 1
|
|
88
|
+
} else {
|
|
89
|
+
appendFlagValue(flags, key, true)
|
|
90
|
+
}
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (/^-[a-zA-Z]$/.test(arg)) {
|
|
95
|
+
const short = arg.slice(1)
|
|
96
|
+
const key = SHORT_FLAG_ALIASES[short] || short
|
|
97
|
+
const next = argv[i + 1]
|
|
98
|
+
if (next !== undefined && !next.startsWith('-')) {
|
|
99
|
+
appendFlagValue(flags, key, next)
|
|
100
|
+
i += 1
|
|
101
|
+
} else {
|
|
102
|
+
appendFlagValue(flags, key, true)
|
|
103
|
+
}
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (/^-[a-zA-Z]=/.test(arg)) {
|
|
108
|
+
const short = arg[1]
|
|
109
|
+
const key = SHORT_FLAG_ALIASES[short] || short
|
|
110
|
+
appendFlagValue(flags, key, arg.slice(3))
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
positionals.push(arg)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { flags, positionals }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getFlag(flags, key) {
|
|
121
|
+
const value = flags[key]
|
|
122
|
+
if (Array.isArray(value)) return value[value.length - 1]
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function toBoolean(value, label) {
|
|
127
|
+
if (typeof value === 'boolean') return value
|
|
128
|
+
if (typeof value === 'string') {
|
|
129
|
+
const normalized = value.trim().toLowerCase()
|
|
130
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true
|
|
131
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no') return false
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Invalid boolean for "${label}": ${String(value)}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function toOptionalBoolean(value, defaultValue = false) {
|
|
137
|
+
if (value === undefined) return defaultValue
|
|
138
|
+
if (typeof value === 'boolean') return value
|
|
139
|
+
if (typeof value === 'string') {
|
|
140
|
+
const normalized = value.trim().toLowerCase()
|
|
141
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true
|
|
142
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no') return false
|
|
143
|
+
}
|
|
144
|
+
return Boolean(value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function toInteger(value, label) {
|
|
148
|
+
if (typeof value === 'number' && Number.isInteger(value)) return value
|
|
149
|
+
const parsed = Number.parseInt(String(value), 10)
|
|
150
|
+
if (!Number.isFinite(parsed)) throw new Error(`Invalid integer for "${label}": ${String(value)}`)
|
|
151
|
+
return parsed
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toCamelCase(input) {
|
|
155
|
+
return input.replace(/-([a-zA-Z0-9])/g, (_, c) => c.toUpperCase())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function coerceValue(value) {
|
|
159
|
+
if (Array.isArray(value)) return value.map(coerceValue)
|
|
160
|
+
if (typeof value !== 'string') return value
|
|
161
|
+
const trimmed = value.trim()
|
|
162
|
+
if (trimmed === 'true') return true
|
|
163
|
+
if (trimmed === 'false') return false
|
|
164
|
+
if (trimmed === 'null') return null
|
|
165
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed)
|
|
166
|
+
if (
|
|
167
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}'))
|
|
168
|
+
|| (trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
169
|
+
) {
|
|
170
|
+
try {
|
|
171
|
+
return JSON.parse(trimmed)
|
|
172
|
+
} catch {
|
|
173
|
+
return value
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return value
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isPlainObject(value) {
|
|
180
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseJsonString(value, label) {
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(value)
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
188
|
+
throw new Error(`Invalid JSON in ${label}: ${msg}`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseJsonFile(filePath) {
|
|
193
|
+
const resolved = path.resolve(process.cwd(), String(filePath))
|
|
194
|
+
const text = fs.readFileSync(resolved, 'utf8')
|
|
195
|
+
return parseJsonString(text, `file ${resolved}`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildBodyFromFlags(flags, reservedKeys = []) {
|
|
199
|
+
const reserved = new Set([...GLOBAL_FLAG_KEYS, ...reservedKeys])
|
|
200
|
+
const body = {}
|
|
201
|
+
|
|
202
|
+
for (const [rawKey, rawValue] of Object.entries(flags)) {
|
|
203
|
+
if (reserved.has(rawKey)) continue
|
|
204
|
+
body[toCamelCase(rawKey)] = coerceValue(rawValue)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return body
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function parseBody(flags, reservedKeys = [], opts = {}) {
|
|
211
|
+
const { allowEmpty = true } = opts
|
|
212
|
+
const derived = buildBodyFromFlags(flags, reservedKeys)
|
|
213
|
+
|
|
214
|
+
const explicitBodies = []
|
|
215
|
+
const fileValue = getFlag(flags, 'file')
|
|
216
|
+
const jsonValue = getFlag(flags, 'json')
|
|
217
|
+
|
|
218
|
+
if (fileValue !== undefined) {
|
|
219
|
+
explicitBodies.push(parseJsonFile(fileValue))
|
|
220
|
+
}
|
|
221
|
+
if (jsonValue !== undefined) {
|
|
222
|
+
explicitBodies.push(parseJsonString(String(jsonValue), '--json'))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (explicitBodies.length === 0) {
|
|
226
|
+
if (!allowEmpty && Object.keys(derived).length === 0) {
|
|
227
|
+
throw new Error('No payload provided. Use --json, --file, or explicit flags.')
|
|
228
|
+
}
|
|
229
|
+
return derived
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let explicit = {}
|
|
233
|
+
for (const item of explicitBodies) {
|
|
234
|
+
if (!isPlainObject(item)) {
|
|
235
|
+
if (Object.keys(derived).length > 0 || explicitBodies.length > 1) {
|
|
236
|
+
throw new Error('Non-object JSON payload cannot be merged with flag-based payload.')
|
|
237
|
+
}
|
|
238
|
+
return item
|
|
239
|
+
}
|
|
240
|
+
explicit = { ...explicit, ...item }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const merged = { ...derived, ...explicit }
|
|
244
|
+
if (!allowEmpty && Object.keys(merged).length === 0) {
|
|
245
|
+
throw new Error('No payload provided. Use --json, --file, or explicit flags.')
|
|
246
|
+
}
|
|
247
|
+
return merged
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function parseCsvList(value) {
|
|
251
|
+
if (value === undefined) return []
|
|
252
|
+
if (Array.isArray(value)) {
|
|
253
|
+
return value.flatMap((item) => parseCsvList(item))
|
|
254
|
+
}
|
|
255
|
+
return String(value)
|
|
256
|
+
.split(',')
|
|
257
|
+
.map((part) => part.trim())
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseIdList(value, fallback = []) {
|
|
262
|
+
const fromFlag = parseCsvList(value)
|
|
263
|
+
if (fromFlag.length) return fromFlag
|
|
264
|
+
return fallback.filter(Boolean)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function requireArg(args, index, label) {
|
|
268
|
+
const value = args[index]
|
|
269
|
+
if (!value) throw new Error(`Missing ${label}`)
|
|
270
|
+
return value
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function pickById(record, id, label) {
|
|
274
|
+
if (!record || typeof record !== 'object') {
|
|
275
|
+
throw new Error(`Unexpected ${label} response format.`)
|
|
276
|
+
}
|
|
277
|
+
const item = record[id]
|
|
278
|
+
if (!item) throw new Error(`${label} "${id}" not found.`)
|
|
279
|
+
return item
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveGroupName(rawName) {
|
|
283
|
+
return GROUP_ALIASES[rawName] || rawName
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function resolveCommandName(groupDef, rawName) {
|
|
287
|
+
if (!rawName) return rawName
|
|
288
|
+
const aliases = {
|
|
289
|
+
...DEFAULT_ACTION_ALIASES,
|
|
290
|
+
...(groupDef.aliases || {}),
|
|
291
|
+
}
|
|
292
|
+
return aliases[rawName] || rawName
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function buildApiUrl(baseUrl, apiPath, query) {
|
|
296
|
+
const normalizedBase = baseUrl.replace(/\/+$/, '')
|
|
297
|
+
const normalizedPath = apiPath.startsWith('/') ? apiPath : `/${apiPath}`
|
|
298
|
+
const url = new URL(`${normalizedBase}/api${normalizedPath}`)
|
|
299
|
+
|
|
300
|
+
if (query) {
|
|
301
|
+
for (const [key, value] of Object.entries(query)) {
|
|
302
|
+
if (value === undefined || value === null || value === '') continue
|
|
303
|
+
if (Array.isArray(value)) {
|
|
304
|
+
for (const item of value) {
|
|
305
|
+
if (item !== undefined && item !== null && item !== '') {
|
|
306
|
+
url.searchParams.append(key, String(item))
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
url.searchParams.set(key, String(value))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return url.toString()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function parseResponsePayload(text, contentType) {
|
|
319
|
+
if (!text) return null
|
|
320
|
+
if ((contentType || '').includes('application/json')) {
|
|
321
|
+
try {
|
|
322
|
+
return JSON.parse(text)
|
|
323
|
+
} catch {
|
|
324
|
+
return text
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return text
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatHttpError(status, payload) {
|
|
331
|
+
if (payload && typeof payload === 'object') {
|
|
332
|
+
const err = payload.error || payload.message
|
|
333
|
+
if (err) return `HTTP ${status}: ${String(err)}`
|
|
334
|
+
return `HTTP ${status}: ${JSON.stringify(payload)}`
|
|
335
|
+
}
|
|
336
|
+
if (typeof payload === 'string' && payload.trim()) {
|
|
337
|
+
return `HTTP ${status}: ${payload.trim()}`
|
|
338
|
+
}
|
|
339
|
+
return `HTTP ${status}`
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function request(ctx, method, apiPath, options = {}) {
|
|
343
|
+
const {
|
|
344
|
+
query,
|
|
345
|
+
body,
|
|
346
|
+
headers = {},
|
|
347
|
+
rawBody = false,
|
|
348
|
+
stream = false,
|
|
349
|
+
} = options
|
|
350
|
+
|
|
351
|
+
const reqHeaders = { ...headers }
|
|
352
|
+
if (ctx.accessKey) reqHeaders['X-Access-Key'] = ctx.accessKey
|
|
353
|
+
|
|
354
|
+
let reqBody
|
|
355
|
+
if (body !== undefined) {
|
|
356
|
+
if (rawBody) {
|
|
357
|
+
reqBody = body
|
|
358
|
+
} else {
|
|
359
|
+
reqHeaders['Content-Type'] = 'application/json'
|
|
360
|
+
reqBody = JSON.stringify(body)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const url = buildApiUrl(ctx.baseUrl, apiPath, query)
|
|
365
|
+
const res = await fetch(url, {
|
|
366
|
+
method,
|
|
367
|
+
headers: reqHeaders,
|
|
368
|
+
body: reqBody,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
if (stream) {
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
const text = await res.text()
|
|
374
|
+
const payload = parseResponsePayload(text, res.headers.get('content-type') || '')
|
|
375
|
+
throw new Error(formatHttpError(res.status, payload))
|
|
376
|
+
}
|
|
377
|
+
return res
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const text = await res.text()
|
|
381
|
+
const payload = parseResponsePayload(text, res.headers.get('content-type') || '')
|
|
382
|
+
if (!res.ok) throw new Error(formatHttpError(res.status, payload))
|
|
383
|
+
return payload
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function printResult(ctx, value) {
|
|
387
|
+
if (ctx.raw && typeof value === 'string') {
|
|
388
|
+
process.stdout.write(value)
|
|
389
|
+
if (!value.endsWith('\n')) process.stdout.write('\n')
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function printChatEvent(event, rawMode) {
|
|
396
|
+
if (rawMode) {
|
|
397
|
+
process.stdout.write(`${JSON.stringify(event)}\n`)
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const type = event?.t
|
|
402
|
+
if ((type === 'd' || type === 'r') && typeof event.text === 'string') {
|
|
403
|
+
process.stdout.write(event.text)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (type === 'md' && typeof event.text === 'string') {
|
|
408
|
+
try {
|
|
409
|
+
const parsed = JSON.parse(event.text)
|
|
410
|
+
if (parsed?.run?.id) {
|
|
411
|
+
const parts = [
|
|
412
|
+
`id=${parsed.run.id}`,
|
|
413
|
+
`status=${parsed.run.status || 'unknown'}`,
|
|
414
|
+
]
|
|
415
|
+
if (parsed.run.position !== undefined) {
|
|
416
|
+
parts.push(`position=${parsed.run.position}`)
|
|
417
|
+
}
|
|
418
|
+
process.stderr.write(`\n[run] ${parts.join(' ')}\n`)
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
} catch {
|
|
422
|
+
// fall through and print metadata text
|
|
423
|
+
}
|
|
424
|
+
process.stderr.write(`\n[meta] ${event.text}\n`)
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (type === 'tool_call') {
|
|
429
|
+
process.stderr.write(
|
|
430
|
+
`\n[tool:call] ${event.toolName || ''}${event.toolInput ? ` ${event.toolInput}` : ''}\n`,
|
|
431
|
+
)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (type === 'tool_result') {
|
|
436
|
+
process.stderr.write(
|
|
437
|
+
`\n[tool:result] ${event.toolName || ''}${event.toolOutput ? ` ${event.toolOutput}` : ''}\n`,
|
|
438
|
+
)
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (type === 'err') {
|
|
443
|
+
process.stderr.write(`\n[error] ${event.text || 'Unknown error'}\n`)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function parseSseBlock(block) {
|
|
449
|
+
const lines = block.replace(/\r\n/g, '\n').split('\n')
|
|
450
|
+
const dataLines = []
|
|
451
|
+
for (const line of lines) {
|
|
452
|
+
if (line.startsWith('data:')) {
|
|
453
|
+
dataLines.push(line.slice(5).trimStart())
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (!dataLines.length) return null
|
|
457
|
+
const raw = dataLines.join('\n')
|
|
458
|
+
try {
|
|
459
|
+
return JSON.parse(raw)
|
|
460
|
+
} catch {
|
|
461
|
+
return { t: 'raw', text: raw }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function consumeSseStream(stream, onEvent) {
|
|
466
|
+
const reader = stream.getReader()
|
|
467
|
+
const decoder = new TextDecoder()
|
|
468
|
+
let buffer = ''
|
|
469
|
+
|
|
470
|
+
while (true) {
|
|
471
|
+
const { value, done } = await reader.read()
|
|
472
|
+
if (done) break
|
|
473
|
+
buffer += decoder.decode(value, { stream: true })
|
|
474
|
+
buffer = buffer.replace(/\r\n/g, '\n')
|
|
475
|
+
|
|
476
|
+
let boundary = buffer.indexOf('\n\n')
|
|
477
|
+
while (boundary >= 0) {
|
|
478
|
+
const chunk = buffer.slice(0, boundary)
|
|
479
|
+
buffer = buffer.slice(boundary + 2)
|
|
480
|
+
const event = parseSseBlock(chunk)
|
|
481
|
+
if (event) onEvent(event)
|
|
482
|
+
boundary = buffer.indexOf('\n\n')
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const tail = buffer.trim()
|
|
487
|
+
if (tail) {
|
|
488
|
+
const event = parseSseBlock(tail)
|
|
489
|
+
if (event) onEvent(event)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function runSessionChat(ctx, sessionId, body) {
|
|
494
|
+
const response = await request(ctx, 'POST', `/sessions/${encodeURIComponent(sessionId)}/chat`, {
|
|
495
|
+
body,
|
|
496
|
+
stream: true,
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
if (!response.body) throw new Error('Chat stream did not return a response body.')
|
|
500
|
+
await consumeSseStream(response.body, (event) => printChatEvent(event, ctx.raw))
|
|
501
|
+
if (!ctx.raw) process.stdout.write('\n')
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function createContext(flags) {
|
|
505
|
+
const rawFlag = getFlag(flags, 'raw')
|
|
506
|
+
return {
|
|
507
|
+
baseUrl: String(getFlag(flags, 'url') || DEFAULT_BASE_URL).replace(/\/+$/, ''),
|
|
508
|
+
accessKey: String(getFlag(flags, 'key') || DEFAULT_ACCESS_KEY),
|
|
509
|
+
raw: toOptionalBoolean(rawFlag, false),
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const GROUPS = {
|
|
514
|
+
auth: {
|
|
515
|
+
description: 'Access key bootstrap/auth checks',
|
|
516
|
+
commands: {
|
|
517
|
+
status: {
|
|
518
|
+
summary: 'Check auth setup status',
|
|
519
|
+
usage: 'auth status',
|
|
520
|
+
run: async ({ ctx }) => {
|
|
521
|
+
const data = await request({ ...ctx, accessKey: '' }, 'GET', '/auth')
|
|
522
|
+
printResult(ctx, data)
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
login: {
|
|
526
|
+
summary: 'Validate an access key',
|
|
527
|
+
usage: 'auth login --key <ACCESS_KEY> | auth login <ACCESS_KEY>',
|
|
528
|
+
run: async ({ ctx, args, flags }) => {
|
|
529
|
+
const key = String(getFlag(flags, 'key') || args[0] || '')
|
|
530
|
+
if (!key) throw new Error('Missing key. Use auth login --key <ACCESS_KEY>.')
|
|
531
|
+
const data = await request({ ...ctx, accessKey: '' }, 'POST', '/auth', { body: { key } })
|
|
532
|
+
printResult(ctx, data)
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
agents: {
|
|
538
|
+
description: 'Manage agents',
|
|
539
|
+
commands: {
|
|
540
|
+
list: {
|
|
541
|
+
summary: 'List agents',
|
|
542
|
+
usage: 'agents list',
|
|
543
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/agents')),
|
|
544
|
+
},
|
|
545
|
+
get: {
|
|
546
|
+
summary: 'Get one agent by id',
|
|
547
|
+
usage: 'agents get <id>',
|
|
548
|
+
run: async ({ ctx, args }) => {
|
|
549
|
+
const id = requireArg(args, 0, 'agent id')
|
|
550
|
+
const all = await request(ctx, 'GET', '/agents')
|
|
551
|
+
printResult(ctx, pickById(all, id, 'Agent'))
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
create: {
|
|
555
|
+
summary: 'Create an agent',
|
|
556
|
+
usage: 'agents create --name "Agent" --provider openai --model gpt-4o',
|
|
557
|
+
run: async ({ ctx, flags }) => {
|
|
558
|
+
const body = parseBody(flags)
|
|
559
|
+
printResult(ctx, await request(ctx, 'POST', '/agents', { body }))
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
update: {
|
|
563
|
+
summary: 'Update an agent',
|
|
564
|
+
usage: 'agents update <id> --name "New Name"',
|
|
565
|
+
run: async ({ ctx, args, flags }) => {
|
|
566
|
+
const id = requireArg(args, 0, 'agent id')
|
|
567
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
568
|
+
printResult(ctx, await request(ctx, 'PUT', `/agents/${encodeURIComponent(id)}`, { body }))
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
delete: {
|
|
572
|
+
summary: 'Delete an agent',
|
|
573
|
+
usage: 'agents delete <id>',
|
|
574
|
+
run: async ({ ctx, args }) => {
|
|
575
|
+
const id = requireArg(args, 0, 'agent id')
|
|
576
|
+
printResult(ctx, await request(ctx, 'DELETE', `/agents/${encodeURIComponent(id)}`))
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
sessions: {
|
|
582
|
+
description: 'Manage sessions and session actions',
|
|
583
|
+
aliases: { send: 'chat' },
|
|
584
|
+
commands: {
|
|
585
|
+
list: {
|
|
586
|
+
summary: 'List sessions',
|
|
587
|
+
usage: 'sessions list',
|
|
588
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/sessions')),
|
|
589
|
+
},
|
|
590
|
+
get: {
|
|
591
|
+
summary: 'Get one session by id',
|
|
592
|
+
usage: 'sessions get <id>',
|
|
593
|
+
run: async ({ ctx, args }) => {
|
|
594
|
+
const id = requireArg(args, 0, 'session id')
|
|
595
|
+
const all = await request(ctx, 'GET', '/sessions')
|
|
596
|
+
printResult(ctx, pickById(all, id, 'Session'))
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
create: {
|
|
600
|
+
summary: 'Create a session',
|
|
601
|
+
usage: 'sessions create --name "My Session" --cwd ~/Dev --provider claude-cli',
|
|
602
|
+
run: async ({ ctx, flags }) => {
|
|
603
|
+
const body = parseBody(flags)
|
|
604
|
+
printResult(ctx, await request(ctx, 'POST', '/sessions', { body }))
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
update: {
|
|
608
|
+
summary: 'Update a session',
|
|
609
|
+
usage: 'sessions update <id> --name "Renamed Session"',
|
|
610
|
+
run: async ({ ctx, args, flags }) => {
|
|
611
|
+
const id = requireArg(args, 0, 'session id')
|
|
612
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
613
|
+
printResult(ctx, await request(ctx, 'PUT', `/sessions/${encodeURIComponent(id)}`, { body }))
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
delete: {
|
|
617
|
+
summary: 'Delete one session',
|
|
618
|
+
usage: 'sessions delete <id>',
|
|
619
|
+
run: async ({ ctx, args }) => {
|
|
620
|
+
const id = requireArg(args, 0, 'session id')
|
|
621
|
+
printResult(ctx, await request(ctx, 'DELETE', `/sessions/${encodeURIComponent(id)}`))
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
'delete-many': {
|
|
625
|
+
summary: 'Delete multiple sessions',
|
|
626
|
+
usage: 'sessions delete-many --ids id1,id2,id3',
|
|
627
|
+
run: async ({ ctx, args, flags }) => {
|
|
628
|
+
const ids = parseIdList(getFlag(flags, 'ids'), args)
|
|
629
|
+
if (!ids.length) throw new Error('Missing ids. Use --ids id1,id2 or pass ids as args.')
|
|
630
|
+
printResult(ctx, await request(ctx, 'DELETE', '/sessions', { body: { ids } }))
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
clear: {
|
|
634
|
+
summary: 'Clear session messages and resume ids',
|
|
635
|
+
usage: 'sessions clear <id>',
|
|
636
|
+
run: async ({ ctx, args }) => {
|
|
637
|
+
const id = requireArg(args, 0, 'session id')
|
|
638
|
+
printResult(ctx, await request(ctx, 'POST', `/sessions/${encodeURIComponent(id)}/clear`))
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
stop: {
|
|
642
|
+
summary: 'Stop active/queued runs in a session',
|
|
643
|
+
usage: 'sessions stop <id>',
|
|
644
|
+
run: async ({ ctx, args }) => {
|
|
645
|
+
const id = requireArg(args, 0, 'session id')
|
|
646
|
+
printResult(ctx, await request(ctx, 'POST', `/sessions/${encodeURIComponent(id)}/stop`))
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
messages: {
|
|
650
|
+
summary: 'Get session messages',
|
|
651
|
+
usage: 'sessions messages <id>',
|
|
652
|
+
run: async ({ ctx, args }) => {
|
|
653
|
+
const id = requireArg(args, 0, 'session id')
|
|
654
|
+
printResult(ctx, await request(ctx, 'GET', `/sessions/${encodeURIComponent(id)}/messages`))
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
chat: {
|
|
658
|
+
summary: 'Send chat message (streams response)',
|
|
659
|
+
usage: 'sessions chat <id> --message "Implement X"',
|
|
660
|
+
run: async ({ ctx, args, flags }) => {
|
|
661
|
+
const id = requireArg(args, 0, 'session id')
|
|
662
|
+
const message = String(getFlag(flags, 'message') || args.slice(1).join(' ')).trim()
|
|
663
|
+
if (!message) throw new Error('Missing message. Use --message or pass it after the session id.')
|
|
664
|
+
|
|
665
|
+
const body = { message }
|
|
666
|
+
const imagePath = getFlag(flags, 'image-path')
|
|
667
|
+
const imageUrl = getFlag(flags, 'image-url')
|
|
668
|
+
const internal = getFlag(flags, 'internal')
|
|
669
|
+
const queueMode = getFlag(flags, 'queue-mode')
|
|
670
|
+
if (imagePath !== undefined) body.imagePath = String(imagePath)
|
|
671
|
+
if (imageUrl !== undefined) body.imageUrl = String(imageUrl)
|
|
672
|
+
if (internal !== undefined) body.internal = toBoolean(internal, 'internal')
|
|
673
|
+
if (queueMode !== undefined) body.queueMode = String(queueMode)
|
|
674
|
+
|
|
675
|
+
await runSessionChat(ctx, id, body)
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
browser: {
|
|
679
|
+
summary: 'Get browser active state for a session',
|
|
680
|
+
usage: 'sessions browser <id>',
|
|
681
|
+
run: async ({ ctx, args }) => {
|
|
682
|
+
const id = requireArg(args, 0, 'session id')
|
|
683
|
+
printResult(ctx, await request(ctx, 'GET', `/sessions/${encodeURIComponent(id)}/browser`))
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
'browser-clear': {
|
|
687
|
+
summary: 'Cleanup/close browser state for a session',
|
|
688
|
+
usage: 'sessions browser-clear <id>',
|
|
689
|
+
run: async ({ ctx, args }) => {
|
|
690
|
+
const id = requireArg(args, 0, 'session id')
|
|
691
|
+
printResult(ctx, await request(ctx, 'DELETE', `/sessions/${encodeURIComponent(id)}/browser`))
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
devserver: {
|
|
695
|
+
summary: 'Control session dev server',
|
|
696
|
+
usage: 'sessions devserver <id> --action start|stop|status',
|
|
697
|
+
run: async ({ ctx, args, flags }) => {
|
|
698
|
+
const id = requireArg(args, 0, 'session id')
|
|
699
|
+
const action = String(getFlag(flags, 'action') || args[1] || '').trim()
|
|
700
|
+
if (!action) throw new Error('Missing action. Use --action start|stop|status.')
|
|
701
|
+
printResult(
|
|
702
|
+
ctx,
|
|
703
|
+
await request(ctx, 'POST', `/sessions/${encodeURIComponent(id)}/devserver`, {
|
|
704
|
+
body: { action },
|
|
705
|
+
}),
|
|
706
|
+
)
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
deploy: {
|
|
710
|
+
summary: 'Commit and push session cwd',
|
|
711
|
+
usage: 'sessions deploy <id> --message "Deploy from CLI"',
|
|
712
|
+
run: async ({ ctx, args, flags }) => {
|
|
713
|
+
const id = requireArg(args, 0, 'session id')
|
|
714
|
+
const message = String(getFlag(flags, 'message') || 'Deploy from SwarmClaw CLI')
|
|
715
|
+
printResult(
|
|
716
|
+
ctx,
|
|
717
|
+
await request(ctx, 'POST', `/sessions/${encodeURIComponent(id)}/deploy`, {
|
|
718
|
+
body: { message },
|
|
719
|
+
}),
|
|
720
|
+
)
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
tasks: {
|
|
726
|
+
description: 'Manage task board tasks',
|
|
727
|
+
commands: {
|
|
728
|
+
list: {
|
|
729
|
+
summary: 'List tasks',
|
|
730
|
+
usage: 'tasks list',
|
|
731
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/tasks')),
|
|
732
|
+
},
|
|
733
|
+
get: {
|
|
734
|
+
summary: 'Get task by id',
|
|
735
|
+
usage: 'tasks get <id>',
|
|
736
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'GET', `/tasks/${encodeURIComponent(requireArg(args, 0, 'task id'))}`)),
|
|
737
|
+
},
|
|
738
|
+
create: {
|
|
739
|
+
summary: 'Create a task',
|
|
740
|
+
usage: 'tasks create --title "Fix bug" --agent-id <agentId>',
|
|
741
|
+
run: async ({ ctx, flags }) => {
|
|
742
|
+
const body = parseBody(flags)
|
|
743
|
+
printResult(ctx, await request(ctx, 'POST', '/tasks', { body }))
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
update: {
|
|
747
|
+
summary: 'Update a task',
|
|
748
|
+
usage: 'tasks update <id> --status queued',
|
|
749
|
+
run: async ({ ctx, args, flags }) => {
|
|
750
|
+
const id = requireArg(args, 0, 'task id')
|
|
751
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
752
|
+
printResult(ctx, await request(ctx, 'PUT', `/tasks/${encodeURIComponent(id)}`, { body }))
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
delete: {
|
|
756
|
+
summary: 'Delete a task',
|
|
757
|
+
usage: 'tasks delete <id>',
|
|
758
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'DELETE', `/tasks/${encodeURIComponent(requireArg(args, 0, 'task id'))}`)),
|
|
759
|
+
},
|
|
760
|
+
queue: {
|
|
761
|
+
summary: 'Queue a task immediately',
|
|
762
|
+
usage: 'tasks queue <id>',
|
|
763
|
+
run: async ({ ctx, args }) => {
|
|
764
|
+
const id = requireArg(args, 0, 'task id')
|
|
765
|
+
printResult(ctx, await request(ctx, 'PUT', `/tasks/${encodeURIComponent(id)}`, { body: { status: 'queued' } }))
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
comment: {
|
|
769
|
+
summary: 'Append comment to a task',
|
|
770
|
+
usage: 'tasks comment <id> --text "Progress update" [--author user] [--agent-id <id>]',
|
|
771
|
+
run: async ({ ctx, args, flags }) => {
|
|
772
|
+
const id = requireArg(args, 0, 'task id')
|
|
773
|
+
const text = String(getFlag(flags, 'text') || args.slice(1).join(' ')).trim()
|
|
774
|
+
if (!text) throw new Error('Missing comment text. Use --text "..."')
|
|
775
|
+
const comment = {
|
|
776
|
+
id: crypto.randomBytes(4).toString('hex'),
|
|
777
|
+
author: String(getFlag(flags, 'author') || 'user'),
|
|
778
|
+
text,
|
|
779
|
+
createdAt: Date.now(),
|
|
780
|
+
}
|
|
781
|
+
const agentId = getFlag(flags, 'agent-id')
|
|
782
|
+
if (agentId !== undefined) comment.agentId = String(agentId)
|
|
783
|
+
printResult(
|
|
784
|
+
ctx,
|
|
785
|
+
await request(ctx, 'PUT', `/tasks/${encodeURIComponent(id)}`, {
|
|
786
|
+
body: { appendComment: comment },
|
|
787
|
+
}),
|
|
788
|
+
)
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
schedules: {
|
|
794
|
+
description: 'Manage schedules',
|
|
795
|
+
commands: {
|
|
796
|
+
list: {
|
|
797
|
+
summary: 'List schedules',
|
|
798
|
+
usage: 'schedules list',
|
|
799
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/schedules')),
|
|
800
|
+
},
|
|
801
|
+
get: {
|
|
802
|
+
summary: 'Get schedule by id',
|
|
803
|
+
usage: 'schedules get <id>',
|
|
804
|
+
run: async ({ ctx, args }) => {
|
|
805
|
+
const all = await request(ctx, 'GET', '/schedules')
|
|
806
|
+
printResult(ctx, pickById(all, requireArg(args, 0, 'schedule id'), 'Schedule'))
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
create: {
|
|
810
|
+
summary: 'Create a schedule',
|
|
811
|
+
usage: 'schedules create --name "Daily" --agent-id <id> --schedule-type cron --cron "0 * * * *"',
|
|
812
|
+
run: async ({ ctx, flags }) => {
|
|
813
|
+
const body = parseBody(flags)
|
|
814
|
+
printResult(ctx, await request(ctx, 'POST', '/schedules', { body }))
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
update: {
|
|
818
|
+
summary: 'Update a schedule',
|
|
819
|
+
usage: 'schedules update <id> --status paused',
|
|
820
|
+
run: async ({ ctx, args, flags }) => {
|
|
821
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
822
|
+
printResult(
|
|
823
|
+
ctx,
|
|
824
|
+
await request(ctx, 'PUT', `/schedules/${encodeURIComponent(requireArg(args, 0, 'schedule id'))}`, {
|
|
825
|
+
body,
|
|
826
|
+
}),
|
|
827
|
+
)
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
delete: {
|
|
831
|
+
summary: 'Delete a schedule',
|
|
832
|
+
usage: 'schedules delete <id>',
|
|
833
|
+
run: async ({ ctx, args }) => {
|
|
834
|
+
printResult(ctx, await request(ctx, 'DELETE', `/schedules/${encodeURIComponent(requireArg(args, 0, 'schedule id'))}`))
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
run: {
|
|
838
|
+
summary: 'Run a schedule immediately',
|
|
839
|
+
usage: 'schedules run <id>',
|
|
840
|
+
run: async ({ ctx, args }) => {
|
|
841
|
+
printResult(ctx, await request(ctx, 'POST', `/schedules/${encodeURIComponent(requireArg(args, 0, 'schedule id'))}/run`))
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
memory: {
|
|
847
|
+
description: 'Manage memory entries',
|
|
848
|
+
aliases: { find: 'search' },
|
|
849
|
+
commands: {
|
|
850
|
+
list: {
|
|
851
|
+
summary: 'List memory entries',
|
|
852
|
+
usage: 'memory list [--agent-id <id>]',
|
|
853
|
+
run: async ({ ctx, flags }) => {
|
|
854
|
+
const agentId = getFlag(flags, 'agent-id')
|
|
855
|
+
printResult(
|
|
856
|
+
ctx,
|
|
857
|
+
await request(ctx, 'GET', '/memory', {
|
|
858
|
+
query: { agentId },
|
|
859
|
+
}),
|
|
860
|
+
)
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
search: {
|
|
864
|
+
summary: 'Search memory',
|
|
865
|
+
usage: 'memory search --q "topic" [--agent-id <id>]',
|
|
866
|
+
run: async ({ ctx, args, flags }) => {
|
|
867
|
+
const q = String(getFlag(flags, 'q') || getFlag(flags, 'query') || args.join(' ')).trim()
|
|
868
|
+
if (!q) throw new Error('Missing search query. Use --q "..."')
|
|
869
|
+
const agentId = getFlag(flags, 'agent-id')
|
|
870
|
+
printResult(
|
|
871
|
+
ctx,
|
|
872
|
+
await request(ctx, 'GET', '/memory', {
|
|
873
|
+
query: { q, agentId },
|
|
874
|
+
}),
|
|
875
|
+
)
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
create: {
|
|
879
|
+
summary: 'Create memory entry',
|
|
880
|
+
usage: 'memory create --title "Note" --content "..." --category note',
|
|
881
|
+
run: async ({ ctx, flags }) => {
|
|
882
|
+
const body = parseBody(flags)
|
|
883
|
+
printResult(ctx, await request(ctx, 'POST', '/memory', { body }))
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
update: {
|
|
887
|
+
summary: 'Update memory entry',
|
|
888
|
+
usage: 'memory update <id> --title "Updated"',
|
|
889
|
+
run: async ({ ctx, args, flags }) => {
|
|
890
|
+
const id = requireArg(args, 0, 'memory id')
|
|
891
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
892
|
+
printResult(ctx, await request(ctx, 'PUT', `/memory/${encodeURIComponent(id)}`, { body }))
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
delete: {
|
|
896
|
+
summary: 'Delete memory entry',
|
|
897
|
+
usage: 'memory delete <id>',
|
|
898
|
+
run: async ({ ctx, args }) => {
|
|
899
|
+
const id = requireArg(args, 0, 'memory id')
|
|
900
|
+
printResult(ctx, await request(ctx, 'DELETE', `/memory/${encodeURIComponent(id)}`))
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
connectors: {
|
|
906
|
+
description: 'Manage chat connectors',
|
|
907
|
+
commands: {
|
|
908
|
+
list: {
|
|
909
|
+
summary: 'List connectors',
|
|
910
|
+
usage: 'connectors list',
|
|
911
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/connectors')),
|
|
912
|
+
},
|
|
913
|
+
get: {
|
|
914
|
+
summary: 'Get connector by id',
|
|
915
|
+
usage: 'connectors get <id>',
|
|
916
|
+
run: async ({ ctx, args }) => {
|
|
917
|
+
printResult(ctx, await request(ctx, 'GET', `/connectors/${encodeURIComponent(requireArg(args, 0, 'connector id'))}`))
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
create: {
|
|
921
|
+
summary: 'Create connector',
|
|
922
|
+
usage: 'connectors create --platform telegram --agent-id <id> --name "My Bot"',
|
|
923
|
+
run: async ({ ctx, flags }) => {
|
|
924
|
+
const body = parseBody(flags)
|
|
925
|
+
printResult(ctx, await request(ctx, 'POST', '/connectors', { body }))
|
|
926
|
+
},
|
|
927
|
+
},
|
|
928
|
+
update: {
|
|
929
|
+
summary: 'Update connector',
|
|
930
|
+
usage: 'connectors update <id> --name "Renamed"',
|
|
931
|
+
run: async ({ ctx, args, flags }) => {
|
|
932
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
933
|
+
printResult(
|
|
934
|
+
ctx,
|
|
935
|
+
await request(ctx, 'PUT', `/connectors/${encodeURIComponent(requireArg(args, 0, 'connector id'))}`, { body }),
|
|
936
|
+
)
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
delete: {
|
|
940
|
+
summary: 'Delete connector',
|
|
941
|
+
usage: 'connectors delete <id>',
|
|
942
|
+
run: async ({ ctx, args }) => {
|
|
943
|
+
printResult(ctx, await request(ctx, 'DELETE', `/connectors/${encodeURIComponent(requireArg(args, 0, 'connector id'))}`))
|
|
944
|
+
},
|
|
945
|
+
},
|
|
946
|
+
start: {
|
|
947
|
+
summary: 'Start connector',
|
|
948
|
+
usage: 'connectors start <id>',
|
|
949
|
+
run: async ({ ctx, args }) => {
|
|
950
|
+
const id = requireArg(args, 0, 'connector id')
|
|
951
|
+
printResult(ctx, await request(ctx, 'PUT', `/connectors/${encodeURIComponent(id)}`, { body: { action: 'start' } }))
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
stop: {
|
|
955
|
+
summary: 'Stop connector',
|
|
956
|
+
usage: 'connectors stop <id>',
|
|
957
|
+
run: async ({ ctx, args }) => {
|
|
958
|
+
const id = requireArg(args, 0, 'connector id')
|
|
959
|
+
printResult(ctx, await request(ctx, 'PUT', `/connectors/${encodeURIComponent(id)}`, { body: { action: 'stop' } }))
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
repair: {
|
|
963
|
+
summary: 'Repair connector',
|
|
964
|
+
usage: 'connectors repair <id>',
|
|
965
|
+
run: async ({ ctx, args }) => {
|
|
966
|
+
const id = requireArg(args, 0, 'connector id')
|
|
967
|
+
printResult(ctx, await request(ctx, 'PUT', `/connectors/${encodeURIComponent(id)}`, { body: { action: 'repair' } }))
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
providers: {
|
|
973
|
+
description: 'Provider and model config operations',
|
|
974
|
+
commands: {
|
|
975
|
+
list: {
|
|
976
|
+
summary: 'List full provider registry',
|
|
977
|
+
usage: 'providers list',
|
|
978
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/providers')),
|
|
979
|
+
},
|
|
980
|
+
configs: {
|
|
981
|
+
summary: 'List provider configs',
|
|
982
|
+
usage: 'providers configs',
|
|
983
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/providers/configs')),
|
|
984
|
+
},
|
|
985
|
+
'config-get': {
|
|
986
|
+
summary: 'Get one provider config',
|
|
987
|
+
usage: 'providers config-get <id>',
|
|
988
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'GET', `/providers/${encodeURIComponent(requireArg(args, 0, 'provider id'))}`)),
|
|
989
|
+
},
|
|
990
|
+
'config-create': {
|
|
991
|
+
summary: 'Create custom provider config',
|
|
992
|
+
usage: 'providers config-create --name "My API" --base-url https://...',
|
|
993
|
+
run: async ({ ctx, flags }) => {
|
|
994
|
+
const body = parseBody(flags)
|
|
995
|
+
printResult(ctx, await request(ctx, 'POST', '/providers', { body }))
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
'config-update': {
|
|
999
|
+
summary: 'Update provider config',
|
|
1000
|
+
usage: 'providers config-update <id> --name "New Name"',
|
|
1001
|
+
run: async ({ ctx, args, flags }) => {
|
|
1002
|
+
const id = requireArg(args, 0, 'provider id')
|
|
1003
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1004
|
+
printResult(ctx, await request(ctx, 'PUT', `/providers/${encodeURIComponent(id)}`, { body }))
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
'config-delete': {
|
|
1008
|
+
summary: 'Delete custom provider config',
|
|
1009
|
+
usage: 'providers config-delete <id>',
|
|
1010
|
+
run: async ({ ctx, args }) => {
|
|
1011
|
+
printResult(ctx, await request(ctx, 'DELETE', `/providers/${encodeURIComponent(requireArg(args, 0, 'provider id'))}`))
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
'models-get': {
|
|
1015
|
+
summary: 'Get provider model list/override state',
|
|
1016
|
+
usage: 'providers models-get <id>',
|
|
1017
|
+
run: async ({ ctx, args }) => {
|
|
1018
|
+
printResult(ctx, await request(ctx, 'GET', `/providers/${encodeURIComponent(requireArg(args, 0, 'provider id'))}/models`))
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
'models-set': {
|
|
1022
|
+
summary: 'Override provider models',
|
|
1023
|
+
usage: 'providers models-set <id> --models modelA,modelB',
|
|
1024
|
+
run: async ({ ctx, args, flags }) => {
|
|
1025
|
+
const id = requireArg(args, 0, 'provider id')
|
|
1026
|
+
const fromFlag = parseCsvList(getFlag(flags, 'models'))
|
|
1027
|
+
const baseBody = parseBody(flags, ['models'])
|
|
1028
|
+
const models = fromFlag.length ? fromFlag : baseBody.models
|
|
1029
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
1030
|
+
throw new Error('Missing models. Use --models a,b or --json \'{"models":["a","b"]}\'.')
|
|
1031
|
+
}
|
|
1032
|
+
printResult(
|
|
1033
|
+
ctx,
|
|
1034
|
+
await request(ctx, 'PUT', `/providers/${encodeURIComponent(id)}/models`, {
|
|
1035
|
+
body: { ...baseBody, models },
|
|
1036
|
+
}),
|
|
1037
|
+
)
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
'models-reset': {
|
|
1041
|
+
summary: 'Remove provider model override',
|
|
1042
|
+
usage: 'providers models-reset <id>',
|
|
1043
|
+
run: async ({ ctx, args }) => {
|
|
1044
|
+
printResult(ctx, await request(ctx, 'DELETE', `/providers/${encodeURIComponent(requireArg(args, 0, 'provider id'))}/models`))
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
ollama: {
|
|
1048
|
+
summary: 'List models from an Ollama endpoint',
|
|
1049
|
+
usage: 'providers ollama [--endpoint http://localhost:11434]',
|
|
1050
|
+
run: async ({ ctx, flags }) => {
|
|
1051
|
+
printResult(
|
|
1052
|
+
ctx,
|
|
1053
|
+
await request(ctx, 'GET', '/providers/ollama', {
|
|
1054
|
+
query: { endpoint: getFlag(flags, 'endpoint') },
|
|
1055
|
+
}),
|
|
1056
|
+
)
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
credentials: {
|
|
1062
|
+
description: 'Manage provider credentials',
|
|
1063
|
+
commands: {
|
|
1064
|
+
list: {
|
|
1065
|
+
summary: 'List credentials metadata',
|
|
1066
|
+
usage: 'credentials list',
|
|
1067
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/credentials')),
|
|
1068
|
+
},
|
|
1069
|
+
create: {
|
|
1070
|
+
summary: 'Create credential',
|
|
1071
|
+
usage: 'credentials create --provider openai --api-key sk-... [--name "OpenAI Key"]',
|
|
1072
|
+
run: async ({ ctx, args, flags }) => {
|
|
1073
|
+
const provider = String(getFlag(flags, 'provider') || args[0] || '').trim()
|
|
1074
|
+
const apiKey = String(getFlag(flags, 'api-key') || '').trim()
|
|
1075
|
+
const name = getFlag(flags, 'name')
|
|
1076
|
+
if (!provider) throw new Error('Missing provider. Use --provider <id>.')
|
|
1077
|
+
if (!apiKey) throw new Error('Missing API key. Use --api-key <key>.')
|
|
1078
|
+
printResult(
|
|
1079
|
+
ctx,
|
|
1080
|
+
await request(ctx, 'POST', '/credentials', {
|
|
1081
|
+
body: {
|
|
1082
|
+
provider,
|
|
1083
|
+
apiKey,
|
|
1084
|
+
...(name !== undefined ? { name: String(name) } : {}),
|
|
1085
|
+
},
|
|
1086
|
+
}),
|
|
1087
|
+
)
|
|
1088
|
+
},
|
|
1089
|
+
},
|
|
1090
|
+
delete: {
|
|
1091
|
+
summary: 'Delete credential',
|
|
1092
|
+
usage: 'credentials delete <id>',
|
|
1093
|
+
run: async ({ ctx, args }) => {
|
|
1094
|
+
printResult(ctx, await request(ctx, 'DELETE', `/credentials/${encodeURIComponent(requireArg(args, 0, 'credential id'))}`))
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
},
|
|
1098
|
+
},
|
|
1099
|
+
secrets: {
|
|
1100
|
+
description: 'Manage encrypted orchestrator secrets',
|
|
1101
|
+
commands: {
|
|
1102
|
+
list: {
|
|
1103
|
+
summary: 'List secrets metadata',
|
|
1104
|
+
usage: 'secrets list',
|
|
1105
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/secrets')),
|
|
1106
|
+
},
|
|
1107
|
+
create: {
|
|
1108
|
+
summary: 'Create secret',
|
|
1109
|
+
usage: 'secrets create --name "Gmail" --service gmail --value "..."',
|
|
1110
|
+
run: async ({ ctx, flags }) => {
|
|
1111
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1112
|
+
printResult(ctx, await request(ctx, 'POST', '/secrets', { body }))
|
|
1113
|
+
},
|
|
1114
|
+
},
|
|
1115
|
+
update: {
|
|
1116
|
+
summary: 'Update secret metadata',
|
|
1117
|
+
usage: 'secrets update <id> --name "Updated"',
|
|
1118
|
+
run: async ({ ctx, args, flags }) => {
|
|
1119
|
+
const id = requireArg(args, 0, 'secret id')
|
|
1120
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1121
|
+
printResult(ctx, await request(ctx, 'PUT', `/secrets/${encodeURIComponent(id)}`, { body }))
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
delete: {
|
|
1125
|
+
summary: 'Delete secret',
|
|
1126
|
+
usage: 'secrets delete <id>',
|
|
1127
|
+
run: async ({ ctx, args }) => {
|
|
1128
|
+
printResult(ctx, await request(ctx, 'DELETE', `/secrets/${encodeURIComponent(requireArg(args, 0, 'secret id'))}`))
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
skills: {
|
|
1134
|
+
description: 'Manage skills',
|
|
1135
|
+
commands: {
|
|
1136
|
+
list: {
|
|
1137
|
+
summary: 'List skills',
|
|
1138
|
+
usage: 'skills list',
|
|
1139
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/skills')),
|
|
1140
|
+
},
|
|
1141
|
+
get: {
|
|
1142
|
+
summary: 'Get skill by id',
|
|
1143
|
+
usage: 'skills get <id>',
|
|
1144
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'GET', `/skills/${encodeURIComponent(requireArg(args, 0, 'skill id'))}`)),
|
|
1145
|
+
},
|
|
1146
|
+
create: {
|
|
1147
|
+
summary: 'Create skill',
|
|
1148
|
+
usage: 'skills create --name "Skill" --content "...markdown..."',
|
|
1149
|
+
run: async ({ ctx, flags }) => {
|
|
1150
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1151
|
+
printResult(ctx, await request(ctx, 'POST', '/skills', { body }))
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
update: {
|
|
1155
|
+
summary: 'Update skill',
|
|
1156
|
+
usage: 'skills update <id> --name "Updated Name"',
|
|
1157
|
+
run: async ({ ctx, args, flags }) => {
|
|
1158
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1159
|
+
printResult(
|
|
1160
|
+
ctx,
|
|
1161
|
+
await request(ctx, 'PUT', `/skills/${encodeURIComponent(requireArg(args, 0, 'skill id'))}`, {
|
|
1162
|
+
body,
|
|
1163
|
+
}),
|
|
1164
|
+
)
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
delete: {
|
|
1168
|
+
summary: 'Delete skill',
|
|
1169
|
+
usage: 'skills delete <id>',
|
|
1170
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'DELETE', `/skills/${encodeURIComponent(requireArg(args, 0, 'skill id'))}`)),
|
|
1171
|
+
},
|
|
1172
|
+
import: {
|
|
1173
|
+
summary: 'Import skill from URL',
|
|
1174
|
+
usage: 'skills import --url https://.../SKILL.md',
|
|
1175
|
+
run: async ({ ctx, args, flags }) => {
|
|
1176
|
+
const url = String(getFlag(flags, 'url') || args[0] || '').trim()
|
|
1177
|
+
if (!url) throw new Error('Missing url. Use --url https://...')
|
|
1178
|
+
const body = parseBody(flags, ['url'])
|
|
1179
|
+
printResult(ctx, await request(ctx, 'POST', '/skills/import', { body: { ...body, url } }))
|
|
1180
|
+
},
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
settings: {
|
|
1185
|
+
description: 'Manage app settings',
|
|
1186
|
+
commands: {
|
|
1187
|
+
get: {
|
|
1188
|
+
summary: 'Get settings',
|
|
1189
|
+
usage: 'settings get',
|
|
1190
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/settings')),
|
|
1191
|
+
},
|
|
1192
|
+
set: {
|
|
1193
|
+
summary: 'Update settings',
|
|
1194
|
+
usage: 'settings set --json \'{"loopMode":"bounded"}\'',
|
|
1195
|
+
run: async ({ ctx, flags }) => {
|
|
1196
|
+
const body = parseBody(flags, [], { allowEmpty: false })
|
|
1197
|
+
printResult(ctx, await request(ctx, 'PUT', '/settings', { body }))
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
daemon: {
|
|
1203
|
+
description: 'Control background daemon',
|
|
1204
|
+
commands: {
|
|
1205
|
+
status: {
|
|
1206
|
+
summary: 'Get daemon status',
|
|
1207
|
+
usage: 'daemon status',
|
|
1208
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/daemon')),
|
|
1209
|
+
},
|
|
1210
|
+
start: {
|
|
1211
|
+
summary: 'Start daemon',
|
|
1212
|
+
usage: 'daemon start',
|
|
1213
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'POST', '/daemon', { body: { action: 'start' } })),
|
|
1214
|
+
},
|
|
1215
|
+
stop: {
|
|
1216
|
+
summary: 'Stop daemon',
|
|
1217
|
+
usage: 'daemon stop',
|
|
1218
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'POST', '/daemon', { body: { action: 'stop' } })),
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
runs: {
|
|
1223
|
+
description: 'Inspect session run queue state',
|
|
1224
|
+
commands: {
|
|
1225
|
+
list: {
|
|
1226
|
+
summary: 'List runs',
|
|
1227
|
+
usage: 'runs list [--session-id <id>] [--status queued] [--limit 50]',
|
|
1228
|
+
run: async ({ ctx, flags }) => {
|
|
1229
|
+
const limit = getFlag(flags, 'limit')
|
|
1230
|
+
printResult(
|
|
1231
|
+
ctx,
|
|
1232
|
+
await request(ctx, 'GET', '/runs', {
|
|
1233
|
+
query: {
|
|
1234
|
+
sessionId: getFlag(flags, 'session-id'),
|
|
1235
|
+
status: getFlag(flags, 'status'),
|
|
1236
|
+
limit: limit !== undefined ? toInteger(limit, 'limit') : undefined,
|
|
1237
|
+
},
|
|
1238
|
+
}),
|
|
1239
|
+
)
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
get: {
|
|
1243
|
+
summary: 'Get run by id',
|
|
1244
|
+
usage: 'runs get <id>',
|
|
1245
|
+
run: async ({ ctx, args }) => printResult(ctx, await request(ctx, 'GET', `/runs/${encodeURIComponent(requireArg(args, 0, 'run id'))}`)),
|
|
1246
|
+
},
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
1249
|
+
logs: {
|
|
1250
|
+
description: 'Query/clear server logs',
|
|
1251
|
+
commands: {
|
|
1252
|
+
list: {
|
|
1253
|
+
summary: 'List logs',
|
|
1254
|
+
usage: 'logs list [--lines 200] [--level INFO,ERROR] [--search "term"]',
|
|
1255
|
+
run: async ({ ctx, flags }) => {
|
|
1256
|
+
const lines = getFlag(flags, 'lines')
|
|
1257
|
+
printResult(
|
|
1258
|
+
ctx,
|
|
1259
|
+
await request(ctx, 'GET', '/logs', {
|
|
1260
|
+
query: {
|
|
1261
|
+
lines: lines !== undefined ? toInteger(lines, 'lines') : undefined,
|
|
1262
|
+
level: getFlag(flags, 'level'),
|
|
1263
|
+
search: getFlag(flags, 'search'),
|
|
1264
|
+
},
|
|
1265
|
+
}),
|
|
1266
|
+
)
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
clear: {
|
|
1270
|
+
summary: 'Clear log file',
|
|
1271
|
+
usage: 'logs clear',
|
|
1272
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'DELETE', '/logs')),
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
plugins: {
|
|
1277
|
+
description: 'Manage plugins',
|
|
1278
|
+
commands: {
|
|
1279
|
+
list: {
|
|
1280
|
+
summary: 'List plugins',
|
|
1281
|
+
usage: 'plugins list',
|
|
1282
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/plugins')),
|
|
1283
|
+
},
|
|
1284
|
+
set: {
|
|
1285
|
+
summary: 'Enable/disable plugin',
|
|
1286
|
+
usage: 'plugins set <filename> --enabled true|false',
|
|
1287
|
+
run: async ({ ctx, args, flags }) => {
|
|
1288
|
+
const filename = String(requireArg(args, 0, 'plugin filename'))
|
|
1289
|
+
const enabledRaw = getFlag(flags, 'enabled')
|
|
1290
|
+
if (enabledRaw === undefined) throw new Error('Missing --enabled true|false')
|
|
1291
|
+
const enabled = toBoolean(enabledRaw, 'enabled')
|
|
1292
|
+
printResult(ctx, await request(ctx, 'POST', '/plugins', { body: { filename, enabled } }))
|
|
1293
|
+
},
|
|
1294
|
+
},
|
|
1295
|
+
marketplace: {
|
|
1296
|
+
summary: 'Fetch plugin marketplace registry',
|
|
1297
|
+
usage: 'plugins marketplace',
|
|
1298
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/plugins/marketplace')),
|
|
1299
|
+
},
|
|
1300
|
+
install: {
|
|
1301
|
+
summary: 'Install plugin from HTTPS URL',
|
|
1302
|
+
usage: 'plugins install --url https://... --filename my-plugin.js',
|
|
1303
|
+
run: async ({ ctx, args, flags }) => {
|
|
1304
|
+
const url = String(getFlag(flags, 'url') || args[0] || '').trim()
|
|
1305
|
+
const filename = String(getFlag(flags, 'filename') || args[1] || '').trim()
|
|
1306
|
+
if (!url) throw new Error('Missing --url')
|
|
1307
|
+
if (!filename) throw new Error('Missing --filename')
|
|
1308
|
+
printResult(ctx, await request(ctx, 'POST', '/plugins/install', { body: { url, filename } }))
|
|
1309
|
+
},
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1313
|
+
usage: {
|
|
1314
|
+
description: 'Usage/cost summary',
|
|
1315
|
+
commands: {
|
|
1316
|
+
summary: {
|
|
1317
|
+
summary: 'Get usage summary',
|
|
1318
|
+
usage: 'usage summary',
|
|
1319
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/usage')),
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
},
|
|
1323
|
+
version: {
|
|
1324
|
+
description: 'Version/update endpoints',
|
|
1325
|
+
commands: {
|
|
1326
|
+
check: {
|
|
1327
|
+
summary: 'Check local vs remote version',
|
|
1328
|
+
usage: 'version check',
|
|
1329
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'GET', '/version')),
|
|
1330
|
+
},
|
|
1331
|
+
update: {
|
|
1332
|
+
summary: 'Pull latest from origin/main',
|
|
1333
|
+
usage: 'version update',
|
|
1334
|
+
run: async ({ ctx }) => printResult(ctx, await request(ctx, 'POST', '/version/update')),
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
},
|
|
1338
|
+
orchestrator: {
|
|
1339
|
+
description: 'Trigger orchestrator run as task',
|
|
1340
|
+
commands: {
|
|
1341
|
+
run: {
|
|
1342
|
+
summary: 'Create+queue task for orchestrator',
|
|
1343
|
+
usage: 'orchestrator run --agent-id <id> --task "Do work"',
|
|
1344
|
+
run: async ({ ctx, flags, args }) => {
|
|
1345
|
+
const agentId = String(getFlag(flags, 'agent-id') || '').trim()
|
|
1346
|
+
const task = String(getFlag(flags, 'task') || args.join(' ')).trim()
|
|
1347
|
+
if (!agentId) throw new Error('Missing --agent-id')
|
|
1348
|
+
if (!task) throw new Error('Missing --task "..."')
|
|
1349
|
+
printResult(ctx, await request(ctx, 'POST', '/orchestrator/run', { body: { agentId, task } }))
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
dirs: {
|
|
1355
|
+
description: 'Directory browser helper endpoints',
|
|
1356
|
+
commands: {
|
|
1357
|
+
list: {
|
|
1358
|
+
summary: 'List directories under a path',
|
|
1359
|
+
usage: 'dirs list [--path ~/Dev]',
|
|
1360
|
+
run: async ({ ctx, flags }) => {
|
|
1361
|
+
printResult(
|
|
1362
|
+
ctx,
|
|
1363
|
+
await request(ctx, 'GET', '/dirs', {
|
|
1364
|
+
query: { path: getFlag(flags, 'path') },
|
|
1365
|
+
}),
|
|
1366
|
+
)
|
|
1367
|
+
},
|
|
1368
|
+
},
|
|
1369
|
+
},
|
|
1370
|
+
},
|
|
1371
|
+
upload: {
|
|
1372
|
+
description: 'Upload files to /api/uploads',
|
|
1373
|
+
commands: {
|
|
1374
|
+
file: {
|
|
1375
|
+
summary: 'Upload a file',
|
|
1376
|
+
usage: 'upload file ./screenshot.png [--filename custom.png]',
|
|
1377
|
+
run: async ({ ctx, args, flags }) => {
|
|
1378
|
+
const localPath = path.resolve(process.cwd(), requireArg(args, 0, 'local file path'))
|
|
1379
|
+
const filename = String(getFlag(flags, 'filename') || path.basename(localPath))
|
|
1380
|
+
const buf = fs.readFileSync(localPath)
|
|
1381
|
+
printResult(
|
|
1382
|
+
ctx,
|
|
1383
|
+
await request(ctx, 'POST', '/upload', {
|
|
1384
|
+
body: buf,
|
|
1385
|
+
rawBody: true,
|
|
1386
|
+
headers: {
|
|
1387
|
+
'X-Filename': filename,
|
|
1388
|
+
'Content-Type': 'application/octet-stream',
|
|
1389
|
+
},
|
|
1390
|
+
}),
|
|
1391
|
+
)
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
},
|
|
1395
|
+
},
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function printGlobalHelp() {
|
|
1399
|
+
const lines = [
|
|
1400
|
+
'SwarmClaw CLI',
|
|
1401
|
+
'',
|
|
1402
|
+
'Usage:',
|
|
1403
|
+
' swarmclaw <group> <command> [args] [--flags]',
|
|
1404
|
+
' swarmclaw help [group]',
|
|
1405
|
+
'',
|
|
1406
|
+
'Global Flags:',
|
|
1407
|
+
' --url <baseUrl> API base URL (default: SWARMCLAW_URL or http://localhost:3456)',
|
|
1408
|
+
' --key <accessKey> Access key (default: SWARMCLAW_ACCESS_KEY)',
|
|
1409
|
+
' --raw Print raw text for text endpoints and chat streams',
|
|
1410
|
+
' --json <json> Inline JSON payload for create/update commands',
|
|
1411
|
+
' --file <path> JSON payload file for create/update commands',
|
|
1412
|
+
' --help Show help',
|
|
1413
|
+
'',
|
|
1414
|
+
'Groups:',
|
|
1415
|
+
]
|
|
1416
|
+
|
|
1417
|
+
for (const [groupName, groupDef] of Object.entries(GROUPS)) {
|
|
1418
|
+
lines.push(` ${groupName.padEnd(14)} ${groupDef.description}`)
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
lines.push('', 'Tip: use "swarmclaw help <group>" for group commands.')
|
|
1422
|
+
process.stdout.write(`${lines.join('\n')}\n`)
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function printGroupHelp(groupName) {
|
|
1426
|
+
const groupDef = GROUPS[groupName]
|
|
1427
|
+
if (!groupDef) {
|
|
1428
|
+
throw new Error(`Unknown group "${groupName}"`)
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const lines = [
|
|
1432
|
+
`SwarmClaw CLI - ${groupName}`,
|
|
1433
|
+
'',
|
|
1434
|
+
groupDef.description,
|
|
1435
|
+
'',
|
|
1436
|
+
'Usage:',
|
|
1437
|
+
` swarmclaw ${groupName} <command> [args] [--flags]`,
|
|
1438
|
+
'',
|
|
1439
|
+
'Commands:',
|
|
1440
|
+
]
|
|
1441
|
+
|
|
1442
|
+
for (const [commandName, commandDef] of Object.entries(groupDef.commands)) {
|
|
1443
|
+
lines.push(` ${commandName.padEnd(14)} ${commandDef.summary}`)
|
|
1444
|
+
if (commandDef.usage) lines.push(` ${commandDef.usage}`)
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
process.stdout.write(`${lines.join('\n')}\n`)
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
async function main() {
|
|
1451
|
+
const { flags, positionals } = parseArgv(process.argv.slice(2))
|
|
1452
|
+
|
|
1453
|
+
const wantsHelp =
|
|
1454
|
+
toOptionalBoolean(getFlag(flags, 'help'), false)
|
|
1455
|
+
|| positionals[0] === 'help'
|
|
1456
|
+
|| positionals.length === 0
|
|
1457
|
+
|
|
1458
|
+
if (wantsHelp) {
|
|
1459
|
+
const maybeGroup = positionals[0] === 'help'
|
|
1460
|
+
? positionals[1]
|
|
1461
|
+
: positionals[0]
|
|
1462
|
+
if (maybeGroup) {
|
|
1463
|
+
const resolved = resolveGroupName(maybeGroup)
|
|
1464
|
+
if (GROUPS[resolved]) {
|
|
1465
|
+
printGroupHelp(resolved)
|
|
1466
|
+
return
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
printGlobalHelp()
|
|
1470
|
+
return
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const rawGroup = positionals[0]
|
|
1474
|
+
const rawCommand = positionals[1]
|
|
1475
|
+
const args = positionals.slice(2)
|
|
1476
|
+
|
|
1477
|
+
const groupName = resolveGroupName(rawGroup)
|
|
1478
|
+
const groupDef = GROUPS[groupName]
|
|
1479
|
+
if (!groupDef) {
|
|
1480
|
+
printGlobalHelp()
|
|
1481
|
+
throw new Error(`Unknown group "${rawGroup}"`)
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (!rawCommand || rawCommand === 'help') {
|
|
1485
|
+
printGroupHelp(groupName)
|
|
1486
|
+
return
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const commandName = resolveCommandName(groupDef, rawCommand)
|
|
1490
|
+
const commandDef = groupDef.commands[commandName]
|
|
1491
|
+
if (!commandDef) {
|
|
1492
|
+
printGroupHelp(groupName)
|
|
1493
|
+
throw new Error(`Unknown command "${rawCommand}" for group "${groupName}"`)
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const ctx = createContext(flags)
|
|
1497
|
+
await commandDef.run({ ctx, args, flags })
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
main().catch((err) => {
|
|
1501
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
1502
|
+
process.stderr.write(`Error: ${message}\n`)
|
|
1503
|
+
process.exit(1)
|
|
1504
|
+
})
|