magpie-cursor-relay 0.3.11 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.13
4
+
5
+ 卸载时删除本机根证书。此前 `setup.command uninstall` 只还原 Cursor 代理,证书和私钥都留在本机。
6
+
7
+ - 卸载按 SHA-1 指纹精确删除钥匙串里本插件的 `Cursor BYOK Local CA`(同名的其他工具证书不受影响),并删除 `data/ca` 目录及私钥;钥匙串删除失败时会明确提示手动删除。
8
+ - README 写明会安装受信任根证书、只在插件页移除不会删除证书,以及 Cursor 使用条款带来的账号风险;说明本插件为社区插件,未上架插件市场。
9
+ - 验证:插件测试 73 项通过(新增卸载删证书回归测试)。引擎与 0.3.12 相同。
10
+
11
+ ## 0.3.12
12
+
13
+ Cursor 模型菜单只显示 Magpie 里的模型名,不再在后面拼接 ` · provider/model` 完整 ID;完整 ID 仍在悬停提示里(`Magpie / <id>`)。
14
+
15
+ - 引擎显示名由「名字 · ID」改为「名字」,引擎重新构建,`integrity.json` 校验值已更新。引擎内部模型编号由显示名参与计算,升级后 Cursor 里已选的模型可能需要重新选一次。
16
+ - `scripts/build-native.sh` 尊重 `CARGO_TARGET_DIR`,可复用编译缓存。
17
+ - 验证:引擎单测 3 项(新增显示名断言)、Lodestar 回归 3 项、插件测试 72 项通过。
18
+
3
19
  ## 0.3.11
4
20
 
5
21
  修复 `magpie cursor-bridge <模型>` 命令行连接报 `ReferenceError: process is not defined`:Magpie 命令行在没有 `process` 的沙箱里执行插件的 `connect()`,自动启动检查现在先判断 `process` 是否存在,连接配置照常写出,由已在运行的后台桥接自行接管。
package/README.md CHANGED
@@ -2,7 +2,15 @@
2
2
 
3
3
  让 Cursor 编辑器直接使用 Magpie 中配置的全部模型与路由组。模型菜单返回 Magpie 模型目录,隐藏 Cursor 默认 Auto 入口,保留 Cursor 原有登录。
4
4
 
5
- 当前发布 **0.3.11,仅随包提供 Apple Silicon macOS 引擎**;Windows / Intel macOS 暂无对应发布产物。
5
+ 当前发布 **0.3.13,仅随包提供 Apple Silicon macOS 引擎**;Windows / Intel macOS 暂无对应发布产物。
6
+
7
+ > **社区插件,未上架 Magpie 插件市场。** 安装前请先了解下面两点风险,自行决定是否使用。
8
+
9
+ ## 使用前必读
10
+
11
+ - **会安装一个受信任的根证书。** 本插件要截获 Cursor 与其后端(`*.cursor.sh`)之间的 HTTPS 流量,才能把模型请求转给 Magpie,所以初始化时会在本机生成名为 `Cursor BYOK Local CA` 的根证书(有效期约 10 年),并加入当前用户登录钥匙串的受信任根证书。私钥保存在 `~/.config/magpie-cursor-bridge/data/ca/`,能读取该文件的程序可以冒充任意 HTTPS 网站。
12
+ - **卸载会删除这张证书和私钥**(0.3.13 起)。请用 `bash setup.command uninstall` 卸载;只在 Magpie 插件页移除插件**不会**执行卸载,证书会留下,此时请先运行卸载命令,或在「钥匙串访问」里手动删除 `Cursor BYOK Local CA`。
13
+ - **Cursor 账号风险。** 桥接会在本地应答 Cursor 的部分接口(账号、套餐、用量等),这类做法违反 Cursor 的使用条款,存在被停用账号的风险。
6
14
 
7
15
  **和市场里其他 Cursor 插件的区别**
8
16
 
@@ -22,7 +30,7 @@
22
30
 
23
31
  ### 安装方式
24
32
 
