Skip to content

Add multi-IDE installer support for OpenCode, Cursor, Codex, Roo, and Kilo#291

Open
RaviTharuma wants to merge 4 commits into
rohitg00:mainfrom
RaviTharuma:feat/multi-ide-installers
Open

Add multi-IDE installer support for OpenCode, Cursor, Codex, Roo, and Kilo#291
RaviTharuma wants to merge 4 commits into
rohitg00:mainfrom
RaviTharuma:feat/multi-ide-installers

Conversation

@RaviTharuma
Copy link
Copy Markdown

@RaviTharuma RaviTharuma commented May 11, 2026

Summary

  • add a first-party agentmemory install CLI command for OpenCode, Cursor, Codex, Roo, Kilo, PI, Hermes, and OpenClaw targets
  • ship new Cursor and Codex hook entrypoints and generate the corresponding host config files plus MCP wiring
  • preserve deep native integrations for Claude/OpenClaw/Hermes/PI while adding best-available automatic capture for hosts that only expose MCP or file-watch surfaces

What changed

  • added src/installers.ts with target-specific installers
  • added new hook adapters: src/hooks/cursor.ts and src/hooks/codex.ts
  • wired agentmemory install into src/cli.ts
  • updated tsdown.config.ts so the new hook scripts are built into both dist/hooks and tracked plugin/scripts
  • added focused tests in test/installers.test.ts

QA

  • npm run build
  • npx vitest run test/installers.test.ts
  • manual installer runs for:
    • node dist/cli.mjs install opencode --project-root <tmp>
    • node dist/cli.mjs install cursor --project-root <tmp>
    • node dist/cli.mjs install codex --project-root <tmp>
    • node dist/cli.mjs install roo --project-root <tmp>
    • node dist/cli.mjs install kilo --project-root <tmp>
      and inspection of generated files/configs

Notes

  • Roo Code and Kilo Code currently receive best-available MCP config generation; they do not expose the same native lifecycle hook surface as Claude/Cursor/OpenCode
  • OpenCode gets both an MCP entry and a generated plugin that forwards session/file/command events into the existing agentmemory hook pipeline

Summary by CodeRabbit

  • New Features

    • Added an install CLI command to install Agent Memory integrations for OpenCode, Cursor, Codex, Roo, Kilo, PI, OpenClaw, and Hermes.
    • Added Cursor and Codex hook and CLI scripts to emit session start/observe/tool-use/stop events and surface returned context.
  • Chores

    • Installer generates and merges editor/plugin configuration and integration artifacts across multiple targets.
  • Tests

    • Expanded tests to verify installer outputs and safe merging into existing configs.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

@RaviTharuma is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

Adds an agentmemory install CLI, installer implementation, Codex/Cursor hook sources and compiled plugin scripts, build config updates, and tests to install agentmemory integrations across multiple editor targets.

Changes

AgentMemory Installation Framework

Layer / File(s) Summary
CLI Install Command
src/cli.ts
agentmemory install handler parses target and flags, dynamically imports installers.js, calls installTarget, and reports files/notes.
Build Configuration
tsdown.config.ts
Adds src/hooks/cursor.ts and src/hooks/codex.ts to hookEntries, enabling builds to dist/hooks and plugin/scripts.
Codex plugin script (compiled)
plugin/scripts/codex.mjs
Reads stdin JSON, posts session/observe/end requests to Agent Memory, writes returned context on session start, and swallows network errors.
Cursor plugin script (compiled)
plugin/scripts/cursor.mjs
Infers Cursor events from stdin JSON, posts observations and session lifecycle events to Agent Memory, writes returned context on session start, and ignores failures.
Codex hook source
src/hooks/codex.ts
TypeScript hook: payload type, auth headers, stdin parsing, event-driven REST calls, timed aborts, and non-blocking error handling.
Cursor hook source
src/hooks/cursor.ts
TypeScript hook: payload type, event inference, session/project helpers, observe/session endpoints, and main routing for cursor events.
Installers: Types & Core Helpers
src/installers.ts
Exports InstallTarget/InstallResult; adds filesystem, JSON (comment-stripping), packageRoot, and hook command utilities.
Installers: Target Implementations
src/installers.ts
Per-target install logic: OpenCode plugin + opencode.json{c}, Cursor mcp.json merge, Codex config.toml + hooks, Roo/Kilo/PI/OpenClaw/Hermes handling, MCP merge helpers, recursive copy, and installTarget orchestrator.
Installation Tests
test/installers.test.ts
Vitest suite creating temp roots and asserting generated files/configs for opencode, cursor, codex, roo, and kilo, including merge and JSONC-path behaviors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble lines and patch the trees,

