@arcships/light-ocr-runtime 0.1.0 → 0.1.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/package.json CHANGED
@@ -28,12 +28,12 @@
28
28
  "module": "./src/index.mjs",
29
29
  "name": "@arcships/light-ocr-runtime",
30
30
  "optionalDependencies": {
31
- "@arcships/light-ocr-darwin-arm64": "0.4.0",
32
- "@arcships/light-ocr-darwin-x64": "0.4.0",
33
- "@arcships/light-ocr-linux-arm64-gnu": "0.4.0",
34
- "@arcships/light-ocr-linux-x64-gnu": "0.4.0",
35
- "@arcships/light-ocr-win32-arm64": "0.4.0",
36
- "@arcships/light-ocr-win32-x64": "0.4.0"
31
+ "@arcships/light-ocr-darwin-arm64": "0.5.3",
32
+ "@arcships/light-ocr-darwin-x64": "0.5.3",
33
+ "@arcships/light-ocr-linux-arm64-gnu": "0.5.3",
34
+ "@arcships/light-ocr-linux-x64-gnu": "0.5.3",
35
+ "@arcships/light-ocr-win32-arm64": "0.5.3",
36
+ "@arcships/light-ocr-win32-x64": "0.5.3"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public",
@@ -45,5 +45,5 @@
45
45
  },
46
46
  "type": "commonjs",
47
47
  "types": "./src/index.d.ts",
48
- "version": "0.1.0"
48
+ "version": "0.1.3"
49
49
  }
package/src/cli.cjs CHANGED
@@ -12,12 +12,14 @@
12
12
  // stdout = machine results only; stderr = logs/warnings/usage (cli-design.md §5).
13
13
  // Exit codes are a stable surface (cli-design.md §10, D106).
14
14
 
15
+ const crypto = require('node:crypto');
15
16
  const fs = require('node:fs');
17
+ const os = require('node:os');
16
18
  const path = require('node:path');
17
19
 
18
20
  const { parseExifOrientation } = require('./exif.cjs');
19
21
 
