Extract integrations agent sections seam

This commit is contained in:
OpenPets Dev 2026-06-19 07:50:37 +00:00
parent af8521801f
commit 4949f5f2e8
8 changed files with 250 additions and 191 deletions

View file

@ -73,6 +73,7 @@ const behaviorTests = [
".test-dist/tests/mcp-toolkit-installer-seams.test.js",
".test-dist/tests/mcp-toolkit-catalog.test.js",
".test-dist/tests/integrations-view-toolkit-split.test.js",
".test-dist/tests/integrations-view-agent-sections.test.js",
".test-dist/tests/integrations-view-detail-dialog.test.js",
".test-dist/tests/integrations-view-state.test.js",
".test-dist/tests/familiars-view-detail-dialog.test.js",

View file

@ -131,6 +131,7 @@ const controlCenterFamiliarsHelpersSource = readFileSync(join(appDir, "src", "re
const controlCenterFamiliarsEffectsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "familiars-view-effects.ts"), "utf8");
const controlCenterIntegrationsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view.tsx"), "utf8");
const controlCenterIntegrationsDetailDialogSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-detail-dialog.tsx"), "utf8");
const controlCenterIntegrationsAgentSectionsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-agent-sections.tsx"), "utf8");
const controlCenterIntegrationsStateSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-state.ts"), "utf8");
const controlCenterIntegrationsSharedSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-shared.tsx"), "utf8");
const controlCenterIntegrationsToolingSectionsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-tooling-sections.tsx"), "utf8");
@ -237,6 +238,7 @@ const agentSetupCombinedSource = `${agentSetupSource}\n${agentSetupActionsSource
const controlCenterIntegrationsCombinedSource = [
controlCenterIntegrationsSource,
controlCenterIntegrationsDetailDialogSource,
controlCenterIntegrationsAgentSectionsSource,
controlCenterIntegrationsSharedSource,
controlCenterIntegrationsToolingSectionsSource,
controlCenterIntegrationsMcpToolkitSectionSource,
@ -608,6 +610,8 @@ assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-detai
assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-state"/, "Control Center integrations route must import the extracted integrations state seam.");
assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-shared"/, "Control Center integrations route must import the extracted integrations shared seam.");
assert.match(controlCenterIntegrationsDetailDialogSource, /export function IntegrationsDetailDialog/, "Control Center integrations detail dialog seam must export the selected-integration overlay.");
assert.match(controlCenterIntegrationsDetailDialogSource, /from "\.\/integrations-view-agent-sections"/, "Control Center integrations detail dialog seam must import the extracted agent sections.");
assert.match(controlCenterIntegrationsAgentSectionsSource, /export function IntegrationCommandModeSection/, "Control Center integrations agent seam must export the command-mode notice.");
assert.match(controlCenterIntegrationsDetailDialogSource, /from "\.\/integrations-view-tooling-sections"/, "Control Center integrations detail dialog seam must import the extracted integrations tooling seam.");
assert.match(controlCenterIntegrationsStateSource, /export function useIntegrationsViewState/, "Control Center integrations state seam must export the route hook.");
assert.match(controlCenterIntegrationsStateSource, /from "\.\/route-data-loaders"/, "Control Center integrations state seam must compose shared route data loaders.");

View file

@ -25,8 +25,9 @@ surface they depend on.
- `integrations-view.tsx` owns the integrations-route shell, card grid, and
message/loading presentation while composing the extracted detail overlay.
- `integrations-view-detail-dialog.tsx` owns the selected-integration overlay,
Claude/OpenCode/Cursor detail sections, and the remaining tooling dialog
composition.
the tooling/Pi/toolkit dialog composition, and the overlay shell.
- `integrations-view-agent-sections.tsx` owns the Claude/OpenCode/Cursor detail
sections plus the shared command-mode notice.
- `integrations-view-shared.tsx` owns route-local integration icons,
command-path field UI, and status-tone helpers.
- `integrations-view-tooling-sections.tsx` owns the MCP tool-server,

View file

@ -0,0 +1,211 @@
import { useI18n } from "../i18n";
import {
PathField,
claudeStatusTone,
cursorStatusTone,
opencodeStatusTone,
} from "./integrations-view-shared";
import * as Shared from "./shared";
import type {
AgentSetupAction,
AgentSetupCommandPaths,
AgentSetupSnapshot,
} from "./shared";
const {
Button,
HookIcon,
InstallIcon,
MemoryIcon,
NextIcon,
RefreshIcon,
RemoveIcon,
ReplaceIcon,
StatusPill,
commandModeLabelKeys,
} = Shared;
export function IntegrationCommandModeSection({
snapshot,
selectedId,
}: {
snapshot: AgentSetupSnapshot;
selectedId: string;
}) {
const { t } = useI18n();
if (selectedId !== "claude" && selectedId !== "opencode" && selectedId !== "cursor") {
return null;
}
return <section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.commandSource")}</small><strong>{t("integrations.cliMode")}</strong></div>
<p className="text-sm text-slatecopy">{t("integrations.linkToCentralPanel")}</p>
<p className="text-xs text-slatecopy mt-2">{t(commandModeLabelKeys[snapshot.commandMode])}</p>
</section>;
}
export function ClaudeIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onUpdatePath,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.statusRouting")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.status.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.status.details}</small>
</div>
<StatusPill tone={claudeStatusTone(snapshot.status.state)}>{snapshot.status.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.configuration")}</small><strong>{t("integrations.commandPaths")}</strong></div>
<div className="flex flex-col gap-3">
<PathField label={t("integrations.claudeCommand")} value={snapshot.commandPaths.claude} placeholder="claude" onSave={(value) => void onUpdatePath("claude", value)} disabled={isBusy} />
</div>
</section>
<div className="grid grid-cols-2 gap-3">
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.optional")}</small><strong>{t("integrations.claudeHooks")}</strong></div>
<div className="flex items-center justify-between mb-2">
<StatusPill tone={snapshot.hookStatus.status === "installed" ? "green" : "blue"}>{snapshot.hookStatus.status}</StatusPill>
</div>
<div className="flex flex-col gap-2">
<Button variant="primary" size="compact" icon={<HookIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installingHooks"), "install-hooks")}>{t("integrations.installHooks")}</Button>
<Button variant="danger" size="compact" icon={<RemoveIcon />} disabled={isBusy || snapshot.hookStatus.status === "needs_setup"} onClick={() => void onRunIntegrationAction(t("integrations.busy.removingHooks"), "uninstall-hooks")}>{t("integrations.removeHooks")}</Button>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.included")}</small><strong>{t("integrations.instructions")}</strong></div>
<div className="flex items-center justify-between mb-2">
<StatusPill tone={snapshot.memoryStatus.state === "installed" ? "green" : "blue"}>{snapshot.memoryStatus.state}</StatusPill>
</div>
<Button variant="secondary" size="compact" icon={<MemoryIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.updatingInstructions"), "install-memory")}>{t("integrations.updateInstructions")}</Button>
</section>
</div>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.status.canConfigure && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "configure")}>{t("integrations.installMcp")}</Button>}
{snapshot.status.canReplace && <Button variant="warning" icon={<ReplaceIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.replacing"), "replace")}>{t("integrations.replaceMcp")}</Button>}
{snapshot.status.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "remove")}>{t("integrations.removeMcp")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
</>;
}
export function OpenCodeIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onUpdatePath,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.globalSetup")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.opencodeStatus.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.opencodeStatus.details}</small>
</div>
<StatusPill tone={opencodeStatusTone(snapshot.opencodeStatus.state)}>{snapshot.opencodeStatus.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.configuration")}</small><strong>{t("integrations.commandPaths")}</strong></div>
<div className="flex flex-col gap-3">
<PathField label={t("integrations.opencodeCommand")} value={snapshot.commandPaths.opencode} placeholder="opencode" onSave={(value) => void onUpdatePath("opencode", value)} disabled={isBusy} />
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.opencodeStatus.canInstall && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "opencode-install")}>{t("integrations.installGlobal")}</Button>}
{snapshot.opencodeStatus.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "opencode-remove")}>{t("integrations.removeGlobal")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
<details className="plugin-section group">
<summary className="cursor-pointer list-none flex items-center justify-between">
<div className="plugin-section-title"><small>{t("integrations.advanced")}</small><strong>{t("integrations.configPreview")}</strong></div>
<span className="text-brand group-open:rotate-180 transition-transform"><NextIcon /></span>
</summary>
<pre className="mt-3 p-3 rounded-xl bg-navy/5 text-[10px] font-mono overflow-x-auto border border-navy/5">
{JSON.stringify(snapshot.opencodePreview.configPreview, null, 2)}
</pre>
</details>
</>;
}
export function CursorIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.globalMcp")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.cursorStatus.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.cursorStatus.details}</small>
</div>
<StatusPill tone={cursorStatusTone(snapshot.cursorStatus.state)}>{snapshot.cursorStatus.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.cursorStatus.canInstall && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "cursor-install")}>{t("integrations.installMcp")}</Button>}
{snapshot.cursorStatus.canReplace && <Button variant="warning" icon={<ReplaceIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.replacing"), "cursor-replace")}>{t("integrations.replaceMcp")}</Button>}
{snapshot.cursorStatus.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "cursor-remove")}>{t("integrations.removeMcp")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
<details className="plugin-section group">
<summary className="cursor-pointer list-none flex items-center justify-between">
<div className="plugin-section-title"><small>{t("integrations.advanced")}</small><strong>{t("integrations.rulesPreview")}</strong></div>
<span className="text-brand group-open:rotate-180 transition-transform"><NextIcon /></span>
</summary>
<p className="mt-3 text-xs text-slatecopy">{snapshot.cursorPreview.rulesPath}</p>
<pre className="mt-3 p-3 rounded-xl bg-navy/5 text-[10px] font-mono overflow-x-auto border border-navy/5">
{snapshot.cursorPreview.rulesContent}
</pre>
</details>
</>;
}

View file

@ -1,12 +1,12 @@
import type { ReactNode } from "react";
import { useI18n } from "../i18n";
import {
IntegrationIcon,
PathField,
claudeStatusTone,
cursorStatusTone,
opencodeStatusTone,
} from "./integrations-view-shared";
ClaudeIntegrationSections,
CursorIntegrationSections,
IntegrationCommandModeSection,
OpenCodeIntegrationSections,
} from "./integrations-view-agent-sections";
import { IntegrationIcon } from "./integrations-view-shared";
import {
FamiliarOSMcpServerSections,
McpToolServersSection,
@ -28,15 +28,6 @@ import type {
const {
Button,
CloseIcon,
HookIcon,
InstallIcon,
MemoryIcon,
NextIcon,
RefreshIcon,
RemoveIcon,
ReplaceIcon,
StatusPill,
commandModeLabelKeys,
} = Shared;
type IntegrationsDetailDialogProps = {
@ -109,13 +100,7 @@ export function IntegrationsDetailDialog({
integrationDialogTitleId={integrationDialogTitleId}
onClose={() => onSelectedIdChange(null)}
>
{(selectedId === "claude" || selectedId === "opencode" || selectedId === "cursor") && (
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.commandSource")}</small><strong>{t("integrations.cliMode")}</strong></div>
<p className="text-sm text-slatecopy">{t("integrations.linkToCentralPanel")}</p>
<p className="text-xs text-slatecopy mt-2">{t(commandModeLabelKeys[snapshot.commandMode])}</p>
</section>
)}
<IntegrationCommandModeSection snapshot={snapshot} selectedId={selectedId} />
{selectedId === "mcp-tool-servers" && (
<McpToolServersSection
@ -219,169 +204,3 @@ function GlassDialog({
<div className="flex flex-col gap-5 mt-4">{children}</div>
</Shared.GlassCard>;
}
function ClaudeIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onUpdatePath,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.statusRouting")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.status.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.status.details}</small>
</div>
<StatusPill tone={claudeStatusTone(snapshot.status.state)}>{snapshot.status.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.configuration")}</small><strong>{t("integrations.commandPaths")}</strong></div>
<div className="flex flex-col gap-3">
<PathField label={t("integrations.claudeCommand")} value={snapshot.commandPaths.claude} placeholder="claude" onSave={(value) => void onUpdatePath("claude", value)} disabled={isBusy} />
</div>
</section>
<div className="grid grid-cols-2 gap-3">
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.optional")}</small><strong>{t("integrations.claudeHooks")}</strong></div>
<div className="flex items-center justify-between mb-2">
<StatusPill tone={snapshot.hookStatus.status === "installed" ? "green" : "blue"}>{snapshot.hookStatus.status}</StatusPill>
</div>
<div className="flex flex-col gap-2">
<Button variant="primary" size="compact" icon={<HookIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installingHooks"), "install-hooks")}>{t("integrations.installHooks")}</Button>
<Button variant="danger" size="compact" icon={<RemoveIcon />} disabled={isBusy || snapshot.hookStatus.status === "needs_setup"} onClick={() => void onRunIntegrationAction(t("integrations.busy.removingHooks"), "uninstall-hooks")}>{t("integrations.removeHooks")}</Button>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.included")}</small><strong>{t("integrations.instructions")}</strong></div>
<div className="flex items-center justify-between mb-2">
<StatusPill tone={snapshot.memoryStatus.state === "installed" ? "green" : "blue"}>{snapshot.memoryStatus.state}</StatusPill>
</div>
<Button variant="secondary" size="compact" icon={<MemoryIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.updatingInstructions"), "install-memory")}>{t("integrations.updateInstructions")}</Button>
</section>
</div>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.status.canConfigure && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "configure")}>{t("integrations.installMcp")}</Button>}
{snapshot.status.canReplace && <Button variant="warning" icon={<ReplaceIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.replacing"), "replace")}>{t("integrations.replaceMcp")}</Button>}
{snapshot.status.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "remove")}>{t("integrations.removeMcp")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
</>;
}
function OpenCodeIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onUpdatePath,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.globalSetup")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.opencodeStatus.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.opencodeStatus.details}</small>
</div>
<StatusPill tone={opencodeStatusTone(snapshot.opencodeStatus.state)}>{snapshot.opencodeStatus.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.configuration")}</small><strong>{t("integrations.commandPaths")}</strong></div>
<div className="flex flex-col gap-3">
<PathField label={t("integrations.opencodeCommand")} value={snapshot.commandPaths.opencode} placeholder="opencode" onSave={(value) => void onUpdatePath("opencode", value)} disabled={isBusy} />
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.opencodeStatus.canInstall && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "opencode-install")}>{t("integrations.installGlobal")}</Button>}
{snapshot.opencodeStatus.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "opencode-remove")}>{t("integrations.removeGlobal")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
<details className="plugin-section group">
<summary className="cursor-pointer list-none flex items-center justify-between">
<div className="plugin-section-title"><small>{t("integrations.advanced")}</small><strong>{t("integrations.configPreview")}</strong></div>
<span className="text-brand group-open:rotate-180 transition-transform"><NextIcon /></span>
</summary>
<pre className="mt-3 p-3 rounded-xl bg-navy/5 text-[10px] font-mono overflow-x-auto border border-navy/5">
{JSON.stringify(snapshot.opencodePreview.configPreview, null, 2)}
</pre>
</details>
</>;
}
function CursorIntegrationSections({
snapshot,
isBusy,
onRunIntegrationAction,
onReloadIntegrations,
}: {
snapshot: AgentSetupSnapshot;
isBusy: boolean;
onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise<void>;
onReloadIntegrations: () => Promise<void>;
}) {
const { t } = useI18n();
return <>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.connection")}</small><strong>{t("integrations.globalMcp")}</strong></div>
<div className="flex items-center justify-between p-3 rounded-2xl bg-blue-50/50 border border-blue-100/50">
<div className="flex flex-col">
<strong className="text-sm text-navy">{snapshot.cursorStatus.label}</strong>
<small className="text-xs text-slatecopy">{snapshot.cursorStatus.details}</small>
</div>
<StatusPill tone={cursorStatusTone(snapshot.cursorStatus.state)}>{snapshot.cursorStatus.state}</StatusPill>
</div>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>{t("integrations.actions")}</small><strong>{t("integrations.management")}</strong></div>
<div className="grid grid-cols-2 gap-2">
{snapshot.cursorStatus.canInstall && <Button variant="primary" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.installing"), "cursor-install")}>{t("integrations.installMcp")}</Button>}
{snapshot.cursorStatus.canReplace && <Button variant="warning" icon={<ReplaceIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.replacing"), "cursor-replace")}>{t("integrations.replaceMcp")}</Button>}
{snapshot.cursorStatus.canRemove && <Button variant="danger" icon={<RemoveIcon />} disabled={isBusy} onClick={() => void onRunIntegrationAction(t("integrations.busy.removing"), "cursor-remove")}>{t("integrations.removeMcp")}</Button>}
<Button variant="secondary" icon={<RefreshIcon />} disabled={isBusy} onClick={() => void onReloadIntegrations()}>{t("integrations.refreshStatus")}</Button>
</div>
</section>
<details className="plugin-section group">
<summary className="cursor-pointer list-none flex items-center justify-between">
<div className="plugin-section-title"><small>{t("integrations.advanced")}</small><strong>{t("integrations.rulesPreview")}</strong></div>
<span className="text-brand group-open:rotate-180 transition-transform"><NextIcon /></span>
</summary>
<p className="mt-3 text-xs text-slatecopy">{snapshot.cursorPreview.rulesPath}</p>
<pre className="mt-3 p-3 rounded-xl bg-navy/5 text-[10px] font-mono overflow-x-auto border border-navy/5">
{snapshot.cursorPreview.rulesContent}
</pre>
</details>
</>;
}

View file

@ -0,0 +1,17 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
const integrationsViewDetailDialogSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-detail-dialog.tsx"), "utf8");
const integrationsViewAgentSectionsSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-agent-sections.tsx"), "utf8");
assert.match(integrationsViewDetailDialogSource, /from "\.\/integrations-view-agent-sections"/, "Integrations detail dialog must import the extracted agent sections seam.");
assert.match(integrationsViewAgentSectionsSource, /export function IntegrationCommandModeSection/, "Integrations agent seam must export the command-mode notice.");
assert.match(integrationsViewAgentSectionsSource, /export function ClaudeIntegrationSections/, "Integrations agent seam must export the Claude detail sections.");
assert.match(integrationsViewAgentSectionsSource, /export function OpenCodeIntegrationSections/, "Integrations agent seam must export the OpenCode detail sections.");
assert.match(integrationsViewAgentSectionsSource, /export function CursorIntegrationSections/, "Integrations agent seam must export the Cursor detail sections.");
assert.match(integrationsViewAgentSectionsSource, /integrations\.rulesPreview/, "Integrations agent seam must retain the Cursor rules preview.");
console.error("Integrations agent sections seam validation passed.");

View file

@ -6,11 +6,13 @@ import { fileURLToPath } from "node:url";
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
const integrationsViewSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view.tsx"), "utf8");
const integrationsViewDetailDialogSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-detail-dialog.tsx"), "utf8");
const integrationsViewAgentSectionsSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-agent-sections.tsx"), "utf8");
assert.match(integrationsViewSource, /from "\.\/integrations-view-detail-dialog"/, "Integrations view must import the extracted detail dialog seam.");
assert.match(integrationsViewSource, /<IntegrationsDetailDialog/, "Integrations view must render the extracted detail dialog seam.");
assert.match(integrationsViewDetailDialogSource, /export function IntegrationsDetailDialog/, "Integrations detail dialog seam must export the selected-integration overlay.");
assert.match(integrationsViewDetailDialogSource, /<FamiliarOSMcpServerSections/, "Integrations detail dialog seam must retain the FamiliarOS MCP server section.");
assert.match(integrationsViewDetailDialogSource, /integrations\.rulesPreview/, "Integrations detail dialog seam must retain the Cursor rules preview.");
assert.match(integrationsViewDetailDialogSource, /from "\.\/integrations-view-agent-sections"/, "Integrations detail dialog seam must import the extracted agent sections.");
assert.match(integrationsViewAgentSectionsSource, /integrations\.rulesPreview/, "Integrations agent seam must retain the Cursor rules preview.");
console.error("Integrations detail dialog seam validation passed.");

View file

@ -6,19 +6,23 @@ import { fileURLToPath } from "node:url";
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
const integrationsViewSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view.tsx"), "utf8");
const integrationsViewDetailDialogSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-detail-dialog.tsx"), "utf8");
const integrationsViewAgentSectionsSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-agent-sections.tsx"), "utf8");
const integrationsViewSharedSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-shared.tsx"), "utf8");
const integrationsViewToolingSectionsSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-tooling-sections.tsx"), "utf8");
const integrationsCombinedSource = [
integrationsViewSource,
integrationsViewDetailDialogSource,
integrationsViewAgentSectionsSource,
integrationsViewSharedSource,
integrationsViewToolingSectionsSource,
].join("\n");
assert.match(integrationsViewSource, /from "\.\/integrations-view-detail-dialog"/, "Integrations view must import the extracted detail dialog seam.");
assert.match(integrationsViewSource, /from "\.\/integrations-view-shared"/, "Integrations view must import the extracted shared helper seam.");
assert.match(integrationsViewDetailDialogSource, /from "\.\/integrations-view-agent-sections"/, "Integrations detail dialog seam must import the extracted agent sections.");
assert.match(integrationsViewDetailDialogSource, /from "\.\/integrations-view-tooling-sections"/, "Integrations detail dialog seam must import the extracted tooling section seam.");
assert.match(integrationsViewDetailDialogSource, /export function IntegrationsDetailDialog/, "Integrations detail dialog seam must export the selected-integration overlay.");
assert.match(integrationsViewAgentSectionsSource, /export function IntegrationCommandModeSection/, "Integrations agent seam must export the command-mode notice.");
assert.match(integrationsViewDetailDialogSource, /<McpToolServersSection/, "Integrations detail dialog seam must render the extracted MCP tool servers section.");
assert.match(integrationsViewDetailDialogSource, /<FamiliarOSMcpServerSections/, "Integrations detail dialog seam must render the extracted FamiliarOS MCP server section.");
assert.match(integrationsViewDetailDialogSource, /<PiIntegrationSection/, "Integrations detail dialog seam must render the extracted Pi guidance section.");