Mini-app API
A package contains a JSON manifest and self-contained HTML. Local reads those data files; it never imports package Python or Node code into its server. The browser hosts the app in a frame with scripts enabled, an opaque origin and no direct network capability.
Manifest
{
"schemaVersion": 1, "id": "tally", "name": "Tally",
"version": "0.1.0", "runtime": "local", "entry": "index.html",
"permissions": ["storage.read", "storage.write", "watcher.display"]
}Use a unique ID of 1–15 lowercase letters, digits or hyphens, starting with a letter. Core and existing remote app IDs are reserved. Names contain 1–40 characters; versions use three numeric parts such as 0.1.0. Local accepts these seven manifest fields only, with no duplicate permissions.
Only Local is supported. Its package contains a UTF-8 manifest.json of at most 16 KiB and a self-contained index.html of at most 512 KiB; symlinked and hard-linked files are rejected. The installer inlines the SDK and app scripts, so external script tags will not work. This package format is separate from the Hub metadata draft.
Connect and store data
const sdk = await FrontierOS.connect();
const count = (await sdk.storage.get("count")) ?? 0;
await sdk.storage.set("label", "Finished tasks");
const updated = await sdk.storage.increment("count", 1);| Method | Capability | Result |
|---|---|---|
storage.get(key) | storage.read | Stored JSON value, or null. |
storage.set(key, value) | storage.write | Persist a bounded JSON value in this app’s namespace. |
storage.increment(key, delta) | storage.write | Atomically update and return an integer counter. |
Storage persists in ~/.gotchi/mini-app-data/<app-id>.json on the Local host and is isolated by app ID. Keys contain 1–64 letters, digits, underscores, dots or hyphens. Each app can store at most 128 keys within 64 KiB of JSON, with values at most 16 levels deep and finite numbers only. This capability does not expose arbitrary host files.
Integer counters stay between −1,000,000,000 and 1,000,000,000; each increment accepts a delta between −1,000,000 and 1,000,000. Tally intentionally restricts its skill to additions of 1–100.
Each bridge message must fit 16,000 characters, and Local’s HTTP request body is limited to 16 KiB. The method, key and other envelope fields share that space with your value, so a single write must be smaller than the app’s total storage limit.
Draw a Watcher screen
const result = await sdk.watcher.screen({
app: "tally", screen: "main", bg: "#F5F0E3",
back: "device", focus: [], focus_i: 0,
nodes: [FrontierOS.text("hello", 56, 180, 300, "Hello", {
font: 28, color: "#2C291F", align: "c"
})]
});Declare watcher.display. Coordinates use a 412×412 square; its visible circle is centered at (206, 206). Keep important content inside the circle. FrontierOS.text and FrontierOS.rect produce scene nodes. Local validates scenes before using the existing acknowledged remote UI transport.
A scene contains 1–96 text or rectangle nodes with unique IDs of 1–15 lowercase letters, digits or hyphens. Text is limited to 64 ASCII characters plus ° and •. Supported font sizes are 14, 16, 22, 24, 28, 32, 36 and 48; colors use #RRGGBB. Keep back: "device" so native navigation remains available.
Check result.displayed: retaining a scene locally does not mean the Watcher is showing it. Scenes cannot request raw device commands, wallet signatures, PIN entry or firmware updates.
Handle errors and close cleanly
Bridge methods reject with an Error when disconnected, denied, malformed or timed out. Opening the HTML directly does not provide a bridge; open it from Local’s Mini apps window. Timeouts do not retry mutations automatically. Show errors and let the user decide whether to retry.
Stop input subscriptions and call sdk.close() when your app closes. There is no general network, clipboard, microphone, wallet or filesystem permission; an unsupported manifest string grants nothing.
Keep the Local Mini apps window open: it maintains the host session and receives device inputs. Only one active host session is allowed per app. If Local restarts or a suspended browser stops renewing its 30-second session, reopen the app to reconnect.