From b17e8e34438bb9586af2ff5d3721ac2198d77b7f Mon Sep 17 00:00:00 2001 From: Abimael Martell Date: Fri, 17 Apr 2026 07:23:08 -0700 Subject: [PATCH] feat: add pdf-inspector CLI to npm package (#44) * feat: add CLI bin to npm package Installing `firecrawl-pdf-inspector` now provides a `pdf-inspector` CLI command. Co-Authored-By: Claude Opus 4.6 (1M context) * chore: bump npm package version to 0.8.0 Co-Authored-By: Claude Opus 4.6 (1M context) * chore: bump npm package version to 1.0.0 Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- napi/bin/pdf-inspector.mjs | 131 +++++++++++++++++++++++++++++++++++++ napi/package.json | 6 +- 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100755 napi/bin/pdf-inspector.mjs diff --git a/napi/bin/pdf-inspector.mjs b/napi/bin/pdf-inspector.mjs new file mode 100755 index 0000000..e1780a6 --- /dev/null +++ b/napi/bin/pdf-inspector.mjs @@ -0,0 +1,131 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from "fs"; +import { createRequire } from "module"; + +const require = createRequire(import.meta.url); +const { version } = require("../package.json"); + +const HELP = `pdf-inspector v${version} — Fast PDF text extraction to Markdown + +Usage: + pdf-inspector Extract markdown (default) + pdf-inspector detect Classify PDF type + +Options: + --json Output as JSON + --pages Comma-separated page numbers (e.g. 1,3,5) + -o, --output Write output to file instead of stdout + -h, --help Show this help + -v, --version Show version + +Examples: + pdf-inspector document.pdf + pdf-inspector document.pdf --json + pdf-inspector document.pdf --pages 1,2,3 + pdf-inspector detect document.pdf --json + cat document.pdf | pdf-inspector -`; + +function die(msg) { + process.stderr.write(`error: ${msg}\n`); + process.exit(1); +} + +function parseArgs(argv) { + const opts = { json: false, pages: null, output: null, file: null, command: "extract" }; + let i = 0; + + // Check for subcommand + if (argv[0] === "detect") { + opts.command = "detect"; + i = 1; + } + + while (i < argv.length) { + const arg = argv[i]; + if (arg === "-h" || arg === "--help") { + process.stdout.write(HELP + "\n"); + process.exit(0); + } else if (arg === "-v" || arg === "--version") { + process.stdout.write(`${version}\n`); + process.exit(0); + } else if (arg === "--json") { + opts.json = true; + } else if (arg === "--pages") { + i++; + if (!argv[i]) die("--pages requires a value (e.g. 1,3,5)"); + opts.pages = argv[i].split(",").map((p) => { + const n = parseInt(p.trim(), 10); + if (Number.isNaN(n) || n < 1) die(`invalid page number: ${p}`); + return n; + }); + } else if (arg === "-o" || arg === "--output") { + i++; + if (!argv[i]) die("-o requires a filename"); + opts.output = argv[i]; + } else if (arg === "-" || !arg.startsWith("-")) { + if (opts.file) die(`unexpected argument: ${arg}`); + opts.file = arg; + } else { + die(`unknown option: ${arg}`); + } + i++; + } + + return opts; +} + +function readInput(file) { + if (file === "-") { + return readFileSync(0); // stdin fd + } + try { + return readFileSync(file); + } catch (err) { + if (err.code === "ENOENT") die(`file not found: ${file}`); + die(err.message); + } +} + +function output(text, outputPath) { + if (outputPath) { + writeFileSync(outputPath, text); + } else { + process.stdout.write(text); + } +} + +// ---- main ---- + +const opts = parseArgs(process.argv.slice(2)); + +if (!opts.file) { + // Check if stdin is piped + if (process.stdin.isTTY !== false) { + process.stderr.write(HELP + "\n"); + process.exit(1); + } + opts.file = "-"; +} + +const { processPdf, classifyPdf } = await import("../index.js"); +const buffer = readInput(opts.file); + +if (opts.command === "detect") { + const result = classifyPdf(buffer); + if (opts.json) { + output(JSON.stringify(result, null, 2) + "\n", opts.output); + } else { + const ocr = result.pagesNeedingOcr.length > 0 + ? `, ${result.pagesNeedingOcr.length} pages need OCR` + : ""; + output(`${result.pdfType} (${result.pageCount} pages, confidence: ${result.confidence.toFixed(2)}${ocr})\n`, opts.output); + } +} else { + const result = processPdf(buffer, opts.pages ?? undefined); + if (opts.json) { + output(JSON.stringify(result, null, 2) + "\n", opts.output); + } else { + output((result.markdown ?? "") + "\n", opts.output); + } +} diff --git a/napi/package.json b/napi/package.json index 632e0be..1c5bb92 100644 --- a/napi/package.json +++ b/napi/package.json @@ -1,9 +1,12 @@ { "name": "firecrawl-pdf-inspector", - "version": "0.7.4", + "version": "1.0.0", "description": "Fast PDF classification and text extraction. Detect text-based vs scanned PDFs, extract text by region with quality checks. Native Rust performance via napi-rs.", "main": "index.js", "types": "index.d.ts", + "bin": { + "pdf-inspector": "bin/pdf-inspector.mjs" + }, "license": "MIT", "keywords": [ "pdf", @@ -20,6 +23,7 @@ "index.js", "index.d.ts", "*.node", + "bin/", "README.md" ], "repository": {