apm-cli 0.8.3__tar.gz → 0.8.4__tar.gz
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.
- {apm_cli-0.8.3/src/apm_cli.egg-info → apm_cli-0.8.4}/PKG-INFO +2 -1
- {apm_cli-0.8.3 → apm_cli-0.8.4}/pyproject.toml +3 -2
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/copilot.py +5 -2
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/vscode.py +37 -15
- apm_cli-0.8.4/src/apm_cli/bundle/lockfile_enrichment.py +76 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/packer.py +8 -19
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/plugin_exporter.py +22 -6
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/_helpers.py +13 -4
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/audit.py +46 -51
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/cli.py +58 -62
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/watcher.py +29 -28
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/config.py +16 -13
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/_utils.py +23 -16
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/cli.py +33 -31
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/init.py +21 -23
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/install.py +471 -218
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/list_cmd.py +7 -8
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/mcp.py +19 -13
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/pack.py +36 -30
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/prune.py +17 -16
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/run.py +32 -28
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/runtime.py +18 -18
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/cli.py +25 -24
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/engine.py +24 -22
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/update.py +23 -22
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/agents_compiler.py +22 -15
- apm_cli-0.8.4/src/apm_cli/core/__init__.py +5 -0
- apm_cli-0.8.4/src/apm_cli/core/auth.py +419 -0
- apm_cli-0.8.4/src/apm_cli/core/command_logger.py +330 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/safe_installer.py +34 -11
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/token_manager.py +25 -2
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/apm_resolver.py +30 -10
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/dependency_graph.py +15 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/github_downloader.py +109 -73
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/drift.py +4 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/instruction_integrator.py +2 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/mcp_integrator.py +220 -72
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/prompt_integrator.py +2 -1
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/skill_integrator.py +23 -10
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/operations.py +9 -3
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/diagnostics.py +48 -5
- {apm_cli-0.8.3 → apm_cli-0.8.4/src/apm_cli.egg-info}/PKG-INFO +2 -1
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/SOURCES.txt +2 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/requires.txt +1 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_apm_resolver.py +0 -2
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_github_downloader.py +7 -5
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_github_downloader_token_precedence.py +2 -2
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_token_manager.py +31 -0
- apm_cli-0.8.3/src/apm_cli/bundle/lockfile_enrichment.py +0 -41
- apm_cli-0.8.3/src/apm_cli/core/__init__.py +0 -1
- {apm_cli-0.8.3 → apm_cli-0.8.4}/AUTHORS +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/LICENSE +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/README.md +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/setup.cfg +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/cli.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/config.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/target_detection.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/lockfile.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/apm_package.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/reference.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/validation.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/version.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_apm_package_models.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_console.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_lockfile.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apm-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: MCP configuration tool
|
|
5
5
|
Author-email: Daniel Meppiel <user@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -52,6 +52,7 @@ Requires-Dist: GitPython>=3.1.0
|
|
|
52
52
|
Provides-Extra: dev
|
|
53
53
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
54
54
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
|
|
55
56
|
Requires-Dist: black>=26.3.1; python_version >= "3.10" and extra == "dev"
|
|
56
57
|
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
57
58
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apm-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.4"
|
|
8
8
|
description = "MCP configuration tool"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -41,6 +41,7 @@ dependencies = [
|
|
|
41
41
|
dev = [
|
|
42
42
|
"pytest>=7.0.0",
|
|
43
43
|
"pytest-cov>=4.0.0",
|
|
44
|
+
"pytest-xdist>=3.0.0",
|
|
44
45
|
"black>=26.3.1; python_version>='3.10'",
|
|
45
46
|
"isort>=5.0.0",
|
|
46
47
|
"mypy>=1.0.0",
|
|
@@ -70,7 +71,7 @@ warn_return_any = true
|
|
|
70
71
|
warn_unused_configs = true
|
|
71
72
|
|
|
72
73
|
[tool.pytest.ini_options]
|
|
73
|
-
addopts = "-m 'not benchmark'"
|
|
74
|
+
addopts = "-m 'not benchmark' -n auto"
|
|
74
75
|
markers = [
|
|
75
76
|
"integration: marks tests as integration tests that may require network access",
|
|
76
77
|
"slow: marks tests as slow running tests",
|
|
@@ -12,6 +12,7 @@ from .base import MCPClientAdapter
|
|
|
12
12
|
from ...registry.client import SimpleRegistryClient
|
|
13
13
|
from ...registry.integration import RegistryIntegration
|
|
14
14
|
from ...core.docker_args import DockerArgsProcessor
|
|
15
|
+
from ...core.token_manager import GitHubTokenManager
|
|
15
16
|
from ...utils.github_host import is_github_hostname
|
|
16
17
|
|
|
17
18
|
|
|
@@ -199,8 +200,10 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
199
200
|
is_github_server = self._is_github_server(server_name, remote.get("url", ""))
|
|
200
201
|
|
|
201
202
|
if is_github_server:
|
|
202
|
-
#
|
|
203
|
-
|
|
203
|
+
# Use centralized token manager (copilot chain: GITHUB_COPILOT_PAT → GITHUB_TOKEN → GITHUB_APM_PAT),
|
|
204
|
+
# falling back to GITHUB_PERSONAL_ACCESS_TOKEN for Copilot CLI compat.
|
|
205
|
+
_tm = GitHubTokenManager()
|
|
206
|
+
github_token = _tm.get_token_for_purpose('copilot') or os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
|
|
204
207
|
if github_token:
|
|
205
208
|
config["headers"] = {
|
|
206
209
|
"Authorization": f"Bearer {github_token}"
|
|
@@ -32,7 +32,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
32
32
|
self.registry_client = SimpleRegistryClient(registry_url)
|
|
33
33
|
self.registry_integration = RegistryIntegration(registry_url)
|
|
34
34
|
|
|
35
|
-
def get_config_path(self):
|
|
35
|
+
def get_config_path(self, logger=None):
|
|
36
36
|
"""Get the path to the VSCode MCP configuration file in the repository.
|
|
37
37
|
|
|
38
38
|
Returns:
|
|
@@ -50,11 +50,14 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
50
50
|
if not vscode_dir.exists():
|
|
51
51
|
vscode_dir.mkdir(parents=True, exist_ok=True)
|
|
52
52
|
except Exception as e:
|
|
53
|
-
|
|
53
|
+
if logger:
|
|
54
|
+
logger.warning(f"Could not create .vscode directory: {e}")
|
|
55
|
+
else:
|
|
56
|
+
print(f"Warning: Could not create .vscode directory: {e}")
|
|
54
57
|
|
|
55
58
|
return str(mcp_config_path)
|
|
56
59
|
|
|
57
|
-
def update_config(self, new_config):
|
|
60
|
+
def update_config(self, new_config, logger=None):
|
|
58
61
|
"""Update the VSCode MCP configuration with new values.
|
|
59
62
|
|
|
60
63
|
Args:
|
|
@@ -63,7 +66,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
63
66
|
Returns:
|
|
64
67
|
bool: True if successful, False otherwise.
|
|
65
68
|
"""
|
|
66
|
-
config_path = self.get_config_path()
|
|
69
|
+
config_path = self.get_config_path(logger=logger)
|
|
67
70
|
|
|
68
71
|
try:
|
|
69
72
|
# Write the updated config
|
|
@@ -72,16 +75,19 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
72
75
|
|
|
73
76
|
return True
|
|
74
77
|
except Exception as e:
|
|
75
|
-
|
|
78
|
+
if logger:
|
|
79
|
+
logger.error(f"Error updating VSCode MCP configuration: {e}")
|
|
80
|
+
else:
|
|
81
|
+
print(f"Error updating VSCode MCP configuration: {e}")
|
|
76
82
|
return False
|
|
77
83
|
|
|
78
|
-
def get_current_config(self):
|
|
84
|
+
def get_current_config(self, logger=None):
|
|
79
85
|
"""Get the current VSCode MCP configuration.
|
|
80
86
|
|
|
81
87
|
Returns:
|
|
82
88
|
dict: Current VSCode MCP configuration from the local .vscode/mcp.json file.
|
|
83
89
|
"""
|
|
84
|
-
config_path = self.get_config_path()
|
|
90
|
+
config_path = self.get_config_path(logger=logger)
|
|
85
91
|
|
|
86
92
|
try:
|
|
87
93
|
try:
|
|
@@ -90,10 +96,13 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
90
96
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
91
97
|
return {}
|
|
92
98
|
except Exception as e:
|
|
93
|
-
|
|
99
|
+
if logger:
|
|
100
|
+
logger.error(f"Error reading VSCode MCP configuration: {e}")
|
|
101
|
+
else:
|
|
102
|
+
print(f"Error reading VSCode MCP configuration: {e}")
|
|
94
103
|
return {}
|
|
95
104
|
|
|
96
|
-
def configure_mcp_server(self, server_url, server_name=None, enabled=True, env_overrides=None, server_info_cache=None, runtime_vars=None):
|
|
105
|
+
def configure_mcp_server(self, server_url, server_name=None, enabled=True, env_overrides=None, server_info_cache=None, runtime_vars=None, logger=None):
|
|
97
106
|
"""Configure an MCP server in VS Code mcp.json file.
|
|
98
107
|
|
|
99
108
|
This method updates the .vscode/mcp.json file to add or update
|
|
@@ -105,6 +114,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
105
114
|
enabled (bool, optional): Whether to enable the server. Defaults to True.
|
|
106
115
|
env_overrides (dict, optional): Environment variable overrides. Defaults to None.
|
|
107
116
|
server_info_cache (dict, optional): Pre-fetched server info to avoid duplicate registry calls.
|
|
117
|
+
logger: Optional CommandLogger for structured output.
|
|
108
118
|
|
|
109
119
|
Returns:
|
|
110
120
|
bool: True if successful, False otherwise.
|
|
@@ -113,7 +123,10 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
113
123
|
ValueError: If server is not found in registry.
|
|
114
124
|
"""
|
|
115
125
|
if not server_url:
|
|
116
|
-
|
|
126
|
+
if logger:
|
|
127
|
+
logger.error("server_url cannot be empty")
|
|
128
|
+
else:
|
|
129
|
+
print("Error: server_url cannot be empty")
|
|
117
130
|
return False
|
|
118
131
|
|
|
119
132
|
try:
|
|
@@ -133,14 +146,17 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
133
146
|
server_config, input_vars = self._format_server_config(server_info)
|
|
134
147
|
|
|
135
148
|
if not server_config:
|
|
136
|
-
|
|
149
|
+
if logger:
|
|
150
|
+
logger.error(f"Unable to configure server: {server_url}")
|
|
151
|
+
else:
|
|
152
|
+
print(f"Unable to configure server: {server_url}")
|
|
137
153
|
return False
|
|
138
154
|
|
|
139
155
|
# Use provided server name or fallback to server_url
|
|
140
156
|
config_key = server_name or server_url
|
|
141
157
|
|
|
142
158
|
# Get current config
|
|
143
|
-
current_config = self.get_current_config()
|
|
159
|
+
current_config = self.get_current_config(logger=logger)
|
|
144
160
|
|
|
145
161
|
# Ensure servers and inputs sections exist
|
|
146
162
|
if "servers" not in current_config:
|
|
@@ -159,17 +175,23 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
159
175
|
existing_input_ids.add(var.get("id"))
|
|
160
176
|
|
|
161
177
|
# Update the configuration
|
|
162
|
-
result = self.update_config(current_config)
|
|
178
|
+
result = self.update_config(current_config, logger=logger)
|
|
163
179
|
|
|
164
180
|
if result:
|
|
165
|
-
|
|
181
|
+
if logger:
|
|
182
|
+
logger.verbose_detail(f"Configured MCP server '{config_key}' for VS Code")
|
|
183
|
+
else:
|
|
184
|
+
print(f"Successfully configured MCP server '{config_key}' for VS Code")
|
|
166
185
|
return result
|
|
167
186
|
|
|
168
187
|
except ValueError:
|
|
169
188
|
# Re-raise ValueError for registry errors
|
|
170
189
|
raise
|
|
171
190
|
except Exception as e:
|
|
172
|
-
|
|
191
|
+
if logger:
|
|
192
|
+
logger.error(f"Error configuring MCP server: {e}")
|
|
193
|
+
else:
|
|
194
|
+
print(f"Error configuring MCP server: {e}")
|
|
173
195
|
return False
|
|
174
196
|
|
|
175
197
|
def _format_server_config(self, server_info):
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Lockfile enrichment for pack-time metadata."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from ..deps.lockfile import LockFile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Must stay in sync with packer._TARGET_PREFIXES
|
|
10
|
+
_TARGET_PREFIXES = {
|
|
11
|
+
"copilot": [".github/"],
|
|
12
|
+
"vscode": [".github/"],
|
|
13
|
+
"claude": [".claude/"],
|
|
14
|
+
"cursor": [".cursor/"],
|
|
15
|
+
"opencode": [".opencode/"],
|
|
16
|
+
"all": [".github/", ".claude/", ".cursor/", ".opencode/"],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _filter_files_by_target(deployed_files: List[str], target: str) -> List[str]:
|
|
21
|
+
"""Filter deployed file paths by target prefix."""
|
|
22
|
+
prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
|
|
23
|
+
return [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def enrich_lockfile_for_pack(
|
|
27
|
+
lockfile: LockFile,
|
|
28
|
+
fmt: str,
|
|
29
|
+
target: str,
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Create an enriched copy of the lockfile YAML with a ``pack:`` section.
|
|
32
|
+
|
|
33
|
+
Filters each dependency's ``deployed_files`` to only include paths
|
|
34
|
+
matching the pack *target*, so the bundle lockfile is consistent with
|
|
35
|
+
the files actually shipped in the bundle.
|
|
36
|
+
|
|
37
|
+
Does NOT mutate the original *lockfile* object -- serialises a copy and
|
|
38
|
+
prepends the pack metadata.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
lockfile: The resolved lockfile to enrich.
|
|
42
|
+
fmt: Bundle format (``"apm"`` or ``"plugin"``).
|
|
43
|
+
target: Effective target used for packing (``"vscode"``, ``"claude"``, ``"all"``).
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A YAML string with the ``pack:`` block followed by the original
|
|
47
|
+
lockfile content.
|
|
48
|
+
"""
|
|
49
|
+
import yaml
|
|
50
|
+
|
|
51
|
+
pack_section = yaml.dump(
|
|
52
|
+
{
|
|
53
|
+
"pack": {
|
|
54
|
+
"format": fmt,
|
|
55
|
+
"target": target,
|
|
56
|
+
"packed_at": datetime.now(timezone.utc).isoformat(),
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
default_flow_style=False,
|
|
60
|
+
sort_keys=False,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Build a filtered lockfile YAML: each dep's deployed_files is narrowed
|
|
64
|
+
# to only the paths matching the pack target.
|
|
65
|
+
data = yaml.safe_load(lockfile.to_yaml())
|
|
66
|
+
if data and "dependencies" in data:
|
|
67
|
+
for dep in data["dependencies"]:
|
|
68
|
+
if "deployed_files" in dep:
|
|
69
|
+
dep["deployed_files"] = _filter_files_by_target(
|
|
70
|
+
dep["deployed_files"], target
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
lockfile_yaml = yaml.dump(
|
|
74
|
+
data, default_flow_style=False, sort_keys=False, allow_unicode=True
|
|
75
|
+
)
|
|
76
|
+
return pack_section + lockfile_yaml
|
|
@@ -10,18 +10,7 @@ from typing import List, Optional
|
|
|
10
10
|
from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
|
|
11
11
|
from ..models.apm_package import APMPackage
|
|
12
12
|
from ..core.target_detection import detect_target
|
|
13
|
-
from .lockfile_enrichment import enrich_lockfile_for_pack
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Target prefix mapping ("copilot" and "vscode" both map to .github/)
|
|
17
|
-
_TARGET_PREFIXES = {
|
|
18
|
-
"copilot": [".github/"],
|
|
19
|
-
"vscode": [".github/"],
|
|
20
|
-
"claude": [".claude/"],
|
|
21
|
-
"cursor": [".cursor/"],
|
|
22
|
-
"opencode": [".opencode/"],
|
|
23
|
-
"all": [".github/", ".claude/", ".cursor/", ".opencode/"],
|
|
24
|
-
}
|
|
13
|
+
from .lockfile_enrichment import enrich_lockfile_for_pack, _TARGET_PREFIXES, _filter_files_by_target
|
|
25
14
|
|
|
26
15
|
|
|
27
16
|
@dataclass
|
|
@@ -33,12 +22,6 @@ class PackResult:
|
|
|
33
22
|
lockfile_enriched: bool = False
|
|
34
23
|
|
|
35
24
|
|
|
36
|
-
def _filter_files_by_target(deployed_files: List[str], target: str) -> List[str]:
|
|
37
|
-
"""Filter deployed file paths by target prefix."""
|
|
38
|
-
prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
|
|
39
|
-
return [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
|
|
40
|
-
|
|
41
|
-
|
|
42
25
|
def pack_bundle(
|
|
43
26
|
project_root: Path,
|
|
44
27
|
output_dir: Path,
|
|
@@ -47,6 +30,7 @@ def pack_bundle(
|
|
|
47
30
|
archive: bool = False,
|
|
48
31
|
dry_run: bool = False,
|
|
49
32
|
force: bool = False,
|
|
33
|
+
logger=None,
|
|
50
34
|
) -> PackResult:
|
|
51
35
|
"""Create a self-contained bundle from installed APM dependencies.
|
|
52
36
|
|
|
@@ -81,6 +65,7 @@ def pack_bundle(
|
|
|
81
65
|
archive=archive,
|
|
82
66
|
dry_run=dry_run,
|
|
83
67
|
force=force,
|
|
68
|
+
logger=logger,
|
|
84
69
|
)
|
|
85
70
|
|
|
86
71
|
lockfile_path = get_lockfile_path(project_root)
|
|
@@ -196,10 +181,14 @@ def pack_bundle(
|
|
|
196
181
|
)
|
|
197
182
|
_scan_findings_total += len(verdict.all_findings)
|
|
198
183
|
if _scan_findings_total:
|
|
199
|
-
|
|
184
|
+
_warn_msg = (
|
|
200
185
|
f"Bundle contains {_scan_findings_total} hidden character(s) across source files "
|
|
201
186
|
f"— run 'apm audit' to inspect before publishing"
|
|
202
187
|
)
|
|
188
|
+
if logger:
|
|
189
|
+
logger.warning(_warn_msg)
|
|
190
|
+
else:
|
|
191
|
+
_rich_warning(_warn_msg)
|
|
203
192
|
|
|
204
193
|
# 6. Build output directory
|
|
205
194
|
bundle_dir = output_dir / f"{pkg_name}-{pkg_version}"
|
|
@@ -319,7 +319,7 @@ def _get_dev_dependency_urls(apm_yml_path: Path) -> Set[Tuple[str, str]]:
|
|
|
319
319
|
|
|
320
320
|
|
|
321
321
|
def _find_or_synthesize_plugin_json(
|
|
322
|
-
project_root: Path, apm_yml_path: Path
|
|
322
|
+
project_root: Path, apm_yml_path: Path, logger=None,
|
|
323
323
|
) -> dict:
|
|
324
324
|
"""Locate an existing ``plugin.json`` or synthesise one from ``apm.yml``."""
|
|
325
325
|
from ..deps.plugin_parser import synthesize_plugin_json_from_apm_yml
|
|
@@ -330,16 +330,24 @@ def _find_or_synthesize_plugin_json(
|
|
|
330
330
|
try:
|
|
331
331
|
return json.loads(plugin_json_path.read_text(encoding="utf-8"))
|
|
332
332
|
except (json.JSONDecodeError, OSError) as exc:
|
|
333
|
-
|
|
333
|
+
_warn_msg = (
|
|
334
334
|
f"Found plugin.json at {plugin_json_path} but could not parse it: {exc}. "
|
|
335
335
|
"Falling back to synthesis from apm.yml."
|
|
336
336
|
)
|
|
337
|
+
if logger:
|
|
338
|
+
logger.warning(_warn_msg)
|
|
339
|
+
else:
|
|
340
|
+
_rich_warning(_warn_msg)
|
|
337
341
|
|
|
338
342
|
else:
|
|
339
|
-
|
|
343
|
+
_warn_msg = (
|
|
340
344
|
"No plugin.json found. Synthesizing from apm.yml. "
|
|
341
345
|
"Consider running 'apm init --plugin'."
|
|
342
346
|
)
|
|
347
|
+
if logger:
|
|
348
|
+
logger.warning(_warn_msg)
|
|
349
|
+
else:
|
|
350
|
+
_rich_warning(_warn_msg)
|
|
343
351
|
return synthesize_plugin_json_from_apm_yml(apm_yml_path)
|
|
344
352
|
|
|
345
353
|
|
|
@@ -400,6 +408,7 @@ def export_plugin_bundle(
|
|
|
400
408
|
archive: bool = False,
|
|
401
409
|
dry_run: bool = False,
|
|
402
410
|
force: bool = False,
|
|
411
|
+
logger=None,
|
|
403
412
|
) -> PackResult:
|
|
404
413
|
"""Export the project as a plugin-native directory.
|
|
405
414
|
|
|
@@ -439,7 +448,7 @@ def export_plugin_bundle(
|
|
|
439
448
|
)
|
|
440
449
|
|
|
441
450
|
# 3. Find or synthesize plugin.json
|
|
442
|
-
plugin_json = _find_or_synthesize_plugin_json(project_root, apm_yml_path)
|
|
451
|
+
plugin_json = _find_or_synthesize_plugin_json(project_root, apm_yml_path, logger=logger)
|
|
443
452
|
|
|
444
453
|
# 4. devDependencies filtering
|
|
445
454
|
dev_dep_urls = _get_dev_dependency_urls(apm_yml_path)
|
|
@@ -510,7 +519,10 @@ def export_plugin_bundle(
|
|
|
510
519
|
|
|
511
520
|
# 7. Emit collision warnings
|
|
512
521
|
for msg in collisions:
|
|
513
|
-
|
|
522
|
+
if logger:
|
|
523
|
+
logger.warning(msg)
|
|
524
|
+
else:
|
|
525
|
+
_rich_warning(msg)
|
|
514
526
|
|
|
515
527
|
# 8. Build output file list (sorted for determinism)
|
|
516
528
|
output_files = sorted(file_map.keys())
|
|
@@ -548,10 +560,14 @@ def export_plugin_bundle(
|
|
|
548
560
|
verdict = SecurityGate.scan_text(text, str(src), policy=WARN_POLICY)
|
|
549
561
|
scan_findings_total += len(verdict.all_findings)
|
|
550
562
|
if scan_findings_total:
|
|
551
|
-
|
|
563
|
+
_warn_msg = (
|
|
552
564
|
f"Bundle contains {scan_findings_total} hidden character(s) across "
|
|
553
565
|
f"source files — run 'apm audit' to inspect before publishing"
|
|
554
566
|
)
|
|
567
|
+
if logger:
|
|
568
|
+
logger.warning(_warn_msg)
|
|
569
|
+
else:
|
|
570
|
+
_rich_warning(_warn_msg)
|
|
555
571
|
|
|
556
572
|
# 11. Write files to output directory (clean slate to prevent symlink attacks)
|
|
557
573
|
if bundle_dir.exists():
|
|
@@ -283,7 +283,7 @@ def _atomic_write(path: Path, data: str) -> None:
|
|
|
283
283
|
raise
|
|
284
284
|
|
|
285
285
|
|
|
286
|
-
def _update_gitignore_for_apm_modules():
|
|
286
|
+
def _update_gitignore_for_apm_modules(logger=None):
|
|
287
287
|
"""Add apm_modules/ to .gitignore if not already present."""
|
|
288
288
|
gitignore_path = Path(GITIGNORE_FILENAME)
|
|
289
289
|
apm_modules_pattern = APM_MODULES_GITIGNORE_PATTERN
|
|
@@ -295,7 +295,10 @@ def _update_gitignore_for_apm_modules():
|
|
|
295
295
|
with open(gitignore_path, "r", encoding="utf-8") as f:
|
|
296
296
|
current_content = [line.rstrip("\n\r") for line in f.readlines()]
|
|
297
297
|
except Exception as e:
|
|
298
|
-
|
|
298
|
+
if logger:
|
|
299
|
+
logger.warning(f"Could not read .gitignore: {e}")
|
|
300
|
+
else:
|
|
301
|
+
_rich_warning(f"Could not read .gitignore: {e}")
|
|
299
302
|
return
|
|
300
303
|
|
|
301
304
|
# Check if apm_modules/ is already in .gitignore
|
|
@@ -310,9 +313,15 @@ def _update_gitignore_for_apm_modules():
|
|
|
310
313
|
f.write("\n")
|
|
311
314
|
f.write(f"\n# APM dependencies\n{apm_modules_pattern}\n")
|
|
312
315
|
|
|
313
|
-
|
|
316
|
+
if logger:
|
|
317
|
+
logger.progress(f"Added {apm_modules_pattern} to .gitignore")
|
|
318
|
+
else:
|
|
319
|
+
_rich_info(f"Added {apm_modules_pattern} to .gitignore")
|
|
314
320
|
except Exception as e:
|
|
315
|
-
|
|
321
|
+
if logger:
|
|
322
|
+
logger.warning(f"Could not update .gitignore: {e}")
|
|
323
|
+
else:
|
|
324
|
+
_rich_warning(f"Could not update .gitignore: {e}")
|
|
316
325
|
|
|
317
326
|
|
|
318
327
|
# ------------------------------------------------------------------
|