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.
Files changed (162) hide show
  1. {apm_cli-0.8.3/src/apm_cli.egg-info → apm_cli-0.8.4}/PKG-INFO +2 -1
  2. {apm_cli-0.8.3 → apm_cli-0.8.4}/pyproject.toml +3 -2
  3. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/copilot.py +5 -2
  4. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/vscode.py +37 -15
  5. apm_cli-0.8.4/src/apm_cli/bundle/lockfile_enrichment.py +76 -0
  6. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/packer.py +8 -19
  7. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/plugin_exporter.py +22 -6
  8. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/_helpers.py +13 -4
  9. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/audit.py +46 -51
  10. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/cli.py +58 -62
  11. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/watcher.py +29 -28
  12. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/config.py +16 -13
  13. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/_utils.py +23 -16
  14. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/cli.py +33 -31
  15. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/init.py +21 -23
  16. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/install.py +471 -218
  17. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/list_cmd.py +7 -8
  18. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/mcp.py +19 -13
  19. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/pack.py +36 -30
  20. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/prune.py +17 -16
  21. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/run.py +32 -28
  22. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/runtime.py +18 -18
  23. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/cli.py +25 -24
  24. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/engine.py +24 -22
  25. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/update.py +23 -22
  26. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/agents_compiler.py +22 -15
  27. apm_cli-0.8.4/src/apm_cli/core/__init__.py +5 -0
  28. apm_cli-0.8.4/src/apm_cli/core/auth.py +419 -0
  29. apm_cli-0.8.4/src/apm_cli/core/command_logger.py +330 -0
  30. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/safe_installer.py +34 -11
  31. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/token_manager.py +25 -2
  32. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/apm_resolver.py +30 -10
  33. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/dependency_graph.py +15 -0
  34. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/github_downloader.py +109 -73
  35. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/drift.py +4 -0
  36. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/instruction_integrator.py +2 -0
  37. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/mcp_integrator.py +220 -72
  38. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/prompt_integrator.py +2 -1
  39. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/skill_integrator.py +23 -10
  40. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/operations.py +9 -3
  41. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/diagnostics.py +48 -5
  42. {apm_cli-0.8.3 → apm_cli-0.8.4/src/apm_cli.egg-info}/PKG-INFO +2 -1
  43. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/SOURCES.txt +2 -0
  44. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/requires.txt +1 -0
  45. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_apm_resolver.py +0 -2
  46. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_github_downloader.py +7 -5
  47. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_github_downloader_token_precedence.py +2 -2
  48. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_token_manager.py +31 -0
  49. apm_cli-0.8.3/src/apm_cli/bundle/lockfile_enrichment.py +0 -41
  50. apm_cli-0.8.3/src/apm_cli/core/__init__.py +0 -1
  51. {apm_cli-0.8.3 → apm_cli-0.8.4}/AUTHORS +0 -0
  52. {apm_cli-0.8.3 → apm_cli-0.8.4}/LICENSE +0 -0
  53. {apm_cli-0.8.3 → apm_cli-0.8.4}/README.md +0 -0
  54. {apm_cli-0.8.3 → apm_cli-0.8.4}/setup.cfg +0 -0
  55. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/__init__.py +0 -0
  56. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/__init__.py +0 -0
  57. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/__init__.py +0 -0
  58. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/base.py +0 -0
  59. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/codex.py +0 -0
  60. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/cursor.py +0 -0
  61. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/client/opencode.py +0 -0
  62. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  63. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
  64. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  65. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/__init__.py +0 -0
  66. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/bundle/unpacker.py +0 -0
  67. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/cli.py +0 -0
  68. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/__init__.py +0 -0
  69. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/compile/__init__.py +0 -0
  70. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/deps/__init__.py +0 -0
  71. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  72. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/__init__.py +0 -0
  73. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
  74. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constants.py +0 -0
  75. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution.py +0 -0
  76. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution_block.py +0 -0
  77. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
  78. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  79. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/injector.py +0 -0
  80. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/link_resolver.py +0 -0
  81. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/compilation/template_builder.py +0 -0
  82. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/config.py +0 -0
  83. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/constants.py +0 -0
  84. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/conflict_detector.py +0 -0
  85. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/docker_args.py +0 -0
  86. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/operations.py +0 -0
  87. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/script_runner.py +0 -0
  88. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/core/target_detection.py +0 -0
  89. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/__init__.py +0 -0
  90. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/aggregator.py +0 -0
  91. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/collection_parser.py +0 -0
  92. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/lockfile.py +0 -0
  93. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/package_validator.py +0 -0
  94. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/plugin_parser.py +0 -0
  95. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/deps/verifier.py +0 -0
  96. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/factory.py +0 -0
  97. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/__init__.py +0 -0
  98. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/agent_integrator.py +0 -0
  99. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/base_integrator.py +0 -0
  100. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/command_integrator.py +0 -0
  101. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/hook_integrator.py +0 -0
  102. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/skill_transformer.py +0 -0
  103. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/targets.py +0 -0
  104. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/integration/utils.py +0 -0
  105. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/__init__.py +0 -0
  106. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/apm_package.py +0 -0
  107. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/__init__.py +0 -0
  108. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/mcp.py +0 -0
  109. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/reference.py +0 -0
  110. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/dependency/types.py +0 -0
  111. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/plugin.py +0 -0
  112. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/results.py +0 -0
  113. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/models/validation.py +0 -0
  114. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/__init__.py +0 -0
  115. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/formatters.py +0 -0
  116. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/models.py +0 -0
  117. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/output/script_formatters.py +0 -0
  118. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/__init__.py +0 -0
  119. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/discovery.py +0 -0
  120. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/models.py +0 -0
  121. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/primitives/parser.py +0 -0
  122. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/__init__.py +0 -0
  123. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/client.py +0 -0
  124. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/registry/integration.py +0 -0
  125. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/__init__.py +0 -0
  126. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/base.py +0 -0
  127. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
  128. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  129. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/factory.py +0 -0
  130. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
  131. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/runtime/manager.py +0 -0
  132. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/__init__.py +0 -0
  133. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/audit_report.py +0 -0
  134. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/content_scanner.py +0 -0
  135. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/security/gate.py +0 -0
  136. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/__init__.py +0 -0
  137. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/console.py +0 -0
  138. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/content_hash.py +0 -0
  139. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/github_host.py +0 -0
  140. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/helpers.py +0 -0
  141. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/path_security.py +0 -0
  142. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/utils/version_checker.py +0 -0
  143. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/version.py +0 -0
  144. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/__init__.py +0 -0
  145. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/discovery.py +0 -0
  146. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/parser.py +0 -0
  147. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli/workflow/runner.py +0 -0
  148. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  149. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
  150. {apm_cli-0.8.3 → apm_cli-0.8.4}/src/apm_cli.egg-info/top_level.txt +0 -0
  151. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_apm_package_models.py +0 -0
  152. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_codex_docker_args_fix.py +0 -0
  153. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
  154. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_collision_integration.py +0 -0
  155. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_console.py +0 -0
  156. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_distributed_compilation.py +0 -0
  157. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_empty_string_and_defaults.py +0 -0
  158. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_enhanced_discovery.py +0 -0
  159. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_lockfile.py +0 -0
  160. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_runnable_prompts.py +0 -0
  161. {apm_cli-0.8.3 → apm_cli-0.8.4}/tests/test_runtime_manager_token_precedence.py +0 -0
  162. {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
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.3"
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
- # Check for GitHub Personal Access Token
203
- github_token = os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
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
- print(f"Warning: Could not create .vscode directory: {e}")
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
- print(f"Error updating VSCode MCP configuration: {e}")
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
- print(f"Error reading VSCode MCP configuration: {e}")
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
- print("Error: server_url cannot be empty")
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
- print(f"Unable to configure server: {server_url}")
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
- print(f"Successfully configured MCP server '{config_key}' for VS Code")
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
- print(f"Error configuring MCP server: {e}")
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
- _rich_warning(
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
- _rich_warning(
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
- _rich_warning(
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
- _rich_warning(msg)
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
- _rich_warning(
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
- _rich_warning(f"Could not read .gitignore: {e}")
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
- _rich_info(f"Added {apm_modules_pattern} to .gitignore")
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
- _rich_warning(f"Could not update .gitignore: {e}")
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
  # ------------------------------------------------------------------