Files
rea/tests/boundary/filesystem/clientLifecycle.test.ts
T
d9d2a17381 feat(setup): register DeepSeek Harness through setup, doctor and uninstall (#1929)
* feat(setup): register DeepSeek Harness through setup, doctor and uninstall

DeepSeek Harness registers MCP clients through a Cordis YAML patch
sequence in the home patch rather than a server table, so setup could
not configure it and users had to append the documented entry by hand.

Add a dsh client: the home patch $DSH_HOME/cordis.patch.yml
(~/.dsh/cordis.patch.yml when unset) is parsed as a patch list, the REA
entry owns the stable rea-mcp id, and setup appends or replaces exactly
that entry while preserving unrelated patches and comments in place.
Doctor reports the registration through the same entry, and uninstall
removes it, dropping insert operations it emptied. Skill destinations
follow ~/.agents/skills ($DSH_AGENTS_HOME/skills when configured), the
home the rest of the client machinery already uses.

Fixes #1839

* fix(setup): carry environment and identity validation into DSH registrations

Address review: the DSH entry discarded discovered provider overrides;
config.env is now emitted and retained like the other dialects. An entry
carrying the rea-mcp id under a wrong or missing package name, or
duplicated entries, reads back as absent so setup rewrites exactly one
canonical entry instead of reporting unchanged. Doctor now requires
serverName rea and stdio transport before calling a DSH registration
aligned, and the Harness skill identifier in the docs matches the
installed reverse-engineer-anything skill.

* fix(setup): apply Cordis patch order and collapse DSH registrations

Address review: a later direct { id, config } operation replaces the
row's config under the native patch DSL, so the registration view now
applies patches in order instead of only reading insert operations;
doctor reports the effective command rather than the inserted one.
Setup rewrites exactly one canonical entry: duplicate id-matching rows
collapse to the first, direct patches to the entry are dropped from the
document and from the edited YAML, and uninstall removes them with the
entry. The client-location test fixture now clears DSH_HOME and
DSH_AGENTS_HOME so a developer environment cannot point the suite at
real Harness homes.

* fix(setup): repair direct-only registrations and preserve Cordis entry state

Serialize now edits the first insertion carrying rea-mcp in place, so a
grouped entry keeps its scope, neighbors, and comments instead of moving
to a fresh root-level insertion, and later carrying insertions and direct
patches collapse away. Entry-level disabled state projects into the
registration the doctor and setup compare, so a disabled row reads as
stale and the repair re-enables it. A direct-only registration no longer
suppresses the canonical insert append, and an update retains the
registration environment. Duplicate entries collapse across operations,
not only within one.

* fix(setup): preserve Harness row state through identity and refresh repairs

A wrong-package row still reads back as a server record carrying its
environment and caller fields, projected with a package marker the
alignment path treats as stale, instead of vanishing and dropping
config.env from the repair. A direct Cordis patch without a preceding
insertion configures nothing, so a direct-only registration reads as
absent even when its config matches and the repair appends the insert.
Caller-owned config fields beyond the REA-owned keys, such as the cwd
the native verifier sets, survive a refresh instead of being silently
dropped.

* fix(setup): preserve Cordis tags and collapse aligned Harness rows

A refresh rebuilt Harness config from parsed values, so a caller !!js
expression was written back as a literal. Owned keys are now overlaid on
the winning config node and other nodes stay in place. Two already-aligned
rea-mcp insertions read as stale, so setup collapses them to one row, and
the bundled skill lists dsh with the other clients.

* fix(setup): drop the unused DSH configuration type

Knip reports DshConfigurationFormat as an unused export. The format union
does not depend on that alias.

* fix(setup): keep Harness env tags and comment-only patches

A refresh replaced the whole env map, so a caller !!js value was written
back as a literal. Environment entries now stay in place unless the value
changed. A comment-only cordis.patch.yml parses as an empty patch list and
keeps those comments when the registration is appended. Docs describe
plain rea uninstall, which scans supported clients and has no --client flag.

* fix(setup): own Harness rows by entry id and expression state

Uninstall removes a rea-mcp row even when the launcher is stale or the
patch never established a server record. A non-boolean disabled value,
including a Cordis expression, reads as stale. Expression text on a known
environment setting is not validated as a literal path, so a refresh can
keep the tag.

* fix(setup): collapse aliased Harness insertions

An anchored insert followed by an alias parses as two rea-mcp rows, but
the YAML edit kept the alias, so readback stayed duplicated and setup
restored the file. Aliases that resolve to the owned entry are now
dropped, including an alias of the entry inside one insert. Aliases of
other patches stay.

* fix(setup): resolve aliased Harness lists and config maps

An insert value that aliases another entry list still loaded a second
rea-mcp row after the operation-level alias collapse. That alias is now
dropped once the anchored list is rewritten. A config: *cfg map is
resolved and cloned before the refresh, so a caller !!js field on the
aliased map is not rewritten as a plain literal.

* fix(setup): follow Harness aliases for ids, lists, and env tags

An insert alias whose anchor is not another insert is copied so
unrelated rows stay while the owned row is rewritten. An aliased
entry id is resolved before ownership checks, and an environment alias
of an unchanged !!js scalar is left in place.

* fix(setup): keep aliased Harness env maps through uninstall

An env alias is cloned before a refresh so tagged values are not rebuilt
as literals, and expression-key scans follow aliases before path checks.
Insert aliases that carry REA are recorded before uninstall empties their
anchor, so the later alias is dropped instead of dangling.

* fix(setup): follow root Harness aliases and mark dsh as main-only

Winning-config walks resolve a root alias before patch order, so a replayed direct config's !!js env is not validated as a literal path. Root aliases that own rea-mcp are recorded before uninstall empties their anchor. An insert whose only owned member is an external alias is cloned so Cordis tags survive. Docs state dsh is not in npm 6.4.0.

* fix(setup): keep rows behind inert Harness aliases

A root alias whose anchor is not a loaded operation is copied and only the owned row is edited, so unrelated rows stay active. Entry id aliases become a scalar before their anchor patch is dropped. Root-alias ownership follows insert-list aliases, so a replay is removed instead of dangling after uninstall.

* refactor(setup): centralize Cordis source ownership

* fix(javascript): share Electron mutation proof with wrapped calls

* test(mcp): align graph and catalog checks with current contracts

* fix(setup): separate Cordis ownership from editability

* docs: clarify ownership and interpretation boundaries

* fix(setup): preserve Cordis applicability and caller metadata

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-12 11:06:26 +08:00

1872 lines
66 KiB
TypeScript

import { constants as fsConstants } from "node:fs";
import {
copyFile,
lstat,
mkdir,
readFile,
realpath,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import { dirname, join } from "node:path";
import { parse as parseToml } from "smol-toml";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { parse as parseJsonc, type ParseError } from "jsonc-parser";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js";
import {
detectClients,
systemSetupHost,
} from "../../../src/application/SetupHost.js";
import {
runUninstall,
systemUninstallHost,
type UninstallFileSystem,
} from "../../../src/application/Uninstall.js";
const roots: string[] = [];
const ownedSkillManifest = `---\nname: ${PRODUCT_IDENTITY.skillName}\n---\n# REA\n`;
beforeEach(clearClientLocationEnvironment);
afterEach(async () => {
vi.unstubAllEnvs();
await Promise.all(
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
);
});
describe("client configuration filesystem lifecycle", () => {
it("detects every supported client and skips absent clients", async () => {
const home = await createTestTempDirectory("rea-detect-");
roots.push(home);
for (const client of supportedClients(home))
if (client.markerPath !== undefined)
await mkdir(client.markerPath, { recursive: true });
const detected = await detectClients(home);
expect(detected.map(({ name }) => name)).toEqual([
"claude_code",
"claude_desktop",
"codex",
"cursor",
"gemini_cli",
"windsurf",
"devin",
"opencode",
"antigravity",
"copilot_cli",
"commandcode",
"qwen_code",
"vscode",
"grok_build",
"omp",
"pi",
"hermes",
"dsh",
"grok_bot",
]);
expect(
detected.find(({ name }) => name === "claude_code")?.configPath,
).toBe(join(home, ".claude.json"));
expect(detected.find(({ name }) => name === "devin")?.format).toBe("json");
const emptyHome = await createTestTempDirectory("rea-empty-");
roots.push(emptyHome);
expect(await detectClients(emptyHome)).toEqual([]);
});
it("preserves unrelated Codex TOML and supports an installed command", async () => {
const home = await createTestTempDirectory("rea-toml-");
roots.push(home);
const configPath = join(home, ".codex/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'model = "gpt-5"\n[mcp_servers.other]\ncommand = "other"\n',
);
expect(
await configureClientConfiguration(
{ name: "codex", configPath, format: "toml" },
{
HOPPER_LAUNCHER_PATH: "/Hopper Path",
GHIDRA_INSTALL_DIR: "/opt/ghidra",
JAVA_HOME: "/opt/jdk-21",
},
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain('model = "gpt-5"');
expect(configured).toContain("[mcp_servers.rea]");
expect(configured).toContain('command = "rea"');
expect(configured).toContain("startup_timeout_sec = 30");
expect(configured).toContain('GHIDRA_INSTALL_DIR = "/opt/ghidra"');
expect(configured).toContain('JAVA_HOME = "/opt/jdk-21"');
expect(
await configureClientConfiguration(
{ name: "codex", configPath, format: "toml" },
{
HOPPER_LAUNCHER_PATH: "/Hopper Path",
GHIDRA_INSTALL_DIR: "/opt/ghidra",
JAVA_HOME: "/opt/jdk-21",
},
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
});
it("does not rewrite an equivalent Codex registration with reordered environment keys", async () => {
const home = await createTestTempDirectory("rea-toml-");
roots.push(home);
const configPath = join(home, ".codex/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
`[mcp_servers.rea]\ncommand = "rea"\nargs = ["mcp"]\nstartup_timeout_sec = 30\n\n[mcp_servers.rea.env]\nJAVA_HOME = "/opt/jdk-21"\nHOPPER_LAUNCHER_PATH = "/Hopper Path"\nGHIDRA_INSTALL_DIR = "/opt/ghidra"\n`,
);
const original = await readFile(configPath, "utf8");
expect(
await configureClientConfiguration(
{ name: "codex", configPath, format: "toml" },
{
HOPPER_LAUNCHER_PATH: "/Hopper Path",
GHIDRA_INSTALL_DIR: "/opt/ghidra",
JAVA_HOME: "/opt/jdk-21",
},
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
expect(await readFile(configPath, "utf8")).toBe(original);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
});
it("updates a symlink target without replacing the TOML config symlink", async () => {
const home = await createTestTempDirectory("rea-toml-symlink-");
roots.push(home);
const configPath = join(home, ".codex/config.toml");
const targetPath = join(home, "managed-config.toml");
const original = 'model = "gpt-5"\n';
await mkdir(dirname(configPath), { recursive: true });
await writeFile(targetPath, original);
await symlink(targetPath, configPath);
expect(
await configureClientConfiguration(
{ name: "codex", configPath, format: "toml" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
expect((await lstat(configPath)).isSymbolicLink()).toBe(true);
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(await readFile(targetPath, "utf8")).toContain("[mcp_servers.rea]");
});
it("fails before mutation when a TOML config symlink is dangling", async () => {
const home = await createTestTempDirectory("rea-toml-symlink-");
roots.push(home);
const configPath = join(home, ".codex/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await symlink(join(home, "missing-config.toml"), configPath);
expect(
await configureClientConfiguration({
name: "codex",
configPath,
format: "toml",
}),
).toEqual({ status: "failed", reason: "path" });
expect((await lstat(configPath)).isSymbolicLink()).toBe(true);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
});
});
describe("Grok Build server table edits", () => {
it("edits only the Grok Build REA tables and removes them on uninstall", async () => {
const home = await createTestTempDirectory("rea-grok-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
"# keep this comment",
'disabled_mcp_servers = ["xapi"]',
"",
"[cli]",
'default_model = "grok-4"',
"",
"[mcp_servers.blender]",
'command = "blender"',
'args = ["--mcp"]',
"",
"[mcp_servers.rea]",
'command = "old"',
'args = ["mcp"]',
"",
"[mcp_servers.rea.env]",
'OLD = "1"',
"",
"[[marketplace.sources]]",
'name = "official"',
'url = "https://example.test/market"',
"",
"[privacy]",
"share = false",
"",
].join("\r\n");
await writeFile(configPath, original);
const environment = {
HOPPER_LAUNCHER_PATH: "/Hopper Path",
GHIDRA_INSTALL_DIR: "/opt/ghidra",
JAVA_HOME: "/opt/jdk-21",
};
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
environment,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("\r\n");
expect(configured).toContain("# keep this comment");
expect(configured).toContain('disabled_mcp_servers = ["xapi"]');
expect(configured).toContain('default_model = "grok-4"');
expect(configured).toContain("[mcp_servers.blender]");
expect(configured).toContain('command = "blender"');
expect(configured).toContain("[[marketplace.sources]]");
expect(configured).toContain('name = "official"');
expect(configured).toContain('url = "https://example.test/market"');
expect(configured).toContain("[privacy]");
expect(configured).toContain("share = false");
expect(configured).toContain("[mcp_servers.rea]");
expect(configured).toContain("startup_timeout_sec = 30");
expect(configured).toContain('OLD = "1"');
expect(configured).not.toContain('command = "old"');
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
blender: { command: "blender", args: ["--mcp"] },
rea: {
command: "rea",
args: ["mcp"],
startup_timeout_sec: 30,
env: {
OLD: "1",
JAVA_HOME: "/opt/jdk-21",
HOPPER_LAUNCHER_PATH: "/Hopper Path",
GHIDRA_INSTALL_DIR: "/opt/ghidra",
},
},
},
marketplace: { sources: [{ name: "official" }] },
privacy: { share: false },
});
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
environment,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
expect(await readFile(configPath, "utf8")).toBe(configured);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("# keep this comment");
expect(removed).toContain("[mcp_servers.blender]");
expect(removed).toContain("[[marketplace.sources]]");
expect(removed).toContain('url = "https://example.test/market"');
expect(removed).toContain("[privacy]");
expect(removed).not.toContain("[mcp_servers.rea]");
expect(removed).not.toContain("startup_timeout_sec");
expect(removed).not.toContain("JAVA_HOME");
});
});
describe("Grok Build disabled server list", () => {
it("removes rea from disabled_mcp_servers and leaves other names", async () => {
const home = await createTestTempDirectory("rea-grok-disabled-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
"disabled_mcp_servers = [",
' "other",',
' "r\\u0065a",',
' "rea", # was off',
' "github",',
"]",
"[mcp_servers.rea]",
"enabled = false",
'command = "rea"',
'args = ["mcp"]',
"startup_timeout_sec = 30",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("# was off");
expect(configured).not.toContain("enabled");
expect(parseToml(configured)).toMatchObject({
disabled_mcp_servers: ["other", "github"],
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
await writeFile(configPath, 'disabled_mcp_servers = ["rea"] # note\n');
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const created = await readFile(configPath, "utf8");
expect(created).toContain("# note");
expect(created).not.toContain("disabled_mcp_servers");
expect(parseToml(created)).toMatchObject({
mcp_servers: { rea: { command: "rea", args: ["mcp"] } },
});
});
it("removes multiline rea entries from disabled_mcp_servers", async () => {
const home = await createTestTempDirectory("rea-grok-disabled-multiline-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
"disabled_mcp_servers = [",
' """rea""",',
" '''rea''',",
' """',
'rea""",',
' """rea',
'""",',
' "github",',
"]",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(parseToml(configured)).toMatchObject({
disabled_mcp_servers: ["rea\n", "github"],
mcp_servers: { rea: { command: "rea", args: ["mcp"] } },
});
await writeFile(configPath, 'disabled_mcp_servers = ["""rea"""] # note\n');
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const created = await readFile(configPath, "utf8");
expect(created).toContain("# note");
expect(created).not.toContain("disabled_mcp_servers");
});
});
describe("Grok Build text that resembles a server table", () => {
it("preserves Grok text that only looks like an REA table", async () => {
const home = await createTestTempDirectory("rea-grok-preserve-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
"banner = '''",
"welcome",
"[mcp_servers.rea]",
"keep this text",
"[privacy]",
"'''",
"",
"[mcp_servers.rea]",
'command = "old"',
'args = ["mcp"]',
"",
"# Production server: preserve this configuration",
'[mcp_servers."rea.env"]',
'command = "other"',
'args = ["serve"]',
"",
'[mcp_servers."other[prod]"]',
'command = "other"',
'args = ["serve"]',
"",
'[[marketplace."sources[prod]"]]',
'name = "official"',
"",
].join("\n");
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("keep this text");
expect(configured).toContain(
"# Production server: preserve this configuration",
);
expect(configured).toContain('[mcp_servers."rea.env"]');
expect(configured).toContain('[mcp_servers."other[prod]"]');
expect(configured).toContain('[[marketplace."sources[prod]"]]');
expect(configured).not.toContain('command = "old"');
expect(parseToml(configured)).toMatchObject({
banner: "welcome\n[mcp_servers.rea]\nkeep this text\n[privacy]\n",
mcp_servers: {
"rea.env": { command: "other", args: ["serve"] },
"other[prod]": { command: "other", args: ["serve"] },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
marketplace: { "sources[prod]": [{ name: "official" }] },
});
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("keep this text");
expect(removed).toContain(
"# Production server: preserve this configuration",
);
expect(removed).toContain('[mcp_servers."other[prod]"]');
expect(parseToml(removed)).toMatchObject({
mcp_servers: {
"rea.env": { command: "other" },
"other[prod]": { command: "other" },
},
});
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
it("replaces spaced and escaped Grok REA headers without duplicating them", async () => {
const home = await createTestTempDirectory("rea-grok-alias-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
"[ mcp_servers . rea ]",
'command = "old"',
'args = ["mcp"]',
"",
'[mcp_servers."\\u0072ea".env]',
'OLD = "1"',
"",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).not.toContain("mcp_servers . rea");
expect(configured).not.toContain("\\u0072ea");
expect(configured).toContain('OLD = "1"');
expect(
configured
.split("\n")
.filter((line) => line.trim() === "[mcp_servers.rea]"),
).toHaveLength(1);
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
rea: {
command: "rea",
args: ["mcp"],
startup_timeout_sec: 30,
env: { OLD: "1" },
},
},
});
});
});
describe("Grok Build multiline and escaped headers", () => {
it("preserves a multiline string opened on the line that closed the previous one", async () => {
const home = await createTestTempDirectory("rea-grok-multiline-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
'banner = ["""',
"first",
'""", """',
"[mcp_servers.rea]",
'command = "keep this text"',
"[privacy]",
"keep this too",
'"""]',
"",
"marker = ['''",
"first",
"''', '''",
"[mcp_servers.rea]",
'command = "keep literal"',
"''']",
"",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain('command = "keep this text"');
expect(configured).toContain("keep this too");
expect(configured).toContain('command = "keep literal"');
expect(parseToml(configured)).toMatchObject({
banner: [
"first\n",
'[mcp_servers.rea]\ncommand = "keep this text"\n[privacy]\nkeep this too\n',
],
marker: ["first\n", '[mcp_servers.rea]\ncommand = "keep literal"\n'],
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
});
it("replaces a hex-escaped Grok REA header and keeps a hex-escaped foreign name", async () => {
const home = await createTestTempDirectory("rea-grok-hex-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
'[mcp_servers."\\x72ea"]',
'command = "rea"',
'args = ["mcp"]',
"",
'[mcp_servers."rea\\x2Eenv"]',
'command = "other"',
'args = ["serve"]',
"",
].join("\n");
await writeFile(configPath, original);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain('[mcp_servers."rea\\x2Eenv"]');
expect(removed).not.toContain("\\x72");
expect(parseToml(removed)).toMatchObject({
mcp_servers: { "rea.env": { command: "other", args: ["serve"] } },
});
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).not.toContain("\\x72");
expect(configured).toContain('[mcp_servers."rea\\x2Eenv"]');
expect(
configured
.split("\n")
.filter((line) => line.trim() === "[mcp_servers.rea]"),
).toHaveLength(1);
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
"rea.env": { command: "other", args: ["serve"] },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
});
describe("Grok Build inline and dotted assignments", () => {
it("updates and removes inline and dotted Grok REA assignments", async () => {
const home = await createTestTempDirectory("rea-grok-assign-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const inline = [
"[mcp_servers]",
'rea = """',
"[mcp_servers.rea]",
'command = "keep? no, this is the old value"',
'"""',
'other = { command = "other" }',
"",
"# next table",
"[privacy]",
"share = false",
"",
].join("\n");
await writeFile(configPath, inline);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configuredInline = await readFile(configPath, "utf8");
expect(configuredInline).not.toContain("keep? no, this is the old value");
expect(configuredInline).toContain('other = { command = "other" }');
expect(configuredInline).toContain("# next table");
expect(configuredInline).toContain("[privacy]");
expect(parseToml(configuredInline)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
privacy: { share: false },
});
expect(configuredInline).not.toContain("enabled");
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removedInline = await readFile(configPath, "utf8");
expect(removedInline).toContain('other = { command = "other" }');
expect(removedInline).toContain("# next table");
expect(parseToml(removedInline)).not.toHaveProperty("mcp_servers.rea");
const dotted = [
'mcp_servers.rea = { command = "rea", args = ["mcp"] }',
'mcp_servers.other.command = "other"',
"",
"# next table",
"[privacy]",
"share = false",
"",
].join("\n");
await writeFile(configPath, dotted);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removedDotted = await readFile(configPath, "utf8");
expect(removedDotted).toContain('mcp_servers.other.command = "other"');
expect(removedDotted).toContain("# next table");
expect(parseToml(removedDotted)).toMatchObject({
mcp_servers: { other: { command: "other" } },
privacy: { share: false },
});
expect(parseToml(removedDotted)).not.toHaveProperty("mcp_servers.rea");
await writeFile(
configPath,
[
"[mcp_servers]",
'rea.command = "old"',
'rea.args = ["old"]',
'other.command = "other"',
"",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configuredDotted = await readFile(configPath, "utf8");
expect(configuredDotted).toContain('other.command = "other"');
expect(configuredDotted).not.toContain('rea.command = "old"');
expect(parseToml(configuredDotted)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
});
describe("Grok Build closed inline tables", () => {
it("splices REA into a closed mcp_servers inline table", async () => {
const home = await createTestTempDirectory("rea-grok-inline-table-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
"# preamble",
'mcp_servers = { other = { command = "other" }, "\\x72ea" = { command = "old", args = ["old"] } }',
"",
"# next table",
"[privacy]",
"share = false",
"",
].join("\n");
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("# preamble");
expect(configured).toContain("# next table");
expect(configured).toContain("[privacy]");
expect(configured).toContain("mcp_servers =");
expect(configured).not.toMatch(/^\[mcp_servers\.rea\]$/mu);
expect(configured).not.toContain("\\x72");
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
privacy: { share: false },
});
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toEqual({ status: "unchanged" });
await writeFile(
configPath,
[
'mcp_servers = { other = { command = "other" }, rea = { command = "rea", args = ["mcp"] } }',
"",
].join("\n"),
);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain('other = { command = "other" }');
expect(removed).toContain("mcp_servers =");
expect(parseToml(removed)).toMatchObject({
mcp_servers: { other: { command: "other" } },
});
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
await writeFile(configPath, "mcp_servers = { }\n");
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const created = await readFile(configPath, "utf8");
expect(created).toContain("mcp_servers =");
expect(created).not.toMatch(/^\[mcp_servers\.rea\]$/mu);
expect(parseToml(created)).toMatchObject({
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
it("keeps a quote that belongs to a multiline string closer", async () => {
const home = await createTestTempDirectory("rea-grok-quotes-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
'banner = ["""keep""""]',
"marker = ['''keep'''']",
"[mcp_servers.rea]",
'command = "rea"',
'args = ["mcp"]',
"",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(parseToml(configured)).toMatchObject({
banner: ['keep"'],
marker: ["keep'"],
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
expect(
configured
.split("\n")
.filter((line) => line.trim() === "[mcp_servers.rea]"),
).toHaveLength(1);
});
});
describe("Grok Build scalar and nested values", () => {
it("preserves a basic string that uses the escape smol-toml accepts", async () => {
const home = await createTestTempDirectory("rea-grok-escape-e-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
[
'banner = "\\e[31m"',
"[mcp_servers.rea]",
'command = "rea"',
'args = ["mcp"]',
"",
].join("\n"),
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain('banner = "\\e[31m"');
expect(parseToml(configured)).toMatchObject({
banner: "\u001b[31m",
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
it("updates a closed inline table whose last value is a scalar", async () => {
const home = await createTestTempDirectory("rea-grok-scalar-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'mcp_servers = { rea.command = "rea", rea.args = ["mcp"], rea.startup_timeout_sec = 10 }\n',
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).not.toMatch(/^\[mcp_servers\.rea\]$/mu);
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
it("removes a nested array inside a Grok REA table", async () => {
const home = await createTestTempDirectory("rea-grok-nested-array-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
"[mcp_servers.rea]",
'command = "rea"',
'args = ["mcp"]',
"extra = [",
" [1, 2],",
"]",
"",
"[privacy]",
"share = false",
"",
].join("\n");
await writeFile(configPath, original);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("[privacy]");
expect(removed).not.toContain("[1, 2]");
expect(parseToml(removed)).toMatchObject({ privacy: { share: false } });
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("[privacy]");
expect(configured).not.toContain("[1, 2]");
expect(parseToml(configured)).toMatchObject({
privacy: { share: false },
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
});
describe("Grok Build newlines and leading marks", () => {
it("edits a BOM-prefixed Grok document without losing the first table", async () => {
const home = await createTestTempDirectory("rea-grok-bom-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'\uFEFF[profile]\nmcp_servers.rea = "keep this setting"\n',
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured.startsWith("\uFEFF")).toBe(true);
expect(configured).toContain('mcp_servers.rea = "keep this setting"');
expect(parseToml(configured)).toMatchObject({
profile: { mcp_servers: { rea: "keep this setting" } },
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
await writeFile(
configPath,
'\uFEFF[mcp_servers.rea]\ncommand = "rea"\nargs = ["mcp"]\n',
);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed.startsWith("\uFEFF")).toBe(true);
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
it("keeps CRLF string values in a Grok file that also uses LF", async () => {
const home = await createTestTempDirectory("rea-grok-crlf-string-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original =
'banner = """a\r\nb"""\n[mcp_servers.rea]\ncommand = "rea"\nargs = ["mcp"]\n';
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
expect(parseToml(await readFile(configPath, "utf8"))).toMatchObject({
banner: "a\r\nb",
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
await writeFile(configPath, original);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(parseToml(removed)).toMatchObject({ banner: "a\r\nb" });
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
it("keeps LF string values in a Grok file that also uses CRLF", async () => {
const home = await createTestTempDirectory("rea-grok-mixed-newline-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'banner = """keep\nthis LF"""\n[mcp_servers.rea]\r\ncommand = "rea"\r\nargs = ["mcp"]\r\n',
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
expect(parseToml(await readFile(configPath, "utf8"))).toMatchObject({
banner: "keep\nthis LF",
mcp_servers: {
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
});
});
describe("Grok Build inline comments", () => {
it("keeps comments on dotted Grok fields without repeating a comma", async () => {
const home = await createTestTempDirectory("rea-grok-dotted-comment-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original = [
'mcp_servers = { other = {command="other"}, # keep other note',
' rea.command = "rea", # command note',
' rea.args = ["mcp"] }',
"",
].join("\n");
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("# keep other note");
expect(configured).toContain("# command note");
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
await writeFile(configPath, original);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("# keep other note");
expect(removed).toContain("# command note");
expect(parseToml(removed)).toMatchObject({
mcp_servers: { other: { command: "other" } },
});
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
it("keeps an inline comment that sits before the Grok REA pair", async () => {
const home = await createTestTempDirectory("rea-grok-inline-before-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
const original =
'mcp_servers = { other = {command="other"}, # important other-server note\n rea = {command="rea",args=["mcp"]} }\n';
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
undefined,
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("# important other-server note");
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: { command: "rea", args: ["mcp"], startup_timeout_sec: 30 },
},
});
await writeFile(configPath, original);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("# important other-server note");
expect(parseToml(removed)).toMatchObject({
mcp_servers: { other: { command: "other" } },
});
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
it("keeps inline-table comments and encodes a DEL in an inline value", async () => {
const home = await createTestTempDirectory("rea-grok-inline-comment-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'mcp_servers = { rea = {command="rea",args=["mcp"]}, # other: one, two\n other = {command="other"} }\n',
);
expect(
await configureClientConfiguration(
{ name: "grok_build", configPath, format: "grok" },
{ HOPPER_LAUNCHER_PATH: "/tmp/path\u007fwith-del" },
["rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured).toContain("# other: one, two");
expect(parseToml(configured)).toMatchObject({
mcp_servers: {
other: { command: "other" },
rea: {
command: "rea",
args: ["mcp"],
startup_timeout_sec: 30,
env: { HOPPER_LAUNCHER_PATH: "/tmp/path\u007fwith-del" },
},
},
});
});
it("keeps comments when uninstall removes the only inline Grok server", async () => {
const home = await createTestTempDirectory("rea-grok-inline-empty-");
roots.push(home);
const configPath = join(home, ".grok/config.toml");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'mcp_servers = { # policy\n rea = { command = "rea", args = ["mcp"] } # owned\n}\n',
);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("# policy");
expect(removed).toContain("# owned");
expect(parseToml(removed)).not.toHaveProperty("mcp_servers.rea");
});
});
describe("JSON client configuration comments", () => {
it("keeps a same-line JSONC comment when uninstall removes Cursor", async () => {
const home = await createTestTempDirectory("rea-cursor-jsonc-comment-");
roots.push(home);
const configPath = join(home, ".cursor/mcp.json");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'{\n "mcpServers": {\n "other": {"command":"other"}, // important other-server note\n "rea": {"command":"rea","args":["mcp"]}\n }\n}\n',
);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("// important other-server note");
const errors: ParseError[] = [];
const document = parseJsonc(removed, errors, { allowTrailingComma: true });
expect(errors).toEqual([]);
expect(document).toEqual({
mcpServers: { other: { command: "other" } },
});
});
it("keeps a JSONC comment when a block comment precedes the comma", async () => {
const home = await createTestTempDirectory("rea-cursor-jsonc-block-");
roots.push(home);
const configPath = join(home, ".cursor/mcp.json");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(
configPath,
'{"mcpServers":{\n "other":{"command":"other"}, // keep other note\n "rea":{"command":"rea","args":["mcp"]} /**/,\n "keep":{"command":"keep"}\n}}\n',
);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("// keep other note");
const errors: ParseError[] = [];
const document = parseJsonc(removed, errors, { allowTrailingComma: true });
expect(errors).toEqual([]);
expect(document).toEqual({
mcpServers: {
other: { command: "other" },
keep: { command: "keep" },
},
});
});
it("keeps a JSONC comment without swallowing the next server or closers", async () => {
const home = await createTestTempDirectory("rea-cursor-jsonc-next-");
roots.push(home);
const configPath = join(home, ".cursor/mcp.json");
await mkdir(dirname(configPath), { recursive: true });
const files = [
'{\n "mcpServers": {\n "other": {"command":"other"}, // important other-server note\n "rea": {"command":"rea","args":["mcp"]}, "keep": {"command":"keep"}\n }\n}\n',
'{\n "mcpServers": {\n "other": {"command":"other"}, // important other-server note\n "rea": {"command":"rea","args":["mcp"]}}}\n',
];
for (const original of files) {
await writeFile(configPath, original);
expect(
(await runUninstall(false, systemUninstallHost(home))).status,
).toBe("complete");
const removed = await readFile(configPath, "utf8");
expect(removed).toContain("// important other-server note");
const errors: ParseError[] = [];
const document = parseJsonc(removed, errors, {
allowTrailingComma: true,
});
expect(errors).toEqual([]);
expect(document).toMatchObject({
mcpServers: { other: { command: "other" } },
});
if (original.includes('"keep"'))
expect(document).toMatchObject({
mcpServers: { keep: { command: "keep" } },
});
}
});
});
describe("Grok Bot registration", () => {
it("does not write or align a data-directory mcp.json", async () => {
const home = await createTestTempDirectory("rea-grokbot-");
roots.push(home);
const directory = join(home, ".grokbot");
const configPath = join(directory, "mcp.json");
await mkdir(directory, { recursive: true });
const original =
'{"mcpServers":{"other":{"command":"other"},"rea":{"command":"rea","args":["mcp"]}}}\n';
await writeFile(configPath, original);
const client = supportedClients(home).find(
({ name }) => name === "grok_bot",
);
if (client === undefined) throw new Error("missing grok_bot");
expect(client.format).toBe("unsupported");
expect(client.configPath).toBe(directory);
expect(
await systemSetupHost().configureClient(client, {}, ["rea", "mcp"]),
).toEqual({ status: "skipped" });
expect(await readFile(configPath, "utf8")).toBe(original);
expect(
await readClientRegistrationStatuses(home, "/current/rea", {
environment: {},
}),
).toEqual([
expect.objectContaining({
client: "grok_bot",
config_path: directory,
state: "manual",
command: [],
}),
]);
const removed = await runUninstall(false, systemUninstallHost(home));
expect(removed.status).toBe("complete");
expect(removed.items).toContainEqual(
expect.objectContaining({ name: "grok_bot", status: "skipped" }),
);
expect(await readFile(configPath, "utf8")).toBe(original);
});
});
describe("Codex configuration comments", () => {
it("keeps Codex comments and value spelling through setup and uninstall", async () => {
const home = await createTestTempDirectory("rea-toml-comments-");
roots.push(home);
const configPath = join(home, ".codex/config.toml");
const original =
'# Keep this explanation.\nnotify = ["a", "b"]\nliteral = \'C:\\demo\\path\'\n';
await mkdir(dirname(configPath), { recursive: true });
await writeFile(configPath, original);
expect(
await configureClientConfiguration(
{ name: "codex", configPath, format: "toml" },
{},
["/isolated prefix/bin/rea", "mcp"],
),
).toMatchObject({ status: "configured" });
const configured = await readFile(configPath, "utf8");
expect(configured.startsWith(original)).toBe(true);
expect(configured).toContain("[mcp_servers.rea]");
const removed = await runUninstall(false, systemUninstallHost(home));
expect(removed.items).toContainEqual(
expect.objectContaining({ name: "codex", status: "removed" }),
);
expect(await readFile(configPath, "utf8")).toBe(original);
});
});
describe("managed data purge root validation", () => {
it.each(["existing", "dangling"] as const)(
"retains cache and state when the managed-root symlink is %s",
async (targetState) => {
const root = await createTestTempDirectory("rea-uninstall-data-root-");
roots.push(root);
const home = join(root, "home");
const outside = join(root, "unrelated-application");
await mkdir(home);
if (targetState === "existing")
for (const name of ["cache", "state"]) {
await mkdir(join(outside, name), { recursive: true });
await writeFile(join(outside, name, "keep.txt"), "unrelated data");
}
const managedRoot = join(home, ".rea");
await symlink(
outside,
managedRoot,
process.platform === "win32" ? "junction" : "dir",
);
const result = await runUninstall(
true,
systemUninstallHost(home, undefined, {}),
);
expect(result.status).toBe("complete");
expect((await lstat(managedRoot)).isSymbolicLink()).toBe(true);
for (const name of ["cache", "state"])
expect(result.items).toContainEqual(
expect.objectContaining({ name, status: "retained" }),
);
if (targetState === "existing")
for (const name of ["cache", "state"])
expect(await readFile(join(outside, name, "keep.txt"), "utf8")).toBe(
"unrelated data",
);
else
await expect(lstat(outside)).rejects.toMatchObject({ code: "ENOENT" });
},
);
it("retains a managed data root that is not a directory", async () => {
const home = await createTestTempDirectory("rea-uninstall-data-file-");
roots.push(home);
const managedRoot = join(home, ".rea");
await writeFile(managedRoot, "unrelated file");
const result = await runUninstall(
true,
systemUninstallHost(home, undefined, {}),
);
expect(result.status).toBe("complete");
for (const name of ["cache", "state"])
expect(result.items).toContainEqual(
expect.objectContaining({ name, status: "retained" }),
);
expect(await readFile(managedRoot, "utf8")).toBe("unrelated file");
});
it("purges ordinary managed data through a legitimate home directory link", async () => {
const root = await createTestTempDirectory("rea-uninstall-home-link-");
roots.push(root);
const home = join(root, "actual-home");
const linkedHome = join(root, "linked-home");
const managedRoot = join(home, ".rea");
for (const name of ["cache", "state"]) {
await mkdir(join(managedRoot, name), { recursive: true });
await writeFile(join(managedRoot, name, "data.txt"), "managed data");
}
await writeFile(join(managedRoot, "keep.txt"), "other data");
await symlink(
home,
linkedHome,
process.platform === "win32" ? "junction" : "dir",
);
const result = await runUninstall(
true,
systemUninstallHost(linkedHome, undefined, {}),
);
expect(result.status).toBe("complete");
for (const name of ["cache", "state"]) {
expect(result.items).toContainEqual(
expect.objectContaining({ name, status: "removed" }),
);
await expect(lstat(join(managedRoot, name))).rejects.toMatchObject({
code: "ENOENT",
});
}
expect((await lstat(linkedHome)).isSymbolicLink()).toBe(true);
expect(await readFile(join(managedRoot, "keep.txt"), "utf8")).toBe(
"other data",
);
});
it("reports root inspection failure without removing managed data", async () => {
const home = await createTestTempDirectory("rea-uninstall-data-denied-");
roots.push(home);
const managedRoot = join(home, ".rea");
for (const name of ["cache", "state"]) {
await mkdir(join(managedRoot, name), { recursive: true });
await writeFile(join(managedRoot, name, "keep.txt"), "managed data");
}
const remove = vi.fn(testFileSystem.remove);
const result = await runUninstall(
true,
systemUninstallHost(
home,
{
...testFileSystem,
stat: (path) =>
path === managedRoot
? Promise.reject(
Object.assign(new Error("root inspection denied"), {
code: "EACCES",
}),
)
: lstat(path),
remove,
},
{},
),
);
expect(result.status).toBe("failed");
expect(remove).not.toHaveBeenCalled();
for (const name of ["cache", "state"]) {
expect(result.items).toContainEqual(
expect.objectContaining({ name, status: "failed" }),
);
expect(await readFile(join(managedRoot, name, "keep.txt"), "utf8")).toBe(
"managed data",
);
}
});
});
describe("client configuration filesystem removal", () => {
it("uninstalls only owned entries and refuses purge symlinks", async () => {
const home = await createTestTempDirectory("rea-uninstall-");
roots.push(home);
const cursor = join(home, ".cursor/mcp.json");
const skill = join(
home,
".agents/skills/reverse-engineer-anything/SKILL.md",
);
await mkdir(dirname(cursor), { recursive: true });
await mkdir(dirname(skill), { recursive: true });
await writeFile(
cursor,
JSON.stringify({
mcpServers: {
rea: { command: "/isolated prefix/bin/rea", args: ["mcp"] },
other: { command: "other" },
},
}),
);
await writeFile(skill, ownedSkillManifest);
await mkdir(join(home, ".rea"), { recursive: true });
await import("node:fs/promises").then(({ symlink }) =>
symlink(home, join(home, ".rea/cache")),
);
const first = await runUninstall(true, systemUninstallHost(home));
expect(first.status).toBe("complete");
expect(JSON.parse(await readFile(cursor, "utf8"))).toEqual({
mcpServers: { other: { command: "other" } },
});
await expect(readFile(skill, "utf8")).rejects.toThrow();
expect(first.items).toContainEqual(
expect.objectContaining({
name: "cache",
status: "retained",
detail:
"REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.",
}),
);
expect((await runUninstall(true, systemUninstallHost(home))).status).toBe(
"complete",
);
});
it("removes an owned entry through a symlink without replacing it", async () => {
const home = await createTestTempDirectory("rea-uninstall-symlink-");
roots.push(home);
const configPath = join(home, ".cursor/mcp.json");
const targetPath = join(home, "managed-mcp.json");
const original = JSON.stringify({
mcpServers: {
rea: { command: "rea", args: ["mcp"] },
other: { command: "other" },
},
});
await mkdir(dirname(configPath), { recursive: true });
await writeFile(targetPath, original);
await symlink(targetPath, configPath);
expect((await runUninstall(false, systemUninstallHost(home))).status).toBe(
"complete",
);
expect((await lstat(configPath)).isSymbolicLink()).toBe(true);
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(JSON.parse(await readFile(targetPath, "utf8"))).toEqual({
mcpServers: { other: { command: "other" } },
});
});
it("fails before mutation when an uninstall config symlink is dangling", async () => {
const home = await createTestTempDirectory("rea-uninstall-symlink-");
roots.push(home);
const configPath = join(home, ".cursor/mcp.json");
await mkdir(dirname(configPath), { recursive: true });
await symlink(join(home, "missing-mcp.json"), configPath);
const result = await runUninstall(false, systemUninstallHost(home));
expect(result.status).toBe("failed");
expect(result.items).toContainEqual({
name: "cursor",
status: "failed",
detail:
"Configuration path could not be safely verified. Check its permissions and, if it is a symbolic link, verify that the link resolves to a regular file owned by the current user, then rerun uninstall.",
});
expect((await lstat(configPath)).isSymbolicLink()).toBe(true);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
});
});
describe("uninstall configuration preflight", () => {
it("fails closed on malformed client configuration", async () => {
const home = await createTestTempDirectory("rea-uninstall-bad-");
roots.push(home);
const config = join(home, ".cursor/mcp.json");
await mkdir(dirname(config), { recursive: true });
await writeFile(config, "not-json");
const result = await runUninstall(false, systemUninstallHost(home));
expect(result.status).toBe("failed");
expect(result.items).toContainEqual(
expect.objectContaining({
name: "cursor",
status: "failed",
detail:
"Configuration is not valid JSON and was not changed. Repair it, then rerun uninstall.",
}),
);
expect(await readFile(config, "utf8")).toBe("not-json");
});
it("stops before any removal when one client configuration is malformed", async () => {
const home = await createTestTempDirectory("rea-uninstall-stop-");
roots.push(home);
const codex = join(home, ".codex/config.toml");
const cursor = join(home, ".cursor/mcp.json");
const skillMarker = join(
home,
".agents/skills/reverse-engineer-anything/marker.txt",
);
const managed = [
skillMarker,
join(home, ".rea/cache/marker.txt"),
join(home, ".rea/state/marker.txt"),
];
const cursorConfig = JSON.stringify({
mcpServers: {
rea: { command: "npx", args: ["-y", "rea-agents@6.1.0", "mcp"] },
other: { command: "echo", args: ["hello"] },
},
});
for (const path of [codex, cursor, ...managed])
await mkdir(dirname(path), { recursive: true });
await writeFile(codex, "[mcp_servers.rea]\ncommand = [broken\n");
await writeFile(cursor, cursorConfig);
for (const path of managed) await writeFile(path, "marker");
const manifest = join(dirname(skillMarker), "SKILL.md");
await writeFile(manifest, ownedSkillManifest);
const stopped = await runUninstall(true, systemUninstallHost(home));
expect(stopped.status).toBe("failed");
expect(stopped.items.map(({ name, status }) => [name, status])).toEqual([
["codex", "failed"],
["uninstall", "skipped"],
["analysis_engine", "retained"],
]);
expect(await readFile(codex, "utf8")).toBe(
"[mcp_servers.rea]\ncommand = [broken\n",
);
expect(await readFile(cursor, "utf8")).toBe(cursorConfig);
for (const path of managed)
expect(await readFile(path, "utf8")).toBe("marker");
expect(await readFile(manifest, "utf8")).toBe(ownedSkillManifest);
await writeFile(codex, "");
const repaired = await runUninstall(true, systemUninstallHost(home));
expect(repaired.status).toBe("complete");
expect(JSON.parse(await readFile(cursor, "utf8"))).toEqual({
mcpServers: { other: { command: "echo", args: ["hello"] } },
});
// Uninstall removes only the managed bundle, never arbitrary skill files.
expect(await readFile(skillMarker, "utf8")).toBe("marker");
await expect(readFile(manifest, "utf8")).rejects.toMatchObject({
code: "ENOENT",
});
for (const path of managed.slice(1))
await expect(readFile(path, "utf8")).rejects.toThrow();
});
});
describe("client configuration changed during uninstall", () => {
it("stops the remaining removals when a configuration fails after preflight", async () => {
const home = await createTestTempDirectory("rea-uninstall-race-");
roots.push(home);
const codex = join(home, ".codex/config.toml");
const cursor = join(home, ".cursor/mcp.json");
const skill = join(
home,
".agents/skills/reverse-engineer-anything/SKILL.md",
);
const cursorConfig = JSON.stringify({
mcpServers: { rea: { command: "rea", args: ["mcp"] } },
});
for (const path of [codex, cursor, skill])
await mkdir(dirname(path), { recursive: true });
await writeFile(
codex,
'[mcp_servers.rea]\ncommand = "rea"\nargs = ["mcp"]\n',
);
await writeFile(cursor, cursorConfig);
await writeFile(skill, "managed");
let codexReads = 0;
const fileSystem: UninstallFileSystem = {
...testFileSystem,
// The preflight read succeeds; the removal reread finds a broken file.
readText: (path) => {
if (path !== codex) return readFile(path, "utf8");
codexReads += 1;
return codexReads === 1
? readFile(path, "utf8")
: Promise.resolve("[mcp_servers.rea]\ncommand = [broken\n");
},
};
const result = await runUninstall(
false,
systemUninstallHost(home, fileSystem),
);
expect(result.status).toBe("failed");
// Clients ordered before Codex have no configuration in this home.
expect(
result.items
.filter(({ status }) => status !== "skipped")
.map(({ name, status }) => [name, status]),
).toEqual([
["codex", "failed"],
["analysis_engine", "retained"],
]);
expect(result.items.map(({ name }) => name)).not.toContain("cursor");
expect(result.items.at(-2)).toMatchObject({
name: "uninstall",
status: "skipped",
});
expect(await readFile(cursor, "utf8")).toBe(cursorConfig);
expect(await readFile(skill, "utf8")).toBe("managed");
});
});
describe("client configuration filesystem failure reporting", () => {
it.each([
[
"read",
"Configuration could not be read. Check file permissions, then rerun uninstall.",
],
[
"backup",
"Configuration could not be backed up, so no change was made. Check file permissions, then rerun uninstall.",
],
[
"update",
"Configuration could not be updated. The original was restored and its `.rea.backup` was retained. Repair the configuration or restore the backup, then rerun uninstall.",
],
[
"restore",
"Configuration could not be updated or restored. Restore its `.rea.backup` manually, then rerun uninstall.",
],
] as const)(
"reports an actionable client %s failure",
async (failure, detail) => {
const { home, config } = await uninstallFixture();
let writes = 0;
const fileSystem: UninstallFileSystem = {
...testFileSystem,
readText: (path) =>
failure === "read" && path === config
? Promise.reject(new Error("SECRET read failure"))
: readFile(path, "utf8"),
copy: (source, destination) =>
failure === "backup"
? Promise.reject(new Error("SECRET backup failure"))
: copyFile(source, destination, fsConstants.COPYFILE_EXCL),
writeText: async (path, contents) => {
writes += 1;
if ((failure === "update" && writes === 1) || failure === "restore")
throw new Error("SECRET write failure");
await writeFile(path, contents);
},
};
const result = await runUninstall(
false,
systemUninstallHost(home, fileSystem),
);
expect(result.status).toBe("failed");
expect(result.items).toContainEqual(
expect.objectContaining({ name: "cursor", status: "failed", detail }),
);
expect(JSON.stringify(result)).not.toContain("SECRET");
},
);
it("restores the current configuration without replacing its initial backup", async () => {
const { home, config } = await uninstallFixture();
const current = await readFile(config, "utf8");
const initial = '{"existing":true}\n';
await writeFile(`${config}.rea.backup`, initial);
let writes = 0;
const result = await systemUninstallHost(home, {
...testFileSystem,
writeText: async (path, contents) => {
writes += 1;
await writeFile(path, contents);
if (writes === 1) throw new Error("update failed after writing");
},
}).removeClient({ name: "cursor", format: "json", configPath: config });
expect(result.status).toBe("failed");
expect(await readFile(config, "utf8")).toBe(current);
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(initial);
});
it("reports a managed-path removal failure", async () => {
const home = await createTestTempDirectory("rea-uninstall-remove-");
roots.push(home);
const skillRoot = join(home, ".agents/skills/reverse-engineer-anything");
await mkdir(skillRoot, { recursive: true });
await writeFile(join(skillRoot, "SKILL.md"), ownedSkillManifest);
const result = await runUninstall(
false,
systemUninstallHost(home, {
...testFileSystem,
remove: () => Promise.reject(new Error("SECRET removal failure")),
}),
);
expect(result.items).toContainEqual(
expect.objectContaining({
name: "skill",
status: "failed",
detail: expect.stringContaining(
`Managed file could not be removed: ${join(skillRoot, "SKILL.md")}`,
),
}),
);
expect(JSON.stringify(result)).not.toContain("SECRET");
expect(await readFile(join(skillRoot, "SKILL.md"), "utf8")).toBe(
ownedSkillManifest,
);
});
it("uninstalls all managed skill roots, including a custom Claude Code directory", async () => {
const home = await createTestTempDirectory("rea-uninstall-skills-");
roots.push(home);
const customClaudeDirectory = join(home, "claude-config");
const rootsToRemove = [
join(home, ".agents/skills/reverse-engineer-anything"),
join(customClaudeDirectory, "skills/reverse-engineer-anything"),
];
await Promise.all(
rootsToRemove.map(async (path) => {
await mkdir(path, { recursive: true });
await writeFile(join(path, "SKILL.md"), ownedSkillManifest);
await writeFile(join(path, "user-notes.txt"), "user-authored");
}),
);
const result = await systemUninstallHost(home, testFileSystem, {
CLAUDE_CONFIG_DIR: customClaudeDirectory,
}).removeSkill();
expect(result).toMatchObject({ status: "removed" });
await Promise.all(
rootsToRemove.map(async (path) => {
await expect(
readFile(join(path, "SKILL.md"), "utf8"),
).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(join(path, "user-notes.txt"), "utf8")).toBe(
"user-authored",
);
}),
);
});
});
const testFileSystem: UninstallFileSystem = {
readText: (path) => readFile(path, "utf8"),
copy: (source, destination) =>
copyFile(source, destination, fsConstants.COPYFILE_EXCL),
writeText: (path, contents) => writeFile(path, contents),
stat: (path) => lstat(path),
realpath: (path) => realpath(path),
remove: (path) => rm(path, { recursive: true }),
};
const uninstallFixture = async (): Promise<{
readonly home: string;
readonly config: string;
}> => {
const home = await createTestTempDirectory("rea-uninstall-failure-");
roots.push(home);
const config = join(home, ".cursor/mcp.json");
await mkdir(dirname(config), { recursive: true });
await writeFile(
config,
JSON.stringify({ mcpServers: { rea: { command: "rea", args: ["mcp"] } } }),
);
return { home, config };
};