--- name: livi description: Put live widgets on the user's iPhone Home Screen, Lock Screen, StandBy and iPad (text, numbers, lists, charts, gauges, photos, timers, checklists, buttons they tap), with versions that switch by time or place; ask them questions they answer with one tap; read context from their phone; and preview every widget as an image before they see it. Works through Livi's MCP server or HTTP API. Use when the user wants something on their phone's Home Screen or Lock Screen, wants to be asked before you do something, mentions Livi, gives you a Livi pairing code, or sends you livi-widgets.vercel.app/start. metadata: version: "2026-10-27.5" --- # Livi > **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" ``` 2. 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. 3. 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. Then add the MCP server. **Claude Code** ```bash 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)** ```json { "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 update a widget by `slug`. `blocks` and `variants` are replaced every time. Other fields you leave out (`theme`, `layout`, `sizes`, `lock`, `accent`, `symbol`, `subtitle`, `url`, `relevance`, `private`) keep their old values: send `null` to clear one, or `"layout":"auto"` to reset the layout. 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 ```json { "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: - `url`: open this when the block is tapped (medium and larger; on small, the whole widget opens the widget `url`). - `color`: `#RRGGBB` for the block's main mark (the value, ring, bar, line or buttons) instead of the accent. - `sensitive`: `true` hides this block while the phone is locked. Links can be `https://`, `tel:`, `sms:`, `mailto:`, `facetime:`, `maps:`, `shortcuts://` (run a Shortcut: `shortcuts://run-shortcut?name=…`), `music://`, `calshow:` or `livi://`. ### text ```json { "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 ```json { "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 ```json { "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 ```json { "type": "progress", "label": "Sprint", "value": 0.6 } ``` A bar with the percentage. `value` 0-1 or 0-100. ### gauge ```json { "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 ```json { "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 ```json { "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`: - `relative` (default): "2 hr, 5 min", then "Now" once it passes. - `timer`: "2:05:09", counting every second; once the date passes it counts **up** (use it as a stopwatch: "time since"). - `date`: "October 27, 2026". `time`: "5:00 PM". `from` adds a bar that fills from `from` to `to`, drawn live by the phone (a flight, a download, a cooking timer). ### clock ```json { "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 ```json { "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 ```json { "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 ```json { "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. - `auto`: up to two compact blocks (metric, clock, ring gauge) in a 112 pt column on the left, and the first other block on the right. Only compact blocks: side by side. Only other blocks: the first one, full width. - With `sizes.medium`: your blocks, stacked (compact ones in pairs), up to 4. **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): - `auto`: the rules above. - `hero`: the first block drawn bigger, then up to 2 (medium) or 3 (large) more in one row under it. - `grid`: blocks in a two-column grid: 2 on medium, 6 on large. - `list`: one block per row: 2 on medium, 6 on large. **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): ```json { "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: - **Rectangular** (170 × 72): the title, then one line from a block. A gauge shows a bar; a timer countdown shows the live timer (and its bar if it has `from`). Pick the block with `lock.rectangular` (default: the first). - **Circular** (72 × 72): a ring for a gauge or progress, a short value for a metric, the time for a clock, a live timer for a countdown, or short text. Pick the block with `lock.circular` (default: the first that suits). - **Inline** (one line above the clock): `lock.inline`, up to 40 characters (20-28 fit), with `lock.symbol` in front. Default: a line from the first block. ```json { "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 ```json { "theme": { "style": "glass", "font": "serif" }, "layout": "hero" } ``` - `theme.style`: - `system` (default) and `glass`: Livi's frosted glass. Pearl in light mode, smoky glass in dark mode, and real see-through Liquid Glass when the user's Home Screen appearance is **Clear** or **Tinted** (touch and hold the Home Screen, Edit, Customize, Clear). iOS only allows see-through widgets in those two appearances; in Default and Dark every widget is opaque. If the user asks for "transparent" or "liquid glass", use this and tell them about Clear. - `light`, `dark`: plain white or near-black, always. - `solid`: one colour, `"color": "#1E3A8A"`. - `gradient`: two `colors`, top-left to bottom-right. - `image`: a photo background, `"image": "https://…"`, darkened by `dim` (0-0.9, default 0.35) so text reads. Text turns white. - `theme.font`: `default` (SF Pro), `rounded` (SF Rounded), `serif` (New York) or `mono` (SF Mono). Numbers use SF Rounded unless you pick a font. - `theme.text`: `auto`, `light` or `dark`. Auto picks readable text for the background. **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: ```json { "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" }] } ] } ``` - `when` combines any of: - `from` and `to`: the phone's local 24-hour time. The window can wrap past midnight. - `days`: `mon` to `sun`. - `place`: `home`, `work`, `away` (not at any saved place), or any other place the user saved in Livi. The `check` warns if they haven't saved it. - The **first matching variant wins**, so put the most specific first. If none match, the widget's own `blocks` show. - Up to 6 variants. Each has its own `blocks`, and optionally `title`, `subtitle` and `relevance`. - Preview each version: `livi_preview_widget` with `"at": "07:30"` or `"place": "home"`. ## 11. Taps: buttons, checklists and Control Center When the user taps an event button or ticks a checklist item, Livi records it. Read them: ```json // 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": "…" } ] } ``` - Each event comes back **once**. Pass `peek: true` to look without marking them read, `slug` for one widget, and `wait_seconds` (up to 50) to wait for the next tap. - `source` is `widget`, `app` or `control` (Control Center, Lock Screen corner or Action button). - Checklist ticks are already saved on the widget; you don't need to update it. Button presses do nothing else until you act, so act: for example, on "snooze", move the reminder and update the widget. - Taps only reach you when you look. For buttons that need a quick response, run a loop that calls `livi_get_events` with `wait_seconds`, or check on your schedule. Tell the user how quickly you'll respond. - Buttons and checklists don't work while the phone is locked (iOS rule); they work on the Home Screen, in StandBy when unlocked, and in the app. **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: ```json { "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" }] } } ``` - **`fetch.url`:** any https URL that returns JSON, RSS/Atom or text (1 MB max). No secrets in URLs: they're stored. - **`values`:** names mapped to JSON paths (`"data.0.price"`), or a list such as `{"stories": {"path": "articles", "title": "title", "detail": "source.name", "url": "url"}}`. RSS feeds automatically give `items` (title and link) and `title`. - **`widget`:** the template: any widget field, blocks included. Strings can use `{{name}}`, `{{name|money}}`, `{{name|money:EUR}}`, `{{name|number}}`, `{{name|round:2}}`, `{{name|percent}}`, `{{name|upper}}` and `{{now|time}}`. Use `"items": "{{stories}}"` to fill a list from a list value. - **Timing:** `every_minutes` is 5 to 1440; 15 or more is kinder. - **First run:** the job runs once immediately and returns `first_run` (the values and the widget) and `check`. A broken URL or path fails right away with the reason. `livi_test_job` tries a job without saving. - Reuse the same `name` to change a job; `livi_delete_job` stops it. `livi_list_jobs` shows `last_status` and `last_error`. The user can pause or delete jobs in the app. **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. ```json { "question": "Deploy v2.3 to production?", "details": "12 files changed, all tests green.", "risk": "high" } ``` - `kind`: - `decision` (default): two buttons. `options` defaults to `["Approve", "Reject"]`, or pass your own two. - `choice`: 2 to 4 `options`. - `input`: a free-text reply, with an optional one-tap `default_answer`. - `risk`: `low`, `medium` (default) or `high`. High risk shows in red. - The phone gets a notification, and the **Needs you** widget shows the question with buttons. `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`. ```json { "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: ```json { "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": "…" } ``` - `place` is a saved place's name, `away`, or `unknown`. Livi never shares exact coordinates. - Fields are missing when the user turned that kind of sharing off. `shared: false` means sharing is off; suggest Livi, Settings, Your phone. - The phone updates this when it arrives at or leaves a place and when the app opens, so check `updated_at`. - Use it to decide **what** to show and **when** to ask: skip a "leaving soon?" question if they're home, hold a non-urgent question while the battery is low, use `places` for place versions. ## 15. HTTP API (no MCP) The same actions over plain HTTP: ```bash 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** ```json { "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** ```json { "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** ```json { "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** ```json { "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** ```json { "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 - Preview before you tell the user it's done (section 4). Fix every warning. - Reuse slugs. One widget per topic, updated in place, beats ten stale ones. - Put the most important block first; small shows only that unless you choose with `sizes`. - Numbers as short preformatted strings: `"$67.2k"`, not `"67241.3378"`. - Put a time in `subtitle` ("9:00 AM") when freshness matters. - Don't rely on colour alone: tinted and clear Home Screens remove it. - Give every button a symbol; on small only the symbols may fit. - One question at a time, in one short line, with the detail in `details`. Say what you'll do with each answer before you ask.