Extract integrations toolkit panel seam

This commit is contained in:
OpenPets Dev 2026-06-19 02:38:36 +00:00
parent d7a5306317
commit bb5aa4fc9e
6 changed files with 300 additions and 261 deletions

View file

@ -62,6 +62,7 @@ const behaviorTests = [
".test-dist/tests/knowledge-store.test.js",
".test-dist/tests/control-center-service-barrels.test.js",
".test-dist/tests/mcp-toolkit-catalog.test.js",
".test-dist/tests/integrations-view-toolkit-split.test.js",
".test-dist/tests/integrations-view-state.test.js",
".test-dist/tests/familiars-view-state.test.js",
".test-dist/tests/agent-setup-actions.test.js",

View file

@ -119,6 +119,7 @@ const controlCenterIntegrationsSource = readFileSync(join(appDir, "src", "render
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");
const controlCenterIntegrationsMcpToolkitSectionSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-mcp-toolkit-section.tsx"), "utf8");
const openApiChatSource = readFileSync(join(appDir, "src", "openapi-chat.ts"), "utf8");
const openApiChatSettingsSource = readFileSync(join(appDir, "src", "openapi-chat-settings.ts"), "utf8");
const openApiChatPresentationSource = readFileSync(join(appDir, "src", "openapi-chat-presentation.ts"), "utf8");
@ -204,6 +205,7 @@ const controlCenterIntegrationsCombinedSource = [
controlCenterIntegrationsSource,
controlCenterIntegrationsSharedSource,
controlCenterIntegrationsToolingSectionsSource,
controlCenterIntegrationsMcpToolkitSectionSource,
].join("\n");
const controlCenterSharedCombinedSource = [
controlCenterSharedSource,
@ -490,7 +492,10 @@ assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-tooli
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.");
assert.match(controlCenterIntegrationsStateSource, /from "\.\/integrations-view-shared"/, "Control Center integrations state seam must compose the extracted integrations shared helpers.");
assert.match(controlCenterIntegrationsToolingSectionsSource, /from "\.\/integrations-view-mcp-toolkit-section"/, "Control Center integrations tooling shell must import the extracted MCP toolkit section seam.");
assert.match(controlCenterIntegrationsToolingSectionsSource, /export function McpToolkitSection/, "Control Center integrations tooling seam must own the curated toolkit panel.");
assert.match(controlCenterIntegrationsMcpToolkitSectionSource, /export function McpToolkitSection/, "Control Center integrations toolkit seam must export the curated toolkit panel.");
assert.match(controlCenterIntegrationsMcpToolkitSectionSource, /buildPersistentToolkitBundle/, "Control Center integrations toolkit seam must own persistent bundle composition.");
assert.match(controlCenterIntegrationsCombinedSource, /Persistent Full Access/, "Control Center integrations seam must retain the persistent toolkit install path.");
assert.match(controlCenterIntegrationsCombinedSource, /pi install npm:@familiaros\/pi/, "Control Center integrations seam must retain Pi install guidance.");
assert.match(openApiChatSource, /from "\.\/openapi-chat-settings(?:\.js)?"/, "OpenAPI chat must import the extracted settings seam.");

View file

@ -10,7 +10,7 @@ React/Tailwind source for the Control Center management UI. This renderer presen
- **Route Modules**: Each major Control Center route now lives in `control-center/*.tsx`, so dashboard, familiars, integrations, plugins, and settings can evolve independently without re-growing a single renderer god file.
- **Dashboard**: Reads a narrowed dashboard snapshot for default familiar preview, install/catalog counts, plugin health, update status, and activity totals.
- **Familiars**: `familiars-view-state.ts` now owns installed/catalog/Codex data loading, selection state, and familiar actions while `familiars-view-helpers.ts` holds the denser familiar list, remote-page planning, focus/status helpers, and `familiars-view-effects.ts` owns the detail focus trap plus catalog/logging hydration effects so the route shell stays focused on filters, cards, detail rendering, and animated sprite previews.
- **Integrations**: Card-first setup UI for Claude Code, OpenCode, Cursor, and Pi guidance, with `integrations-view-state.ts` owning route state/effects/actions, extracted integrations shared helpers plus tooling detail sections, and `mcp-toolkit-catalog.ts` staying as a thin lookup surface over a small data barrel plus tier-split starter/system/advanced catalog seams and the shared type contract in `mcp-toolkit-catalog-types.ts`.
- **Integrations**: Card-first setup UI for Claude Code, OpenCode, Cursor, and Pi guidance, with `integrations-view-state.ts` owning route state/effects/actions, extracted integrations shared helpers plus tooling detail sections, the curated toolkit panel now split into its own route-local seam, and `mcp-toolkit-catalog.ts` staying as a thin lookup surface over a small data barrel plus tier-split starter/system/advanced catalog seams and the shared type contract in `mcp-toolkit-catalog-types.ts`.
- **Plugins**: Gallery-first plugin hub for installed/catalog/local/broken filters, catalog refresh, local load, install/update/uninstall, enable/disable, config modal, command execution, runtime/status display, and broken-state feedback.
- **Plugin Config Editor Seam**: `plugins-view-config-helpers.ts` now owns config draft materialization while `plugins-view-config-fields.tsx` owns the recursive config field editor, keeping the main plugins route focused on state, install flows, and inspector actions.
- **Settings**: `settings-view.tsx` now orchestrates route state/data loading while `settings-view-state.ts` owns route-local state bundling, `settings-view-state-actions.ts` stays as a thin mutation shell over extracted plugin and knowledge binders plus shared action-option types, route-local tab modules own general, reactions, plugin-settings shell, extracted OpenAPI/TTS/plugin-platform panes, the TTS pane now composes extracted row and prop-type seams, knowledge-store panes, and extracted settings helper shells/actions keep mutations out of the route component.
@ -37,7 +37,8 @@ React/Tailwind source for the Control Center management UI. This renderer presen
- `control-center/integrations-view.tsx`: Coding-agent integrations route shell and action wiring.
- `control-center/integrations-view-state.ts`: Integrations route state, effect loading, async action wiring, and derived card/detail metadata.
- `control-center/integrations-view-shared.tsx`: Route-local integrations icons, path field, and status-tone helpers.
- `control-center/integrations-view-tooling-sections.tsx`: Extracted MCP-tooling, FamiliarOS-MCP, curated toolkit, and Pi detail panels.
- `control-center/integrations-view-tooling-sections.tsx`: Integrations tooling shell for MCP-tool servers, FamiliarOS MCP controls, Pi guidance, and the extracted curated toolkit panel.
- `control-center/integrations-view-mcp-toolkit-section.tsx`: Extracted curated toolkit panel with manual versus persistent host install flows, catalog details, and follow-up guidance.
- `control-center/plugins-view.tsx`: Plugin management route.
- `control-center/plugins-view-config-helpers.ts`: Extracted plugin config draft/materialization helpers for the plugins route.
- `control-center/plugins-view-config-fields.tsx`: Extracted recursive plugin config field editor for the plugins route inspector.

View file

@ -0,0 +1,268 @@
import { getMcpToolkitEntry, mcpToolkitEntries, mcpToolkitTierLabels } from "../mcp-toolkit-catalog";
import {
buildPersistentToolkitBundle,
Button,
CopyIcon,
ExternalLinkIcon,
InstallIcon,
StatusPill,
type McpToolkitInstallMode,
type McpToolkitInstallResult,
type McpToolkitPersistentTarget,
} from "./shared";
import { getPersistentToolkitFollowUps } from "./shared";
import { mcpToolkitTrustTone } from "./integrations-view-shared";
export type McpToolkitSectionProps = {
selectedToolkitId: string;
onSelectedToolkitIdChange: (toolkitId: string) => void;
toolkitInstallMode: McpToolkitInstallMode;
onToolkitInstallModeChange: (mode: McpToolkitInstallMode) => void;
toolkitPersistentTarget: McpToolkitPersistentTarget;
onToolkitPersistentTargetChange: (target: McpToolkitPersistentTarget) => void;
toolkitLastInstall: McpToolkitInstallResult | null;
isBusy: boolean;
onInstallToolkitHost: () => Promise<void>;
onCopyText: (label: string, value: string) => Promise<void>;
onOpenDocs: (url: string) => Promise<void>;
};
export function McpToolkitSection({
selectedToolkitId,
onSelectedToolkitIdChange,
toolkitInstallMode,
onToolkitInstallModeChange,
toolkitPersistentTarget,
onToolkitPersistentTargetChange,
toolkitLastInstall,
isBusy,
onInstallToolkitHost,
onCopyText,
onOpenDocs,
}: McpToolkitSectionProps) {
const selectedToolkit = getMcpToolkitEntry(selectedToolkitId) ?? mcpToolkitEntries[0];
const persistentBundle = buildPersistentToolkitBundle(toolkitPersistentTarget);
const persistentFollowUps = getPersistentToolkitFollowUps();
return (
<>
<section className="plugin-section">
<div className="plugin-section-title"><small>Install Choice</small><strong>Toolkit behavior</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
Choose how you want to set up MCP tools for external host agents like Claude Code or Codex CLI.
</p>
<div className="plugin-chip-list">
<button type="button" className={`plugin-chip ${toolkitInstallMode === "manual" ? "active" : ""}`} onClick={() => onToolkitInstallModeChange("manual")}>
Manual Setup
</button>
<button type="button" className={`plugin-chip ${toolkitInstallMode === "persistent" ? "active" : ""}`} onClick={() => onToolkitInstallModeChange("persistent")}>
Persistent Full Access
</button>
</div>
<small className="text-xs text-slatecopy leading-relaxed">
<strong>Manual Setup</strong> shows copy-paste commands for each tool so you can install them yourself into your host agent. <strong>Persistent Full Access</strong> lets FamiliarOS register the supported baseline automatically into Claude Code or Codex CLI.
</small>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>Catalog</small><strong>Browse & Details</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
Click any tool below to view its details, installation snippets, and safety notes.
</p>
<div className="flex flex-col gap-4 mt-3">
{(["starter", "system", "advanced"] as const).map((tier) => {
const entries = mcpToolkitEntries.filter((entry) => entry.tier === tier);
return (
<div key={tier} className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<strong className="text-sm text-navy">{mcpToolkitTierLabels[tier]}</strong>
<small className="text-xs text-slatecopy">{entries.length} options</small>
</div>
<div className="plugin-chip-list">
{entries.map((entry) => (
<button key={entry.id} type="button" className={`plugin-chip ${selectedToolkit.id === entry.id ? "active" : ""}`} onClick={() => onSelectedToolkitIdChange(entry.id)}>
{entry.name}
</button>
))}
</div>
</div>
);
})}
</div>
</section>
{toolkitInstallMode === "manual" ? (
<>
<section className="plugin-section">
<div className="flex items-start justify-between gap-3">
<div className="plugin-section-title">
<small>{mcpToolkitTierLabels[selectedToolkit.tier]}</small>
<strong>{selectedToolkit.name}</strong>
</div>
<div className="flex items-center gap-2">
<StatusPill tone={mcpToolkitTrustTone(selectedToolkit.trust)}>{selectedToolkit.badge}</StatusPill>
<Button variant="secondary" size="compact" icon={<ExternalLinkIcon />} onClick={() => void onOpenDocs(selectedToolkit.docsUrl)}>
{selectedToolkit.docsLabel ?? "Open docs"}
</Button>
</div>
</div>
<p className="text-sm text-slatecopy leading-relaxed">{selectedToolkit.summary}</p>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<div className="rounded-2xl border border-blue-100/70 bg-blue-50/35 p-4">
<strong className="block text-sm text-navy mb-1">Why it matters</strong>
<p className="m-0 text-sm text-slatecopy leading-relaxed">{selectedToolkit.whyItMatters}</p>
</div>
<div className="rounded-2xl border border-blue-100/70 bg-blue-50/35 p-4">
<strong className="block text-sm text-navy mb-1">VectorShell fit</strong>
<p className="m-0 text-sm text-slatecopy leading-relaxed">{selectedToolkit.vectorShellFit}</p>
</div>
</div>
<div className="rounded-2xl border border-amber-100/70 bg-amber-50/55 p-4">
<strong className="block text-sm text-amber-900 mb-1">Permission boundary</strong>
<p className="m-0 text-sm text-amber-900/80 leading-relaxed">{selectedToolkit.safety}</p>
</div>
<div className="flex flex-wrap gap-2">
{selectedToolkit.tags.map((tag) => (
<span key={tag} className="pill pill-slate">{tag}</span>
))}
</div>
</section>
{selectedToolkit.snippets?.length ? (
<section className="plugin-section">
<div className="plugin-section-title"><small>Install Surface</small><strong>Ready-to-paste snippets</strong></div>
<div className="flex flex-col gap-3">
{selectedToolkit.snippets.map((snippet) => (
<div key={snippet.id} className="rounded-2xl border border-blue-100/70 bg-navy/5 p-4">
<div className="mb-2 flex items-start justify-between gap-3">
<div className="min-w-0">
<strong className="block text-sm text-navy">{snippet.label}</strong>
{snippet.description && <small className="mt-1 block text-xs text-slatecopy leading-relaxed">{snippet.description}</small>}
</div>
<Button variant="secondary" size="compact" icon={<CopyIcon />} onClick={() => void onCopyText(snippet.label, snippet.value)}>
Copy
</Button>
</div>
<pre className="m-0 overflow-x-auto rounded-xl border border-navy/5 bg-white/80 p-3 text-[10px] leading-relaxed text-navy">{snippet.value}</pre>
</div>
))}
</div>
</section>
) : null}
{selectedToolkit.notes?.length ? (
<section className="plugin-section">
<div className="plugin-section-title"><small>Notes</small><strong>Selection guidance</strong></div>
<ul className="m-0 pl-5 text-sm text-slatecopy">
{selectedToolkit.notes.map((note) => <li key={note}>{note}</li>)}
</ul>
</section>
) : null}
</>
) : (
<>
<section className="plugin-section">
<div className="plugin-section-title"><small>Persistent Install</small><strong>Choose your host</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
This mode is for the install it for good path. FamiliarOS can install the supported persistent baseline for you now, and it also shows the exact matching bundle if you prefer to run it yourself.
</p>
<div className="plugin-chip-list">
<button type="button" className={`plugin-chip ${toolkitPersistentTarget === "claude-user" ? "active" : ""}`} onClick={() => onToolkitPersistentTargetChange("claude-user")}>
Claude User Scope
</button>
<button type="button" className={`plugin-chip ${toolkitPersistentTarget === "codex-global" ? "active" : ""}`} onClick={() => onToolkitPersistentTargetChange("codex-global")}>
Codex Global
</button>
</div>
</section>
<section className="plugin-section">
<div className="flex items-start justify-between gap-3">
<div className="plugin-section-title">
<small>Persistent Full Access</small>
<strong>{persistentBundle.label}</strong>
</div>
<div className="flex flex-wrap gap-2">
<Button variant="primary" size="compact" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onInstallToolkitHost()}>
Install Now
</Button>
<Button variant="secondary" size="compact" icon={<CopyIcon />} onClick={() => void onCopyText(persistentBundle.label, persistentBundle.value)}>
Copy Bundle
</Button>
</div>
</div>
<p className="text-sm text-slatecopy leading-relaxed">{persistentBundle.description}</p>
<pre className="m-0 overflow-x-auto rounded-xl border border-navy/5 bg-white/80 p-3 text-[10px] leading-relaxed text-navy">{persistentBundle.value}</pre>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>Included Now</small><strong>Stable persistent baseline</strong></div>
<div className="flex flex-wrap gap-2">
{["filesystem", "playwright", "browser-use", "memory", "fetch-web", "sequential-thinking"].map((id) => {
const entry = getMcpToolkitEntry(id);
return entry ? <span key={entry.id} className="pill pill-green">{entry.name}</span> : null;
})}
<span className="pill pill-green">Context7 / Docs</span>
</div>
<small className="text-xs text-slatecopy leading-relaxed">
These are the lanes FamiliarOS can attempt automatically today. Browser Use and Fetch / Web still depend on `uvx`, and Browser Use still needs its own runtime credential in the host environment.
</small>
</section>
{toolkitLastInstall && toolkitLastInstall.target === toolkitPersistentTarget && (
<section className="plugin-section">
<div className="plugin-section-title"><small>Last Install</small><strong>{toolkitLastInstall.label}</strong></div>
{toolkitLastInstall.installed.length > 0 && (
<div className="flex flex-col gap-3">
{toolkitLastInstall.installed.map((entry) => (
<div key={entry.id} className="rounded-2xl border border-green-200/80 bg-green-50/70 p-4">
<div className="flex items-center justify-between gap-3">
<strong className="text-sm text-green-900">{entry.name}</strong>
<StatusPill tone="green">Installed</StatusPill>
</div>
<small className="mt-2 block text-xs text-green-900/80 leading-relaxed">{entry.detail}</small>
</div>
))}
</div>
)}
{toolkitLastInstall.skipped.length > 0 && (
<div className="mt-3 flex flex-col gap-3">
{toolkitLastInstall.skipped.map((entry) => (
<div key={entry.id} className="rounded-2xl border border-amber-200/80 bg-amber-50/75 p-4">
<div className="flex items-center justify-between gap-3">
<strong className="text-sm text-amber-900">{entry.name}</strong>
<StatusPill tone="orange">Skipped</StatusPill>
</div>
<small className="mt-2 block text-xs text-amber-900/80 leading-relaxed">{entry.detail}</small>
</div>
))}
</div>
)}
{toolkitLastInstall.notes.length > 0 && (
<ul className="mt-3 m-0 pl-5 text-sm text-slatecopy">
{toolkitLastInstall.notes.map((note) => <li key={note}>{note}</li>)}
</ul>
)}
</section>
)}
<section className="plugin-section">
<div className="plugin-section-title"><small>Manual Follow-Up</small><strong>Still better done explicitly</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
These lanes stay outside the one-shot persistent bundle for now because they are more dependent on auth, provider choice, host environment, or high-risk permission boundaries.
</p>
<div className="flex flex-wrap gap-2">
{persistentFollowUps.map((name) => (
<span key={name} className="pill pill-slate">{name}</span>
))}
</div>
</section>
</>
)}
</>
);
}

View file

@ -1,25 +1,22 @@
import { useI18n } from "../i18n";
import { getMcpToolkitEntry, mcpToolkitEntries, mcpToolkitTierLabels } from "../mcp-toolkit-catalog";
import { mcpToolkitEntries, mcpToolkitTierLabels } from "../mcp-toolkit-catalog";
import {
buildPersistentToolkitBundle,
Button,
commandModeLabelKeys,
CopyIcon,
ExternalLinkIcon,
InstallIcon,
NextIcon,
RefreshIcon,
Spinner,
StatusPill,
type AgentSetupSnapshot,
type FamiliarOSMcpServerHealth,
type FamiliarOSMcpServerPreview,
type McpToolkitInstallMode,
type McpToolkitInstallResult,
type McpToolkitPersistentTarget,
} from "./shared";
import { getPersistentToolkitFollowUps } from "./shared";
import { PathField, mcpToolkitTrustTone } from "./integrations-view-shared";
import { PathField } from "./integrations-view-shared";
import {
McpToolkitSection as McpToolkitSectionSeam,
type McpToolkitSectionProps,
} from "./integrations-view-mcp-toolkit-section";
export function McpToolServersSection({
vanillaChatTools,
@ -212,254 +209,6 @@ export function PiIntegrationSection() {
);
}
export function McpToolkitSection({
selectedToolkitId,
onSelectedToolkitIdChange,
toolkitInstallMode,
onToolkitInstallModeChange,
toolkitPersistentTarget,
onToolkitPersistentTargetChange,
toolkitLastInstall,
isBusy,
onInstallToolkitHost,
onCopyText,
onOpenDocs,
}: {
selectedToolkitId: string;
onSelectedToolkitIdChange: (toolkitId: string) => void;
toolkitInstallMode: McpToolkitInstallMode;
onToolkitInstallModeChange: (mode: McpToolkitInstallMode) => void;
toolkitPersistentTarget: McpToolkitPersistentTarget;
onToolkitPersistentTargetChange: (target: McpToolkitPersistentTarget) => void;
toolkitLastInstall: McpToolkitInstallResult | null;
isBusy: boolean;
onInstallToolkitHost: () => Promise<void>;
onCopyText: (label: string, value: string) => Promise<void>;
onOpenDocs: (url: string) => Promise<void>;
}) {
const selectedToolkit = getMcpToolkitEntry(selectedToolkitId) ?? mcpToolkitEntries[0];
const persistentBundle = buildPersistentToolkitBundle(toolkitPersistentTarget);
const persistentFollowUps = getPersistentToolkitFollowUps();
return (
<>
<section className="plugin-section">
<div className="plugin-section-title"><small>Install Choice</small><strong>Toolkit behavior</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
Choose how you want to set up MCP tools for external host agents like Claude Code or Codex CLI.
</p>
<div className="plugin-chip-list">
<button type="button" className={`plugin-chip ${toolkitInstallMode === "manual" ? "active" : ""}`} onClick={() => onToolkitInstallModeChange("manual")}>
Manual Setup
</button>
<button type="button" className={`plugin-chip ${toolkitInstallMode === "persistent" ? "active" : ""}`} onClick={() => onToolkitInstallModeChange("persistent")}>
Persistent Full Access
</button>
</div>
<small className="text-xs text-slatecopy leading-relaxed">
<strong>Manual Setup</strong> shows copy-paste commands for each tool so you can install them yourself into your host agent. <strong>Persistent Full Access</strong> lets FamiliarOS register the supported baseline automatically into Claude Code or Codex CLI.
</small>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>Catalog</small><strong>Browse & Details</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
Click any tool below to view its details, installation snippets, and safety notes.
</p>
<div className="flex flex-col gap-4 mt-3">
{(["starter", "system", "advanced"] as const).map((tier) => {
const entries = mcpToolkitEntries.filter((entry) => entry.tier === tier);
return (
<div key={tier} className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<strong className="text-sm text-navy">{mcpToolkitTierLabels[tier]}</strong>
<small className="text-xs text-slatecopy">{entries.length} options</small>
</div>
<div className="plugin-chip-list">
{entries.map((entry) => (
<button key={entry.id} type="button" className={`plugin-chip ${selectedToolkit.id === entry.id ? "active" : ""}`} onClick={() => onSelectedToolkitIdChange(entry.id)}>
{entry.name}
</button>
))}
</div>
</div>
);
})}
</div>
</section>
{toolkitInstallMode === "manual" ? (
<>
<section className="plugin-section">
<div className="flex items-start justify-between gap-3">
<div className="plugin-section-title">
<small>{mcpToolkitTierLabels[selectedToolkit.tier]}</small>
<strong>{selectedToolkit.name}</strong>
</div>
<div className="flex items-center gap-2">
<StatusPill tone={mcpToolkitTrustTone(selectedToolkit.trust)}>{selectedToolkit.badge}</StatusPill>
<Button variant="secondary" size="compact" icon={<ExternalLinkIcon />} onClick={() => void onOpenDocs(selectedToolkit.docsUrl)}>
{selectedToolkit.docsLabel ?? "Open docs"}
</Button>
</div>
</div>
<p className="text-sm text-slatecopy leading-relaxed">{selectedToolkit.summary}</p>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<div className="rounded-2xl border border-blue-100/70 bg-blue-50/35 p-4">
<strong className="block text-sm text-navy mb-1">Why it matters</strong>
<p className="m-0 text-sm text-slatecopy leading-relaxed">{selectedToolkit.whyItMatters}</p>
</div>
<div className="rounded-2xl border border-blue-100/70 bg-blue-50/35 p-4">
<strong className="block text-sm text-navy mb-1">VectorShell fit</strong>
<p className="m-0 text-sm text-slatecopy leading-relaxed">{selectedToolkit.vectorShellFit}</p>
</div>
</div>
<div className="rounded-2xl border border-amber-100/70 bg-amber-50/55 p-4">
<strong className="block text-sm text-amber-900 mb-1">Permission boundary</strong>
<p className="m-0 text-sm text-amber-900/80 leading-relaxed">{selectedToolkit.safety}</p>
</div>
<div className="flex flex-wrap gap-2">
{selectedToolkit.tags.map((tag) => (
<span key={tag} className="pill pill-slate">{tag}</span>
))}
</div>
</section>
{selectedToolkit.snippets?.length ? (
<section className="plugin-section">
<div className="plugin-section-title"><small>Install Surface</small><strong>Ready-to-paste snippets</strong></div>
<div className="flex flex-col gap-3">
{selectedToolkit.snippets.map((snippet) => (
<div key={snippet.id} className="rounded-2xl border border-blue-100/70 bg-navy/5 p-4">
<div className="mb-2 flex items-start justify-between gap-3">
<div className="min-w-0">
<strong className="block text-sm text-navy">{snippet.label}</strong>
{snippet.description && <small className="mt-1 block text-xs text-slatecopy leading-relaxed">{snippet.description}</small>}
</div>
<Button variant="secondary" size="compact" icon={<CopyIcon />} onClick={() => void onCopyText(snippet.label, snippet.value)}>
Copy
</Button>
</div>
<pre className="m-0 overflow-x-auto rounded-xl border border-navy/5 bg-white/80 p-3 text-[10px] leading-relaxed text-navy">{snippet.value}</pre>
</div>
))}
</div>
</section>
) : null}
{selectedToolkit.notes?.length ? (
<section className="plugin-section">
<div className="plugin-section-title"><small>Notes</small><strong>Selection guidance</strong></div>
<ul className="m-0 pl-5 text-sm text-slatecopy">
{selectedToolkit.notes.map((note) => <li key={note}>{note}</li>)}
</ul>
</section>
) : null}
</>
) : (
<>
<section className="plugin-section">
<div className="plugin-section-title"><small>Persistent Install</small><strong>Choose your host</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
This mode is for the install it for good path. FamiliarOS can install the supported persistent baseline for you now, and it also shows the exact matching bundle if you prefer to run it yourself.
</p>
<div className="plugin-chip-list">
<button type="button" className={`plugin-chip ${toolkitPersistentTarget === "claude-user" ? "active" : ""}`} onClick={() => onToolkitPersistentTargetChange("claude-user")}>
Claude User Scope
</button>
<button type="button" className={`plugin-chip ${toolkitPersistentTarget === "codex-global" ? "active" : ""}`} onClick={() => onToolkitPersistentTargetChange("codex-global")}>
Codex Global
</button>
</div>
</section>
<section className="plugin-section">
<div className="flex items-start justify-between gap-3">
<div className="plugin-section-title">
<small>Persistent Full Access</small>
<strong>{persistentBundle.label}</strong>
</div>
<div className="flex flex-wrap gap-2">
<Button variant="primary" size="compact" icon={<InstallIcon />} disabled={isBusy} onClick={() => void onInstallToolkitHost()}>
Install Now
</Button>
<Button variant="secondary" size="compact" icon={<CopyIcon />} onClick={() => void onCopyText(persistentBundle.label, persistentBundle.value)}>
Copy Bundle
</Button>
</div>
</div>
<p className="text-sm text-slatecopy leading-relaxed">{persistentBundle.description}</p>
<pre className="m-0 overflow-x-auto rounded-xl border border-navy/5 bg-white/80 p-3 text-[10px] leading-relaxed text-navy">{persistentBundle.value}</pre>
</section>
<section className="plugin-section">
<div className="plugin-section-title"><small>Included Now</small><strong>Stable persistent baseline</strong></div>
<div className="flex flex-wrap gap-2">
{["filesystem", "playwright", "browser-use", "memory", "fetch-web", "sequential-thinking"].map((id) => {
const entry = getMcpToolkitEntry(id);
return entry ? <span key={entry.id} className="pill pill-green">{entry.name}</span> : null;
})}
<span className="pill pill-green">Context7 / Docs</span>
</div>
<small className="text-xs text-slatecopy leading-relaxed">
These are the lanes FamiliarOS can attempt automatically today. Browser Use and Fetch / Web still depend on `uvx`, and Browser Use still needs its own runtime credential in the host environment.
</small>
</section>
{toolkitLastInstall && toolkitLastInstall.target === toolkitPersistentTarget && (
<section className="plugin-section">
<div className="plugin-section-title"><small>Last Install</small><strong>{toolkitLastInstall.label}</strong></div>
{toolkitLastInstall.installed.length > 0 && (
<div className="flex flex-col gap-3">
{toolkitLastInstall.installed.map((entry) => (
<div key={entry.id} className="rounded-2xl border border-green-200/80 bg-green-50/70 p-4">
<div className="flex items-center justify-between gap-3">
<strong className="text-sm text-green-900">{entry.name}</strong>
<StatusPill tone="green">Installed</StatusPill>
</div>
<small className="mt-2 block text-xs text-green-900/80 leading-relaxed">{entry.detail}</small>
</div>
))}
</div>
)}
{toolkitLastInstall.skipped.length > 0 && (
<div className="mt-3 flex flex-col gap-3">
{toolkitLastInstall.skipped.map((entry) => (
<div key={entry.id} className="rounded-2xl border border-amber-200/80 bg-amber-50/75 p-4">
<div className="flex items-center justify-between gap-3">
<strong className="text-sm text-amber-900">{entry.name}</strong>
<StatusPill tone="orange">Skipped</StatusPill>
</div>
<small className="mt-2 block text-xs text-amber-900/80 leading-relaxed">{entry.detail}</small>
</div>
))}
</div>
)}
{toolkitLastInstall.notes.length > 0 && (
<ul className="mt-3 m-0 pl-5 text-sm text-slatecopy">
{toolkitLastInstall.notes.map((note) => <li key={note}>{note}</li>)}
</ul>
)}
</section>
)}
<section className="plugin-section">
<div className="plugin-section-title"><small>Manual Follow-Up</small><strong>Still better done explicitly</strong></div>
<p className="text-sm text-slatecopy leading-relaxed">
These lanes stay outside the one-shot persistent bundle for now because they are more dependent on auth, provider choice, host environment, or high-risk permission boundaries.
</p>
<div className="flex flex-wrap gap-2">
{persistentFollowUps.map((name) => (
<span key={name} className="pill pill-slate">{name}</span>
))}
</div>
</section>
</>
)}
</>
);
export function McpToolkitSection(props: McpToolkitSectionProps) {
return <McpToolkitSectionSeam {...props} />;
}

View file

@ -0,0 +1,15 @@
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 toolingSectionsSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-tooling-sections.tsx"), "utf8");
const toolkitSectionSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-mcp-toolkit-section.tsx"), "utf8");
assert.match(toolingSectionsSource, /from "\.\/integrations-view-mcp-toolkit-section"/, "Integrations tooling shell must import the extracted MCP toolkit section seam.");
assert.match(toolkitSectionSource, /export function McpToolkitSection/, "Extracted integrations toolkit seam must export the curated toolkit section.");
assert.match(toolkitSectionSource, /buildPersistentToolkitBundle/, "Extracted integrations toolkit seam must own persistent bundle composition.");
assert.match(toolkitSectionSource, /getPersistentToolkitFollowUps/, "Extracted integrations toolkit seam must own persistent follow-up guidance.");
console.error("Integrations toolkit split validation passed.");