mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 23:06:18 +00:00
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.
149 lines
5.2 KiB
TypeScript
149 lines
5.2 KiB
TypeScript
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();
|
|
});
|
|
});
|