aiex-cli 0.0.0 → 0.0.1-beta.2

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/README.md CHANGED
@@ -21,9 +21,10 @@ npm install -g aiex-cli
21
21
  ```
22
22
 
23
23
  ```bash
24
- cd my-project && aiex schema --init # set up your schema directory
25
- aiex schema "*.json" # generate SQLite from JSON Schema
26
- aiex extract -s invoice -f invoice.pdf # extract data with AI
24
+ aiex schema --init # set up .aiex/schema/ directory
25
+ aiex schema # generate SQLite from JSON Schema files
26
+ aiex extract -s invoice -f invoice.pdf # extract data with AI
27
+ aiex extract -s invoice -f invoice.pdf --db # extract and insert into database
27
28
  ```
28
29
 
29
30
  <br>
@@ -47,6 +48,8 @@ aiex schema --init
47
48
 
48
49
  Creates a `.aiex/` directory with example schemas to get you started.
49
50
 
51
+ Add your own JSON Schema files to `.aiex/schema/` (one file per table), then run `aiex schema` to migrate them into the database.
52
+
50
53
  ### 2. Visual Editor
51
54
 
52
55
  ```bash
@@ -58,7 +61,7 @@ Opens a browser UI where you can visually design and manage your schemas, config
58
61
  ### 3. Generate Database
59
62
 
60
63
  ```bash
61
- aiex schema "*.json"
64
+ aiex schema
62
65
  ```
63
66
 
64
67
  Converts your JSON Schema files into a SQLite database with full migration support.
@@ -72,11 +75,12 @@ aiex extract -s <schema> -t <text> # from text
72
75
 
73
76
  The AI reads your document and outputs structured JSON matching your schema.
74
77
 
75
- **Example:**
78
+ **Examples:**
76
79
  ```bash
77
- aiex extract -s paper -f research.pdf
80
+ aiex extract -s paper -f research.pdf # save result to .aiex/extracted/
81
+ aiex extract -s paper -f research.pdf --db # also insert into SQLite database
78
82
  ```
79
- Creates `output.json` with fields like `title`, `firstAuthor`, `journal`, `year` — exactly as defined in your schema.
83
+ Saves the extracted result to `.aiex/extracted/<schema-name>-<timestamp>.json` with fields like `title`, `firstAuthor`, `journal`, `year` — exactly as defined in your schema. Add `--db` to also insert the data directly into the SQLite database.
80
84
 
81
85
  <br>
82
86
 
@@ -85,10 +89,11 @@ Creates `output.json` with fields like `title`, `firstAuthor`, `journal`, `year`
85
89
  | Command | Description |
86
90
  | --- | --- |
87
91
  | `aiex schema --init` | Scaffold `.aiex/` directory with example schemas |
88
- | `aiex schema <files>` | Parse JSON Schema files and migrate to SQLite |
92
+ | `aiex schema` | Parse JSON Schema files and migrate to SQLite |
89
93
  | `aiex schema --generate` | Generate Drizzle schema code only (skip migration) |
90
94
  | `aiex web` | Launch visual schema editor in browser |
91
- | `aiex extract -s <name>` | Extract structured data from documents via AI |
95
+ | `aiex extract -s <name> -f <file>` | Extract structured data from documents via AI |
96
+ | `aiex extract -s <name> -f <file> --db` | Extract and insert into SQLite database |
92
97
  | `aiex doctor` | System and configuration diagnostics |
93
98
 
94
99
  <br>
package/dist/cli.mjs CHANGED
@@ -1,15 +1,16 @@
1
- import { C as formatDoctorDiagnosticsJson, S as doctorDiagnosticsTableRows, a as writeAIConfig, b as generateDrizzleSchema, c as PLACEHOLDER_TEXT, d as seedConfig, f as description$1, g as createMigrationConfig, h as version, i as readAIConfig, l as AIConfigSchema, m as package_default, n as getDefaultAIConfig, o as DEFAULT_PROMPT_CONFIG, p as name, r as maskApiKey, s as PLACEHOLDER_SCHEMA, t as collectDoctorDiagnostics, u as createConfig, v as JsonSchemaDefinitionSchema, y as parseJsonSchema } from "./doctor-Wic10PLt.mjs";
1
+ import { C as doctorDiagnosticsTableRows, a as writeAIConfig, b as toSnakeCase, c as PLACEHOLDER_TEXT, d as seedConfig, f as description$1, g as createMigrationConfig, h as version, i as readAIConfig, l as AIConfigSchema, m as package_default, n as getDefaultAIConfig, o as DEFAULT_PROMPT_CONFIG, p as name, r as maskApiKey, s as PLACEHOLDER_SCHEMA, t as collectDoctorDiagnostics, u as createConfig, v as JsonSchemaDefinitionSchema, w as formatDoctorDiagnosticsJson, x as generateDrizzleSchema, y as parseJsonSchema } from "./doctor-Ddn9BjxC.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { ZodError } from "zod";
7
- import fs, { glob } from "node:fs/promises";
7
+ import fs from "node:fs/promises";
8
8
  import { defineCommand, runMain } from "citty";
