@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.
Files changed (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. 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
+ })