Qwen Code 桌面客户端白标化实战:一个 brandId 加一张 logo 出定制品牌安装包
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
你只需交出一个 brandId 和一张 logo,Qwen Code 内置的品牌构建管线就能基于 Tauri 桌面壳,产出你自家品牌的桌面客户端与 DMG/EXE/AppImage/deb 安装包。先把一条铁律说在前面:定制品牌包绝不轮询官方 Qwen Code 更新源,官方源也绝不更新品牌包。本文按「改什么、怎么改、如何可信」三个问题展开,末尾附一份踩坑备忘。
🧩 一、到底在改什么?
Tauri 壳是唯一桌面形态,白标只动三个挂载点
Qwen Code 的桌面端此前基于 Electron,对应的packages/desktop目录已整体移除;如今唯一的桌面实现就是packages/desktop-shell下的 Tauri 壳。仓库把白标流程做成了一个 Agent Skill(desktop-brand-builder):出现"定制品牌桌面客户端/白标安装包"这类需求时,Agent 读取它的 SKILL.md 并按其中流程执行,而不是临时发挥。
换肤真正落地的位置只有三处:
src-tauri/tauri.conf.json:应用显示名、Bundle Identifier、打包目标与应用内更新配置;src-tauri/icons/:桌面图标全家桶(png/icns/ico 及各平台尺寸变体);bootstrap/:应用启动瞬间的启动 UI,承载品牌标题与 Logo。
brandId 校验规则与最小输入契约
输入契约被压到最少:brandId+logo,website可选。应用显示名、appId、产物名、版权、更新端点一律不追问——除非你主动要求覆盖。必填项缺失时只问一次,问完直接进构建,没有确认环节。
| 字段 | 必填 | 规则与含义 |
|---|---|---|
brandId | 是 | 匹配^[a-z][a-z0-9-]*$:小写字母开头,仅小写字母、数字、短横线 |
logo | 是 | 存在的本地文件路径,推荐 ≥1024px 方形 PNG |
website | 否 | 官网地址,用于推导 appId |
appName/appId/artifactPrefix | 否 | 分别覆盖对应的派生值 |
updaterEndpoints | 否 | 更新源端点 JSON 数组,空数组 = 禁用应用内更新 |
updaterPubkey | 条件必填 | 端点非空时必填,base64 公钥须与签名私钥配对 |
target | 否 | mac/win/linux/all |
不填的都会替你算:确定性派生
之所以不追问,是因为缺省值可以被确定性地推导出来:
appName:brandId 按短横线切段、每段首字母大写后空格连接,acme-ai→Acme AI;artifactPrefix:同样首字母大写但用短横线连接,acme-ai→Acme-AI;appId:website 的 host 去掉www.前缀后把域名标签反转、追加.desktop,https://acme.ai→ai.acme.desktop;host 不足两段时回退app.<brandId>.desktop;updaterEndpoints:默认空数组;- 常见缩写
ai、api、cli、ide、sdk、ui、url整体大写:brandId 为acme-cli时派生出Acme CLI而非Acme Cli。
品牌包与官方更新源彻底隔离
这条约束贯穿整条管线,也是派生默认值里最需要理解的一条:定制品牌包绝不轮询官方 Qwen Code 更新源,官方源也绝不更新品牌包。所以updaterEndpoints默认是空数组,只有品牌方自建更新源时才填端点。清空端点时脚本还同步做两件事:把bundle.createUpdaterArtifacts置为false,免得 Tauri 打包器继续产出没人消费的签名更新产物;把plugins.updater.pubkey置为空字符串而不是删除字段——updater 插件的 schema 声明pubkey: String且没有 serde 默认值,删掉字段应用一启动就会反序列化失败,而端点为空时空字符串没有任何副作用。
🔧 二、怎么改?
完整命令链:隔离克隆 → brand.json → 两级依赖 → 品牌脚本
每个品牌都用一份全新克隆,避免污染工作仓库:先git clone --branch main --single-branch https://gitcode.com/GitHub_Trending/qw/qwen-code到brand-builds/<brandId>-<时间戳>/qwen-code,再git checkout -B brand-<brandId> origin/main。clone 或 checkout 失败就停在这里上报,绝不能假装分支已创建继续往下走。
然后在构建目录里写一份brand.json(路径用绝对路径):
{ "brandId": "acme-ai", "logo": "/absolute/path/to/logo.png", "website": "https://acme.ai", "updaterEndpoints": [], "updaterPubkey": "" }依赖要装两级:仓库根部的npm install是给build:runtime用的(它依赖根部的 cross-env、esbuild 等 devDependencies);packages/desktop-shell下再npm install --workspaces=false装壳自身的依赖。最后用纯 Node 跑技能自带的零依赖脚本(Node ≥ 18 即可):
node packages/desktop-shell/.agents/skills/desktop-brand-builder/scripts/brand-create.mjs \ --shell-root /absolute/path/to/qwen-code/packages/desktop-shell \ --config /absolute/path/to/brand.json内置脚本可用时,不要手工去改tauri.conf.json、图标或 bootstrap 里的品牌字符串——脚本是补丁配置与生成资源的唯一权威来源。跑完它会打印一份 JSON 报告:brandId、appName、appId、artifactPrefix、updaterEndpoints、被补丁的 tauriConfig 路径、icons 生成结果、bootstrapPatched 文件列表。
补丁一:tauri.conf.json 的四字段与失败关闭
脚本按序做三个补丁,第一个落在src-tauri/tauri.conf.json,改动四个字段:productName换成 appName,identifier换成 appId,bundle.shortDescription换成品牌描述,plugins.updater.endpoints换成品牌端点。
这里有两个容易被忽略的设计。其一,失败关闭:如果品牌给了 updater 配置(端点非空或带了公钥),而目标文件里偏偏没有plugins.updater段(fork 或手工改过的 shell-root 可能出现),脚本会在写任何文件之前直接报错。否则这份已校验的配置会被悄悄丢弃,最后交付一个永远无法更新的品牌包。其二,单次使用守卫:脚本启动时检查productName是否还是默认值Qwen Code Desktop,不是就拒绝运行——bootstrap 补丁依赖原始字面量,pubkey 和 endpoints 的变更也无法回滚,补丁不可逆。
补丁二:图标全家桶重建,顺带防注入
图标这一步从 logo 重新生成整套尺寸。调用方式上,脚本先用require.resolve('@tauri-apps/cli/tauri.js')解析出 Tauri CLI 入口,再以spawnSync(process.execPath, [...])直接拉起,logo 路径以普通 argv 元素传递、不经过任何命令解释器——如果走 shell 字符串拼接,路径里带$(cmd)或反引号的文件名就能注入命令。
CLI 跑不起来时还有两级回退:logo 是 PNG 就复制为src-tauri/icons/icon.png,并明确告警"其余尺寸仍是 Qwen Code 旧 logo",提示用npx --yes @tauri-apps/cli icon <logo>补齐;logo 非 PNG 且 CLI 也失败,就什么都不替换,只提示先转 PNG 再重试。宁可多一句告警,不静默留下半新半旧的图标。
补丁三:bootstrap 启动 UI 换肤与双语境转义
换肤把 logo 复制为bootstrap/brand-logo<ext>,再把index.html的<title>、<h1>与启动文案里的Qwen Code字面量、svg 引用全部替换为品牌名与品牌 logo 文件名。
难点在于同一个品牌名要插进两种语境:bootstrap.js里是单引号字符串字面量,index.html里是文本内容和双引号属性。JS 语境先经JSON.stringify转义反斜杠、换行等控制字符,剥掉外层双引号再转义单引号——只转单引号不够,以反斜杠结尾的字符串(比如Bob's App\)会把字面量的结束引号"吃掉"。HTML 语境按&→<→>→"→'的顺序做实体转义,先转&是为了不让后面引入的实体被二次转义。替换统一用函数式 replacer,避免替换串里的$&之类模式被展开成匹配文本、把内容弄坏。
✅ 三、如何可信?
交叉编译的坑:每个 target 都要重跑 build:runtime
宿主平台打包就是npm run build:runtime --workspaces=false接npx tauri build。真正会翻车的是 Tauri 交叉编译:build:runtime(由 prepare-runtime.js 实现)按QWEN_DESKTOP_TARGET环境变量(默认宿主平台)下载对应平台的 Node.js 运行时并捆绑进runtime/qwen-code/,该取值会被归一化为 darwin-arm64、darwin-x64、linux-arm64、linux-x64、win32-x64 五种受支持目标之一,不在其内直接抛错。
目标平台与宿主不同时,每次tauri build --target之前必须带着环境变量重跑build:runtime,否则包里内嵌的是错误架构的 Node 二进制,应用一启动就是 exec format error:
QWEN_DESKTOP_TARGET=aarch64-apple-darwin npm run build:runtime --workspaces=false npx tauri build --target aarch64-apple-darwintarget设为all时按目标逐个执行 build:runtime → tauri build,且只能跑当前机器或 CI 环境支持的 target。文件不存在,就不能声称产出了跨平台产物。
产物落在哪:bundle 目录速查
宿主目标:packages/desktop-shell/src-tauri/target/release/bundle/;指定 triple 时:src-tauri/target/<triple>/release/bundle/。bundle 下的子目录按平台分为dmg/(macOS)、nsis/(Windows)、appimage/与deb/(Linux),与tauri.conf.json里声明的 targets 一一对应。
生成你自己的 updater 签名密钥对
品牌包默认不签名:上游流水线的签名密钥(Apple、Windows)和更新私钥只属于官方 Qwen Code 发布。需要签名发布或应用内更新时,必须自备独立凭据和独立更新源,绝不复用官方密钥。
npx @tauri-apps/cli signer generate -w ~/.tauri/my-brand.key.key是私钥,设进构建 CI 的TAURI_SIGNING_PRIVATE_KEY;配套.pub里的 base64 公钥填进 brand.json 的updaterPubkey。公私钥必须配对:加载期就会校验"端点非空而公钥缺失"并直接报错。原因不复杂——更新器用这把公钥校验每个更新的签名,配错或漏配意味着每次更新检查都验证失败,应用从此永远更新不了。
另有一条加载期校验:appName 不能等于Qwen Code Desktop。单次使用守卫依赖 productName 偏离默认值才能生效,品牌名恰好撞上默认值,二次运行时守卫失效,bootstrap 文件里的品牌字符串会被重复拼接。
产物自检清单
- 确认产物落在对应 bundle 目录(
dmg/、nsis/、appimage/、deb/之一); - 每个产物算一次
sha256sum(macOS 用shasum -a 256); - macOS 上对生成的 DMG 执行
hdiutil verify; - 向用户报告:产物路径、SHA-256、应用名、appId、构建目录。
⚠️ 四、踩坑备忘:哪里容易翻车
| 场景 | 处理 |
|---|---|
| brandId 非法 | 出示正则^[a-z][a-z0-9-]*$,请用户修正 |
| logo 缺失 | 请用户给出存在的本地文件路径 |
| 内置脚本缺失 | 报告 brand-create.mjs 预期路径与调用命令 |
| shell-root 已品牌化 | 脚本拒跑(productName 已不是默认值),从全新克隆开始 |
| 构建失败 | 保留构建目录,返回最后有用的错误行与完整日志路径 |
两条红线要刻进流程:失败时不删构建目录——它是留给事后排查用的,不是用来原地重试品牌步骤的;同一克隆里不重跑 brand-create——脚本是单次使用的,配置写错就丢弃克隆、从头再来。
路径速查
- 技能说明:SKILL.md
- 品牌脚本:brand-create.mjs
- 应用配置:tauri.conf.json
- 启动 UI:
packages/desktop-shell/bootstrap/index.html、bootstrap/bootstrap.js - 运行时捆绑:prepare-runtime.js
结语
一份 brand.json 加一个零依赖 Node 脚本,就把"桌面客户端怎么白标化"压缩成了一条可复制的命令链:配置补丁、图标重建、启动界面换肤一次完成,叠加按目标的 build:runtime 与 tauri build,就是可分发的品牌安装包。单次使用守卫、公私钥配对校验、注入防护与 JS/HTML 双语境转义这些细节,让整条换肤流程既安全又可复现——白标场景里,这几乎是能直接照抄的工程范本。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考