apm-cli 0.12.2__tar.gz → 0.12.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.12.2/src/apm_cli.egg-info → apm_cli-0.12.4}/PKG-INFO +1 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4}/pyproject.toml +1 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/claude.py +6 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/copilot.py +481 -39
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/cursor.py +6 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/gemini.py +6 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/opencode.py +6 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/windsurf.py +6 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/packer.py +2 -2
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/unpacker.py +2 -2
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/url_normalize.py +6 -3
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cli.py +2 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/_helpers.py +46 -7
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/audit.py +69 -14
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/compile/cli.py +94 -0
- apm_cli-0.12.4/src/apm_cli/commands/init.py +572 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/install.py +14 -5
- apm_cli-0.12.4/src/apm_cli/commands/targets.py +135 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/link_resolver.py +204 -12
- apm_cli-0.12.4/src/apm_cli/core/apm_yml.py +97 -0
- apm_cli-0.12.4/src/apm_cli/core/errors.py +156 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/target_detection.py +199 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/plugin_parser.py +10 -12
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/context.py +9 -0
- apm_cli-0.12.4/src/apm_cli/install/heals/__init__.py +33 -0
- apm_cli-0.12.4/src/apm_cli/install/heals/base.py +122 -0
- apm_cli-0.12.4/src/apm_cli/install/heals/branch_ref_drift.py +66 -0
- apm_cli-0.12.4/src/apm_cli/install/heals/buggy_lockfile_recovery.py +99 -0
- apm_cli-0.12.4/src/apm_cli/install/phases/heal.py +90 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/integrate.py +39 -3
- apm_cli-0.12.4/src/apm_cli/install/phases/targets.py +435 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/pipeline.py +17 -2
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/sources.py +59 -16
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/base_integrator.py +9 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/mcp_integrator.py +12 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/skill_integrator.py +8 -12
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/dependency/reference.py +18 -13
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/discovery.py +21 -7
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/outcome_routing.py +17 -8
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/security/__init__.py +2 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/security/gate.py +18 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/content_hash.py +3 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4/src/apm_cli.egg-info}/PKG-INFO +1 -1
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli.egg-info/SOURCES.txt +8 -0
- apm_cli-0.12.2/src/apm_cli/commands/init.py +0 -294
- apm_cli-0.12.2/src/apm_cli/install/phases/targets.py +0 -215
- {apm_cli-0.12.2 → apm_cli-0.12.4}/AUTHORS +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/LICENSE +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/NOTICE +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/README.md +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/setup.cfg +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/local_bundle.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/git_cache.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/http_cache.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/integrity.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/locking.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/cache/paths.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/_apm_yml_writer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/cache.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/deps/_utils.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/deps/cli.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/experimental.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/check.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/doctor.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/init.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/migrate.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/outdated.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/plugin/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/plugin/add.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/plugin/remove.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/plugin/set.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/publish.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/marketplace/validate.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/mcp.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/outdated.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/pack.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/policy.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/runtime.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/uninstall/engine.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/update.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/agents_compiler.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/build_id.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/gemini_formatter.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/output_writer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/config.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/auth.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/azure_cli.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/build_orchestrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/experimental.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/null_logger.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/core/token_manager.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/bare_cache.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/download_strategies.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/git_remote_ops.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/github_downloader.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/github_downloader_validation.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/lockfile.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/shared_clone_cache.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/cache_pin.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/drift.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/errors.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/local_bundle_handler.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/args.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/command.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/conflicts.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/entry.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/registry.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/warnings.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/mcp/writer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/cleanup.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/policy_gate.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/policy_target_check.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/request.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/service.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/skill_path_migration.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/summary.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/install/validation.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/copilot_cowork_paths.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/instruction_integrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/_git_utils.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/_io.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/builder.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/git_stderr.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/init_template.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/migration.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/pr_integration.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/publisher.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/ref_resolver.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/semver.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/tag_pattern.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/yml_editor.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/marketplace/yml_schema.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/apm_package.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/models/validation.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/_help_text.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/ci_checks.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/install_preflight.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/parser.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/policy_checks.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/project_config.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/policy/schema.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/atomic_io.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/git_env.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/guards.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/install_tui.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/normalization.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/reflink.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/short_sha.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/subprocess_env.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/version.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_apm_package_models.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_console.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_lockfile.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_token_manager.py +0 -0
- {apm_cli-0.12.2 → apm_cli-0.12.4}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -42,6 +42,12 @@ class ClaudeClientAdapter(CopilotClientAdapter):
|
|
|
42
42
|
target_name: str = "claude"
|
|
43
43
|
mcp_servers_key: str = "mcpServers"
|
|
44
44
|
|
|
45
|
+
# Claude Desktop / Code's mcp config does NOT support runtime env-var
|
|
46
|
+
# substitution -- the value in ``env`` must be a literal string. This
|
|
47
|
+
# adapter MUST keep the legacy install-time resolution behaviour.
|
|
48
|
+
# See #1152 supply-chain analysis.
|
|
49
|
+
_supports_runtime_env_substitution: bool = False
|
|
50
|
+
|
|
45
51
|
@staticmethod
|
|
46
52
|
def _normalize_mcp_entry_for_claude_code(entry: dict) -> dict:
|
|
47
53
|
"""Normalize a server entry to Claude Code's on-disk shape.
|
|
@@ -9,11 +9,15 @@ import json
|
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from typing import ClassVar
|
|
13
|
+
|
|
14
|
+
import click
|
|
12
15
|
|
|
13
16
|
from ...core.docker_args import DockerArgsProcessor
|
|
14
17
|
from ...core.token_manager import GitHubTokenManager
|
|
15
18
|
from ...registry.client import SimpleRegistryClient
|
|
16
19
|
from ...registry.integration import RegistryIntegration
|
|
20
|
+
from ...utils.console import _rich_warning
|
|
17
21
|
from ...utils.github_host import is_github_hostname
|
|
18
22
|
from .base import _ENV_VAR_RE, MCPClientAdapter
|
|
19
23
|
|
|
@@ -27,6 +31,67 @@ from .base import _ENV_VAR_RE, MCPClientAdapter
|
|
|
27
31
|
# per-call cost. ``${input:...}`` is intentionally not matched here.
|
|
28
32
|
_COPILOT_ENV_RE = re.compile(r"<([A-Z_][A-Z0-9_]*)>|" + _ENV_VAR_RE.pattern)
|
|
29
33
|
|
|
34
|
+
# Detects the legacy ``<VAR>`` placeholder syntax. Used both for translation
|
|
35
|
+
# and for emitting an aggregated deprecation warning, mirroring the analogous
|
|
36
|
+
# pattern in ``vscode.py``.
|
|
37
|
+
_LEGACY_ANGLE_VAR_RE = re.compile(r"<([A-Z_][A-Z0-9_]*)>")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _translate_env_placeholder(value):
|
|
41
|
+
"""Pure-textual translation of env-var placeholders to Copilot CLI's
|
|
42
|
+
native runtime substitution syntax (``${VAR}``).
|
|
43
|
+
|
|
44
|
+
This is the security-critical helper for issue #1152: it MUST NOT read
|
|
45
|
+
``os.environ`` and MUST NOT resolve placeholders to their literal values.
|
|
46
|
+
Copilot CLI resolves ``${VAR}`` from the host environment at server-start
|
|
47
|
+
time, so APM emits placeholders verbatim rather than baking secrets into
|
|
48
|
+
``~/.copilot/mcp-config.json``.
|
|
49
|
+
|
|
50
|
+
Translations:
|
|
51
|
+
``${env:VAR}`` -> ``${VAR}`` (strip ``env:`` prefix)
|
|
52
|
+
``${VAR}`` -> ``${VAR}`` (no-op)
|
|
53
|
+
``<VAR>`` -> ``${VAR}`` (legacy syntax migration)
|
|
54
|
+
``${VAR:-default}``-> passthrough (regex doesn't match)
|
|
55
|
+
``$VAR`` (bare) -> passthrough (regex doesn't match)
|
|
56
|
+
``${input:foo}`` -> passthrough (regex doesn't match)
|
|
57
|
+
non-string -> passthrough
|
|
58
|
+
|
|
59
|
+
The translation is idempotent: applying it twice produces the same
|
|
60
|
+
result as applying it once.
|
|
61
|
+
"""
|
|
62
|
+
if not isinstance(value, str):
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
def _to_brace(match):
|
|
66
|
+
# group(1) = legacy <VAR>; group(2) = ${VAR} / ${env:VAR}
|
|
67
|
+
var_name = match.group(1) or match.group(2)
|
|
68
|
+
return "${" + var_name + "}"
|
|
69
|
+
|
|
70
|
+
return _COPILOT_ENV_RE.sub(_to_brace, value)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_legacy_angle_vars(value):
|
|
74
|
+
"""Return the set of legacy ``<VAR>`` names present in *value*.
|
|
75
|
+
|
|
76
|
+
Used to aggregate deprecation warnings across all servers in a single
|
|
77
|
+
install run, so authors see one helpful list instead of one warning per
|
|
78
|
+
occurrence.
|
|
79
|
+
"""
|
|
80
|
+
if not isinstance(value, str):
|
|
81
|
+
return set()
|
|
82
|
+
return set(_LEGACY_ANGLE_VAR_RE.findall(value))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _has_env_placeholder(value):
|
|
86
|
+
"""True if *value* is a string containing any recognised env-var
|
|
87
|
+
placeholder syntax (``${VAR}``, ``${env:VAR}``, or legacy ``<VAR>``).
|
|
88
|
+
Used to distinguish placeholder-sourced env values (which translate)
|
|
89
|
+
from hardcoded literal defaults (which stay literal).
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(value, str):
|
|
92
|
+
return False
|
|
93
|
+
return bool(_COPILOT_ENV_RE.search(value))
|
|
94
|
+
|
|
30
95
|
|
|
31
96
|
class CopilotClientAdapter(MCPClientAdapter):
|
|
32
97
|
"""Copilot CLI implementation of MCP client adapter.
|
|
@@ -41,6 +106,42 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
41
106
|
target_name: str = "copilot"
|
|
42
107
|
mcp_servers_key: str = "mcpServers"
|
|
43
108
|
|
|
109
|
+
# When True, env-var placeholders (``${VAR}``, ``${env:VAR}``, legacy
|
|
110
|
+
# ``<VAR>``) are translated to Copilot CLI's native runtime-substitution
|
|
111
|
+
# syntax (``${VAR}``) and emitted into mcp-config.json verbatim. The
|
|
112
|
+
# secret never touches disk.
|
|
113
|
+
#
|
|
114
|
+
# When False, placeholders are resolved at install time against the host
|
|
115
|
+
# environment and the literal value is baked into the config file
|
|
116
|
+
# (legacy pre-#1152 behaviour).
|
|
117
|
+
#
|
|
118
|
+
# Subclasses (Cursor / Windsurf / OpenCode / Claude / Gemini) override
|
|
119
|
+
# this to ``False`` until their respective config formats are individually
|
|
120
|
+
# audited for runtime-substitution support. Critically, Claude Desktop's
|
|
121
|
+
# config format does NOT support runtime substitution -- it MUST keep
|
|
122
|
+
# resolving at install time.
|
|
123
|
+
_supports_runtime_env_substitution: bool = True
|
|
124
|
+
|
|
125
|
+
# Process-wide aggregation of legacy ``<VAR>`` offenders, keyed by
|
|
126
|
+
# adapter class so subclasses (Cursor, etc.) maintain their own
|
|
127
|
+
# buckets. Populated by ``configure_mcp_server`` and drained by the
|
|
128
|
+
# post-install summary helper. Class-level so cross-server warnings
|
|
129
|
+
# work even when a fresh adapter instance is created per dep.
|
|
130
|
+
_legacy_angle_offenders_by_server: ClassVar[dict] = {}
|
|
131
|
+
# Process-wide aggregation of env-var keys whose values were previously
|
|
132
|
+
# baked as plaintext literals on disk and have just been rewritten to
|
|
133
|
+
# ``${KEY}`` placeholders. Drives the security-improvement notice.
|
|
134
|
+
_security_upgraded_keys: ClassVar[set] = set()
|
|
135
|
+
# Process-wide aggregation of env-var names referenced by configs that
|
|
136
|
+
# are NOT exported in the current shell. Drives the post-install
|
|
137
|
+
# actionable warning that lists vars the user must export before
|
|
138
|
+
# launching ``gh copilot``.
|
|
139
|
+
_unset_env_keys_by_server: ClassVar[dict] = {}
|
|
140
|
+
# Guard so the post-install summary is emitted at most once per CLI
|
|
141
|
+
# invocation, regardless of how many ``configure_mcp_server`` calls
|
|
142
|
+
# contributed to the aggregation buckets.
|
|
143
|
+
_install_run_summary_emitted: ClassVar[bool] = False
|
|
144
|
+
|
|
44
145
|
def __init__(
|
|
45
146
|
self,
|
|
46
147
|
registry_url=None,
|
|
@@ -61,6 +162,14 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
61
162
|
super().__init__(project_root=project_root, user_scope=user_scope)
|
|
62
163
|
self.registry_client = SimpleRegistryClient(registry_url)
|
|
63
164
|
self.registry_integration = RegistryIntegration(registry_url)
|
|
165
|
+
# Per-server tracking of placeholder-sourced env-var keys, populated
|
|
166
|
+
# during ``_format_server_config`` and consumed by the post-install
|
|
167
|
+
# summary line. Keys: env-var names; never holds resolved values.
|
|
168
|
+
self._last_env_placeholder_keys = set()
|
|
169
|
+
# Per-server collection of legacy ``<VAR>`` offenders, populated by
|
|
170
|
+
# the resolution helpers and consumed by ``configure_mcp_server`` to
|
|
171
|
+
# feed the aggregated deprecation warning.
|
|
172
|
+
self._last_legacy_angle_vars = set()
|
|
64
173
|
|
|
65
174
|
def get_config_path(self):
|
|
66
175
|
"""Get the path to the Copilot CLI MCP configuration file.
|
|
@@ -154,6 +263,26 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
154
263
|
print(f"Error: MCP server '{server_url}' not found in registry")
|
|
155
264
|
return False
|
|
156
265
|
|
|
266
|
+
# Reset per-server tracking before formatting (so the per-server
|
|
267
|
+
# summary line and aggregated diagnostics reflect this server only).
|
|
268
|
+
self._last_env_placeholder_keys = set()
|
|
269
|
+
self._last_legacy_angle_vars = set()
|
|
270
|
+
|
|
271
|
+
# Detect security upgrade: was the previous on-disk config for
|
|
272
|
+
# this server holding literal (resolved) values for env keys
|
|
273
|
+
# we are about to replace with ${KEY} placeholders? If so,
|
|
274
|
+
# remember the affected keys for the post-install notice. We
|
|
275
|
+
# snapshot BEFORE writing the new config.
|
|
276
|
+
previously_baked_keys = set()
|
|
277
|
+
previously_baked_headers = False
|
|
278
|
+
if self._supports_runtime_env_substitution:
|
|
279
|
+
previously_baked_keys, previously_baked_headers = (
|
|
280
|
+
self._collect_previously_baked_keys(server_url, server_name)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Generate server configuration with environment and runtime variable resolution
|
|
284
|
+
server_config = self._format_server_config(server_info, env_overrides, runtime_vars)
|
|
285
|
+
|
|
157
286
|
# Determine the server name for configuration key
|
|
158
287
|
if server_name:
|
|
159
288
|
# Use explicitly provided server name
|
|
@@ -168,19 +297,193 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
168
297
|
# Fallback to full server_url if no slash
|
|
169
298
|
config_key = server_url
|
|
170
299
|
|
|
171
|
-
# Generate server configuration with environment and runtime variable resolution
|
|
172
|
-
server_config = self._format_server_config(server_info, env_overrides, runtime_vars)
|
|
173
|
-
|
|
174
300
|
# Update configuration using the chosen key
|
|
175
301
|
self.update_config({config_key: server_config})
|
|
176
302
|
|
|
177
|
-
|
|
303
|
+
# Aggregate diagnostics for the post-install summary.
|
|
304
|
+
if self._supports_runtime_env_substitution:
|
|
305
|
+
if self._last_legacy_angle_vars:
|
|
306
|
+
self._legacy_angle_offenders_by_server[config_key] = set(
|
|
307
|
+
self._last_legacy_angle_vars
|
|
308
|
+
)
|
|
309
|
+
# Only flag a security upgrade when the previously baked keys
|
|
310
|
+
# actually overlap with what we are now placeholderizing -- OR
|
|
311
|
+
# when the previous on-disk state had baked HTTP header
|
|
312
|
+
# literals (which don't expose env-var names directly, so we
|
|
313
|
+
# surface every newly-placeholderised key for this server).
|
|
314
|
+
upgraded = previously_baked_keys & self._last_env_placeholder_keys
|
|
315
|
+
if previously_baked_headers and self._last_env_placeholder_keys:
|
|
316
|
+
upgraded = upgraded | self._last_env_placeholder_keys
|
|
317
|
+
if upgraded:
|
|
318
|
+
self._security_upgraded_keys.update(upgraded)
|
|
319
|
+
|
|
320
|
+
# Per-server install line with env-var summary parenthetical.
|
|
321
|
+
self._emit_install_summary(config_key, server_config)
|
|
178
322
|
return True
|
|
179
323
|
|
|
180
324
|
except Exception as e:
|
|
181
325
|
print(f"Error configuring MCP server: {e}")
|
|
182
326
|
return False
|
|
183
327
|
|
|
328
|
+
def _collect_previously_baked_keys(self, server_url, server_name):
|
|
329
|
+
"""Return ``(env_keys, headers_were_baked)`` for the existing on-disk
|
|
330
|
+
entry: the set of env-block keys whose values are literal
|
|
331
|
+
(non-placeholder) strings, and a flag indicating whether the headers
|
|
332
|
+
block contained any literal values. Together these drive the
|
|
333
|
+
security-improvement notice. Headers don't expose env-var names
|
|
334
|
+
directly, so the caller unions current-write placeholder keys when
|
|
335
|
+
``headers_were_baked`` is True.
|
|
336
|
+
"""
|
|
337
|
+
try:
|
|
338
|
+
current = self.get_current_config()
|
|
339
|
+
except Exception:
|
|
340
|
+
return set(), False
|
|
341
|
+
servers = current.get("mcpServers") or {}
|
|
342
|
+
# Match the same key resolution rule used below.
|
|
343
|
+
if server_name:
|
|
344
|
+
key = server_name
|
|
345
|
+
elif "/" in server_url:
|
|
346
|
+
key = server_url.split("/")[-1]
|
|
347
|
+
else:
|
|
348
|
+
key = server_url
|
|
349
|
+
existing = servers.get(key)
|
|
350
|
+
if not isinstance(existing, dict):
|
|
351
|
+
return set(), False
|
|
352
|
+
baked_env_keys = set()
|
|
353
|
+
env_block = existing.get("env") or {}
|
|
354
|
+
if isinstance(env_block, dict):
|
|
355
|
+
for k, v in env_block.items():
|
|
356
|
+
if isinstance(v, str) and v.strip() and not _has_env_placeholder(v):
|
|
357
|
+
baked_env_keys.add(k)
|
|
358
|
+
headers_were_baked = False
|
|
359
|
+
headers_block = existing.get("headers") or {}
|
|
360
|
+
if isinstance(headers_block, dict):
|
|
361
|
+
for v in headers_block.values():
|
|
362
|
+
if isinstance(v, str) and v.strip() and not _has_env_placeholder(v):
|
|
363
|
+
headers_were_baked = True
|
|
364
|
+
break
|
|
365
|
+
return baked_env_keys, headers_were_baked
|
|
366
|
+
|
|
367
|
+
def _emit_install_summary(self, config_key, server_config):
|
|
368
|
+
"""Record env-var references for the post-install aggregated
|
|
369
|
+
summary. No per-server line is emitted here; the integrator's
|
|
370
|
+
tree (``| + {name} -> Copilot (configured)``) is the success
|
|
371
|
+
signal. The summary references env-var names only -- never their
|
|
372
|
+
values.
|
|
373
|
+
"""
|
|
374
|
+
if not self._supports_runtime_env_substitution:
|
|
375
|
+
return
|
|
376
|
+
keys = set(self._last_env_placeholder_keys)
|
|
377
|
+
if isinstance(server_config, dict):
|
|
378
|
+
for block_key in ("env", "headers"):
|
|
379
|
+
block = server_config.get(block_key)
|
|
380
|
+
if not isinstance(block, dict):
|
|
381
|
+
continue
|
|
382
|
+
for value in block.values():
|
|
383
|
+
if isinstance(value, str):
|
|
384
|
+
for match in _ENV_VAR_RE.finditer(value):
|
|
385
|
+
keys.add(match.group(1))
|
|
386
|
+
unset = sorted(name for name in keys if not os.environ.get(name))
|
|
387
|
+
if unset:
|
|
388
|
+
self.__class__._unset_env_keys_by_server.setdefault(config_key, []).extend(
|
|
389
|
+
u
|
|
390
|
+
for u in unset
|
|
391
|
+
if u not in self.__class__._unset_env_keys_by_server.get(config_key, [])
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def emit_install_run_summary(cls):
|
|
396
|
+
"""Emit aggregated cross-server diagnostics at the end of an install
|
|
397
|
+
run. Idempotent: subsequent calls within the same process are no-ops.
|
|
398
|
+
|
|
399
|
+
Three diagnostics are emitted (when applicable):
|
|
400
|
+
|
|
401
|
+
1. Security improvement notice -- when the install rewrote
|
|
402
|
+
previously baked literal env values to runtime placeholders.
|
|
403
|
+
Emitted as a warning because it is an action item (the user
|
|
404
|
+
must export the affected vars).
|
|
405
|
+
2. Aggregated unset-env warning -- when one or more configured
|
|
406
|
+
servers reference env vars that are not currently exported.
|
|
407
|
+
Includes a copy-pasteable ``export`` hint.
|
|
408
|
+
3. Aggregated legacy ``<VAR>`` deprecation warning -- one line
|
|
409
|
+
naming all affected servers, mirroring the established VS Code
|
|
410
|
+
adapter pattern.
|
|
411
|
+
|
|
412
|
+
State is drained after emission so a subsequent install run in
|
|
413
|
+
the same process (e.g. tests) starts clean.
|
|
414
|
+
"""
|
|
415
|
+
if cls._install_run_summary_emitted:
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
# Visual separator from the install tree's closing line so the
|
|
419
|
+
# post-tree summary block reads as a distinct section.
|
|
420
|
+
emitted_any = False
|
|
421
|
+
|
|
422
|
+
def _emit_separator_once():
|
|
423
|
+
nonlocal emitted_any
|
|
424
|
+
if not emitted_any:
|
|
425
|
+
click.echo("")
|
|
426
|
+
emitted_any = True
|
|
427
|
+
|
|
428
|
+
if cls._security_upgraded_keys:
|
|
429
|
+
visible = sorted(cls._security_upgraded_keys)
|
|
430
|
+
count = len(visible)
|
|
431
|
+
noun = "variable" if count == 1 else "variables"
|
|
432
|
+
affected = ", ".join(visible)
|
|
433
|
+
_emit_separator_once()
|
|
434
|
+
_rich_warning(
|
|
435
|
+
f"Security improvement: {count} environment {noun} previously stored as "
|
|
436
|
+
f"plaintext in the Copilot config are now resolved at runtime.\n"
|
|
437
|
+
f" Affected: {affected}\n"
|
|
438
|
+
f" Ensure these are exported in your shell before running 'gh copilot'",
|
|
439
|
+
symbol="warning",
|
|
440
|
+
)
|
|
441
|
+
if cls._unset_env_keys_by_server:
|
|
442
|
+
all_unset: set[str] = set()
|
|
443
|
+
for names in cls._unset_env_keys_by_server.values():
|
|
444
|
+
all_unset.update(names)
|
|
445
|
+
sorted_unset = sorted(all_unset)
|
|
446
|
+
export_hint = " ".join(f"{name}=..." for name in sorted_unset)
|
|
447
|
+
count = len(sorted_unset)
|
|
448
|
+
noun = "variable" if count == 1 else "variables"
|
|
449
|
+
_emit_separator_once()
|
|
450
|
+
_rich_warning(
|
|
451
|
+
f"Copilot CLI will resolve {count} environment {noun} at runtime "
|
|
452
|
+
f"that {'is' if count == 1 else 'are'} not currently set: "
|
|
453
|
+
f"{', '.join(sorted_unset)}.\n"
|
|
454
|
+
f" Export {'it' if count == 1 else 'them'} in your shell before "
|
|
455
|
+
f"running 'gh copilot', e.g.:\n"
|
|
456
|
+
f" export {export_hint}",
|
|
457
|
+
symbol="warning",
|
|
458
|
+
)
|
|
459
|
+
# Deprecation notice is informational housekeeping (not a runtime
|
|
460
|
+
# blocker), but it ships unguarded for now so legacy <VAR> usage
|
|
461
|
+
# remains visible until the v1.0 removal. If --quiet gating is
|
|
462
|
+
# added in future, the unset-env and security warnings above must
|
|
463
|
+
# remain unsuppressible because they describe action-required state.
|
|
464
|
+
if cls._legacy_angle_offenders_by_server:
|
|
465
|
+
servers = sorted(cls._legacy_angle_offenders_by_server.keys())
|
|
466
|
+
count = len(servers)
|
|
467
|
+
noun = "server" if count == 1 else "servers"
|
|
468
|
+
_emit_separator_once()
|
|
469
|
+
_rich_warning(
|
|
470
|
+
f"Deprecated: <VAR> placeholder syntax used in {count} {noun} "
|
|
471
|
+
f"({', '.join(servers)}). Migrate to ${{VAR}} in apm.yml. "
|
|
472
|
+
f"<VAR> support will be removed in v1.0.",
|
|
473
|
+
symbol="warning",
|
|
474
|
+
)
|
|
475
|
+
cls._install_run_summary_emitted = True
|
|
476
|
+
|
|
477
|
+
@classmethod
|
|
478
|
+
def reset_install_run_state(cls):
|
|
479
|
+
"""Reset the process-wide aggregation buckets. Intended for tests
|
|
480
|
+
and for explicitly starting a new install run within the same
|
|
481
|
+
process."""
|
|
482
|
+
cls._legacy_angle_offenders_by_server = {}
|
|
483
|
+
cls._security_upgraded_keys = set()
|
|
484
|
+
cls._unset_env_keys_by_server = {}
|
|
485
|
+
cls._install_run_summary_emitted = False
|
|
486
|
+
|
|
184
487
|
def _format_server_config(self, server_info, env_overrides=None, runtime_vars=None):
|
|
185
488
|
"""Format server information into Copilot CLI MCP configuration format.
|
|
186
489
|
|
|
@@ -202,14 +505,27 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
202
505
|
"id": server_info.get("id", ""), # Add registry UUID for conflict detection
|
|
203
506
|
}
|
|
204
507
|
|
|
205
|
-
# Self-defined stdio deps carry raw command/args -- use directly
|
|
508
|
+
# Self-defined stdio deps carry raw command/args -- use directly,
|
|
509
|
+
# but route values through the env-var translation/resolution pipeline
|
|
510
|
+
# so secrets are not baked into the persisted config when the harness
|
|
511
|
+
# supports runtime substitution (Copilot CLI).
|
|
206
512
|
raw = server_info.get("_raw_stdio")
|
|
207
513
|
if raw:
|
|
208
514
|
config["command"] = raw["command"]
|
|
209
|
-
|
|
515
|
+
resolved_env_for_args = {}
|
|
210
516
|
if raw.get("env"):
|
|
211
|
-
|
|
517
|
+
resolved_env_for_args = self._resolve_environment_variables(
|
|
518
|
+
raw["env"], env_overrides=env_overrides
|
|
519
|
+
)
|
|
520
|
+
config["env"] = resolved_env_for_args
|
|
212
521
|
self._warn_input_variables(raw["env"], server_info.get("name", ""), "Copilot CLI")
|
|
522
|
+
args = raw.get("args") or []
|
|
523
|
+
config["args"] = [
|
|
524
|
+
self._resolve_variable_placeholders(arg, resolved_env_for_args, runtime_vars)
|
|
525
|
+
if isinstance(arg, str)
|
|
526
|
+
else arg
|
|
527
|
+
for arg in args
|
|
528
|
+
]
|
|
213
529
|
# Apply tools override if present
|
|
214
530
|
tools_override = server_info.get("_apm_tools_override")
|
|
215
531
|
if tools_override:
|
|
@@ -385,15 +701,93 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
385
701
|
return config
|
|
386
702
|
|
|
387
703
|
def _resolve_environment_variables(self, env_vars, env_overrides=None):
|
|
388
|
-
"""Resolve
|
|
704
|
+
"""Resolve (or translate) declared environment variables.
|
|
705
|
+
|
|
706
|
+
Behaviour depends on ``self._supports_runtime_env_substitution``:
|
|
707
|
+
|
|
708
|
+
- True (Copilot CLI default): each declared env var ``NAME`` gets a
|
|
709
|
+
``${NAME}`` placeholder that Copilot CLI resolves at server-start
|
|
710
|
+
from the host environment. Hardcoded literal defaults
|
|
711
|
+
(``GITHUB_TOOLSETS``, ``GITHUB_DYNAMIC_TOOLSETS``) stay literal
|
|
712
|
+
because they are not secrets and provide essential server
|
|
713
|
+
configuration. The host environment is NOT read; secrets never
|
|
714
|
+
touch disk. See issue #1152 for context.
|
|
715
|
+
|
|
716
|
+
- False (legacy / sibling-adapter behaviour): resolve each variable
|
|
717
|
+
to its literal value via ``env_overrides`` -> ``os.environ`` ->
|
|
718
|
+
optional interactive prompt, baking the result into the config.
|
|
389
719
|
|
|
390
720
|
Args:
|
|
391
|
-
env_vars (list): List of environment variable definitions from
|
|
392
|
-
|
|
721
|
+
env_vars (list): List of environment variable definitions from
|
|
722
|
+
server info (each item is ``{name, description, required}``).
|
|
723
|
+
env_overrides (dict, optional): Pre-collected environment
|
|
724
|
+
variable overrides. Ignored in translate mode.
|
|
393
725
|
|
|
394
726
|
Returns:
|
|
395
|
-
dict:
|
|
727
|
+
dict: ``{name: value}`` -- placeholder string in translate mode,
|
|
728
|
+
literal value in legacy mode.
|
|
396
729
|
"""
|
|
730
|
+
# Hardcoded literal defaults that supply essential server behaviour
|
|
731
|
+
# rather than secrets. These stay literal in translate mode so that
|
|
732
|
+
# tool-selection still works without a user export step.
|
|
733
|
+
default_github_env = {"GITHUB_TOOLSETS": "context", "GITHUB_DYNAMIC_TOOLSETS": "1"}
|
|
734
|
+
|
|
735
|
+
# Self-defined stdio deps pass ``env`` as a plain dict
|
|
736
|
+
# ({NAME: value-or-placeholder}); registry-sourced deps pass a list
|
|
737
|
+
# of {name, description, required} dicts. Translate-mode handling
|
|
738
|
+
# for the dict shape: each value is either already a placeholder
|
|
739
|
+
# (translate it to the canonical ${VAR} form) or a literal (record
|
|
740
|
+
# the key as a placeholder reference and emit ${NAME} so the
|
|
741
|
+
# value never lands on disk). See issue #1152.
|
|
742
|
+
if isinstance(env_vars, dict) and self._supports_runtime_env_substitution:
|
|
743
|
+
translated = {}
|
|
744
|
+
placeholder_keys = []
|
|
745
|
+
for name, raw_value in env_vars.items():
|
|
746
|
+
if not name:
|
|
747
|
+
continue
|
|
748
|
+
if not isinstance(raw_value, str):
|
|
749
|
+
translated[name] = raw_value
|
|
750
|
+
continue
|
|
751
|
+
if _has_env_placeholder(raw_value):
|
|
752
|
+
self._last_legacy_angle_vars.update(_extract_legacy_angle_vars(raw_value))
|
|
753
|
+
translated[name] = _translate_env_placeholder(raw_value)
|
|
754
|
+
# Record every ${VAR} in the translated value (handles
|
|
755
|
+
# both ${env:VAR} -> ${VAR} and bare ${VAR} cases).
|
|
756
|
+
for match in _ENV_VAR_RE.finditer(translated[name]):
|
|
757
|
+
placeholder_keys.append(match.group(1))
|
|
758
|
+
elif name in default_github_env and raw_value == default_github_env[name]:
|
|
759
|
+
translated[name] = raw_value
|
|
760
|
+
else:
|
|
761
|
+
# Literal value present in apm.yml -- replace with a
|
|
762
|
+
# runtime placeholder so the secret never touches disk.
|
|
763
|
+
translated[name] = "${" + name + "}"
|
|
764
|
+
placeholder_keys.append(name)
|
|
765
|
+
self._last_env_placeholder_keys = set(placeholder_keys)
|
|
766
|
+
return translated
|
|
767
|
+
|
|
768
|
+
if self._supports_runtime_env_substitution:
|
|
769
|
+
resolved = {}
|
|
770
|
+
placeholder_keys = []
|
|
771
|
+
for env_var in env_vars:
|
|
772
|
+
if not isinstance(env_var, dict):
|
|
773
|
+
continue
|
|
774
|
+
name = env_var.get("name", "")
|
|
775
|
+
if not name:
|
|
776
|
+
continue
|
|
777
|
+
if name in default_github_env:
|
|
778
|
+
# Non-secret literal default -- preserve as-is.
|
|
779
|
+
resolved[name] = default_github_env[name]
|
|
780
|
+
else:
|
|
781
|
+
# Emit a runtime-substitution placeholder; Copilot CLI
|
|
782
|
+
# resolves ``${NAME}`` from the host environment at
|
|
783
|
+
# server-start. APM never reads or stores the value.
|
|
784
|
+
resolved[name] = "${" + name + "}"
|
|
785
|
+
placeholder_keys.append(name)
|
|
786
|
+
# Record for the post-install summary line and the
|
|
787
|
+
# security-improvement notice.
|
|
788
|
+
self._last_env_placeholder_keys = set(placeholder_keys)
|
|
789
|
+
return resolved
|
|
790
|
+
|
|
397
791
|
import os
|
|
398
792
|
import sys
|
|
399
793
|
|
|
@@ -416,10 +810,6 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
416
810
|
if not is_interactive:
|
|
417
811
|
skip_prompting = True
|
|
418
812
|
|
|
419
|
-
# Add default GitHub MCP server environment variables for essential functionality first
|
|
420
|
-
# This ensures variables have defaults when user provides empty values or they're optional
|
|
421
|
-
default_github_env = {"GITHUB_TOOLSETS": "context", "GITHUB_DYNAMIC_TOOLSETS": "1"}
|
|
422
|
-
|
|
423
813
|
# Track which variables were explicitly provided with empty values (user wants defaults)
|
|
424
814
|
empty_value_vars = set()
|
|
425
815
|
if env_overrides:
|
|
@@ -465,16 +855,42 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
465
855
|
return resolved
|
|
466
856
|
|
|
467
857
|
def _resolve_env_variable(self, name, value, env_overrides=None):
|
|
468
|
-
"""Resolve a single environment variable value.
|
|
858
|
+
"""Resolve (or translate) a single environment variable value.
|
|
859
|
+
|
|
860
|
+
Behaviour depends on ``self._supports_runtime_env_substitution``:
|
|
861
|
+
|
|
862
|
+
- True (Copilot CLI default): translate placeholders to Copilot CLI's
|
|
863
|
+
native runtime substitution syntax (``${VAR}``). The host
|
|
864
|
+
environment is NOT read; the secret never touches disk. See issue
|
|
865
|
+
#1152 for context. Legacy ``<VAR>`` offenders are tracked for the
|
|
866
|
+
aggregated deprecation warning emitted by
|
|
867
|
+
``configure_mcp_server``.
|
|
868
|
+
|
|
869
|
+
- False (legacy / sibling-adapter behaviour): resolve placeholders
|
|
870
|
+
to literal values via ``env_overrides`` -> ``os.environ`` ->
|
|
871
|
+
optional interactive prompt, baking the result into the config.
|
|
469
872
|
|
|
470
873
|
Args:
|
|
471
874
|
name (str): Environment variable name.
|
|
472
875
|
value (str): Environment variable value or placeholder.
|
|
473
|
-
env_overrides (dict, optional): Pre-collected environment
|
|
876
|
+
env_overrides (dict, optional): Pre-collected environment
|
|
877
|
+
variable overrides. Ignored in translate mode.
|
|
474
878
|
|
|
475
879
|
Returns:
|
|
476
|
-
str:
|
|
880
|
+
str: Translated placeholder (translate mode) or resolved
|
|
881
|
+
literal value (legacy mode).
|
|
477
882
|
"""
|
|
883
|
+
if self._supports_runtime_env_substitution:
|
|
884
|
+
# Track legacy <VAR> offenders for the aggregated deprecation
|
|
885
|
+
# warning. Translation itself is a pure-textual rewrite.
|
|
886
|
+
self._last_legacy_angle_vars.update(_extract_legacy_angle_vars(value))
|
|
887
|
+
# Track env-var names referenced via this header/value so the
|
|
888
|
+
# security-upgrade detector and per-server summary can see
|
|
889
|
+
# them (the env-block path tracks via _resolve_environment_variables).
|
|
890
|
+
for match in _ENV_VAR_RE.finditer(value):
|
|
891
|
+
self._last_env_placeholder_keys.add(match.group(1))
|
|
892
|
+
return _translate_env_placeholder(value)
|
|
893
|
+
|
|
478
894
|
import sys
|
|
479
895
|
|
|
480
896
|
from rich.prompt import Prompt
|
|
@@ -683,15 +1099,31 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
683
1099
|
return processed
|
|
684
1100
|
|
|
685
1101
|
def _resolve_variable_placeholders(self, value, resolved_env, runtime_vars):
|
|
686
|
-
"""Resolve
|
|
1102
|
+
"""Resolve runtime template variables and translate or resolve env-var
|
|
1103
|
+
placeholders in argument strings.
|
|
1104
|
+
|
|
1105
|
+
Behaviour depends on ``self._supports_runtime_env_substitution``:
|
|
1106
|
+
|
|
1107
|
+
- True (Copilot CLI default): env-var placeholders (``<VAR>``,
|
|
1108
|
+
``${VAR}``, ``${env:VAR}``) are translated to ``${VAR}`` for
|
|
1109
|
+
runtime substitution by Copilot CLI. APM template variables
|
|
1110
|
+
(``{runtime_var}``) are still resolved at install time because
|
|
1111
|
+
they are an APM-internal concept Copilot cannot interpret.
|
|
1112
|
+
|
|
1113
|
+
- False (legacy / sibling-adapter behaviour): legacy ``<VAR>``
|
|
1114
|
+
placeholders are resolved against ``resolved_env`` (the dict of
|
|
1115
|
+
literal env-var values), and ``{runtime_var}`` against
|
|
1116
|
+
``runtime_vars``. Newer ``${VAR}`` / ``${env:VAR}`` syntaxes are
|
|
1117
|
+
left as-is for backward compatibility.
|
|
687
1118
|
|
|
688
1119
|
Args:
|
|
689
|
-
value (str): Value that may contain placeholders
|
|
690
|
-
resolved_env (dict): Dictionary of resolved
|
|
1120
|
+
value (str): Value that may contain placeholders.
|
|
1121
|
+
resolved_env (dict): Dictionary of resolved env vars (legacy
|
|
1122
|
+
mode) or placeholder strings (translate mode).
|
|
691
1123
|
runtime_vars (dict): Dictionary of resolved runtime variables.
|
|
692
1124
|
|
|
693
1125
|
Returns:
|
|
694
|
-
str: Processed value with
|
|
1126
|
+
str: Processed value with placeholders translated or resolved.
|
|
695
1127
|
"""
|
|
696
1128
|
import re
|
|
697
1129
|
|
|
@@ -700,23 +1132,33 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
700
1132
|
|
|
701
1133
|
processed = str(value)
|
|
702
1134
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1135
|
+
if self._supports_runtime_env_substitution:
|
|
1136
|
+
# Track legacy <VAR> offenders before translating them away.
|
|
1137
|
+
self._last_legacy_angle_vars.update(_extract_legacy_angle_vars(processed))
|
|
1138
|
+
# Translate all three env-var placeholder syntaxes to ${VAR}.
|
|
1139
|
+
processed = _translate_env_placeholder(processed)
|
|
1140
|
+
else:
|
|
1141
|
+
# Replace <TOKEN_NAME> with actual values from resolved_env (for Docker env vars)
|
|
1142
|
+
env_pattern = r"<([A-Z_][A-Z0-9_]*)>"
|
|
1143
|
+
|
|
1144
|
+
def replace_env_var(match):
|
|
1145
|
+
env_name = match.group(1)
|
|
1146
|
+
return resolved_env.get(env_name, match.group(0)) # Return original if not found
|
|
1147
|
+
|
|
1148
|
+
processed = re.sub(env_pattern, replace_env_var, processed)
|
|
1149
|
+
|
|
1150
|
+
# Replace {runtime_var} with actual values from runtime_vars (for NPM args).
|
|
1151
|
+
# Negative lookbehind on `$` so we never re-substitute inside an already-translated
|
|
1152
|
+
# ${VAR} env placeholder (the brace is part of a Copilot CLI runtime substitution,
|
|
1153
|
+
# not an APM template variable).
|
|
1154
|
+
if runtime_vars:
|
|
1155
|
+
runtime_pattern = r"(?<!\$)\{([a-zA-Z_][a-zA-Z0-9_]*)\}"
|
|
1156
|
+
|
|
1157
|
+
def replace_runtime_var(match):
|
|
1158
|
+
var_name = match.group(1)
|
|
1159
|
+
return runtime_vars.get(var_name, match.group(0))
|
|
1160
|
+
|
|
1161
|
+
processed = re.sub(runtime_pattern, replace_runtime_var, processed)
|
|
720
1162
|
|
|
721
1163
|
return processed
|
|
722
1164
|
|