apm-cli 0.8.11__tar.gz → 0.9.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 (223) hide show
  1. {apm_cli-0.8.11 → apm_cli-0.9.0}/AUTHORS +1 -0
  2. {apm_cli-0.8.11/src/apm_cli.egg-info → apm_cli-0.9.0}/PKG-INFO +15 -3
  3. {apm_cli-0.8.11 → apm_cli-0.9.0}/README.md +14 -2
  4. {apm_cli-0.8.11 → apm_cli-0.9.0}/pyproject.toml +1 -1
  5. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/base.py +6 -0
  6. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/codex.py +3 -1
  7. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/copilot.py +3 -1
  8. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/cursor.py +2 -0
  9. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/opencode.py +6 -1
  10. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/vscode.py +26 -5
  11. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/bundle/lockfile_enrichment.py +39 -8
  12. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/bundle/packer.py +19 -11
  13. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/bundle/plugin_exporter.py +13 -10
  14. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/bundle/unpacker.py +2 -1
  15. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/cli.py +12 -0
  16. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/_helpers.py +22 -9
  17. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/compile/cli.py +62 -15
  18. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/config.py +99 -26
  19. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/deps/_utils.py +1 -1
  20. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/deps/cli.py +61 -130
  21. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/init.py +53 -17
  22. apm_cli-0.9.0/src/apm_cli/commands/install.py +1498 -0
  23. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/marketplace.py +128 -12
  24. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/mcp.py +133 -14
  25. apm_cli-0.9.0/src/apm_cli/commands/outdated.py +474 -0
  26. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/pack.py +3 -2
  27. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/cli.py +1 -1
  28. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/engine.py +2 -2
  29. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/update.py +9 -1
  30. apm_cli-0.9.0/src/apm_cli/commands/view.py +499 -0
  31. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/agents_compiler.py +52 -8
  32. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/config.py +54 -0
  33. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/auth.py +111 -27
  34. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/command_logger.py +79 -4
  35. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/script_runner.py +3 -3
  36. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/target_detection.py +142 -1
  37. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/token_manager.py +49 -17
  38. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/github_downloader.py +714 -294
  39. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/lockfile.py +63 -8
  40. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/plugin_parser.py +113 -10
  41. apm_cli-0.9.0/src/apm_cli/deps/transport_selection.py +331 -0
  42. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/drift.py +55 -8
  43. apm_cli-0.9.0/src/apm_cli/install/__init__.py +23 -0
  44. apm_cli-0.9.0/src/apm_cli/install/context.py +118 -0
  45. apm_cli-0.9.0/src/apm_cli/install/helpers/__init__.py +1 -0
  46. apm_cli-0.9.0/src/apm_cli/install/helpers/security_scan.py +51 -0
  47. apm_cli-0.9.0/src/apm_cli/install/insecure_policy.py +239 -0
  48. apm_cli-0.9.0/src/apm_cli/install/mcp_registry.py +256 -0
  49. apm_cli-0.9.0/src/apm_cli/install/mcp_warnings.py +122 -0
  50. apm_cli-0.9.0/src/apm_cli/install/phases/__init__.py +1 -0
  51. apm_cli-0.9.0/src/apm_cli/install/phases/cleanup.py +153 -0
  52. apm_cli-0.9.0/src/apm_cli/install/phases/download.py +141 -0
  53. apm_cli-0.9.0/src/apm_cli/install/phases/finalize.py +55 -0
  54. apm_cli-0.9.0/src/apm_cli/install/phases/integrate.py +412 -0
  55. apm_cli-0.9.0/src/apm_cli/install/phases/local_content.py +130 -0
  56. apm_cli-0.9.0/src/apm_cli/install/phases/lockfile.py +182 -0
  57. apm_cli-0.9.0/src/apm_cli/install/phases/post_deps_local.py +122 -0
  58. apm_cli-0.9.0/src/apm_cli/install/phases/resolve.py +338 -0
  59. apm_cli-0.9.0/src/apm_cli/install/phases/targets.py +104 -0
  60. apm_cli-0.9.0/src/apm_cli/install/pipeline.py +311 -0
  61. apm_cli-0.9.0/src/apm_cli/install/presentation/__init__.py +1 -0
  62. apm_cli-0.9.0/src/apm_cli/install/presentation/dry_run.py +93 -0
  63. apm_cli-0.9.0/src/apm_cli/install/request.py +43 -0
  64. apm_cli-0.9.0/src/apm_cli/install/service.py +79 -0
  65. apm_cli-0.9.0/src/apm_cli/install/services.py +230 -0
  66. apm_cli-0.9.0/src/apm_cli/install/sources.py +595 -0
  67. apm_cli-0.9.0/src/apm_cli/install/template.py +126 -0
  68. apm_cli-0.9.0/src/apm_cli/install/validation.py +358 -0
  69. apm_cli-0.9.0/src/apm_cli/integration/cleanup.py +211 -0
  70. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/hook_integrator.py +34 -6
  71. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/mcp_integrator.py +66 -0
  72. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/targets.py +48 -10
  73. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/client.py +95 -10
  74. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/resolver.py +123 -9
  75. apm_cli-0.9.0/src/apm_cli/marketplace/shadow_detector.py +75 -0
  76. apm_cli-0.9.0/src/apm_cli/marketplace/validator.py +80 -0
  77. apm_cli-0.9.0/src/apm_cli/marketplace/version_pins.py +163 -0
  78. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/apm_package.py +3 -1
  79. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/dependency/__init__.py +2 -1
  80. apm_cli-0.9.0/src/apm_cli/models/dependency/mcp.py +254 -0
  81. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/dependency/reference.py +181 -71
  82. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/dependency/types.py +9 -0
  83. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/validation.py +96 -22
  84. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/policy_checks.py +15 -9
  85. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/primitives/discovery.py +45 -20
  86. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/registry/client.py +75 -7
  87. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/registry/operations.py +13 -2
  88. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/manager.py +2 -1
  89. apm_cli-0.9.0/src/apm_cli/update_policy.py +51 -0
  90. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/content_hash.py +23 -0
  91. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/github_host.py +38 -8
  92. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/path_security.py +8 -1
  93. {apm_cli-0.8.11 → apm_cli-0.9.0/src/apm_cli.egg-info}/PKG-INFO +15 -3
  94. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli.egg-info/SOURCES.txt +34 -0
  95. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_apm_package_models.py +127 -0
  96. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_github_downloader.py +341 -138
  97. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_lockfile.py +95 -1
  98. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_token_manager.py +66 -2
  99. apm_cli-0.8.11/src/apm_cli/commands/install.py +0 -2466
  100. apm_cli-0.8.11/src/apm_cli/models/dependency/mcp.py +0 -136
  101. {apm_cli-0.8.11 → apm_cli-0.9.0}/LICENSE +0 -0
  102. {apm_cli-0.8.11 → apm_cli-0.9.0}/setup.cfg +0 -0
  103. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/__init__.py +0 -0
  104. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/__init__.py +0 -0
  105. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/client/__init__.py +0 -0
  106. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  107. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/base.py +0 -0
  108. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  109. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/bundle/__init__.py +0 -0
  110. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/__init__.py +0 -0
  111. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/audit.py +0 -0
  112. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/compile/__init__.py +0 -0
  113. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/compile/watcher.py +0 -0
  114. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/deps/__init__.py +0 -0
  115. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/list_cmd.py +0 -0
  116. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/prune.py +0 -0
  117. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/run.py +0 -0
  118. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/runtime.py +0 -0
  119. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  120. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/__init__.py +0 -0
  121. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/claude_formatter.py +0 -0
  122. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/constants.py +0 -0
  123. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/constitution.py +0 -0
  124. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/constitution_block.py +0 -0
  125. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/context_optimizer.py +0 -0
  126. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  127. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/injector.py +0 -0
  128. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/link_resolver.py +0 -0
  129. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/compilation/template_builder.py +0 -0
  130. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/constants.py +0 -0
  131. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/__init__.py +0 -0
  132. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/conflict_detector.py +0 -0
  133. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/docker_args.py +0 -0
  134. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/operations.py +0 -0
  135. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/safe_installer.py +0 -0
  136. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/core/scope.py +0 -0
  137. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/__init__.py +0 -0
  138. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/aggregator.py +0 -0
  139. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/apm_resolver.py +0 -0
  140. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/artifactory_entry.py +0 -0
  141. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/collection_parser.py +0 -0
  142. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/dependency_graph.py +0 -0
  143. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/installed_package.py +0 -0
  144. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/package_validator.py +0 -0
  145. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/registry_proxy.py +0 -0
  146. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/deps/verifier.py +0 -0
  147. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/factory.py +0 -0
  148. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/__init__.py +0 -0
  149. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/agent_integrator.py +0 -0
  150. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/base_integrator.py +0 -0
  151. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/command_integrator.py +0 -0
  152. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/coverage.py +0 -0
  153. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/dispatch.py +0 -0
  154. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/instruction_integrator.py +0 -0
  155. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/prompt_integrator.py +0 -0
  156. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/skill_integrator.py +0 -0
  157. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/skill_transformer.py +0 -0
  158. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/integration/utils.py +0 -0
  159. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/__init__.py +0 -0
  160. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/errors.py +0 -0
  161. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/models.py +0 -0
  162. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/marketplace/registry.py +0 -0
  163. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/__init__.py +0 -0
  164. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/plugin.py +0 -0
  165. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/models/results.py +0 -0
  166. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/output/__init__.py +0 -0
  167. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/output/formatters.py +0 -0
  168. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/output/models.py +0 -0
  169. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/output/script_formatters.py +0 -0
  170. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/__init__.py +0 -0
  171. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/ci_checks.py +0 -0
  172. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/discovery.py +0 -0
  173. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/inheritance.py +0 -0
  174. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/matcher.py +0 -0
  175. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/models.py +0 -0
  176. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/parser.py +0 -0
  177. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/policy/schema.py +0 -0
  178. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/primitives/__init__.py +0 -0
  179. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/primitives/models.py +0 -0
  180. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/primitives/parser.py +0 -0
  181. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/registry/__init__.py +0 -0
  182. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/registry/integration.py +0 -0
  183. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/__init__.py +0 -0
  184. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/base.py +0 -0
  185. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/codex_runtime.py +0 -0
  186. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  187. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/factory.py +0 -0
  188. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/runtime/llm_runtime.py +0 -0
  189. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/security/__init__.py +0 -0
  190. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/security/audit_report.py +0 -0
  191. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/security/content_scanner.py +0 -0
  192. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/security/file_scanner.py +0 -0
  193. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/security/gate.py +0 -0
  194. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/__init__.py +0 -0
  195. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/console.py +0 -0
  196. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/diagnostics.py +0 -0
  197. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/exclude.py +0 -0
  198. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/file_ops.py +0 -0
  199. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/helpers.py +0 -0
  200. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/paths.py +0 -0
  201. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/version_checker.py +0 -0
  202. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/utils/yaml_io.py +0 -0
  203. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/version.py +0 -0
  204. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/workflow/__init__.py +0 -0
  205. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/workflow/discovery.py +0 -0
  206. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/workflow/parser.py +0 -0
  207. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli/workflow/runner.py +0 -0
  208. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  209. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli.egg-info/entry_points.txt +0 -0
  210. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli.egg-info/requires.txt +0 -0
  211. {apm_cli-0.8.11 → apm_cli-0.9.0}/src/apm_cli.egg-info/top_level.txt +0 -0
  212. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_apm_resolver.py +0 -0
  213. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_codex_docker_args_fix.py +0 -0
  214. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_codex_empty_string_and_defaults.py +0 -0
  215. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_collision_integration.py +0 -0
  216. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_console.py +0 -0
  217. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_distributed_compilation.py +0 -0
  218. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_empty_string_and_defaults.py +0 -0
  219. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_enhanced_discovery.py +0 -0
  220. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_github_downloader_token_precedence.py +0 -0
  221. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_runnable_prompts.py +0 -0
  222. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_runtime_manager_token_precedence.py +0 -0
  223. {apm_cli-0.8.11 → apm_cli-0.9.0}/tests/test_virtual_package_multi_install.py +0 -0
