* feat(clients): register Grok Build and Grok Bot during setup Detect both clients and install the local REA MCP registration the same way setup already handles the other agents. Grok Build edits only the REA tables in its TOML config and sets startup_timeout_sec to 30. Grok Bot writes the mcpServers.rea entry into mcp.json. That file is the registration to add to the signed-in account; setup does not call the account API. Filed by Grok. * fix(clients): clear a disabled Grok Build registration during setup Grok Build keeps a server suppressed when its name stays in disabled_mcp_servers, and setup was treating that file as already aligned. Setup now removes only that name, doctor reports the registration stale until it is gone, and uninstall keeps comments in an inline table whose only server was REA. Split the lifecycle suite and reflow the get-started page so static checks pass. * fix(verify): check packed Hopper search as literal-only The package verifier still called a regex probe that main deleted. The native bridge refuses regex and joins string hits with typed records, so the restored probe stubs that inventory and the check asserts the literal matches and the regex refusal. The new README translations also name Grok Build and Grok Bot. * fix(clients): stop treating a Grok Bot data directory as registered Grok Bot stores connectors in the signed-in account and runs them on its hosted computer. Setup no longer writes mcp.json or reports that file as aligned. Doctor and setup tell the caller to add the server from the Grok Bot chat. * fix(clients): drop multiline Grok disable-list entries Decode disabled_mcp_servers strings with the document parser so a multiline basic or literal string that loads as rea is removed. --------- Co-authored-by: Grok <eugene.ray@gmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
250 lines
7.2 KiB
JavaScript
250 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFile } from "node:child_process";
|
|
import {
|
|
access,
|
|
mkdir,
|
|
mkdtemp,
|
|
readFile,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
import { supportedClients } from "../dist/application/SupportedClients.js";
|
|
import { promisify } from "node:util";
|
|
|
|
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
|
|
import { verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs";
|
|
import { verifyPackageUpdate } from "./verify-package-update.mjs";
|
|
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
|
|
|
const exec = promisify(execFile);
|
|
const verifierRun = createVerifierRun();
|
|
const root = process.cwd();
|
|
const workspace = await mkdtemp(join(tmpdir(), "rea-windows-package-"));
|
|
const prefix = join(workspace, "prefix");
|
|
let report;
|
|
|
|
try {
|
|
const packed = JSON.parse(
|
|
(
|
|
await npm(
|
|
["pack", "--json", "--silent", "--pack-destination", workspace],
|
|
root,
|
|
)
|
|
).stdout,
|
|
);
|
|
const packageResult = packed[0];
|
|
if (
|
|
packageResult === undefined ||
|
|
typeof packageResult.filename !== "string" ||
|
|
!Array.isArray(packageResult.files)
|
|
)
|
|
throw new Error(
|
|
`npm pack returned invalid metadata: ${JSON.stringify(packed)}`,
|
|
);
|
|
const packagedPaths = new Set(packageResult.files.map(({ path }) => path));
|
|
for (const required of [
|
|
"bridge/ghidra/ReaGhidraBridge.java",
|
|
"native/windows/build/manifest.json",
|
|
"native/windows/build/rea-windows-x64.node",
|
|
"dist/main.js",
|
|
"dist/cli.js",
|
|
"scripts/rea.mjs",
|
|
])
|
|
if (!packagedPaths.has(required))
|
|
throw new Error(`Windows package omitted ${required}`);
|
|
|
|
const tarball = join(workspace, packageResult.filename);
|
|
await access(tarball);
|
|
await npm(
|
|
["install", "--no-package-lock", "--no-save", "--prefix", prefix, tarball],
|
|
workspace,
|
|
);
|
|
const entry = join(
|
|
prefix,
|
|
"node_modules",
|
|
"rea-agents",
|
|
"scripts",
|
|
"rea.mjs",
|
|
);
|
|
const environment = {
|
|
...process.env,
|
|
REA_ANALYSIS_PROVIDER: "auto",
|
|
};
|
|
const help = await exec(process.execPath, [entry, "--help"], {
|
|
env: environment,
|
|
windowsHide: true,
|
|
});
|
|
if (
|
|
!/^\s{2}inspect\s/mu.test(help.stdout) ||
|
|
!/^\s{2}decompile\s/mu.test(help.stdout) ||
|
|
!/^\s{2}providers\s/mu.test(help.stdout)
|
|
)
|
|
throw new Error("Packaged Windows CLI help omitted analysis commands");
|
|
|
|
const server = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [entry, "mcp"],
|
|
env: environment,
|
|
stderr: "pipe",
|
|
});
|
|
const client = new Client({ name: "rea-windows-package", version: "1.0.0" });
|
|
let toolCount = 0;
|
|
try {
|
|
await client.connect(server);
|
|
toolCount = (await verifyCompleteToolCatalog(client)).length;
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
|
|
const home = join(workspace, "home");
|
|
const setupEnvironment = {
|
|
...environment,
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
APPDATA: join(home, "AppData", "Roaming"),
|
|
CLAUDE_CONFIG_DIR: home,
|
|
CODEX_HOME: join(home, ".codex"),
|
|
XDG_CONFIG_HOME: join(home, ".config"),
|
|
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.jsonc"),
|
|
COPILOT_HOME: join(home, ".copilot"),
|
|
GROK_HOME: join(home, ".grok"),
|
|
SAND_DATA_ROOT: join(home, ".grokbot"),
|
|
};
|
|
await mkdir(join(home, ".config", "opencode"), { recursive: true });
|
|
await writeFile(
|
|
setupEnvironment.OPENCODE_CONFIG,
|
|
'{\n // preserve user settings\n "theme": "dark",\n}\n',
|
|
);
|
|
const setupArgs = [
|
|
"setup",
|
|
"--yes",
|
|
"--client",
|
|
"opencode",
|
|
"--client",
|
|
"vscode",
|
|
"--client",
|
|
"copilot_cli",
|
|
"--skill=false",
|
|
"--json",
|
|
];
|
|
const setup = JSON.parse(
|
|
(
|
|
await exec(process.execPath, [entry, ...setupArgs], {
|
|
env: setupEnvironment,
|
|
windowsHide: true,
|
|
})
|
|
).stdout,
|
|
);
|
|
if (setup.status !== "ready" || setup.appliedActions.length !== 3)
|
|
throw new Error(
|
|
`Packaged agent-only setup failed: ${JSON.stringify(setup)}`,
|
|
);
|
|
const expectedCommand =
|
|
process.platform === "win32"
|
|
? [process.execPath, entry, "mcp"]
|
|
: [entry, "mcp"];
|
|
const clients = supportedClients(home, process.platform, setupEnvironment);
|
|
for (const id of ["opencode", "vscode", "copilot_cli"]) {
|
|
const clientLocation = clients.find(({ name }) => name === id);
|
|
if (clientLocation === undefined)
|
|
throw new Error(`Missing packaged client ${id}`);
|
|
const text = await readFile(clientLocation.configPath, "utf8");
|
|
const config = parseJsonc(text);
|
|
const registration =
|
|
id === "opencode"
|
|
? config.mcp?.rea
|
|
: id === "vscode"
|
|
? config.servers?.rea
|
|
: config.mcpServers?.rea;
|
|
const actualCommand =
|
|
id === "opencode"
|
|
? registration?.command
|
|
: [registration?.command, ...(registration?.args ?? [])];
|
|
if (JSON.stringify(actualCommand) !== JSON.stringify(expectedCommand))
|
|
throw new Error(
|
|
`Packaged ${id} has an unusable launcher: ${JSON.stringify(actualCommand)}`,
|
|
);
|
|
if (id === "opencode" && !text.includes("// preserve user settings"))
|
|
throw new Error("Packaged setup discarded OpenCode JSONC comments");
|
|
}
|
|
const repeated = JSON.parse(
|
|
(
|
|
await exec(process.execPath, [entry, ...setupArgs], {
|
|
env: setupEnvironment,
|
|
windowsHide: true,
|
|
})
|
|
).stdout,
|
|
);
|
|
if (
|
|
repeated.status !== "ready" ||
|
|
repeated.plannedActions.length !== 0 ||
|
|
repeated.appliedActions.length !== 0
|
|
)
|
|
throw new Error("Packaged agent setup is not idempotent");
|
|
const uninstalled = JSON.parse(
|
|
(
|
|
await exec(process.execPath, [entry, "uninstall", "--json"], {
|
|
env: setupEnvironment,
|
|
windowsHide: true,
|
|
})
|
|
).stdout,
|
|
);
|
|
for (const id of ["opencode", "vscode", "copilot_cli"])
|
|
if (
|
|
uninstalled.items?.find(({ name }) => name === id)?.status !== "removed"
|
|
)
|
|
throw new Error(`Packaged uninstall did not remove ${id}`);
|
|
|
|
if (process.platform !== "win32")
|
|
await npm(
|
|
["install", "--global", "--ignore-scripts", "--prefix", prefix, tarball],
|
|
workspace,
|
|
);
|
|
const update = await verifyPackageUpdate({
|
|
prefix,
|
|
packageRoot:
|
|
process.platform === "win32"
|
|
? join(prefix, "node_modules", "rea-agents")
|
|
: join(prefix, "lib", "node_modules", "rea-agents"),
|
|
tarball,
|
|
workspace,
|
|
environment,
|
|
});
|
|
|
|
report = {
|
|
ok: true,
|
|
platform: process.platform,
|
|
package: packageResult.filename,
|
|
tools: toolCount,
|
|
ghidra_bridge: "present",
|
|
agent_setup: ["opencode", "vscode", "copilot_cli"],
|
|
setup_idempotent: true,
|
|
uninstall: true,
|
|
update,
|
|
};
|
|
} finally {
|
|
await rm(workspace, { recursive: true, force: true });
|
|
}
|
|
process.stdout.write(
|
|
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), ...report })}\n`,
|
|
);
|
|
|
|
function npm(arguments_, cwd) {
|
|
const npmExecPath = process.env.npm_execpath;
|
|
return npmExecPath === undefined
|
|
? exec(process.platform === "win32" ? "npm.cmd" : "npm", arguments_, {
|
|
cwd,
|
|
windowsHide: true,
|
|
})
|
|
: exec(process.execPath, [npmExecPath, ...arguments_], {
|
|
cwd,
|
|
windowsHide: true,
|
|
});
|
|
}
|