Skip to content

OpenTray · desktop status runtime

A desktop home for your CLI and AI skills — without the weight of an app framework.

CLI tools and AI skills do long, quiet work — builds, agents, watchers — and finish in a terminal nobody keeps open. OpenTray is a desktop status runtime for Node, Deno, and Bun: one createTray() call starts the local broker automatically and puts a resident status atom in the tray — Rust core, public tray/session protocol, no Node addon, no app framework.

Node · Deno · BunRust core · no addonone call: createTray()MIT
GitHub ↗
opentray — first-app

$node first-app.mjs

broker: started automatically — Rust core

tray: com.example.first-app · icon "OT" resident

menu: Quit (primaryEvent)

session: live — events streaming

Quick start

One install, one createTray()

The packaged runtime starts the local broker on first use — visible tray text is icon projection, runtime identity (appId/appName) is separate, and primaryEvent is a role on a normal menu item that emits the usual menuClick.

first-app.mjs
import {
  createTray,
  type CreateTrayHandle,
  type CreateTrayOptions,
  type TrayIcon,
} from "opentray";

const icon: TrayIcon = { "text-only": "OT" };
let tray: CreateTrayHandle;
const options: CreateTrayOptions = {
  id: "com.example.first-app",
  icon,
  menu: {
    items: [
      {
        title: "Quit",
        primaryEvent: true,
        onMenuClick: () => void tray.destroy(),
      },
    ],
  },
};

tray = await createTray(options, {
  appId: "com.example.first-app",
  appName: "First App",
});

Already have a command that serves HTTP locally? npx create-opentray wraps it into an OpenTray-hosted app.

What’s inside

Why it exists

Not a tray icon — an entry point

The goal was never "show a tray icon": lightweight tools deserve a system-level entry point without being forced into Electron or a full desktop app framework. The model stays four atoms: App is the caller-owned runtime identity and isolation boundary, Tray is one desktop status atom, Session is the live source of authority for tray events and mutations, Extension is the optional native capability atom.

App · Tray · Session · Extension

SDK

One call owns its lifetime

You should not bootstrap an app shell to show status. Application code calls createTray() directly and owns foreground/background lifetime itself. Top-level createTray(...) and its returned setMenu(...) accept app-facing menu shorthand; lower-level createClient(...) stays protocol-only for tools that need exact wire shapes.

opentray · menu shorthand · typed handles

Rust core

A broker, not an addon

Why trust a tray that arrives over npm? The native side is a Rust core delivered as a packaged executable: the default createTray() transport targets the local runtime host and starts it on first use when needed, and ordinary app code talks to it through the public tray/session protocol. No Node addon, no worker split for tray work.

tray/session protocol · auto-started Rust host

Onboarding

Bring your own command

You should not rewrite a working tool to give it a tray. Already have a command that serves HTTP locally? npx create-opentray wraps it into an OpenTray-hosted app — interactively through the browser wizard (create-opentray web), fully non-interactively (create-opentray create --app-id … --app-name … --exec …), or by handing the built-in AI skill to your agent (npx create-opentray skill).

npx create-opentray · web · create · skill

Extension family

Native capability, opt-in atoms

When a status atom needs more than a menu, extensions add native capability without changing the model: @opentray/ext-webview is the rich popup facade — its windows are tray-owned utilities by default, and style.appMode: true makes one behave as an ordinary desktop window (taskbar/Alt-Tab, Dock/Command-Tab). @opentray/ext-badge adds badge/progress/overlay APIs; @opentray/ext-island is the roadmap dynamic island extension.

ext-webview · ext-badge · ext-island

Packaging layer

Bundler-neutral, four adapters

Shipping should not depend on your bundler. @opentray/packaging stages runtime executables, native sidecars, and companion assets into app-id-derived output paths and writes an opentray-app-manifest.json manifest; adapters ship for Vite, tsdown, esbuild, and webpack, and all four write the same manifest shape.

opentray-app-manifest.json · 4 adapters

Platform runtimes

Six platforms, plain binaries

The runtime arrives the boring way: platform packages such as @opentray/darwin-arm64 carry bin/opentray or bin/opentray.exe for three OSes × two architectures (darwin, linux, windows × arm64, x64). Packaging stays a build-layer concern — it stages artifacts and emits manifest truth; tray lifecycle, session authority, and extension dispatch stay with the runtime.

@opentray/<os>-<arch> · bin/opentray

Releases

Protocol-line dist-tags

Mixed package sets should not drift apart. When an app uses official extensions, lock the same OpenTray protocol-line tag across the package set: pnpm add opentray@stable-A-B @opentray/ext-webview@stable-A-B. alpha-A-B carries alpha packages on the same protocol line; the tags are published by @opentray/spec.

stable-A-B · alpha-A-B

Workspace

One repository, one package family

The npm surface mirrors the repository layout. The Lynx extension is maintained in the independent jixoai/opentray-ext-lynx repository; OpenTray core does not build, stage, or publish Lynx artifacts.

Directorynpm packagePurpose
packages/cliopentrayDeveloper-facing tray-first SDK and CLI package.
packages/spec@opentray/specTypeScript protocol and shared contract package.
packages/packaging@opentray/packagingBundler-neutral runtime artifact staging contract.
packages/vite-plugin@opentray/vite-pluginFirst Vite adapter over the packaging contract.
packages/ext-webview@opentray/ext-webviewRich popup extension facade.
packages/ext-webview-*@opentray/ext-webview-*Platform WebView dynamic library packages.
packages/ext-badge@opentray/ext-badgePlatform badge/progress/overlay API extension.
packages/ext-island@opentray/ext-islandRoadmap dynamic island / live activity extension.
packages/<os>-<arch>@opentray/<os>-<arch>Platform runtime artifact packages.

Ecosystem

Where things live

Source, packages, and the public guides — the same links the README carries.