@@ -3,6 +3,7 @@
3
3
  ## Project Maintainers
4
4
 
5
5
  * Daniel Meppiel (@danielmeppiel) - Creator and Lead Maintainer
6
+ * Sergio Sisternes (@sergio-sisternes-epam) - Maintainer
6
7
 
7
8
  ## Contributors
8
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apm-cli
3
- Version: 0.8.11
3
+ Version: 0.9.0
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -90,6 +90,10 @@ dependencies:
90
90
  - github/awesome-copilot/agents/api-architect.agent.md
91
91
  # A full APM package with instructions, skills, prompts, hooks...
92
92
  - microsoft/apm-sample-package#v1.0.0
93
+ mcp:
94
+ # MCP servers -- installed into every detected client
95
+ - name: io.github.github/github-mcp-server
96
+ transport: http # MCP transport name, not URL scheme -- connects over HTTPS
93
97
  ```
94
98
 
95
99
  ```bash
@@ -99,7 +103,7 @@ apm install # every agent is configured
99
103
 
100
104
  ## Highlights
101
105
 
102
- - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
106
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
103
107
  - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
104
108
  - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
105
109
  - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
@@ -161,6 +165,14 @@ apm marketplace add github/awesome-copilot
161
165
  apm install azure-cloud-development@awesome-copilot
