Livi for agents

Everything an agent needs to connect to Livi and use it. Point your agent at this page, at /skill.md, or at the agent kit on GitHub.

Fastest setup: tell your agent Set up Livi for me: https://livi-widgets.vercel.app/start. It shows the user a code to type into Livi, waits for it, and gets its own token. The full walkthrough is at /start.

Stay current. Livi changes. At the start of every Livi task, fetch the latest guide from https://livi-widgets.vercel.app/skill.md and follow it over this copy. If you keep it as a local skill, overwrite your copy with it.

Livi is an iPhone app whose widgets are drawn by you, the agent. Everything Apple's WidgetKit can show, you control with JSON: what each size shows, how it looks, when it changes, and what happens when the user taps it.

The one rule that keeps widgets from breaking: build, then look, then fix. Every time you make or change a widget, call livi_preview_widget (or read the check that livi_set_widget returns) and fix every warning before you tell the user it's done. Section 4 explains the loop.

1. What you can control

Every widget capability in iOS 26, and the field that controls it:

On the iPhone What it is Livi field
Home Screen, small / medium / large The three iPhone widget sizes blocks, layout, sizes
iPad and Mac, extra large The wide iPad widget sizes.extra_large
Lock Screen, rectangular Two lines under the clock lock.rectangular
Lock Screen, circular A ring or a short value in a circle lock.circular
Lock Screen, inline One line of text above the clock lock.inline, lock.symbol
StandBy, CarPlay The small widget, full screen, background removed automatic (design small well)
Text that ticks by itself Clocks, countdowns, timers, live progress bars clock, countdown blocks
Charts Sparklines from your numbers chart block
Gauges Rings and capacity bars gauge, progress blocks
Photos Pictures in a block or as the background image block, theme.style: "image"
Buttons on the widget The user taps; you get an event buttons block, livi_get_events
Checklists on the widget The user ticks items right on the widget checklist block
Control Center, Lock Screen corners, Action button Any of your widget buttons, placed by the user automatic for every buttons item without a url
Tap to open Open a web page, call, map, Shortcut or app url on the widget, any block, list row or button
Smart Stack Rotate your widget to the top when it matters relevance (0-100), per version too
Privacy Hide content while the phone is locked private, or sensitive on a block
Light, dark, tinted, clear (Liquid Glass) How the Home Screen is styled; the default look is frosted glass, fully see-through in Clear theme (system, light, dark, glass, solid, gradient, image)
Fonts SF Pro, SF Rounded, New York, SF Mono theme.font
Versions by time, day, place Phone switches content with no agent running variants
Updates with no agent Livi fetches a URL every N minutes livi_schedule
Updates you push Changes reach the phone in seconds livi_set_widget (Livi sends the widget push)

What widgets can't do, so don't promise it: scroll, play video or sound, take typed text, show a web view, load anything from the network while drawing, animate freely, or update more than about 40 to 70 times a day on their own. Buttons and checklists don't work while the phone is locked.

2. Connect (once)

You show the user a code; they type it into the Livi app (like pairing a TV). Full walkthrough, including getting the app from TestFlight: https://livi-widgets.vercel.app/start

  1. Get a code, with your own name (URL-encoded):
curl -s "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/connect?start=1&name=Claude%20Code"
  1. Show them the 8-character code (like K7QX-29PL) and say: "Open Livi, tap +, and type this code." The reply also gives a QR that opens Livi with the code filled in.
  2. Run the wait command from the reply (connect?wait=lvw_…) until they enter it. It then prints your Livi token (lv_…), shown only once, plus setup steps. Codes work once, for 15 minutes; if one expires, get a new one.

Older builds of the app showed the code instead. If the user gives you one, run curl -s "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/connect?code=K7QX29PL&name=Claude%20Code" with it.

Then add the MCP server.

Claude Code

claude mcp add --transport http --scope user livi https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp --header "Authorization: Bearer lv_YOUR_TOKEN"

Run /mcp or restart so the livi_* tools load. Until then, use the HTTP API in section 15.

Any MCP client (JSON config)

{
  "mcpServers": {
    "livi": {
      "type": "http",
      "url": "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp",
      "headers": { "Authorization": "Bearer lv_YOUR_TOKEN" }
    }
  }
}

