sourman/cursor-driver
GitHub: sourman/cursor-driver
通过 CDP 协议从外部以编程方式读取和修改 Cursor 编辑器运行时状态,无需手动点击或重启。
Stars: 0 | Forks: 0
# cursor-driver
用于以编程方式驱动 Cursor 编辑器的工具和笔记:无需点击,无需每次更改后重启,而是通过其自身代码从应用外部读取并更改其实时运行时状态。
这个完整的示例演示了如何实时切换“使用我自己的 OpenAI key”设置。但该方法并不局限于此。它适用于 Cursor 存储在其响应式 store 中的任何内容,这包括了它的大部分设置。
Linux 是这里阻力最小的途径。这些理念可以移植到 macOS 和 Windows,但路径和启动参数并不通用。
## 你要处理的对象
Cursor 是一个构建在 VS Code 之上的 Electron 应用。
- 渲染进程就是 UI。Cursor 的 UI 是一个名为 Glass 的 React 应用。
- 主进程是服务端。它掌握着应用状态,并将其写入位于 `~/.config/Cursor/User/globalStorage/state.vscdb` 的 SQLite 文件中。
- 一个设置会同时存在于两个地方:渲染进程内存中的一个对象,以及 state.vscdb 中的一行记录。
这种分裂就是问题的关键所在。如果在 Cursor 运行时编辑 state.vscdb,渲染进程会将其内存中的副本重新覆盖你的修改。你的更改几秒钟后就会消失。要更改设置并使其生效,你必须调用渲染进程自身的代码。
这里没有本地 HTTP API,也没有可以通信的 socket。客户端和服务端通过 Electron 的进程内 IPC 进行通信。因此,从外部介入的方法是使用 Chrome DevTools Protocol (CDP):连接到渲染进程并在其中运行 JavaScript。
## 一次性设置
需要做三件事。
1. 在 Cursor 上开启 CDP 端口。
2. 修改 Cursor 的 Glass bundle,暴露出你想要调用的实时服务。
3. 清除 Cursor 的 v8 字节码缓存,否则它会运行旧的未修改代码。
然后重启 Cursor。它会提示安装看起来已损坏。那只是它的完整性检查发现了修改。忽略它即可。只要修改还在,每次启动它都会出现。
`bin/cursor-glass-plass-patch` 会执行步骤 2 和 3,并检查步骤 1。
### 1. 开启 CDP 端口
在启动 Cursor 的方式中添加 `--remote-debugging-port=9223`。在 Linux 上,可以在 `~/.local/share/applications/cursor.desktop` 放置一个用户级别的覆盖配置,将该参数添加到 Exec 行中。它仅绑定到 127.0.0.1。
我机器上的 9222 端口已被 Chrome 占用,所以选了 9223。你可以选择任何空闲端口。
### 2. 修改
我们想要的设置由 Cursor 响应式存储服务上的一个方法进行读写:
```
reactiveStorageService.setApplicationUserPersistentStorage(key, value)
```
该服务对象被困在一个闭包中。你无法从外部获取对它的引用。因此,我们在其构造函数中添加了一行,将引用交给我们:
```
globalThis.__cursorRS = this
```
需要修改的文件是:
```
/usr/share/cursor/resources/app/out/vs/workbench/workbench.glass.main.js
```
不是 `workbench.desktop.main.js`。那个是旧版的。Glass 渲染进程根本不会加载它。Glass bundle 是通过动态 import 加载的,因此当你列出页面已加载的资源时,它并不会显示出来。这个细节让我折腾了好一阵子。不要掉进同一个坑里。
运行 `bin/cursor-glass-patch`。它是幂等的,并会保留一个 `.orig` 备份。
### 3. 清除缓存
```
rm -rf ~/.config/Cursor/CachedData/* ~/.config/Cursor/Code\ Cache/*
```
修改脚本会执行此操作。如果不这样做,旧的已编译字节码就会生效,而你对源代码的修改将毫无作用。这部分情况会让你误以为修改失败了,但其实并没有。
## 驱动它
`bin/cursor-cdp` 是一个小型、零依赖的 CDP 客户端。它需要 Node 22 或更高版本(使用了内置的 `fetch` 和 `WebSocket`)。它在 Cursor 的渲染进程中运行 JavaScript。
```
bin/cursor-cdp targets # list pages/workers
bin/cursor-cdp eval 'document.title' # run JS in the workbench
bin/cursor-cdp --context isolated eval 'typeof define' # run JS in the isolated world
bin/cursor-cdp contexts # list execution contexts
```
修改完成后,通过 hook 读取该设置:
```
bin/cursor-cdp eval 'globalThis.__cursorRS.applicationUserPersistentStorage.useOpenAIKey'
```
通过真正的处理程序切换它:
```
bin/cursor-cdp eval 'globalThis.__cursorRS.setApplicationUserPersistentStorage("useOpenAIKey", true)'
```
最后一次调用通过 Cursor 自身的代码,更新了内存中的 store 并将其持久化到 state.vscdb。它会保留下来。无需点击,无需重启。
## 示例工具
`bin/cursor-openai-toggle` 将上述内容封装成了一个友好的切换工具。它需要 Python 3。它会读取实时值、切换它并确认。它还有一个 `--disk` 模式可以直接编辑 state.vscdb,但这种修改只在 Cursor 退出时才会保留。
```
bin/cursor-openai-toggle --status # show live and disk values
bin/cursor-openai-toggle # toggle
bin/cursor-openai-toggle --on
bin/cursor-openai-toggle --off
```
## 寻找你自己的 hook
这不局限于 OpenAI key。要驱动 Cursor 状态的其他部分:
1. 找到 setter。在 Glass bundle 中搜索字段名或你在 UI 中看到的标签。你要找的是类似 `someService.setSomething("yourKey", value)` 形状的调用,或者服务上的某个方法。
2. 找到该服务构造的位置,并注入 `globalThis.__x = this` 来获取实例。构造函数是一个安全的切入点,因为它总是会执行。
3. 清除缓存,重启,并通过 cursor-cdp 调用你的 hook。
`tools/` 中的两个侦察工具就是绘制此地图的方式。使用该驱动并不需要它们。
- `tools/memscan.py` 读取进程的内存以查找字符串。需要以 root 身份运行。我们就是这样确认某个值是否存在于 RAM 中,以及哪个进程持有它的。这对于排查“这个字符串到底有没有被加载”很有用。
- `bin/cursor-netmon` 通过 CDP 捕获渲染进程 N 秒内的网络流量。我们正是通过它了解到,切换设置根本不会发出任何网络请求,这也正是引导我们回到进程内状态的原因。
## 注意事项
- Cursor 更新会覆盖已修改的文件。请重新运行 `cursor-glass-patch`(并重启)。`.orig` 备份会跨运行保留。
- 在修改生效期间,出现“安装看起来已损坏”的提示是正常现象。忽略它即可。
- CDP 监听在 127.0.0.1 上。任何本地进程都可以驱动你的编辑器。这既是目的所在,也是风险所在。请留意你运行的程序。
- 这涉及到了 Cursor 的内部机制。Cursor 的不同版本会调整代码位置。如果脚本失效,可能是 bundle 的布局发生了变化。请根据字段名和上述步骤重新推导。
## 许可证
MIT。详情请见 [LICENSE](LICENSE)。
标签:Chrome DevTools Protocol, Electron, MITM代理, SOC Prime, 云资产清单, 开发工具, 运行时状态修改, 逆向工具, 逆向工程