162
166
  ```
163
167
 
168
+ Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and OpenCode):
169
+
170
+ ```bash
171
+ apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
172
+ ```
173
+
174
+ > *Codex CLI currently does not support remote MCP servers; the install will skip Codex with a notice. Omit `--transport http` to use the local Docker variant on Codex (requires `GITHUB_PERSONAL_ACCESS_TOKEN`).*
175
+
164
176
  See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
165
177
 
166
178
  ## Works with agentrc
@@ -171,7 +183,7 @@ Use agentrc to author high-quality instructions, then package them with APM to s
171
183
 
172
184
  ## Community
173
185
 
174
- Created and maintained by [@danielmeppiel](https://github.com/danielmeppiel).
186
+ Created by [@danielmeppiel](https://github.com/danielmeppiel). Maintained by [@danielmeppiel](https://github.com/danielmeppiel) and [@sergio-sisternes-epam](https://github.com/sergio-sisternes-epam).
175
187
 
176
188
  - [Roadmap & Discussions](https://github.com/microsoft/apm/discussions/116)
177
189
  - [Contributing](CONTRIBUTING.md)
@@ -28,6 +28,10 @@ dependencies:
28
28
  - github/awesome-copilot/agents/api-architect.agent.md
29
29
  # A full APM package with instructions, skills, prompts, hooks...
30
30
  - microsoft/apm-sample-package#v1.0.0
31
+ mcp:
32
+ # MCP servers -- installed into every detected client
33
+ - name: io.github.github/github-mcp-server
34
+ transport: http # MCP transport name, not URL scheme -- connects over HTTPS
31
35
  ```
