apm-cli 0.13.0__tar.gz → 0.14.0__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 (332) hide show
  1. {apm_cli-0.13.0/src/apm_cli.egg-info → apm_cli-0.14.0}/PKG-INFO +2 -1
  2. {apm_cli-0.13.0 → apm_cli-0.14.0}/pyproject.toml +2 -1
  3. apm_cli-0.14.0/src/apm_cli/adapters/client/base.py +451 -0
  4. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/codex.py +14 -102
  5. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/copilot.py +149 -178
  6. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/cursor.py +22 -108
  7. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/gemini.py +42 -19
  8. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/opencode.py +3 -13
  9. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/plugin_exporter.py +35 -8
  10. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cli.py +2 -0
  11. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/_helpers.py +1 -24
  12. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/audit.py +15 -7
  13. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/compile/cli.py +83 -2
  14. apm_cli-0.14.0/src/apm_cli/commands/compile/watcher.py +249 -0
  15. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/init.py +64 -5
  16. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/install.py +60 -5
  17. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/__init__.py +14 -15
  18. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/doctor.py +59 -1
  19. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/init.py +2 -1
  20. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/outdated.py +33 -0
  21. apm_cli-0.14.0/src/apm_cli/commands/pack.py +765 -0
  22. apm_cli-0.14.0/src/apm_cli/commands/plugin/__init__.py +21 -0
  23. apm_cli-0.14.0/src/apm_cli/commands/plugin/init.py +44 -0
  24. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/policy.py +3 -3
  25. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/uninstall/cli.py +14 -6
  26. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/uninstall/engine.py +222 -12
  27. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/update.py +34 -0
  28. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/claude_formatter.py +4 -15
  29. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/distributed_compiler.py +4 -13
  30. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/template_builder.py +40 -0
  31. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/build_orchestrator.py +59 -18
  32. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/script_runner.py +11 -11
  33. apm_cli-0.14.0/src/apm_cli/deps/_shared.py +40 -0
  34. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/artifactory_orchestrator.py +3 -13
  35. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/github_downloader.py +18 -19
  36. apm_cli-0.14.0/src/apm_cli/deps/tiered_ref_resolver.py +491 -0
  37. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/context.py +1 -0
  38. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/local_bundle_handler.py +7 -7
  39. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/resolve.py +26 -0
  40. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/services.py +38 -5
  41. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/validation.py +43 -0
  42. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/agent_integrator.py +82 -21
  43. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/base_integrator.py +172 -0
  44. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/command_integrator.py +17 -10
  45. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/hook_integrator.py +190 -11
  46. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/instruction_integrator.py +12 -10
  47. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/mcp_integrator.py +166 -545
  48. apm_cli-0.14.0/src/apm_cli/integration/mcp_integrator_install.py +601 -0
  49. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/prompt_integrator.py +24 -0
  50. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/targets.py +20 -2
  51. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/__init__.py +2 -0
  52. apm_cli-0.14.0/src/apm_cli/marketplace/_shared.py +48 -0
  53. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/builder.py +271 -308
  54. apm_cli-0.14.0/src/apm_cli/marketplace/diagnostics.py +13 -0
  55. apm_cli-0.14.0/src/apm_cli/marketplace/drift_check.py +258 -0
  56. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/init_template.py +22 -1
  57. apm_cli-0.14.0/src/apm_cli/marketplace/output_mappers.py +342 -0
  58. apm_cli-0.14.0/src/apm_cli/marketplace/output_profiles.py +100 -0
  59. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/resolver.py +155 -2
  60. apm_cli-0.14.0/src/apm_cli/marketplace/version_check.py +248 -0
  61. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/yml_schema.py +356 -4
  62. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/apm_package.py +25 -1
  63. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/dependency/reference.py +22 -3
  64. apm_cli-0.14.0/src/apm_cli/policy/_shared.py +40 -0
  65. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/ci_checks.py +40 -22
  66. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/discovery.py +5 -5
  67. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/inheritance.py +61 -10
  68. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/matcher.py +1 -1
  69. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/models.py +1 -0
  70. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/parser.py +28 -10
  71. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/policy_checks.py +9 -21
  72. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/schema.py +29 -7
  73. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/registry/client.py +206 -64
  74. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/base.py +40 -0
  75. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/codex_runtime.py +4 -3
  76. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/copilot_runtime.py +4 -24
  77. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/llm_runtime.py +2 -20
  78. apm_cli-0.14.0/src/apm_cli/runtime/utils.py +66 -0
  79. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/console.py +19 -4
  80. {apm_cli-0.13.0 → apm_cli-0.14.0/src/apm_cli.egg-info}/PKG-INFO +2 -1
  81. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli.egg-info/SOURCES.txt +13 -0
  82. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli.egg-info/requires.txt +1 -0
  83. apm_cli-0.13.0/src/apm_cli/adapters/client/base.py +0 -198
  84. apm_cli-0.13.0/src/apm_cli/commands/compile/watcher.py +0 -170
  85. apm_cli-0.13.0/src/apm_cli/commands/pack.py +0 -417
  86. {apm_cli-0.13.0 → apm_cli-0.14.0}/AUTHORS +0 -0
  87. {apm_cli-0.13.0 → apm_cli-0.14.0}/LICENSE +0 -0
  88. {apm_cli-0.13.0 → apm_cli-0.14.0}/NOTICE +0 -0
  89. {apm_cli-0.13.0 → apm_cli-0.14.0}/README.md +0 -0
  90. {apm_cli-0.13.0 → apm_cli-0.14.0}/setup.cfg +0 -0
  91. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/__init__.py +0 -0
  92. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/__init__.py +0 -0
  93. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/__init__.py +0 -0
  94. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/claude.py +0 -0
  95. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/vscode.py +0 -0
  96. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/client/windsurf.py +0 -0
  97. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  98. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/package_manager/base.py +0 -0
  99. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  100. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/__init__.py +0 -0
  101. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/local_bundle.py +0 -0
  102. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
  103. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/packer.py +0 -0
  104. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/bundle/unpacker.py +0 -0
  105. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/__init__.py +0 -0
  106. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/git_cache.py +0 -0
  107. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/http_cache.py +0 -0
  108. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/integrity.py +0 -0
  109. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/locking.py +0 -0
  110. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/paths.py +0 -0
  111. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/cache/url_normalize.py +0 -0
  112. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/__init__.py +0 -0
  113. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/_apm_yml_writer.py +0 -0
  114. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/cache.py +0 -0
  115. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/compile/__init__.py +0 -0
  116. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/config.py +0 -0
  117. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/deps/__init__.py +0 -0
  118. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/deps/_utils.py +0 -0
  119. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/deps/cli.py +0 -0
  120. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/experimental.py +0 -0
  121. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/list_cmd.py +0 -0
  122. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/check.py +0 -0
  123. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/migrate.py +0 -0
  124. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/outdated.py +0 -0
  125. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/plugin/__init__.py +0 -0
  126. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/plugin/add.py +0 -0
  127. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/plugin/remove.py +0 -0
  128. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/plugin/set.py +0 -0
  129. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/publish.py +0 -0
  130. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/marketplace/validate.py +0 -0
  131. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/mcp.py +0 -0
  132. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/prune.py +0 -0
  133. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/run.py +0 -0
  134. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/runtime.py +0 -0
  135. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/self_update.py +0 -0
  136. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/targets.py +0 -0
  137. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  138. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/commands/view.py +0 -0
  139. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/__init__.py +0 -0
  140. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/agents_compiler.py +0 -0
  141. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/build_id.py +0 -0
  142. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/constants.py +0 -0
  143. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/constitution.py +0 -0
  144. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/constitution_block.py +0 -0
  145. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/context_optimizer.py +0 -0
  146. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/gemini_formatter.py +0 -0
  147. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/injector.py +0 -0
  148. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/link_resolver.py +0 -0
  149. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/compilation/output_writer.py +0 -0
  150. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/config.py +0 -0
  151. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/constants.py +0 -0
  152. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/__init__.py +0 -0
  153. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/apm_yml.py +0 -0
  154. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/auth.py +0 -0
  155. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/azure_cli.py +0 -0
  156. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/command_logger.py +0 -0
  157. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/conflict_detector.py +0 -0
  158. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/docker_args.py +0 -0
  159. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/errors.py +0 -0
  160. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/experimental.py +0 -0
  161. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/null_logger.py +0 -0
  162. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/operations.py +0 -0
  163. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/safe_installer.py +0 -0
  164. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/scope.py +0 -0
  165. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/target_detection.py +0 -0
  166. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/core/token_manager.py +0 -0
  167. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/__init__.py +0 -0
  168. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/aggregator.py +0 -0
  169. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/apm_resolver.py +0 -0
  170. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/artifactory_entry.py +0 -0
  171. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/bare_cache.py +0 -0
  172. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/clone_engine.py +0 -0
  173. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/dependency_graph.py +0 -0
  174. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/download_strategies.py +0 -0
  175. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/git_auth_env.py +0 -0
  176. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/git_reference_resolver.py +0 -0
  177. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/git_remote_ops.py +0 -0
  178. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/github_downloader_validation.py +0 -0
  179. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/host_backends.py +0 -0
  180. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/installed_package.py +0 -0
  181. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/lockfile.py +0 -0
  182. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/package_validator.py +0 -0
  183. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/plugin_parser.py +0 -0
  184. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/registry_proxy.py +0 -0
  185. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/shared_clone_cache.py +0 -0
  186. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/transport_selection.py +0 -0
  187. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/deps/verifier.py +0 -0
  188. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/drift.py +0 -0
  189. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/factory.py +0 -0
  190. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/__init__.py +0 -0
  191. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/cache_pin.py +0 -0
  192. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/drift.py +0 -0
  193. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/errors.py +0 -0
  194. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/gitlab_resolver.py +0 -0
  195. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/heals/__init__.py +0 -0
  196. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/heals/base.py +0 -0
  197. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/heals/branch_ref_drift.py +0 -0
  198. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/heals/buggy_lockfile_recovery.py +0 -0
  199. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/helpers/__init__.py +0 -0
  200. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/helpers/security_scan.py +0 -0
  201. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/insecure_policy.py +0 -0
  202. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/__init__.py +0 -0
  203. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/args.py +0 -0
  204. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/command.py +0 -0
  205. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/conflicts.py +0 -0
  206. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/entry.py +0 -0
  207. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/registry.py +0 -0
  208. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/warnings.py +0 -0
  209. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/mcp/writer.py +0 -0
  210. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/package_resolution.py +0 -0
  211. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/__init__.py +0 -0
  212. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/cleanup.py +0 -0
  213. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/download.py +0 -0
  214. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/finalize.py +0 -0
  215. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/heal.py +0 -0
  216. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/integrate.py +0 -0
  217. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/local_content.py +0 -0
  218. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/lockfile.py +0 -0
  219. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/policy_gate.py +0 -0
  220. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/policy_target_check.py +0 -0
  221. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/post_deps_local.py +0 -0
  222. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/phases/targets.py +0 -0
  223. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/pipeline.py +0 -0
  224. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/plan.py +0 -0
  225. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/presentation/__init__.py +0 -0
  226. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/presentation/dry_run.py +0 -0
  227. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/request.py +0 -0
  228. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/service.py +0 -0
  229. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/skill_path_migration.py +0 -0
  230. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/sources.py +0 -0
  231. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/summary.py +0 -0
  232. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/install/template.py +0 -0
  233. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/__init__.py +0 -0
  234. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/cleanup.py +0 -0
  235. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/copilot_cowork_paths.py +0 -0
  236. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/coverage.py +0 -0
  237. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/dispatch.py +0 -0
  238. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/skill_integrator.py +0 -0
  239. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/skill_transformer.py +0 -0
  240. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/integration/utils.py +0 -0
  241. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/_git_utils.py +0 -0
  242. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/_io.py +0 -0
  243. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/client.py +0 -0
  244. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/errors.py +0 -0
  245. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/git_stderr.py +0 -0
  246. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/migration.py +0 -0
  247. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/models.py +0 -0
  248. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/pr_integration.py +0 -0
  249. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/publisher.py +0 -0
  250. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/ref_resolver.py +0 -0
  251. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/registry.py +0 -0
  252. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/semver.py +0 -0
  253. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/shadow_detector.py +0 -0
  254. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/tag_pattern.py +0 -0
  255. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/validator.py +0 -0
  256. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/version_pins.py +0 -0
  257. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/marketplace/yml_editor.py +0 -0
  258. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/__init__.py +0 -0
  259. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/dependency/__init__.py +0 -0
  260. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/dependency/mcp.py +0 -0
  261. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/dependency/types.py +0 -0
  262. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/plugin.py +0 -0
  263. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/results.py +0 -0
  264. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/models/validation.py +0 -0
  265. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/output/__init__.py +0 -0
  266. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/output/formatters.py +0 -0
  267. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/output/models.py +0 -0
  268. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/output/script_formatters.py +0 -0
  269. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/__init__.py +0 -0
  270. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/_help_text.py +0 -0
  271. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/install_preflight.py +0 -0
  272. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/outcome_routing.py +0 -0
  273. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/policy/project_config.py +0 -0
  274. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/primitives/__init__.py +0 -0
  275. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/primitives/discovery.py +0 -0
  276. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/primitives/models.py +0 -0
  277. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/primitives/parser.py +0 -0
  278. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/registry/__init__.py +0 -0
  279. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/registry/integration.py +0 -0
  280. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/registry/operations.py +0 -0
  281. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/__init__.py +0 -0
  282. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/factory.py +0 -0
  283. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/runtime/manager.py +0 -0
  284. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/security/__init__.py +0 -0
  285. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/security/audit_report.py +0 -0
  286. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/security/content_scanner.py +0 -0
  287. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/security/file_scanner.py +0 -0
  288. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/security/gate.py +0 -0
  289. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/update_policy.py +0 -0
  290. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/__init__.py +0 -0
  291. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/atomic_io.py +0 -0
  292. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/content_hash.py +0 -0
  293. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/diagnostics.py +0 -0
  294. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/exclude.py +0 -0
  295. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/file_ops.py +0 -0
  296. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/git_env.py +0 -0
  297. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/github_host.py +0 -0
  298. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/guards.py +0 -0
  299. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/helpers.py +0 -0
  300. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/install_tui.py +0 -0
  301. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/normalization.py +0 -0
  302. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/path_security.py +0 -0
  303. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/paths.py +0 -0
  304. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/reflink.py +0 -0
  305. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/short_sha.py +0 -0
  306. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/subprocess_env.py +0 -0
  307. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/version_checker.py +0 -0
  308. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/utils/yaml_io.py +0 -0
  309. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/version.py +0 -0
  310. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/workflow/__init__.py +0 -0
  311. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/workflow/discovery.py +0 -0
  312. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/workflow/parser.py +0 -0
  313. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli/workflow/runner.py +0 -0
  314. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  315. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli.egg-info/entry_points.txt +0 -0
  316. {apm_cli-0.13.0 → apm_cli-0.14.0}/src/apm_cli.egg-info/top_level.txt +0 -0
  317. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_apm_package_models.py +0 -0
  318. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_apm_resolver.py +0 -0
  319. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_codex_docker_args_fix.py +0 -0
  320. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_codex_empty_string_and_defaults.py +0 -0
  321. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_collision_integration.py +0 -0
  322. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_console.py +0 -0
  323. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_distributed_compilation.py +0 -0
  324. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_empty_string_and_defaults.py +0 -0
  325. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_enhanced_discovery.py +0 -0
  326. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_github_downloader.py +0 -0
  327. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_github_downloader_token_precedence.py +0 -0
  328. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_lockfile.py +0 -0
  329. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_runnable_prompts.py +0 -0
  330. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_runtime_manager_token_precedence.py +0 -0
  331. {apm_cli-0.13.0 → apm_cli-0.14.0}/tests/test_token_manager.py +0 -0
  332. {apm_cli-0.13.0 → apm_cli-0.14.0}/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.13.0
