apm-cli 0.8.12__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apm_cli-0.8.12/src/apm_cli.egg-info → apm_cli-0.9.0}/PKG-INFO +14 -2
- {apm_cli-0.8.12 → apm_cli-0.9.0}/README.md +13 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/pyproject.toml +1 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/base.py +6 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/codex.py +3 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/copilot.py +3 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/cursor.py +2 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/opencode.py +2 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/vscode.py +26 -5
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/bundle/lockfile_enrichment.py +39 -8
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/bundle/packer.py +19 -11
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/bundle/plugin_exporter.py +1 -8
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/_helpers.py +7 -9
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/compile/cli.py +55 -5
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/config.py +70 -21
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/deps/cli.py +57 -20
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/init.py +25 -14
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/install.py +769 -38
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/marketplace.py +87 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/mcp.py +133 -14
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/outdated.py +143 -26
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/pack.py +3 -2
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/cli.py +1 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/engine.py +2 -2
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/update.py +6 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/view.py +130 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/auth.py +96 -22
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/target_detection.py +142 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/token_manager.py +49 -17
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/github_downloader.py +294 -75
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/lockfile.py +43 -8
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/plugin_parser.py +47 -3
- apm_cli-0.9.0/src/apm_cli/deps/transport_selection.py +331 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/drift.py +19 -7
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/context.py +4 -0
- apm_cli-0.9.0/src/apm_cli/install/insecure_policy.py +239 -0
- apm_cli-0.9.0/src/apm_cli/install/mcp_registry.py +256 -0
- apm_cli-0.9.0/src/apm_cli/install/mcp_warnings.py +122 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/resolve.py +29 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/pipeline.py +11 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/request.py +5 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/service.py +4 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/sources.py +23 -7
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/validation.py +29 -11
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/mcp_integrator.py +66 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/targets.py +48 -10
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/client.py +5 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/resolver.py +123 -9
- apm_cli-0.9.0/src/apm_cli/marketplace/shadow_detector.py +75 -0
- apm_cli-0.9.0/src/apm_cli/marketplace/validator.py +80 -0
- apm_cli-0.9.0/src/apm_cli/marketplace/version_pins.py +163 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/apm_package.py +1 -1
- apm_cli-0.9.0/src/apm_cli/models/dependency/mcp.py +254 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/dependency/reference.py +181 -71
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/validation.py +96 -22
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/policy_checks.py +15 -9
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/registry/client.py +75 -7
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/registry/operations.py +13 -2
- apm_cli-0.9.0/src/apm_cli/update_policy.py +51 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/github_host.py +22 -5
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/path_security.py +8 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0/src/apm_cli.egg-info}/PKG-INFO +14 -2
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli.egg-info/SOURCES.txt +8 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_apm_package_models.py +127 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_github_downloader.py +1 -1
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_lockfile.py +54 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_token_manager.py +66 -2
- apm_cli-0.8.12/src/apm_cli/models/dependency/mcp.py +0 -136
- {apm_cli-0.8.12 → apm_cli-0.9.0}/AUTHORS +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/LICENSE +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/setup.cfg +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/cli.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/audit.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/deps/_utils.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/runtime.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/agents_compiler.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/config.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/cleanup.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/integrate.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/instruction_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/skill_integrator.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/ci_checks.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/discovery.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/parser.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/policy/schema.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/version.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_console.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.8.12 → apm_cli-0.9.0}/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.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: MCP configuration tool
|
|
5
5
|
Author-email: Daniel Meppiel <user@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -90,6 +90,10 @@ dependencies:
|
|
|
90
90
|
- github/awesome-copilot/agents/api-architect.agent.md
|
|
91
91
|
# A full APM package with instructions, skills, prompts, hooks...
|
|
92
92
|
- microsoft/apm-sample-package#v1.0.0
|
|
93
|
+
mcp:
|
|
94
|
+
# MCP servers -- installed into every detected client
|
|
95
|
+
- name: io.github.github/github-mcp-server
|
|
96
|
+
transport: http # MCP transport name, not URL scheme -- connects over HTTPS
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
```bash
|
|
@@ -99,7 +103,7 @@ apm install # every agent is configured
|
|
|
99
103
|
|
|
100
104
|
## Highlights
|
|
101
105
|
|
|
102
|
-
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
|
|
106
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
|
|
103
107
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
104
108
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
105
109
|
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
|
|
@@ -161,6 +165,14 @@ apm marketplace add github/awesome-copilot
|
|
|
161
165
|
apm install azure-cloud-development@awesome-copilot
|
|
162
166
|
```
|
|
163
167
|
|
|
168
|
+
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and OpenCode):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> *Codex CLI currently does not support remote MCP servers; the install will skip Codex with a notice. Omit `--transport http` to use the local Docker variant on Codex (requires `GITHUB_PERSONAL_ACCESS_TOKEN`).*
|
|
175
|
+
|
|
164
176
|
See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
|
|
165
177
|
|
|
166
178
|
## Works with agentrc
|
|
@@ -28,6 +28,10 @@ dependencies:
|
|
|
28
28
|
- github/awesome-copilot/agents/api-architect.agent.md
|
|
29
29
|
# A full APM package with instructions, skills, prompts, hooks...
|
|
30
30
|
- microsoft/apm-sample-package#v1.0.0
|
|
31
|
+
mcp:
|
|
32
|
+
# MCP servers -- installed into every detected client
|
|
33
|
+
- name: io.github.github/github-mcp-server
|
|
34
|
+
transport: http # MCP transport name, not URL scheme -- connects over HTTPS
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
```bash
|
|
@@ -37,7 +41,7 @@ apm install # every agent is configured
|
|
|
37
41
|
|
|
38
42
|
## Highlights
|
|
39
43
|
|
|
40
|
-
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, MCP servers
|
|
44
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
|
|
41
45
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
42
46
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
43
47
|
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
|
|
@@ -99,6 +103,14 @@ apm marketplace add github/awesome-copilot
|
|
|
99
103
|
apm install azure-cloud-development@awesome-copilot
|
|
100
104
|
```
|
|
101
105
|
|
|
106
|
+
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and OpenCode):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> *Codex CLI currently does not support remote MCP servers; the install will skip Codex with a notice. Omit `--transport http` to use the local Docker variant on Codex (requires `GITHUB_PERSONAL_ACCESS_TOKEN`).*
|
|
113
|
+
|
|
102
114
|
See the **[Getting Started guide](https://microsoft.github.io/apm/getting-started/quick-start/)** for the full walkthrough.
|
|
103
115
|
|
|
104
116
|
## Works with agentrc
|
|
@@ -9,6 +9,12 @@ _INPUT_VAR_RE = re.compile(r"\$\{input:([^}]+)\}")
|
|
|
9
9
|
class MCPClientAdapter(ABC):
|
|
10
10
|
"""Base adapter for MCP clients."""
|
|
11
11
|
|
|
12
|
+
# Whether this adapter's config path is user/global-scoped (e.g.
|
|
13
|
+
# ``~/.copilot/``) rather than workspace-scoped (e.g. ``.vscode/``).
|
|
14
|
+
# Adapters that target a global path should override this to ``True``
|
|
15
|
+
# so that ``apm install --global`` can install MCP servers to them.
|
|
16
|
+
supports_user_scope: bool = False
|
|
17
|
+
|
|
12
18
|
@abstractmethod
|
|
13
19
|
def get_config_path(self):
|
|
14
20
|
"""Get the path to the MCP configuration file."""
|
|
@@ -20,7 +20,9 @@ class CodexClientAdapter(MCPClientAdapter):
|
|
|
20
20
|
a global ~/.codex/config.toml file, following the TOML format for
|
|
21
21
|
MCP server configuration.
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
supports_user_scope: bool = True
|
|
25
|
+
|
|
24
26
|
def __init__(self, registry_url=None):
|
|
25
27
|
"""Initialize the Codex CLI client adapter.
|
|
26
28
|
|
|
@@ -23,7 +23,9 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
23
23
|
a global ~/.copilot/mcp-config.json file, following the JSON format for
|
|
24
24
|
MCP server configuration.
|
|
25
25
|
"""
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
supports_user_scope: bool = True
|
|
28
|
+
|
|
27
29
|
def __init__(self, registry_url=None):
|
|
28
30
|
"""Initialize the Copilot CLI client adapter.
|
|
29
31
|
|
|
@@ -25,6 +25,8 @@ class CursorClientAdapter(CopilotClientAdapter):
|
|
|
25
25
|
of global ``~/.copilot/mcp-config.json``.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
supports_user_scope: bool = False
|
|
29
|
+
|
|
28
30
|
# ------------------------------------------------------------------ #
|
|
29
31
|
# Config path
|
|
30
32
|
# ------------------------------------------------------------------ #
|
|
@@ -40,6 +40,8 @@ class OpenCodeClientAdapter(CopilotClientAdapter):
|
|
|
40
40
|
and writes to ``opencode.json`` in the project root.
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
+
supports_user_scope: bool = False
|
|
44
|
+
|
|
43
45
|
def get_config_path(self):
|
|
44
46
|
"""Return the path to ``opencode.json`` in the repository root."""
|
|
45
47
|
return str(Path(os.getcwd()) / "opencode.json")
|
|
@@ -320,17 +320,25 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
320
320
|
}
|
|
321
321
|
# Check for remotes (similar to Copilot adapter)
|
|
322
322
|
elif "remotes" in server_info and server_info["remotes"]:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
remote = self._select_remote_with_url(server_info["remotes"])
|
|
324
|
+
if remote:
|
|
325
|
+
transport = (remote.get("transport_type") or "").strip()
|
|
326
|
+
# Default to "http" when transport_type is missing/empty,
|
|
327
|
+
# matching the Copilot adapter behavior (copilot.py:190-192).
|
|
328
|
+
if not transport:
|
|
329
|
+
transport = "http"
|
|
330
|
+
elif transport not in ("sse", "http", "streamable-http"):
|
|
331
|
+
raise ValueError(
|
|
332
|
+
f"Unsupported remote transport '{transport}' for VS Code. "
|
|
333
|
+
f"Server: {server_info.get('name', 'unknown')}. "
|
|
334
|
+
f"Supported transports: http, sse, streamable-http.")
|
|
327
335
|
headers = remote.get("headers", {})
|
|
328
336
|
# Normalize header list format to dict
|
|
329
337
|
if isinstance(headers, list):
|
|
330
338
|
headers = {h["name"]: h["value"] for h in headers if "name" in h and "value" in h}
|
|
331
339
|
server_config = {
|
|
332
340
|
"type": transport,
|
|
333
|
-
"url": remote
|
|
341
|
+
"url": remote["url"].strip(),
|
|
334
342
|
"headers": headers,
|
|
335
343
|
}
|
|
336
344
|
input_vars.extend(
|
|
@@ -424,6 +432,19 @@ class VSCodeClientAdapter(MCPClientAdapter):
|
|
|
424
432
|
|
|
425
433
|
return []
|
|
426
434
|
|
|
435
|
+
@staticmethod
|
|
436
|
+
def _select_remote_with_url(remotes):
|
|
437
|
+
"""Return the first remote entry that has a non-empty URL.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
dict or None: The first usable remote, or None if none found.
|
|
441
|
+
"""
|
|
442
|
+
for remote in remotes:
|
|
443
|
+
url = (remote.get("url") or "").strip()
|
|
444
|
+
if url:
|
|
445
|
+
return remote
|
|
446
|
+
return None
|
|
447
|
+
|
|
427
448
|
def _select_best_package(self, packages):
|
|
428
449
|
"""Select the best package for VS Code installation from available packages.
|
|
429
450
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Lockfile enrichment for pack-time metadata."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import Dict, List, Tuple
|
|
4
|
+
from typing import Dict, List, Tuple, Union
|
|
5
5
|
|
|
6
6
|
from ..deps.lockfile import LockFile
|
|
7
7
|
|
|
@@ -55,7 +55,7 @@ _CROSS_TARGET_MAPS: Dict[str, Dict[str, str]] = {
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def _filter_files_by_target(
|
|
58
|
-
deployed_files: List[str], target: str
|
|
58
|
+
deployed_files: List[str], target: Union[str, List[str]]
|
|
59
59
|
) -> Tuple[List[str], Dict[str, str]]:
|
|
60
60
|
"""Filter deployed file paths by target prefix, with cross-target mapping.
|
|
61
61
|
|
|
@@ -64,16 +64,38 @@ def _filter_files_by_target(
|
|
|
64
64
|
remapped to the equivalent target path. Commands, instructions, and hooks
|
|
65
65
|
are NOT remapped -- they are target-specific.
|
|
66
66
|
|
|
67
|
+
*target* may be a single string or a list of strings. For a list, the
|
|
68
|
+
union of all relevant prefixes and cross-target maps is used.
|
|
69
|
+
|
|
67
70
|
Returns:
|
|
68
71
|
A tuple of ``(filtered_files, path_mappings)`` where *path_mappings*
|
|
69
72
|
maps ``bundle_path -> disk_path`` for any file that was cross-target
|
|
70
73
|
remapped. Direct matches have no entry in the dict.
|
|
71
74
|
"""
|
|
72
|
-
|
|
75
|
+
if isinstance(target, list):
|
|
76
|
+
# Union all prefixes for the targets in the list
|
|
77
|
+
prefixes: List[str] = []
|
|
78
|
+
seen_prefixes: set = set()
|
|
79
|
+
for t in target:
|
|
80
|
+
for p in _TARGET_PREFIXES.get(t, []):
|
|
81
|
+
if p not in seen_prefixes:
|
|
82
|
+
seen_prefixes.add(p)
|
|
83
|
+
prefixes.append(p)
|
|
84
|
+
# Union all cross-target maps
|
|
85
|
+
# NOTE: dict.update() means the last target's mapping wins when
|
|
86
|
+
# multiple targets map the same source prefix. In practice this
|
|
87
|
+
# is benign -- common multi-target combos (e.g. claude+copilot)
|
|
88
|
+
# match prefixes directly without needing cross-maps.
|
|
89
|
+
cross_map: Dict[str, str] = {}
|
|
90
|
+
for t in target:
|
|
91
|
+
cross_map.update(_CROSS_TARGET_MAPS.get(t, {}))
|
|
92
|
+
else:
|
|
93
|
+
prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
|
|
94
|
+
cross_map = _CROSS_TARGET_MAPS.get(target, {})
|
|
95
|
+
|
|
73
96
|
direct = [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
|
|
74
97
|
|
|
75
98
|
path_mappings: Dict[str, str] = {}
|
|
76
|
-
cross_map = _CROSS_TARGET_MAPS.get(target, {})
|
|
77
99
|
if cross_map:
|
|
78
100
|
direct_set = set(direct)
|
|
79
101
|
for f in deployed_files:
|
|
@@ -94,7 +116,7 @@ def _filter_files_by_target(
|
|
|
94
116
|
def enrich_lockfile_for_pack(
|
|
95
117
|
lockfile: LockFile,
|
|
96
118
|
fmt: str,
|
|
97
|
-
target: str,
|
|
119
|
+
target: Union[str, List[str]],
|
|
98
120
|
) -> str:
|
|
99
121
|
"""Create an enriched copy of the lockfile YAML with a ``pack:`` section.
|
|
100
122
|
|
|
@@ -109,7 +131,8 @@ def enrich_lockfile_for_pack(
|
|
|
109
131
|
lockfile: The resolved lockfile to enrich.
|
|
110
132
|
fmt: Bundle format (``"apm"`` or ``"plugin"``).
|
|
111
133
|
target: Effective target used for packing (e.g. ``"copilot"``, ``"claude"``,
|
|
112
|
-
``"all"``).
|
|
134
|
+
``"all"``). May also be a list of target strings for multi-target
|
|
135
|
+
packing. The internal alias ``"vscode"`` is also accepted.
|
|
113
136
|
|
|
114
137
|
Returns:
|
|
115
138
|
A YAML string with the ``pack:`` block followed by the original
|
|
@@ -132,9 +155,12 @@ def enrich_lockfile_for_pack(
|
|
|
132
155
|
|
|
133
156
|
# Build the pack: metadata section (after filtering so we know if mapping
|
|
134
157
|
# occurred).
|
|
158
|
+
# Serialize target as a comma-joined string for backward compatibility
|
|
159
|
+
# with consumers that expect a plain string in pack.target.
|
|
160
|
+
target_str = ",".join(target) if isinstance(target, list) else target
|
|
135
161
|
pack_meta: Dict = {
|
|
136
162
|
"format": fmt,
|
|
137
|
-
"target":
|
|
163
|
+
"target": target_str,
|
|
138
164
|
"packed_at": datetime.now(timezone.utc).isoformat(),
|
|
139
165
|
}
|
|
140
166
|
if all_mappings:
|
|
@@ -142,7 +168,12 @@ def enrich_lockfile_for_pack(
|
|
|
142
168
|
# bundle paths differ from the original lockfile. Use the canonical
|
|
143
169
|
# prefix keys from _CROSS_TARGET_MAPS rather than reverse-engineering
|
|
144
170
|
# them from file paths.
|
|
145
|
-
|
|
171
|
+
if isinstance(target, list):
|
|
172
|
+
cross_map: Dict[str, str] = {}
|
|
173
|
+
for t in target:
|
|
174
|
+
cross_map.update(_CROSS_TARGET_MAPS.get(t, {}))
|
|
175
|
+
else:
|
|
176
|
+
cross_map = _CROSS_TARGET_MAPS.get(target, {})
|
|
146
177
|
used_src_prefixes = set()
|
|
147
178
|
for original in all_mappings.values():
|
|
148
179
|
for src_prefix in cross_map:
|
|
@@ -5,7 +5,7 @@ import shutil
|
|
|
5
5
|
import tarfile
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Dict, List, Optional
|
|
8
|
+
from typing import Dict, List, Optional, Union
|
|
9
9
|
|
|
10
10
|
from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
|
|
11
11
|
from ..models.apm_package import APMPackage
|
|
@@ -28,7 +28,7 @@ def pack_bundle(
|
|
|
28
28
|
project_root: Path,
|
|
29
29
|
output_dir: Path,
|
|
30
30
|
fmt: str = "apm",
|
|
31
|
-
target: Optional[str] = None,
|
|
31
|
+
target: Optional[Union[str, List[str]]] = None,
|
|
32
32
|
archive: bool = False,
|
|
33
33
|
dry_run: bool = False,
|
|
34
34
|
force: bool = False,
|
|
@@ -40,7 +40,8 @@ def pack_bundle(
|
|
|
40
40
|
project_root: Root of the project containing ``apm.lock.yaml`` and ``apm.yml``.
|
|
41
41
|
output_dir: Directory where the bundle will be created.
|
|
42
42
|
fmt: Bundle format -- ``"apm"`` (default) or ``"plugin"``.
|
|
43
|
-
target: Target filter -- ``"copilot"``, ``"claude"``, ``"all"``,
|
|
43
|
+
target: Target filter -- ``"copilot"``, ``"claude"``, ``"all"``, a list of
|
|
44
|
+
target strings (e.g. ``["claude", "vscode"]``), or *None*
|
|
44
45
|
(auto-detect from apm.yml / project structure).
|
|
45
46
|
archive: If *True*, produce a ``.tar.gz`` and remove the directory.
|
|
46
47
|
dry_run: If *True*, resolve the file list but write nothing to disk.
|
|
@@ -102,14 +103,21 @@ def pack_bundle(
|
|
|
102
103
|
config_target = None
|
|
103
104
|
|
|
104
105
|
# 3. Resolve effective target
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
effective_target =
|
|
106
|
+
if isinstance(target, list):
|
|
107
|
+
# List from CLI (e.g. --target claude,copilot) passes through directly
|
|
108
|
+
effective_target = target
|
|
109
|
+
elif isinstance(config_target, list) and target is None:
|
|
110
|
+
# List from apm.yml target: [claude, copilot]
|
|
111
|
+
effective_target = config_target
|
|
112
|
+
else:
|
|
113
|
+
effective_target, _reason = detect_target(
|
|
114
|
+
project_root,
|
|
115
|
+
explicit_target=target,
|
|
116
|
+
config_target=config_target if isinstance(config_target, str) else None,
|
|
117
|
+
)
|
|
118
|
+
# For packing purposes, "minimal" means nothing to pack -- treat as "all"
|
|
119
|
+
if effective_target == "minimal":
|
|
120
|
+
effective_target = "all"
|
|
113
121
|
|
|
114
122
|
# 4. Collect deployed_files from all dependencies, filtered by target
|
|
115
123
|
all_deployed: List[str] = []
|
|
@@ -396,14 +396,7 @@ def _update_plugin_json_paths(plugin_json: dict, output_files: List[str]) -> dic
|
|
|
396
396
|
|
|
397
397
|
def _dep_install_path(dep: LockedDependency, apm_modules_dir: Path) -> Path:
|
|
398
398
|
"""Compute the filesystem install path for a locked dependency."""
|
|
399
|
-
dep_ref =
|
|
400
|
-
repo_url=dep.repo_url,
|
|
401
|
-
host=dep.host,
|
|
402
|
-
virtual_path=dep.virtual_path,
|
|
403
|
-
is_virtual=dep.is_virtual,
|
|
404
|
-
is_local=(dep.source == "local"),
|
|
405
|
-
local_path=dep.local_path,
|
|
406
|
-
)
|
|
399
|
+
dep_ref = dep.to_dependency_ref()
|
|
407
400
|
return dep_ref.get_install_path(apm_modules_dir)
|
|
408
401
|
|
|
409
402
|
|
|
@@ -21,6 +21,7 @@ from ..constants import (
|
|
|
21
21
|
GITIGNORE_FILENAME,
|
|
22
22
|
)
|
|
23
23
|
from ..utils.console import _rich_echo, _rich_info, _rich_warning
|
|
24
|
+
from ..update_policy import get_update_hint_message, is_self_update_enabled
|
|
24
25
|
from ..version import get_build_sha, get_version
|
|
25
26
|
from ..utils.version_checker import check_for_updates
|
|
26
27
|
|
|
@@ -121,8 +122,6 @@ def _build_expected_install_paths(declared_deps, lockfile, apm_modules_dir: Path
|
|
|
121
122
|
(depth > 1 from ``apm.lock``), using ``get_install_path()`` for
|
|
122
123
|
consistency with how packages are actually installed.
|
|
123
124
|
"""
|
|
124
|
-
from ..models.apm_package import DependencyReference
|
|
125
|
-
|
|
126
125
|
expected = builtins.set()
|
|
127
126
|
for dep in declared_deps:
|
|
128
127
|
install_path = dep.get_install_path(apm_modules_dir)
|
|
@@ -135,12 +134,7 @@ def _build_expected_install_paths(declared_deps, lockfile, apm_modules_dir: Path
|
|
|
135
134
|
if lockfile:
|
|
136
135
|
for dep in lockfile.get_all_dependencies():
|
|
137
136
|
if dep.depth is not None and dep.depth > 1:
|
|
138
|
-
dep_ref =
|
|
139
|
-
repo_url=dep.repo_url,
|
|
140
|
-
host=dep.host,
|
|
141
|
-
virtual_path=dep.virtual_path,
|
|
142
|
-
is_virtual=dep.is_virtual,
|
|
143
|
-
)
|
|
137
|
+
dep_ref = dep.to_dependency_ref()
|
|
144
138
|
install_path = dep_ref.get_install_path(apm_modules_dir)
|
|
145
139
|
try:
|
|
146
140
|
relative_path = install_path.relative_to(apm_modules_dir)
|
|
@@ -240,6 +234,10 @@ def print_version(ctx, param, value):
|
|
|
240
234
|
def _check_and_notify_updates():
|
|
241
235
|
"""Check for updates and notify user non-blockingly."""
|
|
242
236
|
try:
|
|
237
|
+
# Skip notifications when self-update is disabled by distribution policy.
|
|
238
|
+
if not is_self_update_enabled():
|
|
239
|
+
return
|
|
240
|
+
|
|
243
241
|
# Skip version check in E2E test mode to avoid interfering with tests
|
|
244
242
|
if os.environ.get("APM_E2E_TESTS", "").lower() in ("1", "true", "yes"):
|
|
245
243
|
return
|
|
@@ -260,7 +258,7 @@ def _check_and_notify_updates():
|
|
|
260
258
|
)
|
|
261
259
|
|
|
262
260
|
# Show update command using helper for consistency
|
|
263
|
-
_rich_echo(
|
|
261
|
+
_rich_echo(get_update_hint_message(), color="yellow", bold=True)
|
|
264
262
|
|
|
265
263
|
# Add a blank line for visual separation
|
|
266
264
|
click.echo()
|
|
@@ -8,6 +8,7 @@ import click
|
|
|
8
8
|
from ...constants import AGENTS_MD_FILENAME, APM_DIR, APM_MODULES_DIR, APM_YML_FILENAME
|
|
9
9
|
from ...compilation import AgentsCompiler, CompilationConfig
|
|
10
10
|
from ...core.command_logger import CommandLogger
|
|
11
|
+
from ...core.target_detection import TargetParamType
|
|
11
12
|
from ...primitives.discovery import discover_primitives
|
|
12
13
|
from ...utils.console import (
|
|
13
14
|
_rich_error,
|
|
@@ -162,6 +163,36 @@ def _get_validation_suggestion(error_msg):
|
|
|
162
163
|
return "Check primitive structure and frontmatter"
|
|
163
164
|
|
|
164
165
|
|
|
166
|
+
def _resolve_compile_target(target):
|
|
167
|
+
"""Map CLI target input to compiler-understood target string.
|
|
168
|
+
|
|
169
|
+
The compiler only understands ``"vscode"``, ``"claude"``, and ``"all"``.
|
|
170
|
+
Multi-target lists are mapped to the narrowest equivalent.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
target: A single target string, a list of target strings, or ``None``.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
A single string (or ``None``) suitable for :func:`detect_target`.
|
|
177
|
+
"""
|
|
178
|
+
if target is None:
|
|
179
|
+
return None # will trigger detect_target() auto-detection
|
|
180
|
+
if isinstance(target, list):
|
|
181
|
+
target_set = set(target)
|
|
182
|
+
# Any target that produces AGENTS.md (copilot/vscode/agents/cursor/opencode/codex)
|
|
183
|
+
has_agents_family = bool(
|
|
184
|
+
target_set & {"copilot", "vscode", "agents", "cursor", "opencode", "codex"}
|
|
185
|
+
)
|
|
186
|
+
has_claude = "claude" in target_set
|
|
187
|
+
if has_agents_family and has_claude:
|
|
188
|
+
return "all"
|
|
189
|
+
elif has_claude:
|
|
190
|
+
return "claude"
|
|
191
|
+
else:
|
|
192
|
+
return "vscode" # agents-family only
|
|
193
|
+
return target # single string pass-through
|
|
194
|
+
|
|
195
|
+
|
|
165
196
|
@click.command(help="Compile APM context into distributed AGENTS.md files")
|
|
166
197
|
@click.option(
|
|
167
198
|
"--output",
|
|
@@ -172,9 +203,9 @@ def _get_validation_suggestion(error_msg):
|
|
|
172
203
|
@click.option(
|
|
173
204
|
"--target",
|
|
174
205
|
"-t",
|
|
175
|
-
type=
|
|
206
|
+
type=TargetParamType(),
|
|
176
207
|
default=None,
|
|
177
|
-
help="Target platform
|
|
208
|
+
help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified.",
|
|
178
209
|
)
|
|
179
210
|
@click.option(
|
|
180
211
|
"--dry-run",
|
|
@@ -354,10 +385,14 @@ def compile(
|
|
|
354
385
|
# No apm.yml or parsing error - proceed with auto-detection
|
|
355
386
|
pass
|
|
356
387
|
|
|
388
|
+
# Resolve list targets to compiler-understood string
|
|
389
|
+
compile_target = _resolve_compile_target(target)
|
|
390
|
+
# Also handle config_target being a list (from apm.yml target: [claude, copilot])
|
|
391
|
+
compile_config_target = _resolve_compile_target(config_target)
|
|
357
392
|
detected_target, detection_reason = detect_target(
|
|
358
393
|
project_root=Path("."),
|
|
359
|
-
explicit_target=
|
|
360
|
-
config_target=
|
|
394
|
+
explicit_target=compile_target,
|
|
395
|
+
config_target=compile_config_target,
|
|
361
396
|
)
|
|
362
397
|
|
|
363
398
|
# Map 'minimal' to 'vscode' for the compiler (AGENTS.md only, no folder integration)
|
|
@@ -383,7 +418,22 @@ def compile(
|
|
|
383
418
|
# Show target-aware message with detection reason. Use
|
|
384
419
|
# get_target_description() so any future target added to
|
|
385
420
|
# target_detection shows up here automatically.
|
|
386
|
-
if
|
|
421
|
+
if isinstance(target, list):
|
|
422
|
+
# Multi-target list: show what the compiler will produce
|
|
423
|
+
_target_label = ",".join(target)
|
|
424
|
+
if effective_target == "all":
|
|
425
|
+
logger.progress(
|
|
426
|
+
f"Compiling for AGENTS.md + CLAUDE.md (--target {_target_label})"
|
|
427
|
+
)
|
|
428
|
+
elif effective_target == "claude":
|
|
429
|
+
logger.progress(
|
|
430
|
+
f"Compiling for CLAUDE.md (--target {_target_label})"
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
logger.progress(
|
|
434
|
+
f"Compiling for AGENTS.md (--target {_target_label})"
|
|
435
|
+
)
|
|
436
|
+
elif detected_target == "minimal":
|
|
387
437
|
logger.progress(f"Compiling for AGENTS.md only ({detection_reason})")
|
|
388
438
|
logger.progress(
|
|
389
439
|
" Create .github/, .claude/, .codex/, .opencode/ or .cursor/ folder for full integration",
|
|
@@ -14,6 +14,46 @@ from ._helpers import HIGHLIGHT, RESET, _get_console, _load_apm_config
|
|
|
14
14
|
# Restore builtin since a subcommand is named ``set``
|
|
15
15
|
set = builtins.set
|
|
16
16
|
|
|
17
|
+
_BOOLEAN_TRUE_VALUES = {"true", "1", "yes"}
|
|
18
|
+
_BOOLEAN_FALSE_VALUES = {"false", "0", "no"}
|
|
19
|
+
_CONFIG_KEY_DISPLAY_NAMES = {
|
|
20
|
+
"auto_integrate": "auto-integrate",
|
|
21
|
+
"temp_dir": "temp-dir",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _parse_bool_value(value: str) -> bool:
|
|
26
|
+
"""Parse a CLI boolean value."""
|
|
27
|
+
normalized = value.strip().lower()
|
|
28
|
+
if normalized in _BOOLEAN_TRUE_VALUES:
|
|
29
|
+
return True
|
|
30
|
+
if normalized in _BOOLEAN_FALSE_VALUES:
|
|
31
|
+
return False
|
|
32
|
+
raise ValueError(f"Invalid value '{value}'. Use 'true' or 'false'.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_config_setters():
|
|
36
|
+
"""Return config setters keyed by CLI option name."""
|
|
37
|
+
from ..config import set_auto_integrate
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"auto-integrate": (set_auto_integrate, "Auto-integration"),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_config_getters():
|
|
45
|
+
"""Return config getters keyed by CLI option name."""
|
|
46
|
+
from ..config import get_auto_integrate
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"auto-integrate": get_auto_integrate,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _valid_config_keys() -> str:
|
|
54
|
+
"""Return valid config keys for messages."""
|
|
55
|
+
return ", ".join(["auto-integrate", "temp-dir"])
|
|
56
|
+
|
|
17
57
|
|
|
18
58
|
@click.group(help="Configure APM CLI", invoke_without_command=True)
|
|
19
59
|
@click.pass_context
|
|
@@ -128,35 +168,41 @@ def set(key, value):
|
|
|
128
168
|
apm config set auto-integrate false
|
|
129
169
|
apm config set auto-integrate true
|
|
130
170
|
"""
|
|
131
|
-
from ..config import
|
|
171
|
+
from ..config import get_temp_dir, set_temp_dir
|
|
132
172
|
|
|
133
173
|
logger = CommandLogger("config set")
|
|
134
|
-
if key == "
|
|
135
|
-
if value.lower() in ["true", "1", "yes"]:
|
|
136
|
-
set_auto_integrate(True)
|
|
137
|
-
logger.success("Auto-integration enabled")
|
|
138
|
-
elif value.lower() in ["false", "0", "no"]:
|
|
139
|
-
set_auto_integrate(False)
|
|
140
|
-
logger.success("Auto-integration disabled")
|
|
141
|
-
else:
|
|
142
|
-
logger.error(f"Invalid value '{value}'. Use 'true' or 'false'.")
|
|
143
|
-
sys.exit(1)
|
|
144
|
-
elif key == "temp-dir":
|
|
174
|
+
if key == "temp-dir":
|
|
145
175
|
try:
|
|
146
176
|
set_temp_dir(value)
|
|
147
|
-
from ..config import get_temp_dir
|
|
148
177
|
logger.success(f"Temporary directory set to: {get_temp_dir()}")
|
|
149
178
|
except ValueError as exc:
|
|
150
179
|
logger.error(str(exc))
|
|
151
180
|
sys.exit(1)
|
|
152
|
-
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
setters = _get_config_setters()
|
|
184
|
+
config_entry = setters.get(key)
|
|
185
|
+
if config_entry is None:
|
|
153
186
|
logger.error(f"Unknown configuration key: '{key}'")
|
|
154
|
-
logger.progress("Valid keys:
|
|
187
|
+
logger.progress(f"Valid keys: {_valid_config_keys()}")
|
|
155
188
|
logger.progress(
|
|
156
189
|
"This error may indicate a bug in command routing. Please report this issue."
|
|
157
190
|
)
|
|
158
191
|
sys.exit(1)
|
|
159
192
|
|
|
193
|
+
try:
|
|
194
|
+
enabled = _parse_bool_value(value)
|
|
195
|
+
except ValueError as exc:
|
|
196
|
+
logger.error(str(exc))
|
|
197
|
+
sys.exit(1)
|
|
198
|
+
|
|
199
|
+
setter, label = config_entry
|
|
200
|
+
setter(enabled)
|
|
201
|
+
if enabled:
|
|
202
|
+
logger.success(f"{label} enabled")
|
|
203
|
+
else:
|
|
204
|
+
logger.success(f"{label} disabled")
|
|
205
|
+
|
|
160
206
|
|
|
161
207
|
@config.command(help="Get a configuration value")
|
|
162
208
|
@click.argument("key", required=False)
|
|
@@ -170,23 +216,26 @@ def get(key):
|
|
|
170
216
|
from ..config import get_auto_integrate, get_temp_dir
|
|
171
217
|
|
|
172
218
|
logger = CommandLogger("config get")
|
|
219
|
+
getters = _get_config_getters()
|
|
173
220
|
if key:
|
|
174
|
-
if key == "
|
|
175
|
-
value = get_auto_integrate()
|
|
176
|
-
click.echo(f"auto-integrate: {value}")
|
|
177
|
-
elif key == "temp-dir":
|
|
221
|
+
if key == "temp-dir":
|
|
178
222
|
value = get_temp_dir()
|
|
179
223
|
if value is None:
|
|
180
224
|
click.echo("temp-dir: Not set (using system default)")
|
|
181
225
|
else:
|
|
182
226
|
click.echo(f"temp-dir: {value}")
|
|
183
|
-
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
getter = getters.get(key)
|
|
230
|
+
if getter is None:
|
|
184
231
|
logger.error(f"Unknown configuration key: '{key}'")
|
|
185
|
-
logger.progress("Valid keys:
|
|
232
|
+
logger.progress(f"Valid keys: {_valid_config_keys()}")
|
|
186
233
|
logger.progress(
|
|
187
234
|
"This error may indicate a bug in command routing. Please report this issue."
|
|
188
235
|
)
|
|
189
236
|
sys.exit(1)
|
|
237
|
+
value = getter()
|
|
238
|
+
click.echo(f"{key}: {value}")
|
|
190
239
|
else:
|
|
191
240
|
# Show all user-settable keys with their effective values (including
|
|
192
241
|
# defaults). Iterating raw config keys would hide settings that
|