32
36
 
33
37
  ```bash
@@ -37,7 +41,7 @@ apm install # every agent is configured
37
41
 
38
42
  ## Highlights
39
43
 
40
- - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
44
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
41
45
  - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
42
46
  - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
43
47
  - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
@@ -99,6 +103,14 @@ apm marketplace add github/awesome-copilot
99
103
  apm install azure-cloud-development@awesome-copilot
100
104
  ```
101
105
 
106
+ Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and OpenCode):
107
+
108
+ ```bash
109
+ apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
110
+ ```
111
+
112
+ > *Codex CLI currently does not support remote MCP servers; the install will skip Codex with a notice. Omit `--transport http` to use the local Docker variant on Codex (requires `GITHUB_PERSONAL_ACCESS_TOKEN`).*
113
+
102
114
  See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
103
115
 
104
116
  ## Works with agentrc
@@ -109,7 +121,7 @@ Use agentrc to author high-quality instructions, then package them with APM to s
109
121
 
110
122
  ## Community
111
123
 
112
- Created and maintained by [@danielmeppiel](https://github.com/danielmeppiel).
124
+ Created by [@danielmeppiel](https://github.com/danielmeppiel). Maintained by [@danielmeppiel](https://github.com/danielmeppiel) and [@sergio-sisternes-epam](https://github.com/sergio-sisternes-epam).
113
125
 
114
126
  - [Roadmap & Discussions](https://github.com/microsoft/apm/discussions/116)
115
127
  - [Contributing](CONTRIBUTING.md)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.8.11"
7
+ version = "0.9.0"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -9,6 +9,12 @@ _INPUT_VAR_RE = re.compile(r"\$\{input:([^}]+)\}")
9
9
  class MCPClientAdapter(ABC):
10
10
  """Base adapter for MCP clients."""
11
11
 
12
+ # Whether this adapter's config path is user/global-scoped (e.g.
13
+ # ``~/.copilot/``) rather than workspace-scoped (e.g. ``.vscode/``).
14
+ # Adapters that target a global path should override this to ``True``
15
+ # so that ``apm install --global`` can install MCP servers to them.
16
+ supports_user_scope: bool = False
17
+
12
18
  @abstractmethod
13
19
  def get_config_path(self):
14
20
  """Get the path to the MCP configuration file."""
@@ -20,7 +20,9 @@ class CodexClientAdapter(MCPClientAdapter):
20
20
  a global ~/.codex/config.toml file, following the TOML format for
21
21
  MCP server configuration.
22
22
  """
23
-
23
+
24
+ supports_user_scope: bool = True
25
+
24
26
  def __init__(self, registry_url=None):
25
27
  """Initialize the Codex CLI client adapter.
26
28
 
@@ -23,7 +23,9 @@ class CopilotClientAdapter(MCPClientAdapter):
23
23
  a global ~/.copilot/mcp-config.json file, following the JSON format for
24
24
  MCP server configuration.
25
25
  """
26
-
26
+
27
+ supports_user_scope: bool = True
28
+
27
29
  def __init__(self, registry_url=None):
28
30
  """Initialize the Copilot CLI client adapter.
29
31
 
@@ -25,6 +25,8 @@ class CursorClientAdapter(CopilotClientAdapter):
25
25
  of global ``~/.copilot/mcp-config.json``.
26
26
  """
27
27
 
28
+ supports_user_scope: bool = False
29
+
28
30
  # ------------------------------------------------------------------ #
29
31
  # Config path
30
32
  # ------------------------------------------------------------------ #
@@ -40,6 +40,8 @@ class OpenCodeClientAdapter(CopilotClientAdapter):
40
40
  and writes to ``opencode.json`` in the project root.
41
41
  """
42
42
 
43
+ supports_user_scope: bool = False
44
+
43
45
  def get_config_path(self):
44
46
  """Return the path to ``opencode.json`` in the repository root."""
45
47
  return str(Path(os.getcwd()) / "opencode.json")
@@ -149,9 +151,12 @@ class OpenCodeClientAdapter(CopilotClientAdapter):
149
151
  elif "url" in copilot_entry:
150
152
  entry["type"] = "remote"
151
153
  entry["url"] = copilot_entry["url"]
154
+ headers = copilot_entry.get("headers")
155
+ if headers:
156
+ entry["headers"] = dict(headers)
152
157
 
153
158
  env = copilot_entry.get("env") or {}
154
159
  if env:
155
- entry["environment"] = env
160
+ entry["environment"] = dict(env)
156
161
 
157
162
  return entry
@@ -320,17 +320,25 @@ class VSCodeClientAdapter(MCPClientAdapter):
320
320
  }
321
321
  # Check for remotes (similar to Copilot adapter)
322
322
  elif "remotes" in server_info and server_info["remotes"]:
323
- remotes = server_info["remotes"]
324
- remote = remotes[0] # Take the first remote
325
- transport = remote.get("transport_type", "")
326
- if transport in ("sse", "http", "streamable-http"):
323
+ remote = self._select_remote_with_url(server_info["remotes"])
324
+ if remote:
325
+ transport = (remote.get("transport_type") or "").strip()
326
+ # Default to "http" when transport_type is missing/empty,
327
+ # matching the Copilot adapter behavior (copilot.py:190-192).
328
+ if not transport:
329
+ transport = "http"
330
+ elif transport not in ("sse", "http", "streamable-http"):
331
+ raise ValueError(
332
+ f"Unsupported remote transport '{transport}' for VS Code. "
333
+ f"Server: {server_info.get('name', 'unknown')}. "
334
+ f"Supported transports: http, sse, streamable-http.")
327
335
  headers = remote.get("headers", {})
328
336
  # Normalize header list format to dict
329
337
  if isinstance(headers, list):
330
338
  headers = {h["name"]: h["value"] for h in headers if "name" in h and "value" in h}
331
339
  server_config = {
332
340
  "type": transport,
333
- "url": remote.get("url", ""),
341
+ "url": remote["url"].strip(),
334
342
  "headers": headers,
335
343
  }
336
344
  input_vars.extend(
@@ -424,6 +432,19 @@ class VSCodeClientAdapter(MCPClientAdapter):
424
432
 
425
433
  return []
426
434
 
435
+ @staticmethod
436
+ def _select_remote_with_url(remotes):
437
+ """Return the first remote entry that has a non-empty URL.
438
+
439
+ Returns:
440
+ dict or None: The first usable remote, or None if none found.
441
+ """
442
+ for remote in remotes:
443
+ url = (remote.get("url") or "").strip()
444
+ if url:
445
+ return remote
446
+ return None
447
+
427
448
  def _select_best_package(self, packages):
428
449
  """Select the best package for VS Code installation from available packages.
429
450
 
@@ -1,7 +1,7 @@
1
1
  """Lockfile enrichment for pack-time metadata."""
2
2
 
3
3
  from datetime import datetime, timezone
4
- from typing import Dict, List, Tuple
4
+ from typing import Dict, List, Tuple, Union
5
5
 
6
6
  from ..deps.lockfile import LockFile
7
7
 
@@ -55,7 +55,7 @@ _CROSS_TARGET_MAPS: Dict[str, Dict[str, str]] = {
55
55
 
56
56
 
57
57
  def _filter_files_by_target(
58
- deployed_files: List[str], target: str
58
+ deployed_files: List[str], target: Union[str, List[str]]
59
59
  ) -> Tuple[List[str], Dict[str, str]]:
60
60
  """Filter deployed file paths by target prefix, with cross-target mapping.
61
61
 
@@ -64,16 +64,38 @@ def _filter_files_by_target(
64
64
  remapped to the equivalent target path. Commands, instructions, and hooks
65
65
  are NOT remapped -- they are target-specific.
66
66
 
67
+ *target* may be a single string or a list of strings. For a list, the
68
+ union of all relevant prefixes and cross-target maps is used.
69
+
67
70
  Returns:
68
71
  A tuple of ``(filtered_files, path_mappings)`` where *path_mappings*
69
72
  maps ``bundle_path -> disk_path`` for any file that was cross-target
70
73
  remapped. Direct matches have no entry in the dict.
71
74
  """
72
- prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
75
+ if isinstance(target, list):
76
+ # Union all prefixes for the targets in the list
77
+ prefixes: List[str] = []
78
+ seen_prefixes: set = set()
79
+ for t in target:
80
+ for p in _TARGET_PREFIXES.get(t, []):
81
+ if p not in seen_prefixes:
82
+ seen_prefixes.add(p)
83
+ prefixes.append(p)
84
+ # Union all cross-target maps
85
+ # NOTE: dict.update() means the last target's mapping wins when
86
+ # multiple targets map the same source prefix. In practice this
87
+ # is benign -- common multi-target combos (e.g. claude+copilot)
88
+ # match prefixes directly without needing cross-maps.
89
+ cross_map: Dict[str, str] = {}
90
+ for t in target:
91
+ cross_map.update(_CROSS_TARGET_MAPS.get(t, {}))
92
+ else:
93
+ prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
94
+ cross_map = _CROSS_TARGET_MAPS.get(target, {})
95
+
73
96
  direct = [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
74
97
 
75
98
  path_mappings: Dict[str, str] = {}
76
- cross_map = _CROSS_TARGET_MAPS.get(target, {})
77
99
  if cross_map:
78
100
  direct_set = set(direct)
79
101
  for f in deployed_files:
@@ -94,7 +116,7 @@ def _filter_files_by_target(
94
116
  def enrich_lockfile_for_pack(
95
117
  lockfile: LockFile,
96
118
  fmt: str,
97
- target: str,
119
+ target: Union[str, List[str]],
98
120
  ) -> str:
99
121
  """Create an enriched copy of the lockfile YAML with a ``pack:`` section.
100
122
 
@@ -109,7 +131,8 @@ def enrich_lockfile_for_pack(
109
131
  lockfile: The resolved lockfile to enrich.
110
132
  fmt: Bundle format (``"apm"`` or ``"plugin"``).
111
133
  target: Effective target used for packing (e.g. ``"copilot"``, ``"claude"``,
112
- ``"all"``). The internal alias ``"vscode"`` is also accepted.
134
+ ``"all"``). May also be a list of target strings for multi-target
135
+ packing. The internal alias ``"vscode"`` is also accepted.
113
136
 
114
137
  Returns:
115
138
  A YAML string with the ``pack:`` block followed by the original
@@ -132,9 +155,12 @@ def enrich_lockfile_for_pack(
132
155
 
133
156
  # Build the pack: metadata section (after filtering so we know if mapping
134
157
  # occurred).
158
+ # Serialize target as a comma-joined string for backward compatibility
159
+ # with consumers that expect a plain string in pack.target.
160
+ target_str = ",".join(target) if isinstance(target, list) else target
135
161
  pack_meta: Dict = {
136
162
  "format": fmt,
137
- "target": target,
163
+ "target": target_str,
138
164
  "packed_at": datetime.now(timezone.utc).isoformat(),
139
165
  }
140
166
  if all_mappings:
@@ -142,7 +168,12 @@ def enrich_lockfile_for_pack(
142
168
  # bundle paths differ from the original lockfile. Use the canonical
143
169
  # prefix keys from _CROSS_TARGET_MAPS rather than reverse-engineering
144
170
  # them from file paths.
145
- cross_map = _CROSS_TARGET_MAPS.get(target, {})
171
+ if isinstance(target, list):
172
+ cross_map: Dict[str, str] = {}
173
+ for t in target:
174
+ cross_map.update(_CROSS_TARGET_MAPS.get(t, {}))
175
+ else:
176
+ cross_map = _CROSS_TARGET_MAPS.get(target, {})
146
177
  used_src_prefixes = set()
147
178
  for original in all_mappings.values():
148
179
  for src_prefix in cross_map:
@@ -5,7 +5,7 @@ import shutil
5
5
  import tarfile
6
6
  from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
- from typing import Dict, List, Optional
8
+ from typing import Dict, List, Optional, Union
9
9
 
10
10
  from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
11
11
  from ..models.apm_package import APMPackage
@@ -28,7 +28,7 @@ def pack_bundle(
28
28
  project_root: Path,
29
29
  output_dir: Path,
30
30
  fmt: str = "apm",
31
- target: Optional[str] = None,
31
+ target: Optional[Union[str, List[str]]] = None,
32
32
  archive: bool = False,
33
33
  dry_run: bool = False,
34
34
  force: bool = False,
@@ -40,7 +40,8 @@ def pack_bundle(
40
40
  project_root: Root of the project containing ``apm.lock.yaml`` and ``apm.yml``.
41
41
  output_dir: Directory where the bundle will be created.
42
42
  fmt: Bundle format -- ``"apm"`` (default) or ``"plugin"``.
43
- target: Target filter -- ``"copilot"``, ``"claude"``, ``"all"``, or *None*
43
+ target: Target filter -- ``"copilot"``, ``"claude"``, ``"all"``, a list of
44
+ target strings (e.g. ``["claude", "vscode"]``), or *None*
44
45
  (auto-detect from apm.yml / project structure).
45
46
  archive: If *True*, produce a ``.tar.gz`` and remove the directory.
46
47
  dry_run: If *True*, resolve the file list but write nothing to disk.
@@ -102,14 +103,21 @@ def pack_bundle(
102
103
  config_target = None
103
104
 
104
105
  # 3. Resolve effective target
105
- effective_target, _reason = detect_target(
106
- project_root,
107
- explicit_target=target,
108
- config_target=config_target,
109
- )
110
- # For packing purposes, "minimal" means nothing to pack -- treat as "all"
111
- if effective_target == "minimal":
112
- effective_target = "all"
106
+ if isinstance(target, list):
107
+ # List from CLI (e.g. --target claude,copilot) passes through directly
108
+ effective_target = target
109
+ elif isinstance(config_target, list) and target is None:
110
+ # List from apm.yml target: [claude, copilot]
111
+ effective_target = config_target
112
+ else:
113
+ effective_target, _reason = detect_target(
114
+ project_root,
115
+ explicit_target=target,
116
+ config_target=config_target if isinstance(config_target, str) else None,
117
+ )
118
+ # For packing purposes, "minimal" means nothing to pack -- treat as "all"
119
+ if effective_target == "minimal":
120
+ effective_target = "all"
113
121
 
114
122
  # 4. Collect deployed_files from all dependencies, filtered by target
115
123
  all_deployed: List[str] = []
@@ -9,7 +9,7 @@ import json
9
9
  import os
10
10
  import shutil
11
11
  import tarfile
12
- from pathlib import Path
12
+ from pathlib import Path, PurePosixPath
13
13
  from typing import Dict, List, Optional, Set, Tuple
14
14
 
15
15
  import yaml
@@ -63,6 +63,16 @@ def _rename_prompt(name: str) -> str:
63
63
  return name
64
64
 
65
65
 
66
+ def _normalize_bare_skill_slug(slug: str) -> str:
67
+ """Normalize bare-skill slugs derived from dependency virtual paths."""
68
+ normalized = slug.replace("\\", "/").strip("/")
69
+ while normalized.startswith("skills/"):
70
+ normalized = normalized[len("skills/") :].lstrip("/")
71
+ if normalized == "skills":
72
+ return ""
73
+ return PurePosixPath(normalized).as_posix() if normalized else ""
74
+
75
+
66
76
  # ---------------------------------------------------------------------------
67
77
  # Component collectors
68
78
  # ---------------------------------------------------------------------------
@@ -130,7 +140,7 @@ def _collect_bare_skill(
130
140
  return
131
141
  # Derive a slug: prefer virtual_path (e.g. "frontend-design"), else last
132
142
  # segment of repo_url (e.g. "my-skill" from "owner/my-skill")
133
- slug = (getattr(dep, "virtual_path", "") or "").strip("/")
143
+ slug = _normalize_bare_skill_slug(getattr(dep, "virtual_path", "") or "")
134
144
  if not slug:
135
145
  slug = dep.repo_url.rsplit("/", 1)[-1] if dep.repo_url else "skill"
136
146
  for f in sorted(install_path.iterdir()):
@@ -386,14 +396,7 @@ def _update_plugin_json_paths(plugin_json: dict, output_files: List[str]) -> dic
386
396
 
387
397
  def _dep_install_path(dep: LockedDependency, apm_modules_dir: Path) -> Path:
388
398
  """Compute the filesystem install path for a locked dependency."""
389
- dep_ref = DependencyReference(
390
- repo_url=dep.repo_url,
391
- host=dep.host,
392
- virtual_path=dep.virtual_path,
393
- is_virtual=dep.is_virtual,
394
- is_local=(dep.source == "local"),
395
- local_path=dep.local_path,
396
- )
399
+ dep_ref = dep.to_dependency_ref()
397
400
  return dep_ref.get_install_path(apm_modules_dir)
398
401
 
399
402
 
@@ -56,7 +56,8 @@ def unpack_bundle(
56
56
  # 1. If archive, extract to temp dir
57
57
  cleanup_temp = False
58
58
  if bundle_path.is_file() and bundle_path.name.endswith(".tar.gz"):
59
- temp_dir = Path(tempfile.mkdtemp(prefix="apm-unpack-"))
59
+ from ..config import get_apm_temp_dir
60
+ temp_dir = Path(tempfile.mkdtemp(prefix="apm-unpack-", dir=get_apm_temp_dir()))
60
61
  cleanup_temp = True
61
62
  try:
62
63
  with tarfile.open(bundle_path, "r:gz") as tar:
@@ -20,11 +20,13 @@ from apm_cli.commands.audit import audit
20
20
  from apm_cli.commands.compile import compile as compile_cmd
21
21
  from apm_cli.commands.config import config
22
22
  from apm_cli.commands.deps import deps
23
+ from apm_cli.commands.view import view as view_cmd
23
24
  from apm_cli.commands.init import init
24
25
  from apm_cli.commands.install import install
25
26
  from apm_cli.commands.list_cmd import list as list_cmd
26
27
  from apm_cli.commands.marketplace import marketplace, search as marketplace_search
27
28
  from apm_cli.commands.mcp import mcp
29
+ from apm_cli.commands.outdated import outdated as outdated_cmd
28
30
  from apm_cli.commands.pack import pack_cmd, unpack_cmd
29
31
  from apm_cli.commands.prune import prune
30
32
  from apm_cli.commands.run import preview, run
@@ -57,6 +59,15 @@ def cli(ctx):
57
59
  # Register command groups
58
60
  cli.add_command(audit)
59
61
  cli.add_command(deps)
62
+ cli.add_command(view_cmd)
63
+ # Hidden backward-compatible alias: ``apm info`` → ``apm view``
64
+ cli.add_command(click.Command(
65
+ name="info",
66
+ callback=view_cmd.callback,
67
+ params=list(view_cmd.params),
68
+ help=view_cmd.help,
69
+ hidden=True,
70
+ ))
60
71
  cli.add_command(pack_cmd, name="pack")
61
72
  cli.add_command(unpack_cmd, name="unpack")
62
73
  cli.add_command(init)
@@ -71,6 +82,7 @@ cli.add_command(list_cmd, name="list")
71
82
  cli.add_command(config)
72
83
  cli.add_command(runtime)
73
84
  cli.add_command(mcp)
85
+ cli.add_command(outdated_cmd, name="outdated")
74
86
  cli.add_command(marketplace)
75
87
  cli.add_command(marketplace_search, name="search")
76
88
 
@@ -21,6 +21,7 @@ from ..constants import (
21
21
  GITIGNORE_FILENAME,
22
22
  )
23
23
  from ..utils.console import _rich_echo, _rich_info, _rich_warning
24
+ from ..update_policy import get_update_hint_message, is_self_update_enabled
24
25
  from ..version import get_build_sha, get_version
25
26
  from ..utils.version_checker import check_for_updates
26
27
 
@@ -121,8 +122,6 @@ def _build_expected_install_paths(declared_deps, lockfile, apm_modules_dir: Path
121
122
  (depth > 1 from ``apm.lock``), using ``get_install_path()`` for
122
123
  consistency with how packages are actually installed.
123
124
  """
124
- from ..models.apm_package import DependencyReference
125
-
126
125
  expected = builtins.set()
127
126
  for dep in declared_deps:
128
127
  install_path = dep.get_install_path(apm_modules_dir)
@@ -135,12 +134,7 @@ def _build_expected_install_paths(declared_deps, lockfile, apm_modules_dir: Path
135
134
  if lockfile:
136
135
  for dep in lockfile.get_all_dependencies():
137
136
  if dep.depth is not None and dep.depth > 1:
138
- dep_ref = DependencyReference(
139
- repo_url=dep.repo_url,
140
- host=dep.host,
141
- virtual_path=dep.virtual_path,
142
- is_virtual=dep.is_virtual,
143
- )
137
+ dep_ref = dep.to_dependency_ref()
144
138
  install_path = dep_ref.get_install_path(apm_modules_dir)
145
139
  try:
146
140
  relative_path = install_path.relative_to(apm_modules_dir)
@@ -240,6 +234,10 @@ def print_version(ctx, param, value):
240
234
  def _check_and_notify_updates():
241
235
  """Check for updates and notify user non-blockingly."""
242
236
  try:
237
+ # Skip notifications when self-update is disabled by distribution policy.
238
+ if not is_self_update_enabled():
239
+ return
240
+
243
241
  # Skip version check in E2E test mode to avoid interfering with tests
244
242
  if os.environ.get("APM_E2E_TESTS", "").lower() in ("1", "true", "yes"):
245
243
  return
@@ -260,7 +258,7 @@ def _check_and_notify_updates():
260
258
  )
261
259
 
262
260
  # Show update command using helper for consistency
263
- _rich_echo("Run apm update to upgrade", color="yellow", bold=True)
261
+ _rich_echo(get_update_hint_message(), color="yellow", bold=True)
264
262
 
265
263
  # Add a blank line for visual separation
266
264
  click.echo()
@@ -414,6 +412,21 @@ def _validate_plugin_name(name):
414
412
  return bool(re.match(r"^[a-z][a-z0-9-]{0,63}$", name))
415
413
 
416
414
 
415
+ def _validate_project_name(name):
416
+ """Validate that a project name is safe to use as a directory name.
417
+
418
+ Project names are used directly as directory names and must not contain
419
+ '/' or '\' so the name is not interpreted as a filesystem path,
420
+ and must not be '..' to prevent directory traversal.
421
+
422
+ Returns True if valid, False otherwise.
423
+ """
424
+ if "/" in name or "\\" in name:
425
+ return False
426
+ if name == "..":
427
+ return False
428
+ return True
429
+
417
430
  def _create_plugin_json(config):
418
431
  """Create plugin.json file with package metadata.
419
432