@dotrino/vaultd 0.7.6 → 0.7.8

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.
@@ -38,7 +38,7 @@ const mgr = await runDaemon()
38
38
  if (process.argv.includes('--pair')) {
39
39
  const { qr, expiresInMs } = mgr.current().startPairing({ label: 'cli' })
40
40
  console.log(`\nEmparejá un dispositivo (válido ${expiresInMs / 60000} min):\n`)
41
- console.log(qrToString(JSON.stringify(qr)))
41
+ console.log(qrToString(JSON.stringify(qr), 2))
42
42
  console.log(JSON.stringify(qr))
43
43
  }
44
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
5
  "description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
6
6
  "bin": {
package/src/ctl.js CHANGED
@@ -186,11 +186,11 @@ async function cmdPair (args = []) {
186
186
  const acct = pair.profileName || pair.profile
187
187
  if (acct) console.log('\nCuenta que se comparte: %s%s', acct, pair.profileName && pair.profile ? ` (${pair.profile})` : '')
188
188
  console.log('\nEscanea este QR con el dispositivo que quieres conectar (válido %d min):\n', mins)
189
- console.log(qrToString(url)) // el QR abre vault.dotrino.com/dispositivos y empareja solo
189
+ console.log(qrToString(payload, 2)) // QR con el JSON crudo (menos datos) y zona de silencio reducida
190
190
  console.log(`${R}${B}⚠ Este código deja LEER tus datos y FIRMAR con tu identidad.${Z}`)
191
191
  console.log(`${R} NO lo compartas con nadie, ni con "soporte". Solo escanéalo en TU dispositivo.${Z}`)
192
192
  console.log('\nO abre esta dirección en el dispositivo:\n ' + url)
193
- console.log('\nO pega este código en vault.dotrino.com/dispositivos :\n ' + payload)
193
+ console.log('\nO pega este código en vault.dotrino.com/dispositivos :\n ' + b64)
194
194
 
195
195
  // --save [archivo]: escribe la invitación (.dpair) para transferirla y abrirla en profile.
196
196
  const saveIdx = args.indexOf('--save')
package/src/tui/app.js CHANGED
@@ -118,6 +118,19 @@ function renderList (rows, selIdx, height, cols, t, scrollRef) {
118
118
  return out
119
119
  }
120
120
 
121
+ /**
122
+ * Scroll simple para una lista de líneas planas (no seleccionables). Igual idea
123
+ * que `renderList`, pero sin índice de selección: solo desplaza la ventana.
124
+ */
125
+ function scrollBody (lines, height, scrollRef) {
126
+ let top = scrollRef.value || 0
127
+ top = Math.max(0, Math.min(top, Math.max(0, lines.length - height)))
128
+ scrollRef.value = top
129
+ const out = []
130
+ for (let i = 0; i < height; i++) out.push(lines[top + i] ?? '')
131
+ return out
132
+ }
133
+
121
134
  /**
122
135
  * Barra de ayuda que SIEMPRE deja ver lo global (idioma y salir): si los segmentos
123
136
  * no caben, recorta desde el MEDIO y marca el corte con «…». Sin esto, en 80
@@ -450,7 +463,7 @@ async function onKeyDevices (term, st, key) {
450
463
  /** Abre el emparejamiento contra `profile` y salta a la pantalla del QR. */
451
464
  async function beginPairing (term, st, profile) {
452
465
  const r = await guard(term, st, L(st).startingPairing, () => vc.startPairing({ profile }))
453
- if (r.ok) { st.pairing = r.v; st.pending = null; st.screen = 'pairing' }
466
+ if (r.ok) { st.pairing = r.v; st.pending = null; st.scroll.pairing = { value: 0 }; st.screen = 'pairing' }
454
467
  return r.ok
455
468
  }
456
469
 
@@ -514,6 +527,21 @@ async function onKeyPairing (term, st, key) {
514
527
  if (pend) st.pending = pend
515
528
  return true
516
529
  }
530
+ // Scroll vertical para ver el QR completo cuando no cabe en la pantalla.
531
+ if (['up', 'down', 'pageup', 'pagedown', 'home', 'end'].includes(key.name)) {
532
+ const { rows } = term.size()
533
+ const contentH = Math.max(1, rows - 7)
534
+ const pb = pairingBody(st, term.t, term.size().cols, contentH)
535
+ const maxScroll = Math.max(0, pb.length - contentH)
536
+ const scroll = st.scroll.pairing || (st.scroll.pairing = { value: 0 })
537
+ if (key.name === 'up') scroll.value = Math.max(0, scroll.value - 1)
538
+ else if (key.name === 'down') scroll.value = Math.min(maxScroll, scroll.value + 1)
539
+ else if (key.name === 'pageup') scroll.value = Math.max(0, scroll.value - 5)
540
+ else if (key.name === 'pagedown') scroll.value = Math.min(maxScroll, scroll.value + 5)
541
+ else if (key.name === 'home') scroll.value = 0
542
+ else if (key.name === 'end') scroll.value = maxScroll
543
+ return true
544
+ }
517
545
  if (ch === 'a' && st.pending) { promptApprove(term, st); return true }
518
546
  if (ch === 'x' && st.pending) {
519
547
  const r = await guard(term, st, i.rejecting, () => vc.rejectPending(st.pending.deviceId, activeId(st)))
@@ -522,7 +550,7 @@ async function onKeyPairing (term, st, key) {
522
550
  }
523
551
  if (ch === 'r') { // restart: reiniciar el emparejamiento
524
552
  const r = await guard(term, st, i.restartingPairing, () => vc.startPairing({ profile: activeId(st) }))
525
- if (r.ok) { st.pairing = r.v; st.pending = null }
553
+ if (r.ok) { st.pairing = r.v; st.pending = null; st.scroll.pairing = { value: 0 } }
526
554
  return true
527
555
  }
528
556
  if (key.name === 'escape' || ch === 'b') { st.screen = 'devices'; st.pairing = null; await refreshDevices(term, st) }
@@ -670,20 +698,23 @@ function pairingBody (st, t, cols, height) {
670
698
  const left = Math.max(0, Math.round((info.expiresAt - Date.now()) / 60000))
671
699
  lines.push(t.muted(i.pairValid(left)))
672
700
  lines.push('')
673
- // QR solo si entra cómodo (es "alto": ~ (módulos+8)/2 filas).
701
+ // QR: se dibuja siempre que quepa de ancho; si es más alto que la pantalla se
702
+ // puede hacer scroll hacia arriba/abajo para verlo completo.
674
703
  let qr = ''
675
- try { qr = qrToString(info.url) } catch (_) {}
704
+ try { qr = qrToString(info.payload, 2) } catch (_) {}
676
705
  const qrLines = qr ? qr.replace(/\n$/, '').split('\n') : []
677
706
  const qrWidth = qrLines.length ? Math.max(...qrLines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, '').length)) : 0
678
- const reserved = 9 // encabezado + cuenta + URL + payload + aviso
679
- if (qrLines.length && qrWidth <= cols && qrLines.length <= height - reserved) {
707
+ if (qrLines.length && qrWidth <= cols) {
680
708
  for (const l of qrLines) lines.push(l)
681
709
  lines.push('')
710
+ } else if (qrLines.length) {
711
+ lines.push(t.warn(i.pairQrTooNarrow(cols, qrWidth)))
712
+ lines.push('')
682
713
  }
683
714
  lines.push(t.bold(i.pairUrl) + info.url)
684
715
  lines.push('')
685
716
  lines.push(t.muted(i.pairPaste))
686
- lines.push(info.payload)
717
+ lines.push(info.b64 || info.payload)
687
718
  lines.push('')
688
719
  lines.push(t.danger(i.pairWarning))
689
720
  lines.push('')
@@ -731,8 +762,7 @@ function render (term, st) {
731
762
  else if (st.screen === 'pairmode') body = renderList(pairModeRows(st, t), st.sel.pairmode, contentH, cols, t, scrollRef)
732
763
  else if (st.screen === 'pairing') {
733
764
  const pb = pairingBody(st, t, cols, contentH)
734
- body = pb.slice(0, contentH)
735
- while (body.length < contentH) body.push('')
765
+ body = scrollBody(pb, contentH, scrollRef)
736
766
  }
737
767
  for (let n = 0; n < contentH; n++) lines[top + n] = body[n] ?? ''
738
768
 
@@ -887,4 +917,4 @@ export async function runTui () {
887
917
  }
888
918
 
889
919
  // Solo para pruebas headless (render sin terminal real). No usar en runtime.
890
- export const __test = { render, profileRows, deviceRows, secretRows, pairModeRows, pairingBody, fitHelp, toggleLang }
920
+ export const __test = { render, profileRows, deviceRows, secretRows, pairModeRows, pairingBody, scrollBody, fitHelp, toggleLang }
package/src/tui/i18n.js CHANGED
@@ -141,6 +141,7 @@ const es = {
141
141
  pairWarning: '⚠ Este código deja LEER tus datos y FIRMAR con tu identidad. No lo compartas.',
142
142
  pairConnected: (id) => `⧗ Se conectó: ${id} — pulsa A y escribe el código que muestra.`,
143
143
  pairWaiting: 'Esperando a que el dispositivo se conecte…',
144
+ pairQrTooNarrow: (cols, need) => `Agranda el terminal (el QR necesita ${need} cols; hay ${cols}).`,
144
145
 
145
146
  // confirmación / entrada
146
147
  confirmKeys: ' (s / N)',
@@ -154,7 +155,7 @@ const es = {
154
155
  helpProfiles: ['↑↓', 'Enter entrar', 'p emparejar', 'n nueva', 'r renombrar', 'd borrar', 'c clave', 'x quitar-clave', 'u desbloq', 'k bloquear', 'l English', 'q salir'],
155
156
  helpDevices: ['←→ pestaña', '↑↓', 'p emparejar', 'a aprobar', 'x rechazar', 'v revocar', 'r refrescar', 'Esc bóvedas', 'l English', 'q salir'],
156
157
  helpSecrets: ['←→ pestaña', '↑↓', 'n nueva variable', 'x quitar (variable/scope)', 'r refrescar', 'Esc bóvedas', 'l English', 'q salir'],
157
- helpPairing: ['a aprobar', 'x rechazar', 'r reiniciar', 'Esc atrás', 'l English'],
158
+ helpPairing: ['a aprobar', 'x rechazar', 'r reiniciar', '↑↓ scroll', 'Esc atrás', 'l English'],
158
159
  helpPairMode: ['↑↓', 'Enter elegir', 'Esc atrás', 'l English', 'q salir'],
159
160
 
160
161
  // pantalla "daemon caído"
@@ -303,6 +304,7 @@ const en = {
303
304
  pairWarning: '⚠ This code lets someone READ your data and SIGN as you. Do not share it.',
304
305
  pairConnected: (id) => `⧗ Connected: ${id} — press A and type the code it shows.`,
305
306
  pairWaiting: 'Waiting for the device to connect…',
307
+ pairQrTooNarrow: (cols, need) => `Widen the terminal (the QR needs ${need} cols; you have ${cols}).`,
306
308
 
307
309
  confirmKeys: ' (y / N)',
308
310
  helpInput: 'Enter confirm · Esc cancel · Ctrl-U clear',
@@ -311,7 +313,7 @@ const en = {
311
313
  helpProfiles: ['↑↓', 'Enter open', 'p pair', 'n new', 'r rename', 'd delete', 'c password', 'x drop-password', 'u unlock', 'k lock', 'l Español', 'q quit'],
312
314
  helpDevices: ['←→ tab', '↑↓', 'p pair', 'a approve', 'x reject', 'v revoke', 'r refresh', 'Esc vaults', 'l Español', 'q quit'],
313
315
  helpSecrets: ['←→ tab', '↑↓', 'n new variable', 'x remove (variable/scope)', 'r refresh', 'Esc vaults', 'l Español', 'q quit'],
314
- helpPairing: ['a approve', 'x reject', 'r restart', 'Esc back', 'l Español'],
316
+ helpPairing: ['a approve', 'x reject', 'r restart', '↑↓ scroll', 'Esc back', 'l Español'],
315
317
  helpPairMode: ['↑↓', 'Enter choose', 'Esc back', 'l Español', 'q quit'],
316
318
 
317
319
  downTitle: 'The vault daemon is not running.',
@@ -262,7 +262,7 @@ export function pairUrl (qr) {
262
262
  const payload = JSON.stringify(qr)
263
263
  const b64 = Buffer.from(payload, 'utf8').toString('base64')
264
264
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
265
- return { url: PROFILE_URL + b64, payload }
265
+ return { url: PROFILE_URL + b64, payload, b64 }
266
266
  }
267
267
 
268
268
  /**
@@ -279,11 +279,11 @@ export async function startPairing ({ profile, service } = {}) {
279
279
  await sleep(100)
280
280
  const pr = read(F.pair, null)
281
281
  if (pr?.expiresAt > Date.now()) {
282
- const { url, payload } = pairUrl(pr.qr)
282
+ const { url, payload, b64 } = pairUrl(pr.qr)
283
283
  // `profile`/`profileName`: DE QUÉ CUENTA del vault sale este QR. El vault
284
284
  // puede tener varias y el emparejamiento mete al dispositivo en UNA; la TUI
285
285
  // y la CLI lo muestran para que no se enrole en la equivocada.
286
- return { qr: pr.qr, expiresAt: pr.expiresAt, url, payload, profile: pr.profile || null, profileName: pr.profileName || '' }
286
+ return { qr: pr.qr, expiresAt: pr.expiresAt, url, payload, b64, profile: pr.profile || null, profileName: pr.profileName || '' }
287
287
  }
288
288
  }
289
289
  throw coded('el daemon no inició el emparejamiento', 'PAIR_FAILED')