Clients that only take a URL (Claude and ChatGPT custom connectors): the token goes in the path.

https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp/lv_YOUR_TOKEN

Treat the token like a password. Don't print it back to the user unless they ask, and don't commit it to a repo.

3. Tools

Tool What it does
livi_set_widget Create or replace a widget by slug. Send the whole widget each time. Returns check: what each size shows, plus warnings.
livi_preview_widget See a widget: an image of every size and Lock Screen shape, plus warnings. Pass a slug, or a full draft to check before saving. Only you see it.
livi_list_widgets Every widget and its blocks.
livi_delete_widget Remove a widget by slug.
livi_get_events Taps on your widget buttons and checklist items.
livi_schedule Keep a widget updated automatically: Livi fetches a URL every N minutes and fills your template.
livi_test_job Try a job once without saving it.
livi_list_jobs / livi_delete_job See or stop scheduled jobs.
livi_ask Ask the user a question on their phone and wait up to wait_seconds (default 45) for the answer.
livi_get_answer Keep waiting on a question (call again while status is pending).
livi_list_requests / livi_cancel_request Questions still waiting, or take one back.
livi_get_phone_context What the phone shares: place, battery, time zone, local time, city.

4. Build, look, fix (every time)

  1. Draft the widget JSON.
  2. Preview it: call livi_preview_widget with the draft (same fields as livi_set_widget). You get back:
    • an image of the small, medium and large Home Screen widgets and the rectangular, circular and inline Lock Screen widgets, drawn with the phone's layout rules, theme and the user's time zone;
    • sizes: in words, which blocks each size shows and how many are hidden;
    • warnings: things that look broken (text cut off, blocks running off the bottom, versions that never show, unreadable colours, bad indexes) with how to fix each;
    • notes: things worth knowing (glass needs Clear mode, a picture loads after a refresh).
  3. Fix every warning, and look at the image like a designer would. Is the most important thing on the small size? Does anything look cramped?
  4. Save with livi_set_widget. Its check repeats the warnings; it should be empty.
  5. For versions, preview each one with at ("07:30") or place ("home") to see what shows then and there.

Preview a saved widget with just { "slug": "news" }. The image is approximate (the phone uses SF Pro and real SF Symbols; the preview draws symbols as dots) but the layout, sizes, cut-offs and colours match. Never show the preview image to the user as if it were their phone; it's for you.

Over HTTP, preview_widget returns png_base64; add "raw": true to get the PNG itself (curl … -o preview.png).

5. A widget

{
  "slug": "brief",
  "title": "Morning brief",
  "subtitle": "9:00 AM",
  "symbol": "newspaper.fill",
  "accent": "#0A84FF",
  "url": "https://news.ycombinator.com",
  "relevance": 40,
  "theme": { "style": "system", "font": "default" },
  "layout": "auto",
  "blocks": [
    { "type": "metric", "label": "AAPL", "value": "$291.13", "change": "+1.2%" },
    { "type": "list", "title": "Top stories", "items": [
      { "title": "Apple ships iOS 27.1", "detail": "Verge", "url": "https://www.theverge.com/…" },
      { "title": "Fed holds rates steady", "detail": "Reuters" }
    ] },
    { "type": "clock", "label": "New York", "timezone": "America/New_York" }
  ],
  "sizes": { "small": [0] },
  "lock": { "inline": "AAPL $291 ▲1.2%", "circular": 0 }
}
Field What it does
slug Stable id you reuse to update it: lowercase letters, digits, dashes, up to 40.
title Shown at the top, up to 40 characters. About 15 fit on small, 35 on medium.
subtitle Small grey text at the top right on medium and larger. Good for a time ("9:00 AM").
symbol SF Symbol name next to the title, like newspaper.fill, bitcoinsign.circle.fill, airplane.
accent #RRGGBB. Colours the symbol, list dots, bars, rings, charts and buttons.
url Opened when the widget is tapped. Default opens it in Livi.
relevance 0-100. How much this matters now; the Smart Stack rotates the most relevant widget to the top.
private true hides the content (not the title) while the phone is locked.
theme How it looks. Section 9.
layout auto, hero, grid or list. Section 7.
blocks Up to 8 blocks, most important first. Section 6.
sizes Which blocks each size shows, by index from 0. Section 7.
lock What the Lock Screen widgets show. Section 8.
variants Versions by time, day or place. Section 10.

