@gotcos/glasses-server 6.20.0 → 6.20.1

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/cli.cjs +59 -24
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 6.20.1
2
+
3
+ Adaptive setup now survives the slow or interrupted downloads that exposed the
4
+ first public 6.20.0 install to a false-success state.
5
+
6
+ - **Resumable model downloads.** Whisper model downloads retain their `.partial`
7
+ files, resume with HTTP range requests, retry transient network errors eight
8
+ times, and use one-hour bounds for Small.en/Turbo plus a two-hour bound for
9
+ Large-v3. A rerun continues from the last byte instead of restarting at zero.
10
+ - **Truthful guided setup.** `--setup-transcription --prepare-only` exits nonzero
11
+ and names any missing lane instead of printing “setup complete” after a model
12
+ download failed. Existing Turbo transcription remains the safe runtime
13
+ fallback while setup is incomplete.
14
+
1
15
  ## 6.20.0
2
16
 
3
17
  Adaptive transcription makes fast feedback additive instead of a quality
package/bin/cli.cjs CHANGED
@@ -393,9 +393,9 @@ const WHISPER_LARGE_MODEL_PATH = join(WHISPER_MODEL_DIR, 'ggml-large-v3.bin')
393
393
  const WHISPER_LARGE_MODEL_PARTIAL = WHISPER_LARGE_MODEL_PATH + '.partial'
394
394
  const WHISPER_LARGE_MODEL_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin'
395
395
  const WHISPER_LARGE_MODEL_MIN_BYTES = 2_800_000_000
