@flame0510/project-aether 1.1.9

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 (290) hide show
  1. package/README.md +150 -0
  2. package/agent-templates/README.md +37 -0
  3. package/agent-templates/argus/AGENTS.md +52 -0
  4. package/agent-templates/argus/IDENTITY.md +7 -0
  5. package/agent-templates/argus/MEMORY.md +9 -0
  6. package/agent-templates/argus/SOUL.md +24 -0
  7. package/agent-templates/argus/TOOLS.md +8 -0
  8. package/agent-templates/atlas/AGENTS.md +47 -0
  9. package/agent-templates/atlas/HEARTBEAT.md +10 -0
  10. package/agent-templates/atlas/IDENTITY.md +6 -0
  11. package/agent-templates/atlas/MEMORY.md +9 -0
  12. package/agent-templates/atlas/SOUL.md +21 -0
  13. package/agent-templates/atlas/TOOLS.md +4 -0
  14. package/agent-templates/base-image/.trigger +0 -0
  15. package/agent-templates/base-image/Dockerfile +90 -0
  16. package/agent-templates/base-image/entrypoint.sh +37 -0
  17. package/agent-templates/custom/AGENTS.md +29 -0
  18. package/agent-templates/custom/BOOTSTRAP.md +56 -0
  19. package/agent-templates/custom/MEMORY.md +8 -0
  20. package/agent-templates/prometheus/AGENTS.md +37 -0
  21. package/agent-templates/prometheus/IDENTITY.md +6 -0
  22. package/agent-templates/prometheus/MEMORY.md +9 -0
  23. package/agent-templates/prometheus/SOUL.md +21 -0
  24. package/agent-templates/prometheus/TOOLS.md +4 -0
  25. package/app/agents/ChannelManager.tsx +465 -0
  26. package/app/agents/ImageDownloadBanner.tsx +159 -0
  27. package/app/agents/PageClient.tsx +1285 -0
  28. package/app/agents/create/PageClient.tsx +437 -0
  29. package/app/agents/create/page.tsx +126 -0
  30. package/app/agents/loading.tsx +14 -0
  31. package/app/agents/page.tsx +5 -0
  32. package/app/api/agents/[id]/backup/route.ts +169 -0
  33. package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
  34. package/app/api/agents/[id]/channels/route.ts +14 -0
  35. package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
  36. package/app/api/agents/[id]/lifecycle/route.ts +94 -0
  37. package/app/api/agents/[id]/recreate/route.ts +199 -0
  38. package/app/api/agents/[id]/restart/route.ts +33 -0
  39. package/app/api/agents/[id]/restore/route.ts +81 -0
  40. package/app/api/agents/[id]/route.ts +280 -0
  41. package/app/api/agents/channels-summary/route.ts +39 -0
  42. package/app/api/agents/config-json/route.ts +58 -0
  43. package/app/api/agents/create/route.ts +488 -0
  44. package/app/api/agents/download-image/route.ts +32 -0
  45. package/app/api/agents/image-status/route.ts +126 -0
  46. package/app/api/agents/route.ts +213 -0
  47. package/app/api/agents/token/route.ts +94 -0
  48. package/app/api/agents-active/route.ts +190 -0
  49. package/app/api/agents-config/route.ts +629 -0
  50. package/app/api/alerts/smoke/route.ts +26 -0
  51. package/app/api/aliases/add/route.ts +28 -0
  52. package/app/api/aliases/remove/route.ts +27 -0
  53. package/app/api/assistant/route.ts +204 -0
  54. package/app/api/auth/check/route.ts +38 -0
  55. package/app/api/auth/login/route.ts +85 -0
  56. package/app/api/auth/logout/route.ts +7 -0
  57. package/app/api/auth/status/route.ts +33 -0
  58. package/app/api/config/env/route.ts +132 -0
  59. package/app/api/config/restart/route.ts +26 -0
  60. package/app/api/containers/route.ts +75 -0
  61. package/app/api/cost-override/route.ts +46 -0
  62. package/app/api/costs/route.ts +84 -0
  63. package/app/api/credentials/[id]/reveal/route.ts +21 -0
  64. package/app/api/credentials/[id]/route.ts +52 -0
  65. package/app/api/credentials/[id]/sync/route.ts +58 -0
  66. package/app/api/credentials/detect/route.ts +246 -0
  67. package/app/api/credentials/route.ts +33 -0
  68. package/app/api/crons/[id]/route.ts +69 -0
  69. package/app/api/crons/route.ts +12 -0
  70. package/app/api/crons/runs/route.ts +31 -0
  71. package/app/api/debug/route.ts +11 -0
  72. package/app/api/envcheck/route.ts +18 -0
  73. package/app/api/events/route.ts +24 -0
  74. package/app/api/gateway/agent/route.ts +154 -0
  75. package/app/api/gateway/provider/balance/route.ts +38 -0
  76. package/app/api/gateway/provider/keys.ts +38 -0
  77. package/app/api/gateway/provider/oauth/route.ts +304 -0
  78. package/app/api/gateway/provider/route.ts +209 -0
  79. package/app/api/gateway/route.ts +223 -0
  80. package/app/api/gateway/sync.ts +189 -0
  81. package/app/api/lineage/route.ts +54 -0
  82. package/app/api/memory-context/route.ts +15 -0
  83. package/app/api/metrics/route.ts +35 -0
  84. package/app/api/models/route.ts +77 -0
  85. package/app/api/plugins/route.js +37 -0
  86. package/app/api/provider/upstream.ts +142 -0
  87. package/app/api/provider/v1/chat/completions/route.ts +146 -0
  88. package/app/api/provider/v1/models/route.ts +167 -0
  89. package/app/api/provider-usage/route.ts +629 -0
  90. package/app/api/session/route.ts +39 -0
  91. package/app/api/sessions/[id]/route.ts +38 -0
  92. package/app/api/sessions/route.ts +23 -0
  93. package/app/api/setup/agent-image/route.ts +102 -0
  94. package/app/api/setup/password/route.ts +111 -0
  95. package/app/api/setup/restart/route.ts +76 -0
  96. package/app/api/skills/delete/route.js +105 -0
  97. package/app/api/skills/promote/route.js +129 -0
  98. package/app/api/skills/route.js +203 -0
  99. package/app/api/skills/save/route.js +142 -0
  100. package/app/api/stats/route.ts +34 -0
  101. package/app/api/stats-since/route.ts +20 -0
  102. package/app/api/stream/route.ts +70 -0
  103. package/app/api/system-health/route.ts +233 -0
  104. package/app/api/tool-calls/route.ts +24 -0
  105. package/app/api/tools-config/route.ts +163 -0
  106. package/app/api/update-check/route.ts +127 -0
  107. package/app/api/version/route.ts +25 -0
  108. package/app/api/wizard/complete/route.ts +32 -0
  109. package/app/api/wizard/reset/route.ts +28 -0
  110. package/app/api/wizard/status/route.ts +28 -0
  111. package/app/api/workspace/route.ts +392 -0
  112. package/app/api/workspace/stream/route.ts +136 -0
  113. package/app/components/AppShell.tsx +33 -0
  114. package/app/components/AuthGuard.tsx +91 -0
  115. package/app/components/CostBreakdown.tsx +37 -0
  116. package/app/components/DashboardHeader.tsx +73 -0
  117. package/app/components/DashboardLayout.tsx +76 -0
  118. package/app/components/DashboardToolbar.tsx +61 -0
  119. package/app/components/FloatingActions.tsx +33 -0
  120. package/app/components/Icons.tsx +78 -0
  121. package/app/components/LineageGraphPage.tsx +142 -0
  122. package/app/components/LiveFeed.tsx +48 -0
  123. package/app/components/MobileBottomNav.tsx +84 -0
  124. package/app/components/ModelPickerModal.tsx +94 -0
  125. package/app/components/PasswordInput.tsx +86 -0
  126. package/app/components/PulseChat.tsx +362 -0
  127. package/app/components/Rev4aLoader.tsx +48 -0
  128. package/app/components/SessionDrawer.tsx +263 -0
  129. package/app/components/SessionTopology.tsx +289 -0
  130. package/app/components/Sidebar.tsx +294 -0
  131. package/app/components/Skeleton.tsx +133 -0
  132. package/app/components/SystemCockpit.tsx +169 -0
  133. package/app/components/VersionBanner.tsx +105 -0
  134. package/app/components/WizardButton.tsx +30 -0
  135. package/app/components/ui/Badge.tsx +30 -0
  136. package/app/components/ui/Button.tsx +59 -0
  137. package/app/components/ui/ConfirmModal.tsx +46 -0
  138. package/app/components/ui/FilterBar.tsx +33 -0
  139. package/app/components/ui/Input.tsx +25 -0
  140. package/app/components/ui/ItemList.tsx +83 -0
  141. package/app/components/ui/LoadingSpinner.tsx +30 -0
  142. package/app/components/ui/Metric.tsx +22 -0
  143. package/app/components/ui/Modal.tsx +42 -0
  144. package/app/components/ui/Page.tsx +18 -0
  145. package/app/components/ui/Pill.tsx +6 -0
  146. package/app/components/ui/PropertyList.tsx +24 -0
  147. package/app/components/ui/Select.tsx +26 -0
  148. package/app/components/ui/StatusCard.tsx +32 -0
  149. package/app/components/ui/Surface.tsx +14 -0
  150. package/app/components/ui/Tabs.tsx +55 -0
  151. package/app/components/ui/TemplateOption.tsx +59 -0
  152. package/app/components/ui/Toast.tsx +44 -0
  153. package/app/components/ui/index.ts +19 -0
  154. package/app/components/ui/tokens.ts +18 -0
  155. package/app/config/PageClient.tsx +293 -0
  156. package/app/config/loading.tsx +10 -0
  157. package/app/config/page.tsx +7 -0
  158. package/app/containers/ContainersClient.tsx +129 -0
  159. package/app/containers/loading.tsx +21 -0
  160. package/app/containers/page.tsx +5 -0
  161. package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
  162. package/app/containers/terminal/[id]/page.tsx +6 -0
  163. package/app/credentials/PageClient.tsx +598 -0
  164. package/app/credentials/loading.tsx +10 -0
  165. package/app/credentials/page.tsx +7 -0
  166. package/app/crons/PageClient.tsx +538 -0
  167. package/app/crons/loading.tsx +10 -0
  168. package/app/crons/page.tsx +5 -0
  169. package/app/design-system.ts +57 -0
  170. package/app/gateway/PageClient.tsx +1048 -0
  171. package/app/gateway/loading.tsx +24 -0
  172. package/app/gateway/page.tsx +5 -0
  173. package/app/globals.css +1474 -0
  174. package/app/layout.tsx +45 -0
  175. package/app/lib/models-context.tsx +41 -0
  176. package/app/lineage/loading.tsx +10 -0
  177. package/app/lineage/page.tsx +13 -0
  178. package/app/loading.tsx +16 -0
  179. package/app/login/page.tsx +160 -0
  180. package/app/memory/MemoryContextPageClient.tsx +196 -0
  181. package/app/memory/loading.tsx +10 -0
  182. package/app/memory/page.tsx +8 -0
  183. package/app/page.tsx +31 -0
  184. package/app/plugins/PageClient.tsx +296 -0
  185. package/app/plugins/loading.tsx +10 -0
  186. package/app/plugins/page.tsx +5 -0
  187. package/app/setup/PageClient.tsx +227 -0
  188. package/app/setup/loading.tsx +21 -0
  189. package/app/setup/page.tsx +7 -0
  190. package/app/skills/PageClient.tsx +499 -0
  191. package/app/skills/loading.tsx +10 -0
  192. package/app/skills/page.tsx +5 -0
  193. package/app/tools/ToolsPageClient.tsx +308 -0
  194. package/app/tools/loading.tsx +10 -0
  195. package/app/tools/page.tsx +62 -0
  196. package/app/tools/tools-catalog.ts +141 -0
  197. package/app/wizard/PageClient.tsx +485 -0
  198. package/app/wizard/icons.tsx +89 -0
  199. package/app/wizard/loading.tsx +24 -0
  200. package/app/wizard/page.tsx +7 -0
  201. package/app/wizard/storage.ts +33 -0
  202. package/app/wizard/useWizard.ts +101 -0
  203. package/app/workspace/WorkspaceClient.tsx +633 -0
  204. package/app/workspace/loading.tsx +36 -0
  205. package/app/workspace/page.tsx +8 -0
  206. package/bin/docker-entrypoint.sh +21 -0
  207. package/bin/postinstall.js +20 -0
  208. package/bin/rev4a.js +696 -0
  209. package/daemon.js +783 -0
  210. package/docs/ARCHITECTURE.md +557 -0
  211. package/docs/CONTAINER-TERMINAL.md +280 -0
  212. package/docs/DESIGN-SYSTEM.md +99 -0
  213. package/docs/FRONTEND-ARCHITECTURE.md +119 -0
  214. package/docs/REV4A.md +504 -0
  215. package/docs/dev/API-REFERENCE.md +1835 -0
  216. package/docs/dev/DATABASE.md +224 -0
  217. package/docs/dev/GATEWAY.md +300 -0
  218. package/docs/dev/PROVIDERS.md +229 -0
  219. package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
  220. package/docs/dev/WORKSPACE.md +199 -0
  221. package/docs/rag/DATA-FRESHNESS.md +109 -0
  222. package/docs/rag/GLOSSARY.md +125 -0
  223. package/docs/rag/REV4A-OVERVIEW.md +135 -0
  224. package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
  225. package/lib/agent-setup.ts +130 -0
  226. package/lib/alerts.ts +115 -0
  227. package/lib/apiFetch.ts +7 -0
  228. package/lib/auth.ts +28 -0
  229. package/lib/billing.ts +100 -0
  230. package/lib/buildAgentImage.ts +346 -0
  231. package/lib/channelManager.ts +386 -0
  232. package/lib/container.ts +60 -0
  233. package/lib/credentials/db.ts +86 -0
  234. package/lib/credentials/delivery.ts +448 -0
  235. package/lib/credentials/detect.ts +149 -0
  236. package/lib/credentials/hash.ts +9 -0
  237. package/lib/credentials/providers.ts +110 -0
  238. package/lib/credentials/vault.ts +195 -0
  239. package/lib/db-bootstrap.d.ts +3 -0
  240. package/lib/db-bootstrap.mjs +144 -0
  241. package/lib/db.ts +25 -0
  242. package/lib/docker-utils.ts +60 -0
  243. package/lib/hooks/useDashboard.ts +264 -0
  244. package/lib/hooks/useRev4aTimezone.ts +28 -0
  245. package/lib/hooks/useScreenshot.ts +30 -0
  246. package/lib/memory-context.ts +385 -0
  247. package/lib/model-catalogue.ts +131 -0
  248. package/lib/model-pricing.ts +63 -0
  249. package/lib/openclaw-cron.ts +350 -0
  250. package/lib/patterns/ApiAdapter.ts +73 -0
  251. package/lib/patterns/ApiClient.ts +65 -0
  252. package/lib/patterns/EventBus.ts +113 -0
  253. package/lib/patterns/FilterStrategy.ts +79 -0
  254. package/lib/patterns/SessionFactory.ts +117 -0
  255. package/lib/patterns/sessionPresentation.ts +117 -0
  256. package/lib/provider-balance.ts +163 -0
  257. package/lib/requireAuth.tsx +38 -0
  258. package/lib/rev4a-auth.d.ts +3 -0
  259. package/lib/rev4a-auth.js +36 -0
  260. package/lib/rev4a-paths.ts +145 -0
  261. package/lib/timezone.ts +46 -0
  262. package/lib/types/index.ts +96 -0
  263. package/lib/utils/format.ts +82 -0
  264. package/lineage.js +56 -0
  265. package/model-pricing.json +482 -0
  266. package/models.config.json +844 -0
  267. package/next-env.d.ts +6 -0
  268. package/next.config.mjs +7 -0
  269. package/package.json +82 -0
  270. package/public/apple-touch-icon.png +0 -0
  271. package/public/favicon-64.png +0 -0
  272. package/public/favicon.svg +58 -0
  273. package/public/icon-192-maskable.png +0 -0
  274. package/public/icon-192.png +0 -0
  275. package/public/icon-512-maskable.png +0 -0
  276. package/public/icon-512.png +0 -0
  277. package/public/manifest.json +38 -0
  278. package/public/rev4a-logo.png +0 -0
  279. package/public/rev4a-logo.svg +58 -0
  280. package/rev4a-rules/AGENTS.md +37 -0
  281. package/scripts/add-auth.cjs +185 -0
  282. package/scripts/backup.sh +66 -0
  283. package/scripts/check-providers.mjs +143 -0
  284. package/scripts/docker/traefik/docker-compose.yml +32 -0
  285. package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
  286. package/scripts/docker/traefik/traefik.yml +37 -0
  287. package/scripts/remove-auth-profile.js +56 -0
  288. package/scripts/restore.sh +82 -0
  289. package/terminal-ws-server.js +139 -0
  290. package/tsconfig.json +42 -0