25
- 在 Magpie → **Plugins** 搜索 **Magpie-Cursor-Relay** 安装,或在终端执行:
33
+ 本插件不在 Magpie 插件市场,请在终端执行:
26
34
  ```text
27
35
  magpie plugin add magpie-cursor-relay
28
36
  ```
@@ -82,7 +90,7 @@ magpie plugin add magpie-cursor-relay
82
90
  | :--- | :--- | :--- |
83
91
  | **启动/更新服务** | `powershell -ExecutionPolicy Bypass -File setup.ps1` | `bash setup.command` |
84
92
  | **查看后台运行状态** | `powershell -ExecutionPolicy Bypass -File setup.ps1 status` | `bash setup.command status` |
85
- | **停用并卸载后台服务** | `powershell -ExecutionPolicy Bypass -File setup.ps1 uninstall` | `bash setup.command uninstall` |
93
+ | **停用并卸载(含删除根证书)** | `powershell -ExecutionPolicy Bypass -File setup.ps1 uninstall` | `bash setup.command uninstall` |
86
94
 
87
95
  - **断开连接**:在 Magpie 的 Agents 页面选择 Disconnect,或在终端执行 `magpie cursor-bridge default`。
88
96
  - **代理自愈与还原**:当桥接服务退出或在 Magpie 中断开连接时,Cursor 的代理配置会自动恢复为初始状态。
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/cursor-magpie-bridge
3
- size: 47264576 bytes
4
- sha256: 2ed05bbb3f9e42f2874f3417324655d1f7f2c0c30fa00430bf965e98c4def327
3
+ size: 47265904 bytes
4
+ sha256: 631a94fc402efa38cefa79264171cafdf2dc1308e02a4f2cec59d4cc0cd306bf
package/install.js CHANGED
@@ -4,7 +4,7 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import {fileURLToPath} from 'node:url';
6
6
  import {atomic,restoreProxy} from './lib/settings.js';
7
- import {paths,run,verifyPackage,initializeCertificate,stage,activate,stopJob,label} from './lib/install.js';
7
+ import {paths,run,verifyPackage,initializeCertificate,removeCertificate,stage,activate,stopJob,label} from './lib/install.js';
8
8
  const source=path.dirname(fileURLToPath(import.meta.url));
9
9
  const manifest=JSON.parse(fs.readFileSync(path.join(source,'package.json'),'utf8'));
10
10
  const command=process.argv[2]||'setup';
@@ -73,7 +73,10 @@ try {
73
73
  if(magpie) run(magpie,['plugin','rm',p.target],{optional:true});
74
74
  if(p.plist && fs.existsSync(p.plist)) fs.unlinkSync(p.plist);
75
75
  if(p.startupScript && fs.existsSync(p.startupScript)) fs.unlinkSync(p.startupScript);
76
- console.log('后台已卸载,Cursor 代理已还原。若从 Magpie 插件页安装,请同时移除该插件。证书、数据及恢复记录保留。');
76
+ const ca=removeCertificate(p);
77
+ console.log(ca.removed
78
+ ? '后台已卸载,Cursor 代理已还原,本机根证书及其私钥已删除。若从 Magpie 插件页安装,请同时移除该插件。会话数据保留。'
79
+ : `后台已卸载,Cursor 代理已还原,证书私钥已删除;钥匙串里的证书未能删除(SHA-1 ${ca.sha1}),请在「钥匙串访问」里手动删除 Cursor BYOK Local CA。`);
77
80
  } else {
78
81
  verifyPackage(source);
79
82
  const isWin = process.platform === 'win32';
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.11",
2
+ "version": "0.3.13",
3
3
  "engine_version": "0.2.1",
4
- "engine": "2ed05bbb3f9e42f2874f3417324655d1f7f2c0c30fa00430bf965e98c4def327"
4
+ "engine": "631a94fc402efa38cefa79264171cafdf2dc1308e02a4f2cec59d4cc0cd306bf"
5
5
  }
package/lib/install.js CHANGED
@@ -106,6 +106,29 @@ export function initializeCertificate(engine,p,exec=run,{trust=true}={}) {
106
106
  }
107
107
  return state;
108
108
  }
