Cursor CLI 绿色便携版:从零构建一个免安装 AI 工具包
把 Cursor 的 AI Agent 能力做成一个 U 盘级别的便携工具——解压即用、用完即删、不污染宿主系统。这个需求来自一个真实场景:需要在不同用户的 Windows 机器上快速启动 AI 助手协助排查问题,而这些机器上不可能预装 Cursor IDE。
本文记录整个工具包从方案选型到最终落地的过程,重点放在环境隔离、API Key 安全存储和进程管理这几个实际踩坑比较多的地方。
需求和约束
目标很明确:一个自包含的 ZIP 压缩包,解压到任意 Windows 10/11 x64 机器上,双击 start.bat 就能使用 Cursor AI Agent 的全部能力(读写文件、执行命令、代码分析)。具体约束:
- 零安装:不依赖宿主系统的 Node.js、Python 或任何运行时
- 零残留:所有临时文件、配置、缓存都在工具包内部,删除文件夹即彻底清除
- 安全传输:API Key 不能明文存储,工具包可以安全地通过即时通讯传给别人
- 一键启动:用户不需要懂技术,双击 bat 就能用
方案选型
最初考虑过几个方向:
| 方案 | 优点 | 否决原因 |
|---|---|---|
| Docker 容器 | 完美隔离 | 用户机器没装 Docker,且镜像体积大 |
| Windows Sandbox | 系统级隔离 | 需要 Hyper-V,家庭版不支持 |
| QEMU 虚拟机 | 跨平台 | 启动慢、资源占用高、体积 GB 级 |
| 直接打包二进制 | 简单轻量 | 最终选择 |
容器方案全部否决的核心原因是一样的:它们解决的是「开发环境一致性」问题,但我要解决的是「在陌生机器上快速跑起来」的问题。后者对启动速度和体积有极高要求。
架构设计
最终的目录结构:
CursorToolkit/
├── start.bat # 入口(纯 ASCII,只设环境变量)
├── toolkit.py # 交互界面 + 全部业务逻辑
├── README.txt
├── bin/ # Cursor CLI(node.exe + index.js + agent.cmd + rg.exe)
├── python/ # Python 3.12 嵌入式包 + pip
│ ├── python.exe
│ ├── Scripts/pip.exe
│ └── python312._pth
└── data/ # 所有运行时数据(隔离边界)
├── config/key.enc # 加密的 API Key
├── temp/ # 临时文件(重定向 %TEMP%)
├── node_cache/ # Node 编译缓存
├── .cursor/
│ ├── mcp.json # MCP 配置
│ └── skills/ # Skills 定义
└── workspace/ # 默认工作目录
├── .cursor/rules/ # Agent 规则
├── AGENTS.md
├── CLAUDE.md
└── .cursorignore分层很清晰:bin/ 和 python/ 是不可变的运行时,data/ 是可变的用户数据和配置。整个 data/ 目录就是隔离边界——通过环境变量重定向,CLI 和 Python 的所有读写操作都被限制在这个目录内。
环境隔离:让 CLI「以为」自己装在这台机器上
这是整个方案最关键的部分。Cursor CLI 基于 Node.js,运行时会读写多个系统路径:%TEMP% 存临时文件、%USERPROFILE%\.cursor\ 存配置、%LOCALAPPDATA%\cursor-compile-cache\ 存编译缓存。如果不处理,这些文件会散落在宿主系统的各个角落。
start.bat 的核心就是在启动 Python 之前,把所有相关环境变量重定向到 data/ 下:
@echo off
chcp 65001 >nul 2>&1
set "PYTHONIOENCODING=utf-8"
set "T=%~dp0"
if "%T:~-1%"=="\" set "T=%T:~0,-1%"
set "PATH=%T%\bin;%T%\python;%T%\python\Scripts;%PATH%"
set "TEMP=%T%\data\temp"
set "TMP=%T%\data\temp"
set "CURSOR_CONFIG_DIR=%T%\data\config"
set "NODE_COMPILE_CACHE=%T%\data\node_cache"
set "USERPROFILE=%T%\data"
set "HOME=%T%\data"
set "HOMEDRIVE=%T:~0,2%"
set "HOMEPATH=%T:~2%\data"这里有几个踩坑点:
USERPROFILE 和 HOME 必须同时重定向。Cursor CLI 内部通过 Node.js 的 os.homedir() 定位 ~/.cursor/ 目录,而 os.homedir() 在 Windows 上的查找顺序是 HOME → USERPROFILE → HOMEDRIVE+HOMEPATH。三个都得改,否则某些代码路径会读到真实的用户目录。
set 命令必须用引号包裹。set TEMP=%T%\data\temp 和 set "TEMP=%T%\data\temp" 的区别在于:前者在值末尾可能带一个不可见的空格(取决于编辑器和换行符),导致路径拼接出 data \temp 这种诡异的结果。调试这个问题花了不少时间。
chcp 65001 放在第一行。CLI 的输出包含 Unicode 字符(进度条、图标),如果控制台代码页是默认的 GBK (936),这些字符会变成乱码或导致渲染错位。
Python 嵌入式包 vs 虚拟环境
Python 官方提供了一种「嵌入式包」(embeddable package),是一个不到 12MB 的 ZIP,解压后就是一个最小化的 Python 运行时。和完整安装器的区别:
| 嵌入式包 | 完整安装 / venv | |
|---|---|---|
| 体积 | ~12 MB | ~100+ MB |
| 注册表 | 不写 | 写入 |
| PATH | 不改 | 可选修改 |
| pip | 需手动安装 | 自带 |
| site-packages | 默认禁用 | 正常 |
嵌入式包默认禁用了 import site,意味着 pip 安装的第三方包不会被加载。需要编辑 python312._pth 文件,把 #import site 的注释去掉:
python312.zip
.
import sitepip 本身还有一个坑:pip.exe 内部硬编码了 Python 的绝对路径。如果在 A 机器上打包、在 B 机器上使用,路径不匹配就会报 Fatal error in launcher: Unable to create process。解决方案是生成 CMD 包装器,用相对路径调用 python -m pip:
@echo off
"%~dp0..\python\python.exe" -m pip %*API Key 安全存储
工具包需要通过网络传输,API Key 不能明文存储。但 CLI 不支持 OAuth 等交互式登录(--trust 只能在 headless 模式使用),只能通过 CURSOR_API_KEY 环境变量认证。
最终方案是用户设置一个密码,Key 用密码派生的密钥加密后存盘。启动时输入密码解密到内存,关闭窗口后内存清除:
_PBKDF2_ITER = 100_000
_VERIFY_PREFIX = b"OK:"
def _encrypt(password: str, api_key: str) -> bytes:
plaintext = _VERIFY_PREFIX + api_key.encode("utf-8")
salt = os.urandom(16)
dk = hashlib.pbkdf2_hmac(
"sha256", password.encode("utf-8"), salt, _PBKDF2_ITER, dklen=len(plaintext)
)
encrypted = bytes(a ^ b for a, b in zip(plaintext, dk))
return base64.b64encode(salt + encrypted)几个设计决策:
- PBKDF2 而非 AES:XOR 加密配合 PBKDF2 派生的等长密钥,在密码学上等价于流密码。优势是不需要引入
cryptography等第三方库,纯标准库实现。 OK:前缀:用于验证密码是否正确。解密后检查前缀匹配,不匹配说明密码错误。- 随机 salt:每次加密生成不同的密文,防止彩虹表攻击。
- 10 万次迭代:PBKDF2 的迭代次数,暴力破解一个 6 位密码需要数年。
密码输入使用自定义的 _mask_input 函数,通过 msvcrt.getwch() 逐字符读取,显示 * 号,支持退格。这比 getpass 的体验好,因为用户能看到自己输入了几个字符。
进程管理:防止僵尸进程
Cursor CLI 启动后会 fork 出多个子进程(Node.js 主进程、MCP Server 进程等)。如果用户直接关闭 CMD 窗口(点 X 而不是 Ctrl+D),Python 主进程被终止,但这些子进程可能变成孤儿进程继续占用资源。
解决方案是三层防护:
_active_pid = None
def _kill_tree(pid):
try:
subprocess.run(
["taskkill", "/T", "/F", "/PID", str(pid)],
capture_output=True, timeout=5,
)
except Exception:
pass
def _cleanup_on_exit():
if _active_pid:
_kill_tree(_active_pid)
# 第一层:正常退出时清理
atexit.register(_cleanup_on_exit)
# 第二层:Ctrl+Break / 窗口关闭时清理
if os.name == "nt":
def _ctrl_handler(sig, frame):
_cleanup_on_exit()
sys.exit(1)
signal.signal(signal.SIGBREAK, _ctrl_handler)
# 第三层:Agent 退出后主动清理进程树
proc = subprocess.Popen(cmd, cwd=str(WORKSPACE))
_active_pid = proc.pid
proc.wait()
_kill_tree(proc.pid) # 即使 Agent 正常退出,也清理残留的 MCP 进程
_active_pid = Nonetaskkill /T /F /PID 的 /T 参数会终止整个进程树(包括所有子进程),这比只杀主进程靠谱得多。
BAT + Python 混合架构
最初尝试把所有逻辑写在 BAT 里,包括菜单、密码输入、加密解密。很快发现这是条死路:
- BAT 不支持 UTF-8 字符串操作,中文界面要么乱码要么需要用
chcp配合 BOM 文件,极其脆弱 - BAT 没有加密库,实现 PBKDF2 不现实
- BAT 的
set /p不支持密码掩码 - BAT 的错误处理几乎不存在
最终架构是 BAT 做环境、Python 做逻辑:start.bat 是一个纯 ASCII 文件,只负责设置环境变量和调用 python toolkit.py。所有交互界面、业务逻辑、错误处理都在 toolkit.py 里。这样 BAT 永远不会遇到编码问题,Python 侧有完整的标准库可用。
打包脚本
整个工具包的构建由一个 Python 脚本完成(cursor_toolkit_pack.py)。它的工作流程:
- 从 Cursor CDN 下载最新 CLI(解析安装脚本获取版本号和下载地址)
- 下载 Python 3.12 嵌入式包,启用
import site,安装 pip - 检查 CLI 自带的
rg.exe(ripgrep),没有则单独下载 - 生成
start.bat、toolkit.py、README.txt - 生成配置模板文件(
mcp.json、AGENTS.md等) - 生成 pip CMD 包装器
- 打包为 ZIP
CLI 的获取优先从 CDN 下载,失败时回退到本地已安装的 CLI(%LOCALAPPDATA%\cursor-agent\versions\),再失败则提示用户手动指定路径。这种三级回退保证了脚本在不同网络环境下都能工作。
最终产物是一个约 56 MB 的 ZIP,解压后约 152 MB(CLI 占 120 MB,Python 占 32 MB)。
中文显示和输入
中文乱码
Windows CMD 默认编码是 GBK,而 CLI 输出是 UTF-8,两边对不上就会乱码。解决方法是在 BAT 脚本最前面加一行:
chcp 65001 >nul 2>&1这行命令把 CMD 切换成 UTF-8 编码,加了之后中文就正常了。>nul 2>&1 只是不让它打印多余的提示。
如果用 Python 脚本捕获 CLI 输出,也要手动指定 UTF-8 解码,否则 Python 会按系统默认的 GBK 去解读然后报错:
r = subprocess.run(["agent.cmd", "-p", "--trust", "reply OK"], capture_output=True)
output = r.stdout.decode("utf-8", errors="replace")中文输入
CLI 的 TUI 基于 Node.js 终端渲染库,对 Windows CMD 下的中文输入法支持不够好——输入法候选窗口位置偏移、有时需要按回车才能刷新显示。这是上游的问题,目前的缓解方案:
- 在记事本等应用中打好中文,粘贴到 Agent 对话框
- 用 Windows Terminal(
wt)替代传统 CMD 启动,IME 兼容性更好 - 使用 headless 模式:
start.bat -p "你的问题",直接得到回答
回顾
最终的工具包在实际使用中达到了预期效果:传 ZIP 给同事,解压,双击,输入 Key 和密码,就能在他的机器上用 AI Agent 帮他分析日志、读截图、搜文件。用完删除文件夹,机器上没有任何残留。
核心挑战不在于打包本身,而在于 环境隔离的彻底性。漏掉任何一个环境变量的重定向,CLI 就会在宿主系统上留下痕迹。这类问题在开发机上很难发现(因为开发机上已经有 Cursor 的完整安装),只有在干净的测试机上才会暴露。建议配合自动化测试脚本,检查每个环境变量的指向和每个目录的创建情况。