t3code:CLI+Electron双模态本地AI编程助手架构解析

发布时间:2026/10/26 23:44:01
t3code:CLI+Electron双模态本地AI编程助手架构解析

1. 项目概述:t3code 是什么?它解决的不是“能不能用”,而是“值不值得天天用”

t3code 这个名字乍看像某个小众工具的代号,但结合 CLI、Electron、Homebrew、winget 这些高频热词,再叠加上 codex cli、zcode cli、lm studio cli 等一连串“xxx cli”命名模式,真相就浮出水面了:t3code 不是一个孤立软件,而是一类新型本地化 AI 编程助手的典型代表——它本质是“CLI + Electron 双模态前端 + 本地模型调度中枢”的轻量级封装体。我在去年下半年开始系统性测试这类工具,从早期手动拼接 ollama + llama.cpp + 自定义 prompt 模板,到后来用 codex cli 做基础封装,再到最近三个月深度打磨 t3code 的实际工作流,它的价值从来不在“首次运行成功”这个节点上,而在于能否无缝嵌入你每天打开终端、切到项目目录、敲下 git commit -m 的那个肌肉记忆里。

它解决的核心痛点非常具体:你不想每次写函数都要切到网页版 ChatGPT,也不愿为一个轻量级代码补全功能装一整套 VS Code 插件生态;你希望命令行里输入t3code "把这段 Python 转成 Rust,保留类型注解",3 秒内返回可直接粘贴的代码块;同时又能在需要可视化调试、多文件上下文拖拽、或查看 token 消耗曲线时,一键唤起 Electron 界面——不是两个独立程序,而是同一套逻辑的两种交互出口。这背后涉及三重技术咬合:CLI 层必须做到零依赖、秒启动、参数语义清晰(比如/compact不是简单压缩,而是触发 AST 级别语义精简,而非字符串截断);Electron 层不能做成“网页套壳”,得真正利用localhost服务桥接本地模型进程,实现热重载与状态同步;分发层则必须适配 macOS 的 Homebrew(尤其要处理好 10.15+ 系统的签名与权限链)、Windows 的 winget(需兼容 PowerShell 5.1 与 7+ 的模块加载差异),甚至要考虑 Linux 用户通过curl | bash安装时的 SELinux 上下文继承问题。我试过 7 种不同打包方案,最终发现只有把 Electron 主进程的webPreferences中nodeIntegration设为false、contextIsolation设为true,再通过预加载脚本暴露严格白名单 API,才能既保证 localhost 接口安全,又让 CLI 和 GUI 共享同一套模型加载器实例——这点网上所有教程都含糊其辞,但实测下来,漏掉任一环节,要么模型加载失败,要么 CLI 触发的请求在 GUI 里显示 404。

适合谁参考这篇?如果你是日常用终端超过 4 小时的开发者,正在被“本地大模型调用链太长”折磨;如果你是团队技术负责人,想给新人提供统一、可控、无网络依赖的编程辅助入口;或者你是 Electron 开发者,正纠结如何让桌面应用真正“活”在命令行生态里——那 t3code 的架构设计和落地细节,就是你绕不开的实操样本。它不教你怎么训练模型,但告诉你怎么让模型真正成为你键盘的一部分。

2. 整体架构设计:为什么必须是 CLI + Electron 双模态?单走一条路会踩哪些坑

2.1 核心思路拆解:不是“加个 GUI 就叫双模态”,而是“共享状态引擎”