9
9
  import updateNotifier from "update-notifier";
10
10
  import CliTable3 from "cli-table3";
11
11
  import { consola } from "consola";
12
12
  import { intro, outro, spinner } from "@clack/prompts";
13
+ import Database from "better-sqlite3";
13
14
  import pc from "picocolors";
14
15
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
15
16
  import { Output, generateText, jsonSchema } from "ai";
@@ -9174,6 +9175,143 @@ async function extractStructuredData(input) {
9174
9175
  }
9175
9176
  }
9176
9177
 
9178
+ //#endregion
9179
+ //#region src/core/ai-extraction/inserter.ts
9180
+ const DRIZZLE_MODE_RE = /mode:\s*'(\w+)'/;
9181
+ function extractDrizzleMode(column) {
9182
+ return column.drizzleType.match(DRIZZLE_MODE_RE)?.[1];
9183
+ }
9184
+ function convertValue(value, column) {
9185
+ if (value === null || value === void 0) return null;
9186
+ const mode = extractDrizzleMode(column);
9187
+ if (mode === "json") return typeof value === "string" ? value : JSON.stringify(value);
9188
+ if (mode === "boolean") return value ? 1 : 0;
9189
+ if (mode === "timestamp" || mode === "timestamp_ms") {
9190
+ if (typeof value === "string") {
9191
+ const ms = Date.parse(value);
9192
+ if (Number.isNaN(ms)) return value;
9193
+ return mode === "timestamp_ms" ? ms : Math.floor(ms / 1e3);
9194
+ }
9195
+ return value;
9196
+ }
9197
+ return value;
9198
+ }
9199
+ function buildInsertSql(table, data) {
9200
+ const columns = [];
9201
+ const values = [];
9202
+ for (const col of table.columns) {
9203
+ if (col.isAutoIncrement) continue;
9204
+ const value = data[col.name];
9205
+ if (value === void 0) {
9206
+ if (col.defaultValue !== void 0) {
9207
+ columns.push(col.name);
9208
+ values.push(convertValue(JSON.parse(col.defaultValue), col));
9209
+ }
9210
+ continue;
9211
+ }
9212
+ columns.push(col.name);
9213
+ values.push(convertValue(value, col));
9214
+ }
9215
+ const placeholders = values.map(() => "?").join(", ");
9216
+ return {
9217
+ sql: `INSERT INTO ${table.name} (${columns.join(", ")}) VALUES (${placeholders})`,
9218
+ values
9219
+ };
9220
+ }
9221
+ function insertTableRow({ db, table, data, parentRowId, foreignKeyColumn }) {
9222
+ const rowData = { ...data };
9223
+ if (parentRowId !== void 0 && foreignKeyColumn) rowData[foreignKeyColumn] = parentRowId;
9224
+ const { sql, values } = buildInsertSql(table, rowData);
9225
+ const info = db.prepare(sql).run(...values);
9226
+ return Number(info.lastInsertRowid);
9227
+ }
9228
+ function parseDataByColumns(data, schema, table) {
9229
+ const result = {};
9230
+ if ("properties" in schema) {
9231
+ const s = schema;
9232
+ for (const [propName, prop] of Object.entries(s.properties)) {
9233
+ if (prop.nested?.enabled) continue;
9234
+ if (prop.type === "array" && prop.items?.nested?.enabled) continue;
9235
+ const colName = toSnakeCase(propName);
9236
+ if (table.columns.some((c) => c.name === colName && c.isAutoIncrement)) continue;
9237
+ if (propName in data) result[colName] = data[propName];
9238
+ }
9239
+ }
9240
+ if (schema.table?.timestamps) {
9241
+ if (!("created_at" in result)) result.created_at = Math.floor(Date.now() / 1e3);
9242
+ if (!("updated_at" in result)) result.updated_at = Math.floor(Date.now() / 1e3);
9243
+ }
9244
+ return result;
9245
+ }
9246
+ function insertExtractedData(db, schema, data) {
9247
+ const inserted = [];
9248
+ try {
9249
+ const parseResult = parseJsonSchema(schema);
9250
+ const mainTable = parseResult.tables[0];
9251
+ db.transaction(() => {
9252
+ const mainRowId = insertTableRow({
9253
+ db,
9254
+ table: mainTable,
9255
+ data: parseDataByColumns(data, schema, mainTable)
9256
+ });
9257
+ inserted.push({
9258
+ table: mainTable.name,
9259
+ rowId: mainRowId
9260
+ });
9261
+ for (const revRel of parseResult.reverseRelations) {
9262
+ const rel = parseResult.relations.find((r) => r.fromTable === revRel.toTable && r.toTable === revRel.fromTable);
9263
+ if (!rel) continue;
9264
+ const propEntry = Object.entries(schema.properties).find(([key]) => toSnakeCase(key) === revRel.name && key in data);
9265
+ if (!propEntry) continue;
9266
+ const [propName] = propEntry;
9267
+ const nestedValue = data[propName];
9268
+ if (nestedValue === null || nestedValue === void 0) continue;
9269
+ const nestedTable = parseResult.tables.find((t) => t.name === revRel.toTable);
9270
+ if (!nestedTable) continue;
9271
+ if (revRel.type === "has-one") {
9272
+ const rowId = insertTableRow({
9273
+ db,
9274
+ table: nestedTable,
9275
+ data: parseDataByColumns(nestedValue, schema.properties[propName], nestedTable),
9276
+ parentRowId: mainRowId,
9277
+ foreignKeyColumn: rel.fromColumn
9278
+ });
9279
+ inserted.push({
9280
+ table: revRel.toTable,
9281
+ rowId
9282
+ });
9283
+ } else if (revRel.type === "has-many") {
9284
+ const items = nestedValue;
9285
+ for (const item of items) {
9286
+ const rowId = insertTableRow({
9287
+ db,
9288
+ table: nestedTable,
9289
+ data: parseDataByColumns(item, schema.properties[propName].items, nestedTable),
9290
+ parentRowId: mainRowId,
9291
+ foreignKeyColumn: rel.fromColumn
9292
+ });
9293
+ inserted.push({
9294
+ table: revRel.toTable,
9295
+ rowId
9296
+ });
9297
+ }
9298
+ }
9299
+ }
9300
+ return mainRowId;
9301
+ })();
9302
+ return {
9303
+ success: true,
9304
+ tablesInserted: inserted
9305
+ };
9306
+ } catch (e) {
9307
+ return {
9308
+ success: false,
9309
+ tablesInserted: inserted,
9310
+ error: e instanceof Error ? e.message : String(e)
9311
+ };
9312
+ }
9313
+ }
9314
+
9177
9315
  //#endregion
