apm-cli 0.9.3__tar.gz → 0.9.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. {apm_cli-0.9.3/src/apm_cli.egg-info → apm_cli-0.9.4}/PKG-INFO +1 -1
  2. {apm_cli-0.9.3 → apm_cli-0.9.4}/pyproject.toml +2 -1
  3. apm_cli-0.9.4/src/apm_cli/commands/_apm_yml_writer.py +81 -0
  4. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/experimental.py +0 -1
  5. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/install.py +50 -22
  6. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/mcp.py +14 -3
  7. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/outdated.py +1 -1
  8. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/pack.py +7 -7
  9. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/runtime.py +1 -1
  10. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/lockfile.py +5 -0
  11. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/context.py +2 -0
  12. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/finalize.py +1 -1
  13. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/lockfile.py +16 -0
  14. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/pipeline.py +9 -0
  15. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/request.py +2 -0
  16. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/service.py +2 -0
  17. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/services.py +2 -1
  18. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/sources.py +1 -0
  19. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/template.py +12 -0
  20. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/validation.py +80 -29
  21. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/skill_integrator.py +120 -6
  22. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/reference.py +42 -1
  23. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/results.py +3 -1
  24. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/validation.py +220 -47
  25. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/script_formatters.py +3 -5
  26. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/ci_checks.py +43 -0
  27. {apm_cli-0.9.3 → apm_cli-0.9.4/src/apm_cli.egg-info}/PKG-INFO +1 -1
  28. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/SOURCES.txt +1 -0
  29. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_apm_package_models.py +62 -12
  30. {apm_cli-0.9.3 → apm_cli-0.9.4}/AUTHORS +0 -0
  31. {apm_cli-0.9.3 → apm_cli-0.9.4}/LICENSE +0 -0
  32. {apm_cli-0.9.3 → apm_cli-0.9.4}/README.md +0 -0
  33. {apm_cli-0.9.3 → apm_cli-0.9.4}/setup.cfg +0 -0
  34. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/__init__.py +0 -0
  35. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/__init__.py +0 -0
  36. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/__init__.py +0 -0
  37. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/base.py +0 -0
  38. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/codex.py +0 -0
  39. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/copilot.py +0 -0
  40. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/cursor.py +0 -0
  41. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/gemini.py +0 -0
  42. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/opencode.py +0 -0
  43. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/vscode.py +0 -0
  44. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  45. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
  46. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  47. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/__init__.py +0 -0
  48. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
  49. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/packer.py +0 -0
  50. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/plugin_exporter.py +0 -0
  51. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/unpacker.py +0 -0
  52. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/cli.py +0 -0
  53. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/__init__.py +0 -0
  54. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/_helpers.py +0 -0
  55. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/audit.py +0 -0
  56. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/__init__.py +0 -0
  57. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/cli.py +0 -0
  58. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/watcher.py +0 -0
  59. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/config.py +0 -0
  60. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/__init__.py +0 -0
  61. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/_utils.py +0 -0
  62. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/cli.py +0 -0
  63. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/init.py +0 -0
  64. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/list_cmd.py +0 -0
  65. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/marketplace.py +0 -0
  66. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/policy.py +0 -0
  67. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/prune.py +0 -0
  68. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/run.py +0 -0
  69. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  70. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/cli.py +0 -0
  71. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/engine.py +0 -0
  72. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/update.py +0 -0
  73. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/view.py +0 -0
  74. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/__init__.py +0 -0
  75. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/agents_compiler.py +0 -0
  76. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
  77. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constants.py +0 -0
  78. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constitution.py +0 -0
  79. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constitution_block.py +0 -0
  80. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
  81. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  82. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/gemini_formatter.py +0 -0
  83. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/injector.py +0 -0
  84. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/link_resolver.py +0 -0
  85. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/template_builder.py +0 -0
  86. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/config.py +0 -0
  87. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/constants.py +0 -0
  88. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/__init__.py +0 -0
  89. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/auth.py +0 -0
  90. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/azure_cli.py +0 -0
  91. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/command_logger.py +0 -0
  92. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/conflict_detector.py +0 -0
  93. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/docker_args.py +0 -0
  94. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/experimental.py +0 -0
  95. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/operations.py +0 -0
  96. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/safe_installer.py +0 -0
  97. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/scope.py +0 -0
  98. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/script_runner.py +0 -0
  99. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/target_detection.py +0 -0
  100. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/token_manager.py +0 -0
  101. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/__init__.py +0 -0
  102. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/aggregator.py +0 -0
  103. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/apm_resolver.py +0 -0
  104. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/artifactory_entry.py +0 -0
  105. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/collection_parser.py +0 -0
  106. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/dependency_graph.py +0 -0
  107. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/github_downloader.py +0 -0
  108. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/installed_package.py +0 -0
  109. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/package_validator.py +0 -0
  110. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/plugin_parser.py +0 -0
  111. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/registry_proxy.py +0 -0
  112. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/transport_selection.py +0 -0
  113. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/verifier.py +0 -0
  114. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/drift.py +0 -0
  115. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/factory.py +0 -0
  116. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/__init__.py +0 -0
  117. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/errors.py +0 -0
  118. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/helpers/__init__.py +0 -0
  119. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/helpers/security_scan.py +0 -0
  120. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/insecure_policy.py +0 -0
  121. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/mcp_registry.py +0 -0
  122. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/mcp_warnings.py +0 -0
  123. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/__init__.py +0 -0
  124. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/cleanup.py +0 -0
  125. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/download.py +0 -0
  126. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/integrate.py +0 -0
  127. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/local_content.py +0 -0
  128. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/policy_gate.py +0 -0
  129. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/policy_target_check.py +0 -0
  130. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/post_deps_local.py +0 -0
  131. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/resolve.py +0 -0
  132. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/targets.py +0 -0
  133. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/presentation/__init__.py +0 -0
  134. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/presentation/dry_run.py +0 -0
  135. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/__init__.py +0 -0
  136. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/agent_integrator.py +0 -0
  137. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/base_integrator.py +0 -0
  138. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/cleanup.py +0 -0
  139. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/command_integrator.py +0 -0
  140. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/coverage.py +0 -0
  141. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/dispatch.py +0 -0
  142. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/hook_integrator.py +0 -0
  143. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/instruction_integrator.py +0 -0
  144. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/mcp_integrator.py +0 -0
  145. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/prompt_integrator.py +0 -0
  146. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/skill_transformer.py +0 -0
  147. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/targets.py +0 -0
  148. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/utils.py +0 -0
  149. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/__init__.py +0 -0
  150. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/client.py +0 -0
  151. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/errors.py +0 -0
  152. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/models.py +0 -0
  153. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/registry.py +0 -0
  154. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/resolver.py +0 -0
  155. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/shadow_detector.py +0 -0
  156. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/validator.py +0 -0
  157. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/version_pins.py +0 -0
  158. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/__init__.py +0 -0
  159. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/apm_package.py +0 -0
  160. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/__init__.py +0 -0
  161. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/mcp.py +0 -0
  162. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/types.py +0 -0
  163. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/plugin.py +0 -0
  164. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/__init__.py +0 -0
  165. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/formatters.py +0 -0
  166. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/models.py +0 -0
  167. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/__init__.py +0 -0
  168. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/discovery.py +0 -0
  169. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/inheritance.py +0 -0
  170. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/install_preflight.py +0 -0
  171. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/matcher.py +0 -0
  172. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/models.py +0 -0
  173. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/outcome_routing.py +0 -0
  174. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/parser.py +0 -0
  175. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/policy_checks.py +0 -0
  176. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/project_config.py +0 -0
  177. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/schema.py +0 -0
  178. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/__init__.py +0 -0
  179. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/discovery.py +0 -0
  180. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/models.py +0 -0
  181. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/parser.py +0 -0
  182. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/__init__.py +0 -0
  183. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/client.py +0 -0
  184. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/integration.py +0 -0
  185. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/operations.py +0 -0
  186. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/__init__.py +0 -0
  187. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/base.py +0 -0
  188. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
  189. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  190. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/factory.py +0 -0
  191. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
  192. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/manager.py +0 -0
  193. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/__init__.py +0 -0
  194. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/audit_report.py +0 -0
  195. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/content_scanner.py +0 -0
  196. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/file_scanner.py +0 -0
  197. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/gate.py +0 -0
  198. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/update_policy.py +0 -0
  199. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/__init__.py +0 -0
  200. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/console.py +0 -0
  201. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/content_hash.py +0 -0
  202. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/diagnostics.py +0 -0
  203. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/exclude.py +0 -0
  204. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/file_ops.py +0 -0
  205. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/github_host.py +0 -0
  206. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/helpers.py +0 -0
  207. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/path_security.py +0 -0
  208. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/paths.py +0 -0
  209. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/subprocess_env.py +0 -0
  210. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/version_checker.py +0 -0
  211. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/yaml_io.py +0 -0
  212. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/version.py +0 -0
  213. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/__init__.py +0 -0
  214. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/discovery.py +0 -0
  215. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/parser.py +0 -0
  216. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/runner.py +0 -0
  217. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  218. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
  219. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/requires.txt +0 -0
  220. {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/top_level.txt +0 -0
  221. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_apm_resolver.py +0 -0
  222. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_codex_docker_args_fix.py +0 -0
  223. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
  224. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_collision_integration.py +0 -0
  225. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_console.py +0 -0
  226. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_distributed_compilation.py +0 -0
  227. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_empty_string_and_defaults.py +0 -0
  228. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_enhanced_discovery.py +0 -0
  229. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_github_downloader.py +0 -0
  230. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_github_downloader_token_precedence.py +0 -0
  231. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_lockfile.py +0 -0
  232. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_runnable_prompts.py +0 -0
  233. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_runtime_manager_token_precedence.py +0 -0
  234. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_token_manager.py +0 -0
  235. {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_virtual_package_multi_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apm-cli
3
- Version: 0.9.3
3
+ Version: 0.9.4
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.9.3"
7
+ version = "0.9.4"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -74,6 +74,7 @@ warn_unused_configs = true
74
74
  addopts = "-m 'not benchmark'"
75
75
  markers = [
76
76
  "integration: marks tests as integration tests that may require network access",
77
+ "live: marks tests that hit real GitHub repos (requires network + optional GITHUB_TOKEN)",
77
78
  "slow: marks tests as slow running tests",
78
79
  "benchmark: marks performance benchmark tests (deselected by default, run with -m benchmark)",
79
80
  ]
@@ -0,0 +1,81 @@
1
+ """Write-back helper for persisting skill subset selection in apm.yml.
2
+
3
+ Single helper ``set_skill_subset_for_entry`` is the one source of truth
4
+ for promoting entries to dict form and setting/clearing the ``skills:``
5
+ field. Keeps write-back logic isolated and unit-testable.
6
+ """
7
+
8
+ from pathlib import Path
9
+ from typing import List, Optional
10
+
11
+ from ..models.dependency.reference import DependencyReference
12
+ from ..utils.yaml_io import dump_yaml, load_yaml
13
+
14
+
15
+ def set_skill_subset_for_entry(
16
+ manifest_path: Path,
17
+ repo_url: str,
18
+ subset: Optional[List[str]],
19
+ ) -> bool:
20
+ """Promote entry to dict form and set/clear skills: field.
21
+
22
+ subset=None or empty list -> remove skills: from entry (reset to all).
23
+ subset=[...] -> set skills: to sorted+deduped list.
24
+
25
+ Returns True if file was modified.
26
+ """
27
+ data = load_yaml(manifest_path) or {}
28
+ deps_section = data.get("dependencies", {})
29
+ apm_deps = deps_section.get("apm", [])
30
+ if not apm_deps:
31
+ return False
32
+
33
+ modified = False
34
+ new_deps = []
35
+
36
+ for entry in apm_deps:
37
+ if _entry_matches(entry, repo_url):
38
+ entry = _apply_subset(entry, subset)
39
+ modified = True
40
+ new_deps.append(entry)
41
+
42
+ if not modified:
43
+ return False
44
+
45
+ deps_section["apm"] = new_deps
46
+ data["dependencies"] = deps_section
47
+ dump_yaml(data, manifest_path)
48
+ return True
49
+
50
+
51
+ def _entry_matches(entry, repo_url: str) -> bool:
52
+ """Check if an apm.yml entry matches the given repo_url."""
53
+ try:
54
+ if isinstance(entry, str):
55
+ ref = DependencyReference.parse(entry)
56
+ elif isinstance(entry, dict):
57
+ ref = DependencyReference.parse_from_dict(entry)
58
+ else:
59
+ return False
60
+ return ref.repo_url == repo_url
61
+ except (ValueError, TypeError, AttributeError, KeyError):
62
+ return False
63
+
64
+
65
+ def _apply_subset(entry, subset: Optional[List[str]]):
66
+ """Apply skill subset to an entry, promoting to dict form if needed."""
67
+ # Parse current entry to get canonical info
68
+ if isinstance(entry, str):
69
+ ref = DependencyReference.parse(entry)
70
+ elif isinstance(entry, dict):
71
+ ref = DependencyReference.parse_from_dict(entry)
72
+ else:
73
+ return entry
74
+
75
+ # Determine if we should set or clear
76
+ if subset:
77
+ ref.skill_subset = sorted(set(subset))
78
+ else:
79
+ ref.skill_subset = None
80
+
81
+ return ref.to_apm_yml_entry()
@@ -140,7 +140,6 @@ def _handle_unknown_flag(name: str, logger: CommandLogger) -> None:
140
140
  @click.group(
141
141
  help="Manage experimental feature flags",
142
142
  invoke_without_command=True,
143
- context_settings={"allow_interspersed_args": True, "ignore_unknown_options": True},
144
143
  )
145
144
  @click.option("--verbose", "-v", is_flag=True, default=False, help="Show verbose output")
146
145
  @click.pass_context
@@ -1050,43 +1050,29 @@ def _run_mcp_install(
1050
1050
  "or a stdio command (self-defined entries)."
1051
1051
  ),
1052
1052
  )
1053
- @click.option(
1054
- "--no-policy",
1055
- "no_policy",
1056
- is_flag=True,
1057
- default=False,
1058
- help="Skip org policy enforcement for this invocation. Loudly logged. Does NOT bypass apm audit --ci.",
1059
- )
1053
+ @click.option("--skill", "skill_names", multiple=True, metavar="NAME", help="Install only named skill(s) from a SKILL_BUNDLE. Repeatable. Persisted in apm.yml and apm.lock so bare 'apm install' is deterministic. Use --skill '*' to reset to all skills.")
1054
+ @click.option("--no-policy", "no_policy", is_flag=True, default=False, help="Skip org policy enforcement for this invocation. Does NOT bypass apm audit --ci.")
1060
1055
  @click.pass_context
1061
- def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url, no_policy):
1056
+ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url, skill_names, no_policy):
1062
1057
  """Install APM and MCP dependencies from apm.yml (like npm install).
1063
1058
 
1064
1059
  Detects AI runtimes from your apm.yml scripts and installs MCP servers for
1065
1060
  all detected runtimes; also installs APM package dependencies from GitHub.
1066
1061
  --only filters by type (apm or mcp).
1067
1062
 
1068
- HTTP dependencies require `allow_insecure: true` in apm.yml and
1069
- `--allow-insecure` on the install command. Transitive HTTP dependencies are
1070
- allowed automatically when they stay on the same host as a direct HTTP
1071
- dependency, or explicitly with `--allow-insecure-host <hostname>`.
1072
-
1073
1063
  Examples:
1074
1064
  apm install # Install existing deps from apm.yml
1075
1065
  apm install org/pkg1 # Add package to apm.yml and install
1076
- apm install org/pkg1 org/pkg2 # Add multiple packages and install
1077
1066
  apm install --exclude codex # Install for all except Codex CLI
1078
1067
  apm install --only=apm # Install only APM dependencies
1079
- apm install --only=mcp # Install only MCP dependencies
1080
1068
  apm install --update # Update dependencies to latest Git refs
1081
1069
  apm install --dry-run # Show what would be installed
1082
1070
  apm install -g org/pkg1 # Install to user scope (~/.apm/)
1083
- apm install --allow-insecure http://my-server.example.com/owner/repo # Install from HTTP URL with allow_insecure
1084
- apm install --allow-insecure-host mirror.example.com # Allow transitive HTTP dependencies from mirror.example.com
1085
-
1086
- MCP servers (also: 'apm mcp install'):
1087
- apm install --mcp io.github.github/github-mcp-server # registry shorthand
1088
- apm install --mcp api --url https://example.com/mcp # remote http/sse
1089
- apm install --mcp fetch -- npx -y @modelcontextprotocol/server-fetch # stdio (post-- argv)
1071
+ apm install --allow-insecure http://... # HTTP URL (needs allow_insecure)
1072
+ apm install --skill my-skill org/bundle # Install one skill from bundle
1073
+ apm install --mcp io.github.github/github-mcp-server # MCP registry
1074
+ apm install --mcp api --url https://example.com/mcp # MCP remote
1075
+ apm install --mcp fetch -- npx -y @mcp/server-fetch # MCP stdio
1090
1076
  """