I stitch the hooks with nimble knees,
Cursor listens, Codex sings,
Installers plant configurable springs,
A rabbit hops — integrations breeze.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a multi-IDE installer command with support for multiple IDE targets (OpenCode, Cursor, Codex, Roo, Kilo, and others mentioned in objectives).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/codex.ts`:
- Around line 38-95: After determining the event (variable event), capture a
single timestamp once (e.g., const timestamp = new Date().toISOString()) and
reuse that variable in all fetch bodies instead of calling new
Date().toISOString() multiple times; update the payloads in the UserPromptSubmit
and PostToolUse/PreToolUse fetch calls to use this shared timestamp variable
(keep sid, root and hookType fields unchanged) so the same timestamp is sent for
all observations within the same execution.

In `@src/hooks/cursor.ts`:
- Around line 98-160: The code calls new Date().toISOString() multiple times
inside the switch handling inferred events; capture a single timestamp string
once before calling inferEvent/switch (e.g., const ts = new
Date().toISOString()) and replace each inline new Date().toISOString() in the
postObserve/postSessionStart payloads with that ts; update places using
postObserve (and postSessionStart if it needs timestamp) referenced in this
file—look for inferEvent(payload), postSessionStart({ sessionId: sid, project:
root, cwd: root }), and each postObserve(...) case to reuse the captured
timestamp variable.

In `@src/installers.ts`:
- Around line 94-121: The plugin currently generates a new session_id for every
event which prevents correlating events; inside generateOpenCodePlugin
(AgentmemoryPlugin) capture and store a single session_id when handling the
'session.created' event (e.g., assign to a closure-scoped variable) and then
reuse that stored session_id in all other event handlers and hooks
('session.idle', 'file.edited', 'command.executed', 'tool.execute.after',
'chat.message') instead of calling Date.now().toString(36) each time; ensure you
fall back to a sensible default if the stored session_id is not yet set.
- Around line 43-45: The packageRootFromCliDist function currently uses new
URL(import.meta.url).pathname which yields Windows paths with a leading slash;
change it to convert import.meta.url via fileURLToPath (from 'url') and use that
value with dirname and resolve so filesystem paths are correct
cross-platform—update packageRootFromCliDist to call
fileURLToPath(import.meta.url) instead of accessing .pathname and ensure the
function still uses dirname and resolve to return the package root.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39354f66-c30b-44f8-90e6-cd2820ff76c4

📥 Commits

Reviewing files that changed from the base of the PR and between 03fb42d and 42bc096.

📒 Files selected for processing (8)
  • plugin/scripts/codex.mjs
  • plugin/scripts/cursor.mjs
  • src/cli.ts
  • src/hooks/codex.ts
  • src/hooks/cursor.ts
  • src/installers.ts
  • test/installers.test.ts
  • tsdown.config.ts

Comment thread src/hooks/codex.ts
Comment thread src/hooks/cursor.ts
Comment thread src/installers.ts
@RaviTharuma
Copy link
Copy Markdown
Author

Addressed the actionable review feedback in commit 2e7e169:\n\n- reused a single timestamp in the Codex and Cursor hook handlers\n- switched installer path resolution to fileURLToPath for cross-platform correctness\n- fixed the generated OpenCode plugin to keep one stable session id across events\n\nRe-ran verification after the fixes:\n- npm run build\n- npx vitest run test/installers.test.ts\n- manual installer QA for OpenCode, Cursor, Codex, Roo, and Kilo\n\nI also checked the PR review threads — the actionable CodeRabbit threads are already marked resolved on GitHub after the update. The remaining Vercel failure is an authorization gate on the upstream team side rather than a code issue.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/installers.test.ts (2)

86-111: ⚡ Quick win

Use imported writeFileSync and mkdirSync for consistency.

Lines 88-98 use require("node:fs") to access filesystem functions that are already imported at line 2. For consistency with the rest of the test file, use the imported functions.

♻️ Proposed consistency fix
 it("reuses native .jsonc paths when they already exist", () => {
   const root = tempRoot();
-  require("node:fs").writeFileSync(
+  writeFileSync(
     join(root, "opencode.jsonc"),
     '{\n  // comment\n  "mcp": {"existing": {"type": "local", "command": ["foo"]}}\n}\n',
     "utf8",
   );
-  require("node:fs").mkdirSync(join(root, ".kilo"), { recursive: true });
-  require("node:fs").writeFileSync(
+  mkdirSync(join(root, ".kilo"), { recursive: true });
+  writeFileSync(
     join(root, "kilo.jsonc"),
     '{\n  // comment\n  "mcp": {"existing": {"type": "local", "command": ["foo"]}}\n}\n',
     "utf8",
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/installers.test.ts` around lines 86 - 111, Replace the inline requires
for node:fs with the already-imported filesystem helpers to keep the test
consistent: change the three occurrences of require("node:fs").writeFileSync and
require("node:fs").mkdirSync in the "reuses native .jsonc paths when they
already exist" test to use the imported writeFileSync and mkdirSync functions
(the rest of the test uses readFileSync/join), ensuring you reference the same
symbols used elsewhere in the file.

