@skillsgate/tui 0.1.12 → 0.1.13

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.
@@ -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: two-column layout.
13
- * LEFT - Favorites list (40%)
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", width: "100%", flexGrow: 1 }}>
146
- {/* Status line */}
147
- <box
148
- style={{
149
- height: 1,
150
- width: "100%",
151
- paddingLeft: 1,
152
- backgroundColor: colors.bgAlt,
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
- }
@@ -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",
@@ -6,6 +6,7 @@ import { useKeyboard } from "@opentui/react"
6
6
  import { useStore, useDispatch } from "../store/context.js"
7
7
  import { useSkillActions } from "../data/use-skill-actions.js"
8
8
  import { ConfirmDialog } from "../components/confirm-dialog.js"
9
+ import { fetchSkillContent } from "../data/api-client.js"
9
10
  import { colors, agentBadges as badgeMap } from "../utils/colors.js"
10
11
  import { agents } from "../../../cli/src/core/agents.js"
11
12
 
@@ -75,19 +76,15 @@ export function SkillDetailView() {
75
76
  return
76
77
  }
77
78
 
78
- // Catalog skill: fetch content from API
79
- const githubUrl = skill.metadata?.githubUrl as string | undefined
80
- const urlPath = skill.metadata?.urlPath as string | undefined
81
- if (githubUrl || urlPath) {
79
+ // Catalog skill: fetch SKILL.md content from GitHub
80
+ const source = skill.metadata?.source as string | undefined
81
+ const skillId = skill.metadata?.skillId as string | undefined
82
+ if (source && skillId) {
82
83
  setContentLoading(true)
83
- const detailPath = urlPath
84
- ? `/api/v1/skills/detail?path=${encodeURIComponent(urlPath)}`
85
- : `/api/v1/skills/detail?path=${encodeURIComponent(skill.name)}`
86
- fetch(`https://api.skillsgate.ai${detailPath}`)
87
- .then(res => res.ok ? res.json() : null)
88
- .then((data: any) => {
89
- if (data?.content) {
90
- setContent(stripFrontmatter(data.content))
84
+ fetchSkillContent(source, skillId)
85
+ .then((md) => {
86
+ if (md) {
87
+ setContent(stripFrontmatter(md))
91
88
  } else {
92
89
  setContent(skill.description || "(No content available)")
93
90
  }
@@ -424,8 +421,12 @@ export function SkillDetailView() {
424
421
  <text>{" "}</text>
425
422
 
426
423
  {/* Description */}
427
- <text fg={colors.text}>{skill.description}</text>
428
- <text>{" "}</text>
424
+ {skill.description ? (
425
+ <>
426
+ <text fg={colors.text}>{skill.description}</text>
427
+ <text>{" "}</text>
428
+ </>
429
+ ) : null}
429
430
 
430
431
  {/* Source */}
431
432
  <text fg={colors.textDim}>Source</text>
package/tmp.json ADDED
File without changes