1091
1077
  # C1 #856: defaults BEFORE try so the finally clause never sees an
1092
1078
  # UnboundLocalError if InstallLogger(...) raises during construction.
@@ -1149,6 +1135,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1149
1135
  registry_url=validated_registry_url,
1150
1136
  )
1151
1137
 
1138
+ # Normalize --skill: '*' means all (same as absent). Reject with --mcp.
1139
+ _skill_subset = None
1140
+ if skill_names:
1141
+ if mcp_name is not None:
1142
+ raise click.UsageError("--skill cannot be combined with --mcp.")
1143
+ if not any(s == "*" for s in skill_names):
1144
+ _skill_subset = builtins.tuple(skill_names)
1145
+
1152
1146
  if mcp_name is not None:
1153
1147
  # MCP install routing block. This branch has accreted
1154
1148
  # significantly (--mcp / --registry / --transport / --env /
@@ -1463,11 +1457,41 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1463
1457
  protocol_pref=protocol_pref,
1464
1458
  allow_protocol_fallback=allow_protocol_fallback,
1465
1459
  no_policy=no_policy,
1460
+ skill_subset=_skill_subset,
1461
+ skill_subset_from_cli=bool(skill_names),
1466
1462
  )
1467
1463
  apm_count = install_result.installed_count
