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 +16 -0
- package/README.md +11 -3
- package/bin/cursor-magpie-bridge +2 -2
- package/install.js +5 -2
- package/integrity.json +2 -2
- package/lib/install.js +23 -0
- package/package.json +1 -1
- package/scripts/build-native.sh +1 -1
- package/source.patch +3 -2
- package/test/install.test.js +19 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
|
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 的代理配置会自动恢复为初始状态。
|
package/bin/cursor-magpie-bridge
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/bin/cursor-magpie-bridge
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
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
|
-
|
|
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
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('&','&').replaceAll('<','<').replaceAll('>','>');
|
|
111
134
|
const args=[runtime,path.join(release,'bridge.js'),'supervise'].map(s=>`<string>${esc(s)}</string>`).join('');
|
package/package.json
CHANGED
package/scripts/build-native.sh
CHANGED
|
@@ -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,
|
|
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 {
|
|
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");
|
package/test/install.test.js
CHANGED
|
@@ -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
|
+
});
|