apm-cli 0.8.7__tar.gz → 0.8.9__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 (184) hide show
  1. {apm_cli-0.8.7/src/apm_cli.egg-info → apm_cli-0.8.9}/PKG-INFO +2 -2
  2. {apm_cli-0.8.7 → apm_cli-0.8.9}/README.md +1 -1
  3. {apm_cli-0.8.7 → apm_cli-0.8.9}/pyproject.toml +1 -1
  4. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/bundle/lockfile_enrichment.py +6 -1
  5. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/cli.py +3 -0
  6. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/_helpers.py +4 -2
  7. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/compile/cli.py +1 -1
  8. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/deps/cli.py +227 -191
  9. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/install.py +438 -78
  10. apm_cli-0.8.9/src/apm_cli/commands/marketplace.py +436 -0
  11. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/pack.py +1 -1
  12. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/uninstall/cli.py +41 -17
  13. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/uninstall/engine.py +15 -9
  14. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/command_logger.py +1 -0
  15. apm_cli-0.8.9/src/apm_cli/core/scope.py +179 -0
  16. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/target_detection.py +31 -8
  17. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/github_downloader.py +32 -9
  18. apm_cli-0.8.9/src/apm_cli/deps/installed_package.py +54 -0
  19. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/lockfile.py +60 -12
  20. apm_cli-0.8.9/src/apm_cli/deps/registry_proxy.py +231 -0
  21. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/drift.py +26 -3
  22. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/agent_integrator.py +57 -5
  23. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/base_integrator.py +17 -12
  24. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/command_integrator.py +6 -4
  25. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/hook_integrator.py +97 -0
  26. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/instruction_integrator.py +6 -4
  27. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/prompt_integrator.py +4 -4
  28. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/skill_integrator.py +21 -4
  29. apm_cli-0.8.9/src/apm_cli/integration/targets.py +364 -0
  30. apm_cli-0.8.9/src/apm_cli/marketplace/__init__.py +28 -0
  31. apm_cli-0.8.9/src/apm_cli/marketplace/client.py +302 -0
  32. apm_cli-0.8.9/src/apm_cli/marketplace/errors.py +44 -0
  33. apm_cli-0.8.9/src/apm_cli/marketplace/models.py +229 -0
  34. apm_cli-0.8.9/src/apm_cli/marketplace/registry.py +130 -0
  35. apm_cli-0.8.9/src/apm_cli/marketplace/resolver.py +251 -0
  36. {apm_cli-0.8.7 → apm_cli-0.8.9/src/apm_cli.egg-info}/PKG-INFO +2 -2
  37. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli.egg-info/SOURCES.txt +10 -0
  38. apm_cli-0.8.7/src/apm_cli/integration/targets.py +0 -207
  39. {apm_cli-0.8.7 → apm_cli-0.8.9}/AUTHORS +0 -0
  40. {apm_cli-0.8.7 → apm_cli-0.8.9}/LICENSE +0 -0
  41. {apm_cli-0.8.7 → apm_cli-0.8.9}/setup.cfg +0 -0
  42. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/__init__.py +0 -0
  43. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/__init__.py +0 -0
  44. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/__init__.py +0 -0
  45. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/base.py +0 -0
  46. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/codex.py +0 -0
  47. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/copilot.py +0 -0
  48. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/cursor.py +0 -0
  49. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/opencode.py +0 -0
  50. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/client/vscode.py +0 -0
  51. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  52. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/package_manager/base.py +0 -0
  53. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  54. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/bundle/__init__.py +0 -0
  55. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/bundle/packer.py +0 -0
  56. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/bundle/plugin_exporter.py +0 -0
  57. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/bundle/unpacker.py +0 -0
  58. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/__init__.py +0 -0
  59. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/audit.py +0 -0
  60. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/compile/__init__.py +0 -0
  61. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/compile/watcher.py +0 -0
  62. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/config.py +0 -0
  63. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/deps/__init__.py +0 -0
  64. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/deps/_utils.py +0 -0
  65. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/init.py +0 -0
  66. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/list_cmd.py +0 -0
  67. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/mcp.py +0 -0
  68. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/prune.py +0 -0
  69. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/run.py +0 -0
  70. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/runtime.py +0 -0
  71. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  72. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/commands/update.py +0 -0
  73. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/__init__.py +0 -0
  74. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/agents_compiler.py +0 -0
  75. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/claude_formatter.py +0 -0
  76. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/constants.py +0 -0
  77. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/constitution.py +0 -0
  78. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/constitution_block.py +0 -0
  79. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/context_optimizer.py +0 -0
  80. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  81. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/injector.py +0 -0
  82. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/link_resolver.py +0 -0
  83. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/compilation/template_builder.py +0 -0
  84. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/config.py +0 -0
  85. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/constants.py +0 -0
  86. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/__init__.py +0 -0
  87. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/auth.py +0 -0
  88. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/conflict_detector.py +0 -0
  89. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/docker_args.py +0 -0
  90. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/operations.py +0 -0
  91. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/safe_installer.py +0 -0
  92. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/script_runner.py +0 -0
  93. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/core/token_manager.py +0 -0
  94. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/__init__.py +0 -0
  95. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/aggregator.py +0 -0
  96. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/apm_resolver.py +0 -0
  97. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/collection_parser.py +0 -0
  98. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/dependency_graph.py +0 -0
  99. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/package_validator.py +0 -0
  100. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/plugin_parser.py +0 -0
  101. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/deps/verifier.py +0 -0
  102. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/factory.py +0 -0
  103. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/__init__.py +0 -0
  104. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/mcp_integrator.py +0 -0
  105. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/skill_transformer.py +0 -0
  106. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/integration/utils.py +0 -0
  107. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/__init__.py +0 -0
  108. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/apm_package.py +0 -0
  109. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/dependency/__init__.py +0 -0
  110. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/dependency/mcp.py +0 -0
  111. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/dependency/reference.py +0 -0
  112. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/dependency/types.py +0 -0
  113. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/plugin.py +0 -0
  114. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/results.py +0 -0
  115. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/models/validation.py +0 -0
  116. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/output/__init__.py +0 -0
  117. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/output/formatters.py +0 -0
  118. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/output/models.py +0 -0
  119. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/output/script_formatters.py +0 -0
  120. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/__init__.py +0 -0
  121. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/ci_checks.py +0 -0
  122. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/discovery.py +0 -0
  123. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/inheritance.py +0 -0
  124. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/matcher.py +0 -0
  125. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/models.py +0 -0
  126. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/parser.py +0 -0
  127. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/policy_checks.py +0 -0
  128. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/policy/schema.py +0 -0
  129. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/primitives/__init__.py +0 -0
  130. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/primitives/discovery.py +0 -0
  131. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/primitives/models.py +0 -0
  132. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/primitives/parser.py +0 -0
  133. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/registry/__init__.py +0 -0
  134. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/registry/client.py +0 -0
  135. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/registry/integration.py +0 -0
  136. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/registry/operations.py +0 -0
  137. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/__init__.py +0 -0
  138. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/base.py +0 -0
  139. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/codex_runtime.py +0 -0
  140. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  141. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/factory.py +0 -0
  142. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/llm_runtime.py +0 -0
  143. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/runtime/manager.py +0 -0
  144. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/security/__init__.py +0 -0
  145. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/security/audit_report.py +0 -0
  146. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/security/content_scanner.py +0 -0
  147. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/security/file_scanner.py +0 -0
  148. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/security/gate.py +0 -0
  149. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/__init__.py +0 -0
  150. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/console.py +0 -0
  151. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/content_hash.py +0 -0
  152. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/diagnostics.py +0 -0
  153. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/file_ops.py +0 -0
  154. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/github_host.py +0 -0
  155. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/helpers.py +0 -0
  156. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/path_security.py +0 -0
  157. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/paths.py +0 -0
  158. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/version_checker.py +0 -0
  159. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/utils/yaml_io.py +0 -0
  160. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/version.py +0 -0
  161. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/workflow/__init__.py +0 -0
  162. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/workflow/discovery.py +0 -0
  163. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/workflow/parser.py +0 -0
  164. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli/workflow/runner.py +0 -0
  165. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  166. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli.egg-info/entry_points.txt +0 -0
  167. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli.egg-info/requires.txt +0 -0
  168. {apm_cli-0.8.7 → apm_cli-0.8.9}/src/apm_cli.egg-info/top_level.txt +0 -0
  169. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_apm_package_models.py +0 -0
  170. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_apm_resolver.py +0 -0
  171. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_codex_docker_args_fix.py +0 -0
  172. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_codex_empty_string_and_defaults.py +0 -0
  173. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_collision_integration.py +0 -0
  174. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_console.py +0 -0
  175. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_distributed_compilation.py +0 -0
  176. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_empty_string_and_defaults.py +0 -0
  177. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_enhanced_discovery.py +0 -0
  178. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_github_downloader.py +0 -0
  179. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_github_downloader_token_precedence.py +0 -0
  180. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_lockfile.py +0 -0
  181. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_runnable_prompts.py +0 -0
  182. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_runtime_manager_token_precedence.py +0 -0
  183. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_token_manager.py +0 -0
  184. {apm_cli-0.8.7 → apm_cli-0.8.9}/tests/test_virtual_package_multi_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apm-cli