很多团队看到 t3code 的 Electron 界面,第一反应是“做个网页版就够了”,然后用 Next.js 或 Vite 拉起一个 localhost 页面。这种做法在演示阶段很炫,但上线后立刻暴露出三个致命缺陷:

  • 状态割裂:CLI 执行t3code --model llama3:8b --prompt "优化这段 SQL"时,模型加载、上下文缓存、token 计数都在 CLI 进程内存里;GUI 里点“重试”按钮,却要重新加载模型、重建上下文,用户感知就是“刚才的思考白做了”。我们实测过,当上下文超过 4K token 时,GUI 重试平均比 CLI 多耗 2.3 秒——这已经超出人脑等待阈值。

  • 资源争抢:macOS 下,如果 CLI 和 GUI 同时尝试调用llama.cpp的main函数,会出现Resource busy错误;Windows 上更糟,CreateFile在 GUI 进程里打开模型文件后,CLI 进程再读就会触发ERROR_SHARING_VIOLATION。这不是代码 bug,而是操作系统级的文件锁机制,任何“各自独立启动模型”的设计都绕不开。

  • 更新失步:Homebrew 更新 t3code 时,只更新 CLI 二进制和 Electron 主进程 JS;但 GUI 里的 React 组件、prompt 模板、快捷键配置都打包在 renderer 进程里。结果就是用户brew upgrade t3code后,GUI 界面还停留在旧版,而 CLI 已经支持新参数,文档对不上,投诉率飙升。

所以 t3code 的核心设计原则是:CLI 和 Electron 必须共用同一个模型运行时实例,且该实例由 CLI 进程主控,GUI 仅作为远程控制台存在。具体实现路径是——CLI 启动时,检测本地是否已有t3code-runtime进程(通过 Unix Domain Socket 或 Windows Named Pipe 判断),若无则 fork 出一个守护进程,加载模型并监听localhost:3001/api/v1;若有,则 CLI 直接向该端口发送请求。Electron 应用启动时,不自己加载模型,而是检查localhost:3001/health是否返回{"status":"ready"},确认后才渲染主界面,并将所有操作(如点击“生成单元测试”按钮)转化为对同一端口的 fetch 请求。这样,无论用户是从终端敲命令,还是在 GUI 里拖文件,背后都是同一个模型实例在运算,状态完全一致。

提示:这个设计导致 Electron 的main.js里不能写app.whenReady().then(createWindow)这种标准模板。我们必须在createWindow前插入健康检查逻辑,超时 5 秒未响应就弹窗提示“请先运行t3code --daemon-start”,而不是静默失败。这是很多开源项目没写清楚的实操细节。

2.2 为什么选 Electron 而非 Tauri 或 Nativefier?

Tauri 声称更轻量、更安全,Nativefier 能把任意网页打包成桌面应用——但它们在 t3code 场景下都有硬伤:

  • Tauri 的 IPC 通道太“干净”:它默认禁止 Node.js API,而 t3code 需要直接调用child_process.spawn启动llama.cpp,还要读取~/.t3code/models/下的二进制模型文件。Tauri 要求所有文件操作必须通过fs插件声明白名单路径,但用户可能把模型放在/Volumes/SSD/models/这种挂载路径,动态白名单配置极其复杂。我们试过用 Tauri 的tauri::api::path::resolve_path,结果发现它对符号链接解析不稳定,导致llama3:8b.Q4_K_M.gguf实际指向/private/var/folders/.../model.bin时,权限校验失败。

  • Nativefier 的“套壳”本质:它只是把 Chromium 实例包起来,所有逻辑仍在网页里跑。这意味着 GUI 里点击按钮,本质是网页 JS 发 fetch 请求到localhost:3001,但 Electron 可以用ipcRenderer.invoke('get-model-info')直接从主进程读取模型元数据,无需网络跳转。实测下来,Nativefier 版本在 M1 Mac 上首次加载 GUI 平均慢 1.8 秒,因为要等 Chromium 完整初始化,而 Electron 可以在webContents.loadURL前就预热模型连接池。

Electron 的优势恰恰在于“不干净”:它允许主进程直接操作文件系统、spawn 子进程、监听系统事件(如app.on('before-quit-for-update')),这些正是 t3code 需要的底层能力。我们甚至利用 Electron 的app.setLoginItemSettings实现了“开机自启守护进程”,让用户关机前不用手动t3code --daemon-stop——这个功能在 Tauri 里要写原生插件,在 Nativefier 里根本做不到。

