Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
24 changes: 20 additions & 4 deletions .github/workflows/deploy-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
# cursor -> getsentry/plugin-cursor
# codex -> getsentry/plugin-codex
# grok -> getsentry/plugin-grok
# opencode -> getsentry/plugin-opencode
# opencode2 -> getsentry/plugin-opencode2
Comment thread
sergical marked this conversation as resolved.
Outdated
#
# Marketplaces consume `getsentry/plugin-<agent>` by git ref. Each job builds its
# Marketplaces and installers consume `getsentry/plugin-<agent>` by git ref. Each job builds its
# agent's tree from this repo, then commits it onto the target repo's `main`,
# replacing the previous contents. The four jobs target four different repos,
# replacing the previous contents. The six jobs target six different repos,
# so they run in parallel without contention.
#
# Cross-repo writes use a GitHub App token scoped per-job to a single plugin
# repo; the default GITHUB_TOKEN cannot push to other repositories. The app must
# be installed on the org with contents:write on the three plugin repos, its ID
# be installed on the org with contents:write on the six plugin repos, its ID
# stored as the PLUGIN_DEPLOY_APP_ID variable and its private key as the
# PLUGIN_DEPLOY_KEY secret. Each target repo must already exist with `main` as
# its default branch.
Expand Down Expand Up @@ -49,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
agent: [claude, cursor, codex, grok]
agent: [claude, cursor, codex, grok, opencode, opencode2]
concurrency:
group: deploy-plugin-${{ matrix.agent }}
cancel-in-progress: false
Expand All @@ -69,6 +71,20 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0

- name: Set up Node for OpenCode validation
if: matrix.agent == 'opencode' || matrix.agent == 'opencode2'
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24

- name: Install OpenCode V1 CLI
if: matrix.agent == 'opencode'
run: npm install -g opencode-ai@1.18.0

- name: Install OpenCode V2 CLI
if: matrix.agent == 'opencode2'
run: npm install -g @opencode-ai/cli@0.0.0-next-16420

- name: Build and deploy plugin-${{ matrix.agent }}
env:
AGENT: ${{ matrix.agent }}
Expand Down
98 changes: 94 additions & 4 deletions .github/workflows/smoke-installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
# (--no-interactive) so it installs the Sentry plugin for every detected agent
# without a prompt, and verifies each plugin actually landed.
#
# Claude Code, Codex, and Grok ship cross-platform npm CLIs, so they get a real
# install against their default marketplaces. Cursor has no headless CLI to
# Claude Code, Codex, Grok, and OpenCode V1 ship cross-platform npm CLIs, so
# they get real CLI installs. Cursor has no headless CLI to
# install in CI, but our Cursor install is only a `git clone` of the public
# plugin repo into ~/.cursor/plugins/local/sentry — so we put a `cursor` stub on
# PATH to satisfy detection and verify the real clone (this is what exercises
# the per-OS path handling, including Windows).
#
# OpenCode V2 currently recommends WSL on Windows and cannot share V1's MCP
# config shape, so it runs in a separate Linux smoke job. The OpenCode target
# repositories are replaced with temporary local git repositories in both jobs;
# this tests the real clone flow before those generated repositories are first
# deployed.

name: Smoke installer

Expand Down Expand Up @@ -57,8 +63,24 @@ jobs:
- name: Build
run: pnpm --filter @sentry/ai build

- name: Install Claude Code, Codex, and Grok CLIs
run: npm install -g @anthropic-ai/claude-code @openai/codex @xai-official/grok
- name: Install Claude Code, Codex, Grok, and OpenCode V1 CLIs
run: npm install -g @anthropic-ai/claude-code @openai/codex @xai-official/grok opencode-ai@1.18.0

- name: Prepare local OpenCode V1 distribution
run: |
set -euo pipefail
repo="$RUNNER_TEMP/plugin-opencode"
mkdir -p "$repo/skills/sentry-installer-smoke"
printf '1\n' > "$repo/.sentry-opencode-v1"
printf '%s\n' '---' 'name: sentry-installer-smoke' 'description: Verify the Sentry OpenCode installer' '---' '' '# Sentry installer smoke' > "$repo/skills/sentry-installer-smoke/SKILL.md"
git -C "$repo" init -q
git -C "$repo" config user.name smoke-test
git -C "$repo" config user.email smoke@example.com
git -C "$repo" add .
git -C "$repo" commit -qm 'test: seed OpenCode bundle'
repo_url="$(node -e 'console.log(require("node:url").pathToFileURL(process.argv[1]).href)' "$repo")"
git config --global protocol.file.allow always
git config --global "url.${repo_url}.insteadOf" https://github.com/getsentry/plugin-opencode.git

