Split agent setup editor helpers by editor
This commit is contained in:
parent
a42efdeea9
commit
116d312cca
6 changed files with 377 additions and 339 deletions
180
apps/desktop/src/agent-setup-editor-tools-cursor.ts
Normal file
180
apps/desktop/src/agent-setup-editor-tools-cursor.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { buildCursorRulesPreview, buildFamiliarOSOnlyPreview, classifyCursorMcpStatus, executeCursorMcpWrite, getCursorGlobalMcpPath, planCursorMcpInstall, planCursorMcpRemove, planCursorMcpReplace, readCursorMcpConfig, type CursorMcpStatusResult, type RedactedPreview } from "@familiaros/cursor";
|
||||
|
||||
export interface CursorSetupStatus {
|
||||
readonly state: "configured" | "needs_setup" | "not_detected" | "error" | "conflict" | "needs_update";
|
||||
readonly label: string;
|
||||
readonly details: string;
|
||||
readonly configPath: string;
|
||||
readonly canInstall: boolean;
|
||||
readonly canReplace: boolean;
|
||||
readonly canRemove: boolean;
|
||||
}
|
||||
|
||||
export interface CursorSetupPreview {
|
||||
readonly global: true;
|
||||
readonly configPath: string;
|
||||
readonly mcpEntry: RedactedPreview;
|
||||
readonly rulesPath: string;
|
||||
readonly rulesContent: string;
|
||||
readonly commandMode: "published" | "local" | "bundled";
|
||||
}
|
||||
|
||||
type CursorActionResult = {
|
||||
readonly ok: boolean;
|
||||
readonly action: "cursor-install" | "cursor-replace" | "cursor-remove";
|
||||
readonly message: string;
|
||||
readonly changed: boolean;
|
||||
};
|
||||
|
||||
export function buildCursorSetupSnapshot({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): { readonly status: CursorSetupStatus; readonly preview: CursorSetupPreview } {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const petId = selectedPetId || undefined;
|
||||
|
||||
const configResult = readCursorMcpConfig(configPath);
|
||||
const statusResult = classifyCursorMcpStatus(configResult, configPath, { mcpVersion, petId, commandMode: "published" });
|
||||
|
||||
return {
|
||||
status: {
|
||||
state: mapCursorStatusToState(statusResult.status),
|
||||
label: mapCursorStatusToLabel(statusResult.status),
|
||||
details: statusResult.message,
|
||||
configPath: formatUserPath(configPath) ?? configPath,
|
||||
canInstall: statusResult.canInstall,
|
||||
canReplace: statusResult.canReplace,
|
||||
canRemove: statusResult.canRemove,
|
||||
},
|
||||
preview: {
|
||||
global: true,
|
||||
configPath: formatUserPath(configPath) ?? configPath,
|
||||
mcpEntry: buildFamiliarOSOnlyPreview({ mcpVersion, petId, commandMode: "published" }),
|
||||
rulesPath: ".cursor/rules/familiaros.mdc",
|
||||
rulesContent: buildCursorRulesPreview(),
|
||||
commandMode: "published",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function installCursorGlobalConfig({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpInstall(configPath, { mcpVersion, petId: selectedPetId || undefined, commandMode: "published" });
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-install", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
const backupMsg = plan.backupPath ? ` Backup: ${formatUserPath(plan.backupPath) ?? plan.backupPath}.` : "";
|
||||
return { ok: true, action: "cursor-install", message: `Installed Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}.${backupMsg} Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-install", message: "Failed to plan Cursor MCP install.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-install", message: error instanceof Error ? error.message : "Cursor MCP install failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceCursorGlobalConfig({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpReplace(configPath, { mcpVersion, petId: selectedPetId || undefined, commandMode: "published" });
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-replace", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
const backupMsg = plan.backupPath ? ` Backup: ${formatUserPath(plan.backupPath) ?? plan.backupPath}.` : "";
|
||||
return { ok: true, action: "cursor-replace", message: `Replaced Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}.${backupMsg} Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-replace", message: "Failed to plan Cursor MCP replace.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-replace", message: error instanceof Error ? error.message : "Cursor MCP replace failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function removeCursorGlobalConfig({
|
||||
homeDir,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpRemove(configPath);
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-remove", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
return { ok: true, action: "cursor-remove", message: `Removed Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}. Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-remove", message: "Failed to plan Cursor MCP remove.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-remove", message: error instanceof Error ? error.message : "Cursor MCP remove failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
function mapCursorStatusToState(status: CursorMcpStatusResult["status"]): CursorSetupStatus["state"] {
|
||||
switch (status) {
|
||||
case "installed":
|
||||
return "configured";
|
||||
case "missing":
|
||||
return "needs_setup";
|
||||
case "needs-update":
|
||||
return "needs_update";
|
||||
case "conflict":
|
||||
return "conflict";
|
||||
case "invalid":
|
||||
case "error":
|
||||
return "error";
|
||||
default:
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
function mapCursorStatusToLabel(status: CursorMcpStatusResult["status"]): string {
|
||||
switch (status) {
|
||||
case "installed":
|
||||
return "Configured";
|
||||
case "missing":
|
||||
return "Not configured";
|
||||
case "needs-update":
|
||||
return "Needs update";
|
||||
case "conflict":
|
||||
return "Conflict";
|
||||
case "invalid":
|
||||
case "error":
|
||||
return "Config error";
|
||||
default:
|
||||
return "Checking";
|
||||
}
|
||||
}
|
||||
152
apps/desktop/src/agent-setup-editor-tools-opencode.ts
Normal file
152
apps/desktop/src/agent-setup-editor-tools-opencode.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { doctorOpenCodeGlobalSetup, getGlobalOpenCodeConfigDir, parseOpenCodeConfig, prepareOpenCodeGlobalRemove, prepareOpenCodeGlobalSetup, writePreparedOpenCodeGlobalRemove, writePreparedOpenCodeGlobalSetup } from "@familiaros/opencode";
|
||||
import type { FamiliarOSCommandMode } from "@familiaros/claude";
|
||||
|
||||
export interface OpenCodeSetupStatus {
|
||||
readonly state: "configured" | "needs_setup" | "not_detected" | "error";
|
||||
readonly label: string;
|
||||
readonly details: string;
|
||||
readonly configDir: string;
|
||||
readonly canInstall: boolean;
|
||||
readonly canRemove: boolean;
|
||||
}
|
||||
|
||||
export interface OpenCodeSetupPreview {
|
||||
readonly global: true;
|
||||
readonly configDir: string;
|
||||
readonly configPath: string;
|
||||
readonly cleanupConfigPaths: readonly string[];
|
||||
readonly mcpCommand: readonly string[];
|
||||
readonly plugin: readonly unknown[] | string;
|
||||
readonly instructionPath: string;
|
||||
readonly configPreview: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type OpenCodeActionResult = {
|
||||
readonly ok: boolean;
|
||||
readonly action: "opencode-install" | "opencode-remove";
|
||||
readonly message: string;
|
||||
readonly changed: boolean;
|
||||
};
|
||||
|
||||
export function buildOpenCodeSetupSnapshot({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
selectedPetId,
|
||||
commandMode,
|
||||
cliVersion,
|
||||
pluginVersion,
|
||||
cliEntryPath,
|
||||
detectedOk,
|
||||
preferredOpenCodeCommand,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly commandMode: FamiliarOSCommandMode;
|
||||
readonly cliVersion: string;
|
||||
readonly pluginVersion: string;
|
||||
readonly cliEntryPath: string | undefined;
|
||||
readonly detectedOk: boolean;
|
||||
readonly preferredOpenCodeCommand: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): { readonly status: OpenCodeSetupStatus; readonly preview: OpenCodeSetupPreview } {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const petId = selectedPetId || undefined;
|
||||
const prepared = safePrepareOpenCode(configDir, petId, cliVersion, pluginVersion, commandMode, cliEntryPath);
|
||||
const globalState = doctorOpenCodeGlobalSetup(configDir);
|
||||
const configured = globalState.status === "installed";
|
||||
|
||||
return {
|
||||
status: {
|
||||
state: globalState.status === "error" || globalState.status === "custom" || globalState.status === "conflict" ? "error" : configured ? "configured" : detectedOk ? "needs_setup" : "not_detected",
|
||||
label: configured ? "Installed" : globalState.status === "custom" || globalState.status === "conflict" ? "Needs attention" : detectedOk ? "Ready" : "Not detected",
|
||||
details: globalState.status === "custom" || globalState.status === "conflict" || globalState.status === "error" ? globalState.message : configured ? globalState.message : detectedOk ? "OpenCode was detected. Desktop setup writes global OpenCode config." : preferredOpenCodeCommand === (processPlatform === "win32" ? "opencode.cmd" : "opencode") ? "OpenCode was not found on PATH. You can still preview setup, but OpenCode must be installed to use it." : "OpenCode did not run from the saved command path. You can still preview setup, but OpenCode must be installed to use it.",
|
||||
configDir: formatUserPath(configDir) ?? configDir,
|
||||
canInstall: prepared.ok && !configured,
|
||||
canRemove: configured,
|
||||
},
|
||||
preview: {
|
||||
global: true,
|
||||
configDir: formatUserPath(configDir) ?? configDir,
|
||||
configPath: prepared.ok ? (formatUserPath(prepared.configPath) ?? prepared.configPath) : "",
|
||||
cleanupConfigPaths: prepared.ok ? prepared.cleanupConfigPaths.map((path) => formatUserPath(path) ?? path) : [],
|
||||
mcpCommand: prepared.ok ? prepared.command : [],
|
||||
plugin: prepared.ok ? prepared.plugin : (petId ? [`@familiaros/opencode@${pluginVersion}`, { familiar: petId }] : `@familiaros/opencode@${pluginVersion}`),
|
||||
instructionPath: prepared.ok ? (formatUserPath(prepared.instructionPath) ?? prepared.instructionPath) : "",
|
||||
configPreview: prepared.ok ? prepared.configPreview : {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function installOpenCodeGlobalConfig({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
selectedPetId,
|
||||
commandMode,
|
||||
cliVersion,
|
||||
pluginVersion,
|
||||
cliEntryPath,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly commandMode: FamiliarOSCommandMode;
|
||||
readonly cliVersion: string;
|
||||
readonly pluginVersion: string;
|
||||
readonly cliEntryPath: string | undefined;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): OpenCodeActionResult {
|
||||
try {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const prepared = prepareOpenCodeGlobalSetup({ configDir, petId: selectedPetId || undefined, cliVersion, pluginVersion, commandMode, cliEntryPath });
|
||||
writePreparedOpenCodeGlobalSetup(prepared);
|
||||
return { ok: true, action: "opencode-install", message: `Installed global OpenCode FamiliarOS setup. Config: ${formatUserPath(prepared.configPath) ?? prepared.configPath}. Instructions: ${formatUserPath(prepared.instructionPath) ?? prepared.instructionPath}.`, changed: true };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "opencode-install", message: error instanceof Error ? error.message : "OpenCode setup failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function removeOpenCodeGlobalConfig({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
}): OpenCodeActionResult {
|
||||
try {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const prepared = prepareOpenCodeGlobalRemove(configDir);
|
||||
writePreparedOpenCodeGlobalRemove(prepared);
|
||||
return { ok: true, action: "opencode-remove", message: prepared.configWrites.length > 0 ? "Removed global OpenCode FamiliarOS setup." : "Global OpenCode FamiliarOS setup was already absent.", changed: prepared.configWrites.length > 0 };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "opencode-remove", message: error instanceof Error ? error.message : "OpenCode removal failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
function safePrepareOpenCode(
|
||||
configDir: string,
|
||||
selectedPetId: string | undefined,
|
||||
cliVersion: string,
|
||||
pluginVersion: string,
|
||||
commandMode: FamiliarOSCommandMode,
|
||||
cliEntryPath: string | undefined,
|
||||
): { readonly ok: true; readonly command: readonly string[]; readonly configPath: string; readonly cleanupConfigPaths: readonly string[]; readonly instructionPath: string; readonly plugin: readonly unknown[] | string; readonly configPreview: Record<string, unknown> } | { readonly ok: false; readonly message: string } {
|
||||
try {
|
||||
const prepared = prepareOpenCodeGlobalSetup({ configDir, petId: selectedPetId || undefined, cliVersion, pluginVersion, commandMode, cliEntryPath });
|
||||
const parsed = parseOpenCodeConfig(prepared.configWrite.content);
|
||||
if (!parsed.ok) return { ok: false, message: parsed.message };
|
||||
const config = parsed.value as { mcp?: { familiaros?: { command?: readonly string[] } }; plugin?: readonly unknown[] };
|
||||
const plugin = Array.isArray(config.plugin) ? config.plugin[config.plugin.length - 1] : undefined;
|
||||
return { ok: true, command: config.mcp?.familiaros?.command ?? [], configPath: prepared.configPath, cleanupConfigPaths: prepared.cleanupConfigWrites.map((write) => write.targetPath), instructionPath: prepared.instructionPath, plugin: plugin === undefined ? [] : (plugin as readonly unknown[] | string), configPreview: parsed.value };
|
||||
} catch (error) {
|
||||
return { ok: false, message: error instanceof Error ? error.message : "OpenCode setup preview failed." };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,332 +1,19 @@
|
|||
import { buildCursorRulesPreview, buildFamiliarOSOnlyPreview, classifyCursorMcpStatus, executeCursorMcpWrite, getCursorGlobalMcpPath, planCursorMcpInstall, planCursorMcpRemove, planCursorMcpReplace, readCursorMcpConfig, type CursorMcpStatusResult, type RedactedPreview } from "@familiaros/cursor";
|
||||
import { doctorOpenCodeGlobalSetup, getGlobalOpenCodeConfigDir, parseOpenCodeConfig, prepareOpenCodeGlobalRemove, prepareOpenCodeGlobalSetup, writePreparedOpenCodeGlobalRemove, writePreparedOpenCodeGlobalSetup } from "@familiaros/opencode";
|
||||
import type { FamiliarOSCommandMode } from "@familiaros/claude";
|
||||
|
||||
export interface OpenCodeSetupStatus {
|
||||
readonly state: "configured" | "needs_setup" | "not_detected" | "error";
|
||||
readonly label: string;
|
||||
readonly details: string;
|
||||
readonly configDir: string;
|
||||
readonly canInstall: boolean;
|
||||
readonly canRemove: boolean;
|
||||
}
|
||||
|
||||
export interface OpenCodeSetupPreview {
|
||||
readonly global: true;
|
||||
readonly configDir: string;
|
||||
readonly configPath: string;
|
||||
readonly cleanupConfigPaths: readonly string[];
|
||||
readonly mcpCommand: readonly string[];
|
||||
readonly plugin: readonly unknown[] | string;
|
||||
readonly instructionPath: string;
|
||||
readonly configPreview: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface CursorSetupStatus {
|
||||
readonly state: "configured" | "needs_setup" | "not_detected" | "error" | "conflict" | "needs_update";
|
||||
readonly label: string;
|
||||
readonly details: string;
|
||||
readonly configPath: string;
|
||||
readonly canInstall: boolean;
|
||||
readonly canReplace: boolean;
|
||||
readonly canRemove: boolean;
|
||||
}
|
||||
|
||||
export interface CursorSetupPreview {
|
||||
readonly global: true;
|
||||
readonly configPath: string;
|
||||
readonly mcpEntry: RedactedPreview;
|
||||
readonly rulesPath: string;
|
||||
readonly rulesContent: string;
|
||||
readonly commandMode: "published" | "local" | "bundled";
|
||||
}
|
||||
|
||||
type OpenCodeActionResult = {
|
||||
readonly ok: boolean;
|
||||
readonly action: "opencode-install" | "opencode-remove";
|
||||
readonly message: string;
|
||||
readonly changed: boolean;
|
||||
};
|
||||
|
||||
type CursorActionResult = {
|
||||
readonly ok: boolean;
|
||||
readonly action: "cursor-install" | "cursor-replace" | "cursor-remove";
|
||||
readonly message: string;
|
||||
readonly changed: boolean;
|
||||
};
|
||||
|
||||
export function buildOpenCodeSetupSnapshot({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
selectedPetId,
|
||||
commandMode,
|
||||
cliVersion,
|
||||
pluginVersion,
|
||||
cliEntryPath,
|
||||
detectedOk,
|
||||
preferredOpenCodeCommand,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly commandMode: FamiliarOSCommandMode;
|
||||
readonly cliVersion: string;
|
||||
readonly pluginVersion: string;
|
||||
readonly cliEntryPath: string | undefined;
|
||||
readonly detectedOk: boolean;
|
||||
readonly preferredOpenCodeCommand: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): { readonly status: OpenCodeSetupStatus; readonly preview: OpenCodeSetupPreview } {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const petId = selectedPetId || undefined;
|
||||
const prepared = safePrepareOpenCode(configDir, petId, cliVersion, pluginVersion, commandMode, cliEntryPath);
|
||||
const globalState = doctorOpenCodeGlobalSetup(configDir);
|
||||
const configured = globalState.status === "installed";
|
||||
|
||||
return {
|
||||
status: {
|
||||
state: globalState.status === "error" || globalState.status === "custom" || globalState.status === "conflict" ? "error" : configured ? "configured" : detectedOk ? "needs_setup" : "not_detected",
|
||||
label: configured ? "Installed" : globalState.status === "custom" || globalState.status === "conflict" ? "Needs attention" : detectedOk ? "Ready" : "Not detected",
|
||||
details: globalState.status === "custom" || globalState.status === "conflict" || globalState.status === "error" ? globalState.message : configured ? globalState.message : detectedOk ? "OpenCode was detected. Desktop setup writes global OpenCode config." : preferredOpenCodeCommand === (processPlatform === "win32" ? "opencode.cmd" : "opencode") ? "OpenCode was not found on PATH. You can still preview setup, but OpenCode must be installed to use it." : "OpenCode did not run from the saved command path. You can still preview setup, but OpenCode must be installed to use it.",
|
||||
configDir: formatUserPath(configDir) ?? configDir,
|
||||
canInstall: prepared.ok && !configured,
|
||||
canRemove: configured,
|
||||
},
|
||||
preview: {
|
||||
global: true,
|
||||
configDir: formatUserPath(configDir) ?? configDir,
|
||||
configPath: prepared.ok ? (formatUserPath(prepared.configPath) ?? prepared.configPath) : "",
|
||||
cleanupConfigPaths: prepared.ok ? prepared.cleanupConfigPaths.map((path) => formatUserPath(path) ?? path) : [],
|
||||
mcpCommand: prepared.ok ? prepared.command : [],
|
||||
plugin: prepared.ok ? prepared.plugin : (petId ? [`@familiaros/opencode@${pluginVersion}`, { familiar: petId }] : `@familiaros/opencode@${pluginVersion}`),
|
||||
instructionPath: prepared.ok ? (formatUserPath(prepared.instructionPath) ?? prepared.instructionPath) : "",
|
||||
configPreview: prepared.ok ? prepared.configPreview : {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCursorSetupSnapshot({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): { readonly status: CursorSetupStatus; readonly preview: CursorSetupPreview } {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const petId = selectedPetId || undefined;
|
||||
|
||||
const configResult = readCursorMcpConfig(configPath);
|
||||
const statusResult = classifyCursorMcpStatus(configResult, configPath, { mcpVersion, petId, commandMode: "published" });
|
||||
|
||||
return {
|
||||
status: {
|
||||
state: mapCursorStatusToState(statusResult.status),
|
||||
label: mapCursorStatusToLabel(statusResult.status),
|
||||
details: statusResult.message,
|
||||
configPath: formatUserPath(configPath) ?? configPath,
|
||||
canInstall: statusResult.canInstall,
|
||||
canReplace: statusResult.canReplace,
|
||||
canRemove: statusResult.canRemove,
|
||||
},
|
||||
preview: {
|
||||
global: true,
|
||||
configPath: formatUserPath(configPath) ?? configPath,
|
||||
mcpEntry: buildFamiliarOSOnlyPreview({ mcpVersion, petId, commandMode: "published" }),
|
||||
rulesPath: ".cursor/rules/familiaros.mdc",
|
||||
rulesContent: buildCursorRulesPreview(),
|
||||
commandMode: "published",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function installOpenCodeGlobalConfig({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
selectedPetId,
|
||||
commandMode,
|
||||
cliVersion,
|
||||
pluginVersion,
|
||||
cliEntryPath,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly commandMode: FamiliarOSCommandMode;
|
||||
readonly cliVersion: string;
|
||||
readonly pluginVersion: string;
|
||||
readonly cliEntryPath: string | undefined;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): OpenCodeActionResult {
|
||||
try {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const prepared = prepareOpenCodeGlobalSetup({ configDir, petId: selectedPetId || undefined, cliVersion, pluginVersion, commandMode, cliEntryPath });
|
||||
writePreparedOpenCodeGlobalSetup(prepared);
|
||||
return { ok: true, action: "opencode-install", message: `Installed global OpenCode FamiliarOS setup. Config: ${formatUserPath(prepared.configPath) ?? prepared.configPath}. Instructions: ${formatUserPath(prepared.instructionPath) ?? prepared.instructionPath}.`, changed: true };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "opencode-install", message: error instanceof Error ? error.message : "OpenCode setup failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function removeOpenCodeGlobalConfig({
|
||||
env,
|
||||
homeDir,
|
||||
processPlatform,
|
||||
}: {
|
||||
readonly env: NodeJS.ProcessEnv;
|
||||
readonly homeDir: string;
|
||||
readonly processPlatform: NodeJS.Platform;
|
||||
}): OpenCodeActionResult {
|
||||
try {
|
||||
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, processPlatform);
|
||||
const prepared = prepareOpenCodeGlobalRemove(configDir);
|
||||
writePreparedOpenCodeGlobalRemove(prepared);
|
||||
return { ok: true, action: "opencode-remove", message: prepared.configWrites.length > 0 ? "Removed global OpenCode FamiliarOS setup." : "Global OpenCode FamiliarOS setup was already absent.", changed: prepared.configWrites.length > 0 };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "opencode-remove", message: error instanceof Error ? error.message : "OpenCode removal failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function installCursorGlobalConfig({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpInstall(configPath, { mcpVersion, petId: selectedPetId || undefined, commandMode: "published" });
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-install", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
const backupMsg = plan.backupPath ? ` Backup: ${formatUserPath(plan.backupPath) ?? plan.backupPath}.` : "";
|
||||
return { ok: true, action: "cursor-install", message: `Installed Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}.${backupMsg} Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-install", message: "Failed to plan Cursor MCP install.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-install", message: error instanceof Error ? error.message : "Cursor MCP install failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceCursorGlobalConfig({
|
||||
homeDir,
|
||||
selectedPetId,
|
||||
mcpVersion,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly selectedPetId: string | undefined;
|
||||
readonly mcpVersion: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpReplace(configPath, { mcpVersion, petId: selectedPetId || undefined, commandMode: "published" });
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-replace", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
const backupMsg = plan.backupPath ? ` Backup: ${formatUserPath(plan.backupPath) ?? plan.backupPath}.` : "";
|
||||
return { ok: true, action: "cursor-replace", message: `Replaced Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}.${backupMsg} Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-replace", message: "Failed to plan Cursor MCP replace.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-replace", message: error instanceof Error ? error.message : "Cursor MCP replace failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
export function removeCursorGlobalConfig({
|
||||
homeDir,
|
||||
formatUserPath,
|
||||
}: {
|
||||
readonly homeDir: string;
|
||||
readonly formatUserPath: (path: string | undefined) => string | undefined;
|
||||
}): CursorActionResult {
|
||||
try {
|
||||
const configPath = getCursorGlobalMcpPath(homeDir);
|
||||
const plan = planCursorMcpRemove(configPath);
|
||||
if ("ok" in plan && !plan.ok) {
|
||||
return { ok: false, action: "cursor-remove", message: plan.message, changed: false };
|
||||
}
|
||||
if ("targetPath" in plan) {
|
||||
executeCursorMcpWrite(plan);
|
||||
return { ok: true, action: "cursor-remove", message: `Removed Cursor FamiliarOS MCP config at ${formatUserPath(configPath) ?? configPath}. Cursor may need to be restarted or reloaded.`, changed: true };
|
||||
}
|
||||
return { ok: false, action: "cursor-remove", message: "Failed to plan Cursor MCP remove.", changed: false };
|
||||
} catch (error) {
|
||||
return { ok: false, action: "cursor-remove", message: error instanceof Error ? error.message : "Cursor MCP remove failed.", changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
function mapCursorStatusToState(status: CursorMcpStatusResult["status"]): CursorSetupStatus["state"] {
|
||||
switch (status) {
|
||||
case "installed":
|
||||
return "configured";
|
||||
case "missing":
|
||||
return "needs_setup";
|
||||
case "needs-update":
|
||||
return "needs_update";
|
||||
case "conflict":
|
||||
return "conflict";
|
||||
case "invalid":
|
||||
case "error":
|
||||
return "error";
|
||||
default:
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
function mapCursorStatusToLabel(status: CursorMcpStatusResult["status"]): string {
|
||||
switch (status) {
|
||||
case "installed":
|
||||
return "Configured";
|
||||
case "missing":
|
||||
return "Not configured";
|
||||
case "needs-update":
|
||||
return "Needs update";
|
||||
case "conflict":
|
||||
return "Conflict";
|
||||
case "invalid":
|
||||
case "error":
|
||||
return "Config error";
|
||||
default:
|
||||
return "Checking";
|
||||
}
|
||||
}
|
||||
|
||||
function safePrepareOpenCode(
|
||||
configDir: string,
|
||||
selectedPetId: string | undefined,
|
||||
cliVersion: string,
|
||||
pluginVersion: string,
|
||||
commandMode: FamiliarOSCommandMode,
|
||||
cliEntryPath: string | undefined,
|
||||
): { readonly ok: true; readonly command: readonly string[]; readonly configPath: string; readonly cleanupConfigPaths: readonly string[]; readonly instructionPath: string; readonly plugin: readonly unknown[] | string; readonly configPreview: Record<string, unknown> } | { readonly ok: false; readonly message: string } {
|
||||
try {
|
||||
const prepared = prepareOpenCodeGlobalSetup({ configDir, petId: selectedPetId || undefined, cliVersion, pluginVersion, commandMode, cliEntryPath });
|
||||
const parsed = parseOpenCodeConfig(prepared.configWrite.content);
|
||||
if (!parsed.ok) return { ok: false, message: parsed.message };
|
||||
const config = parsed.value as { mcp?: { familiaros?: { command?: readonly string[] } }; plugin?: readonly unknown[] };
|
||||
const plugin = Array.isArray(config.plugin) ? config.plugin[config.plugin.length - 1] : undefined;
|
||||
return { ok: true, command: config.mcp?.familiaros?.command ?? [], configPath: prepared.configPath, cleanupConfigPaths: prepared.cleanupConfigWrites.map((write) => write.targetPath), instructionPath: prepared.instructionPath, plugin: plugin === undefined ? [] : (plugin as readonly unknown[] | string), configPreview: parsed.value };
|
||||
} catch (error) {
|
||||
return { ok: false, message: error instanceof Error ? error.message : "OpenCode setup preview failed." };
|
||||
}
|
||||
}
|
||||
export {
|
||||
buildOpenCodeSetupSnapshot,
|
||||
installOpenCodeGlobalConfig,
|
||||
removeOpenCodeGlobalConfig,
|
||||
} from "./agent-setup-editor-tools-opencode.js";
|
||||
export type {
|
||||
OpenCodeSetupPreview,
|
||||
OpenCodeSetupStatus,
|
||||
} from "./agent-setup-editor-tools-opencode.js";
|
||||
export {
|
||||
buildCursorSetupSnapshot,
|
||||
installCursorGlobalConfig,
|
||||
replaceCursorGlobalConfig,
|
||||
removeCursorGlobalConfig,
|
||||
} from "./agent-setup-editor-tools-cursor.js";
|
||||
export type {
|
||||
CursorSetupPreview,
|
||||
CursorSetupStatus,
|
||||
} from "./agent-setup-editor-tools-cursor.js";
|
||||
|
|
|
|||
|
|
@ -203,6 +203,8 @@ const agentSetupActionsToolingSource = readFileSync(join(appDir, "src", "agent-s
|
|||
const agentSetupCommandRunnerSource = readFileSync(join(appDir, "src", "agent-setup-command-runner.ts"), "utf8");
|
||||
const agentSetupCommandContextSource = readFileSync(join(appDir, "src", "agent-setup-command-context.ts"), "utf8");
|
||||
const agentSetupEditorToolsSource = readFileSync(join(appDir, "src", "agent-setup-editor-tools.ts"), "utf8");
|
||||
const agentSetupEditorToolsOpenCodeSource = readFileSync(join(appDir, "src", "agent-setup-editor-tools-opencode.ts"), "utf8");
|
||||
const agentSetupEditorToolsCursorSource = readFileSync(join(appDir, "src", "agent-setup-editor-tools-cursor.ts"), "utf8");
|
||||
const agentSetupSupportSource = readFileSync(join(appDir, "src", "agent-setup-support.ts"), "utf8");
|
||||
const pluginSdkBridgeSource = readFileSync(join(appDir, "src", "plugin-sdk-bridge.ts"), "utf8");
|
||||
const pluginSdkApiBuilderSource = readFileSync(join(appDir, "src", "plugin-sdk-api-builder.ts"), "utf8");
|
||||
|
|
@ -591,6 +593,12 @@ assert.match(agentSetupClaudeStatusSource, /export function formatUserPath/, "ag
|
|||
assert.match(agentSetupActionsToolingSource, /export async function getOpenCodeSetup/, "agent-setup tooling seam must export OpenCode setup loading.");
|
||||
assert.match(agentSetupActionsToolingSource, /export async function getCursorSetup/, "agent-setup tooling seam must export Cursor setup loading.");
|
||||
assert.match(agentSetupActionsToolingSource, /export async function runAgentSetupMcpServerHealthCheck/, "agent-setup tooling seam must export MCP server health checks.");
|
||||
assert.match(agentSetupEditorToolsSource, /from "\.\/agent-setup-editor-tools-opencode(?:\.js)?"/, "agent-setup editor helper barrel must compose the extracted OpenCode seam.");
|
||||
assert.match(agentSetupEditorToolsSource, /from "\.\/agent-setup-editor-tools-cursor(?:\.js)?"/, "agent-setup editor helper barrel must compose the extracted Cursor seam.");
|
||||
assert.match(agentSetupEditorToolsOpenCodeSource, /export function buildOpenCodeSetupSnapshot/, "agent-setup OpenCode helper seam must export setup snapshot building.");
|
||||
assert.match(agentSetupEditorToolsOpenCodeSource, /export function installOpenCodeGlobalConfig/, "agent-setup OpenCode helper seam must export config writes.");
|
||||
assert.match(agentSetupEditorToolsCursorSource, /export function buildCursorSetupSnapshot/, "agent-setup Cursor helper seam must export setup snapshot building.");
|
||||
assert.match(agentSetupEditorToolsCursorSource, /export function replaceCursorGlobalConfig/, "agent-setup Cursor helper seam must export replace writes.");
|
||||
assert.match(pluginServiceDefaultPetSource, /export async function buildDefaultPetPluginCommands/, "plugin-service default-pet helper must export default familiar command shaping.");
|
||||
assert.match(pluginServiceSupportSource, /from "\.\/plugin-service-actions(?:\.js)?"/, "plugin-service support barrel must re-export the service action seam.");
|
||||
assert.match(pluginServiceActionsSource, /export async function buildPluginCatalogSnapshot/, "plugin-service action seam must export catalog snapshot shaping.");
|
||||
|
|
|
|||
|
|
@ -86,8 +86,11 @@ windows.ts (IPC handlers)
|
|||
│ ├── FamiliarOS MCP preview shaping
|
||||
│ ├── Claude memory install/uninstall safety wrappers
|
||||
│ └── bounded action journal writes
|
||||
└── agent-setup-editor-tools.ts
|
||||
├── OpenCode global config management (@familiaros/opencode)
|
||||
├── agent-setup-editor-tools.ts
|
||||
│ └── stable barrel over the editor-specific setup helper seams
|
||||
├── agent-setup-editor-tools-opencode.ts
|
||||
│ └── OpenCode global config management (@familiaros/opencode)
|
||||
└── agent-setup-editor-tools-cursor.ts
|
||||
└── Cursor global MCP config management (@familiaros/cursor)
|
||||
```
|
||||
|
||||
|
|
@ -216,6 +219,8 @@ plugin-service-local-support.ts → plugin-local-loader.ts validates selected fo
|
|||
- `control-center-ipc-agent.ts`: Agent setup, toolkit, and FamiliarOS MCP preview/test IPC routes for the Control Center
|
||||
- `agent-setup-actions.ts`: Extracted action execution shell for hook handling plus Claude MCP add/remove orchestration
|
||||
- `agent-setup-actions-tooling.ts`: Extracted OpenCode/Cursor setup loading, global config actions, and FamiliarOS MCP server health checks
|
||||
- `agent-setup-editor-tools-opencode.ts`: Extracted OpenCode global setup status, previews, and config write helpers
|
||||
- `agent-setup-editor-tools-cursor.ts`: Extracted Cursor global setup status, previews, and MCP config write helpers
|
||||
- `agent-setup-support.ts`: Extracted MCP preview, Claude memory safety wrappers, and bounded action journal helpers for agent setup
|
||||
- `mcp-toolkit-installer.ts`: Public MCP toolkit installer facade for persistent host-agent server setup and managed install result shaping
|
||||
- `mcp-toolkit-installer-support.ts`: Extracted MCP toolkit bundle/server-definition, argument, note, and command-candidate helpers
|
||||
|
|
@ -350,7 +355,9 @@ plugin-service-local-support.ts → plugin-local-loader.ts validates selected fo
|
|||
- `agent-setup-claude-status.ts`: Extracted Claude MCP preview/status detection, command wrappers, output sanitization, and packaged-resource error shaping
|
||||
- `agent-setup-command-context.ts`: Agent setup command path persistence, preferred command resolution, and workspace package metadata helpers
|
||||
- `agent-setup-command-runner.ts`: Extracted agent-setup command execution, PATH shaping, Claude command candidate selection, and sanitized command result summarization
|
||||
- `agent-setup-editor-tools.ts`: OpenCode/Cursor global setup status, previews, and config write helpers
|
||||
- `agent-setup-editor-tools.ts`: Stable barrel for the extracted OpenCode and Cursor editor setup helper seams
|
||||
- `agent-setup-editor-tools-opencode.ts`: Extracted OpenCode global setup status, previews, and config write helpers
|
||||
- `agent-setup-editor-tools-cursor.ts`: Extracted Cursor global setup status, previews, and MCP config write helpers
|
||||
- `claude-memory.ts`: Claude instructions file management (`~/.claude/familiaros.md`)
|
||||
- `update-checker.ts`: GitHub release polling, update status
|
||||
- `update-version.ts`: Version parsing and comparison
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ const agentSetupSource = readFileSync(resolve(desktopRoot, "src/agent-setup.ts")
|
|||
const agentSetupActionsSource = readFileSync(resolve(desktopRoot, "src/agent-setup-actions.ts"), "utf8");
|
||||
const agentSetupActionsToolingSource = readFileSync(resolve(desktopRoot, "src/agent-setup-actions-tooling.ts"), "utf8");
|
||||
const agentSetupEditorToolsSource = readFileSync(resolve(desktopRoot, "src/agent-setup-editor-tools.ts"), "utf8");
|
||||
const agentSetupEditorToolsOpenCodeSource = readFileSync(resolve(desktopRoot, "src/agent-setup-editor-tools-opencode.ts"), "utf8");
|
||||
const agentSetupEditorToolsCursorSource = readFileSync(resolve(desktopRoot, "src/agent-setup-editor-tools-cursor.ts"), "utf8");
|
||||
|
||||
assert.match(agentSetupSource, /from "\.\/agent-setup-actions\.js"/, "Agent setup must import the extracted action seam.");
|
||||
assert.match(agentSetupActionsSource, /from "\.\/agent-setup-actions-tooling(?:\.js)?"/, "Agent setup action shell must compose the extracted tooling seam.");
|
||||
|
|
@ -17,9 +19,11 @@ assert.match(agentSetupActionsToolingSource, /buildCursorSetupSnapshot/, "Agent
|
|||
assert.match(agentSetupActionsToolingSource, /installOpenCodeGlobalConfig/, "Agent setup tooling seam must delegate OpenCode config writes through the extracted helper.");
|
||||
assert.match(agentSetupActionsToolingSource, /replaceCursorGlobalConfig/, "Agent setup tooling seam must delegate Cursor replace writes through the extracted helper.");
|
||||
|
||||
assert.match(agentSetupEditorToolsSource, /export function buildOpenCodeSetupSnapshot/, "Agent setup editor helper must own the OpenCode preview/status seam.");
|
||||
assert.match(agentSetupEditorToolsSource, /export function buildCursorSetupSnapshot/, "Agent setup editor helper must own the Cursor preview/status seam.");
|
||||
assert.match(agentSetupEditorToolsSource, /export function installOpenCodeGlobalConfig/, "Agent setup editor helper must own OpenCode global install writes.");
|
||||
assert.match(agentSetupEditorToolsSource, /export function replaceCursorGlobalConfig/, "Agent setup editor helper must own Cursor replace writes.");
|
||||
assert.match(agentSetupEditorToolsSource, /from "\.\/agent-setup-editor-tools-opencode(?:\.js)?"/, "Agent setup editor helper barrel must compose the extracted OpenCode seam.");
|
||||
assert.match(agentSetupEditorToolsSource, /from "\.\/agent-setup-editor-tools-cursor(?:\.js)?"/, "Agent setup editor helper barrel must compose the extracted Cursor seam.");
|
||||
assert.match(agentSetupEditorToolsOpenCodeSource, /export function buildOpenCodeSetupSnapshot/, "Agent setup OpenCode helper must own the OpenCode preview/status seam.");
|
||||
assert.match(agentSetupEditorToolsOpenCodeSource, /export function installOpenCodeGlobalConfig/, "Agent setup OpenCode helper must own OpenCode global install writes.");
|
||||
assert.match(agentSetupEditorToolsCursorSource, /export function buildCursorSetupSnapshot/, "Agent setup Cursor helper must own the Cursor preview/status seam.");
|
||||
assert.match(agentSetupEditorToolsCursorSource, /export function replaceCursorGlobalConfig/, "Agent setup Cursor helper must own Cursor replace writes.");
|
||||
|
||||
console.error("Agent setup editor helper seam validation passed.");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue