interactiveSelectItems runs a bubbletea multi-select picker.
(items []discoveredItem, kind entities.Kind, source string)
| 1819 | |
| 1820 | // interactiveSelectItems runs a bubbletea multi-select picker. |
| 1821 | func interactiveSelectItems(items []discoveredItem, kind entities.Kind, source string) ([]discoveredItem, error) { |
| 1822 | msItems := make([]multiSelectItem, len(items)) |
| 1823 | for i, item := range items { |
| 1824 | desc := extractFrontmatterDescription(item.content) |
| 1825 | if len(desc) > 120 { |
| 1826 | desc = desc[:117] + "..." |
| 1827 | } |
| 1828 | msItems[i] = multiSelectItem{ |
| 1829 | label: item.name, |
| 1830 | description: desc, |
| 1831 | } |
| 1832 | } |
| 1833 | |
| 1834 | title := fmt.Sprintf("Select %s(s) to install from %s:", kind, source) |
| 1835 | selected, err := runMultiSelect(title, msItems) |
| 1836 | if err != nil { |
| 1837 | return nil, err |
| 1838 | } |
| 1839 | if len(selected) == 0 { |
| 1840 | return nil, fmt.Errorf("no %ss selected", kind) |
| 1841 | } |
| 1842 | |
| 1843 | // Map selected labels back to items (label starts with the name). |
| 1844 | nameSet := make(map[string]bool) |
| 1845 | for _, label := range selected { |
| 1846 | // Extract the name from "name description" format. |
| 1847 | name := strings.Fields(label)[0] |
| 1848 | nameSet[name] = true |
| 1849 | } |
| 1850 | |
| 1851 | var result []discoveredItem |
| 1852 | for _, item := range items { |
| 1853 | if nameSet[item.name] { |
| 1854 | result = append(result, item) |
| 1855 | } |
| 1856 | } |
| 1857 | return result, nil |
| 1858 | } |
| 1859 | |
| 1860 | // promptSearchInstall shows an interactive multi-select picker over GitHub |
| 1861 | // search results, then asks for a target app, and installs the selected |
no test coverage detected