mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 01:45:32 +00:00
152 lines
5.8 KiB
TypeScript
152 lines
5.8 KiB
TypeScript
import { access } from "node:fs/promises";
|
|
import { homedir } from "node:os";
|
|
import { resolve } from "node:path";
|
|
|
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
import { supportsNodeVersion } from "../domain/runtimeVersion.js";
|
|
import { runDoctor, systemDoctorHost, type DoctorHost } from "./Doctor.js";
|
|
import { installLinuxHopper, readLinuxDistribution } from "./LinuxHopper.js";
|
|
import { installMacHopper } from "./MacHopper.js";
|
|
import { supportedClients, type SetupClient } from "./SupportedClients.js";
|
|
import {
|
|
canonicalSkillNeedsInstall,
|
|
installCanonicalSkill,
|
|
} from "./SetupSkill.js";
|
|
import {
|
|
clientConfigurationAligned,
|
|
configureClientConfiguration,
|
|
inspectClientConfiguration,
|
|
} from "./SetupClientConfiguration.js";
|
|
import { setupInstallFailure } from "./SetupInstallFailure.js";
|
|
import { providerRegistrationEnvironment } from "./SetupRegistrationEnvironment.js";
|
|
import type { SetupHost, SetupProviderEnvironment } from "./SetupTypes.js";
|
|
|
|
/** Resolve the executable and arguments used in a managed MCP registration. */
|
|
export const setupRegistrationCommand = (
|
|
platform: NodeJS.Platform,
|
|
useNpmRunner: boolean = process.env.npm_command === "exec",
|
|
): readonly string[] =>
|
|
useNpmRunner
|
|
? PRODUCT_IDENTITY.mcpCommand.split(" ")
|
|
: platform === "win32"
|
|
? [
|
|
process.execPath,
|
|
resolve(process.argv[1] ?? PRODUCT_IDENTITY.cliBinary),
|
|
"mcp",
|
|
]
|
|
: [resolve(process.argv[1] ?? PRODUCT_IDENTITY.cliBinary), "mcp"];
|
|
|
|
export const filterClientsNeedingConfigure = async (
|
|
host: SetupHost,
|
|
detectedClients: readonly SetupClient[],
|
|
providerEnvironment: SetupProviderEnvironment,
|
|
command: readonly string[],
|
|
): Promise<readonly SetupClient[]> => {
|
|
const needs = await Promise.all(
|
|
detectedClients.map((client) =>
|
|
host.clientNeedsConfigure(client, providerEnvironment, command),
|
|
),
|
|
);
|
|
return detectedClients.filter((_, index) => needs[index]);
|
|
};
|
|
|
|
export const initialProviderEnvironment = async (
|
|
host: SetupHost,
|
|
hopperPath: string | undefined,
|
|
): Promise<SetupProviderEnvironment> => ({
|
|
...(await host.providerEnvironment?.()),
|
|
...(hopperPath === undefined ? {} : { HOPPER_LAUNCHER_PATH: hopperPath }),
|
|
});
|
|
|
|
export const hostRemediation = async (
|
|
host: SetupHost,
|
|
installHopper: boolean,
|
|
): Promise<string | undefined> => {
|
|
if (!supportsNodeVersion(host.nodeVersion))
|
|
return "Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ and rerun setup.";
|
|
if (!installHopper) return undefined;
|
|
if (host.platform !== "darwin" && host.platform !== "linux")
|
|
return "REA supports Hopper on macOS and selected 64-bit Linux distributions.";
|
|
if (host.platform === "darwin") {
|
|
const version = await host.macosVersion();
|
|
return version === undefined || major(version) < 12
|
|
? "Upgrade to macOS 12 or newer."
|
|
: undefined;
|
|
}
|
|
if ((await host.linuxDistribution())?.supported === true) return undefined;
|
|
return "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux; configure an existing supported provider instead.";
|
|
};
|
|
|
|
/** Production setup effects for Hopper, agent configuration, and the canonical skill directory. */
|
|
export const systemSetupHost = (
|
|
doctorHost: DoctorHost = systemDoctorHost(),
|
|
): SetupHost => {
|
|
const platform = doctorHost.platform;
|
|
return {
|
|
platform,
|
|
nodeVersion: process.versions.node,
|
|
macosVersion: () => doctorHost.macosVersion(),
|
|
linuxDistribution: readLinuxDistribution,
|
|
hopperPath: async () => (await runDoctor(undefined, doctorHost)).hopperPath,
|
|
providerEnvironment: async () => {
|
|
const diagnosis = await runDoctor(undefined, doctorHost);
|
|
return {
|
|
...providerRegistrationEnvironment(diagnosis.providerInspections ?? []),
|
|
...(diagnosis.hopperPath === undefined
|
|
? {}
|
|
: { HOPPER_LAUNCHER_PATH: diagnosis.hopperPath }),
|
|
};
|
|
},
|
|
installHopper: async (replaceExisting) => {
|
|
const result =
|
|
platform === "linux"
|
|
? await installLinuxHopper()
|
|
: await installMacHopper({ replaceExisting });
|
|
if (result.status === "installed") return result;
|
|
return setupInstallFailure(result.reason);
|
|
},
|
|
detectedClients: () => detectClients(homedir()),
|
|
supportedClients: () => Promise.resolve(supportedClients(homedir())),
|
|
configureClient: (client, providerEnvironment, command) =>
|
|
client.format === "unsupported"
|
|
? Promise.resolve({ status: "skipped" })
|
|
: configureClientConfiguration(client, providerEnvironment, command),
|
|
clientNeedsConfigure: (client, providerEnvironment, command) =>
|
|
clientConfigurationAligned(client, providerEnvironment, command).then(
|
|
(aligned) => !aligned,
|
|
),
|
|
inspectClientConfiguration: inspectClientConfiguration,
|
|
skillNeedsInstall: () => canonicalSkillNeedsInstall(homedir()),
|
|
installSkill: () => installCanonicalSkill(homedir()),
|
|
doctor: (scope) => runDoctor(undefined, doctorHost, scope),
|
|
};
|
|
};
|
|
|
|
/** Detect supported agents from their config files or stable installation markers. */
|
|
export const detectClients = async (
|
|
home: string,
|
|
): Promise<readonly SetupClient[]> => {
|
|
const detected: SetupClient[] = [];
|
|
for (const candidate of supportedClients(home)) {
|
|
const [hasConfig, hasMarker] = await Promise.all([
|
|
exists(candidate.configPath),
|
|
candidate.markerPath === undefined ? false : exists(candidate.markerPath),
|
|
]);
|
|
if (hasConfig || hasMarker) detected.push(candidate);
|
|
}
|
|
return detected;
|
|
};
|
|
|
|
const major = (version: string): number =>
|
|
Number.parseInt(version.split(".")[0] ?? "0", 10);
|
|
const exists = async (path: string): Promise<boolean> => {
|
|
try {
|
|
await access(path);
|
|
return true;
|
|
} catch (cause: unknown) {
|
|
// best-effort cleanup: optional host probing; absence means unavailable.
|
|
void cause;
|
|
return false;
|
|
}
|
|
};
|