agentnode-sdk 0.11.3__tar.gz → 0.11.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/CHANGELOG.md +18 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/PKG-INFO +1 -1
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/__init__.py +1 -1
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/main.py +2 -1
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/publish.py +39 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/pyproject.toml +1 -1
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_publish.py +90 -5
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/.env.example +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/.gitignore +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/README.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/REGISTRY_SIGNING_SPEC.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/THREAT_MODEL.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/TRUST_STACK.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode.lock +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/_fileutil.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/async_client.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/capability_graph.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/capability_taxonomy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/__init__.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/__main__.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/audit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/auth.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/cassette_audit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/commands.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/complements.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/init.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/mcp_commands.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/mcp_status.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/mcp_submit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/mcp_verify.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/output.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/record_cases.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/sandbox_commands.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/serve.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/setup_wizard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/smart_run.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/templates.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/validate.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/cli/verify_local.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/client.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/compatibility.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/config.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/credential_handle.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/credential_resolver.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/credential_store.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/detect.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/exceptions.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/guard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/input_guard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/installer.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/key_status.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/lock_integrity.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/mcp_server.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/models.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/planner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/policy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/references.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/registry_trust.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/resolve.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/resource_provider.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/risk_profile.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/run_log.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtime.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtimes/__init__.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtimes/agent_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtimes/python_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/runtimes/remote_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/sandbox/__init__.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/sandbox/backend.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/sandbox/container_backend.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/sandbox/policy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/sandbox/types.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/signature.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/signing_key.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/agentnode_sdk/skill.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/sandbox-image/Dockerfile +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/sandbox-image/README.md +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/analyze_scores.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/batch_verify.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/ci_smoke_test.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/generate_compatibility_artifacts.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/verify_toolcalls.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/scripts/weekly_retest.sh +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/__init__.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/conftest.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_agent_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_async_client.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_audit_ux.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_auto_upgrade_policy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_cli.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_cli_lock.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_cli_run_resolution.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_client.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_client_json_guard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_client_sprint_b.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_config.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_credential_handle.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_credential_integration.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_credential_resolver.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_credential_store.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_detect.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_detect_and_install.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_e2e_runtime.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_edge_cases.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_check.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_config_cache.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_policy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_preview.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_schema.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_set.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_status.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_tool_override.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_tool_override_audit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_tool_override_cli.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_guard_ux.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_input_guard.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_input_guard_escalation.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_install_hardening.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_installer_sprint_b.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_intelligence.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_key_status.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_llm_binding.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_llm_call_runlog.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_lock_integrity.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_lock_runtime.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_mcp_audit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_mcp_doctor.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_mcp_sandbox.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_mcp_server.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_observability.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_planner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_policy.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_policy_integration.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_prompt_specs.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_provider_matrix.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_references.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_registry_trust.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_remote_hardening.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_remote_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_resource_provider.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_resource_specs.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_risk_profile.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_run_log.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_runner.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_runtime.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_runtime_audit.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_sandbox_backend.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_sandbox_doctor.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_sandbox_gate.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_security_hardening.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_signature.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_signing_key.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_skill.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_smart.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_stability.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_toolpack_sandbox.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_v02.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_validate.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/test_validate_skill.py +0 -0
- {agentnode_sdk-0.11.3 → agentnode_sdk-0.11.4}/tests/validation_lockfile.json +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.4 — Publish confirm gate
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`agentnode publish` now asks for confirmation before publishing.** After the
|
|
8
|
+
preview, the command prompts `Publish <pkg>@<version> to <registry>? [y/N]`
|
|
9
|
+
(default No) and only uploads on explicit `y`. A new `--yes`/`-y` flag skips
|
|
10
|
+
the prompt for CI/automation. `--dry-run` is unchanged (never prompts, never
|
|
11
|
+
publishes). Prevents accidental publishes of the wrong package/version/folder.
|
|
12
|
+
|
|
13
|
+
### BREAKING / Upgrade Notes
|
|
14
|
+
|
|
15
|
+
- **Non-interactive publish now requires `--yes`.** Previously `agentnode publish`
|
|
16
|
+
in a non-interactive context (CI, piped stdin, or `AGENTNODE_NON_INTERACTIVE=1`)
|
|
17
|
+
uploaded silently. It now **refuses** without `--yes` and exits non-zero.
|
|
18
|
+
Automation that publishes must add `--yes`. Interactive use is unaffected
|
|
19
|
+
beyond the new prompt.
|
|
20
|
+
|
|
3
21
|
## 0.11.3 — Test hygiene + multi-tool run guidance
|
|
4
22
|
|
|
5
23
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnode-sdk
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.4
|
|
4
4
|
Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
|
|
5
5
|
Project-URL: Homepage, https://agentnode.net
|
|
6
6
|
Project-URL: Repository, https://github.com/agentnode-ai/agentnode
|
|
@@ -154,6 +154,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
154
154
|
publish_parser.add_argument("--dry-run", action="store_true", help="Validate and build without uploading")
|
|
155
155
|
publish_parser.add_argument("--skip-validate", action="store_true", help="Continue despite validation errors")
|
|
156
156
|
publish_parser.add_argument("--token", default=None, help="API key (default: AGENTNODE_API_KEY env)")
|
|
157
|
+
publish_parser.add_argument("--yes", "-y", action="store_true", help="Skip the confirmation prompt (required for non-interactive/CI publish)")
|
|
157
158
|
|
|
158
159
|
# record-cases
|
|
159
160
|
record_parser = sub.add_parser("record-cases", help="Record VCR cassettes for API verification cases")
|
|
@@ -338,7 +339,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
338
339
|
return commands.cmd_verify_local(args.path)
|
|
339
340
|
if args.command == "publish":
|
|
340
341
|
from agentnode_sdk.cli.publish import cmd_publish
|
|
341
|
-
return cmd_publish(args.path, dry_run=args.dry_run, skip_validate=args.skip_validate, token=args.token)
|
|
342
|
+
return cmd_publish(args.path, dry_run=args.dry_run, skip_validate=args.skip_validate, token=args.token, yes=args.yes)
|
|
342
343
|
if args.command == "record-cases":
|
|
343
344
|
return commands.cmd_record_cases(args.path, strict=args.strict)
|
|
344
345
|
if args.command == "inspect":
|
|
@@ -286,12 +286,38 @@ def _post_publish(
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
def _confirm_publish(
|
|
290
|
+
pkg_id: str, version: str, registry: str, *, yes: bool, interactive: bool
|
|
291
|
+
) -> bool:
|
|
292
|
+
"""Final consent gate before an external publish. Returns True to proceed.
|
|
293
|
+
|
|
294
|
+
- ``yes``: explicit bypass for CI/automation → proceed.
|
|
295
|
+
- not ``interactive``: refuse — never publish non-interactively without --yes.
|
|
296
|
+
- interactive: prompt ``[y/N]``, default No.
|
|
297
|
+
"""
|
|
298
|
+
if yes:
|
|
299
|
+
return True
|
|
300
|
+
if not interactive:
|
|
301
|
+
print(
|
|
302
|
+
" Refusing to publish non-interactively. "
|
|
303
|
+
"Re-run with --yes to confirm in CI/automation.",
|
|
304
|
+
file=sys.stderr,
|
|
305
|
+
)
|
|
306
|
+
return False
|
|
307
|
+
answer = input(f" Publish {pkg_id}@{version} to {registry}? [y/N] ").strip().lower()
|
|
308
|
+
if answer in ("y", "yes"):
|
|
309
|
+
return True
|
|
310
|
+
print(" Publish cancelled.")
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
|
|
289
314
|
def cmd_publish(
|
|
290
315
|
path_str: str,
|
|
291
316
|
*,
|
|
292
317
|
dry_run: bool = False,
|
|
293
318
|
skip_validate: bool = False,
|
|
294
319
|
token: str | None = None,
|
|
320
|
+
yes: bool = False,
|
|
295
321
|
) -> int:
|
|
296
322
|
"""Publish a package to the AgentNode registry."""
|
|
297
323
|
from agentnode_sdk.cli.validate import validate_package_dir
|
|
@@ -389,6 +415,19 @@ def cmd_publish(
|
|
|
389
415
|
)
|
|
390
416
|
return 1
|
|
391
417
|
|
|
418
|
+
# Publish confirm gate — publish is an external, hard-to-undo action.
|
|
419
|
+
# Detect interactivity robustly (CI/pipes have a non-TTY stdin even without
|
|
420
|
+
# AGENTNODE_NON_INTERACTIVE set); non-interactive requires explicit --yes.
|
|
421
|
+
registry = _resolve_api_base().replace("https://", "").replace("http://", "")
|
|
422
|
+
interactive = sys.stdin.isatty() and os.environ.get(
|
|
423
|
+
"AGENTNODE_NON_INTERACTIVE", ""
|
|
424
|
+
).lower() not in ("true", "1")
|
|
425
|
+
if not _confirm_publish(pkg_id, version, registry, yes=yes, interactive=interactive):
|
|
426
|
+
# interactive decline = clean cancel (0); non-interactive without --yes
|
|
427
|
+
# = refusal (1). When yes=True the gate always proceeds, so we never land
|
|
428
|
+
# here in that case.
|
|
429
|
+
return 0 if interactive else 1
|
|
430
|
+
|
|
392
431
|
try:
|
|
393
432
|
sig_block = _sign_for_publish(pkg_id, manifest, artifact_bytes)
|
|
394
433
|
manifest["_signatures"] = {"publisher": [sig_block]}
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentnode-sdk"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.4"
|
|
8
8
|
description = "Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -4,7 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
import io
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
import tarfile
|
|
9
|
+
import types
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from unittest.mock import patch, MagicMock
|
|
10
12
|
|
|
@@ -15,6 +17,7 @@ from agentnode_sdk.cli.publish import (
|
|
|
15
17
|
PUBLISH_EXCLUDE_FILES,
|
|
16
18
|
PUBLISH_EXCLUDE_SUFFIXES,
|
|
17
19
|
_build_artifact,
|
|
20
|
+
_confirm_publish,
|
|
18
21
|
_should_exclude,
|
|
19
22
|
cmd_publish,
|
|
20
23
|
)
|
|
@@ -259,7 +262,7 @@ class TestCmdPublish:
|
|
|
259
262
|
"message": "Published test-pack@1.0.0",
|
|
260
263
|
}
|
|
261
264
|
with patch("agentnode_sdk.cli.publish._post_publish", return_value=mock_resp):
|
|
262
|
-
rc = cmd_publish(str(tmp_path), token="test-key-123")
|
|
265
|
+
rc = cmd_publish(str(tmp_path), token="test-key-123", yes=True)
|
|
263
266
|
assert rc == 0
|
|
264
267
|
out = capsys.readouterr().out
|
|
265
268
|
assert "Published test-pack@1.0.0" in out
|
|
@@ -276,7 +279,7 @@ class TestCmdPublish:
|
|
|
276
279
|
"details": [],
|
|
277
280
|
}
|
|
278
281
|
with patch("agentnode_sdk.cli.publish._post_publish", return_value=mock_resp):
|
|
279
|
-
rc = cmd_publish(str(tmp_path), token="test-key-123")
|
|
282
|
+
rc = cmd_publish(str(tmp_path), token="test-key-123", yes=True)
|
|
280
283
|
assert rc == 1
|
|
281
284
|
out = capsys.readouterr().out
|
|
282
285
|
assert "already exists" in out
|
|
@@ -484,7 +487,7 @@ class TestCmdPublishSigning:
|
|
|
484
487
|
return mock_resp
|
|
485
488
|
|
|
486
489
|
with patch("agentnode_sdk.cli.publish._post_publish", side_effect=capture_post):
|
|
487
|
-
rc = cmd_publish(str(tmp_path), token="test-key")
|
|
490
|
+
rc = cmd_publish(str(tmp_path), token="test-key", yes=True)
|
|
488
491
|
|
|
489
492
|
assert rc == 0
|
|
490
493
|
assert "_signatures" in captured_manifest
|
|
@@ -517,7 +520,7 @@ class TestCmdPublishSigning:
|
|
|
517
520
|
return {"slug": "test-pack", "version": "1.0.0", "message": "ok"}
|
|
518
521
|
|
|
519
522
|
with patch("agentnode_sdk.cli.publish._post_publish", side_effect=capture_post):
|
|
520
|
-
rc = cmd_publish(str(tmp_path), token="test-key")
|
|
523
|
+
rc = cmd_publish(str(tmp_path), token="test-key", yes=True)
|
|
521
524
|
|
|
522
525
|
assert rc == 0
|
|
523
526
|
|
|
@@ -544,7 +547,7 @@ class TestCmdPublishSigning:
|
|
|
544
547
|
|
|
545
548
|
with patch("agentnode_sdk.cli.publish._sign_for_publish", side_effect=RuntimeError("key error")):
|
|
546
549
|
with patch("agentnode_sdk.cli.publish._post_publish", return_value=mock_resp):
|
|
547
|
-
rc = cmd_publish(str(tmp_path), token="test-key")
|
|
550
|
+
rc = cmd_publish(str(tmp_path), token="test-key", yes=True)
|
|
548
551
|
|
|
549
552
|
assert rc == 0
|
|
550
553
|
err = capsys.readouterr().err
|
|
@@ -591,3 +594,85 @@ class TestPublisherSlugRegressionGuards:
|
|
|
591
594
|
from agentnode_sdk.cli.publish import manifest_to_entry
|
|
592
595
|
entry = manifest_to_entry(MINIMAL_MANIFEST, "sha256:abc123")
|
|
593
596
|
assert "publisher_slug" not in entry
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
# ---------------------------------------------------------------------------
|
|
600
|
+
# Publish confirm gate (0.11.4)
|
|
601
|
+
# ---------------------------------------------------------------------------
|
|
602
|
+
|
|
603
|
+
class TestConfirmPublishHelper:
|
|
604
|
+
"""The decision logic in isolation — the security-critical part."""
|
|
605
|
+
|
|
606
|
+
def test_yes_bypasses(self):
|
|
607
|
+
assert _confirm_publish("p", "1.0", "reg", yes=True, interactive=False) is True
|
|
608
|
+
|
|
609
|
+
def test_non_interactive_without_yes_refuses(self, capsys):
|
|
610
|
+
assert _confirm_publish("p", "1.0", "reg", yes=False, interactive=False) is False
|
|
611
|
+
assert "non-interactively" in capsys.readouterr().err
|
|
612
|
+
|
|
613
|
+
def test_interactive_y_proceeds(self, monkeypatch):
|
|
614
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "y")
|
|
615
|
+
assert _confirm_publish("p", "1.0", "reg", yes=False, interactive=True) is True
|
|
616
|
+
|
|
617
|
+
def test_interactive_yes_word_proceeds(self, monkeypatch):
|
|
618
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "YES")
|
|
619
|
+
assert _confirm_publish("p", "1.0", "reg", yes=False, interactive=True) is True
|
|
620
|
+
|
|
621
|
+
def test_interactive_n_cancels(self, monkeypatch, capsys):
|
|
622
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "n")
|
|
623
|
+
assert _confirm_publish("p", "1.0", "reg", yes=False, interactive=True) is False
|
|
624
|
+
assert "cancelled" in capsys.readouterr().out
|
|
625
|
+
|
|
626
|
+
def test_interactive_enter_default_cancels(self, monkeypatch):
|
|
627
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "")
|
|
628
|
+
assert _confirm_publish("p", "1.0", "reg", yes=False, interactive=True) is False
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
class TestCmdPublishConfirmGate:
|
|
632
|
+
"""End-to-end wiring: the gate must sit before _post_publish."""
|
|
633
|
+
|
|
634
|
+
def test_non_interactive_without_yes_refuses(self, tmp_path, capsys, monkeypatch):
|
|
635
|
+
_write_manifest(tmp_path)
|
|
636
|
+
(tmp_path / "tools.py").write_text("def run(): pass")
|
|
637
|
+
# force non-tty deterministically (independent of pytest -s)
|
|
638
|
+
monkeypatch.setattr(sys, "stdin", types.SimpleNamespace(isatty=lambda: False))
|
|
639
|
+
with patch("agentnode_sdk.cli.publish._post_publish") as mock_post:
|
|
640
|
+
rc = cmd_publish(str(tmp_path), token="test-key") # no --yes
|
|
641
|
+
assert rc == 1
|
|
642
|
+
mock_post.assert_not_called()
|
|
643
|
+
assert "non-interactively" in capsys.readouterr().err
|
|
644
|
+
|
|
645
|
+
def test_non_interactive_with_yes_publishes(self, tmp_path, monkeypatch):
|
|
646
|
+
monkeypatch.setenv("AGENTNODE_CONFIG", str(tmp_path / "config"))
|
|
647
|
+
_write_manifest(tmp_path)
|
|
648
|
+
(tmp_path / "tools.py").write_text("def run(): pass")
|
|
649
|
+
monkeypatch.setattr(sys, "stdin", types.SimpleNamespace(isatty=lambda: False))
|
|
650
|
+
resp = {"slug": "test-pack", "version": "1.0.0", "message": "Published"}
|
|
651
|
+
with patch("agentnode_sdk.cli.publish._post_publish", return_value=resp) as mock_post:
|
|
652
|
+
rc = cmd_publish(str(tmp_path), token="test-key", yes=True)
|
|
653
|
+
assert rc == 0
|
|
654
|
+
mock_post.assert_called_once()
|
|
655
|
+
|
|
656
|
+
def test_interactive_decline_does_not_publish(self, tmp_path, monkeypatch):
|
|
657
|
+
_write_manifest(tmp_path)
|
|
658
|
+
(tmp_path / "tools.py").write_text("def run(): pass")
|
|
659
|
+
monkeypatch.delenv("AGENTNODE_NON_INTERACTIVE", raising=False)
|
|
660
|
+
monkeypatch.setattr(sys, "stdin", types.SimpleNamespace(isatty=lambda: True))
|
|
661
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "n")
|
|
662
|
+
with patch("agentnode_sdk.cli.publish._post_publish") as mock_post:
|
|
663
|
+
rc = cmd_publish(str(tmp_path), token="test-key")
|
|
664
|
+
assert rc == 0
|
|
665
|
+
mock_post.assert_not_called()
|
|
666
|
+
|
|
667
|
+
def test_interactive_accept_publishes(self, tmp_path, monkeypatch):
|
|
668
|
+
monkeypatch.setenv("AGENTNODE_CONFIG", str(tmp_path / "config"))
|
|
669
|
+
_write_manifest(tmp_path)
|
|
670
|
+
(tmp_path / "tools.py").write_text("def run(): pass")
|
|
671
|
+
monkeypatch.delenv("AGENTNODE_NON_INTERACTIVE", raising=False)
|
|
672
|
+
monkeypatch.setattr(sys, "stdin", types.SimpleNamespace(isatty=lambda: True))
|
|
673
|
+
monkeypatch.setattr("builtins.input", lambda *a, **k: "y")
|
|
674
|
+
resp = {"slug": "test-pack", "version": "1.0.0", "message": "Published"}
|
|
675
|
+
with patch("agentnode_sdk.cli.publish._post_publish", return_value=resp) as mock_post:
|
|
676
|
+
rc = cmd_publish(str(tmp_path), token="test-key")
|
|
677
|
+
assert rc == 0
|
|
678
|
+
mock_post.assert_called_once()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|