- name: Put a Cursor stub on PATH
run: |
Expand Down Expand Up @@ -96,4 +118,72 @@ jobs:
echo "== cursor =="
node -e "const p=require('path').join(require('os').homedir(),'.cursor','plugins','local','sentry'); if(!require('fs').existsSync(p)){console.error('cursor: plugin dir missing at '+p);process.exit(1)} console.log(p)"

echo "== opencode =="
opencode debug skill > "$RUNNER_TEMP/opencode-skills.json"
grep -q 'sentry-installer-smoke' "$RUNNER_TEMP/opencode-skills.json" || { echo "opencode: sentry skill missing"; exit 1; }
node - <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const root = path.join(os.homedir(), ".config", "opencode");
const file = ["opencode.json", "opencode.jsonc"].map((name) => path.join(root, name)).find(fs.existsSync);
if (!file) throw new Error("opencode: global config missing");
const config = JSON.parse(fs.readFileSync(file, "utf8"));
if (!config.mcp?.sentry?.url?.includes("mcp.sentry.dev")) throw new Error("opencode: Sentry MCP missing");
NODE

echo "All agents have the Sentry plugin."

smoke-opencode2:
Comment thread
sergical marked this conversation as resolved.
name: OpenCode V2 beta
runs-on: ubuntu-latest
env:
SENTRY_DSN: ""
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: pnpm/action-setup@a15d269cd4658e1107c09f1fabf4cbd7bd1f308a # v4.4.0

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm --filter @sentry/ai build
- run: npm install -g @opencode-ai/cli@0.0.0-next-16420

- name: Prepare local OpenCode V2 distribution
run: |
set -euo pipefail
repo="$RUNNER_TEMP/plugin-opencode2"
mkdir -p "$repo/skills/sentry-installer-smoke"
printf '2\n' > "$repo/.sentry-opencode-v2"
printf '%s\n' '---' 'name: sentry-installer-smoke' 'description: Verify the Sentry OpenCode V2 installer' 'metadata:' ' opencode/autoinvoke: "false"' '---' '' '# Sentry installer smoke' > "$repo/skills/sentry-installer-smoke/SKILL.md"
git -C "$repo" init -q
git -C "$repo" config user.name smoke-test
git -C "$repo" config user.email smoke@example.com
git -C "$repo" add .
git -C "$repo" commit -qm 'test: seed OpenCode V2 bundle'
repo_url="$(node -e 'console.log(require("node:url").pathToFileURL(process.argv[1]).href)' "$repo")"
git config --global protocol.file.allow always
git config --global "url.${repo_url}.insteadOf" https://github.com/getsentry/plugin-opencode2.git

- name: Run installer (non-interactive)
run: node packages/installer/dist/index.js install --no-interactive

- name: Verify OpenCode V2 installation
run: |
set -euo pipefail
node - <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const bundle = path.join(os.homedir(), ".config", "opencode", "skills", "sentry", "skills", "sentry-installer-smoke", "SKILL.md");
if (!fs.existsSync(bundle)) throw new Error(`opencode2: Sentry skill missing at ${bundle}`);
const root = path.join(os.homedir(), ".config", "opencode");
const file = ["opencode.json", "opencode.jsonc"].map((name) => path.join(root, name)).find(fs.existsSync);
if (!file) throw new Error("opencode2: global config missing");
const config = JSON.parse(fs.readFileSync(file, "utf8"));
if (!config.mcp?.servers?.sentry?.url?.includes("mcp.sentry.dev")) throw new Error("opencode2: Sentry MCP missing");
NODE
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Agent Instructions

## Project Overview
Sentry plugin for AI coding assistants (Claude Code, Cursor, Codex, and Grok). Provides MCP server integration and skills.
Sentry plugin for AI coding assistants (Claude Code, Cursor, Codex, Grok, and OpenCode V1/V2). Provides MCP server integration and skills.