@@ -0,0 +1,1835 @@
1
+ # Rev4a API Reference
2
+
3
+ > **Last updated:** 2026-07-19
4
+
5
+ All routes are under `/api/`. Authentication is required on every endpoint
6
+ unless otherwise noted.
7
+
8
+ ---
9
+
10
+ ## Authentication
11
+
12
+ ### Bearer token (programmatic)
13
+ ```
14
+ Authorization: Bearer <REV4A_TOKEN>
15
+ ```
16
+ Set `REV4A_TOKEN` env var. No default — auth is disabled when unset.
17
+
18
+ ### Browser cookie (UI)
19
+ After a successful `POST /api/auth/login`, the server sets an `rev4a_token`
20
+ cookie (signed JWT, 7-day expiry). All subsequent UI requests use this cookie
21
+ automatically.
22
+
23
+ ### API-only fallbacks (in order of precedence)
24
+ 1. `Authorization: Bearer <token>`
25
+ 2. `?token=<token>` query param
26
+ 3. `x-agent-token` header
27
+
28
+ For Control UI deep links, prefer `#token=<token>` instead of `?token=<token>`. The hash fragment is consumed by the browser client before the WebSocket connects.
29
+
30
+ ---
31
+
32
+ ## Auth
33
+
34
+ ### `POST /api/auth/login`
35
+ Login — exchange the static password for a signed JWT cookie.
36
+
37
+ **Body:** `{ "password": "***" }`
38
+
39
+ **Response:**
40
+ ```json
41
+ { "ok": true }
42
+ ```
43
+ Sets `Set-Cookie: rev4a_token=<jwt>; HttpOnly; SameSite=Lax`.
44
+
45
+ **Error:** `401` if password is wrong.
46
+
47
+ ### `GET /api/auth/check`
48
+ Validate the current browser cookie and report whether the user is authenticated.
49
+
50
+ **Auth:** browser cookie
51
+
52
+ **Response:**
53
+ ```json
54
+ { "authenticated": true }
55
+ ```
56
+
57
+ **Error:** `401` with `{ "authenticated": false }` if the cookie is missing or invalid.
58
+
59
+ ### `POST /api/auth/logout`
60
+ Delete the `rev4a_token` cookie.
61
+
62
+ **Auth:** browser cookie
63
+
64
+ **Response:**
65
+ ```json
66
+ { "ok": true }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Onboarding
72
+
73
+ ### `GET /api/onboarding/status`
74
+ Returns per-step onboarding progress.
75
+
76
+ **Auth:** browser cookie
77
+
78
+ **Response:**
79
+ ```json
80
+ {
81
+ "complete": false,
82
+ "steps": { "welcome": true, "providers": false, "agents": false },
83
+ "completedAt": null
84
+ }
85
+ ```
86
+
87
+ If no progress file exists, returns all steps as `false`.
88
+
89
+ ### `POST /api/onboarding/complete`
90
+ Mark a step as completed, complete all steps, or reset the wizard.
91
+
92
+ **Auth:** browser cookie
93
+
94
+ **Body:**
95
+ | Shape | Effect |
96
+ |---|---|
97
+ | `{ "step": "providers" }` | Mark a single step as done |
98
+ | `{}` | Mark all steps as done (complete onboarding) |
99
+ | `{ "reset": true }` | Reset all steps to `false` (restart wizard) |
100
+
101
+ **Response:**
102
+ ```json
103
+ {
104
+ "status": "ok",
105
+ "steps": { "welcome": true, "providers": true, "agents": false },
106
+ "complete": false
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Gateway
113
+
114
+ The Gateway API reads the model catalogue from [`models.config.json`](../models.config.json)
115
+ (tracked in the repository). Full documentation at [GATEWAY.md](GATEWAY.md) and
116
+ [PROVIDERS.md](PROVIDERS.md).
117
+
118
+ ### `GET /api/gateway`
119
+ Returns live gateway status from all agent containers.
120
+
121
+ **Auth:** any auth method
122
+
123
+ **Response:**
124
+ ```json
125
+ {
126
+ "agents": {
127
+ "total": 1,
128
+ "list": [
129
+ {
130
+ "containerName": "openclaw-atlas",
131
+ "agentId": "atlas",
132
+ "agentName": "Atlas",
133
+ "defaultModel": "rev4a/deepseek/deepseek-v4-flash",
134
+ "configured": true
135
+ }
136
+ ]
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### `PUT /api/gateway/provider`
142
+ Trigger a full provider model sync to all agent containers.
143
+
144
+ **Auth:** any auth method
145
+
146
+ **Body:** none (reads current provider state from `models.config.json` and `data/provider-keys.json`)
147
+
148
+ **Response:**
149
+ ```json
150
+ {
151
+ "status": "ok",
152
+ "activeModels": 2,
153
+ "results": ["Active models: 2 across 1 agent(s)"]
154
+ }
155
+ ```
156
+
157
+ **Side effects:**
158
+ - Writes `models.providers.rev4a` to each agent container's `openclaw.json`
159
+ - Cleans up stale `models.json` and `auth-profiles.json` inside containers
160
+ - **Does NOT touch `agents.defaults.model` or `agents.list[].model`** — model references are managed per-container via the Agents Tab (`PUT /api/gateway/agent`)
161
+ - Does NOT restart the gateway (config is written live to the file)
162
+
163
+ ### `PUT /api/gateway/agent`
164
+ Update model config for a single agent container.
165
+
166
+ **Auth:** any auth method
167
+
168
+ **Body:**
169
+ ```json
170
+ {
171
+ "containerName": "openclaw-atlas",
172
+ "model": "deepseek/deepseek-v4-flash",
173
+ "fallbacks": []
174
+ }
175
+ ```
176
+ If `fallbacks` is omitted or empty, no fallback models are set.
177
+
178
+ **Response:**
179
+ ```json
180
+ {
181
+ "status": "ok",
182
+ "agent": "openclaw-atlas",
183
+ "model": {
184
+ "primary": "rev4a/deepseek/deepseek-v4-flash"
185
+ },
186
+ "verify": { "ok": true, "bytes": 2058 }
187
+ }
188
+ ```
189
+
190
+ **Behaviour:**
191
+ - Writes `agents.defaults.model.primary` and `agents.list[0].model.primary` with format `rev4a/<provider>/<model>`
192
+ - No restart — writes are live via `docker exec node -e`
193
+ - Does NOT set fallbacks unless explicitly provided
194
+
195
+ ### `GET /api/gateway/provider`
196
+ Returns current provider configuration state.
197
+
198
+ **Auth:** any auth method
199
+
200
+ **Response:**
201
+ ```json
202
+ {
203
+ "providers": [
204
+ {
205
+ "provider": "deepseek",
206
+ "label": "DeepSeek",
207
+ "configured": true,
208
+ "baseUrl": "https://api.deepseek.com",
209
+ "docsUrl": "https://platform.deepseek.com/api_keys",
210
+ "models": [
211
+ { "id": "deepseek/deepseek-v4-flash", "name": "DeepSeek V4 Flash", "enabled": true },
212
+ { "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro", "enabled": true }
213
+ ]
214
+ }
215
+ ]
216
+ }
217
+ ```
218
+
219
+ ### `GET /api/provider/v1/models`
220
+
221
+ Rev4a Provider Gateway — returns available models filtered by auth token.
222
+
223
+ **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
224
+
225
+ **Response (authenticated):**
226
+ ```json
227
+ {
228
+ "object": "list",
229
+ "total": 9,
230
+ "data": [
231
+ { "id": "deepseek/deepseek-v4-flash", "object": "model", "created": 1700000000, "owned_by": "deepseek", "permission": [], "root": "deepseek/deepseek-v4-flash" }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ **Response (unauthenticated):**
237
+ ```json
238
+ { "object": "list", "total": 0, "data": [] }
239
+ ```
240
+
241
+ ### `POST /api/provider/v1/chat/completions`
242
+
243
+ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
244
+
245
+ **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
246
+
247
+ **Body:**
248
+ ```json
249
+ {
250
+ "model": "rev4a/deepseek-v4-flash",
251
+ "messages": [{"role": "user", "content": "Hello"}]
252
+ }
253
+ ```
254
+
255
+ **Error (401):**
256
+ ```json
257
+ { "error": { "message": "Invalid API key...", "type": "authentication_error" } }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Config
263
+
264
+ ### `PUT /api/config/env`
265
+ Update environment variables on the VPS host.
266
+
267
+ **Auth:** browser cookie
268
+
269
+ **Body:** `{ "REV4A_PASSWORD": "newpass", ... }`
270
+
271
+ **Side effects:**
272
+ - Writes to `.env` in the project root
273
+ - Automatically syncs to `/etc/systemd/system/rev4a.service` (removes all `Environment=REV4A_*` and `Environment=PROVIDER_*` lines, appends current vars)
274
+ - Runs `systemctl daemon-reload` so the next restart picks up the new environment
275
+ - Only keys prefixed with `REV4A_` or `PROVIDER_` are accepted
276
+
277
+ **Response:** `{ "status": "ok", "updated": ["REV4A_PASSWORD", ...] }`
278
+
279
+ **Error (400):** `{ "error": "Keys not allowed: ..." }`
280
+
281
+ ### `POST /api/config/restart`
282
+ Restart the Rev4a server (via systemd).
283
+
284
+ **Auth:** browser cookie
285
+
286
+ **Response:** `{ "status": "ok", "message": "Service restarted" }`
287
+
288
+ ---
289
+
290
+ ## Sessions
291
+
292
+ ### `GET /api/sessions`
293
+ Returns all sessions (up to 2000), newest first, joined with lineage labels.
294
+
295
+ **Auth:** browser cookie
296
+
297
+ **Response:**
298
+ ```json
299
+ [
300
+ {
301
+ "session_id": "agent:ops:main",
302
+ "parent_id": null,
303
+ "label": "Argus",
304
+ "model": "openai-codex/gpt-5.4",
305
+ "tokens_in": 12500,
306
+ "tokens_out": 3200,
307
+ "cost_usd": 0.085,
308
+ "status": "idle",
309
+ "task_preview": "Run hygiene audit...",
310
+ "started_at": 1749200000,
311
+ "ended_at": null,
312
+ "updated_at": 1749201000,
313
+ "trello_card_url": null,
314
+ "lineage_label": null,
315
+ "lineage_agent_name": null
316
+ }
317
+ ]
318
+ ```
319
+
320
+ ---
321
+
322
+ ### `GET /api/session?id=<session_id>`
323
+ Returns a single session with its events and child sessions.
324
+
325
+ **Auth:** browser cookie
326
+
327
+ **Query params:** `id` (required)
328
+
329
+ **Response:**
330
+ ```json
331
+ {
332
+ "session": { ...session row... },
333
+ "events": [ ...event rows... ],
334
+ "children": [ ...session rows... ]
335
+ }
336
+ ```
337
+
338
+ **Error:** `400` if `id` missing; `404` if not found.
339
+
340
+ ---
341
+
342
+ ## Stats & Costs
343
+
344
+ ### `GET /api/stats`
345
+ Current-month aggregate stats (tokens + cost) grouped by model.
346
+
347
+ **Auth:** Bearer or browser cookie
348
+
349
+ **Response:**
350
+ ```json
351
+ {
352
+ "total": {
353
+ "total": 1.24,
354
+ "total_in": 850000,
355
+ "total_out": 210000,
356
+ "sessions": 47
357
+ },
358
+ "byModel": [
359
+ { "model": "openai-codex/gpt-5.4", "cost": 0.95, "tokens_in": 600000, "tokens_out": 150000, "sessions": 30 }
360
+ ]
361
+ }
362
+ ```
363
+
364
+ ---
365
+
366
+ ### `GET /api/stats-since?ts=<unix_ms>`
367
+ Total cost (USD) since a given timestamp.
368
+
369
+ **Auth:** Bearer or browser cookie
370
+
371
+ **Query params:** `ts` — Unix timestamp in milliseconds
372
+
373
+ **Response:**
374
+ ```json
375
+ { "total": 0.42 }
376
+ ```
377
+
378
+ ---
379
+
380
+ ### `GET /api/costs`
381
+ Full cost breakdown: today, 7d, 30d, all-time, per-model, plus cost-override for the current month.
382
+
383
+ **Auth:** browser cookie
384
+
385
+ **Response:**
386
+ ```json
387
+ {
388
+ "today": 0.12,
389
+ "week": 1.85,
390
+ "month": 6.40,
391
+ "allTime": 42.10,
392
+ "override": null,
393
+ "byModel": [ ... ]
394
+ }
395
+ ```
396
+
397
+ ---
398
+
399
+ ### `GET /api/cost-override?month=YYYY-MM`
400
+ Read manual cost override for a month.
401
+
402
+ **Auth:** Bearer or browser cookie
403
+
404
+ **Response:** `{ "month": "2026-06", "amount": 124.10, "note": "GitHub billing" }` or `{ "month": "2026-06", "amount": null, "note": null }` if not set.
405
+
406
+ ---
407
+
408
+ ### `POST /api/cost-override`
409
+ Set or update a manual cost override.
410
+
411
+ **Auth:** Bearer or browser cookie
412
+
413
+ **Body:** `{ "month": "2026-06", "amount": 124.10, "note": "GitHub billing" }`
414
+
415
+ **Response:** `{ "ok": true }`
416
+
417
+ **Error:** `400` if `amount` missing.
418
+
419
+ ---
420
+
421
+ ## Events
422
+
423
+ ### `GET /api/events?limit=50&offset=0`
424
+ Paginated event log.
425
+
426
+ **Auth:** browser cookie
427
+
428
+ **Query params:** `limit` (default 50), `offset` (default 0)
429
+
430
+ **Response:**
431
+ ```json
432
+ [
433
+ {
434
+ "id": 1042,
435
+ "ts": 1749201500000,
436
+ "session_id": "agent:ops:subagent:uuid",
437
+ "type": "spawn",
438
+ "data": "{\"task\":\"run hygiene...\"}"
439
+ }
440
+ ]
441
+ ```
442
+
443
+ ---
444
+
445
+ ### `GET /api/stream`
446
+ Server-Sent Events stream. Pushes a combined payload every ~3 s.
447
+
448
+ **Auth:** browser cookie
449
+
450
+ **Event format:**
451
+ ```
452
+ data: {"events":[...],"sessions":[...],"costs":{"today":0.12},"lineage":[...]}
453
+ ```
454
+
455
+ Use `EventSource` in the browser or `curl -N` for testing.
456
+
457
+ ### `GET /api/workspace/stream`
458
+ Workspace filesystem change stream for the editor.
459
+
460
+ **Auth:** browser cookie
461
+
462
+ **Response:** `text/event-stream`
463
+
464
+ **Event types:**
465
+ - `workspace_ready` — initial snapshot metadata
466
+ - `workspace_changed` — added, modified, or removed files/directories
467
+ - `heartbeat` — emitted when no changes are detected
468
+ - `workspace_error` — emitted if a scan cycle fails
469
+
470
+ **Initial payload example:**
471
+ ```json
472
+ {
473
+ "type": "workspace_ready",
474
+ "ts": 1751270000000,
475
+ "root": "/path/to/workspace",
476
+ "count": 42
477
+ }
478
+ ```
479
+
480
+ **Change payload example:**
481
+ ```json
482
+ {
483
+ "type": "workspace_changed",
484
+ "ts": 1751270003000,
485
+ "changed": [
486
+ {
487
+ "path": "/path/to/workspace/rev4a/README.md",
488
+ "rel_path": "rev4a/README.md",
489
+ "type": "file",
490
+ "size": 2048,
491
+ "mtimeMs": 1751270002999,
492
+ "change": "modified"
493
+ }
494
+ ],
495
+ "truncated": false
496
+ }
497
+ ```
498
+
499
+ **Notes:**
500
+ - The stream polls every 3 seconds.
501
+ - Hidden paths and directories such as `.trash` and `node_modules` are ignored.
502
+ - Only a fixed allowlist of file extensions is included.
503
+
504
+ ---
505
+
506
+ ## Tool Calls
507
+
508
+ ### `GET /api/tool-calls?session_id=<id>`
509
+ Tool call events for a session.
510
+
511
+ **Auth:** Bearer or browser cookie
512
+
513
+ **Query params:** `session_id` (required)
514
+
515
+ **Response:**
516
+ ```json
517
+ [
518
+ { "id": 12, "ts": 1749200100000, "session_id": "...", "type": "tool_call", "data": "{...}" }
519
+ ]
520
+ ```
521
+
522
+ ---
523
+
524
+ ## Agents
525
+
526
+ ### `GET /api/agents`
527
+ List running agent containers (Docker containers with `AGENT_ID` label) with full metadata.
528
+
529
+ **Auth:** browser cookie
530
+
531
+ **Response:**
532
+ ```json
533
+ [
534
+ {
535
+ "id": "abc123def456",
536
+ "agentId": "prometheus",
537
+ "name": "prometheus",
538
+ "image": "openclaw-agent-base:latest",
539
+ "imageTag": "latest",
540
+ "template": "prometheus",
541
+ "status": "running",
542
+ "state": "running",
543
+ "ports": "",
544
+ "ip": "172.19.0.5",
545
+ "created": "2026-07-01T16:58:42.876829416Z",
546
+ "env": ["AGENT_ID=prometheus", "AGENT_HOSTNAME=<agent-name>.<your-domain>.com"],
547
+ "authToken": "***",
548
+ "traefikUrl": "http://187.77.156.41:3033#token=asdfghjkl"
549
+ }
550
+ ]
551
+
552
+ **Notes:**
553
+ - The URL is always `http://<host-ip>:<port>#token=...` (every agent gets a host port mapping)
554
+ - The `authToken` uses the shared gateway token from `data/agents-token.json`
555
+ - Containers are discovered via Docker API with label filter `AGENT_ID`
556
+ - Template is inferred from the container image name + agent ID directory match in `agent-templates/`
557
+
558
+ ---
559
+
560
+ ### `POST /api/agents/create`
561
+ Create a new agent container from a template.
562
+
563
+ **Auth:** browser cookie
564
+
565
+ **Body:**
566
+ ```json
567
+ {
568
+ "name": "my-agent",
569
+ "template": "prometheus",
570
+ "portRange": "3700-3709",
571
+ "model": "deepseek/deepseek-v4-flash",
572
+ "fallbacks": []
573
+ }
574
+ ```
575
+
576
+ | Field | Required | Description |
577
+ |---|---|---|
578
+ | `name` | yes | Container name, also becomes `AGENT_ID` and subdomain |
579
+ | `template` | yes | Template name (directory in `agent-templates/`) |
580
+ | `portRange` | no | Optional port range (e.g. `3700-3709`) or single port (e.g. `3700`). Default: auto-assigned 10-port block |
581
+ | `model` | no | Primary model ID from `models.config.json` |
582
+ | `fallbacks` | no | Array of fallback model IDs |
583
+
584
+ **Every agent is created with a host port mapping:**
585
+ - Port block: `-p <start>-<end>:3000-<3000+offset>` — maps a range of host ports to the same range starting at container port 3000
586
+ - Single port: `-p <port>:3000` — maps a single host port to container port 3000
587
+ - URL format: `http://<host-ip>:<portStart>#token=...`
588
+ - Port 3740 is reserved for Rev4a
589
+
590
+ **Response:**
591
+ ```json
592
+ {
593
+ "success": true,
594
+ "containerId": "00f5de1eb08d...",
595
+ "name": "my-agent",
596
+ "image": "openclaw-agent-base:latest",
597
+ "network": "rev4a-network",
598
+ "traefikUrl": "http://187.77.156.41:3700#token=asdfghjkl",
599
+ "port": 3700,
600
+ "portEnd": 3709,
601
+ "isBlock": true,
602
+ "portDisplay": "3700-3709"
603
+ }
604
+ ```
605
+
606
+ **Post-creation pipeline:**
607
+ 1. OpenClaw gateway starts with `--allow-unconfigured`, generating its own default config
608
+ 2. The route waits for the gateway to be fully up (health check poll, up to 60 s)
609
+ 3. Once ready, writes `gateway.controlUi.allowedOrigins` + `agents.defaults.model.primary` + fallbacks
610
+ 4. Writes `models.providers.rev4a` + extraDirs + update config → single `openclaw.json` write → single `gateway restart`
611
+
612
+ > **Note:** The entrypoint no longer generates `openclaw.json`. All post-creation config is written by the create route after gateway readiness, eliminating the race condition where the entrypoint would overwrite synced provider config.
613
+
614
+ **Side effects:**
615
+ - Template files (`AGENTS.md`, `SOUL.md`, etc.) are copied into the container workspace via `docker cp`
616
+ - The agent URL is included in the response
617
+ - An event is logged to `data/events.db` if available
618
+
619
+ **Error:** `400` for validation errors, `409` if name/port already in use
620
+
621
+ ---
622
+
623
+ ### `DELETE /api/agents/[id]`
624
+ **Destructive.** Permanently deletes the agent: the container, its persistent
625
+ volume (`agent-<id>-data`), and **all** its backups (`agent-<id>-*.tar.gz` in the
626
+ `rev4a-backups` volume). This cannot be undone. Volume and backup removal are
627
+ best-effort — a missing volume or absent backups do not fail the delete.
628
+
629
+ The `id` is validated (`isValidAgentId`) before being interpolated into any
630
+ docker command.
631
+
632
+ **Auth:** browser cookie
633
+
634
+ **Response:**
635
+ ```json
636
+ { "success": true }
637
+ ```
638
+
639
+ **Error (400):** `{ "success": false, "error": "Invalid agent id" }`
640
+ **Error (404):** `{ "error": "No container found with AGENT_ID '...'" }`
641
+
642
+ ---
643
+
644
+ ### `POST /api/agents/[id]/restart`
645
+ Restart an agent container (shorter than recreate — keeps everything intact).
646
+
647
+ **Auth:** browser cookie
648
+
649
+ **Response:**
650
+ ```json
651
+ { "success": true }
652
+ ```
653
+
654
+ **Error (404):** `{ "error": "No container found with AGENT_ID '...'" }`
655
+
656
+ ---
657
+
658
+ ### `POST /api/agents/[id]/backup`
659
+ Create a backup of the agent's persistent volume.
660
+
661
+ **Auth:** browser cookie
662
+
663
+ **Response:**
664
+ ```json
665
+ { "success": true, "file": "agent-prometheus-2026-07-12_043512345.tar.gz" }
666
+ ```
667
+
668
+ The backup file lives in Docker volume `rev4a-backups`. Backups exclude the npm
669
+ package cache (`~/.npm/_cacache/`) to keep them small (~1.5 MB instead of 227 MB).
670
+
671
+ **Error (404):** `{ "error": "No persistent volume found for agent '...'" }`
672
+
673
+ ---
674
+
675
+ ### `GET /api/agents/[id]/backup`
676
+ List available backups for a specific agent.
677
+
678
+ **Auth:** browser cookie
679
+
680
+ **Response:**
681
+ ```json
682
+ {
683
+ "backups": [
684
+ { "name": "agent-prometheus-2026-07-12_043512345.tar.gz", "size": "1.6 MB", "date": "12/07/2026 04:35" }
685
+ ]
686
+ }
687
+ ```
688
+
689
+ Backups are sorted most-recent-first.
690
+
691
+ ---
692
+
693
+ ### `DELETE /api/agents/[id]/backup?file=agent-prometheus-2026-07-12_043512345.tar.gz`
694
+ Remove a specific backup file.
695
+
696
+ **Auth:** browser cookie
697
+
698
+ **Response:**
699
+ ```json
700
+ { "success": true }
701
+ ```
702
+
703
+ **Error (400):** if filename is invalid or contains path traversal
704
+
705
+ ---
706
+
707
+ ### `POST /api/agents/[id]/restore?file=agent-prometheus-2026-07-12_043512345.tar.gz`
708
+ Restore an agent's persistent volume from a backup.
709
+
710
+ The container is stopped, the volume content is replaced with the backup, then
711
+ the container is restarted. If no container exists with the given `AGENT_ID`,
712
+ the volume is restored anyway (useful for volume-only agents).
713
+
714
+ **Auth:** browser cookie
715
+
716
+ **Response:**
717
+ ```json
718
+ { "success": true, "container": "prometheus" }
719
+ ```
720
+
721
+ **Error (404):** if the backup file is not found
722
+
723
+ ---
724
+
725
+ ### `POST /api/agents/[id]/recreate`
726
+ Rebuild the agent container from the **latest** `openclaw-agent-base:latest` image
727
+ while preserving the persistent volume (workspace files, credentials, state DB,
728
+ config). This is how an existing agent picks up image/OpenClaw/CLI updates: the
729
+ software lives in the image, the data in the volume.
730
+
731
+ Flow:
732
+ 1. **Auto-backup first.** The volume is backed up to `agent-<id>-prerecreate-<ts>.tar.gz`
733
+ in `rev4a-backups`. **If the backup fails, the recreate is aborted** and the
734
+ running container is left untouched.
735
+ 2. The old container is removed (`docker rm -f`) and a new one is created with the
736
+ same image tag, environment variables (`AGENT_*`, `MODEL_*`, `OPENCLAW_*`),
737
+ labels, port mappings, and network — reattaching the same volume.
738
+ 3. After startup, `applyRuntimeConfig()` (from `lib/agent-setup.ts`) guarantees:
739
+ - `hooks.bootstrap-extra-files` (Rev4a system rules injection)
740
+ - `skills.load.extraDirs` (shared skills discovery)
741
+ - `models.providers.rev4a` (provider proxy, host-dependent)
742
+ All patches use `config patch --stdin` (deep-merge) — existing user
743
+ customizations are never overwritten.
744
+
745
+ On the next boot, the image entrypoint runs `openclaw doctor --non-interactive`
746
+ **only if the OpenClaw version changed** (safe migrations, no service restart),
747
+ aligning the volume's state/config to the new binary.
748
+
749
+ **Auth:** browser cookie
750
+
751
+ **Response:**
752
+ ```json
753
+ { "success": true, "name": "prometheus", "image": "openclaw-agent-base:latest", "network": "openclaw-core_default", "backup": "agent-prometheus-prerecreate-2026-07-12_195512345.tar.gz" }
754
+ ```
755
+
756
+ **Error (500):** `{ "error": "Pre-recreate backup failed, aborting: ..." }`
757
+
758
+ ---
759
+
760
+ ## Channels
761
+
762
+ Channel APIs read/write agent channel configuration (Telegram) directly inside agent containers via `docker exec`. All endpoints require the agent container to be running.
763
+
764
+ ### `GET /api/agents/[id]/channels`
765
+ Get channel configuration for an agent.
766
+
767
+ **Auth:** browser cookie
768
+
769
+ **Response:**
770
+ ```json
771
+ {
772
+ "agentId": "atlas",
773
+ "telegram": {
774
+ "connected": true,
775
+ "botToken": "1234...5678",
776
+ "dmPolicy": "pairing",
777
+ "allowFrom": ["123456789"]
778
+ }
779
+ }
780
+ ```
781
+
782
+ - `telegram` is `null` if not configured
783
+ - `botToken` is masked (first 4 + last 4 chars)
784
+ - `allowFrom` lists approved sender IDs
785
+
786
+ ---
787
+
788
+ ### `PUT /api/agents/[id]/channels/telegram`
789
+ Connect Telegram to an agent by saving a bot token.
790
+
791
+ **Auth:** browser cookie
792
+
793
+ **Body:**
794
+ ```json
795
+ {
796
+ "botToken": "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
797
+ }
798
+ ```
799
+
800
+ | Field | Required | Description |
801
+ |---|---|---|
802
+ | `botToken` | yes | Bot token from @BotFather |
803
+
804
+ **Response (200):** `{ "success": true }`
805
+
806
+ **Response (400):** `{ "error": "botToken is required" }`
807
+
808
+ **Response (404):** `{ "error": "Agent container not found: ..." }`
809
+
810
+ **Side effects:**
811
+ - Writes `channels.telegram.botToken` and `channels.telegram.dmPolicy: "pairing"` to `/root/.openclaw/openclaw.json`
812
+ - Creates a binding for this agent → telegram channel
813
+ - If the token changed, wipes previous pairing data
814
+ - The agent must be restarted for the gateway to pick up the new config
815
+
816
+ ---
817
+
818
+ ### `DELETE /api/agents/[id]/channels/telegram`
819
+ Disconnect Telegram from an agent.
820
+
821
+ **Auth:** browser cookie
822
+
823
+ **Response (200):** `{ "success": true }`
824
+
825
+ **Response (404):** `{ "error": "Agent container not found: ..." }`
826
+
827
+ **Side effects:**
828
+ - Sets `channels.telegram.enabled: false`, removes `botToken` and `allowFrom`
829
+ - Removes the telegram binding
830
+ - Deletes pairing files (`telegram-pairing.json`, `telegram-default-allowFrom.json`)
831
+ - No restart required
832
+
833
+ ---
834
+
835
+ ### `GET /api/agents/[id]/channels/pairing?channel=telegram`
836
+ Get pending and approved pairings for a channel.
837
+
838
+ **Auth:** browser cookie
839
+
840
+ **Query params:**
841
+ | Param | Required | Description |
842
+ |---|---|---|
843
+ | `channel` | no | Channel name (default: `telegram`) |
844
+
845
+ **Response:**
846
+ ```json
847
+ {
848
+ "pending": [
849
+ { "code": "A1B2", "senderId": null }
850
+ ],
851
+ "approved": [
852
+ { "senderId": "123456789" }
853
+ ]
854
+ }
855
+ ```
856
+
857
+ - Pending codes are from `openclaw pairing list --json`
858
+ - Approved senders are from the credentials allowFrom file
859
+
860
+ ---
861
+
862
+ ### `POST /api/agents/[id]/channels/pairing?channel=telegram`
863
+ Approve a pending pairing code.
864
+
865
+ **Auth:** browser cookie
866
+
867
+ **Query params:**
868
+ | Param | Required | Description |
869
+ |---|---|---|
870
+ | `channel` | no | Channel name (default: `telegram`) |
871
+
872
+ **Body:**
873
+ ```json
874
+ { "code": "A1B2" }
875
+ ```
876
+
877
+ **Response (200):** `{ "success": true }`
878
+
879
+ **Response (400):** `{ "error": "Pairing code is required" }`
880
+
881
+ **Response (404):** `{ "error": "..." }`
882
+
883
+ ---
884
+
885
+ ### `DELETE /api/agents/[id]/channels/pairing?channel=telegram&senderId=123456789`
886
+ Revoke an approved sender.
887
+
888
+ **Auth:** browser cookie
889
+
890
+ **Query params:**
891
+ | Param | Required | Description |
892
+ |---|---|---|
893
+ | `channel` | no | Channel name (default: `telegram`) |
894
+ | `senderId` | yes | Sender ID to revoke |
895
+
896
+ **Response (200):** `{ "success": true }`
897
+
898
+ **Response (400):** `{ "error": "senderId query param is required" }`
899
+
900
+ ---
901
+
902
+ ### `GET /api/agents/channels-summary`
903
+ Lightweight channel status for all running agents (used for TG chips on the agent list).
904
+
905
+ **Auth:** browser cookie
906
+
907
+ **Response:**
908
+ ```json
909
+ {
910
+ "atlas": { "telegram": true },
911
+ "prometheus": { "telegram": false }
912
+ }
913
+ ```
914
+
915
+ - Only includes agents with `AGENT_ID` Docker label
916
+ - `telegram: true` means the channel is connected and configured
917
+ - Cached for 15 seconds (maxDuration)
918
+
919
+ ---
920
+
921
+ ### `GET /api/agents/config-json`
922
+ Read the full `openclaw.json` from a running agent container (token masked).
923
+ Useful for inspecting the container config without SSH or terminal.
924
+
925
+ **Query params:** `name` (required) — Docker container name
926
+
927
+ **Response:**
928
+ ```json
929
+ {
930
+ "name": "my-agent",
931
+ "config": {
932
+ "gateway": { "controlUi": { "allowedOrigins": ["..."], "dangerouslyDisableDeviceAuth": true } },
933
+ "agents": { "defaults": { "model": { "primary": "rev4a/deepseek/deepseek-v4-flash", "fallbacks": [] } } },
934
+ "models": { "providers": { "rev4a": { "baseUrl": "...", "apiKey": "***", "models": [...] } } }
935
+ }
936
+ }
937
+ ```
938
+
939
+ **Error (404):** `{ "error": "Config file not found or empty in container" }`
940
+ **Error (400):** `{ "error": "name query param is required" }`
941
+
942
+ ---
943
+
944
+ ### `GET /api/agents/token`
945
+ Read the shared agents gateway token.
946
+
947
+ **Auth:** browser cookie
948
+
949
+ **Response:**
950
+ ```json
951
+ { "token": "asdfghjkl", "updated_at": 1751270000000 }
952
+ ```
953
+
954
+ The token is stored in `data/agents-token.json`.
955
+
956
+ ---
957
+
958
+ ### `PUT /api/agents/token`
959
+ Update the shared agents gateway token and push it to all running agent containers.
960
+
961
+ **Auth:** browser cookie
962
+
963
+ **Body:**
964
+ ```json
965
+ { "token": "new-token-value" }
966
+ ```
967
+
968
+ **Response:**
969
+ ```json
970
+ { "success": true, "containersUpdated": 2 }
971
+ ```
972
+
973
+ **Side effects:**
974
+ - Writes the new token to `data/agents-token.json`
975
+ - Writes the token file (`/root/.agent-token`) into every running container with `AGENT_ID` label
976
+ - Restarts each container so the entrypoint picks up the new token via `OPENCLAW_GATEWAY_TOKEN` env
977
+
978
+ ---
979
+
980
+ ## Agent Image Management
981
+
982
+ ### `GET /api/agents/image-status`
983
+ Check the status of the `openclaw-agent-base` Docker image.
984
+
985
+ **Auth:** public (no auth required)
986
+
987
+ **Response:**
988
+ ```json
989
+ {
990
+ "exists": true,
991
+ "needsUpdate": false,
992
+ "downloading": false,
993
+ "localId": "sha256:a1b2c3d4e5f6..."
994
+ }
995
+ ```
996
+
997
+ | Field | Description |
998
+ |---|---|
999
+ | `exists` | Whether `openclaw-agent-base:latest` exists locally |
1000
+ | `needsUpdate` | `true` if local manifest digest differs from remote registry |
1001
+ | `downloading` | Whether a download is currently running |
1002
+ | `localId` | SHA256 ID of the local image, or null |
1003
+
1004
+ ### `POST /api/agents/download-image`
1005
+ Starts pulling or building the `openclaw-agent-base:latest` image. Returns 202
1006
+ Accepted immediately — the operation runs in the background and writes logs.
1007
+ The frontend polls `GET /api/agents/image-status` for progress updates.
1008
+
1009
+ **Auth:** public (no auth required)
1010
+
1011
+ **Response (202):**
1012
+ ```json
1013
+ { "status": "downloading" }
1014
+ ```
1015
+
1016
+ **Error (409):**
1017
+ ```json
1018
+ { "error": "A download is already in progress" }
1019
+ ```
1020
+
1021
+ ### `DELETE /api/agents/abort-download`
1022
+ Kills an in-progress docker pull/build.
1023
+
1024
+ **Auth:** public (no auth required)
1025
+
1026
+ **Response:**
1027
+ ```json
1028
+ { "aborted": true }
1029
+ ```
1030
+
1031
+ Image state is managed in `lib/buildAgentImage.ts`:
1032
+ - `downloadAgentImage(opts)` — attempts `docker pull` from registry first, then falls back to `docker build`
1033
+ all stdout/stderr to a log file, supports optional `onEvent` callback and `AbortSignal`
1034
+ - `abortDownload()` — sends SIGKILL to the child process, releases the download lock
1035
+ - `getDownloadLogPath()` — returns the path to the current/last download log file
1036
+ - `getIsDownloading()` — checks the in-memory lock flag
1037
+
1038
+ ### `GET /api/agents-active`
1039
+
1040
+ ### `GET /api/agents-active`
1041
+ Configured agents (from `openclaw.json`) enriched with recent session activity
1042
+ and workspace files.
1043
+
1044
+ **Auth:** browser cookie
1045
+
1046
+ **Response:**
1047
+ ```json
1048
+ [
1049
+ {
1050
+ "agent_id": "ops",
1051
+ "label": "Argus",
1052
+ "displayName": "Argus",
1053
+ "config_model": "openai-codex/gpt-5.4",
1054
+ "workspace_path": "/data/.openclaw/workspace-ops/",
1055
+ "files": [ { "name": "MEMORY.md", "path": "...", "rel_path": "MEMORY.md", "type": "markdown" } ],
1056
+ "sessions": [ ...last 5 session rows... ],
1057
+ "status": "idle",
1058
+ "config": { ...raw agent config... }
1059
+ }
1060
+ ]
1061
+ ```
1062
+
1063
+ ---
1064
+
1065
+ ### `GET /api/agents-config`
1066
+ Read agents, Telegram accounts, and bindings from `openclaw.json` (sanitized — no raw tokens).
1067
+
1068
+ **Auth:** browser cookie
1069
+
1070
+ **Response:**
1071
+ ```json
1072
+ {
1073
+ "agents": [ { "id": "ops", "label": "Argus", "model": "..." } ],
1074
+ "telegramAccounts": [ { "accountId": "argus", "tokenStatus": "masked", "enabled": true } ],
1075
+ "bindings": [ { "bindingKey": "0", "type": "telegram", "agentId": "ops" } ]
1076
+ }
1077
+ ```
1078
+
1079
+ ---
1080
+
1081
+ ### `POST /api/agents-config`
1082
+ Update agents, Telegram accounts, or bindings. Writes to `openclaw.json` with atomic rename + backup.
1083
+
1084
+ **Auth:** browser cookie
1085
+
1086
+ **Body:**
1087
+ ```json
1088
+ {
1089
+ "agents": [ { "currentId": "ops", "id": "ops", "name": "Argus" } ],
1090
+ "telegramAccounts": [],
1091
+ "bindings": []
1092
+ }
1093
+ ```
1094
+
1095
+ **Response:** `{ "success": true, "data": { ...updated config payload... } }`
1096
+
1097
+ ---
1098
+
1099
+ ## Lineage
1100
+
1101
+ ### `POST /api/lineage`
1102
+ Register a parent→child relationship between sessions.
1103
+
1104
+ **Auth:** browser cookie or Bearer
1105
+
1106
+ **Body:**
1107
+ ```json
1108
+ { "childId": "agent:ops:subagent:uuid", "parentId": "agent:ops:main", "label": "Hygiene Agent" }
1109
+ ```
1110
+
1111
+ **Response:** `{ "ok": true, "childId": "...", "parentId": "...", "label": "Hygiene Agent" }`
1112
+
1113
+ **Error:** `400` if `childId` or `parentId` missing.
1114
+
1115
+ ---
1116
+
1117
+ ## Metrics
1118
+
1119
+ ### `GET /api/metrics`
1120
+ System metrics: latest snapshot + 24 h history + 24 h aggregates.
1121
+
1122
+ **Auth:** Bearer or browser cookie
1123
+
1124
+ **Response:**
1125
+ ```json
1126
+ {
1127
+ "latest": { "ts": 1749201000000, "cpu_percent": 12.5, "ram_used_mb": 1820, "ram_total_mb": 4096, "disk_used_gb": 38.2, "disk_total_gb": 100.0, "load_avg_1m": 0.42 },
1128
+ "history": [ ...up to 288 rows (24h at 5min intervals)... ],
1129
+ "stats_24h": { "avg_cpu": 14.2, "max_cpu": 68.0, "avg_ram_mb": 1750 }
1130
+ }
1131
+ ```
1132
+
1133
+ ---
1134
+
1135
+ ## System Health
1136
+
1137
+ ### `GET /api/system-health`
1138
+ Aggregated health checks with recommendations.
1139
+
1140
+ **Auth:** browser cookie
1141
+
1142
+ **Response:**
1143
+ ```json
1144
+ {
1145
+ "health": "ok",
1146
+ "checks": [
1147
+ { "name": "daemon", "status": "ok", "detail": "last poll 18s ago" },
1148
+ { "name": "disk", "status": "warn", "detail": "82% used" }
1149
+ ],
1150
+ "recommendations": [ "Consider archiving old sessions to reduce disk usage." ],
1151
+ "generatedAt": 1749201500000
1152
+ }
1153
+ ```
1154
+
1155
+ `health` values: `"ok"` / `"warn"` / `"error"`
1156
+
1157
+ ---
1158
+
1159
+ ## Crons
1160
+
1161
+ ### `GET /api/crons`
1162
+ List of scheduled cron jobs from the host OpenClaw gateway (via `openclaw cron list --json`) and every running Docker container with the `AGENT_ID` label (via `docker exec openclaw cron list --json`).
1163
+
1164
+ Jobs from agent containers include `containerName` and `source: "container"` fields.
1165
+ Jobs from the host gateway have `source: "host"`.
1166
+
1167
+ **Auth:** Bearer or browser cookie
1168
+
1169
+ **Response:**
1170
+ ```json
1171
+ [
1172
+ {
1173
+ "id": "eef708de-...",
1174
+ "name": "openclaw-hygiene-nightly",
1175
+ "description": "Nightly workspace cleanup",
1176
+ "schedule": "15 3 * * *",
1177
+ "scheduleExpr": "15 3 * * *",
1178
+ "model": "openai-codex/gpt-5.4-mini",
1179
+ "enabled": true,
1180
+ "source": "host",
1181
+ "state": {
1182
+ "lastStatus": "ok",
1183
+ "lastRunAtMs": 1749100000000,
1184
+ "nextRunAtMs": 1749186000000
1185
+ },
1186
+ "computedNextRunAtMs": 1749186000000,
1187
+ "createdAtMs": 1748000000000
1188
+ },
1189
+ {
1190
+ "id": "c1b3e47f-...",
1191
+ "name": "atlas-weekly-report",
1192
+ "schedule": "0 9 * * 1",
1193
+ "enabled": true,
1194
+ "source": "container",
1195
+ "agentId": "atlas",
1196
+ "containerName": "openclaw-atlas",
1197
+ "computedNextRunAtMs": 1749600000000
1198
+ }
1199
+ ]
1200
+ ```
1201
+
1202
+ ### `PATCH /api/crons/[id]`
1203
+ Update a cron job. Only the `enabled` field can be patched.
1204
+
1205
+ For jobs inside a container, pass `containerName` in the body to identify which container to write to.
1206
+
1207
+ **Auth:** Bearer or browser cookie
1208
+
1209
+ **Body:**
1210
+ ```json
1211
+ {
1212
+ "enabled": false
1213
+ }
1214
+ ```
1215
+
1216
+ **Body (container cron):**
1217
+ ```json
1218
+ {
1219
+ "enabled": false,
1220
+ "containerName": "openclaw-atlas"
1221
+ }
1222
+ ```
1223
+
1224
+ **Response:**
1225
+ ```json
1226
+ {
1227
+ "id": "eef708de-...",
1228
+ "name": "openclaw-hygiene-nightly",
1229
+ "enabled": false,
1230
+ "scheduleExpr": "15 3 * * *"
1231
+ }
1232
+ ```
1233
+
1234
+ ### `DELETE /api/crons/[id]`
1235
+ Remove a cron job permanently. For jobs inside a container, pass `containerName` as a query parameter.
1236
+
1237
+ **Auth:** Bearer or browser cookie
1238
+
1239
+ **Query params:**
1240
+ - `containerName` (optional) — Docker container name for container-scoped jobs
1241
+
1242
+ **Response:**
1243
+ ```json
1244
+ { "ok": true }
1245
+ ```
1246
+
1247
+ **Error (404):** `{ "error": "Job ... not found" }`
1248
+ **Error (502):** `{ "error": "Failed to remove cron job in container ..." }`
1249
+
1250
+ ### `GET /api/crons/runs`
1251
+ Run history for a specific cron job. Queries the container's gateway via `openclaw cron runs --id <jobId> --limit <limit>`.
1252
+
1253
+ **Auth:** Bearer or browser cookie
1254
+
1255
+ **Query params:**
1256
+ - `containerName` (required) — Docker container name
1257
+ - `jobId` (required) — cron job UUID
1258
+ - `limit` (optional, default 20, max 100) — number of recent runs to return
1259
+
1260
+ **Response:**
1261
+ ```json
1262
+ {
1263
+ "entries": [
1264
+ {
1265
+ "ts": 1784820036635,
1266
+ "jobId": "7a607208-...",
1267
+ "action": "finished",
1268
+ "status": "error",
1269
+ "error": "Channel is required ...",
1270
+ "runAtMs": 1784820025581,
1271
+ "durationMs": 11046,
1272
+ "model": "deepseek/deepseek-v4-flash",
1273
+ "usage": { "input_tokens": 15872, "output_tokens": 597 }
1274
+ }
1275
+ ]
1276
+ }
1277
+ ```
1278
+
1279
+ On gateway-connection failure:
1280
+ ```json
1281
+ { "error": "Cannot connect to container gateway for runs", "entries": [] }
1282
+ ```
1283
+ (HTTP 502)
1284
+
1285
+ ---
1286
+
1287
+ ## Memory & Context
1288
+
1289
+ ### `GET /api/memory-context`
1290
+ Memory context snapshot for the current agent workspace.
1291
+
1292
+ **Auth:** browser cookie
1293
+
1294
+ **Response:**
1295
+ ```json
1296
+ {
1297
+ "files": [ { "path": "MEMORY.md", "sizeBytes": 13800, "lastModified": 1749200000000 } ],
1298
+ "totalBytes": 29800,
1299
+ "budgetBytes": 25600,
1300
+ "overBudget": true
1301
+ }
1302
+ ```
1303
+
1304
+ ---
1305
+
1306
+ ## Workspace
1307
+
1308
+ Full design, decisions, and usage guide at [WORKSPACE.md](WORKSPACE.md).
1309
+
1310
+ ### `GET /api/workspace?action=list`
1311
+ List available workspaces (VPS host + Docker containers with `AGENT_ID` label).
1312
+
1313
+ **Auth:** Bearer, query param, x-agent-token, or browser cookie
1314
+
1315
+ **Response:**
1316
+ ```json
1317
+ {
1318
+ "workspaces": [
1319
+ { "id": "vps", "label": "VPS Host (Nexus)", "type": "host" },
1320
+ { "id": "container-agent_8ba21f83", "label": "Giuanmuzzziins", "type": "container" }
1321
+ ]
1322
+ }
1323
+ ```
1324
+
1325
+ ### `GET /api/workspace?workspace=<id>`
1326
+ List files in a workspace, read a file, or return a recursive tree payload.
1327
+
1328
+ **Auth:** any auth method
1329
+
1330
+ **Query params:** `workspace` (optional, defaults to `vps`), `path` (optional), `tree=1` (optional)
1331
+
1332
+ **Response (directory):**
1333
+ ```json
1334
+ {
1335
+ "workspace": "vps",
1336
+ "label": "VPS Host (Nexus)",
1337
+ "path": "/path/to/workspace",
1338
+ "type": "host",
1339
+ "files": [
1340
+ { "name": "config", "isDirectory": true, "isFile": false, "path": "..." },
1341
+ { "name": "SOUL.md", "isDirectory": false, "isFile": true, "path": "..." }
1342
+ ]
1343
+ }
1344
+ ```
1345
+
1346
+ **Response (file):** Same structure with `content` field instead of `files`.
1347
+
1348
+ ### `PUT /api/workspace`
1349
+ Write content to a workspace file.
1350
+
1351
+ **Auth:** any auth method
1352
+
1353
+ **Body:** `{ "workspace": "vps", "path": "/path/to/workspace/test.md", "content": "hello" }`
1354
+
1355
+ **Response:** `{ "ok": true }`
1356
+
1357
+ ---
1358
+
1359
+ ## Vault
1360
+
1361
+ Full key management documentation at [PROVIDERS.md](PROVIDERS.md).
1362
+
1363
+ ### `GET /api/vault`
1364
+ List all stored credentials (keys masked).
1365
+
1366
+ **Auth:** browser cookie
1367
+
1368
+ **Response:**
1369
+ ```json
1370
+ {
1371
+ "providers": {
1372
+ "deepseek": { "keyStatus": "present", "scoped": ["atlas"] }
1373
+ },
1374
+ "services": {
1375
+ "github": { "tokenStatus": "present", "user": "Flame0510", "scoped": ["argus", "atlas"] }
1376
+ }
1377
+ }
1378
+ ```
1379
+
1380
+ ### `GET /api/vault/provider/key`
1381
+ Return the full API key for a provider.
1382
+
1383
+ **Auth:** browser cookie
1384
+
1385
+ **Query params:** `provider` (required), `agent` (optional)
1386
+
1387
+ **Response (200):**
1388
+ ```json
1389
+ {
1390
+ "provider": "deepseek",
1391
+ "apiKey": "sk-...full-key...",
1392
+ "masked": "sk-...be03",
1393
+ "source": "local"
1394
+ }
1395
+ ```
1396
+
1397
+ **Error:** `400` if `provider` missing; `404` if key not found.
1398
+
1399
+ See [PROVIDERS.md](PROVIDERS.md#get-apivaultproviderkey) for full detail.
1400
+
1401
+ ### `POST /api/vault/provider`
1402
+ Add or update a provider API key.
1403
+
1404
+ **Auth:** browser cookie
1405
+
1406
+ **Body:** `{ "provider": "deepseek", "apiKey": "sk-...", "baseUrl": "https://api.deepseek.com" }`
1407
+
1408
+ **Response:** `{ "status": "ok", "provider": "deepseek", "masked": "sk-...be03", "updatedAt": 1749200000000 }`
1409
+
1410
+ ### `DELETE /api/vault/provider`
1411
+ Remove a provider and all its keys.
1412
+
1413
+ **Auth:** browser cookie
1414
+
1415
+ **Body:** `{ "provider": "deepseek" }`
1416
+
1417
+ **Response:** `{ "status": "removed" }`
1418
+
1419
+ ### `PUT /api/vault/service`
1420
+ Add or update a service token.
1421
+
1422
+ **Auth:** browser cookie
1423
+
1424
+ **Body:** `{ "id": "github", "token": "***", "user": "Flame0510", "scopes": ["atlas"] }`
1425
+
1426
+ **Response:** `{ "ok": true }`
1427
+
1428
+ ### `DELETE /api/vault/service`
1429
+ Remove a service.
1430
+
1431
+ **Auth:** browser cookie
1432
+
1433
+ **Body:** `{ "id": "github" }`
1434
+
1435
+ **Response:** `{ "ok": true }`
1436
+
1437
+ ### `PUT /api/vault/permissions`
1438
+ Update agent permissions for a credential.
1439
+
1440
+ **Body:** `{ "type": "provider" | "service", "id": "deepseek", "scopes": ["atlas", "argus"] }`
1441
+
1442
+ **Response:** `{ "ok": true }`
1443
+
1444
+ ---
1445
+
1446
+ ## Containers
1447
+
1448
+ ### `GET /api/containers`
1449
+ List all running Docker containers with resource usage.
1450
+
1451
+ **Auth:** browser cookie
1452
+
1453
+ **Response:**
1454
+ ```json
1455
+ {
1456
+ "containers": [
1457
+ {
1458
+ "name": "openclaw-atlas",
1459
+ "image": "openclaw-agent-base:latest",
1460
+ "status": "running",
1461
+ "ports": ["0.0.0.0:3731->3000/tcp"],
1462
+ "created": "2026-06-20T10:00:00Z",
1463
+ "cpu_percent": 2.1,
1464
+ "mem_usage_mb": 340
1465
+ }
1466
+ ]
1467
+ }
1468
+ ```
1469
+
1470
+ ### `GET /api/containers/logs?name=<name>&tail=<lines>`
1471
+ Get container logs.
1472
+
1473
+ **Auth:** browser cookie
1474
+
1475
+ **Query params:** `name` (required), `tail` (default 50)
1476
+
1477
+ **Response:** `{ "name": "openclaw-atlas", "logs": "[log lines...]" }`
1478
+
1479
+ ### `POST /api/containers/action`
1480
+ Execute action on a container.
1481
+
1482
+ **Body:** `{ "action": "restart" | "stop" | "start", "name": "openclaw-atlas" }`
1483
+
1484
+ **Response:** `{ "ok": true, "message": "Container openclaw-atlas restarted" }`
1485
+
1486
+ ---
1487
+
1488
+ ## Tools
1489
+
1490
+ ### `GET /api/tools-config`
1491
+ Read audio/TTS and timezone tool configuration.
1492
+
1493
+ **Auth:** browser cookie
1494
+
1495
+ **Response:** `{ "audio": { ... }, "timezone": "Europe/Rome" }`
1496
+
1497
+ ### `POST /api/tools-config`
1498
+ Update audio or timezone config in `openclaw.json`.
1499
+
1500
+ **Body:** `{ "timezone": "America/New_York" }` or `{ "audio": { ... } }`
1501
+
1502
+ **Response:** `{ "ok": true, "timezone": "America/New_York", "audio": { ... } }`
1503
+
1504
+ ---
1505
+
1506
+ ## Version & WebSocket
1507
+
1508
+ ### `GET /api/version`
1509
+ Return the installed OpenClaw CLI version.
1510
+
1511
+ **Auth:** browser cookie
1512
+
1513
+ **Response:**
1514
+ ```json
1515
+ { "version": "openclaw x.y.z" }
1516
+ ```
1517
+
1518
+ **Fallback:** `{ "version": "unknown" }` if the command fails.
1519
+
1520
+ ### `GET /api/ws`
1521
+ Documentation placeholder for WebSocket access.
1522
+
1523
+ **Auth:** browser cookie
1524
+
1525
+ **Response:** plain text with HTTP `426 Upgrade Required`
1526
+
1527
+ Example body:
1528
+ ```text
1529
+ WebSocket endpoint available at ws://HOST/ws. This route is a documentation placeholder and does not upgrade connections.
1530
+ ```
1531
+
1532
+ ### `GET /ws`
1533
+ Live WebSocket endpoint exposed by the custom server.
1534
+
1535
+ **Auth:** same browser session as the dashboard
1536
+
1537
+ **Protocol notes:**
1538
+ - Hosted on the main HTTP port, not under `/api/`
1539
+ - Accepts JSON messages such as `chat.send` and `chat.history`
1540
+ - Intended for the dashboard client rather than generic REST consumers
1541
+
1542
+ ---
1543
+
1544
+ ## Plugins
1545
+
1546
+ ### `GET /api/plugins`
1547
+ List installed OpenClaw plugins.
1548
+
1549
+ **Auth:** browser cookie
1550
+
1551
+ **Response:** `{ "plugins": [ { "id": "file-transfer", "enabled": true, ... } ] }`
1552
+
1553
+ ### `POST /api/plugins`
1554
+ Enable or disable a plugin.
1555
+
1556
+ **Body:** `{ "action": "enable" | "disable", "pluginId": "file-transfer" }`
1557
+
1558
+ **Response:** `{ "ok": true }`
1559
+
1560
+ ---
1561
+
1562
+ ## Skills
1563
+
1564
+ Skill discovery across three sources:
1565
+ - **Shared:** skills in `/docker/shared-skills` on the VPS host (global, available to all agents)
1566
+ - **Workspace:** skills in `<workspace>/skills` inside each agent container (discovered via `docker exec`)
1567
+ - **Bundled:** skills shipped with OpenClaw (read-only)
1568
+
1569
+ ### `GET /api/skills`
1570
+ List all available skills from all sources.
1571
+
1572
+ **Auth:** browser cookie
1573
+
1574
+ **Response:**
1575
+ ```json
1576
+ {
1577
+ "skills": [
1578
+ {
1579
+ "name": "code-review",
1580
+ "type": "shared",
1581
+ "path": "/docker/shared-skills/code-review",
1582
+ "skillMdPath": "/docker/shared-skills/code-review/SKILL.md",
1583
+ "hasSkillMd": true,
1584
+ "description": "Systematic code review patterns...",
1585
+ "version": "1.0"
1586
+ },
1587
+ {
1588
+ "name": "my-custom-skill",
1589
+ "type": "workspace",
1590
+ "agentId": "atlas",
1591
+ "containerName": "openclaw-atlas",
1592
+ "path": "/root/.openclaw/workspace/skills/my-custom-skill",
1593
+ "skillMdPath": "/root/.openclaw/workspace/skills/my-custom-skill/SKILL.md",
1594
+ "hasSkillMd": true,
1595
+ "description": "Custom agent skill"
1596
+ }
1597
+ ]
1598
+ }
1599
+ ```
1600
+
1601
+ ### `GET /api/skills/save?path=<path>[&containerName=<name>]`
1602
+ Read a SKILL.md file. For container skills, pass `containerName`.
1603
+
1604
+ **Auth:** browser cookie
1605
+
1606
+ **Response:** `{ "content": "---\nname: my-skill\n..." }`
1607
+
1608
+ ### `POST /api/skills/save`
1609
+ Write a SKILL.md file. Supports shared directory and container workspaces.
1610
+
1611
+ **Auth:** browser cookie
1612
+
1613
+ **Body:**
1614
+ ```json
1615
+ {
1616
+ "skillPath": "/root/.openclaw/workspace/skills/my-skill/SKILL.md",
1617
+ "content": "---\nname: my-skill\n...",
1618
+ "containerName": "openclaw-atlas"
1619
+ }
1620
+ ```
1621
+ - `containerName` is required for container skills, omitted for shared/host skills
1622
+ - Bundled skills are read-only
1623
+
1624
+ **Response:** `{ "ok": true, "saved": "..." }`
1625
+
1626
+ ### `POST /api/skills/promote`
1627
+ Copy a skill from an agent's workspace to the shared skills directory, making it available to all agents.
1628
+
1629
+ **Auth:** browser cookie
1630
+
1631
+ **Body:**
1632
+ ```json
1633
+ {
1634
+ "containerName": "openclaw-atlas",
1635
+ "skillName": "my-custom-skill"
1636
+ }
1637
+ ```
1638
+
1639
+ **Response:**
1640
+ ```json
1641
+ {
1642
+ "ok": true,
1643
+ "skillName": "my-custom-skill",
1644
+ "agentId": "atlas",
1645
+ "containerName": "openclaw-atlas",
1646
+ "destPath": "/docker/shared-skills/my-custom-skill",
1647
+ "overwritten": false,
1648
+ "extraCopied": 0
1649
+ }
1650
+ ```
1651
+
1652
+ - `overwritten`: `true` if a shared skill with the same name already existed
1653
+ - `extraCopied`: number of additional files (beyond SKILL.md) copied from the skill directory
1654
+ - The skill name is validated against `^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$`
1655
+ - Container must have an `AGENT_ID` Docker label
1656
+
1657
+ ### `DELETE /api/skills/delete`
1658
+ Delete a skill from shared skills directory or from an agent's workspace.
1659
+
1660
+ **Auth:** browser cookie
1661
+
1662
+ **Body (shared skill):**
1663
+ ```json
1664
+ {
1665
+ "skillName": "my-custom-skill"
1666
+ }
1667
+ ```
1668
+
1669
+ **Body (workspace/container skill):**
1670
+ ```json
1671
+ {
1672
+ "skillName": "my-custom-skill",
1673
+ "containerName": "openclaw-atlas"
1674
+ }
1675
+ ```
1676
+
1677
+ **Response:**
1678
+ ```json
1679
+ {
1680
+ "ok": true,
1681
+ "skillName": "my-custom-skill",
1682
+ "deleted": true,
1683
+ "from": "shared"
1684
+ }
1685
+ ```
1686
+
1687
+ - `from` is either `"shared"` or `"workspace"`
1688
+ - Bundled skills cannot be deleted (read-only)
1689
+ - The skill name is validated against the same regex as promote
1690
+
1691
+ ---
1692
+
1693
+ ## Assistant (PULSE)
1694
+
1695
+ ### `POST /api/assistant`
1696
+ Chat with the built-in AI assistant (PULSE).
1697
+
1698
+ **Auth:** browser cookie
1699
+
1700
+ **Body:**
1701
+ ```json
1702
+ {
1703
+ "message": "What can I do on the Agents page?",
1704
+ "page": "/agents",
1705
+ "model": "deepseek/deepseek-v4-flash",
1706
+ "history": []
1707
+ }
1708
+ ```
1709
+
1710
+ | Field | Type | Required | Description |
1711
+ |-----------|-------------------------|----------|--------------------------------------------------|
1712
+ | `message` | string | yes | The user's question |
1713
+ | `page` | string | no | Current page path (used for contextual prompts) |
1714
+ | `model` | string | no | AI model to use (e.g. `deepseek/deepseek-v4-flash`) |
1715
+ | `history` | `{role, content}[]` | no | Recent conversation history (up to 10 messages) |
1716
+
1717
+ **Response:** `text/event-stream` (Server-Sent Events)
1718
+
1719
+ PULSE streams the assistant response as SSE chunks. Each chunk follows the
1720
+ [OpenAI streaming format](https://platform.openai.com/docs/api-reference/chat/streaming):
1721
+ ```text
1722
+ data: {"choices":[{"delta":{"content":"Hello"},"index":0}]}
1723
+
1724
+ data: {"choices":[{"delta":{"content":"! How can"},"index":0}]}
1725
+
1726
+ data: [DONE]
1727
+ ```
1728
+
1729
+ **Errors:** JSON `{ "error": "..." }` with the appropriate HTTP status code.
1730
+
1731
+ **Notes:**
1732
+ - PULSE routes the request **directly to the upstream provider** (not via the local Provider Gateway), using the API key from `data/provider-keys.json`.
1733
+ - The system prompt is built dynamically based on `PAGE_CONTEXT` (hardcoded in the route handler) — no database access.
1734
+ - PULSE does **not** have access to live data; she only knows page descriptions and navigation links.
1735
+
1736
+ ---
1737
+
1738
+ ## Credentials
1739
+
1740
+ ### `GET /api/credentials`
1741
+ List all stored credential profiles and the provider registry.
1742
+
1743
+ **Response:** `{ profiles: CredentialProfile[], providers: CredentialProviderDef[] }`
1744
+
1745
+ ### `POST /api/credentials`
1746
+ Create a new credential profile.
1747
+
1748
+ **Body:** `{ providerId: string, label: string, secret: Record<string, string> }`
1749
+
1750
+ **Response:** `{ id: string }` (201)
1751
+
1752
+ ### `GET /api/credentials/[id]`
1753
+ Get a single credential profile (no secret returned).
1754
+
1755
+ ### `PATCH /api/credentials/[id]`
1756
+ Update label and/or secret of a credential.
1757
+
1758
+ **Body:** `{ label: string, secret: Record<string, string> }`
1759
+
1760
+ ### `DELETE /api/credentials/[id]`
1761
+ Delete a credential and its secret permanently.
1762
+
1763
+ ### `POST /api/credentials/[id]/sync`
1764
+ Sync the credential to all agent containers (or a specified subset).
1765
+
1766
+ **Body (optional):** `{ containers?: string[] }` — if omitted or empty, syncs to all agent containers with `AGENT_ID` labels.
1767
+
1768
+ **Response:** `{ results: { container: string, status: 'ok'|'error', error?: string }[] }`
1769
+
1770
+ Before syncing, **all agent containers are cleaned first** (env files, CLI auth, TOOLS.md integration markers removed).
1771
+ Only the selected containers receive the new credential and TOOLS.md section.
1772
+ This ensures that deselecting an agent or deleting a profile removes the credential from all agents.
1773
+
1774
+ ### `GET /api/credentials/detect`
1775
+ Live-detect which credentials are actually present inside agent containers.
1776
+
1777
+ **Query params (optional):** `?agent=prometheus` — filter to a single container.
1778
+
1779
+ **Response:**
1780
+ ```json
1781
+ {
1782
+ "agents": [
1783
+ {
1784
+ "container": "prometheus",
1785
+ "agentId": "prometheus",
1786
+ "displayName": "Prometheus",
1787
+ "githubLoggedIn": true,
1788
+ "githubUser": "Flame0510",
1789
+ "githubCliInstalled": true,
1790
+ "vercelLoggedIn": false,
1791
+ "vercelUser": "",
1792
+ "vercelTeam": "",
1793
+ "vercelCliInstalled": true,
1794
+ "supabaseLoggedIn": true,
1795
+ "supabaseLinked": false,
1796
+ "supabaseProjectRef": "",
1797
+ "supabaseCliInstalled": true,
1798
+ "trelloLoggedIn": false,
1799
+ "trelloCliInstalled": true,
1800
+ "toolsMdHasMarkers": true,
1801
+ "matchedProfiles": [
1802
+ { "profileId": "cred_xxx", "providerId": "github-pat", "label": "GitHub" }
1803
+ ]
1804
+ }
1805
+ ]
1806
+ }
1807
+ ```
1808
+
1809
+ Detection runs `docker exec` inside each container:
1810
+ - `gh auth status` for GitHub (token read from `~/.config/gh/hosts.yml`)
1811
+ - `vercel whoami` for Vercel (token read from `~/.local/share/com.vercel.cli/auth.json`)
1812
+ - `supabase projects list --output json` for Supabase (token read from `~/.supabase/access-token`)
1813
+ - `test -f ~/.trello-cli/default/config.json` for Trello
1814
+ - TOOLS.md markers at `/root/.openclaw/workspace/TOOLS.md`
1815
+
1816
+ `matchedProfiles` is determined by SHA256-hashing the token installed in the container
1817
+ and comparing it against the hashed token of each stored profile in the vault.
1818
+ Only exact token matches produce a match — no heuristics, no broad profile-level guesses.
1819
+
1820
+ ### `POST /api/credentials/[id]/reveal`
1821
+ Return the raw secret payload. Every call is logged in the audit trail.
1822
+ Use sparingly.
1823
+
1824
+ **Response:** `{ secret: Record<string, string> }`
1825
+
1826
+ ---
1827
+
1828
+ ## Error Format
1829
+
1830
+ All errors return JSON:
1831
+ ```json
1832
+ { "error": "description of what went wrong" }
1833
+ ```
1834
+
1835
+ Common status codes: `400` bad request, `401` unauthorized, `404` not found, `500` server error.