1468
1464
  prompt_count = install_result.prompts_integrated
1469
1465
  agent_count = install_result.agents_integrated
1470
1466
  apm_diagnostics = install_result.diagnostics
1467
+
1468
+ # -- Skill subset write-back (Phase 11) --
1469
+ # When CLI provided --skill on a SKILL_BUNDLE package, persist
1470
+ # the subset selection in apm.yml so bare `apm install` is
1471
+ # deterministic.
1472
+ if skill_names and packages:
1473
+ from ._apm_yml_writer import set_skill_subset_for_entry
1474
+
1475
+ _star_sentinel = any(s == "*" for s in skill_names)
1476
+ for dep_key, pkg_type in install_result.package_types.items():
1477
+ if pkg_type == "skill_bundle":
1478
+ if _star_sentinel:
1479
+ # Explicit-all: REMOVE any persisted skills:
1480
+ if set_skill_subset_for_entry(manifest_path, dep_key, None):
1481
+ logger.success(f"Cleared skill subset for {dep_key}")
1482
+ else:
1483
+ subset_list = sorted(builtins.set(_skill_subset))
1484
+ if set_skill_subset_for_entry(manifest_path, dep_key, subset_list):
1485
+ logger.success(
1486
+ f"Persisted skill subset for {dep_key}: "
1487
+ f"[{', '.join(subset_list)}]"
1488
+ )
1489
+ elif pkg_type != "skill_bundle" and not _star_sentinel:
1490
+ # Non-bundle: warn but do NOT persist
1491
+ logger.warning(
1492
+ f"--skill ignored for {dep_key} "
1493
+ f"(package type: {pkg_type}, not a skill bundle)"
1494
+ )
1471
1495
  except InsecureDependencyPolicyError:
1472
1496
  _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1473
1497
  sys.exit(1)
@@ -1664,6 +1688,8 @@ def _install_apm_dependencies(
1664
1688
  protocol_pref=None,
1665
1689
  allow_protocol_fallback: "Optional[bool]" = None,
1666
1690
  no_policy: bool = False,
1691
+ skill_subset: "Optional[builtins.tuple]" = None,
1692
+ skill_subset_from_cli: bool = False,
1667
1693
  ):
1668
1694
  """Thin wrapper -- builds an :class:`InstallRequest` and delegates to
1669
1695
  :class:`apm_cli.install.service.InstallService`.
@@ -1696,5 +1722,7 @@ def _install_apm_dependencies(
1696
1722
  protocol_pref=protocol_pref,
1697
1723
  allow_protocol_fallback=allow_protocol_fallback,
1698
1724
  no_policy=no_policy,
1725
+ skill_subset=skill_subset,
1726
+ skill_subset_from_cli=skill_subset_from_cli,
1699
1727
  )
1700
1728
  return InstallService().run(request)
@@ -83,9 +83,20 @@ def mcp():
83
83
  " apm mcp install fetch -- npx -y @modelcontextprotocol/server-fetch\n\n"
84
84
  " apm mcp install api --transport http --url https://example.com/mcp"
85
85
  ),
86
+ epilog=(
87
+ "Common options (see `apm install --mcp --help` for full list):\n"
88
+ " --transport [stdio|http|sse|streamable-http]\n"
89
+ " --url URL Server URL for remote transports\n"
90
+ " --env KEY=VALUE Environment variable (repeatable)\n"
91
+ " --header KEY=VALUE HTTP header (repeatable)\n"
92
+ " --registry URL Custom registry URL\n"
93
+ " --mcp-version VER Pin registry entry to a specific version\n"
94
+ " --dev / --dry-run / --force / --verbose / --no-policy\n"
95
+ ),
86
96
  )
97
+ @click.argument("name", required=True)
87
98
  @click.pass_context
88
- def mcp_install(ctx):
99
+ def mcp_install(ctx, name):
89
100
  """Forward all args to 'apm install --mcp ...'.
