import { useMemo, useState } from "react"; import { useAtomValue } from "@effect/atom-react"; import { Effect, Exit } from "effect"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { connectionsAllAtom } from "@executor-js/react/api/atoms"; import { Alert, AlertDescription, AlertTitle } from "@executor-js/react/components/alert"; import { Button } from "@executor-js/react/components/button"; import { CardStack, CardStackContent, CardStackEntryField, } from "@executor-js/react/components/card-stack"; import { FloatActions } from "@executor-js/react/components/float-actions"; import { Input } from "@executor-js/react/components/input"; import { NativeSelect, NativeSelectOption } from "@executor-js/react/components/native-select"; import { toast } from "@executor-js/react/components/sonner"; import { FormErrorAlert } from "@executor-js/react/lib/integration-add"; import { consoleIntegrationHref, formatSyncErrors, githubConnections, syncCustomToolSourceEffect, syncStatusLabel, validateGitHubRepo, } from "./custom-tools-client"; export default function AddCustomToolsSource(props: { readonly onComplete: (slug?: string) => void; readonly onCancel: () => void; }) { const connectionsResult = useAtomValue(connectionsAllAtom); const connections = useMemo( () => AsyncResult.isSuccess(connectionsResult) ? githubConnections(connectionsResult.value) : [], [connectionsResult], ); const [repo, setRepo] = useState(""); const [ref, setRef] = useState(""); const [connection, setConnection] = useState(""); const [repoError, setRepoError] = useState(null); const [syncing, setSyncing] = useState(false); const [syncError, setSyncError] = useState(null); const selectedConnection = connection || connections[0]?.address || ""; const loadingConnections = !AsyncResult.isSuccess(connectionsResult); const canSubmit = repo.trim().length > 0 && selectedConnection.length > 0 && !syncing && !loadingConnections; const submit = async () => { const validation = validateGitHubRepo(repo); setRepoError(validation); setSyncError(null); if (validation || !selectedConnection) return; setSyncing(true); const exit = await Effect.runPromiseExit( syncCustomToolSourceEffect({ repo, ref, connection: selectedConnection, }), ); if (Exit.isFailure(exit)) { setSyncError("Failed to sync custom tools."); setSyncing(false); return; } const result = exit.value; if (result.status === "failed") { setSyncError(formatSyncErrors(result).join("\n") || "Sync failed."); setSyncing(false); return; } toast.success(syncStatusLabel(result)); props.onComplete("apps"); }; return (

Add custom tools

Sync a GitHub repository that defines tools with executor:app.

{ setRepo((event.target as HTMLInputElement).value); setRepoError(null); setSyncError(null); }} onBlur={() => setRepoError(validateGitHubRepo(repo))} placeholder="owner/name" className="font-mono text-sm" aria-invalid={repoError ? true : undefined} /> {repoError &&

{repoError}

}
setRef((event.target as HTMLInputElement).value)} placeholder="default branch" className="font-mono text-sm" /> {connections.length > 0 ? ( setConnection((event.target as HTMLSelectElement).value)} className="w-full font-mono text-sm" disabled={syncing} > {connections.map((candidate) => ( {candidate.address} ))} ) : ( No GitHub connections Connect GitHub first, then return here to sync a custom-tools repo.{" "} Open GitHub )}
{syncError && }
); }