precis-cli 0.1.3__py3-none-any.whl

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 (442) hide show
  1. app/__init__.py +16 -0
  2. app/api/__init__.py +32 -0
  3. app/api/dependencies.py +185 -0
  4. app/api/main.py +301 -0
  5. app/api/middleware/__init__.py +34 -0
  6. app/api/middleware/exception_handler.py +126 -0
  7. app/api/middleware/request_logging.py +97 -0
  8. app/api/middleware/token_auth.py +215 -0
  9. app/api/models/__init__.py +151 -0
  10. app/api/models/connection_rules.py +98 -0
  11. app/api/models/files.py +99 -0
  12. app/api/models/full_validation.py +554 -0
  13. app/api/models/project.py +117 -0
  14. app/api/models/projects.py +80 -0
  15. app/api/models/schema.py +121 -0
  16. app/api/models/v2_responses.py +46 -0
  17. app/api/models/validation.py +303 -0
  18. app/api/models/workspace.py +118 -0
  19. app/api/routers/__init__.py +56 -0
  20. app/api/routers/ai/__init__.py +36 -0
  21. app/api/routers/ai/chat.py +243 -0
  22. app/api/routers/ai/generate.py +156 -0
  23. app/api/routers/ai/hardware.py +199 -0
  24. app/api/routers/ai/jobs.py +544 -0
  25. app/api/routers/ai/migrate.py +471 -0
  26. app/api/routers/ai/models.py +352 -0
  27. app/api/routers/ai/ollama.py +108 -0
  28. app/api/routers/ai/providers.py +491 -0
  29. app/api/routers/ai/router.py +44 -0
  30. app/api/routers/ai/stream.py +281 -0
  31. app/api/routers/ai/utils.py +182 -0
  32. app/api/routers/core/__init__.py +34 -0
  33. app/api/routers/core/connection_rules.py +188 -0
  34. app/api/routers/core/data_sources.py +374 -0
  35. app/api/routers/core/regex.py +333 -0
  36. app/api/routers/core/reporting.py +263 -0
  37. app/api/routers/files/__init__.py +24 -0
  38. app/api/routers/files/ops.py +179 -0
  39. app/api/routers/files/transfer.py +101 -0
  40. app/api/routers/preview/__init__.py +35 -0
  41. app/api/routers/preview/content_mode.py +264 -0
  42. app/api/routers/preview/header_row.py +154 -0
  43. app/api/routers/preview/models.py +81 -0
  44. app/api/routers/preview/path_mode.py +440 -0
  45. app/api/routers/preview/router.py +30 -0
  46. app/api/routers/project/__init__.py +61 -0
  47. app/api/routers/project/base.py +111 -0
  48. app/api/routers/project/constraint.py +345 -0
  49. app/api/routers/project/full_config.py +411 -0
  50. app/api/routers/project/full_config_writer.py +327 -0
  51. app/api/routers/project/helpers.py +167 -0
  52. app/api/routers/project/inspection_fix.py +330 -0
  53. app/api/routers/project/manifest.py +605 -0
  54. app/api/routers/project/models.py +177 -0
  55. app/api/routers/project/pattern.py +268 -0
  56. app/api/routers/project/regex.py +358 -0
  57. app/api/routers/project/scanner.py +157 -0
  58. app/api/routers/project/schema.py +567 -0
  59. app/api/routers/project/schema_helpers.py +149 -0
  60. app/api/routers/project/settings.py +399 -0
  61. app/api/routers/project/template.py +325 -0
  62. app/api/routers/project/validation.py +227 -0
  63. app/api/routers/project/view.py +177 -0
  64. app/api/routers/project/workspaces.py +167 -0
  65. app/api/routers/projects/__init__.py +25 -0
  66. app/api/routers/projects/create.py +121 -0
  67. app/api/routers/projects/open.py +103 -0
  68. app/api/routers/projects/scan.py +100 -0
  69. app/api/routers/validation/__init__.py +39 -0
  70. app/api/routers/validation/common.py +263 -0
  71. app/api/routers/validation/content_mode.py +401 -0
  72. app/api/routers/validation/history.py +235 -0
  73. app/api/routers/validation/inline_mode.py +196 -0
  74. app/api/routers/validation/path_mode.py +314 -0
  75. app/api/routers/validation/router.py +51 -0
  76. app/api/services/full_validation_response_builder.py +412 -0
  77. app/api/services/io_error_messages.py +41 -0
  78. app/api/services/preview_service.py +248 -0
  79. app/cli/__init__.py +18 -0
  80. app/cli/__main__.py +30 -0
  81. app/cli/shared_services/__init__.py +42 -0
  82. app/cli/shared_services/config_ops.py +581 -0
  83. app/cli/shared_services/generation_ops.py +160 -0
  84. app/cli/shared_services/project_ops.py +288 -0
  85. app/cli/shell/__init__.py +48 -0
  86. app/cli/shell/commands/__init__.py +63 -0
  87. app/cli/shell/commands/ai/__init__.py +260 -0
  88. app/cli/shell/commands/ai/chat.py +331 -0
  89. app/cli/shell/commands/ai/delete.py +165 -0
  90. app/cli/shell/commands/ai/diff.py +70 -0
  91. app/cli/shell/commands/ai/display.py +428 -0
  92. app/cli/shell/commands/ai/executor.py +225 -0
  93. app/cli/shell/commands/ai/executor_utils.py +271 -0
  94. app/cli/shell/commands/ai/generate.py +285 -0
  95. app/cli/shell/commands/ai/interaction.py +262 -0
  96. app/cli/shell/commands/ai/migrate.py +281 -0
  97. app/cli/shell/commands/ai/resolver.py +224 -0
  98. app/cli/shell/commands/ai/status.py +101 -0
  99. app/cli/shell/commands/ai/switch.py +158 -0
  100. app/cli/shell/commands/ai/utils.py +52 -0
  101. app/cli/shell/commands/base.py +343 -0
  102. app/cli/shell/commands/config/__init__.py +131 -0
  103. app/cli/shell/commands/config/base.py +80 -0
  104. app/cli/shell/commands/config/check.py +246 -0
  105. app/cli/shell/commands/config/edit.py +148 -0
  106. app/cli/shell/commands/config/get.py +111 -0
  107. app/cli/shell/commands/config/init.py +123 -0
  108. app/cli/shell/commands/config/inspect.py +164 -0
  109. app/cli/shell/commands/config/list.py +104 -0
  110. app/cli/shell/commands/config/set.py +119 -0
  111. app/cli/shell/commands/config/show.py +150 -0
  112. app/cli/shell/commands/exit.py +67 -0
  113. app/cli/shell/commands/help.py +104 -0
  114. app/cli/shell/commands/infer_schema.py +156 -0
  115. app/cli/shell/commands/open.py +201 -0
  116. app/cli/shell/commands/project.py +215 -0
  117. app/cli/shell/commands/provider.py +619 -0
  118. app/cli/shell/commands/system.py +170 -0
  119. app/cli/shell/commands/validate.py +455 -0
  120. app/cli/shell/completer.py +231 -0
  121. app/cli/shell/config_storage.py +191 -0
  122. app/cli/shell/exceptions.py +90 -0
  123. app/cli/shell/formatter.py +371 -0
  124. app/cli/shell/interactive_menu.py +348 -0
  125. app/cli/shell/main.py +286 -0
  126. app/cli/shell/parser.py +266 -0
  127. app/cli/start.py +180 -0
  128. app/cli_main.py +52 -0
  129. app/mcp_server.py +311 -0
  130. app/shared/core/__init__.py +18 -0
  131. app/shared/core/app_version.py +59 -0
  132. app/shared/core/config/__init__.py +119 -0
  133. app/shared/core/config/server.py +193 -0
  134. app/shared/core/data_source/__init__.py +106 -0
  135. app/shared/core/data_source/loader.py +348 -0
  136. app/shared/core/data_source/loaders/__init__.py +213 -0
  137. app/shared/core/data_source/loaders/base.py +215 -0
  138. app/shared/core/data_source/loaders/converter.py +337 -0
  139. app/shared/core/data_source/loaders/csv_loader.py +252 -0
  140. app/shared/core/data_source/loaders/excel_loader.py +666 -0
  141. app/shared/core/data_source/loaders/extractor.py +268 -0
  142. app/shared/core/data_source/loaders/json_loader.py +354 -0
  143. app/shared/core/data_source/loaders/registry.py +88 -0
  144. app/shared/core/data_source/loaders/sql_loader.py +313 -0
  145. app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
  146. app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
  147. app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
  148. app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
  149. app/shared/core/data_source/schema_info.py +50 -0
  150. app/shared/core/data_source/specs/__init__.py +128 -0
  151. app/shared/core/data_source/specs/base.py +258 -0
  152. app/shared/core/data_source/specs/csv_source.py +137 -0
  153. app/shared/core/data_source/specs/excel_source.py +141 -0
  154. app/shared/core/data_source/specs/file_base.py +252 -0
  155. app/shared/core/data_source/specs/json_source.py +226 -0
  156. app/shared/core/data_source/specs/sql_source.py +161 -0
  157. app/shared/core/encoding.py +36 -0
  158. app/shared/core/io/__init__.py +24 -0
  159. app/shared/core/io/yaml.py +379 -0
  160. app/shared/core/manifest_schema/__init__.py +52 -0
  161. app/shared/core/manifest_schema/types.py +99 -0
  162. app/shared/core/manifest_schema/version.py +175 -0
  163. app/shared/core/patterns/__init__.py +24 -0
  164. app/shared/core/patterns/loader.py +134 -0
  165. app/shared/core/patterns/writer.py +244 -0
  166. app/shared/core/project/__init__.py +47 -0
  167. app/shared/core/project/constraint/builders/__init__.py +50 -0
  168. app/shared/core/project/constraint/builders/base.py +100 -0
  169. app/shared/core/project/constraint/builders/composite.py +77 -0
  170. app/shared/core/project/constraint/builders/conditional.py +67 -0
  171. app/shared/core/project/constraint/builders/foreign_key.py +53 -0
  172. app/shared/core/project/constraint/builders/registry.py +64 -0
  173. app/shared/core/project/constraint/builders/scripted.py +51 -0
  174. app/shared/core/project/constraint/builders/single_column.py +86 -0
  175. app/shared/core/project/constraint/builders/unique.py +53 -0
  176. app/shared/core/project/constraint/factory.py +170 -0
  177. app/shared/core/project/constraint/reader.py +214 -0
  178. app/shared/core/project/constraint/registry.py +233 -0
  179. app/shared/core/project/constraint/types/__init__.py +63 -0
  180. app/shared/core/project/constraint/types/constraint_file.py +261 -0
  181. app/shared/core/project/constraint/types/refs.py +460 -0
  182. app/shared/core/project/constraint/types.py +28 -0
  183. app/shared/core/project/constraint/writer.py +181 -0
  184. app/shared/core/project/loader/__init__.py +56 -0
  185. app/shared/core/project/loader/loader.py +30 -0
  186. app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
  187. app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
  188. app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
  189. app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
  190. app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
  191. app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
  192. app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
  193. app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
  194. app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
  195. app/shared/core/project/loader/loader_parts/main.py +432 -0
  196. app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
  197. app/shared/core/project/loader/loader_parts/runtime.py +125 -0
  198. app/shared/core/project/loader/types.py +246 -0
  199. app/shared/core/project/manifest/coverage.py +391 -0
  200. app/shared/core/project/manifest/reader.py +260 -0
  201. app/shared/core/project/manifest/types.py +91 -0
  202. app/shared/core/project/manifest/types_parts/__init__.py +60 -0
  203. app/shared/core/project/manifest/types_parts/constants.py +40 -0
  204. app/shared/core/project/manifest/types_parts/data_source.py +68 -0
  205. app/shared/core/project/manifest/types_parts/info.py +59 -0
  206. app/shared/core/project/manifest/types_parts/manifest.py +294 -0
  207. app/shared/core/project/manifest/types_parts/refs.py +153 -0
  208. app/shared/core/project/manifest/types_parts/settings.py +92 -0
  209. app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
  210. app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
  211. app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
  212. app/shared/core/project/manifest/types_parts/template.py +66 -0
  213. app/shared/core/project/manifest/writer.py +262 -0
  214. app/shared/core/project/manual_data/__init__.py +27 -0
  215. app/shared/core/project/manual_data/types.py +75 -0
  216. app/shared/core/project/regex/reader.py +197 -0
  217. app/shared/core/project/regex/types.py +405 -0
  218. app/shared/core/project/regex/writer.py +123 -0
  219. app/shared/core/project/schema/reader.py +170 -0
  220. app/shared/core/project/schema/types.py +47 -0
  221. app/shared/core/project/schema/types_parts/__init__.py +36 -0
  222. app/shared/core/project/schema/types_parts/column.py +174 -0
  223. app/shared/core/project/schema/types_parts/column_utils.py +72 -0
  224. app/shared/core/project/schema/types_parts/constraint.py +165 -0
  225. app/shared/core/project/schema/types_parts/schema_id.py +66 -0
  226. app/shared/core/project/schema/types_parts/source.py +255 -0
  227. app/shared/core/project/schema/types_parts/source_options.py +347 -0
  228. app/shared/core/project/schema/types_parts/table.py +230 -0
  229. app/shared/core/project/schema/writer.py +139 -0
  230. app/shared/core/project/schema_ref_check.py +95 -0
  231. app/shared/core/project/template/__init__.py +27 -0
  232. app/shared/core/project/template/expander.py +263 -0
  233. app/shared/core/project/template/reader.py +120 -0
  234. app/shared/core/project/template/types.py +114 -0
  235. app/shared/core/project/transform/reader.py +76 -0
  236. app/shared/core/project/transform/types.py +116 -0
  237. app/shared/core/project/transform/writer.py +84 -0
  238. app/shared/core/pydantic_messages.py +59 -0
  239. app/shared/core/reporter/__init__.py +46 -0
  240. app/shared/core/reporter/reporter.py +220 -0
  241. app/shared/core/reporter/reporters/__init__.py +65 -0
  242. app/shared/core/reporter/reporters/base.py +188 -0
  243. app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
  244. app/shared/core/reporter/reporters/email_reporter.py +271 -0
  245. app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
  246. app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
  247. app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
  248. app/shared/core/utils/__init__.py +18 -0
  249. app/shared/core/utils/path_utils.py +60 -0
  250. app/shared/core/utils/regex_extract.py +113 -0
  251. app/shared/domain/__init__.py +114 -0
  252. app/shared/domain/constraints/__init__.py +76 -0
  253. app/shared/domain/constraints/allowed_values.py +211 -0
  254. app/shared/domain/constraints/base.py +141 -0
  255. app/shared/domain/constraints/charset.py +337 -0
  256. app/shared/domain/constraints/composite.py +174 -0
  257. app/shared/domain/constraints/condition_registry.py +130 -0
  258. app/shared/domain/constraints/conditional.py +629 -0
  259. app/shared/domain/constraints/date_logic.py +731 -0
  260. app/shared/domain/constraints/foreign_key.py +261 -0
  261. app/shared/domain/constraints/key_normalization.py +56 -0
  262. app/shared/domain/constraints/not_null.py +185 -0
  263. app/shared/domain/constraints/range.py +360 -0
  264. app/shared/domain/constraints/regex.py +218 -0
  265. app/shared/domain/constraints/scripted.py +276 -0
  266. app/shared/domain/constraints/unique.py +233 -0
  267. app/shared/domain/data_engine.py +384 -0
  268. app/shared/domain/data_types.py +113 -0
  269. app/shared/domain/data_types_parts/__init__.py +67 -0
  270. app/shared/domain/data_types_parts/base.py +204 -0
  271. app/shared/domain/data_types_parts/composite.py +250 -0
  272. app/shared/domain/data_types_parts/expression.py +202 -0
  273. app/shared/domain/data_types_parts/extracted.py +106 -0
  274. app/shared/domain/data_types_parts/json_types.py +234 -0
  275. app/shared/domain/data_types_parts/scalars.py +719 -0
  276. app/shared/domain/data_types_parts/sequence.py +129 -0
  277. app/shared/domain/dataset_schema.py +48 -0
  278. app/shared/domain/eval_sandbox.py +63 -0
  279. app/shared/domain/expression_system.py +366 -0
  280. app/shared/domain/regex_flags.py +53 -0
  281. app/shared/domain/schema/builder.py +223 -0
  282. app/shared/domain/schema/models.py +339 -0
  283. app/shared/domain/transforms/__init__.py +28 -0
  284. app/shared/domain/transforms/aggregate.py +141 -0
  285. app/shared/domain/transforms/base.py +171 -0
  286. app/shared/domain/transforms/cast_type.py +120 -0
  287. app/shared/domain/transforms/concat.py +103 -0
  288. app/shared/domain/transforms/conditional_assign.py +114 -0
  289. app/shared/domain/transforms/date_format.py +81 -0
  290. app/shared/domain/transforms/digits.py +77 -0
  291. app/shared/domain/transforms/drop_duplicates.py +94 -0
  292. app/shared/domain/transforms/fill_na.py +94 -0
  293. app/shared/domain/transforms/filter_rows.py +97 -0
  294. app/shared/domain/transforms/lookup.py +78 -0
  295. app/shared/domain/transforms/lower_case.py +70 -0
  296. app/shared/domain/transforms/map_value.py +90 -0
  297. app/shared/domain/transforms/math_expr.py +112 -0
  298. app/shared/domain/transforms/modulo.py +82 -0
  299. app/shared/domain/transforms/regex_extract.py +103 -0
  300. app/shared/domain/transforms/registry.py +95 -0
  301. app/shared/domain/transforms/replace.py +88 -0
  302. app/shared/domain/transforms/sort_rows.py +94 -0
  303. app/shared/domain/transforms/string_split.py +84 -0
  304. app/shared/domain/transforms/strip.py +73 -0
  305. app/shared/domain/transforms/substring.py +92 -0
  306. app/shared/domain/transforms/upper_case.py +70 -0
  307. app/shared/domain/transforms/weighted_sum.py +104 -0
  308. app/shared/domain/validation_constraints.py +69 -0
  309. app/shared/services/__init__.py +62 -0
  310. app/shared/services/ai/__init__.py +48 -0
  311. app/shared/services/ai/agent/__init__.py +40 -0
  312. app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
  313. app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
  314. app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
  315. app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
  316. app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
  317. app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
  318. app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
  319. app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
  320. app/shared/services/ai/agent/executor.py +554 -0
  321. app/shared/services/ai/agent/memory.py +256 -0
  322. app/shared/services/ai/agent/planner.py +282 -0
  323. app/shared/services/ai/agent/tool_registry.py +296 -0
  324. app/shared/services/ai/agent/tools/__init__.py +32 -0
  325. app/shared/services/ai/agent/tools/config_generate.py +133 -0
  326. app/shared/services/ai/agent/tools/config_refine.py +109 -0
  327. app/shared/services/ai/agent/tools/config_validate.py +403 -0
  328. app/shared/services/ai/agent/tools/merge_results.py +136 -0
  329. app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
  330. app/shared/services/ai/agent/tools/script_parse.py +359 -0
  331. app/shared/services/ai/agent/types.py +138 -0
  332. app/shared/services/ai/chat_agent_runner.py +596 -0
  333. app/shared/services/ai/chat_orchestrator.py +725 -0
  334. app/shared/services/ai/failure_messages.py +65 -0
  335. app/shared/services/ai/job_storage.py +274 -0
  336. app/shared/services/ai/migrate_service.py +550 -0
  337. app/shared/services/ai/streaming/__init__.py +65 -0
  338. app/shared/services/ai/streaming/event_journal.py +197 -0
  339. app/shared/services/ai/streaming/orchestrator.py +262 -0
  340. app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
  341. app/shared/services/ai/streaming/sse_response.py +148 -0
  342. app/shared/services/ai/streaming/types.py +73 -0
  343. app/shared/services/ai/types.py +213 -0
  344. app/shared/services/ai/utils.py +322 -0
  345. app/shared/services/diff/config_diff.py +320 -0
  346. app/shared/services/hardware.py +237 -0
  347. app/shared/services/llm/__init__.py +62 -0
  348. app/shared/services/llm/actions/__init__.py +42 -0
  349. app/shared/services/llm/actions/_canvas_validator.py +147 -0
  350. app/shared/services/llm/actions/_constraint_validator.py +401 -0
  351. app/shared/services/llm/actions/_regex_validator.py +85 -0
  352. app/shared/services/llm/actions/_schema_validator.py +82 -0
  353. app/shared/services/llm/actions/_settings_validator.py +76 -0
  354. app/shared/services/llm/actions/_transform_validator.py +78 -0
  355. app/shared/services/llm/actions/action_handlers.py +400 -0
  356. app/shared/services/llm/actions/action_parser.py +63 -0
  357. app/shared/services/llm/actions/action_processor.py +468 -0
  358. app/shared/services/llm/actions/action_validator.py +311 -0
  359. app/shared/services/llm/actions/diff_compute.py +195 -0
  360. app/shared/services/llm/actions/regex_handlers.py +284 -0
  361. app/shared/services/llm/actions/registry.py +336 -0
  362. app/shared/services/llm/actions/schema_handlers.py +335 -0
  363. app/shared/services/llm/actions/settings_handlers.py +180 -0
  364. app/shared/services/llm/actions/specs.py +259 -0
  365. app/shared/services/llm/actions/transform_handlers.py +279 -0
  366. app/shared/services/llm/actions/validation_types.py +147 -0
  367. app/shared/services/llm/cache/__init__.py +18 -0
  368. app/shared/services/llm/cache/response_cache.py +80 -0
  369. app/shared/services/llm/chat/__init__.py +35 -0
  370. app/shared/services/llm/chat/chat_system_prompt.py +561 -0
  371. app/shared/services/llm/chat/response_parser.py +296 -0
  372. app/shared/services/llm/config/__init__.py +45 -0
  373. app/shared/services/llm/config/crypto.py +135 -0
  374. app/shared/services/llm/config/loader.py +258 -0
  375. app/shared/services/llm/config/models.py +202 -0
  376. app/shared/services/llm/config/presets.py +128 -0
  377. app/shared/services/llm/config_generator.py +163 -0
  378. app/shared/services/llm/constraints/__init__.py +41 -0
  379. app/shared/services/llm/constraints/constraint_builder.py +177 -0
  380. app/shared/services/llm/constraints/constraint_deletion.py +84 -0
  381. app/shared/services/llm/constraints/constraint_id.py +174 -0
  382. app/shared/services/llm/constraints/frontend_instructions.py +333 -0
  383. app/shared/services/llm/constraints/inline_batch.py +279 -0
  384. app/shared/services/llm/discovery/__init__.py +35 -0
  385. app/shared/services/llm/discovery/scanner.py +181 -0
  386. app/shared/services/llm/generation/__init__.py +42 -0
  387. app/shared/services/llm/generation/agent_wiring.py +156 -0
  388. app/shared/services/llm/generation/config_builder.py +386 -0
  389. app/shared/services/llm/generation/errors.py +36 -0
  390. app/shared/services/llm/generation/existing_config.py +99 -0
  391. app/shared/services/llm/generation/profiler.py +279 -0
  392. app/shared/services/llm/generation/prompt_builder.py +199 -0
  393. app/shared/services/llm/generation/response_parser.py +144 -0
  394. app/shared/services/llm/generation/service.py +622 -0
  395. app/shared/services/llm/models.py +104 -0
  396. app/shared/services/llm/providers/__init__.py +48 -0
  397. app/shared/services/llm/providers/base.py +310 -0
  398. app/shared/services/llm/providers/cached_provider.py +90 -0
  399. app/shared/services/llm/providers/ollama.py +498 -0
  400. app/shared/services/llm/providers/openai.py +334 -0
  401. app/shared/services/llm/providers/registry.py +108 -0
  402. app/shared/services/llm/schema_resolver.py +141 -0
  403. app/shared/services/llm/suggestion_utils.py +219 -0
  404. app/shared/services/llm/validate_executor.py +163 -0
  405. app/shared/services/llm/yaml_io.py +406 -0
  406. app/shared/services/preview/__init__.py +18 -0
  407. app/shared/services/preview/loader.py +145 -0
  408. app/shared/services/preview/path_validation.py +138 -0
  409. app/shared/services/project_loader.py +57 -0
  410. app/shared/services/schema_inference.py +260 -0
  411. app/shared/services/schema_runtime_builder.py +120 -0
  412. app/shared/services/validation/__init__.py +52 -0
  413. app/shared/services/validation/chunked_loader.py +509 -0
  414. app/shared/services/validation/dag/__init__.py +35 -0
  415. app/shared/services/validation/dag/builder.py +141 -0
  416. app/shared/services/validation/dag/executor.py +225 -0
  417. app/shared/services/validation/dag/sorter.py +77 -0
  418. app/shared/services/validation/data_loader.py +231 -0
  419. app/shared/services/validation/engine.py +446 -0
  420. app/shared/services/validation/executor.py +1030 -0
  421. app/shared/services/validation/extractors.py +266 -0
  422. app/shared/services/validation/history.py +209 -0
  423. app/shared/services/validation/json_payload.py +136 -0
  424. app/shared/services/validation/loader.py +152 -0
  425. app/shared/services/validation/memory_monitor.py +179 -0
  426. app/shared/services/validation/postprocess.py +208 -0
  427. app/shared/services/validation/progress.py +64 -0
  428. app/shared/services/validation/report_export.py +222 -0
  429. app/shared/services/validation/resolver.py +180 -0
  430. app/shared/services/validation/service.py +418 -0
  431. app/shared/services/validation/types.py +238 -0
  432. app/shared/services/validation/validators/__init__.py +36 -0
  433. app/shared/services/validation/validators/adapter.py +182 -0
  434. app/shared/services/validation/validators/base.py +332 -0
  435. app/shared/services/validation/validators/composite.py +233 -0
  436. app/shared/services/validation/validators/date_logic.py +276 -0
  437. app/start_server.py +133 -0
  438. precis_cli-0.1.3.dist-info/METADATA +180 -0
  439. precis_cli-0.1.3.dist-info/RECORD +442 -0
  440. precis_cli-0.1.3.dist-info/WHEEL +5 -0
  441. precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
  442. precis_cli-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,281 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # backend/app/cli/shell/commands/ai/migrate.py
