DeepSeek Harness 桌面端这个项目,核心不是做一个简单聊天窗口,而是把 DeepSeek 的 API 调用、会话管理、提示词模板、工具调用和本地配置整合成一套可安装的桌面工具。当功能开发接近稳定后,真正花时间的往往不是功能本身,而是如何把这套代码变成 Windows 或 macOS 上的一键安装包。
在这篇文章里,我会从实际项目思路出发,讲清楚桌面端 DeepSeek Harness 为什么值得做、如何搭建一个能够“自举打包”的工程骨架、怎样在应用内部实现“自己打包自己”的构建模块,以及如何用 electron-builder 配置出真正的一键安装版本。后面会给出可以直接复用的配置、代码、验证清单和排错路径,适合正在做桌面端 AI 工具、或者想把本地项目发布成安装包的开发者参考。
1. 先理解 DeepSeek Harness 桌面端要解决的问题
1.1 harness 在 DeepSeek 场景下到底意味着什么
直接调用 DeepSeek API 其实不复杂,一次 HTTP POST 就能拿到返回内容。但在真实项目里,单轮对话、多轮上下文、系统提示词、工具函数、重试策略、流式输出、日志记录、模型参数管理,这些东西如果全部散落在业务代码里,项目很快会失控。
Harness 的直观含义是“控制台”或“驾驭层”。放到 DeepSeek 场景里,它是一层专门负责编排模型调用的工程化封装:把请求参数、上下文策略、提示词模板、工具注册、结果解析和异常处理集中管理。桌面端 DeepSeek Harness 不是简单把网页版套个壳,而是希望把模型能力变成用户可以本地管理的生产力工具。
桌面端天然适合这类 Harness。聊天历史可以存在本地文件,常用的 prompt 模板可以随时加载,工具函数可以对接本地文件系统、剪切板、定时任务等能力。浏览器环境里这些能力要么受权限限制,要么操作起来很别扭。
1.2 桌面端和纯 Web 端的关键差异
很多 AI 工具先把 Web 端做出来,再考虑桌面端。但 DeepSeek Harness 桌面端有几个纯 Web 端不好替代的价值。
| 维度 | Web 端 | 桌面端 |
|---|---|---|
| 本地文件访问 | 受浏览器沙箱限制 | 可以直接读取、写入项目目录或配置文件 |
| 会话持久化 | 依赖 LocalStorage 或后端存储 | 可以保存为本地 JSON、SQLite 或 Markdown |
| 系统集成 | 很难 | 可以注册系统托盘、全局快捷键、开机启动 |
| API Key 管理 | 容易暴露在浏览器环境 | 可以放主进程或系统钥匙串,安全性更高 |
| 发布安装 | 部署在服务器 | 生成安装包,分发给个人或团队使用 |
| 离线能力 | 受限 | 可以缓存模板和会话,模型调用之外的部分可以离线工作 |
这也是为什么体验过桌面端之后,会明显感受到它比浏览器标签页更适合作“日常生产力入口”。
1.3 技术选型:Electron、Tauri 和 Python 桌面方案的取舍
构建桌面端 DeepSeek Harness,技术栈选择直接决定打包方式、包体积和你后续维护成本。
| 方案 | 开发语言 | 安装包体积 | 生态成熟度 | 最适合场景 |
|---|---|---|---|---|
| Electron | JavaScript / TypeScript | 较大,通常在 80MB 以上 | 最成熟,示例多 | AI 工具、跨平台桌面客户端 |
| Tauri | Rust + Web 前端 | 小,几 MB 到几十 MB | 快速成熟,但 Rust 学习成本高 | 对体积敏感、希望低内存占用的场景 |
| PySide / PyWebview | Python | 中等 | 中等,依赖管理较麻烦 | 已经有 Python 代码资产的项目 |
从“一键安装版”这个目标来看,Electron + electron-builder 是目前最稳妥的组合。electron-builder 能直接产出 Windows 的 NSIS 安装包、macOS 的 dmg 和 Linux 的 AppImage,配置项覆盖图标、快捷方式、安装目录、卸载行为,并且有大量资料可以排查。下面所有示例都基于这个技术栈,但“自己打包自己”的思路同样适用于 Tauri 或 Python 项目。
2. 搭起一个可以自我打包的桌面端骨架
“自己打包自己”并不是一个凭空出现的需求。实际场景是:你维护一个 DeepSeek Harness 桌面端项目,每次改完代码都要在终端里手敲npm run dist,然后打开 release 目录把安装包拖出来测试。这个过程重复几次后,自然会想把这些操作做成应用内的一个功能按钮。
实现这个功能的前提是工程骨架清晰,主进程、渲染进程、预加载脚本各司其职。
2.1 环境准备与版本要求
在开始写代码前,先确认机器环境。下面的版本是常见可用组合,实际项目落地前要按自己的 Node 版本和依赖版本再核对一次。
| 依赖 | 版本建议 | 说明 |
|---|---|---|
| Node.js | 18.x 或 20.x LTS | Electron 和 electron-builder 在 LTS 上表现最稳定 |
| npm | 9.x 或 10.x | 跟随 Node 版本即可 |
| electron | 28.x 及以上 | 新版本对 ASAR 和打包支持更好 |
| electron-builder | 24.x 及以上 | 一键安装版配置都在这一层完成 |
建议提前配置 Electron 二进制镜像,否则首次npm install时下载 Electron 可能很慢。在项目根目录新建.npmrc:
electron_mirror=https://npmmirror.com/mirrors/electron/ electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/这里设置镜像不会改变业务逻辑,只影响安装速度和稳定性。国内网络环境如果不配置镜像,Electron 安装失败的概率会高很多。
2.2 项目目录结构设计
为了让“自己打包自己”的构建模块不乱,建议目录结构保持清晰:
deepseek-harness-desktop/ ├── package.json ├── electron-builder.yml ├── .npmrc ├── build/ │ ├── icon.ico │ └── icon.png ├── src/ │ ├── main/ │ │ ├── index.js │ │ ├── buildCenter.js │ │ └── proxy.js │ ├── preload/ │ │ └── index.js │ └── renderer/ │ ├── index.html │ ├── renderer.js │ └── styles.css └── release/ └── 构建产物输出目录常用目录职责如下:
| 目录/文件 | 职责 |
|---|---|
src/main/index.js | Electron 主进程入口,创建窗口、管理生命周期 |
src/main/buildCenter.js | 自举构建模块,负责在应用内执行打包命令 |
src/main/proxy.js | DeepSeek API 请求代理,避免在渲染进程暴露 API Key |
src/preload/index.js | 通过 contextBridge 暴露安全接口给渲染进程 |
src/renderer/ | 前端界面,包含对话、设置、构建中心等面板 |
electron-builder.yml | 打包配置,决定一键安装版行为 |
2.3 主进程、预加载脚本和渲染进程的最小实现
先实现一个最精简的 Electron 入口。目的不是实现完整功能,而是让项目具备“启动、通信、打包验证”的基础能力。
src/main/index.js:
const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); function createWindow() { const win = new BrowserWindow({ width: 1080, height: 720, webPreferences: { preload: path.join(__dirname, '../preload/index.js'), contextIsolation: true, nodeIntegration: false, }, }); // 开发环境加载本地调试服务器,生产环境加载打包后的 HTML if (process.env.VITE_DEV_SERVER_URL) { win.loadURL(process.env.VITE_DEV_SERVER_URL); } else { win.loadFile(path.join(__dirname, '../renderer/index.html')); } } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });src/preload/index.js:
const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('harness', { startBuild: (options) => ipcRenderer.invoke('build:start', options), onBuildLog: (callback) => ipcRenderer.on('build:log', (_event, line) => callback(line)), getAppInfo: () => ipcRenderer.invoke('app:info'), });src/renderer/index.html只需要一个核心界面框架,后面构建中心面板也挂在里面:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>DeepSeek Harness Desktop</title> </head> <body> <div id="app"> <h1>DeepSeek Harness Desktop</h1> <button id="buildBtn">生成一键安装包</button> <pre id="buildLog"></pre> </div> <script src="./renderer.js"></script> </body> </html>这里有一个容易理解错的地方:不是所有代码都能放在渲染进程里。由于contextIsolation设置为true,渲染进程只能通过window.harness调用主进程能力,不能直接访问 Node.js API。这个隔离设计在后面做“自举打包”时非常重要,因为构建命令属于高风险操作,必须由主进程控制。
3. 让应用自己打包自己:自举构建模块的实现
3.1 “自己打包自己”是什么意思,边界在哪里
“自己打包自己”在工程领域也叫自举构建。更直白的理解是:应用内部内置一个“构建中心”面板,你不需要打开终端敲命令,直接在应用界面里点击按钮,应用就把当前源码目录中的项目打包成安装程序。
这个功能适合以下几类场景:
- 个人工具,同一个项目在开发机和实际使用机上都需要频繁更新。
- 团队内部使用,非技术人员拿到安装包后不需要理解 npm 和 electron-builder。
- 自己需要同时维护多个平台版本,比如 Windows 和 macOS 各打一个安装包。
需要明确边界:这个功能只负责“构建你正在运行的这个项目”,不负责执行任意命令。构建命令是写死的,用户只能选择平台和版本号。这样既能实现“一键打包”,又不会把应用变成一个暴露命令执行接口的安全漏洞。
3.2 在应用内触发构建命令
自举构建的核心代码放在src/main/buildCenter.js。它要做三件事:
- 接收渲染进程发来的构建请求。
- 用
child_process.spawn执行npm run dist,并把 stdout 和 stderr 实时回传给渲染进程。 - 构建结束后返回安装包路径和产物大小。
实现代码:
const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const { app, ipcMain } = require('electron'); function registerBuildCenter() { ipcMain.handle('build:start', async (_event, options = {}) => { const isPackaged = app.isPackaged; const allowSelfBuild = process.argv.includes('--enable-self-build') || !isPackaged; if (!allowSelfBuild) { throw new Error('当前环境不允许执行自举构建'); } const projectRoot = app.getAppPath(); const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const args = ['run', 'dist', '--', '--publish', 'never']; if (options.win !== false && process.platform === 'win32') { args.push('--win'); } if (options.mac && process.platform === 'darwin') { args.push('--mac'); } return new Promise((resolve, reject) => { const child = spawn(npmCommand, args, { cwd: projectRoot, shell: true, env: process.env, }); let output = ''; child.stdout.on('data', (chunk) => { const line = chunk.toString(); output += line; _event.sender.send('build:log', line); }); child.stderr.on('data', (chunk) => { const line = chunk.toString(); output += line; _event.sender.send('build:log', line); }); child.on('close', (code) => { const releaseDir = path.join(projectRoot, 'release'); let artifacts = []; if (fs.existsSync(releaseDir)) { artifacts = fs.readdirSync(releaseDir).filter((f) => !f.endsWith('.yml')); } if (code === 0) { resolve({ success: true, output, artifacts }); } else { reject(new Error(`构建失败,退出码 ${code}`)); } }); child.on('error', (err) => { reject(err); }); }); }); } module.exports = { registerBuildCenter };这段代码里的关键点:
| 关键点 | 说明 |
|---|---|
app.isPackaged | 判断当前是否运行在打包后的安装版环境中 |
--enable-self-build | 显式开启自举构建的开关,避免误触发 |
--publish never | 构建时不尝试上传发布,避免 electron-builder 等待发布配置 |
npm.cmd判断 | Windows 下必须使用npm.cmd,否则 spawn 找不到命令 |
cwd 指向app.getAppPath() | 项目源码根目录是构建上下文 |
有一点值得注意:生产安装版默认不允许自举构建,因为安装后的 asar 包内不可写,而且你在用户机器上触发构建本身没有意义。这个功能更适合“源码运行模式”下使用。后面的安全小节会展开说明。
3.3 安全限制:为什么只在开发模式开启自举
“应用内执行命令行”是一个典型的高风险能力。如果被恶意利用,攻击者可以让应用执行任意命令。所以自举构建模块必须满足以下安全条件:
- 构建命令固定,不接受用户传入的可执行命令字符串。
- 默认只在
app.isPackaged === false时开启;安装版需要显式加--enable-self-build参数才允许执行。 - 构建输出目录限制在当前项目 release 目录,不能通过参数穿越到其他路径。
- 渲染进程只能通过 IPC 发起构建请求,无法直接修改主进程中的执行逻辑。
建议在应用界面里明确提示当前是源码模式还是安装包模式。渲染进程可以拿到这个状态,并决定是否显示“生成安装包”按钮:
async function checkBuildPermission() { const appInfo = await window.harness.getAppInfo(); const canBuild = appInfo.isPackaged === false; document.getElementById('buildBtn').disabled = !canBuild; }对应的app:info处理可以放在主进程入口:
ipcMain.handle('app:info', () => { return { isPackaged: app.isPackaged, version: app.getVersion(), platform: process.platform, arch: process.arch, }; });这个设计既能解决“自己打包自己”的真实需求,又不会把应用本身变成不安全的命令执行器。
4. 配置一键安装版:electron-builder 与 NSIS 实战
4.1 electron-builder 配置文件拆解
electron-builder 支持把配置写在package.json的build字段中,也支持独立electron-builder.yml。推荐用独立 YAML 文件,配置更清晰,不会被 package.json 里的其他字段干扰。
项目根目录新建electron-builder.yml:
appId: com.example.deepseekharness productName: DeepSeekHarness directories: output: release buildResources: build files: - src/**/* - package.json asar: true win: target: - target: nsis arch: - x64 icon: build/icon.ico nsis: oneClick: true perMachine: false allowElevation: true allowToChangeInstallationDirectory: false createDesktopShortcut: true createStartMenuShortcut: true shortcutName: DeepSeek Harness artifactName: DeepSeekHarness-${version}-Setup.${ext} mac: target: - dmg icon: build/icon.icns配置文件里真正决定“一键安装”体验的是nsis段落。下面详细解释每个参数。
4.2 一键安装包的关键参数
| 参数 | 值 | 含义 | 错误配置表现 |
|---|---|---|---|
oneClick | true | 点击安装包后直接进入安装流程,不显示复杂向导 | 设为false会变成传统多步安装向导 |
perMachine | false | 只安装到当前用户目录,不需要管理员权限 | 设为true会弹 UAC 管理员授权 |
allowElevation | true | 允许安装时提升权限 | 如果 perMachine 为 true 但这里关闭,安装会失败 |
allowToChangeInstallationDirectory | false | 不显示目录选择页面 | 设为true后会多一步选择安装路径 |
createDesktopShortcut | true | 创建桌面快捷方式 | 设为false后用户可能找不到入口 |
artifactName | DeepSeekHarness-${version}-Setup.${ext} | 控制安装包文件名 | 不设置会生成默认产品名,区分度低 |
这里有一个常见取舍:oneClick: true的安装包只能安装到默认目录,普通用户使用没问题,但有些团队希望允许选择安装路径。如果团队内部有“自定义安装目录”的需求,可以把参数改成:
nsis: oneClick: false perMachine: false allowToChangeInstallationDirectory: true这样的安装界面会更接近传统 Windows 软件,但不再是一键安装。实际选哪种,取决于使用对象。个人工具选oneClick: true最省事,团队内部工具可以考虑允许改安装目录。
4.3 图标、桌面快捷方式和卸载行为
图标配置需要注意两点:
- Windows 必须准备
.ico文件,分辨率建议至少包含 256x256。 - macOS 必须准备
.icns文件,不能直接复用.ico。
如果不想维护两套图标,可以生成图标后分别转换格式。build/目录下的icon.ico和icon.icns是 electron-builder 的默认扫描路径。
卸载行为由 NSIS 默认逻辑处理,但有一个参数值得主动配置:
nsis: deleteAppDataOnUninstall: false这里的含义是:卸载时是否删除用户数据。如果 DeepSeek Harness 会把会话历史、配置文件放在app.getPath('userData')目录,建议保留默认false,否则卸载软件会把用户聊天记录一起删掉。只有明确“卸载即清除所有数据”的场景才设成true。
electron-builder 还支持installerLanguages、uninstallDisplayName等细粒度配置,但对个人项目来说,上面这些已经覆盖了最核心的一键安装体验。
5. 验证打包结果并做一轮完整自测
5.1 从源码到安装包的完整命令流程
先安装依赖,再启动开发模式,确认界面正常:
npm install npm run devdev脚本在 package.json 中可以这样定义:
{ "scripts": { "dev": "cross-env VITE_DEV_SERVER_URL=http://localhost:5173 electron .", "dist": "electron-builder --publish never", "dist:win": "electron-builder --win --publish never", "dist:mac": "electron-builder --mac --publish never" } }如果项目没有 Vite 或其他前端开发服务器,直接用electron .也可以加载本地 HTML:
{ "scripts": { "dev": "electron ." } }执行打包:
npm run dist构建成功后会在release/目录下看到:
release/ ├── DeepSeekHarness-1.0.0-Setup.exe ├── builder-debug.yml ├── builder-effective-config.yaml └── unpacked/ └── DeepSeekHarness.exeDeepSeekHarness-1.0.0-Setup.exe就是最终的一键安装版。unpacked/目录里是免安装版,用于快速调试。
5.2 安装包验证清单
安装包生成不等于可以发布。下面这个验证清单可以直接复用到所有桌面端项目。
| 检查项 | 操作 | 预期结果 |
|---|---|---|
| 安装 | 双击安装包 | 无报错,进度条正常,自动完成 |
| 启动 | 从桌面快捷方式启动 | 应用窗口打开,无白屏 |
| 渲染进程日志 | 打开 DevTools 或查看日志文件 | 无明显 JavaScript 报错 |
| API 请求 | 发起一次 DeepSeek 对话请求 | 主进程代理正常返回结果 |
| 本地持久化 | 新建会话后重启应用 | 会话记录仍在 |
| 卸载 | 从控制面板卸载 | 快捷方式和程序文件被移除 |
| 产物校验 | 查看安装包属性 | 文件名包含版本号,图标正常 |
其中“从桌面快捷方式启动”非常关键。因为在源码模式下资源路径基于项目目录,打包后路径会变成 asar 内部路径,很多路径写错的问题只有安装后才会暴露。
5.3 日志从哪里看
桌面端应用上线前一定要有日志输出习惯。主进程日志建议写入app.getPath('userData')/logs/main.log:
const fs = require('fs'); const path = require('path'); function initLogger() { const logDir = path.join(app.getPath('userData'), 'logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } const logPath = path.join(logDir, 'main.log'); return { info: (message) => fs.appendFileSync(logPath, `[INFO] ${new Date().toISOString()} ${message}\n`), error: (message) => fs.appendFileSync(logPath, `[ERROR] ${new Date().toISOString()} ${message}\n`), }; }渲染进程的日志通过主进程转发到文件,避免白屏时看不到任何信息。日志是排查安装后问题的第一入口,比“重装一遍试试”有效得多。
6. 常见问题排查:从构建失败到安装后白屏
6.1 构建阶段的常见报错
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
Cannot find module electron | 依赖未完整安装 | 查看 node_modules/electron 是否存在 | 重新执行npm install,确认 .npmrc 镜像生效 |
| 下载 Electron 超时 | 网络问题或镜像未配置 | 观察 npm 输出 | 配置 electron_mirror 并清除缓存 |
| 安装包名重复 | 上次构建产物未被清理 | 查看 release 目录 | 构建前删除 release 目录 |
ENOENT: no such file or directory | 图标路径不存在 | 检查 electron-builder.yml 中 icon 路径 | 确认 build/icon.ico 存在且格式正确 |
A native module could not be loaded | 原生模块与 Electron ABI 不匹配 | 查看错误堆栈 | 使用 electron-rebuild 重新编译原生模块 |
自举构建模块里,最常见的是.npmrc镜像配置导致 Electron 下载失败。自举功能只是调用了同一个 npm 命令,所以构建环境中的镜像配置同样必须正确。
6.2 安装过程被杀毒软件拦截
新打包的 Electron 应用没有数字签名,被杀毒软件拦截是常见现象。Windows 上 SmartScreen 会提示“Windows 已保护你的电脑”,或者杀毒软件直接隔离安装包。
处理路径按优先级排列:
- 给安装包配置代码签名证书,这是最规范方案。
- 如果是团队内部工具,可以把安装包加入 IT 白名单。
- 本地测试时可以选择“更多信息”然后“仍要运行”,但这不是分发给用户的正规方式。
对于个人项目,可以先通过 Windows Defender 的“允许”流程完成首次运行验证,然后再考虑购买证书。这个问题不影响功能,但会直接影响别人是否愿意安装。
6.3 安装后白屏、资源缺失和 API 请求失败
安装后白屏是本类项目最高频问题。原因通常是生产环境加载路径和开发环境不一致。
主进程加载页面时,开发环境可能指向http://localhost:5173,但打包后没有本地服务器,必须加载本地 HTML。处理方式如下:
if (process.env.VITE_DEV_SERVER_URL) { win.loadURL(process.env.VITE_DEV_SERVER_URL); } else { win.loadFile(path.join(__dirname, '../renderer/index.html')); }如果preload路径写错,界面可以打开,但window.harness会是 undefined,点击任何按钮都会报错。检查方式是在主进程窗口创建后打开开发者工具,搜索window.harness是否存在。
API 请求失败的情况更复杂,通常集中在以下几点:
| 现象 | 原因 | 检查路径 |
|---|---|---|
| 请求报 CORS 错误 | 渲染进程直接访问 DeepSeek API | 把请求移到主进程,用net模块发起 |
| 请求返回 401 | API Key 未生效 | 检查 key 配置来源 |
| 请求超时 | 网络或代理配置问题 | 查看主进程日志和系统代理设置 |
| 自己的 HTTPS 证书校验失败 | 企业内部 CA | 在主进程请求中配置额外 CA 文件 |
最稳妥的架构是渲染进程不直接发请求,而是通过 IPC 调用主进程,由主进程使用 Node 的net模块请求 DeepSeek API。这样既能规避浏览器跨域限制,又能避免把 API Key 放在渲染进程的全局变量里。
7. 生产分发的关键实践
7.1 版本号、产物命名和构建产物保存
版本号只维护一个来源,推荐统一放在package.json的version字段。electron-builder 会自动读取,并且artifactName里的${version}会随之变化。
{ "name": "deepseek-harness-desktop", "version": "1.0.0" }每次构建前先清理 release 目录,避免安装包和旧文件混在一起:
rm -rf release构建完成后,建议把安装包连同latest.yml(如果需要自动更新)一起归档到带版本号的目录:
release/ ├── 1.0.0/ │ ├── DeepSeekHarness-1.0.0-Setup.exe │ └── latest.yml7.2 代码签名、自动更新和升级策略
如果要把安装包分享给更多人,代码签名是绕不开的话题。Windows 下没有签名的 exe 会触发 SmartScreen,macOS 下未签名应用会被 Gatekeeper 拦截。个人项目可以先做本地分发,但正式对外发布前一定要处理签名。
自动更新方面,electron-builder 提供了electron-updater。配置好 publish 服务器后,应用启动时会检查更新:
const { autoUpdater } = require('electron-updater'); function initAutoUpdater() { autoUpdater.checkForUpdatesAndNotify(); }对应 builder 配置中需要设置 publish 地址:
publish: provider: generic url: https://example.com/download/没有自己的下载服务器时,可以先用 GitHub Releases 或对象存储托管。自动更新不是“必须一开始就做”的功能,但它决定后续分发维护成本。
7.3 安全底线:不要把 API Key 打进渲染层
DeepSeek Harness 这类工具一定会涉及 API Key。最容易犯的错误就是把 Key 写在前端代码或 localStorage 里。桌面端的合理做法是:
- 渲染进程只保存用户输入的 Key 的“存在状态”,不保存 Key 本身明文。
- Key 保存到主进程控制的本地配置文件中,Windows 下可以结合系统凭据管理器。
- 所有 API 请求由主进程代发,渲染进程只接收结果。
一个最小实现思路:
// 主进程读取 Key,渲染进程无法直接访问 ipcMain.handle('api:chat', async (_event, messages) => { const config = loadConfig(); const response = await callDeepSeek(config.apiKey, messages); return response; });这样就算安装包被反编译,也只暴露了调用逻辑,不会直接暴露用户 Key。
7.4 可复用的发布前检查清单
最后整理一份可以直接复制到团队文档中的发布前检查清单:
- package.json 和 electron-builder.yml 中的版本号、应用 ID、产品名是否已确认。
- 构建前是否清理 release 目录。
- 图标是否包含 Windows ICO 和 macOS ICNS 两套。
- 是否已经完成一次从安装包启动的完整验证。
- 主进程日志是否写入到 userData 目录。
- DeepSeek API 请求是否全部走主进程代理。
- 是否检查过 unpacked 目录中的文件列表,确认没有多余调试文件。
- 是否配置了自动更新和代码签名;如果没配置,是否明确知道风险。
- 安装包是否做过空目录、无网络、杀毒软件拦截三种异常场景测试。
- 是否已经安排至少一位不熟悉项目的用户先试用安装版。
这份清单不依赖具体业务逻辑,任何 Electron 桌面工具发布前都可以按顺序走一遍。
桌面端 DeepSeek Harness 的价值在于把模型调用、本地数据、系统能力和安装分发全部串起来。做完整功能只是第一步,真正决定工具能不能被日常使用的是打包体验和发布后维护。自己打包自己这个能力,看起来像是“偷懒”,但它把构建流程真正还给了使用者,让后续每次发布都少一次手敲命令、少一次路径错误。下一步值得优先投入的方向是自动更新、日志上报和签名证书这三件事,它们会把工具从“本机能用”推向“真正可分发”。