109
+ // Undo initializeCertificate: untrust and delete exactly our CA (matched by
110
+ // SHA-1, so another tool's CA with the same name is left alone), then delete
111
+ // the CA directory with its private key. Returns whether the store still holds it.
112
+ export function removeCertificate(p,exec=run) {
113
+ const dir=path.join(p.root,'data/ca');
114
+ const cert=path.join(dir,'ca.crt');
115
+ if(!fs.existsSync(cert)) { fs.rmSync(dir,{recursive:true,force:true}); return {removed:true}; }
116
+ const pem=fs.readFileSync(cert,'utf8');
117
+ const der=Buffer.from(pem.replace(/-----[^-]+-----|\s/g,''),'base64');
118
+ const sha1=createHash('sha1').update(der).digest('hex').toUpperCase();
119
+ let removed;
120
+ if (process.platform === 'win32') {
121
+ exec('powershell',['-NoProfile','-Command',`Start-Process certutil -ArgumentList '-delstore Root ${sha1}' -Verb RunAs -Wait`],{optional:true});
122
+ removed=!exec('certutil',['-store','Root',sha1],{optional:true}).stdout?.toUpperCase().includes(sha1);
123
+ } else {
124
+ const keychain=path.join(p.home,'Library/Keychains/login.keychain-db');
125
+ exec('/usr/bin/security',['remove-trusted-cert',cert],{optional:true,stdio:'inherit'});
126
+ exec('/usr/bin/security',['delete-certificate','-Z',sha1,keychain],{optional:true});
127
+ removed=!(exec('/usr/bin/security',['find-certificate','-a','-Z',keychain],{optional:true}).stdout||'').includes(sha1);
128
+ }
129
+ fs.rmSync(dir,{recursive:true,force:true});
130
+ return {removed,sha1};
131
+ }
109
132
  export function plistText(p,runtime,release) {
110
133
  const esc=s=>s.replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;');
111
134
  const args=[runtime,path.join(release,'bridge.js'),'supervise'].map(s=>`<string>${esc(s)}</string>`).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magpie-cursor-relay",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "type": "module",
5
5
  "description": "Magpie-Cursor-Relay:让 Cursor 用 Magpie 的全部模型与路由组(Apple Silicon macOS)",
6
6
  "license": "MIT",
@@ -17,7 +17,7 @@ git -C "$bridge_build/source" apply "$bridge_package/source.patch"
17
17
  CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-2}" cargo test --manifest-path "$bridge_build/source/Cargo.toml" --release --locked -p cursor-server --test lodestar_replay_repro
18
18
  CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-2}" cargo build --manifest-path "$bridge_build/source/Cargo.toml" --release --locked -p cursor-server --bin cursor-magpie-bridge
19
19
  mkdir -p "$bridge_package/bin"
20
- cp "$bridge_build/source/target/release/cursor-magpie-bridge" "$bridge_package/bin/cursor-magpie-bridge.new"
20
+ cp "${CARGO_TARGET_DIR:-$bridge_build/source/target}/release/cursor-magpie-bridge" "$bridge_package/bin/cursor-magpie-bridge.new"
21
21
  chmod 755 "$bridge_package/bin/cursor-magpie-bridge.new"
22
22
  codesign --force --sign - "$bridge_package/bin/cursor-magpie-bridge.new"
23
23
  mv "$bridge_package/bin/cursor-magpie-bridge.new" "$bridge_package/bin/cursor-magpie-bridge"
package/source.patch CHANGED
@@ -91,7 +91,7 @@ new file mode 100644
91
91
  index 0000000..29b7fc2
92
92
  --- /dev/null
93
93
  +++ b/server/src/bin/cursor-magpie-bridge.rs
94
- @@ -0,0 +1,151 @@
94
+ @@ -0,0 +1,152 @@
95
95
  +//! Headless Cursor protocol bridge. No desktop/account writes or management API.
96
96
  +use std::{path::PathBuf, sync::Arc, time::Duration};
97
97
  +use cursor_server::{api, config, cursor::{prompting::{PromptAssets, PromptCompiler}, transport::TransportRegistry}, local_app::CursorHarness, model::ModelConfigInput, network::NetworkClients, plugin::{PluginRegistry, PluginRuntime}, provider::ProviderRouter, search::WebCache, store::Store};
