okf-cli-0.11.0.0: src/Okf/Cli/Kit/Selector.hs
-- | Application-owned kit presentation; Baikai retains the installer lifecycle.
module Okf.Cli.Kit.Selector
( KitSelectionError (..),
kitCandidates,
prepareKitChooser,
chooseKitItem,
)
where
import Baikai.Kit.Manifest (KitItem (..), KitManifest (..), itemKind, itemName)
import Control.Exception (Exception, throwIO)
import Control.Lens ((^.))
import Data.Char (isControl)
import Data.Generics.Labels ()
import Data.Text (Text)
import Data.Text qualified as Text
import Okf.Cli.Fzf
data KitSelectionError
= KitSelectionUnavailable
| KitSelectionEmpty
| KitSelectionFailed !Text
deriving stock (Show, Eq)
instance Exception KitSelectionError
-- | The index maps back to original identity, never to sanitized display text.
kitCandidates :: KitManifest -> [Candidate Text]
kitCandidates manifest =
[ Candidate (label item description) (itemName item)
| (item, description) <-
[(KitSkillItem entry, entry ^. #description) | entry <- manifest ^. #skills]
<> [(KitAgentItem entry, entry ^. #description) | entry <- manifest ^. #agents]
]
where
safe = Text.filter (not . isControl)
label item description = safe (itemName item) <> " [" <> itemKind item <> "] " <> safe description
-- | Probe before the engine can clone or refresh anything.
prepareKitChooser :: IO (Either KitSelectionError FzfConfig)
prepareKitChooser = do
config <- detectFzfConfig
pure $
if fzfAvailable config && ttyAvailable config
then Right config
else Left KitSelectionUnavailable
-- | Only explicit cancellation crosses the engine seam as Nothing. Errors keep
-- their type across its Maybe callback and are caught at the CLI boundary.
chooseKitItem :: FzfConfig -> KitManifest -> IO (Maybe Text)
chooseKitItem config manifest
| null candidates = throwIO KitSelectionEmpty
| otherwise = do
selected <- runFzf config (withPrompt "Install kit item> " <> withHeader "Name [kind] description; Esc cancels" <> withNoSort) candidates
case selected of
FzfSelected name -> pure (Just name)
FzfCancelled -> pure Nothing
FzfNoMatch -> throwIO (KitSelectionFailed "No kit item matched; use `okf kit list` or `okf kit install NAME`.")
FzfError message -> throwIO (KitSelectionFailed message)
where
candidates = kitCandidates manifest