OpenTray v0.21.1: turning one command into a tray app
The OpenTray repo's first GitHub Release. It gives lightweight programs such as CLI tools, AI skills, and local daemons a resident tray entry without pulling in Electron or a full desktop application framework. One `npx create-opentray` line wraps an existing command into a tray app, and one `createTray()` call is enough on the SDK side.
この記事は英語で書かれています。 中国語版を読む →
$ npm view create-opentray version
0.21.1
OpenTray v0.21.1 is out (2026-09-06, the repo's first GitHub Release). It gives lightweight programs such as CLI tools, AI skills, and local daemons a resident tray entry: an icon and a menu that receive click events, without pulling in Electron or a full desktop application framework for one small icon.
One command turns an existing command into a tray app
If you already have a command that serves HTTP locally, npx create-opentray wraps it in an OpenTray app. All three entry paths are in the create-app guide.
npx create-opentray web # browser wizard, interactive
npx create-opentray create --app-id com.example.build \
--app-name "Build" --exec "npm run dev" # fully non-interactive
npx create-opentray skill # the built-in AI skill, for agents to read
Command input in the wizard is now a single-row input group (command families, organized by runner family): a family-selector prefix plus the command body. The families cover npm (npx/pnpx/bunx/yarn dlx/nubx/deno run/vpx), go run, cargo install, uvx/pipx run, and dnx. Each family renders as a read-only command surface that opens a structured form for the runner, the package identifier (name / module / crate / tool id), the version, and the arguments. Confirming writes the command string back; cancelling discards the whole input.
The appId is derived from the family, so the same package keeps a stable identity when you switch runners:
npx @deepseek-ai/dsh@latest web → web.dsh.npmjs
go run rsc.io/fortune@latest → fortune.golang
cargo install ripgrep (rg) → rg.rust
Application code calls one createTray()
Writing a tray app needs no bundler and loads no Node addon. Application code calls createTray() and owns its own foreground and background lifetime:
import { createTray, type TrayIcon } from "opentray";
let tray;
tray = await createTray(
{
id: "com.example.first-app",
icon: { "text-only": "OT" },
menu: {
items: [
{ title: "Quit", primaryEvent: true, onMenuClick: () => void tray.destroy() },
],
},
},
{ appId: "com.example.first-app", appName: "First App" },
);
Application code talks to the runtime over the public tray/session protocol. The runtime side has four entities.
App: the runtime identity the caller holds, and the isolation boundaryTray: a single tray icon and its state under one appSession: the live source of authority for tray events and mutationsExtension: a native capability package scoped to this app and its trays
The Space and Surface entities of the previous public API are retired.
Every package in the workspace publishes on one fixed version: opentray (SDK + CLI), @opentray/spec (the protocol contract), @opentray/packaging with four bundler adapters (vite/tsdown/esbuild/webpack writing the same manifest shape), @opentray/ext-webview with platform WebView dynamic libraries, and @opentray/ext-badge. The Lynx extension is maintained separately at jixoai/opentray-ext-lynx.
Other changes
Fixed the fresh-install 404: the private workspace packages
@create-opentray/coreand@create-opentray/clishipped in runtimedependencies(0b0fe63). That rule was located by a real install-failure report on 2026-08-20, and this is the first version where command families installs.Release automation: every published version now cuts a GitHub Release of its own (tag = the package version, notes = the changesets-generated changelog section), and this site's version pill showed a real number for the first time instead of "v—". This one was cut through the bootstrap path; from the next version on, release notes are fully automated.
@opentray/spec,@opentray/packaging, and@opentray/vite-pluginpublish at 0.21.1 alongside it.
Upgrade
npm i create-opentray@0.21.1 # 0.21.0 never installed, so upgrading is mandatory
npm i opentray@0.21.1 # the SDK, same line
When using official extensions, lock the same protocol line: pnpm add opentray@stable-A-B @opentray/ext-webview@stable-A-B (protocol-line tags are published by @opentray/spec; do not mix with latest).
Acknowledgements
Thanks to the install-failure report from 2026-08-20. The devDependencies rule was tightened because of it.
Links
Changelog: GitHub Release opentray@0.21.1 · packages/create/CHANGELOG.md
Docs: opentray.jixoai.com · README · create-app guide
Upgrade: see the Upgrade section above (two
npm ilines)Discussion: GitHub Issues
npm: opentray · create-opentray
On this blog: UniPty v0.2.0 · Jixoai UI v0.3.0 · OpenSpecUI 12.0.0
Chinese version: /zh/blog/2026-09-06-opentray-v0-21-1/
