opencode-wyvern 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -8,8 +8,42 @@ API key vengono **richiesti/creati durante il setup**, mai salvati in locale
8
8
 
9
9
  ## Installazione
10
10
 
11
+ Prerequisiti: **Node.js 18+** e **npm** (su Ubuntu/WSL si consiglia `nvm`).
12
+
11
13
  ```bash
12
14
  npm install -g opencode-wyvern
15
+ ```
16
+
17
+ > Attenzione: `npm rm -g opencode-wyvern` **disinstalla**. Per installare
18
+ > si usa solo `npm install -g opencode-wyvern`.
19
+
20
+ Il comando si chiama **`oc-setup`** (non `oc-help`, non `oc`). Verifica:
21
+
22
+ ```bash
23
+ oc-setup --version # → 0.2.3
24
+ ```
25
+
26
+ Se compare `oc-setup: command not found` subito dopo l'installazione, il
27
+ binario è appena fuori dal PATH della shell corrente. Ricaricalo:
28
+
29
+ ```bash
30
+ hash -r # svuota la cache dei comandi (immediato) e riprova
31
+ source ~/.bashrc # oppure ricarica il profilo
32
+ # oppure: chiudi e riapri il terminale WSL
33
+ ```
34
+
35
+ Con **nvm** (Linux/WSL) il binario finisce in
36
+ `~/.nvm/versions/node/<versione>/bin`; quell'alias deve essere attivo
37
+ (`nvm use default`). Controlla che il file ci sia:
38
+
39
+ ```bash
40
+ nvm use default
41
+ ls -l ~/.nvm/versions/node/$(nvm current)/bin/oc-setup
42
+ ```
43
+
44
+ Poi avvia la procedura guidata:
45
+
46
+ ```bash
13
47
  oc-setup
14
48
  ```
15
49
 
@@ -115,4 +149,4 @@ node bin/oc-setup.js --help
115
149
  - Nessuna chiave/token/password nel pacchetto o in `config.json` locale.
116
150
  - Le API key sono scritte dal bootstrap remoto in `~/.config/opencode/.env` (`chmod 600`).
117
151
  - I file remoti viaggiano come base64 dentro lo script; con server non raggiungibile
118
- lo script non viene stampato a video (evita leak delle chiavi).
152
+ lo script non viene stampato a video (evita leak delle chiavi).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-wyvern",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",
package/src/prompts.js CHANGED
@@ -153,24 +153,34 @@ function enableRaw() {
153
153
  * piping/test e ambiente completamente non interattivo).
154
154
  */
155
155
  const fallbackQueue = []
156
+ let fallbackEof = false
157
+ let fallbackBound = false
158
+ const fallbackWaiters = []
159
+
160
+ function pumpFallback() {
161
+ while (fallbackWaiters.length && (fallbackQueue.length || fallbackEof)) {
162
+ const done = fallbackWaiters.shift()
163
+ done(fallbackEof ? "q" : fallbackQueue.shift())
164
+ }
165
+ }
156
166
 
