apm-cli 0.9.3__tar.gz → 0.9.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.9.3/src/apm_cli.egg-info → apm_cli-0.9.4}/PKG-INFO +1 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/pyproject.toml +2 -1
- apm_cli-0.9.4/src/apm_cli/commands/_apm_yml_writer.py +81 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/experimental.py +0 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/install.py +50 -22
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/mcp.py +14 -3
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/outdated.py +1 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/pack.py +7 -7
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/runtime.py +1 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/lockfile.py +5 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/context.py +2 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/finalize.py +1 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/lockfile.py +16 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/pipeline.py +9 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/request.py +2 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/service.py +2 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/services.py +2 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/sources.py +1 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/template.py +12 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/validation.py +80 -29
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/skill_integrator.py +120 -6
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/reference.py +42 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/results.py +3 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/validation.py +220 -47
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/script_formatters.py +3 -5
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/ci_checks.py +43 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4/src/apm_cli.egg-info}/PKG-INFO +1 -1
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/SOURCES.txt +1 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_apm_package_models.py +62 -12
- {apm_cli-0.9.3 → apm_cli-0.9.4}/AUTHORS +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/LICENSE +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/README.md +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/setup.cfg +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/copilot.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/gemini.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/packer.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/cli.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/_helpers.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/audit.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/cli.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/_utils.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/deps/cli.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/marketplace.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/policy.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/uninstall/engine.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/update.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/agents_compiler.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/gemini_formatter.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/config.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/auth.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/azure_cli.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/experimental.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/target_detection.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/core/token_manager.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/github_downloader.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/errors.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/mcp_registry.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/mcp_warnings.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/cleanup.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/integrate.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/policy_gate.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/policy_target_check.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/instruction_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/mcp_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/apm_package.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/discovery.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/install_preflight.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/outcome_routing.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/parser.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/policy_checks.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/project_config.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/policy/schema.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/subprocess_env.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/version.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_console.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_lockfile.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_token_manager.py +0 -0
- {apm_cli-0.9.3 → apm_cli-0.9.4}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apm-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.4"
|
|
8
8
|
description = "MCP configuration tool"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -74,6 +74,7 @@ warn_unused_configs = true
|
|
|
74
74
|
addopts = "-m 'not benchmark'"
|
|
75
75
|
markers = [
|
|
76
76
|
"integration: marks tests as integration tests that may require network access",
|
|
77
|
+
"live: marks tests that hit real GitHub repos (requires network + optional GITHUB_TOKEN)",
|
|
77
78
|
"slow: marks tests as slow running tests",
|
|
78
79
|
"benchmark: marks performance benchmark tests (deselected by default, run with -m benchmark)",
|
|
79
80
|
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Write-back helper for persisting skill subset selection in apm.yml.
|
|
2
|
+
|
|
3
|
+
Single helper ``set_skill_subset_for_entry`` is the one source of truth
|
|
4
|
+
for promoting entries to dict form and setting/clearing the ``skills:``
|
|
5
|
+
field. Keeps write-back logic isolated and unit-testable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from ..models.dependency.reference import DependencyReference
|
|
12
|
+
from ..utils.yaml_io import dump_yaml, load_yaml
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_skill_subset_for_entry(
|
|
16
|
+
manifest_path: Path,
|
|
17
|
+
repo_url: str,
|
|
18
|
+
subset: Optional[List[str]],
|
|
19
|
+
) -> bool:
|
|
20
|
+
"""Promote entry to dict form and set/clear skills: field.
|
|
21
|
+
|
|
22
|
+
subset=None or empty list -> remove skills: from entry (reset to all).
|
|
23
|
+
subset=[...] -> set skills: to sorted+deduped list.
|
|
24
|
+
|
|
25
|
+
Returns True if file was modified.
|
|
26
|
+
"""
|
|
27
|
+
data = load_yaml(manifest_path) or {}
|
|
28
|
+
deps_section = data.get("dependencies", {})
|
|
29
|
+
apm_deps = deps_section.get("apm", [])
|
|
30
|
+
if not apm_deps:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
modified = False
|
|
34
|
+
new_deps = []
|
|
35
|
+
|
|
36
|
+
for entry in apm_deps:
|
|
37
|
+
if _entry_matches(entry, repo_url):
|
|
38
|
+
entry = _apply_subset(entry, subset)
|
|
39
|
+
modified = True
|
|
40
|
+
new_deps.append(entry)
|
|
41
|
+
|
|
42
|
+
if not modified:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
deps_section["apm"] = new_deps
|
|
46
|
+
data["dependencies"] = deps_section
|
|
47
|
+
dump_yaml(data, manifest_path)
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _entry_matches(entry, repo_url: str) -> bool:
|
|
52
|
+
"""Check if an apm.yml entry matches the given repo_url."""
|
|
53
|
+
try:
|
|
54
|
+
if isinstance(entry, str):
|
|
55
|
+
ref = DependencyReference.parse(entry)
|
|
56
|
+
elif isinstance(entry, dict):
|
|
57
|
+
ref = DependencyReference.parse_from_dict(entry)
|
|
58
|
+
else:
|
|
59
|
+
return False
|
|
60
|
+
return ref.repo_url == repo_url
|
|
61
|
+
except (ValueError, TypeError, AttributeError, KeyError):
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _apply_subset(entry, subset: Optional[List[str]]):
|
|
66
|
+
"""Apply skill subset to an entry, promoting to dict form if needed."""
|
|
67
|
+
# Parse current entry to get canonical info
|
|
68
|
+
if isinstance(entry, str):
|
|
69
|
+
ref = DependencyReference.parse(entry)
|
|
70
|
+
elif isinstance(entry, dict):
|
|
71
|
+
ref = DependencyReference.parse_from_dict(entry)
|
|
72
|
+
else:
|
|
73
|
+
return entry
|
|
74
|
+
|
|
75
|
+
# Determine if we should set or clear
|
|
76
|
+
if subset:
|
|
77
|
+
ref.skill_subset = sorted(set(subset))
|
|
78
|
+
else:
|
|
79
|
+
ref.skill_subset = None
|
|
80
|
+
|
|
81
|
+
return ref.to_apm_yml_entry()
|
|
@@ -140,7 +140,6 @@ def _handle_unknown_flag(name: str, logger: CommandLogger) -> None:
|
|
|
140
140
|
@click.group(
|
|
141
141
|
help="Manage experimental feature flags",
|
|
142
142
|
invoke_without_command=True,
|
|
143
|
-
context_settings={"allow_interspersed_args": True, "ignore_unknown_options": True},
|
|
144
143
|
)
|
|
145
144
|
@click.option("--verbose", "-v", is_flag=True, default=False, help="Show verbose output")
|
|
146
145
|
@click.pass_context
|
|
@@ -1050,43 +1050,29 @@ def _run_mcp_install(
|
|
|
1050
1050
|
"or a stdio command (self-defined entries)."
|
|
1051
1051
|
),
|
|
1052
1052
|
)
|
|
1053
|
-
@click.option(
|
|
1054
|
-
|
|
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
|
-
)
|
|
1053
|
+
@click.option("--skill", "skill_names", multiple=True, metavar="NAME", help="Install only named skill(s) from a SKILL_BUNDLE. Repeatable. Persisted in apm.yml and apm.lock so bare 'apm install' is deterministic. Use --skill '*' to reset to all skills.")
|
|
1054
|
+
@click.option("--no-policy", "no_policy", is_flag=True, default=False, help="Skip org policy enforcement for this invocation. Does NOT bypass apm audit --ci.")
|
|
1060
1055
|
@click.pass_context
|
|
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):
|
|
1056
|
+
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, skill_names, no_policy):
|
|
1062
1057
|
"""Install APM and MCP dependencies from apm.yml (like npm install).
|
|
1063
1058
|
|
|
1064
1059
|
Detects AI runtimes from your apm.yml scripts and installs MCP servers for
|
|
1065
1060
|
all detected runtimes; also installs APM package dependencies from GitHub.
|
|
1066
1061
|
--only filters by type (apm or mcp).
|
|
1067
1062
|
|
|
1068
|
-
HTTP dependencies require `allow_insecure: true` in apm.yml and
|
|
1069
|
-
`--allow-insecure` on the install command. Transitive HTTP dependencies are
|
|
1070
|
-
allowed automatically when they stay on the same host as a direct HTTP
|
|
1071
|
-
dependency, or explicitly with `--allow-insecure-host <hostname>`.
|
|
1072
|
-
|
|
1073
1063
|
Examples:
|
|
1074
1064
|
apm install # Install existing deps from apm.yml
|
|
1075
1065
|
apm install org/pkg1 # Add package to apm.yml and install
|
|
1076
|
-
apm install org/pkg1 org/pkg2 # Add multiple packages and install
|
|
1077
1066
|
apm install --exclude codex # Install for all except Codex CLI
|
|
1078
1067
|
apm install --only=apm # Install only APM dependencies
|
|
1079
|
-
apm install --only=mcp # Install only MCP dependencies
|
|
1080
1068
|
apm install --update # Update dependencies to latest Git refs
|
|
1081
1069
|
apm install --dry-run # Show what would be installed
|
|
1082
1070
|
apm install -g org/pkg1 # Install to user scope (~/.apm/)
|
|
1083
|
-
apm install --allow-insecure http
|
|
1084
|
-
apm install --
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
apm install --mcp
|
|
1088
|
-
apm install --mcp api --url https://example.com/mcp # remote http/sse
|
|
1089
|
-
apm install --mcp fetch -- npx -y @modelcontextprotocol/server-fetch # stdio (post-- argv)
|
|
1071
|
+
apm install --allow-insecure http://... # HTTP URL (needs allow_insecure)
|
|
1072
|
+
apm install --skill my-skill org/bundle # Install one skill from bundle
|
|
1073
|
+
apm install --mcp io.github.github/github-mcp-server # MCP registry
|
|
1074
|
+
apm install --mcp api --url https://example.com/mcp # MCP remote
|
|
1075
|
+
apm install --mcp fetch -- npx -y @mcp/server-fetch # MCP stdio
|
|
1090
1076
|
"""
|
|
1091
1077
|
# C1 #856: defaults BEFORE try so the finally clause never sees an
|
|
1092
1078
|
# UnboundLocalError if InstallLogger(...) raises during construction.
|
|
@@ -1149,6 +1135,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1149
1135
|
registry_url=validated_registry_url,
|
|
1150
1136
|
)
|
|
1151
1137
|
|
|
1138
|
+
# Normalize --skill: '*' means all (same as absent). Reject with --mcp.
|
|
1139
|
+
_skill_subset = None
|
|
1140
|
+
if skill_names:
|
|
1141
|
+
if mcp_name is not None:
|
|
1142
|
+
raise click.UsageError("--skill cannot be combined with --mcp.")
|
|
1143
|
+
if not any(s == "*" for s in skill_names):
|
|
1144
|
+
_skill_subset = builtins.tuple(skill_names)
|
|
1145
|
+
|
|
1152
1146
|
if mcp_name is not None:
|
|
1153
1147
|
# MCP install routing block. This branch has accreted
|
|
1154
1148
|
# significantly (--mcp / --registry / --transport / --env /
|
|
@@ -1463,11 +1457,41 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1463
1457
|
protocol_pref=protocol_pref,
|
|
1464
1458
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1465
1459
|
no_policy=no_policy,
|
|
1460
|
+
skill_subset=_skill_subset,
|
|
1461
|
+
skill_subset_from_cli=bool(skill_names),
|
|
1466
1462
|
)
|
|
1467
1463
|
apm_count = install_result.installed_count
|
|
1468
1464
|
prompt_count = install_result.prompts_integrated
|
|
1469
1465
|
agent_count = install_result.agents_integrated
|
|
1470
1466
|
apm_diagnostics = install_result.diagnostics
|
|
1467
|
+
|
|
1468
|
+
# -- Skill subset write-back (Phase 11) --
|
|
1469
|
+
# When CLI provided --skill on a SKILL_BUNDLE package, persist
|
|
1470
|
+
# the subset selection in apm.yml so bare `apm install` is
|
|
1471
|
+
# deterministic.
|
|
1472
|
+
if skill_names and packages:
|
|
1473
|
+
from ._apm_yml_writer import set_skill_subset_for_entry
|
|
1474
|
+
|
|
1475
|
+
_star_sentinel = any(s == "*" for s in skill_names)
|
|
1476
|
+
for dep_key, pkg_type in install_result.package_types.items():
|
|
1477
|
+
if pkg_type == "skill_bundle":
|
|
1478
|
+
if _star_sentinel:
|
|
1479
|
+
# Explicit-all: REMOVE any persisted skills:
|
|
1480
|
+
if set_skill_subset_for_entry(manifest_path, dep_key, None):
|
|
1481
|
+
logger.success(f"Cleared skill subset for {dep_key}")
|
|
1482
|
+
else:
|
|
1483
|
+
subset_list = sorted(builtins.set(_skill_subset))
|
|
1484
|
+
if set_skill_subset_for_entry(manifest_path, dep_key, subset_list):
|
|
1485
|
+
logger.success(
|
|
1486
|
+
f"Persisted skill subset for {dep_key}: "
|
|
1487
|
+
f"[{', '.join(subset_list)}]"
|
|
1488
|
+
)
|
|
1489
|
+
elif pkg_type != "skill_bundle" and not _star_sentinel:
|
|
1490
|
+
# Non-bundle: warn but do NOT persist
|
|
1491
|
+
logger.warning(
|
|
1492
|
+
f"--skill ignored for {dep_key} "
|
|
1493
|
+
f"(package type: {pkg_type}, not a skill bundle)"
|
|
1494
|
+
)
|
|
1471
1495
|
except InsecureDependencyPolicyError:
|
|
1472
1496
|
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1473
1497
|
sys.exit(1)
|
|
@@ -1664,6 +1688,8 @@ def _install_apm_dependencies(
|
|
|
1664
1688
|
protocol_pref=None,
|
|
1665
1689
|
allow_protocol_fallback: "Optional[bool]" = None,
|
|
1666
1690
|
no_policy: bool = False,
|
|
1691
|
+
skill_subset: "Optional[builtins.tuple]" = None,
|
|
1692
|
+
skill_subset_from_cli: bool = False,
|
|
1667
1693
|
):
|
|
1668
1694
|
"""Thin wrapper -- builds an :class:`InstallRequest` and delegates to
|
|
1669
1695
|
:class:`apm_cli.install.service.InstallService`.
|
|
@@ -1696,5 +1722,7 @@ def _install_apm_dependencies(
|
|
|
1696
1722
|
protocol_pref=protocol_pref,
|
|
1697
1723
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1698
1724
|
no_policy=no_policy,
|
|
1725
|
+
skill_subset=skill_subset,
|
|
1726
|
+
skill_subset_from_cli=skill_subset_from_cli,
|
|
1699
1727
|
)
|
|
1700
1728
|
return InstallService().run(request)
|
|
@@ -83,9 +83,20 @@ def mcp():
|
|
|
83
83
|
" apm mcp install fetch -- npx -y @modelcontextprotocol/server-fetch\n\n"
|
|
84
84
|
" apm mcp install api --transport http --url https://example.com/mcp"
|
|
85
85
|
),
|
|
86
|
+
epilog=(
|
|
87
|
+
"Common options (see `apm install --mcp --help` for full list):\n"
|
|
88
|
+
" --transport [stdio|http|sse|streamable-http]\n"
|
|
89
|
+
" --url URL Server URL for remote transports\n"
|
|
90
|
+
" --env KEY=VALUE Environment variable (repeatable)\n"
|
|
91
|
+
" --header KEY=VALUE HTTP header (repeatable)\n"
|
|
92
|
+
" --registry URL Custom registry URL\n"
|
|
93
|
+
" --mcp-version VER Pin registry entry to a specific version\n"
|
|
94
|
+
" --dev / --dry-run / --force / --verbose / --no-policy\n"
|
|
95
|
+
),
|
|
86
96
|
)
|
|
97
|
+
@click.argument("name", required=True)
|
|
87
98
|
@click.pass_context
|
|
88
|
-
def mcp_install(ctx):
|
|
99
|
+
def mcp_install(ctx, name):
|
|
89
100
|
"""Forward all args to 'apm install --mcp ...'.
|
|
90
101
|
|
|
91
102
|
Examples:
|
|
@@ -106,9 +117,9 @@ def mcp_install(ctx):
|
|
|
106
117
|
_, post_dd = _split_argv_at_double_dash(_get_invocation_argv())
|
|
107
118
|
if post_dd:
|
|
108
119
|
pre_args = ctx.args[: len(ctx.args) - len(post_dd)]
|
|
109
|
-
forwarded = ["install", "--mcp", *pre_args, "--", *post_dd]
|
|
120
|
+
forwarded = ["install", "--mcp", name, *pre_args, "--", *post_dd]
|
|
110
121
|
else:
|
|
111
|
-
forwarded = ["install", "--mcp", *ctx.args]
|
|
122
|
+
forwarded = ["install", "--mcp", name, *ctx.args]
|
|
112
123
|
|
|
113
124
|
try:
|
|
114
125
|
cli.main(args=forwarded, standalone_mode=False)
|
|
@@ -225,7 +225,7 @@ def _check_one_dep(dep, downloader, verbose):
|
|
|
225
225
|
@click.option("--parallel-checks", "-j", type=int, default=4,
|
|
226
226
|
help="Max concurrent remote checks (default: 4, 0 = sequential)")
|
|
227
227
|
def outdated(global_, verbose, parallel_checks):
|
|
228
|
-
"""Show outdated locked dependencies
|
|
228
|
+
"""Show outdated locked dependencies
|
|
229
229
|
|
|
230
230
|
Compares each locked dependency against the remote to detect staleness.
|
|
231
231
|
Tag-pinned deps use semver comparison; branch-pinned deps compare commit SHAs.
|
|
@@ -17,25 +17,25 @@ from ..core.target_detection import TargetParamType
|
|
|
17
17
|
"fmt",
|
|
18
18
|
type=click.Choice(["apm", "plugin"]),
|
|
19
19
|
default="apm",
|
|
20
|
-
help="Bundle format
|
|
20
|
+
help="Bundle format",
|
|
21
21
|
)
|
|
22
22
|
@click.option(
|
|
23
23
|
"--target",
|
|
24
24
|
"-t",
|
|
25
25
|
type=TargetParamType(),
|
|
26
26
|
default=None,
|
|
27
|
-
help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified
|
|
27
|
+
help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified",
|
|
28
28
|
)
|
|
29
|
-
@click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive
|
|
29
|
+
@click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive")
|
|
30
30
|
@click.option(
|
|
31
31
|
"-o",
|
|
32
32
|
"--output",
|
|
33
33
|
type=click.Path(),
|
|
34
34
|
default="./build",
|
|
35
|
-
help="Output directory (default: ./build)
|
|
35
|
+
help="Output directory (default: ./build)",
|
|
36
36
|
)
|
|
37
|
-
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be packed without writing
|
|
38
|
-
@click.option("--force", is_flag=True, default=False, help="On collision, last writer wins
|
|
37
|
+
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be packed without writing")
|
|
38
|
+
@click.option("--force", is_flag=True, default=False, help="On collision, last writer wins")
|
|
39
39
|
@click.option("--verbose", "-v", is_flag=True, help="Show detailed packing information")
|
|
40
40
|
@click.pass_context
|
|
41
41
|
def pack_cmd(ctx, fmt, target, archive, output, dry_run, force, verbose):
|
|
@@ -107,7 +107,7 @@ def pack_cmd(ctx, fmt, target, archive, output, dry_run, force, verbose):
|
|
|
107
107
|
help="Target directory (default: current directory).",
|
|
108
108
|
)
|
|
109
109
|
@click.option("--skip-verify", is_flag=True, default=False, help="Skip bundle completeness check.")
|
|
110
|
-
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be unpacked without writing
|
|
110
|
+
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be unpacked without writing")
|
|
111
111
|
@click.option("--force", is_flag=True, default=False, help="Deploy despite critical hidden-character findings.")
|
|
112
112
|
@click.option("--verbose", "-v", is_flag=True, help="Show detailed unpacking information")
|
|
113
113
|
@click.pass_context
|
|
@@ -124,7 +124,7 @@ def list():
|
|
|
124
124
|
|
|
125
125
|
@runtime.command(help="Remove an installed runtime")
|
|
126
126
|
@click.argument("runtime_name", type=click.Choice(["copilot", "codex", "llm", "gemini"]))
|
|
127
|
-
@click.confirmation_option(prompt="Are you sure you want to remove this runtime?", help="Confirm the action without prompting")
|
|
127
|
+
@click.confirmation_option("--yes", "-y", prompt="Are you sure you want to remove this runtime?", help="Confirm the action without prompting")
|
|
128
128
|
def remove(runtime_name):
|
|
129
129
|
"""Remove an installed runtime from APM management."""
|
|
130
130
|
logger = CommandLogger("runtime remove")
|
|
@@ -44,6 +44,7 @@ class LockedDependency:
|
|
|
44
44
|
marketplace_plugin_name: Optional[str] = None # Plugin name in marketplace
|
|
45
45
|
is_insecure: bool = False # True when the locked source was http://
|
|
46
46
|
allow_insecure: bool = False # True when the manifest explicitly allowed HTTP
|
|
47
|
+
skill_subset: List[str] = field(default_factory=list) # Sorted skill names for SKILL_BUNDLE
|
|
47
48
|
|
|
48
49
|
def get_unique_key(self) -> str:
|
|
49
50
|
"""Returns unique key for this dependency."""
|
|
@@ -100,6 +101,8 @@ class LockedDependency:
|
|
|
100
101
|
result["is_insecure"] = True
|
|
101
102
|
if self.allow_insecure:
|
|
102
103
|
result["allow_insecure"] = True
|
|
104
|
+
if self.skill_subset:
|
|
105
|
+
result["skill_subset"] = sorted(self.skill_subset)
|
|
103
106
|
return result
|
|
104
107
|
|
|
105
108
|
@classmethod
|
|
@@ -153,6 +156,7 @@ class LockedDependency:
|
|
|
153
156
|
marketplace_plugin_name=data.get("marketplace_plugin_name"),
|
|
154
157
|
is_insecure=data.get("is_insecure", False),
|
|
155
158
|
allow_insecure=data.get("allow_insecure", False),
|
|
159
|
+
skill_subset=list(data.get("skill_subset") or []),
|
|
156
160
|
)
|
|
157
161
|
|
|
158
162
|
@classmethod
|
|
@@ -201,6 +205,7 @@ class LockedDependency:
|
|
|
201
205
|
is_dev=is_dev,
|
|
202
206
|
is_insecure=dep_ref.is_insecure,
|
|
203
207
|
allow_insecure=dep_ref.allow_insecure,
|
|
208
|
+
skill_subset=sorted(dep_ref.skill_subset) if isinstance(getattr(dep_ref, "skill_subset", None), list) else [],
|
|
204
209
|
)
|
|
205
210
|
|
|
206
211
|
def to_dependency_ref(self) -> DependencyReference:
|
|
@@ -123,6 +123,8 @@ class InstallContext:
|
|
|
123
123
|
policy_fetch: Any = None # Optional[PolicyFetchResult] from discovery
|
|
124
124
|
policy_enforcement_active: bool = False
|
|
125
125
|
no_policy: bool = False # W2-escape-hatch will wire --no-policy here
|
|
126
|
+
skill_subset: Optional[Tuple[str, ...]] = None # --skill filter for SKILL_BUNDLE packages
|
|
127
|
+
skill_subset_from_cli: bool = False # True when user passed --skill (even --skill '*')
|
|
126
128
|
direct_mcp_deps: Optional[List[Any]] = None # Direct MCP deps from apm.yml for policy gate
|
|
127
129
|
|
|
128
130
|
# ------------------------------------------------------------------
|
|
@@ -52,4 +52,4 @@ def run(ctx: "InstallContext") -> "InstallResult":
|
|
|
52
52
|
f"-- pin with #tag or #sha to prevent drift"
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
return InstallResult(ctx.installed_count, ctx.total_prompts_integrated, ctx.total_agents_integrated, ctx.diagnostics)
|
|
55
|
+
return InstallResult(ctx.installed_count, ctx.total_prompts_integrated, ctx.total_agents_integrated, ctx.diagnostics, package_types=dict(ctx.package_types))
|
|
@@ -77,6 +77,8 @@ class LockfileBuilder:
|
|
|
77
77
|
# Attach deployed_files and package_type to each LockedDependency
|
|
78
78
|
self._attach_deployed_files(lockfile)
|
|
79
79
|
self._attach_package_types(lockfile)
|
|
80
|
+
# Apply CLI --skill override to lockfile entries (skill_bundle only)
|
|
81
|
+
self._attach_skill_subset_override(lockfile)
|
|
80
82
|
# Attach content hashes captured at download/verify time
|
|
81
83
|
self._attach_content_hashes(lockfile)
|
|
82
84
|
# Attach marketplace provenance if available
|
|
@@ -124,6 +126,20 @@ class LockfileBuilder:
|
|
|
124
126
|
if dep_key in lockfile.dependencies:
|
|
125
127
|
lockfile.dependencies[dep_key].package_type = pkg_type
|
|
126
128
|
|
|
129
|
+
def _attach_skill_subset_override(self, lockfile: LockFile) -> None:
|
|
130
|
+
"""Apply CLI --skill override to lockfile skill_bundle entries.
|
|
131
|
+
|
|
132
|
+
When the user runs `apm install bundle --skill foo`, the CLI
|
|
133
|
+
skill_subset takes precedence over the per-entry skill_subset
|
|
134
|
+
from the manifest for this invocation's lockfile.
|
|
135
|
+
"""
|
|
136
|
+
if not self.ctx.skill_subset:
|
|
137
|
+
return # No CLI override; dep_ref.skill_subset already flows through
|
|
138
|
+
effective = sorted(set(self.ctx.skill_subset))
|
|
139
|
+
for dep_key, locked_dep in lockfile.dependencies.items():
|
|
140
|
+
if locked_dep.package_type == "skill_bundle":
|
|
141
|
+
locked_dep.skill_subset = effective
|
|
142
|
+
|
|
127
143
|
def _attach_content_hashes(self, lockfile: LockFile) -> None:
|
|
128
144
|
for dep_key, locked_dep in lockfile.dependencies.items():
|
|
129
145
|
if dep_key in self.ctx.package_hashes:
|
|
@@ -28,6 +28,7 @@ from typing import TYPE_CHECKING, List, Optional
|
|
|
28
28
|
from ..models.results import InstallResult
|
|
29
29
|
from ..utils.console import _rich_error
|
|
30
30
|
from ..utils.diagnostics import DiagnosticCollector
|
|
31
|
+
from ..utils.path_security import PathTraversalError
|
|
31
32
|
from .errors import DirectDependencyError, PolicyViolationError
|
|
32
33
|
|
|
33
34
|
if TYPE_CHECKING:
|
|
@@ -60,6 +61,8 @@ def run_install_pipeline(
|
|
|
60
61
|
protocol_pref=None,
|
|
61
62
|
allow_protocol_fallback: "Optional[bool]" = None,
|
|
62
63
|
no_policy: bool = False,
|
|
64
|
+
skill_subset: "Optional[tuple]" = None,
|
|
65
|
+
skill_subset_from_cli: bool = False,
|
|
63
66
|
):
|
|
64
67
|
"""Install APM package dependencies.
|
|
65
68
|
|
|
@@ -152,6 +155,8 @@ def run_install_pipeline(
|
|
|
152
155
|
root_has_local_primitives=_root_has_local_primitives,
|
|
153
156
|
old_local_deployed=_old_local_deployed,
|
|
154
157
|
no_policy=no_policy,
|
|
158
|
+
skill_subset=skill_subset,
|
|
159
|
+
skill_subset_from_cli=skill_subset_from_cli,
|
|
155
160
|
)
|
|
156
161
|
|
|
157
162
|
# ------------------------------------------------------------------
|
|
@@ -381,5 +386,9 @@ def run_install_pipeline(
|
|
|
381
386
|
# #946: same pattern -- surface the message as-is instead of
|
|
382
387
|
# double-wrapping it through the generic RuntimeError below.
|
|
383
388
|
raise
|
|
389
|
+
except PathTraversalError:
|
|
390
|
+
# Path-safety violation in SKILL_BUNDLE or other nested
|
|
391
|
+
# resolution -- surface as-is for actionable user guidance.
|
|
392
|
+
raise
|
|
384
393
|
except Exception as e:
|
|
385
394
|
raise RuntimeError(f"Failed to resolve APM dependencies: {e}")
|
|
@@ -42,3 +42,5 @@ class InstallRequest:
|
|
|
42
42
|
protocol_pref: Any = None # ProtocolPreference (NONE/SSH/HTTPS) for shorthand transport
|
|
43
43
|
allow_protocol_fallback: Optional[bool] = None # None => read APM_ALLOW_PROTOCOL_FALLBACK env
|
|
44
44
|
no_policy: bool = False # W2-escape-hatch: skip org policy enforcement
|
|
45
|
+
skill_subset: Optional[Tuple[str, ...]] = None # --skill filter for SKILL_BUNDLE packages
|
|
46
|
+
skill_subset_from_cli: bool = False # True when user passed --skill (even --skill '*')
|
|
@@ -54,6 +54,7 @@ def integrate_package_primitives(
|
|
|
54
54
|
package_name: str = "",
|
|
55
55
|
logger: Optional["InstallLogger"] = None,
|
|
56
56
|
scope: Optional["InstallScope"] = None,
|
|
57
|
+
skill_subset: "Optional[tuple]" = None,
|
|
57
58
|
) -> dict:
|
|
58
59
|
"""Run the full integration pipeline for a single package.
|
|
59
60
|
|
|
@@ -141,7 +142,7 @@ def integrate_package_primitives(
|
|
|
141
142
|
skill_result = skill_integrator.integrate_package_skill(
|
|
142
143
|
package_info, project_root,
|
|
143
144
|
diagnostics=diagnostics, managed_files=managed_files, force=force,
|
|
144
|
-
targets=targets,
|
|
145
|
+
targets=targets, skill_subset=skill_subset,
|
|
145
146
|
)
|
|
146
147
|
_skill_target_dirs: set = builtins.set()
|
|
147
148
|
for tp in skill_result.target_paths:
|
|
@@ -58,6 +58,7 @@ def _format_package_type_label(pkg_type) -> Optional[str]:
|
|
|
58
58
|
PackageType.HYBRID: "Hybrid (apm.yml + SKILL.md)",
|
|
59
59
|
PackageType.APM_PACKAGE: "APM Package (apm.yml)",
|
|
60
60
|
PackageType.HOOK_PACKAGE: "Hook Package (hooks/*.json only)",
|
|
61
|
+
PackageType.SKILL_BUNDLE: "Skill Bundle (skills/<name>/SKILL.md)",
|
|
61
62
|
}.get(pkg_type)
|
|
62
63
|
|
|
63
64
|
|
|
@@ -86,6 +86,18 @@ def _integrate_materialization(
|
|
|
86
86
|
package_name=dep_key,
|
|
87
87
|
logger=logger,
|
|
88
88
|
scope=ctx.scope,
|
|
89
|
+
# Per-package effective subset: CLI --skill overrides per-entry
|
|
90
|
+
# apm.yml skills:. When CLI is absent (bare reinstall), fall back
|
|
91
|
+
# to the dep_ref's persisted skill_subset.
|
|
92
|
+
# When CLI explicitly provided (even --skill '*'), use ctx value
|
|
93
|
+
# (which is None for '*' = install all).
|
|
94
|
+
skill_subset=(
|
|
95
|
+
ctx.skill_subset
|
|
96
|
+
if ctx.skill_subset_from_cli
|
|
97
|
+
else (
|
|
98
|
+
tuple(dep_ref.skill_subset) if dep_ref.skill_subset else None
|
|
99
|
+
)
|
|
100
|
+
),
|
|
89
101
|
)
|
|
90
102
|
for k in (
|
|
91
103
|
"prompts", "agents", "skills", "sub_skills",
|