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 +14 -9
- package/dist/cli.mjs +204 -26
- package/dist/{doctor-Wic10PLt.mjs → doctor-Ddn9BjxC.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,10 @@ npm install -g aiex-cli
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
25
|
-
aiex schema
|
|
26
|
-
aiex extract -s invoice -f invoice.pdf
|
|
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
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9778
|
+
outro("Run: aiex schema");
|
|
9601
9779
|
return;
|
|
9602
9780
|
}
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
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 };
|