import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { expect } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest, type TestCli } from "../../support/cli/cliFixture.js"; // JSON.parse, JSONC parse, and native Claude Code read the last duplicate. const shadowParent = '"mcpServers": {"shadow": {"command": "/usr/bin/false"}},'; const shadowLeaf = '"rea": {"command": "/usr/bin/false"},'; const owned = '"rea": {"command": "npx", "args": ["-y", "rea-agents@6.6.0", "mcp"]}'; const runIn = (cli: TestCli, home: string) => (arguments_: readonly string[]) => cli.run({ arguments: arguments_, cwd: home, environment: { HOME: home, USERPROFILE: home, CLAUDE_CONFIG_DIR: home, XDG_CONFIG_HOME: join(home, ".config"), }, }); const setupArguments = (client: string) => ["setup", "--client", client, "--skill=false", "--json", "--yes"] as const; const openCodeOwned = '{"type":"local","command":["npx","-y","rea-agents@6.6.0","mcp"]}'; const openCodeForeign = '{"type":"local","command":["/usr/bin/false"]}'; const writeConfiguration = async (path: string, text: string) => { await mkdir(dirname(path), { recursive: true }); await writeFile(path, text); }; for (const [layout, original] of [ [ "a repeated server table", `{\n ${shadowParent}\n "mcpServers": {"peer": {"command": "/usr/bin/true"}}\n}\n`, ], [ "a repeated rea member", `{\n "mcpServers": {${shadowLeaf} "rea": {"command": "/usr/bin/true"}}\n}\n`, ], ] as const) cliTest( `Claude Code setup edits the effective member of ${layout}`, async ({ cli }) => { const home = await createTestTempDirectory("rea-json-duplicate-"); const config = join(home, ".claude.json"); await writeFile(config, original); const run = runIn(cli, home); const setup = await run(setupArguments("claude_code")); expect(setup.exitCode, setup.stdout).toBe(0); expect(setup.json).toMatchObject({ status: "ready", clients: { claude_code: { status: "configured" } }, }); expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original); const configured = await readFile(config, "utf8"); expect(configured).toContain( layout === "a repeated server table" ? shadowParent : shadowLeaf, ); expect(JSON.parse(configured)).toMatchObject({ mcpServers: { rea: { command: expect.not.stringMatching(/^\/usr/) } }, }); const repeated = await run(setupArguments("claude_code")); expect(repeated.exitCode, repeated.stdout).toBe(0); expect(repeated.json).toMatchObject({ status: "ready", plannedActions: [], }); expect(await readFile(config, "utf8")).toBe(configured); }, ); cliTest( "JSONC setup keeps comments and the shadowed server table", async ({ cli }) => { const home = await createTestTempDirectory("rea-jsonc-duplicate-"); await mkdir(join(home, ".cursor")); const config = join(home, ".cursor", "mcp.json"); const head = `{\n // shadowed table\n ${shadowParent}\n /* effective table */\n`; await writeFile( config, `${head} "mcpServers": {\n "peer": {"command": "/usr/bin/true"} // peer\n }\n}\n`, ); const setup = await runIn(cli, home)(setupArguments("cursor")); expect(setup.exitCode, setup.stdout).toBe(0); expect(setup.json).toMatchObject({ clients: { cursor: { status: "configured" } }, }); const configured = await readFile(config, "utf8"); expect(configured.startsWith(head)).toBe(true); expect(configured).toContain("// peer"); expect(configured.indexOf('"rea"')).toBeGreaterThan(head.length); }, ); cliTest( "uninstall removes an owned registration from the effective server table", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-duplicate-"); const config = join(home, ".claude.json"); const original = `{\n ${shadowParent}\n "mcpServers": {"peer": {"command": "/usr/bin/true"}, ${owned}}\n}\n`; await writeFile(config, original); const removed = await runIn(cli, home)(["uninstall", "--json"]); expect(removed.exitCode, removed.stdout).toBe(0); expect(removed.json).toMatchObject({ items: expect.arrayContaining([ expect.objectContaining({ name: "claude_code", status: "removed" }), ]), }); const remaining = await readFile(config, "utf8"); expect(remaining.startsWith(`{\n ${shadowParent}\n`)).toBe(true); expect(JSON.parse(remaining)).toEqual({ mcpServers: { peer: { command: "/usr/bin/true" } }, }); }, ); cliTest( "uninstall refuses to activate a shadowed duplicate registration", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-duplicate-"); const config = join(home, ".claude.json"); const original = `{\n "mcpServers": {${shadowLeaf} ${owned}}\n}\n`; await writeFile(config, original); const removed = await runIn(cli, home)(["uninstall", "--json"]); expect(removed.exitCode, removed.stdout).toBe(1); expect(removed.json).toMatchObject({ status: "failed", items: expect.arrayContaining([ expect.objectContaining({ name: "claude_code", status: "failed", detail: expect.stringContaining("activate an earlier duplicate"), }), ]), }); expect(await readFile(config, "utf8")).toBe(original); await expect(readFile(`${config}.rea.backup`)).rejects.toMatchObject({ code: "ENOENT", }); }, ); cliTest( "uninstall removes repeated owned entries and preserves inactive parents, peers, and its original backup", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-owned-repeats-"); const config = join(home, ".claude.json"); const inactive = '{"inactivePeer":{"command":"/usr/bin/false"}}'; const active = `{"rea":{"command":"npx","args":["-y","rea-agents@6.6.0","mcp"]},"peer":{"command":"/usr/bin/true"},"rea":{"command":"npx","args":["-y","rea-agents@6.5.0","mcp"]}}`; const original = `{"mcpServers":${inactive},"mcpServers":${active}}\n`; await writeFile(config, original); const run = runIn(cli, home); const removed = await run(["uninstall", "--json"]); expect(removed.exitCode, removed.stdout).toBe(0); expect(removed.json).toMatchObject({ status: "complete", items: expect.arrayContaining([ expect.objectContaining({ name: "claude_code", status: "removed" }), ]), }); const afterRemoval = await readFile(config, "utf8"); expect(JSON.parse(afterRemoval)).toEqual({ mcpServers: { peer: { command: "/usr/bin/true" } }, }); expect(afterRemoval).toContain(`"mcpServers":${inactive}`); expect(afterRemoval).toContain('"peer":{"command":"/usr/bin/true"}'); expect(afterRemoval).not.toContain("rea-agents@"); expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original); const repeated = await run(["uninstall", "--json"]); expect(repeated.exitCode, repeated.stdout).toBe(0); expect(await readFile(config, "utf8")).toBe(afterRemoval); expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original); }, ); cliTest( "preflights a later Cursor null duplicate before changing Claude or creating backups", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-repeat-preflight-"); const claude = join(home, ".claude.json"); const claudeOriginal = `{"mcpServers":{${owned}}}\n`; await writeFile(claude, claudeOriginal); const cursor = join(home, ".cursor", "mcp.json"); const cursorOriginal = `{"mcpServers":{"rea":null,${owned}}}\n`; await writeConfiguration(cursor, cursorOriginal); const removed = await runIn(cli, home)(["uninstall", "--json"]); expect(removed.exitCode, removed.stdout).toBe(1); expect(removed.json).toMatchObject({ status: "failed", items: expect.arrayContaining([ expect.objectContaining({ name: "cursor", status: "failed", detail: expect.stringContaining("earlier duplicate"), }), expect.objectContaining({ name: "uninstall", status: "skipped" }), ]), }); expect(await readFile(claude, "utf8")).toBe(claudeOriginal); expect(await readFile(cursor, "utf8")).toBe(cursorOriginal); for (const path of [claude, cursor]) await expect(readFile(`${path}.rea.backup`)).rejects.toMatchObject({ code: "ENOENT", }); }, ); cliTest( "preflights a foreign OpenCode V1 fallback before changing Claude or creating backups", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-opencode-fallback-"); const claude = join(home, ".claude.json"); const claudeOriginal = `{"mcpServers":{${owned}}}\n`; await writeFile(claude, claudeOriginal); const openCode = join(home, ".config", "opencode", "opencode.json"); const openCodeOriginal = `{"mcp":{"servers":{"rea":${openCodeOwned}},"rea":${openCodeForeign}}}\n`; await writeConfiguration(openCode, openCodeOriginal); const removed = await runIn(cli, home)(["uninstall", "--json"]); expect(removed.exitCode, removed.stdout).toBe(1); expect(removed.json).toMatchObject({ status: "failed", items: expect.arrayContaining([ expect.objectContaining({ name: "opencode", status: "failed", detail: expect.stringContaining("would activate it"), }), expect.objectContaining({ name: "uninstall", status: "skipped" }), ]), }); expect(await readFile(claude, "utf8")).toBe(claudeOriginal); expect(await readFile(openCode, "utf8")).toBe(openCodeOriginal); for (const path of [claude, openCode]) await expect(readFile(`${path}.rea.backup`)).rejects.toMatchObject({ code: "ENOENT", }); }, ); cliTest( "OpenCode setup replaces every repeated V1 entry while preserving the V2 peer", async ({ cli }) => { const home = await createTestTempDirectory("rea-json-opencode-migration-"); const config = join(home, ".config", "opencode", "opencode.json"); const original = `{"mcp":{"servers":{"peer":{"type":"local","command":["/usr/bin/true"]}},"rea":${openCodeForeign},"rea":${openCodeForeign}}}\n`; await writeConfiguration(config, original); const setup = await runIn(cli, home)(setupArguments("opencode")); expect(setup.exitCode, setup.stdout).toBe(0); expect(setup.json).toMatchObject({ clients: { opencode: { status: "configured" } }, }); const { mcp } = JSON.parse(await readFile(config, "utf8")) as { mcp: Record & { servers: Record }; }; expect(Object.hasOwn(mcp, "rea")).toBe(false); const launcher = resolve("scripts/rea.mjs"); expect(mcp.servers).toMatchObject({ peer: { command: ["/usr/bin/true"] }, rea: { type: "local", command: process.platform === "win32" ? [process.execPath, launcher, "mcp"] : [launcher, "mcp"], }, }); }, );