claudeup 4.16.0 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__tests__/alias-parser.test.ts +317 -0
- package/src/__tests__/alias-shell-writer.test.ts +661 -0
- package/src/__tests__/alias-store.test.ts +86 -0
- package/src/__tests__/gitignore-fixer.test.ts +64 -1
- package/src/__tests__/gitignore-prerun.test.ts +2 -2
- package/src/__tests__/gitignore-service.test.ts +42 -0
- package/src/__tests__/marketplaces.test.ts +40 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +120 -0
- package/src/__tests__/useGitignoreModal.test.ts +2 -2
- package/src/data/alias-flags.js +196 -0
- package/src/data/alias-flags.ts +291 -0
- package/src/data/gitignore-reasons.js +97 -0
- package/src/data/gitignore-reasons.ts +103 -0
- package/src/data/marketplaces.js +19 -1
- package/src/data/marketplaces.ts +17 -1
- package/src/services/alias-settings.js +51 -0
- package/src/services/alias-settings.ts +63 -0
- package/src/services/alias-shell-writer.js +764 -0
- package/src/services/alias-shell-writer.ts +873 -0
- package/src/services/alias-store.js +77 -0
- package/src/services/alias-store.ts +112 -0
- package/src/services/gitignore-fixer.js +70 -10
- package/src/services/gitignore-fixer.ts +76 -9
- package/src/services/gitignore-prerun.js +3 -3
- package/src/services/gitignore-prerun.ts +3 -3
- package/src/services/gitignore-service.js +20 -2
- package/src/services/gitignore-service.ts +23 -1
- package/src/services/marketplace-fetcher.js +96 -0
- package/src/services/marketplace-fetcher.ts +137 -0
- package/src/services/plugin-manager.js +6 -59
- package/src/services/plugin-manager.ts +16 -91
- package/src/services/skillsmp-client.js +29 -9
- package/src/services/skillsmp-client.ts +38 -8
- package/src/types/gitignore.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/ui/App.js +10 -4
- package/src/ui/App.tsx +9 -3
- package/src/ui/components/TabBar.js +2 -1
- package/src/ui/components/TabBar.tsx +2 -1
- package/src/ui/components/layout/FooterHints.js +29 -0
- package/src/ui/components/layout/FooterHints.tsx +52 -0
- package/src/ui/components/layout/ScreenLayout.js +2 -1
- package/src/ui/components/layout/ScreenLayout.tsx +12 -3
- package/src/ui/components/layout/index.js +1 -0
- package/src/ui/components/layout/index.ts +5 -0
- package/src/ui/components/modals/SelectModal.js +8 -1
- package/src/ui/components/modals/SelectModal.tsx +12 -1
- package/src/ui/hooks/useGitignoreModal.js +7 -8
- package/src/ui/hooks/useGitignoreModal.ts +8 -9
- package/src/ui/renderers/gitignoreRenderers.js +36 -23
- package/src/ui/renderers/gitignoreRenderers.tsx +50 -41
- package/src/ui/screens/AliasScreen.js +1008 -0
- package/src/ui/screens/AliasScreen.tsx +1402 -0
- package/src/ui/screens/CliToolsScreen.js +6 -1
- package/src/ui/screens/CliToolsScreen.tsx +6 -1
- package/src/ui/screens/EnvVarsScreen.js +6 -1
- package/src/ui/screens/EnvVarsScreen.tsx +6 -1
- package/src/ui/screens/GitignoreScreen.js +189 -88
- package/src/ui/screens/GitignoreScreen.tsx +312 -132
- package/src/ui/screens/McpRegistryScreen.js +13 -2
- package/src/ui/screens/McpRegistryScreen.tsx +13 -2
- package/src/ui/screens/McpScreen.js +6 -1
- package/src/ui/screens/McpScreen.tsx +6 -1
- package/src/ui/screens/ModelSelectorScreen.js +8 -2
- package/src/ui/screens/ModelSelectorScreen.tsx +8 -2
- package/src/ui/screens/PluginsScreen.js +13 -2
- package/src/ui/screens/PluginsScreen.tsx +13 -2
- package/src/ui/screens/ProfilesScreen.js +8 -1
- package/src/ui/screens/ProfilesScreen.tsx +8 -1
- package/src/ui/screens/SkillsScreen.js +21 -4
- package/src/ui/screens/SkillsScreen.tsx +39 -5
- package/src/ui/screens/StatusLineScreen.js +7 -1
- package/src/ui/screens/StatusLineScreen.tsx +7 -1
- package/src/ui/screens/index.js +1 -0
- package/src/ui/screens/index.ts +1 -0
- package/src/ui/state/types.ts +4 -2
|
@@ -217,7 +217,12 @@ export function McpScreen() {
|
|
|
217
217
|
title="claudeup MCP Servers"
|
|
218
218
|
subtitle={subtitle}
|
|
219
219
|
currentScreen="mcp"
|
|
220
|
-
footerHints=
|
|
220
|
+
footerHints={[
|
|
221
|
+
{ keys: ["↑", "↓"], label: "nav" },
|
|
222
|
+
{ keys: ["Enter"], label: "toggle" },
|
|
223
|
+
{ keys: ["/"], label: "search" },
|
|
224
|
+
{ keys: ["r"], label: "registry" },
|
|
225
|
+
]}
|
|
221
226
|
listPanel={
|
|
222
227
|
<ScrollableList
|
|
223
228
|
items={allListItems}
|
|
@@ -5,6 +5,7 @@ import { useDimensions } from "../state/DimensionsContext.js";
|
|
|
5
5
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
6
6
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
|
+
import { FooterHints } from "../components/layout/index.js";
|
|
8
9
|
const RECENT_MODELS = [
|
|
9
10
|
{
|
|
10
11
|
id: "recent-1",
|
|
@@ -278,13 +279,18 @@ export function ModelSelectorScreen() {
|
|
|
278
279
|
return (_jsxs("box", { overflow: "hidden", children: [_jsx(Label, {}), _jsx("box", { flexGrow: 1 }), item.provider && _jsx("text", { fg: "gray", children: item.provider })] }));
|
|
279
280
|
}
|
|
280
281
|
};
|
|
281
|
-
const footerHints =
|
|
282
|
+
const footerHints = [
|
|
283
|
+
{ keys: ["↑", "↓"], label: "choose" },
|
|
284
|
+
{ keys: ["tab"], label: "toggle type" },
|
|
285
|
+
{ keys: ["enter"], label: "choose" },
|
|
286
|
+
{ keys: ["esc"], label: "exit" },
|
|
287
|
+
];
|
|
282
288
|
// Available height calculation
|
|
283
289
|
// Header: 1 line (title) + 1 line (search) + 1 line (separator) = 3 lines?
|
|
284
290
|
// Title line: Switch Model /// (o) Large
|
|
285
291
|
// Cursor line: > c
|
|
286
292
|
// Separator: handled by list? or explicit?
|
|
287
293
|
const listHeight = Math.max(5, dimensions.contentHeight - 5);
|
|
288
|
-
return (_jsxs("box", { flexDirection: "column", height: dimensions.contentHeight, children: [_jsx("box", { flexDirection: "row", border: true, borderStyle: "single", borderColor: "#7e57c2", paddingLeft: 1, paddingRight: 1, children: _jsxs("box", { flexDirection: "column", flexGrow: 1, children: [_jsx("box", { flexDirection: "row", justifyContent: "space-between", children: _jsxs("box", { children: [_jsx("text", { fg: "#7e57c2", children: "Switch Model " }), _jsxs("text", { fg: modelSelector.taskSize === "large" ? "white" : "gray", children: [modelSelector.taskSize === "large" ? "◎" : "○", " Large Task", " "] }), _jsxs("text", { fg: modelSelector.taskSize === "small" ? "white" : "gray", children: [modelSelector.taskSize === "small" ? "◎" : "○", " Small Task"] })] }) }), _jsxs("box", { flexDirection: "row", marginTop: 1, children: [_jsx("text", { fg: "green", children: "> " }), _jsx("text", { children: modelSelector.searchQuery }), _jsx("text", { bg: "gray", fg: "black", children: " " })] })] }) }), _jsx("box", { flexGrow: 1, paddingLeft: 1, paddingRight: 1, children: _jsx(ScrollableList, { items: filteredItems, selectedIndex: modelSelector.selectedIndex, renderItem: renderItem, maxHeight: listHeight, getKey: (item) => item.id }) }), _jsx("box", { height: 1, children: _jsx(
|
|
294
|
+
return (_jsxs("box", { flexDirection: "column", height: dimensions.contentHeight, children: [_jsx("box", { flexDirection: "row", border: true, borderStyle: "single", borderColor: "#7e57c2", paddingLeft: 1, paddingRight: 1, children: _jsxs("box", { flexDirection: "column", flexGrow: 1, children: [_jsx("box", { flexDirection: "row", justifyContent: "space-between", children: _jsxs("box", { children: [_jsx("text", { fg: "#7e57c2", children: "Switch Model " }), _jsxs("text", { fg: modelSelector.taskSize === "large" ? "white" : "gray", children: [modelSelector.taskSize === "large" ? "◎" : "○", " Large Task", " "] }), _jsxs("text", { fg: modelSelector.taskSize === "small" ? "white" : "gray", children: [modelSelector.taskSize === "small" ? "◎" : "○", " Small Task"] })] }) }), _jsxs("box", { flexDirection: "row", marginTop: 1, children: [_jsx("text", { fg: "green", children: "> " }), _jsx("text", { children: modelSelector.searchQuery }), _jsx("text", { bg: "gray", fg: "black", children: " " })] })] }) }), _jsx("box", { flexGrow: 1, paddingLeft: 1, paddingRight: 1, children: _jsx(ScrollableList, { items: filteredItems, selectedIndex: modelSelector.selectedIndex, renderItem: renderItem, maxHeight: listHeight, getKey: (item) => item.id }) }), _jsx("box", { height: 1, children: _jsx(FooterHints, { hints: footerHints }) })] }));
|
|
289
295
|
}
|
|
290
296
|
export default ModelSelectorScreen;
|
|
@@ -5,6 +5,7 @@ import { useDimensions } from "../state/DimensionsContext.js";
|
|
|
5
5
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
6
6
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
|
+
import { FooterHints } from "../components/layout/index.js";
|
|
8
9
|
|
|
9
10
|
interface ModelItem {
|
|
10
11
|
id: string;
|
|
@@ -372,7 +373,12 @@ export function ModelSelectorScreen() {
|
|
|
372
373
|
}
|
|
373
374
|
};
|
|
374
375
|
|
|
375
|
-
const footerHints =
|
|
376
|
+
const footerHints = [
|
|
377
|
+
{ keys: ["↑", "↓"], label: "choose" },
|
|
378
|
+
{ keys: ["tab"], label: "toggle type" },
|
|
379
|
+
{ keys: ["enter"], label: "choose" },
|
|
380
|
+
{ keys: ["esc"], label: "exit" },
|
|
381
|
+
];
|
|
376
382
|
// Available height calculation
|
|
377
383
|
// Header: 1 line (title) + 1 line (search) + 1 line (separator) = 3 lines?
|
|
378
384
|
// Title line: Switch Model /// (o) Large
|
|
@@ -429,7 +435,7 @@ export function ModelSelectorScreen() {
|
|
|
429
435
|
</box>
|
|
430
436
|
|
|
431
437
|
<box height={1}>
|
|
432
|
-
<
|
|
438
|
+
<FooterHints hints={footerHints} />
|
|
433
439
|
</box>
|
|
434
440
|
</box>
|
|
435
441
|
);
|
|
@@ -735,8 +735,19 @@ export function PluginsScreen() {
|
|
|
735
735
|
}
|
|
736
736
|
const selectedItem = selectableItems[pluginsState.selectedIndex];
|
|
737
737
|
const footerHints = isSearchActive
|
|
738
|
-
?
|
|
739
|
-
|
|
738
|
+
? [
|
|
739
|
+
{ keys: ["type"], label: "filter" },
|
|
740
|
+
{ keys: ["Enter"], label: "done" },
|
|
741
|
+
{ keys: ["Esc"], label: "clear" },
|
|
742
|
+
]
|
|
743
|
+
: [
|
|
744
|
+
{ keys: ["u", "/", "p", "/", "l"], label: "toggle" },
|
|
745
|
+
{ keys: ["U"], label: "update" },
|
|
746
|
+
{ keys: ["a"], label: "all" },
|
|
747
|
+
{ keys: ["m"], label: "mismatches" },
|
|
748
|
+
{ keys: ["s"], label: "profile" },
|
|
749
|
+
{ keys: ["/"], label: "search" },
|
|
750
|
+
];
|
|
740
751
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
741
752
|
const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
|
|
742
753
|
const installedCount = plugins.filter((p) => p.enabled).length;
|
|
@@ -937,8 +937,19 @@ export function PluginsScreen() {
|
|
|
937
937
|
const selectedItem = selectableItems[pluginsState.selectedIndex];
|
|
938
938
|
|
|
939
939
|
const footerHints = isSearchActive
|
|
940
|
-
?
|
|
941
|
-
|
|
940
|
+
? [
|
|
941
|
+
{ keys: ["type"], label: "filter" },
|
|
942
|
+
{ keys: ["Enter"], label: "done" },
|
|
943
|
+
{ keys: ["Esc"], label: "clear" },
|
|
944
|
+
]
|
|
945
|
+
: [
|
|
946
|
+
{ keys: ["u", "/", "p", "/", "l"], label: "toggle" },
|
|
947
|
+
{ keys: ["U"], label: "update" },
|
|
948
|
+
{ keys: ["a"], label: "all" },
|
|
949
|
+
{ keys: ["m"], label: "mismatches" },
|
|
950
|
+
{ keys: ["s"], label: "profile" },
|
|
951
|
+
{ keys: ["/"], label: "search" },
|
|
952
|
+
];
|
|
942
953
|
|
|
943
954
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
944
955
|
const plugins: PluginInfo[] =
|
|
@@ -286,6 +286,13 @@ export function ProfilesScreen() {
|
|
|
286
286
|
const errorMessage = profilesState.profiles.status === "error"
|
|
287
287
|
? profilesState.profiles.error.message
|
|
288
288
|
: undefined;
|
|
289
|
-
return (_jsx(ScreenLayout, { title: "claudeup Plugin Profiles", currentScreen: "profiles", statusLine: statusContent, footerHints:
|
|
289
|
+
return (_jsx(ScreenLayout, { title: "claudeup Plugin Profiles", currentScreen: "profiles", statusLine: statusContent, footerHints: [
|
|
290
|
+
{ keys: ["↑", "↓"], label: "nav" },
|
|
291
|
+
{ keys: ["Enter", "/", "a"], label: "apply" },
|
|
292
|
+
{ keys: ["r"], label: "rename" },
|
|
293
|
+
{ keys: ["d"], label: "delete" },
|
|
294
|
+
{ keys: ["c"], label: "copy" },
|
|
295
|
+
{ keys: ["i"], label: "import" },
|
|
296
|
+
], listPanel: _jsx(ScrollableList, { items: allItems, selectedIndex: effectiveIndex, renderItem: renderProfileRow, maxHeight: dimensions.listPanelHeight }), detailPanel: renderProfileDetail(allItems[effectiveIndex], loadingStatus, errorMessage) }));
|
|
290
297
|
}
|
|
291
298
|
export default ProfilesScreen;
|
|
@@ -393,7 +393,14 @@ export function ProfilesScreen() {
|
|
|
393
393
|
title="claudeup Plugin Profiles"
|
|
394
394
|
currentScreen="profiles"
|
|
395
395
|
statusLine={statusContent}
|
|
396
|
-
footerHints=
|
|
396
|
+
footerHints={[
|
|
397
|
+
{ keys: ["↑", "↓"], label: "nav" },
|
|
398
|
+
{ keys: ["Enter", "/", "a"], label: "apply" },
|
|
399
|
+
{ keys: ["r"], label: "rename" },
|
|
400
|
+
{ keys: ["d"], label: "delete" },
|
|
401
|
+
{ keys: ["c"], label: "copy" },
|
|
402
|
+
{ keys: ["i"], label: "import" },
|
|
403
|
+
]}
|
|
397
404
|
listPanel={
|
|
398
405
|
<ScrollableList
|
|
399
406
|
items={allItems}
|
|
@@ -42,6 +42,7 @@ export function SkillsScreen() {
|
|
|
42
42
|
// ── Remote search (debounced, cached) ─────────────────────────────────────
|
|
43
43
|
const [searchResults, setSearchResults] = useState([]);
|
|
44
44
|
const [isSearchLoading, setIsSearchLoading] = useState(false);
|
|
45
|
+
const [searchError, setSearchError] = useState(null);
|
|
45
46
|
const searchTimerRef = useRef(null);
|
|
46
47
|
const searchCacheRef = useRef(new Map());
|
|
47
48
|
useEffect(() => {
|
|
@@ -49,15 +50,18 @@ export function SkillsScreen() {
|
|
|
49
50
|
if (query.length < 2) {
|
|
50
51
|
setSearchResults([]);
|
|
51
52
|
setIsSearchLoading(false);
|
|
53
|
+
setSearchError(null);
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
const cached = searchCacheRef.current.get(query);
|
|
55
57
|
if (cached) {
|
|
56
58
|
setSearchResults(cached);
|
|
57
59
|
setIsSearchLoading(false);
|
|
60
|
+
setSearchError(null);
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
setIsSearchLoading(true);
|
|
64
|
+
setSearchError(null);
|
|
61
65
|
if (searchTimerRef.current)
|
|
62
66
|
clearTimeout(searchTimerRef.current);
|
|
63
67
|
searchTimerRef.current = setTimeout(async () => {
|
|
@@ -86,9 +90,13 @@ export function SkillsScreen() {
|
|
|
86
90
|
});
|
|
87
91
|
searchCacheRef.current.set(query, mapped);
|
|
88
92
|
setSearchResults(mapped);
|
|
93
|
+
setSearchError(null);
|
|
89
94
|
}
|
|
90
|
-
catch {
|
|
95
|
+
catch (error) {
|
|
96
|
+
// A thrown error means the search service is unreachable — distinct
|
|
97
|
+
// from a successful search that returned zero matches.
|
|
91
98
|
setSearchResults([]);
|
|
99
|
+
setSearchError(error instanceof Error ? error.message : "Search service unavailable");
|
|
92
100
|
}
|
|
93
101
|
setIsSearchLoading(false);
|
|
94
102
|
}, 400);
|
|
@@ -517,7 +525,7 @@ export function SkillsScreen() {
|
|
|
517
525
|
const skills = skillsState.skills.status === "success" ? skillsState.skills.data : [];
|
|
518
526
|
const installedCount = skills.filter((s) => s.installed).length;
|
|
519
527
|
const query = skillsState.searchQuery.trim();
|
|
520
|
-
const statusContent = statusMsg ? (_jsx("text", { children: _jsx("span", { fg: statusMsg.tone === "success" ? "green" : "red", children: statusMsg.text }) })) : (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Skills: " }), _jsxs("span", { fg: "cyan", children: [installedCount, " installed"] }), query.length >= 2 && isSearchLoading && (_jsx("span", { fg: "yellow", children: " \u2502 searching..." })), query.length >= 2 && !isSearchLoading && searchResults.length > 0 && (_jsxs("span", { fg: "green", children: [" \u2502 ", searchResults.length, " found"] })), !query && _jsx("span", { fg: "gray", children: " \u2502 89K+ searchable" })] }));
|
|
528
|
+
const statusContent = statusMsg ? (_jsx("text", { children: _jsx("span", { fg: statusMsg.tone === "success" ? "green" : "red", children: statusMsg.text }) })) : (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Skills: " }), _jsxs("span", { fg: "cyan", children: [installedCount, " installed"] }), query.length >= 2 && isSearchLoading && (_jsx("span", { fg: "yellow", children: " \u2502 searching..." })), query.length >= 2 && !isSearchLoading && !searchError && searchResults.length > 0 && (_jsxs("span", { fg: "green", children: [" \u2502 ", searchResults.length, " found"] })), query.length >= 2 && !isSearchLoading && searchError && (_jsx("span", { fg: "red", children: " \u2502 search unavailable" })), !query && _jsx("span", { fg: "gray", children: " \u2502 89K+ searchable" })] }));
|
|
521
529
|
// ── Render ────────────────────────────────────────────────────────────────
|
|
522
530
|
return (_jsx(ScreenLayout, { title: "claudeup Skills", currentScreen: "skills", statusLine: statusContent, search: skillsState.searchQuery || isSearchActive
|
|
523
531
|
? {
|
|
@@ -526,7 +534,16 @@ export function SkillsScreen() {
|
|
|
526
534
|
placeholder: "type to search",
|
|
527
535
|
}
|
|
528
536
|
: undefined, footerHints: isSearchActive
|
|
529
|
-
?
|
|
530
|
-
|
|
537
|
+
? [
|
|
538
|
+
{ keys: ["type"], label: "filter" },
|
|
539
|
+
{ keys: ["Enter"], label: "done" },
|
|
540
|
+
{ keys: ["Esc"], label: "clear" },
|
|
541
|
+
]
|
|
542
|
+
: [
|
|
543
|
+
{ keys: ["u"], label: "user" },
|
|
544
|
+
{ keys: ["p"], label: "project" },
|
|
545
|
+
{ keys: ["o"], label: "open" },
|
|
546
|
+
{ keys: ["/"], label: "search" },
|
|
547
|
+
], listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: allItems, selectedIndex: skillsState.selectedIndex, renderItem: renderSkillRow, maxHeight: dimensions.listPanelHeight, getKey: (item, index) => `${index}:${item.id}` }), !query && skillsState.skills.status === "loading" && (_jsx("box", { marginTop: 2, paddingLeft: 2, children: _jsx("text", { fg: "yellow", children: "Loading popular skills..." }) })), query.length >= 2 && isSearchLoading && (_jsx("box", { marginTop: 2, paddingLeft: 2, children: _jsxs("text", { fg: "yellow", children: ["Searching for \"", skillsState.searchQuery, "\"..."] }) })), query.length >= 2 && !isSearchLoading && searchError && (_jsxs("box", { flexDirection: "column", marginTop: 2, paddingLeft: 2, paddingRight: 2, children: [_jsx("text", { fg: "red", children: "Skill search is unavailable right now." }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "The search service could not be reached" }) }), _jsxs("text", { fg: "gray", children: ["(", searchError, "). Check your connection and retry."] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "Press Esc to clear the search." }) })] })), query.length >= 2 && !isSearchLoading && !searchError && searchResults.length === 0 && (_jsx(EmptyFilterState, { query: skillsState.searchQuery, entityName: "skills" }))] }), detailPanel: renderSkillDetail(selectedItem) }));
|
|
531
548
|
}
|
|
532
549
|
export default SkillsScreen;
|
|
@@ -60,6 +60,7 @@ export function SkillsScreen() {
|
|
|
60
60
|
|
|
61
61
|
const [searchResults, setSearchResults] = useState<SkillInfo[]>([]);
|
|
62
62
|
const [isSearchLoading, setIsSearchLoading] = useState(false);
|
|
63
|
+
const [searchError, setSearchError] = useState<string | null>(null);
|
|
63
64
|
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
64
65
|
const searchCacheRef = useRef<Map<string, SkillInfo[]>>(new Map());
|
|
65
66
|
|
|
@@ -68,6 +69,7 @@ export function SkillsScreen() {
|
|
|
68
69
|
if (query.length < 2) {
|
|
69
70
|
setSearchResults([]);
|
|
70
71
|
setIsSearchLoading(false);
|
|
72
|
+
setSearchError(null);
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -75,10 +77,12 @@ export function SkillsScreen() {
|
|
|
75
77
|
if (cached) {
|
|
76
78
|
setSearchResults(cached);
|
|
77
79
|
setIsSearchLoading(false);
|
|
80
|
+
setSearchError(null);
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
setIsSearchLoading(true);
|
|
85
|
+
setSearchError(null);
|
|
82
86
|
|
|
83
87
|
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
|
|
84
88
|
searchTimerRef.current = setTimeout(async () => {
|
|
@@ -107,8 +111,14 @@ export function SkillsScreen() {
|
|
|
107
111
|
});
|
|
108
112
|
searchCacheRef.current.set(query, mapped);
|
|
109
113
|
setSearchResults(mapped);
|
|
110
|
-
|
|
114
|
+
setSearchError(null);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// A thrown error means the search service is unreachable — distinct
|
|
117
|
+
// from a successful search that returned zero matches.
|
|
111
118
|
setSearchResults([]);
|
|
119
|
+
setSearchError(
|
|
120
|
+
error instanceof Error ? error.message : "Search service unavailable",
|
|
121
|
+
);
|
|
112
122
|
}
|
|
113
123
|
setIsSearchLoading(false);
|
|
114
124
|
}, 400);
|
|
@@ -583,9 +593,12 @@ export function SkillsScreen() {
|
|
|
583
593
|
{query.length >= 2 && isSearchLoading && (
|
|
584
594
|
<span fg="yellow"> │ searching...</span>
|
|
585
595
|
)}
|
|
586
|
-
{query.length >= 2 && !isSearchLoading && searchResults.length > 0 && (
|
|
596
|
+
{query.length >= 2 && !isSearchLoading && !searchError && searchResults.length > 0 && (
|
|
587
597
|
<span fg="green"> │ {searchResults.length} found</span>
|
|
588
598
|
)}
|
|
599
|
+
{query.length >= 2 && !isSearchLoading && searchError && (
|
|
600
|
+
<span fg="red"> │ search unavailable</span>
|
|
601
|
+
)}
|
|
589
602
|
{!query && <span fg="gray"> │ 89K+ searchable</span>}
|
|
590
603
|
</text>
|
|
591
604
|
);
|
|
@@ -608,8 +621,17 @@ export function SkillsScreen() {
|
|
|
608
621
|
}
|
|
609
622
|
footerHints={
|
|
610
623
|
isSearchActive
|
|
611
|
-
?
|
|
612
|
-
|
|
624
|
+
? [
|
|
625
|
+
{ keys: ["type"], label: "filter" },
|
|
626
|
+
{ keys: ["Enter"], label: "done" },
|
|
627
|
+
{ keys: ["Esc"], label: "clear" },
|
|
628
|
+
]
|
|
629
|
+
: [
|
|
630
|
+
{ keys: ["u"], label: "user" },
|
|
631
|
+
{ keys: ["p"], label: "project" },
|
|
632
|
+
{ keys: ["o"], label: "open" },
|
|
633
|
+
{ keys: ["/"], label: "search" },
|
|
634
|
+
]
|
|
613
635
|
}
|
|
614
636
|
listPanel={
|
|
615
637
|
<box flexDirection="column">
|
|
@@ -630,7 +652,19 @@ export function SkillsScreen() {
|
|
|
630
652
|
<text fg="yellow">Searching for "{skillsState.searchQuery}"...</text>
|
|
631
653
|
</box>
|
|
632
654
|
)}
|
|
633
|
-
{query.length >= 2 && !isSearchLoading &&
|
|
655
|
+
{query.length >= 2 && !isSearchLoading && searchError && (
|
|
656
|
+
<box flexDirection="column" marginTop={2} paddingLeft={2} paddingRight={2}>
|
|
657
|
+
<text fg="red">Skill search is unavailable right now.</text>
|
|
658
|
+
<box marginTop={1}>
|
|
659
|
+
<text fg="gray">The search service could not be reached</text>
|
|
660
|
+
</box>
|
|
661
|
+
<text fg="gray">({searchError}). Check your connection and retry.</text>
|
|
662
|
+
<box marginTop={1}>
|
|
663
|
+
<text fg="gray">Press Esc to clear the search.</text>
|
|
664
|
+
</box>
|
|
665
|
+
</box>
|
|
666
|
+
)}
|
|
667
|
+
{query.length >= 2 && !isSearchLoading && !searchError && searchResults.length === 0 && (
|
|
634
668
|
<EmptyFilterState query={skillsState.searchQuery} entityName="skills" />
|
|
635
669
|
)}
|
|
636
670
|
</box>
|
|
@@ -195,6 +195,12 @@ export function StatusLineScreen() {
|
|
|
195
195
|
const statusContent = (_jsxs(_Fragment, { children: [_jsx("text", { fg: "gray", children: "Scope: " }), _jsx("text", { fg: "cyan", children: scopeLabel }), _jsx("text", { fg: "gray", children: " \u2502 Current: " }), _jsx("text", { fg: "green", children: currentValue
|
|
196
196
|
? currentValue.slice(0, 35) + (currentValue.length > 35 ? "..." : "")
|
|
197
197
|
: "(not set)" })] }));
|
|
198
|
-
return (_jsx(ScreenLayout, { title: "claudeup Status Line", currentScreen: "statusline", statusLine: statusContent, footerHints:
|
|
198
|
+
return (_jsx(ScreenLayout, { title: "claudeup Status Line", currentScreen: "statusline", statusLine: statusContent, footerHints: [
|
|
199
|
+
{ keys: ["↑", "↓"], label: "nav" },
|
|
200
|
+
{ keys: ["Enter"], label: "apply" },
|
|
201
|
+
{ keys: ["p"], label: "project" },
|
|
202
|
+
{ keys: ["g"], label: "global" },
|
|
203
|
+
{ keys: ["r"], label: "reset" },
|
|
204
|
+
], listPanel: _jsx(ScrollableList, { items: listItems, selectedIndex: statusLine.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderPreview() }));
|
|
199
205
|
}
|
|
200
206
|
export default StatusLineScreen;
|
|
@@ -393,7 +393,13 @@ export function StatusLineScreen() {
|
|
|
393
393
|
title="claudeup Status Line"
|
|
394
394
|
currentScreen={"statusline" as never}
|
|
395
395
|
statusLine={statusContent}
|
|
396
|
-
footerHints=
|
|
396
|
+
footerHints={[
|
|
397
|
+
{ keys: ["↑", "↓"], label: "nav" },
|
|
398
|
+
{ keys: ["Enter"], label: "apply" },
|
|
399
|
+
{ keys: ["p"], label: "project" },
|
|
400
|
+
{ keys: ["g"], label: "global" },
|
|
401
|
+
{ keys: ["r"], label: "reset" },
|
|
402
|
+
]}
|
|
397
403
|
listPanel={
|
|
398
404
|
<ScrollableList
|
|
399
405
|
items={listItems}
|
package/src/ui/screens/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
|
|
|
7
7
|
export { ProfilesScreen } from "./ProfilesScreen.js";
|
|
8
8
|
export { SkillsScreen } from "./SkillsScreen.js";
|
|
9
9
|
export { GitignoreScreen } from "./GitignoreScreen.js";
|
|
10
|
+
export { AliasScreen } from "./AliasScreen.js";
|
package/src/ui/screens/index.ts
CHANGED
|
@@ -7,3 +7,4 @@ export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
|
|
|
7
7
|
export { ProfilesScreen } from "./ProfilesScreen.js";
|
|
8
8
|
export { SkillsScreen } from "./SkillsScreen.js";
|
|
9
9
|
export { GitignoreScreen } from "./GitignoreScreen.js";
|
|
10
|
+
export { AliasScreen } from "./AliasScreen.js";
|
package/src/ui/state/types.ts
CHANGED
|
@@ -21,7 +21,8 @@ export type Screen =
|
|
|
21
21
|
| "model-selector"
|
|
22
22
|
| "profiles"
|
|
23
23
|
| "skills"
|
|
24
|
-
| "gitignore"
|
|
24
|
+
| "gitignore"
|
|
25
|
+
| "alias";
|
|
25
26
|
|
|
26
27
|
export type Route =
|
|
27
28
|
| { screen: "plugins" }
|
|
@@ -32,7 +33,8 @@ export type Route =
|
|
|
32
33
|
| { screen: "model-selector" }
|
|
33
34
|
| { screen: "profiles" }
|
|
34
35
|
| { screen: "skills" }
|
|
35
|
-
| { screen: "gitignore" }
|
|
36
|
+
| { screen: "gitignore" }
|
|
37
|
+
| { screen: "alias" };
|
|
36
38
|
|
|
37
39
|
// ============================================================================
|
|
38
40
|
// Async Data Types
|