apm-cli 0.9.0__tar.gz → 0.9.2__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.9.0/src/apm_cli.egg-info → apm_cli-0.9.2}/PKG-INFO +31 -6
- {apm_cli-0.9.0 → apm_cli-0.9.2}/README.md +30 -5
- {apm_cli-0.9.0 → apm_cli-0.9.2}/pyproject.toml +1 -1
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/cli.py +2 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/audit.py +74 -29
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/install.py +200 -2
- apm_cli-0.9.2/src/apm_cli/commands/policy.py +378 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/auth.py +354 -34
- apm_cli-0.9.2/src/apm_cli/core/azure_cli.py +311 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/command_logger.py +260 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/token_manager.py +5 -1
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/github_downloader.py +176 -12
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/context.py +8 -0
- apm_cli-0.9.2/src/apm_cli/install/errors.py +50 -0
- apm_cli-0.9.2/src/apm_cli/install/phases/policy_gate.py +196 -0
- apm_cli-0.9.2/src/apm_cli/install/phases/policy_target_check.py +114 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/pipeline.py +59 -1
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/request.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/service.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/validation.py +52 -3
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/__init__.py +4 -2
- apm_cli-0.9.2/src/apm_cli/policy/discovery.py +1377 -0
- apm_cli-0.9.2/src/apm_cli/policy/install_preflight.py +219 -0
- apm_cli-0.9.2/src/apm_cli/policy/outcome_routing.py +190 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/parser.py +15 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/policy_checks.py +160 -32
- apm_cli-0.9.2/src/apm_cli/policy/project_config.py +230 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/schema.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/diagnostics.py +59 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/github_host.py +49 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2/src/apm_cli.egg-info}/PKG-INFO +31 -6
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/SOURCES.txt +8 -0
- apm_cli-0.9.0/src/apm_cli/policy/discovery.py +0 -427
- {apm_cli-0.9.0 → apm_cli-0.9.2}/AUTHORS +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/LICENSE +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/setup.cfg +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/copilot.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/packer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/_helpers.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/_utils.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/marketplace.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/mcp.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/outdated.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/pack.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/engine.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/update.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/agents_compiler.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/config.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/target_detection.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/mcp_registry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/mcp_warnings.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/cleanup.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/integrate.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/sources.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/instruction_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/mcp_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/skill_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/apm_package.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/reference.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/validation.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/ci_checks.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/version.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_apm_package_models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_console.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_token_manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apm-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: MCP configuration tool
|
|
5
5
|
Author-email: Daniel Meppiel <user@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -70,6 +70,11 @@ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
|
|
|
70
70
|
|
|
71
71
|
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
|
|
72
72
|
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
> **Portable by manifest. Secure by default. Governed by policy.**
|
|
76
|
+
> One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
|
|
77
|
+
|
|
73
78
|
## Why APM
|
|
74
79
|
|
|
75
80
|
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.
|
|
@@ -101,17 +106,37 @@ git clone <org/repo> && cd <repo>
|
|
|
101
106
|
apm install # every agent is configured
|
|
102
107
|
```
|
|
103
108
|
|
|
104
|
-
##
|
|
109
|
+
## The three promises
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
### 1. Portable by manifest
|
|
112
|
+
|
|
113
|
+
One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
|
|
114
|
+
|
|
115
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
|
|
107
116
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
108
117
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
109
|
-
- **[
|
|
110
|
-
- **[
|
|
111
|
-
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
|
|
118
|
+
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
119
|
+
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
|
|
112
120
|
- **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
|
|
113
121
|
- **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
|
|
114
122
|
|
|
123
|
+
### 2. Secure by default
|
|
124
|
+
|
|
125
|
+
Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
|
|
126
|
+
|
|
127
|
+
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
|
|
128
|
+
- **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
|
|
129
|
+
- **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
|
|
130
|
+
|
|
131
|
+
### 3. Governed by policy
|
|
132
|
+
|
|
133
|
+
`apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
|
|
134
|
+
|
|
135
|
+
- **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
|
|
136
|
+
- **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
|
|
137
|
+
- **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
|
|
138
|
+
- **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
|
|
139
|
+
|
|
115
140
|
## Get Started
|
|
116
141
|
|
|
117
142
|
#### Linux / macOS
|
|
@@ -8,6 +8,11 @@ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
|
|
|
8
8
|
|
|
9
9
|
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
|
|
10
10
|
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
> **Portable by manifest. Secure by default. Governed by policy.**
|
|
14
|
+
> One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
|
|
15
|
+
|
|
11
16
|
## Why APM
|
|
12
17
|
|
|
13
18
|
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.
|
|
@@ -39,17 +44,37 @@ git clone <org/repo> && cd <repo>
|
|
|
39
44
|
apm install # every agent is configured
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
##
|
|
47
|
+
## The three promises
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
### 1. Portable by manifest
|
|
50
|
+
|
|
51
|
+
One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
|
|
52
|
+
|
|
53
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
|
|
45
54
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
46
55
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
47
|
-
- **[
|
|
48
|
-
- **[
|
|
49
|
-
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
|
|
56
|
+
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
57
|
+
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
|
|
50
58
|
- **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
|
|
51
59
|
- **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
|
|
52
60
|
|
|
61
|
+
### 2. Secure by default
|
|
62
|
+
|
|
63
|
+
Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
|
|
64
|
+
|
|
65
|
+
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
|
|
66
|
+
- **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
|
|
67
|
+
- **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
|
|
68
|
+
|
|
69
|
+
### 3. Governed by policy
|
|
70
|
+
|
|
71
|
+
`apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
|
|
72
|
+
|
|
73
|
+
- **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
|
|
74
|
+
- **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
|
|
75
|
+
- **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
|
|
76
|
+
- **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
|
|
77
|
+
|
|
53
78
|
## Get Started
|
|
54
79
|
|
|
55
80
|
#### Linux / macOS
|
|
@@ -28,6 +28,7 @@ from apm_cli.commands.marketplace import marketplace, search as marketplace_sear
|
|
|
28
28
|
from apm_cli.commands.mcp import mcp
|
|
29
29
|
from apm_cli.commands.outdated import outdated as outdated_cmd
|
|
30
30
|
from apm_cli.commands.pack import pack_cmd, unpack_cmd
|
|
31
|
+
from apm_cli.commands.policy import policy
|
|
31
32
|
from apm_cli.commands.prune import prune
|
|
32
33
|
from apm_cli.commands.run import preview, run
|
|
33
34
|
from apm_cli.commands.runtime import runtime
|
|
@@ -82,6 +83,7 @@ cli.add_command(list_cmd, name="list")
|
|
|
82
83
|
cli.add_command(config)
|
|
83
84
|
cli.add_command(runtime)
|
|
84
85
|
cli.add_command(mcp)
|
|
86
|
+
cli.add_command(policy)
|
|
85
87
|
cli.add_command(outdated_cmd, name="outdated")
|
|
86
88
|
cli.add_command(marketplace)
|
|
87
89
|
cli.add_command(marketplace_search, name="search")
|
|
@@ -447,6 +447,15 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
|
|
|
447
447
|
is_flag=True,
|
|
448
448
|
help="Force fresh policy fetch (skip cache).",
|
|
449
449
|
)
|
|
450
|
+
@click.option(
|
|
451
|
+
"--no-policy",
|
|
452
|
+
"no_policy",
|
|
453
|
+
is_flag=True,
|
|
454
|
+
help=(
|
|
455
|
+
"Skip org policy discovery and enforcement. "
|
|
456
|
+
"Overridden when --policy is passed explicitly."
|
|
457
|
+
),
|
|
458
|
+
)
|
|
450
459
|
@click.option(
|
|
451
460
|
"--no-fail-fast",
|
|
452
461
|
"no_fail_fast",
|
|
@@ -454,7 +463,7 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
|
|
|
454
463
|
help="Run all checks even after a failure (default: stop at first failure).",
|
|
455
464
|
)
|
|
456
465
|
@click.pass_context
|
|
457
|
-
def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_fail_fast):
|
|
466
|
+
def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_policy, no_fail_fast):
|
|
458
467
|
"""Scan deployed prompt files for hidden Unicode characters.
|
|
459
468
|
|
|
460
469
|
Detects invisible characters that could embed hidden instructions in
|
|
@@ -512,45 +521,81 @@ def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, outpu
|
|
|
512
521
|
# Always run baseline checks
|
|
513
522
|
ci_result = run_baseline_checks(project_root, fail_fast=fail_fast)
|
|
514
523
|
|
|
515
|
-
#
|
|
516
|
-
|
|
517
|
-
|
|
524
|
+
# Resolve policy source: explicit --policy wins; otherwise mirror
|
|
525
|
+
# install's auto-discovery (closes #827) so CI catches sideloaded
|
|
526
|
+
# files via unmanaged-files checks. --no-policy skips discovery.
|
|
527
|
+
from ..policy.discovery import discover_policy, discover_policy_with_chain
|
|
528
|
+
from ..policy.project_config import (
|
|
529
|
+
read_project_fetch_failure_default,
|
|
530
|
+
)
|
|
518
531
|
|
|
532
|
+
fetch_result = None
|
|
533
|
+
if policy_source and (not fail_fast or ci_result.passed):
|
|
519
534
|
fetch_result = discover_policy(
|
|
520
535
|
project_root,
|
|
521
536
|
policy_override=policy_source,
|
|
522
537
|
no_cache=no_cache,
|
|
523
538
|
)
|
|
539
|
+
elif (
|
|
540
|
+
not policy_source
|
|
541
|
+
and not no_policy
|
|
542
|
+
and (not fail_fast or ci_result.passed)
|
|
543
|
+
):
|
|
544
|
+
# Auto-discovery (mirror install path)
|
|
545
|
+
fetch_result = discover_policy_with_chain(project_root)
|
|
546
|
+
# Treat outcomes that mean "no policy to enforce" as a no-op.
|
|
547
|
+
if fetch_result.outcome in ("absent", "no_git_remote", "empty", "disabled"):
|
|
548
|
+
fetch_result = None
|
|
549
|
+
|
|
550
|
+
if fetch_result is not None:
|
|
551
|
+
# Honor project-side fetch_failure_default when the org policy
|
|
552
|
+
# could not be fetched / parsed (closes #829). Default "warn"
|
|
553
|
+
# downgrades the previous unconditional sys.exit(1) into a log.
|
|
554
|
+
if fetch_result.error or (
|
|
555
|
+
fetch_result.outcome
|
|
556
|
+
in ("malformed", "cache_miss_fetch_fail", "garbage_response")
|
|
557
|
+
):
|
|
558
|
+
project_default = read_project_fetch_failure_default(project_root)
|
|
559
|
+
err_text = fetch_result.error or fetch_result.fetch_error or fetch_result.outcome
|
|
560
|
+
if project_default == "block":
|
|
561
|
+
logger.error(
|
|
562
|
+
f"Policy fetch failed: {err_text} "
|
|
563
|
+
"(policy.fetch_failure_default=block)"
|
|
564
|
+
)
|
|
565
|
+
sys.exit(1)
|
|
566
|
+
else:
|
|
567
|
+
logger.warning(
|
|
568
|
+
f"Policy fetch failed: {err_text}; "
|
|
569
|
+
"proceeding without policy checks "
|
|
570
|
+
"(set policy.fetch_failure_default=block in apm.yml to fail closed)"
|
|
571
|
+
)
|
|
572
|
+
fetch_result = None
|
|
524
573
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
sys.exit(1)
|
|
574
|
+
if fetch_result is not None and fetch_result.found:
|
|
575
|
+
policy_obj = fetch_result.policy
|
|
528
576
|
|
|
529
|
-
|
|
530
|
-
|
|
577
|
+
# Respect enforcement level
|
|
578
|
+
if policy_obj.enforcement == "off":
|
|
579
|
+
pass # Policy checks disabled
|
|
580
|
+
else:
|
|
581
|
+
from ..policy.models import CheckResult
|
|
531
582
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
583
|
+
policy_result = run_policy_checks(
|
|
584
|
+
project_root, policy_obj, fail_fast=fail_fast
|
|
585
|
+
)
|
|
586
|
+
if policy_obj.enforcement == "block":
|
|
587
|
+
ci_result.checks.extend(policy_result.checks)
|
|
535
588
|
else:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# enforcement == "warn": include results but don't fail
|
|
545
|
-
for check in policy_result.checks:
|
|
546
|
-
ci_result.checks.append(
|
|
547
|
-
CheckResult(
|
|
548
|
-
name=check.name,
|
|
549
|
-
passed=True, # downgrade to pass
|
|
550
|
-
message=check.message + (" (enforcement: warn)" if not check.passed else ""),
|
|
551
|
-
details=check.details,
|
|
552
|
-
)
|
|
589
|
+
# enforcement == "warn": include results but don't fail
|
|
590
|
+
for check in policy_result.checks:
|
|
591
|
+
ci_result.checks.append(
|
|
592
|
+
CheckResult(
|
|
593
|
+
name=check.name,
|
|
594
|
+
passed=True, # downgrade to pass
|
|
595
|
+
message=check.message + (" (enforcement: warn)" if not check.passed else ""),
|
|
596
|
+
details=check.details,
|
|
553
597
|
)
|
|
598
|
+
)
|
|
554
599
|
|
|
555
600
|
# Resolve effective format
|
|
556
601
|
effective_format = output_format
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""APM install command and dependency installation engine."""
|
|
2
2
|
|
|
3
3
|
import builtins
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import List, Optional
|
|
@@ -59,6 +60,7 @@ from apm_cli.install.phases.local_content import (
|
|
|
59
60
|
_has_local_apm_content,
|
|
60
61
|
_project_has_root_primitives,
|
|
61
62
|
)
|
|
63
|
+
from apm_cli.install.errors import PolicyViolationError
|
|
62
64
|
from apm_cli.install.insecure_policy import (
|
|
63
65
|
_InsecureDependencyInfo,
|
|
64
66
|
_allow_insecure_host_callback,
|
|
@@ -86,6 +88,64 @@ from ._helpers import (
|
|
|
86
88
|
_update_gitignore_for_apm_modules,
|
|
87
89
|
)
|
|
88
90
|
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Manifest snapshot + rollback (W2-pkg-rollback, #827)
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# When the user runs ``apm install <pkg>``, ``_validate_and_add_packages_to_apm_yml``
|
|
96
|
+
# mutates ``apm.yml`` BEFORE the install pipeline runs. If the pipeline fails
|
|
97
|
+
# (policy block, download error, etc.) the failed package would stay in
|
|
98
|
+
# ``apm.yml`` forever. These helpers snapshot the raw bytes before mutation
|
|
99
|
+
# and atomically restore on failure.
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def _restore_manifest_from_snapshot(
|
|
103
|
+
manifest_path: "Path",
|
|
104
|
+
snapshot: bytes,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Atomically restore ``apm.yml`` from a raw-bytes snapshot.
|
|
107
|
+
|
|
108
|
+
Uses temp-file + ``os.replace`` to avoid torn writes, mirroring the
|
|
109
|
+
W1 cache atomic-write pattern (``discovery.py``).
|
|
110
|
+
"""
|
|
111
|
+
import os
|
|
112
|
+
import tempfile
|
|
113
|
+
|
|
114
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
115
|
+
prefix="apm-restore-", dir=str(manifest_path.parent),
|
|
116
|
+
)
|
|
117
|
+
try:
|
|
118
|
+
with os.fdopen(fd, "wb") as fh:
|
|
119
|
+
fh.write(snapshot)
|
|
120
|
+
os.replace(tmp_name, str(manifest_path))
|
|
121
|
+
except Exception:
|
|
122
|
+
try:
|
|
123
|
+
os.unlink(tmp_name)
|
|
124
|
+
except OSError:
|
|
125
|
+
pass
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _maybe_rollback_manifest(
|
|
130
|
+
manifest_path: "Path",
|
|
131
|
+
snapshot: "bytes | None",
|
|
132
|
+
logger: "InstallLogger",
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Restore ``apm.yml`` from *snapshot* if one was captured, then log.
|
|
135
|
+
|
|
136
|
+
No-op when *snapshot* is ``None`` (i.e. the command was not
|
|
137
|
+
``apm install <pkg>`` or the manifest did not exist before mutation).
|
|
138
|
+
"""
|
|
139
|
+
if snapshot is None:
|
|
140
|
+
return
|
|
141
|
+
try:
|
|
142
|
+
_restore_manifest_from_snapshot(manifest_path, snapshot)
|
|
143
|
+
logger.progress("apm.yml restored to its previous state.")
|
|
144
|
+
except Exception:
|
|
145
|
+
# Best-effort: if the restore itself fails, warn but don't mask
|
|
146
|
+
# the original exception that triggered the rollback.
|
|
147
|
+
logger.warning("Failed to restore apm.yml to its previous state.")
|
|
148
|
+
|
|
89
149
|
# CRITICAL: Shadow Python builtins that share names with Click commands
|
|
90
150
|
set = builtins.set
|
|
91
151
|
list = builtins.list
|
|
@@ -990,8 +1050,15 @@ def _run_mcp_install(
|
|
|
990
1050
|
"or a stdio command (self-defined entries)."
|
|
991
1051
|
),
|
|
992
1052
|
)
|
|
1053
|
+
@click.option(
|
|
1054
|
+
"--no-policy",
|
|
1055
|
+
"no_policy",
|
|
1056
|
+
is_flag=True,
|
|
1057
|
+
default=False,
|
|
1058
|
+
help="Skip org policy enforcement for this invocation. Loudly logged. Does NOT bypass apm audit --ci.",
|
|
1059
|
+
)
|
|
993
1060
|
@click.pass_context
|
|
994
|
-
def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url):
|
|
1061
|
+
def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url, no_policy):
|
|
995
1062
|
"""Install APM and MCP dependencies from apm.yml (like npm install).
|
|
996
1063
|
|
|
997
1064
|
Detects AI runtimes from your apm.yml scripts and installs MCP servers for
|
|
@@ -1021,12 +1088,28 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1021
1088
|
apm install --mcp api --url https://example.com/mcp # remote http/sse
|
|
1022
1089
|
apm install --mcp fetch -- npx -y @modelcontextprotocol/server-fetch # stdio (post-- argv)
|
|
1023
1090
|
"""
|
|
1091
|
+
# C1 #856: defaults BEFORE try so the finally clause never sees an
|
|
1092
|
+
# UnboundLocalError if InstallLogger(...) raises during construction.
|
|
1093
|
+
_apm_verbose_prev = os.environ.get("APM_VERBOSE")
|
|
1024
1094
|
try:
|
|
1025
1095
|
# Create structured logger for install output early so exception
|
|
1026
1096
|
# handlers can always reference it (avoids UnboundLocalError if
|
|
1027
1097
|
# scope initialisation below throws).
|
|
1028
1098
|
is_partial = bool(packages)
|
|
1029
1099
|
logger = InstallLogger(verbose=verbose, dry_run=dry_run, partial=is_partial)
|
|
1100
|
+
# HACK(#852): surface --verbose to deeper auth layers via env var until
|
|
1101
|
+
# AuthResolver gains a first-class verbose channel. Restored in finally
|
|
1102
|
+
# below to keep the mutation scoped to this command invocation.
|
|
1103
|
+
if verbose:
|
|
1104
|
+
os.environ["APM_VERBOSE"] = "1"
|
|
1105
|
+
|
|
1106
|
+
# W2-pkg-rollback (#827): snapshot bytes captured BEFORE
|
|
1107
|
+
# _validate_and_add_packages_to_apm_yml mutates apm.yml.
|
|
1108
|
+
# Initialised to None here so exception handlers always have it.
|
|
1109
|
+
_manifest_snapshot: "bytes | None" = None
|
|
1110
|
+
# manifest_path is set later (scope-dependent); keep a stable ref
|
|
1111
|
+
# so exception handlers can use it without NameError.
|
|
1112
|
+
_snapshot_manifest_path: "Path | None" = None
|
|
1030
1113
|
|
|
1031
1114
|
# ----------------------------------------------------------------
|
|
1032
1115
|
# --mcp branch (W3): when --mcp is set, route to the dedicated
|
|
@@ -1088,6 +1171,43 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1088
1171
|
mcp_scope = InstallScope.PROJECT
|
|
1089
1172
|
mcp_manifest_path = get_manifest_path(mcp_scope)
|
|
1090
1173
|
mcp_apm_dir = get_apm_dir(mcp_scope)
|
|
1174
|
+
# -- W2-mcp-preflight: policy enforcement before MCP install --
|
|
1175
|
+
# Build a lightweight MCPDependency for policy evaluation.
|
|
1176
|
+
# This mirrors _build_mcp_entry routing but we only need the
|
|
1177
|
+
# fields that policy checks inspect (name, transport, registry).
|
|
1178
|
+
from ..models.dependency.mcp import MCPDependency as _MCPDep
|
|
1179
|
+
from ..policy.install_preflight import (
|
|
1180
|
+
PolicyBlockError,
|
|
1181
|
+
run_policy_preflight,
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
_is_self_defined = bool(url or command_argv)
|
|
1185
|
+
_preflight_transport = transport
|
|
1186
|
+
if _preflight_transport is None:
|
|
1187
|
+
if command_argv:
|
|
1188
|
+
_preflight_transport = "stdio"
|
|
1189
|
+
elif url:
|
|
1190
|
+
_preflight_transport = "http"
|
|
1191
|
+
_preflight_dep = _MCPDep(
|
|
1192
|
+
name=mcp_name,
|
|
1193
|
+
transport=_preflight_transport,
|
|
1194
|
+
registry=False if _is_self_defined else None,
|
|
1195
|
+
url=url,
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
try:
|
|
1199
|
+
_pf_result, _pf_active = run_policy_preflight(
|
|
1200
|
+
project_root=Path.cwd(),
|
|
1201
|
+
mcp_deps=[_preflight_dep],
|
|
1202
|
+
no_policy=no_policy,
|
|
1203
|
+
logger=logger,
|
|
1204
|
+
dry_run=dry_run,
|
|
1205
|
+
)
|
|
1206
|
+
except PolicyBlockError:
|
|
1207
|
+
# Diagnostics already emitted by the helper + logger.
|
|
1208
|
+
logger.render_summary()
|
|
1209
|
+
sys.exit(1)
|
|
1210
|
+
|
|
1091
1211
|
if dry_run:
|
|
1092
1212
|
# C1: validate eagerly so dry-run rejects what real install would.
|
|
1093
1213
|
_validate_mcp_dry_run_entry(
|
|
@@ -1162,6 +1282,10 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1162
1282
|
# Create shared auth resolver for all downloads in this CLI invocation
|
|
1163
1283
|
# to ensure credentials are cached and reused (prevents duplicate auth popups)
|
|
1164
1284
|
auth_resolver = AuthResolver()
|
|
1285
|
+
# F2/F3 #856: thread the InstallLogger into AuthResolver so the verbose
|
|
1286
|
+
# auth-source line and the deferred stale-PAT [!] warning route through
|
|
1287
|
+
# CommandLogger / DiagnosticCollector instead of stderr/inline writes.
|
|
1288
|
+
auth_resolver.set_logger(logger)
|
|
1165
1289
|
|
|
1166
1290
|
# Check if apm.yml exists
|
|
1167
1291
|
apm_yml_exists = manifest_path.exists()
|
|
@@ -1186,6 +1310,15 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1186
1310
|
|
|
1187
1311
|
# If packages are specified, validate and add them to apm.yml first
|
|
1188
1312
|
if packages:
|
|
1313
|
+
# ── W2-pkg-rollback (#827): snapshot raw bytes BEFORE mutation ──
|
|
1314
|
+
# _validate_and_add_packages_to_apm_yml does a YAML round-trip
|
|
1315
|
+
# (load + dump) which may alter whitespace, key ordering, or
|
|
1316
|
+
# trailing newlines. We snapshot the raw bytes so rollback is
|
|
1317
|
+
# byte-exact -- no YAML drift.
|
|
1318
|
+
if manifest_path.exists():
|
|
1319
|
+
_manifest_snapshot = manifest_path.read_bytes()
|
|
1320
|
+
_snapshot_manifest_path = manifest_path
|
|
1321
|
+
|
|
1189
1322
|
validated_packages, outcome = _validate_and_add_packages_to_apm_yml(
|
|
1190
1323
|
packages, dry_run, dev=dev, logger=logger,
|
|
1191
1324
|
manifest_path=manifest_path, auth_resolver=auth_resolver,
|
|
@@ -1245,6 +1378,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1245
1378
|
|
|
1246
1379
|
# Show what will be installed if dry run
|
|
1247
1380
|
if dry_run:
|
|
1381
|
+
# -- W2-dry-run (#827): policy preflight in preview mode --
|
|
1382
|
+
# Runs discovery + checks against direct manifest deps (not
|
|
1383
|
+
# resolved/transitive -- dry-run does not run the resolver).
|
|
1384
|
+
# Block-severity violations render as "Would be blocked by
|
|
1385
|
+
# policy" without raising. Documented limitation: transitive
|
|
1386
|
+
# deps are NOT evaluated since the resolver does not run.
|
|
1387
|
+
from apm_cli.policy.install_preflight import run_policy_preflight as _dr_preflight
|
|
1388
|
+
|
|
1389
|
+
_dr_apm_deps = builtins.list(apm_deps) + builtins.list(dev_apm_deps)
|
|
1390
|
+
_dr_preflight(
|
|
1391
|
+
project_root=project_root,
|
|
1392
|
+
apm_deps=_dr_apm_deps,
|
|
1393
|
+
mcp_deps=mcp_deps if should_install_mcp else None,
|
|
1394
|
+
no_policy=no_policy,
|
|
1395
|
+
logger=logger,
|
|
1396
|
+
dry_run=True,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1248
1399
|
from apm_cli.install.presentation.dry_run import render_and_exit
|
|
1249
1400
|
|
|
1250
1401
|
render_and_exit(
|
|
@@ -1311,15 +1462,20 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1311
1462
|
),
|
|
1312
1463
|
protocol_pref=protocol_pref,
|
|
1313
1464
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1465
|
+
no_policy=no_policy,
|
|
1314
1466
|
)
|
|
1315
1467
|
apm_count = install_result.installed_count
|
|
1316
1468
|
prompt_count = install_result.prompts_integrated
|
|
1317
1469
|
agent_count = install_result.agents_integrated
|
|
1318
1470
|
apm_diagnostics = install_result.diagnostics
|
|
1319
1471
|
except InsecureDependencyPolicyError:
|
|
1472
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1320
1473
|
sys.exit(1)
|
|
1321
1474
|
except Exception as e:
|
|
1322
|
-
|
|
1475
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1476
|
+
# #832: surface PolicyViolationError verbatim (no double-nesting).
|
|
1477
|
+
msg = str(e) if isinstance(e, PolicyViolationError) else f"Failed to install APM dependencies: {e}"
|
|
1478
|
+
logger.error(msg)
|
|
1323
1479
|
if not verbose:
|
|
1324
1480
|
logger.progress("Run with --verbose for detailed diagnostics")
|
|
1325
1481
|
sys.exit(1)
|
|
@@ -1333,6 +1489,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1333
1489
|
clear_apm_yml_cache()
|
|
1334
1490
|
|
|
1335
1491
|
# Collect transitive MCP dependencies from resolved APM packages
|
|
1492
|
+
transitive_mcp = []
|
|
1336
1493
|
apm_modules_path = get_modules_dir(scope)
|
|
1337
1494
|
if should_install_mcp and apm_modules_path.exists():
|
|
1338
1495
|
lock_path = get_lockfile_path(apm_dir)
|
|
@@ -1344,6 +1501,37 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1344
1501
|
logger.verbose_detail(f"Collected {len(transitive_mcp)} transitive MCP dependency(ies)")
|
|
1345
1502
|
mcp_deps = MCPIntegrator.deduplicate(mcp_deps + transitive_mcp)
|
|
1346
1503
|
|
|
1504
|
+
# -- S1/S2 fix (#827-C2/C3): enforce policy on ALL MCP deps ----
|
|
1505
|
+
# The pipeline gate phase (policy_gate.py) checks direct APM deps
|
|
1506
|
+
# and direct MCP deps from apm.yml. However, transitive MCP
|
|
1507
|
+
# servers (discovered via collect_transitive above) are only known
|
|
1508
|
+
# after APM packages are installed. Run a second preflight
|
|
1509
|
+
# against the *merged* MCP set (direct + transitive) BEFORE
|
|
1510
|
+
# MCPIntegrator writes runtime configs. On PolicyBlockError we
|
|
1511
|
+
# abort the MCP write but leave already-installed APM packages
|
|
1512
|
+
# in place (they were approved by the gate phase).
|
|
1513
|
+
if should_install_mcp and mcp_deps:
|
|
1514
|
+
from apm_cli.policy.install_preflight import (
|
|
1515
|
+
PolicyBlockError as _TransitivePBE,
|
|
1516
|
+
run_policy_preflight as _transitive_preflight,
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1519
|
+
try:
|
|
1520
|
+
_transitive_preflight(
|
|
1521
|
+
project_root=project_root,
|
|
1522
|
+
mcp_deps=mcp_deps,
|
|
1523
|
+
no_policy=no_policy,
|
|
1524
|
+
logger=logger,
|
|
1525
|
+
dry_run=False,
|
|
1526
|
+
)
|
|
1527
|
+
except _TransitivePBE:
|
|
1528
|
+
logger.error(
|
|
1529
|
+
"MCP server(s) blocked by org policy. "
|
|
1530
|
+
"APM packages remain installed; MCP configs were NOT written."
|
|
1531
|
+
)
|
|
1532
|
+
logger.render_summary()
|
|
1533
|
+
sys.exit(1)
|
|
1534
|
+
|
|
1347
1535
|
# Continue with MCP installation (existing logic)
|
|
1348
1536
|
mcp_count = 0
|
|
1349
1537
|
new_mcp_servers: builtins.set = builtins.set()
|
|
@@ -1407,16 +1595,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1407
1595
|
sys.exit(1)
|
|
1408
1596
|
|
|
1409
1597
|
except InsecureDependencyPolicyError:
|
|
1598
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1410
1599
|
sys.exit(1)
|
|
1411
1600
|
except click.UsageError:
|
|
1412
1601
|
# Conflict matrix / argv parser raises UsageError -- let Click
|
|
1413
1602
|
# render with exit code 2 and the standard "Usage: ..." prefix.
|
|
1414
1603
|
raise
|
|
1415
1604
|
except Exception as e:
|
|
1605
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1416
1606
|
logger.error(f"Error installing dependencies: {e}")
|
|
1417
1607
|
if not verbose:
|
|
1418
1608
|
logger.progress("Run with --verbose for detailed diagnostics")
|
|
1419
1609
|
sys.exit(1)
|
|
1610
|
+
finally:
|
|
1611
|
+
# HACK(#852) cleanup: restore APM_VERBOSE so it stays scoped to this call.
|
|
1612
|
+
if _apm_verbose_prev is None:
|
|
1613
|
+
os.environ.pop("APM_VERBOSE", None)
|
|
1614
|
+
else:
|
|
1615
|
+
os.environ["APM_VERBOSE"] = _apm_verbose_prev
|
|
1420
1616
|
|
|
1421
1617
|
|
|
1422
1618
|
# ---------------------------------------------------------------------------
|
|
@@ -1463,6 +1659,7 @@ def _install_apm_dependencies(
|
|
|
1463
1659
|
marketplace_provenance: dict = None,
|
|
1464
1660
|
protocol_pref=None,
|
|
1465
1661
|
allow_protocol_fallback: "Optional[bool]" = None,
|
|
1662
|
+
no_policy: bool = False,
|
|
1466
1663
|
):
|
|
1467
1664
|
"""Thin wrapper -- builds an :class:`InstallRequest` and delegates to
|
|
1468
1665
|
:class:`apm_cli.install.service.InstallService`.
|
|
@@ -1494,5 +1691,6 @@ def _install_apm_dependencies(
|
|
|
1494
1691
|
marketplace_provenance=marketplace_provenance,
|
|
1495
1692
|
protocol_pref=protocol_pref,
|
|
1496
1693
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1694
|
+
no_policy=no_policy,
|
|
1497
1695
|
)
|
|
1498
1696
|
return InstallService().run(request)
|