claudeup 3.7.2 → 3.9.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/data/settings-catalog.js +612 -0
- package/src/data/settings-catalog.ts +689 -0
- package/src/data/skill-repos.js +86 -0
- package/src/data/skill-repos.ts +97 -0
- package/src/services/plugin-manager.js +2 -0
- package/src/services/plugin-manager.ts +3 -0
- package/src/services/profiles.js +161 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/settings-manager.js +108 -0
- package/src/services/settings-manager.ts +140 -0
- package/src/services/skills-manager.js +239 -0
- package/src/services/skills-manager.ts +328 -0
- package/src/services/skillsmp-client.js +67 -0
- package/src/services/skillsmp-client.ts +89 -0
- package/src/types/index.ts +101 -1
- package/src/ui/App.js +23 -18
- package/src/ui/App.tsx +27 -23
- package/src/ui/components/TabBar.js +9 -8
- package/src/ui/components/TabBar.tsx +15 -19
- package/src/ui/components/layout/ScreenLayout.js +8 -14
- package/src/ui/components/layout/ScreenLayout.tsx +51 -58
- package/src/ui/components/modals/ModalContainer.js +43 -11
- package/src/ui/components/modals/ModalContainer.tsx +44 -12
- package/src/ui/components/modals/SelectModal.js +4 -18
- package/src/ui/components/modals/SelectModal.tsx +10 -21
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +8 -8
- package/src/ui/screens/EnvVarsScreen.js +248 -116
- package/src/ui/screens/EnvVarsScreen.tsx +419 -184
- package/src/ui/screens/McpRegistryScreen.tsx +18 -6
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +15 -5
- package/src/ui/screens/ModelSelectorScreen.js +3 -5
- package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
- package/src/ui/screens/PluginsScreen.js +154 -66
- package/src/ui/screens/PluginsScreen.tsx +280 -97
- package/src/ui/screens/ProfilesScreen.js +255 -0
- package/src/ui/screens/ProfilesScreen.tsx +487 -0
- package/src/ui/screens/SkillsScreen.js +325 -0
- package/src/ui/screens/SkillsScreen.tsx +574 -0
- package/src/ui/screens/StatusLineScreen.js +2 -2
- package/src/ui/screens/StatusLineScreen.tsx +10 -12
- package/src/ui/screens/index.js +3 -2
- package/src/ui/screens/index.ts +3 -2
- package/src/ui/state/AppContext.js +2 -1
- package/src/ui/state/AppContext.tsx +2 -0
- package/src/ui/state/reducer.js +151 -19
- package/src/ui/state/reducer.ts +167 -19
- package/src/ui/state/types.ts +58 -14
- package/src/utils/clipboard.js +56 -0
- package/src/utils/clipboard.ts +58 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { useEffect, useCallback, useMemo } from "react";
|
|
3
|
+
import { useApp, useModal } from "../state/AppContext.js";
|
|
4
|
+
import { useDimensions } from "../state/DimensionsContext.js";
|
|
5
|
+
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
6
|
+
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
|
+
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
|
+
import { fetchAvailableSkills, fetchSkillFrontmatter, installSkill, uninstallSkill, } from "../../services/skills-manager.js";
|
|
9
|
+
import { DEFAULT_SKILL_REPOS, RECOMMENDED_SKILLS } from "../../data/skill-repos.js";
|
|
10
|
+
const CATEGORY_COLORS = {
|
|
11
|
+
recommended: "#2e7d32",
|
|
12
|
+
frontend: "#1565c0",
|
|
13
|
+
design: "#6a1b9a",
|
|
14
|
+
media: "#e65100",
|
|
15
|
+
security: "#b71c1c",
|
|
16
|
+
debugging: "#00838f",
|
|
17
|
+
database: "#4527a0",
|
|
18
|
+
search: "#4e342e",
|
|
19
|
+
general: "#333333",
|
|
20
|
+
};
|
|
21
|
+
const RECOMMENDED_NAMES = new Set(RECOMMENDED_SKILLS.map((r) => r.name));
|
|
22
|
+
export function SkillsScreen() {
|
|
23
|
+
const { state, dispatch } = useApp();
|
|
24
|
+
const { skills: skillsState } = state;
|
|
25
|
+
const modal = useModal();
|
|
26
|
+
const dimensions = useDimensions();
|
|
27
|
+
const isSearchActive = state.isSearching &&
|
|
28
|
+
state.currentRoute.screen === "skills" &&
|
|
29
|
+
!state.modal;
|
|
30
|
+
// Fetch data
|
|
31
|
+
const fetchData = useCallback(async () => {
|
|
32
|
+
dispatch({ type: "SKILLS_DATA_LOADING" });
|
|
33
|
+
try {
|
|
34
|
+
const skills = await fetchAvailableSkills(DEFAULT_SKILL_REPOS, state.projectPath);
|
|
35
|
+
dispatch({ type: "SKILLS_DATA_SUCCESS", skills });
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
dispatch({
|
|
39
|
+
type: "SKILLS_DATA_ERROR",
|
|
40
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}, [dispatch, state.projectPath]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
fetchData();
|
|
46
|
+
}, [fetchData, state.dataRefreshVersion]);
|
|
47
|
+
// Build flat list: recommended first (as their own category), then by repo
|
|
48
|
+
const allItems = useMemo(() => {
|
|
49
|
+
if (skillsState.skills.status !== "success")
|
|
50
|
+
return [];
|
|
51
|
+
const skills = skillsState.skills.data;
|
|
52
|
+
const query = skillsState.searchQuery.toLowerCase();
|
|
53
|
+
const filtered = query
|
|
54
|
+
? skills.filter((s) => s.name.toLowerCase().includes(query) ||
|
|
55
|
+
s.source.repo.toLowerCase().includes(query) ||
|
|
56
|
+
s.frontmatter?.description?.toLowerCase().includes(query))
|
|
57
|
+
: skills;
|
|
58
|
+
const items = [];
|
|
59
|
+
// Recommended section
|
|
60
|
+
const recommendedSkills = filtered.filter((s) => RECOMMENDED_NAMES.has(s.name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())) || RECOMMENDED_SKILLS.some((r) => r.skillPath === s.name));
|
|
61
|
+
if (recommendedSkills.length > 0) {
|
|
62
|
+
items.push({
|
|
63
|
+
id: "cat:recommended",
|
|
64
|
+
type: "category",
|
|
65
|
+
label: "Recommended",
|
|
66
|
+
categoryKey: "recommended",
|
|
67
|
+
});
|
|
68
|
+
for (const skill of recommendedSkills) {
|
|
69
|
+
items.push({
|
|
70
|
+
id: `skill:${skill.id}`,
|
|
71
|
+
type: "skill",
|
|
72
|
+
label: skill.name,
|
|
73
|
+
skill,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Group remaining skills by repo
|
|
78
|
+
const repoMap = new Map();
|
|
79
|
+
for (const skill of filtered) {
|
|
80
|
+
const isRec = recommendedSkills.includes(skill);
|
|
81
|
+
if (isRec)
|
|
82
|
+
continue;
|
|
83
|
+
const existing = repoMap.get(skill.source.repo) || [];
|
|
84
|
+
existing.push(skill);
|
|
85
|
+
repoMap.set(skill.source.repo, existing);
|
|
86
|
+
}
|
|
87
|
+
for (const [repo, repoSkills] of repoMap) {
|
|
88
|
+
items.push({
|
|
89
|
+
id: `cat:${repo}`,
|
|
90
|
+
type: "category",
|
|
91
|
+
label: `${repo} (${repoSkills.length})`,
|
|
92
|
+
categoryKey: repo,
|
|
93
|
+
});
|
|
94
|
+
for (const skill of repoSkills) {
|
|
95
|
+
items.push({
|
|
96
|
+
id: `skill:${skill.id}`,
|
|
97
|
+
type: "skill",
|
|
98
|
+
label: skill.name,
|
|
99
|
+
skill,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return items;
|
|
104
|
+
}, [skillsState.skills, skillsState.searchQuery]);
|
|
105
|
+
const selectableItems = useMemo(() => allItems.filter((item) => item.type === "skill" || item.type === "category"), [allItems]);
|
|
106
|
+
const selectedItem = selectableItems[skillsState.selectedIndex];
|
|
107
|
+
const selectedSkill = selectedItem?.type === "skill" ? selectedItem.skill : undefined;
|
|
108
|
+
// Lazy-load frontmatter for selected skill
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!selectedSkill || selectedSkill.frontmatter)
|
|
111
|
+
return;
|
|
112
|
+
fetchSkillFrontmatter(selectedSkill).then((fm) => {
|
|
113
|
+
dispatch({
|
|
114
|
+
type: "SKILLS_UPDATE_ITEM",
|
|
115
|
+
name: selectedSkill.name,
|
|
116
|
+
updates: { frontmatter: fm },
|
|
117
|
+
});
|
|
118
|
+
}).catch(() => { });
|
|
119
|
+
}, [selectedSkill?.id, dispatch]);
|
|
120
|
+
// Install handler
|
|
121
|
+
const handleInstall = useCallback(async (scope) => {
|
|
122
|
+
if (!selectedSkill)
|
|
123
|
+
return;
|
|
124
|
+
modal.loading(`Installing ${selectedSkill.name}...`);
|
|
125
|
+
try {
|
|
126
|
+
await installSkill(selectedSkill, scope, state.projectPath);
|
|
127
|
+
modal.hideModal();
|
|
128
|
+
dispatch({
|
|
129
|
+
type: "SKILLS_UPDATE_ITEM",
|
|
130
|
+
name: selectedSkill.name,
|
|
131
|
+
updates: {
|
|
132
|
+
installed: true,
|
|
133
|
+
installedScope: scope,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
await modal.message("Installed", `${selectedSkill.name} installed to ${scope === "user" ? "~/.claude/skills/" : ".claude/skills/"}`, "success");
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
modal.hideModal();
|
|
140
|
+
await modal.message("Error", `Failed to install: ${error}`, "error");
|
|
141
|
+
}
|
|
142
|
+
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
143
|
+
// Uninstall handler
|
|
144
|
+
const handleUninstall = useCallback(async () => {
|
|
145
|
+
if (!selectedSkill || !selectedSkill.installed)
|
|
146
|
+
return;
|
|
147
|
+
const scope = selectedSkill.installedScope;
|
|
148
|
+
if (!scope)
|
|
149
|
+
return;
|
|
150
|
+
const confirmed = await modal.confirm(`Uninstall "${selectedSkill.name}"?`, `This will remove it from the ${scope} scope.`);
|
|
151
|
+
if (!confirmed)
|
|
152
|
+
return;
|
|
153
|
+
modal.loading(`Uninstalling ${selectedSkill.name}...`);
|
|
154
|
+
try {
|
|
155
|
+
await uninstallSkill(selectedSkill.name, scope, state.projectPath);
|
|
156
|
+
modal.hideModal();
|
|
157
|
+
dispatch({
|
|
158
|
+
type: "SKILLS_UPDATE_ITEM",
|
|
159
|
+
name: selectedSkill.name,
|
|
160
|
+
updates: {
|
|
161
|
+
installed: false,
|
|
162
|
+
installedScope: null,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
await modal.message("Uninstalled", `${selectedSkill.name} removed.`, "success");
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
modal.hideModal();
|
|
169
|
+
await modal.message("Error", `Failed to uninstall: ${error}`, "error");
|
|
170
|
+
}
|
|
171
|
+
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
172
|
+
// Keyboard handling
|
|
173
|
+
useKeyboard((event) => {
|
|
174
|
+
if (state.modal)
|
|
175
|
+
return;
|
|
176
|
+
if (event.name === "up" || event.name === "k") {
|
|
177
|
+
if (state.isSearching)
|
|
178
|
+
return;
|
|
179
|
+
const newIndex = Math.max(0, skillsState.selectedIndex - 1);
|
|
180
|
+
dispatch({ type: "SKILLS_SELECT", index: newIndex });
|
|
181
|
+
}
|
|
182
|
+
else if (event.name === "down" || event.name === "j") {
|
|
183
|
+
if (state.isSearching)
|
|
184
|
+
return;
|
|
185
|
+
const newIndex = Math.min(Math.max(0, selectableItems.length - 1), skillsState.selectedIndex + 1);
|
|
186
|
+
dispatch({ type: "SKILLS_SELECT", index: newIndex });
|
|
187
|
+
}
|
|
188
|
+
else if (event.name === "u") {
|
|
189
|
+
if (state.isSearching)
|
|
190
|
+
return;
|
|
191
|
+
if (selectedSkill) {
|
|
192
|
+
if (selectedSkill.installed && selectedSkill.installedScope === "user") {
|
|
193
|
+
handleUninstall();
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
handleInstall("user");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (event.name === "p") {
|
|
201
|
+
if (state.isSearching)
|
|
202
|
+
return;
|
|
203
|
+
if (selectedSkill) {
|
|
204
|
+
if (selectedSkill.installed && selectedSkill.installedScope === "project") {
|
|
205
|
+
handleUninstall();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
handleInstall("project");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (event.name === "return" || event.name === "enter") {
|
|
213
|
+
if (state.isSearching)
|
|
214
|
+
return;
|
|
215
|
+
if (selectedSkill && !selectedSkill.installed) {
|
|
216
|
+
handleInstall("project");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (event.name === "d") {
|
|
220
|
+
if (state.isSearching)
|
|
221
|
+
return;
|
|
222
|
+
if (selectedSkill?.installed) {
|
|
223
|
+
handleUninstall();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (event.name === "r") {
|
|
227
|
+
if (state.isSearching)
|
|
228
|
+
return;
|
|
229
|
+
fetchData();
|
|
230
|
+
}
|
|
231
|
+
else if (event.name === "escape") {
|
|
232
|
+
if (skillsState.searchQuery) {
|
|
233
|
+
dispatch({ type: "SKILLS_SET_SEARCH", query: "" });
|
|
234
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (event.name === "backspace") {
|
|
238
|
+
if (skillsState.searchQuery) {
|
|
239
|
+
const newQuery = skillsState.searchQuery.slice(0, -1);
|
|
240
|
+
dispatch({ type: "SKILLS_SET_SEARCH", query: newQuery });
|
|
241
|
+
if (!newQuery) {
|
|
242
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (event.name &&
|
|
247
|
+
event.name.length === 1 &&
|
|
248
|
+
!/[0-9]/.test(event.name) &&
|
|
249
|
+
!event.ctrl &&
|
|
250
|
+
!event.meta) {
|
|
251
|
+
// Inline search: type to filter
|
|
252
|
+
const newQuery = skillsState.searchQuery + event.name;
|
|
253
|
+
dispatch({ type: "SKILLS_SET_SEARCH", query: newQuery });
|
|
254
|
+
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
const renderListItem = (item, _idx, isSelected) => {
|
|
258
|
+
if (item.type === "category") {
|
|
259
|
+
const catKey = item.categoryKey || "general";
|
|
260
|
+
const isRec = catKey === "recommended";
|
|
261
|
+
const bgColor = CATEGORY_COLORS[catKey] || CATEGORY_COLORS.general;
|
|
262
|
+
const star = isRec ? "★ " : "";
|
|
263
|
+
if (isSelected) {
|
|
264
|
+
return (_jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", star, item.label, " "] }) }));
|
|
265
|
+
}
|
|
266
|
+
return (_jsx("text", { bg: bgColor, fg: "white", children: _jsxs("strong", { children: [" ", star, item.label, " "] }) }));
|
|
267
|
+
}
|
|
268
|
+
if (item.type === "skill" && item.skill) {
|
|
269
|
+
const skill = item.skill;
|
|
270
|
+
const indicator = skill.installed ? "●" : "○";
|
|
271
|
+
const indicatorColor = skill.installed ? "cyan" : "gray";
|
|
272
|
+
const scopeLabel = skill.installedScope
|
|
273
|
+
? skill.installedScope === "user"
|
|
274
|
+
? "[u]"
|
|
275
|
+
: "[p]"
|
|
276
|
+
: " ";
|
|
277
|
+
const updateBadge = skill.hasUpdate ? "[UPDT] " : "";
|
|
278
|
+
if (isSelected) {
|
|
279
|
+
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", indicator, " ", scopeLabel, " ", updateBadge, skill.name.padEnd(30), skill.source.repo, " "] }));
|
|
280
|
+
}
|
|
281
|
+
return (_jsxs("text", { children: [_jsxs("span", { fg: indicatorColor, children: [" ", indicator, " "] }), _jsx("span", { fg: skill.installedScope === "user" ? "cyan" : skill.installedScope === "project" ? "green" : "gray", children: scopeLabel }), _jsx("span", { children: " " }), skill.hasUpdate && (_jsx("span", { bg: "yellow", fg: "black", children: updateBadge })), _jsx("span", { fg: "white", children: skill.name.padEnd(30) }), _jsx("span", { fg: "gray", children: skill.source.repo })] }));
|
|
282
|
+
}
|
|
283
|
+
return _jsx("text", { fg: "gray", children: item.label });
|
|
284
|
+
};
|
|
285
|
+
const renderDetail = () => {
|
|
286
|
+
if (skillsState.skills.status === "loading") {
|
|
287
|
+
return _jsx("text", { fg: "gray", children: "Loading skills..." });
|
|
288
|
+
}
|
|
289
|
+
if (skillsState.skills.status === "error") {
|
|
290
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "red", children: "Failed to load skills" }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: skillsState.skills.error.message }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "Set GITHUB_TOKEN to increase rate limits." }) })] }));
|
|
291
|
+
}
|
|
292
|
+
if (!selectedItem) {
|
|
293
|
+
return _jsx("text", { fg: "gray", children: "Select a skill to see details" });
|
|
294
|
+
}
|
|
295
|
+
if (selectedItem.type === "category") {
|
|
296
|
+
const catKey = selectedItem.categoryKey || "general";
|
|
297
|
+
const color = CATEGORY_COLORS[catKey] ? "green" : "cyan";
|
|
298
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: color, children: _jsx("strong", { children: selectedItem.label }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "Skills in this category" }) })] }));
|
|
299
|
+
}
|
|
300
|
+
if (!selectedSkill)
|
|
301
|
+
return null;
|
|
302
|
+
const fm = selectedSkill.frontmatter;
|
|
303
|
+
const scopeColor = selectedSkill.installedScope === "user" ? "cyan" : "green";
|
|
304
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: selectedSkill.name }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: fm ? fm.description : "Loading..." }) }), fm?.category && (_jsx("box", { marginTop: 1, children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Category " }), _jsx("span", { fg: "cyan", children: fm.category })] }) })), fm?.author && (_jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Author " }), _jsx("span", { children: fm.author })] }) })), fm?.version && (_jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Version " }), _jsx("span", { children: fm.version })] }) })), fm?.tags && fm.tags.length > 0 && (_jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Tags " }), _jsx("span", { children: fm.tags.join(", ") })] }) })), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Source " }), _jsx("span", { fg: "#5c9aff", children: selectedSkill.source.repo })] }), _jsxs("text", { children: [_jsx("span", { fg: "gray", children: " " }), _jsx("span", { fg: "gray", children: selectedSkill.repoPath })] })] }), selectedSkill.installed && selectedSkill.installedScope && (_jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { children: "─".repeat(24) }), _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Installed " }), _jsxs("span", { fg: scopeColor, children: [selectedSkill.installedScope === "user"
|
|
305
|
+
? "~/.claude/skills/"
|
|
306
|
+
: ".claude/skills/", selectedSkill.name, "/"] })] })] })), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { children: "─".repeat(24) }), _jsx("text", { children: _jsx("strong", { children: "Install scope:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { bg: "cyan", fg: "black", children: " u " }), _jsx("span", { fg: selectedSkill.installedScope === "user" ? "cyan" : "gray", children: selectedSkill.installedScope === "user" ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { fg: "gray", children: " ~/.claude/skills/" })] }), _jsxs("text", { children: [_jsx("span", { bg: "green", fg: "black", children: " p " }), _jsx("span", { fg: selectedSkill.installedScope === "project" ? "green" : "gray", children: selectedSkill.installedScope === "project" ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { fg: "gray", children: " .claude/skills/" })] })] })] }), selectedSkill.hasUpdate && (_jsx("box", { marginTop: 1, children: _jsx("text", { bg: "yellow", fg: "black", children: " UPDATE AVAILABLE " }) })), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [!selectedSkill.installed && (_jsx("text", { fg: "gray", children: "Press u/p to install in scope" })), selectedSkill.installed && (_jsx("text", { fg: "gray", children: "Press d to uninstall" }))] })] }));
|
|
307
|
+
};
|
|
308
|
+
const skills = skillsState.skills.status === "success" ? skillsState.skills.data : [];
|
|
309
|
+
const installedCount = skills.filter((s) => s.installed).length;
|
|
310
|
+
const totalCount = skills.length;
|
|
311
|
+
const updateCount = skills.filter((s) => s.hasUpdate).length;
|
|
312
|
+
const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Skills: " }), _jsxs("span", { fg: "cyan", children: [installedCount, " installed"] }), _jsxs("span", { fg: "gray", children: [" \u2502 ", totalCount, " available"] }), updateCount > 0 && (_jsxs("span", { fg: "yellow", children: [" \u2502 ", updateCount, " updates"] }))] }));
|
|
313
|
+
return (_jsx(ScreenLayout, { title: "claudeup Skills", currentScreen: "skills", statusLine: statusContent, search: skillsState.searchQuery || isSearchActive
|
|
314
|
+
? {
|
|
315
|
+
isActive: isSearchActive,
|
|
316
|
+
query: skillsState.searchQuery,
|
|
317
|
+
placeholder: "type to search",
|
|
318
|
+
}
|
|
319
|
+
: undefined, footerHints: "\u2191\u2193:nav \u2502 u:user scope \u2502 p:project scope \u2502 Enter:install \u2502 d:uninstall \u2502 type to search", listPanel: skillsState.skills.status !== "success" ? (_jsx("text", { fg: "gray", children: skillsState.skills.status === "loading"
|
|
320
|
+
? "Loading skills..."
|
|
321
|
+
: skillsState.skills.status === "error"
|
|
322
|
+
? "Error loading skills"
|
|
323
|
+
: "Press r to load skills" })) : (_jsx(ScrollableList, { items: selectableItems, selectedIndex: skillsState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
|
|
324
|
+
}
|
|
325
|
+
export default SkillsScreen;
|