1. Docs
  2. Plugin API reference

Plugin API

This reference is generated from the canonical English repository document. Navigation and guides follow your selected language.

Read Markdown for agents · Repository source

Token Monitor plugins run code you choose to trust (tmApi: 1). The App does not sandbox a plugin's access to your files, credentials or network. An integrity hash identifies bytes, not a security review.

Choose a guide for your next step:

This is the plugin ctx API. docs/API.md documents the separate Hub HTTP API, not plugin context methods.

Execution and trust

Author UI is an ES module loaded into the host renderer document. It can use DOM/CSS/JS, existing host elements and exposed renderer services. It does not receive Node require: Electron retains context isolation, disabled Node integration and its normal CSP. For filesystem, network, subprocesses or computation, declare an optional Node backend. It runs in an Electron utility process under the same OS user, with ordinary Node authority. It is not a credential-isolating sandbox. Formal data projections remain useful contracts, not a security boundary against trusted backend code reading accessible internal files.

Module-private variables and another process's memory are not automatically available. Direct access to internal files, database schemas or exposed services is unsupported coupling that authors must maintain. Neither the host nor a hash guarantees plugin safety. Same-page JS can freeze the renderer; the host cannot independently terminate it while preserving that renderer. Recovery reloads the renderer or restarts without plugins.

Manifest

json
{
  "tmApi": 1,
  "id": "my-panel",
  "name": "My panel",
  "version": "1.0.0",
  "ui": "ui.mjs",
  "backend": "backend.cjs",
  "css": ["panel.css"],
  "files": ["ui.mjs", "backend.cjs", "panel.css"],
  "views": [
    {"id": "panel", "title": "My panel", "surfaces": ["home", "page", "notch", "edgeDock", "settings"], "defaultSurfaces": ["page", "notch"]},
    {"id": "layout", "title": "My layout", "targets": ["main.limits.content"], "mode": "replace"}
  ],
  "background": true,
  "defaults": {
    "content": {"main.limits.content": "layout"},
    "background": false,
    "skin": null
  }
}

ui, backend, css, views, trayMenu, settings, skins, background and defaults are optional; at least UI, backend or skins is required. UI views require ui. List every distributed file explicitly. Paths are package-relative, portable and cannot traverse parents or symlinks. Dependencies must be bundled beforehand; installation never runs npm or lifecycle scripts. Native addons require compatible OS/architecture/Electron ABI, and utility-process loading must be tested in a packaged build; arbitrary addons are not universally supported.

name, description and view titles support localized strings. Optional author, HTTPS homepage and validated identityIcon provide attribution, not trust. The App uses identityIcon in main-page navigation and Notch tabs (and their settings choices) before loading the plugin UI. Without artwork, navigation keeps its built-in fallback; image decode failures also fall back. Do not rely on UI CSS loading to supply navigation artwork. engines.tokenMonitor can constrain the App version. tmApi identifies the plugin API contract, not the plugin release or package format. Only tmApi: 1 is accepted; other values fail with incompatible-plugin-api and are not converted. This experimental API can break; authors must follow release changes.

Plugin labels

The App displays labels beside the version to describe the plugin's interfaces and purpose:

Label Manifest declaration
Widgets A view supports home, tray, edgeDock or notch
Pages A view supports page or declares content targets
Appearance At least one skin is declared in skins
Automation category: "automation" for monitoring, reminders or scheduled tasks

A plugin can have more than one label. category is optional. See catalog metadata for discovery type hints.

First-use settings

Authors suggest first-use settings; the user reviews them before enabling. These choices remain independent after setup:

Declaration Initial suggestion when omitted Allowed values
View defaultSurfaces No placements Duplicate-free subset of that view's surfaces, excluding settings
defaults.content The plugin's replacement for each target Map of a declared replacement target to its view ID, or null for native content
defaults.background false Boolean; true requires a backend and background: true
defaults.skin Keep current appearance Declared skin ID, or null to keep current appearance

Install-and-enable applies the confirmed choices together. Install-only stores them for review on first enable, including across restarts. If another enabled, trusted plugin is the selected source, the App asks before switching; the user can keep its content and still enable the new plugin.

