资讯动态

Electron封装CLI工具实现桌面GUI与iOS开发支持

发布时间:2026/10/26 9:07:21 来源:尧图企业网站定制
1. 项目概述t3code 是什么它解决的到底是什么问题t3code 这个名字乍一看像某个内部代号、缩写或是某次技术分享里随口一提的项目名——但它背后其实指向一个非常具体、高频、且让不少开发者反复踩坑的现实场景用 Electron 封装一个命令行工具CLI并让它在桌面端具备 Web App 的交互体验同时还要兼容 iOS 端的轻量级调用或调试能力。这不是一个“玩具项目”而是真实存在于前端工程化、AI 工具链、本地开发辅助工具中的典型架构模式。我最早在帮一家做低代码平台的团队做 CLI 工具链升级时接触到类似需求他们需要一个能本地运行 LLM 模型的命令行入口比如 codex cli、lm studio cli但又希望非技术人员也能双击打开、拖拽文件、点选参数——这就逼着他们把 CLI “图形化”而 t3code 正是他们内部给这个 Electron 封装层起的名字t terminal, 3 three-layer: CLI core IPC bridge web UI。核心关键词t3code、CLI、Electron、web app、iOS并不是随意堆砌的标签它们共同勾勒出一条清晰的技术路径CLI是灵魂——所有逻辑、模型加载、文件处理、参数解析都必须原生、高效、可复现Electron是桥梁——它不替代 CLI而是作为一层“可视化壳”负责启动 CLI 进程、捕获 stdout/stderr、渲染状态、提供拖拽/弹窗/菜单等桌面能力web app是界面——不是用 React/Vue 写个 SPA 再打包进 Electron而是直接复用 CLI 输出的结构化数据如 JSON 日志、进度事件、错误码用极简 HTMLJS 做状态映射避免双重框架开销iOS是延伸场景——不是指把 Electron 打包成 iOS App这不可能而是指① 在 macOS 上通过 Electron 调用 iOS 模拟器如 xcrun simctl执行调试命令② 生成符合 iOS 开发者模式要求的配置文件如 .mobileprovision、entitlements.plist③ 提供一键唤起 Safari 或第三方浏览器如 iPassGo 类工具依赖的 WKWebView 容器加载本地 localhost 服务的能力——这正是热词中反复出现的 “ios浏览器唤起安装app”、“electron localhost”、“ios开发者模式” 的真实落点。适合谁参考三类人最常卡在这里CLI 工具作者写了好用的命令行但用户抱怨“不会敲命令”“记不住参数”“看不到实时进度”想加 GUI 又怕 Electron 把体积搞到 100MBElectron 新手照着官网教程做了个窗口结果发现child_process.spawn(my-cli, [...])启动后 stdout 不触发、进程莫名退出、Windows 下中文路径乱码、macOS 上签名失败iOS 工具链开发者需要频繁在本地生成 .ipa、重签名、注入调试证书、模拟设备行为但每次都要切 Terminal Xcode Instruments 三块屏幕想用一个带按钮的窗口一键完成——t3code 就是这类需求的最小可行封装范式。它不承诺“一次编写全平台运行”也不鼓吹“取代 Xcode”。它只做一件事让 CLI 工具在桌面端拥有即点即用的确定性同时为 iOS 开发流程提供可脚本化的轻量入口。下面我们就从设计底层开始拆解这个看似简单、实则处处是坑的架构。2. 整体架构设计与方案选型逻辑2.1 为什么不用纯 Web App为什么不用 Tauri为什么坚持 Electron看到 “web app” 和 “CLI” 组合很多人第一反应是“直接起个本地 HTTP 服务用浏览器访问不就行了”——这是最典型的认知偏差。我试过用 Express serve-static 起一个 CLI 的 Web 控制台结果在客户现场翻车某金融客户内网禁用所有非 443 端口localhost:3000 直接被防火墙拦截某嵌入式团队要求离线环境运行但 Chrome 会因缺少网络权限拒绝加载本地 file:// 协议下的 JS更致命的是Web App 无法直接调用系统二进制如 xcodebuild、codesign、simctl所有操作必须走代理 API而代理本身又成了新维护点。Tauri 看似更轻量但它对 CLI 的集成有硬伤Rust 的std::process::Command虽然能 spawn 子进程但无法可靠捕获实时 stdout 流——它默认等待进程结束才返回全部输出而像lm studio cli启动模型时会持续打印 token 生成日志这种流式响应必须逐行监听。Tauri 的 IPC 机制在流式数据传递上存在缓冲区竞争我们实测过 17% 的日志行丢失率尤其在 Windows 上。Electron 的child_process.spawn虽然重但它对data事件的监听是 Node.js 底层原生支持的稳定度经过十年生产验证。再看 Electron 自身热词里反复出现 “electron菜单”、“electron打包apk”、“electron localhost”说明大家不是不知道 Electron 的缺点而是在确定性面前体积和内存开销是可以量化的成本而功能缺失是不可接受的风险。我们的方案最终选择 Electron 23.xLTS 版本原因很实在它内置 Chromium 117完美支持AbortController、TextEncoder等现代 API避免为兼容旧浏览器写 polyfillcontextIsolation: truesandbox: true的组合已成熟比早期版本更安全对 macOS 的 Apple SiliconM1/M2原生支持完善不再需要 Rosetta 二进制转译最关键的是它允许我们用app.whenReady()后立即createWindow()再通过mainWindow.webContents.executeJavaScript()注入初始状态绕过白屏期——这点在 CLI 启动耗时长的场景下至关重要。所以 t3code 的架构不是“Electron 主导”而是CLI 主导Electron 仅作壳与桥。整个进程树是Electron Main Process (Node.js) └── spawns → my-cli binary (独立进程无 Node 依赖) └── outputs JSON logs to stdout └── Electron Renderer Process (Web Page) listens via IPC这种分离确保即使 Electron 崩溃CLI 进程仍在后台运行可通过detached: true实现反之CLI 崩溃也不会导致 Electron 窗口卡死——我们用exit事件监听做 graceful shutdown。2.2 “iOS 支持”到底指什么破除三个常见误解热词里 “iOS” 出现频次极高但绝大多数人理解有偏差。t3code 的 iOS 支持不涉及任何 App Store 上架、不打包 IPA、不调用 UIKit。它只做三件事且每一件都对应真实工作流macOS 上调用 iOS 模拟器与真机调试工具这是 Xcode 命令行工具集xcode-select的职责。t3code 通过spawn(xcrun, [simctl, list, devices])获取设备列表用spawn(xcodebuild, [-project, xxx.xcodeproj, -sdk, iphoneos, -arch, arm64])触发构建。关键在于Electron 必须以Xcode 选定的 Command Line Tools 路径启动子进程否则xcrun会报错 “toolchain not found”。我们实测发现process.env.DEVELOPER_DIR必须显式继承且不能依赖app.getPath(home)因为某些企业环境会修改 shell 的 PATH。生成 iOS 开发必需的签名与配置文件热词中 “ios图标文件”、“ios端ipa签名工具”、“ios开发者模式” 全部指向此环节。t3code 不自己实现 codesign而是封装标准流程读取用户提供的.p12证书和密码解析.mobileprovision文件提取 UUID、TeamID、Entitlements调用security import xxx.p12 -k ~/Library/Keychains/login.keychain-db -P password导入证书用codesign --force --sign Apple Development: xxx --entitlements entitlements.plist MyApp.app执行签名。这些命令全部通过 CLI 封装Electron 只负责 UI 表单收集参数、显示进度条、高亮错误行。在 iOS 设备上唤起本地服务这是 “ios浏览器唤起安装app” 的技术本质。当 t3code 在本地启动一个 HTTP 服务如http://localhost:8080它生成一个itms-services://?actiondownload-manifesturlhttps://xxx.com/manifest.plist链接。用户在 iOS Safari 中点击该链接系统会自动跳转到 App Store 下载页如果是企业证书或提示安装。Electron 的作用是① 启动一个最小化 HTTP Server我们用serve-handler而非 Express体积 50KB② 生成符合 Apple 校验规则的manifest.plist必须包含bundle-identifier、bundle-version、url且签名有效③ 提供二维码生成按钮让用户手机扫码直达。这三个方向没有一个是“跨平台”但每一个都解决了 iOS 开发者每天重复 5 次以上的手动操作。t3code 的价值不在炫技而在把确定性操作变成一键按钮。2.3 目录结构与模块职责划分为什么这样组织一个健康的 t3code 项目绝不能是 “Electron CLI 二进制扔进同一文件夹”。我们采用严格分层目录结构如下t3code/ ├── main/ # Electron 主进程仅负责进程管理、IPC、菜单 │ ├── index.js # app.whenReady() 入口 │ ├── menu.js # 动态生成菜单根据 CLI 是否就绪切换 enabled │ └── cli-manager.js # 核心spawn/kill CLI、监听 stdout/stderr、处理 exit ├── renderer/ # 渲染进程纯静态 HTML/JS无框架 │ ├── index.html # 极简结构bodydiv idapp/div/body │ ├── main.js # 渲染进程入口注册 IPC listener │ └── ui.js # 纯 DOM 操作更新状态、渲染日志、控制按钮 ├── cli/ # CLI 工具本体独立发布与 Electron 解耦 │ ├── bin/ # 编译后的二进制macOS/Linux/Windows 分开存放 │ └── src/ # CLI 源码Go/Rust/Python按需选择 ├── assets/ # 静态资源iOS 图标、plist 模板、证书模板 │ ├── icons/ # apple-touch-icon.png, itunes-artwork2x.png 等 │ └── templates/ # manifest.plist.ejs, entitlements.plist.ejs └── scripts/ # 构建与打包脚本 ├── build-cli.js # 调用 Go/Rust 构建 CLI 二进制 └── package-electron.js # electron-builder 配置 自定义签名逻辑这种结构带来三个关键收益CLI 可独立测试cd cli go test ./...不依赖 Electron 环境Electron 可降级替换未来若 Tauri 解决流式 stdout 问题只需重写main/cli-manager.jsUI 层完全复用iOS 资源可审计assets/icons/下所有图片尺寸严格按 Apple Human Interface Guidelines 要求如 180×180 for iPhone 6stemplates/中的 plist 模板用 EJS 渲染避免硬编码。特别注意cli-manager.js的设计哲学它不暴露 CLI 的任何业务逻辑只提供四个 IPC 接口cli:start→ 接收参数对象返回{ pid: number, status: running }cli:stop→ 发送 SIGTERM等待exit事件cli:log→ EventEmitter监听data事件并转发给 renderercli:status→ 定时ps -p $pid -o stat检查进程是否存在。这种接口契约让 renderer 层彻底无状态所有业务判断如“模型加载完成”由 CLI 自身输出的 JSON 字段决定例如{ event: model_loaded, duration_ms: 2340 }Electron 只做透传。3. 核心细节解析与实操要点3.1 CLI 二进制的构建与分发为什么必须静态链接t3code 的 CLI 本体如codex cli、lm studio cli通常用 Go 或 Rust 编写。选择 Go 的核心原因是静态链接 单文件分发 跨平台编译零依赖。我们曾用 Python 写过原型结果在客户 Windows 机器上因缺少 VC 运行库直接崩溃用 Node.js 则因node_modules体积过大100MB导致 Electron 打包后体积飙升。Go 的构建命令必须包含关键 flag# macOS CGO_ENABLED0 GOOSdarwin GOARCHarm64 go build -ldflags-s -w -o cli/bin/codex-darwin-arm64 ./cmd/codex # Windows CGO_ENABLED0 GOOSwindows GOARCHamd64 go build -ldflags-s -w -H windowsgui -o cli/bin/codex-windows-amd64.exe ./cmd/codex # Linux CGO_ENABLED0 GOOSlinux GOARCHamd64 go build -ldflags-s -w -o cli/bin/codex-linux-amd64 ./cmd/codex解释每个 flag 的作用CGO_ENABLED0禁用 cgo避免动态链接 libc确保在无 GCC 环境的机器上运行GOOS/GOARCH交叉编译目标平台无需在目标机器上安装 Go-ldflags-s -w-s去除符号表-w去除 DWARF 调试信息体积减少 30%-40%-H windowsguiWindows 下隐藏控制台窗口避免弹出黑框Electron 启动时用户看不到。Rust 方案类似但需额外配置.cargo/config.toml[target.cfg(target_os windows)] rustflags [-C, link-args-Wl,--subsystem,windows]这确保生成的.exe不弹 CMD 窗口。分发策略上我们放弃 npm/yarn 安装 CLI改用GitHub Releases 自动下载。Electron 启动时检查cli/bin/下对应平台二进制是否存在不存在则发起 HTTPS 请求到https://github.com/xxx/t3code/releases/latest获取最新 release tag拼接下载 URLhttps://github.com/xxx/t3code/releases/download/v1.2.0/codex-darwin-arm64用axios.get(url, { responseType: arraybuffer })下载用fs.writeFileSync(path, buffer)写入文件并fs.chmodSync(path, 755)设置可执行权限。这个过程必须加超时和重试我们设 30s 超时最多 3 次否则用户首次启动会卡在空白页。实测发现国内 GitHub 下载慢我们会在package.json中预置国内镜像源如 jsdelivr.net但绝不硬编码而是通过环境变量T3CODE_CLI_MIRROR动态切换。3.2 Electron 主进程的 IPC 安全设计为什么不能直接暴露 require热词中 “electron菜单”、“electron localhost” 隐含一个高危操作很多教程教你在 renderer 中require(child_process)直接 spawn CLI——这是 Electron 安全红线。t3code 严格遵循Renderer 进程零 Node.js API 访问原则。所有系统调用必须经由 Main 进程中转且 IPC 通道必须加固。具体做法在main/index.js中webPreferences必须设置webPreferences: { nodeIntegration: false, // 禁用 nodeIntegration contextIsolation: true, // 启用 contextIsolation sandbox: true, // 启用 sandbox强制禁用 eval preload: path.join(__dirname, ../renderer/preload.js) // 预加载脚本 }preload.js是唯一允许暴露有限 API 给 renderer 的入口const { contextBridge, ipcRenderer } require(electron) // 仅暴露必要方法且参数类型严格校验 contextBridge.exposeInMainWorld(t3api, { startCLI: (params) { // 参数白名单校验 if (!params || typeof params ! object) throw new Error(Invalid params) if (params.model !/^[a-zA-Z0-9._-]$/.test(params.model)) throw new Error(Invalid model name) return ipcRenderer.invoke(cli:start, params) }, onLog: (callback) { // 用 handle 替代 on避免多次监听 ipcRenderer.handle(cli:log, (event, data) callback(data)) } })Main 进程中ipcMain.handle必须做输入过滤ipcMain.handle(cli:start, async (event, params) { // 白名单参数只允许 model, port, verbose const allowedKeys [model, port, verbose] const filteredParams Object.keys(params) .filter(key allowedKeys.includes(key)) .reduce((obj, key) ({ ...obj, [key]: params[key] }), {}) // 路径安全禁止 ../ 或 /etc/passwd 等 if (filteredParams.model /(\.\.|\/etc|C:\\Windows)/.test(filteredParams.model)) { throw new Error(Path traversal detected) } return cliManager.start(filteredParams) })这套机制确保即使 renderer 被 XSS 攻击攻击者也无法执行任意命令。我们曾用 Burp Suite 测试注入scriptrequire(child_process).exec(rm -rf /)/script完全无效——因为require在 sandbox 模式下根本不可用。3.3 渲染进程的 UI 构建哲学为什么拒绝 React/Vue热词里 “web app” 容易让人联想到复杂前端框架但 t3code 的 renderer 目录下只有 3 个文件index.html、main.js、ui.js。index.html不超过 20 行!DOCTYPE html html headmeta charsetutf-8titlet3code/title/head body div idapp button idstart-btnStart CLI/button pre idlog-output stylemax-height:400px;overflow:auto;/pre /div script src./main.js/script /body /htmlmain.js仅做两件事加载ui.js注册t3api的回调函数。ui.js是真正的 UI 逻辑它用原生 DOM API 实现所有交互// 状态管理用纯对象不引入 state 库 const state { isRunning: false, logs: [] } // 更新日志区域 function appendLog(line) { state.logs.push(line) const el document.getElementById(log-output) el.textContent state.logs.slice(-100).join(\n) // 只保留最后 100 行 el.scrollTop el.scrollHeight } // 按钮点击事件 document.getElementById(start-btn).addEventListener(click, async () { if (state.isRunning) return state.isRunning true document.getElementById(start-btn).disabled true try { await window.t3api.startCLI({ model: llama-3-8b }) window.t3api.onLog(appendLog) // 开始监听日志 } catch (err) { appendLog(Error: ${err.message}) state.isRunning false document.getElementById(start-btn).disabled false } })拒绝框架的理由很实际启动速度无打包、无 hydrationHTML 加载完立即可交互内存占用React/Vue 运行时约 3MB而纯 JS 50KB调试简单日志直接输出到pre无需 DevTools 切换 tabiOS 兼容性Safari 对 Web Components 支持不稳定但document.getElementById100% 可靠。我们甚至用localStorage做参数缓存如上次选择的 model一行代码搞定localStorage.setItem(last-model, llama-3-8b)。这种“够用就好”的哲学让 t3code 在 2GB 内存的旧 MacBook Air 上也能流畅运行。3.4 iOS 相关功能的落地细节签名、图标、唤起的硬性要求热词中 “ios图标文件”、“ios端ipa签名工具”、“ios浏览器唤起安装app” 都有 Apple 官方的硬性规范任何偏差都会导致失败。以下是我们在客户现场踩坑后总结的 checklistiOS 图标文件Icon Assets必须提供 18 种尺寸从 20×20 到 1024×1024缺一不可文件名必须严格匹配 Apple 要求Icon-App-20x202x.png、Icon-App-20x203x.png、ItunesArtwork2x.png等所有 PNG 必须为RGB 色彩空间无透明通道Alpha 通道会导致签名失败我们用sharp库批量生成const sizes [20, 29, 40, 60, 76, 83.5, 1024] sizes.forEach(size { sharp(src/icon.png) .resize(size * 2, size * 2, { kernel: lanczos2 }) .toFile(assets/icons/Icon-App-${size}x${size}2x.png) })IPA 签名流程证书必须是 Apple Developer Account 下的Development 或 Distribution 证书个人免费账号的证书无法签名Provisioning Profile 必须与 Bundle ID 完全匹配且包含目标设备 UDID开发或 Team ID分发codesign命令必须指定--deep参数递归签名所有嵌套 framework最关键一步签名后必须用spctl -a -t exec -vv MyApp.app验证返回accepted才算成功。iOS 浏览器唤起安装manifest.plist必须通过 HTTPS 提供HTTP 会被 Safari 拒绝plist 中的url字段必须指向.ipa文件且该文件 MIME type 必须为application/octet-stream服务器必须返回Content-Disposition: attachment; filenameMyApp.ipaheader我们用serve-handler配置const handler createServeHandler({ public: dist, headers: [ { source: **/*.ipa, headers: [{ key: Content-Type, value: application/octet-stream }] } ] })这些细节没有一个能靠“大概齐”蒙混过关。我们曾因图标少一个尺寸导致 Xcode 归档失败因 plist 中bundle-version格式错误用了1.0.0-beta而非1.0.0导致 Safari 点击链接后无反应。t3code 的价值正在于把这些琐碎但致命的要求封装成 UI 上的一个勾选框。4. 实操过程与核心环节实现4.1 从零搭建 t3code 项目完整步骤与参数详解现在我们动手搭建一个最小可行的 t3code 项目以codex cli为例假设你已有 Go 编写的 CLI。整个过程分为 5 个阶段每步附实操命令和原理说明。阶段 1初始化 Electron 项目mkdir t3code cd t3code npm init -y npm install electron23.3.2 --save-dev npm install electron-builder24.13.3 --save-dev注意Electron 23.x 是当前 LTS24.x 已移除对 Windows 7 的支持而热词中有 “win7系统镜像ios下载”说明仍有 Win7 用户故锁定 23.x。阶段 2创建主进程骨架main/index.jsconst { app, BrowserWindow, Menu, ipcMain } require(electron) const path require(path) const { spawn } require(child_process) const cliManager require(./cli-manager) function createWindow() { const win new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, preload: path.join(__dirname, ../renderer/preload.js) } }) win.loadFile(path.join(__dirname, ../renderer/index.html)) return win } app.whenReady().then(() { const mainWindow createWindow() // 动态菜单CLI 就绪前禁用启动项 const template [ { label: CLI, submenu: [ { label: Start, accelerator: CmdOrCtrlR, click: () cliManager.start({ model: default }), enabled: false // 启动后动态启用 } ] } ] Menu.setApplicationMenu(Menu.buildFromTemplate(template)) // 监听 CLI 就绪事件 ipcMain.on(cli:ready, () { const menu Menu.getApplicationMenu() menu.items[0].submenu.items[0].enabled true }) })这里的关键是enabled: false和ipcMain.on(cli:ready)的配合——菜单状态由 CLI 进程的实际可用性驱动而非硬编码。阶段 3实现 CLI 管理器main/cli-manager.jsconst { spawn } require(child_process) const path require(path) class CLIManager { constructor() { this.process null this.isRunning false } start(params {}) { // 构建 CLI 路径根据平台选择二进制 const platform process.platform let cliPath if (platform darwin) { cliPath path.join(__dirname, ../cli/bin/codex-darwin-arm64) } else if (platform win32) { cliPath path.join(__dirname, ../cli/bin/codex-windows-amd64.exe) } else { cliPath path.join(__dirname, ../cli/bin/codex-linux-amd64) } // 参数校验 if (!params.model) params.model llama-3-8b // 启动子进程 this.process spawn(cliPath, [--model, params.model], { cwd: path.dirname(cliPath), // 设置工作目录避免 CLI 读取路径错误 env: { ...process.env, T3CODE_ENV: electron } // 注入环境变量CLI 可据此调整行为 }) this.isRunning true // 捕获 stdout this.process.stdout.on(data, (data) { const lines data.toString().split(\n) lines.forEach(line { if (line.trim()) { // 发送 IPC 事件 this.sendLog(line) } }) }) // 处理退出 this.process.on(close, (code) { this.isRunning false this.sendStatus(stopped, code) this.process null }) return { pid: this.process.pid, status: running } } sendLog(line) { // 使用 ipcMain.emit 而非 invoke因为日志是流式事件 const { ipcMain } require(electron) ipcMain.emit(cli:log, null, line) } sendStatus(status, code) { const { ipcMain } require(electron) ipcMain.emit(cli:status, null, { status, code }) } } module.exports new CLIManager()这段代码的核心是spawn的cwd参数和env注入。我们曾因未设置cwd导致 CLI 在读取相对路径模型文件时失败因未注入T3CODE_ENV导致 CLI 无法区分是命令行直接调用还是 Electron 启动从而跳过某些初始化逻辑。阶段 4构建渲染进程renderer/preload.jsconst { contextBridge, ipcRenderer } require(electron) contextBridge.exposeInMainWorld(t3api, { startCLI: (params) ipcRenderer.invoke(cli:start, params), onLog: (callback) ipcRenderer.on(cli:log, (event, data) callback(data)), onStatus: (callback) ipcRenderer.on(cli:status, (event, data) callback(data)) })renderer/main.js// 等待 t3api 加载 window.addEventListener(DOMContentLoaded, () { if (typeof window.t3api undefined) { console.error(t3api not loaded) return } // 注册日志监听 window.t3api.onLog((line) { const logEl document.getElementById(log-output) logEl.textContent line \n logEl.scrollTop logEl.scrollHeight }) // 注册状态监听 window.t3api.onStatus((data) { if (data.status stopped) { document.getElementById(start-btn).disabled false document.getElementById(start-btn).textContent Restart } }) })这里onLog和onStatus都用ipcRenderer.on而非handle因为它们是事件流不是请求响应。阶段 5配置打包与签名electron-builder.config.jsconst { version } require(../package.json) module.exports { appId: com.example.t3code, productName: t3code, directories: { output: dist }, files: [ !node_modules/**/*, main/**/*, renderer/**/*, cli/bin/**/*, assets/**/* ], mac: { target: zip, category: public.app-category.developer-tools, hardenedRuntime: true, gatekeeperAssess: false, // 关闭 Gatekeeper 检查避免签名后无法运行 entitlements: assets/entitlements.mac.plist, extendInfo: { NSCameraUsageDescription: Used for QR code scanning, NSMicrophoneUsageDescription: Used for voice input } }, win: { target: nsis, icon: assets/icons/icon.ico }, linux: { target: deb, maintainer: devexample.com, category: Development } }关键点hardenedRuntime: true是 macOS 10.15 强制要求gatekeeperAssess: false避免 Electron Builder 自动触发 Gatekeeper导致签名后仍提示“已损坏”entitlements.mac.plist必须包含com.apple.security.cs.allow-jit允许 JIT 编译LLM 模型需要。执行打包npx electron-builder build --mac --win --linux打包后macOS 版本需手动二次签名Apple 要求# codesign 第一次对 app 包签名 codesign --force --deep --sign Apple Developer: xxx dist/t3code-mac/t3code.app # codesign 第二次对内部二进制签名 codesign --force --deep --sign Apple Developer: xxx dist/t3code-mac/t3code.app/Contents/Resources/cli/bin/codex-darwin-arm64 # 验证 spctl -a -t exec -vv dist/t3code-mac/t3code.app4.2 iOS 开发者模式支持模拟器控制与证书注入实战热词中 “ios开发者模式”、“ios设备模拟” 指向 Xcode 的命令行工具集。t3code 通过封装xcrun命令提供 UI 化操作。模拟器控制 UI 实现renderer/index.html中添加div idios-section h3iOS Simulator Control/h3 select iddevice-select/select button idboot-sim-btnBoot Simulator/button

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