Up to 12 widgets per user. Send null for url, relevance, sizes or lock to clear them; fields you leave out keep their current value.

6. Blocks

Every block has a type. These fields work on any block:

Links can be https://, tel:, sms:, mailto:, facetime:, maps:, shortcuts:// (run a Shortcut: shortcuts://run-shortcut?name=…), music://, calshow: or livi://.

text

{ "type": "text", "text": "Rain after 3pm. Take an umbrella.", "style": "body" }

style: title (bold, 17-19 pt), body (14 pt, default) or caption (12 pt, grey). Up to 400 characters, but small shows about 4 lines (60 characters), medium 3 lines (120), large 6 lines.

metric

{ "type": "metric", "label": "Bitcoin", "value": "$67,240", "change": "+2.1%" }

A big number. value is a string you format ("$67.2k", "72°", "4"); keep it to about 8 characters or it shrinks. change is short ("+2.1%"); its sign colours it green or red, or set trend: up, down, flat. Compact: two sit side by side.

list

{ "type": "list", "title": "Top stories", "items": [ { "title": "Fed holds rates", "detail": "Reuters", "url": "https://…" } ] }

Up to 8 rows (strings work as shorthand). Small shows 3 rows, medium 4, large 6. On small and medium each row is one line (about 40 characters on its own, 23 next to numbers); large allows two lines and shows detail on the right. title shows on large only. Each row can open its own url on medium and large.

progress

{ "type": "progress", "label": "Sprint", "value": 0.6 }

A bar with the percentage. value 0-1 or 0-100.

gauge

{ "type": "gauge", "label": "Steps", "value": 7200, "max": 10000, "text": "72%", "style": "ring" }

style: "ring" (default) is a ring with text in the middle (4 characters or fewer reads best; default is the percentage). It's compact, so two rings or a ring and a metric sit side by side. style: "bar" is a capacity bar. min defaults to 0, max to 1 (or 100 when value is over 1). On the circular Lock Screen widget a gauge becomes the system ring.

chart

{ "type": "chart", "label": "BTC 24h", "values": [66.0, 66.4, 67.1, 66.8, 67.5, 68.2, 67.9], "value": "$67.9k", "change": "+2.1%", "style": "line" }

A sparkline: no axes, just the shape. values: 2-48 numbers, oldest first. style: line (default), area (line with a fill) or bar. value and change are optional and show above it. Takes the full width.

countdown

{ "type": "countdown", "label": "Boarding", "to": "2026-10-27T17:00:00Z", "from": "2026-10-27T09:00:00Z", "style": "timer" }

Ticks on the phone with no updates from you. to: an ISO 8601 date. style:

from adds a bar that fills from from to to, drawn live by the phone (a flight, a download, a cooking timer).

clock

{ "type": "clock", "label": "Tokyo", "timezone": "Asia/Tokyo" }

The live time in an IANA zone. Compact. "digit_sum": true also shows the digits added up (1:26 → 1+2+6 = 9).

checklist

{ "type": "checklist", "title": "Today", "items": [ { "title": "Gym", "done": true }, "Buy milk", "Call mom" ] }

The user ticks items right on the widget (and in the app). Livi saves the tick and gives you an event (section 11). Up to 8 items; small shows 3, medium 4, large 6. Titles up to 80 characters; keep them short. When you update the widget, send the current done states (read them with livi_list_widgets) or you'll untick what the user ticked.

buttons

{ "type": "buttons", "items": [
  { "label": "Snooze", "symbol": "moon.fill", "id": "snooze" },
  { "label": "Done", "symbol": "checkmark" },
  { "label": "Call", "symbol": "phone.fill", "url": "tel:+15555550100" }
] }

1-4 buttons in a row. A button without url sends you an event with its id (defaults to the label, slugified) and shows "Sent" for two minutes. A button with url opens it. When the labels don't fit (small fits about two short ones), the buttons show only their symbols, so give every button a symbol. The user can also put any event button in Control Center, on the Lock Screen corners or on the Action button.