Updates and enable/disable cycles preserve confirmed choices, including no placements, native content, background off and unchanged appearance. Newly declared locations, targets or capabilities receive their own first-use review; existing choices stay intact. Changing author defaults does not reset user decisions. Locations that are full are skipped without evicting existing items. Pending suggestions for removed views or surfaces are ignored. Defaults do not enable the host's Notch, Edge Dock or Tray master switches. The tray suggestion places a bitmap in the system Tray; Floating Bubble and Notch-summary custom layouts use their manual composers.

Uninstall with kept placements retains placement and content choices; removing placements resets those choices for a fresh install. Reinstall reviews background execution and appearance again. An already applied appearance remains until the user changes it. See content replacement behavior for independent content selection.

Bitmap views declaring tray can be added from the existing Tray, Floating Bubble and custom Notch-summary composers after the plugin is enabled. They use the same reorder, duplicate, remove and save controls as native items. Notch “Same as tray” follows the active Tray layout; its custom layout stays independent. Uninstall with placement removal also clears bubble/summary entries and mixed Notch page-order entries. Keeping placements retains them for a later reinstall and explicit enable.

UI lifecycle

Export activate(ctx) returning {mount, renderTray?, dispose?}, or export mount directly. mount(container, context) may be asynchronous and returns {update?, dispose?, hasUnsavedChanges?}. The host retains mounted controls during data updates and ordinary page switching where that surface retains its DOM. Do not assume every native surface retains nodes: dispose timers/listeners and persist important drafts. The same module activates once per enabled lifetime per renderer, not once globally across windows. After disable it is disposed; re-enabling calls activate again. Native JavaScript import caching can retain the module namespace, so do not rely on top-level statements rerunning. Put reusable initialization in activate.

context includes viewId, ref, surface, container, current backend data, and surface-specific values. Updates can include visible, width/height, selected period/source, native data and native content nodes. surfaceVariant identifies the specific placement and may differ between mounts. Do not start a new sampler for every mount. ctx.onDispose(fn) registers module cleanup; instance disposal owns mount-specific cleanup. CSS is loaded once per renderer/revision. Author CSS is free to choose its own look; host theme tokens are conveniences.

The host waits for each mounted instance's update() promise before invoking its next update. Updates received while one is running are coalesced to the latest context; do not depend on receiving every intermediate snapshot. Return asynchronous rendering work from update() so the host can order it and drain it before disposal. When replacing an instance in the same container, the host waits for its returned asynchronous mount, update and disposal work before starting the replacement. Ordinary replacement has no automatic timeout. Returned mount, update and disposal promises must settle; use cancellation or timeouts for network and I/O work. Independently scheduled work still needs author-owned cancellation or generation checks. Each update(context) receives the current host state.

Context services (see data/settings parameters, results and examples):

  • ctx.backend.call(method, args) returns the backend handler result or rejection.
  • ctx.backend.subscribe(callback) observes published backend values and returns an unsubscribe function; the latest known value is replayed.
  • ctx.data.query(query) reads a captured App snapshot.
  • await ctx.data.subscribe(query, callback) returns an unsubscribe function. The initial callback is a snapshot; later invalidations indicate when to query again.
  • ctx.host.call(method, args) invokes a formal host operation.
  • ctx.asset(path) resolves a package-local asset relative to the UI entry.
  • Host-specific ctx.app, ctx.native and ctx.navigate expose existing renderer services where available. They differ between main and compact windows; feature-detect them.

See content surfaces and native composition.

Local backend

js
exports.activate = async ({ dataDirectory, publish, host, log }) => {
  return {
    async call(method, value) {
      if (method === 'echo') return value;
      throw new Error('Unknown operation');
    },
    update(context) { /* selected host source, visible/background state */ },
    dispose() { /* stop timers, close connections, flush owned state */ }
  };
};