20
- const SUBCOMMANDS = new Set(['recognize', 'detect', 'info']);
22
+ const SUBCOMMANDS = new Set(['recognize', 'detect', 'info', 'document', 'doctor']);
21
23
  const EXIT = {
22
24
  success: 0,
23
25
  usage: 64,
@@ -88,7 +90,7 @@ function parseArgs(argv) {
88
90
  const name = arg.slice(2);
89
91
  const knownBooleans = new Set([
90
92
  'stdin', 'no-exif', 'no-color', 'quiet', 'help',
91
- 'model-info', 'version', 'crop',
93
+ 'model-info', 'version', 'crop', 'json',
92
94
  ]);
93
95
  if (knownBooleans.has(name)) {
94
96
  flags[name] = true;
@@ -470,6 +472,188 @@ function writeResult(envelope, format, stdout, subcommand) {
470
472
  stdout.write(JSON.stringify(envelope, null, 2) + '\n');
471
473
  }
472
474
 
475
+ // --- doctor subcommand (system diagnostics, no user content) ---
476
+ function safeRequireResolve(spec) {
477
+ try { return require.resolve(spec); } catch { return undefined; }
478
+ }
479
+
480
+ async function loadGpuInfo(config) {
481
+ try {
482
+ const engine = await config.createEngine();
483
+ try {
484
+ return { ...engine.info };
485
+ } finally {
486
+ await engine.close();
487
+ }
488
+ } catch {
489
+ return undefined;
490
+ }
491
+ }
492
+
493
+ async function runDoctor(rest, flags, stdout, stderr, config) {
494
+ if (rest.length > 0) {
495
+ throw { code: EXIT.invalid_argument, message: `doctor does not accept arguments: ${rest[0]}` };
496
+ }
497
+ for (const blocked of ['stdin', 'type', 'format', 'region', 'no-exif', 'provider', 'crop',
498
+ 'model-info', 'version']) {
499
+ if (flags[blocked] !== undefined) {
500
+ throw { code: EXIT.invalid_argument, message: `doctor does not accept --${blocked}` };
501
+ }
502
+ }
503
+
504
+ const info = {
505
+ schemaVersion: 1,
506
+ tool: {
507
+ command: config.commandName,
508
+ version: config.packageVersion,
509
+ coreVersion: config.coreVersion,
510
+ },
511
+ model: { ...config.modelProfile },
512
+ system: {
513
+ node: process.version,
514
+ platform: process.platform,
515
+ arch: process.arch,
516
+ release: os.release(),
517
+ hostHash: crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 16),
518
+ cpuModel: os.cpus()[0]?.model || 'unknown',
519
+ cpuCores: os.cpus().length,
520
+ totalMemoryGB: +(os.totalmem() / (1024 ** 3)).toFixed(1),
521
+ },
522
+ native: { status: 'unavailable' },
523
+ modules: {
524
+ runtime: safeRequireResolve('@arcships/light-ocr-runtime') !== undefined,
525
+ model: safeRequireResolve(config.modelProfile.model ? `@arcships/light-ocr-model-${config.modelProfile.model}` : '@arcships/light-ocr-model-ppocrv6-small') !== undefined,
526
+ pdfium: false,
527
+ },
528
+ };
529
+
530
+ // Native runtime details
531
+ if (typeof config.loadNative === 'function') {
532
+ try {
533
+ const native = config.loadNative();
534
+ info.native = {
535
+ status: 'ok',
536
+ availableProviders: native.runtimePolicy.availableProviders,
537
+ runtimeFlavor: native.runtimePolicy.runtimeFlavor,
538
+ runtimeVersion: native.runtimePolicy.runtimeVersion,
539
+ platformId: native.runtimePolicy.platformId,
540
+ qualificationOnly: native.runtimePolicy.qualificationOnly,
541
+ released: native.runtimePolicy.released,
542
+ descriptorPath: native.descriptorPath,
543
+ };
544
+ } catch (error) {
545
+ info.native = { status: 'error', message: error.message || String(error) };
546
+ }
547
+ }
548
+
549
+ // GPU/engine info (requires engine creation — slow, only when available)
550
+ if (flags.gpu !== true) {
551
+ // skip GPU probe unless explicitly requested
552
+ } else {
553
+ const gpuInfo = await loadGpuInfo(config);
554
+ if (gpuInfo) info.gpu = gpuInfo;
555
+ }
556
+
557
+ // PDF support
558
+ if (typeof config.hasPdfSupport === 'function') {
559
+ info.modules.pdfium = config.hasPdfSupport();
560
+ }
561
+
562
+ stdout.write(JSON.stringify(info, null, 2) + '\n');
563
+ }
564
+
565
+ // --- document subcommand (PDF and multi-page support) ---
566
+ function parsePageRange(rangeStr) {
567
+ if (!rangeStr) return undefined;
568
+ const match = String(rangeStr).match(/^(\d+)(?:-(\d+))?$/);
569
+ if (!match) {
570
+ throw { code: EXIT.invalid_argument, message: `--pages expects N or N-M (got ${rangeStr})` };
571
+ }
572
+ return {
573
+ start: parseInt(match[1]),
574
+ end: match[2] ? parseInt(match[2]) : parseInt(match[1])
575
+ };
576
+ }
577
+
578
+ async function runDocument(rest, flags, stdout, stderr, config) {
579
+ const format = resolveFormat(flags, 'recognize');
580
+ const provider = resolveProvider(flags);
581
+
582
+ if (rest.length === 0) {
583
+ throw { code: EXIT.usage, message: 'expected a PDF or image file path' };
584
+ }
585
+
586
+ // Parse document-specific flags
587
+ const pageRange = parsePageRange(flags.pages);
588
+ const dpi = flags.dpi ? parseInt(flags.dpi) : 150;
589
+ const maxPages = flags['max-pages'] ? parseInt(flags['max-pages']) : 100;
590
+ const quiet = flags.quiet === true;
591
+
592
+ // Check if recognizeDocument is available
593
+ if (typeof config.recognizeDocument !== 'function') {
594
+ throw { code: EXIT.unsupported_capability, message: 'PDF/document support not available' };
595
+ }
596
+
597
+ const source = rest.length === 1 ? rest[0] : rest;
598
+
599
+ const pages = [];
600
+ let pageCount = 0;
601
+
602
+ try {
603
+ for await (const page of config.recognizeDocument(source, {
604
+ pageRange,
605
+ dpi,
606
+ maxPages,
607
+ engine: undefined // Will use default
608
+ })) {
609
+ pages.push(page);
610
+ pageCount++;
611
+
612
+ // Output JSONL as we go
613
+ if (format === 'jsonl') {
614
+ stdout.write(JSON.stringify({
615
+ schemaVersion: SUPPORTED_SCHEMA_VERSION,
616
+ source: { kind: page.source.kind },
617
+ pageIndex: page.index,
618
+ status: 'ok',
619
+ page
620
+ }) + '\n');
621
+ }
622
+
623
+ // Progress output
624
+ if (!quiet) {
625
+ stderr.write(`\rProcessed page ${pageCount}...`);
626
+ }
627
+ }
628
+
629
+ if (!quiet && pageCount > 0) {
630
+ stderr.write('\n');
631
+ }
632
+
633
+ // Output final result for non-JSONL formats
634
+ if (format === 'json') {
635
+ const result = {
636
+ schemaVersion: SUPPORTED_SCHEMA_VERSION,
637
+ source: {
638
+ kind: Array.isArray(source) ? 'page-images' :
639
+ (typeof source === 'string' && source.endsWith('.pdf') ? 'pdf' : 'image'),
640
+ mediaType: typeof source === 'string' && source.endsWith('.pdf') ? 'application/pdf' : 'image/*',
641
+ identity: { files: Array.isArray(source) ? source : [source] },
642
+ pageCount: pages.length
643
+ },
644
+ pages
645
+ };
646
+ stdout.write(JSON.stringify(result, null, 2) + '\n');
647
+ } else if (format === 'text') {
648
+ for (const page of pages) {
649
+ stdout.write(page.lines.map(l => l.text).join('\n') + '\n');
650
+ }
651
+ }
652
+ } catch (e) {
653
+ throw e;
654
+ }
655
+ }
656
+
473
657
  // --- help ---
474
658
  function printHelp(stdout, verbose, config) {
475
659
  const command = config.commandName;
@@ -477,7 +661,9 @@ function printHelp(stdout, verbose, config) {
477
661
  stdout.write('Usage:\n');
478
662
  stdout.write(` ${command} recognize <path|--stdin> [flags] Recognize text in an image (default)\n`);
479
663
  stdout.write(` ${command} detect <path|--stdin> [flags] Detect text regions only\n`);
664
+ stdout.write(` ${command} document <path|paths...> [flags] Process PDF or multiple images\n`);
480
665
  stdout.write(` ${command} info --model-info | --version Show engine/version info\n`);
666
+ stdout.write(` ${command} doctor [--json] System diagnostics\n`);
481
667
  stdout.write(` ${command} <image> [flags] Implicit recognize\n\n`);
482
668
  stdout.write(`Run \`${command} <subcommand> --help\` for flags of that subcommand.\n`);
483
669
  }
@@ -518,6 +704,27 @@ function printSubcommandHelp(stdout, subcommand, config) {
518
704
  stdout.write(' --version Print npm/core/model version triple\n');
519
705
  return;
520
706
  }
707
+ if (subcommand === 'document') {
708
+ stdout.write(`${command} document — process PDF or multiple images\n\n`);
709
+ stdout.write(`Usage:\n ${command} document <file.pdf> [flags]\n ${command} document <img1.png> <img2.jpg> [flags]\n\n`);
710
+ stdout.write('Flags:\n');
711
+ stdout.write(' --format json|jsonl|text Output format (default: json)\n');
712
+ stdout.write(' --pages N-M Page range for PDF (e.g., 1-5 or 3)\n');
713
+ stdout.write(' --dpi <n> PDF raster DPI (default: 150)\n');
714
+ stdout.write(' --max-pages <n> Maximum pages to process (default: 100)\n');
715
+ stdout.write(' --provider auto|cpu|apple|webgpu Execution provider (default: auto)\n');
716
+ stdout.write(' --quiet Suppress progress output\n');
717
+ return;
718
+ }
719
+ if (subcommand === 'doctor') {
720
+ stdout.write(`${command} doctor — system diagnostics for troubleshooting\n\n`);
721
+ stdout.write(`Usage:\n ${command} doctor [--json]\n\n`);
722
+ stdout.write('Collects hardware and runtime information (no user content).\n');
723
+ stdout.write('Output is always JSON. Use --json for explicit intent.\n\n');
724
+ stdout.write('Flags:\n');
725
+ stdout.write(' --json Explicit JSON output flag (default behavior)\n');
726
+ return;
727
+ }
521
728
  printHelp(stdout, false, config);
522
729
  }
523
730
 
@@ -548,6 +755,8 @@ async function main(argv, config) {
548
755
  try {
549
756
  if (subcommand === 'info') {
550
757
  await runInfo(rest, parsed.flags, stdout, stderr, config);
758
+ } else if (subcommand === 'doctor') {
759
+ await runDoctor(rest, parsed.flags, stdout, stderr, config);
551
760
  } else if (subcommand === 'recognize') {
552
761
  await runRecognize(rest, parsed.flags, stdout, stderr, config);
553
762
  } else if (subcommand === 'detect') {
@@ -563,6 +772,8 @@ async function main(argv, config) {
563
772
  };
564
773
  }
565
774
  await runDetect(rest, parsed.flags, stdout, stderr, config);
775
+ } else if (subcommand === 'document') {
776
+ await runDocument(rest, parsed.flags, stdout, stderr, config);
566
777
  } else {
567
778
  die(stderr, config.commandName, `unknown subcommand: ${subcommand}`);
568
779
  return EXIT.usage;
@@ -600,6 +811,7 @@ function createCli(config) {
600
811
  resolveSchemaVersion,
601
812
  inferMediaType,
602
813
  parseRegion,
814
+ runDoctor: (rest, flags, stdout, stderr) => runDoctor(rest, flags, stdout, stderr, frozenConfig),
603
815
  });
604
816
  }
605
817
 
package/src/index.cjs CHANGED
@@ -174,4 +174,4 @@ async function createEngine(options) {
174
174
  }
175
175
  }
176
176
 
177
- module.exports = { createEngine, OcrError };
177
+ module.exports = { createEngine, OcrError, loadNative };
package/src/metadata.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  'use strict';
2
2
 
3
- module.exports = Object.freeze({ coreVersion: '0.4.0' });
3
+ module.exports = Object.freeze({ coreVersion: '0.5.3' });