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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
app/cli/shell/parser.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
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/parser.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI Shell 命令解析与注册模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 管理所有可用命令的注册和查找
|
|
22
|
+
- 解析用户输入并分发到对应命令执行
|
|
23
|
+
- 支持引号包围的参数分割
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- CommandRegistry 维护命令名称与别名的映射
|
|
27
|
+
- CommandParser 分割参数并查找对应命令
|
|
28
|
+
- CommandExecutor 协调解析与上下文执行
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
|
|
33
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
34
|
+
from app.cli.shell.exceptions import CommandNotFoundError
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CommandRegistry:
|
|
38
|
+
"""命令注册表。
|
|
39
|
+
|
|
40
|
+
管理所有可用命令的注册和查找。
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self._commands: dict[str, Command] = {}
|
|
45
|
+
self._aliases: dict[str, str] = {}
|
|
46
|
+
|
|
47
|
+
def register(self, command: Command) -> None:
|
|
48
|
+
"""注册命令。
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
command: 命令实例
|
|
52
|
+
"""
|
|
53
|
+
self._commands[command.name] = command
|
|
54
|
+
for alias in command.aliases:
|
|
55
|
+
self._aliases[alias] = command.name
|
|
56
|
+
|
|
57
|
+
def unregister(self, name: str) -> None:
|
|
58
|
+
"""注销命令。
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
name: 命令名称
|
|
62
|
+
"""
|
|
63
|
+
if name in self._commands:
|
|
64
|
+
command = self._commands[name]
|
|
65
|
+
for alias in command.aliases:
|
|
66
|
+
self._aliases.pop(alias, None)
|
|
67
|
+
self._commands.pop(name)
|
|
68
|
+
|
|
69
|
+
def get(self, name: str) -> Command | None:
|
|
70
|
+
"""获取命令。
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
name: 命令名称或别名
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
命令实例,如果不存在则返回 None
|
|
77
|
+
"""
|
|
78
|
+
if name in self._commands:
|
|
79
|
+
return self._commands[name]
|
|
80
|
+
if name in self._aliases:
|
|
81
|
+
return self._commands[self._aliases[name]]
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def list_commands(self) -> list[str]:
|
|
85
|
+
"""列出所有注册的命令名称。"""
|
|
86
|
+
return list(self._commands.keys())
|
|
87
|
+
|
|
88
|
+
def get_all_commands(self) -> list[Command]:
|
|
89
|
+
"""获取所有已注册的命令。"""
|
|
90
|
+
return list(self._commands.values())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CommandParser:
|
|
94
|
+
"""命令解析器。
|
|
95
|
+
|
|
96
|
+
解析用户输入并分发到对应命令执行。
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, registry: CommandRegistry):
|
|
100
|
+
self.registry = registry
|
|
101
|
+
|
|
102
|
+
def parse(self, input_line: str) -> tuple[Command | None, list[str]]:
|
|
103
|
+
"""解析输入行。
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
input_line: 用户输入的行
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
(命令实例, 参数列表) 元组
|
|
110
|
+
"""
|
|
111
|
+
input_line = input_line.strip()
|
|
112
|
+
if not input_line:
|
|
113
|
+
return None, []
|
|
114
|
+
|
|
115
|
+
parts = self._split_args(input_line)
|
|
116
|
+
if not parts:
|
|
117
|
+
return None, []
|
|
118
|
+
|
|
119
|
+
command_name = parts[0]
|
|
120
|
+
args = parts[1:]
|
|
121
|
+
|
|
122
|
+
command = self.registry.get(command_name)
|
|
123
|
+
if command is None:
|
|
124
|
+
raise CommandNotFoundError(command_name)
|
|
125
|
+
|
|
126
|
+
return command, args
|
|
127
|
+
|
|
128
|
+
def _split_args(self, line: str) -> list[str]:
|
|
129
|
+
"""分割命令行参数(4.6 重写:引号状态机)。
|
|
130
|
+
|
|
131
|
+
- 全角空格(U+3000)先归一为半角再分词
|
|
132
|
+
- 撇号(')与双引号(")同作引号配对处理,支持反斜杠转义引号
|
|
133
|
+
- 输入结束引号未闭合时显式报错(原实现静默合并剩余输入,掩盖真实根因)
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
line: 输入行
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
参数列表
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: 引号未闭合
|
|
143
|
+
"""
|
|
144
|
+
# 全角空格归一(中文输入法常见,归一后按普通分隔符处理)
|
|
145
|
+
normalized = line.replace(" ", " ")
|
|
146
|
+
|
|
147
|
+
tokens: list[str] = []
|
|
148
|
+
current: list[str] = []
|
|
149
|
+
in_quote = False
|
|
150
|
+
quote_char: str | None = None
|
|
151
|
+
|
|
152
|
+
i = 0
|
|
153
|
+
while i < len(normalized):
|
|
154
|
+
char = normalized[i]
|
|
155
|
+
|
|
156
|
+
if in_quote:
|
|
157
|
+
if char == "\\" and i + 1 < len(normalized) and normalized[i + 1] in ('"', "'"):
|
|
158
|
+
# 转义引号:保留引号本身
|
|
159
|
+
current.append(normalized[i + 1])
|
|
160
|
+
i += 2
|
|
161
|
+
continue
|
|
162
|
+
if char == quote_char:
|
|
163
|
+
in_quote = False
|
|
164
|
+
quote_char = None
|
|
165
|
+
else:
|
|
166
|
+
current.append(char)
|
|
167
|
+
else:
|
|
168
|
+
if char in ('"', "'"):
|
|
169
|
+
in_quote = True
|
|
170
|
+
quote_char = char
|
|
171
|
+
elif char == " ":
|
|
172
|
+
if current:
|
|
173
|
+
tokens.append("".join(current))
|
|
174
|
+
current = []
|
|
175
|
+
else:
|
|
176
|
+
current.append(char)
|
|
177
|
+
i += 1
|
|
178
|
+
|
|
179
|
+
if in_quote and quote_char:
|
|
180
|
+
raise ValueError(f"引号未闭合: 缺少配对的 {quote_char}")
|
|
181
|
+
|
|
182
|
+
if current:
|
|
183
|
+
tokens.append("".join(current))
|
|
184
|
+
|
|
185
|
+
return tokens
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class CommandExecutor:
|
|
189
|
+
"""命令执行器。
|
|
190
|
+
|
|
191
|
+
负责执行解析后的命令。
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(self, parser: CommandParser, context: ProjectContext):
|
|
195
|
+
self.parser = parser
|
|
196
|
+
self.context = context
|
|
197
|
+
|
|
198
|
+
def execute(self, input_line: str) -> CommandResult:
|
|
199
|
+
"""执行命令。
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
input_line: 用户输入的行
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
命令执行结果
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
command, args = self.parser.parse(input_line)
|
|
209
|
+
if command is None:
|
|
210
|
+
return CommandResult.error("请输入命令")
|
|
211
|
+
|
|
212
|
+
return command.execute(args, self.context)
|
|
213
|
+
except CommandNotFoundError as e:
|
|
214
|
+
# 命令不存在属参数错误,单发模式下退出码 2(工具自身错误)
|
|
215
|
+
return CommandResult.error(str(e), exit_code=2)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
# 命令异常崩溃属工具自身错误,单发模式下退出码 2(区别于校验发现违规的 1)
|
|
218
|
+
return CommandResult.error(f"命令执行失败: {e}", exit_code=2)
|
|
219
|
+
|
|
220
|
+
def execute_with_args(self, args: list[str]) -> CommandResult:
|
|
221
|
+
"""使用预分割参数列表执行命令。
|
|
222
|
+
|
|
223
|
+
跳过 join/split 循环,保留参数中的空格(如文件路径)。
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
args: 预分割的参数列表,第一个元素为命令名
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
命令执行结果
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
if not args:
|
|
233
|
+
return CommandResult.error("请输入命令")
|
|
234
|
+
|
|
235
|
+
command = self.parser.registry.get(args[0])
|
|
236
|
+
if command is None:
|
|
237
|
+
raise CommandNotFoundError(args[0])
|
|
238
|
+
|
|
239
|
+
return command.execute(args[1:], self.context)
|
|
240
|
+
except CommandNotFoundError as e:
|
|
241
|
+
# 命令不存在属参数错误,单发模式下退出码 2(工具自身错误)
|
|
242
|
+
return CommandResult.error(str(e), exit_code=2)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
# 命令异常崩溃属工具自身错误,单发模式下退出码 2(区别于校验发现违规的 1)
|
|
245
|
+
return CommandResult.error(f"命令执行失败: {e}", exit_code=2)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def command(name: str, aliases: list[str] | None = None) -> Callable:
|
|
249
|
+
"""命令装饰器。
|
|
250
|
+
|
|
251
|
+
用于注册命令类的装饰器。
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
name: 命令名称
|
|
255
|
+
aliases: 命令别名
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
装饰器函数
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
def decorator(cls: type[Command]) -> type[Command]:
|
|
262
|
+
cls.name = name
|
|
263
|
+
cls.aliases = aliases or []
|
|
264
|
+
return cls
|
|
265
|
+
|
|
266
|
+
return decorator
|
app/cli/start.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
Precis Web Mode - Start Server
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
precis-start [--work-dir <path>] [--port <port>] [--no-browser]
|
|
21
|
+
|
|
22
|
+
Starts the FastAPI backend server and opens the browser for Web mode.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import webbrowser
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from app.shared.core.config.server import (
|
|
34
|
+
DEFAULT_BACKEND_HOST,
|
|
35
|
+
acquire_port,
|
|
36
|
+
clear_port_file,
|
|
37
|
+
register_port_file_cleanup,
|
|
38
|
+
resolve_port,
|
|
39
|
+
write_port_file,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_args() -> argparse.Namespace:
|
|
44
|
+
parser = argparse.ArgumentParser(
|
|
45
|
+
prog="precis-start",
|
|
46
|
+
description="Start Precis in Web mode",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--work-dir",
|
|
50
|
+
default=None,
|
|
51
|
+
help="Precis projects work directory (default: env PRECIS_WORK_DIR or current dir)",
|
|
52
|
+
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--port",
|
|
55
|
+
type=int,
|
|
56
|
+
default=None,
|
|
57
|
+
help="Server port (default: 0=OS dynamic allocation; fixed port via VITE_BACKEND_PORT)",
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--no-browser",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="Do not automatically open browser",
|
|
63
|
+
)
|
|
64
|
+
return parser.parse_args()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve_work_dir(arg_work_dir: str | None) -> str:
|
|
68
|
+
"""确定工作目录:CLI 参数 > 环境变量 > 用户配置 > 当前目录。"""
|
|
69
|
+
if arg_work_dir:
|
|
70
|
+
return os.path.abspath(arg_work_dir)
|
|
71
|
+
|
|
72
|
+
env_dir = os.environ.get("PRECIS_WORK_DIR")
|
|
73
|
+
if env_dir:
|
|
74
|
+
return os.path.abspath(env_dir)
|
|
75
|
+
|
|
76
|
+
config_path = Path.home() / ".precis" / "config.yaml"
|
|
77
|
+
if config_path.exists():
|
|
78
|
+
try:
|
|
79
|
+
import yaml
|
|
80
|
+
|
|
81
|
+
with open(config_path, encoding="utf-8") as f:
|
|
82
|
+
config = yaml.safe_load(f)
|
|
83
|
+
if isinstance(config, dict) and "work_dir" in config:
|
|
84
|
+
work_dir_value = config["work_dir"]
|
|
85
|
+
if isinstance(work_dir_value, str):
|
|
86
|
+
return os.path.abspath(work_dir_value)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
return os.path.abspath(".")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _resolve_port(arg_port: int | None) -> int:
|
|
94
|
+
"""确定端口:委托给 server.resolve_port(CLI 参数 > 环境变量 > 默认 0)。
|
|
95
|
+
|
|
96
|
+
0 表示交由 OS 动态分配。
|
|
97
|
+
"""
|
|
98
|
+
return resolve_port(arg_port)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> int:
|
|
102
|
+
"""Main entry point for precis-start."""
|
|
103
|
+
args = _parse_args()
|
|
104
|
+
work_dir = _resolve_work_dir(args.work_dir)
|
|
105
|
+
preferred_port = _resolve_port(args.port)
|
|
106
|
+
|
|
107
|
+
if not os.path.isdir(work_dir):
|
|
108
|
+
print(f"错误:工作目录不存在: {work_dir}", file=sys.stderr)
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
# OS 原子分配端口(preferred_port=0 时动态分配),并写入端口文件供外部发现
|
|
112
|
+
actual_port = acquire_port(preferred_port)
|
|
113
|
+
write_port_file(actual_port)
|
|
114
|
+
# 传递给 uvicorn --reload 子进程,app 启动时据此自愈重写端口文件(见 api/main.py)
|
|
115
|
+
os.environ["PRECIS_BACKEND_PORT_ACTUAL"] = str(actual_port)
|
|
116
|
+
# 注册端口文件清理兜底(atexit + signal)
|
|
117
|
+
register_port_file_cleanup()
|
|
118
|
+
|
|
119
|
+
print(f" ✓ Work directory: {work_dir}")
|
|
120
|
+
print(f" ✓ Port: {actual_port}")
|
|
121
|
+
|
|
122
|
+
# Scan and count projects
|
|
123
|
+
project_count = 0
|
|
124
|
+
if os.path.isdir(work_dir):
|
|
125
|
+
for entry in os.scandir(work_dir):
|
|
126
|
+
if entry.is_dir():
|
|
127
|
+
manifest = os.path.join(entry.path, "project.precis.yaml")
|
|
128
|
+
if os.path.isfile(manifest):
|
|
129
|
+
project_count += 1
|
|
130
|
+
print(f" ✓ Found {project_count} project(s)")
|
|
131
|
+
|
|
132
|
+
# Inject work_dir into app state via environment variable
|
|
133
|
+
os.environ["PRECIS_WORK_DIR"] = work_dir
|
|
134
|
+
|
|
135
|
+
url = f"http://localhost:{actual_port}"
|
|
136
|
+
|
|
137
|
+
# §4.9: 浏览器在服务就绪后才打开——原实现先开浏览器再起 uvicorn,首开大概率
|
|
138
|
+
# "连接被拒绝"。后台线程轮询端口就绪后 open(uvicorn.run 阻塞主线程)。
|
|
139
|
+
def _open_browser_when_ready() -> None:
|
|
140
|
+
import socket
|
|
141
|
+
import time as _time
|
|
142
|
+
|
|
143
|
+
for _ in range(150): # 最长等 75s
|
|
144
|
+
try:
|
|
145
|
+
with socket.create_connection(("127.0.0.1", actual_port), timeout=0.5):
|
|
146
|
+
if not args.no_browser:
|
|
147
|
+
print(f"\n → Opening browser at {url}")
|
|
148
|
+
webbrowser.open(url)
|
|
149
|
+
return
|
|
150
|
+
except OSError:
|
|
151
|
+
_time.sleep(0.5)
|
|
152
|
+
|
|
153
|
+
import threading
|
|
154
|
+
|
|
155
|
+
browser_thread = threading.Thread(target=_open_browser_when_ready, daemon=True)
|
|
156
|
+
browser_thread.start()
|
|
157
|
+
|
|
158
|
+
print("\n Server starting... (Ctrl+C to stop)")
|
|
159
|
+
print(f" {url}")
|
|
160
|
+
print()
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Start uvicorn server
|
|
164
|
+
import uvicorn
|
|
165
|
+
|
|
166
|
+
uvicorn.run(
|
|
167
|
+
"app.api.main:app",
|
|
168
|
+
host=DEFAULT_BACKEND_HOST,
|
|
169
|
+
port=actual_port,
|
|
170
|
+
log_level="info",
|
|
171
|
+
)
|
|
172
|
+
finally:
|
|
173
|
+
# 退出时清理端口文件
|
|
174
|
+
clear_port_file()
|
|
175
|
+
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
sys.exit(main())
|
app/cli_main.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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_main.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview Precis CLI 入口模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 `python app/cli_main.py` 直接启动交互式 CLI Shell 的能力
|
|
22
|
+
- 配置 PYTHONDONTWRITEBYTECODE 禁止生成字节码缓存
|
|
23
|
+
- 自动将 backend 目录加入 Python 路径
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 兼容两种启动方式: 直接运行与模块运行 (-m)
|
|
27
|
+
- 委托给 cli.shell.main 执行交互式 Shell 主循环
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
python app/cli_main.py
|
|
31
|
+
|
|
32
|
+
输出示例:
|
|
33
|
+
Precis CLI Shell 交互式界面
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
|
|
38
|
+
os.environ.setdefault("PYTHONDONTWRITEBYTECODE", "1")
|
|
39
|
+
|
|
40
|
+
import sys
|
|
41
|
+
|
|
42
|
+
backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
43
|
+
if backend_dir not in sys.path:
|
|
44
|
+
sys.path.insert(0, backend_dir)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from cli.shell.main import main
|
|
48
|
+
except ImportError:
|
|
49
|
+
from app.cli.shell.main import main
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
sys.exit(main())
|