Skip to content

OpenTray v0.23.0:一行命令将你的 webui 打包成原生窗口(现已支持 command 和 url 双模式)

jixoai v0.23.0 opentray

OpenTray v0.23.0 起 create-opentray 有两种入口:给一条命令,或给一个网址。命令模式把本地 webui 变成常驻托盘的桌面应用,URL 模式把任意页面直接打包成应用窗口,App 图标也会自动生成。

$ npm view create-opentray version
0.23.0

OpenTray v0.23.0 发布了(2026-09-10)。create-opentray 现在有两种入口:给它一条命令,或者给它一个网址,出来都是一个带自己身份的桌面应用。这一版还顺手把 App 图标的自动生成补上了。

命令模式:让 webui 有个自己的窗口

先看原来就有的这条路。很多项目的界面其实是个 webui,入口却只是一条命令:ComfyUI、gradio、llama.cpp 的 server,或者你自己项目的 npm run dev。跑起来之后它只占一个浏览器标签页,混在几十个标签里;没有自己的 Dock 图标,也没有托盘入口,关掉标签页不等于关掉进程,下次用还得回到终端敲一遍。

命令模式接管的就是这几步:把命令 spawn 起来,等到它真的监听了端口,再用一个固定的应用身份开一个原生窗口指向那个端口,托盘里留一个入口负责显示、唤起与退出。端口不写死在配置里,运行时嗅探命令自己监听的端口。

npx create-opentray create \
  --app-id app.local.mytool --app-name "My Tool" \
  --exec npm --arg run --arg dev --cwd /path/to/project

--arg 可重复,每个值就是一个 argv 元素,不做 shell 拆分,所以 && 会被当成字面参数。想看命令的输出,可以在向导里打开「显示启动终端」,旁边会多一个终端窗口。

URL 模式:给网址也能打包

上面那条链路有个前提,就是你手里得有一条命令。但很多时候你只有一个地址:别人部署好的实例、线上的服务、纯静态页面。URL 模式补的就是这个缺口。

npx create-opentray create --url https://example.com
# https://example.com/app → app id "app.com.example",名字 "App"

坦白说这不是什么新能力,窗口、托盘与身份推导都是现成的,只是入口从命令换成了地址,产物因此更干净:不跑任何东西,没有 PTY,没有 shell 资源,也不依赖 node-pty。做它的目的是把覆盖面补齐到 PWA 那类用途上:装一个图标,点开就到,常驻托盘。区别在于 PWA 走浏览器的安装模型,能力受浏览器沙箱约束;这里的产物是一个有自己身份的桌面应用,托盘菜单、Dock 图钉与窗口生命周期都由它自己掌管。

--exec(命令模式)--url(URL 模式)
入口一条本地命令一个 http(s) 地址
产物做什么起进程、等端口、开窗口只开窗口
运行时依赖可选 PTY 与 shell 资源无 PTY、无 shell
应用身份runner 与包名从地址推导
标题与图标默认值你自己给抓一次页面

地址在动手之前就已知,所以创建时会抓一次页面,把 <title> 和最佳 favicon 作为默认值。显式传入的 flag 永远优先;抓取失败就静默回落到地址推导出的名字和字形图标;--no-scrape 关掉这次抓取。

--toolbar 会套一层地址栏外壳,带后退、前进与重载按钮,以及焦点在壳内时可用的 ⌘/Ctrl+←→、⌘/Ctrl+[ 、⌘/Ctrl+R、F5、⌘/Ctrl+L 快捷键。禁止被嵌入的页面套不了这层壳:创建时会在抓取阶段读响应头(X-Frame-Options 与 CSP frame-ancestors),对方拒绝就回退成直连窗口并打印提示。不管用不用 toolbar,URL 应用的托盘菜单里都有 Reload。

下一步:让前端也有后端能力

URL 模式现在的边界很清楚:窗口里的页面仍然是个普通网页,能做的事和在浏览器里一样多。

下一步打算补一组标准接口,让纯前端页面也能调到 fschild_process 这类基础能力。这两项补齐之后,一个用 OpenTray 打包的网页理论上就能做原生应用做的事:读写本地文件、起子进程、管自己的数据,而不必先写一个 Node 后端。这是计划中的方向,还没有发布。

App 图标不用你画

省略 appIcon 时,运行时会用应用名的首字母合成一个字形图标写进 macOS 应用包,缓存在运行时目录下,Dock 上不再出现那个通用的可执行占位图。appBundle.defaultAppIcon: false 退回旧行为,显式传入的 appIcon 始终优先。

await createTray({ /* 托盘图标与菜单 */ }, {
  appId: "com.example.build",
  appName: "Build",
  appBundle: { defaultAppIcon: false },   // 不合成默认图标
});

生成内核现在收在一个包里:@opentray/icon。字形默认值、合成与 squircle 切图都在其中,ICNS、ICO 和 Linux PNG 三档编码也一并收进来,底层是一套 WebAssembly 图像栈(jsquash 负责解码与缩放,resvg 负责渲染 SVG),全程没有 sharp,也没有 libvips。@opentray/vite-plugin 调的是同一个内核,公开 API 不变。

向导的 URL 模式把 macOS 需要的两个图标分开处理:App 图标候选是原图加上 AI 抠出的主体,托盘图标用实心剪影,后者才是 macOS 托盘模板要的东西。抠图在浏览器里跑,模型由向导服务端代理,后面垫着一层持久的磁盘缓存:模型从 CDN 下载一次,之后每次会话(端口随机)都从本机回环地址取。高级设置(模型精度、alpha 阈值与边缘收缩)改动即生效,替换掉上一个主体和由它派生的剪影。

其他变更

  • macOS 上的 @opentray/ext-webview 不再在每个事件排空周期重复断言激活策略。原来每秒约 60 次的 tick 都调 set_activation_policy,连带重设应用图标并重绘 Dock 图块,一个 broker 稳定吃掉单核 50–80%,窗口内容一动不动也一样。现在先跟 AppKit 的当前值比对再决定(029d286)。

  • @opentray/icon 的自动背景会匹配实色边缘环,白底方角 favicon 能保住自己的底色。

  • 需要中日韩字符但系统没装对应字体时,图标生成退到中性终端标记,不再直接失败。

  • @opentray/vite-plugin 去掉了 sharp 依赖。

升级

npm i create-opentray@0.23.0
npm i opentray@0.23.0          # SDK

用官方扩展时锁同一条协议线:pnpm add opentray@stable-A-B @opentray/ext-webview@stable-A-B(协议线 tag 由 @opentray/spec 发布,不要和 latest 混用)。

链接