apm-cli 0.8.2__tar.gz → 0.8.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. {apm_cli-0.8.2/src/apm_cli.egg-info → apm_cli-0.8.4}/PKG-INFO +12 -11
  2. {apm_cli-0.8.2 → apm_cli-0.8.4}/README.md +10 -10
  3. {apm_cli-0.8.2 → apm_cli-0.8.4}/pyproject.toml +3 -2
  4. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/copilot.py +5 -2
  5. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/vscode.py +37 -15
  6. apm_cli-0.8.4/src/apm_cli/bundle/__init__.py +13 -0
  7. apm_cli-0.8.4/src/apm_cli/bundle/lockfile_enrichment.py +76 -0
  8. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/bundle/packer.py +24 -19
  9. apm_cli-0.8.4/src/apm_cli/bundle/plugin_exporter.py +659 -0
  10. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/_helpers.py +72 -16
  11. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/audit.py +46 -51
  12. apm_cli-0.8.4/src/apm_cli/commands/compile/__init__.py +11 -0
  13. apm_cli-0.8.4/src/apm_cli/commands/compile/cli.py +578 -0
  14. apm_cli-0.8.4/src/apm_cli/commands/compile/watcher.py +172 -0
  15. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/config.py +19 -15
  16. apm_cli-0.8.4/src/apm_cli/commands/deps/__init__.py +34 -0
  17. apm_cli-0.8.4/src/apm_cli/commands/deps/_utils.py +303 -0
  18. apm_cli-0.8.2/src/apm_cli/commands/deps.py → apm_cli-0.8.4/src/apm_cli/commands/deps/cli.py +94 -376
  19. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/init.py +63 -31
  20. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/install.py +723 -876
  21. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/list_cmd.py +7 -8
  22. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/mcp.py +19 -13
  23. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/pack.py +41 -27
  24. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/prune.py +38 -32
  25. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/run.py +32 -28
  26. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/runtime.py +18 -18
  27. apm_cli-0.8.4/src/apm_cli/commands/uninstall/__init__.py +23 -0
  28. apm_cli-0.8.4/src/apm_cli/commands/uninstall/cli.py +188 -0
  29. apm_cli-0.8.4/src/apm_cli/commands/uninstall/engine.py +385 -0
  30. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/update.py +26 -26
  31. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/agents_compiler.py +22 -15
  32. apm_cli-0.8.4/src/apm_cli/constants.py +31 -0
  33. apm_cli-0.8.4/src/apm_cli/core/__init__.py +5 -0
  34. apm_cli-0.8.4/src/apm_cli/core/auth.py +419 -0
  35. apm_cli-0.8.4/src/apm_cli/core/command_logger.py +330 -0
  36. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/safe_installer.py +34 -11
  37. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/token_manager.py +25 -2
  38. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/apm_resolver.py +50 -16
  39. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/dependency_graph.py +16 -0
  40. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/github_downloader.py +141 -91
  41. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/lockfile.py +20 -2
  42. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/plugin_parser.py +44 -0
  43. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/drift.py +16 -15
  44. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/instruction_integrator.py +2 -0
  45. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/mcp_integrator.py +238 -75
  46. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/prompt_integrator.py +2 -1
  47. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/skill_integrator.py +23 -10
  48. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/__init__.py +6 -0
  49. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/apm_package.py +49 -0
  50. apm_cli-0.8.4/src/apm_cli/models/dependency/__init__.py +14 -0
  51. apm_cli-0.8.4/src/apm_cli/models/dependency/mcp.py +136 -0
  52. apm_cli-0.8.4/src/apm_cli/models/dependency/reference.py +958 -0
  53. apm_cli-0.8.4/src/apm_cli/models/dependency/types.py +63 -0
  54. apm_cli-0.8.4/src/apm_cli/models/results.py +25 -0
  55. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/validation.py +57 -42
  56. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/operations.py +9 -3
  57. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/content_scanner.py +3 -1
  58. apm_cli-0.8.4/src/apm_cli/utils/content_hash.py +72 -0
  59. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/diagnostics.py +48 -5
  60. {apm_cli-0.8.2 → apm_cli-0.8.4/src/apm_cli.egg-info}/PKG-INFO +12 -11
  61. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/SOURCES.txt +19 -4
  62. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/requires.txt +1 -0
  63. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_apm_package_models.py +174 -3
  64. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_apm_resolver.py +0 -2
  65. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_github_downloader.py +8 -6
  66. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_github_downloader_token_precedence.py +2 -2
  67. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_token_manager.py +31 -0
  68. apm_cli-0.8.2/src/apm_cli/bundle/__init__.py +0 -6
  69. apm_cli-0.8.2/src/apm_cli/bundle/lockfile_enrichment.py +0 -41
  70. apm_cli-0.8.2/src/apm_cli/commands/compile.py +0 -765
  71. apm_cli-0.8.2/src/apm_cli/commands/uninstall.py +0 -591
  72. apm_cli-0.8.2/src/apm_cli/core/__init__.py +0 -1
  73. apm_cli-0.8.2/src/apm_cli/models/dependency.py +0 -1189
  74. {apm_cli-0.8.2 → apm_cli-0.8.4}/AUTHORS +0 -0
  75. {apm_cli-0.8.2 → apm_cli-0.8.4}/LICENSE +0 -0
  76. {apm_cli-0.8.2 → apm_cli-0.8.4}/setup.cfg +0 -0
  77. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/__init__.py +0 -0
  78. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/__init__.py +0 -0
  79. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/__init__.py +0 -0
  80. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/base.py +0 -0
  81. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/codex.py +0 -0
  82. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/cursor.py +0 -0
  83. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/opencode.py +0 -0
  84. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  85. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
  86. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  87. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/bundle/unpacker.py +0 -0
  88. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/cli.py +0 -0
  89. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/__init__.py +0 -0
  90. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/__init__.py +0 -0
  91. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
  92. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constants.py +0 -0
  93. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution.py +0 -0
  94. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution_block.py +0 -0
  95. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
  96. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  97. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/injector.py +0 -0
  98. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/link_resolver.py +0 -0
  99. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/template_builder.py +0 -0
  100. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/config.py +0 -0
  101. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/conflict_detector.py +0 -0
  102. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/docker_args.py +0 -0
  103. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/operations.py +0 -0
  104. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/script_runner.py +0 -0
  105. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/target_detection.py +0 -0
  106. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/__init__.py +0 -0
  107. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/aggregator.py +0 -0
  108. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/collection_parser.py +0 -0
  109. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/package_validator.py +0 -0
  110. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/verifier.py +0 -0
  111. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/factory.py +0 -0
  112. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/__init__.py +0 -0
  113. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/agent_integrator.py +0 -0
  114. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/base_integrator.py +0 -0
  115. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/command_integrator.py +0 -0
  116. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/hook_integrator.py +0 -0
  117. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/skill_transformer.py +0 -0
  118. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/targets.py +0 -0
  119. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/utils.py +0 -0
  120. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/plugin.py +0 -0
  121. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/__init__.py +0 -0
  122. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/formatters.py +0 -0
  123. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/models.py +0 -0
  124. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/script_formatters.py +0 -0
  125. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/__init__.py +0 -0
  126. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/discovery.py +0 -0
  127. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/models.py +0 -0
  128. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/parser.py +0 -0
  129. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/__init__.py +0 -0
  130. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/client.py +0 -0
  131. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/integration.py +0 -0
  132. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/__init__.py +0 -0
  133. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/base.py +0 -0
  134. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
  135. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  136. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/factory.py +0 -0
  137. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
  138. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/manager.py +0 -0
  139. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/__init__.py +0 -0
  140. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/audit_report.py +0 -0
  141. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/gate.py +0 -0
  142. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/__init__.py +0 -0
  143. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/console.py +0 -0
  144. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/github_host.py +0 -0
  145. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/helpers.py +0 -0
  146. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/path_security.py +0 -0
  147. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/version_checker.py +0 -0
  148. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/version.py +0 -0
  149. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/__init__.py +0 -0
  150. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/discovery.py +0 -0
  151. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/parser.py +0 -0
  152. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/runner.py +0 -0
  153. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  154. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
  155. {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/top_level.txt +0 -0
  156. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_codex_docker_args_fix.py +0 -0
  157. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
  158. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_collision_integration.py +0 -0
  159. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_console.py +0 -0
  160. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_distributed_compilation.py +0 -0
  161. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_empty_string_and_defaults.py +0 -0
  162. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_enhanced_discovery.py +0 -0
  163. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_lockfile.py +0 -0
  164. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_runnable_prompts.py +0 -0
  165. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_runtime_manager_token_precedence.py +0 -0
  166. {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_virtual_package_multi_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apm-cli
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -52,6 +52,7 @@ Requires-Dist: GitPython>=3.1.0
52
52
  Provides-Extra: dev
53
53
  Requires-Dist: pytest>=7.0.0; extra == "dev"
54
54
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
55
+ Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
55
56
  Requires-Dist: black>=26.3.1; python_version >= "3.10" and extra == "dev"
56
57
  Requires-Dist: isort>=5.0.0; extra == "dev"
57
58
  Requires-Dist: mypy>=1.0.0; extra == "dev"
@@ -73,7 +74,7 @@ GitHub Copilot · Claude Code · Cursor · OpenCode
73
74
 
74
75
  AI coding agents need context to be useful — standards, prompts, skills, plugins — but today every developer sets this up manually. Nothing is portable nor reproducible. There's no manifest for it.
75
76
 
76
- **APM fixes this.** Declare your project's agentic dependencies once in `apm.yml`, and every developer who clones your repo gets a fully configured agent setup in seconds — with transitive dependency resolution, just like npm or pip.
77
+ **APM fixes this.** Declare your project's agentic dependencies once in `apm.yml`, and every developer who clones your repo gets a fully configured agent setup in seconds — with transitive dependency resolution, just like npm or pip. It's also the first tool that lets you **author plugins** with a real dependency manager and export standard `plugin.json` packages.
77
78
 
78
79
  ```yaml
79
80
  # apm.yml — ships with your project
@@ -98,26 +99,26 @@ apm install # every agent is configured
98
99
 
99
100
  ## Highlights
100
101
 
101
- - **One manifest for everything** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
102
- - **Install from anywhere** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
103
- - **Transitive dependencies** — packages can depend on packages; APM resolves the full tree
104
- - **Compile to standards** — `apm compile` produces `AGENTS.md` (GitHub Copilot, OpenCode), `CLAUDE.md` (Claude Code), and `.cursor/rules/` (Cursor)
105
- - **Content security** — `apm audit` scans for hidden Unicode characters; `apm install` blocks compromised packages before agents can read them
106
- - **Create & share** — `apm pack` bundles your current configuration as a zipped package
107
- - **CI/CD ready** — [GitHub Action](https://github.com/microsoft/apm-action) for automated workflows
102
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
103
+ - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
104
+ - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
105
+ - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
106
+ - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management and security scanning, then export standard `plugin.json`
107
+ - **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
108
+ - **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
108
109
 
109
110
  ## Get Started
110
111
 
111
112
  #### Linux / macOS
112
113
 
113
114
  ```bash
114
- curl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh
115
+ curl -sSL https://aka.ms/apm-unix | sh
115
116
  ```
116
117
 
117
118
  #### Windows
118
119
 
119
120
  ```powershell
120
- irm https://raw.githubusercontent.com/microsoft/apm/main/install.ps1 | iex
121
+ irm https://aka.ms/apm-windows | iex
121
122
  ```
122
123
 
123
124
  Native release binaries are published for macOS, Linux, and Windows x86_64. `apm update` reuses the matching platform installer.
@@ -12,7 +12,7 @@ GitHub Copilot · Claude Code · Cursor · OpenCode
12
12
 
13
13
  AI coding agents need context to be useful — standards, prompts, skills, plugins — but today every developer sets this up manually. Nothing is portable nor reproducible. There's no manifest for it.
14
14
 
15
- **APM fixes this.** Declare your project's agentic dependencies once in `apm.yml`, and every developer who clones your repo gets a fully configured agent setup in seconds — with transitive dependency resolution, just like npm or pip.
15
+ **APM fixes this.** Declare your project's agentic dependencies once in `apm.yml`, and every developer who clones your repo gets a fully configured agent setup in seconds — with transitive dependency resolution, just like npm or pip. It's also the first tool that lets you **author plugins** with a real dependency manager and export standard `plugin.json` packages.
16
16
 
17
17
  ```yaml
18
18
  # apm.yml — ships with your project
@@ -37,26 +37,26 @@ apm install # every agent is configured
37
37
 
38
38
  ## Highlights
39
39
 
40
- - **One manifest for everything** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
41
- - **Install from anywhere** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
42
- - **Transitive dependencies** — packages can depend on packages; APM resolves the full tree
43
- - **Compile to standards** — `apm compile` produces `AGENTS.md` (GitHub Copilot, OpenCode), `CLAUDE.md` (Claude Code), and `.cursor/rules/` (Cursor)
44
- - **Content security** — `apm audit` scans for hidden Unicode characters; `apm install` blocks compromised packages before agents can read them
45
- - **Create & share** — `apm pack` bundles your current configuration as a zipped package
46
- - **CI/CD ready** — [GitHub Action](https://github.com/microsoft/apm-action) for automated workflows
40
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
41
+ - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
42
+ - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
43
+ - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
44
+ - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management and security scanning, then export standard `plugin.json`
45
+ - **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
46
+ - **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
47
47
 
48
48
  ## Get Started
49
49
 
50
50
  #### Linux / macOS
51
51
 
52
52
  ```bash
53
- curl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh
53
+ curl -sSL https://aka.ms/apm-unix | sh
54
54
  ```
55
55
 
56
56
  #### Windows
57
57
 
58
58
  ```powershell
59
- irm https://raw.githubusercontent.com/microsoft/apm/main/install.ps1 | iex
59
+ irm https://aka.ms/apm-windows | iex
60
60
  ```
61
61
 
62
62
  Native release binaries are published for macOS, Linux, and Windows x86_64. `apm update` reuses the matching platform installer.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.8.2"
7
+ version = "0.8.4"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -41,6 +41,7 @@ dependencies = [
41
41
  dev = [
42
42
  "pytest>=7.0.0",
43
43
  "pytest-cov>=4.0.0",
44
+ "pytest-xdist>=3.0.0",
44
45
  "black>=26.3.1; python_version>='3.10'",
45
46
  "isort>=5.0.0",
46
47
  "mypy>=1.0.0",
@@ -70,7 +71,7 @@ warn_return_any = true
70
71
  warn_unused_configs = true
71
72
 
72
73
  [tool.pytest.ini_options]
73
- addopts = "-m 'not benchmark'"
74
+ addopts = "-m 'not benchmark' -n auto"
74
75
  markers = [
75
76
  "integration: marks tests as integration tests that may require network access",
76
77
  "slow: marks tests as slow running tests",
@@ -12,6 +12,7 @@ from .base import MCPClientAdapter
12
12
  from ...registry.client import SimpleRegistryClient
13
13
  from ...registry.integration import RegistryIntegration
14
14
  from ...core.docker_args import DockerArgsProcessor
15
+ from ...core.token_manager import GitHubTokenManager
15
16
  from ...utils.github_host import is_github_hostname
16
17
 
17
18
 
@@ -199,8 +200,10 @@ class CopilotClientAdapter(MCPClientAdapter):
199
200
  is_github_server = self._is_github_server(server_name, remote.get("url", ""))
200
201
 
201
202
  if is_github_server:
202
- # Check for GitHub Personal Access Token
203
- github_token = os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
203
+ # Use centralized token manager (copilot chain: GITHUB_COPILOT_PAT → GITHUB_TOKEN → GITHUB_APM_PAT),
204
+ # falling back to GITHUB_PERSONAL_ACCESS_TOKEN for Copilot CLI compat.
205
+ _tm = GitHubTokenManager()
206
+ github_token = _tm.get_token_for_purpose('copilot') or os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
204
207
  if github_token:
205
208
  config["headers"] = {
206
209
  "Authorization": f"Bearer {github_token}"
@@ -32,7 +32,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
32
32
  self.registry_client = SimpleRegistryClient(registry_url)
33
33
  self.registry_integration = RegistryIntegration(registry_url)
34
34
 
35
- def get_config_path(self):
35
+ def get_config_path(self, logger=None):
36
36
  """Get the path to the VSCode MCP configuration file in the repository.
37
37
 
38
38
  Returns:
@@ -50,11 +50,14 @@ class VSCodeClientAdapter(MCPClientAdapter):
50
50
  if not vscode_dir.exists():
51
51
  vscode_dir.mkdir(parents=True, exist_ok=True)
52
52
  except Exception as e:
53
- print(f"Warning: Could not create .vscode directory: {e}")
53
+ if logger:
54
+ logger.warning(f"Could not create .vscode directory: {e}")
55
+ else:
56
+ print(f"Warning: Could not create .vscode directory: {e}")
54
57
 
55
58
  return str(mcp_config_path)
56
59
 
57
- def update_config(self, new_config):
60
+ def update_config(self, new_config, logger=None):
58
61
  """Update the VSCode MCP configuration with new values.
59
62
 
60
63
  Args:
@@ -63,7 +66,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
63
66
  Returns:
64
67
  bool: True if successful, False otherwise.
65
68
  """
66
- config_path = self.get_config_path()
69
+ config_path = self.get_config_path(logger=logger)
67
70
 
68
71
  try:
69
72
  # Write the updated config
@@ -72,16 +75,19 @@ class VSCodeClientAdapter(MCPClientAdapter):
72
75
 
73
76
  return True
74
77
  except Exception as e:
75
- print(f"Error updating VSCode MCP configuration: {e}")
78
+ if logger:
79
+ logger.error(f"Error updating VSCode MCP configuration: {e}")
80
+ else:
81
+ print(f"Error updating VSCode MCP configuration: {e}")
76
82
  return False
77
83
 
78
- def get_current_config(self):
84
+ def get_current_config(self, logger=None):
79
85
  """Get the current VSCode MCP configuration.
80
86
 
81
87
  Returns:
82
88
  dict: Current VSCode MCP configuration from the local .vscode/mcp.json file.
83
89
  """
84
- config_path = self.get_config_path()
90
+ config_path = self.get_config_path(logger=logger)
85
91
 
86
92
  try:
87
93
  try:
@@ -90,10 +96,13 @@ class VSCodeClientAdapter(MCPClientAdapter):
90
96
  except (FileNotFoundError, json.JSONDecodeError):
91
97
  return {}
92
98
  except Exception as e:
93
- print(f"Error reading VSCode MCP configuration: {e}")
99
+ if logger:
100
+ logger.error(f"Error reading VSCode MCP configuration: {e}")
101
+ else:
102
+ print(f"Error reading VSCode MCP configuration: {e}")
94
103
  return {}
95
104
 
96
- def configure_mcp_server(self, server_url, server_name=None, enabled=True, env_overrides=None, server_info_cache=None, runtime_vars=None):
105
+ def configure_mcp_server(self, server_url, server_name=None, enabled=True, env_overrides=None, server_info_cache=None, runtime_vars=None, logger=None):
97
106
  """Configure an MCP server in VS Code mcp.json file.
98
107
 
99
108
  This method updates the .vscode/mcp.json file to add or update
@@ -105,6 +114,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
105
114
  enabled (bool, optional): Whether to enable the server. Defaults to True.
106
115
  env_overrides (dict, optional): Environment variable overrides. Defaults to None.
107
116
  server_info_cache (dict, optional): Pre-fetched server info to avoid duplicate registry calls.
117
+ logger: Optional CommandLogger for structured output.
108
118
 
109
119
  Returns:
110
120
  bool: True if successful, False otherwise.
@@ -113,7 +123,10 @@ class VSCodeClientAdapter(MCPClientAdapter):
113
123
  ValueError: If server is not found in registry.
114
124
  """
115
125
  if not server_url:
116
- print("Error: server_url cannot be empty")
126
+ if logger:
127
+ logger.error("server_url cannot be empty")
128
+ else:
129
+ print("Error: server_url cannot be empty")
117
130
  return False
118
131
 
119
132
  try:
@@ -133,14 +146,17 @@ class VSCodeClientAdapter(MCPClientAdapter):
133
146
  server_config, input_vars = self._format_server_config(server_info)
134
147
 
135
148
  if not server_config:
136
- print(f"Unable to configure server: {server_url}")
149
+ if logger:
150
+ logger.error(f"Unable to configure server: {server_url}")
151
+ else:
152
+ print(f"Unable to configure server: {server_url}")
137
153
  return False
138
154
 
139
155
  # Use provided server name or fallback to server_url
140
156
  config_key = server_name or server_url
141
157
 
142
158
  # Get current config
143
- current_config = self.get_current_config()
159
+ current_config = self.get_current_config(logger=logger)
144
160
 
145
161
  # Ensure servers and inputs sections exist
146
162
  if "servers" not in current_config:
@@ -159,17 +175,23 @@ class VSCodeClientAdapter(MCPClientAdapter):
159
175
  existing_input_ids.add(var.get("id"))
160
176
 
161
177
  # Update the configuration
162
- result = self.update_config(current_config)
178
+ result = self.update_config(current_config, logger=logger)
163
179
 
164
180
  if result:
165
- print(f"Successfully configured MCP server '{config_key}' for VS Code")
181
+ if logger:
182
+ logger.verbose_detail(f"Configured MCP server '{config_key}' for VS Code")
183
+ else:
184
+ print(f"Successfully configured MCP server '{config_key}' for VS Code")
166
185
  return result
167
186
 
168
187
  except ValueError:
169
188
  # Re-raise ValueError for registry errors
170
189
  raise
171
190
  except Exception as e:
172
- print(f"Error configuring MCP server: {e}")
191
+ if logger:
192
+ logger.error(f"Error configuring MCP server: {e}")
193
+ else:
194
+ print(f"Error configuring MCP server: {e}")
173
195
  return False
174
196
 
175
197
  def _format_server_config(self, server_info):
@@ -0,0 +1,13 @@
1
+ """Bundle creation and consumption for APM packages."""
2
+
3
+ from .packer import pack_bundle, PackResult
4
+ from .plugin_exporter import export_plugin_bundle
5
+ from .unpacker import unpack_bundle, UnpackResult
6
+
7
+ __all__ = [
8
+ "pack_bundle",
9
+ "PackResult",
10
+ "export_plugin_bundle",
11
+ "unpack_bundle",
12
+ "UnpackResult",
13
+ ]
@@ -0,0 +1,76 @@
1
+ """Lockfile enrichment for pack-time metadata."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import List
5
+
6
+ from ..deps.lockfile import LockFile
7
+
8
+
9
+ # Must stay in sync with packer._TARGET_PREFIXES
10
+ _TARGET_PREFIXES = {
11
+ "copilot": [".github/"],
12
+ "vscode": [".github/"],
13
+ "claude": [".claude/"],
14
+ "cursor": [".cursor/"],
15
+ "opencode": [".opencode/"],
16
+ "all": [".github/", ".claude/", ".cursor/", ".opencode/"],
17
+ }
18
+
19
+
20
+ def _filter_files_by_target(deployed_files: List[str], target: str) -> List[str]:
21
+ """Filter deployed file paths by target prefix."""
22
+ prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
23
+ return [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
24
+
25
+
26
+ def enrich_lockfile_for_pack(
27
+ lockfile: LockFile,
28
+ fmt: str,
29
+ target: str,
30
+ ) -> str:
31
+ """Create an enriched copy of the lockfile YAML with a ``pack:`` section.
32
+
33
+ Filters each dependency's ``deployed_files`` to only include paths
34
+ matching the pack *target*, so the bundle lockfile is consistent with
35
+ the files actually shipped in the bundle.
36
+
37
+ Does NOT mutate the original *lockfile* object -- serialises a copy and
38
+ prepends the pack metadata.
39
+
40
+ Args:
41
+ lockfile: The resolved lockfile to enrich.
42
+ fmt: Bundle format (``"apm"`` or ``"plugin"``).
43
+ target: Effective target used for packing (``"vscode"``, ``"claude"``, ``"all"``).
44
+
45
+ Returns:
46
+ A YAML string with the ``pack:`` block followed by the original
47
+ lockfile content.
48
+ """
49
+ import yaml
50
+
51
+ pack_section = yaml.dump(
52
+ {
53
+ "pack": {
54
+ "format": fmt,
55
+ "target": target,
56
+ "packed_at": datetime.now(timezone.utc).isoformat(),
57
+ }
58
+ },
59
+ default_flow_style=False,
60
+ sort_keys=False,
61
+ )
62
+
63
+ # Build a filtered lockfile YAML: each dep's deployed_files is narrowed
64
+ # to only the paths matching the pack target.
65
+ data = yaml.safe_load(lockfile.to_yaml())
66
+ if data and "dependencies" in data:
67
+ for dep in data["dependencies"]:
68
+ if "deployed_files" in dep:
69
+ dep["deployed_files"] = _filter_files_by_target(
70
+ dep["deployed_files"], target
71
+ )
72
+
73
+ lockfile_yaml = yaml.dump(
74
+ data, default_flow_style=False, sort_keys=False, allow_unicode=True
75
+ )
76
+ return pack_section + lockfile_yaml
@@ -10,18 +10,7 @@ from typing import List, Optional
10
10
  from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
11
11
  from ..models.apm_package import APMPackage
12
12
  from ..core.target_detection import detect_target
13
- from .lockfile_enrichment import enrich_lockfile_for_pack
14
-
15
-
16
- # Target prefix mapping ("copilot" and "vscode" both map to .github/)
17
- _TARGET_PREFIXES = {
18
- "copilot": [".github/"],
19
- "vscode": [".github/"],
20
- "claude": [".claude/"],
21
- "cursor": [".cursor/"],
22
- "opencode": [".opencode/"],
23
- "all": [".github/", ".claude/", ".cursor/", ".opencode/"],
24
- }
13
+ from .lockfile_enrichment import enrich_lockfile_for_pack, _TARGET_PREFIXES, _filter_files_by_target
25
14
 
26
15
 
27
16
  @dataclass
@@ -33,12 +22,6 @@ class PackResult:
33
22
  lockfile_enriched: bool = False
34
23
 
35
24
 
36
- def _filter_files_by_target(deployed_files: List[str], target: str) -> List[str]:
37
- """Filter deployed file paths by target prefix."""
38
- prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
39
- return [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
40
-
41
-
42
25
  def pack_bundle(
43
26
  project_root: Path,
44
27
  output_dir: Path,
@@ -46,6 +29,8 @@ def pack_bundle(
46
29
  target: Optional[str] = None,
47
30
  archive: bool = False,
48
31
  dry_run: bool = False,
32
+ force: bool = False,
33
+ logger=None,
49
34
  ) -> PackResult:
50
35
  """Create a self-contained bundle from installed APM dependencies.
51
36
 
@@ -57,6 +42,7 @@ def pack_bundle(
57
42
  (auto-detect from apm.yml / project structure).
58
43
  archive: If *True*, produce a ``.tar.gz`` and remove the directory.
59
44
  dry_run: If *True*, resolve the file list but write nothing to disk.
45
+ force: On collision (plugin format), last writer wins.
60
46
 
61
47
  Returns:
62
48
  :class:`PackResult` describing what was (or would be) produced.
@@ -67,6 +53,21 @@ def pack_bundle(
67
53
  """
68
54
  # 1. Read lockfile (migrate legacy apm.lock → apm.lock.yaml if needed)
69
55
  migrate_lockfile_if_needed(project_root)
56
+
57
+ # Plugin format: delegate to dedicated exporter
58
+ if fmt == "plugin":
59
+ from .plugin_exporter import export_plugin_bundle
60
+
61
+ return export_plugin_bundle(
62
+ project_root=project_root,
63
+ output_dir=output_dir,
64
+ target=target,
65
+ archive=archive,
66
+ dry_run=dry_run,
67
+ force=force,
68
+ logger=logger,
69
+ )
70
+
70
71
  lockfile_path = get_lockfile_path(project_root)
71
72
  lockfile = LockFile.read(lockfile_path)
72
73
  if lockfile is None:
@@ -180,10 +181,14 @@ def pack_bundle(
180
181
  )
181
182
  _scan_findings_total += len(verdict.all_findings)
182
183
  if _scan_findings_total:
183
- _rich_warning(
184
+ _warn_msg = (
184
185
  f"Bundle contains {_scan_findings_total} hidden character(s) across source files "
185
186
  f"— run 'apm audit' to inspect before publishing"
186
187
  )
188
+ if logger:
189
+ logger.warning(_warn_msg)
190
+ else:
191
+ _rich_warning(_warn_msg)
187
192
 
188
193
  # 6. Build output directory
189
194
  bundle_dir = output_dir / f"{pkg_name}-{pkg_version}"