9178
9316
  //#region src/core/ai-extraction/snapshot.ts
9179
9317
  async function savePromptSnapshot(schema, aiexDir) {
@@ -9197,6 +9335,25 @@ const IMAGE_EXTENSIONS = new Set([
9197
9335
  "bmp",
9198
9336
  "svg"
9199
9337
  ]);
9338
+ async function ensureDatabaseReady(dbPath, schema) {
9339
+ try {
9340
+ await fs.access(dbPath);
9341
+ } catch {
9342
+ return `Database not found at ${pc.cyan(".aiex/database.db")}. Run ${pc.cyan("aiex schema")} first to create the database.`;
9343
+ }
9344
+ try {
9345
+ const result = parseJsonSchema(schema);
9346
+ const db = new Database(dbPath);
9347
+ try {
9348
+ for (const table of result.tables) if (!db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table.name)) return `Table "${table.name}" not found in database. Run ${pc.cyan("aiex schema")} first to create tables.`;
9349
+ } finally {
9350
+ db.close();
9351
+ }
9352
+ } catch (e) {
9353
+ return `Cannot verify database: ${e instanceof Error ? e.message : String(e)}`;
9354
+ }
9355
+ return null;
9356
+ }
9200
9357
  const extractCommand = defineCommand({
9201
9358
  meta: {
9202
9359
  name: "extract",
@@ -9218,6 +9375,12 @@ const extractCommand = defineCommand({
9218
9375
  type: "string",
9219
9376
  alias: "f",
9220
9377
  description: "File path (text/image/PDF) to extract from"
9378
+ },
9379
+ db: {
9380
+ type: "boolean",
9381
+ alias: "d",
9382
+ description: "Insert extracted data into SQLite database",
9383
+ default: false
9221
9384
  }
9222
9385
  },
9223
9386
  async run({ args }) {
@@ -9302,6 +9465,37 @@ const extractCommand = defineCommand({
9302
9465
  s.stop("Extraction complete");
9303
9466
  if (result.outputPath) consola.success(`Result saved: ${pc.cyan(result.outputPath)}`);
9304
9467
  if (result.tokensUsed) consola.info(pc.gray(`Token usage: prompt=${result.tokensUsed.prompt}, completion=${result.tokensUsed.completion}, total=${result.tokensUsed.total}`));
9468
+ if (args.db && result.data) {
9469
+ const s2 = spinner();
9470
+ s2.start("Inserting into database...");
9471
+ const dbError = await ensureDatabaseReady(config.databasePath, schema);
9472
+ if (dbError) {
9473
+ s2.stop("Database not ready");
9474
+ consola.error(dbError);
9475
+ outro("Failed!");
9476
+ return;
9477
+ }
9478
+ try {
9479
+ const db = new Database(config.databasePath);
9480
+ try {
9481
+ const insertResult = insertExtractedData(db, schema, result.data);
9482
+ if (insertResult.success) s2.stop(`Inserted into ${insertResult.tablesInserted.length} table(s)`);
9483
+ else {
9484
+ s2.stop("Database insert failed");
9485
+ consola.error(insertResult.error || "Unknown error");
9486
+ outro("Failed!");
9487
+ return;
9488
+ }
9489
+ } finally {
9490
+ db.close();
9491
+ }
9492
+ } catch (e) {
9493
+ s2.stop("Database insert failed");
9494
+ consola.error(e instanceof Error ? e.message : String(e));
9495
+ outro("Failed!");
9496
+ return;
9497
+ }
9498
+ }
9305
9499
  outro("Done!");
9306
9500
  }
9307
9501
  });
@@ -9309,17 +9503,6 @@ const extractCommand = defineCommand({
9309
9503
  //#endregion
9310
9504
  //#region src/commands/schema.ts
9311
9505
  const execFileAsync$1 = promisify(execFile);
9312
- async function resolveSchemaFiles(pattern) {
9313
- const cwd = process.cwd();
9314
- const absPattern = path.isAbsolute(pattern) ? pattern : path.resolve(cwd, pattern);
9315
- const files = [];
9316
- try {
9317
- for await (const entry of glob(absPattern)) files.push(entry);
9318
- } catch {
9319
- if (await fs.access(absPattern).then(() => true, () => false)) files.push(absPattern);
9320
- }
9321
- return files.filter((f) => f.endsWith(".json"));
9322
- }
9323
9506
  async function generateFromFiles(schemaFiles, config) {
9324
9507
  const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
9325
9508
  return {
@@ -9375,11 +9558,6 @@ const schemaCommand = defineCommand({
9375
9558
  description: "Sync JSON Schema to SQLite database"
9376
9559
  },
9377
9560
  args: {
9378
- files: {
9379
- type: "positional",
9380
- description: "JSON Schema file path(s), supports glob patterns",
9381
- required: false
9382
- },
9383
9561
  init: {
9384
9562
  type: "boolean",
9385
9563
  alias: "i",
@@ -9597,18 +9775,18 @@ const schemaCommand = defineCommand({
9597
9775
  await fs.writeFile(path.join(config.schemaPath, "post.json"), `${JSON.stringify(postSchema, null, 2)}\n`);
9598
9776
  consola.success(`Initialized ${pc.cyan(".aiex/")} with example schemas`);
9599
9777
  consola.info("Example includes: User (with preferences has-one), Post (with comments has-many)");
9600
- outro("Run: aiex schema .aiex/schema/*.json");
9778
+ outro("Run: aiex schema");
9601
9779
  return;
9602
9780
  }
9603
- if (!args.files) {
9604
- consola.error("Missing required argument: FILES");
9605
- consola.info("Use --init to initialize with an example schema");
9606
- outro("Failed!");
9607
- return;
9781
+ let schemaFiles;
9782
+ try {
9783
+ schemaFiles = await fs.readdir(config.schemaPath);
9784
+ schemaFiles = schemaFiles.filter((f) => f.endsWith(".json")).map((f) => path.join(config.schemaPath, f));
9785
+ } catch {
9786
+ schemaFiles = [];
9608
9787
  }
9609
- const schemaFiles = await resolveSchemaFiles(args.files);
9610
9788
  if (schemaFiles.length === 0) {
9611
- consola.error(`No schema files found for: ${args.files}`);
9789
+ consola.error(`No schema files found in ${pc.cyan(".aiex/schema/")}`);
9612
9790
  consola.info("Use --init to initialize with an example schema");
9613
9791
  outro("Failed!");
9614
9792
  return;
@@ -411,7 +411,7 @@ function generateDrizzleConfig() {
411
411
  //#endregion
412
412
  //#region package.json
413
413
  var name = "aiex-cli";
414
- var version = "0.0.0";
414
+ var version = "0.0.1-beta.2";
415
415
  var description = "JSON Schema → SQLite with AI-powered data extraction";
416
416
  var package_default = {
417
417
  name,
@@ -708,4 +708,4 @@ async function collectDoctorDiagnostics(options = {}) {
708
708
  }
709
709
 
710
710
  //#endregion
711
- export { formatDoctorDiagnosticsJson as C, doctorDiagnosticsTableRows as S, generateDrizzleConfig as _, writeAIConfig as a, generateDrizzleSchema as b, PLACEHOLDER_TEXT as c, seedConfig as d, description as f, createMigrationConfig as g, version as h, readAIConfig as i, AIConfigSchema as l, package_default as m, getDefaultAIConfig as n, DEFAULT_PROMPT_CONFIG as o, name as p, maskApiKey as r, PLACEHOLDER_SCHEMA as s, collectDoctorDiagnostics as t, createConfig as u, JsonSchemaDefinitionSchema as v, buildDoctorDiagnostics as x, parseJsonSchema as y };
711
+ export { doctorDiagnosticsTableRows as C, buildDoctorDiagnostics as S, generateDrizzleConfig as _, writeAIConfig as a, toSnakeCase as b, PLACEHOLDER_TEXT as c, seedConfig as d, description as f, createMigrationConfig as g, version as h, readAIConfig as i, AIConfigSchema as l, package_default as m, getDefaultAIConfig as n, DEFAULT_PROMPT_CONFIG as o, name as p, maskApiKey as r, PLACEHOLDER_SCHEMA as s, collectDoctorDiagnostics as t, createConfig as u, JsonSchemaDefinitionSchema as v, formatDoctorDiagnosticsJson as w, generateDrizzleSchema as x, parseJsonSchema as y };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { C as formatDoctorDiagnosticsJson, S as doctorDiagnosticsTableRows, _ as generateDrizzleConfig, b as generateDrizzleSchema, g as createMigrationConfig, t as collectDoctorDiagnostics, v as JsonSchemaDefinitionSchema, x as buildDoctorDiagnostics, y as parseJsonSchema } from "./doctor-Wic10PLt.mjs";
1
+ import { C as doctorDiagnosticsTableRows, S as buildDoctorDiagnostics, _ as generateDrizzleConfig, g as createMigrationConfig, t as collectDoctorDiagnostics, v as JsonSchemaDefinitionSchema, w as formatDoctorDiagnosticsJson, x as generateDrizzleSchema, y as parseJsonSchema } from "./doctor-Ddn9BjxC.mjs";
2
2
 
3
3
  export { JsonSchemaDefinitionSchema, buildDoctorDiagnostics, collectDoctorDiagnostics, createMigrationConfig, doctorDiagnosticsTableRows, formatDoctorDiagnosticsJson, generateDrizzleConfig, generateDrizzleSchema, parseJsonSchema };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aiex-cli",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.1-beta.2",
5
5
  "description": "JSON Schema → SQLite with AI-powered data extraction",
6
6
  "author": "OSpoon <zxin088@gmail.com>",
7
7
  "license": "MIT",