apm-cli 0.9.1__tar.gz → 0.9.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apm_cli-0.9.1/src/apm_cli.egg-info → apm_cli-0.9.3}/PKG-INFO +34 -9
- {apm_cli-0.9.1 → apm_cli-0.9.3}/README.md +33 -8
- {apm_cli-0.9.1 → apm_cli-0.9.3}/pyproject.toml +1 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/copilot.py +41 -6
- apm_cli-0.9.3/src/apm_cli/adapters/client/gemini.py +271 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/bundle/lockfile_enrichment.py +11 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/bundle/packer.py +33 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/cli.py +2 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/_helpers.py +26 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/compile/cli.py +9 -4
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/deps/_utils.py +16 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/deps/cli.py +1 -1
- apm_cli-0.9.3/src/apm_cli/commands/experimental.py +349 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/install.py +25 -2
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/runtime.py +2 -2
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/engine.py +3 -3
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/update.py +11 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/agents_compiler.py +65 -10
- apm_cli-0.9.3/src/apm_cli/compilation/gemini_formatter.py +119 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/constants.py +22 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/auth.py +354 -34
- apm_cli-0.9.3/src/apm_cli/core/azure_cli.py +311 -0
- apm_cli-0.9.3/src/apm_cli/core/experimental.py +266 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/script_runner.py +37 -16
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/target_detection.py +35 -73
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/token_manager.py +5 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/github_downloader.py +176 -12
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/lockfile.py +53 -19
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/package_validator.py +45 -37
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/factory.py +2 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/context.py +7 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/errors.py +10 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/cleanup.py +7 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/integrate.py +25 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/policy_gate.py +12 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/pipeline.py +17 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/validation.py +52 -3
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/base_integrator.py +7 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/command_integrator.py +40 -3
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/hook_integrator.py +70 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/instruction_integrator.py +2 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/mcp_integrator.py +63 -16
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/skill_integrator.py +17 -4
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/targets.py +26 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/apm_package.py +18 -1
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/validation.py +93 -3
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/ci_checks.py +140 -13
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/discovery.py +4 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/parser.py +45 -5
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/policy_checks.py +76 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/schema.py +1 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/primitives/discovery.py +120 -50
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/manager.py +14 -5
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/github_host.py +49 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/path_security.py +15 -2
- apm_cli-0.9.3/src/apm_cli/utils/subprocess_env.py +83 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3/src/apm_cli.egg-info}/PKG-INFO +34 -9
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli.egg-info/SOURCES.txt +6 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_apm_package_models.py +173 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/AUTHORS +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/LICENSE +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/setup.cfg +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/audit.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/marketplace.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/mcp.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/outdated.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/pack.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/policy.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/config.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/mcp_registry.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/mcp_warnings.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/policy_target_check.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/request.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/service.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/sources.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/dependency/reference.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/install_preflight.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/outcome_routing.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/policy/project_config.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/version.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_console.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_lockfile.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_token_manager.py +0 -0
- {apm_cli-0.9.1 → apm_cli-0.9.3}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apm-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: MCP configuration tool
|
|
5
5
|
Author-email: Daniel Meppiel <user@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -66,9 +66,14 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
|
|
68
68
|
|
|
69
|
-
GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
|
|
69
|
+
GitHub Copilot · Claude Code · Cursor · OpenCode · Codex · Gemini
|
|
70
70
|
|
|
71
|
-
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
|
|
71
|
+
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)** · **[Roadmap](https://github.com/orgs/microsoft/projects/2304)**
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
> **Portable by manifest. Secure by default. Governed by policy.**
|
|
76
|
+
> One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
|
|
72
77
|
|
|
73
78
|
## Why APM
|
|
74
79
|
|
|
@@ -101,17 +106,37 @@ git clone <org/repo> && cd <repo>
|
|
|
101
106
|
apm install # every agent is configured
|
|
102
107
|
```
|
|
103
108
|
|
|
104
|
-
##
|
|
109
|
+
## The three promises
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
### 1. Portable by manifest
|
|
112
|
+
|
|
113
|
+
One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
|
|
114
|
+
|
|
115
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex, Gemini
|
|
107
116
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
108
117
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
109
|
-
- **[
|
|
110
|
-
- **[
|
|
111
|
-
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
|
|
118
|
+
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
119
|
+
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
|
|
112
120
|
- **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
|
|
113
121
|
- **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
|
|
114
122
|
|
|
123
|
+
### 2. Secure by default
|
|
124
|
+
|
|
125
|
+
Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
|
|
126
|
+
|
|
127
|
+
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
|
|
128
|
+
- **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
|
|
129
|
+
- **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
|
|
130
|
+
|
|
131
|
+
### 3. Governed by policy
|
|
132
|
+
|
|
133
|
+
`apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
|
|
134
|
+
|
|
135
|
+
- **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
|
|
136
|
+
- **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
|
|
137
|
+
- **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
|
|
138
|
+
- **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
|
|
139
|
+
|
|
115
140
|
## Get Started
|
|
116
141
|
|
|
117
142
|
#### Linux / macOS
|
|
@@ -165,7 +190,7 @@ apm marketplace add github/awesome-copilot
|
|
|
165
190
|
apm install azure-cloud-development@awesome-copilot
|
|
166
191
|
```
|
|
167
192
|
|
|
168
|
-
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and
|
|
193
|
+
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, OpenCode, and Gemini):
|
|
169
194
|
|
|
170
195
|
```bash
|
|
171
196
|
apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Think `package.json`, `requirements.txt`, or `Cargo.toml` — but for AI agent configuration.
|
|
6
6
|
|
|
7
|
-
GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
|
|
7
|
+
GitHub Copilot · Claude Code · Cursor · OpenCode · Codex · Gemini
|
|
8
8
|
|
|
9
|
-
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
|
|
9
|
+
**[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)** · **[Roadmap](https://github.com/orgs/microsoft/projects/2304)**
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
> **Portable by manifest. Secure by default. Governed by policy.**
|
|
14
|
+
> One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
|
|
10
15
|
|
|
11
16
|
## Why APM
|
|
12
17
|
|
|
@@ -39,17 +44,37 @@ git clone <org/repo> && cd <repo>
|
|
|
39
44
|
apm install # every agent is configured
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
##
|
|
47
|
+
## The three promises
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
### 1. Portable by manifest
|
|
50
|
+
|
|
51
|
+
One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
|
|
52
|
+
|
|
53
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex, Gemini
|
|
45
54
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
46
55
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
47
|
-
- **[
|
|
48
|
-
- **[
|
|
49
|
-
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
|
|
56
|
+
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
57
|
+
- **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
|
|
50
58
|
- **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
|
|
51
59
|
- **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
|
|
52
60
|
|
|
61
|
+
### 2. Secure by default
|
|
62
|
+
|
|
63
|
+
Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
|
|
64
|
+
|
|
65
|
+
- **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
|
|
66
|
+
- **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
|
|
67
|
+
- **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
|
|
68
|
+
|
|
69
|
+
### 3. Governed by policy
|
|
70
|
+
|
|
71
|
+
`apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
|
|
72
|
+
|
|
73
|
+
- **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
|
|
74
|
+
- **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
|
|
75
|
+
- **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
|
|
76
|
+
- **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
|
|
77
|
+
|
|
53
78
|
## Get Started
|
|
54
79
|
|
|
55
80
|
#### Linux / macOS
|
|
@@ -103,7 +128,7 @@ apm marketplace add github/awesome-copilot
|
|
|
103
128
|
apm install azure-cloud-development@awesome-copilot
|
|
104
129
|
```
|
|
105
130
|
|
|
106
|
-
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, and
|
|
131
|
+
Or add an MCP server (wired into Copilot, Claude, Cursor, Codex, OpenCode, and Gemini):
|
|
107
132
|
|
|
108
133
|
```bash
|
|
109
134
|
apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
|
|
@@ -186,17 +186,36 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
186
186
|
# Check for remote endpoints first (registry-defined priority)
|
|
187
187
|
remotes = server_info.get("remotes", [])
|
|
188
188
|
if remotes:
|
|
189
|
-
#
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
# Select the first remote with a non-empty URL; fall back to the
|
|
190
|
+
# first entry so downstream code still emits the historical empty
|
|
191
|
+
# URL error path when no remote is usable.
|
|
192
|
+
remote = self._select_remote_with_url(remotes) or remotes[0]
|
|
193
|
+
|
|
194
|
+
# Validate transport_type from registry: default to "http" when
|
|
195
|
+
# missing/empty, raise ValueError for unsupported values. Mirrors
|
|
196
|
+
# the VS Code adapter check introduced in PR #656 so registry data
|
|
197
|
+
# with, e.g. transport_type="grpc" fails loudly instead of silently
|
|
198
|
+
# producing a garbage config.
|
|
199
|
+
transport = (remote.get("transport_type") or "").strip()
|
|
200
|
+
if not transport:
|
|
201
|
+
transport = "http"
|
|
202
|
+
elif transport not in ("sse", "http", "streamable-http"):
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"Unsupported remote transport '{transport}' for Copilot. "
|
|
205
|
+
f"Server: {server_info.get('name', 'unknown')}. "
|
|
206
|
+
f"Supported transports: http, sse, streamable-http."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Copilot CLI writes "type": "http" for all remote servers so
|
|
210
|
+
# authentication flows (headers) are consistent regardless of the
|
|
211
|
+
# underlying transport advertised by the registry.
|
|
193
212
|
config = {
|
|
194
213
|
"type": "http",
|
|
195
|
-
"url": remote.get("url"
|
|
214
|
+
"url": (remote.get("url") or "").strip(),
|
|
196
215
|
"tools": ["*"], # Required by Copilot CLI specification
|
|
197
216
|
"id": server_info.get("id", "") # Add registry UUID for conflict detection
|
|
198
217
|
}
|
|
199
|
-
|
|
218
|
+
|
|
200
219
|
# Add authentication headers for GitHub MCP server
|
|
201
220
|
server_name = server_info.get("name", "")
|
|
202
221
|
is_github_server = self._is_github_server(server_name, remote.get("url", ""))
|
|
@@ -649,6 +668,22 @@ class CopilotClientAdapter(MCPClientAdapter):
|
|
|
649
668
|
"""Legacy method for backward compatibility. Use _resolve_variable_placeholders instead."""
|
|
650
669
|
return self._resolve_variable_placeholders(value, resolved_env, {})
|
|
651
670
|
|
|
671
|
+
@staticmethod
|
|
672
|
+
def _select_remote_with_url(remotes):
|
|
673
|
+
"""Return the first remote entry that has a non-empty URL.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
remotes (list): Candidate remote entries from the registry.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
dict or None: The first usable remote, or None if none qualify.
|
|
680
|
+
"""
|
|
681
|
+
for remote in remotes:
|
|
682
|
+
url = (remote.get("url") or "").strip()
|
|
683
|
+
if url:
|
|
684
|
+
return remote
|
|
685
|
+
return None
|
|
686
|
+
|
|
652
687
|
def _select_best_package(self, packages):
|
|
653
688
|
"""Select the best package for installation from available packages.
|
|
654
689
|
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Gemini CLI implementation of MCP client adapter.
|
|
2
|
+
|
|
3
|
+
Gemini CLI uses ``.gemini/settings.json`` at the project root with an
|
|
4
|
+
``mcpServers`` key. Unlike Copilot, Gemini infers transport from which
|
|
5
|
+
key is present (``command`` for stdio, ``url`` for SSE, ``httpUrl`` for
|
|
6
|
+
streamable HTTP) and does not use ``type``, ``tools``, or ``id`` fields.
|
|
7
|
+
|
|
8
|
+
.. code-block:: json
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"server-name": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "@modelcontextprotocol/server-foo"],
|
|
15
|
+
"env": { "KEY": "value" }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
APM only writes to ``.gemini/settings.json`` when the ``.gemini/``
|
|
21
|
+
directory already exists -- Gemini CLI support is opt-in.
|
|
22
|
+
|
|
23
|
+
Ref: https://geminicli.com/docs/reference/configuration/
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from .copilot import CopilotClientAdapter
|
|
32
|
+
from ...core.docker_args import DockerArgsProcessor
|
|
33
|
+
from ...utils.console import _rich_error, _rich_success
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GeminiClientAdapter(CopilotClientAdapter):
|
|
39
|
+
"""Gemini CLI MCP client adapter.
|
|
40
|
+
|
|
41
|
+
Inherits Copilot's helper methods for package selection, env-var
|
|
42
|
+
resolution, and argument processing but fully reimplements
|
|
43
|
+
``_format_server_config`` to emit Gemini-valid JSON.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
supports_user_scope: bool = True
|
|
47
|
+
|
|
48
|
+
def get_config_path(self):
|
|
49
|
+
"""Return the path to ``.gemini/settings.json`` in the repository root."""
|
|
50
|
+
return str(Path(os.getcwd()) / ".gemini" / "settings.json")
|
|
51
|
+
|
|
52
|
+
def update_config(self, config_updates):
|
|
53
|
+
"""Merge *config_updates* into the ``mcpServers`` section of settings.json.
|
|
54
|
+
|
|
55
|
+
The ``.gemini/`` directory must already exist; if it does not, this
|
|
56
|
+
method returns silently (opt-in behaviour).
|
|
57
|
+
|
|
58
|
+
Preserves all other top-level keys in settings.json (theme, tools,
|
|
59
|
+
hooks, etc.).
|
|
60
|
+
"""
|
|
61
|
+
gemini_dir = Path(os.getcwd()) / ".gemini"
|
|
62
|
+
if not gemini_dir.is_dir():
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
config_path = Path(self.get_config_path())
|
|
66
|
+
current_config = self.get_current_config()
|
|
67
|
+
if "mcpServers" not in current_config:
|
|
68
|
+
current_config["mcpServers"] = {}
|
|
69
|
+
|
|
70
|
+
for name, entry in config_updates.items():
|
|
71
|
+
current_config["mcpServers"][name] = entry
|
|
72
|
+
|
|
73
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
75
|
+
json.dump(current_config, f, indent=2)
|
|
76
|
+
|
|
77
|
+
def get_current_config(self):
|
|
78
|
+
"""Read the current ``.gemini/settings.json`` contents."""
|
|
79
|
+
config_path = self.get_config_path()
|
|
80
|
+
if not os.path.exists(config_path):
|
|
81
|
+
return {}
|
|
82
|
+
try:
|
|
83
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
84
|
+
return json.load(f)
|
|
85
|
+
except (json.JSONDecodeError, IOError):
|
|
86
|
+
return {}
|
|
87
|
+
|
|
88
|
+
def _format_server_config(self, server_info, env_overrides=None, runtime_vars=None):
|
|
89
|
+
"""Format server info into Gemini CLI MCP configuration.
|
|
90
|
+
|
|
91
|
+
Gemini's schema differs from Copilot's:
|
|
92
|
+
- No ``type``, ``tools``, or ``id`` fields.
|
|
93
|
+
- Transport inferred from key: ``command`` (stdio), ``url`` (SSE),
|
|
94
|
+
``httpUrl`` (streamable HTTP).
|
|
95
|
+
- Tool filtering via ``includeTools``/``excludeTools``.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
server_info: Server information from registry.
|
|
99
|
+
env_overrides: Pre-collected environment variable overrides.
|
|
100
|
+
runtime_vars: Pre-collected runtime variable values.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
dict suitable for writing to ``.gemini/settings.json``.
|
|
104
|
+
"""
|
|
105
|
+
if runtime_vars is None:
|
|
106
|
+
runtime_vars = {}
|
|
107
|
+
|
|
108
|
+
config: dict = {}
|
|
109
|
+
|
|
110
|
+
# --- raw stdio (self-defined deps) ---
|
|
111
|
+
raw = server_info.get("_raw_stdio")
|
|
112
|
+
if raw:
|
|
113
|
+
config["command"] = raw["command"]
|
|
114
|
+
config["args"] = raw["args"]
|
|
115
|
+
if raw.get("env"):
|
|
116
|
+
config["env"] = raw["env"]
|
|
117
|
+
self._warn_input_variables(
|
|
118
|
+
raw["env"], server_info.get("name", ""), "Gemini CLI"
|
|
119
|
+
)
|
|
120
|
+
return config
|
|
121
|
+
|
|
122
|
+
# --- remote endpoints ---
|
|
123
|
+
remotes = server_info.get("remotes", [])
|
|
124
|
+
if remotes:
|
|
125
|
+
remote = self._select_remote_with_url(remotes) or remotes[0]
|
|
126
|
+
|
|
127
|
+
transport = (remote.get("transport_type") or "").strip()
|
|
128
|
+
if not transport:
|
|
129
|
+
transport = "http"
|
|
130
|
+
elif transport not in ("sse", "http", "streamable-http"):
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Unsupported remote transport '{transport}' for Gemini. "
|
|
133
|
+
f"Server: {server_info.get('name', 'unknown')}. "
|
|
134
|
+
f"Supported transports: http, sse, streamable-http."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
url = (remote.get("url") or "").strip()
|
|
138
|
+
if transport == "sse":
|
|
139
|
+
config["url"] = url
|
|
140
|
+
else:
|
|
141
|
+
config["httpUrl"] = url
|
|
142
|
+
|
|
143
|
+
# Registry-supplied headers
|
|
144
|
+
for header in remote.get("headers", []):
|
|
145
|
+
name = header.get("name", "")
|
|
146
|
+
value = header.get("value", "")
|
|
147
|
+
if name and value:
|
|
148
|
+
config.setdefault("headers", {})[name] = (
|
|
149
|
+
self._resolve_env_variable(name, value, env_overrides)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if config.get("headers"):
|
|
153
|
+
self._warn_input_variables(
|
|
154
|
+
config["headers"], server_info.get("name", ""), "Gemini CLI"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return config
|
|
158
|
+
|
|
159
|
+
# --- local packages ---
|
|
160
|
+
packages = server_info.get("packages", [])
|
|
161
|
+
|
|
162
|
+
if not packages:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"MCP server has no package information or remote endpoints. "
|
|
165
|
+
f"Server: {server_info.get('name', 'unknown')}"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
package = self._select_best_package(packages)
|
|
169
|
+
if not package:
|
|
170
|
+
return config
|
|
171
|
+
|
|
172
|
+
registry_name = self._infer_registry_name(package)
|
|
173
|
+
package_name = package.get("name", "")
|
|
174
|
+
runtime_hint = package.get("runtime_hint", "")
|
|
175
|
+
runtime_arguments = package.get("runtime_arguments", [])
|
|
176
|
+
package_arguments = package.get("package_arguments", [])
|
|
177
|
+
env_vars = package.get("environment_variables", [])
|
|
178
|
+
|
|
179
|
+
resolved_env = self._resolve_environment_variables(
|
|
180
|
+
env_vars, env_overrides
|
|
181
|
+
)
|
|
182
|
+
processed_rt = self._process_arguments(
|
|
183
|
+
runtime_arguments, resolved_env, runtime_vars
|
|
184
|
+
)
|
|
185
|
+
processed_pkg = self._process_arguments(
|
|
186
|
+
package_arguments, resolved_env, runtime_vars
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if registry_name == "npm":
|
|
190
|
+
config["command"] = runtime_hint or "npx"
|
|
191
|
+
config["args"] = ["-y", package_name] + processed_rt + processed_pkg
|
|
192
|
+
elif registry_name == "docker":
|
|
193
|
+
config["command"] = "docker"
|
|
194
|
+
if processed_rt:
|
|
195
|
+
config["args"] = self._inject_env_vars_into_docker_args(
|
|
196
|
+
processed_rt, resolved_env
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
config["args"] = DockerArgsProcessor.process_docker_args(
|
|
200
|
+
["run", "-i", "--rm", package_name], resolved_env
|
|
201
|
+
)
|
|
202
|
+
elif registry_name == "pypi":
|
|
203
|
+
config["command"] = runtime_hint or "uvx"
|
|
204
|
+
config["args"] = [package_name] + processed_rt + processed_pkg
|
|
205
|
+
elif registry_name == "homebrew":
|
|
206
|
+
config["command"] = (
|
|
207
|
+
package_name.split("/")[-1] if "/" in package_name else package_name
|
|
208
|
+
)
|
|
209
|
+
config["args"] = processed_rt + processed_pkg
|
|
210
|
+
else:
|
|
211
|
+
config["command"] = runtime_hint or package_name
|
|
212
|
+
config["args"] = processed_rt + processed_pkg
|
|
213
|
+
|
|
214
|
+
if resolved_env:
|
|
215
|
+
config["env"] = resolved_env
|
|
216
|
+
|
|
217
|
+
return config
|
|
218
|
+
|
|
219
|
+
def configure_mcp_server(
|
|
220
|
+
self,
|
|
221
|
+
server_url,
|
|
222
|
+
server_name=None,
|
|
223
|
+
enabled=True,
|
|
224
|
+
env_overrides=None,
|
|
225
|
+
server_info_cache=None,
|
|
226
|
+
runtime_vars=None,
|
|
227
|
+
):
|
|
228
|
+
"""Configure an MCP server in ``.gemini/settings.json``.
|
|
229
|
+
|
|
230
|
+
Delegates to the parent for config formatting, then writes to
|
|
231
|
+
the Gemini CLI settings file.
|
|
232
|
+
"""
|
|
233
|
+
if not server_url:
|
|
234
|
+
_rich_error("server_url cannot be empty", symbol="error")
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
gemini_dir = Path(os.getcwd()) / ".gemini"
|
|
238
|
+
if not gemini_dir.is_dir():
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
if server_info_cache and server_url in server_info_cache:
|
|
243
|
+
server_info = server_info_cache[server_url]
|
|
244
|
+
else:
|
|
245
|
+
server_info = self.registry_client.find_server_by_reference(server_url)
|
|
246
|
+
|
|
247
|
+
if not server_info:
|
|
248
|
+
_rich_error(f"MCP server '{server_url}' not found in registry", symbol="error")
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
if server_name:
|
|
252
|
+
config_key = server_name
|
|
253
|
+
elif "/" in server_url:
|
|
254
|
+
config_key = server_url.split("/")[-1]
|
|
255
|
+
else:
|
|
256
|
+
config_key = server_url
|
|
257
|
+
|
|
258
|
+
server_config = self._format_server_config(
|
|
259
|
+
server_info, env_overrides, runtime_vars
|
|
260
|
+
)
|
|
261
|
+
self.update_config({config_key: server_config})
|
|
262
|
+
|
|
263
|
+
_rich_success(
|
|
264
|
+
f"Configured MCP server '{config_key}' for Gemini CLI", symbol="success"
|
|
265
|
+
)
|
|
266
|
+
return True
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.debug("Gemini MCP configuration failed: %s", e)
|
|
270
|
+
_rich_error("Failed to configure MCP server for Gemini CLI", symbol="error")
|
|
271
|
+
return False
|
|
@@ -153,6 +153,17 @@ def enrich_lockfile_for_pack(
|
|
|
153
153
|
dep["deployed_files"] = filtered
|
|
154
154
|
all_mappings.update(mappings)
|
|
155
155
|
|
|
156
|
+
# Issue #887: strip packaging-time local-content fields from the bundle
|
|
157
|
+
# lockfile. ``local_deployed_files`` / ``local_deployed_file_hashes``
|
|
158
|
+
# describe the packager's own repo content, which is intentionally NOT
|
|
159
|
+
# shipped in the bundle (see packer.py source-local guard). Leaving them
|
|
160
|
+
# in the bundle lockfile would cause ``LockFile.from_yaml()`` on the
|
|
161
|
+
# consumer side to synthesize a self-entry whose ``deployed_files`` do
|
|
162
|
+
# not exist under the bundle source dir, breaking unpacker verification.
|
|
163
|
+
if isinstance(data, dict):
|
|
164
|
+
data.pop("local_deployed_files", None)
|
|
165
|
+
data.pop("local_deployed_file_hashes", None)
|
|
166
|
+
|
|
156
167
|
# Build the pack: metadata section (after filtering so we know if mapping
|
|
157
168
|
# occurred).
|
|
158
169
|
# Serialize target as a comma-joined string for backward compatibility
|
|
@@ -80,12 +80,38 @@ def pack_bundle(
|
|
|
80
80
|
|
|
81
81
|
# 2. Read apm.yml for name / version / config target
|
|
82
82
|
apm_yml_path = project_root / "apm.yml"
|
|
83
|
+
skill_md_path = project_root / "SKILL.md"
|
|
84
|
+
is_hybrid_root = apm_yml_path.exists() and skill_md_path.exists()
|
|
83
85
|
try:
|
|
84
86
|
package = APMPackage.from_apm_yml(apm_yml_path)
|
|
85
87
|
pkg_name = package.name
|
|
86
88
|
pkg_version = package.version or "0.0.0"
|
|
87
89
|
config_target = package.target
|
|
88
90
|
|
|
91
|
+
# HYBRID author guard: apm.yml.description and SKILL.md
|
|
92
|
+
# description serve different consumers (human-facing CLI/search
|
|
93
|
+
# vs. agent-runtime invocation matcher) and are NOT merged. If
|
|
94
|
+
# the author shipped a SKILL.md description but left
|
|
95
|
+
# apm.yml.description blank, the human-facing surfaces (apm view,
|
|
96
|
+
# apm search, marketplace listings) will degrade silently while
|
|
97
|
+
# Claude/Copilot still invoke the skill correctly. Warn loudly
|
|
98
|
+
# at pack time -- this is the publish gate for the AUTHOR.
|
|
99
|
+
if is_hybrid_root and not package.description and logger:
|
|
100
|
+
try:
|
|
101
|
+
import frontmatter as _frontmatter
|
|
102
|
+
with open(skill_md_path, "r", encoding="utf-8") as _f:
|
|
103
|
+
_skill_post = _frontmatter.load(_f)
|
|
104
|
+
_skill_desc = _skill_post.metadata.get("description")
|
|
105
|
+
except Exception:
|
|
106
|
+
_skill_desc = None
|
|
107
|
+
if _skill_desc:
|
|
108
|
+
logger.warning(
|
|
109
|
+
"apm.yml is missing 'description'. SKILL.md has its own "
|
|
110
|
+
"description, but that is for agent invocation -- not "
|
|
111
|
+
"for 'apm view' or search. Add a short tagline to "
|
|
112
|
+
"apm.yml: description: \"One-line human summary\""
|
|
113
|
+
)
|
|
114
|
+
|
|
89
115
|
# Guard: reject local-path dependencies (non-portable)
|
|
90
116
|
for dep_ref in package.get_apm_dependencies():
|
|
91
117
|
if dep_ref.is_local:
|
|
@@ -119,9 +145,15 @@ def pack_bundle(
|
|
|
119
145
|
if effective_target == "minimal":
|
|
120
146
|
effective_target = "all"
|
|
121
147
|
|
|
122
|
-
# 4. Collect deployed_files from all dependencies, filtered by target
|
|
148
|
+
# 4. Collect deployed_files from all dependencies, filtered by target.
|
|
149
|
+
# Skip local-source entries: these include the synthesized root self-entry
|
|
150
|
+
# (local_path == ".") and any local-path manifest deps. Local content is
|
|
151
|
+
# not portable and is bundled separately via the project's own files
|
|
152
|
+
# (or rejected outright at L89-97 for manifest-declared local deps).
|
|
123
153
|
all_deployed: List[str] = []
|
|
124
154
|
for dep in lockfile.get_all_dependencies():
|
|
155
|
+
if dep.source == "local":
|
|
156
|
+
continue
|
|
125
157
|
all_deployed.extend(dep.deployed_files)
|
|
126
158
|
|
|
127
159
|
filtered_files, path_mappings = _filter_files_by_target(all_deployed, effective_target)
|
|
@@ -20,6 +20,7 @@ from apm_cli.commands.audit import audit
|
|
|
20
20
|
from apm_cli.commands.compile import compile as compile_cmd
|
|
21
21
|
from apm_cli.commands.config import config
|
|
22
22
|
from apm_cli.commands.deps import deps
|
|
23
|
+
from apm_cli.commands.experimental import experimental
|
|
23
24
|
from apm_cli.commands.view import view as view_cmd
|
|
24
25
|
from apm_cli.commands.init import init
|
|
25
26
|
from apm_cli.commands.install import install
|
|
@@ -81,6 +82,7 @@ cli.add_command(run)
|
|
|
81
82
|
cli.add_command(preview)
|
|
82
83
|
cli.add_command(list_cmd, name="list")
|
|
83
84
|
cli.add_command(config)
|
|
85
|
+
cli.add_command(experimental)
|
|
84
86
|
cli.add_command(runtime)
|
|
85
87
|
cli.add_command(mcp)
|
|
86
88
|
cli.add_command(policy)
|
|
@@ -132,7 +132,7 @@ def _build_expected_install_paths(declared_deps, lockfile, apm_modules_dir: Path
|
|
|
132
132
|
expected.add(str(install_path))
|
|
133
133
|
|
|
134
134
|
if lockfile:
|
|
135
|
-
for dep in lockfile.
|
|
135
|
+
for dep in lockfile.get_package_dependencies():
|
|
136
136
|
if dep.depth is not None and dep.depth > 1:
|
|
137
137
|
dep_ref = dep.to_dependency_ref()
|
|
138
138
|
install_path = dep_ref.get_install_path(apm_modules_dir)
|
|
@@ -228,6 +228,25 @@ def print_version(ctx, param, value):
|
|
|
228
228
|
f"{TITLE}Agent Package Manager (APM) CLI{RESET} version {version_str}"
|
|
229
229
|
)
|
|
230
230
|
|
|
231
|
+
# Gated verbose-version output (experimental flag)
|
|
232
|
+
try:
|
|
233
|
+
from ..core.experimental import is_enabled
|
|
234
|
+
|
|
235
|
+
if is_enabled("verbose_version"):
|
|
236
|
+
import platform
|
|
237
|
+
import sys
|
|
238
|
+
|
|
239
|
+
python_ver = platform.python_version()
|
|
240
|
+
plat = f"{sys.platform}-{platform.machine()}"
|
|
241
|
+
install_path = str(Path(__file__).resolve().parent.parent)
|
|
242
|
+
|
|
243
|
+
_rich_echo(f" {'Python:':<14}{python_ver}", color="dim")
|
|
244
|
+
_rich_echo(f" {'Platform:':<14}{plat}", color="dim")
|
|
245
|
+
_rich_echo(f" {'Install path:':<14}{install_path}", color="dim")
|
|
246
|
+
except Exception:
|
|
247
|
+
# Never let experimental flag logic break --version
|
|
248
|
+
pass
|
|
249
|
+
|
|
231
250
|
ctx.exit()
|
|
232
251
|
|
|
233
252
|
|
|
@@ -462,6 +481,12 @@ def _create_minimal_apm_yml(config, plugin=False, target_path=None):
|
|
|
462
481
|
"description": config["description"],
|
|
463
482
|
"author": config["author"],
|
|
464
483
|
"dependencies": {"apm": [], "mcp": []},
|
|
484
|
+
# Issue #887: scaffold with explicit consent for local content
|
|
485
|
+
# deployment so day-2 audit doesn't surprise the maintainer with
|
|
486
|
+
# an "includes not declared" advisory the moment they drop a
|
|
487
|
+
# primitive in .apm/. Override with an explicit path list to
|
|
488
|
+
# gate what gets deployed.
|
|
489
|
+
"includes": "auto",
|
|
465
490
|
}
|
|
466
491
|
|
|
467
492
|
if plugin:
|