booking-agent-cli 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/PKG-INFO +25 -2
- booking_agent_cli-0.4.0/README.md +57 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/pyproject.toml +3 -1
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/__init__.py +1 -1
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/__init__.py +1 -1
- booking_agent_cli-0.2.0/src/booking_agent_cli/SKILL.md → booking_agent_cli-0.4.0/src/booking_agent_cli/bundled_skill.md +55 -12
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/flow.py +68 -2
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/update.py +59 -17
- booking_agent_cli-0.4.0/src/booking_agent_cli/install_integrity.py +68 -0
- booking_agent_cli-0.4.0/src/booking_agent_cli/provenance.py +328 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/session.py +18 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/skill_bundle.py +5 -3
- booking_agent_cli-0.4.0/src/booking_agent_cli/skill_integrity.py +162 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/support_report.py +2 -1
- booking_agent_cli-0.4.0/tests/integration/test_install_integrity.py +145 -0
- booking_agent_cli-0.4.0/tests/integration/test_skill_integrity.py +338 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_skill_portability.py +13 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_update_command.py +56 -3
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_wheel_contents.py +1 -1
- booking_agent_cli-0.4.0/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +66 -0
- booking_agent_cli-0.4.0/tests/unit/test_check_version.py +324 -0
- booking_agent_cli-0.4.0/tests/unit/test_provenance.py +341 -0
- booking_agent_cli-0.2.0/README.md +0 -35
- booking_agent_cli-0.2.0/tests/unit/test_check_version.py +0 -140
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/.gitignore +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/authorization.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/backend/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/backend/live.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/filter.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/google.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/oauth_listener.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/scope.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/circuit_breaker.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/config.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/defaults.env +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/discovery.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/headless.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_deadlines.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_retry.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_trace.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/merchant_pacing.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/platform.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/wire.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/namespace.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/observability.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/platform_profile.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/pop.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/profile_cache.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/registry_url.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/rest_client.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/secure_file.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/settings.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/signals.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/step_timing.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_capabilities.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_checkout.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_negotiation.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_profile.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent/usp_methods.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/activity_log.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/booking_poll.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/booking_summary.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_connection.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_event.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_preference.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/catalog.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/checkout_prereq.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/config.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/lowlevel.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/runtime.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/config_loader.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/engine.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/flow_timings.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/geo_eligibility.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/geocode.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/identity.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/link_cli.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/main.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/output.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/payment_helpers.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/payment_token.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/reschedule.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/runtime.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/runtime_prepare.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/search_area.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/support_bundle_schema.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/timezone_context.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/updates.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/.env.tests +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/conftest.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_latency_soak.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_staging_registry_integration.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_timezone_booking_e2e.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_ucp_native_e2e.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/e2e/ucp_native.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_activity_log.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_at_rest_hygiene.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_booking_rejection.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_bulk_cancel.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_connection.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_connector.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_flow.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_preference.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_checkout_signals.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_circuit_breaker.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_config.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_correlation_flow.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_declined_spt.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_discover_registry_rejection.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_evidence.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_gates.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_reschedule.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_step_timings.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_google_calendar_oauth.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_home_visit_address.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_http_trace.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_identity_logout.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_lazy_oauth.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_link_auth.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_link_cli_spt.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_namespace.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_oauth_listener.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_oauth_metadata.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_platform_key_pop.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_platform_profile.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_pop_keystore.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_profile_cache.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_release_hold_uses_delete.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_request_retry_semantics.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_runtime_prepare.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_search_area.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_session_compat.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_session_retention.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_support_report.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_timezone_context.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_cancel_result.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_pop.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_validate.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_profile.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/SOURCE +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/acp_booking_extension.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/availability.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/booking.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/calendar_freebusy.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/catalog.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/paid_bookings.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/profile.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/registry.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/rest_common.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/usp.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/waitlist.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/webhook_event.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/profiles.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/services.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/backend.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/link_cli.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/mock_calendar.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/pop_business.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/pop_transport.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/ucp_checkout_store.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/mock/ucp_server.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/offline_stack/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/support/offline_stack/stack.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/__init__.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_authorization_policy.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_booking_poll_credentials.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_booking_request_error.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_calendar_filter.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_catalog.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_conditional_writes.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_delivery_address.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_discovery.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_geo_eligibility.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_geocode.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_headless.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_http_deadlines.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_http_retry.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_live_search_registry.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_merchant_pacing.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_mock_support.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_money_path_events.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_observability.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_outbound_member_names.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_package_layering.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_filters.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_pricing.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_responses.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_url.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_release_notes.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_rest_client.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_scoped_method_authorization.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_session_credentials.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_spec_conformance.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_step_timing.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_capabilities.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_complete_outcome.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_create_price_guard.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_negotiation.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_updates.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/unit/test_wire_model_conformance.py +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/006-cross-key-replay.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/008-replayed-jti.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/009-aud-mismatch.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/010-alg-none.json +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/README.md +0 -0
- {booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/SOURCE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: booking-agent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library.
|
|
5
5
|
Project-URL: Homepage, https://github.com/wix/booking-agent
|
|
6
6
|
Project-URL: Documentation, https://github.com/wix/booking-agent/blob/master/booking-agent/README.md
|
|
@@ -27,6 +27,7 @@ Requires-Dist: click>=8.2
|
|
|
27
27
|
Requires-Dist: cryptography>=42.0
|
|
28
28
|
Requires-Dist: httpx>=0.27
|
|
29
29
|
Requires-Dist: pydantic>=2.6
|
|
30
|
+
Requires-Dist: pypi-attestations>=0.0.30
|
|
30
31
|
Requires-Dist: python-dotenv>=1.0
|
|
31
32
|
Requires-Dist: rich>=13.7
|
|
32
33
|
Requires-Dist: starlette>=0.37
|
|
@@ -63,9 +64,31 @@ pipx install booking-agent-cli
|
|
|
63
64
|
booking-agent --help
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
Every release carries a PEP 740 attestation from the `wix/booking-agent` release
|
|
68
|
+
workflow. Check a downloaded wheel with
|
|
69
|
+
`uvx pypi-attestations verify pypi --repository https://github.com/wix/booking-agent <wheel>`.
|
|
70
|
+
`booking-agent update` installs only verified wheels, and the CLI checks its own installed
|
|
71
|
+
files against the attested release before it books or pays.
|
|
72
|
+
|
|
73
|
+
Agents use the CLI through the Booking Agent skill, installed with
|
|
74
|
+
`npx skills add wix/booking-agent --skill booking-agent`, or taken from the canonical raw URL:
|
|
67
75
|
https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
|
|
68
76
|
|
|
77
|
+
Each release also publishes that skill at an immutable `skill-v<version>` tag and signs it with
|
|
78
|
+
the release workflow, so a harness can pin and verify the exact text it follows:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.50.0/booking-agent/skills/booking-agent/SKILL.md
|
|
82
|
+
gh attestation verify SKILL.md --repo wix/booking-agent \
|
|
83
|
+
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`flow init --skill-path <path>` then checks the file the agent actually loaded against the signed
|
|
87
|
+
copy inside the wheel; a modified file claiming the same version fails `skill_unverified` and
|
|
88
|
+
blocks booking and payment until `booking-agent skill print --output <path>` restores it. A copy
|
|
89
|
+
written to evade the check can omit `--skill-path`, so the first read is what the attestation and
|
|
90
|
+
the harness cover.
|
|
91
|
+
|
|
69
92
|
The skill also needs `link-cli` for buyer identity and payment approval; see the
|
|
70
93
|
skill for details.
|
|
71
94
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# booking-agent-cli
|
|
2
|
+
|
|
3
|
+
The `booking-agent` command books time-based services (haircuts, lessons,
|
|
4
|
+
repairs, classes, appointments) through the Universal Scheduling Protocol (USP),
|
|
5
|
+
paying from a Link wallet. It owns the booking state machine and refuses to
|
|
6
|
+
proceed when a human approval gate is skipped, so an AI agent can drive it safely.
|
|
7
|
+
|
|
8
|
+
The wheel ships two import packages:
|
|
9
|
+
|
|
10
|
+
- `booking_agent_cli` - the `booking-agent` command
|
|
11
|
+
- `booking_agent` - the USP/UCP protocol library (models, discovery, REST client)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Requires Python 3.10 or newer.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv tool install booking-agent-cli
|
|
19
|
+
# or
|
|
20
|
+
pipx install booking-agent-cli
|
|
21
|
+
|
|
22
|
+
booking-agent --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every release carries a PEP 740 attestation from the `wix/booking-agent` release
|
|
26
|
+
workflow. Check a downloaded wheel with
|
|
27
|
+
`uvx pypi-attestations verify pypi --repository https://github.com/wix/booking-agent <wheel>`.
|
|
28
|
+
`booking-agent update` installs only verified wheels, and the CLI checks its own installed
|
|
29
|
+
files against the attested release before it books or pays.
|
|
30
|
+
|
|
31
|
+
Agents use the CLI through the Booking Agent skill, installed with
|
|
32
|
+
`npx skills add wix/booking-agent --skill booking-agent`, or taken from the canonical raw URL:
|
|
33
|
+
https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
|
|
34
|
+
|
|
35
|
+
Each release also publishes that skill at an immutable `skill-v<version>` tag and signs it with
|
|
36
|
+
the release workflow, so a harness can pin and verify the exact text it follows:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.50.0/booking-agent/skills/booking-agent/SKILL.md
|
|
40
|
+
gh attestation verify SKILL.md --repo wix/booking-agent \
|
|
41
|
+
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`flow init --skill-path <path>` then checks the file the agent actually loaded against the signed
|
|
45
|
+
copy inside the wheel; a modified file claiming the same version fails `skill_unverified` and
|
|
46
|
+
blocks booking and payment until `booking-agent skill print --output <path>` restores it. A copy
|
|
47
|
+
written to evade the check can omit `--skill-path`, so the first read is what the attestation and
|
|
48
|
+
the harness cover.
|
|
49
|
+
|
|
50
|
+
The skill also needs `link-cli` for buyer identity and payment approval; see the
|
|
51
|
+
skill for details.
|
|
52
|
+
|
|
53
|
+
## Source and license
|
|
54
|
+
|
|
55
|
+
Source: https://github.com/wix/booking-agent (directory `booking-agent/`).
|
|
56
|
+
Licensed under the Apache License 2.0; see
|
|
57
|
+
https://github.com/wix/booking-agent/blob/master/LICENSE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "booking-agent-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -28,6 +28,8 @@ dependencies = [
|
|
|
28
28
|
"cryptography>=42.0",
|
|
29
29
|
"httpx>=0.27",
|
|
30
30
|
"pydantic>=2.6",
|
|
31
|
+
# PEP 740 provenance checks before an update installs and on first run (provenance.py).
|
|
32
|
+
"pypi-attestations>=0.0.30",
|
|
31
33
|
"python-dotenv>=1.0",
|
|
32
34
|
"rich>=13.7",
|
|
33
35
|
"starlette>=0.37",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.
|
|
2
|
+
version: 0.50.0
|
|
3
3
|
name: booking-agent
|
|
4
4
|
description: |
|
|
5
5
|
Books time-based services via USP from a Link wallet. Use when the user wants to book, schedule, reserve, or pay for a service from a business (haircut, lesson, repair, class, appointment, consult). The `booking-agent` CLI owns the booking state machine and refuses to proceed when human gates are skipped. Sign the user in to Link on your own, right after install and at the start of every booking (`booking-agent flow link-auth connect`, then start `booking-agent flow link-auth poll` in the background and post the sign-in URL in the same turn, exactly like `pay poll`); never wait for the user to ask to sign in or to say they approved. After `booking-agent flow business use`, follow `next_command` through `use-ucp-checkout-mode`; businesses without native checkout fail closed. Payment uses `booking-agent flow pay request` (default returns immediately with the approval URL), then start `booking-agent flow pay poll` in the background and post the approval URL to the user in the same turn; the poll finishes checkout once they approve. Confirm through `booking-agent flow book poll --wait`; never start an inbound webhook. Surface the approval URL exactly once. Personal identification (name, phone, email, address) must always come from the user's Link account via `link-cli`, never from chat or placeholders. Before any location-based search, the CLI proposes the Link shipping address and the user confirms it or gives another; online services skip this. Talk to the user in plain everyday language only; never expose CLI, UCP, auth, session, or other implementation details unless they explicitly ask.
|
|
@@ -18,8 +18,8 @@ metadata:
|
|
|
18
18
|
- booking-agent
|
|
19
19
|
- link-cli
|
|
20
20
|
install:
|
|
21
|
-
- kind:
|
|
22
|
-
package: booking-agent-cli
|
|
21
|
+
- kind: uv
|
|
22
|
+
package: "booking-agent-cli==0.4.0"
|
|
23
23
|
bins: [booking-agent]
|
|
24
24
|
user-invocable: true
|
|
25
25
|
---
|
|
@@ -208,18 +208,27 @@ Only then may you use terms like CLI, UCP, or session. Keep explanations brief.
|
|
|
208
208
|
|
|
209
209
|
## Installing
|
|
210
210
|
|
|
211
|
-
Install the `booking-agent` CLI
|
|
211
|
+
Install the `booking-agent` CLI release this skill was written for (package `booking-agent-cli` on
|
|
212
|
+
PyPI). Every release is signed by the official release workflow (PEP 740), so check the signature
|
|
213
|
+
before installing:
|
|
212
214
|
|
|
213
215
|
```bash
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
dir="$HOME/.booking-agent/verified-wheels"
|
|
217
|
+
uvx pip download "booking-agent-cli==0.4.0" --no-deps --only-binary :all: -d "$dir"
|
|
218
|
+
uvx pypi-attestations==0.0.30 verify pypi --repository https://github.com/wix/booking-agent \
|
|
219
|
+
"$dir/booking_agent_cli-0.4.0-py3-none-any.whl"
|
|
220
|
+
uv tool install --force "$dir/booking_agent_cli-0.4.0-py3-none-any.whl"
|
|
217
221
|
|
|
218
222
|
booking-agent --help
|
|
219
223
|
```
|
|
220
224
|
|
|
225
|
+
Install only when the verify step passes. If `uvx` is not available, install the same pinned
|
|
226
|
+
release with `uv tool install booking-agent-cli==0.4.0` or `pipx install booking-agent-cli==0.4.0`;
|
|
227
|
+
the CLI checks its own files against the signed release on the first `booking-agent flow init`
|
|
228
|
+
and refuses to book or pay if they do not match.
|
|
229
|
+
|
|
221
230
|
Harnesses that read the `install` block in this skill's metadata do this for you. If
|
|
222
|
-
`booking-agent --help` still does not run, run
|
|
231
|
+
`booking-agent --help` still does not run, run the commands above yourself; if that is not
|
|
223
232
|
possible in this runtime, ask the user to install the skill, then try again. To develop the CLI from
|
|
224
233
|
a checkout instead, see the [Booking Agent README](https://github.com/wix/booking-agent/blob/master/booking-agent/README.md).
|
|
225
234
|
|
|
@@ -248,11 +257,37 @@ for each extra cache folder the runtime asks you to keep (a relative persistent
|
|
|
248
257
|
under the root). When the runtime keeps its files anyway, or offers no surviving folder, skip the
|
|
249
258
|
command. Do not tell the user about it. If it fails, continue the booking without it.
|
|
250
259
|
|
|
260
|
+
### This skill file
|
|
261
|
+
|
|
262
|
+
Harnesses install this file with:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npx skills add wix/booking-agent --skill booking-agent
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
That always takes the latest skill. To take a fixed, published version instead, fetch it at the
|
|
269
|
+
release tag the official workflow signs it under, and check the signature before you follow it:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.50.0/booking-agent/skills/booking-agent/SKILL.md
|
|
273
|
+
gh attestation verify SKILL.md \
|
|
274
|
+
--repo wix/booking-agent \
|
|
275
|
+
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Follow the file only when the verify step passes. Every release also attaches that same `SKILL.md`
|
|
279
|
+
and its `SKILL.md.sigstore.json` bundle to the GitHub Release, so a runtime without `gh` can verify
|
|
280
|
+
the bundle offline instead.
|
|
281
|
+
|
|
282
|
+
Then pass the path you loaded this file from on every `flow init` (`--skill-path`, below). The CLI
|
|
283
|
+
compares it with the signed copy inside the installed CLI and refuses to book or pay when a file
|
|
284
|
+
claiming this same version does not match it.
|
|
285
|
+
|
|
251
286
|
### Updates (only with the user's yes)
|
|
252
287
|
|
|
253
288
|
The CLI decides whether a newer version is worth mentioning; you never decide from version
|
|
254
289
|
numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
|
|
255
|
-
0.
|
|
290
|
+
0.50.0`, from the frontmatter above), so the CLI can tell when these instructions and the
|
|
256
291
|
installed CLI no longer match.
|
|
257
292
|
|
|
258
293
|
- When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
|
|
@@ -482,12 +517,17 @@ have it). Do not invent a lookup.
|
|
|
482
517
|
**Step 1b — Create a new session:**
|
|
483
518
|
|
|
484
519
|
```bash
|
|
485
|
-
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.
|
|
520
|
+
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.50.0 \
|
|
521
|
+
--skill-path "<absolute path of the SKILL.md you loaded>"
|
|
486
522
|
```
|
|
487
523
|
|
|
488
524
|
Add `--capability <name>` once for each part of the request that clearly needs it: `calendar`
|
|
489
525
|
(they mention their calendar), `reschedule`, or `cancel`. Leave it out when unsure.
|
|
490
526
|
|
|
527
|
+
Pass `--skill-path` whenever you know where you loaded this file from, so the CLI can check it
|
|
528
|
+
against the signed copy it ships with. Leave it out when you do not know the path; never guess one
|
|
529
|
+
and never point it at a different file.
|
|
530
|
+
|
|
491
531
|
- If `init` returns `awaiting_user: update_consent`, no booking was started yet. Follow
|
|
492
532
|
[Updates](#updates-only-with-the-users-yes); after the answer, run `data.continue_command` to
|
|
493
533
|
start the booking.
|
|
@@ -1129,7 +1169,7 @@ booking-agent flow calendar create-event
|
|
|
1129
1169
|
Once the user has been told they are booked (and after Step 8 when it ran), run:
|
|
1130
1170
|
|
|
1131
1171
|
```bash
|
|
1132
|
-
booking-agent update check --after-booking --skill-version 0.
|
|
1172
|
+
booking-agent update check --after-booking --skill-version 0.50.0
|
|
1133
1173
|
```
|
|
1134
1174
|
|
|
1135
1175
|
If it returns `awaiting_user: update_consent`, ask once, as in [Updates](#updates-only-with-the-users-yes).
|
|
@@ -1247,7 +1287,10 @@ recover.
|
|
|
1247
1287
|
| `session_store_too_new` | Saved bookings on this device were written by a newer version of the assistant | Run `next_command` (`booking-agent update check`) and follow it; never delete or edit the saved state |
|
|
1248
1288
|
| `update_not_confirmed` | `update` ran without `--user-confirmed` | Ask the user first; pass `--user-confirmed` only after they agree |
|
|
1249
1289
|
| `update_session_in_progress` | `update` ran while a booking is unfinished | Finish or cancel the booking first, then offer the update again |
|
|
1250
|
-
| `update_failed` / `update_not_verified` | The update did not install
|
|
1290
|
+
| `update_failed` / `update_not_verified` | The update did not install, did not start, or could not be shown to come from the official release (`data.reason`); nothing unverified is installed | Tell the user the update did not take; carry on with the installed version |
|
|
1291
|
+
| `installation_unverified` | The installed CLI files do not match the signed official release, so it will not book or pay | Tell the user in plain words the assistant needs reinstalling; run `data.reinstall_command` only with their yes, then start the booking again. Never book another way |
|
|
1292
|
+
| `skill_unverified` | The SKILL.md you loaded does not match the signed skill this CLI was released with, so it will not book or pay | Follow `data.skill_text` from now on. Tell the user plainly that the assistant's instructions file was changed and ask whether to restore the official one; only with their yes run `data.replace_command`, then run `flow init` again with `--skill-path`. Never book another way |
|
|
1293
|
+
| `skill_path_invalid` | `--skill-path` named a file that is missing, unreadable, or not a booking-agent SKILL.md | Run `flow init` again with the real path of the file you loaded, or without `--skill-path` if you do not know it |
|
|
1251
1294
|
| `skill_output_refused` | `skill print --output` named a file that is not the loaded skill | Follow `data.skill_text` from `booking-agent skill print` instead |
|
|
1252
1295
|
|
|
1253
1296
|
### Notes to the assistant team
|
|
@@ -137,6 +137,8 @@ from ..runtime import (
|
|
|
137
137
|
resolve_backend,
|
|
138
138
|
resolve_calendar_service,
|
|
139
139
|
)
|
|
140
|
+
from .. import install_integrity
|
|
141
|
+
from .. import skill_integrity
|
|
140
142
|
from .. import reschedule as reschedule_rules
|
|
141
143
|
from .. import timezone_context as tz_context
|
|
142
144
|
from ..activity_log import record_command
|
|
@@ -310,6 +312,44 @@ def _emit_booking_create_error(
|
|
|
310
312
|
return typer.Exit(code=1)
|
|
311
313
|
|
|
312
314
|
|
|
315
|
+
def _require_verified_installation(store: Any, phase: str) -> None:
|
|
316
|
+
"""Refuse to go on when the installed files do not match the attested release."""
|
|
317
|
+
failure = install_integrity.check(store)
|
|
318
|
+
if failure is not None:
|
|
319
|
+
emit_error(
|
|
320
|
+
phase,
|
|
321
|
+
code=install_integrity.CODE,
|
|
322
|
+
message=install_integrity.error_message(),
|
|
323
|
+
data=install_integrity.error_data(failure),
|
|
324
|
+
)
|
|
325
|
+
raise typer.Exit(code=1)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _require_verified_skill(store: Any, phase: str, skill_path: Optional[str] = None) -> Optional[str]:
|
|
329
|
+
"""Refuse to go on while the loaded skill file differs from the bundled skill.
|
|
330
|
+
|
|
331
|
+
Returns the version the file at ``skill_path`` declares (None without a path).
|
|
332
|
+
"""
|
|
333
|
+
try:
|
|
334
|
+
failure, loaded_version = skill_integrity.check(store, skill_path)
|
|
335
|
+
except ValueError as exc:
|
|
336
|
+
emit_error(
|
|
337
|
+
phase,
|
|
338
|
+
code=skill_integrity.PATH_INVALID_CODE,
|
|
339
|
+
message=skill_integrity.path_invalid_message(str(exc)),
|
|
340
|
+
)
|
|
341
|
+
raise typer.Exit(code=1)
|
|
342
|
+
if failure is not None:
|
|
343
|
+
emit_error(
|
|
344
|
+
phase,
|
|
345
|
+
code=skill_integrity.CODE,
|
|
346
|
+
message=skill_integrity.error_message(),
|
|
347
|
+
data=skill_integrity.error_data(failure),
|
|
348
|
+
)
|
|
349
|
+
raise typer.Exit(code=1)
|
|
350
|
+
return loaded_version
|
|
351
|
+
|
|
352
|
+
|
|
313
353
|
# ---------------------------------------------------------------------------
|
|
314
354
|
# init / status
|
|
315
355
|
# ---------------------------------------------------------------------------
|
|
@@ -326,12 +366,19 @@ def init(
|
|
|
326
366
|
skill_version: Optional[str] = typer.Option(
|
|
327
367
|
None, "--skill-version", help="The version in the loaded skill's frontmatter.",
|
|
328
368
|
),
|
|
369
|
+
skill_path: Optional[str] = typer.Option(
|
|
370
|
+
None, "--skill-path",
|
|
371
|
+
help="The SKILL.md file the skill was loaded from; checked against the signed skill.",
|
|
372
|
+
),
|
|
329
373
|
capability: Optional[List[str]] = typer.Option(
|
|
330
374
|
None, "--capability", help="A capability the user's request needs; repeatable.",
|
|
331
375
|
),
|
|
332
376
|
) -> None:
|
|
333
377
|
"""Initialize a new booking session."""
|
|
334
378
|
store = load_store()
|
|
379
|
+
# Before the session checks: a skill the agent points us at is checked whatever the
|
|
380
|
+
# flow state, so a changed file is never masked by session_active.
|
|
381
|
+
loaded_version = _require_verified_skill(store, "INIT", skill_path)
|
|
335
382
|
active = get_active_session(store)
|
|
336
383
|
if active is not None and active.flow_phase not in TERMINAL_PHASES and not force:
|
|
337
384
|
emit_error(
|
|
@@ -346,7 +393,11 @@ def init(
|
|
|
346
393
|
)
|
|
347
394
|
raise typer.Exit(code=1)
|
|
348
395
|
|
|
349
|
-
|
|
396
|
+
_require_verified_installation(store, "INIT")
|
|
397
|
+
|
|
398
|
+
gate = _update_gate(
|
|
399
|
+
user_request, loaded_version or skill_version, capability or [], allow_duplicate, skill_path,
|
|
400
|
+
)
|
|
350
401
|
if gate is not None:
|
|
351
402
|
emit(gate)
|
|
352
403
|
return
|
|
@@ -423,7 +474,11 @@ def init(
|
|
|
423
474
|
|
|
424
475
|
|
|
425
476
|
def _update_gate(
|
|
426
|
-
user_request: str,
|
|
477
|
+
user_request: str,
|
|
478
|
+
skill_version: Optional[str],
|
|
479
|
+
capabilities: List[str],
|
|
480
|
+
allow_duplicate: bool,
|
|
481
|
+
skill_path: Optional[str] = None,
|
|
427
482
|
) -> Optional[AgentOutput]:
|
|
428
483
|
"""Ask about an update before a session exists, only for a mismatch or a ready release."""
|
|
429
484
|
from . import update as update_cmd
|
|
@@ -443,6 +498,8 @@ def _update_gate(
|
|
|
443
498
|
rerun = f"booking-agent flow init --user-request {shlex.quote(user_request)}"
|
|
444
499
|
if skill_version:
|
|
445
500
|
rerun += f" --skill-version {shlex.quote(skill_version)}"
|
|
501
|
+
if skill_path:
|
|
502
|
+
rerun += f" --skill-path {shlex.quote(skill_path)}"
|
|
446
503
|
for c in capabilities:
|
|
447
504
|
rerun += f" --capability {shlex.quote(c)}"
|
|
448
505
|
if allow_duplicate:
|
|
@@ -4300,6 +4357,9 @@ def book(
|
|
|
4300
4357
|
require_buyer_from_link_cli(session)
|
|
4301
4358
|
except GateError as e:
|
|
4302
4359
|
raise _handle_gate(session, e)
|
|
4360
|
+
if not dry_run:
|
|
4361
|
+
_require_verified_installation(load_store(), "BOOKED")
|
|
4362
|
+
_require_verified_skill(load_store(), "BOOKED")
|
|
4303
4363
|
|
|
4304
4364
|
backend = resolve_backend()
|
|
4305
4365
|
|
|
@@ -4533,6 +4593,12 @@ def book(
|
|
|
4533
4593
|
pay_app = typer.Typer(help="Link spend approval and UCP checkout completion.")
|
|
4534
4594
|
app.add_typer(pay_app, name="pay")
|
|
4535
4595
|
|
|
4596
|
+
|
|
4597
|
+
@pay_app.callback()
|
|
4598
|
+
def _pay_requires_verified_installation() -> None:
|
|
4599
|
+
_require_verified_installation(load_store(), "PAYMENT_PENDING")
|
|
4600
|
+
_require_verified_skill(load_store(), "PAYMENT_PENDING")
|
|
4601
|
+
|
|
4536
4602
|
_REBOOK_MSG = (
|
|
4537
4603
|
"Run `booking-agent flow pay rebook --service-id <id>` to create a fresh checkout, "
|
|
4538
4604
|
"then retry payment."
|
{booking_agent_cli-0.2.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/update.py
RENAMED
|
@@ -6,13 +6,14 @@ import os
|
|
|
6
6
|
import shutil
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
|
+
import tempfile
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import List, Optional, Tuple
|
|
11
12
|
|
|
12
13
|
import typer
|
|
13
14
|
|
|
14
15
|
from .. import __version__ as CLI_VERSION
|
|
15
|
-
from .. import updates
|
|
16
|
+
from .. import provenance, skill_integrity, updates
|
|
16
17
|
from ..output import AgentOutput, emit, emit_error
|
|
17
18
|
from ..session import (
|
|
18
19
|
TERMINAL_PHASES,
|
|
@@ -31,6 +32,7 @@ DECLINE_COMMAND = "booking-agent update decline --prompt-key"
|
|
|
31
32
|
SKILL_PRINT_COMMAND = "booking-agent skill print"
|
|
32
33
|
INSTALL_TIMEOUT_SECONDS = 600
|
|
33
34
|
VERIFY_TIMEOUT_SECONDS = 60
|
|
35
|
+
VERIFIED_WHEELS_DIR = "verified-wheels"
|
|
34
36
|
_MAX_ANSWERED = 50
|
|
35
37
|
|
|
36
38
|
|
|
@@ -82,6 +84,10 @@ def skill_print(
|
|
|
82
84
|
output.chmod(0o644)
|
|
83
85
|
except OSError:
|
|
84
86
|
pass
|
|
87
|
+
try:
|
|
88
|
+
skill_integrity.clear(load_store())
|
|
89
|
+
except SessionStoreTooNew:
|
|
90
|
+
pass
|
|
85
91
|
emit(AgentOutput(
|
|
86
92
|
phase="UPDATE",
|
|
87
93
|
prompt=(
|
|
@@ -97,7 +103,7 @@ def skill_print(
|
|
|
97
103
|
def _skill_target_error(path: Path) -> Optional[str]:
|
|
98
104
|
if path.name != "SKILL.md":
|
|
99
105
|
return "--output must name a SKILL.md file."
|
|
100
|
-
if path.is_symlink():
|
|
106
|
+
if path.is_symlink() or skill_integrity.is_source_checkout(path):
|
|
101
107
|
return (
|
|
102
108
|
"That skill file is a link into a source checkout, which updates with the "
|
|
103
109
|
"checkout; it is never overwritten."
|
|
@@ -313,6 +319,22 @@ def update(
|
|
|
313
319
|
))
|
|
314
320
|
return
|
|
315
321
|
|
|
322
|
+
try:
|
|
323
|
+
wheel = verified_wheel(target.cli_version)
|
|
324
|
+
except provenance.ProvenanceError as exc:
|
|
325
|
+
emit_error(
|
|
326
|
+
"UPDATE",
|
|
327
|
+
code="update_not_verified",
|
|
328
|
+
message=(
|
|
329
|
+
"The update could not be shown to come from the official Booking Agent "
|
|
330
|
+
"release, so nothing was installed. Tell the user in plain words that the "
|
|
331
|
+
"update was not installed, and carry on with the installed version."
|
|
332
|
+
),
|
|
333
|
+
data={"target_version": target.cli_version, "reason": exc.reason, "detail": exc.message},
|
|
334
|
+
)
|
|
335
|
+
raise typer.Exit(code=1)
|
|
336
|
+
|
|
337
|
+
commands = install_commands(target.cli_version, newest=target.key == newest, wheel=wheel)
|
|
316
338
|
ok, detail = _run(commands[installer], timeout=INSTALL_TIMEOUT_SECONDS)
|
|
317
339
|
if not ok:
|
|
318
340
|
emit_error(
|
|
@@ -340,6 +362,7 @@ def update(
|
|
|
340
362
|
)
|
|
341
363
|
raise typer.Exit(code=1)
|
|
342
364
|
|
|
365
|
+
_drop_other_wheels(wheel)
|
|
343
366
|
_remember(store, f"release:{target.cli_version}")
|
|
344
367
|
emit(AgentOutput(
|
|
345
368
|
phase="UPDATE",
|
|
@@ -370,19 +393,31 @@ def _remember(store, key: Optional[str]) -> None:
|
|
|
370
393
|
|
|
371
394
|
|
|
372
395
|
def is_editable_install() -> bool:
|
|
373
|
-
|
|
374
|
-
from importlib.metadata import distribution
|
|
396
|
+
return provenance.install_source() == provenance.INSTALL_EDITABLE
|
|
375
397
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
398
|
+
|
|
399
|
+
def verified_wheel(version: str) -> Path:
|
|
400
|
+
"""The PyPI wheel for ``version``, kept only once its PEP 740 provenance verifies.
|
|
401
|
+
|
|
402
|
+
It stays under the booking-agent home because uv and pipx record the file they
|
|
403
|
+
installed from.
|
|
404
|
+
"""
|
|
405
|
+
from booking_agent import settings
|
|
406
|
+
from booking_agent.secure_file import ensure_private_dir
|
|
407
|
+
|
|
408
|
+
folder = settings.booking_agent_home() / VERIFIED_WHEELS_DIR
|
|
409
|
+
ensure_private_dir(folder)
|
|
410
|
+
with tempfile.TemporaryDirectory(dir=folder) as tmp:
|
|
411
|
+
downloaded = provenance.download_verified_wheel(version, Path(tmp))
|
|
412
|
+
wheel = folder / downloaded.name
|
|
413
|
+
os.replace(downloaded, wheel)
|
|
414
|
+
return wheel
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _drop_other_wheels(keep: Path) -> None:
|
|
418
|
+
for old in keep.parent.glob("*.whl"):
|
|
419
|
+
if old != keep:
|
|
420
|
+
old.unlink(missing_ok=True)
|
|
386
421
|
|
|
387
422
|
|
|
388
423
|
def detect_installer() -> Optional[str]:
|
|
@@ -397,9 +432,16 @@ def detect_installer() -> Optional[str]:
|
|
|
397
432
|
return None
|
|
398
433
|
|
|
399
434
|
|
|
400
|
-
def install_commands(version: str, *, newest: bool) -> dict:
|
|
401
|
-
|
|
402
|
-
|
|
435
|
+
def install_commands(version: str, *, newest: bool, wheel: Optional[Path] = None) -> dict:
|
|
436
|
+
"""Install the verified ``wheel`` itself, so nothing is resolved from the index again.
|
|
437
|
+
|
|
438
|
+
Without a wheel (commands shown to the user to run by hand): ">=" leaves later
|
|
439
|
+
upgrades free; "==" holds back a newer release that is not ready.
|
|
440
|
+
"""
|
|
441
|
+
if wheel is not None:
|
|
442
|
+
spec = str(wheel)
|
|
443
|
+
else:
|
|
444
|
+
spec = f"{PACKAGE}>={version}" if newest else f"{PACKAGE}=={version}"
|
|
403
445
|
return {
|
|
404
446
|
"uv": ["uv", "tool", "install", "--force", spec],
|
|
405
447
|
"pipx": ["pipx", "install", "--force", spec],
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Whether this installation may book and take payments: the first-run provenance check.
|
|
2
|
+
|
|
3
|
+
The first ``flow init`` after an install or update checks the installed files against
|
|
4
|
+
the attested release (``provenance.verify_installed``) and remembers the outcome for
|
|
5
|
+
that exact installation (its ``RECORD`` fingerprint). A mismatch is remembered too, so
|
|
6
|
+
``flow book`` and every ``flow pay`` command refuse until the CLI is reinstalled. A
|
|
7
|
+
failure to reach PyPI decides nothing and is retried on the next check.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from . import __version__ as CLI_VERSION
|
|
14
|
+
from . import provenance
|
|
15
|
+
|
|
16
|
+
CODE = "installation_unverified"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _same_install(record: Any, fingerprint: Optional[str]) -> bool:
|
|
20
|
+
return (
|
|
21
|
+
isinstance(record, dict)
|
|
22
|
+
and record.get("version") == CLI_VERSION
|
|
23
|
+
and record.get("record_sha256") == fingerprint
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check(store: Any) -> Optional[Dict[str, Any]]:
|
|
28
|
+
"""The recorded failure for this installation, or None when it may proceed."""
|
|
29
|
+
from .session import save_store
|
|
30
|
+
|
|
31
|
+
fingerprint = provenance.installed_fingerprint()
|
|
32
|
+
if _same_install(store.install_integrity_failure, fingerprint):
|
|
33
|
+
return store.install_integrity_failure
|
|
34
|
+
if _same_install(store.install_verified, fingerprint):
|
|
35
|
+
return None
|
|
36
|
+
result = provenance.verify_installed()
|
|
37
|
+
if result.skipped is not None or result.transient:
|
|
38
|
+
return None
|
|
39
|
+
if result.ok:
|
|
40
|
+
store.install_verified = {"version": CLI_VERSION, "record_sha256": fingerprint}
|
|
41
|
+
store.install_integrity_failure = None
|
|
42
|
+
else:
|
|
43
|
+
store.install_integrity_failure = {
|
|
44
|
+
"version": CLI_VERSION,
|
|
45
|
+
"record_sha256": fingerprint,
|
|
46
|
+
"reason": result.reason,
|
|
47
|
+
"detail": result.message,
|
|
48
|
+
}
|
|
49
|
+
save_store(store)
|
|
50
|
+
return store.install_integrity_failure
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def error_message() -> str:
|
|
54
|
+
return (
|
|
55
|
+
"The booking assistant installed on this device does not match the official "
|
|
56
|
+
"release, so it will not book or take payments. Tell the user in plain words that "
|
|
57
|
+
"the assistant needs to be reinstalled before it can book, and reinstall it only "
|
|
58
|
+
"with their yes, by running data.reinstall_command. Do not try to book another way."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def error_data(failure: Dict[str, Any]) -> Dict[str, Any]:
|
|
63
|
+
return {
|
|
64
|
+
"reason": failure.get("reason"),
|
|
65
|
+
"detail": failure.get("detail"),
|
|
66
|
+
"installed_version": CLI_VERSION,
|
|
67
|
+
"reinstall_command": provenance.reinstall_command(),
|
|
68
|
+
}
|