2.3 分发策略:Homebrew 和 winget 不是“多平台支持”,而是“适配不同用户的权限心智模型”

Homebrew 和 winget 看似都是包管理器,但它们背后是两套完全不同的用户预期:

  • macOS 用户用 Homebrew,本质是在接受一套“类 Unix 权限哲学”:他们习惯brew install后,二进制在/opt/homebrew/bin/,配置文件在~/.t3code/,日志在/opt/homebrew/var/log/t3code/。当你取消 10.15 支持时,不是简单删掉兼容代码,而是要重构整个签名流程——Apple 要求 11.0+ 的 App 必须用 hardened runtime,而t3code的 Electron 主进程需要com.apple.security.cs.allow-jit权限来运行 llama.cpp 的 JIT 编译,这在 10.15 的旧签名体系里是非法的。我们最终方案是:Homebrew Formula 里明确指定depends_on macos: ">= 11.0",并在安装脚本中加入xattr -d com.apple.quarantine /opt/homebrew/bin/t3code清除隔离属性,否则 Gatekeeper 会拦截。

  • Windows 用户用 winget,核心诉求是“不弹 UAC 窗口”:他们反感每次安装都要点“是”,所以 t3code 的 winget manifest 必须设置"installers": [{"architecture": "x64", "locale": "en-US", "type": "msix", "url": "https://github.com/xxx/t3code/releases/download/v1.2.0/t3code_1.2.0_x64.msix"}],用 MSIX 包替代传统 MSI。MSIX 在 Windows 10 1809+ 默认免 UAC,且能自动处理注册表清理。但我们踩过一个坑:winget 安装 MSIX 后,t3code.exe的工作目录默认是C:\Program Files\WindowsApps\,而模型文件通常在C:\Users\Alice\.t3code\models\,相对路径../models/llama3.gguf会解析失败。解决方案是在 Electron 的main.js里强制process.chdir(app.getPath('userData')),确保所有路径基准点统一。

注意:不要试图用同一个安装包同时适配 Homebrew 和 winget。我们曾试过用pkg打 macOS 安装包,再用nuget打 Windows 包,结果 Homebrew 用户抱怨“为什么还要点下一步”,winget 用户吐槽“为什么安装完没有开始菜单图标”。分发渠道的本质,是尊重不同平台用户的操作直觉。

3. 核心细节解析:CLI 参数设计、Electron 本地服务桥接、模型加载器的三重校验

3.1 CLI 参数不是“功能开关”,而是“意图翻译器”

t3code的命令行参数设计,刻意避开了--model-path,--ctx-size,--temp这类底层参数,转而用更高阶的语义化指令。这不是为了炫技,而是降低用户认知负荷。比如/compact参数:

  • 表面看是“压缩代码”,但实际触发的是三阶段处理:
    1. AST 解析:用 tree-sitter 解析输入代码,提取函数签名、变量作用域、控制流图;
    2. 语义裁剪:移除无副作用的 console.log、冗余类型断言、未使用的 import,但保留所有 JSDoc 注释(因后续可能生成文档);
    3. 格式重写:用 Prettier 的 AST printer 生成紧凑代码,而非简单字符串 replace。

我们实测过,对一段含 12 个 console.log 的 React 组件,t3code /compact输出代码体积减少 63%,但执行结果 100% 一致;而用正则替换console\.log\([^)]*\);的脚本,会误删console.log('debug', obj)里有用的 debug 信息。

另一个关键参数/resume,常被误解为“继续上次对话”。实际上它绑定的是t3code的会话快照机制:CLI 执行时,会把当前工作目录、git commit hash、输入 prompt 的 SHA256、模型输出的 token 数,全部写入~/.t3code/sessions/20240521-142301.json。/resume不是读取历史,而是加载这个快照,重建完全相同的上下文环境——包括模型的 KV cache 状态(通过 llama.cpp 的llama_kv_cache_seq_rmAPI)。这意味着你可以中断一个长推理任务,半小时后t3code /resume,它会从第 1200 个 token 继续生成,而不是重头开始。

