@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.
- package/CHANGELOG.md +14 -0
- package/bin/cli.cjs +59 -24
- 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 =
|
|
397
|
-
const WHISPER_SMALL_MODEL_EXPECTED_BYTES =
|
|
398
|
-
const WHISPER_LARGE_MODEL_EXPECTED_BYTES =
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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.
|
|
4
|
-
"description": "COS Glasses
|
|
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",
|