58-84: ⚡ Quick win

Use imported mkdirSync and writeFileSync for consistency.

Lines 62-64 and 69-72 use require("node:fs") to access mkdirSync and writeFileSync, but these functions are already imported at line 2. Using the imported functions improves consistency and avoids mixing import styles.

♻️ Proposed consistency fix
+import { existsSync, mkdirSync, writeFileSync } from "node:fs";
+
 it("preserves JSONC-based existing server entries when merging", () => {
   const root = tempRoot();
   const cursorDir = join(root, ".cursor");
   const kiloDir = join(root, ".kilo");
-  require("node:fs").mkdirSync(cursorDir, { recursive: true });
-  require("node:fs").mkdirSync(kiloDir, { recursive: true });
-  require("node:fs").writeFileSync(
+  mkdirSync(cursorDir, { recursive: true });
+  mkdirSync(kiloDir, { recursive: true });
+  writeFileSync(
     join(cursorDir, "mcp.json"),
     '{\n  // comment\n  "mcpServers": {\n    "existing": {"command": "foo"}\n  }\n}\n',
     "utf8",
   );
-  require("node:fs").writeFileSync(
+  writeFileSync(
     join(kiloDir, "kilo.jsonc"),
     '{\n  // comment\n  "mcp": {\n    "existing": {"type": "local", "command": ["foo"]}\n  }\n}\n',
     "utf8",
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/installers.test.ts` around lines 58 - 84, Replace the ad-hoc
require("node:fs") calls with the already imported fs helpers: use the imported
mkdirSync and writeFileSync instead of require("node:fs").mkdirSync and
require("node:fs").writeFileSync in the "preserves JSONC-based existing server
entries when merging" test (the block that creates cursorDir/kiloDir and writes
mcp.json and kilo.jsonc); this keeps import style consistent and reuses the
top-level imports for mkdirSync and writeFileSync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/installers.ts`:
- Around line 31-68: The stripJsonComments function reads input[i + 1] into next
without bounds checks; update stripJsonComments to check i + 1 < input.length
before using next (and treat end-of-input as non-comment), and when scanning
single-line "//" or block "/*" comments ensure you don't read past the end:
verify i + 1 exists before entering those comment branches and handle an
unclosed block comment by advancing to input.length and breaking/returning
appropriate output. Specifically, guard uses of input[i + 1] (the variable next)
and adjust the while loops that skip to newline or "*/" to safely stop at
input.length to avoid accessing undefined or looping past the buffer.
- Around line 124-157: In generateOpenCodePlugin move the variable declarations
out of the returned object: declare let sessionId and const getSessionId before
the return string is constructed (so the generated plugin code places them as
top-level variables/functions inside the plugin factory), then keep the event,
'tool.execute.after', and 'chat.message' properties unchanged but referencing
sessionId/getSessionId; update any string interpolation that embeds these
symbols (sessionId, getSessionId) so they refer to the moved declarations rather
than being placed as inline variable declarations inside the object literal.

---

Nitpick comments:
In `@test/installers.test.ts`:
- Around line 86-111: Replace the inline requires for node:fs with the
already-imported filesystem helpers to keep the test consistent: change the
three occurrences of require("node:fs").writeFileSync and
require("node:fs").mkdirSync in the "reuses native .jsonc paths when they
already exist" test to use the imported writeFileSync and mkdirSync functions
(the rest of the test uses readFileSync/join), ensuring you reference the same
symbols used elsewhere in the file.
- Around line 58-84: Replace the ad-hoc require("node:fs") calls with the
already imported fs helpers: use the imported mkdirSync and writeFileSync
instead of require("node:fs").mkdirSync and require("node:fs").writeFileSync in
the "preserves JSONC-based existing server entries when merging" test (the block
that creates cursorDir/kiloDir and writes mcp.json and kilo.jsonc); this keeps
import style consistent and reuses the top-level imports for mkdirSync and
writeFileSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f38d3487-2223-49dd-9d64-29b239cc9398

📥 Commits

Reviewing files that changed from the base of the PR and between 2e7e169 and 3e1efba.

📒 Files selected for processing (2)
  • src/installers.ts
  • test/installers.test.ts

Comment thread src/installers.ts
Comment thread src/installers.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
test/installers.test.ts (3)

78-79: 💤 Low value

Optional: Remove unnecessary type assertions.

Same issue as line 53—the as Record<string, any> assertions are unnecessary and inconsistent with other JSON.parse() calls in the test file.

♻️ Simplify to match other parse calls
-    const cursor = JSON.parse(readFileSync(join(cursorDir, "mcp.json"), "utf8")) as Record<string, any>;
-    const kilo = JSON.parse(readFileSync(join(kiloDir, "kilo.jsonc"), "utf8")) as Record<string, any>;
+    const cursor = JSON.parse(readFileSync(join(cursorDir, "mcp.json"), "utf8"));
+    const kilo = JSON.parse(readFileSync(join(kiloDir, "kilo.jsonc"), "utf8"));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/installers.test.ts` around lines 78 - 79, The two JSON.parse calls
creating variables cursor and kilo include unnecessary type assertions ("as
Record<string, any>"); remove those assertions so the lines simply parse the
file contents (using JSON.parse(readFileSync(join(...), "utf8"))) to match the
other parse calls in the test and maintain consistency for cursor and kilo
variable initialization.

22-111: ⚡ Quick win

Consider adding error case and idempotency tests.

The current test suite provides solid happy-path coverage. Consider adding tests for:

  • Invalid target names
  • Permission/filesystem errors
  • Malformed existing JSON/JSONC files
  • Idempotency (running installTarget twice on the same root)

These additions would increase confidence in error handling and ensure the installer gracefully handles edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/installers.test.ts` around lines 22 - 111, Add tests to
installers.test.ts to cover error and idempotency cases for installTarget: add
specs that call installTarget with an invalid target name and assert it throws
or returns an error, simulate filesystem/permission errors by creating read-only
dirs or mocking writeFileSync and assert installTarget fails gracefully, write
malformed JSON/JSONC files into tempRoot before calling installTarget and assert
it either preserves the file or reports a clear parse error, and add an
idempotency test that runs installTarget twice on the same projectRoot and
asserts no duplicate entries and that subsequent runs are no-ops (check
filesWritten and contents); reference the installTarget function and existing
tests in installers.test.ts to place these new cases.

53-53: 💤 Low value

Optional: Remove unnecessary type assertion.

The as Record<string, any> type assertion is unnecessary since JSON.parse() already returns any by default. This assertion appears inconsistently—other JSON.parse() calls in the file (lines 27, 34, 41-42, 52) work without it.

♻️ Simplify to match other parse calls
-    const kilo = JSON.parse(readFileSync(join(root, ".kilo", "kilo.jsonc"), "utf8")) as Record<string, any>;
+    const kilo = JSON.parse(readFileSync(join(root, ".kilo", "kilo.jsonc"), "utf8"));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/installers.test.ts` at line 53, The local variable declaration for kilo
uses an unnecessary type assertion; remove "as Record<string, any>" from the
JSON.parse call so it matches the other parses in the file. Update the line that
assigns to the variable kilo (the JSON.parse(readFileSync(...)) expression) to
omit the explicit cast, leaving the plain JSON.parse(...) result assigned to
kilo.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/installers.test.ts`:
- Around line 78-79: The two JSON.parse calls creating variables cursor and kilo
include unnecessary type assertions ("as Record<string, any>"); remove those
assertions so the lines simply parse the file contents (using
JSON.parse(readFileSync(join(...), "utf8"))) to match the other parse calls in
the test and maintain consistency for cursor and kilo variable initialization.
- Around line 22-111: Add tests to installers.test.ts to cover error and
idempotency cases for installTarget: add specs that call installTarget with an
invalid target name and assert it throws or returns an error, simulate
filesystem/permission errors by creating read-only dirs or mocking writeFileSync
and assert installTarget fails gracefully, write malformed JSON/JSONC files into
tempRoot before calling installTarget and assert it either preserves the file or
reports a clear parse error, and add an idempotency test that runs installTarget
twice on the same projectRoot and asserts no duplicate entries and that
subsequent runs are no-ops (check filesWritten and contents); reference the
installTarget function and existing tests in installers.test.ts to place these
new cases.
- Line 53: The local variable declaration for kilo uses an unnecessary type
assertion; remove "as Record<string, any>" from the JSON.parse call so it
matches the other parses in the file. Update the line that assigns to the
variable kilo (the JSON.parse(readFileSync(...)) expression) to omit the
explicit cast, leaving the plain JSON.parse(...) result assigned to kilo.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d910350-79a4-4e59-9222-89ed46385cad

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1efba and e596721.

📒 Files selected for processing (2)
  • src/installers.ts
  • test/installers.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/installers.ts

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