Files
rea/src/application/SupportedClients.ts
T
I Made Ocy Darma Putra 7f1ae42f71 feat(setup): support Command Code MCP registration (#645)
Configure Command Code's user-scope MCP config (~/.commandcode/mcp.json)
with an explicit stdio registration ({ transport, enabled, command, args })
under mcpServers, matching the shape Command Code writes itself.

Adds the commandcode setup client and dialect, detection/alignment support,
README/docs entries, and regenerated product catalog.
2026-10-06 14:15:59 +08:00

242 lines
7.0 KiB
TypeScript

import { join } from "node:path";
import { lstatSync } from "node:fs";
/** One supported client configuration location. */
export interface SetupClient {
readonly name: string;
readonly displayName?: string;
readonly configPath: string;
readonly markerPath?: string;
readonly format?:
| "json"
| "toml"
| "vscode"
| "copilot_cli"
| "opencode"
| "commandcode"
| "unsupported";
}
type ClientPath = readonly string[] | ((paths: ClientPathContext) => string);
interface ClientPathContext {
readonly home: string;
readonly platform: NodeJS.Platform;
readonly env: {
readonly APPDATA?: string | undefined;
readonly CLAUDE_CONFIG_DIR?: string | undefined;
readonly CODEX_HOME?: string | undefined;
readonly COPILOT_HOME?: string | undefined;
readonly OPENCODE_CONFIG?: string | undefined;
readonly XDG_CONFIG_HOME?: string | undefined;
};
}
interface ClientDefinition {
readonly name: string;
readonly displayName: string;
readonly configPath: ClientPath;
readonly markerPath: ClientPath;
readonly format: NonNullable<SetupClient["format"]>;
}
const vscodeUserDirectory = ({
home,
platform,
env,
}: ClientPathContext): string => {
if (platform === "win32")
return join(
env.APPDATA ?? join(home, "AppData", "Roaming"),
"Code",
"User",
);
if (platform === "darwin")
return join(home, "Library", "Application Support", "Code", "User");
return join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "Code", "User");
};
const claudeDesktopDirectory = ({
home,
platform,
env,
}: ClientPathContext): string => {
if (platform === "win32")
return join(env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude");
if (platform === "linux")
return join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "Claude");
return join(home, "Library", "Application Support", "Claude");
};
const claudeCodeConfigDirectory = ({ home, env }: ClientPathContext): string =>
env.CLAUDE_CONFIG_DIR ?? home;
const claudeCodeMarkerDirectory = ({ home, env }: ClientPathContext): string =>
env.CLAUDE_CONFIG_DIR ?? join(home, ".claude");
const codexDirectory = ({ home, env }: ClientPathContext): string =>
env.CODEX_HOME ?? join(home, ".codex");
const copilotDirectory = ({ home, env }: ClientPathContext): string =>
env.COPILOT_HOME ?? join(home, ".copilot");
const commandCodeDirectory = ({ home }: ClientPathContext): string =>
join(home, ".commandcode");
const opencodeDirectory = ({ home, env }: ClientPathContext): string =>
join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "opencode");
const existingOpenCodeConfigPath = (context: ClientPathContext): string => {
if (context.env.OPENCODE_CONFIG !== undefined)
return context.env.OPENCODE_CONFIG;
const directory = opencodeDirectory(context);
const candidates = [
"opencode.json",
"opencode.jsonc",
".opencode.json",
".opencode.jsonc",
].map((filename) => join(directory, filename));
return (
candidates.find((path) => {
try {
lstatSync(path);
return true;
} catch (cause: unknown) {
return !(
cause instanceof Error &&
"code" in cause &&
cause.code === "ENOENT"
);
}
}) ??
candidates[0] ??
join(directory, "opencode.json")
);
};
const devinDirectory = ({ home, platform, env }: ClientPathContext): string =>
platform === "win32"
? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "devin")
: join(home, ".config", "devin");
/** Stable product metadata used to derive setup discovery and documentation. */
export const SUPPORTED_CLIENT_DEFINITIONS = [
{
name: "claude_code",
displayName: "Claude Code",
configPath: (context: ClientPathContext) =>
join(claudeCodeConfigDirectory(context), ".claude.json"),
markerPath: claudeCodeMarkerDirectory,
format: "json",
},
{
name: "claude_desktop",
displayName: "Claude Desktop",
configPath: (context: ClientPathContext) =>
join(claudeDesktopDirectory(context), "claude_desktop_config.json"),
markerPath: claudeDesktopDirectory,
format: "json",
},
{
name: "codex",
displayName: "Codex",
configPath: (context: ClientPathContext) =>
join(codexDirectory(context), "config.toml"),
markerPath: codexDirectory,
format: "toml",
},
{
name: "cursor",
displayName: "Cursor",
configPath: [".cursor", "mcp.json"],
markerPath: [".cursor"],
format: "json",
},
{
name: "gemini_cli",
displayName: "Gemini CLI",
configPath: [".gemini", "settings.json"],
markerPath: [".gemini"],
format: "json",
},
{
name: "windsurf",
displayName: "Windsurf",
configPath: [".codeium", "windsurf", "mcp_config.json"],
markerPath: [".codeium", "windsurf"],
format: "json",
},
{
name: "devin",
displayName: "Devin",
configPath: ({ home, platform, env }: ClientPathContext) =>
join(devinDirectory({ home, platform, env }), "mcp_config.json"),
markerPath: devinDirectory,
format: "json",
},
{
name: "opencode",
displayName: "OpenCode",
configPath: existingOpenCodeConfigPath,
markerPath: opencodeDirectory,
format: "opencode",
},
{
name: "antigravity",
displayName: "Antigravity",
configPath: [".gemini", "config", "mcp_config.json"],
markerPath: [".gemini", "config"],
format: "json",
},
{
name: "copilot_cli",
displayName: "GitHub Copilot CLI",
configPath: (context: ClientPathContext) =>
join(copilotDirectory(context), "mcp-config.json"),
markerPath: copilotDirectory,
format: "copilot_cli",
},
{
name: "commandcode",
displayName: "Command Code",
configPath: (context: ClientPathContext) =>
join(commandCodeDirectory(context), "mcp.json"),
markerPath: commandCodeDirectory,
format: "commandcode",
},
{
name: "vscode",
displayName: "VS Code",
configPath: (context: ClientPathContext) =>
join(vscodeUserDirectory(context), "mcp.json"),
markerPath: vscodeUserDirectory,
format: "vscode",
},
] as const satisfies readonly ClientDefinition[];
const resolvePath = (path: ClientPath, context: ClientPathContext): string =>
typeof path === "function" ? path(context) : join(context.home, ...path);
/** Describe every client location that setup, doctor, or uninstall may inspect. */
export const supportedClients = (
home: string,
platform: NodeJS.Platform = process.platform,
env: ClientPathContext["env"] = {
APPDATA: process.env.APPDATA,
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
CODEX_HOME: process.env.CODEX_HOME,
COPILOT_HOME: process.env.COPILOT_HOME,
OPENCODE_CONFIG: process.env.OPENCODE_CONFIG,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
},
): readonly SetupClient[] => {
const context = { home, platform, env };
return SUPPORTED_CLIENT_DEFINITIONS.map((definition) => ({
name: definition.name,
displayName: definition.displayName,
configPath: resolvePath(definition.configPath, context),
markerPath: resolvePath(definition.markerPath, context),
format: definition.format,
}));
};