Files
rea/scripts/verify-package-install.mjs
T
zayd / coldandmorluto f970b886aa feat: add Hermes as a supported MCP client (#1403)
* feat: add Hermes as a supported MCP client

Hermes Agent reads stdio MCP servers from `mcp_servers.<name>` in
`config.yaml`. REA had no Hermes client, so setup could not register it and
Hermes users had to wire the server up by hand.

- SupportedClients: add the `hermes` format and client definition, resolving
  the config path from HERMES_HOME, else the platform default that Hermes's own
  resolver uses (LOCALAPPDATA/hermes on Windows, ~/.hermes elsewhere), including
  HERMES_DATA_DIR_SUFFIX.
- ClientConfigurationDocument: parse and serialize Hermes YAML, upserting the
  rea entry into the parsed document so user comments and unrelated settings are
  preserved; emit `enabled: true` since Hermes connects entries unless disabled.
- ClientRegistrationStatus: forward the Hermes location variables so status
  checks resolve the same path as setup.
- Tests: cover path resolution on both platforms and the register/read/uninstall
  round trip, and clear the Hermes location variables between tests.

- delilah 🌸

* docs: document the Hermes client and its config path

- delilah 🌸

* refactor(setup): resolve skill destinations per client

* fix(setup): install Hermes skills in the active profile

* fix(setup): identify each client skill action uniquely

---------

Co-authored-by: morluto <[email protected]>
2026-10-10 16:03:25 +08:00

118 lines
3.5 KiB
JavaScript

import { rm } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { exec, json, run } from "./lib/verify-package-core.mjs";
/** Install the packed CLI globally with and without optional dependencies and verify PTY degradation. */
export async function verifyPackageInstall({
tarball,
prefix,
workspace,
environment,
}) {
await exec(
"npm",
[
"install",
"--global",
"--ignore-scripts",
"--omit=dev",
"--install-strategy=nested",
"--prefix",
prefix,
tarball,
],
{ env: environment },
);
const cli = join(prefix, "bin", "rea");
const packageRunnerCli = join(prefix, "bin", "rea-agents");
if (!(await run(packageRunnerCli, ["--help"], environment)).includes("setup"))
throw new Error("packaged rea-agents alias did not expose setup");
const processCaptureCapabilityUrl = pathToFileURL(
join(
prefix,
"lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js",
),
).href;
const processCaptureCapability = json(
(
await exec(
process.execPath,
[
"--input-type=module",
"--eval",
`const { probeProcessCaptureCapability } = await import(${JSON.stringify(processCaptureCapabilityUrl)}); process.stdout.write(JSON.stringify(await probeProcessCaptureCapability()));`,
],
{ env: environment },
)
).stdout,
);
if (processCaptureCapability.available !== true)
throw new Error(
`packaged PTY backend failed without lifecycle scripts: ${JSON.stringify(processCaptureCapability)}`,
);
const noOptionalPrefix = join(workspace, "prefix-no-optional");
await exec(
"npm",
[
"install",
"--global",
"--ignore-scripts",
"--omit=optional",
"--prefix",
noOptionalPrefix,
tarball,
],
{ env: environment },
);
const noOptionalCli = join(noOptionalPrefix, "bin", "rea");
await rm(
join(
noOptionalPrefix,
"lib/node_modules/rea-agents/node_modules/@lydell",
`node-pty-${process.platform}-${process.arch}`,
),
{ recursive: true, force: true },
);
const noOptionalCapabilityUrl = pathToFileURL(
join(
noOptionalPrefix,
"lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js",
),
).href;
const noOptionalCapability = json(
(
await exec(
process.execPath,
[
"--input-type=module",
"--eval",
`const { probeProcessCaptureCapability } = await import(${JSON.stringify(noOptionalCapabilityUrl)}); process.stdout.write(JSON.stringify(await probeProcessCaptureCapability()));`,
],
{ env: environment },
)
).stdout,
);
if (
!(await run(noOptionalCli, ["--help"], environment)).includes("setup") ||
noOptionalCapability.available !== false
)
throw new Error(
`packaged CLI did not degrade without the optional PTY binary: ${JSON.stringify(noOptionalCapability)}`,
);
await verifyInstalledPackageCanRepack(prefix, environment);
return { cli, packageRunnerCli };
}
const verifyInstalledPackageCanRepack = async (prefix, environment) => {
const installedPackageRoot = join(prefix, "lib/node_modules/rea-agents");
const repackedTarball = (
await exec("npm", ["pack", "--silent"], {
cwd: installedPackageRoot,
env: environment,
})
).stdout.trim();
await rm(join(installedPackageRoot, repackedTarball), { force: true });
};