mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 01:45:32 +00:00
* 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 <76467478+morluto@users.noreply.github.com>
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { parse } from "yaml";
|
|
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"Hermes setup, doctor and uninstall use the expanded profile and its personal skill",
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-cli-");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
const skill = join(profile, "skills/reverse-engineer-anything/SKILL.md");
|
|
const original =
|
|
"# user settings\nmodel: custom\nmcp_servers:\n other:\n command: other-server\n";
|
|
await mkdir(profile);
|
|
await writeFile(config, original);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({
|
|
arguments: arguments_,
|
|
cwd: home,
|
|
environment: {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
HERMES_HOME: " ${HOME}/profile ",
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
const planned = await run([
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--dry-run",
|
|
"--json",
|
|
]);
|
|
expect(planned.exitCode).toBe(0);
|
|
expect(planned.json).toMatchObject({
|
|
status: "planned",
|
|
plannedActions: expect.arrayContaining([
|
|
expect.objectContaining({ target: config }),
|
|
expect.objectContaining({
|
|
target: join(profile, "skills/reverse-engineer-anything"),
|
|
}),
|
|
]),
|
|
});
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
await expect(access(skill)).rejects.toMatchObject({ code: "ENOENT" });
|
|
const applied = await run([
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--yes",
|
|
"--json",
|
|
]);
|
|
expect(applied.exitCode).toBe(0);
|
|
expect(applied.json).toMatchObject({ status: "ready" });
|
|
expect(parse(await readFile(config, "utf8"))).toMatchObject({
|
|
model: "custom",
|
|
mcp_servers: {
|
|
other: { command: "other-server" },
|
|
rea: { enabled: true },
|
|
},
|
|
});
|
|
expect(await readFile(config + ".rea.backup", "utf8")).toBe(original);
|
|
expect(await readFile(skill, "utf8")).toContain(
|
|
"reverse-engineer-anything",
|
|
);
|
|
await expect(
|
|
access(join(home, ".agents/skills/reverse-engineer-anything/SKILL.md")),
|
|
).rejects.toMatchObject({ code: "ENOENT" });
|
|
const doctor = await run([
|
|
"doctor",
|
|
"--client",
|
|
"hermes",
|
|
"--skill",
|
|
"--json",
|
|
]);
|
|
expect(doctor.exitCode).toBe(0);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: true,
|
|
identity: { skill: { state: "aligned" } },
|
|
});
|
|
const repeat = await run([
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--yes",
|
|
"--json",
|
|
]);
|
|
expect(repeat.json).toMatchObject({ status: "ready", appliedActions: [] });
|
|
const uninstall = await run(["uninstall", "--json"]);
|
|
expect(uninstall.exitCode).toBe(0);
|
|
expect(parse(await readFile(config, "utf8"))).toEqual(parse(original));
|
|
await expect(access(skill)).rejects.toMatchObject({ code: "ENOENT" });
|
|
},
|
|
);
|