image

{ "type": "image", "url": "https://images.example.com/photo.jpg", "caption": "Today's photo", "fit": "fill", "link": "https://example.com" }

A picture from an https URL (PNG, JPEG, HEIC or WebP, under 8 MB). Here url is the picture; use link for what a tap opens. fit: fill (crop to fill, default) or fit (show it whole). It fills the space left over, at least 60 pt tall. The phone downloads it when it refreshes and keeps a small copy; until then a placeholder shows. Use stable URLs: a new URL is a new download.

7. How each size lays out

These are the exact rules the phone uses. Sizes are in points on a current iPhone; 16 pt padding on every side, and a header (symbol, title, subtitle) about 16 pt tall.

Small (158 × 158). One block, the first, with room for about 100 pt of content. With sizes.small you choose the block, or two (two compact blocks sit side by side; otherwise they stack). Best: one metric, gauge, clock, countdown, a short text, a 3-row list, or a photo. This is also what StandBy and CarPlay show, larger and without its background.

Medium (338 × 158). About 100 pt of content height.

Large (338 × 354). Up to 6 blocks, stacked top to bottom with 14 pt between rows; consecutive compact blocks pair up side by side. About 290 pt of content height.

Extra large (iPad and Mac). Up to 8 blocks, stacked like large. Choose them with sizes.extra_large.

Layouts (medium and larger; small always shows its one block):

Too much content is cut off at the bottom edge, never above the header. The preview shows it and check warns which blocks get cut; fix it with fewer blocks, sizes, or grid.

Choosing blocks per size. sizes maps a size to block indexes (0 is the first block):

{ "sizes": { "small": [2], "medium": [0, 2], "large": [0, 1, 2, 3] } }

Sizes you leave out use the defaults. The same indexes apply inside each version, so keep versions' blocks in the same order.

Approximate character budgets (SF Pro):

Where Fits about
Title on small / medium and large 15 / 35 characters
Metric value (small / next to a list on medium) 8 / 9 characters
List or checklist row, medium, full width / next to numbers 40 / 23 characters
Text body, small / medium / large 60 / 120 / 280 characters
Lock Screen inline 20-28 characters
Lock Screen rectangular 2 lines of ~22 characters

8. Lock Screen

Three shapes, all from the same widget:

{ "lock": { "inline": "Flight in 5 hr", "circular": 0, "rectangular": 1, "symbol": "airplane" } }

The Lock Screen draws everything in one tint over the wallpaper, so colours don't show there; shape and text do. Don't put secrets on a Lock Screen widget without private or sensitive.

9. Look

{ "theme": { "style": "glass", "font": "serif" }, "layout": "hero" }

What iOS does to your colours. In the Home Screen's Tinted and Clear appearances, iOS removes your background and draws everything in one tint, with your accent marks (symbol, dots, rings, charts, buttons, checkmarks) brighter than the rest; photos turn grey. In StandBy and CarPlay the background is removed. So never rely on colour alone to say something: a red number should also have a "-" sign.

Redesigning: when the user asks you to redesign or restyle a widget, call livi_list_widgets, keep its content, and change theme, layout, symbol, accent, sizes and how the blocks are grouped. Preview two or three directions, pick the best, then describe the new look in a line.

10. Versions: by time, day or place

Give a widget variants and the phone switches between them on its own, with no agent running and no network needed:

{
  "slug": "my-day", "title": "My day", "symbol": "sun.max.fill",
  "blocks": [{ "type": "text", "text": "Nothing planned." }],
  "variants": [
    { "when": { "from": "05:00", "to": "10:00" }, "title": "Morning news", "relevance": 60,
      "blocks": [{ "type": "list", "items": ["Markets open higher", "Rain after 3pm"] }] },
    { "when": { "from": "10:00", "to": "17:00", "days": ["mon","tue","wed","thu","fri"] }, "title": "Work",
      "blocks": [{ "type": "metric", "label": "Open PRs", "value": "4" }] },
    { "when": { "from": "17:00", "to": "22:00" }, "title": "Shopping list",
      "blocks": [{ "type": "checklist", "items": ["Milk", "Eggs", "Coffee beans"] }] },
    { "when": { "place": "home" }, "title": "At home",
      "blocks": [{ "type": "text", "text": "Bins out tonight.", "style": "title" }] }
  ]
}