17
+ """
18
+ @fileoverview CLI AI 配置迁移命令模块
19
+
20
+ 功能概述:
21
+ - 提供 `ai migrate` 子命令,从旧脚本迁移生成 Precis V2 配置
22
+ - 支持 Python pandas / SQL / Excel 公式 / 自然语言
23
+ - 默认仅输出 YAML 预览,加 --apply 后写盘
24
+
25
+ 架构设计:
26
+ - AIMigrateCommand 继承 Command 基类
27
+ - 直接调用 ConfigMigrationService,不依赖后端 HTTP
28
+ - 写盘逻辑委托 shared_services.generation_ops.apply_generated_config(CLI/TUI 同源)
29
+
30
+ 输入示例:
31
+ precis> ai migrate scripts/legacy_rules.sql data/users.xlsx
32
+ precis> ai migrate rules.py data/*.xlsx --apply
33
+
34
+ 输出示例:
35
+ YAML 预览或写盘成功提示
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import asyncio
41
+ import logging
42
+ import os
43
+ from pathlib import Path
44
+ from typing import Any
45
+
46
+ from app.cli.shared_services.generation_ops import (
47
+ SUPPORTED_EXTENSIONS,
48
+ )
49
+ from app.cli.shared_services.generation_ops import (
50
+ apply_generated_config as _apply_generated_config,
51
+ )
52
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
53
+ from app.cli.shell.formatter import Formatter
54
+ from app.shared.services.ai.migrate_service import ConfigMigrationService
55
+ from app.shared.services.llm.generation import (
56
+ GenerationOptions,
57
+ ProfilingOptions,
58
+ )
59
+
60
+ logger = logging.getLogger(__name__)
61
+
62
+
63
+ def _infer_language(file_path: str) -> str:
64
+ """根据文件扩展名推断脚本语言。"""
65
+ lower = file_path.lower()
66
+ if lower.endswith(".py"):
67
+ return "python"
68
+ if lower.endswith(".sql"):
69
+ return "sql"
70
+ if lower.endswith(".xlsx") or lower.endswith(".xls") or lower.endswith(".csv"):
71
+ return "excel_formula"
72
+ return "natural_language"
73
+
74
+
75
+ class AIMigrateCommand(Command):
76
+ """AI 配置迁移命令。
77
+
78
+ 从旧脚本或业务描述迁移生成 Schema、Constraint、Regex 配置。
79
+ 默认输出 YAML 预览,使用 --apply 写入项目目录。
80
+ """
81
+
82
+ def __init__(self) -> None:
83
+ super().__init__("migrate", aliases=["mig"])
84
+
85
+ @property
86
+ def description(self) -> str:
87
+ return "从旧脚本迁移生成 Precis V2 配置"
88
+
89
+ @property
90
+ def usage(self) -> str:
91
+ return "ai migrate <script_file> [data_files...] [--apply] [--language python|sql|excel_formula|natural_language] [--max-iterations N]"
92
+
93
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
94
+ """执行配置迁移命令。
95
+
96
+ Args:
97
+ args: 命令参数列表
98
+ context: 项目上下文
99
+
100
+ Returns:
101
+ 命令执行结果
102
+ """
103
+ if not context.is_project_open:
104
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
105
+
106
+ project_path = context.project_path
107
+ if not project_path:
108
+ return CommandResult.error("项目路径为空")
109
+
110
+ if not args:
111
+ return CommandResult.error(f"请提供脚本文件路径\n用法: {self.usage}")
112
+
113
+ # 解析参数
114
+ script_path = args[0]
115
+ file_patterns: list[str] = []
116
+ apply = False
117
+ language: str | None = None
118
+ max_iterations = 2
119
+ sample_rows = 100
120
+ sample_values = 100
121
+
122
+ i = 1
123
+ while i < len(args):
124
+ arg = args[i]
125
+ if arg == "--apply":
126
+ apply = True
127
+ elif arg == "--language":
128
+ i += 1
129
+ if i < len(args):
130
+ language = args[i]
131
+ else:
132
+ return CommandResult.error("--language 需要参数")
133
+ elif arg == "--max-iterations":
134
+ i += 1
135
+ if i < len(args):
136
+ try:
137
+ max_iterations = max(1, min(5, int(args[i])))
138
+ except ValueError:
139
+ return CommandResult.error(f"--max-iterations 需要整数, got: {args[i]}")
140
+ else:
141
+ return CommandResult.error("--max-iterations 需要参数")
142
+ elif arg == "--sample-rows":
143
+ i += 1
144
+ if i < len(args):
145
+ try:
146
+ sample_rows = max(1, int(args[i]))
147
+ except ValueError:
148
+ return CommandResult.error(f"--sample-rows 需要整数, got: {args[i]}")
149
+ else:
150
+ return CommandResult.error("--sample-rows 需要参数")
151
+ elif arg == "--sample-values":
152
+ i += 1
153
+ if i < len(args):
154
+ try:
155
+ sample_values = max(1, int(args[i]))
156
+ except ValueError:
157
+ return CommandResult.error(f"--sample-values 需要整数, got: {args[i]}")
158
+ else:
159
+ return CommandResult.error("--sample-values 需要参数")
160
+ elif arg.startswith("--"):
161
+ return CommandResult.error(f"未知参数: {arg}\n用法: {self.usage}")
162
+ else:
163
+ file_patterns.append(arg)
164
+ i += 1
165
+
166
+ # 解析脚本路径
167
+ if not os.path.isabs(script_path):
168
+ script_path = os.path.join(project_path, script_path)
169
+ if not os.path.exists(script_path):
170
+ return CommandResult.error(f"脚本文件不存在: {script_path}")
171
+
172
+ if language is None:
173
+ language = _infer_language(script_path)
174
+
175
+ try:
176
+ with open(script_path, encoding="utf-8") as f:
177
+ script_content = f.read()
178
+ except Exception as e:
179
+ return CommandResult.error(f"读取脚本文件失败: {e}")
180
+
181
+ # 展开数据文件路径
182
+ file_paths: list[str] = []
183
+ for pattern in file_patterns:
184
+ if not os.path.isabs(pattern):
185
+ pattern = os.path.join(project_path, pattern)
186
+ import glob as _glob
187
+
188
+ matched = _glob.glob(pattern)
189
+ if matched:
190
+ file_paths.extend(matched)
191
+ elif os.path.exists(pattern):
192
+ file_paths.append(pattern)
193
+
194
+ file_paths = [p for p in file_paths if p.lower().endswith(SUPPORTED_EXTENSIONS)]
195
+ if not file_paths:
196
+ return CommandResult.error("请提供至少一个数据文件(.xlsx/.xls/.csv/.json/.jsonl)")
197
+
198
+ # 获取项目信息
199
+ config = context.project_config or {}
200
+ project_name = config.get("project", {}).get("name", Path(project_path).name)
201
+ project_id = config.get("project", {}).get("id", project_name)
202
+
203
+ print(Formatter.header("\nAI 配置迁移"))
204
+ print(Formatter.info(f"项目: {project_name}"))
205
+ print(Formatter.info(f"脚本: {os.path.relpath(script_path, project_path)} ({language})"))
206
+ print(Formatter.info("数据文件:"))
207
+ for p in file_paths:
208
+ print(f" - {os.path.relpath(p, project_path)}")
209
+
210
+ def progress_callback(stage: str, progress: float, extra: dict[str, Any] | None = None) -> None:
211
+ """终端进度回调。"""
212
+ msg = extra.get("message") if extra else None
213
+ prefix = f"[{stage}]" if not msg else f"[{stage}] {msg}"
214
+ print(Formatter.info(f" {prefix} {progress:.0f}%"))
215
+
216
+ service = ConfigMigrationService()
217
+ profiling_opts = ProfilingOptions(
218
+ sample_rows=sample_rows,
219
+ sample_values_per_column=sample_values,
220
+ )
221
+ gen_opts = GenerationOptions(
222
+ generate_schemas=True,
223
+ generate_constraints=True,
224
+ generate_regex_nodes=False,
225
+ keep_existing=True,
226
+ )
227
+
228
+ # CLI 是同步环境:asyncio.run 创建一次性事件循环并在结束时确保关闭
229
+ # (不再 get_running_loop/new_event_loop 泄漏循环)
230
+ try:
231
+ result = asyncio.run(
232
+ service.migrate_from_script(
233
+ script_content=script_content,
234
+ language=language,
235
+ file_paths=file_paths,
236
+ project_name=project_name,
237
+ project_id=project_id,
238
+ config_path=project_path,
239
+ profiling_options=profiling_opts,
240
+ generation_options=gen_opts,
241
+ max_iterations=max_iterations,
242
+ progress_callback=progress_callback,
243
+ )
244
+ )
245
+ except Exception as e:
246
+ logger.error(f"配置迁移失败: {e}", exc_info=True)
247
+ return CommandResult.error(f"配置迁移失败: {e}")
248
+
249
+ if not result.get("success"):
250
+ error = result.get("error") or "配置迁移失败"
251
+ return CommandResult.error(error)
252
+
253
+ yaml_preview = result.get("yaml_preview", "")
254
+ warnings = result.get("warnings", [])
255
+
256
+ if warnings:
257
+ print(Formatter.warning("\n迁移警告:"))
258
+ for w in warnings:
259
+ print(f" - {w}")
260
+
261
+ if apply:
262
+ try:
263
+ _apply_generated_config(result, project_path)
264
+ except Exception as e:
265
+ logger.error(f"写盘失败: {e}", exc_info=True)
266
+ return CommandResult.error(f"配置已生成,但写盘失败: {e}")
267
+
268
+ schema_count = len(result.get("schemas", {}))
269
+ constraint_count = len(result.get("constraints", {}))
270
+ regex_count = len(result.get("regex_nodes", {}))
271
+ return CommandResult.ok(
272
+ f"配置已迁移并写入项目:{schema_count} 个 schema,"
273
+ f"{constraint_count} 个 constraint,{regex_count} 个 regex。"
274
+ )
275
+
276
+ print(Formatter.header("\nYAML 预览(未写盘,加 --apply 可应用)"))
277
+ print(yaml_preview)
278
+ return CommandResult.ok("配置迁移完成,以上为 YAML 预览。")
279
+
280
+
281
+ __all__ = ["AIMigrateCommand"]
@@ -0,0 +1,224 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # backend/app/cli/shell/commands/ai/resolver.py
17
+ """
18
+ @fileoverview AI 动作歧义解析模块
19
+
20
+ 功能概述:
21
+ - 解析 AI 返回动作中的表名歧义(支持模糊匹配)
22
+ - 当表名匹配到多个结果时,弹出交互式菜单让用户选择正确的表
23
+ - 处理 VALIDATE_PROJECT 与约束操作(ADD/UPDATE/DELETE_CONSTRAINT_NODE)中的表名解析
24
+
25
+ 架构设计:
26
+ - resolve_table_name(): 使用 find_matching_schemas 对表名进行模糊匹配
27
+ - resolve_ambiguities(): 遍历 AI 返回的动作列表,解析表名歧义并标记跳过项
28
+ - 静默模式下自动选择第一个匹配项,避免阻塞非交互流程
29
+
30
+ 输入示例:
31
+ resolve_table_name("users", "/path/to/project")
32
+ resolve_ambiguities(actions, "/path/to/project")
33
+
34
+ 输出示例:
35
+ "users_table_id" 或 None(未找到)
36
+ True/False(是否还有有效动作需要继续执行)
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import logging
42
+ from typing import Any
43
+
44
+ from app.cli.shell.formatter import Formatter
45
+ from app.cli.shell.interactive_menu import InteractiveMenu
46
+ from app.shared.services.llm.schema_resolver import find_matching_schemas
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ def resolve_table_name(table_name: str, project_path: str, silent: bool = False) -> str | None:
52
+ """将表名解析为表ID。
53
+
54
+ 支持模糊匹配,如果有多个匹配则让用户选择。
55
+
56
+ Args:
57
+ table_name: 用户输入的表名
58
+ project_path: 项目路径
59
+ silent: 是否静默模式(不显示选择菜单)
60
+
61
+ Returns:
62
+ 表ID,如果未找到则返回 None
63
+ """
64
+ matches = find_matching_schemas(project_path, table_name)
65
+
66
+ if not matches:
67
+ if not silent:
68
+ print(Formatter.warning(f"[!] 未找到匹配的表: '{table_name}'"))
69
+ return None
70
+
71
+ if len(matches) == 1:
72
+ # 只有一个匹配,直接返回
73
+ selected = matches[0]
74
+ if not silent:
75
+ print(Formatter.info(f"[i] 表 '{table_name}' 匹配到: {selected['name']} ({selected['id'][:10]}...)"))
76
+ return selected["id"]
77
+
78
+ # 多个匹配,让用户选择
79
+ if not silent:
80
+ print(f"\n{Formatter.warning('[!]')} 表名 '{table_name}' 匹配到多个表:")
81
+
82
+ menu = InteractiveMenu("请选择要校验的表:")
83
+ for m in matches:
84
+ display_id = f"{m['id'][:10]}..." if len(m["id"]) > 15 else m["id"]
85
+ menu.add_item(m["id"], f"{m['name']} ({display_id})", f"ID: {m['id']}")
86
+
87
+ menu.add_item("_cancel_", "取消", "不执行此操作")
88
+
89
+ choice = menu.show()
90
+ if choice and choice != "_cancel_":
91
+ selected = next(m for m in matches if m["id"] == choice)
92
+ return selected["id"]
93
+ # 用户取消,返回 None
94
+ return None
95
+
96
+ # 静默模式下有多个匹配,记录警告并返回第一个(最安全的选择)
97
+ logger.warning(f"表名 '{table_name}' 匹配到多个表,静默模式下默认选择第一个: {matches[0]['name']}")
98
+ return matches[0]["id"]
99
+
100
+
101
+ def resolve_ambiguities(actions: list[dict[str, Any]], project_path: str) -> bool:
102
+ """解决动作中的歧义。返回是否继续执行。
103
+
104
+ 遍历 AI 返回的动作列表,解析表名和列名的歧义。
105
+ 对于模糊匹配的表名,弹出交互式菜单让用户选择。
106
+ 标记为跳过的动作会在最后过滤掉。
107
+
108
+ Args:
109
+ actions: AI 返回的动作列表
110
+ project_path: 项目路径
111
+
112
+ Returns:
113
+ 是否还有有效动作需要继续执行
114
+ """
115
+ if not actions or not project_path:
116
+ return True
117
+
118
+ for action in actions:
119
+ action_type = action.get("actionType", "")
120
+ spec = action.get("constraintSpec", {})
121
+
122
+ # 处理 VALIDATE_PROJECT 的表名解析
123
+ if action_type == "VALIDATE_PROJECT":
124
+ table_name = spec.get("tableName") or spec.get("targetNodeId")
125
+ tables = spec.get("tables", [])
126
+
127
+ # 解析单个表名
128
+ if table_name:
129
+ table_id = resolve_table_name(table_name, project_path)
130
+ if table_id:
131
+ spec["targetNodeId"] = table_id
132
+ spec["tableName"] = table_name
133
+ else:
134
+ action["_skip"] = True
135
+ continue
136
+
137
+ # 解析表名列表
138
+ elif tables:
139
+ resolved_ids = []
140
+ for name in tables:
141
+ table_id = resolve_table_name(name, project_path, silent=True)
142
+ if table_id:
143
+ resolved_ids.append(table_id)
144
+ if resolved_ids:
145
+ spec["tableIds"] = resolved_ids
146
+ else:
147
+ action["_skip"] = True
148
+ continue
149
+ continue
150
+
151
+ # Schema/Regex/Transform/Settings 操作不需要表名歧义解析
152
+ non_table_types = {
153
+ "ADD_SCHEMA",
154
+ "UPDATE_SCHEMA",
155
+ "DELETE_SCHEMA",
156
+ "ADD_REGEX",
157
+ "UPDATE_REGEX",
158
+ "DELETE_REGEX",
159
+ "ADD_TRANSFORM",
160
+ "UPDATE_TRANSFORM",
161
+ "DELETE_TRANSFORM",
162
+ "UPDATE_SETTINGS",
163
+ }
164
+ if action_type in non_table_types:
165
+ continue
166
+
167
+ # 处理约束操作的表名解析
168
+ table_id = spec.get("targetNodeId")
169
+ table_name = spec.get("tableName")
170
+
171
+ # 尝试匹配
172
+ identifier = table_id or table_name
173
+ if not identifier:
174
+ continue
175
+
176
+ matches = find_matching_schemas(project_path, identifier)
177
+
178
+ # 如果有多个模糊匹配,且没有精确匹配 ID 的情况
179
+ if len(matches) > 1:
180
+ print(f"\n{Formatter.warning('[!]')} 发现多个匹配的表 '{identifier}':")
181
+
182
+ menu = InteractiveMenu("请选择正确的表:")
183
+ for m in matches:
184
+ # 检查是否为加密 ID 格式,若是则显示一部分,否则显示全名
185
+ display_id = f"{m['id'][:10]}..." if len(m["id"]) > 15 else m["id"]
186
+ menu.add_item(m["id"], f"{m['name']} ({display_id})", f"ID: {m['id']}")
187
+
188
+ menu.add_item("_skip_", "跳过此操作", "不执行此约束更新")
189
+
190
+ choice = menu.show()
191
+ if choice is None or choice == "_skip_":
192
+ action["_skip"] = True
193
+ continue
194
+
195
+ # 找到选中的匹配项
196
+ selected = next(m for m in matches if m["id"] == choice)
197
+
198
+ # 更新 Action 中的关键字段
199
+ spec["targetNodeId"] = selected["id"]
200
+ spec["tableName"] = selected["name"]
201
+
202
+ # 同样更新列 (如果是根据名称匹配,可能也存在列名冲突)
203
+ # 这里简化处理,假设列在特定表内唯一
204
+ print(f"{Formatter.success('[OK]')} 已选择表: {selected['name']}")
205
+ elif len(matches) == 1:
206
+ # 只有一个匹配,直接更新为表ID
207
+ selected = matches[0]
208
+ spec["targetNodeId"] = selected["id"]
209
+ spec["tableName"] = selected["name"]
210
+ else:
211
+ # 4.5: 0 匹配跳过并提示——原实现静默继续(拼错的表名让约束作用于
212
+ # 空表集后"静默成功"),与 VALIDATE 分支的 _skip 行为对齐
213
+ print(f"{Formatter.warning('[!]')} 未找到表 '{identifier}',已跳过该约束操作")
214
+ action["_skip"] = True
215
+ continue
216
+
217
+ # 统计并提示被跳过的动作
218
+ skipped_actions = [a for a in actions if a.get("_skip")]
219
+ if skipped_actions:
220
+ print(Formatter.info(f"\n[i] 已跳过 {len(skipped_actions)} 个操作(用户取消或表名未找到)"))
221
+
222
+ # 过滤掉标记为跳过的动作
223
+ actions[:] = [a for a in actions if not a.get("_skip")]
224
+ return len(actions) > 0
@@ -0,0 +1,101 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # backend/app/cli/shell/commands/ai/status.py
17
+ """
18
+ @fileoverview AI 配置状态命令模块
19
+
20
+ 功能概述:
21
+ - 提供 ai status 子命令显示当前 AI Provider 配置状态
22
+ - 列出所有已配置 Provider,标记当前默认项与密钥状态
23
+ - 显示 Provider 的模型、端点等信息
24
+
25
+ 架构设计:
26
+ - AIStatusCommand 继承 Command 基类
27
+ - 通过 CLI 配置管理器获取 Provider 列表和默认项
28
+ - 使用 Formatter 进行颜色高亮输出
29
+
30
+ 输入示例:
31
+ ai status
32
+
33
+ 输出示例:
34
+ 带颜色高亮的 Provider 配置状态文本
35
+ """
36
+
37
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
38
+ from app.cli.shell.config_storage import get_cli_config
39
+ from app.cli.shell.formatter import Formatter
40
+
41
+
42
+ class AIStatusCommand(Command):
43
+ """AI 配置状态命令。
44
+
45
+ 显示所有已配置的 AI Provider 及其状态信息。
46
+ """
47
+
48
+ def __init__(self) -> None:
49
+ super().__init__("status")
50
+ self._cli_config = get_cli_config()
51
+
52
+ @property
53
+ def description(self) -> str:
54
+ return "显示 AI 配置状态"
55
+
56
+ @property
57
+ def usage(self) -> str:
58
+ return "ai status"
59
+
60
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
61
+ """执行状态查看命令。
62
+
63
+ Args:
64
+ args: 命令参数列表(此命令不需要参数)
65
+ context: 项目上下文
66
+
67
+ Returns:
68
+ 格式化的配置状态文本
69
+ """
70
+ providers = self._cli_config.list_providers()
71
+
72
+ lines = [Formatter.header("\nCLI AI 配置状态")]
73
+
74
+ if not providers:
75
+ lines.append(Formatter.warning("\n未配置任何 AI Provider"))
76
+ lines.append(Formatter.info("请编辑 ~/.precis/ai_providers.yaml 配置文件"))
77
+ else:
78
+ lines.append(f"\n已配置的 Providers ({len(providers)}):")
79
+ active = self._cli_config.get_active_provider()
80
+
81
+ for p in providers:
82
+ is_active = active and active.id == p.id
83
+ status_icon = "*" if is_active else " "
84
+ has_key = "[有密钥]" if p.api_key else "[无密钥]"
85
+
86
+ lines.append(f"\n [{status_icon}] {p.name} ({p.id})")
87
+ lines.append(f" 模型: {p.model}")
88
+ lines.append(f" API Key: {has_key}")
89
+ if p.base_url:
90
+ lines.append(f" 端点: {p.base_url}")
91
+
92
+ if active:
93
+ lines.append("\n" + Formatter.success("[*] 当前默认: " + active.name))
94
+ else:
95
+ lines.append("\n" + Formatter.warning("[!] 没有可用的默认 Provider"))
96
+
97
+ lines.append(Formatter.info("\n配置文件: ~/.precis/ai_providers.yaml"))
98
+ lines.append(Formatter.info("API Key 设置方式: 环境变量"))
99
+ lines.append(Formatter.info(" 例如: OPENAI_API_KEY=sk-xxx"))
100
+
101
+ return CommandResult.ok("\n".join(lines))