@skillsgate/tui 0.1.12 → 0.1.14
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 -8
- package/src/components/help-overlay.tsx +2 -4
- package/src/components/layout.tsx +8 -34
- package/src/components/status-bar.tsx +4 -7
- package/src/data/api-client.ts +78 -109
- package/src/data/use-favorites.ts +18 -4
- package/src/data/use-installed-skills.ts +173 -85
- package/src/data/use-search.ts +14 -31
- package/src/data/use-skill-actions.ts +68 -16
- package/src/db/migrations.ts +20 -0
- package/src/db/skills-cache.ts +89 -0
- package/src/views/discover.tsx +33 -126
- package/src/views/favorites.tsx +10 -354
- package/src/views/settings.tsx +0 -6
- package/src/views/skill-detail.tsx +15 -14
- package/tmp.json +0 -0
package/src/views/discover.tsx
CHANGED
|
@@ -4,31 +4,22 @@ import { useStore, useDispatch } from "../store/context.js"
|
|
|
4
4
|
import { useSearch } from "../data/use-search.js"
|
|
5
5
|
import { useSkillActions } from "../data/use-skill-actions.js"
|
|
6
6
|
import { ConfirmDialog } from "../components/confirm-dialog.js"
|
|
7
|
-
import type { CatalogSkill
|
|
7
|
+
import type { CatalogSkill } from "../data/api-client.js"
|
|
8
8
|
import { colors } from "../utils/colors.js"
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Discover view: two-column layout with search
|
|
12
|
-
* LEFT - Search input +
|
|
11
|
+
* Discover view: two-column layout with search results.
|
|
12
|
+
* LEFT - Search input + results list (40%)
|
|
13
13
|
* RIGHT - Selected result detail (flexGrow)
|
|
14
|
-
*
|
|
15
|
-
* Search modes:
|
|
16
|
-
* - Keyword (public, no auth): ILIKE pattern matching, unlimited
|
|
17
|
-
* - Semantic (auth required): AI-powered vector search, 30/day limit
|
|
18
|
-
* Toggle with 'm' key.
|
|
19
14
|
*/
|
|
20
15
|
export function DiscoverView() {
|
|
21
16
|
const state = useStore()
|
|
22
17
|
const dispatch = useDispatch()
|
|
23
18
|
const [query, setQuery] = useState("")
|
|
24
|
-
const [searchMode, setSearchMode] = useState<SearchMode>("keyword")
|
|
25
19
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
26
20
|
const [installTarget, setInstallTarget] = useState<CatalogSkill | null>(null)
|
|
27
21
|
const [previewSkill, setPreviewSkill] = useState<CatalogSkill | null>(null)
|
|
28
22
|
|
|
29
|
-
const token = state.auth?.token ?? null
|
|
30
|
-
const isAuthenticated = !!state.auth
|
|
31
|
-
|
|
32
23
|
// Auto-focus search input when Discover view mounts
|
|
33
24
|
useEffect(() => {
|
|
34
25
|
if (state.activeView === "discover") {
|
|
@@ -36,8 +27,8 @@ export function DiscoverView() {
|
|
|
36
27
|
}
|
|
37
28
|
}, [state.activeView])
|
|
38
29
|
|
|
39
|
-
const { results, loading, error, total, hasMore, loadMore
|
|
40
|
-
useSearch(query
|
|
30
|
+
const { results, loading, error, total, hasMore, loadMore } =
|
|
31
|
+
useSearch(query)
|
|
41
32
|
const { installSkill } = useSkillActions()
|
|
42
33
|
|
|
43
34
|
// Update preview when selection changes
|
|
@@ -94,24 +85,6 @@ export function DiscoverView() {
|
|
|
94
85
|
setInstallTarget(results[selectedIndex])
|
|
95
86
|
return
|
|
96
87
|
}
|
|
97
|
-
|
|
98
|
-
// m to toggle search mode
|
|
99
|
-
if (key.name === "m") {
|
|
100
|
-
if (searchMode === "keyword") {
|
|
101
|
-
if (!isAuthenticated) {
|
|
102
|
-
dispatch({
|
|
103
|
-
type: "SHOW_NOTIFICATION",
|
|
104
|
-
notification: { type: "info", message: "Sign in to use AI search (press l)" },
|
|
105
|
-
})
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
setSearchMode("semantic")
|
|
109
|
-
} else {
|
|
110
|
-
setSearchMode("keyword")
|
|
111
|
-
}
|
|
112
|
-
setSelectedIndex(0)
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
88
|
})
|
|
116
89
|
|
|
117
90
|
// Confirm dialog for install
|
|
@@ -142,17 +115,11 @@ export function DiscoverView() {
|
|
|
142
115
|
paddingLeft: 1,
|
|
143
116
|
paddingRight: 1,
|
|
144
117
|
}}
|
|
145
|
-
title={state.focusedPane === "search"
|
|
146
|
-
? (searchMode === "semantic" ? "AI Search" : "Keyword Search")
|
|
147
|
-
: "/ to search"}
|
|
118
|
+
title={state.focusedPane === "search" ? "Search" : "/ to search"}
|
|
148
119
|
>
|
|
149
120
|
{state.focusedPane === "search" ? (
|
|
150
121
|
<input
|
|
151
|
-
placeholder=
|
|
152
|
-
searchMode === "semantic"
|
|
153
|
-
? 'AI search -- try "audit website performance" (Enter to search)'
|
|
154
|
-
: "Search by keyword... (Enter to search)"
|
|
155
|
-
}
|
|
122
|
+
placeholder="Search skills... (Enter to search)"
|
|
156
123
|
focused={state.activeView === "discover" && !state.showHelp}
|
|
157
124
|
onSubmit={((value: string) => {
|
|
158
125
|
setQuery(value)
|
|
@@ -164,7 +131,7 @@ export function DiscoverView() {
|
|
|
164
131
|
)}
|
|
165
132
|
</box>
|
|
166
133
|
|
|
167
|
-
{/* Status line
|
|
134
|
+
{/* Status line */}
|
|
168
135
|
<box
|
|
169
136
|
style={{
|
|
170
137
|
height: 1,
|
|
@@ -174,37 +141,16 @@ export function DiscoverView() {
|
|
|
174
141
|
flexDirection: "row",
|
|
175
142
|
}}
|
|
176
143
|
>
|
|
177
|
-
{/* Mode toggle: show both options, highlight active */}
|
|
178
|
-
<text fg={searchMode === "keyword" ? colors.primary : colors.textDim}>
|
|
179
|
-
{searchMode === "keyword" ? "[Keyword]" : " Keyword "}
|
|
180
|
-
</text>
|
|
181
|
-
<text fg={colors.textDim}>{" | "}</text>
|
|
182
|
-
<text fg={searchMode === "semantic" ? colors.warning : colors.textDim}>
|
|
183
|
-
{searchMode === "semantic" ? "[AI Search]" : (isAuthenticated ? " AI Search " : " AI Search (login) ")}
|
|
184
|
-
</text>
|
|
185
|
-
<text fg={colors.textDim}>{" m=switch "}</text>
|
|
186
|
-
|
|
187
144
|
{/* Results info */}
|
|
188
145
|
<text fg={colors.textDim}>
|
|
189
146
|
{loading
|
|
190
147
|
? "Loading..."
|
|
191
148
|
: error
|
|
192
|
-
? error
|
|
193
|
-
? "Session expired -- press l to re-login"
|
|
194
|
-
: error === "RATE_LIMIT"
|
|
195
|
-
? "Daily limit reached -- switch to keyword (m)"
|
|
196
|
-
: `Error: ${error}`
|
|
149
|
+
? `Error: ${error}`
|
|
197
150
|
: query.trim()
|
|
198
151
|
? `${results.length} result${results.length !== 1 ? "s" : ""}`
|
|
199
152
|
: `${results.length}/${total} skills`}
|
|
200
153
|
</text>
|
|
201
|
-
|
|
202
|
-
{/* Remaining searches (semantic only) */}
|
|
203
|
-
{searchMode === "semantic" && remainingSearches !== null ? (
|
|
204
|
-
<text fg={remainingSearches <= 5 ? colors.error : colors.textDim}>
|
|
205
|
-
{` ${remainingSearches} left today`}
|
|
206
|
-
</text>
|
|
207
|
-
) : null}
|
|
208
154
|
</box>
|
|
209
155
|
|
|
210
156
|
{/* Two-column content: results list | detail */}
|
|
@@ -250,7 +196,7 @@ export function DiscoverView() {
|
|
|
250
196
|
>
|
|
251
197
|
{results.map((skill, i) => (
|
|
252
198
|
<box
|
|
253
|
-
key={skill.id ?? `${skill.
|
|
199
|
+
key={skill.id ?? `${skill.skillId}-${i}`}
|
|
254
200
|
style={{
|
|
255
201
|
width: "100%",
|
|
256
202
|
paddingLeft: 1,
|
|
@@ -297,11 +243,6 @@ interface DiscoverDetailPanelProps {
|
|
|
297
243
|
}
|
|
298
244
|
|
|
299
245
|
function DiscoverDetailPanel({ skill }: DiscoverDetailPanelProps) {
|
|
300
|
-
const description = skill.summary || skill.description || ""
|
|
301
|
-
const categories = skill.categories?.join(", ") ?? ""
|
|
302
|
-
const capabilities = skill.capabilities?.join(", ") ?? ""
|
|
303
|
-
const keywords = skill.keywords?.join(", ") ?? ""
|
|
304
|
-
|
|
305
246
|
return (
|
|
306
247
|
<scrollbox
|
|
307
248
|
focused={false}
|
|
@@ -325,58 +266,25 @@ function DiscoverDetailPanel({ skill }: DiscoverDetailPanelProps) {
|
|
|
325
266
|
<strong>{skill.name}</strong>
|
|
326
267
|
</text>
|
|
327
268
|
|
|
328
|
-
{/*
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
{categories ? (
|
|
334
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
335
|
-
<text fg={colors.textDim}>Categories: </text>
|
|
336
|
-
<text fg={colors.secondary}>{categories}</text>
|
|
337
|
-
</box>
|
|
338
|
-
) : null}
|
|
339
|
-
|
|
340
|
-
{/* Capabilities */}
|
|
341
|
-
{capabilities ? (
|
|
342
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
343
|
-
<text fg={colors.textDim}>Capabilities: </text>
|
|
344
|
-
<text fg={colors.secondary}>{capabilities}</text>
|
|
345
|
-
</box>
|
|
346
|
-
) : null}
|
|
347
|
-
|
|
348
|
-
{/* Keywords */}
|
|
349
|
-
{keywords ? (
|
|
350
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
351
|
-
<text fg={colors.textDim}>Keywords: </text>
|
|
352
|
-
<text fg={colors.secondary}>{keywords}</text>
|
|
353
|
-
</box>
|
|
354
|
-
) : null}
|
|
355
|
-
|
|
356
|
-
{/* Score (semantic search only) */}
|
|
357
|
-
{skill.score ? (
|
|
358
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
359
|
-
<text fg={colors.textDim}>Score: </text>
|
|
360
|
-
<text fg={colors.success}>{skill.score.toFixed(3)}</text>
|
|
361
|
-
</box>
|
|
362
|
-
) : null}
|
|
269
|
+
{/* Skill ID */}
|
|
270
|
+
<box style={{ flexDirection: "row", height: 1 }}>
|
|
271
|
+
<text fg={colors.textDim}>ID: </text>
|
|
272
|
+
<text fg={colors.text}>{skill.skillId}</text>
|
|
273
|
+
</box>
|
|
363
274
|
|
|
364
|
-
{/*
|
|
365
|
-
{skill.
|
|
275
|
+
{/* Source (owner/repo) */}
|
|
276
|
+
{skill.source ? (
|
|
366
277
|
<box style={{ flexDirection: "row", height: 1 }}>
|
|
367
|
-
<text fg={colors.textDim}>
|
|
368
|
-
<text fg={colors.primary}>{skill.
|
|
278
|
+
<text fg={colors.textDim}>Source: </text>
|
|
279
|
+
<text fg={colors.primary}>{skill.source}</text>
|
|
369
280
|
</box>
|
|
370
281
|
) : null}
|
|
371
282
|
|
|
372
|
-
{/*
|
|
373
|
-
{
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
<text fg={colors.success}> {skill.installCommand}</text>
|
|
378
|
-
</>
|
|
379
|
-
) : null}
|
|
283
|
+
{/* Installs */}
|
|
284
|
+
<box style={{ flexDirection: "row", height: 1 }}>
|
|
285
|
+
<text fg={colors.textDim}>Installs: </text>
|
|
286
|
+
<text fg={colors.success}>{skill.installs.toLocaleString()}</text>
|
|
287
|
+
</box>
|
|
380
288
|
|
|
381
289
|
<text>{" "}</text>
|
|
382
290
|
<text fg={colors.textDim}>v=full detail i=install Tab=switch pane</text>
|
|
@@ -392,9 +300,11 @@ function DiscoverDetailPanel({ skill }: DiscoverDetailPanelProps) {
|
|
|
392
300
|
* Since catalog skills don't have a local file, we provide a placeholder.
|
|
393
301
|
*/
|
|
394
302
|
function catalogSkillToEnriched(skill: CatalogSkill): import("../store/types.js").EnrichedSkill {
|
|
303
|
+
const githubUrl = skill.source ? `https://github.com/${skill.source}` : undefined
|
|
304
|
+
|
|
395
305
|
return {
|
|
396
306
|
name: skill.name,
|
|
397
|
-
description:
|
|
307
|
+
description: "",
|
|
398
308
|
filePath: "", // No local file for catalog items
|
|
399
309
|
canonicalPath: "",
|
|
400
310
|
agents: [],
|
|
@@ -403,18 +313,15 @@ function catalogSkillToEnriched(skill: CatalogSkill): import("../store/types.js"
|
|
|
403
313
|
hasSupportingFiles: false,
|
|
404
314
|
supportingFiles: [],
|
|
405
315
|
metadata: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
githubUrl: skill.githubUrl,
|
|
410
|
-
installCommand: skill.installCommand,
|
|
411
|
-
urlPath: skill.urlPath,
|
|
316
|
+
source: skill.source,
|
|
317
|
+
skillId: skill.skillId,
|
|
318
|
+
installs: skill.installs,
|
|
412
319
|
},
|
|
413
|
-
lock: skill.
|
|
320
|
+
lock: skill.source
|
|
414
321
|
? {
|
|
415
|
-
source: skill.
|
|
322
|
+
source: skill.source,
|
|
416
323
|
sourceType: "github" as const,
|
|
417
|
-
originalUrl:
|
|
324
|
+
originalUrl: githubUrl ?? "",
|
|
418
325
|
skillFolderHash: "",
|
|
419
326
|
installedAt: "",
|
|
420
327
|
updatedAt: "",
|
package/src/views/favorites.tsx
CHANGED
|
@@ -1,363 +1,19 @@
|
|
|
1
|
-
import { useState, useMemo, useEffect } from "react"
|
|
2
|
-
import { useKeyboard } from "@opentui/react"
|
|
3
|
-
import { useStore, useDispatch } from "../store/context.js"
|
|
4
|
-
import { useFavorites } from "../data/use-favorites.js"
|
|
5
|
-
import { useSkillActions } from "../data/use-skill-actions.js"
|
|
6
|
-
import { ConfirmDialog } from "../components/confirm-dialog.js"
|
|
7
1
|
import { colors } from "../utils/colors.js"
|
|
8
|
-
import type { CatalogSkill } from "../data/api-client.js"
|
|
9
|
-
import type { EnrichedSkill } from "../store/types.js"
|
|
10
2
|
|
|
11
3
|
/**
|
|
12
|
-
* Favorites view:
|
|
13
|
-
*
|
|
14
|
-
* RIGHT - Selected favorite detail (flexGrow)
|
|
15
|
-
*
|
|
16
|
-
* Requires authentication. Shows a prompt to login if not authenticated.
|
|
4
|
+
* Favorites view: Coming soon placeholder.
|
|
5
|
+
* Favorites require authentication which is not yet available in the public TUI.
|
|
17
6
|
*/
|
|
18
7
|
export function FavoritesView() {
|
|
19
|
-
const state = useStore()
|
|
20
|
-
const dispatch = useDispatch()
|
|
21
|
-
const { favorites, loading, error, toggle } = useFavorites()
|
|
22
|
-
const { installSkill } = useSkillActions()
|
|
23
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
24
|
-
const [installTarget, setInstallTarget] = useState<CatalogSkill | null>(null)
|
|
25
|
-
const [previewSkill, setPreviewSkill] = useState<CatalogSkill | null>(null)
|
|
26
|
-
|
|
27
|
-
// Build a set of installed skill names for the "installed" badge
|
|
28
|
-
const installedNames = useMemo(() => {
|
|
29
|
-
return new Set(state.installedSkills.map((s) => s.name.toLowerCase()))
|
|
30
|
-
}, [state.installedSkills])
|
|
31
|
-
|
|
32
|
-
// Update preview when selection changes
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (favorites[selectedIndex]) {
|
|
35
|
-
setPreviewSkill(favorites[selectedIndex])
|
|
36
|
-
} else {
|
|
37
|
-
setPreviewSkill(null)
|
|
38
|
-
}
|
|
39
|
-
}, [selectedIndex, favorites])
|
|
40
|
-
|
|
41
|
-
// Keyboard navigation for the favorites list
|
|
42
|
-
useKeyboard((key) => {
|
|
43
|
-
if (state.activeView !== "favorites") return
|
|
44
|
-
if (state.showHelp) return
|
|
45
|
-
if (!state.auth) return // No navigation when not logged in
|
|
46
|
-
if (installTarget) return // Block navigation during confirm dialog
|
|
47
|
-
|
|
48
|
-
// j/k or arrow keys
|
|
49
|
-
if (key.name === "up" || (key.name === "k" && !key.ctrl)) {
|
|
50
|
-
setSelectedIndex((i) => Math.max(0, i - 1))
|
|
51
|
-
}
|
|
52
|
-
if (key.name === "down" || (key.name === "j" && !key.ctrl)) {
|
|
53
|
-
setSelectedIndex((i) => Math.min(favorites.length - 1, i + 1))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// g = first, G = last
|
|
57
|
-
if (key.name === "g" && !key.shift) {
|
|
58
|
-
setSelectedIndex(0)
|
|
59
|
-
}
|
|
60
|
-
if (key.name === "g" && key.shift) {
|
|
61
|
-
setSelectedIndex(Math.max(0, favorites.length - 1))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// v to view full detail
|
|
65
|
-
if (key.name === "v" && favorites[selectedIndex]) {
|
|
66
|
-
const skill = favorites[selectedIndex]
|
|
67
|
-
dispatch({
|
|
68
|
-
type: "SELECT_SKILL",
|
|
69
|
-
skill: catalogSkillToEnriched(skill, installedNames),
|
|
70
|
-
})
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// x to unfavorite
|
|
75
|
-
if (key.name === "x" && favorites[selectedIndex]) {
|
|
76
|
-
const skill = favorites[selectedIndex]
|
|
77
|
-
toggle(skill.id)
|
|
78
|
-
dispatch({
|
|
79
|
-
type: "SHOW_NOTIFICATION",
|
|
80
|
-
notification: { type: "info", message: `Removed "${skill.name}" from favorites` },
|
|
81
|
-
})
|
|
82
|
-
// Adjust selection if we removed the last item
|
|
83
|
-
if (selectedIndex >= favorites.length - 1 && selectedIndex > 0) {
|
|
84
|
-
setSelectedIndex(selectedIndex - 1)
|
|
85
|
-
}
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// i to install
|
|
90
|
-
if (key.name === "i" && favorites[selectedIndex]) {
|
|
91
|
-
setInstallTarget(favorites[selectedIndex])
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
// Confirm dialog for install
|
|
97
|
-
if (installTarget) {
|
|
98
|
-
return (
|
|
99
|
-
<ConfirmDialog
|
|
100
|
-
message={`Install "${installTarget.name}"?`}
|
|
101
|
-
onConfirm={async () => {
|
|
102
|
-
const skill = catalogSkillToEnriched(installTarget, installedNames)
|
|
103
|
-
setInstallTarget(null)
|
|
104
|
-
await installSkill(skill)
|
|
105
|
-
}}
|
|
106
|
-
onCancel={() => setInstallTarget(null)}
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Not authenticated
|
|
112
|
-
if (!state.auth) {
|
|
113
|
-
return (
|
|
114
|
-
<box style={{ flexDirection: "column", padding: 2 }}>
|
|
115
|
-
<text fg={colors.text}>
|
|
116
|
-
Sign in to view your favorites
|
|
117
|
-
</text>
|
|
118
|
-
<text>{" "}</text>
|
|
119
|
-
<text fg={colors.textDim}>
|
|
120
|
-
Press <span fg={colors.primary}>l</span> to login
|
|
121
|
-
</text>
|
|
122
|
-
</box>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Loading state
|
|
127
|
-
if (loading && favorites.length === 0) {
|
|
128
|
-
return (
|
|
129
|
-
<box style={{ padding: 1 }}>
|
|
130
|
-
<text fg={colors.textDim}>Loading favorites...</text>
|
|
131
|
-
</box>
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Error state
|
|
136
|
-
if (error && favorites.length === 0) {
|
|
137
|
-
return (
|
|
138
|
-
<box style={{ padding: 1 }}>
|
|
139
|
-
<text fg={colors.error}>Error: {error}</text>
|
|
140
|
-
</box>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
8
|
return (
|
|
145
|
-
<box style={{ flexDirection: "column",
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}}
|
|
154
|
-
>
|
|
155
|
-
<text fg={colors.textDim}>
|
|
156
|
-
{favorites.length} favorite{favorites.length !== 1 ? "s" : ""}
|
|
157
|
-
{loading ? " (refreshing...)" : ""}
|
|
158
|
-
</text>
|
|
159
|
-
</box>
|
|
160
|
-
|
|
161
|
-
{/* Two-column content: list | detail */}
|
|
162
|
-
<box style={{ flexDirection: "row", flexGrow: 1, width: "100%" }}>
|
|
163
|
-
{/* LEFT: Favorites list */}
|
|
164
|
-
<box
|
|
165
|
-
style={{
|
|
166
|
-
width: "40%",
|
|
167
|
-
border: true,
|
|
168
|
-
borderColor: colors.border,
|
|
169
|
-
flexDirection: "column",
|
|
170
|
-
} as any}
|
|
171
|
-
>
|
|
172
|
-
{/* List header */}
|
|
173
|
-
<box style={{ height: 1, paddingLeft: 1, backgroundColor: colors.bgAlt }}>
|
|
174
|
-
<text fg={colors.textDim}>FAVORITES</text>
|
|
175
|
-
</box>
|
|
176
|
-
|
|
177
|
-
{favorites.length === 0 ? (
|
|
178
|
-
<box style={{ padding: 1 }}>
|
|
179
|
-
<text fg={colors.textDim}>
|
|
180
|
-
No favorites yet. Browse the Discover tab to find and favorite skills.
|
|
181
|
-
</text>
|
|
182
|
-
</box>
|
|
183
|
-
) : (
|
|
184
|
-
<scrollbox
|
|
185
|
-
focused={state.activeView === "favorites" && !state.showHelp}
|
|
186
|
-
style={{
|
|
187
|
-
width: "100%",
|
|
188
|
-
flexGrow: 1,
|
|
189
|
-
rootOptions: { backgroundColor: colors.bg },
|
|
190
|
-
viewportOptions: { backgroundColor: colors.bg },
|
|
191
|
-
contentOptions: { backgroundColor: colors.bg },
|
|
192
|
-
scrollbarOptions: {
|
|
193
|
-
trackOptions: {
|
|
194
|
-
foregroundColor: colors.primary,
|
|
195
|
-
backgroundColor: colors.border,
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
}}
|
|
199
|
-
>
|
|
200
|
-
{favorites.map((skill, i) => {
|
|
201
|
-
const isInstalled = installedNames.has(skill.name?.toLowerCase() ?? "")
|
|
202
|
-
return (
|
|
203
|
-
<box
|
|
204
|
-
key={skill.id ?? `${skill.slug}-${i}`}
|
|
205
|
-
style={{
|
|
206
|
-
width: "100%",
|
|
207
|
-
paddingLeft: 1,
|
|
208
|
-
paddingRight: 1,
|
|
209
|
-
flexDirection: "row",
|
|
210
|
-
backgroundColor: i === selectedIndex ? colors.bgAlt : "transparent",
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
<text fg={i === selectedIndex ? colors.primary : colors.text}>
|
|
214
|
-
{skill.name}
|
|
215
|
-
</text>
|
|
216
|
-
{isInstalled ? (
|
|
217
|
-
<text fg={colors.success}> *</text>
|
|
218
|
-
) : null}
|
|
219
|
-
</box>
|
|
220
|
-
)
|
|
221
|
-
})}
|
|
222
|
-
</scrollbox>
|
|
223
|
-
)}
|
|
224
|
-
</box>
|
|
225
|
-
|
|
226
|
-
{/* RIGHT: Detail panel */}
|
|
227
|
-
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
228
|
-
{previewSkill ? (
|
|
229
|
-
<FavoriteDetailPanel
|
|
230
|
-
skill={previewSkill}
|
|
231
|
-
isInstalled={installedNames.has(previewSkill.name?.toLowerCase() ?? "")}
|
|
232
|
-
/>
|
|
233
|
-
) : (
|
|
234
|
-
<box style={{ padding: 1 }}>
|
|
235
|
-
<text fg={colors.textDim}>Select a favorite to view details</text>
|
|
236
|
-
</box>
|
|
237
|
-
)}
|
|
238
|
-
</box>
|
|
239
|
-
</box>
|
|
9
|
+
<box style={{ flexDirection: "column", padding: 2 }}>
|
|
10
|
+
<text fg={colors.primary}>
|
|
11
|
+
<strong>Favorites</strong>
|
|
12
|
+
</text>
|
|
13
|
+
<text>{" "}</text>
|
|
14
|
+
<text fg={colors.text}>
|
|
15
|
+
Coming soon. Favorites will be available once accounts are launched.
|
|
16
|
+
</text>
|
|
240
17
|
</box>
|
|
241
18
|
)
|
|
242
19
|
}
|
|
243
|
-
|
|
244
|
-
// ---------- Inline Detail Panel ----------
|
|
245
|
-
|
|
246
|
-
interface FavoriteDetailPanelProps {
|
|
247
|
-
skill: CatalogSkill
|
|
248
|
-
isInstalled: boolean
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function FavoriteDetailPanel({ skill, isInstalled }: FavoriteDetailPanelProps) {
|
|
252
|
-
const description = skill.summary || skill.description || ""
|
|
253
|
-
const categories = skill.categories?.join(", ") ?? ""
|
|
254
|
-
const sourceLabel = skill.githubUrl ? "github" : "skillsgate"
|
|
255
|
-
|
|
256
|
-
return (
|
|
257
|
-
<scrollbox
|
|
258
|
-
focused={false}
|
|
259
|
-
style={{
|
|
260
|
-
width: "100%",
|
|
261
|
-
flexGrow: 1,
|
|
262
|
-
rootOptions: { backgroundColor: colors.bg },
|
|
263
|
-
viewportOptions: { backgroundColor: colors.bg },
|
|
264
|
-
contentOptions: { backgroundColor: colors.bg },
|
|
265
|
-
scrollbarOptions: {
|
|
266
|
-
trackOptions: {
|
|
267
|
-
foregroundColor: colors.primary,
|
|
268
|
-
backgroundColor: colors.border,
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
}}
|
|
272
|
-
>
|
|
273
|
-
<box style={{ paddingLeft: 1, paddingRight: 1, flexDirection: "column" }}>
|
|
274
|
-
{/* Name */}
|
|
275
|
-
<text fg={colors.primary}>
|
|
276
|
-
<strong>{skill.name}</strong>
|
|
277
|
-
</text>
|
|
278
|
-
|
|
279
|
-
{/* Status */}
|
|
280
|
-
{isInstalled ? (
|
|
281
|
-
<text fg={colors.success}>Installed</text>
|
|
282
|
-
) : (
|
|
283
|
-
<text fg={colors.textDim}>Not installed</text>
|
|
284
|
-
)}
|
|
285
|
-
<text>{" "}</text>
|
|
286
|
-
|
|
287
|
-
{/* Description */}
|
|
288
|
-
<text fg={colors.text}>{description}</text>
|
|
289
|
-
<text>{" "}</text>
|
|
290
|
-
|
|
291
|
-
{/* Source */}
|
|
292
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
293
|
-
<text fg={colors.textDim}>Source: </text>
|
|
294
|
-
<text fg={colors.secondary}>{sourceLabel}</text>
|
|
295
|
-
</box>
|
|
296
|
-
|
|
297
|
-
{/* Categories */}
|
|
298
|
-
{categories ? (
|
|
299
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
300
|
-
<text fg={colors.textDim}>Categories: </text>
|
|
301
|
-
<text fg={colors.secondary}>{categories}</text>
|
|
302
|
-
</box>
|
|
303
|
-
) : null}
|
|
304
|
-
|
|
305
|
-
{/* GitHub URL */}
|
|
306
|
-
{skill.githubUrl ? (
|
|
307
|
-
<box style={{ flexDirection: "row", height: 1 }}>
|
|
308
|
-
<text fg={colors.textDim}>GitHub: </text>
|
|
309
|
-
<text fg={colors.primary}>{skill.githubUrl}</text>
|
|
310
|
-
</box>
|
|
311
|
-
) : null}
|
|
312
|
-
|
|
313
|
-
{/* Install command */}
|
|
314
|
-
{skill.installCommand ? (
|
|
315
|
-
<>
|
|
316
|
-
<text>{" "}</text>
|
|
317
|
-
<text fg={colors.textDim}>Install:</text>
|
|
318
|
-
<text fg={colors.success}> {skill.installCommand}</text>
|
|
319
|
-
</>
|
|
320
|
-
) : null}
|
|
321
|
-
|
|
322
|
-
<text>{" "}</text>
|
|
323
|
-
<text fg={colors.textDim}>v=full detail x=unfavorite i=install</text>
|
|
324
|
-
</box>
|
|
325
|
-
</scrollbox>
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ---------- Helpers ----------
|
|
330
|
-
|
|
331
|
-
function catalogSkillToEnriched(
|
|
332
|
-
skill: CatalogSkill,
|
|
333
|
-
installedNames: Set<string>
|
|
334
|
-
): EnrichedSkill {
|
|
335
|
-
return {
|
|
336
|
-
name: skill.name,
|
|
337
|
-
description: skill.summary || skill.description || "",
|
|
338
|
-
filePath: "",
|
|
339
|
-
canonicalPath: "",
|
|
340
|
-
agents: [],
|
|
341
|
-
scope: "custom",
|
|
342
|
-
projectName: null,
|
|
343
|
-
hasSupportingFiles: false,
|
|
344
|
-
supportingFiles: [],
|
|
345
|
-
metadata: {
|
|
346
|
-
categories: skill.categories,
|
|
347
|
-
capabilities: skill.capabilities,
|
|
348
|
-
keywords: skill.keywords,
|
|
349
|
-
githubUrl: skill.githubUrl,
|
|
350
|
-
installCommand: skill.installCommand,
|
|
351
|
-
},
|
|
352
|
-
lock: skill.githubUrl
|
|
353
|
-
? {
|
|
354
|
-
source: skill.githubUrl,
|
|
355
|
-
sourceType: "github" as const,
|
|
356
|
-
originalUrl: skill.githubUrl,
|
|
357
|
-
skillFolderHash: "",
|
|
358
|
-
installedAt: "",
|
|
359
|
-
updatedAt: "",
|
|
360
|
-
}
|
|
361
|
-
: undefined,
|
|
362
|
-
}
|
|
363
|
-
}
|
package/src/views/settings.tsx
CHANGED
|
@@ -34,12 +34,6 @@ const SETTING_DEFS: SettingDef[] = [
|
|
|
34
34
|
options: ["dark", "light", "system"],
|
|
35
35
|
defaultValue: "dark",
|
|
36
36
|
},
|
|
37
|
-
{
|
|
38
|
-
key: "search.preferSemantic",
|
|
39
|
-
label: "Prefer semantic search",
|
|
40
|
-
type: "toggle",
|
|
41
|
-
defaultValue: true,
|
|
42
|
-
},
|
|
43
37
|
{
|
|
44
38
|
key: "telemetry.enabled",
|
|
45
39
|
label: "Anonymous telemetry",
|