aiex-cli 0.0.1-beta.5 → 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.
Files changed (115) hide show
  1. package/README.md +22 -2
  2. package/dist/cli.mjs +303 -315
  3. package/dist/completions-ygS1okck.mjs +90 -0
  4. package/dist/core/schema-sqlite/migrate-helper.mjs +14 -5
  5. package/dist/{doctor-Cd_N16iC.mjs → doctor-SWEG-HYl.mjs} +6 -3
  6. package/dist/index.mjs +1 -1
  7. package/dist/web/assets/AISettings-D_AFhorO.js +334 -0
  8. package/dist/web/assets/DataBrowser-DZT0kGQE.css +1 -0
  9. package/dist/web/assets/DataBrowser-rznfVRaV.js +3 -0
  10. package/dist/web/assets/JsonSchemaEditor-C9iyQs7N.js +929 -0
  11. package/dist/web/assets/api-client-Dsg4WOM9.js +1 -0
  12. package/dist/web/assets/button-kTMweGMc.js +927 -0
  13. package/dist/web/assets/chunk-DtRyYLXJ.js +1 -0
  14. package/dist/web/assets/{cssMode-BloHqzZF.js → cssMode-DAbG0CMn.js} +1 -1
  15. package/dist/web/assets/dialog-CWuu7WjI.js +108 -0
  16. package/dist/web/assets/{editor.api-BG499EJF.js → editor.api-DLXGyrN1.js} +1 -1
  17. package/dist/web/assets/{editor.main-BhEWG0_P.js → editor.main-BqhfoHxy.js} +2 -2
  18. package/dist/web/assets/{freemarker2-DOHaFATh.js → freemarker2-B9_5ct2b.js} +1 -1
  19. package/dist/web/assets/{handlebars-BIFWety9.js → handlebars-TY59WcoQ.js} +1 -1
  20. package/dist/web/assets/{html-YGaqGZNd.js → html-CLULsh27.js} +1 -1
  21. package/dist/web/assets/{htmlMode-Bu3PyHjq.js → htmlMode-BvG7RNbU.js} +1 -1
  22. package/dist/web/assets/index-BRvFRL-3.js +882 -0
  23. package/dist/web/assets/index-DDFnprdM.css +2 -0
  24. package/dist/web/assets/{javascript-N0gZqDK0.js → javascript-DHrLp6gu.js} +1 -1
  25. package/dist/web/assets/{jsonMode-z5YscjcG.js → jsonMode-DBDhdzl1.js} +1 -1
  26. package/dist/web/assets/lib-C30cIFrm.js +1 -0
  27. package/dist/web/assets/{liquid-BHfNNVLs.js → liquid-tGeb-nqF.js} +1 -1
  28. package/dist/web/assets/{mdx-Dqu2t0et.js → mdx-Cmdz78VU.js} +1 -1
  29. package/dist/web/assets/{monaco.contribution-ByQ3yI-W.js → monaco.contribution-CroYPUF5.js} +2 -2
  30. package/dist/web/assets/overlayeventbus-AtOpmI6n.js +80 -0
  31. package/dist/web/assets/{python-icfse9Ji.js → python-Dmfz4iDE.js} +1 -1
  32. package/dist/web/assets/{razor-DwVkryG9.js → razor-BJicZHJs.js} +1 -1
  33. package/dist/web/assets/table-schema-mJrrf9qw.js +2 -0
  34. package/dist/web/assets/{tsMode-CLrI3bdf.js → tsMode-DYqTyE66.js} +1 -1
  35. package/dist/web/assets/{typescript-BzuZVF7m.js → typescript-DLnTe9Hf.js} +1 -1
  36. package/dist/web/assets/{xml-Cr85kdqA.js → xml-BIYqLORk.js} +1 -1
  37. package/dist/web/assets/{yaml-D3RbJnnO.js → yaml-BjmulkMX.js} +1 -1
  38. package/dist/web/index.html +8 -4
  39. package/package.json +5 -2
  40. package/src/core/schema-sqlite/migrate-helper.ts +9 -4
  41. package/src/core/schema-sqlite/migration-name.ts +14 -0
  42. package/dist/web/assets/chunk-BEqpzyXh.js +0 -1
  43. package/dist/web/assets/index-DTABZIZZ.css +0 -2
  44. package/dist/web/assets/index-DVc9DeYI.js +0 -3266
  45. /package/dist/web/assets/{abap-Cry0R76c.js → abap-DrZwwXZX.js} +0 -0
  46. /package/dist/web/assets/{apex-xqbJ58nJ.js → apex-CrCz0btt.js} +0 -0
  47. /package/dist/web/assets/{azcli-D7JTNGKs.js → azcli-BapzKHay.js} +0 -0
  48. /package/dist/web/assets/{bat-Cuq6hn0K.js → bat-C_NRAiA1.js} +0 -0
  49. /package/dist/web/assets/{bicep-eTuQjz9F.js → bicep-C7pp2CNk.js} +0 -0
  50. /package/dist/web/assets/{cameligo-DKgCRl36.js → cameligo-BhhK9vxZ.js} +0 -0
  51. /package/dist/web/assets/{clojure-B_aTBtVh.js → clojure-D0ujmUyE.js} +0 -0
  52. /package/dist/web/assets/{coffee-BWAYpIPu.js → coffee-DHEl7Jbb.js} +0 -0
  53. /package/dist/web/assets/{cpp-BduBQE8d.js → cpp-Iil-3nzZ.js} +0 -0
  54. /package/dist/web/assets/{csharp-CMqOVYKK.js → csharp-Dh0Ee7SY.js} +0 -0
  55. /package/dist/web/assets/{csp-6cGliXw2.js → csp-mwzjw0JL.js} +0 -0
  56. /package/dist/web/assets/{css-CHnKqS9Q.js → css-COIa8ZTR.js} +0 -0
  57. /package/dist/web/assets/{cypher-DMzZBj2L.js → cypher-GVc17FC4.js} +0 -0
  58. /package/dist/web/assets/{dart-7hYfJ1Dv.js → dart-phiCaE7_.js} +0 -0
  59. /package/dist/web/assets/{dockerfile-BflvjnJW.js → dockerfile-BMaDhdim.js} +0 -0
  60. /package/dist/web/assets/{ecl-BEt6xb2p.js → ecl-Cj47kvqp.js} +0 -0
  61. /package/dist/web/assets/{elixir-CnrQCt6o.js → elixir-DBbstcE1.js} +0 -0
  62. /package/dist/web/assets/{flow9-CfLCoUuB.js → flow9-ChHb1adO.js} +0 -0
  63. /package/dist/web/assets/{fsharp-BQqR9uQ6.js → fsharp-CMk2OIJN.js} +0 -0
  64. /package/dist/web/assets/{go-C3AlMVwy.js → go-BrMkuJg0.js} +0 -0
  65. /package/dist/web/assets/{graphql-O_-hDldf.js → graphql-PSR1UKGv.js} +0 -0
  66. /package/dist/web/assets/{hcl-BQQD6Mtj.js → hcl-DAQrbDOW.js} +0 -0
  67. /package/dist/web/assets/{ini-Bf0RDfP_.js → ini-0TG5BxW0.js} +0 -0
  68. /package/dist/web/assets/{java-nqX2KEDD.js → java-rgorz17v.js} +0 -0
  69. /package/dist/web/assets/{julia-B6P9U5er.js → julia-C8VMdHm8.js} +0 -0
  70. /package/dist/web/assets/{kotlin-B-LRk09-.js → kotlin-CllWo3gX.js} +0 -0
  71. /package/dist/web/assets/{less-CEaIdW1f.js → less-Cgca25AP.js} +0 -0
  72. /package/dist/web/assets/{lexon-Qv4pvFSW.js → lexon-D0GHdBaw.js} +0 -0
  73. /package/dist/web/assets/{lua-CFpyR7YN.js → lua-DmRsNG-P.js} +0 -0
  74. /package/dist/web/assets/{m3-CvKhVPQn.js → m3-BgL5dNKT.js} +0 -0
  75. /package/dist/web/assets/{markdown-qldG3Vc4.js → markdown-BuJfycGS.js} +0 -0
  76. /package/dist/web/assets/{mips-0D8PRyHq.js → mips-C9m_93PR.js} +0 -0
  77. /package/dist/web/assets/{msdax-DwZXSC5M.js → msdax-CpFHC9OI.js} +0 -0
  78. /package/dist/web/assets/{mysql-BWq85KY4.js → mysql-qFvltsqN.js} +0 -0
  79. /package/dist/web/assets/{objective-c-D653JUMG.js → objective-c-Bnmr858J.js} +0 -0
  80. /package/dist/web/assets/{pascal-rWjRDdnR.js → pascal-WP0_D5AO.js} +0 -0
  81. /package/dist/web/assets/{pascaligo-Db8EehaF.js → pascaligo-Blom4Rij.js} +0 -0
  82. /package/dist/web/assets/{perl-C68oq8-D.js → perl-B-vk8g64.js} +0 -0
  83. /package/dist/web/assets/{pgsql-BXeHe33s.js → pgsql-Cgvz6v67.js} +0 -0
  84. /package/dist/web/assets/{php-CDVsAbfl.js → php-8a3Lrw9m.js} +0 -0
  85. /package/dist/web/assets/{pla-DnryFT0q.js → pla-DuFqEZ8V.js} +0 -0
  86. /package/dist/web/assets/{postiats-CDg_4Ev-.js → postiats-DkLtSgkp.js} +0 -0
  87. /package/dist/web/assets/{powerquery-CWPi8ROz.js → powerquery-BJ1aNepW.js} +0 -0
  88. /package/dist/web/assets/{powershell-C5A0QX3-.js → powershell-rE98k687.js} +0 -0
  89. /package/dist/web/assets/{preload-helper-DSXbuxSR.js → preload-helper-DWTEM3RW.js} +0 -0
  90. /package/dist/web/assets/{protobuf-Cgt-BQbL.js → protobuf-CUheFacr.js} +0 -0
  91. /package/dist/web/assets/{pug-RPYJC9QB.js → pug-LDcAMD8w.js} +0 -0
  92. /package/dist/web/assets/{qsharp-BZ3S7fu_.js → qsharp-IHfqKOfK.js} +0 -0
  93. /package/dist/web/assets/{r-CN875f1X.js → r-D-QApv87.js} +0 -0
  94. /package/dist/web/assets/{redis-BLesvTwR.js → redis-SXdDyWR9.js} +0 -0
  95. /package/dist/web/assets/{redshift-Byf_0XqD.js → redshift-Y6lsCryn.js} +0 -0
  96. /package/dist/web/assets/{restructuredtext-DYg_6BiZ.js → restructuredtext-edObr9a8.js} +0 -0
  97. /package/dist/web/assets/{ruby-C4OkxbC-.js → ruby-CNnUfF-8.js} +0 -0
  98. /package/dist/web/assets/{rust-xAoaEFMh.js → rust-IHUZWzBr.js} +0 -0
  99. /package/dist/web/assets/{sb-C8dHOW_y.js → sb-DrUvY44N.js} +0 -0
  100. /package/dist/web/assets/{scala-Spx0wP1o.js → scala-B4hbXGLM.js} +0 -0
  101. /package/dist/web/assets/{scheme-D2mZlAUz.js → scheme-BGrd12j3.js} +0 -0
  102. /package/dist/web/assets/{scss-DDCn3Ylu.js → scss-x5G1ES4U.js} +0 -0
  103. /package/dist/web/assets/{shell-M6px0EWn.js → shell-DOehe2Y8.js} +0 -0
  104. /package/dist/web/assets/{solidity-DUWMJi-f.js → solidity-BeRvcwWV.js} +0 -0
  105. /package/dist/web/assets/{sophia-DwJbUG-2.js → sophia-DZbkUNjy.js} +0 -0
  106. /package/dist/web/assets/{sparql-ClQxbRPI.js → sparql-B7_oi5-h.js} +0 -0
  107. /package/dist/web/assets/{sql-BQdjW7Vy.js → sql-CTlsFWVE.js} +0 -0
  108. /package/dist/web/assets/{st-BpISyZ_v.js → st-DJVEJdPE.js} +0 -0
  109. /package/dist/web/assets/{swift-CMbl5gM4.js → swift-CwhT3fYa.js} +0 -0
  110. /package/dist/web/assets/{systemverilog-jx2Xs7uO.js → systemverilog-BQN63pkN.js} +0 -0
  111. /package/dist/web/assets/{tcl-GIGnfs89.js → tcl-DqwfpskA.js} +0 -0
  112. /package/dist/web/assets/{twig-Bc0mxc_m.js → twig-BiyenUgc.js} +0 -0
  113. /package/dist/web/assets/{typespec-CEioAsEm.js → typespec-CWOJribt.js} +0 -0
  114. /package/dist/web/assets/{vb-BPk67J-d.js → vb-Cq5F87m3.js} +0 -0
  115. /package/dist/web/assets/{wgsl-DOnyt8_J.js → wgsl-BAvW2lVr.js} +0 -0
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$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-Cd_N16iC.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({
@@ -12703,246 +12761,26 @@ function lookupModelCapabilities(modelName) {
12703
12761
  }
12704
12762
 
12705
12763
  //#endregion
12706
- //#region schemas/table-schema.json
12707
- var $id = "https://raw.githubusercontent.com/OSpoon/aiex-cli/main/app/cli/schemas/table-schema.json";
12708
- var table_schema_default = {
12709
- $schema: "http://json-schema.org/draft-07/schema#",
12710
- $id,
12711
- title: "aiex Table Schema",
12712
- description: "Defines a database table and its columns for aiex-cli schema-to-SQLite migration.",
12713
- type: "object",
12714
- required: [
12715
- "title",
12716
- "type",
12717
- "table",
12718
- "properties"
12719
- ],
12720
- additionalProperties: false,
12721
- properties: {
12722
- "$schema": {
12723
- "type": "string",
12724
- "description": "Pointer to this schema file for IDE validation.",
12725
- "format": "uri",
12726
- "default": "https://raw.githubusercontent.com/OSpoon/aiex-cli/main/app/cli/schemas/table-schema.json"
12727
- },
12728
- "title": {
12729
- "type": "string",
12730
- "minLength": 1,
12731
- "description": "Human-readable table title (e.g. 'User', 'OrderItem')."
12732
- },
12733
- "description": {
12734
- "type": "string",
12735
- "description": "Optional table description."
12736
- },
12737
- "type": {
12738
- "type": "string",
12739
- "const": "object",
12740
- "description": "Must be 'object' — each file defines one table."
12741
- },
12742
- "table": {
12743
- "$ref": "#/$defs/tableConfig",
12744
- "description": "Table-level configuration."
12745
- },
12746
- "properties": {
12747
- "$ref": "#/$defs/properties",
12748
- "description": "Column definitions keyed by property name (camelCase)."
12749
- },
12750
- "required": {
12751
- "type": "array",
12752
- "items": { "type": "string" },
12753
- "description": "List of property names that are NOT NULL.",
12754
- "default": []
12755
- }
12756
- },
12757
- $defs: {
12758
- "tableConfig": {
12759
- "type": "object",
12760
- "required": ["name"],
12761
- "additionalProperties": false,
12762
- "properties": {
12763
- "name": {
12764
- "type": "string",
12765
- "minLength": 1,
12766
- "pattern": "^[a-z][a-z0-9_]*$",
12767
- "description": "SQLite table name in snake_case (lowercase letters, digits, underscores).",
12768
- "examples": [
12769
- "users",
12770
- "order_items",
12771
- "blog_posts"
12772
- ]
12773
- },
12774
- "timestamps": {
12775
- "type": "boolean",
12776
- "default": false,
12777
- "description": "Automatically add created_at and updated_at TIMESTAMP columns."
12778
- },
12779
- "softDelete": {
12780
- "type": "boolean",
12781
- "default": false,
12782
- "description": "Automatically add deleted_at nullable TIMESTAMP column."
12783
- }
12784
- }
12785
- },
12786
- "properties": {
12787
- "type": "object",
12788
- "minProperties": 1,
12789
- "additionalProperties": { "$ref": "#/$defs/property" },
12790
- "description": "Column definitions. Keys are camelCase property names."
12791
- },
12792
- "property": {
12793
- "type": "object",
12794
- "required": ["type"],
12795
- "additionalProperties": false,
12796
- "properties": {
12797
- "type": {
12798
- "$ref": "#/$defs/columnType",
12799
- "description": "Column data type."
12800
- },
12801
- "format": {
12802
- "$ref": "#/$defs/formatType",
12803
- "description": "Semantic format hint that influences the SQLite column type."
12804
- },
12805
- "primary": {
12806
- "type": "boolean",
12807
- "default": false,
12808
- "description": "Mark this column as PRIMARY KEY. Only one per table."
12809
- },
12810
- "autoIncrement": {
12811
- "type": "boolean",
12812
- "default": false,
12813
- "description": "AUTO_INCREMENT for integer primary keys."
12814
- },
12815
- "unique": {
12816
- "type": "boolean",
12817
- "default": false,
12818
- "description": "Add UNIQUE constraint."
12819
- },
12820
- "default": { "description": "Default value for the column. Type should match the column type." },
12821
- "minLength": {
12822
- "type": "integer",
12823
- "minimum": 0,
12824
- "description": "Minimum string length (validation only, not a DB constraint)."
12825
- },
12826
- "maxLength": {
12827
- "type": "integer",
12828
- "minimum": 1,
12829
- "description": "Maximum string length (validation only, not a DB constraint)."
12830
- },
12831
- "minimum": {
12832
- "type": "number",
12833
- "description": "Minimum numeric value (validation only, not a DB constraint)."
12834
- },
12835
- "maximum": {
12836
- "type": "number",
12837
- "description": "Maximum numeric value (validation only, not a DB constraint)."
12838
- },
12839
- "drizzle": {
12840
- "$ref": "#/$defs/drizzleConfig",
12841
- "description": "Drizzle ORM specific overrides."
12842
- },
12843
- "nested": {
12844
- "$ref": "#/$defs/nestedConfig",
12845
- "description": "Create a separate related table instead of embedding as JSON."
12846
- },
12847
- "foreignKey": {
12848
- "$ref": "#/$defs/foreignKeyRef",
12849
- "description": "Foreign key reference to another table."
12850
- },
12851
- "properties": {
12852
- "$ref": "#/$defs/properties",
12853
- "description": "Sub-properties when type is 'object'. Ignored unless nested.enabled or drizzle.mode='json'."
12854
- },
12855
- "items": {
12856
- "$ref": "#/$defs/property",
12857
- "description": "Item schema when type is 'array'. Ignored unless items.nested.enabled."
12858
- },
12859
- "required": {
12860
- "type": "array",
12861
- "items": { "type": "string" },
12862
- "description": "Required sub-properties for nested objects."
12863
- }
12864
- }
12865
- },
12866
- "columnType": {
12867
- "type": "string",
12868
- "enum": [
12869
- "string",
12870
- "integer",
12871
- "number",
12872
- "boolean",
12873
- "object",
12874
- "array",
12875
- "null"
12876
- ],
12877
- "description": "Data type. Maps to SQLite as: string→TEXT, integer→INTEGER, number→REAL, boolean→INTEGER(boolean), object→TEXT(json), array→TEXT(json), null→TEXT."
12878
- },
12879
- "formatType": {
12880
- "type": "string",
12881
- "enum": [
12882
- "date-time",
12883
- "email",
12884
- "uri",
12885
- "json"
12886
- ],
12887
- "description": "Format hints: 'date-time' → INTEGER(timestamp), 'json' → TEXT(json), 'email'/'uri' → TEXT (no special mapping)."
12888
- },
12889
- "drizzleConfig": {
12890
- "type": "object",
12891
- "additionalProperties": false,
12892
- "properties": {
12893
- "mode": {
12894
- "type": "string",
12895
- "enum": [
12896
- "json",
12897
- "timestamp",
12898
- "timestamp_ms",
12899
- "boolean",
12900
- "bigint"
12901
- ],
12902
- "description": "Override Drizzle column mode. 'json' stores as TEXT(json), 'timestamp'/'timestamp_ms' as INTEGER, 'boolean' as INTEGER(boolean), 'bigint' as INTEGER(bigint)."
12903
- },
12904
- "customType": {
12905
- "type": "string",
12906
- "description": "Custom Drizzle type name (reserved for future use)."
12907
- }
12908
- }
12909
- },
12910
- "nestedConfig": {
12911
- "type": "object",
12912
- "required": ["enabled", "relation"],
12913
- "additionalProperties": false,
12914
- "properties": {
12915
- "enabled": {
12916
- "type": "boolean",
12917
- "const": true,
12918
- "description": "Must be true to activate nested table creation."
12919
- },
12920
- "relation": {
12921
- "type": "string",
12922
- "enum": ["has-one", "has-many"],
12923
- "description": "'has-one' → one() relation, 'has-many' → many() relation. Only one level of nesting is supported."
12924
- }
12925
- }
12926
- },
12927
- "foreignKeyRef": {
12928
- "type": "object",
12929
- "required": ["table", "column"],
12930
- "additionalProperties": false,
12931
- "properties": {
12932
- "table": {
12933
- "type": "string",
12934
- "minLength": 1,
12935
- "description": "Target table name (snake_case)."
12936
- },
12937
- "column": {
12938
- "type": "string",
12939
- "minLength": 1,
12940
- "description": "Target column name in the referenced table."
12941
- }
12942
- }
12943
- }
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));
12944
12781
  }
12945
- };
12782
+ throw lastError ?? /* @__PURE__ */ new Error("Retry failed after all attempts");
12783
+ }
12946
12784
 
