@limeade-labs/sparkui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/SKILL.md +242 -0
- package/bin/deploy +23 -0
- package/bin/sparkui.js +390 -0
- package/docs/README.md +51 -0
- package/docs/api-reference.md +428 -0
- package/docs/chatgpt-setup.md +206 -0
- package/docs/components.md +432 -0
- package/docs/getting-started.md +179 -0
- package/docs/mcp-setup.md +195 -0
- package/docs/openclaw-setup.md +177 -0
- package/docs/templates.md +289 -0
- package/lib/components.js +474 -0
- package/lib/store.js +193 -0
- package/lib/templates.js +48 -0
- package/lib/ws-client.js +197 -0
- package/mcp-server/README.md +189 -0
- package/mcp-server/index.js +174 -0
- package/mcp-server/package.json +15 -0
- package/package.json +52 -0
- package/server.js +620 -0
- package/templates/base.js +82 -0
- package/templates/checkout.js +271 -0
- package/templates/feedback-form.js +140 -0
- package/templates/macro-tracker.js +205 -0
- package/templates/workout-timer.js +510 -0
- package/templates/ws-test.js +136 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
All API endpoints require authentication via a Bearer token in the `Authorization` header (except `GET /` health check and `GET /s/:id` page serving).
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Authorization: Bearer YOUR_PUSH_TOKEN
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Base URL: `http://localhost:3457` (or your configured `SPARKUI_BASE_URL`)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## POST /api/push
|
|
14
|
+
|
|
15
|
+
Create a new ephemeral page from a template, raw HTML, or both.
|
|
16
|
+
|
|
17
|
+
### Request Body
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"template": "macro-tracker",
|
|
22
|
+
"data": { ... },
|
|
23
|
+
"html": "<html>...</html>",
|
|
24
|
+
"ttl": 3600,
|
|
25
|
+
"meta": { "title": "My Page" },
|
|
26
|
+
"og": {
|
|
27
|
+
"title": "Custom OG Title",
|
|
28
|
+
"description": "Custom description",
|
|
29
|
+
"image": "https://example.com/image.png"
|
|
30
|
+
},
|
|
31
|
+
"callbackUrl": "https://your-server.com/webhook",
|
|
32
|
+
"callbackToken": "your-webhook-secret",
|
|
33
|
+
"openclaw": {
|
|
34
|
+
"enabled": true,
|
|
35
|
+
"channel": "slack",
|
|
36
|
+
"to": "C0AKMF5E0KD",
|
|
37
|
+
"eventTypes": ["completion"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Field | Type | Required | Description |
|
|
43
|
+
|-------|------|----------|-------------|
|
|
44
|
+
| `template` | string | One of `template` or `html` | Template name (see [Templates](./templates.md)) |
|
|
45
|
+
| `data` | object | With `template` | Data to pass to the template renderer |
|
|
46
|
+
| `html` | string | One of `template` or `html` | Raw HTML content |
|
|
47
|
+
| `ttl` | number | No | Time-to-live in seconds (default: 3600) |
|
|
48
|
+
| `meta` | object | No | Arbitrary metadata stored with the page |
|
|
49
|
+
| `og` | object | No | Open Graph overrides: `title`, `description`, `image` |
|
|
50
|
+
| `callbackUrl` | string | No | URL to POST browser events to |
|
|
51
|
+
| `callbackToken` | string | No | Bearer token sent with callback requests |
|
|
52
|
+
| `openclaw` | object | No | OpenClaw webhook config (see below) |
|
|
53
|
+
|
|
54
|
+
### OpenClaw Config
|
|
55
|
+
|
|
56
|
+
| Field | Type | Required | Description |
|
|
57
|
+
|-------|------|----------|-------------|
|
|
58
|
+
| `enabled` | boolean | Yes | Enable OpenClaw event forwarding |
|
|
59
|
+
| `channel` | string | No | Delivery channel (default: `"slack"`) |
|
|
60
|
+
| `to` | string | No | Target channel/user ID |
|
|
61
|
+
| `eventTypes` | string[] | No | Events to forward: `["completion"]`, `["event"]`, or both. Default: `["completion"]` |
|
|
62
|
+
| `sessionKey` | string | No | Session key override for routing |
|
|
63
|
+
|
|
64
|
+
### Response — `201 Created`
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
69
|
+
"url": "/s/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
70
|
+
"fullUrl": "http://localhost:3457/s/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Examples
|
|
75
|
+
|
|
76
|
+
**Push from template:**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
curl -X POST http://localhost:3457/api/push \
|
|
80
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
-d '{
|
|
83
|
+
"template": "macro-tracker",
|
|
84
|
+
"data": {
|
|
85
|
+
"date": "2026-03-14",
|
|
86
|
+
"calories": {"current": 1250, "target": 1900},
|
|
87
|
+
"protein": {"current": 62, "target": 86},
|
|
88
|
+
"fat": {"current": 45, "target": 95},
|
|
89
|
+
"carbs": {"current": 15, "target": 25},
|
|
90
|
+
"meals": [
|
|
91
|
+
{"name": "Eggs & bacon", "calories": 450, "time": "6:30 AM"},
|
|
92
|
+
{"name": "Grilled chicken salad", "calories": 480, "time": "12:00 PM"}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"ttl": 7200
|
|
96
|
+
}'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Push raw HTML:**
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl -X POST http://localhost:3457/api/push \
|
|
103
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
104
|
+
-H "Content-Type: application/json" \
|
|
105
|
+
-d '{
|
|
106
|
+
"html": "<!DOCTYPE html><html><body style=\"background:#111;color:#eee;font-family:sans-serif;padding:40px\"><h1>Hello World</h1></body></html>",
|
|
107
|
+
"ttl": 1800
|
|
108
|
+
}'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## POST /api/compose
|
|
114
|
+
|
|
115
|
+
Create a page by composing individual [components](./components.md). This is the recommended approach for custom layouts.
|
|
116
|
+
|
|
117
|
+
### Request Body
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"title": "Page Title",
|
|
122
|
+
"sections": [
|
|
123
|
+
{ "type": "header", "config": { "title": "Hello", "subtitle": "World", "icon": "⚡" } },
|
|
124
|
+
{ "type": "stats", "config": { "items": [{"label": "Views", "value": "42", "icon": "👁️"}] } },
|
|
125
|
+
{ "type": "button", "config": { "label": "Done", "action": "complete", "style": "primary" } }
|
|
126
|
+
],
|
|
127
|
+
"ttl": 3600,
|
|
128
|
+
"openclaw": { "enabled": true, "channel": "slack", "to": "C0AKMF5E0KD" }
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Field | Type | Required | Description |
|
|
133
|
+
|-------|------|----------|-------------|
|
|
134
|
+
| `title` | string | No | Page title (default: `"Composed"`) |
|
|
135
|
+
| `sections` | array | Yes | Array of `{ type, config }` component objects |
|
|
136
|
+
| `ttl` | number | No | Time-to-live in seconds |
|
|
137
|
+
| `openclaw` | object | No | OpenClaw webhook config |
|
|
138
|
+
|
|
139
|
+
### Response — `201 Created`
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"id": "def-456-...",
|
|
144
|
+
"url": "/s/def-456-...",
|
|
145
|
+
"fullUrl": "http://localhost:3457/s/def-456-..."
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> **Tip:** The compose API is the fastest path to a custom page. Mix and match components — no HTML required.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## GET /api/pages
|
|
154
|
+
|
|
155
|
+
List pages with optional filters.
|
|
156
|
+
|
|
157
|
+
### Query Parameters
|
|
158
|
+
|
|
159
|
+
| Parameter | Type | Default | Description |
|
|
160
|
+
|-----------|------|---------|-------------|
|
|
161
|
+
| `status` | string | `"active"` | Filter by status: `"active"` or `"all"` |
|
|
162
|
+
| `template` | string | — | Filter by template name |
|
|
163
|
+
|
|
164
|
+
### Response — `200 OK`
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"pages": [
|
|
169
|
+
{
|
|
170
|
+
"id": "a1b2c3d4-...",
|
|
171
|
+
"createdAt": "2026-03-14T18:00:00.000Z",
|
|
172
|
+
"expiresAt": "2026-03-14T19:00:00.000Z",
|
|
173
|
+
"views": 3,
|
|
174
|
+
"meta": { "title": "My Dashboard", "template": "macro-tracker" }
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"total": 1
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Example
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# List all active pages
|
|
185
|
+
curl http://localhost:3457/api/pages \
|
|
186
|
+
-H "Authorization: Bearer $PUSH_TOKEN"
|
|
187
|
+
|
|
188
|
+
# Filter by template
|
|
189
|
+
curl "http://localhost:3457/api/pages?template=macro-tracker" \
|
|
190
|
+
-H "Authorization: Bearer $PUSH_TOKEN"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## GET /api/pages/:id
|
|
196
|
+
|
|
197
|
+
Get details for a specific page.
|
|
198
|
+
|
|
199
|
+
### Response — `200 OK`
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"id": "a1b2c3d4-...",
|
|
204
|
+
"createdAt": "2026-03-14T18:00:00.000Z",
|
|
205
|
+
"expiresAt": "2026-03-14T19:00:00.000Z",
|
|
206
|
+
"views": 5,
|
|
207
|
+
"meta": {
|
|
208
|
+
"title": "Macro Tracker",
|
|
209
|
+
"template": "macro-tracker",
|
|
210
|
+
"data": { ... }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Errors
|
|
216
|
+
|
|
217
|
+
- `404` — Page not found
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## PATCH /api/pages/:id
|
|
222
|
+
|
|
223
|
+
Update an existing page's content, data, or TTL.
|
|
224
|
+
|
|
225
|
+
### Request Body
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"template": "macro-tracker",
|
|
230
|
+
"data": { ... },
|
|
231
|
+
"html": "<html>...</html>",
|
|
232
|
+
"ttl": 7200
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
| Field | Type | Description |
|
|
237
|
+
|-------|------|-------------|
|
|
238
|
+
| `template` | string | Re-render with a different or same template |
|
|
239
|
+
| `data` | object | New data for template rendering. If no `template` given, re-renders the page's existing template |
|
|
240
|
+
| `html` | string | Replace HTML directly |
|
|
241
|
+
| `ttl` | number | Extend the page's time-to-live |
|
|
242
|
+
|
|
243
|
+
> **Tip:** To update a template page with new data (e.g., updated macro totals), just send `data` — it will re-render using the page's original template.
|
|
244
|
+
|
|
245
|
+
### Response — `200 OK`
|
|
246
|
+
|
|
247
|
+
Returns updated page details (same format as `GET /api/pages/:id`).
|
|
248
|
+
|
|
249
|
+
### Errors
|
|
250
|
+
|
|
251
|
+
- `404` — Page not found
|
|
252
|
+
- `410` — Page expired
|
|
253
|
+
|
|
254
|
+
### Example
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Update macro data
|
|
258
|
+
curl -X PATCH http://localhost:3457/api/pages/a1b2c3d4-... \
|
|
259
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
260
|
+
-H "Content-Type: application/json" \
|
|
261
|
+
-d '{
|
|
262
|
+
"data": {
|
|
263
|
+
"date": "2026-03-14",
|
|
264
|
+
"calories": {"current": 1700, "target": 1900},
|
|
265
|
+
"protein": {"current": 80, "target": 86},
|
|
266
|
+
"fat": {"current": 85, "target": 95},
|
|
267
|
+
"carbs": {"current": 20, "target": 25}
|
|
268
|
+
}
|
|
269
|
+
}'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Connected WebSocket clients receive an `update` message and refresh automatically.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## DELETE /api/pages/:id
|
|
277
|
+
|
|
278
|
+
Delete a page immediately.
|
|
279
|
+
|
|
280
|
+
### Response — `200 OK`
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"id": "a1b2c3d4-...",
|
|
285
|
+
"deleted": true
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Connected WebSocket clients receive a `destroy` message.
|
|
290
|
+
|
|
291
|
+
### Errors
|
|
292
|
+
|
|
293
|
+
- `404` — Page not found
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## GET /s/:id
|
|
298
|
+
|
|
299
|
+
Serve a page's HTML to the browser. **No authentication required.**
|
|
300
|
+
|
|
301
|
+
- `200` — Returns the rendered HTML page
|
|
302
|
+
- `410` — Page has expired or been removed (shows a styled "Gone" page)
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## GET /
|
|
307
|
+
|
|
308
|
+
Health check endpoint. **No authentication required.**
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"status": "ok",
|
|
313
|
+
"service": "sparkui",
|
|
314
|
+
"version": "1.1.0",
|
|
315
|
+
"pages": 3,
|
|
316
|
+
"wsClients": 1,
|
|
317
|
+
"templates": ["macro-tracker", "ws-test", "feedback-form", "checkout", "workout-timer"],
|
|
318
|
+
"uptime": 3600
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## GET /og/:id.svg
|
|
325
|
+
|
|
326
|
+
Dynamic Open Graph image for social previews. Returns an SVG with the page title and template name. Cached for 1 hour.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## WebSocket Protocol
|
|
331
|
+
|
|
332
|
+
Connect to `ws://localhost:3457/ws?page=PAGE_ID` to receive real-time updates.
|
|
333
|
+
|
|
334
|
+
### Client → Server Messages
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{"type": "heartbeat"}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Keeps the connection alive. Server responds with `{"type": "pong"}`.
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{"type": "event", "pageId": "abc-123", "data": {"action": "button_click"}}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
General UI events (button clicks, checklist toggles, etc.).
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{"type": "completion", "pageId": "abc-123", "data": {"formData": {"name": "John", "rating": 5}}}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Completion events (form submissions, checklist completion, timer done).
|
|
353
|
+
|
|
354
|
+
### Server → Client Messages
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{"type": "update", "pageId": "abc-123"}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Page content was updated via `PATCH`. Client should reload.
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{"type": "destroy", "pageId": "abc-123"}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Page was deleted. Client should show an expiration message.
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{"type": "pong"}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Response to heartbeat.
|
|
373
|
+
|
|
374
|
+
### Event Forwarding
|
|
375
|
+
|
|
376
|
+
Events received via WebSocket are forwarded to:
|
|
377
|
+
|
|
378
|
+
1. **Callback URL** — If `callbackUrl` was set during push, events are POSTed there
|
|
379
|
+
2. **OpenClaw** — If `openclaw.enabled` is true, matching events (per `eventTypes`) are forwarded to the OpenClaw hooks endpoint
|
|
380
|
+
|
|
381
|
+
Callback payload:
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"type": "completion",
|
|
386
|
+
"pageId": "abc-123",
|
|
387
|
+
"data": { "formData": { ... } },
|
|
388
|
+
"timestamp": 1710450000000
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Connection Health
|
|
393
|
+
|
|
394
|
+
- Server pings all clients every 30 seconds
|
|
395
|
+
- Stale connections (no pong response) are terminated after 60 seconds
|
|
396
|
+
- Browser client should implement reconnection logic (the built-in templates do this automatically)
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Error Responses
|
|
401
|
+
|
|
402
|
+
All error responses follow this format:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"error": "Description of the error"
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
| Status | Meaning |
|
|
411
|
+
|--------|---------|
|
|
412
|
+
| `400` | Bad request — missing or invalid fields |
|
|
413
|
+
| `401` | Unauthorized — missing or invalid Bearer token |
|
|
414
|
+
| `404` | Not found — page doesn't exist |
|
|
415
|
+
| `410` | Gone — page has expired |
|
|
416
|
+
| `500` | Server error |
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Authentication
|
|
421
|
+
|
|
422
|
+
All `/api/*` endpoints (except the health check) require a Bearer token:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
Authorization: Bearer spk_your_token_here
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The token is configured via the `PUSH_TOKEN` environment variable or `.env` file. If no token is set, SparkUI generates one on first start and appends it to `.env`.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# ChatGPT Setup
|
|
2
|
+
|
|
3
|
+
Use SparkUI with ChatGPT Custom GPTs via the Actions feature. This lets a GPT create ephemeral web pages during conversations and share links with users.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
ChatGPT Custom GPTs support "Actions" — HTTP API calls the GPT can make during a conversation. By configuring SparkUI's push API as an action, your GPT can generate interactive pages (dashboards, forms, timers) and share the URL.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
1. SparkUI server running and **publicly accessible** (not just localhost)
|
|
12
|
+
2. A ChatGPT Plus account (Actions require a paid plan)
|
|
13
|
+
3. Your push token
|
|
14
|
+
|
|
15
|
+
> **Important:** Your SparkUI server must be reachable from the internet. Use a reverse proxy, cloud deployment, or tunnel (e.g., Cloudflare Tunnel, ngrok) and set `SPARKUI_BASE_URL` accordingly.
|
|
16
|
+
|
|
17
|
+
## OpenAPI Spec
|
|
18
|
+
|
|
19
|
+
Create this OpenAPI specification for the GPT builder. Replace `https://your-sparkui-server.com` with your actual public URL.
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
openapi: 3.1.0
|
|
23
|
+
info:
|
|
24
|
+
title: SparkUI
|
|
25
|
+
description: Create ephemeral interactive web pages on demand
|
|
26
|
+
version: 1.1.0
|
|
27
|
+
servers:
|
|
28
|
+
- url: https://your-sparkui-server.com
|
|
29
|
+
paths:
|
|
30
|
+
/api/push:
|
|
31
|
+
post:
|
|
32
|
+
operationId: pushPage
|
|
33
|
+
summary: Create a new ephemeral page from a template or raw HTML
|
|
34
|
+
requestBody:
|
|
35
|
+
required: true
|
|
36
|
+
content:
|
|
37
|
+
application/json:
|
|
38
|
+
schema:
|
|
39
|
+
type: object
|
|
40
|
+
properties:
|
|
41
|
+
template:
|
|
42
|
+
type: string
|
|
43
|
+
description: "Template name: macro-tracker, checkout, workout-timer, feedback-form, ws-test"
|
|
44
|
+
data:
|
|
45
|
+
type: object
|
|
46
|
+
description: Template data (varies by template)
|
|
47
|
+
html:
|
|
48
|
+
type: string
|
|
49
|
+
description: Raw HTML content (alternative to template)
|
|
50
|
+
ttl:
|
|
51
|
+
type: integer
|
|
52
|
+
description: Time-to-live in seconds (default 3600)
|
|
53
|
+
default: 3600
|
|
54
|
+
anyOf:
|
|
55
|
+
- required: [template, data]
|
|
56
|
+
- required: [html]
|
|
57
|
+
responses:
|
|
58
|
+
"201":
|
|
59
|
+
description: Page created successfully
|
|
60
|
+
content:
|
|
61
|
+
application/json:
|
|
62
|
+
schema:
|
|
63
|
+
type: object
|
|
64
|
+
properties:
|
|
65
|
+
id:
|
|
66
|
+
type: string
|
|
67
|
+
url:
|
|
68
|
+
type: string
|
|
69
|
+
fullUrl:
|
|
70
|
+
type: string
|
|
71
|
+
/api/compose:
|
|
72
|
+
post:
|
|
73
|
+
operationId: composePage
|
|
74
|
+
summary: Compose a page from individual components
|
|
75
|
+
requestBody:
|
|
76
|
+
required: true
|
|
77
|
+
content:
|
|
78
|
+
application/json:
|
|
79
|
+
schema:
|
|
80
|
+
type: object
|
|
81
|
+
required: [sections]
|
|
82
|
+
properties:
|
|
83
|
+
title:
|
|
84
|
+
type: string
|
|
85
|
+
default: "SparkUI"
|
|
86
|
+
sections:
|
|
87
|
+
type: array
|
|
88
|
+
items:
|
|
89
|
+
type: object
|
|
90
|
+
required: [type]
|
|
91
|
+
properties:
|
|
92
|
+
type:
|
|
93
|
+
type: string
|
|
94
|
+
description: "Component: header, button, timer, checklist, progress, stats, form, tabs"
|
|
95
|
+
config:
|
|
96
|
+
type: object
|
|
97
|
+
ttl:
|
|
98
|
+
type: integer
|
|
99
|
+
default: 3600
|
|
100
|
+
responses:
|
|
101
|
+
"201":
|
|
102
|
+
description: Page created
|
|
103
|
+
content:
|
|
104
|
+
application/json:
|
|
105
|
+
schema:
|
|
106
|
+
type: object
|
|
107
|
+
properties:
|
|
108
|
+
id:
|
|
109
|
+
type: string
|
|
110
|
+
url:
|
|
111
|
+
type: string
|
|
112
|
+
fullUrl:
|
|
113
|
+
type: string
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Setting Up the Action in GPT Builder
|
|
117
|
+
|
|
118
|
+
1. Go to [ChatGPT GPT Builder](https://chatgpt.com/gpts/editor)
|
|
119
|
+
2. Create a new GPT or edit an existing one
|
|
120
|
+
3. Click **Configure** → scroll to **Actions** → **Create new action**
|
|
121
|
+
4. Paste the OpenAPI spec above into the schema editor
|
|
122
|
+
5. Under **Authentication**:
|
|
123
|
+
- Select **API Key**
|
|
124
|
+
- Auth Type: **Bearer**
|
|
125
|
+
- Paste your `PUSH_TOKEN` as the API key
|
|
126
|
+
6. Click **Save**
|
|
127
|
+
|
|
128
|
+
### GPT Instructions
|
|
129
|
+
|
|
130
|
+
Add these instructions to your GPT's system prompt:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
You have access to SparkUI, which creates ephemeral interactive web pages.
|
|
134
|
+
|
|
135
|
+
When the user needs something visual (dashboards, trackers, forms, timers):
|
|
136
|
+
1. Use the pushPage action with an appropriate template and data
|
|
137
|
+
2. Share the fullUrl with the user
|
|
138
|
+
|
|
139
|
+
Available templates:
|
|
140
|
+
- macro-tracker: Nutrition tracking (data: date, calories, protein, fat, carbs, meals)
|
|
141
|
+
- checkout: Product checkout demo (data: product with name/description/price)
|
|
142
|
+
- workout-timer: Exercise routine (data: title, exercises, rounds, restSeconds)
|
|
143
|
+
- feedback-form: Feedback collection (data: title, subtitle, questions)
|
|
144
|
+
|
|
145
|
+
For custom layouts, use composePage with sections array. Components:
|
|
146
|
+
header, button, timer, checklist, progress, stats, form, tabs
|
|
147
|
+
|
|
148
|
+
Pages expire after TTL seconds. Always share the fullUrl link.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Authentication
|
|
152
|
+
|
|
153
|
+
SparkUI uses Bearer token authentication. In the GPT builder:
|
|
154
|
+
|
|
155
|
+
- **Auth type:** API Key
|
|
156
|
+
- **Scheme:** Bearer
|
|
157
|
+
- **Key:** Your `PUSH_TOKEN` value (e.g., `spk_abc123...`)
|
|
158
|
+
|
|
159
|
+
The GPT sends this automatically with every action call as:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Authorization: Bearer spk_abc123...
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example Prompts and Expected Behavior
|
|
166
|
+
|
|
167
|
+
### Nutrition Dashboard
|
|
168
|
+
|
|
169
|
+
**User:** "Show me a nutrition dashboard for today. I've had 1,200 calories, 55g protein, 40g fat, 20g carbs out of targets 1,900/86/95/25."
|
|
170
|
+
|
|
171
|
+
**GPT calls:** `pushPage` with `macro-tracker` template
|
|
172
|
+
|
|
173
|
+
**GPT responds:** "Here's your nutrition dashboard for today: [link]. You're at 63% of your calorie target with solid macro distribution."
|
|
174
|
+
|
|
175
|
+
### Feedback Form
|
|
176
|
+
|
|
177
|
+
**User:** "Create a feedback form for our new feature launch"
|
|
178
|
+
|
|
179
|
+
**GPT calls:** `pushPage` with `feedback-form` template
|
|
180
|
+
|
|
181
|
+
**GPT responds:** "Here's your feedback form: [link]. Share this with your team to collect ratings and comments."
|
|
182
|
+
|
|
183
|
+
### Custom Dashboard
|
|
184
|
+
|
|
185
|
+
**User:** "Make a project status dashboard showing 42 tasks done, 8 in progress, 3 blocked"
|
|
186
|
+
|
|
187
|
+
**GPT calls:** `composePage` with header + stats + progress components
|
|
188
|
+
|
|
189
|
+
**GPT responds:** "Here's your project dashboard: [link]. It shows your current task breakdown with trend indicators."
|
|
190
|
+
|
|
191
|
+
### Workout Timer
|
|
192
|
+
|
|
193
|
+
**User:** "Create a workout: 3 rounds of push-ups (15), squats (20), and planks (30 sec) with 60-second rest"
|
|
194
|
+
|
|
195
|
+
**GPT calls:** `pushPage` with `workout-timer` template
|
|
196
|
+
|
|
197
|
+
**GPT responds:** "Your workout is ready: [link]. Open it on your phone and hit Start when you're ready."
|
|
198
|
+
|
|
199
|
+
## Limitations
|
|
200
|
+
|
|
201
|
+
- **Public access required** — SparkUI must be internet-accessible (not localhost)
|
|
202
|
+
- **No WebSocket** — ChatGPT can create pages but can't receive real-time events back. Use the `callbackUrl` parameter if you need webhook notifications.
|
|
203
|
+
- **Rate limits** — ChatGPT may throttle action calls. Keep page creation to a reasonable frequency.
|
|
204
|
+
- **TTL** — Pages are ephemeral. Set longer TTL values if users need extended access.
|
|
205
|
+
|
|
206
|
+
> **Tip:** For bidirectional communication (agent receives user actions), consider using [OpenClaw](./openclaw-setup.md) or [MCP](./mcp-setup.md) integrations instead.
|