apm-cli 0.7.7__tar.gz → 0.7.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.
- apm_cli-0.7.9/PKG-INFO +170 -0
- apm_cli-0.7.9/README.md +109 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/pyproject.toml +3 -3
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/client/codex.py +3 -3
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/client/copilot.py +2 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/client/vscode.py +1 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/bundle/lockfile_enrichment.py +1 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/bundle/packer.py +26 -13
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/bundle/unpacker.py +35 -21
- apm_cli-0.7.9/src/apm_cli/cli.py +125 -0
- apm_cli-0.7.9/src/apm_cli/commands/_helpers.py +405 -0
- apm_cli-0.7.9/src/apm_cli/commands/compile.py +739 -0
- apm_cli-0.7.9/src/apm_cli/commands/config.py +169 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/commands/deps.py +38 -38
- apm_cli-0.7.9/src/apm_cli/commands/init.py +192 -0
- apm_cli-0.7.9/src/apm_cli/commands/install.py +1893 -0
- apm_cli-0.7.9/src/apm_cli/commands/list_cmd.py +102 -0
- apm_cli-0.7.9/src/apm_cli/commands/mcp.py +373 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/commands/pack.py +26 -7
- apm_cli-0.7.9/src/apm_cli/commands/prune.py +142 -0
- apm_cli-0.7.9/src/apm_cli/commands/run.py +218 -0
- apm_cli-0.7.9/src/apm_cli/commands/runtime.py +188 -0
- apm_cli-0.7.9/src/apm_cli/commands/uninstall.py +559 -0
- apm_cli-0.7.9/src/apm_cli/commands/update.py +167 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/agents_compiler.py +7 -7
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/context_optimizer.py +14 -8
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/distributed_compiler.py +7 -7
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/link_resolver.py +5 -4
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/safe_installer.py +6 -6
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/script_runner.py +29 -15
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/target_detection.py +4 -4
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/token_manager.py +1 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/apm_resolver.py +1 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/github_downloader.py +103 -38
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/lockfile.py +63 -5
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/plugin_parser.py +22 -22
- apm_cli-0.7.9/src/apm_cli/drift.py +199 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/agent_integrator.py +8 -6
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/base_integrator.py +17 -13
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/command_integrator.py +3 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/hook_integrator.py +21 -17
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/instruction_integrator.py +2 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/mcp_integrator.py +168 -38
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/prompt_integrator.py +4 -3
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/skill_integrator.py +120 -40
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/skill_transformer.py +2 -2
- apm_cli-0.7.9/src/apm_cli/models/__init__.py +38 -0
- apm_cli-0.7.9/src/apm_cli/models/apm_package.py +240 -0
- apm_cli-0.7.7/src/apm_cli/models/apm_package.py → apm_cli-0.7.9/src/apm_cli/models/dependency.py +116 -588
- apm_cli-0.7.9/src/apm_cli/models/validation.py +388 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/output/formatters.py +85 -85
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/output/script_formatters.py +21 -21
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/primitives/models.py +2 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/primitives/parser.py +4 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/registry/operations.py +2 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/manager.py +78 -44
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/utils/__init__.py +16 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/utils/console.py +25 -25
- apm_cli-0.7.9/src/apm_cli/utils/diagnostics.py +238 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/utils/github_host.py +4 -4
- apm_cli-0.7.9/src/apm_cli.egg-info/PKG-INFO +170 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli.egg-info/SOURCES.txt +16 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli.egg-info/requires.txt +3 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_apm_package_models.py +9 -4
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_apm_resolver.py +1 -1
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_enhanced_discovery.py +3 -3
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_github_downloader.py +151 -2
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_lockfile.py +101 -3
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_runnable_prompts.py +2 -2
- apm_cli-0.7.7/PKG-INFO +0 -276
- apm_cli-0.7.7/README.md +0 -215
- apm_cli-0.7.7/src/apm_cli/cli.py +0 -4511
- apm_cli-0.7.7/src/apm_cli/models/__init__.py +0 -23
- apm_cli-0.7.7/src/apm_cli.egg-info/PKG-INFO +0 -276
- {apm_cli-0.7.7 → apm_cli-0.7.9}/AUTHORS +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/LICENSE +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/setup.cfg +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/config.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/version.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_console.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.7.7 → apm_cli-0.7.9}/tests/test_virtual_package_multi_install.py +0 -0
apm_cli-0.7.9/PKG-INFO
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apm-cli
|
|
3
|
+
Version: 0.7.9
|
|
4
|
+
Summary: MCP configuration tool
|
|
5
|
+
Author-email: Daniel Meppiel <user@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) Microsoft Corporation.
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Programming Language :: Python :: 3
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
License-File: AUTHORS
|
|
39
|
+
Requires-Dist: click>=8.0.0
|
|
40
|
+
Requires-Dist: colorama>=0.4.6
|
|
41
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
42
|
+
Requires-Dist: requests>=2.28.0
|
|
43
|
+
Requires-Dist: python-frontmatter>=1.0.0
|
|
44
|
+
Requires-Dist: llm>=0.17.0
|
|
45
|
+
Requires-Dist: llm-github-models>=0.1.0
|
|
46
|
+
Requires-Dist: tomli>=1.2.0; python_version < "3.11"
|
|
47
|
+
Requires-Dist: toml>=0.10.2
|
|
48
|
+
Requires-Dist: rich>=13.0.0
|
|
49
|
+
Requires-Dist: rich-click>=1.7.0
|
|
50
|
+
Requires-Dist: watchdog>=3.0.0
|
|
51
|
+
Requires-Dist: GitPython>=3.1.0
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: black>=26.3.1; python_version >= "3.10" and extra == "dev"
|
|
56
|
+
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
57
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
58
|
+
Provides-Extra: build
|
|
59
|
+
Requires-Dist: pyinstaller>=6.0.0; extra == "build"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# APM – Agent Package Manager
|
|
63
|
+
|
|
64
|
+
**An open-source, community-driven dependency manager for AI agents.**
|
|
65
|
+
|
|
66
|
+
Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
|
|
67
|
+
|
|
68
|
+
GitHub Copilot · Claude Code
|
|
69
|
+
|
|
70
|
+
**[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/)**
|
|
71
|
+
|
|
72
|
+
## Why APM
|
|
73
|
+
|
|
74
|
+
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
|
+
**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
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
# apm.yml — ships with your project
|
|
80
|
+
name: your-project
|
|
81
|
+
version: 1.0.0
|
|
82
|
+
dependencies:
|
|
83
|
+
apm:
|
|
84
|
+
# Skills from any repository
|
|
85
|
+
- anthropics/skills/skills/frontend-design
|
|
86
|
+
# Plugins
|
|
87
|
+
- github/awesome-copilot/plugins/context-engineering
|
|
88
|
+
# Specific agent primitives from any repository
|
|
89
|
+
- github/awesome-copilot/agents/api-architect.agent.md
|
|
90
|
+
# A full APM package with instructions, skills, prompts, hooks...
|
|
91
|
+
- microsoft/apm-sample-package
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone <org/repo> && cd <repo>
|
|
96
|
+
apm install # every agent is configured
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Highlights
|
|
100
|
+
|
|
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) and `CLAUDE.md` (Claude Code)
|
|
105
|
+
- **Create & share** — `apm pack` bundles your current configuration as a zipped package
|
|
106
|
+
- **CI/CD ready** — [GitHub Action](https://github.com/microsoft/apm-action) for automated workflows
|
|
107
|
+
|
|
108
|
+
## Get Started
|
|
109
|
+
|
|
110
|
+
#### Linux / macOS
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
curl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Windows
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
irm https://raw.githubusercontent.com/microsoft/apm/main/install.ps1 | iex
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Native release binaries are published for macOS, Linux, and Windows x86_64. `apm update` reuses the matching platform installer.
|
|
123
|
+
|
|
124
|
+
<details>
|
|
125
|
+
<summary>Other install methods</summary>
|
|
126
|
+
|
|
127
|
+
#### Linux / macOS
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Homebrew
|
|
131
|
+
brew install microsoft/apm/apm
|
|
132
|
+
# pip
|
|
133
|
+
pip install apm-cli
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Windows
|
|
137
|
+
|
|
138
|
+
```powershell
|
|
139
|
+
# Scoop
|
|
140
|
+
scoop bucket add apm https://github.com/microsoft/scoop-apm
|
|
141
|
+
scoop install apm
|
|
142
|
+
# pip
|
|
143
|
+
pip install apm-cli
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
Then start adding packages:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
apm install microsoft/apm-sample-package
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
|
|
155
|
+
|
|
156
|
+
## Community
|
|
157
|
+
|
|
158
|
+
Created and maintained by [@danielmeppiel](https://github.com/danielmeppiel).
|
|
159
|
+
|
|
160
|
+
- [Roadmap & Discussions](https://github.com/microsoft/apm/discussions/116)
|
|
161
|
+
- [Contributing](CONTRIBUTING.md)
|
|
162
|
+
- [AI Native Development guide](https://danielmeppiel.github.io/awesome-ai-native) — a practical learning path for AI-native development
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
**Built on open standards:** [AGENTS.md](https://agents.md) · [Agent Skills](https://agentskills.io) · [MCP](https://modelcontextprotocol.io)
|
|
167
|
+
|
|
168
|
+
## Trademarks
|
|
169
|
+
|
|
170
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
|
apm_cli-0.7.9/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# APM – Agent Package Manager
|
|
2
|
+
|
|
3
|
+
**An open-source, community-driven dependency manager for AI agents.**
|
|
4
|
+
|
|
5
|
+
Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
|
|
6
|
+
|
|
7
|
+
GitHub Copilot · Claude Code
|
|
8
|
+
|
|
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
|
+
|
|
11
|
+
## Why APM
|
|
12
|
+
|
|
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
|
+
|
|
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.
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
# apm.yml — ships with your project
|
|
19
|
+
name: your-project
|
|
20
|
+
version: 1.0.0
|
|
21
|
+
dependencies:
|
|
22
|
+
apm:
|
|
23
|
+
# Skills from any repository
|
|
24
|
+
- anthropics/skills/skills/frontend-design
|
|
25
|
+
# Plugins
|
|
26
|
+
- github/awesome-copilot/plugins/context-engineering
|
|
27
|
+
# Specific agent primitives from any repository
|
|
28
|
+
- github/awesome-copilot/agents/api-architect.agent.md
|
|
29
|
+
# A full APM package with instructions, skills, prompts, hooks...
|
|
30
|
+
- microsoft/apm-sample-package
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone <org/repo> && cd <repo>
|
|
35
|
+
apm install # every agent is configured
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Highlights
|
|
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) and `CLAUDE.md` (Claude Code)
|
|
44
|
+
- **Create & share** — `apm pack` bundles your current configuration as a zipped package
|
|
45
|
+
- **CI/CD ready** — [GitHub Action](https://github.com/microsoft/apm-action) for automated workflows
|
|
46
|
+
|
|
47
|
+
## Get Started
|
|
48
|
+
|
|
49
|
+
#### Linux / macOS
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Windows
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
irm https://raw.githubusercontent.com/microsoft/apm/main/install.ps1 | iex
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Native release binaries are published for macOS, Linux, and Windows x86_64. `apm update` reuses the matching platform installer.
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary>Other install methods</summary>
|
|
65
|
+
|
|
66
|
+
#### Linux / macOS
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Homebrew
|
|
70
|
+
brew install microsoft/apm/apm
|
|
71
|
+
# pip
|
|
72
|
+
pip install apm-cli
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Windows
|
|
76
|
+
|
|
77
|
+
```powershell
|
|
78
|
+
# Scoop
|
|
79
|
+
scoop bucket add apm https://github.com/microsoft/scoop-apm
|
|
80
|
+
scoop install apm
|
|
81
|
+
# pip
|
|
82
|
+
pip install apm-cli
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
</details>
|
|
86
|
+
|
|
87
|
+
Then start adding packages:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
apm install microsoft/apm-sample-package
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
|
|
94
|
+
|
|
95
|
+
## Community
|
|
96
|
+
|
|
97
|
+
Created and maintained by [@danielmeppiel](https://github.com/danielmeppiel).
|
|
98
|
+
|
|
99
|
+
- [Roadmap & Discussions](https://github.com/microsoft/apm/discussions/116)
|
|
100
|
+
- [Contributing](CONTRIBUTING.md)
|
|
101
|
+
- [AI Native Development guide](https://danielmeppiel.github.io/awesome-ai-native) — a practical learning path for AI-native development
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
**Built on open standards:** [AGENTS.md](https://agents.md) · [Agent Skills](https://agentskills.io) · [MCP](https://modelcontextprotocol.io)
|
|
106
|
+
|
|
107
|
+
## Trademarks
|
|
108
|
+
|
|
109
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apm-cli"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.9"
|
|
8
8
|
description = "MCP configuration tool"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = {file = "LICENSE"}
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Daniel Meppiel", email = "user@example.com"}
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
dev = [
|
|
42
42
|
"pytest>=7.0.0",
|
|
43
43
|
"pytest-cov>=4.0.0",
|
|
44
|
-
"black>=
|
|
44
|
+
"black>=26.3.1; python_version>='3.10'",
|
|
45
45
|
"isort>=5.0.0",
|
|
46
46
|
"mypy>=1.0.0",
|
|
47
47
|
]
|
|
@@ -122,7 +122,7 @@ class CodexClientAdapter(MCPClientAdapter):
|
|
|
122
122
|
|
|
123
123
|
# If server has only remote endpoints and no packages, it's a remote-only server
|
|
124
124
|
if remotes and not packages:
|
|
125
|
-
print(f"
|
|
125
|
+
print(f"[!] Warning: MCP server '{server_url}' is a remote server (SSE type)")
|
|
126
126
|
print(" Codex CLI only supports local servers with command/args configuration")
|
|
127
127
|
print(" Remote servers are not supported by Codex CLI")
|
|
128
128
|
print(" Skipping installation for Codex CLI")
|
|
@@ -174,7 +174,7 @@ class CodexClientAdapter(MCPClientAdapter):
|
|
|
174
174
|
"id": server_info.get("id", "") # Add registry UUID for conflict detection
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
# Self-defined stdio deps carry raw command/args
|
|
177
|
+
# Self-defined stdio deps carry raw command/args -- use directly
|
|
178
178
|
raw = server_info.get("_raw_stdio")
|
|
179
179
|
if raw:
|
|
180
180
|
config["command"] = raw["command"]
|
|
@@ -328,7 +328,7 @@ class CodexClientAdapter(MCPClientAdapter):
|
|
|
328
328
|
# Check for CI/automated environment via APM_E2E_TESTS flag (more reliable than TTY detection)
|
|
329
329
|
if os.getenv('APM_E2E_TESTS') == '1':
|
|
330
330
|
skip_prompting = True
|
|
331
|
-
print(f"
|
|
331
|
+
print(f" APM_E2E_TESTS detected, will skip environment variable prompts")
|
|
332
332
|
|
|
333
333
|
# Also skip prompting if we're in a non-interactive environment (fallback)
|
|
334
334
|
is_interactive = sys.stdin.isatty() and sys.stdout.isatty()
|
|
@@ -166,7 +166,7 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
166
166
|
"id": server_info.get("id", "") # Add registry UUID for conflict detection
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
# Self-defined stdio deps carry raw command/args
|
|
169
|
+
# Self-defined stdio deps carry raw command/args -- use directly
|
|
170
170
|
raw = server_info.get("_raw_stdio")
|
|
171
171
|
if raw:
|
|
172
172
|
config["command"] = raw["command"]
|
|
@@ -331,7 +331,7 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
331
331
|
# Check for CI/automated environment via APM_E2E_TESTS flag (more reliable than TTY detection)
|
|
332
332
|
if os.getenv('APM_E2E_TESTS') == '1':
|
|
333
333
|
skip_prompting = True
|
|
334
|
-
print(f"
|
|
334
|
+
print(f" APM_E2E_TESTS detected, will skip environment variable prompts")
|
|
335
335
|
|
|
336
336
|
# Also skip prompting if we're in a non-interactive environment (fallback)
|
|
337
337
|
is_interactive = sys.stdin.isatty() and sys.stdout.isatty()
|
|
@@ -187,7 +187,7 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
187
187
|
server_config = {}
|
|
188
188
|
input_vars = []
|
|
189
189
|
|
|
190
|
-
# Self-defined stdio deps carry raw command/args
|
|
190
|
+
# Self-defined stdio deps carry raw command/args -- use directly
|
|
191
191
|
raw = server_info.get("_raw_stdio")
|
|
192
192
|
if raw:
|
|
193
193
|
server_config = {
|
|
@@ -12,7 +12,7 @@ def enrich_lockfile_for_pack(
|
|
|
12
12
|
) -> str:
|
|
13
13
|
"""Create an enriched copy of the lockfile YAML with a ``pack:`` section.
|
|
14
14
|
|
|
15
|
-
Does NOT mutate the original *lockfile* object
|
|
15
|
+
Does NOT mutate the original *lockfile* object -- serialises a copy and
|
|
16
16
|
prepends the pack metadata.
|
|
17
17
|
|
|
18
18
|
Args:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Bundle packer
|
|
1
|
+
"""Bundle packer -- creates self-contained APM bundles from the resolved dependency tree."""
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
4
|
import tarfile
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import List, Optional
|
|
8
8
|
|
|
9
|
-
from ..deps.lockfile import LockFile
|
|
9
|
+
from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
|
|
10
10
|
from ..models.apm_package import APMPackage
|
|
11
11
|
from ..core.target_detection import detect_target
|
|
12
12
|
from .lockfile_enrichment import enrich_lockfile_for_pack
|
|
@@ -47,10 +47,10 @@ def pack_bundle(
|
|
|
47
47
|
"""Create a self-contained bundle from installed APM dependencies.
|
|
48
48
|
|
|
49
49
|
Args:
|
|
50
|
-
project_root: Root of the project containing ``apm.lock`` and ``apm.yml``.
|
|
50
|
+
project_root: Root of the project containing ``apm.lock.yaml`` and ``apm.yml``.
|
|
51
51
|
output_dir: Directory where the bundle will be created.
|
|
52
|
-
fmt: Bundle format
|
|
53
|
-
target: Target filter
|
|
52
|
+
fmt: Bundle format -- ``"apm"`` (default) or ``"plugin"``.
|
|
53
|
+
target: Target filter -- ``"vscode"``, ``"claude"``, ``"all"``, or *None*
|
|
54
54
|
(auto-detect from apm.yml / project structure).
|
|
55
55
|
archive: If *True*, produce a ``.tar.gz`` and remove the directory.
|
|
56
56
|
dry_run: If *True*, resolve the file list but write nothing to disk.
|
|
@@ -59,15 +59,16 @@ def pack_bundle(
|
|
|
59
59
|
:class:`PackResult` describing what was (or would be) produced.
|
|
60
60
|
|
|
61
61
|
Raises:
|
|
62
|
-
FileNotFoundError: If ``apm.lock`` is missing.
|
|
62
|
+
FileNotFoundError: If ``apm.lock.yaml`` is missing.
|
|
63
63
|
ValueError: If deployed files referenced in the lockfile are missing on disk.
|
|
64
64
|
"""
|
|
65
|
-
# 1. Read lockfile
|
|
66
|
-
|
|
65
|
+
# 1. Read lockfile (migrate legacy apm.lock → apm.lock.yaml if needed)
|
|
66
|
+
migrate_lockfile_if_needed(project_root)
|
|
67
|
+
lockfile_path = get_lockfile_path(project_root)
|
|
67
68
|
lockfile = LockFile.read(lockfile_path)
|
|
68
69
|
if lockfile is None:
|
|
69
70
|
raise FileNotFoundError(
|
|
70
|
-
"apm.lock not found
|
|
71
|
+
"apm.lock.yaml not found -- run 'apm install' first to resolve dependencies."
|
|
71
72
|
)
|
|
72
73
|
|
|
73
74
|
# 2. Read apm.yml for name / version / config target
|
|
@@ -77,7 +78,19 @@ def pack_bundle(
|
|
|
77
78
|
pkg_name = package.name
|
|
78
79
|
pkg_version = package.version or "0.0.0"
|
|
79
80
|
config_target = package.target
|
|
80
|
-
|
|
81
|
+
|
|
82
|
+
# Guard: reject local-path dependencies (non-portable)
|
|
83
|
+
for dep_ref in package.get_apm_dependencies():
|
|
84
|
+
if dep_ref.is_local:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Cannot pack — apm.yml contains local path dependency: "
|
|
87
|
+
f"{dep_ref.local_path}\n"
|
|
88
|
+
f"Local dependencies are for development only. Replace them with "
|
|
89
|
+
f"remote references (e.g., 'owner/repo') before packing."
|
|
90
|
+
)
|
|
91
|
+
except ValueError:
|
|
92
|
+
raise
|
|
93
|
+
except FileNotFoundError:
|
|
81
94
|
pkg_name = project_root.resolve().name
|
|
82
95
|
pkg_version = "0.0.0"
|
|
83
96
|
config_target = None
|
|
@@ -88,7 +101,7 @@ def pack_bundle(
|
|
|
88
101
|
explicit_target=target,
|
|
89
102
|
config_target=config_target,
|
|
90
103
|
)
|
|
91
|
-
# For packing purposes, "minimal" means nothing to pack
|
|
104
|
+
# For packing purposes, "minimal" means nothing to pack -- treat as "all"
|
|
92
105
|
if effective_target == "minimal":
|
|
93
106
|
effective_target = "all"
|
|
94
107
|
|
|
@@ -126,7 +139,7 @@ def pack_bundle(
|
|
|
126
139
|
missing.append(rel_path)
|
|
127
140
|
if missing:
|
|
128
141
|
raise ValueError(
|
|
129
|
-
f"The following deployed files are missing on disk
|
|
142
|
+
f"The following deployed files are missing on disk -- "
|
|
130
143
|
f"run 'apm install' to restore them:\n"
|
|
131
144
|
+ "\n".join(f" - {m}" for m in missing)
|
|
132
145
|
)
|
|
@@ -156,7 +169,7 @@ def pack_bundle(
|
|
|
156
169
|
|
|
157
170
|
# 8. Enrich lockfile copy and write to bundle
|
|
158
171
|
enriched_yaml = enrich_lockfile_for_pack(lockfile, fmt, effective_target)
|
|
159
|
-
(bundle_dir / "apm.lock").write_text(enriched_yaml, encoding="utf-8")
|
|
172
|
+
(bundle_dir / "apm.lock.yaml").write_text(enriched_yaml, encoding="utf-8")
|
|
160
173
|
|
|
161
174
|
result = PackResult(
|
|
162
175
|
bundle_path=bundle_dir,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Bundle unpacker
|
|
1
|
+
"""Bundle unpacker -- extracts and verifies APM bundles."""
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
4
|
import sys
|
|
@@ -6,9 +6,9 @@ import tarfile
|
|
|
6
6
|
import tempfile
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import List
|
|
9
|
+
from typing import Dict, List
|
|
10
10
|
|
|
11
|
-
from ..deps.lockfile import LockFile
|
|
11
|
+
from ..deps.lockfile import LockFile, LOCKFILE_NAME, LEGACY_LOCKFILE_NAME
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
@@ -18,6 +18,8 @@ class UnpackResult:
|
|
|
18
18
|
extracted_dir: Path
|
|
19
19
|
files: List[str] = field(default_factory=list)
|
|
20
20
|
verified: bool = False
|
|
21
|
+
dependency_files: Dict[str, List[str]] = field(default_factory=dict)
|
|
22
|
+
skipped_count: int = 0
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def unpack_bundle(
|
|
@@ -42,7 +44,7 @@ def unpack_bundle(
|
|
|
42
44
|
:class:`UnpackResult` describing what was (or would be) extracted.
|
|
43
45
|
|
|
44
46
|
Raises:
|
|
45
|
-
FileNotFoundError: If the bundle's ``apm.lock`` is missing.
|
|
47
|
+
FileNotFoundError: If the bundle's ``apm.lock.yaml`` is missing.
|
|
46
48
|
ValueError: If verification finds files listed in the lockfile but
|
|
47
49
|
absent from the bundle.
|
|
48
50
|
"""
|
|
@@ -63,7 +65,7 @@ def unpack_bundle(
|
|
|
63
65
|
if sys.version_info >= (3, 12):
|
|
64
66
|
tar.extractall(temp_dir, filter="data")
|
|
65
67
|
else:
|
|
66
|
-
tar.extractall(temp_dir) # noqa: S202
|
|
68
|
+
tar.extractall(temp_dir) # noqa: S202 -- manual checks above
|
|
67
69
|
except Exception:
|
|
68
70
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
69
71
|
raise
|
|
@@ -81,30 +83,37 @@ def unpack_bundle(
|
|
|
81
83
|
raise FileNotFoundError(f"Bundle not found or unsupported format: {bundle_path}")
|
|
82
84
|
|
|
83
85
|
try:
|
|
84
|
-
# 2. Read apm.lock from bundle
|
|
85
|
-
lockfile_path = source_dir /
|
|
86
|
+
# 2. Read apm.lock.yaml (or legacy apm.lock) from bundle
|
|
87
|
+
lockfile_path = source_dir / LOCKFILE_NAME
|
|
88
|
+
if not lockfile_path.exists():
|
|
89
|
+
# Backward compat: older bundles used "apm.lock"
|
|
90
|
+
legacy_lockfile_path = source_dir / LEGACY_LOCKFILE_NAME
|
|
91
|
+
if legacy_lockfile_path.exists():
|
|
92
|
+
lockfile_path = legacy_lockfile_path
|
|
86
93
|
lockfile = LockFile.read(lockfile_path)
|
|
87
94
|
if lockfile is None:
|
|
88
95
|
if not lockfile_path.exists():
|
|
89
96
|
raise FileNotFoundError(
|
|
90
|
-
"
|
|
97
|
+
f"{lockfile_path.name} not found in the bundle -- the bundle may be incomplete."
|
|
91
98
|
)
|
|
92
99
|
raise FileNotFoundError(
|
|
93
|
-
"
|
|
100
|
+
f"{lockfile_path.name} in the bundle could not be parsed -- the bundle may be corrupt."
|
|
94
101
|
)
|
|
95
102
|
|
|
96
|
-
# Collect
|
|
97
|
-
|
|
98
|
-
for dep in lockfile.get_all_dependencies():
|
|
99
|
-
all_deployed.extend(dep.deployed_files)
|
|
100
|
-
|
|
101
|
-
# Deduplicate
|
|
103
|
+
# Collect deployed_files per dependency and deduplicated global list
|
|
104
|
+
dep_file_map: Dict[str, List[str]] = {}
|
|
102
105
|
seen: set[str] = set()
|
|
103
106
|
unique_files: list[str] = []
|
|
104
|
-
for
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
for dep in lockfile.get_all_dependencies():
|
|
108
|
+
dep_key = dep.get_unique_key()
|
|
109
|
+
dep_files: list[str] = []
|
|
110
|
+
for f in dep.deployed_files:
|
|
111
|
+
dep_files.append(f)
|
|
112
|
+
if f not in seen:
|
|
113
|
+
seen.add(f)
|
|
114
|
+
unique_files.append(f)
|
|
115
|
+
if dep_files:
|
|
116
|
+
dep_file_map[dep_key] = dep_files
|
|
108
117
|
|
|
109
118
|
# 3. Verify completeness
|
|
110
119
|
verified = True
|
|
@@ -114,7 +123,7 @@ def unpack_bundle(
|
|
|
114
123
|
]
|
|
115
124
|
if missing:
|
|
116
125
|
raise ValueError(
|
|
117
|
-
"Bundle verification failed
|
|
126
|
+
"Bundle verification failed -- the following deployed files "
|
|
118
127
|
"are missing from the bundle:\n"
|
|
119
128
|
+ "\n".join(f" - {m}" for m in missing)
|
|
120
129
|
)
|
|
@@ -128,15 +137,17 @@ def unpack_bundle(
|
|
|
128
137
|
extracted_dir=bundle_path,
|
|
129
138
|
files=unique_files,
|
|
130
139
|
verified=verified,
|
|
140
|
+
dependency_files=dep_file_map,
|
|
131
141
|
)
|
|
132
142
|
|
|
133
143
|
# 4. Copy target files to output_dir (additive, no deletes)
|
|
134
144
|
output_dir = Path(output_dir)
|
|
135
145
|
output_dir_resolved = output_dir.resolve()
|
|
146
|
+
skipped = 0
|
|
136
147
|
for rel_path in unique_files:
|
|
137
148
|
# Guard against absolute paths or path-traversal entries in deployed_files
|
|
138
149
|
p = Path(rel_path)
|
|
139
|
-
if p.is_absolute() or ".." in p.parts:
|
|
150
|
+
if p.is_absolute() or rel_path.startswith("/") or ".." in p.parts:
|
|
140
151
|
raise ValueError(
|
|
141
152
|
f"Refusing to unpack unsafe path from bundle lockfile: {rel_path!r}"
|
|
142
153
|
)
|
|
@@ -147,6 +158,7 @@ def unpack_bundle(
|
|
|
147
158
|
)
|
|
148
159
|
src = source_dir / rel_path
|
|
149
160
|
if not src.exists():
|
|
161
|
+
skipped += 1
|
|
150
162
|
continue # skip_verify may allow missing files
|
|
151
163
|
if src.is_dir():
|
|
152
164
|
shutil.copytree(src, dest, dirs_exist_ok=True)
|
|
@@ -158,6 +170,8 @@ def unpack_bundle(
|
|
|
158
170
|
extracted_dir=bundle_path,
|
|
159
171
|
files=unique_files,
|
|
160
172
|
verified=verified,
|
|
173
|
+
dependency_files=dep_file_map,
|
|
174
|
+
skipped_count=skipped,
|
|
161
175
|
)
|
|
162
176
|
finally:
|
|
163
177
|
# Clean up temp dir if we created one
|