157
167
  function readLineFallback(done) {
158
- const next = () => {
159
- if (fallbackQueue.length) {
160
- const v = fallbackQueue.shift()
161
- return done(v)
162
- }
163
- const onData = (chunk) => {
164
- const text = chunk.toString()
165
- const parts = text.split(/\r\n|\r|\n/)
166
- fallbackQueue.push(...parts)
167
- input.removeListener("data", onData)
168
- next()
168
+ if (!fallbackBound) {
169
+ fallbackBound = true
170
+ input.on("data", (chunk) => {
171
+ fallbackQueue.push(...chunk.toString().split(/\r\n|\r|\n/))
172
+ pumpFallback()
173
+ })
174
+ const onEof = () => {
175
+ fallbackEof = true
176
+ pumpFallback()
169
177
  }
170
- input.on("data", onData)
178
+ input.on("end", onEof)
179
+ input.on("close", onEof)
171
180
  input.resume()
172
181
  }
173
- next()
182
+ fallbackWaiters.push(done)
183
+ pumpFallback()
174
184
  }
175
185
 
176
186
  /** Legge una riga da tastiera. `masked` oscura la digitazione (per segreti); */
package/src/server.js CHANGED
@@ -244,11 +244,12 @@ export function ensureLocalKey() {
244
244
  export function installKeyOnServer(cfg) {
245
245
  const { pub } = keyPaths()
246
246
  const pubContent = fs.readFileSync(pub, "utf8").trim()
247
+ const pubB64 = Buffer.from(pubContent, "utf8").toString("base64")
247
248
  const target = sshTargetArgs(cfg)
248
249
  const remoteCmd =
249
- "umask 077; mkdir -p ~/.ssh && grep -qF '" +
250
- pubContent.split(" ")[0] +
251
- "' ~/.ssh/authorized_keys 2>/dev/null || (cat >> ~/.ssh/authorized_keys); " +
250
+ `umask 077; mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys; ` +
251
+ `PUB="$(printf '%s' '${pubB64}' | base64 -d)"; ` +
252
+ `grep -qxF "$PUB" ~/.ssh/authorized_keys 2>/dev/null || printf '%s\\n' "$PUB" >> ~/.ssh/authorized_keys; ` +
252
253
  "chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys; echo OK"
253
254
 
254
255
  console.log(c.cyan(ui(
@@ -278,6 +279,8 @@ export function hostAliasBlock(cfg) {
278
279
  if (cfg.host) parts.push(` HostName ${cfg.host}`)
279
280
  if (cfg.user) parts.push(` User ${cfg.user}`)
280
281
  if (cfg.port && String(cfg.port) !== "22") parts.push(` Port ${cfg.port}`)
282
+ parts.push(" IdentityFile ~/.ssh/id_ed25519")
283
+ parts.push(" IdentitiesOnly yes")
281
284
  parts.push(" ServerAliveInterval 30")
282
285
  parts.push(" ServerAliveCountMax 5")
283
286
  parts.push(" StrictHostKeyChecking accept-new")
@@ -289,19 +292,37 @@ export function ensureSshConfigAlias(cfg) {
289
292
  const p = sshConfigPath()
290
293
  const target = `Host ${cfg.server}`
291
294
  let content = ""
295
+ const nextBlock = hostAliasBlock(cfg)
292
296
  if (fs.existsSync(p)) {
293
297
  content = fs.readFileSync(p, "utf8")
294
- if (content.includes(target)) return false
298
+ const re = new RegExp(`(^|\\n)Host\\s+${cfg.server.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n(?:[ \\t].*(?:\\n|$))*`, "m")
299
+ if (re.test(content)) {
300
+ const updated = content.replace(re, (m, lead = "") => `${lead}${nextBlock}\n`)
301
+ if (updated === content) return false
302
+ fs.writeFileSync(p, updated, "utf8")
303
+ return true
304
+ }
295
305
  if (content && !content.endsWith("\n")) content += "\n"
296
306
  }
297
307
  ensureDir(path.dirname(p))
298
- fs.writeFileSync(p, content + hostAliasBlock(cfg) + "\n", "utf8")
308
+ fs.writeFileSync(p, content + nextBlock + "\n", "utf8")
299
309
  return true
300
310
  }
301
311
 
302
312
  /** Verifica la connessione SSH senza password (BatchMode). */
303
313
  export function verifyConnection(cfg) {
304
- const res = run("ssh", [...sshTargetArgs(cfg), "-o", "BatchMode=yes", "-o", "ConnectTimeout=8", "echo CONN_OK"], {
314
+ const { key } = keyPaths()
315
+ const target = cfg.server || sshTargetArgs(cfg).at(-1)
316
+ const targetArgs = cfg.server ? [] : sshTargetArgs(cfg).slice(0, -1)
317
+ const keyArgs = fs.existsSync(key) ? ["-i", key, "-o", "IdentitiesOnly=yes"] : []
318
+ const res = run("ssh", [
319
+ ...targetArgs,
320
+ ...keyArgs,
321
+ "-o", "BatchMode=yes",
322
+ "-o", "ConnectTimeout=8",
323
+ target,
324
+ "echo CONN_OK",
325
+ ], {
305
326
  silent: true,
306
327
  timeout: 20000,
307
328
  })
@@ -335,7 +356,7 @@ export function buildRemoteScript({ sections = new Set(), providers = new Set(),
335
356
  "if [ -z \"$OPENCODE_BIN\" ]; then",
336
357
  " log 'opencode mancante: lo installo (npm i -g opencode-ai)'",
337
358
  " npm install -g opencode-ai >/dev/null 2>&1 || true",
338
- " OPENCODE_BIN=\"$(command -v opencode 2>/dev/null)\"",
359
+ " OPENCODE_BIN=\"$(command -v opencode 2>/dev/null || ls -t $HOME/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)\"",
339
360
  "fi",
340
361
  '[ -f package.json ] || printf \'{\\n "private": true,\\n "dependencies": {}\\n}\\n\' > package.json',
341
362
  "[ -f .gitignore ] || printf 'node_modules\\n.env\\n' > .gitignore",
@@ -448,4 +469,4 @@ export function runScriptLocal(script) {
448
469
 
449
470
  export function isPortValid(v) {
450
471
  return v === "" || (/^\d+$/.test(v) && Number(v) >= 1 && Number(v) <= 65535)
451
- }
472
+ }
package/src/setup.js CHANGED
@@ -323,6 +323,14 @@ if (!localOnly) {
323
323
  if (doInstall) {
324
324
  try {
325
325
  installKeyOnServer(entry)
326
+ if (verifyConnection(entry)) {
327
+ console.log(c.green(ui(" autenticazione SSH a chiave verificata.", " SSH key authentication verified.")))
328
+ } else {
329
+ console.log(c.yellow(ui(
330
+ " attenzione: la chiave e' stata copiata, ma l'accesso senza password non e' stato verificato. Controlla sshd e ~/.ssh/authorized_keys sul server.",
331
+ " warning: the key was copied, but passwordless login could not be verified. Check sshd and ~/.ssh/authorized_keys on the server.",
332
+ )))
333
+ }
326
334
  } catch (err) {
327
335
  console.log(c.yellow(` ${ui("attenzione:", "warning:")} ${err.message}`))
328
336
  }
@@ -447,4 +455,4 @@ function fail(msg) {
447
455
  export async function runPrintClient(kind) {
448
456
  const id = kind === "bash" ? "client-bash" : "client-pwsh"
449
457
  await cmdPrint(id)
450
- }
458
+ }
@@ -28,11 +28,10 @@ oc-sync-env
28
28
 
29
29
  oc-path() {
30
30
  oc-sync-env
31
- if [ -z "$OC_OPENCODE" ]; then
32
- OC_OPENCODE=$(ssh -o BatchMode=yes "$OC_SERVER" "(command -v opencode || ls -t \$HOME/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)" 2>/dev/null | head -n1)
33
- [ -n "$OC_OPENCODE" ] || OC_OPENCODE='opencode'
34
- fi
35
- echo "$OC_OPENCODE"
31
+ # Risolve opencode SUL SERVER al momento dell'esecuzione: funziona anche
32
+ # senza chiave SSH configurata (BatchMode non tenuto) e senza opencode nel
33
+ # PATH del server (cade sulle installazioni nvm `~/.nvm/versions/node/*/bin`).
34
+ printf '%s' '__oc_wyvern(){ OPENCODE_BIN="$(command -v opencode 2>/dev/null || ls -t $HOME/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)"; if [ -z "$OPENCODE_BIN" ]; then echo "[oc] ERRORE: opencode non trovato sul server. Esegui oc-setup scegliendo Client + Server, oppure installa sul server: npm install -g opencode-ai" >&2; return 127; fi; "$OPENCODE_BIN" "$@"; }; __oc_wyvern'
36
35
  }
37
36
 
38
37
  oc-connect() {
@@ -201,10 +200,23 @@ oc-delete() {
201
200
 
202
201
  oc-go() {
203
202
  oc-sync-env
204
- local id="$1" dir="${2:-~}" norecap=0 oc exit_code
203
+ local id="$1" dir="${2:-}" norecap=0 oc exit_code session_dir
205
204
  [ "$3" = "--no-recap" ] && norecap=1
205
+ if [ -z "$id" ]; then
206
+ echo -e "${RED}[FAIL] Specifica l'ID della sessione.${NC}"
207
+ return 2
208
+ fi
209
+ case "$id" in
210
+ *[![:alnum:]_-]*) echo -e "${RED}[FAIL] ID sessione non valido.${NC}"; return 2 ;;
211
+ esac
206
212
  oc="$(oc-path)"
207
- oc-sessions-all >/dev/null 2>&1
213
+ if [ -z "$dir" ]; then
214
+ session_dir="$(oc-cache-load | awk -F '\t' -v wanted="$id" '$2 == wanted { print $4; exit }')"
215
+ if [ -z "$session_dir" ]; then
216
+ session_dir="$(oc-sessions-all | awk -F '\t' -v wanted="$id" '$2 == wanted { print $4; exit }')"
217
+ fi
218
+ dir="${session_dir:-~}"
219
+ fi
208
220
  mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/opencode-wyvern"
209
221
  printf '%s\t%s\n' "$id" "${dir:-~}" > "${XDG_CACHE_HOME:-$HOME/.cache}/opencode-wyvern/last.tsv"
210
222
  if [ "$dir" = "~" ] || [ -z "$dir" ]; then
@@ -229,6 +241,12 @@ oc-recap() {
229
241
  live="$(oc-sessions-today 2>/dev/null)"
230
242
  if [ -z "$live" ]; then
231
243
  cached="$(oc-cache-load)"
244
+ if [ "$last_exit" = "127" ]; then
245
+ echo -e "${RED} [!] Comando remoto non trovato (SSH exit 127).${NC}"
246
+ echo -e "${YELLOW} Probabile causa: opencode non e' installato sul server o non e' nel PATH della shell SSH.${NC}"
247
+ echo -e "${DARKGRAY:-} Risolvi con: oc-setup scegliendo Client + Server, oppure sul server: npm install -g opencode-ai${NC}"
248
+ return 127
249
+ fi
232
250
  if [ -n "$cached" ]; then
233
251
  m="$(stat -c %y "${XDG_CACHE_HOME:-$HOME/.cache}/opencode-wyvern/sessions.tsv" 2>/dev/null | cut -d'.' -f1)"
234
252
  echo -e "${RED} [!] Connessione persa alle $err_time (SSH exit $last_exit).${NC}"
@@ -448,4 +466,4 @@ oc-help() {
448
466
  echo ""
449
467
  echo -e "${DARKGRAY:-}Nota: oc-ssh, oc-sessions, oc-resume richiedono SSH key configurata"
450
468
  echo ""
451
- }
469
+ }
@@ -26,13 +26,10 @@ function Sync-OcEnv {
26
26
  Sync-OcEnv
27
27
 
28
28
  function Get-OcPath {
29
- if (-not $global:OC_OPENCODE) {
30
- $remotePath = "(command -v opencode || ls -t ~/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)"
31
- $found = ssh -o BatchMode=yes $env:OC_SERVER $remotePath 2>$null | Select-Object -First 1
32
- if ($found) { $global:OC_OPENCODE = $found.Trim() }
33
- else { $global:OC_OPENCODE = 'opencode' }
34
- }
35
- return $global:OC_OPENCODE
29
+ # Risolve opencode SUL SERVER al momento dell'esecuzione: funziona anche
30
+ # senza chiave SSH configurata (niente BatchMode) e senza opencode nel PATH
31
+ # del server (fallback sulle installazioni nvm ~/.nvm/versions/node/*/bin).
32
+ return '__oc_wyvern(){ OPENCODE_BIN="$(command -v opencode 2>/dev/null || ls -t $HOME/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)"; if [ -z "$OPENCODE_BIN" ]; then echo "[oc] ERRORE: opencode non trovato sul server. Esegui oc-setup scegliendo Client + Server, oppure installa sul server: npm install -g opencode-ai" >&2; return 127; fi; "$OPENCODE_BIN" "$@"; }; __oc_wyvern'
36
33
  }
37
34
 
38
35
  function oc-connect {
@@ -300,11 +297,21 @@ function oc-go {
300
297
  param(
301
298
  [Parameter(Mandatory)]
302
299
  [string]$Id,
303
- [string]$Dir = "~",
300
+ [string]$Dir = "",
304
301
  [switch]$NoRecap
305
302
  )
306
303
  Sync-OcEnv
307
- $null = @(Get-OcAllSessions)
304
+ if ($Id -notmatch '^[A-Za-z0-9_-]+$') {
305
+ Write-Host "ID sessione non valido." -ForegroundColor Red
306
+ return
307
+ }
308
+ if ([string]::IsNullOrWhiteSpace($Dir)) {
309
+ $found = @(Get-OcCachedSessions | Where-Object { $_.Id -eq $Id } | Select-Object -First 1)
310
+ if (-not $found.Count) {
311
+ $found = @(Get-OcAllSessions | Where-Object { $_.Id -eq $Id } | Select-Object -First 1)
312
+ }
313
+ $Dir = if ($found.Count) { $found[0].Dir } else { "~" }
314
+ }
308
315
  $oc = Get-OcPath
309
316
  if ($Dir -eq '~' -or [string]::IsNullOrEmpty($Dir)) {
310
317
  $remoteCmd = "cd ~ && $oc -s $Id"
@@ -334,6 +341,12 @@ function Show-OcRecap {
334
341
  $sessions = @(Get-OcRecentSessions)
335
342
  $fromCache = $false
336
343
  if ($sessions.Count -eq 0) {
344
+ if ($LastExit -eq 127) {
345
+ Write-Host " [!] Comando remoto non trovato (SSH exit 127)." -ForegroundColor Red
346
+ Write-Host " Probabile causa: opencode non e' installato sul server o non e' nel PATH della shell SSH." -ForegroundColor Yellow
347
+ Write-Host " Risolvi con: oc-setup scegliendo Client + Server, oppure sul server: npm install -g opencode-ai" -ForegroundColor DarkGray
348
+ return
349
+ }
337
350
  $status = Get-OcServerStatus
338
351
  if ($status.Up) {
339
352
  Write-Host " Server raggiungibile ma nessuna sessione nelle ultime 24 ore." -ForegroundColor Yellow