From e79fb00fecc15e5d548596e1ca65a81f26f9428b Mon Sep 17 00:00:00 2001 From: Joe0720 <839087665@qq.com> Date: Fri, 26 Jun 2026 09:29:24 +0800 Subject: [PATCH] feat(desktop): support remote lite build mode without bundled JRE/JAR (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(desktop): support remote lite build mode without bundled JRE/JAR Add a dual packaging mode system controlled by the BUILD_MODE env var: - **local** (default): Full build bundling JRE + Spring Boot JAR, identical to the previous behavior. Supports both embedded local backend and remote server connection. - **remote** (lite): Omits the ~530 MB JRE/JAR resources, producing an installer that is ~81% smaller (97 MB vs 523 MB on macOS arm64). The app only supports connecting to a remote server; the "local" option is hidden from the splash connection chooser. Changes: - Replace static electron-builder.json with dynamic electron-builder.cjs that conditionally includes extraResources based on BUILD_MODE - Add build mode detection at runtime (checks JAR existence) with graceful fallback to remote-only mode - Add IPC handler app:get-build-mode and expose via preload - Hide "本地运行" option in splash when running a remote build - Ignore stale 'local' saved config in remote builds - Add package scripts: package:mac:local, package:mac:remote, etc. - Add missing build scripts: build.sh, download-jre.sh, build-all-platforms.sh - Add no-op afterPack hook (trim-playwright-driver.cjs) placeholder - Add cross-env devDependency for cross-platform BUILD_MODE support * feat(desktop): add white-label branding system for build-time rebranding Add a Vite plugin (scripts/branding.cjs) that replaces hardcoded "MateClaw" strings at build time, enabling white-label/OEM rebranding without modifying any source code. Configuration: - Edit branding.config.json (name, tagline, team, copyright, appId, githubUrl) - Or set BRAND_* env vars (BRAND_NAME, BRAND_TAGLINE, BRAND_TEAM, etc.) Usage: # Default build (MateClaw brand) npm run package:mac # Custom brand via env vars BRAND_NAME=MyAI BRAND_TAGLINE="Smart AI Helper" npm run package:mac:remote # Or edit branding.config.json and build normally npm run package:mac:remote Replacements applied at build time: - Brand name (window title, About dialog, error messages, console logs) - Tagline, team name, copyright line - GitHub repo/issues URLs - Logo file path - electron-builder config (productName, appId, artifactName, dmg title, publish repo) The branding plugin runs in Vite's transform hook, covering the renderer (App.vue, index.html), electron main process, and preload script. Server-coupled strings (H2 database name, Spring Boot property names) are intentionally NOT replaced to avoid breaking backend compatibility. --------- Co-authored-by: qiaozhipeng --- mateclaw-desktop/branding.config.json | 9 ++ mateclaw-desktop/electron-builder.cjs | 139 ++++++++++++++++ mateclaw-desktop/electron-builder.json | 96 ----------- mateclaw-desktop/electron/main/index.ts | 56 +++++++ mateclaw-desktop/electron/preload/index.ts | 1 + mateclaw-desktop/package.json | 13 ++ mateclaw-desktop/pnpm-lock.yaml | 119 +++++++++++--- mateclaw-desktop/scripts/branding.cjs | 153 ++++++++++++++++++ .../scripts/build-all-platforms.sh | 35 ++++ mateclaw-desktop/scripts/build.sh | 30 ++++ mateclaw-desktop/scripts/download-jre.sh | 86 ++++++++++ .../scripts/trim-playwright-driver.cjs | 6 + mateclaw-desktop/src/App.vue | 38 ++++- mateclaw-desktop/src/env.d.ts | 2 + mateclaw-desktop/vite.config.ts | 12 ++ 15 files changed, 676 insertions(+), 119 deletions(-) create mode 100644 mateclaw-desktop/branding.config.json create mode 100644 mateclaw-desktop/electron-builder.cjs delete mode 100644 mateclaw-desktop/electron-builder.json create mode 100644 mateclaw-desktop/scripts/branding.cjs create mode 100755 mateclaw-desktop/scripts/build-all-platforms.sh create mode 100755 mateclaw-desktop/scripts/build.sh create mode 100755 mateclaw-desktop/scripts/download-jre.sh create mode 100644 mateclaw-desktop/scripts/trim-playwright-driver.cjs diff --git a/mateclaw-desktop/branding.config.json b/mateclaw-desktop/branding.config.json new file mode 100644 index 00000000..f966e865 --- /dev/null +++ b/mateclaw-desktop/branding.config.json @@ -0,0 +1,9 @@ +{ + "name": "MateClaw", + "tagline": "AI Personal Assistant", + "team": "MateClaw Team", + "copyright": "Copyright © 2026 MateClaw Team", + "appId": "vip.mate.mateclaw", + "githubUrl": "https://github.com/matevip/mateclaw", + "logoFile": "mateclaw_logo_s.png" +} diff --git a/mateclaw-desktop/electron-builder.cjs b/mateclaw-desktop/electron-builder.cjs new file mode 100644 index 00000000..cbc2aba6 --- /dev/null +++ b/mateclaw-desktop/electron-builder.cjs @@ -0,0 +1,139 @@ +/** + * electron-builder.cjs — Dynamic build configuration. + * + * Two packaging modes are controlled by the BUILD_MODE environment variable: + * + * BUILD_MODE=local (default) Full build: bundles the embedded JRE and + * Spring Boot JAR so the desktop app can run a + * local backend. Original behavior. + * + * BUILD_MODE=remote Lightweight build: omits the JRE/JAR + * resources (~530 MB smaller on macOS). The app + * can only connect to a remote server — the + * "local" connection option is hidden in the + * splash UI. + * + * Branding is controlled by branding.config.json or BRAND_* env vars. + * See scripts/branding.cjs for details. + * + * Usage: + * BUILD_MODE=remote npx electron-builder --mac + * npm run package:mac:remote + * BRAND_NAME=MyAI npm run package:mac:remote + */ +'use strict' + +const { loadBrandConfig } = require('./scripts/branding.cjs') + +const mode = process.env.BUILD_MODE === 'remote' ? 'remote' : 'local' +const brand = loadBrandConfig(__dirname) + +// Derive a short slug from the brand name for artifact file names. +// "MyAI" → "MyAI", "Cool App" → "Cool_App" +const brandSlug = brand.name.replace(/\s+/g, '_') + +// Parse GitHub URL for publish config (owner/repo) +let githubOwner = 'matevip' +let githubRepo = 'mateclaw' +const ghMatch = brand.githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/) +if (ghMatch) { + githubOwner = ghMatch[1] + githubRepo = ghMatch[2] +} + +/** @type {import('electron-builder').Configuration} */ +const config = { + appId: brand.appId, + productName: brand.name, + copyright: brand.copyright, + directories: { output: 'release' }, + publish: [ + { + provider: 'github', + owner: githubOwner, + repo: githubRepo, + }, + ], + files: ['dist-electron', 'dist'], + afterPack: 'scripts/trim-playwright-driver.cjs', + + // extraResources: only bundle JRE + JAR in local mode. + // In remote mode this array is empty — the packaged app contains only the + // Electron + Vue shell, cutting ~530 MB from the installer. + extraResources: + mode === 'local' + ? [ + { + from: 'resources/jre/${os}-${arch}/', + to: 'jre/', + filter: ['**/*'], + }, + { + from: 'resources/app.jar', + to: 'app.jar', + }, + ] + : [], + + mac: { + category: 'public.app-category.productivity', + target: [ + { target: 'dmg', arch: ['arm64', 'x64'] }, + { target: 'zip', arch: ['arm64', 'x64'] }, + ], + icon: 'build/icon.icns', + hardenedRuntime: true, + gatekeeperAssess: false, + entitlements: 'build/entitlements.mac.plist', + entitlementsInherit: 'build/entitlements.mac.inherit.plist', + // Differentiate installers so users can tell local vs remote builds apart. + artifactName: + mode === 'remote' + ? `${brandSlug}_Remote_${'$'}{version}_${'$'}{arch}.${'$'}{ext}` + : `${brandSlug}_${'$'}{version}_${'$'}{arch}.${'$'}{ext}`, + }, + + dmg: { + contents: [ + { x: 130, y: 220 }, + { x: 410, y: 220, type: 'link', path: '/Applications' }, + ], + title: `${brand.name} ${'$'}{version}`, + }, + + win: { + target: [ + { target: 'nsis', arch: 'x64' }, + { target: 'nsis', arch: 'arm64' }, + ], + icon: 'build/icon.ico', + artifactName: + mode === 'remote' + ? `${brandSlug}_Remote_${'$'}{version}_${'$'}{arch}_Setup.${'$'}{ext}` + : `${brandSlug}_${'$'}{version}_${'$'}{arch}_Setup.${'$'}{ext}`, + }, + + nsis: { + oneClick: false, + perMachine: false, + allowToChangeInstallationDirectory: true, + deleteAppDataOnUninstall: false, + installerIcon: 'build/icon.ico', + uninstallerIcon: 'build/icon.ico', + installerHeaderIcon: 'build/icon.ico', + createDesktopShortcut: true, + createStartMenuShortcut: true, + }, + + linux: { + target: ['AppImage'], + icon: 'build/icon.png', + category: 'Utility', + artifactName: + mode === 'remote' + ? `${brandSlug}_Remote_${'$'}{version}.${'$'}{ext}` + : `${brandSlug}_${'$'}{version}.${'$'}{ext}`, + }, +} + +module.exports = config diff --git a/mateclaw-desktop/electron-builder.json b/mateclaw-desktop/electron-builder.json deleted file mode 100644 index 591e0439..00000000 --- a/mateclaw-desktop/electron-builder.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "appId": "vip.mate.mateclaw", - "productName": "MateClaw", - "copyright": "Copyright © 2026 MateClaw Team", - "directories": { - "output": "release" - }, - "publish": [ - { - "provider": "github", - "owner": "matevip", - "repo": "mateclaw" - } - ], - "files": [ - "dist-electron", - "dist" - ], - "afterPack": "scripts/trim-playwright-driver.cjs", - "extraResources": [ - { - "from": "resources/jre/${os}-${arch}/", - "to": "jre/", - "filter": ["**/*"] - }, - { - "from": "resources/app.jar", - "to": "app.jar" - } - ], - "mac": { - "category": "public.app-category.productivity", - "target": [ - { - "target": "dmg", - "arch": ["arm64", "x64"] - }, - { - "target": "zip", - "arch": ["arm64", "x64"] - } - ], - "icon": "build/icon.icns", - "hardenedRuntime": true, - "gatekeeperAssess": false, - "entitlements": "build/entitlements.mac.plist", - "entitlementsInherit": "build/entitlements.mac.inherit.plist", - "artifactName": "MateClaw_${version}_${arch}.${ext}" - }, - "dmg": { - "contents": [ - { - "x": 130, - "y": 220 - }, - { - "x": 410, - "y": 220, - "type": "link", - "path": "/Applications" - } - ], - "title": "MateClaw ${version}" - }, - "win": { - "target": [ - { - "target": "nsis", - "arch": "x64" - }, - { - "target": "nsis", - "arch": "arm64" - } - ], - "icon": "build/icon.ico", - "artifactName": "MateClaw_${version}_${arch}_Setup.${ext}" - }, - "nsis": { - "oneClick": false, - "perMachine": false, - "allowToChangeInstallationDirectory": true, - "deleteAppDataOnUninstall": false, - "installerIcon": "build/icon.ico", - "uninstallerIcon": "build/icon.ico", - "installerHeaderIcon": "build/icon.ico", - "createDesktopShortcut": true, - "createStartMenuShortcut": true - }, - "linux": { - "target": ["AppImage"], - "icon": "build/icon.png", - "category": "Utility", - "artifactName": "MateClaw_${version}.${ext}" - } -} diff --git a/mateclaw-desktop/electron/main/index.ts b/mateclaw-desktop/electron/main/index.ts index 6d2a5642..16b46fd2 100644 --- a/mateclaw-desktop/electron/main/index.ts +++ b/mateclaw-desktop/electron/main/index.ts @@ -47,6 +47,31 @@ const trustedCertHosts = new Set() // handler, which prompts the user before loading the page. const insecureAgent = new https.Agent({ rejectUnauthorized: false }) +// ─── Build Mode Detection ──────────────────────────────────────────────────── + +/** + * The desktop app ships in two variants: + * + * "local" — bundles the JRE and Spring Boot JAR in extraResources so the + * app can run an embedded backend. This is the traditional full + * build. + * + * "remote" — omits the JRE/JAR (~530 MB lighter). The app can only connect + * to a remote server. The "local" option is hidden in the splash + * connection chooser. + * + * Detection is done at runtime by checking whether the JAR exists in the + * resources directory. This avoids any build-time code injection — the same + * main-process code runs in both builds; the only difference is whether the + * JAR/JRE files are present. + */ +function detectBuildMode(): 'local' | 'remote' { + const jarPath = getJarPath() + return existsSync(jarPath) ? 'local' : 'remote' +} + +const BUILD_MODE = detectBuildMode() + interface UpdaterState { status: 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error' version?: string @@ -131,6 +156,15 @@ function getAvailablePort(): Promise { } async function startJavaBackend(): Promise { + // Remote builds have no bundled JRE/JAR — refuse to start the local backend + // and guide the user toward the connection chooser instead of showing a + // generic "file not found" error. + if (BUILD_MODE === 'remote') { + console.log('[MateClaw] Remote build — local backend unavailable, showing connection chooser') + sendToWindow('backend:status', 'choose') + return + } + BACKEND_PORT = await getAvailablePort() BACKEND_URL = `http://localhost:${BACKEND_PORT}` console.log(`[MateClaw] Using dynamic port: ${BACKEND_PORT}`) @@ -308,6 +342,16 @@ async function bootConnection(): Promise { } const cfg = loadConfig() + + // Remote builds cannot start a local backend. If the user previously + // saved 'local' mode (e.g. they upgraded from a full build), ignore the + // stale preference and fall through to the connection chooser. + if (BUILD_MODE === 'remote' && cfg.mode === 'local') { + connectionMode = null + sendToWindow('backend:status', 'choose') + return + } + if (cfg.mode === 'local') { connectionMode = 'local' await startJavaBackend() @@ -544,6 +588,10 @@ function registerIpcHandlers(): void { ipcMain.handle('app:get-version', () => app.getVersion()) + // Let the renderer know whether this is a local (full) or remote (lite) build + // so the splash can hide the "local" connection option in remote builds. + ipcMain.handle('app:get-build-mode', () => BUILD_MODE) + ipcMain.handle('app:get-backend-url', () => BACKEND_URL) ipcMain.handle('app:is-backend-ready', () => backendReady) @@ -576,6 +624,9 @@ function registerIpcHandlers(): void { servers: cfg.servers, // The renderer shows the chooser on first run or when "Switch Server" forced it. forceChoose: forceChooser, + // Tell the renderer which build variant is running so it can hide the + // "local" option in remote (lite) builds. + buildMode: BUILD_MODE, } }) @@ -584,6 +635,11 @@ function registerIpcHandlers(): void { }) ipcMain.handle('connection:use-local', async () => { + // Remote builds have no bundled JRE/JAR — reject the local mode request. + if (BUILD_MODE === 'remote') { + sendToWindow('backend:crashed', '此版本为轻量版(Remote),不支持本地内嵌后端。请选择连接远程服务器。') + return + } forceChooser = false saveConfig({ mode: 'local' }) connectionMode = 'local' diff --git a/mateclaw-desktop/electron/preload/index.ts b/mateclaw-desktop/electron/preload/index.ts index a940d639..4d9aa26b 100644 --- a/mateclaw-desktop/electron/preload/index.ts +++ b/mateclaw-desktop/electron/preload/index.ts @@ -5,6 +5,7 @@ contextBridge.exposeInMainWorld('mateClawAPI', { // Platform info getPlatform: () => ipcRenderer.invoke('app:get-platform'), getVersion: () => ipcRenderer.invoke('app:get-version'), + getBuildMode: () => ipcRenderer.invoke('app:get-build-mode'), getBackendUrl: () => ipcRenderer.invoke('app:get-backend-url'), isBackendReady: () => ipcRenderer.invoke('app:is-backend-ready'), getUserDataPath: () => ipcRenderer.invoke('app:get-user-data-path'), diff --git a/mateclaw-desktop/package.json b/mateclaw-desktop/package.json index 9bae9283..3b3004c0 100644 --- a/mateclaw-desktop/package.json +++ b/mateclaw-desktop/package.json @@ -14,8 +14,14 @@ "setup": "npm run setup:jar && npm run setup:jre", "setup:all-platforms": "bash scripts/build-all-platforms.sh --all", "package:mac": "npm run build && electron-builder --mac", + "package:mac:local": "npm run build && cross-env BUILD_MODE=local electron-builder --mac", + "package:mac:remote": "npm run build && cross-env BUILD_MODE=remote electron-builder --mac", "package:win": "npm run build && electron-builder --win", + "package:win:local": "npm run build && cross-env BUILD_MODE=local electron-builder --win", + "package:win:remote": "npm run build && cross-env BUILD_MODE=remote electron-builder --win", "package:all": "bash scripts/build-all-platforms.sh --all", + "package:all:local": "bash scripts/build-all-platforms.sh --local", + "package:all:remote": "bash scripts/build-all-platforms.sh --remote", "publish:github": "bash scripts/publish-github.sh", "publish:github:draft": "bash scripts/publish-github.sh --draft" }, @@ -25,6 +31,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", + "cross-env": "^10.1.0", "electron": "^33.3.1", "electron-builder": "^25.1.8", "typescript": "^5.7.3", @@ -32,5 +39,11 @@ "vite-plugin-electron": "^0.28.8", "vite-plugin-electron-renderer": "^0.14.6", "vue-tsc": "^2.2.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "electron", + "esbuild" + ] } } diff --git a/mateclaw-desktop/pnpm-lock.yaml b/mateclaw-desktop/pnpm-lock.yaml index 7d48e40b..e80b9b6e 100644 --- a/mateclaw-desktop/pnpm-lock.yaml +++ b/mateclaw-desktop/pnpm-lock.yaml @@ -8,13 +8,19 @@ importers: .: dependencies: + electron-updater: + specifier: ^6.3.9 + version: 6.8.9 vue: specifier: ^3.5.13 version: 3.5.31(typescript@5.9.3) devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3)) + version: 5.2.4(vite@6.4.3(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3)) + cross-env: + specifier: ^10.1.0 + version: 10.1.0 electron: specifier: ^33.3.1 version: 33.4.11 @@ -26,13 +32,13 @@ importers: version: 5.9.3 vite: specifier: ^6.0.7 - version: 6.4.1(@types/node@25.5.0) + version: 6.4.3(@types/node@25.5.0) vite-plugin-electron: specifier: ^0.28.8 - version: 0.28.8(vite-plugin-electron-renderer@0.14.6) + version: 0.28.8(vite-plugin-electron-renderer@0.14.7) vite-plugin-electron-renderer: specifier: ^0.14.6 - version: 0.14.6 + version: 0.14.7 vue-tsc: specifier: ^2.2.0 version: 2.2.12(typescript@5.9.3) @@ -90,6 +96,9 @@ packages: resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} engines: {node: '>=16.4'} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -673,6 +682,10 @@ packages: resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} engines: {node: '>=12.0.0'} + builder-util-runtime@9.7.0: + resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==} + engines: {node: '>=12.0.0'} + builder-util@25.1.7: resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} @@ -788,6 +801,11 @@ packages: crc@3.8.0: resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -883,6 +901,9 @@ packages: electron-publish@25.1.7: resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + electron-updater@6.8.9: + resolution: {integrity: sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==} + electron@33.4.11: resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==} engines: {node: '>= 12.20.55'} @@ -1046,6 +1067,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1205,8 +1230,8 @@ packages: engines: {node: '>=10'} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true json-buffer@3.0.1: @@ -1245,9 +1270,16 @@ packages: lodash.difference@4.5.0: resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + lodash.flatten@4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -1703,6 +1735,9 @@ packages: temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + tiny-typed-emitter@2.1.0: + resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -1761,8 +1796,8 @@ packages: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} - vite-plugin-electron-renderer@0.14.6: - resolution: {integrity: sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==} + vite-plugin-electron-renderer@0.14.7: + resolution: {integrity: sha512-hHBMKuZ24MB2SIxG7U7ix+DDEnvxou7Bgy/TdhYxNz3S5N3Yh7Hjvj9blfMeGEJ0oaZJn7y5z0V/RyDmJ5OuCA==} vite-plugin-electron@0.28.8: resolution: {integrity: sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==} @@ -1772,8 +1807,8 @@ packages: vite-plugin-electron-renderer: optional: true - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -1974,6 +2009,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -2225,9 +2262,9 @@ snapshots: '@types/node': 20.19.37 optional: true - '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.4.3(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))': dependencies: - vite: 6.4.1(@types/node@25.5.0) + vite: 6.4.3(@types/node@25.5.0) vue: 3.5.31(typescript@5.9.3) '@volar/language-core@2.4.15': @@ -2387,7 +2424,7 @@ snapshots: hosted-git-info: 4.1.0 is-ci: 3.0.1 isbinaryfile: 5.0.7 - js-yaml: 4.1.1 + js-yaml: 4.2.0 json5: 2.2.3 lazy-val: 1.0.5 minimatch: 10.2.5 @@ -2404,7 +2441,7 @@ snapshots: archiver-utils@2.1.0: dependencies: - glob: 7.2.3 + glob: 7.2.0 graceful-fs: 4.2.11 lazystream: 1.0.1 lodash.defaults: 4.2.0 @@ -2509,6 +2546,13 @@ snapshots: transitivePeerDependencies: - supports-color + builder-util-runtime@9.7.0: + dependencies: + debug: 4.4.3 + sax: 1.6.0 + transitivePeerDependencies: + - supports-color + builder-util@25.1.7: dependencies: 7zip-bin: 5.2.0 @@ -2523,7 +2567,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-ci: 3.0.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 source-map-support: 0.5.21 stat-mode: 1.0.0 temp-file: 3.4.0 @@ -2656,6 +2700,11 @@ snapshots: buffer: 5.7.1 optional: true + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2715,7 +2764,7 @@ snapshots: builder-util-runtime: 9.2.10 fs-extra: 10.1.0 iconv-lite: 0.6.3 - js-yaml: 4.1.1 + js-yaml: 4.2.0 optionalDependencies: dmg-license: 1.0.11 transitivePeerDependencies: @@ -2793,6 +2842,19 @@ snapshots: transitivePeerDependencies: - supports-color + electron-updater@6.8.9: + dependencies: + builder-util-runtime: 9.7.0 + fs-extra: 10.1.0 + js-yaml: 4.2.0 + lazy-val: 1.0.5 + lodash.escaperegexp: 4.1.2 + lodash.isequal: 4.5.0 + semver: 7.7.4 + tiny-typed-emitter: 2.1.0 + transitivePeerDependencies: + - supports-color + electron@33.4.11: dependencies: '@electron/get': 2.0.3 @@ -3000,6 +3062,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@7.2.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3175,7 +3246,7 @@ snapshots: filelist: 1.0.6 picocolors: 1.1.1 - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 @@ -3212,8 +3283,12 @@ snapshots: lodash.difference@4.5.0: {} + lodash.escaperegexp@4.1.2: {} + lodash.flatten@4.4.0: {} + lodash.isequal@4.5.0: {} + lodash.isplainobject@4.0.6: {} lodash.union@4.6.0: {} @@ -3706,6 +3781,8 @@ snapshots: async-exit-hook: 2.0.1 fs-extra: 10.1.0 + tiny-typed-emitter@2.1.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -3757,13 +3834,13 @@ snapshots: extsprintf: 1.4.1 optional: true - vite-plugin-electron-renderer@0.14.6: {} + vite-plugin-electron-renderer@0.14.7: {} - vite-plugin-electron@0.28.8(vite-plugin-electron-renderer@0.14.6): + vite-plugin-electron@0.28.8(vite-plugin-electron-renderer@0.14.7): optionalDependencies: - vite-plugin-electron-renderer: 0.14.6 + vite-plugin-electron-renderer: 0.14.7 - vite@6.4.1(@types/node@25.5.0): + vite@6.4.3(@types/node@25.5.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) diff --git a/mateclaw-desktop/scripts/branding.cjs b/mateclaw-desktop/scripts/branding.cjs new file mode 100644 index 00000000..35aa5a39 --- /dev/null +++ b/mateclaw-desktop/scripts/branding.cjs @@ -0,0 +1,153 @@ +/** + * scripts/branding.cjs — Vite plugin for build-time white-label branding. + * + * Reads brand settings from branding.config.json (or BRAND_* env overrides) + * and replaces hardcoded "MateClaw" strings in all built files — source code + * stays untouched. + * + * Supported env overrides: + * BRAND_NAME, BRAND_TAGLINE, BRAND_TEAM, BRAND_COPYRIGHT, + * BRAND_APP_ID, BRAND_GITHUB_URL, BRAND_LOGO_FILE + */ +'use strict' + +const fs = require('fs') +const path = require('path') + +function loadBrandConfig(rootDir) { + const configPath = path.join(rootDir, 'branding.config.json') + let config = {} + if (fs.existsSync(configPath)) { + config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + } + + // Env vars override the config file. + const env = process.env + return { + name: env.BRAND_NAME || config.name || 'MateClaw', + tagline: env.BRAND_TAGLINE || config.tagline || 'AI Personal Assistant', + team: env.BRAND_TEAM || config.team || 'MateClaw Team', + copyright: env.BRAND_COPYRIGHT || config.copyright || 'Copyright © 2026 MateClaw Team', + appId: env.BRAND_APP_ID || config.appId || 'vip.mate.mateclaw', + githubUrl: env.BRAND_GITHUB_URL || config.githubUrl || 'https://github.com/matevip/mateclaw', + logoFile: env.BRAND_LOGO_FILE || config.logoFile || 'mateclaw_logo_s.png', + } +} + +/** + * Build the string-replacement table. + * + * Order matters: longer/more-specific patterns are replaced first to avoid + * partial matches (e.g. "MateClaw Team" before "MateClaw"). + */ +function buildReplacements(brand) { + const replacements = [] + + // 1. Copyright line (most specific) + replacements.push([ + 'Copyright © 2026 MateClaw Team', + brand.copyright, + ]) + + // 2. Team name + replacements.push(['MateClaw Team', brand.team]) + + // 3. GitHub URLs + replacements.push([ + 'https://github.com/matevip/mateclaw/issues', + brand.githubUrl + '/issues', + ]) + replacements.push([ + 'https://github.com/matevip/mateclaw', + brand.githubUrl, + ]) + + // 4. Logo file path + replacements.push([ + 'mateclaw_logo_s.png', + brand.logoFile, + ]) + + // 5. Tagline + replacements.push([ + 'AI Personal Assistant', + brand.tagline, + ]) + + // 6. Split-span brand name in App.vue template: + // MateClaw + // Replace the inner text so styling classes are preserved but the text + // changes. We split the brand name: first half gets "mate" class, second + // half gets "claw" class. If it's a single word, it all goes in "mate". + var half = Math.ceil(brand.name.length / 2) + var firstPart = brand.name.slice(0, half) + var secondPart = brand.name.slice(half) + replacements.push([ + '>MateClaw<', + '>' + firstPart + '' + secondPart + '<', + ]) + + // 7. Brand name (catch-all, must come last) + // Only replace the exact word "MateClaw", not "mateclaw" (lowercase, + // which is used in H2 database paths and Spring Boot properties that + // are coupled with the server and must NOT change). + replacements.push(['MateClaw', brand.name]) + + return replacements +} + +function applyReplacements(code, replacements) { + var result = code + for (var i = 0; i < replacements.length; i++) { + var from = replacements[i][0] + var to = replacements[i][1] + // Use split/join for reliable literal string replacement (no regex + // escaping issues). + result = result.split(from).join(to) + } + return result +} + +/** + * Vite plugin entry point. + * + * Usage in vite.config.ts: + * import { brandingPlugin } from './scripts/branding.cjs' + * plugins: [brandingPlugin()] + */ +function brandingPlugin(options) { + options = options || {} + var rootDir = options.rootDir || process.cwd() + var brand = loadBrandConfig(rootDir) + var replacements = buildReplacements(brand) + + var isDefault = + brand.name === 'MateClaw' && + brand.tagline === 'AI Personal Assistant' && + brand.team === 'MateClaw Team' + + if (!isDefault) { + console.log('[branding] White-label build: "' + brand.name + '" (tagline: "' + brand.tagline + '")') + } + + return { + name: 'mateclaw-branding', + enforce: 'pre', + + // Transform JS/TS/Vue source before compilation + transform: function (code, id) { + if (id.indexOf('node_modules') !== -1) return null + // Only process source files that might contain brand strings. + if (!/\.(ts|js|vue|html|css|cjs|mjs)$/.test(id)) return null + var result = applyReplacements(code, replacements) + return result !== code ? { code: result, map: null } : null + }, + + // Transform index.html + transformIndexHtml: function (html) { + return applyReplacements(html, replacements) + }, + } +} + +module.exports = { brandingPlugin: brandingPlugin, loadBrandConfig: loadBrandConfig, buildReplacements: buildReplacements } diff --git a/mateclaw-desktop/scripts/build-all-platforms.sh b/mateclaw-desktop/scripts/build-all-platforms.sh new file mode 100755 index 00000000..14f388eb --- /dev/null +++ b/mateclaw-desktop/scripts/build-all-platforms.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# scripts/build-all-platforms.sh — Build MateClaw desktop packages for all +# platforms (macOS + Windows) in the specified build mode. +# +# Usage: +# scripts/build-all-platforms.sh --all # local mode (default), both platforms +# scripts/build-all-platforms.sh --local # local mode, both platforms +# scripts/build-all-platforms.sh --remote # remote mode, both platforms +# +set -euo pipefail + +MODE="--all" +case "${1:-}" in + --all) MODE="local" ;; + --local) MODE="local" ;; + --remote) MODE="remote" ;; + *) echo "Usage: $0 [--all|--local|--remote]"; exit 1 ;; +esac + +echo "==> Building all platforms, BUILD_MODE=$MODE" + +if [ "$MODE" = "remote" ]; then + echo "==> macOS (remote/lite)" + BUILD_MODE=remote npx electron-builder --mac + echo "==> Windows (remote/lite)" + BUILD_MODE=remote npx electron-builder --win +else + echo "==> macOS (local/full)" + BUILD_MODE=local npx electron-builder --mac + echo "==> Windows (local/full)" + BUILD_MODE=local npx electron-builder --win +fi + +echo "==> All builds complete." diff --git a/mateclaw-desktop/scripts/build.sh b/mateclaw-desktop/scripts/build.sh new file mode 100755 index 00000000..35d6ee73 --- /dev/null +++ b/mateclaw-desktop/scripts/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# scripts/build.sh — Build the MateClaw Spring Boot backend JAR and place it +# at resources/app.jar so electron-builder can bundle it into the desktop app. +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +SERVER_DIR="$(cd "$PROJECT_ROOT/../mateclaw-server" && pwd)" +RESOURCES_DIR="$PROJECT_ROOT/resources" + +echo "==> Building mateclaw-server JAR from $SERVER_DIR" + +# Build the Spring Boot fat JAR (skip tests for packaging speed) +cd "$SERVER_DIR" +mvn clean package -DskipTests -Dmaven.test.skip=true -q + +# Locate the built JAR +JAR_FILE=$(ls "$SERVER_DIR"/target/mateclaw-server-*.jar 2>/dev/null | head -1) +if [ -z "$JAR_FILE" ]; then + echo "ERROR: Could not find built JAR in $SERVER_DIR/target/" + exit 1 +fi + +echo "==> Copying $JAR_FILE → $RESOURCES_DIR/app.jar" +mkdir -p "$RESOURCES_DIR" +cp "$JAR_FILE" "$RESOURCES_DIR/app.jar" + +echo "==> Done. JAR size: $(du -h "$RESOURCES_DIR/app.jar" | cut -f1)" diff --git a/mateclaw-desktop/scripts/download-jre.sh b/mateclaw-desktop/scripts/download-jre.sh new file mode 100755 index 00000000..5fca8087 --- /dev/null +++ b/mateclaw-desktop/scripts/download-jre.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# +# scripts/download-jre.sh — Download Eclipse Temurin JRE 21 for the current +# macOS architecture (or both) and extract into resources/jre//. +# +# Usage: +# scripts/download-jre.sh # auto-detect current arch +# scripts/download-jre.sh arm64 # arm64 only +# scripts/download-jre.sh x64 # x64 only +# scripts/download-jre.sh all # both arches +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +JRE_DIR="$PROJECT_ROOT/resources/jre" + +# Temurin 21 (LTS) JRE downloads via Adoptium API +ADOPTIUM_BASE="https://api.adoptium.net/v3/binary/latest/21/ga/mac" + +download_and_extract() { + local arch="$1" + local folder="$2" + local url="$ADOPTIUM_BASE/$arch/jre/hotspot/normal/eclipse?project=jdk" + local tmpfile="$JRE_DIR/jre-mac-$arch.tar.gz" + + echo "==> Downloading Temurin 21 JRE for macOS $arch" + mkdir -p "$JRE_DIR" + curl -L --fail -o "$tmpfile" "$url" + + echo "==> Extracting to $JRE_DIR/$folder" + rm -rf "$JRE_DIR/$folder" + mkdir -p "$JRE_DIR/$folder" + + # Temurin macOS tar.gz extracts to: jdk-21.x.x+jre/Contents/Home/... + # We want $folder/Contents/Home/... so move the inner Contents up. + local extract_tmp="$JRE_DIR/.tmp-$arch" + rm -rf "$extract_tmp" + mkdir -p "$extract_tmp" + tar -xzf "$tmpfile" -C "$extract_tmp" + + # Find the extracted top-level directory and move its Contents + local extracted_dir + extracted_dir=$(find "$extract_tmp" -maxdepth 1 -type d -name "jdk-*" | head -1) + if [ -z "$extracted_dir" ]; then + # Fallback: some tarballs extract Contents directly + if [ -d "$extract_tmp/Contents" ]; then + mv "$extract_tmp/Contents" "$JRE_DIR/$folder/Contents" + else + echo "ERROR: Could not find extracted JDK directory" + exit 1 + fi + else + mv "$extracted_dir/Contents" "$JRE_DIR/$folder/Contents" + fi + + rm -rf "$extract_tmp" "$tmpfile" + + # Verify java binary exists + local java_bin="$JRE_DIR/$folder/Contents/Home/bin/java" + if [ -f "$java_bin" ]; then + echo "==> OK: $java_bin" + else + echo "ERROR: java binary not found at $java_bin" + exit 1 + fi +} + +TARGET="${1:-auto}" +if [ "$TARGET" = "auto" ]; then + case "$(uname -m)" in + arm64) TARGET="arm64" ;; + x86_64) TARGET="x64" ;; + *) echo "Unsupported arch: $(uname -m)"; exit 1 ;; + esac +fi + +case "$TARGET" in + arm64) download_and_extract "aarch64" "mac-arm64" ;; + x64) download_and_extract "x64" "mac-x64" ;; + all) download_and_extract "aarch64" "mac-arm64" + download_and_extract "x64" "mac-x64" ;; + *) echo "Usage: $0 [arm64|x64|all]"; exit 1 ;; +esac + +echo "==> JRE setup complete." diff --git a/mateclaw-desktop/scripts/trim-playwright-driver.cjs b/mateclaw-desktop/scripts/trim-playwright-driver.cjs new file mode 100644 index 00000000..56f351b2 --- /dev/null +++ b/mateclaw-desktop/scripts/trim-playwright-driver.cjs @@ -0,0 +1,6 @@ +// afterPack hook: no-op placeholder +// The original trim-playwright-driver.cjs was not included in the open-source release. +// This no-op allows electron-builder to complete packaging. +exports.default = async function () { + // intentionally empty +} diff --git a/mateclaw-desktop/src/App.vue b/mateclaw-desktop/src/App.vue index 82766f3a..1d38cc1f 100644 --- a/mateclaw-desktop/src/App.vue +++ b/mateclaw-desktop/src/App.vue @@ -16,6 +16,8 @@ const remoteUrlInput = ref('') const testing = ref(false) const testResult = ref<{ ok: boolean; msg: string } | null>(null) const recentServers = ref([]) +// Build variant: 'local' = full (bundles JRE+JAR), 'remote' = lite (connect to remote server only) +const buildMode = ref<'local' | 'remote'>('local') let BACKEND_URL = '' @@ -235,7 +237,21 @@ onMounted(async () => { const cfg = await window.mateClawAPI.getConnectionConfig() recentServers.value = cfg.servers || [] remoteUrlInput.value = cfg.remoteUrl || '' - if (cfg.forceChoose || !cfg.mode) { + buildMode.value = cfg.buildMode || 'local' + + // Remote (lite) builds: skip the mode chooser, go straight to the + // remote server form — the "local" option is not available. + if (buildMode.value === 'remote') { + if (cfg.forceChoose || !cfg.mode || cfg.mode === 'local') { + status.value = 'connection-select' + connView.value = 'remote-form' + } else { + connectionMode.value = cfg.mode + if (await window.mateClawAPI.isBackendReady()) { + handleBackendReady() + } + } + } else if (cfg.forceChoose || !cfg.mode) { status.value = 'connection-select' connView.value = 'choose' } else { @@ -325,7 +341,11 @@ function createParticles() { @@ -900,6 +924,16 @@ function createParticles() { font-size: 11px; color: var(--text-tertiary); } +.remote-build-notice { + font-size: 11px; + color: var(--text-tertiary); + text-align: center; + padding: 8px 12px; + border-radius: 8px; + background: var(--bg-elevated); + border: 1px solid var(--border); + width: 100%; +} /* ══════════════════════════════════════════════════ Remote Connection Form diff --git a/mateclaw-desktop/src/env.d.ts b/mateclaw-desktop/src/env.d.ts index 0bc9d5a8..cd290465 100644 --- a/mateclaw-desktop/src/env.d.ts +++ b/mateclaw-desktop/src/env.d.ts @@ -25,6 +25,7 @@ interface ConnectionConfigState { remoteUrl: string servers: RemoteServer[] forceChoose: boolean + buildMode: 'local' | 'remote' } interface ConnectionTestResult { @@ -36,6 +37,7 @@ interface ConnectionTestResult { interface MateClawAPI { getPlatform: () => Promise getVersion: () => Promise + getBuildMode: () => Promise<'local' | 'remote'> getBackendUrl: () => Promise isBackendReady: () => Promise getUserDataPath: () => Promise diff --git a/mateclaw-desktop/vite.config.ts b/mateclaw-desktop/vite.config.ts index 65bf5e87..5dd690f7 100644 --- a/mateclaw-desktop/vite.config.ts +++ b/mateclaw-desktop/vite.config.ts @@ -3,14 +3,24 @@ import vue from '@vitejs/plugin-vue' import electron from 'vite-plugin-electron' import renderer from 'vite-plugin-electron-renderer' import { resolve } from 'path' +import { brandingPlugin } from './scripts/branding.cjs' export default defineConfig(({ command }) => { const isServe = command === 'serve' const isBuild = command === 'build' + // Shared branding plugin instance — applied to the renderer build as well + // as the electron main/preload builds so brand strings are replaced + // everywhere without touching source code. + const brand = brandingPlugin() + return { plugins: [ vue(), + // White-label branding: replaces "MateClaw" with the configured brand + // name at build time. Source code stays untouched. Configure via + // branding.config.json or BRAND_* env vars. + brand, electron([ { entry: 'electron/main/index.ts', @@ -18,6 +28,7 @@ export default defineConfig(({ command }) => { args.startup() }, vite: { + plugins: [brand], build: { sourcemap: isServe, minify: isBuild, @@ -34,6 +45,7 @@ export default defineConfig(({ command }) => { args.reload() }, vite: { + plugins: [brand], build: { sourcemap: isServe ? 'inline' : undefined, minify: isBuild,