aiex-cli 0.0.3-beta.2 → 0.0.3-beta.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/dist/cli.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { S as version, T as formatDoctorDiagnosticsJson, _ as createConfig, a as parseJsonSchema, b as name, c as getDefaultAIConfig, d as DEFAULT_MARKITDOWN_CONFIG, f as DEFAULT_MINERU_CONFIG, g as AIConfigSchema, h as PLACEHOLDER_TEXT, i as JsonSchemaDefinitionSchema, l as readAIConfig, m as PLACEHOLDER_SCHEMA, n as createMigrationConfig, o as toSnakeCase, p as DEFAULT_PROMPT_CONFIG, s as generateDrizzleSchema, t as collectDoctorDiagnostics, u as writeAIConfig, v as seedConfig, w as doctorDiagnosticsTableRows, x as package_default, y as description } from "./doctor-collector-Bnkbl48V.mjs";
1
+ import { C as package_default, D as formatDoctorDiagnosticsJson, E as doctorDiagnosticsTableRows, S as name, _ as PLACEHOLDER_TEXT, a as parseJsonSchema, b as seedConfig, c as recognizeImageText, d as readAIConfig, f as writeAIConfig, g as PLACEHOLDER_SCHEMA, h as DEFAULT_PROMPT_CONFIG, i as JsonSchemaDefinitionSchema, l as shouldUseImageOcrFallback, m as DEFAULT_MINERU_CONFIG, n as createMigrationConfig, o as toSnakeCase, p as DEFAULT_MARKITDOWN_CONFIG, s as generateDrizzleSchema, t as collectDoctorDiagnostics, u as getDefaultAIConfig, v as AIConfigSchema, w as version, x as description, y as createConfig } from "./doctor-collector-Cz7mb2L8.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { readFile, writeFile } from "jsonfile";
8
9
  import { ZodError, z } from "zod";
9
- import { fileURLToPath } from "node:url";
10
10
  import { defineCommand, runMain } from "citty";
11
11
  import { consola } from "consola";
12
12
  import updateNotifier from "update-notifier";
@@ -14078,14 +14078,21 @@ async function listSchemas(aiexDir) {
14078
14078
  return [];
14079
14079
  }
14080
14080
  }
