py-wlcli 0.8.0__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.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
wl_cli/env_status.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Shared payload builder for the ``wl env check`` probe (issue #221).
|
|
2
|
+
|
|
3
|
+
``wl env check`` (the ``wl_cli.env`` domain) and the deprecated
|
|
4
|
+
``wl self capabilities`` alias (``wl_cli.commands``) must answer with the same
|
|
5
|
+
five-state envelope. The quality domain may not import the env domain (#220),
|
|
6
|
+
so the builder lives here in the shared layer and both commands call it.
|
|
7
|
+
|
|
8
|
+
The probe reports **the wl that is running**: its version, its interpreter and
|
|
9
|
+
the shared platform matrix from :func:`wl_cli.platform.platform_payload`
|
|
10
|
+
(wlcc #321). It does not look up another ``wl`` on ``PATH`` and it writes
|
|
11
|
+
nothing. The probe runs with a throwaway temporary directory as its CWD, so a
|
|
12
|
+
future probe cannot create or rewrite ``.wl/`` in the caller's directory; the
|
|
13
|
+
startup self-sync is independently skipped by the dispatcher for read-only
|
|
14
|
+
commands (#220).
|
|
15
|
+
|
|
16
|
+
Envelope contract: ``status`` is ``ok`` for a supported interpreter,
|
|
17
|
+
``unverifiable`` when the platform matrix rejects it (unreachable for an
|
|
18
|
+
installed wl -- the wheel requires Python 3.11+), and ``error`` only when the
|
|
19
|
+
payload itself cannot be built. The envelope's ``reason`` is the platform
|
|
20
|
+
reason when unsupported; the matrix's own ``reason`` is always carried as
|
|
21
|
+
``platform_reason``, so the two keys never collide.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import shlex
|
|
28
|
+
import sys
|
|
29
|
+
import tempfile
|
|
30
|
+
from contextlib import contextmanager
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
from . import __version__
|
|
34
|
+
from .capabilities import CAPABILITY_SINCE, capability_names, capability_since
|
|
35
|
+
from .envelope import OK_STATUSES, dump, exit_code
|
|
36
|
+
from .platform import platform_payload
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from collections.abc import Generator
|
|
40
|
+
|
|
41
|
+
#: Stable tool name in the envelope's ``tool`` key (the alias reuses it: both
|
|
42
|
+
#: commands answer the same question, so consumers key off one name).
|
|
43
|
+
TOOL = "env check"
|
|
44
|
+
|
|
45
|
+
#: Minimum release the repair hint and ``wl env repair`` install. Derived from
|
|
46
|
+
#: the shared capability registry -- ``env.check`` first ships in the release
|
|
47
|
+
#: the repair should install -- so the floor is not a second version literal
|
|
48
|
+
#: that can drift from the registry (#227). ``VERSION`` itself is bumped by the
|
|
49
|
+
#: release process, not by this change.
|
|
50
|
+
INSTALL_FLOOR = CAPABILITY_SINCE["env.check"]
|
|
51
|
+
|
|
52
|
+
#: The repair command as argv -- executed literally, with no shell (so the
|
|
53
|
+
#: ``>=`` spec can never be reinterpreted as a redirection) and no ``--force``
|
|
54
|
+
#: (uv treats an identical install as a no-op, which is what makes repair
|
|
55
|
+
#: idempotent).
|
|
56
|
+
INSTALL_ARGV: tuple[str, ...] = (
|
|
57
|
+
"uv",
|
|
58
|
+
"tool",
|
|
59
|
+
"install",
|
|
60
|
+
"--python",
|
|
61
|
+
"3.12",
|
|
62
|
+
f"py-wlcli>={INSTALL_FLOOR}",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
#: Human-readable form derived from the argv, so the printed command and the
|
|
66
|
+
#: executed argv cannot drift apart (wlcc #266: printed == executed).
|
|
67
|
+
INSTALL_COMMAND = shlex.join(INSTALL_ARGV)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@contextmanager
|
|
71
|
+
def _isolated_cwd() -> Generator[None, None, None]:
|
|
72
|
+
"""Run the read-only probe with a throwaway directory as the CWD.
|
|
73
|
+
|
|
74
|
+
``wl env check`` must not create or rewrite ``.wl/`` in the caller's
|
|
75
|
+
directory. Running the probe in a temporary CWD keeps that guarantee even
|
|
76
|
+
if a future probe grows a relative write; the previous CWD is restored on
|
|
77
|
+
every exit path.
|
|
78
|
+
"""
|
|
79
|
+
previous = os.getcwd()
|
|
80
|
+
with tempfile.TemporaryDirectory(prefix="wl-env-check-") as tmp:
|
|
81
|
+
os.chdir(tmp)
|
|
82
|
+
try:
|
|
83
|
+
yield
|
|
84
|
+
finally:
|
|
85
|
+
os.chdir(previous)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def executable_path() -> str:
|
|
89
|
+
"""Return the interpreter running this wl (diagnostic; #221 ruling 2).
|
|
90
|
+
|
|
91
|
+
``sys.executable`` is empty for some embedded interpreters, so fall back to
|
|
92
|
+
the invocation path. Resolve before any CWD change so a relative
|
|
93
|
+
``sys.argv[0]`` keeps its original meaning.
|
|
94
|
+
"""
|
|
95
|
+
candidate = sys.executable or (sys.argv[0] if sys.argv else "")
|
|
96
|
+
return os.path.abspath(candidate) if candidate else "unknown"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def install_fields() -> dict[str, str]:
|
|
100
|
+
"""Return the ``install`` payload: the actual command and its floor."""
|
|
101
|
+
return {"command": INSTALL_COMMAND, "floor": INSTALL_FLOOR}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def build_payload() -> tuple[str, str | None, dict[str, Any]]:
|
|
105
|
+
"""Build the ``wl env check`` fields.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
``(status, reason, fields)``. ``status`` is ``ok`` when the running
|
|
109
|
+
interpreter is supported, ``unverifiable`` when the shared platform
|
|
110
|
+
matrix says otherwise, and ``error`` only when the payload cannot be
|
|
111
|
+
built at all. ``reason`` carries the readable platform reason when the
|
|
112
|
+
status is not ``ok``; ``fields`` is the envelope payload.
|
|
113
|
+
"""
|
|
114
|
+
executable = executable_path()
|
|
115
|
+
try:
|
|
116
|
+
with _isolated_cwd():
|
|
117
|
+
platform_payload_fields = platform_payload()
|
|
118
|
+
# Consume the shared payload read-only: pop ``reason`` from a copy, so a
|
|
119
|
+
# future platform_payload that caches and returns one dict cannot lose
|
|
120
|
+
# the key on the next build (#227).
|
|
121
|
+
platform_fields = dict(platform_payload_fields)
|
|
122
|
+
raw_reason = platform_fields.pop("reason")
|
|
123
|
+
platform_reason = raw_reason if isinstance(raw_reason, str) else None
|
|
124
|
+
fields: dict[str, Any] = {
|
|
125
|
+
"version": __version__,
|
|
126
|
+
"executable": executable,
|
|
127
|
+
**platform_fields,
|
|
128
|
+
"platform_reason": platform_reason,
|
|
129
|
+
"capabilities": capability_names(),
|
|
130
|
+
"capability_since": capability_since(),
|
|
131
|
+
"install": install_fields(),
|
|
132
|
+
}
|
|
133
|
+
except Exception as exc: # noqa: BLE001 - the envelope is the error report
|
|
134
|
+
return (
|
|
135
|
+
"error",
|
|
136
|
+
f"could not probe the environment: {exc}",
|
|
137
|
+
{
|
|
138
|
+
"version": __version__,
|
|
139
|
+
"executable": executable,
|
|
140
|
+
"install": install_fields(),
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if fields["supported"] is True:
|
|
145
|
+
return "ok", None, fields
|
|
146
|
+
return "unverifiable", platform_reason or "unsupported platform", fields
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def emit_check() -> int:
|
|
150
|
+
"""Write the ``env check`` envelope to stdout and return its exit code.
|
|
151
|
+
|
|
152
|
+
Both ``wl env check --json`` and the deprecated ``wl self capabilities
|
|
153
|
+
--json`` alias call this, so the two commands cannot answer differently.
|
|
154
|
+
"""
|
|
155
|
+
status, reason, fields = build_payload()
|
|
156
|
+
dump(status, status in OK_STATUSES, tool=TOOL, reason=reason, **fields)
|
|
157
|
+
return exit_code(status)
|
wl_cli/envelope.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Shared five-state JSON envelope for the domain commands (issue #220).
|
|
2
|
+
|
|
3
|
+
Every ``wl`` domain command that answers a machine-readable question
|
|
4
|
+
(``wl env check``, ``wl cc repair``, ...) reports through this one envelope,
|
|
5
|
+
so consumers learn the contract once instead of per tool. The line on stdout
|
|
6
|
+
is exactly:
|
|
7
|
+
|
|
8
|
+
{"tool": "<tool>", "ok": <bool>, "status": "<one of five>", "reason": null, ...payload}
|
|
9
|
+
|
|
10
|
+
The five states and their exit codes are defined **only** here:
|
|
11
|
+
|
|
12
|
+
ok probe holds, nothing pending -> rc 0
|
|
13
|
+
dangling probe holds, dangling entries remain (not fixed) -> rc 0
|
|
14
|
+
repaired probe holds, changes were written -> rc 0
|
|
15
|
+
unverifiable probe does not hold, but reported explicitly -> rc 1
|
|
16
|
+
error the tool itself failed (bad JSON, write, args) -> rc 1
|
|
17
|
+
|
|
18
|
+
``ok`` is the boolean reduction of ``status`` (``ok == status in OK_STATUSES``);
|
|
19
|
+
the conclusion lives in the JSON, never in the exit code alone. stdout is
|
|
20
|
+
always exactly one JSON line; diagnostics go to stderr.
|
|
21
|
+
|
|
22
|
+
Mirrors ``scripts/probe_json.py`` in the wlcc repo (the contract oracle)
|
|
23
|
+
exactly: ``exit_code`` maps every status outside :data:`OK_STATUSES` — known
|
|
24
|
+
or unknown — to rc 1, while :func:`dump` rejects unknown statuses so an
|
|
25
|
+
unknown state can never be emitted.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
STATUSES = ("ok", "dangling", "repaired", "unverifiable", "error")
|
|
33
|
+
OK_STATUSES = ("ok", "dangling", "repaired")
|
|
34
|
+
|
|
35
|
+
EXIT_ESTABLISHED = 0
|
|
36
|
+
EXIT_NOT_ESTABLISHED = 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def exit_code(status: str) -> int:
|
|
40
|
+
"""Map a five-state status to an exit code.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
status: One of :data:`STATUSES`.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
``0`` when the probe is established (``ok``/``dangling``/``repaired``),
|
|
47
|
+
``1`` otherwise — including unknown statuses, matching the wlcc oracle.
|
|
48
|
+
Emitting an unknown status is still impossible because :func:`dump`
|
|
49
|
+
rejects it.
|
|
50
|
+
"""
|
|
51
|
+
return EXIT_ESTABLISHED if status in OK_STATUSES else EXIT_NOT_ESTABLISHED
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def dump(
|
|
55
|
+
status: str,
|
|
56
|
+
ok: bool,
|
|
57
|
+
*,
|
|
58
|
+
tool: str,
|
|
59
|
+
reason: str | None = None,
|
|
60
|
+
**fields: Any,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Write one envelope line to stdout as machine-readable JSON.
|
|
63
|
+
|
|
64
|
+
``tool``, ``ok``, ``status`` and ``reason`` are the four fixed top-level
|
|
65
|
+
keys (in that order); ``fields`` carries the tool-specific payload after
|
|
66
|
+
them. ``ok`` must agree with ``status`` — disagreement raises immediately
|
|
67
|
+
rather than emitting two contradictory conclusions.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
status: One of :data:`STATUSES`.
|
|
71
|
+
ok: Boolean reduction of ``status`` (``status in OK_STATUSES``).
|
|
72
|
+
tool: Stable tool name reported in the envelope.
|
|
73
|
+
reason: Human-readable explanation, or ``None``.
|
|
74
|
+
**fields: Tool-specific payload appended after the fixed keys.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If ``status`` is unknown or ``ok`` contradicts it.
|
|
78
|
+
"""
|
|
79
|
+
if status not in STATUSES:
|
|
80
|
+
raise ValueError(f"未知 status: {status!r}(应 ∈ {', '.join(STATUSES)})")
|
|
81
|
+
if ok != (status in OK_STATUSES):
|
|
82
|
+
raise ValueError(f"ok={ok!r} 与 status={status!r} 不自洽")
|
|
83
|
+
|
|
84
|
+
payload: dict[str, Any] = {
|
|
85
|
+
"tool": tool,
|
|
86
|
+
"ok": ok,
|
|
87
|
+
"status": status,
|
|
88
|
+
"reason": reason,
|
|
89
|
+
}
|
|
90
|
+
payload.update(fields)
|
|
91
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
wl_cli/exceptions.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for WL Commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ErrorCode:
|
|
7
|
+
"""Standard error codes."""
|
|
8
|
+
|
|
9
|
+
SUCCESS = 0
|
|
10
|
+
COMMAND_NOT_FOUND = 1
|
|
11
|
+
COMMAND_EXECUTION_FAILED = 2
|
|
12
|
+
INVALID_ARGUMENT = 3
|
|
13
|
+
MISSING_DEPENDENCY = 4
|
|
14
|
+
MAKE_NOT_FOUND = 5
|
|
15
|
+
UV_NOT_FOUND = 6
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CommandError(Exception):
|
|
19
|
+
"""Base exception for command failures."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self, message: str, error_code: int = ErrorCode.COMMAND_EXECUTION_FAILED
|
|
23
|
+
) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Initialize command error.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
message (str): Error message.
|
|
29
|
+
error_code (int): Error code.
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.error_code = error_code
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MakeNotFoundError(CommandError):
|
|
36
|
+
"""Raised when 'make' command is not found."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message: str = "Make command not found") -> None:
|
|
39
|
+
super().__init__(message, ErrorCode.MAKE_NOT_FOUND)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class UVNotFoundError(CommandError):
|
|
43
|
+
"""Raised when 'uv' command is not found."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message: str = "UV command not found") -> None:
|
|
46
|
+
super().__init__(message, ErrorCode.UV_NOT_FOUND)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConfigError(CommandError):
|
|
50
|
+
"""Raised when configuration errors occur."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, message: str) -> None:
|
|
53
|
+
super().__init__(message, ErrorCode.INVALID_ARGUMENT)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FileOperationError(CommandError):
|
|
57
|
+
"""Raised when file operation errors occur."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, message: str, path: str | None = None) -> None:
|
|
60
|
+
super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
|
|
61
|
+
self.path = path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class GitError(CommandError):
|
|
65
|
+
"""Raised when Git operation errors occur."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, message: str, command: str | None = None) -> None:
|
|
68
|
+
super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
|
|
69
|
+
self.command = command
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NetworkError(CommandError):
|
|
73
|
+
"""Raised when network operation errors occur."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, message: str, url: str | None = None) -> None:
|
|
76
|
+
super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
|
|
77
|
+
self.url = url
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ValidationError(CommandError):
|
|
81
|
+
"""Raised when validation errors occur."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str, field: str | None = None) -> None:
|
|
84
|
+
super().__init__(message, ErrorCode.INVALID_ARGUMENT)
|
|
85
|
+
self.field = field
|
wl_cli/lib/__init__.py
ADDED
wl_cli/lib/rust_utils.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 尝试导入 Rust 扩展,如果失败则使用 Python 实现
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# 使用当前文件所在目录动态构建 Rust 扩展路径
|
|
8
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
+
sys.path.insert(0, current_dir)
|
|
10
|
+
|
|
11
|
+
# 导入 Rust 扩展
|
|
12
|
+
from wl_cli_native import sum_as_string as rust_sum_as_string
|
|
13
|
+
|
|
14
|
+
HAS_RUST_EXTENSION = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_RUST_EXTENSION = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sum_as_string(a: int, b: int) -> str:
|
|
20
|
+
"""
|
|
21
|
+
计算两个整数的和并返回字符串表示。
|
|
22
|
+
优先使用 Rust 扩展实现,不可用时回落至 Python 实现。
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
a: 第一个整数
|
|
26
|
+
b: 第二个整数
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
两个整数和的字符串表示
|
|
30
|
+
"""
|
|
31
|
+
if HAS_RUST_EXTENSION:
|
|
32
|
+
return cast("str", rust_sum_as_string(a, b))
|
|
33
|
+
else:
|
|
34
|
+
# Python 实现作为回落
|
|
35
|
+
return str(a + b)
|
wl_cli/mcp/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""MCP capabilities owned by this domain (issue #224).
|
|
2
|
+
|
|
3
|
+
Since #221 the shared registry :mod:`wl_cli.capabilities` is the single source
|
|
4
|
+
of truth for every capability name. This module keeps the domain-local view
|
|
5
|
+
that #224 introduced -- ``mcp.*`` entries only, derived from the shared
|
|
6
|
+
mapping -- so domain code and tests keep importing the same ``CAPABILITIES``
|
|
7
|
+
dict without keeping a second copy of the data.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from ..capabilities import CAPABILITY_SINCE
|
|
13
|
+
|
|
14
|
+
#: Capability name -> the first wl release that provides it (domain subset).
|
|
15
|
+
CAPABILITIES: dict[str, str] = {
|
|
16
|
+
name: since for name, since in CAPABILITY_SINCE.items() if name.startswith("mcp.")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
__all__ = ["CAPABILITIES"]
|
wl_cli/mcp/cli.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Click commands for the ``mcp`` domain."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group(
|
|
7
|
+
name="mcp",
|
|
8
|
+
help="Model Context Protocol server commands.",
|
|
9
|
+
)
|
|
10
|
+
def mcp_group() -> None:
|
|
11
|
+
"""MCP domain entry point."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@mcp_group.command("serve")
|
|
15
|
+
@click.argument("server", type=click.Choice(("gitlab", "k8s")))
|
|
16
|
+
def serve_command(server: str) -> None:
|
|
17
|
+
"""Serve a registered MCP server over stdin/stdout."""
|
|
18
|
+
if server == "gitlab":
|
|
19
|
+
from .gitlab import serve
|
|
20
|
+
else:
|
|
21
|
+
from .k8s import serve
|
|
22
|
+
serve()
|