## Commit Attribution
AI commits MUST include:
Expand Down Expand Up @@ -78,6 +78,8 @@ Skills use YAML frontmatter with `allowed-tools` — this is required by Cursor
## MCP Server
Sentry MCP server configured at `https://mcp.sentry.dev/mcp`. The source of truth is `mcp.json`: Cursor consumes it as-is at the plugin root, while the Codex and Grok builds emit it as `.mcp.json` (Codex's validator requires the dotted name; Grok auto-discovers it). Claude declares the server inline in its `plugin.json` (`mcpServers`), so the Claude build ships no MCP file. The root `.mcp.json` is a backwards-compat copy (identical to `mcp.json`) retained for existing installs that consumed the plugin from this repo's root; keep the two in sync until the root compat surface is removed.

OpenCode V1 and V2 generate native `opencode.json` files from `mcp.json`. V1 places `sentry` directly under `mcp`; V2 places it under `mcp.servers`. Their installer harnesses use the corresponding CLI command to merge that entry into the user's global config.

## Key Conventions
- All setup skills must **detect platform/SDK before suggesting configuration** — never assume
- Sentry code review skill only processes comments from `sentry[bot]`, ignores other bots
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
> The skills here are built from this source into installable plugins for
> [Claude Code](https://github.com/getsentry/plugin-claude),
> [Cursor](https://github.com/getsentry/plugin-cursor),
> [Codex](https://github.com/getsentry/plugin-codex), and
> [Grok](https://github.com/getsentry/plugin-grok) — install one of those, not
> [Codex](https://github.com/getsentry/plugin-codex),
> [Grok](https://github.com/getsentry/plugin-grok),
> [OpenCode V1](https://github.com/getsentry/plugin-opencode), and
> [OpenCode V2](https://github.com/getsentry/plugin-opencode2) — install one of those, not
> this repo. They're also served over HTTP at
> [skills.sentry.dev](https://skills.sentry.dev) for agents to fetch directly.
> In the future we may also publish the skills as a generic, standalone skills
Expand All @@ -16,7 +18,7 @@ Your AI coding assistant already knows how to write code. This plugin teaches it

Whether you're adding Sentry to a new project, debugging a spike in errors, or configuring alerts, just ask. The plugin gives your assistant the context it needs to do it right.

Supports [**Claude Code**](https://github.com/getsentry/plugin-claude), [**Cursor**](https://github.com/getsentry/plugin-cursor), [**Codex**](https://github.com/getsentry/plugin-codex), and [**Grok**](https://github.com/getsentry/plugin-grok).
Supports [**Claude Code**](https://github.com/getsentry/plugin-claude), [**Cursor**](https://github.com/getsentry/plugin-cursor), [**Codex**](https://github.com/getsentry/plugin-codex), [**Grok**](https://github.com/getsentry/plugin-grok), [**OpenCode V1**](https://github.com/getsentry/plugin-opencode), and [**OpenCode V2**](https://github.com/getsentry/plugin-opencode2).

## What You Can Do

Expand Down Expand Up @@ -65,6 +67,8 @@ exactly that agent's plugin:
| Cursor | [`getsentry/plugin-cursor`](https://github.com/getsentry/plugin-cursor) |
| Codex | [`getsentry/plugin-codex`](https://github.com/getsentry/plugin-codex) |
| Grok | [`getsentry/plugin-grok`](https://github.com/getsentry/plugin-grok) |
| OpenCode V1 | [`getsentry/plugin-opencode`](https://github.com/getsentry/plugin-opencode) |
| OpenCode V2 | [`getsentry/plugin-opencode2`](https://github.com/getsentry/plugin-opencode2) |

These repositories are generated; do not edit them. Each one's README has the
install instructions for that agent.
Expand All @@ -83,11 +87,11 @@ and swaps the skill tree's `disable-model-invocation` flags for Codex's
```bash
git clone https://github.com/getsentry/sentry-for-ai.git
cd sentry-for-ai
src/plugins/codex/build.sh /tmp/sentry-codex # or src/plugins/{claude,cursor,grok}
src/plugins/codex/build.sh /tmp/sentry-codex # or src/plugins/{claude,cursor,grok,opencode,opencode2}
```

To build any target locally, run `src/plugins/<agent>/build.sh <output-dir>`
(`claude`, `cursor`, `codex`, or `grok`).
(`claude`, `cursor`, `codex`, `grok`, `opencode`, or `opencode2`).

## Skills

Expand Down
12 changes: 8 additions & 4 deletions packages/installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Install the [Sentry plugin](https://github.com/getsentry/sentry-for-ai) into you

The plugin teaches your assistant Sentry — how to set it up in any project, how to find and fix production issues, and how to configure alerts, AI monitoring, and more. This package detects which assistants you have installed and wires the plugin into each one for you.

Supports **Claude Code**, **Codex**, **Cursor**, and **Grok**.
Supports **Claude Code**, **Codex**, **Cursor**, **Grok**, **OpenCode V1**, and **OpenCode V2**.

```bash
npx @sentry/ai install
Expand All @@ -31,14 +31,16 @@ When an instruction follows `install`, the installer offers to copy a prompt suc

## What it installs

For each detected assistant, the installer runs that tool's native plugin command:
For each detected assistant, the installer uses its native package command or installs the generated skill bundle:

| Assistant | How it's installed |
| ----------- | ----------------------------------------------------------------------------- |
| Claude Code | `claude plugin install sentry` from the official plugin marketplace |
| Codex | `codex plugin add sentry` from the Sentry plugin marketplace |
| Cursor | Clones [`getsentry/plugin-cursor`](https://github.com/getsentry/plugin-cursor) into `~/.cursor/plugins/local/sentry` |
| Grok | `grok plugin install getsentry/plugin-grok` |
| OpenCode V1 | Clones [`getsentry/plugin-opencode`](https://github.com/getsentry/plugin-opencode) into OpenCode's global skills and adds the V1 Sentry MCP entry |
| OpenCode V2 | Clones [`getsentry/plugin-opencode2`](https://github.com/getsentry/plugin-opencode2) into OpenCode's global skills and adds the V2 Sentry MCP entry |

Each per-agent plugin is built and published from the [`sentry-for-ai`](https://github.com/getsentry/sentry-for-ai) repository, which is the source of truth for all skills.

Expand All @@ -49,13 +51,15 @@ npx @sentry/ai remove # interactive — pick which agents to re
npx @sentry/ai remove --no-interactive # remove from every agent that has it
```

`uninstall` is an alias for `remove`. This only offers agents that currently have the plugin, and removes the Sentry plugin itself — each tool's plugin marketplace is left registered. Restart your AI tools afterward to drop the plugin.
`uninstall` is an alias for `remove`. This only offers agents that currently have the plugin, and removes the Sentry plugin itself — each tool's plugin marketplace is left registered. OpenCode's CLIs do not provide an MCP remove command, so the installer identifies the config entry to delete manually. Restart your AI tools afterward to drop the plugin.

## Requirements

- Node.js 18 or newer
- The assistant CLI you want to set up must already be installed and on your `PATH`
- `git` is required for the Cursor install
- `git` is required for the Cursor and OpenCode installs

OpenCode V1 and the OpenCode V2 beta use the same global config path but incompatible MCP shapes. When both `opencode` and `opencode2` are installed, the installer configures V2 only.

## License

Expand Down
1 change: 1 addition & 0 deletions packages/installer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@listr2/prompt-adapter-inquirer": "^4.2.4",
"@sentry/node": "^10.57.0",
"citty": "^0.1.6",
"jsonc-parser": "^3.3.1",
"listr2": "^10.2.1"
},
"devDependencies": {
Expand Down
15 changes: 14 additions & 1 deletion packages/installer/src/__tests__/fake-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,30 @@ import type { ShellResult, SystemDeps } from "../system";
export interface FakeSystemOptions {
run?: (command: string) => ShellResult;
existing?: string[];
files?: Record<string, string>;
platform?: NodeJS.Platform;
homedir?: string;
}

export function fakeSystem(options: FakeSystemOptions = {}): SystemDeps {
const existing = new Set(options.existing ?? []);
const files = new Map(Object.entries(options.files ?? {}));
const existing = new Set([...(options.existing ?? []), ...files.keys()]);
const run = options.run ?? (() => ({ ok: true }));

return {
run: vi.fn(async (command: string) => run(command)),
exists: vi.fn((path: string) => existing.has(path)),
readTextFile: vi.fn((path: string) => {
const contents = files.get(path);
if (contents === undefined) {
throw new Error(`ENOENT: ${path}`);
}
return contents;
}),
writeTextFile: vi.fn((path: string, contents: string) => {
files.set(path, contents);
existing.add(path);
}),
platform: options.platform ?? "linux",
homedir: options.homedir ?? "/home/user",
};
Expand Down
Loading
Loading