14081
- async function readExtractFileInput(filePath, aiConfig) {
14081
+ async function readExtractFileInput(filePath, aiConfig, modelOverride) {
14082
14082
  const stat = fs$1.statSync(filePath);
14083
14083
  if (stat.size > MAX_UPLOAD_SIZE) throw new Error(`File size (${bytesToMB(stat.size).toFixed(1)}MB) exceeds ${MAX_UPLOAD_SIZE_TEXT} limit: ${filePath}`);
14084
14084
  const ext = path.extname(filePath).toLowerCase().replace(".", "");
14085
- if (FILE_PART_EXTENSIONS.has(ext)) return {
14086
- text: "",
14087
- filePath
14088
- };
14085
+ if (FILE_PART_EXTENSIONS.has(ext)) {
14086
+ if (shouldUseImageOcrFallback(aiConfig, modelOverride)) {
14087
+ const result = await recognizeImageText(filePath, aiConfig?.image);
14088
+ consola.info(`Extracted image text via local OCR (confidence: ${(result.confidence * 100).toFixed(1)}%)`);
14089
+ return { text: result.text };
14090
+ }
14091
+ return {
14092
+ text: "",
14093
+ filePath
14094
+ };
14095
+ }
14089
14096
  if (ext === "pdf") {
14090
14097
  const buffer = await fs.readFile(filePath);
14091
14098
  const converter = createPdfConverter(aiConfig?.pdf);
@@ -14203,7 +14210,7 @@ async function processOneFile(aiexDir, config, aiConfig, schemaName, filePath, m
14203
14210
  }
14204
14211
  });
14205
14212
  try {
14206
- const input = await readExtractFileInput(filePath, aiConfig);
14213
+ const input = await readExtractFileInput(filePath, aiConfig, modelOverride);
14207
14214
  const r = await extractSingle(aiexDir, config, aiConfig, schemaName, input.text, input.filePath, modelOverride, {
14208
14215
  quiet: false,
14209
14216
  insert: options?.insert
@@ -14606,7 +14613,7 @@ const extractCommand = defineCommand({
14606
14613
  let text$1 = "";
14607
14614
  let filePath;
14608
14615
  if (args.file) try {
14609
- const input = await readExtractFileInput(args.file, aiConfig);
14616
+ const input = await readExtractFileInput(args.file, aiConfig, modelOverride);
14610
14617
  text$1 = input.text;
14611
14618
  filePath = input.filePath;
14612
14619
  } catch (e) {
@@ -14715,7 +14722,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
14715
14722
  }
14716
14723
  const fp = filePathStr;
14717
14724
  try {
14718
- const input = await readExtractFileInput(fp, aiConfig);
14725
+ const input = await readExtractFileInput(fp, aiConfig, modelOverride);
14719
14726
  return runAuditedSingleExtraction({
14720
14727
  aiexDir,
14721
14728
  config,
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { readFile, writeFile } from "jsonfile";
6
5
  import { fileURLToPath } from "node:url";
6
+ import { readFile, writeFile } from "jsonfile";
7
7
  import Database from "better-sqlite3";
8
8
  import * as esbuild from "esbuild";
9
9
  import lockfile from "proper-lockfile";
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
5
6
  import Conf from "conf";
6
7
  import { readFile, writeFile } from "jsonfile";
7
8
  import { z } from "zod";
@@ -25,6 +26,7 @@ function buildDoctorDiagnostics(input) {
25
26
  os: `${input.osType} ${input.osRelease}`,
26
27
  cwd: input.cwd
27
28
  },
29
+ imageOcr: { ...input.imageOcr },
28
30
  config: {
29
31
  path: input.configPath,
30
32
  keys: [...input.configStoreKeys].sort()
@@ -56,6 +58,13 @@ function doctorDiagnosticsTableRows(d) {
56
58
  rows.push(["aiModels", p.aiModelCount ? p.aiModels.join(", ") : "none"]);
57
59
  rows.push(["aiProvider", p.aiProvider ?? "none"]);
58
60
  rows.push(["aiConnectionOk", p.aiConnectionOk === null ? "not tested" : String(p.aiConnectionOk)]);
61
+ rows.push(["imageOcrPlatform", String(d.imageOcr.platformSupported)]);
62
+ rows.push(["imageOcrDependency", String(d.imageOcr.dependencyLoaded)]);
63
+ rows.push(["imageOcrOk", d.imageOcr.ocrOk === null ? "not tested" : String(d.imageOcr.ocrOk)]);
64
+ if (d.imageOcr.imagePath) rows.push(["imageOcrImage", d.imageOcr.imagePath]);
65
+ if (d.imageOcr.recognizedText) rows.push(["imageOcrText", d.imageOcr.recognizedText]);
66
+ if (typeof d.imageOcr.confidence === "number") rows.push(["imageOcrConfidence", `${(d.imageOcr.confidence * 100).toFixed(1)}%`]);
67
+ if (d.imageOcr.error) rows.push(["imageOcrError", d.imageOcr.error]);
59
68
  rows.push(["hasDatabase", String(p.hasDatabase)]);
60
69
  rows.push(["migrations", String(p.migrationCount)]);
61
70
  for (const err of p.errors) rows.push(["error", err]);
@@ -65,7 +74,7 @@ function doctorDiagnosticsTableRows(d) {
65
74
  //#endregion
66
75
  //#region package.json
67
76
  var name = "aiex-cli";
68
- var version = "0.0.3-beta.2";
77
+ var version = "0.0.3-beta.3";
69
78
  var description = "JSON Schema → SQLite with AI-powered data extraction";
70
79
  var package_default = {
71
80
  name,
@@ -156,6 +165,7 @@ var package_default = {
156
165
  "update-notifier": "catalog:",
157
166
  "zod": "catalog:"
158
167
  },
168
+ optionalDependencies: { "@napi-rs/system-ocr": "catalog:" },
159
169
  devDependencies: {
160
170
  "@antfu/eslint-config": "catalog:cli",
161
171
  "@antfu/ni": "catalog:cli",
@@ -210,6 +220,15 @@ const PromptConfigSchema = z.object({
210
220
  userTemplate: z.string().min(1)
211
221
  });
212
222
  const ExtractionConfigSchema = z.object({ outputDir: z.string().min(1) });
223
+ const ImageOcrConfigSchema = z.object({
224
+ ocrFallback: z.enum([
225
+ "auto",
226
+ "off",
227
+ "local"
228
+ ]).default("auto").optional(),
229
+ ocrLanguages: z.string().min(1).optional(),
230
+ ocrMinConfidence: z.number().min(0).max(1).optional()
231
+ });
213
232
  const ExternalPdfConverterConfigSchema = z.object({
214
233
  command: z.string().min(1),
215
234
  args: z.array(z.string()),
@@ -248,6 +267,7 @@ const AIConfigSchema = z.object({
248
267
  provider: AIProviderConfigSchema,
249
268
  prompt: PromptConfigSchema,
250
269
  extraction: ExtractionConfigSchema,
270
+ image: ImageOcrConfigSchema.optional(),
251
271
  pdf: PdfConfigSchema.optional(),
252
272
  langfuse: LangfuseConfigSchema.optional(),
253
273
  notion: NotionConfigSchema.optional()
@@ -290,6 +310,11 @@ Extraction requirements:
290
310
  {text}`
291
311
  };
292
312
  const DEFAULT_EXTRACTION_CONFIG = { outputDir: ".aiex/extracted" };
313
+ const DEFAULT_IMAGE_OCR_CONFIG = {
314
+ ocrFallback: "auto",
315
+ ocrLanguages: "en-US, zh-Hans",
316
+ ocrMinConfidence: 0
317
+ };
293
318
  const DEFAULT_MINERU_CONFIG = {
294
319
  command: "mineru",
295
320
  args: [
@@ -323,6 +348,7 @@ const DEFAULT_AI_CONFIG = {
323
348
  provider: DEFAULT_PROVIDER_CONFIG,
324
349
  prompt: DEFAULT_PROMPT_CONFIG,
325
350
  extraction: DEFAULT_EXTRACTION_CONFIG,
351
+ image: DEFAULT_IMAGE_OCR_CONFIG,
326
352
  pdf: DEFAULT_PDF_CONFIG
327
353
  };
328
354
 
@@ -364,6 +390,111 @@ async function addToGitignore(aiexDir, fileName) {
364
390
  }
365
391
  }
366
392
 
393
+ //#endregion
394
+ //#region src/core/image-ocr/index.ts
395
+ const DEFAULT_OCR_LANGUAGES = "en-US, zh-Hans";
396
+ const SELF_CHECK_EXPECTED_TEXT = "AIEX";
397
+ const defaultRuntime = {
398
+ platform: process.platform,
399
+ async loadLocalOcr() {
400
+ return await import("@napi-rs/system-ocr");
401
+ }
402
+ };
403
+ function imageOcrMode(config) {
404
+ return config?.ocrFallback ?? "auto";
405
+ }
406
+ function hasVisionModel(aiConfig, modelOverride) {
407
+ if (modelOverride) return modelOverride.capabilities.vision;
408
+ return aiConfig?.provider.models.some((model) => model.capabilities.vision) ?? true;
409
+ }
410
+ function shouldUseImageOcrFallback(aiConfig, modelOverride, runtime = defaultRuntime) {
411
+ if (hasVisionModel(aiConfig, modelOverride)) return false;
412
+ const mode = imageOcrMode(aiConfig?.image);
413
+ if (mode === "off") return false;
414
+ if (mode === "local") return true;
415
+ return isLocalOcrPlatform(runtime.platform);
416
+ }
417
+ function isLocalOcrPlatform(platform) {
418
+ return platform === "darwin" || platform === "win32";
419
+ }
420
+ function parseOcrLanguages(languages) {
421
+ return (languages ?? DEFAULT_OCR_LANGUAGES).split(",").map((language) => language.trim()).filter(Boolean);
422
+ }
423
+ async function recognizeImageText(imagePath, config, runtime = defaultRuntime) {
424
+ const mode = imageOcrMode(config);
425
+ if (!isLocalOcrPlatform(runtime.platform)) throw new Error(`Local OCR is only available on macOS or Windows. Current platform: ${runtime.platform}.`);
426
+ if (mode === "off") throw new Error("Image OCR fallback is disabled in AI settings.");
427
+ let localOcr;
428
+ try {
429
+ localOcr = await runtime.loadLocalOcr();
430
+ } catch (error) {
431
+ const message = error instanceof Error ? error.message : String(error);
432
+ throw new Error(`Local OCR is unavailable. Install optional dependency @napi-rs/system-ocr and approve its native build scripts. ${message}`);
433
+ }
434
+ const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, parseOcrLanguages(config?.ocrLanguages));
435
+ const text = result.text.trim();
436
+ if (!text) throw new Error("Local OCR did not recognize any text in the image.");
437
+ const confidence = result.confidence;
438
+ const minConfidence = config?.ocrMinConfidence ?? 0;
439
+ if (confidence < minConfidence) throw new Error(`Local OCR confidence ${(confidence * 100).toFixed(1)}% is below the configured minimum ${(minConfidence * 100).toFixed(1)}%.`);
440
+ return {
441
+ text,
442
+ confidence
443
+ };
444
+ }
445
+ function normalizeOcrText(text) {
446
+ return text.replace(/\s+/g, "").trim().toUpperCase();
447
+ }
448
+ async function checkImageOcrAvailability(imagePath, runtime = defaultRuntime) {
449
+ if (!isLocalOcrPlatform(runtime.platform)) return {
450
+ platformSupported: false,
451
+ dependencyLoaded: false,
452
+ ocrOk: null,
453
+ imagePath,
454
+ error: `Local OCR is only available on macOS or Windows. Current platform: ${runtime.platform}.`
455
+ };
456
+ let localOcr;
457
+ try {
458
+ localOcr = await runtime.loadLocalOcr();
459
+ } catch (error) {
460
+ return {
461
+ platformSupported: true,
462
+ dependencyLoaded: false,
463
+ ocrOk: null,
464
+ imagePath,
465
+ error: error instanceof Error ? error.message : String(error)
466
+ };
467
+ }
468
+ if (!imagePath) return {
469
+ platformSupported: true,
470
+ dependencyLoaded: true,
471
+ ocrOk: null,
472
+ error: "No OCR self-check image was found."
473
+ };
474
+ try {
475
+ const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, ["en-US"]);
476
+ const recognizedText = result.text.trim();
477
+ const ocrOk = normalizeOcrText(recognizedText).includes(SELF_CHECK_EXPECTED_TEXT);
478
+ return {
479
+ platformSupported: true,
480
+ dependencyLoaded: true,
481
+ ocrOk,
482
+ imagePath,
483
+ recognizedText,
484
+ confidence: result.confidence,
485
+ error: ocrOk ? void 0 : `Expected OCR text "${SELF_CHECK_EXPECTED_TEXT}" was not recognized.`
486
+ };
487
+ } catch (error) {
488
+ return {
489
+ platformSupported: true,
490
+ dependencyLoaded: true,
491
+ ocrOk: false,
492
+ imagePath,
493
+ error: error instanceof Error ? error.message : String(error)
494
+ };
495
+ }
496
+ }
497
+
367
498
  //#endregion
368
499
  //#region src/core/schema-sqlite/generator.ts
369
500
  function generateColumnDefinition(column) {
@@ -715,6 +846,7 @@ function generateDrizzleConfig() {
715
846
  //#endregion
716
847
  //#region src/core/doctor-collector.ts
717
848
  const V1_SUFFIX_RE = /\/v1\/?$/;
849
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
718
850
  async function checkConnection(baseURL) {
719
851
  try {
720
852
  const base = baseURL.replace(V1_SUFFIX_RE, "");
@@ -723,10 +855,26 @@ async function checkConnection(baseURL) {
723
855
  return false;
724
856
  }
725
857
  }
858
+ async function findImageOcrSelfCheckLogo() {
859
+ const candidates = [
860
+ path.resolve(MODULE_DIR, "logo.png"),
861
+ path.resolve(MODULE_DIR, "assets/logo.png"),
862
+ path.resolve(MODULE_DIR, "../../assets/logo.png"),
863
+ path.resolve(MODULE_DIR, "../../../web/public/logo.png"),
864
+ path.resolve(MODULE_DIR, "../../web/public/logo.png"),
865
+ path.resolve(MODULE_DIR, "../../dist/web/logo.png"),
866
+ path.resolve(MODULE_DIR, "web/logo.png")
867
+ ];
868
+ for (const candidate of candidates) try {
869
+ await fs.access(candidate);
870
+ return candidate;
871
+ } catch {}
872
+ }
726
873
  async function collectDoctorDiagnostics(options = {}) {
727
874
  const config = options.config ?? createConfig();
728
875
  const cwd = process.cwd();
729
876
  const errors = [];
877
+ const imageOcrLogoPath = await findImageOcrSelfCheckLogo();
730
878
  const migConfig = createMigrationConfig(cwd);
731
879
  const aiexDir = path.dirname(migConfig.schemaPath);
732
880
  const dirExists = await fs.stat(aiexDir).then((s) => s.isDirectory()).catch(() => false);
@@ -778,6 +926,7 @@ async function collectDoctorDiagnostics(options = {}) {
778
926
  osType: os.type(),
779
927
  osRelease: os.release(),
780
928
  cwd,
929
+ imageOcr: await checkImageOcrAvailability(imageOcrLogoPath),
781
930
  configPath: config.path,
782
931
  configStoreKeys: Object.keys(config.store),
783
932
  project: {
@@ -799,4 +948,4 @@ async function collectDoctorDiagnostics(options = {}) {
799
948
  }
800
949
 
801
950
  //#endregion
802
- export { buildDoctorDiagnostics as C, version as S, formatDoctorDiagnosticsJson as T, createConfig as _, parseJsonSchema as a, name as b, getDefaultAIConfig as c, DEFAULT_MARKITDOWN_CONFIG as d, DEFAULT_MINERU_CONFIG as f, AIConfigSchema as g, PLACEHOLDER_TEXT as h, JsonSchemaDefinitionSchema as i, readAIConfig as l, PLACEHOLDER_SCHEMA as m, createMigrationConfig as n, toSnakeCase as o, DEFAULT_PROMPT_CONFIG as p, generateDrizzleConfig as r, generateDrizzleSchema as s, collectDoctorDiagnostics as t, writeAIConfig as u, seedConfig as v, doctorDiagnosticsTableRows as w, package_default as x, description as y };
951
+ export { package_default as C, formatDoctorDiagnosticsJson as D, doctorDiagnosticsTableRows as E, name as S, buildDoctorDiagnostics as T, PLACEHOLDER_TEXT as _, parseJsonSchema as a, seedConfig as b, recognizeImageText as c, readAIConfig as d, writeAIConfig as f, PLACEHOLDER_SCHEMA as g, DEFAULT_PROMPT_CONFIG as h, JsonSchemaDefinitionSchema as i, shouldUseImageOcrFallback as l, DEFAULT_MINERU_CONFIG as m, createMigrationConfig as n, toSnakeCase as o, DEFAULT_MARKITDOWN_CONFIG as p, generateDrizzleConfig as r, generateDrizzleSchema as s, collectDoctorDiagnostics as t, getDefaultAIConfig as u, AIConfigSchema as v, version as w, description as x, createConfig as y };
package/dist/index.d.mts CHANGED
@@ -19,6 +19,15 @@ interface DoctorDiagnostics {
19
19
  os: string;
20
20
  cwd: string;
21
21
  };
22
+ imageOcr: {
23
+ platformSupported: boolean;
24
+ dependencyLoaded: boolean;
25
+ ocrOk: boolean | null;
26
+ imagePath?: string;
27
+ recognizedText?: string;
28
+ confidence?: number;
29
+ error?: string;
30
+ };
22
31
  config: {
23
32
  path: string;
24
33
  keys: string[];
@@ -53,6 +62,7 @@ declare function buildDoctorDiagnostics(input: {
53
62
  osType: string;
54
63
  osRelease: string;
55
64
  cwd: string;
65
+ imageOcr: DoctorDiagnostics['imageOcr'];
56
66
  configPath: string;
57
67
  configStoreKeys: string[];
58
68
  project: DoctorDiagnostics['project'];
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { C as buildDoctorDiagnostics, T as formatDoctorDiagnosticsJson, a as parseJsonSchema, i as JsonSchemaDefinitionSchema, n as createMigrationConfig, r as generateDrizzleConfig, s as generateDrizzleSchema, t as collectDoctorDiagnostics, w as doctorDiagnosticsTableRows } from "./doctor-collector-Bnkbl48V.mjs";
1
+ import { D as formatDoctorDiagnosticsJson, E as doctorDiagnosticsTableRows, T as buildDoctorDiagnostics, a as parseJsonSchema, i as JsonSchemaDefinitionSchema, n as createMigrationConfig, r as generateDrizzleConfig, s as generateDrizzleSchema, t as collectDoctorDiagnostics } from "./doctor-collector-Cz7mb2L8.mjs";
2
2
 
3
3
  export { JsonSchemaDefinitionSchema, buildDoctorDiagnostics, collectDoctorDiagnostics, createMigrationConfig, doctorDiagnosticsTableRows, formatDoctorDiagnosticsJson, generateDrizzleConfig, generateDrizzleSchema, parseJsonSchema };
package/dist/logo.png ADDED
Binary file