@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,130 @@
1
+ import path from 'path'
2
+
3
+ export type SkillSourceFormat = 'openclaw' | 'plain'
4
+
5
+ type NormalizeSkillInput = {
6
+ name?: unknown
7
+ description?: unknown
8
+ filename?: unknown
9
+ content?: unknown
10
+ sourceUrl?: unknown
11
+ }
12
+
13
+ export type NormalizedSkill = {
14
+ name: string
15
+ description: string
16
+ filename: string
17
+ content: string
18
+ sourceUrl?: string
19
+ sourceFormat: SkillSourceFormat
20
+ }
21
+
22
+ function asTrimmedString(value: unknown): string | null {
23
+ if (typeof value !== 'string') return null
24
+ const trimmed = value.trim()
25
+ return trimmed.length > 0 ? trimmed : null
26
+ }
27
+
28
+ function stripQuotes(value: string): string {
29
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
30
+ return value.slice(1, -1)
31
+ }
32
+ return value
33
+ }
34
+
35
+ function slugify(input: string): string {
36
+ return input
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9]+/g, '-')
39
+ .replace(/^-+|-+$/g, '')
40
+ .slice(0, 80) || 'skill'
41
+ }
42
+
43
+ function sanitizeFilename(input: string): string {
44
+ const base = path.basename(input.trim())
45
+ const safe = base.replace(/[^a-zA-Z0-9._-]/g, '-')
46
+ if (!safe) return 'skill.md'
47
+ return safe.toLowerCase().endsWith('.md') ? safe : `${safe}.md`
48
+ }
49
+
50
+ function parseFrontmatterBlock(content: string): { frontmatter: Record<string, string>; body: string } | null {
51
+ const match = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
52
+ if (!match) return null
53
+ const rawFrontmatter = match[1]
54
+ const body = match[2] || ''
55
+
56
+ const frontmatter: Record<string, string> = {}
57
+ for (const rawLine of rawFrontmatter.split(/\r?\n/)) {
58
+ const line = rawLine.trim()
59
+ if (!line || line.startsWith('#')) continue
60
+ const idx = line.indexOf(':')
61
+ if (idx === -1) continue
62
+ const key = line.slice(0, idx).trim().toLowerCase()
63
+ const value = stripQuotes(line.slice(idx + 1).trim())
64
+ if (key) frontmatter[key] = value
65
+ }
66
+
67
+ return { frontmatter, body }
68
+ }
69
+
70
+ function deriveNameFromFilename(filename: string): string {
71
+ return filename
72
+ .replace(/\.md$/i, '')
73
+ .replace(/[-_]+/g, ' ')
74
+ .trim() || 'Unnamed Skill'
75
+ }
76
+
77
+ function deriveFilenameFromUrl(url: string): string | null {
78
+ try {
79
+ const u = new URL(url)
80
+ const basename = path.basename(u.pathname)
81
+ if (!basename) return null
82
+ if (basename.toUpperCase() === 'SKILL.MD') {
83
+ const parent = path.basename(path.dirname(u.pathname))
84
+ if (parent) return `${slugify(parent)}.md`
85
+ }
86
+ return sanitizeFilename(basename)
87
+ } catch {
88
+ return null
89
+ }
90
+ }
91
+
92
+ export function normalizeSkillPayload(input: NormalizeSkillInput): NormalizedSkill {
93
+ const rawContent = typeof input.content === 'string' ? input.content : ''
94
+ const parsed = parseFrontmatterBlock(rawContent)
95
+
96
+ const frontmatterName = asTrimmedString(parsed?.frontmatter?.name)
97
+ const frontmatterDescription = asTrimmedString(parsed?.frontmatter?.description)
98
+
99
+ const sourceUrl = asTrimmedString(input.sourceUrl) || undefined
100
+
101
+ const initialFilename = asTrimmedString(input.filename)
102
+ || (sourceUrl ? deriveFilenameFromUrl(sourceUrl) : null)
103
+ || (frontmatterName ? `${slugify(frontmatterName)}.md` : null)
104
+ || 'skill.md'
105
+ const filename = sanitizeFilename(initialFilename)
106
+
107
+ const name = asTrimmedString(input.name)
108
+ || frontmatterName
109
+ || deriveNameFromFilename(filename)
110
+
111
+ const description = asTrimmedString(input.description)
112
+ || frontmatterDescription
113
+ || ''
114
+
115
+ // For OpenClaw SKILL.md, keep only body instructions when frontmatter exists.
116
+ const normalizedContent = parsed ? parsed.body.trimStart() : rawContent
117
+
118
+ const sourceFormat: SkillSourceFormat = parsed && (frontmatterName !== null || frontmatterDescription !== null || parsed.frontmatter.metadata)
119
+ ? 'openclaw'
120
+ : 'plain'
121
+
122
+ return {
123
+ name,
124
+ description,
125
+ filename,
126
+ content: normalizedContent,
127
+ sourceUrl,
128
+ sourceFormat,
129
+ }
130
+ }
@@ -0,0 +1,161 @@
1
+ import { describe, it, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import Database from 'better-sqlite3'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
6
+ import fs from 'node:fs'
7
+
8
+ /**
9
+ * Tests for the MCP server storage operations (loadMcpServers, saveMcpServers, deleteMcpServer).
10
+ *
11
+ * Since storage.ts uses a hardcoded DB path with module-level initialization,
12
+ * we replicate the same SQL pattern against a temporary SQLite database.
13
+ */
14
+
15
+ const TABLE = 'mcp_servers'
16
+
17
+ let dbPath: string
18
+ let db: InstanceType<typeof Database>
19
+
20
+ // --- Replicated storage helpers (mirror storage.ts logic) ---
21
+
22
+ function loadMcpServers(): Record<string, any> {
23
+ const rows = db.prepare(`SELECT id, data FROM ${TABLE}`).all() as { id: string; data: string }[]
24
+ const result: Record<string, any> = {}
25
+ for (const row of rows) {
26
+ try {
27
+ result[row.id] = JSON.parse(row.data)
28
+ } catch {
29
+ // skip malformed
30
+ }
31
+ }
32
+ return result
33
+ }
34
+
35
+ function saveMcpServers(m: Record<string, any>) {
36
+ const upsert = db.prepare(`INSERT OR REPLACE INTO ${TABLE} (id, data) VALUES (?, ?)`)
37
+ const transaction = db.transaction(() => {
38
+ for (const [id, val] of Object.entries(m)) {
39
+ upsert.run(id, JSON.stringify(val))
40
+ }
41
+ })
42
+ transaction()
43
+ }
44
+
45
+ function deleteMcpServer(id: string) {
46
+ db.prepare(`DELETE FROM ${TABLE} WHERE id = ?`).run(id)
47
+ }
48
+
49
+ // --- Test setup / teardown ---
50
+
51
+ before(() => {
52
+ dbPath = path.join(os.tmpdir(), `test-storage-mcp-${Date.now()}.db`)
53
+ db = new Database(dbPath)
54
+ db.pragma('journal_mode = WAL')
55
+ db.exec(`CREATE TABLE IF NOT EXISTS ${TABLE} (id TEXT PRIMARY KEY, data TEXT NOT NULL)`)
56
+ })
57
+
58
+ after(() => {
59
+ db.close()
60
+ try { fs.unlinkSync(dbPath) } catch { /* ignore */ }
61
+ try { fs.unlinkSync(dbPath + '-wal') } catch { /* ignore */ }
62
+ try { fs.unlinkSync(dbPath + '-shm') } catch { /* ignore */ }
63
+ })
64
+
65
+ // --- Tests ---
66
+
67
+ describe('MCP server storage', () => {
68
+ it('saveMcpServers saves a config and it is retrievable', () => {
69
+ const config = { id: 'srv-1', name: 'Test MCP', url: 'http://localhost:9000' }
70
+ saveMcpServers({ 'srv-1': config })
71
+
72
+ const row = db.prepare(`SELECT data FROM ${TABLE} WHERE id = ?`).get('srv-1') as { data: string } | undefined
73
+ assert.ok(row, 'row should exist after save')
74
+ assert.deepStrictEqual(JSON.parse(row.data), config)
75
+ })
76
+
77
+ it('loadMcpServers returns all saved configs', () => {
78
+ // srv-1 already exists from previous test
79
+ saveMcpServers({ 'srv-2': { id: 'srv-2', name: 'Second' } })
80
+
81
+ const all = loadMcpServers()
82
+ assert.ok('srv-1' in all)
83
+ assert.ok('srv-2' in all)
84
+ assert.equal(all['srv-2'].name, 'Second')
85
+ })
86
+
87
+ it('loadMcpServers returns empty object when table is empty', () => {
88
+ db.exec(`DELETE FROM ${TABLE}`)
89
+ const all = loadMcpServers()
90
+ assert.deepStrictEqual(all, {})
91
+ })
92
+
93
+ it('saveMcpServers with same id updates the record', () => {
94
+ saveMcpServers({ 'srv-u': { id: 'srv-u', name: 'Original' } })
95
+ saveMcpServers({ 'srv-u': { id: 'srv-u', name: 'Updated' } })
96
+
97
+ const all = loadMcpServers()
98
+ assert.equal(all['srv-u'].name, 'Updated')
99
+
100
+ // only one row for that id
101
+ const count = (db.prepare(`SELECT COUNT(*) as c FROM ${TABLE} WHERE id = ?`).get('srv-u') as { c: number }).c
102
+ assert.equal(count, 1)
103
+ })
104
+
105
+ it('deleteMcpServer removes the record', () => {
106
+ saveMcpServers({ 'srv-d': { id: 'srv-d', name: 'ToDelete' } })
107
+ deleteMcpServer('srv-d')
108
+
109
+ const row = db.prepare(`SELECT data FROM ${TABLE} WHERE id = ?`).get('srv-d')
110
+ assert.equal(row, undefined)
111
+ })
112
+
113
+ it('deleteMcpServer does not throw for nonexistent id', () => {
114
+ assert.doesNotThrow(() => {
115
+ deleteMcpServer('nonexistent-id-xyz')
116
+ })
117
+ })
118
+
119
+ it('round-trip: save multiple, load all, verify count and data', () => {
120
+ db.exec(`DELETE FROM ${TABLE}`)
121
+
122
+ const configs: Record<string, any> = {}
123
+ for (let i = 0; i < 5; i++) {
124
+ configs[`rt-${i}`] = { id: `rt-${i}`, name: `Server ${i}`, port: 3000 + i }
125
+ }
126
+ saveMcpServers(configs)
127
+
128
+ const all = loadMcpServers()
129
+ assert.equal(Object.keys(all).length, 5)
130
+ for (let i = 0; i < 5; i++) {
131
+ assert.equal(all[`rt-${i}`].port, 3000 + i)
132
+ }
133
+ })
134
+
135
+ it('data integrity: saved JSON is correctly parsed back with all fields', () => {
136
+ db.exec(`DELETE FROM ${TABLE}`)
137
+
138
+ const config = {
139
+ id: 'integrity-1',
140
+ name: 'Full Config',
141
+ url: 'https://mcp.example.com',
142
+ transport: 'stdio',
143
+ command: 'npx',
144
+ args: ['-y', '@modelcontextprotocol/server-filesystem'],
145
+ env: { HOME: '/tmp' },
146
+ enabled: true,
147
+ createdAt: 1700000000000,
148
+ tags: ['production', 'filesystem'],
149
+ nested: { deep: { value: 42 } },
150
+ }
151
+
152
+ saveMcpServers({ 'integrity-1': config })
153
+ const loaded = loadMcpServers()
154
+
155
+ assert.deepStrictEqual(loaded['integrity-1'], config)
156
+ assert.ok(Array.isArray(loaded['integrity-1'].args))
157
+ assert.equal(loaded['integrity-1'].args.length, 2)
158
+ assert.equal(loaded['integrity-1'].nested.deep.value, 42)
159
+ assert.equal(typeof loaded['integrity-1'].enabled, 'boolean')
160
+ })
161
+ })