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.
- {apm_cli-0.8.2/src/apm_cli.egg-info → apm_cli-0.8.4}/PKG-INFO +12 -11
- {apm_cli-0.8.2 → apm_cli-0.8.4}/README.md +10 -10
- {apm_cli-0.8.2 → apm_cli-0.8.4}/pyproject.toml +3 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/copilot.py +5 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/vscode.py +37 -15
- apm_cli-0.8.4/src/apm_cli/bundle/__init__.py +13 -0
- apm_cli-0.8.4/src/apm_cli/bundle/lockfile_enrichment.py +76 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/bundle/packer.py +24 -19
- apm_cli-0.8.4/src/apm_cli/bundle/plugin_exporter.py +659 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/_helpers.py +72 -16
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/audit.py +46 -51
- apm_cli-0.8.4/src/apm_cli/commands/compile/__init__.py +11 -0
- apm_cli-0.8.4/src/apm_cli/commands/compile/cli.py +578 -0
- apm_cli-0.8.4/src/apm_cli/commands/compile/watcher.py +172 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/config.py +19 -15
- apm_cli-0.8.4/src/apm_cli/commands/deps/__init__.py +34 -0
- apm_cli-0.8.4/src/apm_cli/commands/deps/_utils.py +303 -0
- 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
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/init.py +63 -31
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/install.py +723 -876
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/list_cmd.py +7 -8
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/mcp.py +19 -13
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/pack.py +41 -27
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/prune.py +38 -32
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/run.py +32 -28
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/runtime.py +18 -18
- apm_cli-0.8.4/src/apm_cli/commands/uninstall/__init__.py +23 -0
- apm_cli-0.8.4/src/apm_cli/commands/uninstall/cli.py +188 -0
- apm_cli-0.8.4/src/apm_cli/commands/uninstall/engine.py +385 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/update.py +26 -26
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/agents_compiler.py +22 -15
- apm_cli-0.8.4/src/apm_cli/constants.py +31 -0
- apm_cli-0.8.4/src/apm_cli/core/__init__.py +5 -0
- apm_cli-0.8.4/src/apm_cli/core/auth.py +419 -0
- apm_cli-0.8.4/src/apm_cli/core/command_logger.py +330 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/safe_installer.py +34 -11
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/token_manager.py +25 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/apm_resolver.py +50 -16
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/dependency_graph.py +16 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/github_downloader.py +141 -91
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/lockfile.py +20 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/plugin_parser.py +44 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/drift.py +16 -15
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/instruction_integrator.py +2 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/mcp_integrator.py +238 -75
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/prompt_integrator.py +2 -1
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/skill_integrator.py +23 -10
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/__init__.py +6 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/apm_package.py +49 -0
- apm_cli-0.8.4/src/apm_cli/models/dependency/__init__.py +14 -0
- apm_cli-0.8.4/src/apm_cli/models/dependency/mcp.py +136 -0
- apm_cli-0.8.4/src/apm_cli/models/dependency/reference.py +958 -0
- apm_cli-0.8.4/src/apm_cli/models/dependency/types.py +63 -0
- apm_cli-0.8.4/src/apm_cli/models/results.py +25 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/validation.py +57 -42
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/operations.py +9 -3
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/content_scanner.py +3 -1
- apm_cli-0.8.4/src/apm_cli/utils/content_hash.py +72 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/diagnostics.py +48 -5
- {apm_cli-0.8.2 → apm_cli-0.8.4/src/apm_cli.egg-info}/PKG-INFO +12 -11
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/SOURCES.txt +19 -4
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/requires.txt +1 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_apm_package_models.py +174 -3
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_apm_resolver.py +0 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_github_downloader.py +8 -6
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_github_downloader_token_precedence.py +2 -2
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_token_manager.py +31 -0
- apm_cli-0.8.2/src/apm_cli/bundle/__init__.py +0 -6
- apm_cli-0.8.2/src/apm_cli/bundle/lockfile_enrichment.py +0 -41
- apm_cli-0.8.2/src/apm_cli/commands/compile.py +0 -765
- apm_cli-0.8.2/src/apm_cli/commands/uninstall.py +0 -591
- apm_cli-0.8.2/src/apm_cli/core/__init__.py +0 -1
- apm_cli-0.8.2/src/apm_cli/models/dependency.py +0 -1189
- {apm_cli-0.8.2 → apm_cli-0.8.4}/AUTHORS +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/LICENSE +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/setup.cfg +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/cli.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/config.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/core/target_detection.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/version.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_console.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_lockfile.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.8.2 → apm_cli-0.8.4}/tests/test_runtime_manager_token_precedence.py +0 -0
- {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.
|
|
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
|
-
- **
|
|
105
|
-
- **
|
|
106
|
-
- **
|
|
107
|
-
- **CI/CD ready
|
|
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://
|
|
115
|
+
curl -sSL https://aka.ms/apm-unix | sh
|
|
115
116
|
```
|
|
116
117
|
|
|
117
118
|
#### Windows
|
|
118
119
|
|
|
119
120
|
```powershell
|
|
120
|
-
irm https://
|
|
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
|
-
- **
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **CI/CD ready
|
|
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://
|
|
53
|
+
curl -sSL https://aka.ms/apm-unix | sh
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
#### Windows
|
|
57
57
|
|
|
58
58
|
```powershell
|
|
59
|
-
irm https://
|
|
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.
|
|
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
|
-
#
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}"
|