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-BTfByg-I.mjs";
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.6";
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-BTfByg-I.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-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.6",
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
+ }