Skip to content

feat(cursor): land extension v0.0.1 into main (merges PR #130 into main)#131

Merged
George-iam merged 8 commits into
mainfrom
feat/cursor-full-support-20260510
May 11, 2026
Merged

feat(cursor): land extension v0.0.1 into main (merges PR #130 into main)#131
George-iam merged 8 commits into
mainfrom
feat/cursor-full-support-20260510

Conversation

@George-iam
Copy link
Copy Markdown
Contributor

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:

  • `feat(extension): Cursor-only extension v0.0.1` — entire extension/ subdir
  • `fix(extension): bundle bin/axme-code as CJS, not ESM`
  • `fix(extension): correct Cursor dashboard deep-link URL to /integrations`
  • `Merge branch 'feat/cursor-full-support-20260510' into feat/vscode-extension-20260510` (housekeeping)
  • `fix: inline claude-agent-sdk in extension bundle + fresh agentId per Cursor SDK call`
  • `fix(cli): suppress noisy fallback + MaxListeners warnings during setup`
  • `fix(hooks): normalize Cursor tool_name=Shell to Bash for safety dispatch` (critical — without this, force-push deny silently bypasses)
  • `Merge pull request feat(extension): Cursor-only extension v0.0.1 (full axme-code in Cursor, 1-click install) #130 from AxmeAI/feat/vscode-extension-20260510`

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

George-iam and others added 8 commits May 11, 2026 11:46
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)
@George-iam George-iam merged commit 589b53d into main May 11, 2026
10 of 11 checks passed
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