Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ jobs:
fi
- name: Build and validate plugins
run: make test
- name: Build Cursor extension
working-directory: dist/cursor
run: |
npm ci --ignore-scripts --no-audit --no-fund
npm run build

# Pi and OpenCode run inside the user's agent process, so their unit tests
# run on every host OS. Packaging and validation stay in `plugins`.
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
antigravity) dist_repo=braintrustdata/braintrust-antigravity-plugin ;;
claude) dist_repo=braintrustdata/braintrust-claude-plugin ;;
codex) dist_repo=braintrustdata/braintrust-codex-plugin ;;
cursor) dist_repo=braintrustdata/braintrust-cursor-plugin ;;
cursor) dist_repo=braintrustdata/braintrust-cursor-extension ;;
grok) dist_repo=braintrustdata/braintrust-grok-plugin ;;
*) echo "::error::Unsupported release plugin."; exit 1 ;;
esac
Expand All @@ -73,7 +73,7 @@ jobs:
case "$PLUGIN" in
claude) printf '%s\n' 'src/plugins/claude/content/plugins/*/.claude-plugin/plugin.json' ;;
codex) printf '%s\n' 'src/plugins/codex/content/plugins/*/.codex-plugin/plugin.json' ;;
cursor) printf '%s\n' 'src/plugins/cursor/content/.cursor-plugin/plugin.json' ;;
cursor) printf '%s\n' 'src/plugins/cursor/content/.cursor-plugin/plugin.json' 'src/plugins/cursor/content/package.json' 'src/plugins/cursor/content/package-lock.json' ;;
grok) printf '%s\n' 'src/plugins/grok/content/.grok-plugin/plugin.json' 'src/plugins/grok/content/hooks/hooks.json' ;;
esac
printf 'EOF\n'
Expand Down Expand Up @@ -204,7 +204,7 @@ jobs:
antigravity) dist_repo=braintrustdata/braintrust-antigravity-plugin ;;
claude) dist_repo=braintrustdata/braintrust-claude-plugin ;;
codex) dist_repo=braintrustdata/braintrust-codex-plugin ;;
cursor) dist_repo=braintrustdata/braintrust-cursor-plugin ;;
cursor) dist_repo=braintrustdata/braintrust-cursor-extension ;;
grok) dist_repo=braintrustdata/braintrust-grok-plugin ;;
esac
{
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/smoke-cursor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ jobs:
import json, pathlib, sys
root = pathlib.Path(sys.argv[1])
manifest = json.loads((root / '.cursor-plugin/plugin.json').read_text())
assert manifest['name'] == 'trace-cursor', manifest
assert manifest['name'] == 'braintrust', manifest
assert (root / 'hooks/hooks.json').is_file()
assert (root / 'mcp.json').is_file()
PY
mkdir -p "${RUNNER_TEMP}/cursor-smoke/.cursor"
cat > "${RUNNER_TEMP}/cursor-smoke/.cursor/hooks.json" <<'JSON'
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ is unversioned.
| antigravity | `braintrustdata/braintrust-antigravity-plugin` |
| claude | `braintrustdata/braintrust-claude-plugin` |
| codex | `braintrustdata/braintrust-codex-plugin` |
| cursor | `braintrustdata/braintrust-cursor-plugin` |
| cursor | `braintrustdata/braintrust-cursor-extension` |
| grok | `braintrustdata/braintrust-grok-plugin` |

A distribution repository is a generated artifact. Each deploy clones it,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ All six support `bt trace enable`, `update`, `disable`, and `doctor`.
| [Google Antigravity](src/plugins/antigravity/content/README.md) | `antigravity` | — | Yes | [GitHub](https://github.com/braintrustdata/braintrust-antigravity-plugin) |
| [Claude Code](src/plugins/claude/content/README.md) | `claude` | Yes | Yes | [GitHub](https://github.com/braintrustdata/braintrust-claude-plugin) |
| [Codex](src/plugins/codex/content/README.md) | `codex` | Yes | Yes | [GitHub](https://github.com/braintrustdata/braintrust-codex-plugin) |
| [Cursor](src/plugins/cursor/content/README.md) | `cursor` | Yes | Yes, transcript-limited | [GitHub](https://github.com/braintrustdata/braintrust-cursor-plugin) |
| [Cursor](src/plugins/cursor/content/README.md) | `cursor` | Yes | Yes, transcript-limited | [GitHub](https://github.com/braintrustdata/braintrust-cursor-extension) |
| [Grok](src/plugins/grok/content/README.md) | `grok` | — | — | [GitHub](https://github.com/braintrustdata/braintrust-grok-plugin) |
| [OpenCode](src/plugins/opencode/content/README.md) | `opencode` | Yes | — | [npm](https://www.npmjs.com/package/@braintrust/trace-opencode) |
| [Pi](src/plugins/pi/content/README.md) | `pi` | Yes | — | [npm](https://www.npmjs.com/package/@braintrust/pi-extension) |
Expand Down
41 changes: 32 additions & 9 deletions bt-daemon/src/setup/cursor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use serde_json::Value;
use std::path::Path;

const PLUGIN_NAME: &str = "trace-cursor";
const PLUGIN_SOURCE: &str = "braintrustdata/braintrust-cursor-plugin";
const PLUGIN_SOURCE: &str = "braintrustdata/braintrust-cursor-extension";
const LEGACY_PLUGIN_SOURCE: &str = "braintrustdata/braintrust-cursor-plugin";
const MONOREPO_SOURCE: &str = "braintrustdata/braintrust-coding-agent-plugins";
const PLUGIN_MANIFEST: &str = plugin_source!("cursor/content/.cursor-plugin/plugin.json");
const HOOKS_MANIFEST: &str = plugin_source!("cursor/content/hooks/hooks.json");
Expand All @@ -33,21 +34,26 @@ exit 0
"#;
const README: &str = plugin_source!("cursor/content/README.md");
const LICENSE: &str = plugin_source!("cursor/content/LICENSE");
const MCP_MANIFEST: &str = plugin_source!("cursor/content/mcp.json");
const LOGO: &str = plugin_source!("cursor/content/logo.svg");

fn installed_manifest_at(plugin: &Path) -> Option<Value> {
let raw = std::fs::read(plugin.join(".cursor-plugin/plugin.json")).ok()?;
serde_json::from_slice(&raw).ok()
}

fn is_ours(manifest: &Value) -> bool {
manifest.get("name").and_then(Value::as_str) == Some(PLUGIN_NAME)
&& manifest
.get("repository")
.and_then(Value::as_str)
.is_some_and(|repo| {
github_repo_matches(repo, MONOREPO_SOURCE)
|| github_repo_matches(repo, PLUGIN_SOURCE)
})
matches!(
manifest.get("name").and_then(Value::as_str),
Some("braintrust" | PLUGIN_NAME)
) && manifest
.get("repository")
.and_then(Value::as_str)
.is_some_and(|repo| {
github_repo_matches(repo, MONOREPO_SOURCE)
|| github_repo_matches(repo, PLUGIN_SOURCE)
|| github_repo_matches(repo, LEGACY_PLUGIN_SOURCE)
})
}

/// Fail unless the plugin at `plugin_dir` is the Braintrust plugin, so setup
Expand Down Expand Up @@ -100,6 +106,20 @@ fn plugin_is_installed_for_platform_at(plugin: &Path, windows: bool) -> bool {
if contents != expected_contents {
return false;
}
if std::fs::read_to_string(plugin.join("mcp.json"))
.ok()
.as_deref()
!= Some(MCP_MANIFEST)
{
return false;
}
if std::fs::read_to_string(plugin.join("logo.svg"))
.ok()
.as_deref()
!= Some(LOGO)
{
return false;
}
#[cfg(unix)]
if !windows {
use std::os::unix::fs::PermissionsExt;
Expand Down Expand Up @@ -213,6 +233,8 @@ fn install_plugin_at_for_platform(plugin_dir: &Path, windows: bool) -> anyhow::R
(".cursor-plugin/plugin.json", PLUGIN_MANIFEST.to_owned()),
("hooks/hooks.json", hooks_manifest),
("hooks/trace.sh", HOOK_LAUNCHER.to_owned()),
("mcp.json", MCP_MANIFEST.to_owned()),
("logo.svg", LOGO.to_owned()),
("README.md", README.to_owned()),
("LICENSE", LICENSE.to_owned()),
];
Expand Down Expand Up @@ -366,6 +388,7 @@ mod tests {
.unwrap();
assert!(plugin_is_installed_at(&plugin));
assert!(is_ours(&manifest));
assert!(plugin.join("mcp.json").is_file());
}

#[test]
Expand Down
12 changes: 10 additions & 2 deletions scripts/set-plugin-version.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,25 @@ def main() -> None:
sys.exit(f"no plugin manifests found for '{agent}' ({pattern})")

surfaces = [(path, VERSION_RE, "version") for path in manifests]
if agent == "cursor":
content = "src/plugins/cursor/content"
surfaces.extend([
(f"{content}/package.json", VERSION_RE, "extension version"),
(f"{content}/package-lock.json", VERSION_RE, "lockfile versions"),
])

changes = []
for path, version_re, label in surfaces:
with open(path) as f:
text = f.read()
expected = 2 if label == "lockfile versions" else 1
new_text, n = version_re.subn(
lambda match: f"{match.group(1)}{version}{match.group(2)}",
text,
count=expected,
)
if n != 1:
sys.exit(f"expected one {label} field in {path}, found {n}")
if n != expected:
sys.exit(f"expected {expected} {label} field(s) in {path}, found {n}")
changes.append((path, label, new_text))

for path, label, new_text in changes:
Expand Down
29 changes: 22 additions & 7 deletions src/plugins/cursor/content/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
{
"name": "trace-cursor",
"name": "braintrust",
Comment thread
AbhiPrasad marked this conversation as resolved.
"displayName": "Braintrust",
"version": "0.2.0",
"description": "Trace Cursor agent sessions, model output, and tool operations to Braintrust through the shared bt daemon.",
"author": { "name": "Braintrust" },
"homepage": "https://www.braintrust.dev",
"repository": "https://github.com/braintrustdata/braintrust-coding-agent-plugins",
"license": "Apache-2.0",
"hooks": "hooks/hooks.json"
"description": "Connect Cursor to Braintrust MCP and trace agent sessions.",
"author": { "name": "Braintrust Data, Inc.", "email": "support@braintrust.dev" },
"homepage": "https://braintrust.dev",
"repository": "https://github.com/braintrustdata/braintrust-cursor-extension",
"license": "MIT",
"keywords": ["braintrust", "mcp", "ai", "evaluation", "experiments", "observability", "logs", "tracing"],
"logo": "logo.svg",
"hooks": "hooks/hooks.json",
"mcpServers": "mcp.json",
"variables": {
"type": "object",
"properties": {
"BRAINTRUST_API_KEY": {
"type": "string",
"title": "Braintrust API key",
"description": "API key for the Braintrust MCP server"
}
},
"required": ["BRAINTRUST_API_KEY"]
}
}
4 changes: 4 additions & 0 deletions src/plugins/cursor/content/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
out/
*.vsix
.vscode-test/
13 changes: 13 additions & 0 deletions src/plugins/cursor/content/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: build"
}
]
}
18 changes: 18 additions & 0 deletions src/plugins/cursor/content/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": ["$tsc"]
},
{
"type": "npm",
"script": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": ["$tsc-watch"]
}
]
}
12 changes: 12 additions & 0 deletions src/plugins/cursor/content/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
src/**
.vscode/**
.cursor-plugin/**
mcp.json
logo.svg
tsconfig.json
CONTRIBUTING.md
.gitignore
**/*.ts
**/*.map
node_modules/**
.vscode-test/**
136 changes: 136 additions & 0 deletions src/plugins/cursor/content/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Contributing

The source for this plugin and extension lives in
[braintrust-coding-agent-plugins](https://github.com/braintrustdata/braintrust-coding-agent-plugins)
under `src/plugins/cursor/content/`. The
[braintrust-cursor-extension](https://github.com/braintrustdata/braintrust-cursor-extension)
repository is generated from this directory during a release. Make changes and
open pull requests in the monorepo.

## Development Setup

### Prerequisites

- Node.js 20.18.1 or newer
- npm

### Build from Source

```bash
# Clone the source repository
git clone https://github.com/braintrustdata/braintrust-coding-agent-plugins.git
cd braintrust-coding-agent-plugins/src/plugins/cursor/content

# Install dependencies
npm ci

# Build the extension
npm run build
```

From the monorepo root, run `make validate-cursor` to build and validate the
Cursor distribution tree, including its plugin manifest, MCP configuration,
and tracing hooks.

### Running in Development

1. Open `src/plugins/cursor/content/` in Cursor
2. Press `F5` to launch the Extension Development Host
3. A new Cursor window will open with the extension loaded

### Setting the Environment Variable for Development

The extension reads `BRAINTRUST_API_KEY` from the process environment at runtime. You must set this variable **before** launching Cursor so the extension host process inherits it.

#### macOS / Linux

```bash
# Set the environment variable and launch Cursor
export BRAINTRUST_API_KEY="your-api-key-here"
cursor .

# Or in a single line
BRAINTRUST_API_KEY="your-api-key-here" cursor .
```

For persistent configuration, add to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.):

```bash
export BRAINTRUST_API_KEY="your-api-key-here"
```

Then restart your terminal and Cursor.

#### Windows (PowerShell)

```powershell
# Set for current session
$env:BRAINTRUST_API_KEY = "your-api-key-here"

# Launch Cursor
cursor .
```

For persistent configuration:

```powershell
# Set permanently for user
[Environment]::SetEnvironmentVariable("BRAINTRUST_API_KEY", "your-api-key-here", "User")
```

Then restart PowerShell and Cursor.

#### Windows (Command Prompt)

```cmd
# Set for current session
set BRAINTRUST_API_KEY=your-api-key-here

# Launch Cursor
cursor .
```

For persistent configuration, use System Properties > Environment Variables.

### Installing a Local Build

1. From `src/plugins/cursor/content/`, build the extension: `npm run build`
2. Package it: `npx vsce package` (produces a `.vsix` file)
3. In Cursor, open Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`)
4. Run **Extensions: Install from VSIX...**
5. Select the generated `.vsix` file

## Project Structure

```
braintrust-coding-agent-plugins/src/plugins/cursor/content/
├── .cursor-plugin/ # Cursor plugin manifest
├── hooks/ # Tracing hooks
├── mcp.json # Braintrust MCP server
├── package.json # Extension manifest
├── tsconfig.json # TypeScript configuration
├── src/
│ └── extension.ts # Main extension code
├── out/ # Compiled output (git-ignored)
└── README.md
```

## Scripts

- `npm run build` — Compile TypeScript
- `npm run watch` — Compile in watch mode
- `npm run lint` — Run ESLint (requires eslint to be installed)
- `npm run package` — Create a `.vsix` package
- `npm run publish:openvsx` — Publish to OpenVSX (for Cursor)
- `npm run publish:vscode` — Publish to VS Code Marketplace

## Publishing

The monorepo's **Release Plugin** workflow prepares a version pull request.
After that pull request is approved and merged, the workflow publishes the
generated Cursor plugin tree to `braintrustdata/braintrust-cursor-extension`.
The version script updates the Cursor plugin manifest, `package.json`, and
`package-lock.json` together. Do not edit the distribution repository directly.

Publishing the VS Code extension to an extension marketplace is a separate
release action. `npm run package` creates a `.vsix` file for local testing.
Loading
Loading