The backend is optional and starts on visible demand, an explicit pending call, or separately opted-in background execution. All locations of the same plugin share one process. The final demand disappearing stops it; teardown receives a cooperative grace period before termination. Startup and calls have finite deadlines. Failures remain visible until explicit restart; there is no automatic crash loop. A hidden UI can still have active JS because it shares the renderer: authors must honor visibility and dispose their own work. The host cannot promise that arbitrary trusted code stops all work merely because its mount is hidden.

With a backend and background: true, optional backgroundDescription explains what the plugin does in the background, such as periodic data updates. It accepts the same localized string format as description. Plugin details show this author-supplied purpose. Without author text, the App shows a short generic explanation. This text does not change execution behavior or grant trust.

dataDirectory is an App-managed plugin directory. Store private plugin preferences/files there; it is not a filesystem jail. publish(value) broadcasts structured data to UI instances. host.call uses the same formal host operations as UI. Backend dependencies and native modules are the author's responsibility. No host system-monitoring sampler is provided; plugins implement and share their own sampling.

Host data and settings

Formal host methods are data.query, settings.get, settings.update, app.context, app.settings, app.updateSettings and notify. Host integrations may expose additional explicit operations. app.settings uses the existing public renderer projection; it does not intentionally return stored secrets. Plugin management is separate from generic settings updates.

notify accepts {title, body} and sends a native system notification. It resolves to true after Electron reports show, and rejects on unsupported platforms, native failure, or no display confirmation within five seconds. A show event does not prove the user read a banner; OS permissions, Focus settings and platform installation/signing requirements still apply. Catch errors and show useful feedback in your UI.

Business queries use {resource, period, filter?, sort?, page?}. Resources include usage, models, tools, devices, projects, sessions, history, limits, subscriptions and status. Queries reuse captured selected-source data and existing history resolution, never initiate provider probes or duplicate collectors. Results carry availability, rows, snapshot revision, freshness, coverage and cursor metadata. Unknown cost is not zero. Missing lifetime/remote session detail remains unavailable or partial.

Subscriptions deliver {type:'snapshot', value, sequence} then coalesced invalidations with refetch and reset. Source/privacy changes require discarding the old traversal. Ordinary updates preserve admitted immutable cursors until expiration or eviction. Use the returned cursor with the same query; restart on snapshotExpired/invalidCursor. Query work and retained memory remain bounded to protect the App's data service; these bounds do not restrict a plugin's own Node work. Large sessions use yielding indexed pagination.

manifest.settings declares the versioned field schema and host form. settings.get returns values, schema and revision; settings.update submits {schemaVersion, expectedRevision, patch} and reports conflicts/field errors. The native form works while code is disabled. A custom settings view executes trusted UI and requires enablement. This plain JSON preference store is not a dedicated secret vault. A plugin may implement its own credential handling through its trusted backend; the platform does not advertise encryption it does not provide.

Installation and recovery

Installation, revision trust, atomic updates, rollback and catalogs are independent of execution freedom. Official packages use Install and enable / Update; other sources use Trust, install and enable / Trust and update. Both approve the exact revision in one explicit action. Install only leaves code disabled and untrusted. The master switch and safe mode are respected; background execution follows the confirmed first-use setting. Reload discovers changed package bytes and requires approval before running them. Unchanged trusted code re-enables directly. Normal disable awaits mount/module cleanup and stops its backend without reloading. Mounts reporting hasUnsavedChanges() prompt before disable; cleanup failure or an unresponsive host offers recovery reload. Package updates and removal also cooperatively release affected UI; unsaved mounts prompt first. The package review offers one explicit trust-and-update action for the exact new revision, preserving the enabled/background choices. Failed cleanup or an unresponsive host may still require affected-window recovery. Rollback restores code, not arbitrary migrations a trusted backend performed on its data.

The native tray menu offers Restart without plugins. Alternatively launch with --plugins-safe-mode or TOKEN_MONITOR_PLUGINS_SAFE_MODE=1. Safe mode lists packages and permits management without starting author UI/backends. This recovery route does not depend on a responsive renderer. It cannot undo arbitrary filesystem changes made by trusted code.

No active plugins means no plugin backend, dedicated polling/sampler, or repeated business projection. Baseline imports/registration have a finite cost; byte-identical RAM or zero CPU overhead is not promised. See validation.