90
101
 
91
102
  Examples:
@@ -106,9 +117,9 @@ def mcp_install(ctx):
106
117
  _, post_dd = _split_argv_at_double_dash(_get_invocation_argv())
107
118
  if post_dd:
108
119
  pre_args = ctx.args[: len(ctx.args) - len(post_dd)]
109
- forwarded = ["install", "--mcp", *pre_args, "--", *post_dd]
120
+ forwarded = ["install", "--mcp", name, *pre_args, "--", *post_dd]
110
121
  else:
111
- forwarded = ["install", "--mcp", *ctx.args]
122
+ forwarded = ["install", "--mcp", name, *ctx.args]
112
123
 
113
124
  try:
114
125
  cli.main(args=forwarded, standalone_mode=False)
@@ -225,7 +225,7 @@ def _check_one_dep(dep, downloader, verbose):
225
225
  @click.option("--parallel-checks", "-j", type=int, default=4,
226
226
  help="Max concurrent remote checks (default: 4, 0 = sequential)")
227
227
  def outdated(global_, verbose, parallel_checks):
228
- """Show outdated locked dependencies.
228
+ """Show outdated locked dependencies
229
229
 
230
230
  Compares each locked dependency against the remote to detect staleness.
231
231
  Tag-pinned deps use semver comparison; branch-pinned deps compare commit SHAs.
