feat(identity): rename package and CLI to REA
This commit is contained in:
parent
97541d8dad
commit
3d70812c37
35
README.md
35
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Better Binary
|
||||
# REA
|
||||
|
||||
Agent-ready Hopper Disassembler tooling: a beta.3 MCP server with 31 official proxies, 8 enhanced reverse-engineering workflows, and 3 binary-session tools.
|
||||
REA is agent-ready Hopper Disassembler tooling: one CLI with a beta.3 MCP mode, 31 official proxies, 8 enhanced reverse-engineering workflows, and 3 binary-session tools.
|
||||
|
||||
## Install from this checkout
|
||||
|
||||
|
|
@ -10,24 +10,26 @@ Requires macOS 12+ and Node.js 22+.
|
|||
npm ci
|
||||
npm run build
|
||||
npm link
|
||||
better-binary setup --yes
|
||||
rea setup --yes
|
||||
```
|
||||
|
||||
`setup` is safe to repeat. It checks the host, installs Homebrew when approved and absent, installs Hopper from the official `hopper-disassembler` cask, and finishes with structured diagnostics. It never bundles Hopper or publishes this private package.
|
||||
|
||||
The CLI and MCP adapter share the same binary-session core. Source-checkout development uses `npm link`; packaged verification uses a local tarball and does not require a published package.
|
||||
|
||||
Useful onboarding commands:
|
||||
|
||||
```bash
|
||||
better-binary --help
|
||||
better-binary --llms
|
||||
better-binary doctor --target /path/to/binary
|
||||
better-binary skills add
|
||||
better-binary mcp add
|
||||
rea --help
|
||||
rea --llms
|
||||
rea doctor --target /path/to/binary
|
||||
rea skills add
|
||||
rea mcp add
|
||||
```
|
||||
|
||||
## MCP workflow
|
||||
|
||||
Start `better-binary-mcp` with no target. In the connected agent:
|
||||
Start the MCP adapter with `rea mcp` (or `rea --mcp`) with no target. In the connected agent:
|
||||
|
||||
1. Call `open_binary` with any readable local binary or `.hop` path.
|
||||
2. Call `binary_overview`, then use decompilation, strings, symbols, and xrefs tools.
|
||||
|
|
@ -43,8 +45,9 @@ Manual target-free MCP configuration:
|
|||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"better-binary": {
|
||||
"command": "better-binary-mcp"
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@morluto/rea", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +60,7 @@ The first `open_binary` can take time while Hopper analyzes the file. Analysis c
|
|||
```bash
|
||||
npm run check
|
||||
npm pack --dry-run
|
||||
npm run verify:package
|
||||
HOPPER_TARGET_PATH=/path/to/target-a \
|
||||
HOPPER_SECOND_TARGET_PATH=/path/to/distinct-target-b \
|
||||
npm run verify:hopper
|
||||
|
|
@ -64,7 +68,14 @@ npm run verify:hopper
|
|||
|
||||
Real-Hopper verification requires Hopper and a representative target. The stdio server uses only the repository's authenticated Unix-socket bridge; Hopper's bundled MCP server is not used.
|
||||
|
||||
The provisional package, executable, MCP, skill, and config names are centralized in `src/identity.ts` so release naming can change cleanly. The package remains private and is not ready for publication.
|
||||
The package, executable, MCP, skill, and config names are centralized in `src/identity.ts`. The package remains private and is not ready for publication.
|
||||
|
||||
The package deliberately ships only the `rea` executable. A future zero-install setup will use the floating registration `npx -y @morluto/rea mcp`; local tarballs exercise the same packaged command before publication:
|
||||
|
||||
```bash
|
||||
npm pack
|
||||
npm exec --yes --package ./morluto-rea-0.1.0.tgz -- rea --help
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def _assembly(procedure):
|
|||
def _dispatch(method, params):
|
||||
global _selected_document
|
||||
if method == "health":
|
||||
return {"name": "betterBinaryMCP Hopper bridge", "version": "1.0.0"}
|
||||
return {"name": "REA Hopper bridge", "version": "1.0.0"}
|
||||
if method == "shutdown":
|
||||
return {"shutdown": True}
|
||||
if method == "list_documents":
|
||||
|
|
@ -239,7 +239,7 @@ def _serve_connection(connection):
|
|||
if set(request) != {"id", "token", "method", "params"}:
|
||||
raise ValueError("Invalid bridge request shape")
|
||||
request_id = request["id"]
|
||||
if not isinstance(request["token"], str) or not hmac.compare_digest(request["token"], BETTER_BINARY_TOKEN):
|
||||
if not isinstance(request["token"], str) or not hmac.compare_digest(request["token"], REA_TOKEN):
|
||||
raise PermissionError("Invalid bridge capability")
|
||||
result = _dispatch(request["method"], request["params"])
|
||||
should_stop = request["method"] == "shutdown"
|
||||
|
|
@ -255,19 +255,19 @@ def _serve_connection(connection):
|
|||
|
||||
|
||||
def _run():
|
||||
if os.path.exists(BETTER_BINARY_SOCKET):
|
||||
os.unlink(BETTER_BINARY_SOCKET)
|
||||
if os.path.exists(REA_SOCKET):
|
||||
os.unlink(REA_SOCKET)
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
server.bind(BETTER_BINARY_SOCKET)
|
||||
os.chmod(BETTER_BINARY_SOCKET, 0o600)
|
||||
server.bind(REA_SOCKET)
|
||||
os.chmod(REA_SOCKET, 0o600)
|
||||
server.listen(1)
|
||||
try:
|
||||
connection, _ = server.accept()
|
||||
_serve_connection(connection)
|
||||
finally:
|
||||
server.close()
|
||||
if os.path.exists(BETTER_BINARY_SOCKET):
|
||||
os.unlink(BETTER_BINARY_SOCKET)
|
||||
if os.path.exists(REA_SOCKET):
|
||||
os.unlink(REA_SOCKET)
|
||||
|
||||
|
||||
# Hopper's public objects are bound to its dedicated Python execution thread.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "better-binary-mcp",
|
||||
"name": "@morluto/rea",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "better-binary-mcp",
|
||||
"name": "@morluto/rea",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/server": "2.0.0-beta.3",
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"zod": "4.4.3"
|
||||
},
|
||||
"bin": {
|
||||
"better-binary-mcp": "dist/main.js"
|
||||
"rea": "scripts/rea.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.2",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"name": "better-binary-mcp",
|
||||
"name": "@morluto/rea",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"better-binary": "./scripts/better-binary.mjs",
|
||||
"better-binary-mcp": "./dist/main.js"
|
||||
"rea": "./scripts/rea.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
|
|
@ -13,7 +12,7 @@
|
|||
"files": [
|
||||
"dist",
|
||||
"bridge",
|
||||
"scripts/better-binary.mjs",
|
||||
"scripts/rea.mjs",
|
||||
"skills",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
|
|
@ -31,7 +30,8 @@
|
|||
"typecheck": "tsc --noEmit",
|
||||
"verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs",
|
||||
"verify:package": "npm run build && node scripts/verify-package.mjs",
|
||||
"config:print": "node scripts/print-mcp-config.mjs"
|
||||
"config:print": "node scripts/print-mcp-config.mjs",
|
||||
"prepack": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/server": "2.0.0-beta.3",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
const targetPath = process.argv[2];
|
||||
if (!targetPath) {
|
||||
|
|
@ -22,11 +23,6 @@ const flags = process.argv.slice(3);
|
|||
const kind = readFlag(flags, "--kind") ?? "executable";
|
||||
const loaderArgs = readFlag(flags, "--loader-args");
|
||||
|
||||
const serverPath = resolve(
|
||||
fileURLToPath(new URL("..", import.meta.url)),
|
||||
"dist/main.js",
|
||||
);
|
||||
|
||||
const env = {
|
||||
HOPPER_TARGET_PATH: resolve(targetPath),
|
||||
HOPPER_TARGET_KIND: kind,
|
||||
|
|
@ -35,9 +31,21 @@ if (loaderArgs) env.HOPPER_LOADER_ARGS_JSON = loaderArgs;
|
|||
|
||||
const config = {
|
||||
mcpServers: {
|
||||
betterBinaryMCP: {
|
||||
command: "node",
|
||||
args: [serverPath],
|
||||
rea: {
|
||||
command: "npx",
|
||||
args: [
|
||||
"-y",
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
resolve(
|
||||
fileURLToPath(new URL("..", import.meta.url)),
|
||||
"package.json",
|
||||
),
|
||||
"utf8",
|
||||
),
|
||||
).name,
|
||||
"mcp",
|
||||
],
|
||||
env,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|||
|
||||
const exec = promisify(execFile);
|
||||
const root = process.cwd();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "better-binary-package-"));
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-package-"));
|
||||
let tarball;
|
||||
|
||||
try {
|
||||
|
|
@ -39,7 +39,7 @@ try {
|
|||
await writeFile(swVers, "#!/bin/sh\necho 14.5\n");
|
||||
await writeFile(
|
||||
npx,
|
||||
'#!/bin/sh\nprintf "%s\\n" "$*" > "$BETTER_BINARY_NPX_LOG"\necho "│ ✓ Cursor: isolated │"\n',
|
||||
'#!/bin/sh\nprintf "%s\\n" "$*" > "$REA_NPX_LOG"\necho "│ ✓ Cursor: isolated │"\n',
|
||||
);
|
||||
await writeFile(hopper, "#!/bin/sh\nexit 0\n");
|
||||
await Promise.all([
|
||||
|
|
@ -64,15 +64,14 @@ try {
|
|||
XDG_CONFIG_HOME: join(home, ".config"),
|
||||
PATH: `${fakeBin}:${process.env.PATH ?? ""}`,
|
||||
HOPPER_LAUNCHER_PATH: hopper,
|
||||
BETTER_BINARY_NPX_LOG: npxLog,
|
||||
REA_NPX_LOG: npxLog,
|
||||
};
|
||||
await exec(
|
||||
"npm",
|
||||
["install", "--global", "--prefix", prefix, join(root, tarball)],
|
||||
{ env: environment },
|
||||
);
|
||||
const cli = join(prefix, "bin", "better-binary");
|
||||
const mcp = join(prefix, "bin", "better-binary-mcp");
|
||||
const cli = join(prefix, "bin", "rea");
|
||||
const help = await run(cli, ["--help"], environment);
|
||||
const llms = await run(cli, ["--llms"], environment);
|
||||
const doctor = json(await run(cli, ["doctor", "--json"], environment));
|
||||
|
|
@ -84,10 +83,8 @@ try {
|
|||
throw new Error("packaged CLI discovery or doctor failed");
|
||||
await run(cli, ["mcp", "add"], environment);
|
||||
const mcpRegistration = await readFile(npxLog, "utf8");
|
||||
if (
|
||||
!mcpRegistration.includes("add-mcp better-binary-mcp --name better-binary")
|
||||
)
|
||||
throw new Error("Incur mcp add did not register the beta.3 executable");
|
||||
if (!mcpRegistration.includes("add-mcp npx -y @morluto/rea mcp --name rea"))
|
||||
throw new Error("Incur mcp add did not register the floating npx command");
|
||||
const first = json(await run(cli, ["setup", "--yes", "--json"], environment));
|
||||
const second = json(
|
||||
await run(cli, ["setup", "--yes", "--json"], environment),
|
||||
|
|
@ -102,18 +99,18 @@ try {
|
|||
const config = json(await readFile(configPath, "utf8"));
|
||||
if (
|
||||
config.existing !== true ||
|
||||
config.mcpServers?.["better-binary"]?.command !== "better-binary-mcp"
|
||||
config.mcpServers?.rea?.command !== "npx" ||
|
||||
JSON.stringify(config.mcpServers?.rea?.args) !==
|
||||
JSON.stringify(["-y", "@morluto/rea", "mcp"])
|
||||
)
|
||||
throw new Error("packaged client readback failed");
|
||||
if (
|
||||
!(await readFile(`${configPath}.better-binary.backup`, "utf8")).includes(
|
||||
"existing",
|
||||
)
|
||||
!(await readFile(`${configPath}.rea.backup`, "utf8")).includes("existing")
|
||||
)
|
||||
throw new Error("packaged client backup failed");
|
||||
}
|
||||
const skill = await readFile(
|
||||
join(home, ".agents/skills/better-binary-analysis/SKILL.md"),
|
||||
join(home, ".agents/skills/rea-analysis/SKILL.md"),
|
||||
"utf8",
|
||||
);
|
||||
if (!skill.includes("open_binary"))
|
||||
|
|
@ -136,7 +133,8 @@ try {
|
|||
throw new Error("packaged setup did not recover");
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: mcp,
|
||||
command: cli,
|
||||
args: ["mcp"],
|
||||
env: environment,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
|
@ -163,7 +161,7 @@ try {
|
|||
}
|
||||
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({ cli: true, incurMcpCommand: "better-binary-mcp", doctor: true, setup: "idempotent", clients: 2, backupReadback: true, failureRecovery: true, skill: true, mcpTools: 42, targetFree: true })}\n`,
|
||||
`${JSON.stringify({ cli: true, incurMcpCommand: "npx -y @morluto/rea mcp", doctor: true, setup: "idempotent", clients: 2, backupReadback: true, failureRecovery: true, skill: true, mcpTools: 42, targetFree: true })}\n`,
|
||||
);
|
||||
} finally {
|
||||
if (tarball) await rm(join(root, tarball), { force: true });
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: better-binary-analysis
|
||||
description: Analyze local binaries with Better Binary and Hopper through its MCP session workflow.
|
||||
name: rea-analysis
|
||||
description: Analyze local binaries with REA and Hopper through its MCP session workflow.
|
||||
---
|
||||
|
||||
# Better Binary Analysis
|
||||
# REA Analysis
|
||||
|
||||
Call `open_binary` with a readable local path before analysis. Begin with `binary_overview`, use strings and symbol searches to identify relevant procedures, then use `procedure_pseudo_code`, callers, callees, and xrefs to build evidence. Call `open_binary` again to switch targets and `close_binary` when finished. Never claim real-Hopper behavior from mock-only evidence.
|
||||
|
|
@ -115,8 +115,7 @@ export const runSetup = async (
|
|||
...(doctor.healthy
|
||||
? {}
|
||||
: {
|
||||
remediation:
|
||||
"Run better-binary doctor and apply each reported remediation.",
|
||||
remediation: "Run rea doctor and apply each reported remediation.",
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
@ -186,7 +185,10 @@ export const configureJsonClient = async (
|
|||
if (!isMissing(cause)) return { status: "failed", reason: "readback" };
|
||||
}
|
||||
const servers = objectValue(document.mcpServers);
|
||||
const desired = { command: PRODUCT_IDENTITY.mcpBinary };
|
||||
const desired = {
|
||||
command: "npx",
|
||||
args: ["-y", PRODUCT_IDENTITY.packageName, "mcp"],
|
||||
};
|
||||
if (
|
||||
JSON.stringify(servers[PRODUCT_IDENTITY.mcpServerKey]) ===
|
||||
JSON.stringify(desired)
|
||||
|
|
@ -194,7 +196,7 @@ export const configureJsonClient = async (
|
|||
return { status: "unchanged" };
|
||||
let backupPath: string | undefined;
|
||||
if (original !== undefined) {
|
||||
backupPath = `${client.configPath}.better-binary.backup`;
|
||||
backupPath = `${client.configPath}.rea.backup`;
|
||||
try {
|
||||
await copyFile(client.configPath, backupPath);
|
||||
} catch {
|
||||
|
|
@ -206,7 +208,7 @@ export const configureJsonClient = async (
|
|||
[PRODUCT_IDENTITY.mcpServerKey]: desired,
|
||||
};
|
||||
const encoded = `${JSON.stringify(document, null, 2)}\n`;
|
||||
const temporary = `${client.configPath}.better-binary.tmp`;
|
||||
const temporary = `${client.configPath}.rea.tmp`;
|
||||
try {
|
||||
await mkdir(dirname(client.configPath), { recursive: true });
|
||||
await writeFile(temporary, encoded, { encoding: "utf8", mode: 0o600 });
|
||||
|
|
@ -243,7 +245,7 @@ const installCanonicalSkill = async (
|
|||
PRODUCT_IDENTITY.skillName,
|
||||
"SKILL.md",
|
||||
);
|
||||
const content = `---\nname: ${PRODUCT_IDENTITY.skillName}\ndescription: Analyze binaries with Better Binary and Hopper.\n---\n\nOpen a target with open_binary, begin with binary_overview, and close it when finished.\n`;
|
||||
const content = `---\nname: ${PRODUCT_IDENTITY.skillName}\ndescription: Analyze binaries with REA and Hopper.\n---\n\nOpen a target with open_binary, begin with binary_overview, and close it when finished.\n`;
|
||||
try {
|
||||
if (
|
||||
(await readFile(destination, "utf8").catch(() => undefined)) === content
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export const OFFICIAL_TOOL_CONTRACTS = [
|
|||
),
|
||||
] as const satisfies readonly ToolContract[];
|
||||
|
||||
/** All 8 reverse-engineering tools implemented by betterBinaryMCP. */
|
||||
/** All 8 reverse-engineering tools implemented by REA. */
|
||||
export const ENHANCED_TOOL_CONTRACTS = [
|
||||
enhanced(
|
||||
"swift_classes",
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ export class HopperApplicationLauncher implements BridgeLauncher {
|
|||
): Promise<Result<BridgeLaunch, HopperStartError>> {
|
||||
const bootstrapPath = `${session.directory}/bootstrap.py`;
|
||||
const source = [
|
||||
`BETTER_BINARY_SOCKET = ${JSON.stringify(session.socketPath)}`,
|
||||
`BETTER_BINARY_TOKEN = ${JSON.stringify(session.token)}`,
|
||||
`REA_SOCKET = ${JSON.stringify(session.socketPath)}`,
|
||||
`REA_TOKEN = ${JSON.stringify(session.token)}`,
|
||||
`exec(compile(open(${JSON.stringify(this.options.bridgeScriptPath)}, 'rb').read(), ${JSON.stringify(this.options.bridgeScriptPath)}, 'exec'))`,
|
||||
"",
|
||||
].join("\n");
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ const parseServerInfo = (
|
|||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
value.name === "betterBinaryMCP Hopper bridge" &&
|
||||
value.name === "REA Hopper bridge" &&
|
||||
typeof value.version === "string"
|
||||
) {
|
||||
return ok({ name: value.name, version: value.version });
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/** Provisional product identifiers kept together for release-time renaming. */
|
||||
/** Public REA identifiers kept together for consistent packaging and setup. */
|
||||
export const PRODUCT_IDENTITY = {
|
||||
displayName: "Better Binary",
|
||||
packageName: "better-binary-mcp",
|
||||
cliBinary: "better-binary",
|
||||
mcpBinary: "better-binary-mcp",
|
||||
mcpServerKey: "better-binary",
|
||||
skillName: "better-binary-analysis",
|
||||
configFileName: "better-binary.json",
|
||||
displayName: "REA",
|
||||
packageName: "@morluto/rea",
|
||||
cliBinary: "rea",
|
||||
mcpCommand: "npx -y @morluto/rea mcp",
|
||||
mcpServerKey: "rea",
|
||||
skillName: "rea-analysis",
|
||||
configFileName: "rea.json",
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const server = createServer((socket) => {
|
|||
send(
|
||||
{
|
||||
id: request.id,
|
||||
result: { name: "betterBinaryMCP Hopper bridge", version: "1.0.0" },
|
||||
result: { name: "REA Hopper bridge", version: "1.0.0" },
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ const startClient = async () => {
|
|||
clients.push(client);
|
||||
await expect(client.start()).resolves.toEqual({
|
||||
ok: true,
|
||||
value: { name: "betterBinaryMCP Hopper bridge", version: "1.0.0" },
|
||||
value: { name: "REA Hopper bridge", version: "1.0.0" },
|
||||
});
|
||||
return client;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { readFile } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { PRODUCT_IDENTITY } from "../src/identity.js";
|
||||
|
||||
describe("package identity", () => {
|
||||
it("keeps the manifest, executable, and floating MCP command aligned", async () => {
|
||||
const packageJson = z
|
||||
.object({ name: z.string(), bin: z.record(z.string(), z.string()) })
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
fileURLToPath(new URL("../package.json", import.meta.url)),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(packageJson.name).toBe(PRODUCT_IDENTITY.packageName);
|
||||
expect(Object.keys(packageJson.bin)).toEqual([PRODUCT_IDENTITY.cliBinary]);
|
||||
expect(PRODUCT_IDENTITY.mcpCommand).toBe(`npx -y ${packageJson.name} mcp`);
|
||||
});
|
||||
});
|
||||
|
|
@ -14,42 +14,43 @@ afterEach(async () => {
|
|||
|
||||
describe("JSON client configuration transaction", () => {
|
||||
it("preserves existing keys, creates a backup, and reads back the MCP entry", async () => {
|
||||
directory = await mkdtemp(join(tmpdir(), "bb-setup-"));
|
||||
directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
|
||||
const configPath = join(directory, "mcp.json");
|
||||
const original =
|
||||
'{"theme":"dark","mcpServers":{"other":{"command":"other"}}}\n';
|
||||
await writeFile(configPath, original);
|
||||
const result = await configureJsonClient({ name: "cursor", configPath });
|
||||
expect(result.status).toBe("configured");
|
||||
expect(await readFile(`${configPath}.better-binary.backup`, "utf8")).toBe(
|
||||
original,
|
||||
);
|
||||
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
|
||||
expect(JSON.parse(await readFile(configPath, "utf8"))).toMatchObject({
|
||||
theme: "dark",
|
||||
mcpServers: {
|
||||
other: { command: "other" },
|
||||
"better-binary": { command: "better-binary-mcp" },
|
||||
rea: {
|
||||
command: "npx",
|
||||
args: ["-y", "@morluto/rea", "mcp"],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("performs no write or second backup when configuration already matches", async () => {
|
||||
directory = await mkdtemp(join(tmpdir(), "bb-setup-"));
|
||||
directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
|
||||
const configPath = join(directory, "mcp.json");
|
||||
await writeFile(
|
||||
configPath,
|
||||
'{"mcpServers":{"better-binary":{"command":"better-binary-mcp"}}}\n',
|
||||
'{"mcpServers":{"rea":{"command":"npx","args":["-y","@morluto/rea","mcp"]}}}\n',
|
||||
);
|
||||
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
|
||||
status: "unchanged",
|
||||
});
|
||||
await expect(
|
||||
readFile(`${configPath}.better-binary.backup`, "utf8"),
|
||||
readFile(`${configPath}.rea.backup`, "utf8"),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("refuses malformed existing JSON without overwriting it", async () => {
|
||||
directory = await mkdtemp(join(tmpdir(), "bb-setup-"));
|
||||
directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
|
||||
const configPath = join(directory, "mcp.json");
|
||||
await writeFile(configPath, "not-json");
|
||||
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"noUncheckedIndexedAccess": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2023",
|
||||
"types": ["node"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue