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<string | null>(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 ( <div className="flex flex-1 flex-col gap-6"> <p className="text-[13px] text-muted-foreground">Checking this machine for Codex…</p> </div> ); } if (plugin === undefined) { return ( <div className="flex flex-1 flex-col gap-6"> <p className="text-[13px] text-muted-foreground"> This Codex plugin was not found on this machine. </p> <FloatActions> <Button type="button" variant="ghost" onClick={() => props.onCancel()}> Back </Button> </FloatActions> </div> ); } return ( <div className="flex flex-1 flex-col gap-6"> {/* Mirrors the plugin's own page in Codex: its icon, display name, tagline, and long description, all read from the local install. */} <div className="flex flex-col gap-4"> {/* 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. */} <img src={plugin.icon ?? plugin.fallbackIcon ?? "https://integrations.sh/logo/openai.com"} alt="" className="size-16 rounded-2xl" /> <div className="min-w-0"> <div className="flex items-baseline gap-2"> <h1 className="text-xl font-semibold text-foreground"> {plugin.displayName ?? plugin.name} </h1> <span className="font-mono text-[11px] uppercase tracking-wider text-muted-foreground"> Codex plugin </span> </div> <p className="mt-1 text-[13px] text-muted-foreground"> {plugin.tagline ?? plugin.summary} </p> </div> {plugin.description !== undefined && ( <p className="max-w-prose text-[13px] leading-relaxed text-muted-foreground"> {plugin.description} </p> )} </div> <div className="flex flex-col gap-1 rounded-lg border border-border px-3 py-2.5"> <div className="flex items-baseline justify-between gap-3"> <span className="font-mono text-[11px] uppercase tracking-wider text-muted-foreground"> Status </span> <span className="font-mono text-[11px] text-muted-foreground"> {added ? "Added" : plugin.available ? "Ready" : "Not installed on this Mac"} </span> </div> {!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. <ol className="mt-1 flex list-none flex-col gap-1.5"> {plugin.setupHint.split("\n").map((step) => ( <li key={step} className="text-[12px] leading-relaxed text-muted-foreground"> {step} </li> ))} </ol> )} {plugin.available && !added && ( <p className="text-[12px] text-muted-foreground"> Runs the plugin from your Codex install. Nothing is downloaded. </p> )} </div> {error !== null && <p className="text-[12px] text-destructive">{error}</p>} <FloatActions> <Button type="button" variant="ghost" onClick={() => props.onCancel()} disabled={adding}> Cancel </Button> {added ? ( <Button type="button" onClick={() => props.onComplete(plugin.slug)}> View integration </Button> ) : plugin.available ? ( <Button type="button" onClick={() => void handleAdd()} loading={adding}> Add integration </Button> ) : ( <Button asChild> <a href={plugin.setupUrl ?? "https://openai.com/codex"} target="_blank" rel="noreferrer" > Install Codex </a> </Button> )} </FloatActions> </div> );}
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<string | null>(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 (
<div className="flex flex-1 flex-col gap-6">
<p className="text-[13px] text-muted-foreground">Checking this machine for Codex…</p>
</div>
);
}
if (plugin === undefined) {
return (
<div className="flex flex-1 flex-col gap-6">
<p className="text-[13px] text-muted-foreground">
This Codex plugin was not found on this machine.
</p>
<FloatActions>
<Button type="button" variant="ghost" onClick={() => props.onCancel()}>
Back
</Button>
</FloatActions>
</div>
);
}
return (
<div className="flex flex-1 flex-col gap-6">
{/* Mirrors the plugin's own page in Codex: its icon, display name,
tagline, and long description, all read from the local install. */}
<div className="flex flex-col gap-4">
{/* 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. */}
<img
src={plugin.icon ?? plugin.fallbackIcon ?? "https://integrations.sh/logo/openai.com"}
alt=""
className="size-16 rounded-2xl"
/>
<div className="min-w-0">
<div className="flex items-baseline gap-2">
<h1 className="text-xl font-semibold text-foreground">
{plugin.displayName ?? plugin.name}
</h1>
<span className="font-mono text-[11px] uppercase tracking-wider text-muted-foreground">
Codex plugin
</span>
</div>
<p className="mt-1 text-[13px] text-muted-foreground">
{plugin.tagline ?? plugin.summary}
</p>
</div>
{plugin.description !== undefined && (
<p className="max-w-prose text-[13px] leading-relaxed text-muted-foreground">
{plugin.description}
</p>
)}
</div>
<div className="flex flex-col gap-1 rounded-lg border border-border px-3 py-2.5">
<div className="flex items-baseline justify-between gap-3">
<span className="font-mono text-[11px] uppercase tracking-wider text-muted-foreground">
Status
</span>
<span className="font-mono text-[11px] text-muted-foreground">
{added ? "Added" : plugin.available ? "Ready" : "Not installed on this Mac"}
</span>
</div>
{!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.
<ol className="mt-1 flex list-none flex-col gap-1.5">
{plugin.setupHint.split("\n").map((step) => (
<li key={step} className="text-[12px] leading-relaxed text-muted-foreground">
{step}
</li>
))}
</ol>
)}
{plugin.available && !added && (
<p className="text-[12px] text-muted-foreground">
Runs the plugin from your Codex install. Nothing is downloaded.
</p>
)}
</div>
{error !== null && <p className="text-[12px] text-destructive">{error}</p>}
<FloatActions>
<Button type="button" variant="ghost" onClick={() => props.onCancel()} disabled={adding}>
Cancel
</Button>
{added ? (
<Button type="button" onClick={() => props.onComplete(plugin.slug)}>
View integration
</Button>
) : plugin.available ? (
<Button type="button" onClick={() => void handleAdd()} loading={adding}>
Add integration
</Button>
) : (
<Button asChild>
<a
href={plugin.setupUrl ?? "https://openai.com/codex"}
target="_blank"
rel="noreferrer"
>
Install Codex
</a>
</Button>
)}
</FloatActions>
</div>
);
}