feat(opencode): add V1 and V2 distributions #83
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # End-to-end smoke test of the installer across Linux, macOS, and Windows. | |
| # | |
| # Installs the real agent CLIs, then runs the installer in non-interactive mode | |
| # (--no-interactive) so it installs the Sentry integration for every detected | |
| # agent without a prompt, and verifies each distribution actually landed. | |
| # | |
| # 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). | |
| # | |
| # 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 | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "packages/installer/**" | |
| - "mcp.json" | |
| - ".github/workflows/smoke-installer.yml" | |
| pull_request: | |
| paths: | |
| - "packages/installer/**" | |
| - "mcp.json" | |
| - ".github/workflows/smoke-installer.yml" | |
| workflow_dispatch: | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| smoke: | |
| name: ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| env: | |
| SENTRY_DSN: "" # don't report CI smoke runs to Sentry | |
| 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 | |
| - name: Build | |
| run: pnpm --filter @sentry/ai build | |
| - 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: | | |
| dir="$RUNNER_TEMP/cursor-bin" | |
| mkdir -p "$dir" | |
| if [ "$RUNNER_OS" = "Windows" ]; then | |
| printf '@exit /b 0\r\n' > "$dir/cursor.cmd" | |
| else | |
| printf '#!/bin/sh\nexit 0\n' > "$dir/cursor" | |
| chmod +x "$dir/cursor" | |
| fi | |
| echo "$dir" >> "$GITHUB_PATH" | |
| - name: Run installer (non-interactive) | |
| # No working-directory: on Windows the bash shell mangles the | |
| # backslashes in a `cd` to the workspace path, so run from the root. | |
| run: node packages/installer/dist/index.js install --no-interactive | |
| - name: Verify the Sentry plugin installed for each agent | |
| run: | | |
| set -euo pipefail | |
| echo "== claude ==" | |
| claude plugin list | |
| claude plugin list | grep -iq sentry || { echo "claude: sentry plugin missing"; exit 1; } | |
| echo "== codex ==" | |
| codex plugin list | |
| codex plugin list | grep -iq sentry || { echo "codex: sentry plugin missing"; exit 1; } | |
| echo "== grok ==" | |
| grok plugin list | |
| grok plugin list | grep -iq sentry || { echo "grok: sentry plugin missing"; exit 1; } | |
| 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." | |
| # OpenCode V2 currently recommends WSL on Windows and cannot share V1's MCP | |
| # config shape, so exercise it separately from the cross-platform V1 matrix. | |
| smoke-opencode2: | |
| 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 |