feat(extension): Cursor-only extension v0.0.1 (full axme-code in Cursor, 1-click install)#130
Merged
George-iam merged 7 commits intoMay 11, 2026
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
George-iam
added a commit
that referenced
this pull request
May 11, 2026
Two changes that close a UX hole surfaced during PR #130 extension E2E testing: when a chat agent calls axme_context on a workspace where .axme-code/ is missing, the tool's previous output told the agent to run \`axme-code setup --plugin\` via Bash unconditionally. That works for the Claude Code plugin distribution (where setup is not pre-run by anything else), but it backfires for the Cursor extension path: - The extension itself shows a "Run setup?" toast at activation, and the user clicks it. Setup runs once. - The agent then opens a chat, calls axme_context, sees "not initialized" (race with setup finishing) OR sees a partial state somewhere and decides to run setup AGAIN via Bash. - Result: duplicate D-NNN decisions across preset bundles, spurious 2-minute LLM-scanner re-runs on every fresh chat (verified empirically with /tmp/cursor-fresh-smoke during PR #130 testing — agent ran the second setup itself). Setup is unambiguously a user-initiated operation. The agent must never spawn it. Changes ------- src/tools/context.ts (2 messages reworded): - "Project not initialized" branch (~line 81): drop the "THEN run axme-code setup --plugin via Bash" instruction. The message now tells the agent to relay to the user (Cursor: Command Palette → AXME: Setup; Claude Code: terminal axme-code setup) and stop. Explicitly forbids autonomous setup. - "Deterministic scan only" warning (~line 141): same treatment — tell the user to re-run, do not run yourself. src/setup/cursor-writers.ts: - RULE_BODY (the body of .cursor/rules/axme-code.mdc written by setup) gains a new "NEVER run axme-code setup yourself" paragraph between Session Start and During Work. Closes the same hole at the rule level — even if axme_context's message is ignored, the rule itself bars the action. No behaviour change for users on the Claude Code plugin path — they were running setup via the plugin's own SessionStart hook already; the autonomous Bash spawn was a redundant safety belt that mostly fired in narrow non-plugin contexts. The new text still lets them run it manually if they want. Tests: 604 / 604 pass locally. No test changes needed; this only modifies user-facing output strings. #!axme pr=129 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.
1b15648
into
feat/cursor-full-support-20260510
14 of 16 checks passed
George-iam
added a commit
that referenced
this pull request
May 11, 2026
feat(cursor): land extension v0.0.1 into main (merges PR #130 into main)
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.
Summary
Ships AXME Code as a Cursor extension so users get the full axme-code experience (MCP tools + safety hooks + auto-audit) with a single click in the Cursor Extensions panel — no manual setup, no Enable-MCP gate, no
curl install.sh.Cursor-only for v0.0.1. VS Code / Copilot / Cline are deferred to v0.0.2+ pending Microsoft's chat-tool interception API (no roadmap entry currently, verified via VS Code 1.119 release notes).
This PR is stacked on top of PR #129 (
feat/cursor-full-support-20260510) which delivers the core foundation (AgentSdk factory, Cursor transcript parser, hook adapters, workspace_roots fallback). Merge order: #129 first → this PR. CI matrix builds against this branch automatically; tag-based ovsx publish is gated onextension-v*tag (human-only per D-024).Architecture (monorepo subdir)
Two version schemes (
v0.5.xfor CORE,extension-v0.0.xfor extension) — independent release cadence inside one repo.Activation flow (
extension/src/extension.ts)vscode.cursor !== undefined; bail out with friendly warning on VS Code etc.<extensionPath>/bin/axme-code(CI per-platform;axme.binaryPathsetting /AXME_CLAUDE_EXECUTABLEenv / PATH / standard locations as fallbacks).(vscode as any).cursor.mcp.registerServer({...})+ 3s wait. Bypasses .cursor/mcp.json Enable gate because trust = "user installed extension".~/.cursor/hooks.json(machine-wide safety; preserves user entries, dedups axme entries).claude logindetected..axme-code/missing.AXME ✓ N mems, D dec), command palette (Setup / Reindex / ShowStatus / ReauthAuditor / OpenDashboard / ShowRecentDecisions).CI publish workflow
.github/workflows/publish-extension.yml:.vsixartifacts (linux-x64/arm64, darwin-x64/arm64, win32-x64). No publish.extension-v*(human): downloads artifacts, runsovsx publish --target <platform>per.vsix, attaches all 5 to GitHub Release for sideload.win32-arm64skipped —@cursor/sdk@1.0.12has no native binary for that platform; user can install axme-code separately and sideload the linux-x64 .vsix into WSL2.Auth UX hierarchy (lowest friction first)
claude logindetected) — zero paste, auto-used.Credential persisted via existing CLI helpers (
~/.config/axme-code/auth.yaml+cursor.yaml) so the detached audit worker reads from the same source.VS Code support status
Not in v0.0.1. Extension activates and exits cleanly with a warning. Three blockers documented in
.claude/plans/shimmering-snuggling-lollipop.md:Mitigations land in v0.0.2+ as Microsoft adds these APIs OR via the cooperative
axme_safety_checkMCP tool path (agent calls our tool before risky ops, instructed by a.cursor/rules/axme-code.mdc-equivalent rule file).Test plan
npm run build(core) cleancd extension && npm install && npm run buildcleancd extension && npx tsc --noEmitcleanextension-v0.0.1(human-only) → CI publishes to Open VSX → install from Cursor Extensions panelRisks (full register in plan file)
cursor.mcp.registerServeris undocumented; pinned reference impl (serkan-ozal/browser-devtools-mcp-vscode) used as template.$99/yrApple Developer cost.🤖 Generated with Claude Code