@@ -17,25 +17,25 @@ from ..core.target_detection import TargetParamType
17
17
  "fmt",
18
18
  type=click.Choice(["apm", "plugin"]),
19
19
  default="apm",
20
- help="Bundle format.",
20
+ help="Bundle format",
21
21
  )
22
22
  @click.option(
23
23
  "--target",
24
24
  "-t",
25
25
  type=TargetParamType(),
26
26
  default=None,
27
- help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified.",
27
+ help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified",
28
28
  )
29
- @click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive.")
29
+ @click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive")
30
30
  @click.option(
31
31
  "-o",
32
32
  "--output",
33
33
  type=click.Path(),
34
34
  default="./build",
35
- help="Output directory (default: ./build).",
35
+ help="Output directory (default: ./build)",
36
36
  )
37
- @click.option("--dry-run", is_flag=True, default=False, help="Show what would be packed without writing.")
38
- @click.option("--force", is_flag=True, default=False, help="On collision, last writer wins.")
37
+ @click.option("--dry-run", is_flag=True, default=False, help="Show what would be packed without writing")
38
+ @click.option("--force", is_flag=True, default=False, help="On collision, last writer wins")
39
39
  @click.option("--verbose", "-v", is_flag=True, help="Show detailed packing information")
40
40
  @click.pass_context
41
41
  def pack_cmd(ctx, fmt, target, archive, output, dry_run, force, verbose):
@@ -107,7 +107,7 @@ def pack_cmd(ctx, fmt, target, archive, output, dry_run, force, verbose):
107
107
  help="Target directory (default: current directory).",
108
108
  )
109
109
  @click.option("--skip-verify", is_flag=True, default=False, help="Skip bundle completeness check.")
110
- @click.option("--dry-run", is_flag=True, default=False, help="Show what would be unpacked without writing.")
110
+ @click.option("--dry-run", is_flag=True, default=False, help="Show what would be unpacked without writing")
111
111
  @click.option("--force", is_flag=True, default=False, help="Deploy despite critical hidden-character findings.")
112
112
  @click.option("--verbose", "-v", is_flag=True, help="Show detailed unpacking information")
113
113
  @click.pass_context
@@ -124,7 +124,7 @@ def list():
124
124
 
125
125
  @runtime.command(help="Remove an installed runtime")
126
126
  @click.argument("runtime_name", type=click.Choice(["copilot", "codex", "llm", "gemini"]))
127
- @click.confirmation_option(prompt="Are you sure you want to remove this runtime?", help="Confirm the action without prompting")
127
+ @click.confirmation_option("--yes", "-y", prompt="Are you sure you want to remove this runtime?", help="Confirm the action without prompting")
128
128
  def remove(runtime_name):
129
129
  """Remove an installed runtime from APM management."""
130
130
  logger = CommandLogger("runtime remove")
@@ -44,6 +44,7 @@ class LockedDependency:
44
44
  marketplace_plugin_name: Optional[str] = None # Plugin name in marketplace
45
45
  is_insecure: bool = False # True when the locked source was http://
46
46
  allow_insecure: bool = False # True when the manifest explicitly allowed HTTP
47
+ skill_subset: List[str] = field(default_factory=list) # Sorted skill names for SKILL_BUNDLE
47
48
 
48
49
  def get_unique_key(self) -> str:
49
50
  """Returns unique key for this dependency."""
@@ -100,6 +101,8 @@ class LockedDependency:
100
101
  result["is_insecure"] = True