3
- Version: 0.8.7
3
+ Version: 0.8.9
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -66,7 +66,7 @@ Dynamic: license-file
66
66
 
67
67
  Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
68
68
 
69
- GitHub Copilot · Claude Code · Cursor · OpenCode
69
+ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
70
70
 
71
71
  **[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
72
72
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
6
6
 
7
- GitHub Copilot · Claude Code · Cursor · OpenCode
7
+ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
8
8
 
9
9
  **[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
10
10
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.8.7"
7
+ version = "0.8.9"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -13,7 +13,8 @@ _TARGET_PREFIXES = {
13
13
  "claude": [".claude/"],
14
14
  "cursor": [".cursor/"],
15
15
  "opencode": [".opencode/"],
16
- "all": [".github/", ".claude/", ".cursor/", ".opencode/"],
16
+ "codex": [".codex/", ".agents/"],
17
+ "all": [".github/", ".claude/", ".cursor/", ".opencode/", ".codex/", ".agents/"],
17
18
  }
18
19
 
19
20
  # Cross-target path equivalences for skills/ and agents/ directories.
@@ -46,6 +47,10 @@ _CROSS_TARGET_MAPS: Dict[str, Dict[str, str]] = {
46
47
  ".github/skills/": ".opencode/skills/",
47
48
  ".github/agents/": ".opencode/agents/",
48
49
  },
50
+ "codex": {
51
+ ".github/skills/": ".agents/skills/",
52
+ ".github/agents/": ".codex/agents/",
53
+ },
49
54
  }
50
55
 
51
56
 
@@ -22,6 +22,7 @@ from apm_cli.commands.deps import deps
22
22
  from apm_cli.commands.init import init
23
23
  from apm_cli.commands.install import install
24
24
  from apm_cli.commands.list_cmd import list as list_cmd
25
+ from apm_cli.commands.marketplace import marketplace, search as marketplace_search
25
26
  from apm_cli.commands.mcp import mcp
26
27
  from apm_cli.commands.pack import pack_cmd, unpack_cmd
27
28
  from apm_cli.commands.prune import prune
@@ -69,6 +70,8 @@ cli.add_command(list_cmd, name="list")
69
70
  cli.add_command(config)
70
71
  cli.add_command(runtime)
71
72
  cli.add_command(mcp)
73
+ cli.add_command(marketplace)
74
+ cli.add_command(marketplace_search, name="search")
72
75
 
73
76
 
74
77
  def _configure_encoding() -> None:
@@ -432,12 +432,13 @@ def _create_plugin_json(config):
432
432
  f.write(json.dumps(plugin_data, indent=2) + "\n")
433
433
 
434
434
 
435
- def _create_minimal_apm_yml(config, plugin=False):
435
+ def _create_minimal_apm_yml(config, plugin=False, target_path=None):
436
436
  """Create minimal apm.yml file with auto-detected metadata.
437
437
 
438
438
  Args:
439
439
  config: dict with name, version, description, author keys.
440
440
  plugin: if True, include a devDependencies section.
441
+ target_path: explicit file path to write (defaults to cwd/apm.yml).
441
442
  """
442
443
  # Create minimal apm.yml structure
443
444
  apm_yml_data = {
@@ -455,4 +456,5 @@ def _create_minimal_apm_yml(config, plugin=False):
455
456
 
456
457
  # Write apm.yml
457
458
  from ..utils.yaml_io import dump_yaml
458
- dump_yaml(apm_yml_data, APM_YML_FILENAME)
459
+ out_path = target_path or APM_YML_FILENAME
460
+ dump_yaml(apm_yml_data, out_path)
@@ -172,7 +172,7 @@ def _get_validation_suggestion(error_msg):
172
172
  @click.option(
173
173
  "--target",
174
174
  "-t",
175
- type=click.Choice(["copilot", "vscode", "agents", "claude", "cursor", "opencode", "all"]),
175
+ type=click.Choice(["copilot", "vscode", "agents", "claude", "cursor", "opencode", "codex", "all"]),
176
176
  default=None,
177
177
  help="Target platform: copilot (AGENTS.md), claude (CLAUDE.md), cursor, opencode, or all. 'vscode' and 'agents' are deprecated aliases for 'copilot'. Auto-detects if not specified.",
178
178
  )
@@ -28,14 +28,205 @@ def deps():
28
28
  pass
29
29
 
30
30
 
31
+ def _show_scope_deps(scope_label, apm_dir, logger, console, has_rich):
32
+ """Display dependencies for a single scope (Project or Global)."""
33
+ from ...deps.lockfile import LockFile, get_lockfile_path
34
+
35
+ apm_modules_path = apm_dir / APM_MODULES_DIR
36
+
37
+ # Check if apm_modules exists
38
+ if not apm_modules_path.exists():
39
+ logger.progress(f"No APM dependencies installed ({scope_label} scope)")
40
+ logger.verbose_detail("Run 'apm install' to install dependencies from apm.yml")
41
+ return
42
+
43
+ # Load project dependencies to check for orphaned packages
44
+ # GitHub: owner/repo or owner/virtual-pkg-name (2 levels)
45
+ # Azure DevOps: org/project/repo or org/project/virtual-pkg-name (3 levels)
46
+ declared_sources = {} # dep_path -> 'github' | 'azure-devops'
47
+ try:
48
+ apm_yml_path = apm_dir / APM_YML_FILENAME
49
+ if apm_yml_path.exists():
50
+ project_package = APMPackage.from_apm_yml(apm_yml_path)
51
+ for dep in project_package.get_apm_dependencies():
52
+ # Build the expected installed package name
53
+ repo_parts = dep.repo_url.split('/')
54
+ source = 'azure-devops' if dep.is_azure_devops() else 'github'
55
+ is_ado = dep.is_azure_devops() and len(repo_parts) >= 3
56
+ is_gh = len(repo_parts) >= 2
57
+
58
+ if not dep.is_virtual:
59
+ # Regular package: use full repo_url path
60
+ if is_ado:
61
+ declared_sources[f"{repo_parts[0]}/{repo_parts[1]}/{repo_parts[2]}"] = source
62
+ elif is_gh:
63
+ declared_sources[f"{repo_parts[0]}/{repo_parts[1]}"] = source
64
+ continue
65
+
66
+ if dep.is_virtual_subdirectory() and dep.virtual_path:
67
+ # Virtual subdirectory packages keep natural path structure.
68
+ if is_ado:
69
+ declared_sources[
70
+ f"{repo_parts[0]}/{repo_parts[1]}/{repo_parts[2]}/{dep.virtual_path}"
71
+ ] = source
72
+ elif is_gh:
73
+ declared_sources[
74
+ f"{repo_parts[0]}/{repo_parts[1]}/{dep.virtual_path}"
75
+ ] = source
76
+ continue
77
+
78
+ # Virtual file/collection packages are flattened.
79
+ package_name = dep.get_virtual_package_name()
80
+ if is_ado:
81
+ declared_sources[f"{repo_parts[0]}/{repo_parts[1]}/{package_name}"] = source
82
+ elif is_gh:
83
+ declared_sources[f"{repo_parts[0]}/{package_name}"] = source
84
+ except Exception:
85
+ pass # Continue without orphan detection if apm.yml parsing fails
86
+
87
+ # Also load lockfile deps to avoid false orphan flags on transitive deps
88
+ try:
89
+ lockfile_path = get_lockfile_path(apm_dir)
90
+ if lockfile_path.exists():
91
+ lockfile = LockFile.read(lockfile_path)
92
+ for dep in lockfile.dependencies.values():
93
+ # Lockfile keys match declared_sources format (owner/repo)
94
+ dep_key = dep.get_unique_key()
95
+ if dep_key and dep_key not in declared_sources:
96
+ declared_sources[dep_key] = 'github'
97
+ except Exception:
98
+ pass # Continue without lockfile if it can't be read
99
+
100
+ # Scan for installed packages in org-namespaced structure
101
+ # Walks the tree to find directories containing apm.yml or SKILL.md,
102
+ # handling GitHub (2-level), ADO (3-level), and subdirectory (4+ level) packages.
103
+ installed_packages = []
104
+ orphaned_packages = []
105
+ for candidate in apm_modules_path.rglob("*"):
106
+ if not candidate.is_dir() or candidate.name.startswith('.'):
107
+ continue
108
+ has_apm_yml = (candidate / APM_YML_FILENAME).exists()
109
+ has_skill_md = (candidate / SKILL_MD_FILENAME).exists()
110
+ if not has_apm_yml and not has_skill_md:
111
+ continue
112
+ rel_parts = candidate.relative_to(apm_modules_path).parts
113
+ if len(rel_parts) < 2:
114
+ continue
115
+ org_repo_name = "/".join(rel_parts)
116
+
117
+ # Skip sub-skills inside .apm/ directories -- they belong to the parent package
118
+ if '.apm' in rel_parts:
119
+ continue
120
+
121
+ # Skip skill sub-dirs nested inside another package (e.g. plugin
122
+ # skills/ directories that are deployment artifacts, not packages).
123
+ if has_skill_md and not has_apm_yml and _is_nested_under_package(candidate, apm_modules_path):
124
+ continue
125
+
126
+ try:
127
+ version = 'unknown'
128
+ if has_apm_yml:
129
+ package = APMPackage.from_apm_yml(candidate / APM_YML_FILENAME)
130
+ version = package.version or 'unknown'
131
+ primitives = _count_primitives(candidate)
132
+
133
+ is_orphaned = org_repo_name not in declared_sources
134
+ if is_orphaned:
135
+ orphaned_packages.append(org_repo_name)
136
+
137
+ installed_packages.append({
138
+ 'name': org_repo_name,
139
+ 'version': version,
140
+ 'source': 'orphaned' if is_orphaned else declared_sources.get(org_repo_name, 'github'),
141
+ 'primitives': primitives,
142
+ 'path': str(candidate),
143
+ 'is_orphaned': is_orphaned
144
+ })
145
+ except Exception as e:
146
+ logger.warning(f"Failed to read package {org_repo_name}: {e}")
147
+
148
+ if not installed_packages:
149
+ logger.progress(
150
+ f"apm_modules/ directory exists but contains no valid packages ({scope_label} scope)"
151
+ )
152
+ return
153
+
154
+ # Display packages in table format
155
+ if has_rich:
156
+ from rich.table import Table
157
+
158
+ table = Table(
159
+ title=f" APM Dependencies ({scope_label})",
160
+ show_header=True,
161
+ header_style="bold cyan",
162
+ )
163
+ table.add_column("Package", style="bold white")
164
+ table.add_column("Version", style="yellow")
165
+ table.add_column("Source", style="blue")
166
+ table.add_column("Prompts", style="magenta", justify="center")
167
+ table.add_column("Instructions", style="green", justify="center")
168
+ table.add_column("Agents", style="cyan", justify="center")
169
+ table.add_column("Skills", style="yellow", justify="center")
170
+ table.add_column("Hooks", style="red", justify="center")
171
+
172
+ for pkg in installed_packages:
173
+ p = pkg['primitives']
174
+ table.add_row(
175
+ pkg['name'],
176
+ pkg['version'],
177
+ pkg['source'],
178
+ str(p.get('prompts', 0)) if p.get('prompts', 0) > 0 else "-",
179
+ str(p.get('instructions', 0)) if p.get('instructions', 0) > 0 else "-",
180
+ str(p.get('agents', 0)) if p.get('agents', 0) > 0 else "-",
181
+ str(p.get('skills', 0)) if p.get('skills', 0) > 0 else "-",
182
+ str(p.get('hooks', 0)) if p.get('hooks', 0) > 0 else "-",
183
+ )
184
+
185
+ console.print(table)
186
+
187
+ # Show orphaned packages warning
188
+ if orphaned_packages:
189
+ console.print(f"\n[!] {len(orphaned_packages)} orphaned package(s) found (not in apm.yml):", style="yellow")
190
+ for pkg in orphaned_packages:
191
+ console.print(f" * {pkg}", style="dim yellow")
192
+ console.print("\n Run 'apm prune' to remove orphaned packages", style="cyan")
193
+ else:
194
+ # Fallback text table
195
+ click.echo(f" APM Dependencies ({scope_label}):")
196
+ click.echo(f"{'Package':<30} {'Version':<10} {'Source':<12} {'Prompts':>7} {'Instr':>7} {'Agents':>7} {'Skills':>7} {'Hooks':>7}")
197
+ click.echo("-" * 98)
198
+
199
+ for pkg in installed_packages:
200
+ p = pkg['primitives']
201
+ name = pkg['name'][:28]
202
+ version = pkg['version'][:8]
203
+ source = pkg['source'][:10]
204
+ prompts = str(p.get('prompts', 0)) if p.get('prompts', 0) > 0 else "-"
205
+ instructions = str(p.get('instructions', 0)) if p.get('instructions', 0) > 0 else "-"
206
+ agents = str(p.get('agents', 0)) if p.get('agents', 0) > 0 else "-"
207
+ skills = str(p.get('skills', 0)) if p.get('skills', 0) > 0 else "-"
208
+ hooks = str(p.get('hooks', 0)) if p.get('hooks', 0) > 0 else "-"
209
+ click.echo(f"{name:<30} {version:<10} {source:<12} {prompts:>7} {instructions:>7} {agents:>7} {skills:>7} {hooks:>7}")
210
+
211
+ # Show orphaned packages warning
212
+ if orphaned_packages:
213
+ click.echo(f"\n[!] {len(orphaned_packages)} orphaned package(s) found (not in apm.yml):")
214
+ for pkg in orphaned_packages:
215
+ click.echo(f" * {pkg}")
216
+ click.echo("\n Run 'apm prune' to remove orphaned packages")
217
+
218
+
31
219
  @deps.command(name="list", help="List installed APM dependencies")
32
- def list_packages():
220
+ @click.option("--global", "-g", "global_", is_flag=True, default=False,
221
+ help="List user-scope dependencies (~/.apm/) instead of project")
222
+ @click.option("--all", "show_all", is_flag=True, default=False,
223
+ help="Show both project and user-scope dependencies")
224
+ def list_packages(global_, show_all):
33
225
  """Show all installed APM dependencies with context files and agent workflows."""
34
226
  logger = CommandLogger("deps-list")
35
227
 
36
228
  try:
37
229
  # Import Rich components with fallback
38
- from rich.table import Table
39
230
  from rich.console import Console
40
231
  import shutil
41
232
  term_width = shutil.get_terminal_size((120, 24)).columns
@@ -44,192 +235,29 @@ def list_packages():
44
235
  except ImportError:
45
236
  has_rich = False
46
237
  console = None
47
-
238
+
48
239
  try:
49
- project_root = Path(".")
50
- apm_modules_path = project_root / APM_MODULES_DIR
51
-
52
- # Check if apm_modules exists
53
- if not apm_modules_path.exists():
54
- logger.progress("No APM dependencies installed yet")
55
- logger.verbose_detail("Run 'apm install' to install dependencies from apm.yml")
56
- return
57
-
58
- # Load project dependencies to check for orphaned packages
59
- # GitHub: owner/repo or owner/virtual-pkg-name (2 levels)
60
- # Azure DevOps: org/project/repo or org/project/virtual-pkg-name (3 levels)
61
- declared_sources = {} # dep_path -> 'github' | 'azure-devops'
62
- try:
63
- apm_yml_path = project_root / APM_YML_FILENAME
64
- if apm_yml_path.exists():
65
- project_package = APMPackage.from_apm_yml(apm_yml_path)
66
- for dep in project_package.get_apm_dependencies():
67
- # Build the expected installed package name
68
- repo_parts = dep.repo_url.split('/')
69
- source = 'azure-devops' if dep.is_azure_devops() else 'github'
70
- is_ado = dep.is_azure_devops() and len(repo_parts) >= 3
71
- is_gh = len(repo_parts) >= 2
72
-
73
- if not dep.is_virtual:
74
- # Regular package: use full repo_url path
75
- if is_ado:
76
- declared_sources[f"{repo_parts[0]}/{repo_parts[1]}/{repo_parts[2]}"] = source
77
- elif is_gh:
78
- declared_sources[f"{repo_parts[0]}/{repo_parts[1]}"] = source
79
- continue
80
-
81
- if dep.is_virtual_subdirectory() and dep.virtual_path:
82
- # Virtual subdirectory packages keep natural path structure.
83
- if is_ado:
84
- declared_sources[
85
- f"{repo_parts[0]}/{repo_parts[1]}/{repo_parts[2]}/{dep.virtual_path}"
86
- ] = source
87
- elif is_gh:
88
- declared_sources[
89
- f"{repo_parts[0]}/{repo_parts[1]}/{dep.virtual_path}"
90
- ] = source
91
- continue
92
-
93
- # Virtual file/collection packages are flattened.
94
- package_name = dep.get_virtual_package_name()
95
- if is_ado:
96
- declared_sources[f"{repo_parts[0]}/{repo_parts[1]}/{package_name}"] = source
97
- elif is_gh:
98
- declared_sources[f"{repo_parts[0]}/{package_name}"] = source
99
- except Exception:
100
- pass # Continue without orphan detection if apm.yml parsing fails
101
-
102
- # Also load lockfile deps to avoid false orphan flags on transitive deps
103
- try:
104
- from ...deps.lockfile import LockFile, get_lockfile_path
105
- lockfile_path = get_lockfile_path(project_root)
106
- if lockfile_path.exists():
107
- lockfile = LockFile.read(lockfile_path)
108
- for dep in lockfile.dependencies.values():
109
- # Lockfile keys match declared_sources format (owner/repo)
110
- dep_key = dep.get_unique_key()
111
- if dep_key and dep_key not in declared_sources:
112
- declared_sources[dep_key] = 'github'
113
- except Exception:
114
- pass # Continue without lockfile if it can't be read
115
-
116
- # Scan for installed packages in org-namespaced structure
117
- # Walks the tree to find directories containing apm.yml or SKILL.md,
118
- # handling GitHub (2-level), ADO (3-level), and subdirectory (4+ level) packages.
119
- installed_packages = []
120
- orphaned_packages = []
121
- for candidate in apm_modules_path.rglob("*"):
122
- if not candidate.is_dir() or candidate.name.startswith('.'):
123
- continue
124
- has_apm_yml = (candidate / APM_YML_FILENAME).exists()
125
- has_skill_md = (candidate / SKILL_MD_FILENAME).exists()
126
- if not has_apm_yml and not has_skill_md:
127
- continue
128
- rel_parts = candidate.relative_to(apm_modules_path).parts
129
- if len(rel_parts) < 2:
130
- continue
131
- org_repo_name = "/".join(rel_parts)
132
-
133
- # Skip sub-skills inside .apm/ directories -- they belong to the parent package
134
- if '.apm' in rel_parts:
135
- continue
136
-
137
- # Skip skill sub-dirs nested inside another package (e.g. plugin
138
- # skills/ directories that are deployment artifacts, not packages).
139
- if has_skill_md and not has_apm_yml and _is_nested_under_package(candidate, apm_modules_path):
140
- continue
141
-
142
- try:
143
- version = 'unknown'
144
- if has_apm_yml:
145
- package = APMPackage.from_apm_yml(candidate / APM_YML_FILENAME)
146
- version = package.version or 'unknown'
147
- primitives = _count_primitives(candidate)
148
-
149
- is_orphaned = org_repo_name not in declared_sources
150
- if is_orphaned:
151
- orphaned_packages.append(org_repo_name)
152
-
153
- installed_packages.append({
154
- 'name': org_repo_name,
155
- 'version': version,
156
- 'source': 'orphaned' if is_orphaned else declared_sources.get(org_repo_name, 'github'),
157
- 'primitives': primitives,
158
- 'path': str(candidate),
159
- 'is_orphaned': is_orphaned
160
- })
161
- except Exception as e:
162
- logger.warning(f"Failed to read package {org_repo_name}: {e}")
163
-
164
- if not installed_packages:
165
- logger.progress("apm_modules/ directory exists but contains no valid packages")
166
- return
167
-
168
- # Display packages in table format
169
- if has_rich:
170
- table = Table(title=" APM Dependencies", show_header=True, header_style="bold cyan")
171
- table.add_column("Package", style="bold white")
172
- table.add_column("Version", style="yellow")
173
- table.add_column("Source", style="blue")
174
- table.add_column("Prompts", style="magenta", justify="center")
175
- table.add_column("Instructions", style="green", justify="center")
176
- table.add_column("Agents", style="cyan", justify="center")
177
- table.add_column("Skills", style="yellow", justify="center")
178
- table.add_column("Hooks", style="red", justify="center")
179
-
180
- for pkg in installed_packages:
181
- p = pkg['primitives']
182
- table.add_row(
183
- pkg['name'],
184
- pkg['version'],
185
- pkg['source'],
186
- str(p.get('prompts', 0)) if p.get('prompts', 0) > 0 else "-",
187
- str(p.get('instructions', 0)) if p.get('instructions', 0) > 0 else "-",
188
- str(p.get('agents', 0)) if p.get('agents', 0) > 0 else "-",
189
- str(p.get('skills', 0)) if p.get('skills', 0) > 0 else "-",
190
- str(p.get('hooks', 0)) if p.get('hooks', 0) > 0 else "-",
191
- )
192
-
193
- console.print(table)
194
-
195
- # Show orphaned packages warning
196
- if orphaned_packages:
197
- console.print(f"\n[!] {len(orphaned_packages)} orphaned package(s) found (not in apm.yml):", style="yellow")
198
- for pkg in orphaned_packages:
199
- console.print(f" * {pkg}", style="dim yellow")
200
- console.print("\n Run 'apm prune' to remove orphaned packages", style="cyan")
240
+ from ...core.scope import InstallScope, get_apm_dir
241
+
242
+ if show_all:
243
+ # Show both scopes
244
+ _show_scope_deps("Project", get_apm_dir(InstallScope.PROJECT), logger, console, has_rich)
245
+ if console and has_rich:
246
+ console.print() # spacing between tables
247
+ _show_scope_deps("Global", get_apm_dir(InstallScope.USER), logger, console, has_rich)
248
+ elif global_:
249
+ _show_scope_deps("Global", get_apm_dir(InstallScope.USER), logger, console, has_rich)
201
250
  else:
202
- # Fallback text table
203
- click.echo(" APM Dependencies:")
204
- click.echo(f"{'Package':<30} {'Version':<10} {'Source':<12} {'Prompts':>7} {'Instr':>7} {'Agents':>7} {'Skills':>7} {'Hooks':>7}")
205
- click.echo("-" * 98)
206
-
207
- for pkg in installed_packages:
208
- p = pkg['primitives']
209
- name = pkg['name'][:28]
210
- version = pkg['version'][:8]
211
- source = pkg['source'][:10]
212
- prompts = str(p.get('prompts', 0)) if p.get('prompts', 0) > 0 else "-"
213
- instructions = str(p.get('instructions', 0)) if p.get('instructions', 0) > 0 else "-"
214
- agents = str(p.get('agents', 0)) if p.get('agents', 0) > 0 else "-"
215
- skills = str(p.get('skills', 0)) if p.get('skills', 0) > 0 else "-"
216
- hooks = str(p.get('hooks', 0)) if p.get('hooks', 0) > 0 else "-"
217
- click.echo(f"{name:<30} {version:<10} {source:<12} {prompts:>7} {instructions:>7} {agents:>7} {skills:>7} {hooks:>7}")
218
-
219
- # Show orphaned packages warning
220
- if orphaned_packages:
221
- click.echo(f"\n[!] {len(orphaned_packages)} orphaned package(s) found (not in apm.yml):")
222
- for pkg in orphaned_packages:
223
- click.echo(f" * {pkg}")
224
- click.echo("\n Run 'apm prune' to remove orphaned packages")
225
-
251
+ _show_scope_deps("Project", get_apm_dir(InstallScope.PROJECT), logger, console, has_rich)
226
252
  except Exception as e:
227
253
  logger.error(f"Error listing dependencies: {e}")
228
254
  sys.exit(1)
229
255
 
230
256
 
231
- @deps.command(help="Show dependency tree structure")
232
- def tree():
257
+ @deps.command(help="Show dependency tree structure")
258
+ @click.option("--global", "-g", "global_", is_flag=True, default=False,
259
+ help="Show user-scope dependency tree (~/.apm/)")
260
+ def tree(global_):
233
261
  """Display dependencies in hierarchical tree format using lockfile."""
234
262
  logger = CommandLogger("deps-tree")
235
263
 
@@ -242,26 +270,29 @@ def tree():
242
270
  except ImportError:
243
271
  has_rich = False
244
272
  console = None
245
-
273
+
246
274
  try:
247
- project_root = Path(".")
248
- apm_modules_path = project_root / APM_MODULES_DIR
249
-
275
+ from ...core.scope import InstallScope, get_apm_dir
276
+ scope = InstallScope.USER if global_ else InstallScope.PROJECT
277
+ apm_dir = get_apm_dir(scope)
278
+ project_root = apm_dir
279
+ apm_modules_path = apm_dir / APM_MODULES_DIR
280
+
250
281
  # Load project info
251
282
  project_name = "my-project"
252
283
  try:
253
- apm_yml_path = project_root / APM_YML_FILENAME
284
+ apm_yml_path = apm_dir / APM_YML_FILENAME
254
285
  if apm_yml_path.exists():
255
286
  root_package = APMPackage.from_apm_yml(apm_yml_path)
256
287
  project_name = root_package.name
257
288
  except Exception:
258
289
  pass
259
-
290
+
260
291
  # Try to load lockfile for accurate tree with depth/parent info
261
292
  lockfile_deps = None
262
293
  try:
263
294
  from ...deps.lockfile import LockFile, get_lockfile_path
264
- lockfile_path = get_lockfile_path(project_root)
295
+ lockfile_path = get_lockfile_path(apm_dir)
265
296
  if lockfile_path.exists():
266
297
  lockfile = LockFile.read(lockfile_path)
267
298
  if lockfile:
@@ -460,7 +491,9 @@ def clean(dry_run: bool, yes: bool):
460
491
  show_default=True,
461
492
  help="Max concurrent package downloads (0 to disable parallelism)",
462
493
  )
463
- def update(packages, verbose, force, target, parallel_downloads):
494
+ @click.option("--global", "-g", "global_", is_flag=True, default=False,
495
+ help="Update user-scope dependencies (~/.apm/)")
496
+ def update(packages, verbose, force, target, parallel_downloads, global_):
464
497
  """Update APM dependencies to latest git refs.
465
498
 
466
499
  Re-resolves git references (branches/tags) to their current SHAs,
@@ -490,11 +523,14 @@ def update(packages, verbose, force, target, parallel_downloads):
490
523
  logger.progress(f"Import error: {_APM_IMPORT_ERROR}")
491
524
  sys.exit(1)
492
525
 
493
- project_root = Path.cwd()
526
+ from ...core.scope import InstallScope, get_apm_dir
527
+ scope = InstallScope.USER if global_ else InstallScope.PROJECT
528
+ project_root = get_apm_dir(scope)
494
529
  apm_yml_path = project_root / APM_YML_FILENAME
495
530
 
496
531
  if not apm_yml_path.exists():
497
- logger.error(f"No {APM_YML_FILENAME} found in current directory")
532
+ scope_hint = "~/.apm/" if global_ else "current directory"
533
+ logger.error(f"No {APM_YML_FILENAME} found in {scope_hint}")
498
534
  sys.exit(1)
499
535
 
500
536
  try: