apm-cli 0.9.2__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.2/src/apm_cli.egg-info → apm_cli-0.9.3}/PKG-INFO +5 -5
- {apm_cli-0.9.2 → apm_cli-0.9.3}/README.md +4 -4
- {apm_cli-0.9.2 → apm_cli-0.9.3}/pyproject.toml +1 -1
- {apm_cli-0.9.2 → 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.2 → apm_cli-0.9.3}/src/apm_cli/bundle/lockfile_enrichment.py +11 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/bundle/packer.py +33 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/cli.py +2 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/_helpers.py +26 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/compile/cli.py +9 -4
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/deps/_utils.py +16 -1
- {apm_cli-0.9.2 → 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.2 → apm_cli-0.9.3}/src/apm_cli/commands/install.py +6 -2
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/runtime.py +2 -2
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/engine.py +3 -3
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/update.py +11 -1
- {apm_cli-0.9.2 → 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.2 → apm_cli-0.9.3}/src/apm_cli/constants.py +22 -0
- apm_cli-0.9.3/src/apm_cli/core/experimental.py +266 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/script_runner.py +37 -16
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/target_detection.py +35 -73
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/lockfile.py +53 -19
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/package_validator.py +45 -37
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/factory.py +2 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/context.py +7 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/errors.py +10 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/cleanup.py +7 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/integrate.py +25 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/policy_gate.py +12 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/pipeline.py +17 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/base_integrator.py +7 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/command_integrator.py +40 -3
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/hook_integrator.py +70 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/instruction_integrator.py +2 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/mcp_integrator.py +63 -16
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/skill_integrator.py +17 -4
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/targets.py +26 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/apm_package.py +18 -1
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/validation.py +93 -3
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/ci_checks.py +140 -13
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/discovery.py +4 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/parser.py +45 -5
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/policy_checks.py +76 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/schema.py +1 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/primitives/discovery.py +120 -50
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/manager.py +14 -5
- {apm_cli-0.9.2 → 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.2 → apm_cli-0.9.3/src/apm_cli.egg-info}/PKG-INFO +5 -5
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli.egg-info/SOURCES.txt +5 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_apm_package_models.py +173 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/AUTHORS +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/LICENSE +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/setup.cfg +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/audit.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/marketplace.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/mcp.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/outdated.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/pack.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/policy.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/config.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/auth.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/azure_cli.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/core/token_manager.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/github_downloader.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/mcp_registry.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/mcp_warnings.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/policy_target_check.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/request.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/service.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/sources.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/install/validation.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/dependency/reference.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/install_preflight.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/outcome_routing.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/policy/project_config.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/version.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_console.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_lockfile.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.9.2 → apm_cli-0.9.3}/tests/test_token_manager.py +0 -0
- {apm_cli-0.9.2 → 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,9 @@ 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
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
@@ -112,7 +112,7 @@ apm install # every agent is configured
|
|
|
112
112
|
|
|
113
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
114
|
|
|
115
|
-
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
|
|
115
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex, Gemini
|
|
116
116
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
117
117
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
118
118
|
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
@@ -190,7 +190,7 @@ apm marketplace add github/awesome-copilot
|
|
|
190
190
|
apm install azure-cloud-development@awesome-copilot
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
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):
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
apm install --mcp io.github.github/github-mcp-server --transport http # connects over HTTPS
|
|
@@ -4,9 +4,9 @@
|
|
|
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
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -50,7 +50,7 @@ apm install # every agent is configured
|
|
|
50
50
|
|
|
51
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
52
|
|
|
53
|
-
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
|
|
53
|
+
- **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex, Gemini
|
|
54
54
|
- **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
|
|
55
55
|
- **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
|
|
56
56
|
- **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
|
|
@@ -128,7 +128,7 @@ apm marketplace add github/awesome-copilot
|
|
|
128
128
|
apm install azure-cloud-development@awesome-copilot
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
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):
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
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:
|
|
@@ -166,8 +166,10 @@ def _get_validation_suggestion(error_msg):
|
|
|
166
166
|
def _resolve_compile_target(target):
|
|
167
167
|
"""Map CLI target input to compiler-understood target string.
|
|
168
168
|
|
|
169
|
-
The compiler
|
|
170
|
-
Multi-target lists are mapped to the narrowest
|
|
169
|
+
The compiler understands ``"vscode"``, ``"claude"``, ``"gemini"``,
|
|
170
|
+
and ``"all"``. Multi-target lists are mapped to the narrowest
|
|
171
|
+
equivalent; any combination of two or more distinct compiler
|
|
172
|
+
families collapses to ``"all"``.
|
|
171
173
|
|
|
172
174
|
Args:
|
|
173
175
|
target: A single target string, a list of target strings, or ``None``.
|
|
@@ -179,15 +181,18 @@ def _resolve_compile_target(target):
|
|
|
179
181
|
return None # will trigger detect_target() auto-detection
|
|
180
182
|
if isinstance(target, list):
|
|
181
183
|
target_set = set(target)
|
|
182
|
-
# Any target that produces AGENTS.md (copilot/vscode/agents/cursor/opencode/codex)
|
|
183
184
|
has_agents_family = bool(
|
|
184
185
|
target_set & {"copilot", "vscode", "agents", "cursor", "opencode", "codex"}
|
|
185
186
|
)
|
|
186
187
|
has_claude = "claude" in target_set
|
|
187
|
-
|
|
188
|
+
has_gemini = "gemini" in target_set
|
|
189
|
+
distinct = sum([has_agents_family, has_claude, has_gemini])
|
|
190
|
+
if distinct >= 2:
|
|
188
191
|
return "all"
|
|
189
192
|
elif has_claude:
|
|
190
193
|
return "claude"
|
|
194
|
+
elif has_gemini:
|
|
195
|
+
return "gemini"
|
|
191
196
|
else:
|
|
192
197
|
return "vscode" # agents-family only
|
|
193
198
|
return target # single string pass-through
|
|
@@ -183,10 +183,25 @@ def _get_detailed_package_info(package_path: Path) -> Dict[str, Any]:
|
|
|
183
183
|
package = APMPackage.from_apm_yml(apm_yml_path)
|
|
184
184
|
context_count, workflow_count = _count_package_files(package_path)
|
|
185
185
|
primitives = _count_primitives(package_path)
|
|
186
|
+
# HYBRID-aware description rendering: when apm.yml omits its
|
|
187
|
+
# tagline but a SKILL.md sits alongside, surface the empty
|
|
188
|
+
# apm.yml.description as `--` plus an inline annotation. The
|
|
189
|
+
# SKILL.md description is intentionally NOT borrowed -- it is
|
|
190
|
+
# an agent invocation matcher, not a human tagline.
|
|
191
|
+
is_hybrid = (package_path / "SKILL.md").exists()
|
|
192
|
+
if package.description:
|
|
193
|
+
desc = package.description
|
|
194
|
+
elif is_hybrid:
|
|
195
|
+
desc = (
|
|
196
|
+
"-- (set 'description' in apm.yml; SKILL.md "
|
|
197
|
+
"description is for agent runtime)"
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
desc = 'No description'
|
|
186
201
|
return {
|
|
187
202
|
'name': package.name,
|
|
188
203
|
'version': package.version or 'unknown',
|
|
189
|
-
'description':
|
|
204
|
+
'description': desc,
|
|
190
205
|
'author': package.author or 'Unknown',
|
|
191
206
|
'source': package.source or 'local',
|
|
192
207
|
'install_path': str(package_path.resolve()),
|
|
@@ -336,7 +336,7 @@ def tree(global_):
|
|
|
336
336
|
if lockfile_path.exists():
|
|
337
337
|
lockfile = LockFile.read(lockfile_path)
|
|
338
338
|
if lockfile:
|
|
339
|
-
lockfile_deps = lockfile.
|
|
339
|
+
lockfile_deps = lockfile.get_package_dependencies()
|
|
340
340
|
except Exception:
|
|
341
341
|
pass
|
|
342
342
|
|