@skillsgate/tui 0.1.9 → 0.1.12
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/bin/skillsgate-tui +32 -19
- package/package.json +6 -6
- package/src/components/agent-filter.tsx +2 -2
- package/src/components/help-overlay.tsx +2 -0
- package/src/components/layout.tsx +10 -9
- package/src/data/use-installed-skills.ts +178 -3
- package/src/data/use-skill-actions.ts +19 -2
- package/src/db/skills.ts +24 -0
- package/src/db/ssh.ts +45 -1
- package/src/store/types.ts +8 -0
- package/src/types/bun-sqlite.d.ts +12 -0
- package/src/views/add-server.tsx +3 -3
- package/src/views/discover.tsx +10 -5
- package/src/views/favorites.tsx +7 -2
- package/src/views/home.tsx +531 -9
- package/src/views/login.tsx +1 -1
- package/src/views/server-skills.tsx +61 -4
- package/src/views/servers.tsx +2 -2
- package/src/views/skill-detail.tsx +3 -3
|
@@ -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
|
-
|
|
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 */}
|
package/src/views/servers.tsx
CHANGED
|
@@ -237,10 +237,10 @@ export function ServersView({ onServerCountChange }: ServersViewProps) {
|
|
|
237
237
|
<box
|
|
238
238
|
style={{
|
|
239
239
|
width: "50%",
|
|
240
|
-
|
|
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 }}>
|
|
@@ -85,7 +85,7 @@ export function SkillDetailView() {
|
|
|
85
85
|
: `/api/v1/skills/detail?path=${encodeURIComponent(skill.name)}`
|
|
86
86
|
fetch(`https://api.skillsgate.ai${detailPath}`)
|
|
87
87
|
.then(res => res.ok ? res.json() : null)
|
|
88
|
-
.then(data => {
|
|
88
|
+
.then((data: any) => {
|
|
89
89
|
if (data?.content) {
|
|
90
90
|
setContent(stripFrontmatter(data.content))
|
|
91
91
|
} else {
|
|
@@ -410,12 +410,12 @@ export function SkillDetailView() {
|
|
|
410
410
|
width: "30%",
|
|
411
411
|
flexDirection: "column",
|
|
412
412
|
backgroundColor: colors.bgAlt,
|
|
413
|
-
|
|
413
|
+
border: true,
|
|
414
414
|
borderColor: colors.border,
|
|
415
415
|
paddingLeft: 1,
|
|
416
416
|
paddingRight: 1,
|
|
417
417
|
paddingTop: 1,
|
|
418
|
-
}}
|
|
418
|
+
} as any}
|
|
419
419
|
>
|
|
420
420
|
{/* Skill name */}
|
|
421
421
|
<text fg={colors.primary}>
|