3
+ Version: 0.14.0
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -60,6 +60,7 @@ Requires-Dist: pytest-split>=0.9.0; extra == "dev"
60
60
  Requires-Dist: ruff>=0.11.0; extra == "dev"
61
61
  Requires-Dist: mypy>=1.0.0; extra == "dev"
62
62
  Requires-Dist: jsonschema>=4.0.0; extra == "dev"
63
+ Requires-Dist: pylint>=3.0.0; extra == "dev"
63
64
  Provides-Extra: build
64
65
  Requires-Dist: pyinstaller>=6.0.0; extra == "build"
65
66
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.13.0"
7
+ version = "0.14.0"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -48,6 +48,7 @@ dev = [
48
48
  "ruff>=0.11.0",
49
49
  "mypy>=1.0.0",
50
50
  "jsonschema>=4.0.0",
51
+ "pylint>=3.0.0",
51
52
  ]
52
53
  build = [
53
54
  "pyinstaller>=6.0.0",
@@ -0,0 +1,451 @@
1
+ """Base adapter interface for MCP clients."""
2
+
3
+ import os
4
+ import re
5
+ from abc import ABC, abstractmethod
6
+ from pathlib import Path
7
+
8
+ from ...utils.console import _rich_error, _rich_warning
9
+
10
+ _INPUT_VAR_RE = re.compile(r"\$\{input:([^}]+)\}")
11
+
12
+ # Matches ${VAR} and ${env:VAR}, capturing VAR. Intentionally does NOT match
13
+ # ${input:VAR} (the optional ``env:`` group cannot also satisfy ``input:``),
14
+ # nor GitHub Actions ``${{ ... }}`` templates (the second ``{`` fails the
15
+ # identifier class). This keeps env-var handling fully disjoint from input
16
+ # variable handling, so existing _INPUT_VAR_RE call sites are unaffected.
17
+ _ENV_VAR_RE = re.compile(r"\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}")
18
+
19
+
20
+ class MCPClientAdapter(ABC):
21
+ """Base adapter for MCP clients."""
22
+
23
+ # Identifier matching the corresponding ``KNOWN_TARGETS`` entry name.
24
+ # Subclasses MUST override this so target-aware code can look up
25
+ # per-target metadata via ``KNOWN_TARGETS[adapter.target_name]``
26
+ # instead of sniffing class names. The ``vscode`` adapter is the
27
+ # only MCP-only pseudo-target (no entry in ``KNOWN_TARGETS``), so
28
+ # downstream code that joins on this field must tolerate misses.
29
+ target_name: str = ""
30
+
31
+ # Top-level config key under which this adapter's MCP server entries
32
+ # live (``"mcpServers"``, ``"mcp_servers"``, ``"servers"``, ...).
33
+ # Subclasses MUST override this; ``MCPConflictDetector`` reads it to
34
+ # extract existing server configs without classname dispatch.
35
+ # The adapter is the canonical owner of its config schema, so this
36
+ # field lives here rather than on ``TargetProfile`` (which is
37
+ # primitive-focused) and applies uniformly to MCP-only adapters
38
+ # (e.g. ``VSCodeClientAdapter``) that have no ``KNOWN_TARGETS`` entry.
39
+ mcp_servers_key: str = ""
40
+
41
+ # Whether this adapter's config path is user/global-scoped (e.g.
42
+ # ``~/.copilot/``) rather than workspace-scoped (e.g. ``.vscode/``).
43
+ # Adapters that target a global path should override this to ``True``
44
+ # so that ``apm install --global`` can install MCP servers to them.
45
+ supports_user_scope: bool = False
46
+
47
+ def __init__(
48
+ self,
49
+ project_root: Path | str | None = None,
50
+ user_scope: bool = False,
51
+ ):
52
+ """Initialize the adapter with optional scope-aware path context.
53
+
54
+ Args:
55
+ project_root: Project root used to resolve project-local config paths.
56
+ When not provided, adapters fall back to the current working
57
+ directory for project-scoped paths.
58
+ user_scope: Whether the adapter should resolve user-scope config
59
+ paths instead of project-local paths when supported.
60
+ """
61
+ self._project_root = Path(project_root) if project_root is not None else None
62
+ self.user_scope = user_scope
63
+
64
+ @property
65
+ def project_root(self) -> Path:
66
+ """Return the explicit project root or the current working directory."""
67
+ if self._project_root is not None:
68
+ return self._project_root
69
+ return Path(os.getcwd())
70
+
71
+ @abstractmethod
72
+ def get_config_path(self):
73
+ """Get the path to the MCP configuration file."""
74
+ pass
75
+
76
+ @abstractmethod
77
+ def update_config(self, config_updates) -> bool | None:
78
+ """Update the MCP configuration.
79
+
80
+ Returns ``False`` or ``None`` when the config write was skipped
81
+ (for example because the existing file could not be parsed safely).
82
+ """
83
+ pass
84
+
85
+ @abstractmethod
86
+ def get_current_config(self):
87
+ """Get the current MCP configuration."""
88
+ pass
89
+
90
+ @abstractmethod
91
+ def configure_mcp_server(
92
+ self,
93
+ server_url,
94
+ server_name=None,
95
+ enabled=True,
96
+ env_overrides=None,
97
+ server_info_cache=None,
98
+ runtime_vars=None,
99
+ ):
100
+ """Configure an MCP server in the client configuration.
101
+
102
+ Args:
103
+ server_url (str): URL of the MCP server.
104
+ server_name (str, optional): Name of the server. Defaults to None.
105
+ enabled (bool, optional): Whether to enable the server. Defaults to True.
106
+ env_overrides (dict, optional): Environment variable overrides. Defaults to None.
107
+ server_info_cache (dict, optional): Pre-fetched server info to avoid duplicate registry calls.
108
+ runtime_vars (dict, optional): Runtime variable values. Defaults to None.
109
+
110
+ Returns:
111
+ bool: True if successful, False otherwise.
112
+ """
113
+ pass
114
+
115
+ @staticmethod
116
+ def _infer_registry_name(package):
117
+ """Infer the registry type from package metadata.
118
+
119
+ The MCP registry API often returns empty ``registry_name``. This
120
+ method derives the registry from explicit fields first, then falls
121
+ back to heuristics on the package name.
122
+
123
+ Args:
124
+ package (dict): A single package entry from the registry.
125
+
126
+ Returns:
127
+ str: Inferred registry name (e.g. "npm", "pypi", "docker") or "".
128
+ """
129
+ if not package:
130
+ return ""
131
+
132
+ explicit = package.get("registry_name", "")
133
+ if explicit:
134
+ return explicit
135
+
136
+ name = package.get("name", "")
137
+ runtime_hint = package.get("runtime_hint", "")
138
+
139
+ # Infer from runtime_hint
140
+ if runtime_hint in ("npx", "npm"):
141
+ return "npm"
142
+ if runtime_hint in ("uvx", "pip", "pipx"):
143
+ return "pypi"
144
+ if runtime_hint == "docker":
145
+ return "docker"
146
+ if runtime_hint in ("dotnet", "dnx"):
147
+ return "nuget"
148
+
149
+ # Infer from package name patterns
150
+ if name.startswith("@") and "/" in name:
151
+ return "npm" # scoped npm package, e.g. @azure/mcp
152
+ if name.startswith(("ghcr.io/", "mcr.microsoft.com/", "docker.io/")):
153
+ return "docker"
154
+ if name.startswith("https://") and name.endswith(".mcpb"):
155
+ return "mcpb"
156
+ # PascalCase with dots usually means nuget (e.g. Azure.Mcp)
157
+ if "." in name and not name.startswith("http") and name[0].isupper():
158
+ return "nuget"
159
+
160
+ return ""
161
+
162
+ @staticmethod
163
+ def _warn_input_variables(mapping, server_name, runtime_label):
164
+ """Emit a warning for each ``${input:...}`` reference found in *mapping*.
165
+
166
+ Runtimes that do not support VS Code-style input prompts (Copilot CLI,
167
+ Codex CLI, etc.) should call this so users know their placeholders
168
+ will not be resolved at runtime.
169
+
170
+ Args:
171
+ mapping (dict): Header or env dict to scan.
172
+ server_name (str): Server name for the warning message.
173
+ runtime_label (str): Human-readable runtime name (e.g. "Copilot CLI").
174
+ """
175
+ if not mapping:
176
+ return
177
+ seen: set = set()
178
+ for value in mapping.values():
179
+ if not isinstance(value, str):
180
+ continue
181
+ for match in _INPUT_VAR_RE.finditer(value):
182
+ var_id = match.group(1)
183
+ if var_id in seen:
184
+ continue
185
+ seen.add(var_id)
186
+ _rich_warning(
187
+ f"${{input:{var_id}}} in server "
188
+ f"'{server_name}' will not be resolved -- "
189
+ f"{runtime_label} does not support input variable prompts"
190
+ )
191
+
192
+ def normalize_project_arg(self, value):
193
+ """Normalize workspace placeholders for project-local runtimes."""
194
+ if (
195
+ not self.user_scope
196
+ and isinstance(value, str)
197
+ and value in {"${workspaceFolder}", "${projectRoot}", "${workspaceRoot}"}
198
+ ):
199
+ return "."
200
+ return value
201
+
202
+ # ------------------------------------------------------------------
203
+ # Shared server-info helpers (used by all adapter subclasses)
204
+ # ------------------------------------------------------------------
205
+
206
+ def _fetch_server_info(self, server_url: str, server_info_cache: dict | None) -> dict | None:
207
+ """Look up *server_url* in *server_info_cache* or fetch from registry.
208
+
209
+ Prints a user-visible error and returns ``None`` when the server is
210
+ not found, so callers can do a simple ``if server_info is None: return False``
211
+ guard and the error message stays consistent across adapters.
212
+
213
+ Args:
214
+ server_url: Registry reference (``owner/repo`` or full URL).
215
+ server_info_cache: Optional pre-fetched cache; ``None`` skips
216
+ the cache lookup.
217
+
218
+ Returns:
219
+ Server-info dict on success; ``None`` when not found.
220
+ """
221
+ if server_info_cache and server_url in server_info_cache:
222
+ return server_info_cache[server_url]
223
+ server_info = self.registry_client.find_server_by_reference(server_url)
224
+ if not server_info:
225
+ _rich_error(f"Error: MCP server '{server_url}' not found in registry")
226
+ return None
227
+ return server_info
228
+
229
+ @staticmethod
230
+ def _determine_config_key(server_url: str, server_name: str) -> str:
231
+ """Return the configuration key to use for *server_url*/*server_name*.
232
+
233
+ The caller-supplied *server_name* takes precedence; if empty the last
234
+ path segment of *server_url* is used as a fallback, which mirrors the
235
+ convention ``owner/repo -> repo``.
236
+
237
+ Args:
238
+ server_url: Registry reference used as fallback source.
239
+ server_name: Explicit caller-supplied name (may be empty string).
240
+
241
+ Returns:
242
+ Non-empty configuration key string.
243
+ """
244
+ if server_name:
245
+ return server_name
246
+ if "/" in server_url:
247
+ return server_url.split("/")[-1]
248
+ return server_url
249
+
250
+ @staticmethod
251
+ def _apply_pypi_homebrew_generic_config(
252
+ config: dict,
253
+ registry_name: str,
254
+ package_name: str,
255
+ runtime_hint: str,
256
+ processed_runtime_args: list,
257
+ processed_package_args: list,
258
+ resolved_env: dict,
259
+ ) -> None:
260
+ """Apply pypi / homebrew / generic (uvx / brew / npx) run config to *config*.
261
+
262
+ Mutates *config* in-place with ``command``, ``args``, and optionally
263
+ ``env`` keys appropriate for the detected registry type.
264
+
265
+ Args:
266
+ config: Mutable server-config dict to populate.
267
+ registry_name: Registry identifier (``"pypi"``, ``"homebrew"``,
268
+ ``"npm"``, or any other string treated as generic).
269
+ package_name: Base package / formula / module name.
270
+ runtime_hint: Caller-specified runtime hint (e.g. ``"uvx"``).
271
+ processed_runtime_args: Fully resolved positional args for the
272
+ runtime launcher.
273
+ processed_package_args: Fully resolved positional args appended
274
+ after the package name.
275
+ resolved_env: Pre-resolved environment variables dict; an empty
276
+ dict is omitted.
277
+ """
278
+ if registry_name == "pypi":
279
+ launcher = runtime_hint or "uvx"
280
+ config["command"] = launcher
281
+ config["args"] = [package_name] + processed_runtime_args + processed_package_args # noqa: RUF005
282
+ elif registry_name == "homebrew":
283
+ formula_name = package_name.split("/")[-1] if "/" in package_name else package_name
284
+ config["command"] = formula_name
285
+ config["args"] = processed_runtime_args + processed_package_args
286
+ else:
287
+ # Generic / npm-compatible fallback
288
+ config["command"] = "npx"
289
+ config["args"] = processed_runtime_args + ["-y", package_name] + processed_package_args # noqa: RUF005
290
+ if resolved_env:
291
+ config["env"] = resolved_env
292
+
293
+ def _apply_auth_and_headers_impl(
294
+ self,
295
+ config: dict,
296
+ remote: dict,
297
+ server_info: dict,
298
+ env_overrides: dict,
299
+ runtime_label: str,
300
+ token_manager_class,
301
+ ) -> None:
302
+ """Core implementation of GitHub-token injection and header merging.
303
+
304
+ Factored out so that each concrete adapter subclass can supply its own
305
+ *token_manager_class* (looked up from the subclass module's namespace),
306
+ allowing :func:`unittest.mock.patch` to intercept the class at the
307
+ right module scope in tests.
308
+
309
+ Args:
310
+ config: Mutable config dict updated in place.
311
+ remote: Registry remote entry (may contain a ``"headers"`` list).
312
+ server_info: Registry server metadata used for name / URL lookup.
313
+ env_overrides: Caller-supplied env-var override mapping.
314
+ runtime_label: Label for diagnostic messages.
315
+ token_manager_class: The ``GitHubTokenManager`` class (or mock) to
316
+ instantiate. Passed by the caller so tests can patch the right
317
+ module-level name.
318
+ """
319
+ server_name = server_info.get("name", "")
320
+ is_github_server = self._is_github_server(server_name, remote.get("url", ""))
321
+ local_token_injected = False
322
+ if is_github_server:
323
+ _tm = token_manager_class()
324
+ github_token = _tm.get_token_for_purpose("copilot") or os.getenv(
325
+ "GITHUB_PERSONAL_ACCESS_TOKEN"
326
+ )
327
+ if github_token:
328
+ config["headers"] = {"Authorization": f"Bearer {github_token}"}
329
+ local_token_injected = True
330
+ headers = remote.get("headers", [])
331
+ if headers:
332
+ if "headers" not in config:
333
+ config["headers"] = {}
334
+ for header in headers:
335
+ header_name = header.get("name", "")
336
+ header_value = header.get("value", "")
337
+ if header_name and header_value:
338
+ if header_name == "Authorization" and local_token_injected:
339
+ continue
340
+ resolved_value = self._resolve_env_variable(
341
+ header_name, header_value, env_overrides
342
+ )
343
+ config["headers"][header_name] = resolved_value
344
+ if config.get("headers"):
345
+ self._warn_input_variables(
346
+ config["headers"], server_info.get("name", ""), runtime_label
347
+ )
348
+
349
+ @staticmethod
350
+ def _resolve_env_vars_with_prompting(
351
+ env_vars: list,
352
+ env_overrides: dict,
353
+ default_github_env: dict,
354
+ ) -> dict:
355
+ """Resolve *env_vars* from overrides, environment, or interactive prompts.
356
+
357
+ Identical logic shared between
358
+ :meth:`CopilotClientAdapter._process_environment_variables` and
359
+ :meth:`CodexClientAdapter._process_environment_variables`.
360
+
361
+ All imports are deferred so that ``rich.prompt`` (an optional
362
+ dependency) is never imported at module load time.
363
+
364
+ Args:
365
+ env_vars: List of env-var descriptor dicts from the registry.
366
+ env_overrides: Pre-collected ``{name: value}`` overrides (empty
367
+ dict when none).
368
+ default_github_env: Mapping of well-known GitHub variable names
369
+ to their preferred environment-variable lookup names.
370
+
371
+ Returns:
372
+ ``resolved`` dict mapping each env-var name to its resolved value
373
+ (empty string when unresolvable).
374
+ """
375
+ import sys
376
+
377
+ env_overrides = env_overrides or {}
378
+ resolved: dict = {}
379
+
380
+ # Determine whether interactive prompting is available.
381
+ # If env_overrides is provided the CLI has already collected variables -- never prompt again.
382
+ skip_prompting = (
383
+ bool(env_overrides)
384
+ or bool(os.getenv("CI"))
385
+ or bool(os.getenv("APM_E2E_TESTS"))
386
+ or not sys.stdout.isatty()
387
+ or not sys.stdin.isatty()
388
+ )
389
+
390
+ # First pass: identify variables with empty values to warn the user.
391
+ empty_value_vars = [ev for ev in env_vars if ev.get("required") and not ev.get("value")]
392
+ if empty_value_vars and skip_prompting:
393
+ var_names = [ev.get("name") for ev in empty_value_vars]
394
+ _rich_warning(
395
+ f"Warning: The following required environment variables have no default "
396
+ f"value and cannot be prompted in non-interactive mode: {var_names}"
397
+ )
398
+
399
+ for env_var in env_vars:
400
+ name = env_var.get("name", "")
401
+ if not name:
402
+ continue
403
+
404
+ # Priority 1: caller-supplied override
405
+ if name in env_overrides:
406
+ resolved[name] = env_overrides[name]
407
+ continue
408
+
409
+ # Priority 2: check GitHub-specific defaults (values are literal defaults, not env-var names)
410
+ if name in default_github_env:
411
+ resolved[name] = os.getenv(name) or default_github_env[name]
412
+ continue
413
+
414
+ # Priority 3: environment variable with the same name
415
+ env_val = os.getenv(name, "")
416
+ if env_val:
417
+ resolved[name] = env_val
418
+ continue
419
+
420
+ # Priority 4: interactive prompt
421
+ default_value = env_var.get("value", "")
422
+ required = env_var.get("required", False)
423
+
424
+ if not skip_prompting:
425
+ from rich.prompt import Prompt
426
+
427
+ description = env_var.get("description", "")
428
+ prompt_text = f"Enter value for {name}"
429
+ if description:
430
+ prompt_text += f" ({description})"
431
+ is_secret = "token" in name.lower() or "key" in name.lower()
432
+ user_input = Prompt.ask(
433
+ prompt_text,
434
+ default=default_value,
435
+ password=True # noqa: SIM210
436
+ if is_secret
437
+ else False,
438
+ )
439
+ resolved[name] = user_input
440
+ elif default_value:
441
+ resolved[name] = default_value
442
+ elif required:
443
+ _rich_warning(
444
+ f"Warning: Required environment variable '{name}' could not be resolved. "
445
+ f"The MCP server may not function correctly."
446
+ )
447
+ resolved[name] = ""
448
+ else:
449
+ resolved[name] = default_value
450
+
451
+ return resolved
@@ -143,16 +143,8 @@ class CodexClientAdapter(MCPClientAdapter):
143
143
  return False
144
144
 
145
145
  try:
146
- # Use cached server info if available, otherwise fetch from registry
147
- if server_info_cache and server_url in server_info_cache:
148
- server_info = server_info_cache[server_url]
149
- else:
150
- # Fallback to registry lookup if not cached
151
- server_info = self.registry_client.find_server_by_reference(server_url)
152
-
153
- # Fail if server is not found in registry - security requirement
154
- if not server_info:
155
- print(f"Error: MCP server '{server_url}' not found in registry")
146
+ server_info = self._fetch_server_info(server_url, server_info_cache)
147
+ if server_info is None:
156
148
  return False
157
149
 
158
150
  # Check for remote servers early - Codex doesn't support remote/SSE servers
@@ -299,33 +291,21 @@ class CodexClientAdapter(MCPClientAdapter):
299
291
  if resolved_env:
300
292
  config["env"] = resolved_env
301
293
  elif registry_name == "pypi":
302
- config["command"] = runtime_hint or "uvx"
303
- config["args"] = (
304
- [package_name] + processed_runtime_args + processed_package_args # noqa: RUF005
305
- )
306
- # For PyPI packages, use env block for environment variables
307
- if resolved_env:
308
- config["env"] = resolved_env
309
- elif registry_name == "homebrew":
310
- # For homebrew packages, assume the binary name is the command
311
- config["command"] = (
312
- package_name.split("/")[-1] if "/" in package_name else package_name
294
+ self._apply_pypi_homebrew_generic_config(
295
+ config,
296
+ registry_name,
297
+ package_name,
298
+ runtime_hint,
299
+ processed_runtime_args,
300
+ processed_package_args,
301
+ resolved_env,
313
302
  )
314
- config["args"] = processed_runtime_args + processed_package_args
315
- # For Homebrew packages, use env block for environment variables
316
- if resolved_env:
317
- config["env"] = resolved_env
318
- else:
319
- # Generic package handling
320
- config["command"] = runtime_hint or package_name
321
- config["args"] = processed_runtime_args + processed_package_args
322
- # For generic packages, use env block for environment variables
323
- if resolved_env:
324
- config["env"] = resolved_env
325
303
 
326
304
  return config
327
305
 
328
- def _process_arguments(self, arguments, resolved_env=None, runtime_vars=None):
306
+ def _process_arguments( # pylint: disable=duplicate-code # structural similarity with copilot adapter is intentional
307
+ self, arguments, resolved_env=None, runtime_vars=None
308
+ ):
329
309
  """Process argument objects to extract simple string values with environment resolution.
330
310
 
331
311
  Args:
@@ -390,76 +370,8 @@ class CodexClientAdapter(MCPClientAdapter):
390
370
  Returns:
391
371
  dict: Dictionary of resolved environment variable values.
392
372
  """
393
- import os
394
- import sys
395
-
396
- from rich.prompt import Prompt
397
-
398
- resolved = {}
399
- env_overrides = env_overrides or {}
400
-
401
- # If env_overrides is provided, it means the CLI has already handled environment variable collection
402
- # In this case, we should NEVER prompt for additional variables
403
- skip_prompting = bool(env_overrides)
404
-
405
- # Check for CI/automated environment via APM_E2E_TESTS flag (more reliable than TTY detection)
406
- if os.getenv("APM_E2E_TESTS") == "1":
407
- skip_prompting = True
408
- print(f" APM_E2E_TESTS detected, will skip environment variable prompts") # noqa: F541
409
-
410
- # Also skip prompting if we're in a non-interactive environment (fallback)
411
- is_interactive = sys.stdin.isatty() and sys.stdout.isatty()
412
- if not is_interactive:
413
- skip_prompting = True
414
-
415
- # Add default GitHub MCP server environment variables for essential functionality first
416
- # This ensures variables have defaults when user provides empty values or they're optional
417
373
  default_github_env = {"GITHUB_TOOLSETS": "context", "GITHUB_DYNAMIC_TOOLSETS": "1"}
418
-
419
- # Track which variables were explicitly provided with empty values (user wants defaults)
420
- empty_value_vars = set()
421
- if env_overrides:
422
- for key, value in env_overrides.items():
423
- if key in env_overrides and (not value or not value.strip()):
424
- empty_value_vars.add(key)
425
-
426
- for env_var in env_vars:
427
- if isinstance(env_var, dict):
428
- name = env_var.get("name", "")
429
- description = env_var.get("description", "")
430
- required = env_var.get("required", True)
431
-
432
- if name:
433
- # First check overrides, then environment
434
- value = env_overrides.get(name) or os.getenv(name)
435
-
436
- # Only prompt if not provided in overrides or environment AND it's required AND we're not in managed override mode
437
- if not value and required and not skip_prompting:
438
- # Only prompt if not provided in overrides
439
- prompt_text = f"Enter value for {name}"
440
- if description:
441
- prompt_text += f" ({description})"
442
- value = Prompt.ask(
443
- prompt_text,
444
- password=True # noqa: SIM210
445
- if "token" in name.lower() or "key" in name.lower()
446
- else False,
447
- )
448
-
449
- # Add variable if it has a value OR if user explicitly provided empty and we have a default
450
- if value and value.strip():
451
- resolved[name] = value
452
- elif name in empty_value_vars and name in default_github_env:
453
- # User provided empty value and we have a default - use default
454
- resolved[name] = default_github_env[name]
455
- elif not required and name in default_github_env:
456
- # Variable is optional and we have a default - use default
457
- resolved[name] = default_github_env[name]
458
- elif skip_prompting and name in default_github_env:
459
- # Non-interactive environment and we have a default - use default
460
- resolved[name] = default_github_env[name]
461
-
462
- return resolved
374
+ return self._resolve_env_vars_with_prompting(env_vars, env_overrides, default_github_env)
463
375
 
464
376
  def _resolve_variable_placeholders(self, value, resolved_env, runtime_vars):
465
377
  """Resolve both environment and runtime variable placeholders in values.