Skip to content

feat(extension): Cursor-only extension v0.0.1 (full axme-code in Cursor, 1-click install)#130

Merged
George-iam merged 7 commits into
feat/cursor-full-support-20260510from
feat/vscode-extension-20260510
May 11, 2026
Merged

feat(extension): Cursor-only extension v0.0.1 (full axme-code in Cursor, 1-click install)#130
George-iam merged 7 commits into
feat/cursor-full-support-20260510from
feat/vscode-extension-20260510

Conversation

@George-iam
Copy link
Copy Markdown
Contributor

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 on extension-v* tag (human-only per D-024).

Architecture (monorepo subdir)

axme-code/
├── src/             ← CORE: unchanged in this PR; modified by PR #129
├── extension/       ← NEW: VS Code / Cursor extension
│   ├── package.json (version 0.0.1, independent of CORE 0.5.x)
│   ├── src/         (12 modules — see commit body for table)
│   └── bin/         (populated by CI per-platform)
└── .github/workflows/publish-extension.yml

Two version schemes (v0.5.x for CORE, extension-v0.0.x for extension) — independent release cadence inside one repo.

Activation flow (extension/src/extension.ts)

  1. Cursor gatevscode.cursor !== undefined; bail out with friendly warning on VS Code etc.
  2. Bundled binary<extensionPath>/bin/axme-code (CI per-platform; axme.binaryPath setting / AXME_CLAUDE_EXECUTABLE env / PATH / standard locations as fallbacks).
  3. MCP register(vscode as any).cursor.mcp.registerServer({...}) + 3s wait. Bypasses .cursor/mcp.json Enable gate because trust = "user installed extension".
  4. User-level hooks — writes ~/.cursor/hooks.json (machine-wide safety; preserves user entries, dedups axme entries).
  5. Auditor auth — first-run modal: Anthropic key / Cursor SDK key / Skip. Auto-uses Claude subscription if claude login detected.
  6. Setup offer — non-modal toast "Run setup?" if .axme-code/ missing.
  7. Status bar + commands — live counts (AXME ✓ N mems, D dec), command palette (Setup / Reindex / ShowStatus / ReauthAuditor / OpenDashboard / ShowRecentDecisions).

CI publish workflow

.github/workflows/publish-extension.yml:

  • PR / branch push: 5-platform matrix builds .vsix artifacts (linux-x64/arm64, darwin-x64/arm64, win32-x64). No publish.
  • Tag extension-v* (human): downloads artifacts, runs ovsx publish --target <platform> per .vsix, attaches all 5 to GitHub Release for sideload.
  • win32-arm64 skipped — @cursor/sdk@1.0.12 has 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)

  1. Claude subscription (claude login detected) — zero paste, auto-used.
  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.

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:

  • VS Code 1.119 has no API to intercept/deny chat tool calls (verified: no GitHub issue, no roadmap entry).
  • No sessionEnd lifecycle event for third-party extensions.
  • No access to Copilot chat transcript for the auditor to read.

Mitigations land in v0.0.2+ as Microsoft adds these APIs OR via the cooperative axme_safety_check MCP tool path (agent calls our tool before risky ops, instructed by a .cursor/rules/axme-code.mdc-equivalent rule file).

Test plan

Risks (full register in plan file)

  • 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 if user demand warrants $99/yr Apple Developer cost.

🤖 Generated with Claude Code

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.
@George-iam George-iam merged commit 1b15648 into feat/cursor-full-support-20260510 May 11, 2026
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant