import { useCallback, useMemo } from "react"; import { Text, View } from "react-native"; import { useFocusEffect } from "@react-navigation/native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { ArrowUpRight, Terminal, Blocks, Check } from "lucide-react-native"; import { settingsStyles } from "@/styles/settings"; import { SettingsSection } from "@/screens/settings/settings-section"; import { Button } from "@/components/ui/button"; import { openExternalUrl } from "@/utils/open-external-url"; import { confirmDialog } from "@/utils/confirm-dialog"; import { shouldUseDesktopDaemon, type SkillOp, type SkillsStatus, } from "@/desktop/daemon/desktop-daemon"; import { useCliInstall, useSkillsStatus } from "@/desktop/hooks/use-install-status"; const CLI_DOCS_URL = "https://paseo.sh/docs/cli"; const SKILLS_DOCS_URL = "https://paseo.sh/docs/skills"; const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder]; const UNINSTALL_MESSAGE = "Removes all Paseo orchestration skills from ~/.agents, ~/.claude, ~/.codex."; const OP_KIND_ORDER: Record = { add: 0, update: 1, delete: 2 }; const OP_KIND_LABEL: Record = { add: "Add skill", update: "Update skill", delete: "Delete skill", }; function formatUpdateMessage(ops: readonly SkillOp[]): string { const sorted = [...ops].sort((a, b) => { const kindOrder = OP_KIND_ORDER[a.kind] - OP_KIND_ORDER[b.kind]; return kindOrder !== 0 ? kindOrder : a.name.localeCompare(b.name); }); return sorted.map((op) => `${OP_KIND_LABEL[op.kind]} ${op.name}`).join("\n"); } export function IntegrationsSection() { const { theme } = useUnistyles(); const showSection = shouldUseDesktopDaemon(); const { status: cliStatus, isInstalling: isInstallingCli, install: installCli, refresh: refreshCliStatus, } = useCliInstall(); const { status: skillsStatus, isWorking: isSkillsWorking, install: installSkills, update: updateSkills, uninstall: uninstallSkills, refresh: refreshSkillsStatus, } = useSkillsStatus(); useFocusEffect( useCallback(() => { if (!showSection) return undefined; refreshCliStatus(); void refreshSkillsStatus(); return undefined; }, [refreshCliStatus, refreshSkillsStatus, showSection]), ); const handleInstallCli = useCallback(() => { if (isInstallingCli) return; installCli(); }, [installCli, isInstallingCli]); const handleInstallSkills = useCallback(() => { if (isSkillsWorking) return; void installSkills(); }, [installSkills, isSkillsWorking]); const handleUpdateSkills = useCallback(async () => { if (isSkillsWorking) return; const ops = skillsStatus?.ops ?? []; const confirmed = await confirmDialog({ title: "Update Paseo skills?", message: ops.length > 0 ? formatUpdateMessage(ops) : "Sync bundled skills to your machine.", confirmLabel: "Update", }); if (!confirmed) return; await updateSkills(); }, [isSkillsWorking, skillsStatus, updateSkills]); const handleUninstallSkills = useCallback(async () => { if (isSkillsWorking) return; const confirmed = await confirmDialog({ title: "Uninstall Paseo skills?", message: UNINSTALL_MESSAGE, confirmLabel: "Uninstall", destructive: true, }); if (!confirmed) return; await uninstallSkills(); }, [isSkillsWorking, uninstallSkills]); const handleOpenCliDocs = useCallback(() => { void openExternalUrl(CLI_DOCS_URL); }, []); const handleOpenSkillsDocs = useCallback(() => { void openExternalUrl(SKILLS_DOCS_URL); }, []); const arrowIcon = useMemo( () => , [theme.iconSize.sm, theme.colors.foregroundMuted], ); const trailing = useMemo( () => ( ), [arrowIcon, handleOpenCliDocs, handleOpenSkillsDocs], ); if (!showSection) { return null; } const skillsState = skillsStatus?.state ?? null; return ( Command line Control and script agents from your terminal {cliStatus?.installed ? ( Installed ) : ( )} Orchestration skills {skillsState === "drift" ? "Update available" : "Teach your agents to orchestrate through the CLI"} ); } interface SkillsActionsProps { state: SkillsStatus["state"] | null; isWorking: boolean; onInstall: () => void; onUpdate: () => void; onUninstall: () => void; } function SkillsActions({ state, isWorking, onInstall, onUpdate, onUninstall }: SkillsActionsProps) { const { theme } = useUnistyles(); if (state === "up-to-date") { return ( Installed ); } if (state === "drift") { return ( ); } return ( ); } const styles = StyleSheet.create((theme) => ({ headerLinks: { flexDirection: "row", alignItems: "center", gap: theme.spacing[0], }, rowTitleRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, installedLabel: { flexDirection: "row", alignItems: "center", gap: 4, }, mutedText: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, }, actionsRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, }));