← Back to Skills
DevOps

glance

acfranzen By acfranzen 👁 15 views ▲ 0 votes

Create, update, and manage Glance dashboard widgets.

GitHub
---
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

Comments

Sign in to leave a comment

Loading comments...