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
app/mcp_server.py ADDED
@@ -0,0 +1,311 @@
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
+ """@fileoverview Precis MCP server(stdio)
17
+
18
+ 功能概述:
19
+ - 以 Model Context Protocol (MCP) stdio 方式对外提供数据校验能力
20
+ - 薄协议层:复用 ValidationExecutor / schema_inference / json_payload,
21
+ 不分叉第二套输出格式(validate_data 返回值即 CLI --format json 契约)
22
+ - 入口:`precis-mcp` 命令(pyproject [project.scripts])或 `python -m app.mcp_server`
23
+
24
+ Tools:
25
+ - validate_data: 执行校验,返回契约 payload(docs/contracts/validate-json-v1.md)
26
+ - check_config: 检查项目配置加载情况(loading_errors/warnings + 装载计数)
27
+ - describe_constraints: 列出约束类型与参数说明(类型清单从 registry 派生)
28
+ - infer_schema: 从数据文件推断 schema 草稿
29
+
30
+ 安全(P3-3):
31
+ - manifest/数据路径做白名单校验:只允许访问 server 工作目录(及
32
+ PRECIS_MCP_ALLOWED_ROOTS 环境变量追加的根,os.pathsep 分隔)内的文件,
33
+ 防 agent 被诱导读取任意路径
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import asyncio
39
+ import json
40
+ import os
41
+ import sys
42
+ from pathlib import Path
43
+ from typing import Any
44
+
45
+ # 约束类型的参数说明(类型清单从 core registry 派生,此表只补人话描述;
46
+ # 新增约束类型时 registry 派生会自动带上,缺描述时用通用文案兜底)
47
+ _CONSTRAINT_PARAM_DOCS: dict[str, dict[str, str]] = {
48
+ "NotNull": {"refs": "table_id + column_id", "params": "无"},
49
+ "Unique": {"refs": "table_id + column_ids(列表)", "params": "无"},
50
+ "AllowedValues": {"refs": "table_id + column_id", "params": "allowed_values: 允许值列表"},
51
+ "Range": {"refs": "table_id + column_id", "params": "min/max/boundary_mode(inclusive|exclusive)"},
52
+ "ForeignKey": {"refs": "from_table_id + from_column_id + to_table_id + to_column_id", "params": "无"},
53
+ "Conditional": {
54
+ "refs": "table_id + then_column_id + if_conditions[{if_column_id, operator, value}] + if_logic",
55
+ "params": "then_condition: {operator: not_null}",
56
+ },
57
+ "Scripted": {"refs": "table_id + column_id", "params": "name + expression(需 allow_eval)"},
58
+ "Charset": {"refs": "table_id + column_id", "params": "charset_mode: ascii|chinese|chinese_mixed"},
59
+ "DateLogic": {"refs": "table_id + column_id", "params": "logic_mode: compare + compare_op + reference_date"},
60
+ "Composite": {"refs": "table_id", "params": "logic(all|any) + sub_constraints(内嵌子约束列表)"},
61
+ }
62
+
63
+ # MCP 协议版本(与官方 SDK 卆商;回退值用于异常客户端)
64
+ _PROTOCOL_VERSION = "2024-11-05"
65
+
66
+
67
+ def _allowed_roots() -> list[Path]:
68
+ """路径白名单根:server 工作目录 + 环境变量追加根。"""
69
+ roots = [Path.cwd().resolve()]
70
+ extra = os.environ.get("PRECIS_MCP_ALLOWED_ROOTS", "")
71
+ for part in extra.split(os.pathsep):
72
+ if part.strip():
73
+ roots.append(Path(part.strip()).resolve())
74
+ return roots
75
+
76
+
77
+ def _safe_resolve_path(raw: str, kind: str) -> str:
78
+ """校验路径在白名单根内,返回绝对路径。
79
+
80
+ Args:
81
+ raw: 调用方(agent)传入的路径
82
+ kind: 路径用途描述(用于错误消息)
83
+
84
+ Returns:
85
+ 解析后的绝对路径
86
+
87
+ Raises:
88
+ ValueError: 路径不在任何白名单根内
89
+ """
90
+ resolved = Path(raw).resolve()
91
+ for root in _allowed_roots():
92
+ if resolved == root or root in resolved.parents:
93
+ return str(resolved)
94
+ raise ValueError(
95
+ f"{kind}路径越界: {raw}(仅允许访问工作目录内的文件;如需扩展范围请设置 PRECIS_MCP_ALLOWED_ROOTS 环境变量)"
96
+ )
97
+
98
+
99
+ def tool_validate_data(manifest: str, data_directory: str | None = None, table: str | None = None) -> dict[str, Any]:
100
+ """执行数据校验(复用 ValidationExecutor,返回契约 payload)。
101
+
102
+ Raises:
103
+ ValueError: 路径越界或 manifest 不存在
104
+ """
105
+ from app.shared.services.validation.executor import ValidationExecutor, ValidationOptions
106
+ from app.shared.services.validation.json_payload import build_json_payload
107
+
108
+ manifest_path = _safe_resolve_path(manifest, "manifest")
109
+ if not Path(manifest_path).exists():
110
+ raise ValueError(f"清单文件不存在: {manifest_path}")
111
+
112
+ data_dir = _safe_resolve_path(data_directory, "数据目录") if data_directory else str(Path(manifest_path).parent)
113
+
114
+ executor = ValidationExecutor(manifest_path)
115
+ result = executor.execute(data_dir, ValidationOptions(table_filter=table))
116
+ return build_json_payload(result)
117
+
118
+
119
+ def tool_check_config(manifest: str) -> dict[str, Any]:
120
+ """检查项目配置加载情况(不执行数据校验)。"""
121
+ from app.shared.core.project.loader import load_project
122
+
123
+ manifest_path = _safe_resolve_path(manifest, "manifest")
124
+ if not Path(manifest_path).exists():
125
+ raise ValueError(f"清单文件不存在: {manifest_path}")
126
+
127
+ loaded = load_project(manifest_path)
128
+ loading_errors = [err.to_dict() for err in loaded.loading_errors or []]
129
+ return {
130
+ "manifest_path": manifest_path,
131
+ "version_ok": not any(e.get("error_type") == "ManifestVersionError" for e in loading_errors),
132
+ "schemas_loaded": len(loaded.schema_files),
133
+ "constraints_loaded": len(loaded.constraint_files),
134
+ "loading_errors": loading_errors,
135
+ "warnings": list(loaded.warnings or []),
136
+ }
137
+
138
+
139
+ def tool_describe_constraints() -> dict[str, Any]:
140
+ """列出全部约束类型与参数说明(类型清单从 core registry 派生)。"""
141
+ from app.shared.core.project.constraint.registry import CONSTRAINT_TYPE_ALIASES
142
+
143
+ # registry 的别名值集合即受支持的标准类型名集合
144
+ canonical_types = sorted(set(CONSTRAINT_TYPE_ALIASES.values()) | set(_CONSTRAINT_PARAM_DOCS.keys()))
145
+ types = [
146
+ {
147
+ "type": type_name,
148
+ "refs": _CONSTRAINT_PARAM_DOCS.get(type_name, {}).get("refs", "见 v2-format 文档"),
149
+ "params": _CONSTRAINT_PARAM_DOCS.get(type_name, {}).get("params", "见 v2-format 文档"),
150
+ }
151
+ for type_name in canonical_types
152
+ ]
153
+ return {"types": types, "note": "refs 指向 schema 的表/列 ID;配置格式详见插件 v2-format.md"}
154
+
155
+
156
+ def tool_infer_schema(
157
+ data_file: str,
158
+ sample_rows: int = 1000,
159
+ table_id: str | None = None,
160
+ table_name: str | None = None,
161
+ source_path: str | None = None,
162
+ ) -> dict[str, Any]:
163
+ """从数据文件推断 schema 草稿(复用 services 层推断逻辑)。"""
164
+ from app.shared.services.schema_inference import infer_schema
165
+
166
+ file_path = _safe_resolve_path(data_file, "数据文件")
167
+ return infer_schema(
168
+ file_path,
169
+ sample_rows=sample_rows,
170
+ table_id=table_id,
171
+ table_name=table_name,
172
+ source_path=source_path,
173
+ )
174
+
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # MCP 协议层(官方 SDK,薄封装)
178
+ # ---------------------------------------------------------------------------
179
+
180
+ # tools/list 的输入 schema(JSON Schema 格式)
181
+ _TOOL_SCHEMAS: dict[str, dict[str, Any]] = {
182
+ "validate_data": {
183
+ "type": "object",
184
+ "properties": {
185
+ "manifest": {"type": "string", "description": "project.precis.yaml 路径(须在工作目录内)"},
186
+ "data_directory": {"type": "string", "description": "数据目录(缺省为 manifest 所在目录)"},
187
+ "table": {"type": "string", "description": "只校验指定表(缺省校验全部)"},
188
+ },
189
+ "required": ["manifest"],
190
+ },
191
+ "check_config": {
192
+ "type": "object",
193
+ "properties": {"manifest": {"type": "string", "description": "project.precis.yaml 路径"}},
194
+ "required": ["manifest"],
195
+ },
196
+ "describe_constraints": {"type": "object", "properties": {}},
197
+ "infer_schema": {
198
+ "type": "object",
199
+ "properties": {
200
+ "data_file": {"type": "string", "description": "CSV/Excel/JSON 数据文件路径"},
201
+ "sample_rows": {"type": "integer", "description": "采样行数(默认 1000)"},
202
+ "table_id": {"type": "string", "description": "表 ID(替换既有 schema 时传原 id)"},
203
+ "table_name": {"type": "string", "description": "表显示名"},
204
+ "source_path": {"type": "string", "description": "写入 schema 的 source.path"},
205
+ },
206
+ "required": ["data_file"],
207
+ },
208
+ }
209
+
210
+ _TOOL_DESCRIPTIONS: dict[str, str] = {
211
+ "validate_data": "执行 Precis 数据校验,返回契约 JSON(is_valid/errors/summary 等,见 docs/contracts/validate-json-v1.md)",
212
+ "check_config": "检查项目配置加载情况(loading_errors/装载计数),不执行校验",
213
+ "describe_constraints": "列出全部约束类型与 refs/params 说明",
214
+ "infer_schema": "从数据文件头部推断列类型,返回 schema YAML 结构草稿",
215
+ }
216
+
217
+
218
+ def _dispatch_tool_sync(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
219
+ """同步工具分派(在工作线程执行,避免阻塞事件循环)。"""
220
+ if name == "validate_data":
221
+ return tool_validate_data(
222
+ manifest=str(arguments["manifest"]),
223
+ data_directory=arguments.get("data_directory"),
224
+ table=arguments.get("table"),
225
+ )
226
+ if name == "check_config":
227
+ return tool_check_config(manifest=str(arguments["manifest"]))
228
+ if name == "describe_constraints":
229
+ return tool_describe_constraints()
230
+ if name == "infer_schema":
231
+ return tool_infer_schema(
232
+ data_file=str(arguments["data_file"]),
233
+ sample_rows=int(arguments.get("sample_rows", 1000)),
234
+ table_id=arguments.get("table_id"),
235
+ table_name=arguments.get("table_name"),
236
+ source_path=arguments.get("source_path"),
237
+ )
238
+ raise ValueError(f"未知工具: {name}")
239
+
240
+
241
+ def _build_server() -> Any:
242
+ """构建 MCP Server 实例(官方 SDK 低级 API)。"""
243
+ import anyio
244
+ from mcp.server import Server
245
+ from mcp.types import TextContent, Tool
246
+
247
+ server = Server("precis")
248
+
249
+ @server.list_tools()
250
+ async def list_tools() -> list[Tool]:
251
+ return [
252
+ Tool(name=name, description=_TOOL_DESCRIPTIONS[name], inputSchema=schema)
253
+ for name, schema in _TOOL_SCHEMAS.items()
254
+ ]
255
+
256
+ @server.call_tool()
257
+ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
258
+ # 校验/推断是重同步计算(pandas 加载 + 全表校验),放工作线程执行:
259
+ # 直接在事件循环里阻塞会导致 stdio 读写与工具计算互相等待(Windows 实测死锁)
260
+ try:
261
+ result = await anyio.to_thread.run_sync(_dispatch_tool_sync, name, arguments)
262
+ except ValueError as e:
263
+ # 参数/路径类错误:工具级失败(isError),不崩 server
264
+ return [TextContent(type="text", text=json.dumps({"error": str(e)}, ensure_ascii=False))]
265
+ return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False))]
266
+
267
+ return server
268
+
269
+
270
+ def _warm_up_imports() -> None:
271
+ """启动期(主线程)预热重依赖导入。
272
+
273
+ Windows 实测:首次 tools/call 在 anyio 工作线程内触发 executor→pandas 的
274
+ 重量级 import 会死锁(import 锁与运行中的事件循环互等)。启动期一次性
275
+ 导入后,工具线程内不再发生模块级 import,死锁消除且首调用更快。
276
+ """
277
+ from app.shared.services.schema_inference import infer_schema # noqa: F401
278
+ from app.shared.services.validation import executor # noqa: F401
279
+
280
+
281
+ def main() -> int:
282
+ """precis-mcp 入口:stdio 模式运行 MCP server。"""
283
+ # Windows GBK 控制台下保证协议帧为 UTF-8
284
+ for stream in (sys.stdout, sys.stderr):
285
+ if stream and hasattr(stream, "reconfigure"):
286
+ try:
287
+ stream.reconfigure(encoding="utf-8", errors="replace")
288
+ except (AttributeError, OSError):
289
+ pass
290
+
291
+ from mcp.server import NotificationOptions
292
+ from mcp.server.stdio import stdio_server
293
+
294
+ _warm_up_imports()
295
+
296
+ async def _run() -> None:
297
+ server = _build_server()
298
+ init_options = server.create_initialization_options(
299
+ notification_options=NotificationOptions(
300
+ prompts_changed=False, resources_changed=False, tools_changed=False
301
+ )
302
+ )
303
+ async with stdio_server() as (read_stream, write_stream):
304
+ await server.run(read_stream, write_stream, init_options)
305
+
306
+ asyncio.run(_run())
307
+ return 0
308
+
309
+
310
+ if __name__ == "__main__":
311
+ sys.exit(main())
@@ -0,0 +1,18 @@
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
+ """
17
+ @fileoverview 核心模块入口
18
+ """
@@ -0,0 +1,59 @@
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
+ """@fileoverview 应用包版本解析(CLI/API/欢迎页共用)
17
+
18
+ 版本号单一事实源是仓库根 package.json,发布脚本(scripts/release.mjs)同步到
19
+ backend/pyproject.toml 等六处 manifest;本模块只负责"运行时读当前版本":
20
+
21
+ - 打包环境:PRECIS_APP_VERSION 环境变量(Electron 打包不安装包元数据,
22
+ importlib.metadata 拿不到,环境变量是唯一真实来源)
23
+ - 开发/PyPI 安装环境:包元数据。分发名 2026-09 起为 `precis-cli`
24
+ (PyPI `precis` 已被 2014 年的第三方包占用),旧名 `precis` 仅作为
25
+ 本地存量 editable 安装的回退
26
+ - 都取不到:调用方给定的 fallback
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import os
32
+
33
+ # 分发名候选:新名优先,旧名兼容存量本地安装
34
+ _DISTRIBUTION_NAMES: tuple[str, ...] = ("precis-cli", "precis")
35
+
36
+
37
+ def get_app_version(fallback: str = "0.0.0") -> str:
38
+ """解析当前应用版本。
39
+
40
+ 优先级:PRECIS_APP_VERSION 环境变量 → 包元数据(precis-cli → precis)→ fallback。
41
+
42
+ Args:
43
+ fallback: 全部来源都取不到时返回的兜底版本号
44
+
45
+ Returns:
46
+ 版本号字符串
47
+ """
48
+ env_version = os.environ.get("PRECIS_APP_VERSION")
49
+ if env_version:
50
+ return env_version
51
+
52
+ from importlib import metadata as _md
53
+
54
+ for dist_name in _DISTRIBUTION_NAMES:
55
+ try:
56
+ return _md.version(dist_name)
57
+ except _md.PackageNotFoundError:
58
+ continue
59
+ return fallback
@@ -0,0 +1,119 @@
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
+ """@fileoverview Precis 统一配置管理模块
17
+
18
+ 功能概述:
19
+ - 管理所有配置文件的路径(数据源、Electron、AI 服务商、白名单等)
20
+ - 提供项目级/用户级/系统级配置的优先级加载
21
+ """
22
+
23
+ from pathlib import Path
24
+
25
+
26
+ class ConfigPaths:
27
+ """
28
+ @classdesc 统一配置路径管理类
29
+
30
+ 所有配置文件路径的集中定义,避免硬编码分散在代码各处。
31
+ """
32
+
33
+ # 项目级配置目录名
34
+ PROJECT_CONFIG_DIR = ".precis"
35
+
36
+ # Electron 配置目录
37
+ ELECTRON_DIR = "electron"
38
+
39
+ # 用户级配置目录(用户主目录下)
40
+ USER_CONFIG_DIR = Path.home() / ".precis"
41
+
42
+ @classmethod
43
+ def get_project_config_dir(cls, project_root: str) -> Path:
44
+ """
45
+ @methoddesc 获取项目配置目录路径
46
+
47
+ 参数:
48
+ project_root: 项目根目录的绝对路径字符串
49
+
50
+ 返回:
51
+ 项目配置目录的 Path 对象({project_root}/.precis)
52
+ """
53
+ return Path(project_root) / cls.PROJECT_CONFIG_DIR
54
+
55
+ @classmethod
56
+ def ensure_project_config_dir(cls, project_root: str) -> Path:
57
+ """
58
+ @methoddesc 确保项目配置目录存在,不存在则创建
59
+
60
+ 参数:
61
+ project_root: 项目根目录的绝对路径字符串
62
+
63
+ 返回:
64
+ 已存在或新创建的目录的 Path 对象
65
+ """
66
+ config_dir = cls.get_project_config_dir(project_root)
67
+ config_dir.mkdir(parents=True, exist_ok=True)
68
+ return config_dir
69
+
70
+ # ==================== 数据源配置 ====================
71
+
72
+ @classmethod
73
+ def data_sources(cls, project_root: str) -> Path:
74
+ """
75
+ @methoddesc 数据源和 UI 状态配置文件路径
76
+
77
+ 存储内容:
78
+ - 已添加的数据源列表(文件路径、类型、状态)
79
+ - UI 状态(资源树展开状态等)
80
+ """
81
+ return cls.get_project_config_dir(project_root) / "data_sources.yaml"
82
+
83
+ # ==================== Electron 启动配置 ====================
84
+
85
+ @classmethod
86
+ def electron_launch(cls, project_root: str) -> Path:
87
+ """
88
+ @methoddesc Electron 桌面版启动配置文件路径
89
+
90
+ 存储内容:
91
+ - config_path: 项目配置路径
92
+ - data_path: 数据文件路径
93
+
94
+ 位置:优先放在 electron/ 目录,其次 .precis/
95
+ """
96
+ # 优先使用 electron/ 目录
97
+ electron_dir = Path(project_root) / cls.ELECTRON_DIR
98
+ if electron_dir.exists():
99
+ return electron_dir / "electron_launch.yaml"
100
+ # 备选:放在 .precis/ 目录
101
+ return cls.get_project_config_dir(project_root) / "electron_launch.yaml"
102
+
103
+ # ==================== AI 服务商配置 ====================
104
+
105
+ @classmethod
106
+ def ai_providers(cls) -> Path:
107
+ """
108
+ @methoddesc 用户级 AI 配置文件路径
109
+
110
+ AI Provider 配置是用户级全局设置(包含 API Key 等敏感信息),
111
+ 不属于项目仓库。统一固定存放在 ~/.precis/ai_providers.yaml。
112
+ """
113
+ return cls.USER_CONFIG_DIR / "ai_providers.yaml"
114
+
115
+
116
+ # 便捷导入
117
+ __all__ = [
118
+ "ConfigPaths",
119
+ ]