import { useEffect, useState } from "react"; import { netcattyBridge } from "../infrastructure/services/netcattyBridge"; let shellCache: DiscoveredShell[] | null = null; let shellPromise: Promise | null = null; export function useDiscoveredShells(): DiscoveredShell[] { const [shells, setShells] = useState(shellCache ?? []); useEffect(() => { if (shellCache) { setShells(shellCache); return; } const bridge = netcattyBridge.get(); if (!bridge?.discoverShells) return; if (!shellPromise) { shellPromise = bridge.discoverShells(); } shellPromise.then((result) => { shellCache = result; setShells(result); }).catch((err) => { console.warn("Failed to discover shells:", err); // Clear the failed promise so the next mount can retry shellPromise = null; }); }, []); return shells; } /** * Resolve a localShell setting value to shell command and args. * The value can be a discovered shell id (e.g., "wsl-ubuntu", "pwsh") * or a custom path/command (e.g., "/usr/local/bin/fish" or "fish"). * Returns { command, args } or null when discovery hasn't loaded yet * and the value might be a shell ID that can't be resolved yet. */ export function resolveShellSetting( localShell: string, discoveredShells: DiscoveredShell[] ): { command: string; args?: string[] } | null { if (!localShell) return null; // Try to match as a discovered shell id const shell = discoveredShells.find(s => s.id === localShell); if (shell) { return { command: shell.command, args: shell.args }; } // No ID match — treat as a custom shell path/command and pass through. // This handles both custom executables (e.g., "/usr/local/bin/fish", "pwsh-preview") // and stale/synced IDs that no longer exist on this machine (graceful fallback // to whatever the OS resolves the name to, or a spawn error the user can see). return { command: localShell }; } const DISTRO_ICONS = new Set([ "ubuntu", "debian", "kali", "alpine", "opensuse", "fedora", "arch", "oracle", "linux", ]); export function getShellIconPath(iconId: string): string { if (DISTRO_ICONS.has(iconId)) { return `/distro/${iconId}.svg`; } return `/shells/${iconId}.svg`; } /** Distro icons are monochrome black and need `dark:invert` in dark mode */ export function isMonochromeShellIcon(iconId: string): boolean { return DISTRO_ICONS.has(iconId); }