@skillsgate/tui 0.1.14 → 0.2.0
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 -1
- package/src/components/help-overlay.tsx +1 -0
- package/src/data/api-client.ts +0 -4
- package/src/data/use-skill-actions.ts +2 -14
- package/src/db/local-skills.ts +64 -0
- package/src/db/push.ts +220 -0
- package/src/db/ssh.ts +122 -12
- package/src/store/reducers.ts +0 -12
- package/src/store/types.ts +0 -19
- package/src/utils/colors.ts +2 -0
- package/src/views/push-dialog.tsx +251 -0
- package/src/views/servers.tsx +32 -7
- package/src/data/use-auth.ts +0 -136
- package/src/data/use-favorites.ts +0 -161
- package/src/views/favorites.tsx +0 -19
- package/src/views/login.tsx +0 -202
package/src/views/login.tsx
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from "react"
|
|
2
|
-
import { useKeyboard } from "@opentui/react"
|
|
3
|
-
import { useStore, useDispatch } from "../store/context.js"
|
|
4
|
-
import { useAuth } from "../data/use-auth.js"
|
|
5
|
-
import { API_BASE_URL } from "../../../cli/src/constants.js"
|
|
6
|
-
import { colors } from "../utils/colors.js"
|
|
7
|
-
|
|
8
|
-
type LoginStep = "prompt" | "code" | "exchanging"
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Login view implementing the device code flow:
|
|
12
|
-
* 1. Show instructions with the auth URL
|
|
13
|
-
* 2. Prompt to open browser (y/n)
|
|
14
|
-
* 3. Show input for device code (XXXX-XXXX)
|
|
15
|
-
* 4. Exchange code for token, save auth, navigate back
|
|
16
|
-
*/
|
|
17
|
-
export function LoginView() {
|
|
18
|
-
const state = useStore()
|
|
19
|
-
const dispatch = useDispatch()
|
|
20
|
-
const { auth, login, logout } = useAuth()
|
|
21
|
-
|
|
22
|
-
const [step, setStep] = useState<LoginStep>("prompt")
|
|
23
|
-
const [error, setError] = useState<string | null>(null)
|
|
24
|
-
|
|
25
|
-
const authUrl = `${API_BASE_URL}/cli/auth`
|
|
26
|
-
|
|
27
|
-
function openBrowser() {
|
|
28
|
-
try {
|
|
29
|
-
const { exec } = require("node:child_process")
|
|
30
|
-
const cmd = process.platform === "darwin" ? "open" : "xdg-open"
|
|
31
|
-
exec(`${cmd} "${authUrl}"`)
|
|
32
|
-
dispatch({
|
|
33
|
-
type: "SHOW_NOTIFICATION",
|
|
34
|
-
notification: { type: "info", message: "Opening browser..." },
|
|
35
|
-
})
|
|
36
|
-
} catch {
|
|
37
|
-
// Best effort
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Handle keyboard input
|
|
42
|
-
useKeyboard((key) => {
|
|
43
|
-
if (state.activeView !== "login") return
|
|
44
|
-
if (state.showHelp) return
|
|
45
|
-
|
|
46
|
-
// Esc to go back at any step
|
|
47
|
-
if (key.name === "escape") {
|
|
48
|
-
dispatch({ type: "GO_BACK" })
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (step === "prompt") {
|
|
53
|
-
// "r" to re-login (clear old auth, open browser, go to code step)
|
|
54
|
-
if (key.name === "r") {
|
|
55
|
-
logout()
|
|
56
|
-
openBrowser()
|
|
57
|
-
setStep("code")
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// "o" to logout only (no re-login)
|
|
62
|
-
if (key.name === "o") {
|
|
63
|
-
logout()
|
|
64
|
-
dispatch({
|
|
65
|
-
type: "SHOW_NOTIFICATION",
|
|
66
|
-
notification: { type: "success", message: "Signed out" },
|
|
67
|
-
})
|
|
68
|
-
dispatch({ type: "GO_BACK" })
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// "y" to open browser and proceed to code input
|
|
73
|
-
if (key.name === "y") {
|
|
74
|
-
openBrowser()
|
|
75
|
-
setStep("code")
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// "n" to skip browser, go straight to code input
|
|
80
|
-
if (key.name === "n") {
|
|
81
|
-
setStep("code")
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const handleCodeSubmit = useCallback(async (value: string) => {
|
|
88
|
-
const code = value.trim()
|
|
89
|
-
if (!code) return
|
|
90
|
-
|
|
91
|
-
setStep("exchanging")
|
|
92
|
-
setError(null)
|
|
93
|
-
|
|
94
|
-
const errMsg = await login(code)
|
|
95
|
-
|
|
96
|
-
if (errMsg) {
|
|
97
|
-
setError(errMsg)
|
|
98
|
-
setStep("code") // Let user retry
|
|
99
|
-
} else {
|
|
100
|
-
// Success - navigate back
|
|
101
|
-
dispatch({
|
|
102
|
-
type: "SHOW_NOTIFICATION",
|
|
103
|
-
notification: { type: "success", message: "Logged in successfully!" },
|
|
104
|
-
})
|
|
105
|
-
dispatch({ type: "GO_BACK" })
|
|
106
|
-
}
|
|
107
|
-
}, [login, dispatch])
|
|
108
|
-
|
|
109
|
-
// Already logged in -- offer re-login or logout
|
|
110
|
-
if (auth && step === "prompt") {
|
|
111
|
-
return (
|
|
112
|
-
<box style={{ flexDirection: "column", padding: 2 }}>
|
|
113
|
-
<text fg={colors.success}>
|
|
114
|
-
Logged in as <strong>{auth.user.name}</strong> ({auth.user.email})
|
|
115
|
-
</text>
|
|
116
|
-
<text>{" "}</text>
|
|
117
|
-
<text fg={colors.text}>
|
|
118
|
-
If AI search isn't working, your session may have expired.
|
|
119
|
-
</text>
|
|
120
|
-
<text>{" "}</text>
|
|
121
|
-
<text fg={colors.primary}>r</text>
|
|
122
|
-
<text fg={colors.text}> Re-login with a fresh token</text>
|
|
123
|
-
<text fg={colors.primary}>o</text>
|
|
124
|
-
<text fg={colors.text}> Sign out</text>
|
|
125
|
-
<text fg={colors.textDim}>Esc</text>
|
|
126
|
-
<text fg={colors.text}> Go back</text>
|
|
127
|
-
</box>
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<box style={{ flexDirection: "column", padding: 2 }}>
|
|
133
|
-
{/* Title */}
|
|
134
|
-
<text fg={colors.primary}>
|
|
135
|
-
<strong>Sign in to SkillsGate</strong>
|
|
136
|
-
</text>
|
|
137
|
-
<text>{" "}</text>
|
|
138
|
-
|
|
139
|
-
{/* Instructions */}
|
|
140
|
-
<text fg={colors.text}>
|
|
141
|
-
Visit the following URL in your browser to get a login code:
|
|
142
|
-
</text>
|
|
143
|
-
<text>{" "}</text>
|
|
144
|
-
<text fg={colors.primary}>
|
|
145
|
-
{authUrl}
|
|
146
|
-
</text>
|
|
147
|
-
<text>{" "}</text>
|
|
148
|
-
|
|
149
|
-
{step === "prompt" && (
|
|
150
|
-
<>
|
|
151
|
-
<text fg={colors.text}>
|
|
152
|
-
Open browser? <span fg={colors.textDim}>(y/n)</span>
|
|
153
|
-
</text>
|
|
154
|
-
</>
|
|
155
|
-
)}
|
|
156
|
-
|
|
157
|
-
{step === "code" && (
|
|
158
|
-
<>
|
|
159
|
-
<text fg={colors.text}>
|
|
160
|
-
Paste the code from the browser:
|
|
161
|
-
</text>
|
|
162
|
-
<text>{" "}</text>
|
|
163
|
-
<box
|
|
164
|
-
style={{
|
|
165
|
-
height: 3,
|
|
166
|
-
width: 40,
|
|
167
|
-
border: true,
|
|
168
|
-
borderColor: colors.primary,
|
|
169
|
-
paddingLeft: 1,
|
|
170
|
-
paddingRight: 1,
|
|
171
|
-
}}
|
|
172
|
-
title="Code"
|
|
173
|
-
>
|
|
174
|
-
<input
|
|
175
|
-
placeholder="XXXX-XXXX"
|
|
176
|
-
focused={state.activeView === "login" && step === "code" && !state.showHelp}
|
|
177
|
-
onSubmit={handleCodeSubmit as any}
|
|
178
|
-
/>
|
|
179
|
-
</box>
|
|
180
|
-
<text>{" "}</text>
|
|
181
|
-
<text fg={colors.textDim}>
|
|
182
|
-
Press Enter to submit, Esc to cancel
|
|
183
|
-
</text>
|
|
184
|
-
</>
|
|
185
|
-
)}
|
|
186
|
-
|
|
187
|
-
{step === "exchanging" && (
|
|
188
|
-
<text fg={colors.primary}>
|
|
189
|
-
Verifying code...
|
|
190
|
-
</text>
|
|
191
|
-
)}
|
|
192
|
-
|
|
193
|
-
{/* Error message */}
|
|
194
|
-
{error && (
|
|
195
|
-
<>
|
|
196
|
-
<text>{" "}</text>
|
|
197
|
-
<text fg={colors.error}>{error}</text>
|
|
198
|
-
</>
|
|
199
|
-
)}
|
|
200
|
-
</box>
|
|
201
|
-
)
|
|
202
|
-
}
|