101
102
  if self.allow_insecure:
102
103
  result["allow_insecure"] = True
104
+ if self.skill_subset:
105
+ result["skill_subset"] = sorted(self.skill_subset)
103
106
  return result
104
107
 
105
108
  @classmethod
@@ -153,6 +156,7 @@ class LockedDependency:
153
156
  marketplace_plugin_name=data.get("marketplace_plugin_name"),
154
157
  is_insecure=data.get("is_insecure", False),
155
158
  allow_insecure=data.get("allow_insecure", False),
159
+ skill_subset=list(data.get("skill_subset") or []),
156
160
  )
157
161
 
158
162
  @classmethod
@@ -201,6 +205,7 @@ class LockedDependency:
201
205
  is_dev=is_dev,
202
206
  is_insecure=dep_ref.is_insecure,
203
207
  allow_insecure=dep_ref.allow_insecure,
208
+ skill_subset=sorted(dep_ref.skill_subset) if isinstance(getattr(dep_ref, "skill_subset", None), list) else [],
204
209
  )
205
210
 
206
211
  def to_dependency_ref(self) -> DependencyReference:
@@ -123,6 +123,8 @@ class InstallContext:
123
123
  policy_fetch: Any = None # Optional[PolicyFetchResult] from discovery
124
124
  policy_enforcement_active: bool = False
125
125
  no_policy: bool = False # W2-escape-hatch will wire --no-policy here
126
+ skill_subset: Optional[Tuple[str, ...]] = None # --skill filter for SKILL_BUNDLE packages
127
+ skill_subset_from_cli: bool = False # True when user passed --skill (even --skill '*')
126
128
  direct_mcp_deps: Optional[List[Any]] = None # Direct MCP deps from apm.yml for policy gate
127
129
 
128
130
  # ------------------------------------------------------------------
@@ -52,4 +52,4 @@ def run(ctx: "InstallContext") -> "InstallResult":
52
52
  f"-- pin with #tag or #sha to prevent drift"
53
53
  )
54
54
 
55
- return InstallResult(ctx.installed_count, ctx.total_prompts_integrated, ctx.total_agents_integrated, ctx.diagnostics)
55
+ return InstallResult(ctx.installed_count, ctx.total_prompts_integrated, ctx.total_agents_integrated, ctx.diagnostics, package_types=dict(ctx.package_types))
@@ -77,6 +77,8 @@ class LockfileBuilder:
77
77
  # Attach deployed_files and package_type to each LockedDependency
78
78
  self._attach_deployed_files(lockfile)
79
79
  self._attach_package_types(lockfile)
80
+ # Apply CLI --skill override to lockfile entries (skill_bundle only)
81
+ self._attach_skill_subset_override(lockfile)
80
82
  # Attach content hashes captured at download/verify time
81
83
  self._attach_content_hashes(lockfile)
82
84
  # Attach marketplace provenance if available
@@ -124,6 +126,20 @@ class LockfileBuilder:
124
126
  if dep_key in lockfile.dependencies:
125
127
  lockfile.dependencies[dep_key].package_type = pkg_type
126
128
 
129
+ def _attach_skill_subset_override(self, lockfile: LockFile) -> None:
130
+ """Apply CLI --skill override to lockfile skill_bundle entries.
131
+
132
+ When the user runs `apm install bundle --skill foo`, the CLI
133
+ skill_subset takes precedence over the per-entry skill_subset
134
+ from the manifest for this invocation's lockfile.
135
+ """
136
+ if not self.ctx.skill_subset:
137
+ return # No CLI override; dep_ref.skill_subset already flows through
138
+ effective = sorted(set(self.ctx.skill_subset))
139
+ for dep_key, locked_dep in lockfile.dependencies.items():
140
+ if locked_dep.package_type == "skill_bundle":
141
+ locked_dep.skill_subset = effective
142
+
127
143
  def _attach_content_hashes(self, lockfile: LockFile) -> None:
128
144
  for dep_key, locked_dep in lockfile.dependencies.items():
129
145
  if dep_key in self.ctx.package_hashes:
@@ -28,6 +28,7 @@ from typing import TYPE_CHECKING, List, Optional
28
28
  from ..models.results import InstallResult
29
29
  from ..utils.console import _rich_error
30
30
  from ..utils.diagnostics import DiagnosticCollector
31
+ from ..utils.path_security import PathTraversalError
31
32
  from .errors import DirectDependencyError, PolicyViolationError
32
33
 
33
34
  if TYPE_CHECKING:
@@ -60,6 +61,8 @@ def run_install_pipeline(
60
61
  protocol_pref=None,
61
62
  allow_protocol_fallback: "Optional[bool]" = None,
62
63
  no_policy: bool = False,
64
+ skill_subset: "Optional[tuple]" = None,
65
+ skill_subset_from_cli: bool = False,
63
66
  ):
64
67
  """Install APM package dependencies.
65
68
 
@@ -152,6 +155,8 @@ def run_install_pipeline(
152
155
  root_has_local_primitives=_root_has_local_primitives,
153
156
  old_local_deployed=_old_local_deployed,
154
157
  no_policy=no_policy,
158
+ skill_subset=skill_subset,
159
+ skill_subset_from_cli=skill_subset_from_cli,
155
160
  )
156
161
 
157
162
  # ------------------------------------------------------------------
@@ -381,5 +386,9 @@ def run_install_pipeline(
381
386
  # #946: same pattern -- surface the message as-is instead of
382
387
  # double-wrapping it through the generic RuntimeError below.
383
388
  raise
389
+ except PathTraversalError:
390
+ # Path-safety violation in SKILL_BUNDLE or other nested
391
+ # resolution -- surface as-is for actionable user guidance.
392
+ raise
384
393
  except Exception as e:
385
394
  raise RuntimeError(f"Failed to resolve APM dependencies: {e}")
@@ -42,3 +42,5 @@ class InstallRequest:
42
42
  protocol_pref: Any = None # ProtocolPreference (NONE/SSH/HTTPS) for shorthand transport
43
43
  allow_protocol_fallback: Optional[bool] = None # None => read APM_ALLOW_PROTOCOL_FALLBACK env
44
44
  no_policy: bool = False # W2-escape-hatch: skip org policy enforcement
45
+ skill_subset: Optional[Tuple[str, ...]] = None # --skill filter for SKILL_BUNDLE packages
46
+ skill_subset_from_cli: bool = False # True when user passed --skill (even --skill '*')
@@ -77,4 +77,6 @@ class InstallService:
77
77
  protocol_pref=request.protocol_pref,
78
78
  allow_protocol_fallback=request.allow_protocol_fallback,
79
79
  no_policy=request.no_policy,
80
+ skill_subset=request.skill_subset,
81
+ skill_subset_from_cli=request.skill_subset_from_cli,
80
82
  )
@@ -54,6 +54,7 @@ def integrate_package_primitives(
54
54
  package_name: str = "",
55
55
  logger: Optional["InstallLogger"] = None,
56
56
  scope: Optional["InstallScope"] = None,
57
+ skill_subset: "Optional[tuple]" = None,
57
58
  ) -> dict:
58
59
  """Run the full integration pipeline for a single package.
59
60
 
@@ -141,7 +142,7 @@ def integrate_package_primitives(
141
142
  skill_result = skill_integrator.integrate_package_skill(
142
143
  package_info, project_root,
143
144
  diagnostics=diagnostics, managed_files=managed_files, force=force,
144
- targets=targets,
145
+ targets=targets, skill_subset=skill_subset,
145
146
  )
146
147
  _skill_target_dirs: set = builtins.set()
147
148
  for tp in skill_result.target_paths:
@@ -58,6 +58,7 @@ def _format_package_type_label(pkg_type) -> Optional[str]:
58
58
  PackageType.HYBRID: "Hybrid (apm.yml + SKILL.md)",
59
59
  PackageType.APM_PACKAGE: "APM Package (apm.yml)",
60
60
  PackageType.HOOK_PACKAGE: "Hook Package (hooks/*.json only)",
61
+ PackageType.SKILL_BUNDLE: "Skill Bundle (skills/<name>/SKILL.md)",
61
62
  }.get(pkg_type)
62
63
 
63
64
 
@@ -86,6 +86,18 @@ def _integrate_materialization(
86
86
  package_name=dep_key,
87
87
  logger=logger,
88
88
  scope=ctx.scope,
89
+ # Per-package effective subset: CLI --skill overrides per-entry
90
+ # apm.yml skills:. When CLI is absent (bare reinstall), fall back
91
+ # to the dep_ref's persisted skill_subset.
92
+ # When CLI explicitly provided (even --skill '*'), use ctx value
93
+ # (which is None for '*' = install all).
94
+ skill_subset=(
95
+ ctx.skill_subset
96
+ if ctx.skill_subset_from_cli
97
+ else (
98
+ tuple(dep_ref.skill_subset) if dep_ref.skill_subset else None
99
+ )
100
+ ),
89
101
  )
90
102
  for k in (
91
103
  "prompts", "agents", "skills", "sub_skills",