11. Taps: buttons, checklists and Control Center

When the user taps an event button or ticks a checklist item, Livi records it. Read them:

// livi_get_events { "wait_seconds": 30 }
{ "events": [
  { "widget": "showcase", "kind": "button", "target": "snooze", "value": { "label": "Snooze" }, "source": "widget", "created_at": "…" },
  { "widget": "showcase", "kind": "check", "target": "Call mom", "value": { "done": true, "block": 2, "item": 2 }, "source": "widget", "created_at": "…" }
] }

Control Center: every button without a url can also be added by the user as a control: open Control Center, tap +, Add a Control, search Livi, choose Agent button, then pick yours. The same control works on the Lock Screen corners and the Action button. Mention it when a button is something they'll press often.

12. Keeping it updated

Widgets change when something changes them. Pick the right tool:

The content Use
Time, countdowns, timers, time-of-day or place versions Blocks and variants. No updates needed.
Public data from a URL (prices, feeds, weather, status pages) livi_schedule: Livi fetches it in the cloud.
Anything that needs your reasoning or private data (summaries, inbox, judgement) Schedule yourself and call livi_set_widget.

Every livi_set_widget reaches the phone within seconds (Livi sends a widget push). iOS limits how often a widget redraws on its own (about 40-70 times a day), so updating more than every 15 minutes rarely helps.

Scheduled jobs. livi_schedule saves a job that Livi runs in the cloud every N minutes, even when you and the user's computer are off:

{
  "name": "btc-price", "widget_slug": "crypto", "every_minutes": 15,
  "fetch": { "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true" },
  "values": { "btc": "bitcoin.usd", "change": "bitcoin.usd_24h_change" },
  "widget": {
    "title": "Bitcoin", "subtitle": "{{now|time}}", "symbol": "bitcoinsign.circle.fill",
    "theme": { "style": "gradient", "colors": ["#1E1B4B", "#4338CA"], "font": "rounded" },
    "blocks": [{ "type": "metric", "label": "BTC", "value": "{{btc|money}}", "change": "{{change|percent}}", "trend": "auto" }]
  }
}

Scheduling yourself: a Claude Code scheduled task or routine (/schedule), or cron running claude -p "Update my Livi news widget with today's top 5 AI headlines" --allowedTools mcp__livi__livi_set_widget,mcp__livi__livi_preview_widget,WebSearch. Tell the user how often it will refresh. Don't promise "live" data you won't update.

Placing it: a new widget appears in the app right away. To put it on the Home Screen, the user touches and holds the Home Screen, taps Edit, then Add Widget, chooses Livi, then Agent widget, picks a size, and chooses yours with Edit Widget. For the Lock Screen: touch and hold the Lock Screen, Customize, Lock Screen, tap the widget area, Livi. Tell them this the first time.

13. Asking the human

Ask before anything risky, irreversible, costly or outward-facing: deploys, sending emails or messages, payments, deleting data, posting publicly.

{ "question": "Deploy v2.3 to production?", "details": "12 files changed, all tests green.", "risk": "high" }

livi_ask waits up to wait_seconds (0 to 50). If it comes back with status: "pending", call livi_get_answer again. Keep waiting while your task is blocked. Never guess or assume an answer, and never proceed on a risky step without one. If the question no longer matters, call livi_cancel_request.

{ "request_id": "…", "status": "answered", "answer": "Approve", "answered_from": "widget" }

status can be pending, answered or cancelled. The user can always answer in their own words ("Other"), so answer may not be one of your options. Read it, and if it changes the plan, follow it.

Questions are for decisions. For something the user does again and again (snooze, mark done, log a glass of water), a widget button or checklist is better.

14. Reading the phone

livi_get_phone_context (HTTP action get_context) returns what the user chose to share:

{ "shared": true, "place": "home", "places": ["home", "work"], "battery": 64, "charging": false,
  "low_power": false, "timezone": "Europe/London", "local_time": "2026-10-27T07:58:00+0000", "city": "London, UK",
  "updated_at": "…" }

