@skillsgate/tui 0.1.10 → 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,9 +1,15 @@
1
1
  import { useState, useEffect } from "react"
2
+ import crypto from "node:crypto"
3
+ import { spawnSync } from "node:child_process"
4
+ import fs from "node:fs"
5
+ import os from "node:os"
6
+ import path from "node:path"
2
7
  import { useKeyboard } from "@opentui/react"
3
8
  import { useStore, useDispatch } from "../store/context.js"
4
9
  import { useDb } from "../db/context.js"
5
10
  import { colors } from "../utils/colors.js"
6
11
  import type { RemoteSkill } from "../db/skills.js"
12
+ import { readRemoteFile, writeRemoteFile } from "../db/ssh.js"
7
13
 
8
14
  interface ServerSkillsViewProps {
9
15
  serverId: string
@@ -61,6 +67,57 @@ export function ServerSkillsView({ serverId }: ServerSkillsViewProps) {
61
67
  return
62
68
  }
63
69
 
70
+ if (key.name === "e" && selectedSkill && server) {
71
+ const editor = process.env.VISUAL || process.env.EDITOR || "vi"
72
+ const tempPath = path.join(
73
+ os.tmpdir(),
74
+ `skillsgate-remote-${selectedSkill.id}.md`,
75
+ )
76
+ const initialContent = selectedSkill.content ?? ""
77
+ try {
78
+ fs.writeFileSync(tempPath, initialContent, "utf-8")
79
+ spawnSync(editor, [tempPath], { stdio: "inherit" })
80
+ const nextContent = fs.readFileSync(tempPath, "utf-8")
81
+ if (nextContent !== initialContent) {
82
+ writeRemoteFile(server, selectedSkill.remotePath, nextContent)
83
+ .then(async () => {
84
+ const refreshed = await readRemoteFile(server, selectedSkill.remotePath)
85
+ const contentHash = crypto
86
+ .createHash("sha256")
87
+ .update(refreshed, "utf-8")
88
+ .digest("hex")
89
+ skills.updateContent(serverId, selectedSkill.remotePath, refreshed, contentHash)
90
+ setSkillList(skills.listByServer(serverId))
91
+ dispatch({
92
+ type: "SHOW_NOTIFICATION",
93
+ notification: { type: "success", message: `Saved "${selectedSkill.name}"` },
94
+ })
95
+ })
96
+ .catch((err) => {
97
+ dispatch({
98
+ type: "SHOW_NOTIFICATION",
99
+ notification: {
100
+ type: "error",
101
+ message: err instanceof Error ? err.message : String(err),
102
+ },
103
+ })
104
+ })
105
+ }
106
+ } catch (err) {
107
+ dispatch({
108
+ type: "SHOW_NOTIFICATION",
109
+ notification: {
110
+ type: "error",
111
+ message: err instanceof Error ? err.message : String(err),
112
+ },
113
+ })
114
+ } finally {
115
+ try {
116
+ fs.unlinkSync(tempPath)
117
+ } catch {}
118
+ }
119
+ }
120
+
64
121
  // Esc = go back (handled by layout)
65
122
  })
66
123
 
@@ -97,10 +154,10 @@ export function ServerSkillsView({ serverId }: ServerSkillsViewProps) {
97
154
  <box
98
155
  style={{
99
156
  width: "40%",
100
- borderRight: true,
157
+ border: true,
101
158
  borderColor: colors.border,
102
159
  flexDirection: "column",
103
- }}
160
+ } as any}
104
161
  >
105
162
  <box style={{ height: 1, paddingLeft: 1, backgroundColor: colors.bgAlt }}>
106
163
  <text fg={colors.textDim}>REMOTE SKILLS</text>
@@ -150,7 +207,7 @@ export function ServerSkillsView({ serverId }: ServerSkillsViewProps) {
150
207
 
151
208
  {/* Bottom hints */}
152
209
  <box style={{ height: 1, paddingLeft: 1, backgroundColor: colors.bgAlt }}>
153
- <text fg={colors.textDim}>i=install locally Esc=back</text>
210
+ <text fg={colors.textDim}>i=install locally e=edit Esc=back</text>
154
211
  </box>
155
212
  </box>
156
213
 
@@ -234,7 +291,7 @@ function RemoteSkillDetail({ skill }: RemoteSkillDetailProps) {
234
291
  </box>
235
292
 
236
293
  <text>{" "}</text>
237
- <text fg={colors.textDim}>i=install locally Esc=back to server list</text>
294
+ <text fg={colors.textDim}>i=install locally e=edit Esc=back to server list</text>
238
295
  <text fg={colors.border}>---</text>
239
296
 
240
297
  {/* Skill content */}
@@ -237,10 +237,10 @@ export function ServersView({ onServerCountChange }: ServersViewProps) {
237
237
  <box
238
238
  style={{
239
239
  width: "50%",
240
- borderRight: true,
240
+ border: true,
241
241
  borderColor: colors.border,
242
242
  flexDirection: "column",
243
- }}
243
+ } as any}
244
244
  >
245
245
  {/* Header */}
246
246
  <box style={{ height: 1, paddingLeft: 1, backgroundColor: colors.bgAlt }}>
@@ -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 => {
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
  }
@@ -410,12 +407,12 @@ export function SkillDetailView() {
410
407
  width: "30%",
411
408
  flexDirection: "column",
412
409
  backgroundColor: colors.bgAlt,
413
- borderLeft: true,
410
+ border: true,
414
411
  borderColor: colors.border,
415
412
  paddingLeft: 1,
416
413
  paddingRight: 1,
417
414
  paddingTop: 1,
418
- }}
415
+ } as any}
419
416
  >
420
417
  {/* Skill name */}
421
418
  <text fg={colors.primary}>
@@ -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