1. 项目概述:从“plugins”这个词开始,我们到底在聊什么?
“plugins”不是个新词,但最近半年它在开发者圈子里的热度,几乎追平了“agent”和“TypeScript”。你刷技术社区、看GitHub trending、甚至翻国内开发群聊天记录,十次里有七次会撞见这个词——但它从来不是孤立出现的。它总和Cursor绑定在一起,和plugin.json文件形影不离,和TypeScript SDK搭配使用,最后又悄悄滑进AI Agent的底层架构里。这不是巧合,而是一条正在成型的技术链路:本地化、可扩展、面向AI工作流的插件化开发范式。
我从去年底开始系统性地用 Cursor 做日常编码,前两个月还在用它写 React 组件、调试 Node.js 接口;到第三个月,我发现自己写的 80% 代码,其实是在围绕一个 plugin.json 文件打转——改 schema、调 activationEvents、试 runCommand 的返回值、反复 reload 插件看 console.log 是否输出。那时候我才意识到,“plugins”根本不是“给编辑器加个语法高亮”那种小功能,它是把整个开发环境变成一个可编程的 AI 协作沙盒。你写的不是传统意义上的“编辑器插件”,而是一个个能被 Agent 调度、能主动触发 LLM 调用、能读取当前文件上下文并生成结构化响应的轻量级服务单元。
举个最直白的例子:你写一个叫@linxin666/dsh-p的插件(这名字来自热搜里的报错),它可能只做一件事——当用户选中一段 SQL 语句,右键点击“解释这段查询”,插件就自动提取表名、字段、JOIN 条件,拼成 prompt 发给本地部署的 Ollama 模型,再把返回的自然语言解释插入到注释块里。整个过程不依赖网络请求(除非你主动配置),不打开新窗口,不打断当前编辑节奏。这就是 plugins 的真实形态:原子化、上下文感知、与 AI 深度耦合的执行单元。
所以,当你看到“failed to load plugins web boot: 2 entries did not activate”这类报错时,别急着删 node_modules 或重装 Cursor——这背后大概率是你的插件没通过 harness 的沙盒校验,或是 agent runtime 没正确加载 TypeScript 编译后的 dist 文件。它不是“插件没装好”,而是“AI 工作流的调度链断了”。这也是为什么“harness 和 agent 区别”、“agent 安全”、“agent 怎么扛并发”这些词会高频出现在热搜里:大家已经从“怎么写插件”,快速进化到了“怎么让插件在 AI 驱动的复杂环境中稳定、安全、高效地跑起来”。
适合谁来读这篇?如果你是刚用 Cursor 写了第一个 Hello World 插件的新手,这篇能帮你绕过 90% 的坑;如果你已经在用hermes-agent或pi-agent搭建自己的 agent 项目,这篇会告诉你 plugin.json 里那几行看似简单的字段,如何决定整个 agent 的启动顺序和权限边界;如果你正卡在“cursor 中文设置”或“cursor 设置中文回复”这种基础问题上——抱歉,那不属于 plugins 的范畴,那是 IDE 的 UI 层配置,我们不碰。我们只聚焦一件事:如何让一个 plugin 成为真正可调度、可编排、可审计的 AI 工作流组件。
2. 插件系统设计逻辑:为什么不是 VS Code 的那一套?
2.1 本质差异:从“UI 扩展”到“AI 执行体”
很多人第一次写 Cursor 插件时,下意识去翻 VS Code 的 Extension API 文档,结果越看越懵。不是因为文档写得差,而是两者的设计哲学完全不同。VS Code 插件的核心目标是增强编辑器 UI 和编辑能力:加个按钮、改个颜色、提供代码补全——所有行为都发生在用户界面上,最终目的是提升人机交互效率。而 Cursor 的 plugins,核心目标是成为 AI Agent 的可执行模块:它不一定要有 UI(很多 plugin 根本没有 view、webview 或 contribution point),它的激活时机不是“用户点了菜单”,而是“agent 判断此刻需要调用这个能力”。
我拿一个真实案例对比:
- VS Code 插件
Prettier:监听onCommand:prettier.format,用户按快捷键或点菜单后,读取当前文档文本 → 调用 prettier-core → 返回格式化后字符串 → 替换编辑器内容。整个流程是单向、同步、由人触发的。 - Cursor 插件
@huayu-yuan/sql-explainer(参考热搜里那个 failed to load 的名字):注册activationEvents: ["onLanguage:sql"],但真正激活是在 agent runtime 解析用户 query 时,发现上下文含 SQL 片段 → 触发runCommand:sql.explain→ 插件从workspace.rootPath读取.env获取本地模型地址 → 构造 prompt → 调用fetch发送 POST → 解析 JSON 响应 → 返回{ explanation: "该查询先扫描 users 表...", complexity: "O(n)" }结构化对象 → agent 将其注入到下一步 prompt 中。
看到区别了吗?前者是“工具”,后者是“协作者”。VS Code 插件是编辑器的仆人,Cursor 插件是 agent 的队友。这就决定了它的 manifest 文件、生命周期、通信机制、错误处理方式,全都得重来。
2.2 plugin.json:不是配置文件,而是“能力契约书”
plugin.json看似只是个 JSON 文件,但它是整个插件系统的中枢神经。它不叫package.json,也不叫manifest.json,就叫plugin.json——这个命名本身就暗示了它的唯一性:它定义的不是“怎么打包”,而是“我能提供什么能力、在什么条件下可用、由谁来调用我”。
我们拆解一个最小但完整的plugin.json:
{ "name": "@myorg/translate-code", "version": "0.1.0", "displayName": "Code Translator", "description": "Translate comments and docstrings between English and Chinese", "publisher": "myorg", "engines": { "cursor": "^0.45.0" }, "activationEvents": [ "onCommand:translate.code" ], "main": "./dist/extension.js", "contributes": { "commands": [ { "command": "translate.code", "title": "Translate Selected Code" } ] }, "ai": { "capabilities": ["translation", "context-aware"], "sandbox": { "allowedOrigins": ["http://localhost:3000"], "allowedPermissions": ["fs:read", "network:fetch"] } } }重点不在name或version,而在ai这个顶层字段。这是 VS Code 里绝对没有的。它告诉 harness:
- 这个插件声明自己具备
translation和context-aware两种能力标签,agent 在规划 workflow 时,会根据这些标签匹配任务; - 它运行在沙盒中,只允许访问本地
fs:read(读取当前项目中的 i18n 配置)和network:fetch(调用内部翻译 API),其他如process.env、require、eval全部禁止; allowedOrigins不是 CORS 白名单,而是指明“只有来自http://localhost:3000的 agent 沙盒实例才能加载我”,防止恶意网页注入。
提示:
activationEvents里的onCommand:并非用户手动触发的入口。在 agent 场景下,harness 会模拟 command 触发,作为插件激活的信号。真正的调用链是:agent → harness → plugin.json → activationEvents → 加载 main → 执行 runCommand。所以如果你的插件没写activationEvents,harness 根本不会把它放进启动队列,直接报 “did not activate”。
2.3 TypeScript SDK:不是类型定义,而是“沙盒运行时接口”
Cursor 官方提供的 TypeScript SDK,名字叫@cursor/sdk,但它和@types/node这类纯类型库有本质区别。它包含两层:
- 第一层是类型定义(
index.d.ts),告诉你runCommand函数接收什么参数、返回什么 Promise; - 第二层是沙盒内核的桩代码(stub),比如
fetch函数的实现,它不是浏览器原生 fetch,而是 harness 注入的受控版本,会检查allowedOrigins、记录请求日志、限制超时时间。
我实测过:如果你在插件里直接import { fetch } from 'node:undici',harness 启动时会直接报错Error: Module not allowed in sandbox。必须用 SDK 提供的fetch,因为它已被 harness 劫持并注入了审计钩子。
SDK 还封装了一个关键类:AgentContext。它不是全局变量,而是每次runCommand调用时,harness 传入的上下文对象:
import { AgentContext, runCommand } from '@cursor/sdk'; export async function activate(context: AgentContext) { // context.workspaceRoot: 当前打开的项目根路径(沙盒内路径,非真实 fs) // context.selectedText: 用户当前选中的文本(如果有的话) // context.fileUri: 当前活动文件的 URI(沙盒内虚拟 URI) // context.agentId: 调用此插件的 agent 实例 ID(用于 trace 和 quota 控制) runCommand('translate.code', async (params) => { const text = params.text || context.selectedText; const targetLang = params.lang || 'zh'; // 注意:这里不能直接 new URL(),因为沙盒禁用构造函数 // 必须用 SDK 提供的 createUrl() const url = context.createUrl('http://localhost:3000/api/translate'); const res = await context.fetch(url, { method: 'POST', body: JSON.stringify({ text, targetLang }), headers: { 'Content-Type': 'application/json' } }); return res.json(); }); }看到没?context.fetch、context.createUrl、context.selectedText——这些都不是 Node.js 或浏览器原生 API,而是 harness 提供的、带权限控制和审计能力的代理接口。SDK 的价值,就是让你在写业务逻辑时,完全不用操心沙盒限制,所有危险操作都被封装在context方法里,且每个调用都会被记录到harness.log中。
2.4 Harness:不是加载器,而是“AI 工作流的交通警察”
“harness failed to load plugins” 这个报错,90% 的人以为是插件代码错了。其实恰恰相反——harness 报错,说明插件代码很可能没问题,而是 harness 自己拒绝加载它。Harness 是 Cursor 插件系统的守门人,它的职责不是“把插件跑起来”,而是“确保插件在安全、合规、可追溯的前提下被 agent 调用”。
Harness 的启动流程分三步:
- 静态校验:读取
plugin.json,检查engines.cursor版本是否兼容、ai.sandbox.allowedPermissions是否在白名单内、main指向的文件是否存在; - 动态沙盒初始化:为每个插件创建独立 V8 Context(不是 Node.js 的 vm 模块,而是基于 QuickJS 的轻量沙盒),注入 SDK stub,并设置内存/ CPU 限额(默认 128MB / 1s);
- 激活事件广播:向所有通过校验的插件发送
activationEvents,等待它们调用runCommand注册 handler;若超时(默认 3s)未注册,则标记为 “did not activate”。
所以当你看到web boot: 1 entry did not activate,第一反应不应该是“我的 activate 函数写错了”,而应该是:
- 检查
plugin.json里的activationEvents是否匹配runCommand的命令名; - 查
harness.log,看沙盒初始化阶段是否有PermissionDeniedError; - 运行
cursor --log-level=debug,捕获 harness 启动时的完整 trace。
Harness 还有个隐藏机制:插件优先级队列。它不是按文件顺序加载,而是按plugin.json里的ai.capabilities标签排序。比如 agent 要执行code-review任务,harness 会优先加载capabilities包含static-analysis的插件,再加载test-generation,最后才是documentation。这个顺序不可配置,由 harness 内置策略决定。这也是为什么有些插件明明代码没问题,却总在报错列表里排第一——它被排在了启动队列头部,而前面某个插件失败导致整个队列中断。
3. 核心细节解析:从零写出一个可被 agent 调用的插件
3.1 目录结构与构建链:为什么必须用 TypeScript + esbuild?
Cursor 插件不支持直接运行.ts文件,也不接受tsc编译出的 CommonJS。它强制要求:
- 源码用 TypeScript(
.ts); - 构建产物必须是 ESM 格式(
.js+type: "module"); - 输出目录必须是
dist/,且入口文件名必须与plugin.json的main字段一致(默认./dist/extension.js); - 不能有
require()、__dirname、process.cwd()等 Node.js 特有 API。
我踩过的最大坑:用 Webpack 构建。Webpack 默认生成 IIFE 或 UMD,即使配置output.libraryType: "module",也会注入 runtime 代码,导致 harness 加载时报SyntaxError: Cannot use import statement outside a module。后来换成 esbuild,一行命令搞定:
esbuild src/extension.ts \ --bundle \ --platform=node \ --target=es2020 \ --format=esm \ --outfile=dist/extension.js \ --external:@cursor/sdk \ --minify关键参数解释:
--platform=node:告诉 esbuild 按 Node.js 环境解析模块;--target=es2020:Cursor harness 基于 QuickJS,只支持到 ES2020,不支持??=、?.等新语法;--format=esm:强制输出 ESM,这是 harness 唯一接受的格式;--external:@cursor/sdk:把 SDK 作为外部依赖,不打包进去(harness 会自行注入);--minify:可选,但建议开启,减少沙盒加载时间。
注意:
src/extension.ts里不能写import * as sdk from '@cursor/sdk',必须用import { runCommand, AgentContext } from '@cursor/sdk'。esbuild 的 tree-shaking 会剔除未使用的导出,但如果用* as sdk,它无法判断哪些方法实际被调用,会导致 bundle 体积暴增,甚至触发 harness 的内存限制。
3.2 plugin.json 关键字段详解:每一行都是生产环境的生死线
plugin.json看似简单,但每个字段都直接影响插件能否上线、能否被 agent 调用、能否通过安全审计。我们逐行深挖:
| 字段 | 必填 | 说明 | 生产环境避坑点 |
|---|---|---|---|
name | 是 | npm 包名格式,必须以@scope/name开头(如@myorg/translate)。Scope 名称会被 harness 用作沙盒隔离标识。 | Scope 名称不能含大写字母或下划线,否则 harness 启动时报Invalid scope name;Scope 名称一旦发布,不可更改,否则已安装的插件无法更新。 |
version | 是 | 语义化版本号。harness 会比对engines.cursor和当前 Cursor 版本,不匹配则拒绝加载。 | 不要用0.0.1作为正式版,harness 对0.x版本有额外校验(如要求ai.sandbox必须显式声明),建议从1.0.0起步。 |
activationEvents | 是 | 插件激活条件数组。支持onCommand:xxx、onLanguage:xxx、onUriScheme:xxx。注意:onLanguage不是文件后缀,而是 Language ID(如typescript不是ts)。 | 多个 activationEvents 是 OR 关系,不是 AND。如果写了["onCommand:a", "onCommand:b"],只要任一 command 被触发,插件就激活。不要试图用它做权限控制。 |
main | 是 | 入口文件路径,相对于 plugin.json。必须是 ESM 格式 JS 文件。 | 路径必须用 Unix 风格斜杠/,Windows 的\会导致 harness 读取失败;路径不能以../开头,harness 只允许读取插件目录内的文件。 |
ai.capabilities | 否但强烈建议 | 字符串数组,声明插件能力标签。agent 用它做 workflow 规划。标准标签有code-generation,code-review,translation,test-generation,documentation,debugging。 | 自定义标签(如myorg:custom)会被 harness 接受,但 agent 可能无法识别。建议优先用标准标签,再加自定义前缀。 |
ai.sandbox.allowedPermissions | 否但生产环境必填 | 数组,声明插件需要的权限。合法值:fs:read,fs:write,network:fetch,clipboard:read,clipboard:write,env:read。fs:write默认禁止,需单独申请。 | env:read仅允许读取process.env.CURSOR_PLUGIN_*前缀的变量,其他环境变量一律为空;fs:write需在ai.sandbox下显式声明"writePaths": ["./logs/"],且路径必须是相对路径。 |
特别提醒ai.sandbox的两个隐藏规则:
allowedOrigins不是通配符,["*"]无效,必须写具体域名(如["https://api.myorg.com"]);- 如果插件需要调用本地服务(如
http://localhost:3000),必须在allowedOrigins中显式列出,且 harness 会验证该 origin 是否在localhost或127.0.0.1范围内,否则拒绝加载。
3.3 TypeScript SDK 实战:如何写出既安全又高效的插件逻辑
SDK 的核心是runCommand和AgentContext。但新手常犯的错误是:把插件写成一个巨型函数,所有逻辑堆在runCommand回调里。这样会导致三个问题:
- 单元测试困难(无法 mock context);
- 错误边界模糊(一个 try-catch 包不住所有异步分支);
- agent 无法做细粒度的 timeout 控制(harness 只能控制整个 command 的超时)。
正确的写法是:分层 + 显式错误分类 + 上下文透传。
// src/extension.ts import { runCommand, AgentContext, PermissionDeniedError, TimeoutError } from '@cursor/sdk'; // 第一层:领域逻辑(纯函数,无副作用,可单元测试) export async function translateText( text: string, targetLang: string, context: AgentContext ): Promise<{ translated: string; confidence: number }> { if (!text.trim()) { throw new Error('Empty text cannot be translated'); } // 沙盒内安全的 URL 构造 const url = context.createUrl('http://localhost:3000/api/translate'); try { const res = await context.fetch(url, { method: 'POST', body: JSON.stringify({ text, targetLang }), headers: { 'Content-Type': 'application/json' } }); if (!res.ok) { throw new Error(`Translation API returned ${res.status}`); } const data = await res.json(); return { translated: data.translated, confidence: data.confidence || 0.8 }; } catch (err) { if (err instanceof PermissionDeniedError) { // harness 拒绝了 fetch 权限 throw new Error('Translation service unreachable: permission denied'); } if (err instanceof TimeoutError) { // harness 主动中断了请求 throw new Error('Translation timed out'); } throw err; // 其他错误原样抛出 } } // 第二层:command handler(只做参数校验、context 透传、错误包装) export async function activate(context: AgentContext) { runCommand('translate.code', async (params) => { try { // 参数校验(agent 可能传任意 JSON,必须防御) const text = typeof params.text === 'string' ? params.text : context.selectedText; const targetLang = typeof params.lang === 'string' ? params.lang : 'zh'; // 调用领域逻辑 const result = await translateText(text, targetLang, context); // 返回结构化结果(agent 会解析这个对象) return { success: true, ...result, timestamp: Date.now() }; } catch (err) { // 统一错误格式,便于 agent 做分类处理 return { success: false, error: { message: err.message, code: err.name || 'UNKNOWN_ERROR' } }; } }); }这个结构的优势:
translateText可以单独写 Jest 测试,mockcontext.fetch;runCommand回调里只做薄包装,错误分类清晰(PermissionDeniedError是 harness 报的,TimeoutError是 harness 主动中断的,其他是业务错误);- 返回值结构固定,agent 无需解析不同插件的 error 格式,统一用
response.success判断成败。
3.4 Agent 调用插件的完整链路:从用户提问到插件返回
很多人以为 agent 调用插件是黑盒,其实它有明确的 HTTP-like 协议。我用curl抓包还原了整个流程(harness 默认监听http://127.0.0.1:53217):
# 1. Agent 发起调用(模拟 harness 的 internal RPC) curl -X POST http://127.0.0.1:53217/v1/run-command \ -H "Content-Type: application/json" \ -d '{ "command": "translate.code", "params": {"text": "Hello world", "lang": "zh"}, "context": { "workspaceRoot": "/home/user/myproject", "selectedText": "", "fileUri": "file:///home/user/myproject/src/index.ts", "agentId": "agent-abc123" } }' # 2. Harness 收到请求,查找已激活的插件 # - 匹配 plugin.json 的 "contributes.commands.command" == "translate.code" # - 找到 @myorg/translate 插件的 runCommand handler # 3. Harness 在沙盒中执行 handler,传入 AgentContext 实例 # 4. 插件返回 JSON,harness 封装成标准响应: { "id": "cmd-xyz789", "success": true, "result": { "translated": "你好,世界", "confidence": 0.95, "timestamp": 1715678901234 }, "durationMs": 421 }关键点:
context字段是 harness 注入的,不是 agent 传的。agent 只传command和params,context由 harness 根据当前编辑状态动态生成;durationMs是 harness 计算的,从收到请求到返回结果的毫秒数,agent 用它做 timeout 控制和性能分析;id是 harness 生成的唯一命令 ID,可用于 trace 和 debug(harness.log里会记录cmd-xyz789的完整执行栈)。
所以,当你调试 “cursor 怎么设置中文回复” 这类问题时,要明白:插件本身不负责“设置中文”,它只负责“把英文翻译成中文”。真正的“中文回复”是由 agent 的 system prompt 决定的,比如:
你是一个专业的代码助手,所有回复必须用简体中文,专业术语保持英文(如 React、TypeScript)。插件只是 agent 的一个工具,不是语言设置开关。
4. 实操过程:从创建到上线的全流程手把手
4.1 初始化项目:用官方脚手架还是手动搭建?
Cursor 官方提供了create-cursor-plugin脚手架,但实测下来,它生成的模板过于简单,缺少生产环境必需的配置。我推荐手动搭建,步骤如下:
# 1. 创建项目目录 mkdir my-cursor-plugin && cd my-cursor-plugin # 2. 初始化 package.json(注意:不要用 npm init -y,必须手动写) cat > package.json << 'EOF' { "name": "@myorg/translate-code", "version": "1.0.0", "description": "Translate code comments and docstrings", "main": "./dist/extension.js", "types": "./src/extension.ts", "scripts": { "build": "esbuild src/extension.ts --bundle --platform=node --target=es2020 --format=esm --outfile=dist/extension.js --external:@cursor/sdk --minify", "watch": "esbuild src/extension.ts --bundle --platform=node --target=es2020 --format=esm --outfile=dist/extension.js --external:@cursor/sdk --minify --watch", "test": "jest" }, "devDependencies": { "@cursor/sdk": "^0.45.0", "@types/jest": "^29.5.0", "esbuild": "^0.18.0", "jest": "^29.5.0", "ts-jest": "^29.1.0", "typescript": "^5.0.0" } } EOF # 3. 创建 tsconfig.json(关键:必须启用 esModuleInterop) cat > tsconfig.json << 'EOF' { "compilerOptions": { "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": false, "outDir": "./dist", "rootDir": "./src", "declaration": false, "sourceMap": false, "removeComments": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } EOF # 4. 创建 plugin.json(按前文表格填好生产环境字段) cat > plugin.json << 'EOF' { "name": "@myorg/translate-code", "version": "1.0.0", "displayName": "Code Translator", "description": "Translate comments and docstrings between English and Chinese", "publisher": "myorg", "engines": { "cursor": "^0.45.0" }, "activationEvents": [ "onCommand:translate.code" ], "main": "./dist/extension.js", "contributes": { "commands": [ { "command": "translate.code", "title": "Translate Selected Code" } ] }, "ai": { "capabilities": ["translation", "context-aware"], "sandbox": { "allowedOrigins": ["http://localhost:3000"], "allowedPermissions": ["fs:read", "network:fetch"] } } } EOF # 5. 创建 src/extension.ts(用前文的分层代码) mkdir src curl -o src/extension.ts https://gist.githubusercontent.com/your-gist-id/raw/extension.ts注意:
engines.cursor的版本号必须和你本地 Cursor 版本严格匹配。查看方式:打开 Cursor → Help → About → 记下版本号(如0.45.2),然后在plugin.json里写"^0.45.0"(表示兼容0.45.0到0.45.999)。如果写成"^0.44.0",而你用的是0.45.2,harness 会拒绝加载。
4.2 本地开发与调试:如何让 harness 重新加载你的插件?
Cursor 不像 VS Code 那样支持 F5 启动调试。它的插件热重载靠的是 harness 的 watch 机制。正确流程:
- 确保 harness 正在运行:打开 Cursor,按
Cmd+Shift+P(Mac)或Ctrl+Shift+P(Win),输入Developer: Toggle Developer Tools,在 Console 里能看到harness started日志; - 修改代码后,运行
npm run build:harness 会监听dist/目录,检测到文件变化自动 reload; - 触发插件:在代码里选中一段英文注释,按
Cmd+Shift+P→ 输入Translate Selected Code→ 回车; - 查看日志:打开
Developer Tools→ Console,搜索translate.code,能看到插件的console.log输出; - 抓取 harness 日志:在 Terminal 运行
cursor --log-level=debug 2>&1 | grep harness,能看到详细的加载和调用 trace。
常见失败场景及解决:
harness failed to load plugins web boot: 0 entries activated:说明 harness 根本没找到你的插件。检查plugin.json是否在项目根目录,name字段是否和package.json一致;Error: Cannot find module './dist/extension.js':npm run build没成功,或者main字段路径写错了;ReferenceError: fetch is not defined:你在插件里用了原生fetch,必须用context.fetch;PermissionDeniedError: network:fetch not allowed:plugin.json的ai.sandbox.allowedPermissions没加network:fetch。
4.3 发布与版本管理:如何让团队成员一键安装?
Cursor 插件不走 npm registry,而是用本地文件系统或私有 Git 仓库。生产环境推荐 Git 方式:
# 1. 将插件推送到私有 Git 仓库(如 GitLab) git init git add . git commit -m "feat: initial plugin" git remote add origin https://gitlab.com/myorg/cursor-plugins.git git push -u origin main # 2. 在 Cursor 中安装 # - Cmd+Shift+P → "Plugins: Install Plugin from Git" # - 输入仓库 URL:https://gitlab.com/myorg/cursor-plugins.git # - 选择分支:main # - 点击 Install版本管理的关键是plugin.json的version字段。每次发布新版本,必须:
- 修改
plugin.json的version(如从1.0.0到1.0.1); - 提交新 tag:
git tag v1.0.1 && git push origin v1.0.1; - 在 Cursor 中,插件页面会显示 “Update available”,用户点击即可升级。
注意:Cursor 不支持 semantic versioning 的
~或^,它只认精确版本号。所以engines.cursor写"^0.45.0"是为了向后兼容,而插件自身的version必须是精确值。
4.4 生产环境监控:如何定位 “did not activate” 的真实原因?
harness failed to load plugins这个报错太笼统。要精准定位,必须结合三处日志:
Cursor 主进程日志(最外层):
- 路径:
~/Library/Application Support/Cursor/logs/main.log(Mac)或%APPDATA%\Cursor\logs\main.log(Win); - 搜索关键词:
harness failed,能看到哪几个插件被跳过;
- 路径:
Harness 沙盒日志(核心):
- 路径:
~/Library/Application Support/Cursor/logs/harness.log; - 搜索插件
name,如@myorg/translate-code,能看到沙盒初始化的每一步:[INFO] Loading plugin @myorg/translate-code@1.0.0 [DEBUG] Checking ai.sandbox.allowedPermissions... [ERROR] Permission 'network:fetch' not allowed in sandbox config [WARN] Plugin @myorg/translate-code did not activate
- 路径:
插件自身日志(可选):
- 在
runCommand回调里加console.log('Plugin activated'); - 如果这行没输出,说明 harness 根本没走到激活步骤,问题在
plugin.json或沙盒校验; - 如果有输出但后续报错,说明问题在插件逻辑里。
- 在
我整理了一个快速排查表:
| 现象 | 最可能原因 | 检查位置 | 解决方案 |
|---|---|---|---|
web boot: X entries did not activate(X>0) | plugin.json格式 |