claudeup 4.5.4 → 4.6.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/skill-repos.js +56 -0
- package/src/data/skill-repos.ts +70 -1
- package/src/prerunner/index.js +12 -1
- package/src/prerunner/index.ts +14 -0
- package/src/services/claude-settings.js +128 -10
- package/src/services/claude-settings.ts +149 -9
- package/src/services/skills-manager.js +50 -2
- package/src/services/skills-manager.ts +65 -2
- package/src/types/index.ts +29 -0
- package/src/ui/adapters/skillsAdapter.js +57 -9
- package/src/ui/adapters/skillsAdapter.ts +72 -10
- package/src/ui/components/ScrollableList.js +8 -20
- package/src/ui/components/ScrollableList.tsx +16 -29
- package/src/ui/renderers/skillRenderers.js +72 -9
- package/src/ui/renderers/skillRenderers.tsx +176 -11
- package/src/ui/screens/PluginsScreen.js +1 -1
- package/src/ui/screens/PluginsScreen.tsx +1 -0
- package/src/ui/screens/SkillsScreen.js +177 -39
- package/src/ui/screens/SkillsScreen.tsx +199 -34
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { STAR_RELIABILITY_INFO } from "../../data/skill-repos.js";
|
|
2
3
|
import { SelectableRow, ListCategoryRow, ScopeSquares, MetaText, KeyValueLine, DetailSection, } from "../components/primitives/index.js";
|
|
3
4
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
4
|
-
function
|
|
5
|
+
function formatStarsNum(stars) {
|
|
5
6
|
if (!stars)
|
|
6
7
|
return "";
|
|
7
8
|
if (stars >= 1000000)
|
|
8
|
-
return
|
|
9
|
+
return `${(stars / 1000000).toFixed(1)}M`;
|
|
9
10
|
if (stars >= 10000)
|
|
10
|
-
return
|
|
11
|
+
return `${Math.round(stars / 1000)}K`;
|
|
11
12
|
if (stars >= 1000)
|
|
12
|
-
return
|
|
13
|
-
return
|
|
13
|
+
return `${(stars / 1000).toFixed(1)}K`;
|
|
14
|
+
return `${stars}`;
|
|
15
|
+
}
|
|
16
|
+
/** Star icon and color based on reliability classification */
|
|
17
|
+
function starIcon(reliability) {
|
|
18
|
+
if (reliability === "mega-repo")
|
|
19
|
+
return "☆";
|
|
20
|
+
if (reliability === "skill-dump")
|
|
21
|
+
return "☆";
|
|
22
|
+
return "★";
|
|
23
|
+
}
|
|
24
|
+
function starColor(reliability) {
|
|
25
|
+
if (reliability === "mega-repo")
|
|
26
|
+
return "gray";
|
|
27
|
+
if (reliability === "skill-dump")
|
|
28
|
+
return "#888800";
|
|
29
|
+
return "yellow";
|
|
30
|
+
}
|
|
31
|
+
function formatStars(stars, reliability) {
|
|
32
|
+
if (!stars)
|
|
33
|
+
return "";
|
|
34
|
+
return `${starIcon(reliability)} ${formatStarsNum(stars)}`;
|
|
14
35
|
}
|
|
15
36
|
// ─── Category renderer ────────────────────────────────────────────────────────
|
|
16
37
|
const categoryRenderer = {
|
|
@@ -37,24 +58,60 @@ const skillRenderer = {
|
|
|
37
58
|
const { skill } = item;
|
|
38
59
|
const hasUser = skill.installedScope === "user";
|
|
39
60
|
const hasProject = skill.installedScope === "project";
|
|
40
|
-
const
|
|
61
|
+
const reliability = skill.starReliability;
|
|
62
|
+
const starsStr = formatStars(skill.stars, reliability);
|
|
63
|
+
const sColor = starColor(reliability);
|
|
41
64
|
const displayName = truncateName(skill.name);
|
|
42
|
-
|
|
65
|
+
const indentLevel = item.indent ?? 1;
|
|
66
|
+
return (_jsxs(SelectableRow, { selected: isSelected, indent: indentLevel, children: [_jsx(ScopeSquares, { user: hasUser, project: hasProject, selected: isSelected }), _jsx("span", { children: " " }), _jsx("span", { fg: isSelected ? "white" : skill.installed ? "white" : "gray", children: displayName }), skill.hasUpdate ? _jsx(MetaText, { text: " \u2B06", tone: "warning" }) : null, starsStr ? _jsx("span", { fg: sColor, children: ` ${starsStr}` }) : null] }));
|
|
43
67
|
},
|
|
44
68
|
renderDetail: ({ item }) => {
|
|
45
69
|
const { skill } = item;
|
|
46
70
|
const fm = skill.frontmatter;
|
|
47
71
|
const description = fm?.description || skill.description || "Loading...";
|
|
48
|
-
const
|
|
49
|
-
|
|
72
|
+
const reliability = skill.starReliability;
|
|
73
|
+
const starsStr = formatStars(skill.stars, reliability);
|
|
74
|
+
const sColor = starColor(reliability);
|
|
75
|
+
const reliabilityInfo = reliability ? STAR_RELIABILITY_INFO[reliability] : null;
|
|
76
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { fg: "cyan", children: [_jsx("strong", { children: skill.name }), starsStr ? _jsxs("span", { fg: sColor, children: [" ", starsStr] }) : null] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: description }) }), fm?.category ? (_jsx(KeyValueLine, { label: "Category", value: _jsx("span", { fg: "cyan", children: fm.category }) })) : null, fm?.author ? (_jsx(KeyValueLine, { label: "Author", value: _jsx("span", { children: fm.author }) })) : null, fm?.version ? (_jsx(KeyValueLine, { label: "Version", value: _jsx("span", { children: fm.version }) })) : null, fm?.tags && fm.tags.length > 0 ? (_jsx(KeyValueLine, { label: "Tags", value: _jsx("span", { children: fm.tags.join(", ") }) })) : null, _jsxs(DetailSection, { children: [_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Source " }), _jsx("span", { fg: "#5c9aff", children: skill.source.repo })] }), _jsxs("text", { children: [_jsx("span", { fg: "gray", children: " " }), _jsx("span", { fg: "gray", children: skill.repoPath })] })] }), reliabilityInfo && reliability !== "dedicated" && (_jsx("box", { marginTop: 1, children: _jsxs("text", { children: [_jsxs("span", { fg: sColor, children: [starIcon(reliability), " "] }), _jsx("span", { fg: sColor, children: _jsx("strong", { children: reliabilityInfo.label }) }), _jsxs("span", { fg: "gray", children: [" \u2014 ", reliabilityInfo.description] })] }) })), _jsxs(DetailSection, { children: [_jsx("text", { children: "─".repeat(24) }), _jsx("text", { children: _jsx("strong", { children: "Scopes:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { bg: "cyan", fg: "black", children: " u " }), _jsx("span", { fg: skill.installedScope === "user" ? "cyan" : "gray", children: skill.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: skill.installedScope === "project" ? "green" : "gray", children: skill.installedScope === "project" ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { fg: "gray", children: " .claude/skills/" })] })] })] }), skill.hasUpdate && (_jsx("box", { marginTop: 1, children: _jsx("text", { bg: "yellow", fg: "black", children: " UPDATE AVAILABLE " }) })), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: skill.installed
|
|
50
77
|
? "Press u/p to toggle scope"
|
|
51
78
|
: "Press u/p to install" }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { bg: "#555555", fg: "white", children: " o " }), _jsx("span", { fg: "gray", children: " Open in browser" })] }) })] }));
|
|
52
79
|
},
|
|
53
80
|
};
|
|
81
|
+
// ─── Skill Set renderer ──────────────────────────────────────────────────────
|
|
82
|
+
const MAX_SET_NAME_LEN = 28;
|
|
83
|
+
const skillSetRenderer = {
|
|
84
|
+
renderRow: ({ item, isSelected }) => {
|
|
85
|
+
const { skillSet, expanded } = item;
|
|
86
|
+
const arrow = expanded ? "\u25BC" : "\u25B6";
|
|
87
|
+
const displayName = skillSet.name.length > MAX_SET_NAME_LEN
|
|
88
|
+
? skillSet.name.slice(0, MAX_SET_NAME_LEN - 1) + "\u2026"
|
|
89
|
+
: skillSet.name;
|
|
90
|
+
// Count installed child skills
|
|
91
|
+
const installedCount = skillSet.loaded
|
|
92
|
+
? skillSet.skills.filter((s) => s.installed).length
|
|
93
|
+
: 0;
|
|
94
|
+
const totalCount = skillSet.loaded ? skillSet.skills.length : "...";
|
|
95
|
+
const countStr = `(${installedCount}/${totalCount})`;
|
|
96
|
+
// Any child installed at user or project scope?
|
|
97
|
+
const hasUser = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "user");
|
|
98
|
+
const hasProject = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "project");
|
|
99
|
+
const starsStr = formatStars(skillSet.stars);
|
|
100
|
+
return (_jsxs(SelectableRow, { selected: isSelected, indent: 1, children: [_jsxs("span", { fg: isSelected ? "white" : "gray", children: [arrow, " "] }), _jsxs("span", { fg: isSelected ? "white" : "yellow", children: [skillSet.icon, " "] }), _jsx("span", { fg: isSelected ? "white" : "cyan", children: _jsx("strong", { children: displayName }) }), _jsxs("span", { fg: "gray", children: [" ", countStr] }), starsStr ? _jsx(MetaText, { text: ` ${starsStr}`, tone: "warning" }) : null, _jsx("span", { children: " " }), _jsx(ScopeSquares, { user: hasUser, project: hasProject, selected: isSelected })] }));
|
|
101
|
+
},
|
|
102
|
+
renderDetail: ({ item }) => {
|
|
103
|
+
const { skillSet, expanded } = item;
|
|
104
|
+
const starsStr = formatStars(skillSet.stars);
|
|
105
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { fg: "cyan", children: [_jsxs("strong", { children: [skillSet.icon, " ", skillSet.name] }), starsStr ? _jsxs("span", { fg: "yellow", children: [" ", starsStr] }) : null] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: skillSet.description }) }), _jsx(DetailSection, { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Source " }), _jsx("span", { fg: "#5c9aff", children: skillSet.repo })] }) }), skillSet.loading && (_jsx("box", { marginTop: 1, children: _jsx("text", { fg: "yellow", children: "Loading skills..." }) })), skillSet.error && (_jsx("box", { marginTop: 1, children: _jsxs("text", { fg: "red", children: ["Error: ", skillSet.error] }) })), skillSet.loaded && skillSet.skills.length > 0 && (_jsxs(DetailSection, { children: [_jsxs("text", { children: [_jsx("strong", { children: "Skills in this set:" }), _jsxs("span", { fg: "gray", children: [" ", "(", skillSet.skills.filter((s) => s.installed).length, "/", skillSet.skills.length, " installed)"] })] }), _jsx("box", { marginTop: 1, flexDirection: "column", children: skillSet.skills.map((s) => (_jsxs("text", { children: [_jsx("span", { fg: s.installedScope === "user" ? "cyan" : "gray", children: s.installedScope === "user" ? "\u25A0" : "\u25A1" }), _jsx("span", { fg: s.installedScope === "project" ? "green" : "gray", children: s.installedScope === "project" ? "\u25A0" : "\u25A1" }), _jsxs("span", { fg: s.installed ? "white" : "gray", children: [" ", s.name] })] }, s.id))) })] })), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: expanded
|
|
106
|
+
? "Press Enter to collapse \u2022 u/p to install all"
|
|
107
|
+
: "Press Enter to expand \u2022 u/p to install all" }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { bg: "#555555", fg: "white", children: " o " }), _jsx("span", { fg: "gray", children: " Open in browser" })] }) })] }));
|
|
108
|
+
},
|
|
109
|
+
};
|
|
54
110
|
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
55
111
|
export const skillRenderers = {
|
|
56
112
|
category: categoryRenderer,
|
|
57
113
|
skill: skillRenderer,
|
|
114
|
+
skillset: skillSetRenderer,
|
|
58
115
|
};
|
|
59
116
|
/**
|
|
60
117
|
* Dispatch rendering by item kind.
|
|
@@ -63,6 +120,9 @@ export function renderSkillRow(item, _index, isSelected) {
|
|
|
63
120
|
if (item.kind === "category") {
|
|
64
121
|
return skillRenderers.category.renderRow({ item, isSelected });
|
|
65
122
|
}
|
|
123
|
+
if (item.kind === "skillset") {
|
|
124
|
+
return skillRenderers.skillset.renderRow({ item, isSelected });
|
|
125
|
+
}
|
|
66
126
|
return skillRenderers.skill.renderRow({ item, isSelected });
|
|
67
127
|
}
|
|
68
128
|
export function renderSkillDetail(item) {
|
|
@@ -71,5 +131,8 @@ export function renderSkillDetail(item) {
|
|
|
71
131
|
if (item.kind === "category") {
|
|
72
132
|
return skillRenderers.category.renderDetail({ item });
|
|
73
133
|
}
|
|
134
|
+
if (item.kind === "skillset") {
|
|
135
|
+
return skillRenderers.skillset.renderDetail({ item });
|
|
136
|
+
}
|
|
74
137
|
return skillRenderers.skill.renderDetail({ item });
|
|
75
138
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ItemRenderer } from "../registry.js";
|
|
3
|
-
import type { SkillBrowserItem, SkillCategoryItem, SkillSkillItem } from "../adapters/skillsAdapter.js";
|
|
3
|
+
import type { SkillBrowserItem, SkillCategoryItem, SkillSkillItem, SkillSetItem } from "../adapters/skillsAdapter.js";
|
|
4
|
+
import type { StarReliability } from "../../types/index.js";
|
|
5
|
+
import { STAR_RELIABILITY_INFO } from "../../data/skill-repos.js";
|
|
4
6
|
import {
|
|
5
7
|
SelectableRow,
|
|
6
8
|
ListCategoryRow,
|
|
@@ -14,12 +16,30 @@ import {
|
|
|
14
16
|
|
|
15
17
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
18
|
|
|
17
|
-
function
|
|
19
|
+
function formatStarsNum(stars?: number): string {
|
|
18
20
|
if (!stars) return "";
|
|
19
|
-
if (stars >= 1000000) return
|
|
20
|
-
if (stars >= 10000) return
|
|
21
|
-
if (stars >= 1000) return
|
|
22
|
-
return
|
|
21
|
+
if (stars >= 1000000) return `${(stars / 1000000).toFixed(1)}M`;
|
|
22
|
+
if (stars >= 10000) return `${Math.round(stars / 1000)}K`;
|
|
23
|
+
if (stars >= 1000) return `${(stars / 1000).toFixed(1)}K`;
|
|
24
|
+
return `${stars}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Star icon and color based on reliability classification */
|
|
28
|
+
function starIcon(reliability?: StarReliability): string {
|
|
29
|
+
if (reliability === "mega-repo") return "☆";
|
|
30
|
+
if (reliability === "skill-dump") return "☆";
|
|
31
|
+
return "★";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function starColor(reliability?: StarReliability): string {
|
|
35
|
+
if (reliability === "mega-repo") return "gray";
|
|
36
|
+
if (reliability === "skill-dump") return "#888800";
|
|
37
|
+
return "yellow";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatStars(stars?: number, reliability?: StarReliability): string {
|
|
41
|
+
if (!stars) return "";
|
|
42
|
+
return `${starIcon(reliability)} ${formatStarsNum(stars)}`;
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
// ─── Category renderer ────────────────────────────────────────────────────────
|
|
@@ -75,18 +95,21 @@ const skillRenderer: ItemRenderer<SkillSkillItem> = {
|
|
|
75
95
|
const { skill } = item;
|
|
76
96
|
const hasUser = skill.installedScope === "user";
|
|
77
97
|
const hasProject = skill.installedScope === "project";
|
|
78
|
-
const
|
|
98
|
+
const reliability = skill.starReliability;
|
|
99
|
+
const starsStr = formatStars(skill.stars, reliability);
|
|
100
|
+
const sColor = starColor(reliability);
|
|
79
101
|
const displayName = truncateName(skill.name);
|
|
102
|
+
const indentLevel = item.indent ?? 1;
|
|
80
103
|
|
|
81
104
|
return (
|
|
82
|
-
<SelectableRow selected={isSelected} indent={
|
|
105
|
+
<SelectableRow selected={isSelected} indent={indentLevel}>
|
|
83
106
|
<ScopeSquares user={hasUser} project={hasProject} selected={isSelected} />
|
|
84
107
|
<span> </span>
|
|
85
108
|
<span fg={isSelected ? "white" : skill.installed ? "white" : "gray"}>
|
|
86
109
|
{displayName}
|
|
87
110
|
</span>
|
|
88
111
|
{skill.hasUpdate ? <MetaText text=" ⬆" tone="warning" /> : null}
|
|
89
|
-
{starsStr ? <
|
|
112
|
+
{starsStr ? <span fg={sColor}>{` ${starsStr}`}</span> : null}
|
|
90
113
|
</SelectableRow>
|
|
91
114
|
);
|
|
92
115
|
},
|
|
@@ -95,13 +118,16 @@ const skillRenderer: ItemRenderer<SkillSkillItem> = {
|
|
|
95
118
|
const { skill } = item;
|
|
96
119
|
const fm = skill.frontmatter;
|
|
97
120
|
const description = fm?.description || skill.description || "Loading...";
|
|
98
|
-
const
|
|
121
|
+
const reliability = skill.starReliability;
|
|
122
|
+
const starsStr = formatStars(skill.stars, reliability);
|
|
123
|
+
const sColor = starColor(reliability);
|
|
124
|
+
const reliabilityInfo = reliability ? STAR_RELIABILITY_INFO[reliability] : null;
|
|
99
125
|
|
|
100
126
|
return (
|
|
101
127
|
<box flexDirection="column">
|
|
102
128
|
<text fg="cyan">
|
|
103
129
|
<strong>{skill.name}</strong>
|
|
104
|
-
{starsStr ? <span fg=
|
|
130
|
+
{starsStr ? <span fg={sColor}> {starsStr}</span> : null}
|
|
105
131
|
</text>
|
|
106
132
|
|
|
107
133
|
<box marginTop={1}>
|
|
@@ -138,6 +164,16 @@ const skillRenderer: ItemRenderer<SkillSkillItem> = {
|
|
|
138
164
|
</text>
|
|
139
165
|
</DetailSection>
|
|
140
166
|
|
|
167
|
+
{reliabilityInfo && reliability !== "dedicated" && (
|
|
168
|
+
<box marginTop={1}>
|
|
169
|
+
<text>
|
|
170
|
+
<span fg={sColor}>{starIcon(reliability)} </span>
|
|
171
|
+
<span fg={sColor}><strong>{reliabilityInfo.label}</strong></span>
|
|
172
|
+
<span fg="gray"> — {reliabilityInfo.description}</span>
|
|
173
|
+
</text>
|
|
174
|
+
</box>
|
|
175
|
+
)}
|
|
176
|
+
|
|
141
177
|
<DetailSection>
|
|
142
178
|
<text>{"─".repeat(24)}</text>
|
|
143
179
|
<text><strong>Scopes:</strong></text>
|
|
@@ -185,14 +221,137 @@ const skillRenderer: ItemRenderer<SkillSkillItem> = {
|
|
|
185
221
|
},
|
|
186
222
|
};
|
|
187
223
|
|
|
224
|
+
// ─── Skill Set renderer ──────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
const MAX_SET_NAME_LEN = 28;
|
|
227
|
+
|
|
228
|
+
const skillSetRenderer: ItemRenderer<SkillSetItem> = {
|
|
229
|
+
renderRow: ({ item, isSelected }) => {
|
|
230
|
+
const { skillSet, expanded } = item;
|
|
231
|
+
const arrow = expanded ? "\u25BC" : "\u25B6";
|
|
232
|
+
const displayName =
|
|
233
|
+
skillSet.name.length > MAX_SET_NAME_LEN
|
|
234
|
+
? skillSet.name.slice(0, MAX_SET_NAME_LEN - 1) + "\u2026"
|
|
235
|
+
: skillSet.name;
|
|
236
|
+
|
|
237
|
+
// Count installed child skills
|
|
238
|
+
const installedCount = skillSet.loaded
|
|
239
|
+
? skillSet.skills.filter((s) => s.installed).length
|
|
240
|
+
: 0;
|
|
241
|
+
const totalCount = skillSet.loaded ? skillSet.skills.length : "...";
|
|
242
|
+
const countStr = `(${installedCount}/${totalCount})`;
|
|
243
|
+
|
|
244
|
+
// Any child installed at user or project scope?
|
|
245
|
+
const hasUser = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "user");
|
|
246
|
+
const hasProject = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "project");
|
|
247
|
+
|
|
248
|
+
const starsStr = formatStars(skillSet.stars);
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<SelectableRow selected={isSelected} indent={1}>
|
|
252
|
+
<span fg={isSelected ? "white" : "gray"}>{arrow} </span>
|
|
253
|
+
<span fg={isSelected ? "white" : "yellow"}>{skillSet.icon} </span>
|
|
254
|
+
<span fg={isSelected ? "white" : "cyan"}>
|
|
255
|
+
<strong>{displayName}</strong>
|
|
256
|
+
</span>
|
|
257
|
+
<span fg="gray"> {countStr}</span>
|
|
258
|
+
{starsStr ? <MetaText text={` ${starsStr}`} tone="warning" /> : null}
|
|
259
|
+
<span> </span>
|
|
260
|
+
<ScopeSquares user={hasUser} project={hasProject} selected={isSelected} />
|
|
261
|
+
</SelectableRow>
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
renderDetail: ({ item }) => {
|
|
266
|
+
const { skillSet, expanded } = item;
|
|
267
|
+
const starsStr = formatStars(skillSet.stars);
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<box flexDirection="column">
|
|
271
|
+
<text fg="cyan">
|
|
272
|
+
<strong>
|
|
273
|
+
{skillSet.icon} {skillSet.name}
|
|
274
|
+
</strong>
|
|
275
|
+
{starsStr ? <span fg="yellow"> {starsStr}</span> : null}
|
|
276
|
+
</text>
|
|
277
|
+
|
|
278
|
+
<box marginTop={1}>
|
|
279
|
+
<text fg="white">{skillSet.description}</text>
|
|
280
|
+
</box>
|
|
281
|
+
|
|
282
|
+
<DetailSection>
|
|
283
|
+
<text>
|
|
284
|
+
<span fg="gray">Source </span>
|
|
285
|
+
<span fg="#5c9aff">{skillSet.repo}</span>
|
|
286
|
+
</text>
|
|
287
|
+
</DetailSection>
|
|
288
|
+
|
|
289
|
+
{skillSet.loading && (
|
|
290
|
+
<box marginTop={1}>
|
|
291
|
+
<text fg="yellow">Loading skills...</text>
|
|
292
|
+
</box>
|
|
293
|
+
)}
|
|
294
|
+
|
|
295
|
+
{skillSet.error && (
|
|
296
|
+
<box marginTop={1}>
|
|
297
|
+
<text fg="red">Error: {skillSet.error}</text>
|
|
298
|
+
</box>
|
|
299
|
+
)}
|
|
300
|
+
|
|
301
|
+
{skillSet.loaded && skillSet.skills.length > 0 && (
|
|
302
|
+
<DetailSection>
|
|
303
|
+
<text>
|
|
304
|
+
<strong>Skills in this set:</strong>
|
|
305
|
+
<span fg="gray">
|
|
306
|
+
{" "}
|
|
307
|
+
({skillSet.skills.filter((s) => s.installed).length}/
|
|
308
|
+
{skillSet.skills.length} installed)
|
|
309
|
+
</span>
|
|
310
|
+
</text>
|
|
311
|
+
<box marginTop={1} flexDirection="column">
|
|
312
|
+
{skillSet.skills.map((s) => (
|
|
313
|
+
<text key={s.id}>
|
|
314
|
+
<span fg={s.installedScope === "user" ? "cyan" : "gray"}>
|
|
315
|
+
{s.installedScope === "user" ? "\u25A0" : "\u25A1"}
|
|
316
|
+
</span>
|
|
317
|
+
<span fg={s.installedScope === "project" ? "green" : "gray"}>
|
|
318
|
+
{s.installedScope === "project" ? "\u25A0" : "\u25A1"}
|
|
319
|
+
</span>
|
|
320
|
+
<span fg={s.installed ? "white" : "gray"}> {s.name}</span>
|
|
321
|
+
</text>
|
|
322
|
+
))}
|
|
323
|
+
</box>
|
|
324
|
+
</DetailSection>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
<box marginTop={1}>
|
|
328
|
+
<text fg="gray">
|
|
329
|
+
{expanded
|
|
330
|
+
? "Press Enter to collapse \u2022 u/p to install all"
|
|
331
|
+
: "Press Enter to expand \u2022 u/p to install all"}
|
|
332
|
+
</text>
|
|
333
|
+
</box>
|
|
334
|
+
<box>
|
|
335
|
+
<text>
|
|
336
|
+
<span bg="#555555" fg="white"> o </span>
|
|
337
|
+
<span fg="gray"> Open in browser</span>
|
|
338
|
+
</text>
|
|
339
|
+
</box>
|
|
340
|
+
</box>
|
|
341
|
+
);
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
188
345
|
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
189
346
|
|
|
190
347
|
export const skillRenderers: {
|
|
191
348
|
category: ItemRenderer<SkillCategoryItem>;
|
|
192
349
|
skill: ItemRenderer<SkillSkillItem>;
|
|
350
|
+
skillset: ItemRenderer<SkillSetItem>;
|
|
193
351
|
} = {
|
|
194
352
|
category: categoryRenderer,
|
|
195
353
|
skill: skillRenderer,
|
|
354
|
+
skillset: skillSetRenderer,
|
|
196
355
|
};
|
|
197
356
|
|
|
198
357
|
/**
|
|
@@ -206,6 +365,9 @@ export function renderSkillRow(
|
|
|
206
365
|
if (item.kind === "category") {
|
|
207
366
|
return skillRenderers.category.renderRow({ item, isSelected });
|
|
208
367
|
}
|
|
368
|
+
if (item.kind === "skillset") {
|
|
369
|
+
return skillRenderers.skillset.renderRow({ item, isSelected });
|
|
370
|
+
}
|
|
209
371
|
return skillRenderers.skill.renderRow({ item, isSelected });
|
|
210
372
|
}
|
|
211
373
|
|
|
@@ -216,5 +378,8 @@ export function renderSkillDetail(
|
|
|
216
378
|
if (item.kind === "category") {
|
|
217
379
|
return skillRenderers.category.renderDetail({ item });
|
|
218
380
|
}
|
|
381
|
+
if (item.kind === "skillset") {
|
|
382
|
+
return skillRenderers.skillset.renderDetail({ item });
|
|
383
|
+
}
|
|
219
384
|
return skillRenderers.skill.renderDetail({ item });
|
|
220
385
|
}
|
|
@@ -650,6 +650,6 @@ export function PluginsScreen() {
|
|
|
650
650
|
isActive: isSearchActive,
|
|
651
651
|
query: pluginsState.searchQuery,
|
|
652
652
|
placeholder: searchPlaceholder,
|
|
653
|
-
}, footerHints: footerHints, listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderPluginRow, maxHeight: dimensions.listPanelHeight }), pluginsState.searchQuery && selectableItems.length === 0 && (_jsx(EmptyFilterState, { query: pluginsState.searchQuery, entityName: "plugins" }))] }), detailPanel: renderPluginDetail(selectedItem, pluginsState.collapsedMarketplaces) }));
|
|
653
|
+
}, footerHints: footerHints, listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderPluginRow, maxHeight: dimensions.listPanelHeight, getKey: (item) => item.id }), pluginsState.searchQuery && selectableItems.length === 0 && (_jsx(EmptyFilterState, { query: pluginsState.searchQuery, entityName: "plugins" }))] }), detailPanel: renderPluginDetail(selectedItem, pluginsState.collapsedMarketplaces) }));
|
|
654
654
|
}
|
|
655
655
|
export default PluginsScreen;
|
|
@@ -821,6 +821,7 @@ export function PluginsScreen() {
|
|
|
821
821
|
selectedIndex={pluginsState.selectedIndex}
|
|
822
822
|
renderItem={renderPluginRow}
|
|
823
823
|
maxHeight={dimensions.listPanelHeight}
|
|
824
|
+
getKey={(item) => item.id}
|
|
824
825
|
/>
|
|
825
826
|
{pluginsState.searchQuery && selectableItems.length === 0 && (
|
|
826
827
|
<EmptyFilterState query={pluginsState.searchQuery} entityName="plugins" />
|