@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 +7 -7
- package/src/cli.cjs +214 -2
- package/src/index.cjs +1 -1
- package/src/metadata.cjs +1 -1
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.
|
|
32
|
-
"@arcships/light-ocr-darwin-x64": "0.
|
|
33
|
-
"@arcships/light-ocr-linux-arm64-gnu": "0.
|
|
34
|
-
"@arcships/light-ocr-linux-x64-gnu": "0.
|
|
35
|
-
"@arcships/light-ocr-win32-arm64": "0.
|
|
36
|
-
"@arcships/light-ocr-win32-x64": "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.
|
|
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
package/src/metadata.cjs
CHANGED