feat(cursor): land extension v0.0.1 into main (merges PR #130 into main)#131
Merged
Conversation
Ships AXME Code as a Cursor extension that delivers every axme-code
feature natively — MCP tools, user-level safety hooks, auto-audit at
chat end — without the manual `Enable axme` click in Cursor Settings
that the CLI install path requires.
Cursor-only for v0.0.1
----------------------
v0.0.1 detects Cursor at activation and exits with a friendly warning
on any other VS Code-family IDE. Reason: full functionality needs
three Cursor-specific APIs that vanilla VS Code (1.119, current at
ship time) does not expose:
- cursor.mcp.registerServer() — bypasses .cursor/mcp.json Enable gate
- ~/.cursor/hooks.json user-level write — VS Code has no chat-tool
interception API (verified via VS Code 1.119 release notes; no
GitHub issue, no roadmap entry for `vscode.chat.onWillInvokeTool`)
- sessionEnd hook event — no equivalent in VS Code chat API for
third-party extensions
Shipping a degraded VS Code build (MCP tools only) would confuse
users; v0.0.2+ adds VS Code support once Microsoft exposes the chat
hooks API or we wire the cooperative `axme_safety_check` MCP tool
into the rule file.
Architecture
------------
extension/ monorepo subdir, separate package.json
+ version (0.0.1 vs core 0.5.x)
├── package.json vscode engine ^1.96.0, 4 commands,
3 settings (binaryPath, contextMode,
enableHooks)
├── tsconfig.json strict, Node16, noEmit (esbuild builds)
├── build.mjs esbuild → out/extension.js, vscode
external, CommonJS
├── src/
│ ├── extension.ts 7-step activate(): Cursor gate → binary
│ → MCP → hooks → auditor auth → setup
│ offer → status bar + commands
│ ├── ide-detect.ts (vscode.cursor !== undefined) || appName
│ includes "cursor"
│ ├── binary-detect.ts bundled `<extensionPath>/bin/axme-code`
│ primary; settings override, env var,
│ PATH, standard locations as fallback
│ ├── mcp-register.ts cursor.mcp.registerServer({...}) + 3s
│ wait. Returns Disposable for unregister
│ on deactivate.
│ ├── hooks-install.ts idempotent ~/.cursor/hooks.json writer.
│ User entries preserved, axme entries
│ refreshed on every activation.
│ ├── auditor-auth.ts First-run modal: Anthropic key /
│ Cursor SDK key / skip. Detects existing
│ Claude subscription via `axme-code auth
│ status` and skips prompt when saved.
│ ├── setup-controller.ts Offers "Run setup?" toast when workspace
│ lacks .axme-code/. AXME: Setup command.
│ ├── kb-watcher.ts FS-watch .axme-code/{memory,decisions}
│ for live status bar refresh.
│ ├── status-bar.ts "AXME ✓ N mems, D dec" with click →
│ quick-pick of recent decisions.
│ ├── commands.ts Setup / Reindex / ShowStatus /
│ ReauthAuditor / OpenDashboard /
│ ShowRecentDecisions.
│ └── log.ts Singleton OutputChannel logger.
└── bin/ Empty in source; CI populates per-target.
CI publish (.github/workflows/publish-extension.yml)
----------------------------------------------------
5-platform matrix (linux-x64/arm64, darwin-x64/arm64, win32-x64).
Each runner builds core CLI, bundles into a single shebang shim at
extension/bin/axme-code, then vsce package --target <platform>.
Artifacts on every PR; ovsx publish per-target only on tag push
(`extension-v*`). Tag is human-only per D-024.
Auth UX hierarchy (lowest friction first)
-----------------------------------------
1. Claude subscription via `claude login` — zero paste, auto-detected.
2. Anthropic API key — paste once at first activation.
3. Cursor SDK key (cursor.com → Integrations) — paste once.
4. Skip auditor — MCP tools + hooks only.
The credential is persisted via existing CLI helpers (auth.yaml +
cursor.yaml) so the detached audit worker (spawned by hooks at chat
end, runs without extension context) reads from the same source.
Root package.json
-----------------
New scripts: build:extension, package:extension. test/lint untouched.
Root README
-----------
New "Option 0: Cursor extension" install section above the existing
Claude Code plugin + CLI options.
Risks documented in plan file:
- Cursor's cursor.mcp.registerServer is undocumented; pinned reference
impl (serkan-ozal/browser-devtools-mcp-vscode) used as template.
- macOS Gatekeeper may block unsigned binaries; v0.0.2 adds signing.
#!axme pr=none repo=AxmeAI/axme-code
The bundled binary in extension/bin/ has no file extension (shebang
script). Without a ".mjs" suffix or a sibling package.json declaring
"type":"module", Node loads the file as CJS and explodes on the
first ESM `import` statement at runtime:
SyntaxError: Cannot use import statement outside a module
at wrapSafe (node:internal/modules/cjs/loader:1378:20)
Surfaced when sideloading axme-code-dev.vsix into Cursor and trying
to run AXME: Setup or any axme MCP tool — the MCP server failed to
spawn, hooks crashed on first invocation.
Fix: change esbuild output from --format=esm to --format=cjs. The
shebang stays; the binary is now valid CJS that Node executes
without --experimental flags. Tested locally:
extension/bin/axme-code --version → 0.5.0 (was: SyntaxError)
Updates the publish-extension.yml CI step that runs the same
esbuild bundle inside the 5-platform matrix, so the Open VSX-
distributed .vsix files don't ship with the same crash.
#!axme pr=130 repo=AxmeAI/axme-code
The auditor auth modal's "Open dashboard" button was opening https://cursor.com/dashboard (general dashboard), forcing the user to click around to find the Integrations tab. Direct deep link is https://cursor.com/dashboard/integrations. Verified in real Cursor browser flow during PR #130 E2E testing. Also reworded instruction text to drop the redundant "go to Integrations tab" step. #!axme pr=130 repo=AxmeAI/axme-code
…Cursor SDK call
Two fixes that unblock end-to-end setup inside the Cursor extension
(observed in real Cursor install of axme-code-dev.vsix on
/tmp/cursor-real-test, 2026-05-11):
1. ERR_MODULE_NOT_FOUND for @anthropic-ai/claude-agent-sdk
-----------------------------------------------------------
The .vsix's bundled binary at
~/.cursor-server/extensions/axmeai.axme-code-0.0.1/bin/axme-code
had claude-agent-sdk marked --external in esbuild, so at runtime
Node walked up looking for node_modules/@anthropic-ai/claude-agent-sdk
and never found it (no node_modules ship inside the .vsix). Setup
fell through to deterministic scan + presets (0 LLM decisions
extracted).
Fix .github/workflows/publish-extension.yml: drop claude-agent-sdk
from the --external list. The SDK now gets inlined (binary grows
from 1.6 MB → 2.5 MB, .vsix from 322 KB → 512 KB — totally fine
for Open VSX 256 MB cap). @cursor/sdk stays external because (a)
its 15 MB of platform-specific native binaries would bloat per-
platform .vsixes, and (b) the AgentSdk factory's fallback already
handles MODULE_NOT_FOUND on @cursor/sdk by routing the auditor
through Claude. Cursor SDK as a first-class in-extension auditor
backend is a v0.0.2 follow-up.
2. SQLITE_CONSTRAINT: UNIQUE failed on agents.agent_id
-------------------------------------------------------
src/utils/agent-sdk-cursor.ts always passed
`agentId: "axme-${role}"` to cursor.Agent.create(). Cursor's SDK
persists agents in a local SQLite with UNIQUE(agent_id), so the
4 scanners (oracle/decision/safety/deploy) that all share
role="scanner" collided on "axme-scanner" — the 2nd through 4th
calls threw UnknownAgentError.
Fix: append `-${Date.now()}-${randomShort}` to the agentId so
each Agent.create() gets a fresh row. The id stays "axme-<role>"
prefixed for log readability.
#!axme pr=130 repo=AxmeAI/axme-code
Two cosmetic stderr lines were surfacing as scary "errors" in
Cursor's Output channel even though setup was running fine:
1. "AXME: Cursor SDK unavailable (Cannot find package ...);
falling back to Claude SDK" — AgentSdk factory's expected
fallback path (Cursor SDK is intentionally not bundled in
the .vsix for v0.0.1). Logging it every time a scanner picks
the Claude branch is noise.
2. "MaxListenersExceededWarning: Possible EventTarget memory leak
detected. 11 abort listeners added to [AbortSignal]." — fires
from inside claude-agent-sdk when 4 scanners run in parallel
and each attaches an abort listener. Default Node cap is 10.
Fixes
-----
src/utils/agent-sdk.ts: wrap the fallback log calls in a private
logFallback() that only writes when AXME_VERBOSE_FALLBACK env var
is set. Silent by default.
src/cli.ts: at module load, raise default listener limit to 50
across all three Node knobs that matter:
- process.setMaxListeners(50)
- events.setMaxListeners(50) // EventTarget default
- EventEmitter.defaultMaxListeners = 50
Verified locally: setup on empty /tmp dir produces only expected
user-facing output (Initializing... LLM scanning... Decisions: 22
... Done!) — no MaxListeners noise, no factory fallback noise.
Cursor pre-tool-use stdin uses tool_name=Shell for chat-agent shell commands, while pre-tool-use.ts switches on Bash. Result: git push --force origin main from a Cursor agent fell through the deny rule. Surfaced in PR #130 E2E test on 2026-05-11 (Check 6 FAIL). Manual fire with tool_name=Bash denied correctly; same payload with tool_name=Shell silently allowed. Fix: normalizeCursorToolName in src/hooks/adapters/cursor.ts maps Shell to Bash before stamping NormalizedHookEvent.toolName. Rest of Cursor's tool vocabulary (Read/Glob/Grep/Edit/Write) overlaps.
feat(extension): Cursor-only extension v0.0.1 (full axme-code in Cursor, 1-click install)
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR exists
PR #130 (extension v0.0.1) was merged into PR #129's branch (`feat/cursor-full-support-20260510`) instead of into main, because PR #130's base was the foundation branch and base wasn't retargeted before merge.
Result: `main` has PR #129 foundation only; the full extension/ subdirectory + the 4 post-merge bugfixes (CJS bundle, agent-sdk-sdk inline, Cursor URL deep-link, Shell→Bash hook normalize, stderr noise suppress) live on `feat/cursor-full-support-20260510`.
This PR brings everything into main as one cohesive merge so the next iteration (v0.0.2) can branch from a complete main.
What this PR adds vs main
All 8 commits ahead of main:
Tests / validation
After merge
`feat/vscode-extension-v0.0.2-20260511` (already branched locally) will rebase onto fresh main, then v0.0.2 work proceeds (silent failure detection, healthcheck command, self-test subcommand).
🤖 Generated with Claude Code