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
|
@@ -0,0 +1,67 @@
|
|
|
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/exit.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI Shell 退出命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 exit 命令退出交互式 Shell
|
|
22
|
+
- 返回 should_exit=True 的结果使主循环终止
|
|
23
|
+
- 全局快捷键 qq 可直接退出程序
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ExitCommand 继承 Command 基类
|
|
27
|
+
- execute() 直接返回 CommandResult.exit(),主循环检测 should_exit 后退出
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
precis> exit
|
|
31
|
+
|
|
32
|
+
输出示例:
|
|
33
|
+
CommandResult.exit("再见!")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExitCommand(Command):
|
|
40
|
+
"""退出命令。
|
|
41
|
+
|
|
42
|
+
当用户输入 exit 时触发,使 Shell 主循环终止。
|
|
43
|
+
全局快捷键 qq 仍可直接退出程序。
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
super().__init__("exit")
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def description(self) -> str:
|
|
51
|
+
return "退出 Precis CLI"
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def usage(self) -> str:
|
|
55
|
+
return "exit"
|
|
56
|
+
|
|
57
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
58
|
+
"""执行退出命令。
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
args: 命令参数列表(此命令不需要参数)
|
|
62
|
+
context: 项目上下文
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
带有 should_exit=True 的结果,触发 Shell 退出
|
|
66
|
+
"""
|
|
67
|
+
return CommandResult.exit("再见!")
|
|
@@ -0,0 +1,104 @@
|
|
|
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/help.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI Shell 帮助命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 help 命令显示所有可用命令列表
|
|
22
|
+
- 支持查看特定命令的详细帮助文本
|
|
23
|
+
- 格式化输出命令名称与描述
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- HelpCommand 接收 CommandRegistry 实例,从中获取所有注册命令
|
|
27
|
+
- 无参数时显示所有命令的简要列表
|
|
28
|
+
- 带参数时显示指定命令的详细帮助文本
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
precis> help
|
|
32
|
+
precis> help validate
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
可用命令列表或特定命令的 help_text
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from rich.console import Console
|
|
39
|
+
from rich.table import Table
|
|
40
|
+
|
|
41
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
42
|
+
from app.cli.shell.parser import CommandRegistry
|
|
43
|
+
|
|
44
|
+
_console = Console()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HelpCommand(Command):
|
|
48
|
+
"""帮助命令。
|
|
49
|
+
|
|
50
|
+
显示所有可用命令的列表,或查看特定命令的详细帮助。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, registry: CommandRegistry):
|
|
54
|
+
super().__init__("help", aliases=["?"])
|
|
55
|
+
self._registry = registry
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def description(self) -> str:
|
|
59
|
+
return "显示所有可用命令或特定命令的帮助信息"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def usage(self) -> str:
|
|
63
|
+
return "help [command]"
|
|
64
|
+
|
|
65
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
66
|
+
if not args:
|
|
67
|
+
return self._show_all_commands()
|
|
68
|
+
else:
|
|
69
|
+
return self._show_command_help(args[0])
|
|
70
|
+
|
|
71
|
+
def _show_all_commands(self) -> CommandResult:
|
|
72
|
+
commands = self._registry.get_all_commands()
|
|
73
|
+
|
|
74
|
+
_console.print()
|
|
75
|
+
_console.print("[bold]可用命令:[/bold]")
|
|
76
|
+
_console.print()
|
|
77
|
+
|
|
78
|
+
table = Table(show_header=False, box=None, padding=(0, 2, 0, 0))
|
|
79
|
+
table.add_column("Command", style="cyan bold", min_width=18)
|
|
80
|
+
table.add_column("Description")
|
|
81
|
+
|
|
82
|
+
for cmd in commands:
|
|
83
|
+
# 聚合命令(如 config/project/ai)在描述后追加子命令提示,
|
|
84
|
+
# 让用户无需先输入命令即可发现 inspect 等子命令的存在。
|
|
85
|
+
subs = cmd.list_subcommands()
|
|
86
|
+
if subs:
|
|
87
|
+
desc = f"{cmd.description} [dim](子命令: {', '.join(subs)})[/dim]"
|
|
88
|
+
else:
|
|
89
|
+
desc = cmd.description
|
|
90
|
+
table.add_row(cmd.name, desc)
|
|
91
|
+
|
|
92
|
+
_console.print(table)
|
|
93
|
+
_console.print()
|
|
94
|
+
_console.print("[dim]输入 'help <command>' 查看特定命令的详细帮助[/dim]")
|
|
95
|
+
_console.print("[dim]输入 'exit' 或 'quit' 退出 CLI,'qq' 强制退出[/dim]")
|
|
96
|
+
|
|
97
|
+
return CommandResult.ok("")
|
|
98
|
+
|
|
99
|
+
def _show_command_help(self, command_name: str) -> CommandResult:
|
|
100
|
+
command = self._registry.get(command_name)
|
|
101
|
+
if command is None:
|
|
102
|
+
return CommandResult.error(f"未知命令: {command_name}")
|
|
103
|
+
|
|
104
|
+
return CommandResult.ok(command.help_text)
|
|
@@ -0,0 +1,156 @@
|
|
|
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/infer_schema.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI infer-schema 子命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 读取 CSV/Excel/JSON 数据文件头部,推断列类型并输出合法 schema YAML
|
|
22
|
+
- 默认输出到 stdout(管道友好),--output 写入指定文件
|
|
23
|
+
- 推断逻辑在 services 层(app.shared.services.schema_inference),本模块只做参数解析
|
|
24
|
+
|
|
25
|
+
使用示例:
|
|
26
|
+
precis infer-schema data/orders.csv
|
|
27
|
+
precis infer-schema data/orders.csv --output schemas/orders.schema.yaml
|
|
28
|
+
precis infer-schema data/orders.csv --id <uuid> --name orders --source-path ../orders.csv
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
38
|
+
|
|
39
|
+
# 选项表:选项名 → 结果键
|
|
40
|
+
_INFER_OPTIONS: dict[str, str] = {
|
|
41
|
+
"--output": "output",
|
|
42
|
+
"-o": "output",
|
|
43
|
+
"--id": "table_id",
|
|
44
|
+
"--name": "table_name",
|
|
45
|
+
"--source-path": "source_path",
|
|
46
|
+
"--sample-rows": "sample_rows",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_infer_args(args: list[str]) -> dict:
|
|
51
|
+
"""解析 infer-schema 参数:选项及值剥离,剩余首个位置参数为数据文件路径。
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
args: 命令参数列表
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
包含 data_file/output/table_id/table_name/source_path/sample_rows 的字典
|
|
58
|
+
"""
|
|
59
|
+
result: dict[str, str | None] = {
|
|
60
|
+
"data_file": None,
|
|
61
|
+
"output": None,
|
|
62
|
+
"table_id": None,
|
|
63
|
+
"table_name": None,
|
|
64
|
+
"source_path": None,
|
|
65
|
+
"sample_rows": None,
|
|
66
|
+
}
|
|
67
|
+
positional: list[str] = []
|
|
68
|
+
i = 0
|
|
69
|
+
while i < len(args):
|
|
70
|
+
arg = args[i]
|
|
71
|
+
key = _INFER_OPTIONS.get(arg)
|
|
72
|
+
if key and i + 1 < len(args):
|
|
73
|
+
result[key] = args[i + 1]
|
|
74
|
+
i += 2
|
|
75
|
+
elif key:
|
|
76
|
+
i += 1
|
|
77
|
+
else:
|
|
78
|
+
positional.append(arg)
|
|
79
|
+
i += 1
|
|
80
|
+
if positional:
|
|
81
|
+
result["data_file"] = positional[0]
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class InferSchemaCommand(Command):
|
|
86
|
+
"""schema 推断命令:从数据文件头部推断列类型,输出 schema YAML。"""
|
|
87
|
+
|
|
88
|
+
def __init__(self) -> None:
|
|
89
|
+
super().__init__("infer-schema", aliases=["infer"])
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def description(self) -> str:
|
|
93
|
+
return "从数据文件推断 schema(列类型),输出 YAML 草稿"
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def usage(self) -> str:
|
|
97
|
+
return (
|
|
98
|
+
"infer-schema <数据文件> [--output <path>] [--id <uuid>] [--name <表名>] "
|
|
99
|
+
"[--source-path <数据相对路径>] [--sample-rows <N>]"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
103
|
+
"""执行 schema 推断。
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
args: 命令参数(数据文件路径 + 选项)
|
|
107
|
+
context: 命令上下文(本命令不依赖项目状态)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
成功时 data 携带推断结果字典;参数/文件错误返回 exit_code=2
|
|
111
|
+
"""
|
|
112
|
+
parsed = _parse_infer_args(args)
|
|
113
|
+
data_file = parsed["data_file"]
|
|
114
|
+
|
|
115
|
+
if not data_file:
|
|
116
|
+
return CommandResult.error("缺少数据文件路径参数", exit_code=2)
|
|
117
|
+
|
|
118
|
+
sample_rows: int = 1000
|
|
119
|
+
if parsed["sample_rows"] is not None:
|
|
120
|
+
try:
|
|
121
|
+
sample_rows = int(parsed["sample_rows"]) # type: ignore[arg-type]
|
|
122
|
+
if sample_rows <= 0:
|
|
123
|
+
raise ValueError
|
|
124
|
+
except ValueError:
|
|
125
|
+
return CommandResult.error(f"--sample-rows 必须为正整数: {parsed['sample_rows']}", exit_code=2)
|
|
126
|
+
|
|
127
|
+
from app.shared.services.schema_inference import infer_schema
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
schema = infer_schema(
|
|
131
|
+
data_file,
|
|
132
|
+
sample_rows=sample_rows,
|
|
133
|
+
table_id=parsed["table_id"],
|
|
134
|
+
table_name=parsed["table_name"],
|
|
135
|
+
source_path=parsed["source_path"],
|
|
136
|
+
)
|
|
137
|
+
except ValueError as e:
|
|
138
|
+
return CommandResult.error(str(e), exit_code=2)
|
|
139
|
+
|
|
140
|
+
yaml_text = yaml.safe_dump(schema, allow_unicode=True, sort_keys=False)
|
|
141
|
+
|
|
142
|
+
if parsed["output"]:
|
|
143
|
+
output_path = Path(parsed["output"])
|
|
144
|
+
try:
|
|
145
|
+
# 父目录不存在时创建,方便直接写入 <proj>/schemas/ 子路径
|
|
146
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
output_path.write_text(yaml_text, encoding="utf-8")
|
|
148
|
+
except OSError as e:
|
|
149
|
+
return CommandResult.error(f"写入输出文件失败: {e}", exit_code=2)
|
|
150
|
+
return CommandResult.ok(
|
|
151
|
+
f"schema 已写入: {output_path}", data={"schema": schema, "output": str(output_path)}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# stdout 输出纯 YAML(无 rich 装饰,管道/重定向友好)
|
|
155
|
+
print(yaml_text, end="")
|
|
156
|
+
return CommandResult.ok("", data={"schema": schema})
|
|
@@ -0,0 +1,201 @@
|
|
|
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/open.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI Shell 打开项目命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 open 命令:打开并切换工作项目,维护项目历史记录
|
|
22
|
+
- 管理项目打开历史(持久化到 ~/.precis_project_history)
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- OpenCommand 作为顶层命令注册,同时被 ProjectCommand 复用为子命令
|
|
26
|
+
- 历史记录与项目打开的纯逻辑委托给 shared_services.project_ops(CLI/TUI 同源)
|
|
27
|
+
- 本模块只保留交互部分(InteractiveMenu 历史选择)与命令编排
|
|
28
|
+
- 错误处理通过 CommandResult.error 直接返回,不抛出异常
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
open /path/to/project
|
|
32
|
+
project open /path/to/project
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
CommandResult(success=True, message="已切换到项目: /path/to/project")
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
from app.cli.shared_services import project_ops
|
|
41
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
42
|
+
|
|
43
|
+
# 向后兼容 re-export:历史代码与集成测试通过 open 模块访问历史函数与常量。
|
|
44
|
+
# 真正的实现已迁移到 shared_services.project_ops(CLI/TUI 同源)。
|
|
45
|
+
HISTORY_FILE = project_ops.HISTORY_FILE
|
|
46
|
+
MAX_HISTORY = project_ops.MAX_HISTORY
|
|
47
|
+
_load_history = project_ops.load_history
|
|
48
|
+
_save_history = project_ops._save_history
|
|
49
|
+
_add_to_history = project_ops.add_to_history
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OpenCommand(Command):
|
|
53
|
+
"""打开项目命令。
|
|
54
|
+
|
|
55
|
+
支持三种调用方式:
|
|
56
|
+
open → 从历史记录交互选择(箭头/数字键,Enter=最近项目)
|
|
57
|
+
open <N> → 打开历史列表第 N 个项目(1-based)
|
|
58
|
+
open <项目路径> → 按路径打开项目
|
|
59
|
+
|
|
60
|
+
打开成功后更新上下文(project_path/project_config)并写入历史记录。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
super().__init__("open", aliases=["o"])
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def description(self) -> str:
|
|
68
|
+
return "打开一个项目目录并切换当前上下文(无参数从历史选择)"
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def usage(self) -> str:
|
|
72
|
+
return "open [项目路径 | 序号](无参数则从历史选择)"
|
|
73
|
+
|
|
74
|
+
def execute(self, args: list[str], ctx: ProjectContext) -> CommandResult:
|
|
75
|
+
"""执行打开项目命令。
|
|
76
|
+
|
|
77
|
+
支持三种调用方式:
|
|
78
|
+
open → 方案A:无参数,从历史记录交互选择
|
|
79
|
+
open <N> → 方案B:打开历史列表第 N 个项目(1-based)
|
|
80
|
+
open <项目路径> → 按路径打开项目
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
args: 命令参数列表
|
|
84
|
+
ctx: 项目上下文,用于保存当前项目路径
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
命令执行结果
|
|
88
|
+
"""
|
|
89
|
+
# 方案A:无参数 → 交互式从历史选择
|
|
90
|
+
if not args:
|
|
91
|
+
return self._open_from_history(ctx)
|
|
92
|
+
|
|
93
|
+
# 方案B:纯数字参数 → 按历史序号打开(拦截在路径解析之前,
|
|
94
|
+
# 避免 "1" 被当成名为 "1" 的路径;真有同名目录可用 open ./1 绕过)
|
|
95
|
+
if args[0].isdigit():
|
|
96
|
+
return self._open_by_index(args[0], ctx)
|
|
97
|
+
|
|
98
|
+
# 默认:按路径打开
|
|
99
|
+
return self._do_open_path(os.path.abspath(args[0]), ctx)
|
|
100
|
+
|
|
101
|
+
def _do_open_path(self, project_path: str, ctx: ProjectContext) -> CommandResult:
|
|
102
|
+
"""按绝对路径打开项目的公共逻辑(供方案A/B/路径模式复用)。
|
|
103
|
+
|
|
104
|
+
委托 shared_services.project_ops.open_project 执行纯逻辑(存在性校验 +
|
|
105
|
+
写历史 + 加载清单),再更新 CLI 上下文。
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
project_path: 项目目录的绝对路径
|
|
109
|
+
ctx: 命令上下文
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
命令执行结果
|
|
113
|
+
"""
|
|
114
|
+
result = project_ops.open_project(project_path)
|
|
115
|
+
if result.success:
|
|
116
|
+
# 更新上下文中的项目路径与配置
|
|
117
|
+
ctx.project_path = result.project_path
|
|
118
|
+
ctx.project_config = result.config
|
|
119
|
+
return CommandResult(success=result.success, message=result.message)
|
|
120
|
+
|
|
121
|
+
def _open_from_history(self, ctx: ProjectContext) -> CommandResult:
|
|
122
|
+
"""方案A:无参数时从历史记录交互选择项目。
|
|
123
|
+
|
|
124
|
+
使用 InteractiveMenu 渲染历史项目列表(光标默认停在最近项目,
|
|
125
|
+
直接 Enter 即可打开上次项目)。空历史或取消时返回提示,不报错。
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
ctx: 命令上下文
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
命令执行结果
|
|
132
|
+
"""
|
|
133
|
+
# 延迟导入:InteractiveMenu 依赖 readchar,仅在交互分支才需要
|
|
134
|
+
from app.cli.shell.interactive_menu import InteractiveMenu
|
|
135
|
+
|
|
136
|
+
history = project_ops.load_history()
|
|
137
|
+
if not history:
|
|
138
|
+
return CommandResult(
|
|
139
|
+
success=True,
|
|
140
|
+
message="暂无项目打开历史。请先使用 'open <项目路径>' 打开一个项目。",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# 构建菜单项:key=项目路径,label=项目名(优先 manifest,否则目录名),description=完整路径
|
|
144
|
+
menu = InteractiveMenu("从历史记录中选择项目:", show_cancel=True)
|
|
145
|
+
for item in history:
|
|
146
|
+
path = item.get("path", "")
|
|
147
|
+
if not path:
|
|
148
|
+
continue
|
|
149
|
+
label = project_ops.resolve_project_label(path)
|
|
150
|
+
menu.add_item(key=path, label=label, description=path)
|
|
151
|
+
|
|
152
|
+
if not menu.items:
|
|
153
|
+
return CommandResult(
|
|
154
|
+
success=True,
|
|
155
|
+
message="历史记录中无有效项目路径。请使用 'open <项目路径>' 打开项目。",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
selected_path = menu.show()
|
|
159
|
+
if selected_path is None:
|
|
160
|
+
# 用户取消(ESC / 0 / 选了取消项)
|
|
161
|
+
return CommandResult(success=True, message="已取消。")
|
|
162
|
+
|
|
163
|
+
return self._do_open_path(selected_path, ctx)
|
|
164
|
+
|
|
165
|
+
def _open_by_index(self, index_str: str, ctx: ProjectContext) -> CommandResult:
|
|
166
|
+
"""方案B:按历史序号打开项目(1-based)。
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
index_str: 用户输入的纯数字字符串
|
|
170
|
+
ctx: 命令上下文
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
命令执行结果
|
|
174
|
+
"""
|
|
175
|
+
history = project_ops.load_history()
|
|
176
|
+
if not history:
|
|
177
|
+
return CommandResult(
|
|
178
|
+
success=False,
|
|
179
|
+
message="暂无项目打开历史,无法按序号打开。请使用 'open <项目路径>' 打开项目。",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 解析序号(已在 execute 中确认 isdigit(),此处再防御一次)
|
|
183
|
+
try:
|
|
184
|
+
index = int(index_str)
|
|
185
|
+
except ValueError:
|
|
186
|
+
return CommandResult(success=False, message=f"无效的序号: {index_str}")
|
|
187
|
+
|
|
188
|
+
if index < 1 or index > len(history):
|
|
189
|
+
return CommandResult(
|
|
190
|
+
success=False,
|
|
191
|
+
message=f"无此历史项: {index}(共 {len(history)} 项,使用 'project history' 查看列表)",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
path = history[index - 1].get("path", "")
|
|
195
|
+
if not path:
|
|
196
|
+
return CommandResult(success=False, message=f"历史项 {index} 缺少路径信息")
|
|
197
|
+
|
|
198
|
+
return self._do_open_path(path, ctx)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = ["OpenCommand"]
|