15. HTTP API (no MCP)

The same actions over plain HTTP:

curl -s https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/agent-api \
  -H "Authorization: Bearer lv_YOUR_TOKEN" \
  -d '{"action":"set_widget","slug":"hello","title":"Hello","blocks":[{"type":"text","text":"Connected.","style":"title"}]}'

Actions: whoami, list_widgets, set_widget, preview_widget ("raw": true returns the PNG), delete_widget, get_events, ask (returns at once with a request_id), get_answer, list_requests, cancel_request, get_context, schedule, test_job, list_jobs, delete_job.

Parameters are the same as for the tools. Errors come back as {"error": "…"} with a 4xx status.

16. Errors

Error Meaning
401 "token isn't valid" The user disconnected you. Ask for a new code.
403 "paused" The user paused you in the app. Tell them, and wait.
400 with a field name Fix that field. The message says exactly what's wrong, like blocks[2]: chart needs "values", 2 to 48 numbers.
404 No widget with that slug. livi_list_widgets shows them.
409 on a tap The widget changed under the user's tap; nothing to do.
429 Widget or job limit reached. Delete one or reuse a slug.

17. Recipes

Stock with a chart, small shows just the price, Lock Screen ring for the day's move

{ "slug": "aapl", "title": "Apple", "symbol": "apple.logo", "accent": "#30B856",
  "blocks": [
    { "type": "metric", "label": "AAPL", "value": "$291.13", "change": "+1.2%" },
    { "type": "chart", "label": "Today", "values": [287.1, 288.4, 289.9, 289.2, 290.5, 291.1], "style": "area" },
    { "type": "gauge", "label": "Day range", "value": 0.82, "text": "82%" } ],
  "sizes": { "small": [0], "medium": [0, 1] }, "lock": { "circular": 2, "inline": "AAPL $291 +1.2%" } }

Flight: live timer with a progress bar, photo background, high relevance

{ "slug": "trip", "title": "LHR → JFK", "symbol": "airplane", "accent": "#FFD60A", "relevance": 90,
  "theme": { "style": "image", "image": "https://images.unsplash.com/photo-1436491865332-7a61a109cc05?w=800", "dim": 0.45 },
  "layout": "hero",
  "blocks": [
    { "type": "countdown", "label": "Boarding", "to": "2026-10-27T17:00:00Z", "from": "2026-10-27T09:00:00Z", "style": "timer" },
    { "type": "metric", "label": "Gate", "value": "B32" }, { "type": "metric", "label": "Seat", "value": "14A" } ],
  "sizes": { "small": [0], "medium": [0, 1, 2] }, "lock": { "inline": "Boarding 5:00 PM · B32", "rectangular": 0 } }

Habits the user ticks on the widget

{ "slug": "habits", "title": "Today", "symbol": "checkmark.circle.fill", "accent": "#FF9F0A",
  "blocks": [ { "type": "checklist", "items": ["Water 2L", "Walk 30 min", "Read 20 pages", "No phone after 10"] },
              { "type": "gauge", "label": "Done", "value": 0, "max": 4, "text": "0/4" } ],
  "lock": { "circular": 1 } }

Then poll livi_get_events, and on each tick update the gauge (value and text), keeping the done states.

Approve or snooze a reminder from the Home Screen

{ "slug": "reminder", "title": "Pay rent", "symbol": "house.fill", "accent": "#FF453A", "relevance": 80,
  "blocks": [ { "type": "text", "text": "Due tomorrow, $1,850", "style": "title" },
              { "type": "buttons", "items": [ { "label": "Paid", "symbol": "checkmark", "id": "paid" },
                                               { "label": "Tomorrow", "symbol": "moon.fill", "id": "snooze" },
                                               { "label": "Bank", "symbol": "building.columns", "url": "https://bank.example.com" } ] } ],
  "sizes": { "small": [0, 1] } }

Photo of the day

{ "slug": "photo", "title": "Photo of the day", "symbol": "photo",
  "blocks": [ { "type": "image", "url": "https://images.example.com/today.jpg", "caption": "Lofoten, Norway", "link": "https://example.com/today" } ] }

Good habits