import { useState } from "react"; import { useAtomValue, useAtomSet } from "@effect/atom-react"; import * as Exit from "effect/Exit"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { Button } from "@executor-js/react/components/button"; import { FloatActions } from "@executor-js/react/components/float-actions"; import { integrationsOptimisticAtom } from "@executor-js/react/api/atoms"; import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys"; import { addIntegrationErrorMessage } from "@executor-js/react/lib/integration-add"; import { addMcpServer, codexPluginsAtom } from "./atoms"; // --------------------------------------------------------------------------- // Focused add screen for one Codex plugin, reached from its catalog preset // (e.g. searching "imessage" in the connect dialog). The preset is only a // pointer; everything shown here — icon, availability, spawn recipe — comes // from the server-side scanner reading the user's local Codex install. One // primary action: Add. No transport toggle, no manual command form. // --------------------------------------------------------------------------- export default function CodexPluginAdd(props: { readonly presetId: string; readonly onComplete: (slug?: string) => void; readonly onCancel: () => void; }) { const pluginsResult = useAtomValue(codexPluginsAtom); const integrationsResult = useAtomValue(integrationsOptimisticAtom); const doAddServer = useAtomSet(addMcpServer, { mode: "promiseExit" }); const [adding, setAdding] = useState(false); const [error, setError] = useState(null); const plugin = AsyncResult.isSuccess(pluginsResult) ? pluginsResult.value.plugins.find((entry) => entry.id === props.presetId) : undefined; const added = plugin !== undefined && AsyncResult.isSuccess(integrationsResult) && integrationsResult.value.some((integration) => String(integration.slug) === plugin.slug); const handleAdd = async () => { if (plugin === undefined) return; setAdding(true); setError(null); const exit = await doAddServer({ payload: { transport: "stdio" as const, name: plugin.name, slug: plugin.slug, description: plugin.summary, command: plugin.command, args: [...plugin.args], ...(plugin.cwd !== undefined ? { cwd: plugin.cwd } : {}), ...(plugin.env !== undefined ? { env: { ...plugin.env } } : {}), ...(plugin.appServer !== undefined ? { appServer: { ...plugin.appServer } } : {}), }, reactivityKeys: integrationWriteKeys, }); if (Exit.isFailure(exit)) { setError(addIntegrationErrorMessage(exit, plugin.slug, "Failed to add plugin")); setAdding(false); return; } props.onComplete(exit.value.slug); }; if (!AsyncResult.isSuccess(pluginsResult)) { return (

Checking this machine for Codex…

); } if (plugin === undefined) { return (

This Codex plugin was not found on this machine.

); } return (
{/* Mirrors the plugin's own page in Codex: its icon, display name, tagline, and long description, all read from the local install. */}
{/* The plugin's own icon comes from the local Codex install; without one the card still identifies its provider rather than showing a gap, which matters most on the machines that have no install. */}

{plugin.displayName ?? plugin.name}

Codex plugin

{plugin.tagline ?? plugin.summary}

{plugin.description !== undefined && (

{plugin.description}

)}
Status {added ? "Added" : plugin.available ? "Ready" : "Not installed on this Mac"}
{!plugin.available && plugin.setupHint !== undefined && ( // The hint arrives as numbered lines; render them as the list they // are, so the reader sees an ordered path rather than a paragraph.
    {plugin.setupHint.split("\n").map((step) => (
  1. {step}
  2. ))}
)} {plugin.available && !added && (

Runs the plugin from your Codex install. Nothing is downloaded.

)}
{error !== null &&

{error}

} {added ? ( ) : plugin.available ? ( ) : ( )}
); }