12947
12785
  //#endregion
12948
12786
  //#region src/core/ai-extraction/json-utils.ts
@@ -13132,6 +12970,100 @@ async function readFilePart(filePath) {
13132
12970
  mimeType: mime
13133
12971
  };
13134
12972
  }
12973
+ function nullableType(type) {
12974
+ return type === "null" ? ["null"] : [type, "null"];
12975
+ }
12976
+ function propertyToExtractionSchema(property) {
12977
+ if (property.type === "array") return {
12978
+ type: nullableType("array"),
12979
+ items: property.items ? propertyToExtractionSchema(property.items) : {}
12980
+ };
12981
+ if (property.type === "object") {
12982
+ const childProperties = property.properties ? Object.fromEntries(Object.entries(property.properties).map(([name$1, prop]) => [name$1, propertyToExtractionSchema(prop)])) : void 0;
12983
+ return {
12984
+ type: nullableType("object"),
12985
+ ...childProperties ? {
12986
+ properties: childProperties,
12987
+ required: Object.keys(childProperties),
12988
+ additionalProperties: false
12989
+ } : { additionalProperties: true }
12990
+ };
12991
+ }
12992
+ return { type: nullableType(property.type) };
12993
+ }
12994
+ function isRecord(value) {
12995
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12996
+ }
12997
+ function schemaToExtractionOutputSchema(schema) {
12998
+ const properties = Object.fromEntries(Object.entries(schema.properties).filter(([, prop]) => !(prop.primary && prop.autoIncrement)).map(([name$1, prop]) => [name$1, propertyToExtractionSchema(prop)]));
12999
+ return {
13000
+ type: "object",
13001
+ additionalProperties: false,
13002
+ properties,
13003
+ required: Object.keys(properties)
13004
+ };
13005
+ }
13006
+ function validatePropertyValue(path$1, property, value, issues) {
13007
+ if (value === null) return;
13008
+ switch (property.type) {
13009
+ case "string":
13010
+ if (typeof value !== "string") issues.push(`${path$1}: expected string or null`);
13011
+ return;
13012
+ case "integer":
13013
+ if (!Number.isInteger(value)) issues.push(`${path$1}: expected integer or null`);
13014
+ return;
13015
+ case "number":
13016
+ if (typeof value !== "number" || Number.isNaN(value)) issues.push(`${path$1}: expected number or null`);
13017
+ return;
13018
+ case "boolean":
13019
+ if (typeof value !== "boolean") issues.push(`${path$1}: expected boolean or null`);
13020
+ return;
13021
+ case "array":
13022
+ if (!Array.isArray(value)) {
13023
+ issues.push(`${path$1}: expected array or null`);
13024
+ return;
13025
+ }
13026
+ if (property.items) {
13027
+ const itemProperty = property.items;
13028
+ value.forEach((item, index) => validatePropertyValue(`${path$1}[${index}]`, itemProperty, item, issues));
13029
+ }
13030
+ return;
13031
+ case "object":
13032
+ if (!isRecord(value)) {
13033
+ issues.push(`${path$1}: expected object or null`);
13034
+ return;
13035
+ }
13036
+ if (property.properties) validateProperties(path$1, property.properties, value, issues);
13037
+ return;
13038
+ case "null": issues.push(`${path$1}: expected null`);
13039
+ }
13040
+ }
13041
+ function validateProperties(basePath, properties, data, issues) {
13042
+ const expected = Object.entries(properties).filter(([, prop]) => !(prop.primary && prop.autoIncrement));
13043
+ const expectedKeys = new Set(expected.map(([name$1]) => name$1));
13044
+ for (const key of Object.keys(data)) if (!expectedKeys.has(key)) issues.push(`${basePath}.${key}: unexpected field`);
13045
+ for (const [name$1, prop] of expected) {
13046
+ const path$1 = `${basePath}.${name$1}`;
13047
+ if (!(name$1 in data)) {
13048
+ issues.push(`${path$1}: missing field`);
13049
+ continue;
13050
+ }
13051
+ validatePropertyValue(path$1, prop, data[name$1], issues);
13052
+ }
13053
+ }
13054
+ function validateExtractedData(schema, data) {
13055
+ if (!isRecord(data)) return {
13056
+ success: false,
13057
+ error: "Extracted data must be a JSON object."
13058
+ };
13059
+ const issues = [];
13060
+ validateProperties("$", schema.properties, data, issues);
13061
+ if (issues.length > 0) return {
13062
+ success: false,
13063
+ error: `Extracted data does not match schema:\n${issues.map((issue) => ` - ${issue}`).join("\n")}`
13064
+ };
13065
+ return { success: true };
13066
+ }
13135
13067
  async function loadPromptSnapshot(aiexDir, tableName) {
13136
13068
  const snapshotPath = path.join(aiexDir, "extracted", `${tableName}.prompt.md`);
13137
13069
  try {
@@ -13173,15 +13105,16 @@ async function extractStructuredData(input) {
13173
13105
  let system;
13174
13106
  let user;
13175
13107
  const snapshot = await loadPromptSnapshot(aiexDir, schema.table.name);
13108
+ const promptText = file ? PLACEHOLDER_TEXT : text;
13176
13109
  if (snapshot) {
13177
13110
  system = snapshot.system;
13178
- user = snapshot.user.replaceAll(PLACEHOLDER_TEXT, text);
13111
+ user = snapshot.user.replaceAll(PLACEHOLDER_TEXT, promptText);
13179
13112
  } else {
13180
- const generated = generateExtractionPrompt(schema, text, config.prompt ?? DEFAULT_PROMPT_CONFIG);
13113
+ const generated = generateExtractionPrompt(schema, promptText, config.prompt ?? DEFAULT_PROMPT_CONFIG);
13181
13114
  system = generated.system;
13182
13115
  user = generated.user;
13183
13116
  }
13184
- const outputSchema = jsonSchema(table_schema_default);
13117
+ const outputSchema = jsonSchema(schemaToExtractionOutputSchema(schema));
13185
13118
  let result;
13186
13119
  if (useFileContent) {
13187
13120
  const filePart = await readFilePart(file);
@@ -13208,23 +13141,30 @@ async function extractStructuredData(input) {
13208
13141
  role: "user",
13209
13142
  content: contentParts
13210
13143
  }],
13211
- abortSignal: AbortSignal.timeout(12e4)
13144
+ abortSignal: AbortSignal.timeout(12e4),
13145
+ maxRetries: 0
13212
13146
  };
13213
13147
  if (useStructuredOutput) fileOpts.output = Output.object({ schema: outputSchema });
13214
- result = await generateText(fileOpts);
13148
+ result = await withRetry(() => generateText(fileOpts), input.onRetry);
13215
13149
  } else {
13216
13150
  const textOpts = {
13217
13151
  model: provider.chatModel(selected.name),
13218
13152
  system,
13219
13153
  prompt: user,
13220
- abortSignal: AbortSignal.timeout(6e4)
13154
+ abortSignal: AbortSignal.timeout(6e4),
13155
+ maxRetries: 0
13221
13156
  };
13222
13157
  if (useStructuredOutput) textOpts.output = Output.object({ schema: outputSchema });
13223
- result = await generateText(textOpts);
13158
+ result = await withRetry(() => generateText(textOpts), input.onRetry);
13224
13159
  }
13225
13160
  let data;
13226
13161
  if (useStructuredOutput) data = result.output;
13227
13162
  else data = safeParseJSON(result.text);
13163
+ const validation = validateExtractedData(schema, data);
13164
+ if (!validation.success) return {
13165
+ success: false,
13166
+ error: validation.error
13167
+ };
13228
13168
  const outputDir = path.resolve(aiexDir, config.extraction.outputDir.replace(".aiex/", ""));
13229
13169
  await fs.mkdir(outputDir, { recursive: true });
13230
13170
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -13409,6 +13349,12 @@ const IMAGE_EXTENSIONS = new Set([
13409
13349
  "bmp",
13410
13350
  "svg"
13411
13351
  ]);
13352
+ const FILE_PART_EXTENSIONS = new Set([...IMAGE_EXTENSIONS, "pdf"]);
13353
+ function fail$1(message) {
13354
+ if (message) consola.error(message);
13355
+ outro("Failed!");
13356
+ process.exitCode = 1;
13357
+ }
13412
13358
  async function ensureDatabaseReady(dbPath, schema) {
13413
13359
  try {
13414
13360
  await fs.access(dbPath);
@@ -13467,29 +13413,24 @@ const extractCommand = defineCommand({
13467
13413
  const config = createMigrationConfig(process.cwd());
13468
13414
  const aiexDir = path.dirname(config.schemaPath);
13469
13415
  if (!args.text && !args.file) {
13470
- consola.error("Please provide text (-t) or a file (-f) to extract from");
13471
- outro("Failed!");
13416
+ fail$1("Please provide text (-t) or a file (-f) to extract from");
13472
13417
  return;
13473
13418
  }
13474
13419
  if (args.text && args.file) {
13475
- consola.error("-t and -f cannot be used together");
13476
- outro("Failed!");
13420
+ fail$1("-t and -f cannot be used together");
13477
13421
  return;
13478
13422
  }
13479
13423
  const aiConfig = await readAIConfig(aiexDir);
13480
13424
  if (!aiConfig) {
13481
- consola.error("AI configuration not found. Please configure AI settings in the Web interface first");
13482
- outro("Failed!");
13425
+ fail$1("AI configuration not found. Please configure AI settings in the Web interface first");
13483
13426
  return;
13484
13427
  }
13485
13428
  if (!aiConfig.provider.apiKey) {
13486
- consola.error("API Key not configured. Please configure AI settings in the Web interface first");
13487
- outro("Failed!");
13429
+ fail$1("API Key not configured. Please configure AI settings in the Web interface first");
13488
13430
  return;
13489
13431
  }
13490
13432
  if (!aiConfig.provider.models?.length) {
13491
- consola.error("No models configured. Please add at least one model in AI Settings");
13492
- outro("Failed!");
13433
+ fail$1("No models configured. Please add at least one model in AI Settings");
13493
13434
  return;
13494
13435
  }
13495
13436
  let modelOverride;
@@ -13497,8 +13438,7 @@ const extractCommand = defineCommand({
13497
13438
  const matched = aiConfig.provider.models.find((m) => m.name === args.model);
13498
13439
  if (!matched) {
13499
13440
  const available = aiConfig.provider.models.map((m) => m.name).join(", ");
13500
- consola.error(`Model "${args.model}" not found in configuration. Available models: ${available}`);
13501
- outro("Failed!");
13441
+ fail$1(`Model "${args.model}" not found in configuration. Available models: ${available}`);
13502
13442
  return;
13503
13443
  }
13504
13444
  modelOverride = matched;
@@ -13507,12 +13447,11 @@ const extractCommand = defineCommand({
13507
13447
  let filePath;
13508
13448
  if (args.file) {
13509
13449
  const ext = path.extname(args.file).toLowerCase().replace(".", "");
13510
- if (IMAGE_EXTENSIONS.has(ext)) filePath = args.file;
13450
+ if (FILE_PART_EXTENSIONS.has(ext)) filePath = args.file;
13511
13451
  else try {
13512
13452
  text = await fs.readFile(args.file, "utf-8");
13513
13453
  } catch {
13514
- consola.error(`Cannot read file: ${args.file}`);
13515
- outro("Failed!");
13454
+ fail$1(`Cannot read file: ${args.file}`);
13516
13455
  return;
13517
13456
  }
13518
13457
  } else if (args.text) text = args.text;
@@ -13523,18 +13462,17 @@ const extractCommand = defineCommand({
13523
13462
  const content = await fs.readFile(schemaPath, "utf-8");
13524
13463
  schema = JSON.parse(content);
13525
13464
  } catch {
13526
- consola.error(`Cannot read schema file: ${schemaName}.json`);
13527
- outro("Failed!");
13465
+ fail$1(`Cannot read schema file: ${schemaName}.json`);
13528
13466
  return;
13529
13467
  }
13530
13468
  try {
13531
- JsonSchemaDefinitionSchema.parse(schema);
13469
+ schema = JsonSchemaDefinitionSchema.parse(schema);
13532
13470
  } catch (e) {
13533
13471
  if (e instanceof ZodError) {
13534
13472
  consola.error(`Schema validation failed: ${schemaName}.json`);
13535
13473
  for (const issue of e.issues) consola.error(` - ${issue.path.join(".")}: ${issue.message}`);
13536
13474
  }
13537
- outro("Failed!");
13475
+ fail$1();
13538
13476
  return;
13539
13477
  }
13540
13478
  const s = spinner();
@@ -13545,12 +13483,14 @@ const extractCommand = defineCommand({
13545
13483
  text,
13546
13484
  aiexDir,
13547
13485
  file: filePath,
13548
- 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
+ }
13549
13490
  });
13550
13491
  if (!result.success) {
13551
13492
  s.stop("Extraction failed");
13552
- consola.error(result.error || "Unknown error");
13553
- outro("Failed!");
13493
+ fail$1(result.error || "Unknown error");
13554
13494
  return;
13555
13495
  }
13556
13496
  s.stop("Extraction complete");
@@ -13562,8 +13502,7 @@ const extractCommand = defineCommand({
13562
13502
  const dbError = await ensureDatabaseReady(config.databasePath, schema);
13563
13503
  if (dbError) {
13564
13504
  s2.stop("Database not ready");
13565
- consola.error(dbError);
13566
- outro("Failed!");
13505
+ fail$1(dbError);
13567
13506
  return;
13568
13507
  }
13569
13508
  try {
@@ -13573,8 +13512,7 @@ const extractCommand = defineCommand({
13573
13512
  if (insertResult.success) s2.stop(`Inserted into ${insertResult.tablesInserted.length} table(s)`);
13574
13513
  else {
13575
13514
  s2.stop("Database insert failed");
13576
- consola.error(insertResult.error || "Unknown error");
13577
- outro("Failed!");
13515
+ fail$1(insertResult.error || "Unknown error");
13578
13516
  return;
13579
13517
  }
13580
13518
  } finally {
@@ -13582,8 +13520,7 @@ const extractCommand = defineCommand({
13582
13520
  }
13583
13521
  } catch (e) {
13584
13522
  s2.stop("Database insert failed");
13585
- consola.error(e instanceof Error ? e.message : String(e));
13586
- outro("Failed!");
13523
+ fail$1(e instanceof Error ? e.message : String(e));
13587
13524
  return;
13588
13525
  }
13589
13526
  }
@@ -13591,9 +13528,27 @@ const extractCommand = defineCommand({
13591
13528
  }
13592
13529
  });
13593
13530
 
13531
+ //#endregion
13532
+ //#region schemas/table-schema.json
13533
+ var $id = "https://raw.githubusercontent.com/OSpoon/aiex-cli/main/app/cli/schemas/table-schema.json";
13534
+
13594
13535
  //#endregion
13595
13536
  //#region src/commands/schema.ts
13596
13537
  const execFileAsync$1 = promisify(execFile);
13538
+ function fail(message) {
13539
+ if (message) consola.error(message);
13540
+ outro("Failed!");
13541
+ process.exitCode = 1;
13542
+ }
13543
+ async function writeJsonIfAbsent(filePath, data) {
13544
+ try {
13545
+ await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, { flag: "wx" });
13546
+ return "created";
13547
+ } catch (error) {
13548
+ if (error.code === "EEXIST") return "skipped";
13549
+ throw error;
13550
+ }
13551
+ }
13597
13552
  async function generateFromFiles(schemaFiles, config) {
13598
13553
  const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
13599
13554
  return {
@@ -13611,17 +13566,18 @@ async function generateFromFiles(schemaFiles, config) {
13611
13566
  consola.success(`Generated ${pc.cyan(".aiex/drizzle/schema.ts")} from ${schemaFiles.length} schema file(s)`);
13612
13567
  return true;
13613
13568
  }
13614
- async function migrate(config) {
13569
+ async function migrate(config, migrationName) {
13615
13570
  const helperPath = resolveHelperPath();
13616
- const tsxPath = resolveTsxPath();
13571
+ const helperArgs = [
13572
+ resolveTsxPath(),
13573
+ helperPath,
13574
+ config.drizzleSchemaPath,
13575
+ config.migrationsPath,
13576
+ config.databasePath
13577
+ ];
13578
+ if (migrationName) helperArgs.push(migrationName);
13617
13579
  try {
13618
- const { stdout } = await execFileAsync$1(process.execPath, [
13619
- tsxPath,
13620
- helperPath,
13621
- config.drizzleSchemaPath,
13622
- config.migrationsPath,
13623
- config.databasePath
13624
- ], { cwd: process.cwd() });
13580
+ const { stdout } = await execFileAsync$1(process.execPath, helperArgs, { cwd: process.cwd() });
13625
13581
  const result = JSON.parse(stdout.trim());
13626
13582
  if (!result.success) {
13627
13583
  consola.error("Failed to generate migration");
@@ -13862,9 +13818,11 @@ const schemaCommand = defineCommand({
13862
13818
  "authorId"
13863
13819
  ]
13864
13820
  };
13865
- await fs.writeFile(path.join(config.schemaPath, "user.json"), `${JSON.stringify(userSchema, null, 2)}\n`);
13866
- await fs.writeFile(path.join(config.schemaPath, "post.json"), `${JSON.stringify(postSchema, null, 2)}\n`);
13821
+ const userStatus = await writeJsonIfAbsent(path.join(config.schemaPath, "user.json"), userSchema);
13822
+ const postStatus = await writeJsonIfAbsent(path.join(config.schemaPath, "post.json"), postSchema);
13867
13823
  consola.success(`Initialized ${pc.cyan(".aiex/")} with example schemas`);
13824
+ if (userStatus === "skipped") consola.warn(`${pc.cyan(".aiex/schema/user.json")} already exists, skipped`);
13825
+ if (postStatus === "skipped") consola.warn(`${pc.cyan(".aiex/schema/post.json")} already exists, skipped`);
13868
13826
  consola.info("Example includes: User (with preferences has-one), Post (with comments has-many)");
13869
13827
  outro("Run: aiex schema");
13870
13828
  return;
@@ -13877,9 +13835,8 @@ const schemaCommand = defineCommand({
13877
13835
  schemaFiles = [];
13878
13836
  }
13879
13837
  if (schemaFiles.length === 0) {
13880
- consola.error(`No schema files found in ${pc.cyan(".aiex/schema/")}`);
13881
13838
  consola.info("Use --init to initialize with an example schema");
13882
- outro("Failed!");
13839
+ fail(`No schema files found in ${pc.cyan(".aiex/schema/")}`);
13883
13840
  return;
13884
13841
  }
13885
13842
  const s1 = spinner();
@@ -13887,7 +13844,7 @@ const schemaCommand = defineCommand({
13887
13844
  const genOk = await generateFromFiles(schemaFiles, config);
13888
13845
  s1.stop(genOk ? "Schema generated" : "Generation failed");
13889
13846
  if (!genOk) {
13890
- outro("Failed!");
13847
+ fail();
13891
13848
  return;
13892
13849
  }
13893
13850
  if (args.generate) {
@@ -13896,10 +13853,10 @@ const schemaCommand = defineCommand({
13896
13853
  }
13897
13854
  const s2 = spinner();
13898
13855
  s2.start("Running migrations...");
13899
- const migOk = await migrate(config);
13856
+ const migOk = await migrate(config, args.name);
13900
13857
  s2.stop(migOk ? "Migrations applied" : "Migration failed");
13901
13858
  if (!migOk) {
13902
- outro("Failed!");
13859
+ fail();
13903
13860
  return;
13904
13861
  }
13905
13862
  outro("Done!");
@@ -13977,6 +13934,8 @@ function aiRoutes(config) {
13977
13934
  //#endregion
13978
13935
  //#region src/server/routes/data.ts
13979
13936
  const FILE_REGEX = /\.json$/;
13937
+ const EXTRACTION_FILE_RE = /^[\w.-]+\.json$/;
13938
+ const TABLE_NAME_RE$1 = /^[a-z][a-z0-9_]*$/;
13980
13939
  const TIMESTAMP_CLEANUP = /(\d{2})-(\d{2})-(\d{2})/;
13981
13940
  const TIMESTAMP_TZ = /(\d{3})Z/;
13982
13941
  function dataRoutes(config) {
@@ -14014,20 +13973,6 @@ function dataRoutes(config) {
14014
13973
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
14015
13974
  }
14016
13975
  });
14017
- app.get("/data/:name", async (c) => {
14018
- const name$1 = c.req.param("name");
14019
- const filePath = path.join(extractedDir, name$1);
14020
- try {
14021
- const content = await fs.readFile(filePath, "utf-8");
14022
- return c.json({
14023
- success: true,
14024
- content,
14025
- name: name$1
14026
- });
14027
- } catch {
14028
- return c.json({ error: "Extraction result not found" }, 404);
14029
- }
14030
- });
14031
13976
  app.get("/data/tables", async (c) => {
14032
13977
  try {
14033
13978
  const schemaDir = config.schemaPath;
@@ -14066,6 +14011,7 @@ function dataRoutes(config) {
14066
14011
  });
14067
14012
  app.get("/data/tables/:name", async (c) => {
14068
14013
  const tableName = c.req.param("name");
14014
+ if (!TABLE_NAME_RE$1.test(tableName)) return c.json({ error: "Invalid table name" }, 400);
14069
14015
  const sortField = c.req.query("sortField");
14070
14016
  const sortOrder = c.req.query("sortOrder") || "asc";
14071
14017
  let db;
@@ -14100,12 +14046,33 @@ function dataRoutes(config) {
14100
14046
  db.close();
14101
14047
  }
14102
14048
  });
14049
+ app.get("/data/:name", async (c) => {
14050
+ const name$1 = c.req.param("name");
14051
+ if (name$1 !== path.basename(name$1) || !EXTRACTION_FILE_RE.test(name$1) || name$1.includes("..")) return c.json({ error: "Invalid extraction file name" }, 400);
14052
+ const filePath = path.join(extractedDir, name$1);
14053
+ try {
14054
+ const content = await fs.readFile(filePath, "utf-8");
14055
+ return c.json({
14056
+ success: true,
14057
+ content,
14058
+ name: name$1
14059
+ });
14060
+ } catch {
14061
+ return c.json({ error: "Extraction result not found" }, 404);
14062
+ }
14063
+ });
14103
14064
  return app;
14104
14065
  }
14105
14066
 
14106
14067
  //#endregion
14107
14068
  //#region src/server/routes/schema.ts
14108
14069
  const execFileAsync = promisify(execFile);
14070
+ const SCHEMA_FILE_RE = /^[\w.-]+\.json$/;
14071
+ const TABLE_NAME_RE = /^[a-z][a-z0-9_]*$/;
14072
+ function resolveSchemaFile(schemaDir, name$1) {
14073
+ if (name$1 !== path.basename(name$1) || !SCHEMA_FILE_RE.test(name$1) || name$1.includes("..")) return null;
14074
+ return path.join(schemaDir, name$1);
14075
+ }
14109
14076
  function schemaRoutes(config) {
14110
14077
  const app = new Hono();
14111
14078
  const schemaDir = config.schemaPath;
@@ -14118,8 +14085,8 @@ function schemaRoutes(config) {
14118
14085
  return c.json(jsonFiles);
14119
14086
  });
14120
14087
  app.get("/schema/:name", async (c) => {
14121
- const name$1 = c.req.param("name");
14122
- const filePath = path.join(schemaDir, name$1);
14088
+ const filePath = resolveSchemaFile(schemaDir, c.req.param("name"));
14089
+ if (!filePath) return c.json({ error: "Invalid schema file name" }, 400);
14123
14090
  try {
14124
14091
  const content = await fs.readFile(filePath, "utf-8");
14125
14092
  return c.json(JSON.parse(content));
@@ -14128,8 +14095,8 @@ function schemaRoutes(config) {
14128
14095
  }
14129
14096
  });
14130
14097
  app.post("/schema/:name", async (c) => {
14131
- const name$1 = c.req.param("name");
14132
- const filePath = path.join(schemaDir, name$1);
14098
+ const filePath = resolveSchemaFile(schemaDir, c.req.param("name"));
14099
+ if (!filePath) return c.json({ error: "Invalid schema file name" }, 400);
14133
14100
  try {
14134
14101
  const body = await c.req.json();
14135
14102
  await ensureDir();
@@ -14145,6 +14112,10 @@ function schemaRoutes(config) {
14145
14112
  });
14146
14113
  app.get("/prompt-snapshot/:name", async (c) => {
14147
14114
  const name$1 = c.req.param("name");
14115
+ if (!TABLE_NAME_RE.test(name$1)) return c.json({
14116
+ success: false,
14117
+ error: "Invalid table name"
14118
+ }, 400);
14148
14119
  const aiexDir = path.dirname(schemaDir);
14149
14120
  const snapshotPath = path.join(aiexDir, "extracted", `${name$1}.prompt.md`);
14150
14121
  try {
@@ -14161,8 +14132,8 @@ function schemaRoutes(config) {
14161
14132
  }
14162
14133
  });
14163
14134
  app.delete("/schema/:name", async (c) => {
14164
- const name$1 = c.req.param("name");
14165
- const filePath = path.join(schemaDir, name$1);
14135
+ const filePath = resolveSchemaFile(schemaDir, c.req.param("name"));
14136
+ if (!filePath) return c.json({ error: "Invalid schema file name" }, 400);
14166
14137
  try {
14167
14138
  await fs.unlink(filePath);
14168
14139
  return c.json({ success: true });
@@ -14239,9 +14210,10 @@ function schemaRoutes(config) {
14239
14210
 
14240
14211
  //#endregion
14241
14212
  //#region src/server/index.ts
14213
+ const LOCAL_ORIGIN_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/;
14242
14214
  function createApp(config, staticDir) {
14243
14215
  const app = new Hono();
14244
- app.use("*", cors());
14216
+ app.use("*", cors({ origin: (origin) => LOCAL_ORIGIN_RE.test(origin) ? origin : null }));
14245
14217
  app.route("/api", schemaRoutes(config));
14246
14218
  app.route("/api", aiRoutes(config));
14247
14219
  app.route("/api", dataRoutes(config));
@@ -14317,6 +14289,7 @@ const subCommands = {
14317
14289
  web: webCommand,
14318
14290
  schema: schemaCommand,
14319
14291
  extract: extractCommand,
14292
+ completion: completionCommand,
14320
14293
  doctor: doctorCommand
14321
14294
  };
14322
14295
 
@@ -14324,11 +14297,26 @@ const subCommands = {
14324
14297
  //#region src/cli.ts
14325
14298
  seedConfig(createConfig());
14326
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
+ }
14327
14315
  runMain(defineCommand({
14328
14316
  meta: {
14329
14317
  name,
14330
14318
  version,
14331
- description: description$1
14319
+ description
14332
14320
  },
14333
14321
  subCommands
14334
14322
  }));