fix(freebuff): detect FreeBuff from its profile directory, not shared .agents

The ~/.agents marker made FreeBuff appear installed for any home where
REA had installed shared skills (the default skill destination, also
Gemini CLI's), producing false doctor failures. Detect FreeBuff from
its own fixed profile directory ~/.config/freebuff-desktop instead,
and add a regression test covering a home with only the shared agents
skill directory.
This commit is contained in:
0xLLVEL committed 2026-10-14 11:34:39 +07:00
1 parent 4799644098
commit 783f0fe45e
3 files changed
+43 -18

No files matched your search

+3
View File
@@ -318,6 +318,9 @@ For FreeBuff, setup writes `mcpServers.rea` into the user-scope
`~/.agents/skills`, so setup installs its skill copy there. Because the same
file doubles as ZCode's user-scope fallback (see above), a `freebuff`
registration also loads in ZCode until ZCode defines its own native server.
Setup and doctor detect FreeBuff from its own profile directory
(`~/.config/freebuff-desktop`), not from the shared `.agents` folder that
REA's skill installs create for every client.
FreeBuff is main-only and is not part of npm 6.4.0; `rea setup --client
freebuff` against `@latest` reports an unsupported client until a release
+14 -8
View File
@@ -481,9 +481,14 @@ const devinDirectory = ({ home, platform, env }: ClientPathContext): string =>
? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "devin")
: join(env.XDG_CONFIG_HOME || join(home, ".config"), "devin");
/** FreeBuff loads MCP servers and skills from the shared user agents directory. */
const freebuffDirectory = ({ home }: ClientPathContext): string =>
join(home, ".agents");
/**
* FreeBuff's own profile directory (its `state.json` root, fixed under
* `.config` on every platform). The shared `~/.agents` folder is not
* FreeBuff-specific: REA's own skill installs create it, so it cannot
* prove FreeBuff is installed.
*/
const freebuffProfileDirectory = ({ home }: ClientPathContext): string =>
join(home, ".config", "freebuff-desktop");
/** Stable product metadata used to derive setup discovery and documentation. */
export const SUPPORTED_CLIENT_DEFINITIONS = [
@@ -702,11 +707,12 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
{
name: "freebuff",
displayName: "FreeBuff",
skillPath: (context: ClientPathContext) =>
join(freebuffDirectory(context), "skills"),
configPath: (context: ClientPathContext) =>
join(freebuffDirectory(context), "mcp.json"),
markerPath: freebuffDirectory,
// FreeBuff discovers personal skills in the shared user agents directory.
skillPath: ({ home }: ClientPathContext) => join(home, ".agents", "skills"),
// FreeBuff loads MCP servers from the shared user agents directory.
configPath: ({ home }: ClientPathContext) =>
join(home, ".agents", "mcp.json"),
markerPath: freebuffProfileDirectory,
format: "json",
},
{
@@ -1,11 +1,12 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { npxRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js";
import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js";
import { detectClients } from "../../../src/application/SetupHost.js";
import { skillDestinations } from "../../../src/application/SetupSkill.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { systemUninstallHost } from "../../../src/application/Uninstall.js";
@@ -29,19 +30,35 @@ const freebuffPath = (
supportedClients(home, platform, env).find(({ name }) => name === "freebuff");
describe("FreeBuff configuration paths", () => {
it("resolves the shared user agents file and marker directory", () => {
it("resolves the shared user agents file and the profile marker directory", () => {
expect(freebuffPath("/home/a")).toEqual({
name: "freebuff",
displayName: "FreeBuff",
configPath: join("/home/a", ".agents", "mcp.json"),
markerPath: join("/home/a", ".agents"),
markerPath: join("/home/a", ".config", "freebuff-desktop"),
format: "json",
});
expect(freebuffPath("/Users/a", "darwin")?.configPath).toBe(
join("/Users/a", ".agents", "mcp.json"),
expect(freebuffPath("/Users/a", "darwin")?.markerPath).toBe(
join("/Users/a", ".config", "freebuff-desktop"),
);
expect(freebuffPath("C:\\Users\\a", "win32")?.configPath).toBe(
join("C:\\Users\\a", ".agents", "mcp.json"),
expect(freebuffPath("C:\\Users\\a", "win32")?.markerPath).toBe(
join("C:\\Users\\a", ".config", "freebuff-desktop"),
);
});
it("stays undetected when only the shared agents skill directory exists", async () => {
const home = await createTestTempDirectory("rea-freebuff-shared-");
// REA's own skill installs create this directory for every client.
await mkdir(join(home, ".agents", "skills"), { recursive: true });
expect((await detectClients(home)).map(({ name }) => name)).not.toContain(
"freebuff",
);
await mkdir(join(home, ".config", "freebuff-desktop"), {
recursive: true,
});
expect((await detectClients(home)).map(({ name }) => name)).toContain(
"freebuff",
);
});
@@ -66,9 +83,8 @@ describe("FreeBuff JSON registration", () => {
const freebuffClient = async () => {
const home = await createTestTempDirectory("rea-freebuff-");
const client = freebuffPath(home, process.platform);
if (client?.markerPath === undefined)
throw new Error("missing FreeBuff client");
await mkdir(client.markerPath, { recursive: true });
if (client === undefined) throw new Error("missing FreeBuff client");
await mkdir(dirname(client.configPath), { recursive: true });
return { home, client };
};