DevOps
glance
Create, update, and manage Glance dashboard widgets.
---
name: glance
description: "Create, update, and manage Glance dashboard widgets. Use when user wants to: add something to their dashboard, create a widget, track data visually, show metrics/stats, display API data, or monitor usage."
metadata:
openclaw:
emoji: "π₯οΈ"
homepage: "https://github.com/acfranzen/glance"
requires:
env: ["GLANCE_URL"]
bins: ["curl"]
primaryEnv: GLANCE_URL
---
# Glance
AI-extensible personal dashboard. Create custom widgets with natural language β the AI handles data collection.
## Features
- **Custom Widgets** β Create widgets via AI with auto-generated JSX
- **Agent Refresh** β AI collects data on schedule and pushes to cache
- **Dashboard Export/Import** β Share widget configurations
- **Credential Management** β Secure API key storage
- **Real-time Updates** β Webhook-triggered instant refreshes
## Quick Start
```bash
# Navigate to skill directory (if installed via ClawHub)
cd "$(clawhub list | grep glance | awk '{print $2}')"
# Or clone directly
git clone https://github.com/acfranzen/glance ~/.glance
cd ~/.glance
# Install dependencies
npm install
# Configure environment
cp .env.example .env.local
# Edit .env.local with your settings
# Start development server
npm run dev
# Or build and start production
npm run build && npm start
```
Dashboard runs at **http://localhost:3333**
## Configuration
Edit `.env.local`:
```bash
# Server
PORT=3333
AUTH_TOKEN=your-secret-token # Optional: Bearer token auth
# OpenClaw Integration (for instant widget refresh)
OPENCLAW_GATEWAY_URL=https://localhost:18789
OPENCLAW_TOKEN=your-gateway-token
# Database
DATABASE_PATH=./data/glance.db # SQLite database location
```
## Service Installation (macOS)
```bash
# Create launchd plist
cat > ~/Library/LaunchAgents/com.glance.dashboard.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.glance.dashboard</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/npm</string>
<string>run</string>
<string>dev</string>
</array>
<key>WorkingDirectory</key>
<string>~/.glance</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>~/.glance/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>~/.glance/logs/stderr.log</string>
</dict>
</plist>
EOF
# Load service
mkdir -p ~/.glance/logs
launchctl load ~/Library/LaunchAgents/com.glance.dashboard.plist
# Service commands
launchctl start com.glance.dashboard
launchctl stop com.glance.dashboard
launchctl unload ~/Library/LaunchAgents/com.glance.dashboard.plist
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `PORT` | Server port | `3333` |
| `AUTH_TOKEN` | Bearer token for API auth | β |
| `DATABASE_PATH` | SQLite database path | `./data/glance.db` |
| `OPENCLAW_GATEWAY_URL` | OpenClaw gateway for webhooks | β |
| `OPENCLAW_TOKEN` | OpenClaw auth token | β |
## Requirements
- Node.js 20+
- npm or pnpm
- SQLite (bundled)
---
# Widget Skill
Create and manage dashboard widgets. Most widgets use `agent_refresh` β **you** collect the data.
## Quick Start
```bash
# Check Glance is running (list widgets)
curl -s -H "Origin: $GLANCE_URL" "$GLANCE_URL/api/widgets" | jq '.custom_widgets[].slug'
# Auth note: Local requests with Origin header bypass Bearer token auth
# For external access, use: -H "Authorization: Bearer $GLANCE_TOKEN"
# Refresh a widget (look up instructions, collect data, POST to cache)
sqlite3 $GLANCE_DATA/glance.db "SELECT json_extract(fetch, '$.instructions') FROM custom_widgets WHERE slug = 'my-widget'"
# Follow the instructions, then:
curl -X POST "$GLANCE_URL/api/widgets/my-widget/cache" \
-H "Content-Type: application/json" \
-H "Origin: $GLANCE_URL" \
-d '{"data": {"value": 42, "fetchedAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}}'
# Verify in browser
browser action:open targetUrl:"$GLANCE_URL"
```
## AI Structured Output Generation (REQUIRED)
When generating widget definitions, **use the JSON Schema** at `docs/schemas/widget-schema.json` with your AI model's structured output mode:
- **Anthropic**: Use `tool_use` with the schema
- **OpenAI**: Use `response_format: { type: "json_schema", schema }`
The schema enforces all required fields at generation time β malformed widgets cannot be produced.
### Required Fields Checklist
Every widget **MUST** have these fields (the schema enforces them):
| Field | Type | Notes |
|-------|------|-------|
| `name` | string | Non-empty, human-readable |
| `slug` | string | Lowercase kebab-case (`my-widget`) |
| `source_code` | string | Valid JSX with Widget function |
| `default_size` | `{ w: 1-12, h: 1-20 }` | Grid units |
| `min_size` | `{ w: 1-12, h: 1-20 }` | Cannot resize smaller |
| `fetch.type` | enum | `"server_code"` \| `"webhook"` \| `"agent_refresh"` |
| `fetch.instructions` | string | **REQUIRED if type is `agent_refresh`** |
| `fetch.schedule` | string | **REQUIRED if type is `agent_refresh`** (cron) |
| `data_schema.type` | `"object"` | Always object |
| `data_schema.properties` | object | Define each field |
| `data_schema.required` | array | **MUST include `"fetchedAt"`** |
| `credentials` | array | Use `[]` if none needed |
### Example: Minimal Valid Widget
```json
{
"name": "My Widget",
"slug": "my-widget",
"source_code": "function Widget({ serverData }) { return <div>{serverData?.value}</div>; }",
"default_size": { "w": 2, "h": 2 },
"min_size": { "w": 1, "h": 1 },
"fetch": {
"type": "agent_refresh",
"schedule": "*/15 * * * *",
"instructions": "## Data Collection\nCollect the data...\n\n## Cache Update\nPOST to /api/widgets/my-widget/cache"
},
"data_schema": {
"type": "object",
"properties": {
"value": { "type": "number" },
"fetchedAt": { "type": "string", "format": "date-time" }
},
"required": ["value", "fetchedAt"]
},
"credentials": []
}
```
---
## β οΈ Widget Creation Checklist (MANDATORY)
Every widget must complete ALL steps before being considered done:
```
β‘ Step 1: Create widget definition (POST /api/widgets)
- source_code with Widget function
- data_schema (REQUIRED for validation)
- fetch config (type + instructions for agent_refresh)
β‘ Step 2: Add to dashboard (POST /api/widgets/instances)
- custom_widget_id matches definition
- title and config set
β‘ Step 3: Populate cache (for agent_refresh widgets)
- Data matches data_schema exactly
- Includes fetchedAt timestamp
β‘ Step 4: Set up cron job (for agent_refresh widgets)
- Simple message: "β‘ WIDGET REFRESH: {slug}"
- Appropriate schedule (*/15 or */30 typically)
β‘ Step 5: BROWSER VERIFICATION (MANDATORY)
- Open http://localhost:3333
- Widget is visible on dashboard
- Shows actual data (not loading spinner)
- Data values match what was cached
- No errors or broken layouts
β DO NOT report widget as complete until Step 5 passes!
```
## Quick Reference
- **Full SDK docs:** See `docs/widget-sdk.md` in the Glance repo
- **Component list:** See [references/components.md](references/components.md)
## Widget Package Structure
```
Widget Package
βββ meta (name, slug, description, author, version)
βββ widget (source_code, default_size, min_size)
βββ fetch (server_code | webhook | agent_refresh)
βββ dataSchema? (JSON Schema for cached data - validates on POST)
βββ cache (ttl, staleness, fallback)
βββ credentials[] (API keys, local software requirements)
βββ config_schema? (user options)
βββ error? (retry, fallback, timeout)
```
## Fetch Type Decision Tree
```
Is data available via API that the widget can call?
βββ YES β Use server_code
βββ NO β Does an external service push data?
βββ YES β Use webhook
βββ NO β Use agent_refresh (YOU collect it)
```
| Scenario | Fetch Type | Who Collects Data? |
|----------|-----------|-------------------|
| Public/authenticated API | `server_code` | Widget calls API at render |
| External service pushes data | `webhook` | External service POSTs to cache |
| **Local CLI tools** | `agent_refresh` | **YOU (the agent) via PTY/exec** |
| **Interactive terminals** | `agent_refresh` | **YOU (the agent) via PTY** |
| **Computed/aggregated data** | `agent_refresh` | **YOU (the agent) on a schedule** |
**β οΈ `agent_refresh` means YOU are the data source.** You set up a cron to remind yourself, then YOU collect the data using your tools (exec, PTY, browser, etc.) and POST it to the cache.
## API Endpoints
### Widget Definitions
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/widgets` | Create widget definition |
| `GET` | `/api/widgets` | List all definitions |
| `GET` | `/api/widgets/:slug` | Get single definition |
| `PATCH` | `/api/widgets/:slug` | Update definition |
| `DELETE` | `/api/widgets/:slug` | Delete definition |
### Widget Instances (Dashboard)
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/widgets/instances` | Add widget to dashboard |
| `GET` | `/api/widgets/instances` | List dashboard widgets |
| `PATCH` | `/api/widgets/instances/:id` | Update instance (config, position) |
| `DELETE` | `/api/widgets/instances/:id` | Remove from dashboard |
### Credentials
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/credentials` | List credentials + status |
| `POST` | `/api/credentials` | Store credential |
| `DELETE` | `/api/credentials/:id` | Delete credential |
## Creating a Widget
### Full Widget Package Structure
```json
{
"name": "GitHub PRs",
"slug": "github-prs",
"description": "Shows open pull requests",
"source_code": "function Widget({ serverData }) { ... }",
"default_size": { "w
... (truncated)
devops
By
Comments
Sign in to leave a comment