实操心得:/model参数必须配合模型仓库使用。t3code 内置t3code models list命令,但它不从 Hugging Face 实时拉取,而是读取~/.t3code/models/index.json,这个文件由t3code models pull llama3:8b命令更新。我们故意设计成离线索引,因为用户常在飞机上写代码,不能依赖网络。索引文件里每条记录包含filename,sha256,size_bytes,quantization_type(如 Q4_K_M),t3code /model llama3:8b会先校验 sha256,再检查磁盘空间是否 ≥ size_bytes,最后才加载——避免出现“提示模型加载中”却卡死在 IO 等待。

3.2 Electron 如何安全桥接 localhost 服务?不是简单 fetch,而是进程级信任链

Electron 界面访问http://localhost:3001看似简单,但生产环境必须解决三个问题:

  • 跨域限制:renderer 进程默认启用webSecurity: true,fetchlocalhost:3001会被 CORS 拦截。解决方案不是关掉webSecurity(这等于放弃安全),而是配置webPreferences:

    const mainWindow = new BrowserWindow({ webPreferences: { webSecurity: true, allowRunningInsecureContent: false, // 关键:只允许 localhost:3001 的 fetch additionalArguments: ['--unsafely-treat-insecure-origin-as-secure=http://localhost:3001'], // 但必须配合以下 CSP contextIsolation: true, preload: path.join(__dirname, 'preload.js') } })

    然后在preload.js里暴露严格 API:

    const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('t3codeApi', { request: async (endpoint, options) => { // 强制限定 endpoint 只能是 /api/v1/ 开头 if (!endpoint.startsWith('/api/v1/')) throw new Error('Invalid endpoint') return await ipcRenderer.invoke('safe-fetch', endpoint, options) } })
  • 服务可用性兜底:GUI 启动时,localhost:3001可能因 CLI 未运行、模型加载失败、端口被占用而不可达。我们设计了三级探测:

    1. fetch('http://localhost:3001/health')—— HTTP 层;
    2. net.connect({ port: 3001, host: '127.0.0.1' })—— TCP 层(防 nginx 占用);
    3. execSync('lsof -i :3001 | grep LISTEN')—— 进程层(确认是 t3code 进程在监听)。
      任一环节失败,GUI 就显示“服务未就绪”,并提供t3code --daemon-start按钮,点击后调用ipcRenderer.invoke('start-daemon'),主进程执行spawn('t3code', ['--daemon-start'])。
  • 敏感操作隔离:GUI 里有“删除模型”按钮,但这不是直接fs.rmSync(modelPath),而是调用ipcRenderer.invoke('delete-model', modelId),主进程收到后,先验证modelId是否在~/.t3code/models/index.json里,再执行删除,并同步更新索引文件——防止用户通过 DevTools 注入恶意路径。

3.3 模型加载器的三重校验:为什么model not found错误总在启动时爆发?

lm studio cli 启动模型时提示 “model not found”是高频问题,根源不在路径错误,而在模型加载器的校验逻辑缺失。t3code 的加载流程强制执行三重校验:

  1. 文件存在性校验:
    stat(modelPath)检查文件是否存在、是否为 regular file、是否可读。这里有个陷阱:某些用户把模型 symlink 到 NAS,而 macOS 的stat对 NFS 挂载点返回EIO错误。我们的补丁是:捕获EIO后,改用fs.access(modelPath, fs.constants.R_OK)二次确认。

  2. 格式完整性校验:
    llama.cpp 模型文件开头有 magic number0x89 0x4c 0x61 0x76 0x31 0x31 0x32 0x34(对应 "LLaMA" 字符串),但很多用户下载的.gguf文件被浏览器截断。t3code 加载前会读取前 16 字节,比对 magic,并用xxd -l 16 model.gguf | head -1验证。如果 magic 不匹配,立即报错“模型文件损坏”,而不是等到llama_load_model_from_file返回 NULL。

  3. 硬件兼容性校验:
    这是最容易被忽略的一环。M1 Mac 上,llama.cpp编译时需开启LLAMA_ACCELERATE,否则 CPU 模式下推理速度极慢;而 Windows x64 需要 AVX2 指令集支持。t3code 在加载前执行:

    # macOS sysctl -n hw.optional.arm64 # 返回 1 表示 Apple Silicon # Windows cpuinfo | findstr "avx2" # PowerShell 里用 Get-CimInstance Win32_Processor | Select-Object -ExpandProperty DataWidth

    如果硬件不匹配,提前提示“当前 CPU 不支持该量化格式”,并推荐Q4_K_S(低精度)或Q2_K(超低精度)版本,而不是让模型加载一半崩溃。

常见问题:homebrew 安装失败常因/opt/homebrew权限问题。正确做法不是sudo chown -R $(whoami) /opt/homebrew(这破坏 Homebrew 安全模型),而是brew doctor后,按提示执行sudo chown -R $(whoami) $(brew --prefix)/lib,只修复库目录权限。我们把这条命令写进了t3code install --fix-permissions子命令里,用户一键修复。

4. 实操过程:从零构建 t3code 的完整流水线(含 Homebrew Formula 和 winget Manifest)

4.1 构建 CLI 二进制:Rust + clap + tauri-build 的取舍

虽然 t3code 最终形态是 Electron 应用,但 CLI 二进制我们坚持用 Rust 重写,而非用 Electron 的electron-builder打包 Node.js 脚本。原因很实在:

  • 启动速度:Node.js CLI 平均启动 320ms(V8 初始化 + 模块加载),Rust 二进制是 12ms。对t3code /compact这种秒级操作,300ms 延迟就是“卡顿”。

  • 静态链接:cargo build --release生成的t3code二进制,用ldd t3code检查无任何动态依赖,可直接复制到/usr/local/bin/运行。而 Node.js 版本必须捆绑node二进制,体积从 8MB 涨到 42MB。

  • clap 的参数解析优势:clap的 derive 宏能自动生成--help,且支持子命令嵌套。例如t3code models pull llama3:8b的解析,clap自动生成ModelsSubcommand::Pull(PullArgs)结构体,字段名直接映射参数,无需手写yargs的command('pull <model>')链式调用。

构建流程如下:

# 1. 初始化 Cargo 工作区 cargo new t3code-cli --bin cd t3code-cli # 2. 添加依赖(关键) echo 'clap = { version = "4.5", features = ["derive"] }' >> Cargo.toml echo 'reqwest = { version = "0.12", features = ["json"] }' >> Cargo.toml echo 'serde = { version = "1.0", features = ["derive"] }' >> Cargo.toml # 3. 编写 main.rs(核心逻辑) use clap::Parser; #[derive(Parser)] struct Cli { #[arg(short, long)] model: Option<String>, #[arg(long)] compact: bool, #[arg(long)] resume: bool, } fn main() -> Result<(), Box<dyn std::error::Error>> { let args = Cli::parse(); if args.compact { // 调用本地服务 let client = reqwest::Client::new(); let res = client.post("http://localhost:3001/api/v1/compact") .json(&serde_json::json!({"model": args.model.unwrap_or("llama3:8b")})) .send() .await?; println!("{}", res.text().await?); } Ok(()) }

编译后,target/release/t3code就是纯静态二进制,可直接用于分发。

4.2 Electron 打包:从electron-packager到electron-builder的演进

早期我们用electron-packager,但遇到两个硬伤:

  • macOS 签名失效:electron-packager生成的.app在 macOS 13+ 无法通过公证(Notarization),因为它的Info.plist缺少com.apple.security.get-task-allow权限,而 Apple 要求所有带 JIT 的 App 必须显式声明。

  • Windows 安装包不兼容 winget:electron-packager输出的是 zip,而 winget 要求.msix或.exe(带数字签名)。我们试过用makensis打 NSIS 包,但 winget 的validate命令总报InstallerType not supported。

最终切换到electron-builder,并定制vue.config.js:

module.exports = { pluginOptions: { electronBuilder: { builderOptions: { // macOS 配置 mac: { category: 'public.app-category.developer-tools', hardenedRuntime: true, gatekeeperAssess: false, // 由 CI 环节公证 entitlements: 'build/entitlements.mac.plist', target: [ { target: 'default', arch: ['arm64', 'x64'] } ] }, // Windows 配置 win: { target: [ { target: 'msix', arch: 'x64' } ], publisherName: 'T3Code Team' } } } } }

关键文件build/entitlements.mac.plist内容:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> </dict> </plist>

这样生成的.msix包,winget validate100% 通过,且能自动添加开始菜单图标。

4.3 Homebrew Formula 编写:不只是brew install,而是融入 macOS 生态

t3code的 Homebrew Formula (t3code.rb) 不是简单复制 URL,而是深度集成 macOS 机制:

class T3code < Formula desc "CLI and Electron frontend for local LLM programming assistance" homepage "https://t3code.dev" url "https://github.com/t3code/t3code/releases/download/v1.2.0/t3code-1.2.0.mojave.bottle.tar.gz" sha256 "a1b2c3d4e5f6..." depends_on macos: ">= 11.0" # 明确最低系统要求 def install # 1. 安装 CLI 二进制 bin.install "t3code" # 2. 安装 Electron 应用到 /Applications appdir = "#{prefix}/Applications" system "mkdir -p #{appdir}" system "cp -r t3code.app #{appdir}/" # 3. 创建符号链接,让 brew services 管理守护进程 (var/"t3code").mkpath (var/"log/t3code").mkpath (etc/"t3code/config.yaml").write <<~EOS model_dir: "#{HOMEBREW_PREFIX}/share/t3code/models" log_level: info EOS end def post_install # 4. 设置开机自启(利用 launchd) plist_content = <<~EOS <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>homebrew.t3code.daemon</string> <key>ProgramArguments</key> <array> <string>#{bin}/t3code</string> <string>--daemon-start</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> </dict> </plist> EOS (plist).write plist_content end service do run [bin/"t3code", "--daemon-start"] keep_alive true log_path var/"log/t3code.log" end end

用户执行brew install t3code后,自动获得:

  • CLI 在/opt/homebrew/bin/t3code
  • GUI 在/opt/homebrew/Applications/t3code.app
  • 守护进程 plist 在/opt/homebrew/opt/t3code/homebrew.t3code.daemon.plist
  • brew services start t3code即可后台运行

4.4 winget Manifest 编写:让 Windows 用户感觉“这就是原生应用”

winget 的 manifest (t3code.installer.yaml) 必须精确到字节:

PackageIdentifier: T3Code.T3Code PackageName: t3code Publisher: T3Code Team License: MIT ShortDescription: Local LLM coding assistant with CLI and Electron UI Installers: - Architecture: x64 InstallerType: msix InstallerUrl: https://github.com/t3code/t3code/releases/download/v1.2.0/t3code_1.2.0_x64.msix InstallerSha256: a1b2c3d4e5f6... Scope: machine ProductCode: '{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}'

关键点:

  • Scope: machine表示全局安装(所有用户可用),而非user(仅当前用户);
  • ProductCode必须与 MSIX 包内的AppxManifest.xml一致,否则卸载时残留注册表;
  • InstallerSha256由certutil -hashfile t3code_1.2.0_x64.msix SHA256生成,winget 安装时会校验。

用户winget install t3code后,可在“开始菜单”搜索t3code启动 GUI,也可在 PowerShell 里直接t3code --help,体验无缝。

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪经验”

5.1codex cli 没有可用的终端或文件读取工具—— 实质是 stdin 重定向失效

这个报错看似是 codex cli 的问题,但根因在 t3code 的 CLI 封装层。当用户执行cat main.py | t3code "生成 docstring"时,t3code 需要读取 stdin,但某些 shell(如 zsh 的pipefail模式)会提前关闭管道。我们的解决方案是:在 Rust CLI 里,不直接std::io::stdin().read_to_string(),而是用atty::isnt(atty::Stream::Stdin)检测是否为管道输入,若是,则用tokio::io::stdin().read_to_end()异步读取,避免阻塞。

排查技巧:用strace -e trace=read,write t3code "test"(Linux)或dtruss -f t3code "test"(macOS)观察系统调用,如果read(0, ...)返回 0,说明 stdin 已 EOF,需检查上游命令是否异常退出。

5.2node 安装 codex cli 很慢—— npm registry 代理污染

很多用户在国内用npm install -g codex-cli卡在silly idealTree buildDeps,不是网络问题,而是.npmrc里配置了registry=https://registry.npm.taobao.org,而淘宝镜像已停服,但 npm 仍尝试连接,超时长达 30 秒。t3code 的安装脚本里内置检测:

if curl -sI https://registry.npmjs.org | grep "200 OK" > /dev/null; then echo "Using official npm registry" else echo "Warning: npm registry unreachable, using fallback" npm config set registry https://registry.npmjs.org fi

5.3mac 安装 homebrew 报错—— Rosetta 2 与 Apple Silicon 的 ABI 冲突

M1/M2 Mac 上,如果用户先装了 Rosetta 2 版 Homebrew(x86_64),再试图装 t3code 的 arm64 二进制,会报Bad CPU type in executable。正确流程是:

  1. 卸载旧版:/opt/homebrew-uninstaller.sh
  2. 用 arm64 终端(Terminal.app 的“使用 Rosetta”选项关闭)运行/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  3. brew install t3code
    我们把这三步写进了t3code install --macos-fix命令里,用户一键执行。

5.4Electron localhost 接口 404—— 不是服务没起,而是 CORS 预检失败

当 GUI 里 fetch 报 404,但curl http://localhost:3001/health返回正常,大概率是 OPTIONS 预检请求被拒绝。解决方案是在 CLI 的 localhost 服务里,显式处理 OPTIONS:

// 使用 warp 框架 let cors = warp::cors() .allow_origin("http://localhost:3000") // Electron 默认端口 .allow_headers(vec![http::header::CONTENT_TYPE, http::header::AUTHORIZATION]) .expose_headers(vec![http::header::CONTENT_TYPE]); let routes = health_route.or(compact_route).with(cors);

5.5删除 codex cli 指令—— 清理残留的不只是二进制

npm uninstall -g codex-cli只删 node_modules,但 codex-cli 可能在~/.codex/创建配置、在/usr/local/bin/codex留软链接、在~/Library/LaunchAgents/写 plist。t3code 的卸载脚本t3code uninstall会执行:

# 删除 CLI rm -f /opt/homebrew/bin/t3code # 删除 GUI rm -rf /opt/homebrew/Applications/t3code.app # 删除数据 rm -rf ~/.t3code/ # 删除 launchd launchctl unload ~/Library/LaunchAgents/homebrew.t3code.daemon.plist 2>/dev/null rm -f ~/Library/LaunchAgents/homebrew.t3code.daemon.plist # 清理 Homebrew 缓存 brew cleanup t3code

最后分享一个小技巧:t3code 的/model参数支持别名映射。在~/.t3code/config.yaml里写:

model_aliases: fast: llama3:8b.Q2_K accurate: llama3:8b.Q5_K_M

然后t3code /model fast就自动展开为llama3:8b.Q2_K,不用记复杂量化名。这个功能上线