aiex-cli 0.0.1-beta.6 → 0.0.1-beta.7
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
|
@@ -100,6 +100,26 @@ By default, aiex automatically selects a model based on your input type (vision-
|
|
|
100
100
|
| `aiex extract -s <name> -f <file> --db` | Extract and insert into SQLite database |
|
|
101
101
|
| `aiex extract -s <name> -f <file> -m <model>` | Extract with a specific AI model |
|
|
102
102
|
| `aiex doctor` | System and configuration diagnostics |
|
|
103
|
+
| `aiex completion bash\|zsh\|fish` | Generate shell completion scripts |
|
|
104
|
+
|
|
105
|
+
### Shell Completions
|
|
106
|
+
|
|
107
|
+
Enable tab completion for commands and options:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# bash
|
|
111
|
+
source <(aiex completion bash)
|
|
112
|
+
|
|
113
|
+
# zsh
|
|
114
|
+
source <(aiex completion zsh)
|
|
115
|
+
|
|
116
|
+
# fish
|
|
117
|
+
aiex completion fish | source
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
To make it permanent, add the `source` line to your shell config file (`~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`).
|
|
121
|
+
|
|
122
|
+
> Completions are dynamically generated from the command definitions — no manual updates needed when commands or options change.
|
|
103
123
|
|
|
104
124
|
<br>
|
|
105
125
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as doctorDiagnosticsTableRows, a as writeAIConfig, b as toSnakeCase, c as PLACEHOLDER_TEXT, d as seedConfig, f as description, 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-
|
|
1
|
+
import { C as doctorDiagnosticsTableRows, a as writeAIConfig, b as toSnakeCase, c as PLACEHOLDER_TEXT, d as seedConfig, f as description, 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-SWEG-HYl.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
@@ -6,14 +6,14 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import { defineCommand, runMain } from "citty";
|
|
9
|
+
import { consola } from "consola";
|
|
9
10
|
import updateNotifier from "update-notifier";
|
|
10
11
|
import CliTable3 from "cli-table3";
|
|
11
|
-
import { consola } from "consola";
|
|
12
12
|
import { intro, outro, spinner } from "@clack/prompts";
|
|
13
13
|
import Database from "better-sqlite3";
|
|
14
14
|
import pc from "picocolors";
|
|
15
15
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
16
|
-
import { Output, generateText, jsonSchema } from "ai";
|
|
16
|
+
import { APICallError, Output, generateText, jsonSchema } from "ai";
|
|
17
17
|
import { exec, execFile } from "node:child_process";
|
|
18
18
|
import { promisify } from "node:util";
|
|
19
19
|
import { serve } from "@hono/node-server";
|
|
@@ -95,6 +95,64 @@ function parseAllSchemas(entries) {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/commands/completion.ts
|
|
100
|
+
function bashScript(name$1) {
|
|
101
|
+
return `# ${name$1} bash completion
|
|
102
|
+
_${name$1}() {
|
|
103
|
+
local IFS=\\$'\\n'
|
|
104
|
+
COMPREPLY=($(${name$1} _complete "\${COMP_WORDS[@]}" 2>/dev/null))
|
|
105
|
+
}
|
|
106
|
+
complete -F _${name$1} ${name$1}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
function zshScript(name$1) {
|
|
110
|
+
return `# ${name$1} zsh completion
|
|
111
|
+
#compdef ${name$1}
|
|
112
|
+
|
|
113
|
+
_${name$1}() {
|
|
114
|
+
local -a completions
|
|
115
|
+
completions=("\${(@f)$(${name$1} _complete "\${words[@]}" 2>/dev/null)}")
|
|
116
|
+
_describe '${name$1}' completions
|
|
117
|
+
}
|
|
118
|
+
compdef _${name$1} ${name$1}
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function fishScript(name$1) {
|
|
122
|
+
return `# ${name$1} fish completion
|
|
123
|
+
complete -c ${name$1} -f -a '(${name$1} _complete (commandline -cp) 2>/dev/null)'
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
function generateScript(name$1, shell) {
|
|
127
|
+
switch (shell) {
|
|
128
|
+
case "bash": return bashScript(name$1);
|
|
129
|
+
case "zsh": return zshScript(name$1);
|
|
130
|
+
case "fish": return fishScript(name$1);
|
|
131
|
+
default: throw new Error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const completionCommand = defineCommand({
|
|
135
|
+
meta: {
|
|
136
|
+
name: "completion",
|
|
137
|
+
description: "Generate shell completion scripts (bash|zsh|fish)\n\nUsage:\n aiex completion bash # source <(aiex completion bash)\n aiex completion zsh # source <(aiex completion zsh)\n aiex completion fish # aiex completion fish | source"
|
|
138
|
+
},
|
|
139
|
+
args: { shell: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Shell type: bash, zsh, fish",
|
|
142
|
+
required: true
|
|
143
|
+
} },
|
|
144
|
+
async run({ args }) {
|
|
145
|
+
const name$1 = "aiex";
|
|
146
|
+
const shell = args.shell;
|
|
147
|
+
try {
|
|
148
|
+
process.stdout.write(generateScript(name$1, shell));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
98
156
|
//#endregion
|
|
99
157
|
//#region src/commands/doctor.ts
|
|
100
158
|
const doctorCommand = defineCommand({
|
|
@@ -12702,6 +12760,28 @@ function lookupModelCapabilities(modelName) {
|
|
|
12702
12760
|
return null;
|
|
12703
12761
|
}
|
|
12704
12762
|
|
|
12763
|
+
//#endregion
|
|
12764
|
+
//#region src/utils/retry.ts
|
|
12765
|
+
async function withRetry(fn, onRetry, maxRetries = 5) {
|
|
12766
|
+
let lastError;
|
|
12767
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
12768
|
+
return await fn();
|
|
12769
|
+
} catch (error) {
|
|
12770
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
12771
|
+
lastError = err;
|
|
12772
|
+
if (!(err instanceof APICallError && err.isRetryable && attempt < maxRetries)) throw err;
|
|
12773
|
+
const delayMs = 1e3 * 2 ** attempt + Math.round(Math.random() * 500);
|
|
12774
|
+
onRetry?.({
|
|
12775
|
+
attempt: attempt + 1,
|
|
12776
|
+
maxRetries,
|
|
12777
|
+
delayMs,
|
|
12778
|
+
statusCode: err.statusCode
|
|
12779
|
+
});
|
|
12780
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
12781
|
+
}
|
|
12782
|
+
throw lastError ?? /* @__PURE__ */ new Error("Retry failed after all attempts");
|
|
12783
|
+
}
|
|
12784
|
+
|
|
12705
12785
|
//#endregion
|
|
12706
12786
|
//#region src/core/ai-extraction/json-utils.ts
|
|
12707
12787
|
function stripFences(text) {
|
|
@@ -13061,19 +13141,21 @@ async function extractStructuredData(input) {
|
|
|
13061
13141
|
role: "user",
|
|
13062
13142
|
content: contentParts
|
|
13063
13143
|
}],
|
|
13064
|
-
abortSignal: AbortSignal.timeout(12e4)
|
|
13144
|
+
abortSignal: AbortSignal.timeout(12e4),
|
|
13145
|
+
maxRetries: 0
|
|
13065
13146
|
};
|
|
13066
13147
|
if (useStructuredOutput) fileOpts.output = Output.object({ schema: outputSchema });
|
|
13067
|
-
result = await generateText(fileOpts);
|
|
13148
|
+
result = await withRetry(() => generateText(fileOpts), input.onRetry);
|
|
13068
13149
|
} else {
|
|
13069
13150
|
const textOpts = {
|
|
13070
13151
|
model: provider.chatModel(selected.name),
|
|
13071
13152
|
system,
|
|
13072
13153
|
prompt: user,
|
|
13073
|
-
abortSignal: AbortSignal.timeout(6e4)
|
|
13154
|
+
abortSignal: AbortSignal.timeout(6e4),
|
|
13155
|
+
maxRetries: 0
|
|
13074
13156
|
};
|
|
13075
13157
|
if (useStructuredOutput) textOpts.output = Output.object({ schema: outputSchema });
|
|
13076
|
-
result = await generateText(textOpts);
|
|
13158
|
+
result = await withRetry(() => generateText(textOpts), input.onRetry);
|
|
13077
13159
|
}
|
|
13078
13160
|
let data;
|
|
13079
13161
|
if (useStructuredOutput) data = result.output;
|
|
@@ -13401,7 +13483,10 @@ const extractCommand = defineCommand({
|
|
|
13401
13483
|
text,
|
|
13402
13484
|
aiexDir,
|
|
13403
13485
|
file: filePath,
|
|
13404
|
-
modelOverride
|
|
13486
|
+
modelOverride,
|
|
13487
|
+
onRetry(info) {
|
|
13488
|
+
s.message(`API responded with ${info.statusCode}, retrying in ${info.delayMs / 1e3}s (${info.attempt}/${info.maxRetries})...`);
|
|
13489
|
+
}
|
|
13405
13490
|
});
|
|
13406
13491
|
if (!result.success) {
|
|
13407
13492
|
s.stop("Extraction failed");
|
|
@@ -14204,6 +14289,7 @@ const subCommands = {
|
|
|
14204
14289
|
web: webCommand,
|
|
14205
14290
|
schema: schemaCommand,
|
|
14206
14291
|
extract: extractCommand,
|
|
14292
|
+
completion: completionCommand,
|
|
14207
14293
|
doctor: doctorCommand
|
|
14208
14294
|
};
|
|
14209
14295
|
|
|
@@ -14211,6 +14297,21 @@ const subCommands = {
|
|
|
14211
14297
|
//#region src/cli.ts
|
|
14212
14298
|
seedConfig(createConfig());
|
|
14213
14299
|
updateNotifier({ pkg: package_default }).notify();
|
|
14300
|
+
process.on("uncaughtException", (error) => {
|
|
14301
|
+
consola.error(error);
|
|
14302
|
+
process.exit(1);
|
|
14303
|
+
});
|
|
14304
|
+
process.on("unhandledRejection", (reason) => {
|
|
14305
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
14306
|
+
consola.error(error);
|
|
14307
|
+
process.exit(1);
|
|
14308
|
+
});
|
|
14309
|
+
if (process.argv[2] === "_complete") {
|
|
14310
|
+
const { getCompletions } = await import("./completions-ygS1okck.mjs");
|
|
14311
|
+
const suggestions = getCompletions(subCommands, process.argv.slice(3));
|
|
14312
|
+
for (const s of suggestions) process.stdout.write(`${s}\n`);
|
|
14313
|
+
process.exit(0);
|
|
14314
|
+
}
|
|
14214
14315
|
runMain(defineCommand({
|
|
14215
14316
|
meta: {
|
|
14216
14317
|
name,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/core/completions.ts
|
|
6
|
+
const LEADING_DASHES = /^-+/;
|
|
7
|
+
function getArgNames(cmd) {
|
|
8
|
+
if (!cmd.args) return [];
|
|
9
|
+
return Object.entries(cmd.args).flatMap(([key, arg]) => {
|
|
10
|
+
const names = [`--${key}`];
|
|
11
|
+
if (arg.alias) names.push(`-${arg.alias}`);
|
|
12
|
+
return names;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function getCommandNames(cmds) {
|
|
16
|
+
return Object.keys(cmds).filter((c) => !c.startsWith("_"));
|
|
17
|
+
}
|
|
18
|
+
function getFileCompletions(pattern) {
|
|
19
|
+
try {
|
|
20
|
+
const files = fs.readdirSync(path.dirname(pattern));
|
|
21
|
+
const ext = path.extname(pattern);
|
|
22
|
+
const prefix = path.basename(pattern).replace(ext, "");
|
|
23
|
+
return files.filter((f) => f.endsWith(ext) && f.startsWith(prefix)).map((f) => f.replace(ext, ""));
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getJsonModelNames(configPath) {
|
|
29
|
+
try {
|
|
30
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
31
|
+
const config = JSON.parse(content);
|
|
32
|
+
if (config.provider?.models) return config.provider.models.map((m) => m.name);
|
|
33
|
+
} catch {}
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
function getValueCompletions(prevArg) {
|
|
37
|
+
switch (prevArg) {
|
|
38
|
+
case "--schema":
|
|
39
|
+
case "-s": {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
return getFileCompletions(path.join(cwd, ".aiex/schema/*.json"));
|
|
42
|
+
}
|
|
43
|
+
case "--model":
|
|
44
|
+
case "-m": {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
return getJsonModelNames(path.join(cwd, ".aiex/ai-config.json"));
|
|
47
|
+
}
|
|
48
|
+
case "--file":
|
|
49
|
+
case "-f":
|
|
50
|
+
case "--text":
|
|
51
|
+
case "-t":
|
|
52
|
+
case "--name":
|
|
53
|
+
case "--port":
|
|
54
|
+
case "-p": return [];
|
|
55
|
+
default: return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getCompletions(subCommands, args) {
|
|
59
|
+
const cmds = getCommandNames(subCommands);
|
|
60
|
+
if (args.length <= 1) {
|
|
61
|
+
const word = args[0] ?? "";
|
|
62
|
+
if (!word) return cmds;
|
|
63
|
+
return cmds.filter((c) => c.startsWith(word));
|
|
64
|
+
}
|
|
65
|
+
const cmdName = args[0];
|
|
66
|
+
const cmd = subCommands[cmdName];
|
|
67
|
+
const rest = args.slice(1);
|
|
68
|
+
if (!cmd) {
|
|
69
|
+
const matched = cmds.filter((c) => c.startsWith(cmdName));
|
|
70
|
+
if (matched.length > 0) return matched;
|
|
71
|
+
return cmds;
|
|
72
|
+
}
|
|
73
|
+
const current = rest[rest.length - 1] ?? "";
|
|
74
|
+
const prev = rest.length > 1 ? rest[rest.length - 2] : void 0;
|
|
75
|
+
if (current.startsWith("-")) {
|
|
76
|
+
const names = getArgNames(cmd);
|
|
77
|
+
if (!current) return names;
|
|
78
|
+
const key = current.replace(LEADING_DASHES, "");
|
|
79
|
+
if (!key) return names;
|
|
80
|
+
return names.filter((n) => n.replace(LEADING_DASHES, "").startsWith(key));
|
|
81
|
+
}
|
|
82
|
+
if (!current && prev) {
|
|
83
|
+
const values = getValueCompletions(prev);
|
|
84
|
+
if (values.length > 0) return values;
|
|
85
|
+
}
|
|
86
|
+
return getArgNames(cmd);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { getCompletions };
|
|
@@ -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.1-beta.
|
|
414
|
+
var version = "0.0.1-beta.7";
|
|
415
415
|
var description = "JSON Schema → SQLite with AI-powered data extraction";
|
|
416
416
|
var package_default = {
|
|
417
417
|
name,
|
|
@@ -452,13 +452,15 @@ var package_default = {
|
|
|
452
452
|
files: [
|
|
453
453
|
"bin",
|
|
454
454
|
"dist",
|
|
455
|
-
"src/core/schema-sqlite/migrate-helper.ts"
|
|
455
|
+
"src/core/schema-sqlite/migrate-helper.ts",
|
|
456
|
+
"src/core/schema-sqlite/migration-name.ts"
|
|
456
457
|
],
|
|
457
458
|
scripts: {
|
|
458
459
|
"build": "tsdown && pnpm --filter aiex-web build",
|
|
459
460
|
"dev": "tsdown --watch",
|
|
460
461
|
"start": "tsx src/index.ts",
|
|
461
462
|
"test": "vitest",
|
|
463
|
+
"coverage": "vitest --coverage",
|
|
462
464
|
"typecheck": "tsc",
|
|
463
465
|
"lint": "eslint .",
|
|
464
466
|
"prepublishOnly": "cp ../../README.md . && pnpm run build",
|
|
@@ -491,6 +493,7 @@ var package_default = {
|
|
|
491
493
|
"@types/better-sqlite3": "catalog:types",
|
|
492
494
|
"@types/node": "catalog:types",
|
|
493
495
|
"@types/update-notifier": "catalog:",
|
|
496
|
+
"@vitest/coverage-v8": "catalog:testing",
|
|
494
497
|
"eslint": "catalog:cli",
|
|
495
498
|
"publint": "catalog:cli",
|
|
496
499
|
"tsdown": "catalog:cli",
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
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-
|
|
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-SWEG-HYl.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.1-beta.
|
|
4
|
+
"version": "0.0.1-beta.7",
|
|
5
5
|
"description": "JSON Schema → SQLite with AI-powered data extraction",
|
|
6
6
|
"author": "OSpoon <zxin088@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"bin",
|
|
41
41
|
"dist",
|
|
42
|
-
"src/core/schema-sqlite/migrate-helper.ts"
|
|
42
|
+
"src/core/schema-sqlite/migrate-helper.ts",
|
|
43
|
+
"src/core/schema-sqlite/migration-name.ts"
|
|
43
44
|
],
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"@types/better-sqlite3": "^7.6.0",
|
|
69
70
|
"@types/node": "^25.6.0",
|
|
70
71
|
"@types/update-notifier": "^6.0.8",
|
|
72
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
71
73
|
"eslint": "^10.2.0",
|
|
72
74
|
"publint": "^0.3.18",
|
|
73
75
|
"tsdown": "^0.17.3",
|
|
@@ -80,6 +82,7 @@
|
|
|
80
82
|
"dev": "tsdown --watch",
|
|
81
83
|
"start": "tsx src/index.ts",
|
|
82
84
|
"test": "vitest",
|
|
85
|
+
"coverage": "vitest --coverage",
|
|
83
86
|
"typecheck": "tsc",
|
|
84
87
|
"lint": "eslint ."
|
|
85
88
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function sanitizeMigrationName(name?: string): string | undefined {
|
|
2
|
+
if (!name)
|
|
3
|
+
return undefined
|
|
4
|
+
|
|
5
|
+
const slug = name
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9_\s-]/g, '_')
|
|
9
|
+
.replace(/[\s-]+/g, '_')
|
|
10
|
+
.replace(/_+/g, '_')
|
|
11
|
+
.replace(/^_+|_+$/g, '')
|
|
12
|
+
|
|
13
|
+
return slug || undefined
|
|
14
|
+
}
|