@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
package/.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SparkUI Configuration
|
|
2
|
+
SPARKUI_PORT=3457
|
|
3
|
+
PUSH_TOKEN=your-random-token-here
|
|
4
|
+
SPARKUI_BASE_URL=http://localhost:3457
|
|
5
|
+
|
|
6
|
+
# OpenClaw Webhook Integration (optional)
|
|
7
|
+
# Forward page events (completions, form submissions) to OpenClaw
|
|
8
|
+
OPENCLAW_HOOKS_URL=https://your-openclaw-instance/hooks
|
|
9
|
+
OPENCLAW_HOOKS_TOKEN=your-openclaw-hooks-token
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Contributing to SparkUI
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! Here's how to get started.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- npm or yarn
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone https://github.com/limeade-labs/sparkui.git
|
|
14
|
+
cd sparkui
|
|
15
|
+
npm install
|
|
16
|
+
cp .env.example .env
|
|
17
|
+
# Edit .env — set SPARKUI_PUSH_TOKEN to any random string
|
|
18
|
+
npm start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Server runs at `http://localhost:3457`. Test with:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl http://localhost:3457/
|
|
25
|
+
# {"status":"ok","templates":["macro-tracker","checkout",...]}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Adding a Template
|
|
29
|
+
|
|
30
|
+
1. Create `templates/your-template.js`
|
|
31
|
+
2. Export a `render(data)` function that returns an HTML string
|
|
32
|
+
3. Use `require('./base').base()` for the page shell (dark theme, WS client, OG tags)
|
|
33
|
+
4. Register in `lib/templates.js`
|
|
34
|
+
|
|
35
|
+
Look at `templates/checkout.js` for a good example.
|
|
36
|
+
|
|
37
|
+
## Adding a Component
|
|
38
|
+
|
|
39
|
+
1. Add your function to `lib/components.js`
|
|
40
|
+
2. Components return HTML strings (inline styles, no external deps)
|
|
41
|
+
3. For interactivity, use inline `<script>` blocks
|
|
42
|
+
4. WebSocket events: use `window.sparkui.send({type, ...data})`
|
|
43
|
+
5. Export from the `compose` function's component map
|
|
44
|
+
|
|
45
|
+
## Code Style
|
|
46
|
+
|
|
47
|
+
- Standard JS (semicolons, single quotes)
|
|
48
|
+
- No external CSS frameworks — inline styles only
|
|
49
|
+
- Dark theme: background `#0a0a0a`, cards `#1a1a1a`, text `#e0e0e0`
|
|
50
|
+
- Mobile-first responsive design
|
|
51
|
+
- Zero external runtime dependencies in generated HTML
|
|
52
|
+
|
|
53
|
+
## Pull Requests
|
|
54
|
+
|
|
55
|
+
1. Fork the repo
|
|
56
|
+
2. Create a feature branch (`git checkout -b feat/my-feature`)
|
|
57
|
+
3. Make your changes
|
|
58
|
+
4. Test locally (push a page, verify it renders)
|
|
59
|
+
5. Submit a PR with a clear description
|
|
60
|
+
|
|
61
|
+
## Questions?
|
|
62
|
+
|
|
63
|
+
Open an issue or reach out to the maintainers.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Limeade Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# ⚡ SparkUI
|
|
2
|
+
|
|
3
|
+
**Ephemeral interactive UIs for AI agents.** Generate rich web pages from chat — no app install required.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.npmjs.com/package/sparkui)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What is SparkUI?
|
|
11
|
+
|
|
12
|
+
SparkUI lets AI agents generate interactive web UIs on demand. Instead of walls of text, your agent creates a polished, ephemeral web page and shares a link. Users click, interact, and the results flow back to the agent via WebSocket. Pages self-destruct after a configurable TTL.
|
|
13
|
+
|
|
14
|
+
- 🎯 **No app install** — just a URL that works in any browser
|
|
15
|
+
- ⏱️ **Ephemeral** — pages auto-expire (default 1 hour)
|
|
16
|
+
- 🔄 **Bidirectional** — user actions flow back to the agent via WebSocket
|
|
17
|
+
- 🧩 **Composable** — 15 components you can mix and match
|
|
18
|
+
- 📱 **Mobile-first** — designed for phones, works everywhere
|
|
19
|
+
- 🔌 **MCP compatible** — works with Claude Desktop, Cursor, Windsurf
|
|
20
|
+
- 🌙 **Dark theme** — easy on the eyes, polished look
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Clone and install
|
|
26
|
+
git clone https://github.com/limeade-labs/sparkui.git
|
|
27
|
+
cd sparkui
|
|
28
|
+
npm install
|
|
29
|
+
|
|
30
|
+
# Configure
|
|
31
|
+
cp .env.example .env
|
|
32
|
+
# Edit .env — set your PUSH_TOKEN
|
|
33
|
+
|
|
34
|
+
# Run
|
|
35
|
+
npm start
|
|
36
|
+
# Server running at http://localhost:3457
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Push a Page
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -X POST http://localhost:3457/api/push \
|
|
43
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
44
|
+
-H "Content-Type: application/json" \
|
|
45
|
+
-d '{
|
|
46
|
+
"template": "checkout",
|
|
47
|
+
"data": {
|
|
48
|
+
"product": {
|
|
49
|
+
"name": "SparkUI Pro",
|
|
50
|
+
"description": "Unlimited ephemeral UIs",
|
|
51
|
+
"price": 29.99,
|
|
52
|
+
"image": "⚡"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"id": "abc123",
|
|
62
|
+
"url": "/s/abc123",
|
|
63
|
+
"fullUrl": "http://localhost:3457/s/abc123",
|
|
64
|
+
"expiresAt": "2026-03-14T01:00:00.000Z"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Built-in Templates
|
|
69
|
+
|
|
70
|
+
<p align="center">
|
|
71
|
+
<img src="screenshots/macro-tracker.png" width="200" alt="Macro Tracker" />
|
|
72
|
+
<img src="screenshots/checkout.png" width="200" alt="Checkout" />
|
|
73
|
+
<img src="screenshots/workout-timer.png" width="200" alt="Workout Timer" />
|
|
74
|
+
<img src="screenshots/feedback-form.png" width="200" alt="Feedback Form" />
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
| Template | Description |
|
|
78
|
+
|----------|-------------|
|
|
79
|
+
| `macro-tracker` | Daily nutrition/macro tracking with progress bars |
|
|
80
|
+
| `checkout` | Stripe-like checkout flow with quantity, promo codes, payment |
|
|
81
|
+
| `workout-timer` | Exercise routine with rounds, rest timer, checklists |
|
|
82
|
+
| `feedback-form` | Multi-field form with star ratings and text inputs |
|
|
83
|
+
| `ws-test` | WebSocket connectivity test page |
|
|
84
|
+
|
|
85
|
+
## Compose Custom Pages
|
|
86
|
+
|
|
87
|
+
Don't want a template? Compose pages from individual components:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
curl -X POST http://localhost:3457/api/compose \
|
|
91
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
92
|
+
-H "Content-Type: application/json" \
|
|
93
|
+
-d '{
|
|
94
|
+
"title": "Team Lunch Poll",
|
|
95
|
+
"sections": [
|
|
96
|
+
{
|
|
97
|
+
"type": "header",
|
|
98
|
+
"config": { "title": "Where should we eat?", "subtitle": "Vote by noon", "icon": "🍕" }
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "checklist",
|
|
102
|
+
"config": {
|
|
103
|
+
"items": ["Chipotle", "Chick-fil-A", "Thai place on Main St"],
|
|
104
|
+
"allowAdd": true
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "button",
|
|
109
|
+
"config": { "text": "Submit Vote", "style": "primary", "emitEvent": "vote_submitted" }
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Components
|
|
116
|
+
|
|
117
|
+
| Component | Description | Key Config |
|
|
118
|
+
|-----------|-------------|------------|
|
|
119
|
+
| `header` | Title, subtitle, icon, badge | `title`, `subtitle`, `icon` |
|
|
120
|
+
| `button` | Action button with event emission | `text`, `style`, `emitEvent` |
|
|
121
|
+
| `timer` | Countdown, stopwatch, or interval timer | `mode`, `seconds`, `intervals` |
|
|
122
|
+
| `checklist` | Tappable checklist with progress bar | `items`, `allowAdd` |
|
|
123
|
+
| `progress` | Single or multi-segment progress bars | `segments`, `value`, `max` |
|
|
124
|
+
| `stats` | Grid of stat cards with trends | `items: [{label, value, icon}]` |
|
|
125
|
+
| `form` | Text, number, select, textarea, star ratings | `fields`, `submitText` |
|
|
126
|
+
| `tabs` | Switchable content panels | `tabs: [{label, content}]` |
|
|
127
|
+
|
|
128
|
+
All components emit events via WebSocket. Dark theme. Responsive. Zero external dependencies.
|
|
129
|
+
|
|
130
|
+
## MCP Server
|
|
131
|
+
|
|
132
|
+
Use SparkUI from Claude Desktop, Cursor, or Windsurf via the [MCP server](./mcp-server/README.md):
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"mcpServers": {
|
|
137
|
+
"sparkui": {
|
|
138
|
+
"command": "node",
|
|
139
|
+
"args": ["./mcp-server/index.js"],
|
|
140
|
+
"env": {
|
|
141
|
+
"SPARKUI_URL": "http://localhost:3457",
|
|
142
|
+
"SPARKUI_TOKEN": "your-push-token"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Page Management API
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# List active pages
|
|
153
|
+
GET /api/pages
|
|
154
|
+
|
|
155
|
+
# Page details (includes view count)
|
|
156
|
+
GET /api/pages/:id
|
|
157
|
+
|
|
158
|
+
# Update page data or extend TTL
|
|
159
|
+
PATCH /api/pages/:id
|
|
160
|
+
|
|
161
|
+
# Delete a page
|
|
162
|
+
DELETE /api/pages/:id
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
All endpoints require `Authorization: Bearer <token>`.
|
|
166
|
+
|
|
167
|
+
## Architecture
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
┌─────────┐ POST /api/push ┌──────────────┐ GET /s/:id ┌─────────┐
|
|
171
|
+
│ Agent │ ──────────────────────▶│ SparkUI │◀────────────────── │ Browser │
|
|
172
|
+
│ (AI/MCP) │ │ Server │ ──────────────────▶│ (User) │
|
|
173
|
+
│ │◀── WebSocket ──────────│ :3457 │──── WebSocket ────▶│ │
|
|
174
|
+
└─────────┘ completion events └──────────────┘ user actions └─────────┘
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Flow:**
|
|
178
|
+
1. Agent pushes a page (template or composed)
|
|
179
|
+
2. Server generates HTML, stores in memory, returns URL
|
|
180
|
+
3. User opens URL in any browser
|
|
181
|
+
4. User interacts (checks items, fills forms, clicks buttons)
|
|
182
|
+
5. Actions flow back to the agent via WebSocket
|
|
183
|
+
6. Page auto-expires after TTL
|
|
184
|
+
|
|
185
|
+
## Examples
|
|
186
|
+
|
|
187
|
+
See the [`examples/`](./examples/) directory for working scripts:
|
|
188
|
+
|
|
189
|
+
- **[`basic-push.sh`](./examples/basic-push.sh)** — Push a macro-tracker page with curl
|
|
190
|
+
- **[`compose-page.sh`](./examples/compose-page.sh)** — Compose a page from individual components
|
|
191
|
+
- **[`manage-pages.sh`](./examples/manage-pages.sh)** — Full page lifecycle: list, update, delete
|
|
192
|
+
- **[`node-client.js`](./examples/node-client.js)** — Node.js client with WebSocket event listener
|
|
193
|
+
|
|
194
|
+
## Configuration
|
|
195
|
+
|
|
196
|
+
| Environment Variable | Default | Description |
|
|
197
|
+
|---------------------|---------|-------------|
|
|
198
|
+
| `SPARKUI_PORT` | `3457` | Server port |
|
|
199
|
+
| `PUSH_TOKEN` | *(required)* | Auth token for push/compose/manage APIs |
|
|
200
|
+
| `SPARKUI_BASE_URL` | `http://localhost:3457` | Public URL for generated links |
|
|
201
|
+
| `OPENCLAW_HOOKS_URL` | *(optional)* | OpenClaw webhook URL for event forwarding |
|
|
202
|
+
| `OPENCLAW_HOOKS_TOKEN` | *(optional)* | Auth token for OpenClaw webhook |
|
|
203
|
+
|
|
204
|
+
## OpenClaw Skill
|
|
205
|
+
|
|
206
|
+
SparkUI includes an [OpenClaw](https://github.com/openclaw/openclaw) skill for direct agent integration. See [SKILL.md](./SKILL.md) for details.
|
|
207
|
+
|
|
208
|
+
## Contributing
|
|
209
|
+
|
|
210
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
211
|
+
|
|
212
|
+
## Documentation
|
|
213
|
+
|
|
214
|
+
Full documentation is available in the [`docs/`](./docs/) directory:
|
|
215
|
+
|
|
216
|
+
- **[Getting Started](./docs/getting-started.md)** — Install, configure, and push your first page
|
|
217
|
+
- **[API Reference](./docs/api-reference.md)** — REST API and WebSocket protocol
|
|
218
|
+
- **[Templates](./docs/templates.md)** — 5 built-in templates with schemas and examples
|
|
219
|
+
- **[Components](./docs/components.md)** — 8 composable components for custom pages
|
|
220
|
+
- **[MCP Setup](./docs/mcp-setup.md)** — Claude Desktop, Cursor, and Windsurf integration
|
|
221
|
+
- **[OpenClaw Setup](./docs/openclaw-setup.md)** — Agent skill integration
|
|
222
|
+
- **[ChatGPT Setup](./docs/chatgpt-setup.md)** — Custom GPT Actions integration
|
|
223
|
+
|
|
224
|
+
## Related
|
|
225
|
+
|
|
226
|
+
- **[MCP Server](./mcp-server/README.md)** — Use SparkUI from Claude Desktop, Cursor, or Windsurf
|
|
227
|
+
- **[Examples](./examples/README.md)** — Working scripts and code samples
|
|
228
|
+
- **[Contributing](./CONTRIBUTING.md)** — Setup, testing, and PR guidelines
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
[MIT](./LICENSE) © [Limeade Labs](https://limeadelabs.com)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# SparkUI — Ephemeral Web UI Generator
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
Use SparkUI when the user needs something **visual** that's better as a web page than chat text:
|
|
5
|
+
- **Dashboards** — nutrition trackers, fitness stats, project metrics
|
|
6
|
+
- **Data displays** — tables, charts, comparisons
|
|
7
|
+
- **Trackers** — daily logs, progress views
|
|
8
|
+
- **Rich content** — anything with colors, progress bars, layouts
|
|
9
|
+
|
|
10
|
+
**Don't use** for simple text answers, yes/no questions, or quick lists that work fine in chat.
|
|
11
|
+
|
|
12
|
+
## Server Location
|
|
13
|
+
- **Directory:** `/home/clawd/projects/sparkui/`
|
|
14
|
+
- **Port:** 3457 (configured in .env; override with `SPARKUI_PORT` env)
|
|
15
|
+
- **Config:** Push token is in `/home/clawd/projects/sparkui/.env`
|
|
16
|
+
|
|
17
|
+
## Step 1: Ensure Server is Running
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Check if running
|
|
21
|
+
curl -s http://localhost:3457/ | head -c 200
|
|
22
|
+
|
|
23
|
+
# If not running, start it:
|
|
24
|
+
cd /home/clawd/projects/sparkui && node server.js &
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `exec` with `background: true` to start the server if it's down.
|
|
28
|
+
|
|
29
|
+
## Step 2: Get the Push Token
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
source /home/clawd/projects/sparkui/.env # loads PUSH_TOKEN and SPARKUI_PORT
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Step 3: Push Content
|
|
36
|
+
|
|
37
|
+
### Using a Template (preferred for known types)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl -s -X POST http://localhost:3457/api/push \
|
|
41
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
42
|
+
-H "Content-Type: application/json" \
|
|
43
|
+
-d '{
|
|
44
|
+
"template": "macro-tracker",
|
|
45
|
+
"data": {
|
|
46
|
+
"date": "2026-03-10",
|
|
47
|
+
"calories": {"current": 1250, "target": 1900},
|
|
48
|
+
"protein": {"current": 62, "target": 86},
|
|
49
|
+
"fat": {"current": 45, "target": 95},
|
|
50
|
+
"carbs": {"current": 120, "target": 175},
|
|
51
|
+
"meals": [
|
|
52
|
+
{"name": "Oatmeal with berries", "calories": 350, "time": "6:30 AM"},
|
|
53
|
+
{"name": "Grilled chicken salad", "calories": 480, "time": "12:00 PM"},
|
|
54
|
+
{"name": "Greek yogurt", "calories": 150, "time": "3:00 PM"}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"ttl": 7200
|
|
58
|
+
}'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Using Raw HTML
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -s -X POST http://localhost:3457/api/push \
|
|
65
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
66
|
+
-H "Content-Type: application/json" \
|
|
67
|
+
-d '{"html": "<html>...</html>", "ttl": 3600}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For raw HTML, use the dark theme (#111 bg, #e0e0e0 text, -apple-system font) to match SparkUI's design language.
|
|
71
|
+
|
|
72
|
+
### Updating a Page
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
curl -s -X PATCH http://localhost:3457/api/pages/PAGE_ID \
|
|
76
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
77
|
+
-H "Content-Type: application/json" \
|
|
78
|
+
-d '{"template": "macro-tracker", "data": {...}}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Step 4: Share the Link
|
|
82
|
+
|
|
83
|
+
The push API returns `{ id, url, fullUrl }`. Share the `fullUrl` with the user.
|
|
84
|
+
|
|
85
|
+
**Important:** The server runs on localhost. For the user to access it, they need network access to the host. If behind a tunnel (e.g., Cloudflare Tunnel, ngrok), set `SPARKUI_BASE_URL` env var to the public URL.
|
|
86
|
+
|
|
87
|
+
## Available Templates
|
|
88
|
+
|
|
89
|
+
| Template | Description | Data Shape |
|
|
90
|
+
|----------|-------------|------------|
|
|
91
|
+
| `macro-tracker` | Nutrition macro dashboard | `{date, calories, protein, fat, carbs, meals}` — each macro has `{current, target}`, meals is `[{name, calories, time}]` |
|
|
92
|
+
| `feedback-form` | Rating + text feedback form | `{title, subtitle?, questions?}` — questions is optional `string[]` for extra text fields |
|
|
93
|
+
|
|
94
|
+
## OpenClaw Round-Trip (Event Callbacks)
|
|
95
|
+
|
|
96
|
+
When you want SparkUI to report user interactions back to the agent via OpenClaw webhooks, add `openclaw` config to the push request. This closes the loop: **agent pushes page → user interacts → agent gets notified**.
|
|
97
|
+
|
|
98
|
+
### Push with OpenClaw Config
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
curl -s -X POST http://localhost:3457/api/push \
|
|
102
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
103
|
+
-H "Content-Type: application/json" \
|
|
104
|
+
-d '{
|
|
105
|
+
"template": "feedback-form",
|
|
106
|
+
"data": {"title": "Quick Feedback", "subtitle": "How was the experience?"},
|
|
107
|
+
"openclaw": {
|
|
108
|
+
"enabled": true,
|
|
109
|
+
"channel": "slack",
|
|
110
|
+
"to": "YOUR_CHANNEL_ID",
|
|
111
|
+
"eventTypes": ["completion"]
|
|
112
|
+
}
|
|
113
|
+
}'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### OpenClaw Config Fields
|
|
117
|
+
|
|
118
|
+
| Field | Type | Required | Description |
|
|
119
|
+
|-------|------|----------|-------------|
|
|
120
|
+
| `enabled` | boolean | yes | Enable OpenClaw forwarding |
|
|
121
|
+
| `channel` | string | no | Delivery channel (default: "slack") |
|
|
122
|
+
| `to` | string | no | Target channel/user ID (e.g., "YOUR_CHANNEL_ID" for #sparkui) |
|
|
123
|
+
| `eventTypes` | string[] | no | Which events to forward: `["completion"]`, `["event"]`, or `["event", "completion"]`. Default: `["completion"]` |
|
|
124
|
+
| `sessionKey` | string | no | Optional session key override for routing |
|
|
125
|
+
|
|
126
|
+
### How It Works
|
|
127
|
+
|
|
128
|
+
1. Agent pushes a page with `openclaw` config
|
|
129
|
+
2. User opens the page and interacts (clicks, submits form)
|
|
130
|
+
3. Browser sends events via WebSocket to SparkUI server
|
|
131
|
+
4. SparkUI server checks `openclaw` config and forwards matching events to OpenClaw's `/hooks/agent` endpoint
|
|
132
|
+
5. OpenClaw delivers the message to the specified channel
|
|
133
|
+
6. Agent receives the message and can respond
|
|
134
|
+
|
|
135
|
+
### When to Use OpenClaw Round-Trip
|
|
136
|
+
|
|
137
|
+
- **Feedback forms** — get notified when user submits
|
|
138
|
+
- **Approval workflows** — user clicks approve/reject
|
|
139
|
+
- **Interactive quizzes** — collect answers
|
|
140
|
+
- **Any form** — completion events carry all form data
|
|
141
|
+
|
|
142
|
+
### Environment Variables
|
|
143
|
+
|
|
144
|
+
Set these in `.env` (already configured):
|
|
145
|
+
```
|
|
146
|
+
OPENCLAW_HOOKS_URL=http://127.0.0.1:18789/hooks/agent
|
|
147
|
+
OPENCLAW_HOOKS_TOKEN=your_openclaw_hooks_token
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Composable Components (Preferred)
|
|
151
|
+
|
|
152
|
+
Instead of raw HTML or monolithic templates, use the **compose API** to assemble pages from reusable components. This is the fastest path — under 3 seconds from request to rendered UI.
|
|
153
|
+
|
|
154
|
+
### POST /api/compose
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
curl -s -X POST http://localhost:3457/api/compose \
|
|
158
|
+
-H "Authorization: Bearer $PUSH_TOKEN" \
|
|
159
|
+
-H "Content-Type: application/json" \
|
|
160
|
+
-d '{
|
|
161
|
+
"title": "Page Title",
|
|
162
|
+
"sections": [
|
|
163
|
+
{ "type": "header", "config": { "title": "Hello", "subtitle": "World", "icon": "⚡", "badge": "New" } },
|
|
164
|
+
{ "type": "checklist", "config": { "items": [{"text": "Item 1"}, {"text": "Item 2"}], "showProgress": true, "allowAdd": true } },
|
|
165
|
+
{ "type": "button", "config": { "label": "Done", "action": "complete", "style": "primary" } }
|
|
166
|
+
],
|
|
167
|
+
"openclaw": { "enabled": true, "channel": "slack", "to": "YOUR_CHANNEL_ID" }
|
|
168
|
+
}'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Returns `{ id, url, fullUrl }` — ready to share.
|
|
172
|
+
|
|
173
|
+
### Available Components
|
|
174
|
+
|
|
175
|
+
| Component | Config | Description |
|
|
176
|
+
|-----------|--------|-------------|
|
|
177
|
+
| `header` | `{ title, subtitle?, icon?, badge? }` | Page header with optional icon/badge |
|
|
178
|
+
| `button` | `{ label, action, style?, icon?, disabled? }` | Clickable button. Styles: `primary` (green), `secondary` (outline), `danger` (red). Sends `event` with `{action}` |
|
|
179
|
+
| `timer` | `{ mode, duration?, intervals?, autoStart? }` | Modes: `countdown` (duration in secs), `stopwatch`, `interval` (array of `{label, seconds}`). Sends `timer` event on complete |
|
|
180
|
+
| `checklist` | `{ items: [{text, checked?}], allowAdd?, showProgress? }` | Interactive checklist with progress bar. Sends `completion` when all checked |
|
|
181
|
+
| `progress` | `{ value, max, label?, color?, showPercent?, segments? }` | Single bar or multi-segment. Segments: `[{label, value, max, color}]` |
|
|
182
|
+
| `stats` | `{ items: [{label, value, unit?, icon?, trend?}] }` | 2-column grid of stat cards. Trend: `up`/`down`/`flat` |
|
|
183
|
+
| `form` | `{ fields: [{type, name, label, placeholder?, options?, required?}], submitLabel? }` | Form with text/number/select/textarea/rating fields. Sends `completion` with `{formData}` |
|
|
184
|
+
| `tabs` | `{ tabs: [{label, content}], activeIndex? }` | Tab switcher. Content can be HTML strings (including other component output) |
|
|
185
|
+
|
|
186
|
+
### Component Examples
|
|
187
|
+
|
|
188
|
+
**Workout Timer:**
|
|
189
|
+
```json
|
|
190
|
+
{ "type": "timer", "config": { "mode": "interval", "intervals": [
|
|
191
|
+
{"label": "Work", "seconds": 30}, {"label": "Rest", "seconds": 10},
|
|
192
|
+
{"label": "Work", "seconds": 30}, {"label": "Rest", "seconds": 10}
|
|
193
|
+
] } }
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Stats Dashboard:**
|
|
197
|
+
```json
|
|
198
|
+
{ "type": "stats", "config": { "items": [
|
|
199
|
+
{"label": "Weight", "value": "222", "unit": "lbs", "icon": "⚖️", "trend": "down"},
|
|
200
|
+
{"label": "Streak", "value": "5", "unit": "days", "icon": "🔥", "trend": "up"}
|
|
201
|
+
] } }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Multi-Segment Progress:**
|
|
205
|
+
```json
|
|
206
|
+
{ "type": "progress", "config": { "segments": [
|
|
207
|
+
{"label": "Protein", "value": 62, "max": 86, "color": "#ff6b6b"},
|
|
208
|
+
{"label": "Fat", "value": 45, "max": 95, "color": "#ffd93d"},
|
|
209
|
+
{"label": "Carbs", "value": 120, "max": 175, "color": "#6bcb77"}
|
|
210
|
+
] } }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Feedback Form:**
|
|
214
|
+
```json
|
|
215
|
+
{ "type": "form", "config": {
|
|
216
|
+
"fields": [
|
|
217
|
+
{"type": "rating", "name": "rating", "label": "How was it?"},
|
|
218
|
+
{"type": "textarea", "name": "feedback", "label": "Comments", "placeholder": "Tell us more..."}
|
|
219
|
+
],
|
|
220
|
+
"submitLabel": "Send"
|
|
221
|
+
} }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### When to Use Compose vs Templates
|
|
225
|
+
|
|
226
|
+
- **Compose** — for any new page, custom layouts, mixing components. This is the default choice.
|
|
227
|
+
- **Templates** — only for `macro-tracker` (has specialized chart logic) or when exact existing template behavior is needed.
|
|
228
|
+
|
|
229
|
+
## Example Flow: Daily Macro Tracking
|
|
230
|
+
|
|
231
|
+
1. User says "log my lunch — chicken sandwich, 450 cal, 35g protein, 15g fat, 40g carbs"
|
|
232
|
+
2. Agent updates the nutrition spreadsheet
|
|
233
|
+
3. Agent reads today's totals from the sheet
|
|
234
|
+
4. Agent pushes a macro-tracker page with the current totals
|
|
235
|
+
5. Agent shares the link: "Here's your updated dashboard: http://..."
|
|
236
|
+
6. If the page already exists from earlier, use PATCH to update it instead of creating a new one
|
|
237
|
+
|
|
238
|
+
## Notes
|
|
239
|
+
- Pages expire after their TTL (default 1 hour). This is by design — they're ephemeral.
|
|
240
|
+
- The macro-tracker template auto-refreshes every 30 seconds.
|
|
241
|
+
- WebSocket support is built in — pages will auto-reload when updated via PATCH.
|
|
242
|
+
- For longer-lived pages, set a higher TTL (e.g., 86400 for 24h).
|
package/bin/deploy
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "🚀 Deploying SparkUI..."
|
|
5
|
+
|
|
6
|
+
# Restart the service (adjust to your process manager)
|
|
7
|
+
# systemctl --user restart sparkui
|
|
8
|
+
# pm2 restart sparkui
|
|
9
|
+
|
|
10
|
+
echo "✅ SparkUI restarted"
|
|
11
|
+
|
|
12
|
+
# Quick health check
|
|
13
|
+
sleep 1
|
|
14
|
+
PORT="${SPARKUI_PORT:-3456}"
|
|
15
|
+
STATUS=$(curl -s "http://localhost:${PORT}/" | jq -r '.status // "unknown"' 2>/dev/null)
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Health check: $STATUS"
|
|
18
|
+
|
|
19
|
+
# Check public URL (if configured)
|
|
20
|
+
if [ -n "$SPARKUI_BASE_URL" ]; then
|
|
21
|
+
PUB_STATUS=$(curl -s "$SPARKUI_BASE_URL/" | jq -r '.status // "unknown"' 2>/dev/null)
|
|
22
|
+
echo "Public URL: $PUB_STATUS"
|
|
23
|
+
fi
|