@@ -117,7 +117,7 @@ index 0000000..29b7fc2
117
117
  + models.into_iter().enumerate().map(|(i,m)| {
118
118
  + if !seen.insert(m.id.clone()) { return Err("duplicate model id".into()); }
119
119
  + Ok(serde_json::from_value(json!({
120
- + "sort_order": i, "display_name": if m.name.is_empty() {m.id.clone()} else {format!("{} · {}", m.name, m.id)},
120
+ + "sort_order": i, "display_name": if m.name.is_empty() {m.id.clone()} else {m.name.clone()},
121
121
  + "group_name":"Magpie", "type":"openai", "base_url": c.gateway.trim_end_matches('/'),
122
122
  + "api_key":c.key, "tooltip_data":format!("Magpie / {}",m.id), "model_id":m.id,
123
123
  + "context_window_tokens":m.context.filter(|v| *v > 0),
@@ -215,6 +215,7 @@ index 0000000..29b7fc2
215
215
  + fn selected_model_first_and_route_preserved() {
216
216
  + let models=catalogue(&serde_json::to_vec(&config()).unwrap()).unwrap();
217
217
  + assert_eq!(models[0].model_id,"p/b");
218
+ + assert_eq!(models[0].display_name,"B");
218
219
  + assert_eq!(models[1].context_window_tokens,Some(200000));
219
220
  + assert_eq!(models[0].base_url,"http://127.0.0.1:3425/v1");
220
221
  + assert_eq!(models[0].api_key,"test-only");
@@ -7,7 +7,7 @@ import {fileURLToPath} from 'node:url';
7
7
  import {spawnSync} from 'node:child_process';
8
8
  import {createRequire} from 'node:module';
9
9
  import {createHash} from 'node:crypto';
10
- import {paths,verifyPackage,initializeCertificate,stage,activate,plistText,label} from '../lib/install.js';
10
+ import {paths,verifyPackage,initializeCertificate,removeCertificate,stage,activate,plistText,label} from '../lib/install.js';
11
11
 
12
12
  const isWin = process.platform === 'win32';
13
13
  const source=fileURLToPath(new URL('..',import.meta.url));
@@ -186,3 +186,21 @@ test('GitHub installation supports a dependency hoisted by Magpie Bun',t=>{
186
186
  const release=stage(plugin,paths(home),version,runtime);
187
187
  assert.ok(fs.existsSync(path.join(release,'node_modules/jsonc-parser/package.json')));
188
188
  });
189
+
190
+ test('uninstall removes exactly our CA from the keychain and deletes its private key',t=>{
191
+ if(isWin) return;
192
+ const home=temp(t);const p=paths(home);const dir=path.join(p.root,'data/ca');
193
+ fs.mkdirSync(dir,{recursive:true});
194
+ const der=Buffer.from('fake certificate body');
195
+ fs.writeFileSync(path.join(dir,'ca.crt'),`-----BEGIN CERTIFICATE-----\n${der.toString('base64')}\n-----END CERTIFICATE-----\n`);
196
+ fs.writeFileSync(path.join(dir,'ca.key'),'secret');
197
+ const sha1=createHash('sha1').update(der).digest('hex').toUpperCase();
198
+ for(const stillThere of [false,true]) {
199
+ fs.mkdirSync(dir,{recursive:true});fs.writeFileSync(path.join(dir,'ca.crt'),`-----BEGIN CERTIFICATE-----\n${der.toString('base64')}\n-----END CERTIFICATE-----\n`);
200
+ const calls=[];
201
+ const out=removeCertificate(p,(cmd,args)=>{calls.push(args);return {status:0,stdout:args[0]==='find-certificate'&&stillThere?`SHA-1 hash: ${sha1}`:''};});
202
+ assert.ok(calls.some(a=>a[0]==='delete-certificate'&&a[2]===sha1));
203
+ assert.equal(out.removed,!stillThere);
204
+ assert.equal(fs.existsSync(dir),false);
205
+ }
206
+ });