card-refresh — periodic widget data refresh
You're invoked to re-pull data for whatever widgets are currently shown on the user's ai-desk-card 副屏. This typically runs from cron every 2 hours (see REFRESH.md for the setup). Be quick, idempotent, and silent unless something's wrong.
Operating principles
- Read first, push second. Pull the current widget cache from
GET /widget— it tells you which slots are filled with which widget types. Only refresh those; don't push new widget types unprompted. - No data → don't push. If a data source is unreachable (calendar not synced, mail offline), skip that widget. Leave the existing one in place rather than wiping with stale data.
- Be fast. Don't burn 30 s on one data source. 5 s timeout per fetcher; move on.
- Be quiet. No "I'm refreshing..." chatter when invoked from cron. Emit nothing on stdout unless there's an actionable error.
What to do (every run)
1. Snapshot current cache
curl -sf http://127.0.0.1:9877/widget | python3 -m json.tool
Parse widgets[]. For each {slot, type, age}, decide:
age< 60 s → just pushed, skip.typein{ai-status, ai-tasks}→ driven by conversation, skip.typein{focus, scratch}→ user-supplied, only refresh if explicitly asked.- All others → refresh if you can.
2. Fetch fresh data per widget type
The card-widget skill's schemas tell you the data shape. Below are the recommended sources per widget (use whatever's actually available on the user's machine):
| Widget | Source | Notes |
|---|---|---|
weather |
wttr.in / OpenWeather / system weather | Need user's location |
calendar |
icalbuddy on macOS / Google Calendar API / .ics file |
macOS easiest |
next-meeting |
same as calendar — pick first event with starts_in_min ≥ 0 |
|
inbox |
gmail API / mailctl / IMAP / Slack API | Per-source counts only |
messages |
iMessage db / Slack DMs | macOS: ~/Library/Messages/chat.db (read-only) |
pr-queue |
gh pr list against user's repos |
List repos via config |
git-status |
git -C <cwd> status --porcelain + git log -1 |
One repo only |
system |
psutil / uptime / vm_stat |
macOS: vm_stat for mem, df for disk |
now-playing |
macOS: osascript -e 'tell application "Spotify"...' |
Skip if not running |
todo |
macOS Reminders (use fetch_todo_reminders.py in card-widget) |
|
deadlines |
user-maintained list (see assets/deadlines.yaml if it exists) | |
break-reminder |
local idle / activity tracking | Best-effort |
You don't need to support all of these in one refresh. Start with the 3-4 widgets the user actually has on the card, refresh those, push, exit.
3. Push back
Use the card-widget skill's push_widget.py helper or curl. Re-push the
SAME {slot, type} with new data. Daemon will replace and trigger a
single debounced frame render.
Per-widget TTL: pass ttl=7800 (2h 10min) so widgets fade if cron
breaks. That way a stuck cron doesn't leave hours-old data on the panel
forever.
4. Done — exit
Don't summarize, don't ask follow-up questions. Just exit. Cron user sees nothing in their terminal.
When invoked interactively (not from cron)
If the user says "刷新一下卡片" in conversation, do the same thing but briefly tell them what you did:
Refreshed: calendar (3 events), weather (24°C 晴), pr-queue (2 reviews waiting). Skipped: inbox (gmail API not configured).
Error handling
- Daemon down → run
/card-onboarddecision tree, but don't try to start it from cron (user won't see the prompt). Just exit with a diagnostic line on stderr. - Network down → skip remote sources, push local ones (system, git-status) anyway.
- Schema mismatch on push → log to stderr, continue. The daemon rejects the bad widget; other widgets still get refreshed.
What NOT to do
- ❌ Don't change which widgets are in which slot. That's user-controlled.
- ❌ Don't push widgets that weren't already there (e.g., "I noticed you don't have a weather widget, let me add one").
- ❌ Don't write
ai-statusfrom this skill — that's driven by interactive interactive AI agent sessions. - ❌ Don't fetch data from sources that need OAuth without checking if credentials are configured.
Helper scripts
scripts/refresh_loop.sh— one-shot refresh entry point. Used as the cron command. Wraps the headless AI CLI invocation.scripts/fallback_refresh.py— pure-Python no-AI version that only refreshessystem,git-status, andweather(wttr.in). For users who don't want token cost.