- 新增 web 组件独立发布为 nex-web_<version>.tar.gz
- server 新增 arm64 架构、macOS universal、Windows arm64 产物
- desktop 新增 arm64 架构支持(Linux/Windows)
- Linux desktop 新增 AppImage、deb、rpm 安装包格式
- macOS desktop 新增 unsigned DMG 安装包
- 统一发布资产命名为 {component}_{version}_{platform}_{arch}.{ext}
- 新增 SHA256SUMS 校验和清单覆盖全部发布资产
- versionctl 新增 asset-name CLI 支持按参数生成资产文件名
- Makefile release target 重构为组件/平台/架构参数化
- GitHub Actions release workflow 扩展多组件多架构构建矩阵
- 同步更新 openspec 主规范(desktop-app/release-pipeline/workspace-command-flows)
12 KiB
12 KiB
桌面应用
Purpose
TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源打包为单一可执行文件
Requirements
Requirement: 桌面应用启动
系统 SHALL 支持作为桌面应用启动,将后端服务与前端静态资源打包为单一可执行文件。
Scenario: 双击启动
- WHEN 用户双击桌面应用可执行文件
- THEN 系统使用
gofrs/flock尝试获取排他文件锁 - AND 锁文件路径为系统临时目录下的
nex-gateway.lock - AND 系统启动后端服务
- AND 系统托盘图标出现
- AND 浏览器自动打开
http://localhost:9826显示管理界面
Scenario: 单实例检查
- WHEN 用户尝试启动第二个实例
- THEN 系统检测到已有实例持有文件锁
- AND 显示错误提示"已有 Nex 实例运行"
- AND 新实例退出
Scenario: 退出释放锁
- WHEN 用户点击托盘菜单"退出"
- THEN 系统释放文件锁
- AND 应用进程退出
Requirement: 系统托盘
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择。
Scenario: 托盘图标显示
- WHEN 桌面应用启动成功
- THEN 系统根据平台加载正确的图标格式
- AND 在 Windows 上加载 ICO 格式图标(
assets/icon.ico) - AND 在 macOS 和 Linux 上加载 PNG 格式图标(
assets/icon.png) - AND 托盘图标 tooltip 显示
Nex
Scenario: 托盘菜单显示
- WHEN 用户点击托盘图标(左键或右键)
- THEN 显示托盘菜单
- AND 菜单包含"打开管理界面"选项
- AND 菜单包含"状态: 运行中"选项(禁用状态)
- AND 菜单包含"端口: 9826"选项(禁用状态)
- AND 菜单包含"退出"选项
Scenario: 打开管理界面
- WHEN 用户点击托盘菜单"打开管理界面"
- THEN 系统在浏览器中打开
http://localhost:9826
Scenario: 浏览器打开失败
- WHEN 系统无法打开浏览器(浏览器未安装等)
- THEN 托盘菜单仍可正常使用
- AND 用户可手动访问
http://localhost:9826
Scenario: 退出应用
- WHEN 用户点击托盘菜单"退出"
- THEN 系统优雅关闭后端服务
- AND 托盘图标消失
- AND 应用进程退出
Requirement: 静态文件服务
系统 SHALL 通过 Gin 同时服务 API、协议代理和前端静态资源。
Scenario: API 请求路由
- WHEN 请求路径以
/api/或/health开头 - THEN 请求由现有业务 handler 处理或返回 API 风格 404
Scenario: 版本接口路由
- WHEN desktop 模式收到
GET /api/version请求 - THEN 请求 SHALL 由版本信息 handler 处理
- THEN 响应 SHALL 为 API JSON 响应
- THEN 请求 SHALL NOT 返回前端
index.html
Scenario: 协议代理请求路由
- WHEN 请求路径以
/openai/或/anthropic/开头 - THEN 请求 SHALL 被视为协议代理请求或返回 API 风格 404
- THEN 请求 SHALL NOT 返回前端
index.html
Scenario: OpenAI 代理路由
- WHEN desktop 模式收到
/openai/v1/chat/completions请求 - THEN 请求 SHALL 进入 ProxyHandler
- THEN ProxyHandler SHALL 获取 clientProtocol 为
openai
Scenario: Anthropic 代理路由
- WHEN desktop 模式收到
/anthropic/v1/messages请求 - THEN 请求 SHALL 进入 ProxyHandler
- THEN ProxyHandler SHALL 获取 clientProtocol 为
anthropic
Scenario: 静态资源路由
- WHEN 请求路径为
/assets/* - THEN 返回嵌入的前端静态资源文件
- THEN 请求 SHALL NOT 被协议代理路由处理
Scenario: PNG Favicon 路由
- WHEN 请求路径为
/icon.png - THEN 返回来源于统一应用图标的 PNG favicon 资源
- THEN 请求 SHALL NOT 被协议代理路由处理
Scenario: SPA 路由回退
- WHEN 请求路径不匹配任何 API、协议代理或静态资源路由
- THEN 返回
index.html(支持前端 SPA 路由)
Requirement: 端口冲突检测
系统 SHALL 在启动前检测端口是否可用。
Scenario: 端口可用
- WHEN 端口 9826 未被占用
- THEN 服务正常启动
Scenario: 端口被占用
- WHEN 端口 9826 已被其他程序占用
- THEN 显示错误提示"端口 9826 已被占用"
- AND 应用退出
Requirement: 跨平台构建
系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台和架构分离或参数化,中间构建产物文件名 SHALL 保持可区分目标平台和架构,最终桌面发布资产文件名 SHALL 包含统一版本号、组件、平台、架构和格式信息。
Scenario: macOS 构建
- WHEN 执行 macOS desktop 构建命令且当前版本为
1.2.3 - THEN 系统 SHALL 生成 macOS arm64 和 amd64 桌面可执行文件
- AND 系统 SHALL 使用
lipo生成 macOS universal 桌面可执行文件 - AND 系统 SHALL 生成可打包为
.appbundle 的 macOS desktop 产物 - AND 最终 macOS desktop 发布资产文件名 SHALL 包含
1.2.3、macos和universal
Scenario: Windows 构建
- WHEN 执行 Windows desktop 构建命令且当前版本为
1.2.3 - THEN 系统 SHALL 生成 Windows amd64 和 arm64 desktop 可执行文件
- AND Windows desktop 构建 SHALL 使用
-H=windowsguilinker flag 隐藏控制台窗口 - AND 最终 Windows desktop 发布资产文件名 SHALL 包含
1.2.3、windows和对应架构标识
Scenario: Linux 构建
- WHEN 执行 Linux desktop 构建命令且当前版本为
1.2.3 - THEN 系统 SHALL 生成 Linux amd64 和 arm64 desktop 可执行文件
- AND Linux desktop 构建 SHALL 使用 CGO 和 GTK/AppIndicator 构建依赖
- AND 最终 Linux desktop 发布资产文件名 SHALL 包含
1.2.3、linux和对应架构标识
Requirement: Linux 桌面发布安装包
系统 SHALL 为 Linux desktop amd64 和 arm64 生成 tar.gz、AppImage、deb 和 rpm 发布安装包,并 SHALL 在安装包中包含标准桌面集成元数据。
Scenario: Linux desktop tar.gz 裸包
- WHEN 构建 Linux desktop 发布资产
- THEN 系统 SHALL 为 amd64 生成
nex-desktop_<version>_linux_amd64.tar.gz - AND 系统 SHALL 为 arm64 生成
nex-desktop_<version>_linux_arm64.tar.gz
Scenario: Linux desktop AppImage 包
- WHEN 构建 Linux desktop AppImage 发布资产
- THEN 系统 SHALL 为 amd64 生成
nex-desktop_<version>_linux_amd64.AppImage - AND 系统 SHALL 为 arm64 生成
nex-desktop_<version>_linux_arm64.AppImage - AND AppImage SHALL 包含 desktop entry、应用图标和 desktop 可执行文件
- AND AppImage SHALL 依赖目标系统提供 GTK3、Ayatana AppIndicator 和运行 AppImage 所需的 runtime/FUSE 能力
Scenario: Linux desktop deb 包
- WHEN 构建 Linux desktop deb 发布资产
- THEN 系统 SHALL 为 amd64 生成
nex-desktop_<version>_linux_amd64.deb - AND 系统 SHALL 为 arm64 生成
nex-desktop_<version>_linux_arm64.deb - AND deb 包 SHALL 将可执行文件安装到
/usr/bin/nex - AND deb 包 SHALL 将 desktop entry 安装到
/usr/share/applications/nex.desktop - AND deb 包 SHALL 将 hicolor 图标安装到
/usr/share/icons/hicolor/.../apps/nex.png - AND deb 包 SHALL 声明
libgtk-3-0、libayatana-appindicator3-1和xdg-utils运行时依赖 - AND deb 包 metadata 的架构字段 SHALL 使用
amd64或arm64
Scenario: Linux desktop rpm 包
- WHEN 构建 Linux desktop rpm 发布资产
- THEN 系统 SHALL 为 amd64 生成
nex-desktop_<version>_linux_amd64.rpm - AND 系统 SHALL 为 arm64 生成
nex-desktop_<version>_linux_arm64.rpm - AND rpm 包 SHALL 将可执行文件安装到
/usr/bin/nex - AND rpm 包 SHALL 将 desktop entry 安装到
/usr/share/applications/nex.desktop - AND rpm 包 SHALL 将 hicolor 图标安装到
/usr/share/icons/hicolor/.../apps/nex.png - AND rpm 包 SHALL 声明
gtk3、libayatana-appindicator-gtk3和xdg-utils运行时依赖 - AND rpm 包 metadata 的架构字段 SHALL 使用
x86_64或aarch64
Requirement: macOS DMG 打包
系统 SHALL 为 macOS desktop universal .app 生成 unsigned DMG 安装包,并 SHALL 保留 universal zip 发布资产。
Scenario: macOS universal zip 包
- WHEN 构建 macOS desktop 发布资产且当前版本为
1.2.3 - THEN 系统 SHALL 生成
nex-desktop_1.2.3_macos_universal.zip - AND zip 包 SHALL 包含
Nex.app
Scenario: macOS universal DMG 包
- WHEN 构建 macOS desktop DMG 发布资产且当前版本为
1.2.3 - THEN 系统 SHALL 生成
nex-desktop_1.2.3_macos_universal.dmg - AND DMG SHALL 包含
Nex.app - AND DMG SHALL 包含指向
/Applications的快捷方式 - AND DMG SHALL NOT 要求 macOS 签名或 notarization 才能完成构建
Scenario: macOS universal 架构校验
- WHEN macOS desktop universal 可执行文件生成完成
- THEN 系统 SHALL 验证该可执行文件包含 amd64 和 arm64 架构
Requirement: macOS .app 打包
系统 SHALL 支持打包为 macOS .app bundle,并使 bundle 元数据中的版本字段来源于统一版本号而非硬编码值。
Scenario: .app 结构
- WHEN 执行 macOS 桌面打包脚本
- THEN 生成
Nex.app目录结构 - AND 包含
Contents/Info.plist元数据 - AND 包含
Contents/MacOS/nex可执行文件 - AND 包含
Contents/Resources/icon.icns图标 - AND
Info.plist中LSUIElement为true(不显示 Dock 图标)
Scenario: bundle 版本元数据同步
- WHEN 当前统一版本号为
1.2.3 - THEN
Info.plist中CFBundleShortVersionStringSHALL 为1.2.3 - AND
Info.plist中CFBundleVersionSHALL 为1.2.3 - AND 打包流程 SHALL NOT 使用硬编码版本值
Requirement: Windows 原生对话框
系统 SHALL 在 Windows 上使用 user32.dll 的 MessageBoxW API 显示错误对话框,替代 msg * 命令。
Scenario: 错误提示对话框
- WHEN 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
- THEN 使用
MessageBoxW显示模态对话框 - AND 对话框标题栏显示应用名称
- AND 对话框包含错误描述文本
- AND 对话框显示错误图标(MB_ICONERROR)
Scenario: 非 Windows 平台不受影响
- WHEN 应用运行在 macOS 或 Linux 上
- THEN 错误对话框仍使用平台原有实现(osascript / zenity)
Requirement: Linux 对话框降级策略
系统 SHALL 在 Linux 上按优先级检测并使用可用的对话框工具,确保在不同桌面环境下都能显示对话框。
Scenario: zenity 可用
- WHEN 系统检测到
zenity命令可用 - THEN 使用 zenity 显示 GTK 风格对话框
- AND 错误对话框使用
zenity --error命令 - AND 信息对话框使用
zenity --info命令
Scenario: kdialog 可用
- WHEN zenity 不可用且
kdialog命令可用 - THEN 使用 kdialog 显示 KDE 风格对话框
- AND 错误对话框使用
kdialog --error命令 - AND 信息对话框使用
kdialog --msgbox命令
Scenario: notify-send 可用
- WHEN zenity 和 kdialog 均不可用且
notify-send命令可用 - THEN 使用 notify-send 显示系统通知
- AND 错误通知使用
-u critical参数 - AND 信息通知使用默认参数
Scenario: xmessage 可用
- WHEN zenity、kdialog、notify-send 均不可用且
xmessage命令可用 - THEN 使用 xmessage 显示基础 X11 对话框
- AND 对话框居中显示(
-center参数)
Scenario: 无对话框工具可用
- WHEN 所有对话框工具均不可用
- THEN 降级到标准错误输出
- AND 输出格式为
错误: <title>: <message>
Scenario: 工具检测缓存
- WHEN 应用启动
- THEN 系统检测一次可用对话框工具
- AND 检测结果缓存在包级变量
- AND 后续对话框调用直接使用缓存结果,不重复检测
Requirement: macOS AppleScript 字符转义
系统 SHALL 对 AppleScript 对话框中的特殊字符进行转义,确保脚本正确执行。
Scenario: 转义反斜杠
- WHEN 对话框消息包含反斜杠字符
\ - THEN 转义为
\\
Scenario: 转义双引号
- WHEN 对话框消息包含双引号字符
" - THEN 转义为
\"
Scenario: 多行文本处理
- WHEN 对话框消息包含换行符
\n - THEN AppleScript 正确显示多行文本