notch.monster
DownloadGet it

SDK reference

import { notch, NotchError } from "@notch-monster/plugin-sdk";
import "@notch-monster/plugin-sdk/ui.css"; // optional base look from the theme tokens

Lifecycle

notch.ready(): Promise<Context>

Connects to the host, applies the theme to :root, and resolves the context. It’s safe to call more than once. Call it before anything else, except notch.provide (see Services).

interface Context {
  apiVersion: number;
  pluginId: string;
  widgetId: string;          // this widget instance
  view: "tile" | "expanded" | "service";
  size: { cols: number; rows: number; px: { w: number; h: number } };
  theme: Record<string, string>;   // --notch-* → value
  locale: string;                  // BCP 47, e.g. "en-IN"
  permissions: Partial<Record<Permission, "granted" | "denied" | "ask">>;
}

notch.on(event, listener): () => void

Returns a function that unsubscribes.

Event Data
resize { cols, rows, px: { w, h } }
theme new --notch-* values (the SDK applies them for you)
visibility false while scrolled away, in Edit layout, or while the notch is collapsed (a tile kept running by background or audio)
permissions the user changed an answer
media, timer, alarms prefer notch.media.onChange / notch.timer.onChange / notch.alarms.onChange
service { service, event, data } from a service you use

Storage

await notch.storage.widget.set("city", "Pune");     // this widget; deleted with it
const city = await notch.storage.widget.get<string>("city"); // null if missing
await notch.storage.plugin.set("cache", { t: Date.now() }); // shared by all your widgets
await notch.storage.plugin.delete("cache");          // → whether it existed
await notch.storage.plugin.keys();

Values are JSON, at most 64 KB each and 5 MB per plugin. Service frames have no widget, so use storage.plugin there.

Pill

The collapsed notch can show a short status from your plugin (needs widget.pill).

await notch.pill.set({
  text: "31° Sunny",          // ≤ 24 characters
  icon: "sun",                // from PILL_ICONS
  progress: 0.4,              // optional static ring, 0–1
  expiresInMs: 3_600_000,     // optional, ≤ 7 days
});
await notch.pill.clear();

Changes go out at most once a second, and the latest one wins. Plugin claims rank below the timer and media, and the newest plugin claim wins.

PILL_ICONS: alert, bell, book, calendar, check, clock, cloud, cloud-rain, code, coffee, flame, heart, info, mail, message, moon, music, quote, snowflake, star, sun, trending-down, trending-up, zap.

Stats

Count things for the panel’s Stats view. Declare each metric in the manifest’s stats, then record values as they happen:

await notch.stats.record("pages_read", 3); // value defaults to 1

The app totals them per day (sum or max) and draws one card per metric: today, this week, a 7-day chart and a streak. Recording is write-only (you can’t read stats back), needs no permission beyond declaring the metric, and is limited to 120 records a minute. Values are numbers from 0 to 1,000,000. Removing the plugin deletes its stats.

await notch.openExpanded(); // tile → full panel (needs widget.expanded)
await notch.close();        // expanded → board
await notch.openUrl("https://example.com"); // https only, in the user's browser

Errors

Failed calls reject with NotchError. Its kind is one of invalidInput, invalidState, notFound, permissionDenied, rateLimited, storage, internal, unknownMethod or timeout.

try {
  await notch.location.get();
} catch (e) {
  if (e instanceof NotchError && e.kind === "permissionDenied") showCityPicker();
  else throw e;
}

Calls that can wait on the user (a prompt or a picker) time out after 5 minutes. Others time out after 10 seconds.

Also in the SDK