@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.
@@ -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 }}>
@@ -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
- borderLeft: true,
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}>