5of 9
The JavaScript API
A panel talks to Offsider with offsider.js, a small client Offsider serves to every panel.
offsider.js
Load the client, then wait for it to be ready:
| Call | What it does |
|---|---|
offsider.ready() |
Resolves with { context, input } once Offsider has said hello. input is --input. |
offsider.on("result", fn) |
Called with the data from each offsider send. |
offsider.run(name, args) |
Starts a declared command and returns a job with events and cancel(). |
offsider.model(prompt) |
Generates text with Apple’s on-device model when foundationModels is approved and available. |
offsider.systemNotification(title, body) |
Posts a macOS notification when notifications is approved and macOS allows it. |
offsider.storage |
localStorage for the panel, kept between openings. See Storage. |
offsider.on("storage", fn) |
Called with { action, key, oldValue, newValue, local } whenever the storage changes. |
offsider.on("context", fn) |
Called with the whole context whenever the appearance or system colors change. |
offsider.on("teardown", fn) |
Called just before the window closes. |
offsider.emit(name, data, note) |
Prints an event line. note is a sentence for people, such as “Ticked: Log in works”. |
offsider.message(text) |
Prints a message line. |
offsider.modelContext(text, data) |
Prints a context line: what the app is showing, for Claude to know about. |
offsider.call(name, args) |
Runs a tool from panel.json, and resolves with what it printed. |
offsider.openLink(url) |
Opens an http, https or mailto link in the browser. |
offsider.close() |
Closes the panel. |
offsider.log(level, ...data) |
Prints a log line. console.error, console.warn and console.info already do. |
Storage
Web storage doesn’t last in a panel: each window’s WebKit data is thrown away when it closes, and
the sandboxed page can’t use localStorage at all. offsider.storage has the same interface
instead: getItem, setItem, removeItem, clear, key(index), length, and keys as
properties. Offsider keeps it per panel folder in its Application Support folder, so it lasts
between openings and is shared by every open panel from that folder.
await offsider.
const width = || 210
offsider..
offsider.
Values are strings, like localStorage; store JSON with JSON.stringify. Reads and writes are
synchronous once ready() has resolved, and Offsider saves writes in order. Unlike the browser’s
storage event, "storage" also fires for the page’s own changes. Writing past the 5 MB quota
throws a QuotaExceededError.
The context
offsider.context looks like this:
}
System colors
offsider.js sets these semantic CSS variables on the panel page. Offsider reads the values from
the host operating system and refreshes them when its appearance or color settings change. Panels
can use the same names on other platforms without referring to AppKit.
| CSS variable | Intended use |
|---|---|
--offsider-color-background |
Main window background |
--offsider-color-surface |
A control or inset surface |
--offsider-color-text |
Primary text |
--offsider-color-text-muted |
Secondary text |
--offsider-color-border |
Separators and borders |
--offsider-color-accent |
User’s accent color |
--offsider-color-accent-text |
Text over the accent color |
--offsider-color-selection |
Selected item background |
--offsider-color-selection-text |
Selected item text |
The same values are available in offsider.context.colors without the CSS prefix. Use browser
fallbacks when previewing a page outside Offsider:
:;
:;
}
:;
:;
}
The color variables are solid or translucent colors. The vibrant window material remains a
separate native background effect.
It’s an MCP App
Underneath, a panel talks to Offsider the way an
MCP App talks to its host: JSON-RPC 2.0
over postMessage. offsider.js is a thin layer over it, and an app built with the MCP Apps SDK
can use the supported methods below. This is a local MCP Apps-style UI host, not an MCP server or a claim of full SDK compatibility.
| Method | Direction | Notes |
|---|---|---|
ui/initialize |
app → Offsider | Returns protocolVersion, hostInfo, hostCapabilities and hostContext. |
ui/notifications/initialized |
app → Offsider | Offsider then sends tool-input with the --input. |
ui/notifications/tool-input |
Offsider → app | { arguments } |
ui/notifications/tool-result |
Offsider → app | Each panel send, as { content, structuredContent }. |
ui/notifications/host-context-changed |
Offsider → app | Only the fields that changed. |
ui/resource-teardown |
Offsider → app | The window is closing. |
tools/call |
app → Offsider |
{ name, arguments }, answered with a CallToolResult. |
ui/open-link |
app → Offsider | { url } |
ui/message |
app → Offsider | { role, content } |
ui/update-model-context |
app → Offsider | { content, structuredContent } |
ui/request-display-mode |
app → Offsider | Always inline. |
notifications/message |
app → Offsider | Logging: { level, data }
|
offsider/emit, offsider/close
|
app → Offsider | Offsider’s own additions. |