opencode-wyvern 0.1.1 → 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 +6 -3
- package/src/prompts.js +127 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-wyvern",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Setup modulare per OpenCode: SSH, client PowerShell/bash (comandi oc-*), configurazione server opencode, provider, plugin e comandi custom. Installi tutto, attivi quello che vuoi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"url": "https://github.com/Rhaegal222"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
12
|
+
"node": ">=20.12.0"
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"oc-setup": "bin/oc-setup.js"
|
|
@@ -37,5 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"test": "node bin/oc-setup.js --help"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@clack/prompts": "^1.8.0"
|
|
40
43
|
}
|
|
41
|
-
}
|
|
44
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stdin as input, stdout as output } from "node:process"
|
|
1
|
+
import { stdin as input, stdout as output, stderr as stderrStream } from "node:process"
|
|
2
2
|
|
|
3
3
|
const color = (code, s) => `\x1b[${code}m${s}\x1b[0m`
|
|
4
4
|
export const c = {
|
|
@@ -10,6 +10,46 @@ export const c = {
|
|
|
10
10
|
bold: (s) => color(1, s),
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// --------------------------------------------------------------------------
|
|
14
|
+
// Backend TUI nativo Node (@clack/prompts) — cross-platform.
|
|
15
|
+
// Quando stdin/stdout/stderr sono un terminale reale, i prompt diventano
|
|
16
|
+
// dialoghi interattivi identici su Windows/pwsh, WSL, Linux e macOS
|
|
17
|
+
// (frecce, Spazio, invio, Esc/Ctrl+C per annullare, exit code 1).
|
|
18
|
+
// Negli altri casi (pipe, CI, non-interattivo) si usa il fallback a righe.
|
|
19
|
+
// `secret` mantiene l'input mascherato (via clack password o raw mode).
|
|
20
|
+
//
|
|
21
|
+
// Il modulo @clack/prompts è importato dinamicamente: se manca (repo senza
|
|
22
|
+
// `npm install`) si cade automaticamente sul fallback a righe.
|
|
23
|
+
// --------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
let cpModule = null
|
|
26
|
+
let cpTried = false
|
|
27
|
+
|
|
28
|
+
async function clack() {
|
|
29
|
+
if (!cpTried) {
|
|
30
|
+
cpTried = true
|
|
31
|
+
try {
|
|
32
|
+
cpModule = await import("@clack/prompts")
|
|
33
|
+
} catch {
|
|
34
|
+
cpModule = null // dipendenza non installata: fallback a righe
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return cpModule
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const realTty = () => !!input.isTTY && !!output.isTTY && !!stderrStream.isTTY
|
|
41
|
+
|
|
42
|
+
async function tuiOn() {
|
|
43
|
+
const cp = await clack()
|
|
44
|
+
return !!(cp && realTty())
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function abortSetup() {
|
|
48
|
+
closePrompts()
|
|
49
|
+
console.log(c.yellow("\n annullato (exit 1)."))
|
|
50
|
+
process.exit(1)
|
|
51
|
+
}
|
|
52
|
+
|
|
13
53
|
function enableRaw() {
|
|
14
54
|
try {
|
|
15
55
|
input.setRawMode(true)
|
|
@@ -85,8 +125,11 @@ export function readLine({ masked = false, allowEmpty = false } = {}, done) {
|
|
|
85
125
|
input.on("data", onData)
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
// --------------------------------------------------------------------------
|
|
129
|
+
// Implementazioni classiche (fallback a righe, stesse API precedenti).
|
|
130
|
+
// --------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
async function askClassic(question, { defaultValue = "", hint = "", validate } = {}) {
|
|
90
133
|
const suffix = hint ? ` ${c.dim(`(${hint})`)}` : ""
|
|
91
134
|
const def = defaultValue ? c.dim(`[${defaultValue}]`) : ""
|
|
92
135
|
// eslint-disable-next-line no-constant-condition
|
|
@@ -102,8 +145,7 @@ export async function ask(question, { defaultValue = "", hint = "", validate } =
|
|
|
102
145
|
}
|
|
103
146
|
}
|
|
104
147
|
|
|
105
|
-
|
|
106
|
-
export async function confirm(question, defaultValue = false) {
|
|
148
|
+
async function confirmClassic(question, defaultValue = false) {
|
|
107
149
|
const hint = defaultValue ? "S/n" : "s/N"
|
|
108
150
|
// eslint-disable-next-line no-constant-condition
|
|
109
151
|
for (;;) {
|
|
@@ -116,26 +158,24 @@ export async function confirm(question, defaultValue = false) {
|
|
|
116
158
|
}
|
|
117
159
|
}
|
|
118
160
|
|
|
119
|
-
|
|
120
|
-
export async function select(question, choices, { defaultValue = 0 } = {}) {
|
|
161
|
+
async function selectClassic(question, choices, { defaultValue = 0 } = {}) {
|
|
121
162
|
console.log(`\n${c.cyan("?")} ${question}`)
|
|
122
163
|
choices.forEach((choice, i) => {
|
|
123
164
|
console.log(` ${i + 1}. ${choice.label}`)
|
|
124
165
|
})
|
|
125
|
-
return choices[Number(await
|
|
166
|
+
return choices[Number(await askClassic("Scelta (numero)", {
|
|
126
167
|
defaultValue: String(defaultValue + 1),
|
|
127
168
|
validate: (v) => /^\d+$/.test(v) && Number(v) >= 1 && Number(v) <= choices.length,
|
|
128
169
|
})) - 1]
|
|
129
170
|
}
|
|
130
171
|
|
|
131
|
-
|
|
132
|
-
export async function checkbox(question, choices, { defaultIndices = [] } = {}) {
|
|
172
|
+
async function checkboxClassic(question, choices, { defaultIndices = [] } = {}) {
|
|
133
173
|
console.log(`\n${c.cyan("?")} ${question} ${c.dim("(invio = nessuno; es. 1,3,5)")}`)
|
|
134
174
|
choices.forEach((choice, i) => {
|
|
135
175
|
const sel = defaultIndices.includes(i) ? "•" : " "
|
|
136
176
|
console.log(` ${sel} ${i + 1}. ${choice.label}`)
|
|
137
177
|
})
|
|
138
|
-
const answer = await
|
|
178
|
+
const answer = await askClassic("Seleziona (es. 1,3,5)", {
|
|
139
179
|
validate: (v) => {
|
|
140
180
|
const nums = v.split(/[\s,]+/).filter(Boolean)
|
|
141
181
|
if (!nums.length) return true // invio = nessuno (salta)
|
|
@@ -147,12 +187,86 @@ export async function checkbox(question, choices, { defaultIndices = [] } = {})
|
|
|
147
187
|
return [...new Set(nums.map((n) => Number(n) - 1))].map((i) => choices[i])
|
|
148
188
|
}
|
|
149
189
|
|
|
150
|
-
|
|
151
|
-
export async function secret(question, { allowEmpty = false } = {}) {
|
|
190
|
+
async function secretClassic(question, { allowEmpty = false } = {}) {
|
|
152
191
|
output.write(`${c.cyan("?")} ${question}\n> `)
|
|
153
192
|
return new Promise((resolve) => readLine({ masked: true, allowEmpty }, (v) => resolve(v)))
|
|
154
193
|
}
|
|
155
194
|
|
|
195
|
+
// --------------------------------------------------------------------------
|
|
196
|
+
// API pubbliche: TUI @clack/prompts (TTY) con fallback classico.
|
|
197
|
+
// --------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
/** Domanda a testo semplice con default opzionale (clack text). */
|
|
200
|
+
export async function ask(question, { defaultValue = "", hint = "", validate } = {}) {
|
|
201
|
+
if (!(await tuiOn())) return askClassic(question, { defaultValue, hint, validate })
|
|
202
|
+
const cp = await clack()
|
|
203
|
+
const message = hint ? `${question} (${hint})` : question
|
|
204
|
+
// eslint-disable-next-line no-constant-condition
|
|
205
|
+
for (;;) {
|
|
206
|
+
const raw = await cp.text({
|
|
207
|
+
message,
|
|
208
|
+
initialValue: defaultValue || undefined,
|
|
209
|
+
validate: (val) => {
|
|
210
|
+
const value = String(val ?? "").trim() === "" ? defaultValue : String(val).trim()
|
|
211
|
+
return validate && !validate(value) ? "risposta non valida, riprova." : undefined
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
if (cp.isCancel(raw)) abortSetup()
|
|
215
|
+
const value = String(raw ?? "").trim() === "" ? defaultValue : String(raw).trim()
|
|
216
|
+
if (validate && !validate(value)) {
|
|
217
|
+
cp.log.error("risposta non valida, riprova.")
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
return value
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Domanda sì/no (clack confirm). */
|
|
225
|
+
export async function confirm(question, defaultValue = false) {
|
|
226
|
+
if (!(await tuiOn())) return confirmClassic(question, defaultValue)
|
|
227
|
+
const cp = await clack()
|
|
228
|
+
const v = await cp.confirm({ message: question, initialValue: !!defaultValue })
|
|
229
|
+
if (cp.isCancel(v)) abortSetup()
|
|
230
|
+
return v === true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Selezione singola (clack select, frecce). */
|
|
234
|
+
export async function select(question, choices, { defaultValue = 0 } = {}) {
|
|
235
|
+
if (!(await tuiOn())) return selectClassic(question, choices, { defaultValue })
|
|
236
|
+
const cp = await clack()
|
|
237
|
+
const idx = await cp.select({
|
|
238
|
+
message: question,
|
|
239
|
+
options: choices.map((ch, i) => ({ value: i, label: ch.label })),
|
|
240
|
+
initialValue: defaultValue ?? 0,
|
|
241
|
+
})
|
|
242
|
+
if (cp.isCancel(idx)) abortSetup()
|
|
243
|
+
return choices[idx]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Selezione multipla (clack multiselect, frecce + Spazio). */
|
|
247
|
+
export async function checkbox(question, choices, { defaultIndices = [] } = {}) {
|
|
248
|
+
if (!(await tuiOn())) return checkboxClassic(question, choices, { defaultIndices })
|
|
249
|
+
const cp = await clack()
|
|
250
|
+
const options = choices.map((ch) => ({ value: ch.value, label: ch.label }))
|
|
251
|
+
const initialValues = defaultIndices.map((i) => choices[i]?.value).filter((v) => v !== undefined)
|
|
252
|
+
const vals = await cp.multiselect({ message: question, options, initialValues, required: false })
|
|
253
|
+
if (cp.isCancel(vals)) abortSetup()
|
|
254
|
+
const sel = new Set(vals)
|
|
255
|
+
return choices.filter((ch) => sel.has(ch.value))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Input mascherato per segreti (API key ecc.). */
|
|
259
|
+
export async function secret(question, { allowEmpty = false } = {}) {
|
|
260
|
+
if (!(await tuiOn())) return secretClassic(question, { allowEmpty })
|
|
261
|
+
const cp = await clack()
|
|
262
|
+
const v = await cp.password({
|
|
263
|
+
message: question,
|
|
264
|
+
validate: allowEmpty ? () => undefined : undefined,
|
|
265
|
+
})
|
|
266
|
+
if (cp.isCancel(v)) abortSetup()
|
|
267
|
+
return String(v ?? "")
|
|
268
|
+
}
|
|
269
|
+
|
|
156
270
|
export function closePrompts() {
|
|
157
271
|
input.removeAllListeners("data")
|
|
158
272
|
try {
|