booking-agent-cli 0.3.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.3.0 → booking_agent_cli-0.4.0}/PKG-INFO +18 -2
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/README.md +17 -1
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/pyproject.toml +1 -1
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/__init__.py +1 -1
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/__init__.py +1 -1
- booking_agent_cli-0.3.0/src/booking_agent_cli/SKILL.md → booking_agent_cli-0.4.0/src/booking_agent_cli/bundled_skill.md +42 -9
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/flow.py +45 -2
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/update.py +6 -2
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/session.py +5 -0
- {booking_agent_cli-0.3.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.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/support_report.py +1 -0
- booking_agent_cli-0.4.0/tests/integration/test_skill_integrity.py +338 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_skill_portability.py +13 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_wheel_contents.py +1 -1
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_check_version.py +134 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/.gitignore +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/authorization.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/backend/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/backend/live.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/filter.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/google.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/oauth_listener.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/calendar/scope.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/circuit_breaker.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/config.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/defaults.env +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/discovery.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/headless.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_deadlines.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_retry.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/http_trace.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/merchant_pacing.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/platform.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/models/wire.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/namespace.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/observability.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/platform_profile.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/pop.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/profile_cache.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/registry_url.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/rest_client.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/secure_file.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/settings.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/signals.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/step_timing.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_capabilities.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_checkout.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_negotiation.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/ucp_profile.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent/usp_methods.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/activity_log.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/booking_poll.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/booking_summary.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_connection.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_event.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/calendar_preference.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/catalog.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/checkout_prereq.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/config.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/lowlevel.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/runtime.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/config_loader.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/engine.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/flow_timings.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/geo_eligibility.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/geocode.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/identity.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/install_integrity.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/link_cli.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/main.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/output.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/payment_helpers.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/payment_token.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/provenance.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/reschedule.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/runtime.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/runtime_prepare.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/search_area.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/support_bundle_schema.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/timezone_context.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/updates.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/.env.tests +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/conftest.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_latency_soak.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_staging_registry_integration.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_timezone_booking_e2e.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_ucp_native_e2e.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/e2e/ucp_native.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_activity_log.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_at_rest_hygiene.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_booking_rejection.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_bulk_cancel.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_connection.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_connector.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_flow.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_calendar_preference.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_checkout_signals.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_circuit_breaker.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_config.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_correlation_flow.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_declined_spt.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_discover_registry_rejection.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_evidence.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_gates.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_reschedule.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_flow_step_timings.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_google_calendar_oauth.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_home_visit_address.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_http_trace.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_identity_logout.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_install_integrity.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_lazy_oauth.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_link_auth.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_link_cli_spt.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_namespace.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_oauth_listener.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_oauth_metadata.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_platform_key_pop.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_platform_profile.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_pop_keystore.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_profile_cache.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_release_hold_uses_delete.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_request_retry_semantics.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_runtime_prepare.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_search_area.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_session_compat.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_session_retention.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_support_report.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_timezone_context.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_cancel_result.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_pop.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_checkout_validate.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_ucp_profile.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/integration/test_update_command.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/SOURCE +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/acp_booking_extension.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/availability.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/booking.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/calendar_freebusy.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/catalog.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/paid_bookings.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/profile.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/registry.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/rest_common.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/usp.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/waitlist.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/spec_schemas/webhook_event.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/profiles.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/fixtures/services.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/backend.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/link_cli.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/mock_calendar.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/pop_business.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/pop_transport.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/ucp_checkout_store.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/mock/ucp_server.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/offline_stack/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/support/offline_stack/stack.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/__init__.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_authorization_policy.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_booking_poll_credentials.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_booking_request_error.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_calendar_filter.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_catalog.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_conditional_writes.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_delivery_address.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_discovery.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_geo_eligibility.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_geocode.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_headless.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_http_deadlines.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_http_retry.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_live_search_registry.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_merchant_pacing.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_mock_support.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_money_path_events.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_observability.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_outbound_member_names.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_package_layering.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_provenance.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_filters.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_pricing.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_search_responses.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_registry_url.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_release_notes.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_rest_client.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_scoped_method_authorization.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_session_credentials.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_spec_conformance.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_step_timing.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_capabilities.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_complete_outcome.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_create_price_guard.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_ucp_negotiation.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_updates.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/unit/test_wire_model_conformance.py +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/006-cross-key-replay.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/008-replayed-jti.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/009-aud-mismatch.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/010-alg-none.json +0 -0
- {booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/tests/vectors/pop/README.md +0 -0
- {booking_agent_cli-0.3.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
|
|
@@ -70,9 +70,25 @@ workflow. Check a downloaded wheel with
|
|
|
70
70
|
`booking-agent update` installs only verified wheels, and the CLI checks its own installed
|
|
71
71
|
files against the attested release before it books or pays.
|
|
72
72
|
|
|
73
|
-
Agents use the CLI through the Booking Agent skill
|
|
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:
|
|
74
75
|
https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
|
|
75
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
|
+
|
|
76
92
|
The skill also needs `link-cli` for buyer identity and payment approval; see the
|
|
77
93
|
skill for details.
|
|
78
94
|
|
|
@@ -28,9 +28,25 @@ workflow. Check a downloaded wheel with
|
|
|
28
28
|
`booking-agent update` installs only verified wheels, and the CLI checks its own installed
|
|
29
29
|
files against the attested release before it books or pays.
|
|
30
30
|
|
|
31
|
-
Agents use the CLI through the Booking Agent skill
|
|
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:
|
|
32
33
|
https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
|
|
33
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
|
+
|
|
34
50
|
The skill also needs `link-cli` for buyer identity and payment approval; see the
|
|
35
51
|
skill for details.
|
|
36
52
|
|
|
@@ -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.
|
|
@@ -19,7 +19,7 @@ metadata:
|
|
|
19
19
|
- link-cli
|
|
20
20
|
install:
|
|
21
21
|
- kind: uv
|
|
22
|
-
package: "booking-agent-cli==0.
|
|
22
|
+
package: "booking-agent-cli==0.4.0"
|
|
23
23
|
bins: [booking-agent]
|
|
24
24
|
user-invocable: true
|
|
25
25
|
---
|
|
@@ -214,16 +214,16 @@ before installing:
|
|
|
214
214
|
|
|
215
215
|
```bash
|
|
216
216
|
dir="$HOME/.booking-agent/verified-wheels"
|
|
217
|
-
uvx pip download "booking-agent-cli==0.
|
|
217
|
+
uvx pip download "booking-agent-cli==0.4.0" --no-deps --only-binary :all: -d "$dir"
|
|
218
218
|
uvx pypi-attestations==0.0.30 verify pypi --repository https://github.com/wix/booking-agent \
|
|
219
|
-
"$dir/booking_agent_cli-0.
|
|
220
|
-
uv tool install --force "$dir/booking_agent_cli-0.
|
|
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"
|
|
221
221
|
|
|
222
222
|
booking-agent --help
|
|
223
223
|
```
|
|
224
224
|
|
|
225
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.
|
|
226
|
+
release with `uv tool install booking-agent-cli==0.4.0` or `pipx install booking-agent-cli==0.4.0`;
|
|
227
227
|
the CLI checks its own files against the signed release on the first `booking-agent flow init`
|
|
228
228
|
and refuses to book or pay if they do not match.
|
|
229
229
|
|
|
@@ -257,11 +257,37 @@ for each extra cache folder the runtime asks you to keep (a relative persistent
|
|
|
257
257
|
under the root). When the runtime keeps its files anyway, or offers no surviving folder, skip the
|
|
258
258
|
command. Do not tell the user about it. If it fails, continue the booking without it.
|
|
259
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
|
+
|
|
260
286
|
### Updates (only with the user's yes)
|
|
261
287
|
|
|
262
288
|
The CLI decides whether a newer version is worth mentioning; you never decide from version
|
|
263
289
|
numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
|
|
264
|
-
0.
|
|
290
|
+
0.50.0`, from the frontmatter above), so the CLI can tell when these instructions and the
|
|
265
291
|
installed CLI no longer match.
|
|
266
292
|
|
|
267
293
|
- When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
|
|
@@ -491,12 +517,17 @@ have it). Do not invent a lookup.
|
|
|
491
517
|
**Step 1b — Create a new session:**
|
|
492
518
|
|
|
493
519
|
```bash
|
|
494
|
-
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>"
|
|
495
522
|
```
|
|
496
523
|
|
|
497
524
|
Add `--capability <name>` once for each part of the request that clearly needs it: `calendar`
|
|
498
525
|
(they mention their calendar), `reschedule`, or `cancel`. Leave it out when unsure.
|
|
499
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
|
+
|
|
500
531
|
- If `init` returns `awaiting_user: update_consent`, no booking was started yet. Follow
|
|
501
532
|
[Updates](#updates-only-with-the-users-yes); after the answer, run `data.continue_command` to
|
|
502
533
|
start the booking.
|
|
@@ -1138,7 +1169,7 @@ booking-agent flow calendar create-event
|
|
|
1138
1169
|
Once the user has been told they are booked (and after Step 8 when it ran), run:
|
|
1139
1170
|
|
|
1140
1171
|
```bash
|
|
1141
|
-
booking-agent update check --after-booking --skill-version 0.
|
|
1172
|
+
booking-agent update check --after-booking --skill-version 0.50.0
|
|
1142
1173
|
```
|
|
1143
1174
|
|
|
1144
1175
|
If it returns `awaiting_user: update_consent`, ask once, as in [Updates](#updates-only-with-the-users-yes).
|
|
@@ -1258,6 +1289,8 @@ recover.
|
|
|
1258
1289
|
| `update_session_in_progress` | `update` ran while a booking is unfinished | Finish or cancel the booking first, then offer the update again |
|
|
1259
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 |
|
|
1260
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 |
|
|
1261
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 |
|
|
1262
1295
|
|
|
1263
1296
|
### Notes to the assistant team
|
|
@@ -138,6 +138,7 @@ from ..runtime import (
|
|
|
138
138
|
resolve_calendar_service,
|
|
139
139
|
)
|
|
140
140
|
from .. import install_integrity
|
|
141
|
+
from .. import skill_integrity
|
|
141
142
|
from .. import reschedule as reschedule_rules
|
|
142
143
|
from .. import timezone_context as tz_context
|
|
143
144
|
from ..activity_log import record_command
|
|
@@ -324,6 +325,31 @@ def _require_verified_installation(store: Any, phase: str) -> None:
|
|
|
324
325
|
raise typer.Exit(code=1)
|
|
325
326
|
|
|
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
|
+
|
|
327
353
|
# ---------------------------------------------------------------------------
|
|
328
354
|
# init / status
|
|
329
355
|
# ---------------------------------------------------------------------------
|
|
@@ -340,12 +366,19 @@ def init(
|
|
|
340
366
|
skill_version: Optional[str] = typer.Option(
|
|
341
367
|
None, "--skill-version", help="The version in the loaded skill's frontmatter.",
|
|
342
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
|
+
),
|
|
343
373
|
capability: Optional[List[str]] = typer.Option(
|
|
344
374
|
None, "--capability", help="A capability the user's request needs; repeatable.",
|
|
345
375
|
),
|
|
346
376
|
) -> None:
|
|
347
377
|
"""Initialize a new booking session."""
|
|
348
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)
|
|
349
382
|
active = get_active_session(store)
|
|
350
383
|
if active is not None and active.flow_phase not in TERMINAL_PHASES and not force:
|
|
351
384
|
emit_error(
|
|
@@ -362,7 +395,9 @@ def init(
|
|
|
362
395
|
|
|
363
396
|
_require_verified_installation(store, "INIT")
|
|
364
397
|
|
|
365
|
-
gate = _update_gate(
|
|
398
|
+
gate = _update_gate(
|
|
399
|
+
user_request, loaded_version or skill_version, capability or [], allow_duplicate, skill_path,
|
|
400
|
+
)
|
|
366
401
|
if gate is not None:
|
|
367
402
|
emit(gate)
|
|
368
403
|
return
|
|
@@ -439,7 +474,11 @@ def init(
|
|
|
439
474
|
|
|
440
475
|
|
|
441
476
|
def _update_gate(
|
|
442
|
-
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,
|
|
443
482
|
) -> Optional[AgentOutput]:
|
|
444
483
|
"""Ask about an update before a session exists, only for a mismatch or a ready release."""
|
|
445
484
|
from . import update as update_cmd
|
|
@@ -459,6 +498,8 @@ def _update_gate(
|
|
|
459
498
|
rerun = f"booking-agent flow init --user-request {shlex.quote(user_request)}"
|
|
460
499
|
if skill_version:
|
|
461
500
|
rerun += f" --skill-version {shlex.quote(skill_version)}"
|
|
501
|
+
if skill_path:
|
|
502
|
+
rerun += f" --skill-path {shlex.quote(skill_path)}"
|
|
462
503
|
for c in capabilities:
|
|
463
504
|
rerun += f" --capability {shlex.quote(c)}"
|
|
464
505
|
if allow_duplicate:
|
|
@@ -4318,6 +4359,7 @@ def book(
|
|
|
4318
4359
|
raise _handle_gate(session, e)
|
|
4319
4360
|
if not dry_run:
|
|
4320
4361
|
_require_verified_installation(load_store(), "BOOKED")
|
|
4362
|
+
_require_verified_skill(load_store(), "BOOKED")
|
|
4321
4363
|
|
|
4322
4364
|
backend = resolve_backend()
|
|
4323
4365
|
|
|
@@ -4555,6 +4597,7 @@ app.add_typer(pay_app, name="pay")
|
|
|
4555
4597
|
@pay_app.callback()
|
|
4556
4598
|
def _pay_requires_verified_installation() -> None:
|
|
4557
4599
|
_require_verified_installation(load_store(), "PAYMENT_PENDING")
|
|
4600
|
+
_require_verified_skill(load_store(), "PAYMENT_PENDING")
|
|
4558
4601
|
|
|
4559
4602
|
_REBOOK_MSG = (
|
|
4560
4603
|
"Run `booking-agent flow pay rebook --service-id <id>` to create a fresh checkout, "
|
{booking_agent_cli-0.3.0 → booking_agent_cli-0.4.0}/src/booking_agent_cli/commands/update.py
RENAMED
|
@@ -13,7 +13,7 @@ from typing import List, Optional, Tuple
|
|
|
13
13
|
import typer
|
|
14
14
|
|
|
15
15
|
from .. import __version__ as CLI_VERSION
|
|
16
|
-
from .. import provenance, updates
|
|
16
|
+
from .. import provenance, skill_integrity, updates
|
|
17
17
|
from ..output import AgentOutput, emit, emit_error
|
|
18
18
|
from ..session import (
|
|
19
19
|
TERMINAL_PHASES,
|
|
@@ -84,6 +84,10 @@ def skill_print(
|
|
|
84
84
|
output.chmod(0o644)
|
|
85
85
|
except OSError:
|
|
86
86
|
pass
|
|
87
|
+
try:
|
|
88
|
+
skill_integrity.clear(load_store())
|
|
89
|
+
except SessionStoreTooNew:
|
|
90
|
+
pass
|
|
87
91
|
emit(AgentOutput(
|
|
88
92
|
phase="UPDATE",
|
|
89
93
|
prompt=(
|
|
@@ -99,7 +103,7 @@ def skill_print(
|
|
|
99
103
|
def _skill_target_error(path: Path) -> Optional[str]:
|
|
100
104
|
if path.name != "SKILL.md":
|
|
101
105
|
return "--output must name a SKILL.md file."
|
|
102
|
-
if path.is_symlink():
|
|
106
|
+
if path.is_symlink() or skill_integrity.is_source_checkout(path):
|
|
103
107
|
return (
|
|
104
108
|
"That skill file is a link into a source checkout, which updates with the "
|
|
105
109
|
"checkout; it is never overwritten."
|
|
@@ -285,6 +285,8 @@ class SessionStore:
|
|
|
285
285
|
# Outcome of the installation provenance check for one install ({version, record_sha256, ...}).
|
|
286
286
|
install_verified: Optional[Dict[str, Any]] = None
|
|
287
287
|
install_integrity_failure: Optional[Dict[str, Any]] = None
|
|
288
|
+
# A loaded skill file that differs from the bundled skill ({skill_version, skill_path, ...}).
|
|
289
|
+
skill_integrity_failure: Optional[Dict[str, Any]] = None
|
|
288
290
|
sessions: List[Session] = field(default_factory=list)
|
|
289
291
|
|
|
290
292
|
|
|
@@ -747,6 +749,8 @@ def _store_to_dict(store: SessionStore) -> Dict[str, Any]:
|
|
|
747
749
|
d["install_verified"] = dict(store.install_verified)
|
|
748
750
|
if store.install_integrity_failure is not None:
|
|
749
751
|
d["install_integrity_failure"] = dict(store.install_integrity_failure)
|
|
752
|
+
if store.skill_integrity_failure is not None:
|
|
753
|
+
d["skill_integrity_failure"] = dict(store.skill_integrity_failure)
|
|
750
754
|
return _restore_unknown(store, d)
|
|
751
755
|
|
|
752
756
|
|
|
@@ -772,6 +776,7 @@ def _store_from_dict(raw: Dict[str, Any]) -> SessionStore:
|
|
|
772
776
|
update_prompts_answered=[a for a in answered if isinstance(a, str)] if isinstance(answered, list) else [],
|
|
773
777
|
install_verified=_dict_or_none(raw.get("install_verified")),
|
|
774
778
|
install_integrity_failure=_dict_or_none(raw.get("install_integrity_failure")),
|
|
779
|
+
skill_integrity_failure=_dict_or_none(raw.get("skill_integrity_failure")),
|
|
775
780
|
sessions=[_session_from_dict(s) for s in (raw.get("sessions") or [])],
|
|
776
781
|
)
|
|
777
782
|
return _remember_unknown(store, raw, _STORE_KEYS)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"""The booking-agent skill this CLI was built with.
|
|
2
2
|
|
|
3
|
-
``
|
|
3
|
+
``bundled_skill.md`` next to this module is a copy of ``skills/booking-agent/SKILL.md``
|
|
4
4
|
(``scripts/sync_skill_bundle.py`` writes it, and CI checks it), so the installed CLI can
|
|
5
|
-
always print the instructions that match it, offline.
|
|
5
|
+
always print the instructions that match it, offline. It must not be named ``SKILL.md``:
|
|
6
|
+
skill installers treat any directory holding a ``SKILL.md`` as a skill and would install
|
|
7
|
+
this whole package in place of ``skills/booking-agent/``.
|
|
6
8
|
"""
|
|
7
9
|
from __future__ import annotations
|
|
8
10
|
|
|
9
11
|
import re
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
12
|
-
SKILL_FILE = Path(__file__).with_name("
|
|
14
|
+
SKILL_FILE = Path(__file__).with_name("bundled_skill.md")
|
|
13
15
|
_VERSION_LINE = re.compile(r"^version:\s*(\S+)\s*$", re.MULTILINE)
|
|
14
16
|
|
|
15
17
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Whether the skill file the agent loaded is the skill this CLI was built with.
|
|
2
|
+
|
|
3
|
+
``flow init --skill-path`` hashes the loaded ``SKILL.md`` and compares it with the
|
|
4
|
+
bundled copy (``skill_bundle``), which ships inside the attested wheel. The comparison
|
|
5
|
+
runs only when the loaded file declares the bundled version: another version is a
|
|
6
|
+
stale copy, which the update check offers to replace. A same-version mismatch is
|
|
7
|
+
remembered, so ``flow book`` and every ``flow pay`` command refuse until the file is
|
|
8
|
+
replaced (``skill print --output``) or a later ``flow init --skill-path`` matches.
|
|
9
|
+
|
|
10
|
+
A skill file that resolves into a source checkout is not checked, so developers can
|
|
11
|
+
edit the skill in place. The check cannot catch a copy written to evade it, which can
|
|
12
|
+
omit ``--skill-path`` or point it at a genuine file; the first read is covered by the
|
|
13
|
+
signed release (``gh attestation verify``) and by the harness.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import re
|
|
19
|
+
import shlex
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
from .skill_bundle import SKILL_VERSION, skill_text
|
|
24
|
+
|
|
25
|
+
CODE = "skill_unverified"
|
|
26
|
+
PATH_INVALID_CODE = "skill_path_invalid"
|
|
27
|
+
REPLACE_COMMAND = "booking-agent skill print --output"
|
|
28
|
+
|
|
29
|
+
_VERSION_LINE = re.compile(r"^version:\s*[\"']?([^\s\"']+)[\"']?\s*$", re.MULTILINE)
|
|
30
|
+
_NAME_LINE = re.compile(r"^name:\s*booking-agent\s*$", re.MULTILINE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def normalized(text: str) -> bytes:
|
|
34
|
+
"""``text`` with LF line endings and exactly one trailing newline, as UTF-8."""
|
|
35
|
+
return (text.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n") + "\n").encode("utf-8")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def skill_sha256(text: str) -> str:
|
|
39
|
+
return hashlib.sha256(normalized(text)).hexdigest()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _frontmatter(text: str) -> str:
|
|
43
|
+
text = text.replace("\r\n", "\n")
|
|
44
|
+
if not text.startswith("---\n"):
|
|
45
|
+
return ""
|
|
46
|
+
end = text.find("\n---", 4)
|
|
47
|
+
return text[4:end] if end != -1 else ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def frontmatter_version(text: str) -> Optional[str]:
|
|
51
|
+
match = _VERSION_LINE.search(_frontmatter(text))
|
|
52
|
+
return match.group(1) if match else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_booking_agent_skill(text: str) -> bool:
|
|
56
|
+
return _NAME_LINE.search(_frontmatter(text)) is not None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_source_checkout(path: Path) -> bool:
|
|
60
|
+
"""``path`` resolves to ``skills/booking-agent/SKILL.md`` of a booking-agent checkout.
|
|
61
|
+
|
|
62
|
+
Resolving follows a symlinked file and a symlinked skill directory alike, so the
|
|
63
|
+
``scripts/install.sh`` link into a checkout counts, while a ``npx skills`` link into
|
|
64
|
+
its own copy under ``.agents/skills`` does not.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
resolved = path.expanduser().resolve()
|
|
68
|
+
except (OSError, RuntimeError):
|
|
69
|
+
return False
|
|
70
|
+
parents = resolved.parents
|
|
71
|
+
return (
|
|
72
|
+
resolved.name == "SKILL.md"
|
|
73
|
+
and len(parents) > 2
|
|
74
|
+
and parents[0].name == "booking-agent"
|
|
75
|
+
and parents[1].name == "skills"
|
|
76
|
+
and (parents[2] / "packages" / "cli" / "pyproject.toml").is_file()
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _applies(failure: Any) -> bool:
|
|
81
|
+
return isinstance(failure, dict) and failure.get("skill_version") == SKILL_VERSION
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def recorded_failure(store: Any) -> Optional[Dict[str, Any]]:
|
|
85
|
+
"""The remembered mismatch for this CLI's skill version, if any."""
|
|
86
|
+
failure = store.skill_integrity_failure
|
|
87
|
+
return failure if _applies(failure) else None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _set_failure(store: Any, failure: Optional[Dict[str, Any]]) -> None:
|
|
91
|
+
from .session import save_store
|
|
92
|
+
|
|
93
|
+
if store.skill_integrity_failure != failure:
|
|
94
|
+
store.skill_integrity_failure = failure
|
|
95
|
+
save_store(store)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def clear(store: Any) -> None:
|
|
99
|
+
_set_failure(store, None)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check(store: Any, skill_path: Optional[str]) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
|
|
103
|
+
"""``(failure, loaded_version)`` for the skill file at ``skill_path``.
|
|
104
|
+
|
|
105
|
+
``failure`` is the remembered mismatch when there is one, and ``loaded_version`` is
|
|
106
|
+
the version the loaded file declares (None when no path was given). Raises
|
|
107
|
+
``ValueError`` when ``skill_path`` is not a readable booking-agent skill file.
|
|
108
|
+
"""
|
|
109
|
+
if skill_path is None:
|
|
110
|
+
return recorded_failure(store), None
|
|
111
|
+
path = Path(skill_path).expanduser()
|
|
112
|
+
try:
|
|
113
|
+
text = path.read_text(encoding="utf-8")
|
|
114
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
115
|
+
raise ValueError(f"cannot read {path}: {type(exc).__name__}") from exc
|
|
116
|
+
if not is_booking_agent_skill(text):
|
|
117
|
+
raise ValueError(f"{path} is not a booking-agent SKILL.md")
|
|
118
|
+
loaded_version = frontmatter_version(text)
|
|
119
|
+
if is_source_checkout(path) or (loaded_version is not None and loaded_version != SKILL_VERSION):
|
|
120
|
+
clear(store)
|
|
121
|
+
return None, loaded_version
|
|
122
|
+
actual = skill_sha256(text)
|
|
123
|
+
if actual == skill_sha256(skill_text()):
|
|
124
|
+
clear(store)
|
|
125
|
+
return None, loaded_version
|
|
126
|
+
_set_failure(store, {
|
|
127
|
+
"skill_version": SKILL_VERSION,
|
|
128
|
+
"skill_path": str(path),
|
|
129
|
+
"actual_sha256": actual,
|
|
130
|
+
})
|
|
131
|
+
return store.skill_integrity_failure, loaded_version
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def error_message() -> str:
|
|
135
|
+
return (
|
|
136
|
+
"The booking-agent skill file you loaded has been changed and does not match the "
|
|
137
|
+
"signed skill this assistant was released with, so it will not book or take "
|
|
138
|
+
"payments. Follow data.skill_text, the signed skill, from now on. Tell the user in "
|
|
139
|
+
"plain words that the assistant's instructions file was changed and ask whether to "
|
|
140
|
+
"restore the official one; only with their yes, run data.replace_command, then run "
|
|
141
|
+
"flow init again with --skill-path. Do not try to book another way."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def error_data(failure: Dict[str, Any]) -> Dict[str, Any]:
|
|
146
|
+
path = failure.get("skill_path")
|
|
147
|
+
return {
|
|
148
|
+
"skill_path": path,
|
|
149
|
+
"skill_version": SKILL_VERSION,
|
|
150
|
+
"expected_sha256": skill_sha256(skill_text()),
|
|
151
|
+
"actual_sha256": failure.get("actual_sha256"),
|
|
152
|
+
"replace_command": f"{REPLACE_COMMAND} {shlex.quote(path)}" if path else None,
|
|
153
|
+
"skill_text": skill_text(),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def path_invalid_message(detail: str) -> str:
|
|
158
|
+
return (
|
|
159
|
+
f"--skill-path must be the SKILL.md file you loaded the booking-agent skill from "
|
|
160
|
+
f"({detail}). Run flow init again with that file's path, or without --skill-path if "
|
|
161
|
+
"you do not know it."
|
|
162
|
+
)
|
|
@@ -107,6 +107,7 @@ SKILL_TABLE_CODES = frozenset({
|
|
|
107
107
|
"search_area_input_invalid", "search_area_not_user_confirmed", "search_area_unconfirmed",
|
|
108
108
|
"service_address_unconfirmed", "service_details_changed", "service_not_available_in_area",
|
|
109
109
|
"session_store_too_new", "shared_payment_token_not_issued", "skill_output_refused",
|
|
110
|
+
"skill_path_invalid", "skill_unverified",
|
|
110
111
|
"slot_not_selected", "spend_request_not_approved",
|
|
111
112
|
"spend_request_replacement_required", "timezone_confirmation_required",
|
|
112
113
|
"ucp_complete_failed", "ucp_native_required", "update_failed", "update_not_confirmed",
|