mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 23:06:18 +00:00
feat(setup): support CodeBuddy as a setup client (#2492)
Register rea in the global ~/.codebuddy/mcp.json through a new codebuddy entry in SUPPORTED_CLIENT_DEFINITIONS (json dialect, ~/.codebuddy marker, ~/.codebuddy/skills skill directory). Document the client and cover registration and uninstall with boundary tests. Loading, the bundled skill, and real REA tool calls were verified in the CodeBuddy client.
This commit is contained in:
1 parent
1902fcb13d
commit
fb841a9596
5 files changed
+170
No files matched your search
@@ -663,6 +663,18 @@ The user-level MCP paths follow the editor's VS Code layout. Trae's official
|
||||
documentation describes the raw JSON editor without specifying those paths;
|
||||
loading this registration in Trae remains unverified.
|
||||
|
||||
## CodeBuddy
|
||||
|
||||
For CodeBuddy, setup writes `mcpServers.rea` into the global
|
||||
`~/.codebuddy/mcp.json` (a standard `mcpServers` JSON file at the user home
|
||||
root on every platform). Entries carry `command`, `args`, and `env`; stdio is
|
||||
the default transport. CodeBuddy discovers personal skills from
|
||||
`~/.codebuddy/skills`, so setup installs its skill copy there.
|
||||
|
||||
The global MCP path is a plain JSON file; setup preserves every other server
|
||||
and top-level key when it upserts `rea`. Loading this registration, the
|
||||
bundled skill, and real REA tool calls through CodeBuddy are verified.
|
||||
|
||||
## WorkBuddy
|
||||
|
||||
For [WorkBuddy](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/MCP-Guide), setup writes `mcpServers.rea` into the
|
||||
|
||||
@@ -636,6 +636,7 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
|
||||
| Claude Desktop | `claude_desktop` |
|
||||
| Codex | `codex` |
|
||||
| Cursor | `cursor` |
|
||||
| CodeBuddy | `codebuddy` |
|
||||
| Gemini CLI | `gemini_cli` |
|
||||
| Windsurf | `windsurf` |
|
||||
| Devin | `devin` |
|
||||
|
||||
@@ -588,6 +588,14 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
|
||||
markerPath: [".cursor"],
|
||||
format: "json",
|
||||
},
|
||||
{
|
||||
name: "codebuddy",
|
||||
displayName: "CodeBuddy",
|
||||
skillPath: [".codebuddy", "skills"],
|
||||
configPath: [".codebuddy", "mcp.json"],
|
||||
markerPath: [".codebuddy"],
|
||||
format: "json",
|
||||
},
|
||||
{
|
||||
name: "gemini_cli",
|
||||
displayName: "Gemini CLI",
|
||||
|
||||
@@ -50,6 +50,7 @@ describe("client configuration filesystem lifecycle", () => {
|
||||
"claude_desktop",
|
||||
"codex",
|
||||
"cursor",
|
||||
"codebuddy",
|
||||
"gemini_cli",
|
||||
"windsurf",
|
||||
"devin",
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
|
||||
import { npxRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js";
|
||||
import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js";
|
||||
import { skillDestinations } from "../../../src/application/SetupSkill.js";
|
||||
import { supportedClients } from "../../../src/application/SupportedClients.js";
|
||||
import { systemUninstallHost } from "../../../src/application/Uninstall.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js";
|
||||
|
||||
const command = npxRegistrationCommand(process.platform);
|
||||
|
||||
beforeEach(clearClientLocationEnvironment);
|
||||
afterEach(() => vi.unstubAllEnvs());
|
||||
|
||||
const codebuddyPath = (
|
||||
home: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
env: Parameters<typeof supportedClients>[2] = {},
|
||||
) =>
|
||||
supportedClients(home, platform, env).find(
|
||||
({ name }) => name === "codebuddy",
|
||||
);
|
||||
|
||||
describe("CodeBuddy configuration paths", () => {
|
||||
it("resolves the global mcp.json under the home root", () => {
|
||||
expect(codebuddyPath("/home/a")?.configPath).toBe(
|
||||
join("/home/a", ".codebuddy", "mcp.json"),
|
||||
);
|
||||
expect(codebuddyPath("/Users/a")?.markerPath).toBe(
|
||||
join("/Users/a", ".codebuddy"),
|
||||
);
|
||||
expect(codebuddyPath("C:\\Users\\a", "win32")?.configPath).toBe(
|
||||
join("C:\\Users\\a", ".codebuddy", "mcp.json"),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves the registration format as the plain mcpServers JSON dialect", () => {
|
||||
expect(codebuddyPath("/home/a")?.format).toBe("json");
|
||||
});
|
||||
|
||||
it("resolves the personal skills directory under the home root", () => {
|
||||
expect(
|
||||
skillDestinations("/home/a", ["codebuddy"]).map(({ path }) => path),
|
||||
).toEqual([
|
||||
join("/home/a", ".codebuddy", "skills", "reverse-engineer-anything"),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CodeBuddy JSON registration", () => {
|
||||
const codebuddyClient = async () => {
|
||||
const home = await createTestTempDirectory("rea-codebuddy-");
|
||||
const client = codebuddyPath(home, process.platform);
|
||||
if (client?.markerPath === undefined)
|
||||
throw new Error("missing CodeBuddy client");
|
||||
await mkdir(client.markerPath, { recursive: true });
|
||||
return { home, client };
|
||||
};
|
||||
|
||||
it("upserts rea into mcpServers while preserving unrelated settings", async () => {
|
||||
const { home, client } = await codebuddyClient();
|
||||
await writeFile(
|
||||
client.configPath,
|
||||
`{
|
||||
"mcpServers": {
|
||||
"other": { "command": "other-server" }
|
||||
}
|
||||
}`,
|
||||
);
|
||||
|
||||
expect(
|
||||
(await configureClientConfiguration(client, {}, command)).status,
|
||||
).toBe("configured");
|
||||
const parsed = JSON.parse(await readFile(client.configPath, "utf8"));
|
||||
expect(parsed.mcpServers.other).toEqual({ command: "other-server" });
|
||||
expect(parsed.mcpServers.rea).toEqual({
|
||||
command: command[0],
|
||||
args: command.slice(1),
|
||||
});
|
||||
|
||||
expect(await configureClientConfiguration(client, {}, command)).toEqual({
|
||||
status: "unchanged",
|
||||
});
|
||||
expect(
|
||||
(
|
||||
await readClientRegistrationStatuses(home, undefined, {
|
||||
platform: process.platform,
|
||||
environment: {},
|
||||
})
|
||||
).find(({ client }) => client === "codebuddy"),
|
||||
).toMatchObject({ state: "aligned", command });
|
||||
});
|
||||
|
||||
it("creates the mcpServers table in an existing mcp.json without servers", async () => {
|
||||
const { client } = await codebuddyClient();
|
||||
await writeFile(
|
||||
client.configPath,
|
||||
JSON.stringify({ unrelated: { setting: true } }, null, 2),
|
||||
);
|
||||
expect(
|
||||
(await configureClientConfiguration(client, {}, command)).status,
|
||||
).toBe("configured");
|
||||
const parsed = JSON.parse(await readFile(client.configPath, "utf8"));
|
||||
expect(parsed.unrelated).toEqual({ setting: true });
|
||||
expect(parsed.mcpServers.rea).toEqual({
|
||||
command: command[0],
|
||||
args: command.slice(1),
|
||||
});
|
||||
});
|
||||
|
||||
it("registers into an empty mcp.json", async () => {
|
||||
const { client } = await codebuddyClient();
|
||||
await writeFile(client.configPath, "");
|
||||
expect(
|
||||
(await configureClientConfiguration(client, {}, command)).status,
|
||||
).toBe("configured");
|
||||
const parsed = JSON.parse(await readFile(client.configPath, "utf8"));
|
||||
expect(parsed.mcpServers.rea).toEqual({
|
||||
command: command[0],
|
||||
args: command.slice(1),
|
||||
});
|
||||
});
|
||||
|
||||
it("removes only the rea entry on uninstall", async () => {
|
||||
const { home, client } = await codebuddyClient();
|
||||
await writeFile(
|
||||
client.configPath,
|
||||
JSON.stringify(
|
||||
{ mcpServers: { other: { command: "other-server" } } },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
await configureClientConfiguration(client, {}, command);
|
||||
|
||||
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
|
||||
"removed",
|
||||
);
|
||||
const parsed = JSON.parse(await readFile(client.configPath, "utf8"));
|
||||
expect(parsed.mcpServers.other).toEqual({ command: "other-server" });
|
||||
expect(parsed.mcpServers.rea).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user