From 7f1ae42f71ce85547d317eed5e62fa330d9876bb Mon Sep 17 00:00:00 2001 From: I Made Ocy Darma Putra Date: Tue, 6 Oct 2026 13:15:59 +0700 Subject: [PATCH] feat(setup): support Command Code MCP registration (#645) Configure Command Code's user-scope MCP config (~/.commandcode/mcp.json) with an explicit stdio registration ({ transport, enabled, command, args }) under mcpServers, matching the shape Command Code writes itself. Adds the commandcode setup client and dialect, detection/alignment support, README/docs entries, and regenerated product catalog. --- README.md | 2 +- README_ar.md | 4 +- README_ja.md | 4 +- README_ko.md | 4 +- README_zh.md | 4 +- docs/installation.md | 1 + docs/product-catalog.json | 6 ++ docs/roadmap.md | 2 +- .../ClientConfigurationDocument.ts | 8 +++ src/application/ClientRegistrationStatus.ts | 5 ++ src/application/SupportedClients.ts | 12 ++++ .../clientConfigurationDialects.test.ts | 19 ++++++- .../filesystem/clientLifecycle.test.ts | 1 + .../clientRegistrationStatus.test.ts | 56 +++++++++++++++++++ 14 files changed, 115 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 33f07106e..835eeb576 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Setup shows its changes before applying them and backs up existing configuration After setup, restart your agent and [describe the app or feature](#just-ask-your-agent) you want to understand. Hopper can run in demo mode; if it shows a first-run prompt, choose the demo or enter an existing license. -REA supports Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, and VS Code. Existing REA registrations are selected by default during setup; other detected agents remain unselected until chosen. Other agents can use the [manual MCP configuration](#manual-mcp-configuration). +REA supports Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, and VS Code. Existing REA registrations are selected by default during setup; other detected agents remain unselected until chosen. Other agents can use the [manual MCP configuration](#manual-mcp-configuration). ### From the terminal with npx diff --git a/README_ar.md b/README_ar.md index 9c07329f6..6f73c85a6 100644 --- a/README_ar.md +++ b/README_ar.md @@ -92,7 +92,7 @@ npx rea-agents setup ### الاستخدام مع وكيل -بعد الإعداد، أعد تشغيل الوكيل واشرح التطبيق أو الميزة التي تريد استقصاءها. يدعم REA Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن للوكلاء الآخرين استخدام إعداد MCP أدناه. +بعد الإعداد، أعد تشغيل الوكيل واشرح التطبيق أو الميزة التي تريد استقصاءها. يدعم REA Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وCommand Code وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن للوكلاء الآخرين استخدام إعداد MCP أدناه. يمكن تشغيل Hopper في الوضع التجريبي. إذا ظهرت رسالة عند التشغيل الأول، فاختر التجربة أو أدخل ترخيصًا موجودًا. @@ -219,7 +219,7 @@ rea setup ## استخدام REA مع وكلاء برمجة آخرين -يدعم Setup Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن لأي وكيل يدعم خوادم MCP المحلية الاتصال باستخدام الإعداد التالي: +يدعم Setup Claude Code وClaude Desktop وCodex وCursor وGemini CLI وWindsurf وDevin وOpenCode وAntigravity وGitHub Copilot CLI وCommand Code وVS Code. تكون تسجيلات REA الموجودة محددة افتراضياً، ويجب اختيار العملاء المكتشفين الآخرين يدوياً. يمكن لأي وكيل يدعم خوادم MCP المحلية الاتصال باستخدام الإعداد التالي: diff --git a/README_ja.md b/README_ja.md index edc1763f0..52f3980ea 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,7 +92,7 @@ Setup は最初に連携するエージェントを複数選択できるよう ### エージェントで使う -設定後にエージェントを再起動し、調べたいアプリや機能を説明します。REA は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。その他のエージェントは下記の MCP 設定を使えます。 +設定後にエージェントを再起動し、調べたいアプリや機能を説明します。REA は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。その他のエージェントは下記の MCP 設定を使えます。 Hopper はデモモードで使えます。初回起動の画面ではデモを選択するか、既存のライセンスを入力してください。 @@ -208,7 +208,7 @@ REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェ ## 他のコーディングエージェントで使う -Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。ローカル MCP サーバーに対応するエージェントは、次の設定でも接続できます。 +Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。ローカル MCP サーバーに対応するエージェントは、次の設定でも接続できます。 diff --git a/README_ko.md b/README_ko.md index c0f7096e1..e07729ee1 100644 --- a/README_ko.md +++ b/README_ko.md @@ -93,7 +93,7 @@ Setup은 먼저 연결할 에이전트를 여러 개 선택하도록 안내합 ### 에이전트에서 사용하기 -설정 후 에이전트를 다시 시작하고 조사할 앱이나 기능을 설명하세요. REA는 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 다른 에이전트는 아래 MCP 설정을 사용할 수 있습니다. +설정 후 에이전트를 다시 시작하고 조사할 앱이나 기능을 설명하세요. REA는 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 다른 에이전트는 아래 MCP 설정을 사용할 수 있습니다. Hopper는 데모 모드로 사용할 수 있습니다. 첫 실행 안내가 나오면 데모를 선택하거나 기존 라이선스를 입력하세요. @@ -209,7 +209,7 @@ REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이 ## 다른 코딩 에이전트에서 사용하기 -Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 로컬 MCP 서버를 지원하는 에이전트는 다음 설정으로 연결할 수 있습니다. +Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, VS Code를 지원합니다. 기존 REA 등록은 기본 선택되고, 그 밖의 감지된 클라이언트는 직접 선택해야 합니다. 로컬 MCP 서버를 지원하는 에이전트는 다음 설정으로 연결할 수 있습니다. diff --git a/README_zh.md b/README_zh.md index 671e85754..478da06a1 100644 --- a/README_zh.md +++ b/README_zh.md @@ -92,7 +92,7 @@ Setup 会先展示变更并备份已有配置。要求与更多选项见[安装 ### 使用智能体 -设置完成后,重启智能体并描述你想了解的应用或功能。REA 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。其他智能体可以使用下方的 MCP 配置。 +设置完成后,重启智能体并描述你想了解的应用或功能。REA 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。其他智能体可以使用下方的 MCP 配置。 Hopper 支持演示模式。如果首次启动时出现提示,选择演示模式或输入已有许可证。 @@ -208,7 +208,7 @@ REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常 ## 与其他编程智能体一起使用 -Setup 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。任何支持本地 MCP 服务器的智能体都可以使用以下配置连接 REA。 +Setup 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code 和 VS Code。已有 REA 注册默认选中;其他检测到的客户端需手动选择。任何支持本地 MCP 服务器的智能体都可以使用以下配置连接 REA。 diff --git a/docs/installation.md b/docs/installation.md index 5817f66c7..c8b09bdb8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -77,6 +77,7 @@ Setup can configure these clients for REA's local MCP server: | OpenCode | `opencode` | | Antigravity | `antigravity` | | GitHub Copilot CLI | `copilot_cli` | +| Command Code | `commandcode` | | VS Code | `vscode` | ## Review setup changes diff --git a/docs/product-catalog.json b/docs/product-catalog.json index b5136f003..fb468db03 100644 --- a/docs/product-catalog.json +++ b/docs/product-catalog.json @@ -451,6 +451,12 @@ "format": "copilot_cli", "configuration": "managed" }, + { + "id": "commandcode", + "display_name": "Command Code", + "format": "commandcode", + "configuration": "managed" + }, { "id": "vscode", "display_name": "VS Code", diff --git a/docs/roadmap.md b/docs/roadmap.md index 1da91252e..ec4424a72 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -43,7 +43,7 @@ REA setup lets you select agent integration and optional Hopper installation. It installs the bundled workflow, configures detected agents, and can save verified paths for an existing Ghidra installation. It configures Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, -GitHub Copilot CLI, and VS Code using each client's configuration format. +GitHub Copilot CLI, Command Code, and VS Code using each client's configuration format. Ghidra analysis supports Linux x64 and macOS x64/arm64 with Ghidra 12.1.4 and a 64-bit full JDK 21. macOS also requires the matching native decompiler. The diff --git a/src/application/ClientConfigurationDocument.ts b/src/application/ClientConfigurationDocument.ts index 376cdfd31..e896f651f 100644 --- a/src/application/ClientConfigurationDocument.ts +++ b/src/application/ClientConfigurationDocument.ts @@ -150,6 +150,14 @@ export const clientRegistrationEntry = ( tools: ["*"], ...(Object.keys(environment).length === 0 ? {} : { env: environment }), }; + case "commandcode": + return { + transport: "stdio", + enabled: true, + command: executable, + args, + ...(Object.keys(environment).length === 0 ? {} : { env: environment }), + }; default: return { command: executable, diff --git a/src/application/ClientRegistrationStatus.ts b/src/application/ClientRegistrationStatus.ts index b7d05e1bb..306c7dca0 100644 --- a/src/application/ClientRegistrationStatus.ts +++ b/src/application/ClientRegistrationStatus.ts @@ -47,6 +47,7 @@ const registrationSchema = z args: z.array(z.string()).default([]), startup_timeout_sec: z.number().positive().optional(), type: z.string().optional(), + transport: z.string().optional(), tools: z.array(z.string()).optional(), disabled: z.boolean().optional(), enabled: z.boolean().optional(), @@ -152,6 +153,8 @@ const registrationAligned = ( JSON.stringify(registration.tools) !== JSON.stringify(["*"])) ) return false; + if (client.format === "commandcode" && registration.transport !== "stdio") + return false; if ( command.length === 4 && command[0] === "npx" && @@ -193,6 +196,8 @@ const parseRegistration = ( registration.type !== "stdio" ) throw new TypeError("Expected an stdio registration"); + if (client.format === "commandcode" && registration.transport !== "stdio") + throw new TypeError("Expected an stdio registration"); return registration; }; diff --git a/src/application/SupportedClients.ts b/src/application/SupportedClients.ts index 040c05220..399efb926 100644 --- a/src/application/SupportedClients.ts +++ b/src/application/SupportedClients.ts @@ -13,6 +13,7 @@ export interface SetupClient { | "vscode" | "copilot_cli" | "opencode" + | "commandcode" | "unsupported"; } @@ -79,6 +80,9 @@ const codexDirectory = ({ home, env }: ClientPathContext): string => const copilotDirectory = ({ home, env }: ClientPathContext): string => env.COPILOT_HOME ?? join(home, ".copilot"); +const commandCodeDirectory = ({ home }: ClientPathContext): string => + join(home, ".commandcode"); + const opencodeDirectory = ({ home, env }: ClientPathContext): string => join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "opencode"); @@ -192,6 +196,14 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [ markerPath: copilotDirectory, format: "copilot_cli", }, + { + name: "commandcode", + displayName: "Command Code", + configPath: (context: ClientPathContext) => + join(commandCodeDirectory(context), "mcp.json"), + markerPath: commandCodeDirectory, + format: "commandcode", + }, { name: "vscode", displayName: "VS Code", diff --git a/tests/boundary/filesystem/clientConfigurationDialects.test.ts b/tests/boundary/filesystem/clientConfigurationDialects.test.ts index 6a5abb30e..53b5de096 100644 --- a/tests/boundary/filesystem/clientConfigurationDialects.test.ts +++ b/tests/boundary/filesystem/clientConfigurationDialects.test.ts @@ -35,9 +35,14 @@ afterEach(() => vi.unstubAllEnvs()); const getClients = (home: string) => supportedClients(home).filter(({ name }) => - ["opencode", "antigravity", "copilot_cli", "vscode", "devin"].includes( - name, - ), + [ + "opencode", + "antigravity", + "copilot_cli", + "commandcode", + "vscode", + "devin", + ].includes(name), ); describe("additional client configuration dialects", () => { @@ -45,6 +50,7 @@ describe("additional client configuration dialects", () => { "opencode", "antigravity", "copilot_cli", + "commandcode", "vscode", "devin", ] as const)("registers, reads back, and uninstalls %s", async (name) => { @@ -90,6 +96,13 @@ describe("additional client configuration dialects", () => { }); if (name === "copilot_cli") expect(registration).toMatchObject({ type: "stdio", tools: ["*"] }); + if (name === "commandcode") + expect(registration).toMatchObject({ + transport: "stdio", + enabled: true, + command: "npx", + args: command.slice(1), + }); const statuses = await readClientRegistrationStatuses(home); expect(statuses).toEqual( diff --git a/tests/boundary/filesystem/clientLifecycle.test.ts b/tests/boundary/filesystem/clientLifecycle.test.ts index 410554f57..ed60c4f86 100644 --- a/tests/boundary/filesystem/clientLifecycle.test.ts +++ b/tests/boundary/filesystem/clientLifecycle.test.ts @@ -60,6 +60,7 @@ describe("client configuration filesystem lifecycle", () => { "opencode", "antigravity", "copilot_cli", + "commandcode", "vscode", ]); expect( diff --git a/tests/boundary/filesystem/clientRegistrationStatus.test.ts b/tests/boundary/filesystem/clientRegistrationStatus.test.ts index cb017bb7c..39c87690c 100644 --- a/tests/boundary/filesystem/clientRegistrationStatus.test.ts +++ b/tests/boundary/filesystem/clientRegistrationStatus.test.ts @@ -126,3 +126,59 @@ describe("client registration status", () => { ]); }); }); + +describe("commandcode registration status", () => { + const writeCommandcodeRegistration = async ( + home: string, + registration: Record, + ): Promise => { + await mkdir(join(home, ".commandcode")); + await writeFile( + join(home, ".commandcode/mcp.json"), + JSON.stringify({ mcpServers: { rea: registration } }), + ); + }; + + const ownedArgs = (): readonly string[] => [ + "-y", + PRODUCT_IDENTITY.registrationPackageSpecifier, + "mcp", + ]; + + it.each([undefined, "sse"])( + "reports transport %j as invalid", + async (transport) => { + const home = await createTestTempDirectory("rea-commandcode-transport-"); + await writeCommandcodeRegistration(home, { + ...(transport === undefined ? {} : { transport }), + command: "npx", + args: [...ownedArgs()], + }); + + expect(await readClientRegistrationStatuses(home)).toEqual([ + expect.objectContaining({ + client: "commandcode", + state: "invalid", + command: [], + }), + ]); + }, + ); + + it("reports a disabled registration as stale", async () => { + const home = await createTestTempDirectory("rea-commandcode-disabled-"); + await writeCommandcodeRegistration(home, { + transport: "stdio", + enabled: false, + command: "npx", + args: [...ownedArgs()], + }); + + expect(await readClientRegistrationStatuses(home)).toEqual([ + expect.objectContaining({ + client: "commandcode", + state: "stale", + }), + ]); + }); +});