396
- const WHISPER_MODEL_EXPECTED_BYTES = 1_620_000_000
397
- const WHISPER_SMALL_MODEL_EXPECTED_BYTES = 466_000_000
398
- const WHISPER_LARGE_MODEL_EXPECTED_BYTES = 3_100_000_000
396
+ const WHISPER_MODEL_EXPECTED_BYTES = 1_624_555_275
397
+ const WHISPER_SMALL_MODEL_EXPECTED_BYTES = 487_614_201
398
+ const WHISPER_LARGE_MODEL_EXPECTED_BYTES = 3_095_033_483
399
399
  function findWhisperCli() {
400
400
  for (const p of WHISPER_KNOWN_PATHS) { if (existsSync(p)) return p }
401
401
  try {
@@ -418,9 +418,31 @@ function isValidWhisperModel(p, minBytes = WHISPER_MODEL_MIN_BYTES) {
418
418
  if (!existsSync(p)) return false
419
419
  try { return statSync(p).size >= minBytes } catch { return false }
420
420
  }
421
+ function downloadWhisperModel({ url, destination, partial, expectedBytes, timeoutMs }) {
422
+ mkdirSync(WHISPER_MODEL_DIR, { recursive: true })
423
+ const partialBytes = existsSync(partial) ? statSync(partial).size : 0
424
+ execFileSync('curl', [
425
+ '-fL',
426
+ '--retry', '8',
427
+ '--retry-all-errors',
428
+ '--retry-delay', '2',
429
+ '--connect-timeout', '30',
430
+ '--continue-at', '-',
431
+ '--progress-bar',
432
+ url,
433
+ '-o', partial,
434
+ ], { stdio: 'inherit', timeout: timeoutMs })
435
+ const stats = statSync(partial)
436
+ if (stats.size !== expectedBytes) {
437
+ throw new Error(`Downloaded file size mismatch: expected ${expectedBytes}, received ${stats.size} bytes`)
438
+ }
439
+ renameSync(partial, destination)
440
+ return { resumed: partialBytes > 0 }
441
+ }
421
442
  const whisperCliPath = findWhisperCli()
422
443
  const whisperServerPath = findWhisperServer()
423
444
  const hasValidModel = isValidWhisperModel(WHISPER_MODEL_PATH)
445
+ const transcriptionSetupFailures = []
424
446
 
425
447
  if (SETUP_TRANSCRIPTION && (!whisperCliPath || !whisperServerPath)) {
426
448
  console.log('')
@@ -462,7 +484,6 @@ if (whisperCliPath && hasValidModel) {
462
484
  console.log(green(' ✓') + ' whisper.cpp + model ready ' + dim('— voice = local (FREE)'))
463
485
  } else if (whisperCliPath && !hasValidModel) {
464
486
  if (existsSync(WHISPER_MODEL_PATH)) { try { unlinkSync(WHISPER_MODEL_PATH) } catch {} }
465
- if (existsSync(WHISPER_MODEL_PARTIAL)) { try { unlinkSync(WHISPER_MODEL_PARTIAL) } catch {} }
466
487
  console.log(yellow(' ⚠') + ' whisper.cpp installed but model missing')
467
488
  console.log(' ' + dim('Downloading ggml-large-v3-turbo (~1.5 GB).'))
468
489
  console.log(' ' + dim('Skip: SKIP_WHISPER_DOWNLOAD=1 npx --yes @gotcos/glasses-server@latest'))
@@ -470,17 +491,20 @@ if (whisperCliPath && hasValidModel) {
470
491
  console.log(yellow(' ⚠') + ' SKIP_WHISPER_DOWNLOAD=1 — local voice unavailable')
471
492
  } else {
472
493
  try {
473
- mkdirSync(WHISPER_MODEL_DIR, { recursive: true })
474
- execFileSync('curl', ['-fL', '--progress-bar', WHISPER_MODEL_URL, '-o', WHISPER_MODEL_PARTIAL], { stdio: 'inherit', timeout: 900000 })
475
- const stats = statSync(WHISPER_MODEL_PARTIAL)
476
- if (stats.size < WHISPER_MODEL_MIN_BYTES) throw new Error(`Downloaded file too small: ${stats.size} bytes`)
477
- renameSync(WHISPER_MODEL_PARTIAL, WHISPER_MODEL_PATH)
494
+ downloadWhisperModel({
495
+ url: WHISPER_MODEL_URL,
496
+ destination: WHISPER_MODEL_PATH,
497
+ partial: WHISPER_MODEL_PARTIAL,
498
+ expectedBytes: WHISPER_MODEL_EXPECTED_BYTES,
499
+ timeoutMs: 3_600_000,
500
+ })
478
501
  localVoiceReady = true
479
502
  console.log(green(' ✓') + ' Model downloaded ' + dim('— voice = local (FREE)'))
480
503
  } catch (err) {
481
- try { unlinkSync(WHISPER_MODEL_PARTIAL) } catch {}
482
504
  console.log(red(' ✗') + ' Model download failed ' + dim('— local voice unavailable'))
483
505
  console.log(' ' + dim('Error: ' + (err.message || err).toString().slice(0, 120)))
506
+ console.log(' ' + dim('Partial download retained; rerun setup to resume it.'))
507
+ if (SETUP_TRANSCRIPTION) transcriptionSetupFailures.push('Large-v3-Turbo commit model')
484
508
  }
485
509
  }
486
510
  } else {
@@ -500,23 +524,25 @@ if (whisperCliPath && wantsSmallPreview) {
500
524
  console.log(green(' ✓') + ' Small.en preview model ready ' + dim('— Turbo remains authoritative'))
501
525
  } else {
502
526
  if (existsSync(WHISPER_SMALL_MODEL_PATH)) { try { unlinkSync(WHISPER_SMALL_MODEL_PATH) } catch {} }
503
- if (existsSync(WHISPER_SMALL_MODEL_PARTIAL)) { try { unlinkSync(WHISPER_SMALL_MODEL_PARTIAL) } catch {} }
504
527
  console.log(yellow(' ⚠') + ' Adaptive preview model missing')
505
528
  console.log(' ' + dim('Downloading ggml-small.en (~466 MB).'))
506
529
  if (process.env.SKIP_WHISPER_DOWNLOAD === '1') {
507
530
  console.log(yellow(' ⚠') + ' SKIP_WHISPER_DOWNLOAD=1 — previews use Turbo until Small.en is provisioned')
508
531
  } else {
509
532
  try {
510
- mkdirSync(WHISPER_MODEL_DIR, { recursive: true })
511
- execFileSync('curl', ['-fL', '--progress-bar', WHISPER_SMALL_MODEL_URL, '-o', WHISPER_SMALL_MODEL_PARTIAL], { stdio: 'inherit', timeout: 900000 })
512
- const stats = statSync(WHISPER_SMALL_MODEL_PARTIAL)
513
- if (stats.size < WHISPER_SMALL_MODEL_MIN_BYTES) throw new Error(`Downloaded file too small: ${stats.size} bytes`)
514
- renameSync(WHISPER_SMALL_MODEL_PARTIAL, WHISPER_SMALL_MODEL_PATH)
533
+ downloadWhisperModel({
534
+ url: WHISPER_SMALL_MODEL_URL,
535
+ destination: WHISPER_SMALL_MODEL_PATH,
536
+ partial: WHISPER_SMALL_MODEL_PARTIAL,
537
+ expectedBytes: WHISPER_SMALL_MODEL_EXPECTED_BYTES,
538
+ timeoutMs: 3_600_000,
539
+ })
515
540
  console.log(green(' ✓') + ' Small.en preview model downloaded ' + dim('— Turbo commit unchanged'))
516
541
  } catch (err) {
517
- try { unlinkSync(WHISPER_SMALL_MODEL_PARTIAL) } catch {}
518
542
  console.log(red(' ✗') + ' Small.en download failed ' + dim('— previews safely fall back to Turbo'))
519
543
  console.log(' ' + dim('Error: ' + (err.message || err).toString().slice(0, 120)))
544
+ console.log(' ' + dim('Partial download retained; rerun setup to resume it.'))
545
+ transcriptionSetupFailures.push('Small.en preview model')
520
546
  }
521
547
  }
522
548
  }
@@ -527,23 +553,25 @@ if (whisperCliPath && SETUP_TRANSCRIPTION) {
527
553
  console.log(green(' ✓') + ' Large-v3 HQ model ready ' + dim('— saved meetings use full polish'))
528
554
  } else {
529
555
  if (existsSync(WHISPER_LARGE_MODEL_PATH)) { try { unlinkSync(WHISPER_LARGE_MODEL_PATH) } catch {} }
530
- if (existsSync(WHISPER_LARGE_MODEL_PARTIAL)) { try { unlinkSync(WHISPER_LARGE_MODEL_PARTIAL) } catch {} }
531
556
  console.log(yellow(' ⚠') + ' HQ polish model missing')
532
557
  console.log(' ' + dim('Downloading ggml-large-v3 (~3.1 GB).'))
533
558
  if (process.env.SKIP_WHISPER_DOWNLOAD === '1') {
534
559
  console.log(yellow(' ⚠') + ' SKIP_WHISPER_DOWNLOAD=1 — HQ safely reports unavailable and live Turbo continues')
535
560
  } else {
536
561
  try {
537
- mkdirSync(WHISPER_MODEL_DIR, { recursive: true })
538
- execFileSync('curl', ['-fL', '--progress-bar', WHISPER_LARGE_MODEL_URL, '-o', WHISPER_LARGE_MODEL_PARTIAL], { stdio: 'inherit', timeout: 1800000 })
539
- const stats = statSync(WHISPER_LARGE_MODEL_PARTIAL)
540
- if (stats.size < WHISPER_LARGE_MODEL_MIN_BYTES) throw new Error(`Downloaded file too small: ${stats.size} bytes`)
541
- renameSync(WHISPER_LARGE_MODEL_PARTIAL, WHISPER_LARGE_MODEL_PATH)
562
+ downloadWhisperModel({
563
+ url: WHISPER_LARGE_MODEL_URL,
564
+ destination: WHISPER_LARGE_MODEL_PATH,
565
+ partial: WHISPER_LARGE_MODEL_PARTIAL,
566
+ expectedBytes: WHISPER_LARGE_MODEL_EXPECTED_BYTES,
567
+ timeoutMs: 7_200_000,
568
+ })
542
569
  console.log(green(' ✓') + ' Large-v3 HQ model downloaded')
543
570
  } catch (err) {
544
- try { unlinkSync(WHISPER_LARGE_MODEL_PARTIAL) } catch {}
545
571
  console.log(red(' ✗') + ' Large-v3 download failed ' + dim('— live Turbo remains available'))
546
572
  console.log(' ' + dim('Error: ' + (err.message || err).toString().slice(0, 120)))
573
+ console.log(' ' + dim('Partial download retained; rerun setup to resume it.'))
574
+ transcriptionSetupFailures.push('Large-v3 HQ model')
547
575
  }
548
576
  }
549
577
  }
@@ -551,6 +579,13 @@ if (whisperCliPath && SETUP_TRANSCRIPTION) {
551
579
 
552
580
  if (PREPARE_ONLY && SETUP_TRANSCRIPTION) {
553
581
  console.log('')
582
+ if (transcriptionSetupFailures.length > 0) {
583
+ console.log(red(' ✗ Adaptive transcription setup incomplete'))
584
+ console.log(' Missing: ' + transcriptionSetupFailures.join(', '))
585
+ console.log(' Rerun this command; retained downloads resume from the last byte received.')
586
+ console.log('')
587
+ process.exit(1)
588
+ }
554
589
  console.log(green(' ✓ Adaptive transcription setup complete'))
555
590
  console.log(' Return to COS Control and Restart, install, or update the managed server.')
556
591
  console.log('')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.20.0",
4
- "description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
3
+ "version": "6.20.1",
4
+ "description": "COS Glasses self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "glasses-server": "bin/cli.cjs",