Offsider

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:

<script src="offsider-panel://host/offsider.js"></script>
<script>
  offsider.ready().then(({ context, input }) => {
    render(input)
  })
  offsider.on("result", (data) => render(data))
</script>
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.ready()
const width = Number(offsider.storage.getItem("sidebarWidth")) || 210
offsider.storage.setItem("sidebarWidth", 240)
offsider.on("storage", ({ action, key, oldValue, newValue, local }) => {
  // action: "added", "updated", "removed" or "cleared" (key null).
  // local: true when this page made the change, false when another panel from the folder did.
})

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:

{
  "theme": "dark",
  "colors": { "background": "rgba(40, 40, 40, 1.000)", "accent": "rgba(0, 122, 255, 1.000)" },
  "locale": "en-AU",
  "timeZone": "Australia/Melbourne",
  "platform": "desktop",
  "displayMode": "inline",
  "containerDimensions": { "width": 400, "height": 700 },
  "safeAreaInsets": { "top": 0, "right": 0, "bottom": 0, "left": 0 },
  "panel": { "name": "review", "tools": ["prs"] }
}

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:

body {
  background: var(--offsider-color-background, Canvas);
  color: var(--offsider-color-text, CanvasText);
}
.selected {
  background: var(--offsider-color-selection, Highlight);
  color: var(--offsider-color-selection-text, HighlightText);
}

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.