desktop-brand-builder · diff
git:20260612.3c55295 to git:20260925.73aa65a
111 added, 39 removed. Audit A to A.
---
name: desktop-brand-builder
- description: Generate a branded qwen-code desktop package from a minimal brandId and logo. Use when the user wants a custom, white-label, rebranded, ModelStudio/OpenWork/Qwen Code desktop client, installer, DMG/EXE/AppImage, or one-click brand build.
+ description: Generate a branded Qwen Code desktop package from the Tauri desktop shell using a minimal brandId and logo. Use when the user wants a custom, white-label, or rebranded desktop client, installer, DMG/EXE/AppImage/deb, or one-click brand build on top of packages/desktop.
---
- # Desktop Brand Builder
+ # Desktop Brand Builder (Tauri shell)
## Goal
- Create a branded desktop package with the least user input possible. The user
- should usually provide only:
+ Create a branded desktop package from `packages/desktop` with the least
+ user input possible. The user should usually provide only:
```text
brandId: acme-ai
logo: /absolute/path/to/logo.png
website: https://acme.ai
```
`website` is optional. Do not ask for app name, app id, artifact name,
- copyright, dock icon, renderer symbol, signing, or local installation unless
- the user explicitly asks to override them.
+ copyright, or updater endpoints unless the user explicitly asks to override
+ them.
+ This skill replaces the Electron-era brand builder that lived in the older,
+ since-deleted Electron `packages/desktop` (removed in PR 9085, before this
+ Tauri shell took over that path). The Tauri shell is the only desktop
+ implementation now; branding hooks are `src-tauri/tauri.conf.json`,
+ `src-tauri/icons/`, and the `bootstrap/` startup UI.
+
## Input Rules
Required fields:
- `brandId`: must match `^[a-z][a-z0-9-]*$`
- - `logo`: local file path; must exist
+ - `logo`: local file path; must exist; `.png` recommended (square, >= 1024px)
Optional overrides:
- `website`
- `appName`
- - `appId`
+ - `appId` (Tauri bundle identifier)
- `artifactPrefix`
+ - `updaterEndpoints` (JSON array; empty array disables in-app updates)
+ - `updaterPubkey` (base64 public key; **required** when `updaterEndpoints`
+ is non-empty — must match the `TAURI_SIGNING_PRIVATE_KEY` used to sign
+ your updater artifacts)
- `target`: `mac`, `win`, `linux`, or `all`
If required input is missing, ask once:
```text
请提供:
brandId: 例如 acme-ai,只能小写字母、数字、短横线
- logo: 本地 logo 文件路径
+ logo: 本地 logo 文件路径(建议 1024x1024 PNG)
website: 可选
```
Once the required fields are present, proceed without a confirmation step.
## Derived Defaults
Infer missing values deterministically:
- `appName`: title-case the hyphen-separated `brandId`; `acme-ai` becomes
`Acme AI`
- `artifactPrefix`: title-case the hyphen-separated `brandId` and join with
hyphens; `acme-ai` becomes `Acme-AI`
- `appId`: if `website` has a valid host, reverse the host labels and append
`.desktop`; `https://acme.ai` becomes `ai.acme.desktop`
- fallback `appId`: `app.<brandId>.desktop`
- - `copyright`: `Copyright © <current year> <appName>`
- - all brand images: generate icon, dock icon, and renderer symbol from `logo`
-
- Use explicit user-provided override values as-is after basic validation.
+ - `updaterEndpoints`: empty by default. A branded build must never poll the
+ official Qwen Code updater feed, and the official feed must never update a
+ branded build. Only set endpoints when the user supplies their own feed.
- ## Build Workflow
+ ## Workflow
- Use an isolated build directory under the current working directory so user
- changes in the current worktree are not mutated. Default to the qwen-code main
- branch; do not clone from `craft-agents-oss`, OpenWork, or another local
- checkout unless the user explicitly asks for that source:
+ Work in an isolated build clone so the working repository stays clean:
```bash
BUILD_ROOT="$PWD/brand-builds/<brandId>-<timestamp>"
mkdir -p "$BUILD_ROOT"
git clone --branch main --single-branch \
https://github.com/QwenLM/qwen-code.git \
"$BUILD_ROOT/qwen-code"
cd "$BUILD_ROOT/qwen-code"
git checkout -B brand-<brandId> origin/main
```
- If the branch fetch or checkout fails, stop and report the failure. Do not
- continue as if `brand-<brandId>` was created.
+ If the clone or checkout fails, stop and report the failure. Do not continue
+ as if `brand-<brandId>` was created.
Create a temporary `brand.json` in the build directory:
```json
{
"brandId": "acme-ai",
"logo": "/absolute/path/to/logo.png",
"website": "https://acme.ai",
"appName": "Acme AI",
"appId": "ai.acme.desktop",
"artifactPrefix": "Acme-AI",
- "copyright": "Copyright © 2026 Acme AI"
+ "updaterEndpoints": [],
+ "updaterPubkey": ""
}
```
- Install desktop dependencies if `packages/desktop/node_modules` is missing:
+ Install dependencies. The brand script itself only needs the desktop package's
+ own `node_modules`, but `npm run build:runtime` shells out to the repo
+ root (which uses `cross-env` and other root devDependencies), so the
+ root install is also required before packaging:
```bash
+ # Root dependencies (needed by build:runtime → cross-env, esbuild, etc.)
+ npm install
+
+ # Desktop package dependencies
cd packages/desktop
- bun install
+ npm install --workspaces=false
+ cd ../..
```
- Then run this skill's bundled brand creation script:
+ Then run this skill's bundled brand creation script with plain Node (the
+ script has no dependencies beyond Node >= 18):
```bash
- cd /absolute/path/to/qwen-code
- bun run packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts \
- --desktop-root /absolute/path/to/qwen-code/packages/desktop \
+ node packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.mjs \
+ --shell-root /absolute/path/to/qwen-code/packages/desktop \
--config /absolute/path/to/brand.json
```
- The agent should not hand-edit `branding.ts` or brand asset files when this
- bundled script is available. The bundled script is the source of truth for
- patching code and generating resources.
+ The agent should not hand-edit `tauri.conf.json`, icon files, or bootstrap
+ brand strings when this bundled script is available. The bundled script is the
+ source of truth for patching config and generating resources.
+ What the script does:
+
+ 1. Patches `src-tauri/tauri.conf.json`: `productName`, `identifier`,
+ `bundle.shortDescription`, and `plugins.updater.endpoints`. When
+ `updaterEndpoints` is empty it also clears `bundle.createUpdaterArtifacts`
+ and blanks the official `plugins.updater.pubkey` (set to empty string
+ rather than deleted, because the updater plugin requires the field);
+ a brand supplying its own feed must supply its own pubkey.
+ 2. Regenerates the full icon set from the logo via
+ `npx --yes @tauri-apps/cli icon <logo>` (falls back to a warning if the
+ CLI cannot run; in that case copy the logo over `src-tauri/icons/icon.png`
+ manually and tell the user the remaining sizes are stale).
+ 3. Patches the bootstrap UI: page title, brand heading, startup strings in
+ `bootstrap/index.html` and `bootstrap/bootstrap.js`, and replaces
+ `bootstrap/qwen-code-logo.svg` usage with the brand logo.
+
Package with the current host target unless the user requested a target:
```bash
- CRAFT_BRAND=<brandId> bun run electron:dist:mac
- CRAFT_BRAND=<brandId> bun run electron:dist:win
- CRAFT_BRAND=<brandId> bun run electron:dist:linux
+ cd packages/desktop
+ npm run build:runtime --workspaces=false
+ npx tauri build # current platform
```
- For `target: all`, run only targets supported by the current machine or CI
- environment. Do not claim cross-platform artifacts were produced unless the
- files exist.
+ **Cross-compile:** `build:runtime` bundles the Node runtime for the platform
+ indicated by `QWEN_DESKTOP_TARGET` (defaults to the host). When targeting a
+ different platform you **must** re-run `build:runtime` with the env var set
+ before each `tauri build --target`, otherwise the packaged artifact contains
+ a wrong-arch Node binary and fails at launch with an exec format error:
+ ```bash
+ # Cross-compile: set QWEN_DESKTOP_TARGET and re-run build:runtime per target
+ QWEN_DESKTOP_TARGET=aarch64-apple-darwin npm run build:runtime --workspaces=false
+ npx tauri build --target aarch64-apple-darwin # explicit macOS arm64
+ ```
+
+ For `target: all`, iterate `build:runtime` → `tauri build` per target; run
+ only targets supported by the current machine or CI environment. Do not
+ claim cross-platform artifacts were produced unless the files exist.
+ Artifacts land under `packages/desktop/src-tauri/target/release/bundle/`
+ for the host target, or `src-tauri/target/<triple>/release/bundle/` when
+ `--target <triple>` is used.
+
+ ## Signing and Updates
+
+ Branded builds are unsigned by default. The upstream release pipeline's
+ signing secrets (Apple, Windows) and updater private key belong to the
+ official Qwen Code releases only. For a brand that needs signed releases or
+ in-app updates, set up separate credentials and a separate updater feed; do
+ not reuse the upstream ones.
+
+ To generate a signing key pair for your updater feed:
+
+ ```bash
+ npx @tauri-apps/cli signer generate -w ~/.tauri/my-brand.key
+ # The .key file is the private key (set as TAURI_SIGNING_PRIVATE_KEY in
+ # your build CI). The corresponding .pub file contains the base64 public
+ # key — paste it into brand.json as updaterPubkey.
+ ```
+
## Validation
After packaging:
1. Confirm the expected artifact exists under
- `packages/desktop/apps/electron/release/`.
+ `packages/desktop/src-tauri/target/release/bundle/`
+ (or `src-tauri/target/<triple>/release/bundle/` for cross-compile targets)
+ (`dmg/`, `nsis/`, `appimage/`, or `deb/`).
2. Compute `sha256sum` or `shasum -a 256` for each artifact.
3. On macOS, run `hdiutil verify` for generated DMG files.
4. Report the artifact path, SHA-256, app name, app id, and build directory.
## Failure Handling
- Invalid `brandId`: show the regex and ask for a corrected value.
- Missing `logo`: ask for a valid local path.
- Missing bundled script: report that
- `packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts`
+ `packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.mjs`
is missing, and include the expected command.
+ - Already-branded shell-root: the script refuses to run when
+ `productName` is no longer the default (`Qwen Code Desktop`). Start from
+ a fresh clone — do not re-run the script in an already-patched tree.
- Build failure: preserve the build directory, return the last useful error
lines, and include the full log path or command that produced the failure.
- Do not delete the build directory on failure.
+ Do not delete the build directory on failure. **Never re-run `brand-create`
+ in the same clone** — the script is single-use. The "preserve the build
+ directory" guidance is for post-mortem debugging, not for retrying the
+ brand step. If the brand config was wrong, discard the clone and start
+ fresh.