diff --git a/backend/src/api_docs.rs b/backend/src/api_docs.rs index 880b179b8..b992a09ba 100644 --- a/backend/src/api_docs.rs +++ b/backend/src/api_docs.rs @@ -52,6 +52,7 @@ // AI Services (unified key management) crate::handlers::keys::create_key, crate::handlers::keys::list_keys, + crate::handlers::service_insights::get_insights, crate::handlers::keys::get_key, crate::handlers::keys::get_key_authorization, crate::handlers::keys::update_key, diff --git a/backend/src/billing_integration_tests/usage.rs b/backend/src/billing_integration_tests/usage.rs index 496c80ce7..e574c3b9e 100644 --- a/backend/src/billing_integration_tests/usage.rs +++ b/backend/src/billing_integration_tests/usage.rs @@ -19,6 +19,7 @@ fn meter(owner: &str, quantity: i64) -> UsageMeterRow { wallet_id: Some("wallet".into()), actor_user_id: owner.to_string(), api_key_id: None, + user_service_id: None, service_id: Some("service".into()), service_slug: Some("llm-test".into()), metric: BillingMetric::Tokens, @@ -52,7 +53,10 @@ async fn read_usage(state: &crate::AppState, actor: &str) -> BillingUsageRespons billing::get_usage( State(state.clone()), test_auth_user(actor), - Query(UsageQuery { period: None }), + Query(UsageQuery { + period: None, + bucket: None, + }), ) .await .expect("usage read") @@ -968,3 +972,63 @@ async fn execution_owner_preserves_org_acl_for_non_master_credentials() { )); db.drop().await.unwrap(); } + +#[tokio::test] +async fn day_buckets_split_usage_by_utc_day_without_changing_totals() { + let Some(db) = connect_test_database("billing_usage_day_buckets").await else { + return; + }; + let owner = insert_owner(&db).await; + let state = billing_route_state(db.clone(), Arc::new(FakeLago::default()), 0); + let today = Utc::now(); + let yesterday = today - chrono::Duration::days(1); + for (at, quantity) in [(yesterday, 4), (today, 5), (today, 6)] { + let mut row = meter(&owner, quantity); + row.wallet_id = None; + row.created_at = at; + db.collection::(USAGE_METER) + .insert_one(row) + .await + .unwrap(); + } + let flat = read_usage(&state, &owner).await; + assert_eq!(flat.rows.len(), 1); + assert!(flat.rows[0].day.is_none()); + let daily = billing::get_usage( + State(state.clone()), + test_auth_user(&owner), + Query(UsageQuery { + period: Some("7d".into()), + bucket: Some("day".into()), + }), + ) + .await + .expect("daily usage read") + .0; + let days: Vec<_> = daily + .rows + .iter() + .map(|row| { + ( + row.day.expect("bucketed day").date_naive(), + row.quantity, + row.events, + ) + }) + .collect(); + assert_eq!( + days, + vec![(yesterday.date_naive(), 4, 1), (today.date_naive(), 11, 2),] + ); + assert_eq!(daily.totals.quantity, flat.totals.quantity); + let invalid = billing::get_usage( + State(state), + test_auth_user(&owner), + Query(UsageQuery { + period: None, + bucket: Some("hour".into()), + }), + ) + .await; + assert!(matches!(invalid, Err(AppError::ValidationError(_)))); +} diff --git a/backend/src/db.rs b/backend/src/db.rs index 8fd2db3ab..b4f12858d 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -513,6 +513,14 @@ async fn ensure_core_indexes(db: &Database) -> Result<(), mongodb::error::Error> } // ── audit_log ── let audit = db.collection::("audit_log"); + audit + .create_index( + IndexModel::builder() + .keys(doc! { "event_type": 1, "event_data.user_service_id": 1, "created_at": -1, "_id": -1 }) + .options(IndexOptions::builder().name("audit_service_requests".to_string()).build()) + .build(), + ) + .await?; audit .create_index( IndexModel::builder() @@ -4407,6 +4415,12 @@ async fn migrate_provider_tokens(db: &Database) -> Result<(), Box Result<(), Box Result<(), Box UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: id.to_string(), user_id: user_id.to_string(), diff --git a/backend/src/handlers/assistant_action_effects_endpoints.rs b/backend/src/handlers/assistant_action_effects_endpoints.rs index 68f31cf46..114766a6e 100644 --- a/backend/src/handlers/assistant_action_effects_endpoints.rs +++ b/backend/src/handlers/assistant_action_effects_endpoints.rs @@ -949,6 +949,7 @@ mod tests { fn fixture_external_key(key_id: &str, user_id: &str, label: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_epoch: 1, credential_source: None, id: key_id.to_string(), diff --git a/backend/src/handlers/assistant_action_effects_keys.rs b/backend/src/handlers/assistant_action_effects_keys.rs index a6108a00c..a18b4a598 100644 --- a/backend/src/handlers/assistant_action_effects_keys.rs +++ b/backend/src/handlers/assistant_action_effects_keys.rs @@ -974,6 +974,7 @@ mod tests { fn fixture_user_api_key(id: &str, user_id: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_epoch: 1, credential_source: None, id: id.to_string(), diff --git a/backend/src/handlers/assistant_action_effects_services.rs b/backend/src/handlers/assistant_action_effects_services.rs index 1b34fac31..7596c5dba 100644 --- a/backend/src/handlers/assistant_action_effects_services.rs +++ b/backend/src/handlers/assistant_action_effects_services.rs @@ -1131,6 +1131,7 @@ mod tests { fn fixture_api_key(id: &str, user_id: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_epoch: 1, credential_source: None, id: id.to_string(), diff --git a/backend/src/handlers/billing.rs b/backend/src/handlers/billing.rs index 79f1f14d4..5e4a97205 100644 --- a/backend/src/handlers/billing.rs +++ b/backend/src/handlers/billing.rs @@ -35,6 +35,8 @@ const LAGO_HMAC_SHA256_ALGORITHM: &str = "hmac"; #[derive(Debug, Deserialize)] pub struct UsageQuery { pub period: Option, + /// `day` additionally splits rows by UTC calendar day for usage charts. + pub bucket: Option, } #[derive(Debug, Serialize, ToSchema)] @@ -81,6 +83,9 @@ pub struct BillingUsageRow { /// only). None when no row in the group carried a breakdown. #[serde(skip_serializing_if = "Option::is_none")] pub token_breakdown: Option, + /// UTC day start; present only when requested with `bucket=day`. + #[serde(skip_serializing_if = "Option::is_none")] + pub day: Option>, } #[derive(Debug, Serialize, ToSchema)] @@ -215,7 +220,8 @@ pub struct InvoiceDownloadResponse { path = "/api/v1/billing/usage", tag = "Billing", params( - ("period" = Option, Query, description = "Usage period: 24h, 7d, 30d, 90d, or all") + ("period" = Option, Query, description = "Usage period: 24h, 7d, 30d, 90d, or all"), + ("bucket" = Option, Query, description = "`day` splits rows by UTC day") ), responses( (status = 200, description = "Billing usage summary", body = BillingUsageResponse) @@ -230,6 +236,15 @@ pub async fn get_usage( let owner_id = auth_user.user_id.to_string(); let period = query.period.unwrap_or_else(|| "30d".to_string()); let since = period_start(&period); + let by_day = match query.bucket.as_deref() { + None => false, + Some("day") => true, + Some(_) => { + return Err(AppError::ValidationError( + "bucket must be `day` when supplied".to_string(), + )); + } + }; let mut match_doc = doc! { "billing_owner_id": &owner_id, "quantity": { "$ne": null }, @@ -296,6 +311,13 @@ pub async fn get_usage( // only (service not platform_billable); they carry no // cost and are never pushed to Lago. "billable": { "$ne": [{ "$ifNull": ["$wallet_id", null] }, null] }, + "day": if by_day { + bson::Bson::Document(doc! { "$dateTrunc": { + "date": "$created_at", "unit": "day", "timezone": "UTC", + } }) + } else { + bson::Bson::Null + }, }, "quantity": { "$sum": "$quantity" }, // Keep exact settlements separate from historical estimates. @@ -338,7 +360,7 @@ pub async fn get_usage( "cache_creation_tokens": { "$sum": { "$ifNull": ["$token_breakdown.cache_creation_tokens", 0] } }, } }, - doc! { "$sort": { "_id.service_slug": 1, "_id.layer": 1, "_id.metric": 1 } }, + doc! { "$sort": { "_id.service_slug": 1, "_id.layer": 1, "_id.metric": 1, "_id.day": 1 } }, ]; let mut cursor = state @@ -404,6 +426,7 @@ pub async fn get_usage( 0 }, token_breakdown: usage_row_breakdown(&doc), + day: id_doc.get_datetime("day").ok().map(|day| day.to_chrono()), }); } diff --git a/backend/src/handlers/delegation.rs b/backend/src/handlers/delegation.rs index 5adc25cd0..aed4910bf 100644 --- a/backend/src/handlers/delegation.rs +++ b/backend/src/handlers/delegation.rs @@ -3015,6 +3015,7 @@ mod tests { .db .collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, id: credential_id.to_string(), user_id: TEST_USER_ID.to_string(), label: "full-router credential".to_string(), @@ -3497,6 +3498,7 @@ mod tests { .db .collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, id: credential_id.to_string(), user_id: TEST_USER_ID.to_string(), label: "ac5 credential".to_string(), @@ -3662,6 +3664,7 @@ mod tests { .db .collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, id: "00000000-0000-4000-8000-000000000713".to_string(), user_id: TEST_USER_ID.to_string(), label: "oauth refresh canary".to_string(), diff --git a/backend/src/handlers/keys.rs b/backend/src/handlers/keys.rs index ed0df3d96..9256843a7 100644 --- a/backend/src/handlers/keys.rs +++ b/backend/src/handlers/keys.rs @@ -413,6 +413,8 @@ impl std::fmt::Debug for CreateKeyRequest { #[derive(Debug, Serialize, ToSchema)] pub struct KeyResponse { + /// Whether the current caller may inspect and edit connection configuration. + pub can_edit_configuration: bool, #[serde(skip_serializing_if = "Option::is_none")] pub authorship: Option, pub id: String, @@ -501,6 +503,8 @@ pub struct KeyResponse { /// — so safe to surface. The `client_secret` is never returned by the API. #[serde(skip_serializing_if = "Option::is_none")] pub oauth_client_id: Option, + /// Resolved OAuth app source: platform or byo; absent only when unestablished. + pub oauth_app_source: Option, /// Scopes currently granted on this OAuth connection (NyxID#917 follow-up), /// parsed from the backing `UserApiKey.token_scopes`. The connect UIs /// pre-select and lock these when adding scopes to an existing connection @@ -2680,6 +2684,7 @@ fn key_response_from_result(result: &unified_key_service::CreateKeyResult) -> Ke .to_string(); KeyResponse { + can_edit_configuration: true, authorship: None, recommended_skill_refs: None, skills_revision: None, @@ -2768,6 +2773,11 @@ fn key_response_from_result(result: &unified_key_service::CreateKeyResult) -> Ke // wizard can call `GET /keys/:id` immediately after create if // it needs the field rendered. oauth_client_id: None, + oauth_app_source: result + .api_key + .as_ref() + .and_then(crate::services::oauth_app_source::from_key) + .map(|source| source.as_str().to_owned()), // Fresh create: an OAuth connection has no granted scopes until the // authorize callback completes, so there's nothing to surface yet. granted_scopes: None, @@ -2820,7 +2830,10 @@ fn key_response_from_view(view: unified_key_service::KeyView) -> KeyResponse { .is_some_and(|node_id| !node_id.is_empty()); let endpoint_url = (!view.auto_connected).then_some(view.endpoint_url); + let credential_source: crate::handlers::user_services_handler::CredentialSourceResponse = + view.credential_source.clone().into(); KeyResponse { + can_edit_configuration: !view.auto_connected && credential_source.can_edit_configuration(), authorship: None, recommended_skill_refs: None, skills_revision: None, @@ -2885,6 +2898,7 @@ fn key_response_from_view(view: unified_key_service::KeyView) -> KeyResponse { custom_user_agent: view.custom_user_agent, connection_id: view.connection_id, oauth_client_id: view.oauth_client_id, + oauth_app_source: view.oauth_app_source, granted_scopes: view.granted_scopes, last_authorized_at: view.last_authorized_at, default_request_headers: crate::models::default_request_header::redact_list_for_response( @@ -2914,6 +2928,8 @@ fn key_response_from_view(view: unified_key_service::KeyView) -> KeyResponse { } } +/// Apply after discovery enrichment, which can add instance configuration. +/// Execution and authorization-evidence projections keep their own contracts. async fn enrich_key_node_metadata( db: &mongodb::Database, ws_manager: &crate::services::node_ws_manager::NodeWsManager, @@ -3195,6 +3211,7 @@ mod tests { fn make_blank_api_key() -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), @@ -3222,6 +3239,33 @@ mod tests { } } + #[test] + fn billing_metadata_read_only_key_exposes_oauth_selection_without_app_id() { + let mut key = make_blank_api_key(); + key.credential_type = "oauth2".into(); + key.credential_source = Some("platform".into()); + let result = crate::services::unified_key_service::CreateKeyResult { + endpoint: test_user_endpoint( + "endpoint", + "owner", + "Twitter", + "https://example.test", + None, + None, + ), + api_key: Some(key), + service: test_user_service("service", "owner", "twitter", "endpoint", None, None), + ssh_host: None, + ssh_port: None, + ssh_ca_public_key: None, + ssh_allowed_principals: None, + ssh_certificate_ttl_minutes: None, + }; + let response = super::key_response_from_result(&result); + assert_eq!(response.oauth_app_source.as_deref(), Some("platform")); + assert_aevatar_secret_free(&serde_json::to_value(&response).unwrap()); + } + #[test] fn key_response_always_serializes_authorization_evidence_properties() { let result = crate::services::unified_key_service::CreateKeyResult { @@ -5509,6 +5553,69 @@ mod tests { assert_eq!(old.keys[0].endpoint_url, "https://api.example.com"); } + #[tokio::test] + async fn connection_configuration_flag_requires_editor_without_hiding_data() { + let db = + crate::test_utils::connect_transaction_test_database("configuration_read_acl").await; + let actor = uuid::Uuid::new_v4().to_string(); + let org = uuid::Uuid::new_v4().to_string(); + let service = uuid::Uuid::new_v4().to_string(); + insert_user(&db, &actor, UserType::Person).await; + insert_user(&db, &org, UserType::Org).await; + insert_key_fixture(&db, &org, &service, "shared", "Shared").await; + db.collection::("user_services") + .update_one( + doc! { "_id": &service }, + doc! { "$set": { "custom_user_agent": "private-client" } }, + ) + .await + .unwrap(); + let membership = test_membership(&org, &actor, OrgRole::Admin, Some(vec![service.clone()])); + db.collection::("org_memberships") + .insert_one(&membership) + .await + .unwrap(); + let state = test_app_state(db.clone()); + for (role, editable) in [("admin", true), ("member", false), ("viewer", false)] { + db.collection::("org_memberships") + .update_one( + doc! { "_id": &membership.id }, + doc! { "$set": { "role": role } }, + ) + .await + .unwrap(); + let Json(detail) = super::get_key( + State(state.clone()), + test_auth_user(&actor), + Path(service.clone()), + ) + .await + .unwrap(); + assert_eq!(detail.can_edit_configuration, editable); + // The flag only gates editing UI; readers keep the configuration. + assert!(detail.endpoint_url.is_some()); + let Json(list) = super::list_keys(State(state.clone()), test_auth_user(&actor)) + .await + .unwrap(); + let row = list.keys.iter().find(|row| row.id == service).unwrap(); + assert_eq!(row.can_edit_configuration, editable); + assert!(row.endpoint_url.is_some()); + assert!(row.custom_user_agent.is_some()); + let Json(services) = crate::handlers::user_services_handler::list_user_services( + State(state.clone()), + test_auth_user(&actor), + ) + .await + .unwrap(); + let row = services + .services + .iter() + .find(|row| row.id == service) + .unwrap(); + assert!(row.custom_user_agent.is_some()); + } + } + // ---- get_key org scoping tests ---- #[tokio::test] diff --git a/backend/src/handlers/llm_gateway.rs b/backend/src/handlers/llm_gateway.rs index db9b0cf11..1a6fdd47c 100644 --- a/backend/src/handlers/llm_gateway.rs +++ b/backend/src/handlers/llm_gateway.rs @@ -531,6 +531,16 @@ pub async fn llm_proxy_request( credential_source.as_deref(), &target, ); + let billing_request_id = uuid::Uuid::new_v4().to_string(); + let mut request_audit = crate::services::service_insights_activity::RequestAudit::new( + &state.db, + &auth_user, + operation_user_service_id.as_deref(), + &service_id, + billing_resource_owner_id, + &billing_request_id, + credential_class, + ); let billing_owner = state .billing .owner_resolver() @@ -539,14 +549,15 @@ pub async fn llm_proxy_request( billing_resource_owner_id, credential_class, ) - .await?; + .await + .inspect_err(|error| request_audit.admission_error(error))?; let billing_ctx = crate::services::billing::BillingRouteContext::new( crate::services::billing::BillingIngress::LlmProvider, - uuid::Uuid::new_v4().to_string(), + billing_request_id, billing_owner.owner_id, user_id_str.clone(), auth_user.api_key_id.clone(), - None, + operation_user_service_id.clone(), Some(service_id.clone()), Some(service.slug.clone()), crate::services::billing::NodeIntent::Direct, @@ -557,7 +568,11 @@ pub async fn llm_proxy_request( state.billing.resale_enabled(), ); let billing_ctx = billing_ctx.with_request_body(Some(&body_bytes)); - let metered = state.billing.open(&billing_ctx).await?; + let metered = state + .billing + .open(&billing_ctx) + .await + .inspect_err(|error| request_audit.admission_error(error))?; // Resolve credentials for injection. The new UserService path bakes the // credential into `target` (via auth_method / credential), so we only need @@ -736,6 +751,7 @@ pub async fn llm_proxy_request( })), ); + request_audit.response(response.status().as_u16()); Ok(response) } @@ -1101,6 +1117,16 @@ async fn gateway_provider_request( credential_source.as_deref(), &target, ); + let billing_request_id = uuid::Uuid::new_v4().to_string(); + let mut request_audit = crate::services::service_insights_activity::RequestAudit::new( + &state.db, + &auth_user, + operation_user_service_id.as_deref(), + &service_id, + billing_resource_owner_id, + &billing_request_id, + credential_class, + ); let billing_owner = state .billing .owner_resolver() @@ -1109,14 +1135,15 @@ async fn gateway_provider_request( billing_resource_owner_id, credential_class, ) - .await?; + .await + .inspect_err(|error| request_audit.admission_error(error))?; let billing_ctx = crate::services::billing::BillingRouteContext::new( crate::services::billing::BillingIngress::LlmGateway, - uuid::Uuid::new_v4().to_string(), + billing_request_id, billing_owner.owner_id, user_id_str.clone(), auth_user.api_key_id.clone(), - None, + operation_user_service_id, Some(service_id.clone()), Some(service.slug.clone()), crate::services::billing::NodeIntent::Direct, @@ -1127,7 +1154,11 @@ async fn gateway_provider_request( state.billing.resale_enabled(), ); let billing_ctx = billing_ctx.with_request_body(Some(&body_bytes)); - let metered = state.billing.open(&billing_ctx).await?; + let metered = state + .billing + .open(&billing_ctx) + .await + .inspect_err(|error| request_audit.admission_error(error))?; // Resolve delegated credentials. When the target came from the new // UserService path, the credential is already baked into `target`; we only @@ -1332,6 +1363,7 @@ async fn gateway_provider_request( })), ); + request_audit.response(response.status().as_u16()); Ok(response) } diff --git a/backend/src/handlers/mcp_transport.rs b/backend/src/handlers/mcp_transport.rs index 165630b15..aab24921b 100644 --- a/backend/src/handlers/mcp_transport.rs +++ b/backend/src/handlers/mcp_transport.rs @@ -416,6 +416,8 @@ struct McpAuthContext { user_id: String, auth_method: AuthMethod, acting_client_id: Option, + oauth_client_id: Option, + api_key_credential_id: Option, approval_owner_user_id: Option, /// True when auth was via `x-api-key`. API-key requests are stateless: each /// request authenticates independently, no MCP session is created or required. @@ -450,6 +452,8 @@ impl McpAuthContext { user_id, auth_method, acting_client_id: None, + oauth_client_id: None, + api_key_credential_id: None, approval_owner_user_id: None, is_api_key: false, api_key_id: None, @@ -582,7 +586,7 @@ async fn authenticate_mcp( .map_err(|_| mcp_401(&state.config.base_url))?; match crate::services::key_service::validate_api_key(&state.db, raw_key).await { - Ok((user_id, api_key, _credential_id)) => { + Ok((user_id, api_key, credential_id)) => { if api_key.purpose == crate::models::api_key::ApiKeyPurpose::PermissionBound { return Err(mcp_403_api_key_insufficient_scope()); } @@ -639,6 +643,8 @@ async fn authenticate_mcp( user_id, auth_method: AuthMethod::ApiKey, acting_client_id: None, + oauth_client_id: None, + api_key_credential_id: credential_id, approval_owner_user_id: None, is_api_key: true, api_key_id: Some(api_key.id.clone()), @@ -765,6 +771,7 @@ async fn authenticate_mcp( ctx.api_key_id = api_key_id; ctx.api_key_name = api_key_name; } + ctx.oauth_client_id = claims.client_id.clone(); if let Some(key) = relay_key { crate::services::org_agent_service::validate_key( &state.db, @@ -2239,6 +2246,29 @@ async fn dispatch_service_tool( /// the authenticated MCP caller -- API key identity + node scope. fn mcp_exec_context<'a>(auth: &'a McpAuthContext) -> mcp_service::McpExecContext<'a> { mcp_service::McpExecContext { + attribution: Some( + crate::services::service_insights_activity::RequestAttribution { + actor: crate::services::audit_service::AuditActor { + user_id: auth.user_id.clone(), + api_key_id: auth.api_key_id.clone(), + api_key_name: auth.api_key_name.clone(), + ip_address: auth.ip_address.clone(), + user_agent: auth.user_agent.clone(), + }, + auth_kind: match auth.auth_method { + AuthMethod::Session => "session", + AuthMethod::AccessToken => "access_token", + AuthMethod::ApiKey => "api_key", + AuthMethod::ServiceAccount => "service_account", + AuthMethod::Delegated => "delegated", + AuthMethod::Relay => "relay", + } + .into(), + oauth_client_id: auth.oauth_client_id.clone(), + acting_client_id: auth.acting_client_id.clone(), + api_key_credential_id: auth.api_key_credential_id.clone(), + }, + ), org_agent_access: auth.org_agent_access.as_deref(), agent_owner: auth.assistant_agent_owner_id.as_deref(), operation_scopes: Some(&auth.assistant_operation_scopes), @@ -4608,6 +4638,8 @@ mod tests { user_id: "user-1".into(), auth_method: AuthMethod::ApiKey, acting_client_id: None, + oauth_client_id: None, + api_key_credential_id: None, approval_owner_user_id: None, is_api_key: true, api_key_id: Some("key-1".into()), @@ -5796,11 +5828,19 @@ mod tests { #[test] fn mcp_exec_context_from_api_key_auth() { - let auth = api_key_auth(vec!["svc-1".into()]); + let mut auth = api_key_auth(vec!["svc-1".into()]); + auth.api_key_credential_id = Some("login-credential".into()); let ctx = mcp_exec_context(&auth); assert_eq!(ctx.api_key_id, Some("key-1")); assert!(!ctx.allow_all_nodes); assert!(ctx.allowed_node_ids.is_empty()); + let attribution = ctx.attribution.unwrap(); + assert_eq!(attribution.actor.api_key_id.as_deref(), Some("key-1")); + assert_eq!(attribution.actor.api_key_name.as_deref(), Some("agent")); + assert_eq!( + attribution.api_key_credential_id.as_deref(), + Some("login-credential") + ); } #[test] @@ -5809,6 +5849,21 @@ mod tests { let ctx = mcp_exec_context(&auth); assert!(ctx.api_key_id.is_none()); assert!(ctx.allow_all_nodes); + assert_eq!(ctx.attribution.unwrap().auth_kind, "session"); + } + + #[test] + fn mcp_exec_context_preserves_verified_application_identity() { + let mut auth = McpAuthContext::user("user-1".into(), AuthMethod::AccessToken); + auth.oauth_client_id = Some("registered-client".into()); + let ctx = mcp_exec_context(&auth); + let attribution = ctx.attribution.unwrap(); + assert_eq!( + attribution.oauth_client_id.as_deref(), + Some("registered-client") + ); + assert_eq!(attribution.auth_kind, "access_token"); + assert!(attribution.actor.api_key_id.is_none()); } // ----------------------------------------------------------------------- diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs index 9bc7cab02..aa8dce04d 100644 --- a/backend/src/handlers/mod.rs +++ b/backend/src/handlers/mod.rs @@ -135,6 +135,9 @@ pub mod options; pub(crate) mod org_group; pub mod service_history; +pub mod service_insights; +#[cfg(test)] +mod service_insights_tests; pub mod channel_activities; pub mod machine_activity; diff --git a/backend/src/handlers/proxy.rs b/backend/src/handlers/proxy.rs index 51fbd2c8f..f46025705 100644 --- a/backend/src/handlers/proxy.rs +++ b/backend/src/handlers/proxy.rs @@ -4111,6 +4111,19 @@ async fn execute_resolved_proxy_inner( credential_source.as_deref(), &target, ); + let billing_request_id = pool_accounting + .as_ref() + .map(|ctx| ctx.request_id.clone()) + .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); + let mut request_audit = crate::services::service_insights_activity::RequestAudit::new( + &state.db, + auth_user, + resolved_user_service_id.as_deref(), + &target.service.id, + billing_resource_owner_id, + &billing_request_id, + credential_class, + ); let billing_owner = state .billing .owner_resolver() @@ -4119,11 +4132,8 @@ async fn execute_resolved_proxy_inner( billing_resource_owner_id, credential_class, ) - .await?; - let billing_request_id = pool_accounting - .as_ref() - .map(|ctx| ctx.request_id.clone()) - .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); + .await + .inspect_err(|error| request_audit.admission_error(error))?; let is_ws_candidate = is_ws_upgrade_request(&request); let platform_metric = platform_metric_for_target(&target, is_ws_candidate); let node_intent = match &node_route { @@ -4349,6 +4359,7 @@ async fn execute_resolved_proxy_inner( match approval_outcome { approval_service::ApprovalOutcome::Allowed { .. } => {} approval_service::ApprovalOutcome::Denied => { + request_audit.denied(403); if let Some(api_key_id) = scheduled_api_key_id { audit_service::log_for_user( state.db.clone(), @@ -4756,7 +4767,9 @@ async fn execute_resolved_proxy_inner( billing_ctx.pool_attempt = pool_accounting.as_ref().map(|ctx| ctx.metadata.clone()); // Billing and durable-grant admission carry their own database state. // Do not reserve it in every proxy poll, including early scope refusals. - let metered = Box::pin(state.billing.open(&billing_ctx)).await?; + let metered = Box::pin(state.billing.open(&billing_ctx)) + .await + .inspect_err(|error| request_audit.admission_error(error))?; let durable_reservation = if let Some(api_key_id) = scheduled_api_key_id { let grant_id = match durable_grant_id.as_deref() { @@ -4918,7 +4931,9 @@ async fn execute_resolved_proxy_inner( let ws_upgrade = match WebSocketUpgrade::from_request_parts(&mut parts, &()).await { Ok(ws) => ws, Err(rejection) => { - return Ok(rejection.into_response()); + let response = rejection.into_response(); + request_audit.denied(response.status().as_u16()); + return Ok(response); } }; @@ -4943,7 +4958,8 @@ async fn execute_resolved_proxy_inner( metered.clone(), billing_egress_permit, )) - .await; + .await + .inspect(|response| request_audit.response(response.status().as_u16())); } // Direct WS passthrough: connect to downstream directly. @@ -4963,7 +4979,8 @@ async fn execute_resolved_proxy_inner( metered.clone(), billing_egress_permit, )) - .await; + .await + .inspect(|response| request_audit.response(response.status().as_u16())); } // === Node Proxy Routing (v2: failover + streaming + metrics + HMAC signing) === @@ -5511,6 +5528,7 @@ async fn execute_resolved_proxy_inner( } destination_audit.complete(response.status().as_u16()); + request_audit.response(response.status().as_u16()); return Ok(response); } Err(NodeProxyFailure { @@ -5942,6 +5960,7 @@ async fn execute_resolved_proxy_inner( } destination_audit.complete(response.status().as_u16()); + request_audit.response(response.status().as_u16()); return Ok(response); } @@ -6578,6 +6597,7 @@ async fn execute_resolved_proxy_inner( ); destination_audit.complete(response.status().as_u16()); + request_audit.response(response.status().as_u16()); Ok(response) } @@ -11607,6 +11627,7 @@ mod proxy_resolution_integration_tests { .db .collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: api_key_id.clone(), user_id: owner_user_id.to_string(), diff --git a/backend/src/handlers/service_insights.rs b/backend/src/handlers/service_insights.rs new file mode 100644 index 000000000..5ba225c0f --- /dev/null +++ b/backend/src/handlers/service_insights.rs @@ -0,0 +1,178 @@ +use std::collections::HashSet; + +use axum::{ + Json, + extract::{Query, State}, + http::header, +}; +use serde::{Deserialize, Serialize}; +use utoipa::{IntoParams, ToSchema}; +use uuid::Uuid; + +use crate::{ + AppState, + errors::{AppError, AppResult}, + mw::auth::{AuthMethod, AuthUser}, + services::{ + org_service, service_insights_activity, service_insights_billing, user_service_service, + }, +}; + +#[derive(Deserialize, IntoParams)] +#[serde(deny_unknown_fields)] +pub struct InsightsQuery { + /// Comma-separated exact connection UUIDs, at most 100. + pub ids: String, + /// Optional managed agent key whose execution context should be explained. + pub api_key_id: Option, +} + +#[derive(Serialize, ToSchema)] +pub struct ConnectionInsightResponse { + pub service_id: String, + pub billing: Option, + pub usage: Option, +} + +#[derive(Serialize, ToSchema)] +pub struct InsightsResponse { + pub connections: Vec, +} + +fn connection_ids(raw: &str) -> AppResult> { + let parts: Vec<_> = raw.split(',').collect(); + if parts.is_empty() || parts.len() > 100 || raw.len() > 3_699 { + return Err(AppError::ValidationError( + "Choose between 1 and 100 connection IDs".into(), + )); + } + parts + .into_iter() + .map(|id| { + Uuid::parse_str(id) + .map(|id| id.to_string()) + .map_err(|_| AppError::ValidationError("Connection IDs must be UUIDs".into())) + }) + .collect() +} + +#[utoipa::path( + get, + path = "/api/v1/service-insights", + params(InsightsQuery), + responses((status = 200, body = InsightsResponse), (status = 400, description = "Invalid connection IDs"), (status = 403, description = "Account management session required")), + security(("bearer_auth" = [])), + tag = "AI Services" +)] +pub async fn get_insights( + State(state): State, + auth: AuthUser, + Query(query): Query, +) -> AppResult<( + [(header::HeaderName, &'static str); 1], + Json, +)> { + if !matches!( + auth.auth_method, + AuthMethod::Session | AuthMethod::AccessToken | AuthMethod::Delegated + ) { + return Err(AppError::Forbidden( + "Connection insights require an account management session".into(), + )); + } + let requested = connection_ids(&query.ids)?; + let agent_key_id = query + .api_key_id + .as_deref() + .map(|id| { + Uuid::parse_str(id) + .map(|id| id.to_string()) + .map_err(|_| AppError::ValidationError("Agent key ID must be a UUID".into())) + }) + .transpose()?; + let actor = auth.user_id.to_string(); + let memberships = org_service::list_memberships_for_member(&state.db, &actor, false).await?; + let services = user_service_service::list_user_services_with_sources_including_disabled( + &state.db, + &actor, + &memberships, + ) + .await? + .into_iter() + .filter(|row| requested.contains(&row.service.id)) + .filter(|row| auth.allow_all_services || auth.allowed_service_ids.contains(&row.service.id)) + .map(|row| row.service) + .collect::>(); + // Each optional projection can fail without suppressing the other. No cached + // privilege-bearing result is substituted after an authorization failure. + let billing_principal = auth.proxy_resolution_user_id(); + let (billing, activity) = tokio::join!( + async { + match agent_key_id.as_deref() { + Some(key_id) => { + service_insights_billing::explain_for_agent_key( + &state.db, + &state.billing, + &actor, + &services, + key_id, + ) + .await + } + None => { + service_insights_billing::explain_connections( + &state.db, + &state.billing, + &billing_principal, + &actor, + &services, + ) + .await + } + } + }, + service_insights_activity::insights(&state.db, &actor, &services), + ); + if let Err(error) = &billing { + tracing::warn!(%error, "Service billing insights unavailable"); + } + if let Err(error) = &activity { + tracing::warn!(%error, "Service caller insights unavailable"); + } + let mut billing = billing.unwrap_or_default(); + let mut activity = activity.unwrap_or_default(); + let connections = services + .into_iter() + .map(|service| ConnectionInsightResponse { + billing: billing.remove(&service.id), + usage: activity.remove(&service.id), + service_id: service.id, + }) + .collect(); + Ok(( + [(header::CACHE_CONTROL, "private, no-store")], + Json(InsightsResponse { connections }), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn connection_insights_rejects_invalid_or_unbounded_ids() { + for ids in [ + "".to_string(), + "slug".into(), + format!("{},", Uuid::new_v4()), + vec![Uuid::new_v4().to_string(); 101].join(","), + ] { + assert!(connection_ids(&ids).is_err()); + } + let id = Uuid::new_v4().to_string(); + assert_eq!( + connection_ids(&format!("{id},{id}")).unwrap(), + HashSet::from([id]) + ); + } +} diff --git a/backend/src/handlers/service_insights_tests.rs b/backend/src/handlers/service_insights_tests.rs new file mode 100644 index 000000000..dd6800591 --- /dev/null +++ b/backend/src/handlers/service_insights_tests.rs @@ -0,0 +1,32 @@ +use axum::extract::{Query, State}; + +use crate::{ + errors::AppError, + handlers::service_insights::{InsightsQuery, get_insights}, + mw::auth::AuthMethod, + test_utils::{test_app_state_no_db, test_auth_user}, +}; + +#[tokio::test] +async fn agent_and_service_account_tokens_cannot_enumerate_key_inventory_through_insights() { + let state = test_app_state_no_db().await; + let actor_id = uuid::Uuid::new_v4().to_string(); + for method in [ + AuthMethod::ApiKey, + AuthMethod::ServiceAccount, + AuthMethod::Relay, + ] { + let mut auth = test_auth_user(&actor_id); + auth.auth_method = method; + let result = get_insights( + State(state.clone()), + auth, + Query(InsightsQuery { + ids: uuid::Uuid::new_v4().to_string(), + api_key_id: None, + }), + ) + .await; + assert!(matches!(result, Err(AppError::Forbidden(_)))); + } +} diff --git a/backend/src/handlers/service_pool_billing_tests.rs b/backend/src/handlers/service_pool_billing_tests.rs index dcd844495..976d4bea0 100644 --- a/backend/src/handlers/service_pool_billing_tests.rs +++ b/backend/src/handlers/service_pool_billing_tests.rs @@ -364,7 +364,7 @@ async fn pool_proxy_billing_preserves_reported_consumption_from_both_attempts() to_bytes(response.into_body(), 8192).await.unwrap(); let rows = settled_rows(&fixture, 2).await; assert_ne!(rows[0].billing_request_id, rows[1].billing_request_id); - for row in rows { + for row in &rows { assert_eq!( row.pool_attempt.as_ref().and_then(|a| a.outcome), Some(crate::models::usage_meter::PoolAttemptOutcome::Reported) @@ -427,6 +427,46 @@ async fn pool_proxy_billing_preserves_reported_consumption_from_both_attempts() assert!(!format!("{event:?}").contains("review-platform-secret")); assert!(!format!("{event:?}").contains("review-byok-secret")); } + let request_events: Vec = + tokio::time::timeout(std::time::Duration::from_secs(5), async { + loop { + let events: Vec = fixture + .proxy + .state + .db + .collection::("audit_log") + .find(doc! { "event_type": "service_request" }) + .await + .unwrap() + .try_collect() + .await + .unwrap(); + if events.len() >= 2 { + break events; + } + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + } + }) + .await + .expect("both pool attempts retain service attribution"); + assert_eq!(request_events.len(), 2); + for row in &rows { + let event = request_events + .iter() + .filter_map(|event| event.get_document("event_data").ok()) + .find(|data| { + data.get_str("billing_request_id").ok() == Some(row.billing_request_id.as_str()) + }) + .expect("service history must use the actual attempt billing identity"); + assert_eq!( + event.get_str("execution_id").unwrap(), + row.billing_request_id + ); + assert_eq!( + event.get_str("user_service_id").ok(), + row.user_service_id.as_deref() + ); + } fixture.proxy.state.db.drop().await.unwrap(); } diff --git a/backend/src/handlers/user_api_keys_external.rs b/backend/src/handlers/user_api_keys_external.rs index fdd8e42c7..ebe0f28c1 100644 --- a/backend/src/handlers/user_api_keys_external.rs +++ b/backend/src/handlers/user_api_keys_external.rs @@ -708,6 +708,7 @@ mod tests { fn fixture_external_key(key_id: &str, user_id: &str, label: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.to_string(), user_id: user_id.to_string(), diff --git a/backend/src/handlers/user_services_handler.rs b/backend/src/handlers/user_services_handler.rs index 738e78f58..2e866778e 100644 --- a/backend/src/handlers/user_services_handler.rs +++ b/backend/src/handlers/user_services_handler.rs @@ -176,6 +176,21 @@ pub enum CredentialSourceResponse { }, } +impl CredentialSourceResponse { + /// Listing and detail resolvers have already applied the service scope. + pub fn can_edit_configuration(&self) -> bool { + matches!( + self, + Self::Personal + | Self::Org { + role: OrgRoleResponse::Admin, + allowed: true, + .. + } + ) + } +} + #[derive(Debug, Clone, Copy, Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum OrgRoleResponse { diff --git a/backend/src/handlers/user_tokens.rs b/backend/src/handlers/user_tokens.rs index f2e191bbf..fda5f9430 100644 --- a/backend/src/handlers/user_tokens.rs +++ b/backend/src/handlers/user_tokens.rs @@ -1761,6 +1761,7 @@ mod tests { fn test_pending_oauth_api_key(key_id: &str, user_id: &str, provider_id: &str) -> UserApiKey { let now = Utc::now(); UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.to_string(), user_id: user_id.to_string(), diff --git a/backend/src/handlers/users.rs b/backend/src/handlers/users.rs index 15f823dc5..e266f96eb 100644 --- a/backend/src/handlers/users.rs +++ b/backend/src/handlers/users.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; use crate::AppState; use crate::errors::{AppError, AppResult}; -use crate::models::user::{COLLECTION_NAME as USERS, User}; +use crate::models::user::{ + COLLECTION_NAME as USERS, ServiceViewPreferences, ServiceViewSource, ServiceViewState, + ServiceViewType, User, +}; use crate::mw::auth::AuthUser; use crate::services::{admin_user_service, audit_service, role_service, telemetry_erasure_service}; use crate::telemetry::{TelemetryContext, TelemetryEvent, emit_event}; @@ -22,9 +25,81 @@ pub struct OnboardingStateResponse { /// User-scoped config / preferences surfaced on `GET /users/me`. #[derive(Debug, Serialize)] pub struct ProfileConfigResponse { + pub services_view: Option, pub onboarding: OnboardingStateResponse, } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SaveServiceViewRequest { + #[serde( + default, + alias = "organization_id", + deserialize_with = "crate::models::user::deserialize_service_view_ids" + )] + pub organization_ids: Vec, + #[serde( + default, + alias = "service_group_id", + deserialize_with = "crate::models::user::deserialize_service_view_ids" + )] + pub service_group_ids: Vec, + pub search: String, + pub source: ServiceViewSource, + pub state: ServiceViewState, + pub service_type: ServiceViewType, + pub show_auto_connected: bool, +} + +#[derive(Debug, Serialize)] +pub struct ServiceViewResponse { + pub organization_ids: Vec, + pub service_group_ids: Vec, + pub search: String, + pub source: ServiceViewSource, + pub state: ServiceViewState, + pub service_type: ServiceViewType, + pub show_auto_connected: bool, +} + +impl From for ServiceViewResponse { + fn from(value: ServiceViewPreferences) -> Self { + Self { + organization_ids: value.organization_ids, + service_group_ids: value.service_group_ids, + search: value.search, + source: value.source, + state: value.state, + service_type: value.service_type, + show_auto_connected: value.show_auto_connected, + } + } +} + +/// PUT /api/v1/users/me/preferences/services +pub async fn save_services_view( + State(state): State, + auth_user: AuthUser, + Json(body): Json, +) -> AppResult> { + let preferences = ServiceViewPreferences { + organization_ids: body.organization_ids, + service_group_ids: body.service_group_ids, + search: body.search, + source: body.source, + state: body.state, + service_type: body.service_type, + show_auto_connected: body.show_auto_connected, + }; + let saved = crate::services::user_preferences_service::save_services_view( + &state.db, + &auth_user.user_id.to_string(), + preferences, + ) + .await?; + Ok(Json(saved.into())) +} + #[derive(Debug, Serialize)] pub struct UserCapabilitiesResponse { pub billing_available: bool, @@ -160,6 +235,7 @@ pub async fn get_me( updated_at: user_model.updated_at.to_rfc3339(), last_login_at: user_model.last_login_at.map(|t| t.to_rfc3339()), profile_config: ProfileConfigResponse { + services_view: user_model.profile_config.services_view.map(Into::into), onboarding: OnboardingStateResponse { ai_services_completed_at: user_model .profile_config @@ -388,6 +464,223 @@ mod tests { use crate::test_utils::{connect_test_database, test_app_state, test_auth_user, test_user}; use uuid::Uuid; + fn service_view_request() -> SaveServiceViewRequest { + SaveServiceViewRequest { + organization_ids: vec!["org-selection".into(), "org-second".into()], + service_group_ids: vec!["catalog:service-selection".into(), "catalog:second".into()], + search: "team".into(), + source: ServiceViewSource::Org, + state: ServiceViewState::Enabled, + service_type: ServiceViewType::Http, + show_auto_connected: false, + } + } + + #[tokio::test] + async fn services_view_round_trips_without_overwriting_other_settings_or_users() { + let db = connect_test_database("services_view_round_trip") + .await + .unwrap(); + let user_id = Uuid::new_v4().to_string(); + let other_id = Uuid::new_v4().to_string(); + let mut user = test_user(&user_id, UserType::Person); + user.profile_config.onboarding.ai_services_completed_at = + chrono::DateTime::from_timestamp_millis(Utc::now().timestamp_millis()); + user.profile_config + .release_integrity + .remote_credential_integrity_verification_opt_out = true; + db.collection::(USERS) + .insert_many([user.clone(), test_user(&other_id, UserType::Person)]) + .await + .unwrap(); + db.collection::(USERS) + .update_one( + doc! { "_id": &user_id }, + doc! { "$set": { "profile_config.future_setting": "preserve" } }, + ) + .await + .unwrap(); + role_service::seed_system_roles(&db).await.unwrap(); + let state = test_app_state(db.clone()); + let before = get_me(State(state.clone()), test_auth_user(&user_id)) + .await + .unwrap() + .0; + assert!(before.profile_config.services_view.is_none()); + let saved = save_services_view( + State(state.clone()), + test_auth_user(&user_id), + Json(service_view_request()), + ) + .await + .unwrap() + .0; + let loaded = get_me(State(state.clone()), test_auth_user(&user_id)) + .await + .unwrap() + .0; + assert_eq!( + serde_json::to_value(&saved).unwrap(), + serde_json::to_value(loaded.profile_config.services_view.unwrap()).unwrap() + ); + let persisted = db + .collection::(USERS) + .find_one(doc! { "_id": &user_id }) + .await + .unwrap() + .unwrap(); + assert_eq!( + persisted.profile_config.onboarding, + user.profile_config.onboarding + ); + assert_eq!( + persisted.profile_config.release_integrity, + user.profile_config.release_integrity + ); + let raw = db + .collection::(USERS) + .find_one(doc! { "_id": &user_id }) + .await + .unwrap() + .unwrap(); + assert_eq!( + raw.get_document("profile_config") + .unwrap() + .get_str("future_setting") + .unwrap(), + "preserve" + ); + let other = get_me(State(state.clone()), test_auth_user(&other_id)) + .await + .unwrap() + .0; + assert!(other.profile_config.services_view.is_none()); + + let mut clear = service_view_request(); + clear.search.clear(); + clear.organization_ids.clear(); + clear.service_group_ids.clear(); + clear.source = ServiceViewSource::All; + clear.state = ServiceViewState::All; + clear.service_type = ServiceViewType::All; + clear.show_auto_connected = true; + let _ = save_services_view(State(state.clone()), test_auth_user(&user_id), Json(clear)) + .await + .unwrap(); + let reset = get_me(State(state), test_auth_user(&user_id)) + .await + .unwrap() + .0; + assert_eq!( + reset.profile_config.services_view.unwrap().source, + ServiceViewSource::All + ); + } + + #[tokio::test] + async fn services_view_validates_and_supports_legacy_profiles() { + let db = connect_test_database("services_view_legacy").await.unwrap(); + let user_id = Uuid::new_v4().to_string(); + let mut raw = bson::to_document(&test_user(&user_id, UserType::Person)).unwrap(); + raw.remove("profile_config"); + db.collection::(USERS) + .insert_one(raw) + .await + .unwrap(); + let state = test_app_state(db.clone()); + let mut invalid = service_view_request(); + invalid.search = "x".repeat(201); + assert!(matches!( + save_services_view( + State(state.clone()), + test_auth_user(&user_id), + Json(invalid) + ) + .await, + Err(AppError::ValidationError(_)) + )); + for (organizations, services) in [ + (vec!["org".to_string(); 101], vec![]), + (vec![], vec![" ".to_string()]), + (vec![], vec!["x".repeat(129)]), + ] { + let mut invalid = service_view_request(); + invalid.organization_ids = organizations; + invalid.service_group_ids = services; + assert!(matches!( + save_services_view( + State(state.clone()), + test_auth_user(&user_id), + Json(invalid) + ) + .await, + Err(AppError::ValidationError(_)) + )); + } + let unchanged = db + .collection::(USERS) + .find_one(doc! { "_id": &user_id }) + .await + .unwrap() + .unwrap(); + assert!(!unchanged.contains_key("profile_config")); + let _ = save_services_view( + State(state.clone()), + test_auth_user(&user_id), + Json(service_view_request()), + ) + .await + .unwrap(); + let loaded = db + .collection::(USERS) + .find_one(doc! { "_id": &user_id }) + .await + .unwrap() + .unwrap(); + assert_eq!(loaded.profile_config.services_view.unwrap().search, "team"); + assert!(matches!( + save_services_view( + State(state), + test_auth_user(&Uuid::new_v4().to_string()), + Json(service_view_request()) + ) + .await, + Err(AppError::NotFound(_)) + )); + } + + #[test] + fn services_view_reads_legacy_selections_and_writes_arrays() { + let legacy = serde_json::json!({ "search": "", "organization_id": "org-1", "service_group_id": null, + "source": "all", "state": "all", "service_type": "all", "show_auto_connected": true }); + let persisted: ServiceViewPreferences = serde_json::from_value(legacy.clone()).unwrap(); + assert_eq!(persisted.organization_ids, vec!["org-1"]); + assert!(persisted.service_group_ids.is_empty()); + let request: SaveServiceViewRequest = serde_json::from_value(legacy).unwrap(); + assert_eq!(request.organization_ids, vec!["org-1"]); + let response = serde_json::to_value(ServiceViewResponse::from(persisted)).unwrap(); + assert_eq!(response["organization_ids"], serde_json::json!(["org-1"])); + assert_eq!(response["service_group_ids"], serde_json::json!([])); + assert!(response.get("organization_id").is_none()); + } + + #[test] + fn services_view_rejects_unknown_filter_values_and_owner_injection() { + let body = serde_json::json!({ "search": "", "source": "all", "state": "all", + "service_type": "all", "show_auto_connected": true }); + for (field, value) in [ + ("source", "admin"), + ("state", "healthy"), + ("service_type", "ftp"), + ("user_id", "someone-else"), + ] { + let mut invalid = body.clone(); + invalid[field] = value.into(); + assert!(serde_json::from_value::(invalid).is_err()); + } + assert!(serde_json::from_value::(body).is_ok()); + } + #[tokio::test] async fn get_me_derives_platform_role_fields_from_rbac_membership() { let Some(db) = connect_test_database("users_me_platform_role").await else { @@ -602,6 +895,7 @@ mod tests { updated_at: "2025-01-01T00:00:00+00:00".to_string(), last_login_at: Some("2025-06-01T12:00:00+00:00".to_string()), profile_config: ProfileConfigResponse { + services_view: None, onboarding: OnboardingStateResponse { ai_services_completed_at: Some("2025-03-15T10:00:00+00:00".to_string()), }, @@ -652,6 +946,7 @@ mod tests { updated_at: "2025-06-01T00:00:00+00:00".to_string(), last_login_at: None, profile_config: ProfileConfigResponse { + services_view: None, onboarding: OnboardingStateResponse { ai_services_completed_at: None, }, @@ -693,6 +988,7 @@ mod tests { updated_at: "2026-01-01T00:00:01Z".to_string(), last_login_at: None, profile_config: ProfileConfigResponse { + services_view: None, onboarding: OnboardingStateResponse { ai_services_completed_at: None, }, @@ -737,6 +1033,7 @@ mod tests { #[test] fn profile_config_response_serialization() { let resp = ProfileConfigResponse { + services_view: None, onboarding: OnboardingStateResponse { ai_services_completed_at: Some("2025-01-01T00:00:00+00:00".to_string()), }, @@ -872,6 +1169,7 @@ mod tests { updated_at: "2025-01-01T00:00:00+00:00".to_string(), last_login_at: None, profile_config: ProfileConfigResponse { + services_view: None, onboarding: OnboardingStateResponse { ai_services_completed_at: None, }, diff --git a/backend/src/models/usage_meter.rs b/backend/src/models/usage_meter.rs index 074f32167..6140f37f1 100644 --- a/backend/src/models/usage_meter.rs +++ b/backend/src/models/usage_meter.rs @@ -239,6 +239,9 @@ pub struct UsageMeterRow { pub api_key_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub service_id: Option, + /// Exact connection selected at execution; legacy catalog-only rows stay unknown. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_service_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub service_slug: Option, pub metric: BillingMetric, diff --git a/backend/src/models/user.rs b/backend/src/models/user.rs index beb996871..3d3308344 100644 --- a/backend/src/models/user.rs +++ b/backend/src/models/user.rs @@ -81,6 +81,71 @@ pub struct UserProfileConfig { pub onboarding: OnboardingState, #[serde(default)] pub release_integrity: ReleaseIntegrityProfileConfig, + #[serde(default)] + pub services_view: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ServiceViewSource { + All, + Personal, + Org, + Platform, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ServiceViewState { + All, + Enabled, + Disabled, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ServiceViewType { + All, + Http, + Ssh, +} + +pub(crate) fn deserialize_service_view_ids<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum Selection { + One(String), + Many(Vec), + } + Ok(match Option::::deserialize(deserializer)? { + Some(Selection::One(id)) => vec![id], + Some(Selection::Many(ids)) => ids, + None => Vec::new(), + }) +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ServiceViewPreferences { + #[serde( + default, + alias = "organization_id", + deserialize_with = "crate::models::user::deserialize_service_view_ids" + )] + pub organization_ids: Vec, + #[serde( + default, + alias = "service_group_id", + deserialize_with = "crate::models::user::deserialize_service_view_ids" + )] + pub service_group_ids: Vec, + pub search: String, + pub source: ServiceViewSource, + pub state: ServiceViewState, + pub service_type: ServiceViewType, + pub show_auto_connected: bool, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/backend/src/models/user_api_key.rs b/backend/src/models/user_api_key.rs index 10221a4b0..8ee2ce8bf 100644 --- a/backend/src/models/user_api_key.rs +++ b/backend/src/models/user_api_key.rs @@ -5,6 +5,16 @@ use super::bson_datetime; pub const COLLECTION_NAME: &str = "user_api_keys"; +/// App actually used by a successful OAuth exchange or refresh. This is +/// descriptive metadata; `credential_source` remains the routing choice. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OAuthAppObservation { + pub source: String, + pub credential_epoch: i64, + #[serde(with = "bson::serde_helpers::chrono_datetime_as_bson_datetime")] + pub observed_at: DateTime, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserApiKey { #[serde(rename = "_id")] @@ -65,6 +75,9 @@ pub struct UserApiKey { #[serde(default, skip_serializing_if = "Option::is_none")] pub credential_source: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub oauth_app_observation: Option, + /// "active" | "expired" | "revoked" | "failed" | "refresh_failed" | "pending_auth" pub status: String, #[serde(default, with = "bson_datetime::optional")] @@ -116,6 +129,7 @@ mod tests { #[test] fn bson_roundtrip_api_key() { let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), @@ -153,6 +167,7 @@ mod tests { #[test] fn missing_credential_epoch_defaults_to_one() { let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), @@ -188,6 +203,7 @@ mod tests { fn bson_roundtrip_oauth2() { let conn_id = uuid::Uuid::new_v4().to_string(); let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), diff --git a/backend/src/mw/auth.rs b/backend/src/mw/auth.rs index 9404126ec..ff6027c26 100644 --- a/backend/src/mw/auth.rs +++ b/backend/src/mw/auth.rs @@ -2750,6 +2750,7 @@ mod tests { .unwrap(); db.collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, id: actor_external_key_id.clone(), user_id: actor_id.to_string(), label: "Secret-bearing fixture".to_string(), diff --git a/backend/src/routes.rs b/backend/src/routes.rs index 1bea2c287..64eefe72a 100644 --- a/backend/src/routes.rs +++ b/backend/src/routes.rs @@ -540,6 +540,10 @@ fn build_router_internal(router_state: Option) -> (Router, R .route("/me", get(handlers::users::get_me)) .route("/me", put(handlers::users::update_me)) .route("/me", delete(handlers::users::delete_me)) + .route( + "/me/preferences/services", + put(handlers::users::save_services_view), + ) // Assistant postcondition evidence. These MUST be mounted on the // production router: a browser journey that proves success by a 404 // cannot distinguish "resource absent" from "route absent", so an @@ -1926,6 +1930,10 @@ fn build_router_internal(router_state: Option) -> (Router, R // Shared management routes; individual groups retain service-account gates. // Delegated reads require account:read and the existing route/method policy. let api_v1_shared = Router::new() + .route( + "/service-insights", + get(handlers::service_insights::get_insights), + ) .route( "/assistant-attachments/{id}/content", get(handlers::assistant_uploads::thread_image), diff --git a/backend/src/services/admin_usage_service/tests.rs b/backend/src/services/admin_usage_service/tests.rs index 39962a9aa..c62dd432d 100644 --- a/backend/src/services/admin_usage_service/tests.rs +++ b/backend/src/services/admin_usage_service/tests.rs @@ -2269,7 +2269,10 @@ async fn pico_costs_retain_472_micros_across_raw_rollup_api_and_analytics() { let response = crate::handlers::billing::get_usage( axum::extract::State(test_app_state(db.clone())), test_auth_user(&actor), - axum::extract::Query(crate::handlers::billing::UsageQuery { period: None }), + axum::extract::Query(crate::handlers::billing::UsageQuery { + period: None, + bucket: None, + }), ) .await .unwrap() diff --git a/backend/src/services/agent_binding_service.rs b/backend/src/services/agent_binding_service.rs index acf167717..9457770c2 100644 --- a/backend/src/services/agent_binding_service.rs +++ b/backend/src/services/agent_binding_service.rs @@ -697,6 +697,7 @@ mod tests { fn make_user_api_key(id: &str, user_id: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: id.to_string(), user_id: user_id.to_string(), diff --git a/backend/src/services/billing/funding.rs b/backend/src/services/billing/funding.rs index 5dabf2961..26a272197 100644 --- a/backend/src/services/billing/funding.rs +++ b/backend/src/services/billing/funding.rs @@ -1524,6 +1524,7 @@ mod tests { wallet_id: Some("wallet-1".to_string()), actor_user_id: owner_id.to_string(), api_key_id: None, + user_service_id: None, service_id: Some(service_id.to_string()), service_slug: Some(service_slug.to_string()), metric: BillingMetric::Requests, diff --git a/backend/src/services/billing/funding/target_tests.rs b/backend/src/services/billing/funding/target_tests.rs index b7d276afc..8e85c5479 100644 --- a/backend/src/services/billing/funding/target_tests.rs +++ b/backend/src/services/billing/funding/target_tests.rs @@ -116,6 +116,7 @@ async fn member_removal_blocks_new_funding_but_preserves_admitted_reservations() wallet_id: Some("wallet".into()), actor_user_id: "person".into(), api_key_id: None, + user_service_id: None, service_id: Some("service".into()), service_slug: Some("service".into()), metric: BillingMetric::Requests, diff --git a/backend/src/services/billing/lago_client.rs b/backend/src/services/billing/lago_client.rs index 6cf6b84d0..017e62c8c 100644 --- a/backend/src/services/billing/lago_client.rs +++ b/backend/src/services/billing/lago_client.rs @@ -3164,6 +3164,7 @@ mod tests { wallet_id: Some("wallet-1".to_string()), actor_user_id: "owner-1".to_string(), api_key_id: None, + user_service_id: None, service_id: Some("service-1".to_string()), service_slug: Some("service-one".to_string()), metric: crate::models::service_billing::BillingMetric::Requests, diff --git a/backend/src/services/billing/meter.rs b/backend/src/services/billing/meter.rs index cb2c20076..73362b9b1 100644 --- a/backend/src/services/billing/meter.rs +++ b/backend/src/services/billing/meter.rs @@ -568,6 +568,7 @@ pub(super) fn reserved_row( wallet_id, actor_user_id: ctx.actor_user_id.clone(), api_key_id: ctx.api_key_id.clone(), + user_service_id: ctx.user_service_id.clone(), service_id: ctx .catalog_service_id .clone() @@ -1055,6 +1056,10 @@ mod tests { .expect("collect rows"); assert_eq!(rows.len(), 2); + assert!(rows.iter().all( + |row| row.user_service_id.as_deref() == Some("user-service-1") + && row.service_id.as_deref() == Some("catalog-1") + )); assert!(rows.iter().any(|row| { row.layer == BillingLayer::Platform && row.transaction_id == "billing-request-1:platform" diff --git a/backend/src/services/billing/reconcile.rs b/backend/src/services/billing/reconcile.rs index a89f3de9c..da288578d 100644 --- a/backend/src/services/billing/reconcile.rs +++ b/backend/src/services/billing/reconcile.rs @@ -710,6 +710,7 @@ mod tests { wallet_id: Some("wallet-1".to_string()), actor_user_id: "actor-1".to_string(), api_key_id: None, + user_service_id: None, service_id: Some("service-1".to_string()), service_slug: Some("service-one".to_string()), metric: BillingMetric::Requests, diff --git a/backend/src/services/billing/usage_rollup.rs b/backend/src/services/billing/usage_rollup.rs index c3f7f3ce2..90bf9205e 100644 --- a/backend/src/services/billing/usage_rollup.rs +++ b/backend/src/services/billing/usage_rollup.rs @@ -475,6 +475,10 @@ async fn raw_increments( doc! { "$dateTrunc": { "date": "$created_at", "unit": "hour", "timezone": "UTC" } }, ); key.insert("exact", "$exact"); + key.insert( + "user_service_id", + doc! { "$ifNull": ["$user_service_id", null] }, + ); group.insert("rows_folded", doc! { "$sum": 1_i64 }); let mut groups: Vec = db .collection::(METERS) @@ -492,8 +496,12 @@ async fn raw_increments( return Err(AppError::Internal("Missing usage group key".into())); }; let mut partition_key = Document::new(); - for field in ["api_key", "acked"] { - if let Some(value) = key.remove(field) { + // Keep replay identities stable across replicas. Exact connection is + // additive partition metadata, like the API key, not a new bucket key. + for field in ["api_key", "acked", "user_service_id"] { + if let Some(value) = key.remove(field) + && (field != "user_service_id" || !matches!(value, Bson::Null)) + { partition_key.insert(field, value); } } diff --git a/backend/src/services/billing/usage_rollup/tests.rs b/backend/src/services/billing/usage_rollup/tests.rs index dcfc1292b..ce9f9c543 100644 --- a/backend/src/services/billing/usage_rollup/tests.rs +++ b/backend/src/services/billing/usage_rollup/tests.rs @@ -52,6 +52,55 @@ fn bootstrap_increment(at: DateTime, partitions: usize) -> UsageRollupHourl bson::from_document(document).unwrap() } +#[tokio::test] +async fn exact_connections_remain_distinct_through_rollup_and_legacy_stays_unknown() { + let db = connect_test_database("rollup_exact_connection") + .await + .unwrap(); + let at = hour(Utc::now()); + let mut rows = vec![row(at), row(at), row(at)]; + rows[0].insert("user_service_id", "connection-a"); + rows[1].insert("user_service_id", "connection-b"); + for row in &mut rows { + row.insert("wallet_id", "wallet"); + } + let ids: Vec<_> = rows + .iter() + .map(|r| r.get_str("_id").unwrap().to_owned()) + .collect(); + db.collection::(METERS) + .insert_many(rows) + .await + .unwrap(); + let increments = raw_increments(&db, &ids, 1).await.unwrap(); + assert_eq!(increments.len(), 1); + let connections: std::collections::HashSet<_> = increments[0] + .cost_partitions + .values() + .map(|p| p.key.get_str("user_service_id").ok()) + .collect(); + assert_eq!( + connections, + std::collections::HashSet::from([Some("connection-a"), Some("connection-b"), None]) + ); + let daily = daily_increments(&increments).unwrap(); + assert_eq!(daily.len(), 1); + let daily_connections: std::collections::HashSet<_> = daily[0] + .cost_partitions + .values() + .map(|p| p.key.get_str("user_service_id").ok()) + .collect(); + assert_eq!(daily_connections, connections); + // Both replay bucket identities stay identical to legacy-shaped input. + db.collection::(METERS) + .update_many(doc! {}, doc! { "$unset": { "user_service_id": "" } }) + .await + .unwrap(); + let legacy = raw_increments(&db, &ids, 1).await.unwrap(); + assert_eq!(legacy[0].id, increments[0].id); + assert_eq!(daily_increments(&legacy).unwrap()[0].id, daily[0].id); +} + #[test] fn bootstrap_byte_budget_splits_oversized_increments_and_counts_exact_bson_size() { let increment = bootstrap_increment(hour(Utc::now()), 64); diff --git a/backend/src/services/billing/webhook.rs b/backend/src/services/billing/webhook.rs index 049b9c7ec..a2db48877 100644 --- a/backend/src/services/billing/webhook.rs +++ b/backend/src/services/billing/webhook.rs @@ -760,6 +760,7 @@ mod tests { wallet_id: Some(wallet_id.to_string()), actor_user_id: owner_id.to_string(), api_key_id: None, + user_service_id: None, service_id: Some("svc-1".to_string()), service_slug: Some("svc".to_string()), metric: BillingMetric::Requests, diff --git a/backend/src/services/connection_expiry_service.rs b/backend/src/services/connection_expiry_service.rs index 30c80cdea..34d3f9fd4 100644 --- a/backend/src/services/connection_expiry_service.rs +++ b/backend/src/services/connection_expiry_service.rs @@ -350,6 +350,7 @@ mod tests { fn oauth_key(user_id: &str) -> UserApiKey { let now = Utc::now(); UserApiKey { + oauth_app_observation: None, id: Uuid::new_v4().to_string(), user_id: user_id.to_string(), label: "GitHub work account".to_string(), diff --git a/backend/src/services/destination_routing_tests.rs b/backend/src/services/destination_routing_tests.rs index c1650f356..117f09c95 100644 --- a/backend/src/services/destination_routing_tests.rs +++ b/backend/src/services/destination_routing_tests.rs @@ -519,6 +519,7 @@ pub(crate) async fn mcp_call( &state.token_exchange_cache, &state.cloud_response_cache, &mcp_service::McpExecContext { + attribution: None, org_agent_access: None, agent_owner: None, operation_scopes: None, diff --git a/backend/src/services/exact_service_approval_service.rs b/backend/src/services/exact_service_approval_service.rs index bafb831a8..9fd4bffc9 100644 --- a/backend/src/services/exact_service_approval_service.rs +++ b/backend/src/services/exact_service_approval_service.rs @@ -479,6 +479,23 @@ pub async fn redeem_request( } }; let exec_ctx = mcp_service::McpExecContext { + attribution: Some(super::service_insights_activity::RequestAttribution { + actor: super::audit_service::AuditActor { + user_id: caller.actor_user_id.clone(), + api_key_id: caller.api_key_id.clone(), + api_key_name: caller + .api_key_id + .as_ref() + .and(caller.requester_label.clone()), + ip_address: None, + user_agent: None, + }, + auth_kind: caller.requester_type.clone(), + acting_client_id: (caller.requester_type == "delegated") + .then(|| caller.requester_id.clone()), + oauth_client_id: None, + api_key_credential_id: None, + }), org_agent_access: caller.org_agent_access.as_deref(), agent_owner: caller.agent_owner.as_deref(), operation_scopes: Some(&caller.operation_scopes), diff --git a/backend/src/services/gcp_sa_service.rs b/backend/src/services/gcp_sa_service.rs index 6c7efbe2d..6dab95fbc 100644 --- a/backend/src/services/gcp_sa_service.rs +++ b/backend/src/services/gcp_sa_service.rs @@ -465,6 +465,7 @@ mod tests { fn make_gcp_key(sa_json_enc: Vec) -> UserApiKey { let now = Utc::now(); UserApiKey { + oauth_app_observation: None, credential_source: None, id: Uuid::new_v4().to_string(), user_id: Uuid::new_v4().to_string(), diff --git a/backend/src/services/mcp_service.rs b/backend/src/services/mcp_service.rs index 8d4075e8b..bd9db50bc 100644 --- a/backend/src/services/mcp_service.rs +++ b/backend/src/services/mcp_service.rs @@ -165,6 +165,7 @@ impl McpBillingRouteContextBuilder { /// node allow-list enforcement. OAuth and session callers pass `api_key_id: /// None` and `allow_all_nodes: true`, preserving their existing behavior. pub struct McpExecContext<'a> { + pub attribution: Option, pub org_agent_access: Option<&'a super::org_agent_service::RequestAccess>, pub agent_owner: Option<&'a str>, pub operation_scopes: Option<&'a crate::models::agent_operation_scope::OperationScopes>, @@ -4825,6 +4826,7 @@ pub async fn execute_tool_resolved( } else { build_downstream_request_headers(endpoint, body.is_some())? }; + let resource_owner_id = billing_context_builder.effective_owner_id.clone(); let billing_ctx = billing_context_builder .build( billing.as_ref(), @@ -4837,7 +4839,39 @@ pub async fn execute_tool_resolved( ) .await?; let billing_ctx = billing_ctx.with_request_body(body.as_deref()); - let metered = billing.open(&billing_ctx).await?; + let attribution = exec_ctx.attribution.clone().unwrap_or_else(|| { + super::service_insights_activity::RequestAttribution { + actor: super::audit_service::AuditActor { + user_id: user_id.into(), + api_key_id: exec_ctx.api_key_id.map(str::to_owned), + api_key_name: None, + ip_address: None, + user_agent: None, + }, + auth_kind: if exec_ctx.api_key_id.is_some() { + "api_key" + } else { + "unknown" + } + .into(), + oauth_client_id: None, + acting_client_id: None, + api_key_credential_id: None, + } + }); + let mut request_audit = super::service_insights_activity::RequestAudit::from_attribution( + db, + attribution, + billing_ctx.user_service_id.as_deref(), + &target.service.id, + &resource_owner_id, + &billing_ctx.billing_request_id, + billing_ctx.credential_class, + ); + let metered = billing + .open(&billing_ctx) + .await + .inspect_err(|error| request_audit.admission_error(error))?; let request_len = body.as_ref().map(|body| body.len() as i64).unwrap_or(0); // ------------------------------------------------------------------- @@ -4955,6 +4989,7 @@ pub async fn execute_tool_resolved( ) .await?; destination_audit.complete(resp.status); + request_audit.response(resp.status); return Ok(McpToolExecutionOutcome::Response(tool_response( resp.status, header_value(&resp.headers, "content-type"), @@ -4983,6 +5018,7 @@ pub async fn execute_tool_resolved( ) .await?; destination_audit.complete(status); + request_audit.response(status); return Ok(McpToolExecutionOutcome::Response(tool_response( status, header_value(&headers, "content-type"), @@ -5121,6 +5157,7 @@ pub async fn execute_tool_resolved( .await?; destination_audit.complete(status); + request_audit.response(status); Ok(McpToolExecutionOutcome::Response(ToolResponse { status, text: body_text, @@ -6002,6 +6039,7 @@ mod tests { &state.token_exchange_cache, &state.cloud_response_cache, &McpExecContext { + attribution: None, org_agent_access: None, agent_owner: None, operation_scopes: None, diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index 8ad327679..3e1d355ee 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -112,6 +112,7 @@ pub mod node_routing_service; pub mod node_service; pub mod node_ws_manager; pub mod notification_service; +pub mod oauth_app_source; pub mod oauth_broker_service; pub mod oauth_client_service; pub mod oauth_consent_request_service; @@ -180,6 +181,7 @@ pub mod url_validation; pub mod user_api_key_service; pub mod user_credentials_service; pub mod user_endpoint_service; +pub mod user_preferences_service; pub mod user_service_service; pub mod user_token_service; pub mod webhook_delivery_service; @@ -213,6 +215,8 @@ pub mod channel_turn_delivery; pub mod provider_link_service; pub mod retired_service_service; pub mod service_history; +pub mod service_insights_activity; +pub mod service_insights_billing; #[cfg(test)] pub(crate) mod assistant_authority_tests; diff --git a/backend/src/services/oauth_app_source.rs b/backend/src/services/oauth_app_source.rs new file mode 100644 index 000000000..37cdd6395 --- /dev/null +++ b/backend/src/services/oauth_app_source.rs @@ -0,0 +1,358 @@ +use std::collections::HashMap; + +use futures::TryStreamExt; +use mongodb::{Database, bson::doc}; +use serde::Deserialize; + +use crate::errors::AppResult; +use crate::models::user_api_key::{OAuthAppObservation, UserApiKey}; +use crate::models::user_provider_token::COLLECTION_NAME; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OAuthAppSource { + Platform, + Byo, +} + +impl OAuthAppSource { + pub fn from_credential_owner(owner: Option<&str>) -> Self { + if owner.is_some() { + Self::Byo + } else { + Self::Platform + } + } + + pub fn observation(self, credential_epoch: i64) -> OAuthAppObservation { + OAuthAppObservation { + source: self.as_str().into(), + credential_epoch, + observed_at: chrono::Utc::now(), + } + } + + pub fn as_str(self) -> &'static str { + match self { + Self::Platform => "platform", + Self::Byo => "byo", + } + } +} + +/// App selection or a successful exchange is evidence; a connection ID, +/// retained developer app, or unexpired token alone is not. +pub fn from_key(key: &UserApiKey) -> Option { + if !matches!(key.credential_type.as_str(), "oauth2" | "device_code") { + return None; + } + let observed = key + .oauth_app_observation + .as_ref() + .filter(|observation| { + has_token_material(key) + && observation.credential_epoch == key.credential_epoch + && key + .last_authorized_at + .is_none_or(|authorized| observation.observed_at >= authorized) + }) + .and_then(|observation| parse_source(&observation.source)); + match key.credential_source.as_deref() { + Some(source) if observed.is_some() && parse_source(source) != observed => None, + Some("platform") => Some(OAuthAppSource::Platform), + Some("byo") => Some(OAuthAppSource::Byo), + Some(_) => None, + None => observed, + } +} + +fn parse_source(source: &str) -> Option { + match source { + "platform" => Some(OAuthAppSource::Platform), + "byo" => Some(OAuthAppSource::Byo), + _ => None, + } +} + +fn has_token_material(key: &UserApiKey) -> bool { + key.access_token_encrypted + .as_ref() + .is_some_and(|token| !token.is_empty()) + || key + .refresh_token_encrypted + .as_ref() + .is_some_and(|token| !token.is_empty()) +} + +#[derive(Deserialize)] +struct LegacyAppMetadata { + #[serde(rename = "_id")] + id: String, + user_id: String, + provider_config_id: String, + #[serde(default)] + credential_user_id: Option, + #[serde(default)] + connection_id: Option, + #[serde(default, with = "crate::models::bson_bytes::optional")] + access_token_encrypted: Option>, + #[serde(default, with = "crate::models::bson_bytes::optional")] + refresh_token_encrypted: Option>, +} + +fn needs_legacy_lookup(key: &UserApiKey) -> bool { + matches!(key.credential_type.as_str(), "oauth2" | "device_code") + && key.credential_source.is_none() + && key.provider_config_id.is_some() + && from_key(key).is_none() + && has_token_material(key) +} + +fn from_legacy(key: &UserApiKey, tokens: &[LegacyAppMetadata]) -> Option { + let provider_id = key.provider_config_id.as_deref()?; + let source_id = matches!( + key.source.as_deref(), + Some("migration_provider_token" | "user_created") + ) + .then_some(key.source_id.as_deref()) + .flatten(); + let mut matches = tokens.iter().filter(|token| { + token.user_id == key.user_id + && token.provider_config_id == provider_id + && source_id.is_none_or(|id| token.id == id) + && (key.connection_id.is_none() + || source_id.is_some() + || key.connection_id == token.connection_id) + // Migration and legacy sync copy ciphertext unchanged. Matching + // both token fields ties the source to this credential revision. + && key.access_token_encrypted == token.access_token_encrypted + && key.refresh_token_encrypted == token.refresh_token_encrypted + && has_token_material(key) + }); + let token = matches.next()?; + if matches.next().is_some() { + return None; + } + Some(OAuthAppSource::from_credential_owner( + token.credential_user_id.as_deref(), + )) +} + +/// Request-scoped projection for already-authorized credential records. Only +/// legacy token copies are matched without decrypting them. Ciphertext is kept +/// inside this resolver; only the source enum is returned. +pub async fn load( + db: &Database, + keys: &[&UserApiKey], +) -> AppResult> { + let filters: Vec<_> = keys + .iter() + .filter(|key| needs_legacy_lookup(key)) + .map(|key| { + let mut filter = doc! { + "user_id": &key.user_id, + "provider_config_id": key.provider_config_id.as_deref().unwrap(), + }; + if matches!( + key.source.as_deref(), + Some("migration_provider_token" | "user_created") + ) && let Some(id) = &key.source_id + { + filter.insert("_id", id); + } else if let Some(connection_id) = &key.connection_id { + filter.insert("connection_id", connection_id); + } + filter + }) + .collect(); + let tokens: Vec = if filters.is_empty() { + Vec::new() + } else { + db.collection::(COLLECTION_NAME) + .find(doc! { "$or": filters }) + .projection(doc! { + "_id": 1, "user_id": 1, "provider_config_id": 1, + "credential_user_id": 1, "connection_id": 1, + "access_token_encrypted": 1, "refresh_token_encrypted": 1, + }) + .await? + .try_collect() + .await? + }; + Ok(keys + .iter() + .filter_map(|key| { + from_key(key) + .or_else(|| { + needs_legacy_lookup(key) + .then(|| from_legacy(key, &tokens)) + .flatten() + }) + .map(|source| (key.id.clone(), source)) + }) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn key() -> UserApiKey { + mongodb::bson::from_document(doc! { + "_id": "key", "user_id": "person", "label": "X", + "credential_type": "oauth2", "status": "active", + "provider_config_id": "x", "connection_id": "connection", + "access_token_encrypted": mongodb::bson::Binary { + subtype: mongodb::bson::spec::BinarySubtype::Generic, bytes: vec![1, 2, 3], + }, + "created_at": mongodb::bson::DateTime::now(), + "updated_at": mongodb::bson::DateTime::now(), + }) + .unwrap() + } + + fn token(id: &str, owner: &str, provider: &str, supplied: bool) -> LegacyAppMetadata { + LegacyAppMetadata { + id: id.into(), + user_id: owner.into(), + provider_config_id: provider.into(), + credential_user_id: supplied.then(|| owner.into()), + connection_id: None, + access_token_encrypted: Some(vec![1, 2, 3]), + refresh_token_encrypted: None, + } + } + + #[test] + fn oauth_app_metadata_requires_evidence_for_modern_keys() { + let mut key = key(); + key.expires_at = Some(chrono::Utc::now() + chrono::Duration::days(365)); + assert_eq!(from_key(&key), None); + key.user_oauth_client_id_encrypted = Some(vec![1]); + assert_eq!(from_key(&key), None); + key.oauth_app_observation = Some(OAuthAppSource::Byo.observation(key.credential_epoch)); + assert_eq!(from_key(&key), Some(OAuthAppSource::Byo)); + key.credential_epoch += 1; + assert_eq!(from_key(&key), None); + key.credential_source = Some("platform".into()); + assert_eq!(from_key(&key), Some(OAuthAppSource::Platform)); + key.credential_source = Some("unsupported".into()); + assert_eq!(from_key(&key), None); + } + + #[test] + fn oauth_app_metadata_observation_survives_expiry_but_not_reauthorization_or_conflict() { + let mut key = key(); + let observation = OAuthAppSource::Platform.observation(key.credential_epoch); + key.last_authorized_at = Some(observation.observed_at); + key.oauth_app_observation = Some(observation.clone()); + key.status = "revoked".into(); + key.expires_at = Some(chrono::Utc::now() - chrono::Duration::days(1)); + assert_eq!(from_key(&key), Some(OAuthAppSource::Platform)); + key.credential_source = Some("byo".into()); + assert_eq!(from_key(&key), None); + key.credential_source = None; + key.access_token_encrypted = None; + assert_eq!(from_key(&key), None); + key.access_token_encrypted = Some(vec![1, 2, 3]); + key.last_authorized_at = Some(observation.observed_at + chrono::Duration::seconds(1)); + assert_eq!(from_key(&key), None); + } + + #[tokio::test] + async fn oauth_app_metadata_batch_matches_actual_migrated_tokens() { + let db = crate::test_utils::connect_test_database("oauth_app_metadata") + .await + .unwrap(); + let mut migrated = key(); + migrated.source = Some("migration_provider_token".into()); + migrated.source_id = Some("original".into()); + // A migrated modern key may carry an unrelated retained app. + migrated.user_oauth_client_id_encrypted = Some(vec![99]); + let mut changed = migrated.clone(); + changed.id = "reauthorized".into(); + changed.access_token_encrypted = Some(vec![8, 9]); + let mut wrong_owner = migrated.clone(); + wrong_owner.id = "wrong-owner".into(); + wrong_owner.user_id = "different-owner".into(); + let mut legacy = key(); + legacy.id = "legacy-byo".into(); + legacy.connection_id = None; + legacy.user_id = "org".into(); + let mut explicit = migrated.clone(); + explicit.id = "explicit".into(); + explicit.credential_source = Some("byo".into()); + db.collection::(COLLECTION_NAME) + .insert_many([ + doc! { + "_id": "original", "user_id": "person", "provider_config_id": "x", + "access_token_encrypted": mongodb::bson::Binary { + subtype: mongodb::bson::spec::BinarySubtype::Generic, bytes: vec![1, 2, 3], + }, + }, + doc! { + "_id": "org-token", "user_id": "org", "provider_config_id": "x", + "credential_user_id": "org", + "access_token_encrypted": mongodb::bson::Binary { + subtype: mongodb::bson::spec::BinarySubtype::Generic, bytes: vec![1, 2, 3], + }, + }, + ]) + .await + .unwrap(); + let sources = load( + &db, + &[&migrated, &changed, &wrong_owner, &legacy, &explicit], + ) + .await + .unwrap(); + assert_eq!(sources.len(), 3); + assert_eq!(sources[&migrated.id], OAuthAppSource::Platform); + assert_eq!(sources[&legacy.id], OAuthAppSource::Byo); + assert_eq!(sources[&explicit.id], OAuthAppSource::Byo); + assert!(!sources.contains_key(&changed.id)); + assert!(!sources.contains_key(&wrong_owner.id)); + } + + #[test] + fn oauth_app_metadata_legacy_uses_exact_owner_provider_and_migration_source() { + let mut key = key(); + key.connection_id = None; + key.status = "revoked".into(); + key.source = Some("migration_provider_token".into()); + key.source_id = Some("original".into()); + // A retained unrelated BYO app must not change the original token's app. + key.user_oauth_client_id_encrypted = Some(vec![1]); + let tokens = [ + token("original", "person", "x", false), + token("other", "person", "x", true), + token("original", "org", "x", true), + token("original", "person", "other-provider", true), + ]; + assert_eq!(from_key(&key), None); + assert_eq!(from_legacy(&key, &tokens), Some(OAuthAppSource::Platform)); + key.source_id = Some("other".into()); + assert_eq!(from_legacy(&key, &tokens), Some(OAuthAppSource::Byo)); + key.source_id = Some("missing".into()); + assert_eq!(from_legacy(&key, &tokens), None); + } + + #[test] + fn oauth_app_metadata_does_not_guess_from_absent_or_ambiguous_legacy_tokens() { + let mut key = key(); + key.connection_id = None; + assert!(needs_legacy_lookup(&key)); + assert_eq!(from_legacy(&key, &[]), None); + let tokens = [ + token("a", "person", "x", false), + token("b", "person", "x", true), + ]; + assert_eq!(from_legacy(&key, &tokens), None); + assert_eq!( + from_legacy(&key, &tokens[..1]), + Some(OAuthAppSource::Platform) + ); + key.credential_type = "api_key".into(); + assert!(!needs_legacy_lookup(&key)); + assert_eq!(from_key(&key), None); + } +} diff --git a/backend/src/services/oauth_revocation.rs b/backend/src/services/oauth_revocation.rs index 5a400bd28..7966d2668 100644 --- a/backend/src/services/oauth_revocation.rs +++ b/backend/src/services/oauth_revocation.rs @@ -569,6 +569,7 @@ mod tests { fn credentials() -> ResolvedOAuthCredentials { ResolvedOAuthCredentials { + app_source: crate::services::oauth_app_source::OAuthAppSource::Platform, client_id: "client-id".to_string(), client_secret: Some("client-secret".to_string()), credential_user_id: None, diff --git a/backend/src/services/ownership_transfer_tests.rs b/backend/src/services/ownership_transfer_tests.rs index 5b4e5014c..4386e163a 100644 --- a/backend/src/services/ownership_transfer_tests.rs +++ b/backend/src/services/ownership_transfer_tests.rs @@ -699,6 +699,7 @@ async fn x_channel_onboarding_transfer_moves_one_live_credential_and_rotates_cal Some("stale-callback-refresh"), Some(&X_REQUIRED_SCOPES.join(" ")), None, + None, ) .await .is_err() diff --git a/backend/src/services/permission_policy_service_tests.rs b/backend/src/services/permission_policy_service_tests.rs index ec2c52473..7cf129640 100644 --- a/backend/src/services/permission_policy_service_tests.rs +++ b/backend/src/services/permission_policy_service_tests.rs @@ -73,6 +73,7 @@ async fn fixture() -> (crate::AppState, String, String) { source: None, source_id: None, credential_epoch: 1, + oauth_app_observation: None, created_at: now, updated_at: now, }; diff --git a/backend/src/services/platform_key_service/tests.rs b/backend/src/services/platform_key_service/tests.rs index f8382b76f..297de3155 100644 --- a/backend/src/services/platform_key_service/tests.rs +++ b/backend/src/services/platform_key_service/tests.rs @@ -624,6 +624,7 @@ async fn platform_key_http_llm_gateway_and_mcp_use_server_credential_and_live_ac ) .unwrap(); let ctx = mcp_service::McpExecContext { + attribution: None, org_agent_access: None, agent_owner: None, operation_scopes: None, diff --git a/backend/src/services/proxy_service.rs b/backend/src/services/proxy_service.rs index 72bdb2024..0874df877 100644 --- a/backend/src/services/proxy_service.rs +++ b/backend/src/services/proxy_service.rs @@ -5727,6 +5727,7 @@ mod tests { let encrypted = keys.encrypt(override_secret.as_bytes()).await.unwrap(); db.collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: override_credential_id.clone(), user_id: user_id.clone(), @@ -8419,6 +8420,7 @@ mod tests { fn authority_test_key(credential_type: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), @@ -8497,6 +8499,7 @@ mod tests { #[test] fn missing_credential_error_oauth2_with_provider() { let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: "k".into(), user_id: "u".into(), @@ -8531,6 +8534,7 @@ mod tests { #[test] fn missing_credential_error_api_key() { let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: "k".into(), user_id: "u".into(), diff --git a/backend/src/services/service_history/projection.rs b/backend/src/services/service_history/projection.rs index 89e681077..a23c82213 100644 --- a/backend/src/services/service_history/projection.rs +++ b/backend/src/services/service_history/projection.rs @@ -87,7 +87,11 @@ fn operational(entity: &str, key: &str) -> bool { ) || (entity == "user_api_keys" && matches!( key, - "expires_at" | "error_message" | "status" | "oauth_attempt_nonce" + "expires_at" + | "error_message" + | "status" + | "oauth_attempt_nonce" + | "oauth_app_observation" )) } diff --git a/backend/src/services/service_history/tests.rs b/backend/src/services/service_history/tests.rs index 4f28ae45e..f418aa629 100644 --- a/backend/src/services/service_history/tests.rs +++ b/backend/src/services/service_history/tests.rs @@ -189,6 +189,46 @@ async fn shared_endpoint_fanout_and_concurrent_before_after() { assert_eq!(transitions, (0..6).map(|v| (v, v + 1)).collect::>()); } +#[test] +fn oauth_app_metadata_refresh_does_not_count_as_a_service_edit() { + let now = bson::DateTime::now(); + let before = doc! { + "_id": "key", "user_id": "owner", "label": "X", "credential_type": "oauth2", + "status": "active", "credential_epoch": 1_i64, "created_at": now, "updated_at": now, + "access_token_encrypted": bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: vec![1] }, + }; + let mut after = before.clone(); + after.insert( + "access_token_encrypted", + bson::Binary { + subtype: bson::spec::BinarySubtype::Generic, + bytes: vec![2], + }, + ); + after.insert( + "oauth_app_observation", + doc! { "source": "platform", "credential_epoch": 1_i64, "observed_at": now }, + ); + let (changes, additional, changed) = projection::diff("user_api_keys", &before, &after, true); + assert!(changes.is_empty()); + assert!(!additional); + assert!(!changed); + // Later observation timestamps are operational too. + let mut refreshed = after.clone(); + refreshed + .get_document_mut("oauth_app_observation") + .unwrap() + .insert( + "observed_at", + bson::DateTime::from_millis(now.timestamp_millis() + 1000), + ); + let (changes, additional, changed) = + projection::diff("user_api_keys", &after, &refreshed, true); + assert!(changes.is_empty()); + assert!(!additional); + assert!(!changed); +} + #[tokio::test] async fn credential_replacement_refresh_removal_and_disabled_delete() { let (db, mut s) = fixture().await; @@ -1079,3 +1119,63 @@ async fn new_and_rebound_references_are_included_in_retried_backing_fanout() { } } } + +#[tokio::test] +async fn authorship_summaries_stay_admin_only_within_membership_and_key_scopes() { + use crate::models::org_membership::OrgRole; + let (db, mut service) = fixture().await; + let actor_id = service.user_id.clone(); + let org = uuid::Uuid::new_v4().to_string(); + service.user_id = org.clone(); + db.collection("users") + .insert_one(test_user(&org, UserType::Org)) + .await + .unwrap(); + collection::(&db, "user_services") + .insert_one(&service) + .await + .unwrap(); + let mut other = service.clone(); + other.id = uuid::Uuid::new_v4().to_string(); + collection::(&db, "user_services") + .insert_one(&other) + .await + .unwrap(); + let ids = vec![service.id.clone(), other.id.clone()]; + let reader = read::Reader { + actor_id: &actor_id, + allowed_service_ids: None, + }; + for role in [OrgRole::Admin, OrgRole::Member, OrgRole::Viewer] { + let membership = test_membership(&org, &actor_id, role, Some(vec![service.id.clone()])); + let summaries = read::summaries(&db, &reader, &ids, std::slice::from_ref(&membership)) + .await + .unwrap(); + if role != OrgRole::Admin { + // Members and viewers do not receive history metadata. + assert!(summaries.is_empty()); + continue; + } + assert_eq!(summaries.len(), 1); + assert!(summaries.contains_key(&service.id)); + let excluded = vec![other.id.clone()]; + let restricted = read::Reader { + actor_id: &actor_id, + allowed_service_ids: Some(&excluded), + }; + assert!( + read::summaries(&db, &restricted, &ids, std::slice::from_ref(&membership)) + .await + .unwrap() + .is_empty() + ); + let mut revoked = membership; + revoked.revoked_at = Some(chrono::Utc::now()); + assert!( + read::summaries(&db, &reader, &ids, &[revoked]) + .await + .unwrap() + .is_empty() + ); + } +} diff --git a/backend/src/services/service_insights_activity.rs b/backend/src/services/service_insights_activity.rs new file mode 100644 index 000000000..7988daeca --- /dev/null +++ b/backend/src/services/service_insights_activity.rs @@ -0,0 +1,1033 @@ +//! Metadata-only projections of current scope grants and exact recorded requests. +use std::{ + collections::{HashMap, HashSet}, + time::Duration, +}; + +use bson::{Document, doc}; +use chrono::{DateTime, Utc}; +use futures::TryStreamExt; +use mongodb::Database; +use serde::Serialize; +use utoipa::ToSchema; + +use crate::{ + errors::{AppError, AppResult}, + models::{ + agent_service_binding::AgentServiceBinding, + api_key::{ApiKey, ApiKeyPurpose}, + audit_log::AuditLog, + user_service::UserService, + }, + mw::auth::{AuthMethod, AuthUser}, + services::{ + audit_service, key_service, + org_service::{self, OwnerAccess}, + }, +}; + +const KEY_LIMIT: usize = 500; +const REQUEST_LIMIT: i64 = 3; + +#[derive(Clone, Serialize, ToSchema)] +pub struct ConnectionActivity { + pub access: AccessSummary, + pub activity: ActivitySummary, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct AccessSummary { + /// Counts cover the visible key inventory, never other members' personal keys. + pub visibility: String, + pub keys: Vec, + pub total: usize, + pub truncated: bool, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct AccessKey { + pub id: String, + pub name: String, + pub platform: Option, + pub owner_id: String, + pub permission: String, + pub credential_override: bool, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct ActivitySummary { + pub visibility: String, + pub period_days: u32, + /// Historical events and uninstrumented ingress cannot prove a complete period. + pub tracking: String, + pub request_count: u64, + pub requests: Vec, + pub last_used: Option, + pub truncated: bool, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct RecentRequest { + pub id: String, + pub execution_id: Option, + pub caller: RequestCaller, + pub occurred_at: DateTime, + pub outcome: String, + pub response_status: Option, + pub source: Option, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct RecordedConnectionSource { + pub kind: String, + pub owner_id: String, +} + +#[derive(Clone, Serialize, ToSchema)] +pub struct RequestCaller { + pub id: Option, + pub kind: String, + pub name: String, + pub app_id: Option, + pub app_name: Option, +} + +fn can_execute(access: &OwnerAccess, service: &UserService) -> bool { + access.allows_resource(&service.id) + && match access { + OwnerAccess::Direct | OwnerAccess::AsOrgAdmin { .. } => true, + OwnerAccess::AsOrgMember { role, .. } => { + super::user_service_service::role_can_proxy_service(*role, service) + } + OwnerAccess::Forbidden => false, + } +} + +fn permission(key: &ApiKey, effective_ids: &[String], service_id: &str) -> Option<&'static str> { + if key.purpose != ApiKeyPurpose::General + || !key.is_active + || key.expires_at.is_some_and(|expiry| expiry <= Utc::now()) + || !crate::mw::auth::scope_allows_llm_proxy(&key.scopes) + { + return None; + } + if key.allow_all_services { + Some("all_services") + } else if key.allowed_service_ids.iter().any(|id| id == service_id) { + Some("selected_service") + } else if effective_ids.iter().any(|id| id == service_id) { + Some("platform_services") + } else { + None + } +} + +/// The handler must apply its token's service allowlist before passing rows here. +/// This layer independently checks owner/member visibility and scopes key inventory. +pub async fn insights( + db: &Database, + actor_id: &str, + services: &[UserService], +) -> AppResult> { + if services.len() > 100 { + return Err(AppError::ValidationError( + "At most 100 connections may be requested".into(), + )); + } + let mut owner_access = HashMap::new(); + for service in services { + if !owner_access.contains_key(&service.user_id) { + owner_access.insert( + service.user_id.clone(), + org_service::resolve_owner_access(db, actor_id, &service.user_id).await?, + ); + } + } + let visible: Vec<_> = services + .iter() + .filter(|service| { + owner_access + .get(&service.user_id) + .is_some_and(|access| access.can_read() && access.allows_resource(&service.id)) + }) + .collect(); + if visible.is_empty() { + return Ok(HashMap::new()); + } + let mut key_owners = HashSet::from([actor_id.to_owned()]); + for service in &visible { + if owner_access[&service.user_id].can_write() { + key_owners.insert(service.user_id.clone()); + } + } + let mut keys: Vec = db.collection("api_keys") + .find(doc! { "user_id": { "$in": key_owners.into_iter().collect::>() }, "is_active": true, "$or": [{ "expires_at": null }, { "expires_at": { "$gt": bson::DateTime::now() } }] }) + .sort(doc! { "name": 1, "_id": 1 }) + .limit((KEY_LIMIT + 1) as i64) + .max_time(Duration::from_secs(3)) + .await?.try_collect().await?; + let keys_truncated = keys.len() > KEY_LIMIT; + keys.truncate(KEY_LIMIT); + let ids: Vec<_> = visible.iter().map(|s| s.id.as_str()).collect(); + let key_ids: Vec<_> = keys.iter().map(|k| k.id.as_str()).collect(); + let bindings: Vec = db + .collection("agent_service_bindings") + .find(doc! { "api_key_id": { "$in": key_ids }, "user_service_id": { "$in": &ids } }) + .max_time(Duration::from_secs(3)) + .await? + .try_collect() + .await?; + let overrides: HashSet<_> = bindings + .iter() + .map(|b| { + ( + b.api_key_id.as_str(), + b.user_service_id.as_str(), + b.user_id.as_str(), + ) + }) + .collect(); + // Reuse the runtime expansion, once per owner, for dynamic platform grants. + let mut platform_grants: HashMap> = HashMap::new(); + for key in &keys { + if key.allow_auto_connected_services + && !key.allow_all_services + && !platform_grants.contains_key(&key.user_id) + { + let mut grant_key = key.clone(); + grant_key.allowed_service_ids.clear(); + platform_grants.insert( + key.user_id.clone(), + key_service::effective_allowed_service_ids(db, &grant_key).await?, + ); + } + } + let mut result = HashMap::new(); + let mut managed_ids = Vec::new(); + let mut own_ids = Vec::new(); + for service in &visible { + let access = &owner_access[&service.user_id]; + let managed = access.can_write(); + if managed { + managed_ids.push(service.id.as_str()); + } else { + own_ids.push(service.id.as_str()); + } + let mut allowed = Vec::new(); + for key in &keys { + let key_access = if key.user_id == service.user_id { + Some(&OwnerAccess::Direct) + } else if key.user_id == actor_id { + Some(access) + } else { + None + }; + if !key_access.is_some_and(|a| can_execute(a, service)) { + continue; + } + let effective = platform_grants + .get(&key.user_id) + .map(Vec::as_slice) + .unwrap_or_default(); + let effective = if key.allow_auto_connected_services { + effective + } else { + &[] + }; + if let Some(reason) = permission(key, effective, &service.id) { + allowed.push(AccessKey { + id: key.id.clone(), + name: key.name.clone(), + platform: key.platform.clone(), + owner_id: key.user_id.clone(), + permission: reason.into(), + credential_override: key.user_id == service.user_id + && overrides.contains(&( + key.id.as_str(), + service.id.as_str(), + service.user_id.as_str(), + )), + }); + } + } + result.insert( + service.id.clone(), + ConnectionActivity { + access: AccessSummary { + visibility: if managed { "managed_keys" } else { "own_keys" }.into(), + total: allowed.len(), + keys: allowed, + truncated: keys_truncated, + }, + activity: ActivitySummary { + visibility: if managed { + "all_requests" + } else { + "own_requests" + } + .into(), + period_days: 30, + tracking: "partial".into(), + request_count: 0, + requests: Vec::new(), + last_used: None, + truncated: false, + }, + }, + ); + } + // Apply privacy before aggregation: service visibility does not reveal other + // org members' callers. No legacy catalog-only event is allocated to a row. + let filter = request_filter(actor_id, &managed_ids, &own_ids); + let groups: Vec = db.collection::("audit_log").aggregate(vec![ + doc! { "$match": filter }, + doc! { "$sort": { "created_at": -1, "_id": -1 } }, + doc! { "$set": { "_service_dispatched": { "$or": [ + { "$eq": ["$event_data.outcome", "completed"] }, + { "$and": [ { "$isNumber": "$event_data.response_status" }, { "$in": ["$event_data.outcome", ["response_received", "connection_opened", "failed"]] } ] }, + ] } } }, + doc! { "$group": { "_id": "$event_data.user_service_id", "count": { "$sum": 1 }, + "requests": { "$firstN": { "input": "$$ROOT", "n": REQUEST_LIMIT } }, + "last_used": { "$top": { "sortBy": { "_service_dispatched": -1, "created_at": -1, "_id": -1 }, "output": { "$cond": ["$_service_dispatched", "$$ROOT", null] } } }, + } }, + ]).max_time(Duration::from_secs(3)).await?.try_collect().await?; + let mut events = Vec::new(); + let mut last_uses = Vec::new(); + for group in groups { + let Some(summary) = group.get_str("_id").ok().and_then(|id| result.get_mut(id)) else { + continue; + }; + summary.activity.request_count = group + .get_i64("count") + .or_else(|_| group.get_i32("count").map(i64::from)) + .unwrap_or_default() + .max(0) as u64; + if let Ok(rows) = group.get_array("requests") { + for row in rows { + if let Ok(event) = bson::from_bson::(row.clone()) { + events.push(event); + } + } + } + summary.activity.truncated = summary.activity.request_count > REQUEST_LIMIT as u64; + if let Ok(row) = group.get_document("last_used") + && let Ok(event) = bson::from_document::(row.clone()) + { + last_uses.push(event); + } + } + let mut app_ids = HashSet::new(); + let mut subject_ids = HashSet::new(); + for event in events.iter().chain(last_uses.iter()) { + if let Some(app) = event_string(event, "oauth_client_id") + .or_else(|| event_string(event, "acting_client_id")) + { + app_ids.insert(app); + } + if let Some(subject) = &event.user_id { + subject_ids.insert(subject.as_str()); + } + } + let apps = names( + db, + "oauth_clients", + "client_name", + app_ids.into_iter().collect(), + ) + .await?; + let subjects: Vec<_> = subject_ids.into_iter().collect(); + let users = names(db, "users", "display_name", subjects.clone()).await?; + let accounts = names(db, "service_accounts", "name", subjects).await?; + for (event, is_last_use) in events + .into_iter() + .map(|event| (event, false)) + .chain(last_uses.into_iter().map(|event| (event, true))) + { + let Some(summary) = + event_string(&event, "user_service_id").and_then(|id| result.get_mut(id)) + else { + continue; + }; + let mut request = recent_request(&event, actor_id, &apps, &users, &accounts); + request.source = recorded_source(&event, &owner_access); + if is_last_use { + summary.activity.last_used = Some(request); + } else { + summary.activity.requests.push(request); + } + } + Ok(result) +} + +fn request_filter(actor_id: &str, managed: &[&str], own: &[&str]) -> Document { + doc! { + "event_type": "service_request", + "created_at": { "$gte": bson::DateTime::from_chrono(Utc::now() - chrono::Duration::days(30)) }, + "$or": [ + { "event_data.user_service_id": { "$in": managed } }, + { "event_data.user_service_id": { "$in": own }, "user_id": actor_id }, + ], + } +} + +async fn names( + db: &Database, + collection: &str, + field: &str, + ids: Vec<&str>, +) -> AppResult> { + if ids.is_empty() { + return Ok(HashMap::new()); + } + let rows: Vec = db + .collection::(collection) + .find(doc! { "_id": { "$in": ids } }) + .projection(doc! { "_id": 1, field: 1 }) + .max_time(Duration::from_secs(3)) + .await? + .try_collect() + .await?; + Ok(rows + .into_iter() + .filter_map(|row| { + Some(( + row.get_str("_id").ok()?.into(), + row.get_str(field).ok()?.into(), + )) + }) + .collect()) +} + +fn event_string<'a>(event: &'a AuditLog, key: &str) -> Option<&'a str> { + event.event_data.as_ref()?.get(key)?.as_str() +} + +fn recorded_source( + event: &AuditLog, + owners: &HashMap, +) -> Option { + let owner_id = event_string(event, "owner_user_id")?; + let access = owners.get(owner_id)?; + if !access.can_read() { + return None; + } + let kind = match event_string(event, "credential_class")? { + "nyxid_managed_master" => "platform", + "user_owned" + | "agent_override_user_owned" + | "node_managed" + | "nyxid_platform_oauth_app" + | "no_auth" => match access { + OwnerAccess::Direct => "personal", + OwnerAccess::AsOrgAdmin { .. } | OwnerAccess::AsOrgMember { .. } => "org", + OwnerAccess::Forbidden => return None, + }, + _ => return None, + }; + Some(RecordedConnectionSource { + kind: kind.into(), + owner_id: owner_id.into(), + }) +} + +fn recent_request( + event: &AuditLog, + viewer: &str, + apps: &HashMap, + users: &HashMap, + accounts: &HashMap, +) -> RecentRequest { + let app_id = event_string(event, "oauth_client_id") + .or_else(|| event_string(event, "acting_client_id")) + .map(str::to_owned); + let app_name = app_id.as_ref().and_then(|id| apps.get(id)).cloned(); + let subject = event.user_id.clone(); + let (kind, id, name) = if let Some(key_id) = &event.api_key_id { + ( + "agent_key", + Some(key_id.clone()), + event + .api_key_name + .clone() + .unwrap_or_else(|| "Agent key".into()), + ) + } else if event_string(event, "auth_kind") == Some("service_account") { + ( + "service_account", + subject.clone(), + subject + .as_ref() + .and_then(|id| accounts.get(id)) + .cloned() + .unwrap_or_else(|| "Service account".into()), + ) + } else if app_id.is_some() { + ( + "oauth_app", + app_id.clone(), + app_name.clone().unwrap_or_else(|| "OAuth app".into()), + ) + } else if let Some(kind @ ("session" | "access_token" | "relay" | "delegated")) = + event_string(event, "auth_kind") + { + ( + kind, + subject.clone(), + if subject.as_deref() == Some(viewer) { + "You".into() + } else { + subject + .as_ref() + .and_then(|id| users.get(id)) + .cloned() + .unwrap_or_else(|| { + if kind == "session" { + "User session" + } else { + "Authenticated user" + } + .into() + }) + }, + ) + } else { + ("unknown", subject, "Client not recorded".into()) + }; + RecentRequest { + id: event.id.clone(), + source: None, + execution_id: event_string(event, "execution_id").map(str::to_owned), + caller: RequestCaller { + id, + kind: kind.into(), + name, + app_id, + app_name, + }, + occurred_at: event.created_at, + outcome: event_string(event, "outcome").unwrap_or("unknown").into(), + response_status: event + .event_data + .as_ref() + .and_then(|v| v.get("response_status")) + .and_then(|v| v.as_u64()) + .and_then(|v| u16::try_from(v).ok()), + } +} + +/// One exact connection event per resolved execution, including early returns. +/// A response header is not stream completion, so successful HTTP responses keep +/// the explicit `response_received` state and WS upgrades use `connection_opened`. +pub struct RequestAudit { + db: Database, + actor: audit_service::AuditActor, + event: serde_json::Value, +} + +#[derive(Clone)] +pub struct RequestAttribution { + pub actor: audit_service::AuditActor, + pub auth_kind: String, + pub oauth_client_id: Option, + pub acting_client_id: Option, + pub api_key_credential_id: Option, +} + +fn admission_denial_status(error: &AppError) -> Option { + match error { + AppError::InsufficientCredits + | AppError::WalletSuspended + | AppError::PlanEntitlementRequired(_) => Some(402), + AppError::Forbidden(_) + | AppError::ApiKeyScopeForbidden(_) + | AppError::OrgRoleInsufficient(_) => Some(403), + _ => None, + } +} + +impl RequestAudit { + #[allow(clippy::too_many_arguments)] + pub fn new( + db: &Database, + auth: &AuthUser, + service_id: Option<&str>, + catalog_id: &str, + owner_id: &str, + execution_id: &str, + credential_class: crate::models::usage_meter::CredentialClass, + ) -> Self { + Self::from_attribution( + db, + RequestAttribution { + actor: audit_service::AuditActor::from_auth_user(auth), + auth_kind: match auth.auth_method { + AuthMethod::Session => "session", + AuthMethod::AccessToken => "access_token", + AuthMethod::ApiKey => "api_key", + AuthMethod::ServiceAccount => "service_account", + AuthMethod::Delegated => "delegated", + AuthMethod::Relay => "relay", + } + .into(), + oauth_client_id: auth.oauth_client_id.clone(), + acting_client_id: auth.acting_client_id.clone(), + api_key_credential_id: auth.api_key_credential_id.clone(), + }, + service_id, + catalog_id, + owner_id, + execution_id, + credential_class, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn from_attribution( + db: &Database, + attribution: RequestAttribution, + service_id: Option<&str>, + catalog_id: &str, + owner_id: &str, + execution_id: &str, + credential_class: crate::models::usage_meter::CredentialClass, + ) -> Self { + Self { + db: db.clone(), + actor: attribution.actor, + event: serde_json::json!({ + "user_service_id": service_id, "service_id": catalog_id, "owner_user_id": owner_id, + "execution_id": execution_id, "billing_request_id": execution_id, + "credential_class": credential_class, "oauth_client_id": attribution.oauth_client_id, + "acting_client_id": attribution.acting_client_id, "api_key_credential_id": attribution.api_key_credential_id, + "auth_kind": attribution.auth_kind, + "outcome": "unknown", + }), + } + } + + pub fn response(&mut self, status: u16) { + self.event["response_status"] = status.into(); + self.event["outcome"] = match status { + 101 => "connection_opened", + 400..=599 => "failed", + _ => "response_received", + } + .into(); + } + + pub fn denied(&mut self, status: u16) { + self.event["response_status"] = status.into(); + self.event["outcome"] = "denied".into(); + self.event["dispatch_state"] = "not_dispatched".into(); + } + + /// Call only at admission gates before dispatch. Transient database/billing + /// failures remain unknown and must not be turned into provider failures. + pub fn admission_error(&mut self, error: &AppError) { + if let Some(status) = admission_denial_status(error) { + self.denied(status); + } + } +} + +impl Drop for RequestAudit { + fn drop(&mut self) { + if self.event["user_service_id"].is_null() { + return; + } + audit_service::log_async( + self.db.clone(), + Some(self.actor.user_id.clone()), + "service_request".into(), + Some(self.event.clone()), + self.actor.ip_address.clone(), + self.actor.user_agent.clone(), + self.actor.api_key_id.clone(), + self.actor.api_key_name.clone(), + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn key() -> ApiKey { + bson::from_document(doc! { + "_id": "key", "user_id": "owner", "name": "CI agent", "key_prefix": "prefix", + "key_hash": "hash", "scopes": "proxy", "is_active": true, + "created_at": bson::DateTime::now(), "allow_all_services": false, + "allowed_service_ids": ["selected"], "allow_auto_connected_services": true, + }) + .unwrap() + } + + fn event() -> AuditLog { + bson::from_document(doc! { + "_id": "event", "user_id": "owner", "event_type": "service_request", + "event_data": { "user_service_id": "connection", "execution_id": "execution", "auth_kind": "session", "outcome": "response_received", "response_status": 200 }, + "created_at": bson::DateTime::now(), + }).unwrap() + } + + #[test] + fn recorded_source_follows_the_request_credential_and_owner_not_current_binding() { + let owners = HashMap::from([ + ("person".into(), OwnerAccess::Direct), + ( + "org".into(), + OwnerAccess::AsOrgAdmin { + org_user_id: "org".into(), + membership_id: "member".into(), + allowed_service_ids: None, + }, + ), + ]); + let mut row = event(); + for (owner, class, expected) in [ + ("person", "user_owned", "personal"), + ("org", "user_owned", "org"), + ("org", "nyxid_managed_master", "platform"), + ("org", "agent_override_user_owned", "org"), + ("org", "nyxid_platform_oauth_app", "org"), + ("person", "node_managed", "personal"), + ] { + row.event_data = + Some(serde_json::json!({ "owner_user_id": owner, "credential_class": class })); + let source = recorded_source(&row, &owners).unwrap(); + assert_eq!(source.kind, expected); + assert_eq!(source.owner_id, owner); + } + for data in [ + serde_json::json!({ "owner_user_id": "org" }), + serde_json::json!({ "owner_user_id": "org", "credential_class": "future_class" }), + serde_json::json!({ "owner_user_id": "inaccessible", "credential_class": "user_owned" }), + ] { + row.event_data = Some(data); + assert!(recorded_source(&row, &owners).is_none()); + } + } + + #[test] + fn scope_grants_distinguish_explicit_dynamic_and_broad_permission() { + let mut key = key(); + let effective = vec!["dynamic".into()]; + assert_eq!( + permission(&key, &effective, "selected"), + Some("selected_service") + ); + assert_eq!( + permission(&key, &effective, "dynamic"), + Some("platform_services") + ); + assert_eq!(permission(&key, &effective, "other"), None); + key.allow_all_services = true; + assert_eq!(permission(&key, &effective, "other"), Some("all_services")); + key.is_active = false; + assert_eq!(permission(&key, &effective, "other"), None); + key.is_active = true; + key.expires_at = Some(Utc::now() - chrono::Duration::seconds(1)); + assert_eq!(permission(&key, &effective, "other"), None); + key.expires_at = None; + key.purpose = ApiKeyPurpose::ScheduledInvocation; + assert_eq!(permission(&key, &effective, "other"), None); + key.purpose = ApiKeyPurpose::General; + key.scopes = "read write".into(); + assert_eq!(permission(&key, &effective, "other"), None); + } + + #[test] + fn member_activity_is_actor_scoped_before_counting() { + let filter = request_filter("viewer", &["personal"], &["org"]); + let branches = filter.get_array("$or").unwrap(); + assert!(!branches[0].as_document().unwrap().contains_key("user_id")); + assert_eq!( + branches[1] + .as_document() + .unwrap() + .get_str("user_id") + .unwrap(), + "viewer" + ); + assert_eq!(filter.get_str("event_type").unwrap(), "service_request"); + assert!(!filter.to_string().contains("service_slug")); + } + + #[test] + fn caller_identity_does_not_infer_apps_or_success_from_provisioning() { + let mut event = event(); + let empty = HashMap::new(); + let own = recent_request(&event, "owner", &empty, &empty, &empty); + assert_eq!(own.caller.kind, "session"); + assert_eq!(own.caller.name, "You"); + assert_eq!(own.outcome, "response_received"); + let other = recent_request(&event, "viewer", &empty, &empty, &empty); + assert_eq!(other.caller.name, "User session"); + for kind in ["access_token", "relay", "delegated"] { + event.event_data = + Some(serde_json::json!({ "source_app_name": "Heca", "auth_kind": kind })); + let own = recent_request(&event, "owner", &empty, &empty, &empty); + assert_eq!(own.caller.kind, kind); + assert_eq!(own.caller.name, "You"); + assert_eq!(own.caller.id.as_deref(), Some("owner")); + assert!(own.caller.app_id.is_none()); + assert!(own.caller.app_name.is_none()); + assert_eq!(own.outcome, "unknown"); + let names = HashMap::from([("owner".into(), "Alicia".into())]); + let shared = recent_request(&event, "viewer", &empty, &names, &empty); + assert_eq!(shared.caller.kind, kind); + assert_eq!(shared.caller.name, "Alicia"); + } + event.event_data = Some(serde_json::json!({ "source_app_name": "Heca" })); + let unknown = recent_request(&event, "owner", &empty, &empty, &empty); + assert_eq!(unknown.caller.kind, "unknown"); + assert_eq!(unknown.caller.name, "Client not recorded"); + event.api_key_id = Some("agent".into()); + event.api_key_name = Some("Codex CI".into()); + event.event_data = + Some(serde_json::json!({ "oauth_client_id": "app", "auth_kind": "relay" })); + let apps = HashMap::from([("app".into(), "Release app".into())]); + let agent = recent_request(&event, "owner", &apps, &empty, &empty); + assert_eq!(agent.caller.kind, "agent_key"); + assert_eq!(agent.caller.name, "Codex CI"); + assert_eq!(agent.caller.app_name.as_deref(), Some("Release app")); + } + + #[test] + fn admission_failures_only_claim_denial_when_the_gate_confirms_it() { + assert_eq!( + admission_denial_status(&AppError::InsufficientCredits), + Some(402) + ); + assert_eq!( + admission_denial_status(&AppError::WalletSuspended), + Some(402) + ); + assert_eq!( + admission_denial_status(&AppError::Forbidden("policy".into())), + Some(403) + ); + assert_eq!( + admission_denial_status(&AppError::BillingProviderUnavailable("temporary".into())), + None + ); + assert_eq!( + admission_denial_status(&AppError::Internal("database".into())), + None + ); + } + + #[tokio::test] + async fn exact_connection_activity_and_key_inventory_apply_independent_org_visibility() { + use crate::{ + models::{org_membership::OrgRole, user::UserType}, + test_utils::{connect_test_database, test_membership, test_user, test_user_service}, + }; + let db = connect_test_database("service_insights_acl").await.unwrap(); + audit_service::init_audit_chain_hmac_key(zeroize::Zeroizing::new([7; 32])); + db.collection("users") + .insert_many([ + test_user("viewer", UserType::Person), + test_user("other", UserType::Person), + test_user("org", UserType::Org), + ]) + .await + .unwrap(); + let membership = test_membership("org", "viewer", OrgRole::Member, Some(vec!["a".into()])); + db.collection::("org_memberships") + .insert_one(&membership) + .await + .unwrap(); + let a = test_user_service("a", "org", "same-catalog-a", "ep", Some("catalog"), None); + let b = test_user_service("b", "org", "same-catalog-b", "ep", Some("catalog"), None); + let mut personal = key(); + personal.id = "personal-key".into(); + personal.user_id = "viewer".into(); + personal.allow_all_services = true; + let mut org = personal.clone(); + org.id = "org-key".into(); + org.user_id = "org".into(); + let mut hidden = personal.clone(); + hidden.id = "other-private-key".into(); + hidden.user_id = "other".into(); + db.collection("api_keys") + .insert_many([personal, org, hidden]) + .await + .unwrap(); + db.collection::("agent_service_bindings").insert_one(doc! { + "_id": "binding", "api_key_id": "org-key", "user_service_id": "a", "user_api_key_id": "external-secret", + "user_id": "org", "created_at": bson::DateTime::now(), "updated_at": bson::DateTime::now(), + }).await.unwrap(); + for (actor_id, connection, event_type) in [ + ("viewer", Some("a"), "service_request"), + ("other", Some("a"), "service_request"), + ("viewer", Some("b"), "service_request"), + ("viewer", None, "proxy_request"), + ] { + audit_service::log_actor_event(db.clone(), &audit_service::AuditActor { + user_id: actor_id.into(), ip_address: None, user_agent: None, + api_key_id: None, api_key_name: None, + }, event_type, Some(serde_json::json!({ + "user_service_id": connection, "service_id": "catalog", "auth_kind": "session", "outcome": "response_received", + }))).await.unwrap(); + } + let member = insights(&db, "viewer", &[a.clone(), b.clone()]) + .await + .unwrap(); + assert!(!member.contains_key("b")); + assert_eq!(member["a"].access.visibility, "own_keys"); + assert_eq!( + member["a"] + .access + .keys + .iter() + .map(|k| k.id.as_str()) + .collect::>(), + ["personal-key"] + ); + assert_eq!(member["a"].activity.visibility, "own_requests"); + assert_eq!(member["a"].activity.request_count, 1); + assert_eq!(member["a"].activity.requests[0].caller.name, "You"); + assert_eq!(member["a"].activity.tracking, "partial"); + + db.collection::("org_memberships") + .update_one( + doc! { "_id": &membership.id }, + doc! { "$set": { "role": "admin" } }, + ) + .await + .unwrap(); + let admin = insights(&db, "viewer", &[a.clone(), b]).await.unwrap(); + assert!(!admin.contains_key("b")); + assert_eq!(admin["a"].activity.request_count, 2); + assert_eq!(admin["a"].access.total, 2); + assert!( + admin["a"] + .access + .keys + .iter() + .any(|k| k.id == "org-key" && k.credential_override) + ); + assert!( + !admin["a"] + .access + .keys + .iter() + .any(|k| k.id == "other-private-key") + ); + assert!( + admin["a"] + .activity + .requests + .iter() + .any(|r| r.caller.name == "Test User") + ); + let serialized = serde_json::to_string(&admin).unwrap(); + for secret_field in ["external-secret", "key_hash", "key_prefix"] { + assert!(!serialized.contains(secret_field)); + } + db.collection::("org_memberships") + .update_one( + doc! { "_id": &membership.id }, + doc! { "$set": { "role": "member" } }, + ) + .await + .unwrap(); + for (actor_id, outcome, status) in [ + ("viewer", "response_received", 200), + ("viewer", "denied", 403), + ("viewer", "denied", 403), + ("viewer", "denied", 403), + ("viewer", "denied", 403), + ("other", "response_received", 200), + ] { + audit_service::log_actor_event(db.clone(), &audit_service::AuditActor { + user_id: actor_id.into(), ip_address: None, user_agent: None, api_key_id: None, api_key_name: None, + }, "service_request", Some(serde_json::json!({ + "user_service_id": "a", "owner_user_id": "org", "credential_class": "nyxid_managed_master", + "auth_kind": "session", "outcome": outcome, "response_status": status, + }))).await.unwrap(); + } + let history = insights(&db, "viewer", &[a]).await.unwrap(); + let activity = &history["a"].activity; + assert_eq!(activity.requests.len(), 3); + assert!( + activity + .requests + .iter() + .all(|request| request.outcome == "denied") + ); + let last = activity.last_used.as_ref().unwrap(); + assert_eq!(last.caller.name, "You"); + assert_eq!(last.outcome, "response_received"); + assert_eq!(last.source.as_ref().unwrap().kind, "platform"); + assert!( + !activity + .requests + .iter() + .any(|request| request.id == last.id) + ); + } + + #[tokio::test] + async fn request_capture_keeps_exact_identity_and_billing_link_without_claiming_stream_completion() + { + use crate::{ + models::usage_meter::CredentialClass, + test_utils::{connect_test_database, test_auth_user}, + }; + let db = connect_test_database("service_request_capture") + .await + .unwrap(); + audit_service::init_audit_chain_hmac_key(zeroize::Zeroizing::new([7; 32])); + let mut auth = test_auth_user(&uuid::Uuid::new_v4().to_string()); + auth.auth_method = AuthMethod::ApiKey; + auth.api_key_id = Some("agent-key".into()); + auth.api_key_name = Some("Codex CI".into()); + auth.api_key_credential_id = Some("login-credential".into()); + auth.oauth_client_id = Some("verified-app".into()); + let mut audit = RequestAudit::new( + &db, + &auth, + Some("exact-connection"), + "catalog", + "owner", + "execution", + CredentialClass::AgentOverrideUserOwned, + ); + audit.response(200); + drop(audit); + let event = tokio::time::timeout(Duration::from_secs(3), async { + loop { + if let Some(event) = db + .collection::("audit_log") + .find_one(doc! { "event_type": "service_request" }) + .await + .unwrap() + { + break event; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .unwrap(); + let data = event.event_data.unwrap(); + assert_eq!(event.api_key_id.as_deref(), Some("agent-key")); + assert!(event.seq.is_some()); + assert_eq!(data["user_service_id"], "exact-connection"); + assert_eq!(data["service_id"], "catalog"); + assert_eq!(data["execution_id"], data["billing_request_id"]); + assert_eq!(data["oauth_client_id"], "verified-app"); + assert_eq!(data["api_key_credential_id"], "login-credential"); + assert_eq!(data["credential_class"], "agent_override_user_owned"); + assert_eq!(data["outcome"], "response_received"); + assert_eq!( + db.collection::("audit_log") + .count_documents(doc! { "event_type": "service_request" }) + .await + .unwrap(), + 1 + ); + } +} diff --git a/backend/src/services/service_insights_billing.rs b/backend/src/services/service_insights_billing.rs new file mode 100644 index 000000000..ba88a7e10 --- /dev/null +++ b/backend/src/services/service_insights_billing.rs @@ -0,0 +1,2081 @@ +use std::collections::HashMap; + +use futures::TryStreamExt; +use mongodb::{Database, bson::doc}; +use serde::Serialize; +use utoipa::ToSchema; + +use crate::errors::{AppError, AppResult}; +use crate::models::agent_service_binding::{AgentServiceBinding, COLLECTION_NAME as BINDINGS}; +use crate::models::api_key::{ApiKey, ApiKeyPurpose, COLLECTION_NAME as AGENT_KEYS}; +use crate::models::downstream_service::{COLLECTION_NAME as CATALOG, DownstreamService}; +use crate::models::service_billing::{BillingMetric, PricingSyncStatus, ServiceBilling}; +use crate::models::usage_meter::CredentialClass; +use crate::models::user::{COLLECTION_NAME as USERS, User}; +use crate::models::user_api_key::{COLLECTION_NAME as CREDENTIALS, UserApiKey}; +use crate::models::user_endpoint::{COLLECTION_NAME as ENDPOINTS, UserEndpoint}; +use crate::models::user_service::UserService; +use crate::services::billing::{BillingIngress, BillingRouteContext, BillingService, NodeIntent}; +use crate::services::{feature_flag_service, org_service, platform_key_service, proxy_service}; + +#[derive(Clone, Copy, Debug, Serialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum BillingExplanationStatus { + Resolved, + Conditional, + Restricted, + Unavailable, +} + +#[derive(Clone, Copy, Debug, Serialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConnectionChargeStatus { + UsageBased, + NotCharged, + Conditional, + Restricted, + Unavailable, +} + +#[derive(Clone, Copy, Debug, Serialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum BillingAccountKind { + Personal, + Organization, +} + +#[derive(Clone, Debug, Serialize, ToSchema)] +pub struct ServiceBillingAccount { + pub id: String, + pub kind: BillingAccountKind, + pub name: String, +} + +#[derive(Clone, Debug, Serialize, ToSchema)] +pub struct ServiceBillingRate { + /// "platform" or "resale"; both are charges collected by NyxID. + pub layer: String, + pub metric: BillingMetric, + /// Exact decimal credits per one unit; absent when the plan rate is unknown. + pub credits_per_unit: Option, + pub currency: String, + /// "credential_lane", "service_price", or "legacy_plan". + pub source: String, +} + +#[derive(Clone, Copy, Debug, Serialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ProviderBillingDisclosure { + SeparateProviderAccount, + NyxidCredential, + NoCredential, + Unknown, +} + +#[derive(Clone, Copy, Debug, Serialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CredentialSupplier { + Nyxid, + Own, + None, + Unknown, +} + +#[derive(Clone, Debug, Serialize, ToSchema)] +pub struct ServiceBillingExplanation { + pub status: BillingExplanationStatus, + pub credential_class: Option, + pub credential_label: String, + pub account: Option, + pub charge_status: ConnectionChargeStatus, + /// Saved usage-charge configuration, independent of availability and caller rollout. + pub credit_billing_configured: Option, + /// Whether any credential class has a configured charge for this service. + pub service_billing_configured: Option, + /// Credential supply is distinct from the execution price-lane classification. + pub credential_supplier: Option, + pub rates: Vec, + pub provider_billing: ProviderBillingDisclosure, + /// "for_you" uses the viewer's default; "agent_key" includes that key's override. + pub context: String, + pub notes: Vec, +} + +/// Metadata-only preview for rows already selected by the inventory ACL. +/// Distinct catalog, credential and account rows are loaded once per request. +/// No credential resolution, wallet provisioning, reservation or provider call +/// may be added here: opening AI Services must not execute a service. +pub async fn explain_connections( + db: &Database, + billing: &BillingService, + billing_principal_id: &str, + actor_user_id: &str, + services: &[UserService], +) -> AppResult> { + explain_connections_in_context( + db, + billing, + billing_principal_id, + actor_user_id, + services, + None, + ) + .await +} + +/// Inspect a managed agent identity without authenticating as that key or +/// granting its permissions to the viewer. The handler's service ACL remains +/// in force in addition to the key's live execution authority. +pub async fn explain_for_agent_key( + db: &Database, + billing: &BillingService, + actor_id: &str, + services: &[UserService], + api_key_id: &str, +) -> AppResult> { + if services.len() > 100 { + return Err(AppError::ValidationError( + "At most 100 connections may be requested".into(), + )); + } + let key = db + .collection::(AGENT_KEYS) + .find_one(doc! { "_id": api_key_id }) + .await? + .ok_or_else(|| AppError::NotFound("Agent key not found".into()))?; + let management_access = org_service::resolve_owner_access(db, actor_id, &key.user_id).await?; + if !management_access.can_write() { + return Err(AppError::NotFound("Agent key not found".into())); + } + let active = key.is_active + && key.purpose == ApiKeyPurpose::General + && key + .expires_at + .is_none_or(|expires| expires > chrono::Utc::now()) + && crate::mw::auth::scope_allows_llm_proxy(&key.scopes); + let effective_ids = if active { + crate::services::key_service::effective_allowed_service_ids(db, &key).await? + } else { + Vec::new() + }; + let mut selected = Vec::new(); + let mut result = HashMap::new(); + let mut viewer_accesses = HashMap::new(); + for service in services { + if !viewer_accesses.contains_key(&service.user_id) { + viewer_accesses.insert( + service.user_id.clone(), + org_service::resolve_owner_access(db, actor_id, &service.user_id).await?, + ); + } + let access = &viewer_accesses[&service.user_id]; + let explanation = if !access.can_read() || !access.allows_resource(&service.id) { + Some(restricted()) + } else if !active { + Some(unavailable( + "This agent key is inactive, expired, or lacks proxy permission.", + )) + } else if !key.allow_all_services && !effective_ids.contains(&service.id) { + Some(unavailable( + "This agent key does not have access to this connection.", + )) + } else if !key.allow_all_nodes + && service + .node_id + .as_ref() + .is_some_and(|id| !key.allowed_node_ids.contains(id)) + { + Some(unavailable( + "This agent key does not have access to the connection's node.", + )) + } else { + None + }; + if let Some(explanation) = explanation { + result.insert(service.id.clone(), explanation); + } else { + selected.push(service.clone()); + } + } + result.extend( + explain_connections_in_context( + db, + billing, + &key.user_id, + &key.user_id, + &selected, + Some(&key), + ) + .await?, + ); + for explanation in result.values_mut() { + explanation.context = "agent_key".into(); + } + Ok(result) +} + +async fn explain_connections_in_context( + db: &Database, + billing: &BillingService, + billing_principal_id: &str, + actor_user_id: &str, + services: &[UserService], + agent_key: Option<&ApiKey>, +) -> AppResult> { + if services.is_empty() { + return Ok(HashMap::new()); + } + let bindings: Vec = if let Some(key) = agent_key { + let service_ids: Vec<_> = services.iter().map(|s| s.id.as_str()).collect(); + db.collection::(BINDINGS) + .find(doc! { + "api_key_id": &key.id, "user_id": &key.user_id, + "user_service_id": { "$in": service_ids }, + }) + .await? + .try_collect() + .await? + } else { + Vec::new() + }; + let overrides: HashMap<_, _> = bindings + .iter() + .map(|b| (b.user_service_id.as_str(), b)) + .collect(); + let catalog_ids: Vec<_> = services + .iter() + .filter_map(|s| s.catalog_service_id.as_deref()) + .collect(); + let mut credential_ids: Vec<_> = services + .iter() + .filter_map(|s| s.api_key_id.as_deref()) + .collect(); + credential_ids.extend( + bindings + .iter() + .map(|binding| binding.user_api_key_id.as_str()), + ); + let endpoints: HashMap = if bindings.is_empty() { + HashMap::new() + } else { + let ids: Vec<_> = services + .iter() + .filter(|s| overrides.contains_key(s.id.as_str())) + .map(|s| s.endpoint_id.as_str()) + .collect(); + db.collection::(ENDPOINTS) + .find(doc! { "_id": { "$in": ids } }) + .await? + .try_collect::>() + .await? + .into_iter() + .map(|e| (e.id.clone(), e)) + .collect() + }; + let mut owner_ids: Vec<_> = services.iter().map(|s| s.user_id.as_str()).collect(); + owner_ids.push(billing_principal_id); + let (catalog, credentials, owners): (Vec, Vec, Vec) = tokio::try_join!( + async { + db.collection::(CATALOG) + .find(doc! { "_id": { "$in": catalog_ids } }) + .await? + .try_collect() + .await + }, + async { + db.collection::(CREDENTIALS) + .find(doc! { "_id": { "$in": credential_ids } }) + .await? + .try_collect() + .await + }, + async { + db.collection::(USERS) + .find(doc! { "_id": { "$in": owner_ids } }) + .await? + .try_collect() + .await + }, + )?; + let app_sources = + super::oauth_app_source::load(db, &credentials.iter().collect::>()).await?; + let catalog: HashMap<_, _> = catalog.into_iter().map(|s| (s.id.clone(), s)).collect(); + let credentials: HashMap<_, _> = credentials.into_iter().map(|k| (k.id.clone(), k)).collect(); + let owners: HashMap<_, _> = owners.into_iter().map(|u| (u.id.clone(), u)).collect(); + let needs_platform_grants = services.iter().any(uses_platform_binding); + let grants = if needs_platform_grants { + Some(platform_key_service::OwnerGrants::load(db, billing_principal_id).await?) + } else { + None + }; + let providers = if needs_platform_grants { + platform_key_service::load_providers(db).await? + } else { + HashMap::new() + }; + let mut accesses = HashMap::new(); + let mut materializable = HashMap::new(); + let mut rollout = HashMap::new(); + let mut resolved_owners = HashMap::new(); + let mut explanations = HashMap::new(); + + for service in services { + if !accesses.contains_key(&service.user_id) { + accesses.insert( + service.user_id.clone(), + org_service::resolve_owner_access(db, billing_principal_id, &service.user_id) + .await?, + ); + } + let access = &accesses[&service.user_id]; + if !access.can_read() || !access.allows_resource(&service.id) { + explanations.insert(service.id.clone(), restricted()); + continue; + } + if matches!(access, org_service::OwnerAccess::AsOrgMember { role, .. } if !role.can_proxy()) + || (service.admin_only + && matches!(access, org_service::OwnerAccess::AsOrgMember { .. })) + { + explanations.insert( + service.id.clone(), + unavailable("You do not have execution access to this connection."), + ); + continue; + } + if !service.is_active || service.deleted_at.is_some() { + explanations.insert( + service.id.clone(), + unavailable("This connection is disabled."), + ); + continue; + } + let catalog_service = service + .catalog_service_id + .as_ref() + .and_then(|id| catalog.get(id)); + if service.catalog_service_id.is_some() && catalog_service.is_none() { + explanations.insert( + service.id.clone(), + unavailable("The service billing configuration is unavailable."), + ); + continue; + } + let credential = service + .api_key_id + .as_ref() + .and_then(|id| credentials.get(id)); + let mut effective_auth_method = service.auth_method.clone(); + let default_class = if uses_platform_binding(service) { + let available = catalog_service.is_some_and(|catalog| { + grants.as_ref().is_some_and(|grants| { + platform_key_service::available_with_grants( + catalog, + catalog + .provider_config_id + .as_ref() + .and_then(|id| providers.get(id)), + &service.user_id, + grants, + ) + }) + }); + if !available || service.node_id.is_some() { + explanations.insert( + service.id.clone(), + unavailable("The NyxID credential is unavailable for this connection."), + ); + continue; + } + // Explicit platform bindings can override the stored connection auth. + let Some(catalog_service) = catalog_service else { + explanations.insert( + service.id.clone(), + unavailable("The service billing configuration is unavailable."), + ); + continue; + }; + match platform_key_service::effective_auth(db, catalog_service).await { + Ok((method, _)) => { + effective_auth_method = method; + CredentialClass::NyxidManagedMaster + } + Err(AppError::ValidationError(_)) => { + explanations.insert( + service.id.clone(), + unavailable("The NyxID credential configuration is incomplete."), + ); + continue; + } + Err(error) => return Err(error), + } + } else if service.auth_method == "none" { + if service.node_id.is_some() { + CredentialClass::NodeManaged + } else { + CredentialClass::NoAuth + } + } else { + let Some(credential) = credential.filter(|key| key.user_id == service.user_id) else { + explanations.insert( + service.id.clone(), + unavailable("No connection credential is available."), + ); + continue; + }; + if !materializable.contains_key(&credential.id) { + materializable.insert( + credential.id.clone(), + proxy_service::credential_is_materializable(db, credential).await?, + ); + } + let has_server_credential = materializable[&credential.id]; + if service.node_id.is_none() + && (credential.status != "active" || !has_server_credential) + { + explanations.insert( + service.id.clone(), + unavailable( + "Reconnect or replace the connection credential before using this service.", + ), + ); + continue; + } + default_credential_class(service, credential, has_server_credential) + }; + let credential_class = if let Some(binding) = overrides.get(service.id.as_str()) { + let override_key = credentials.get(&binding.user_api_key_id).filter(|key| { + key.user_id == billing_principal_id && key.user_id == service.user_id + }); + let Some(override_key) = override_key else { + explanations.insert( + service.id.clone(), + unavailable("The agent's credential override is unavailable."), + ); + continue; + }; + if !materializable.contains_key(&override_key.id) { + materializable.insert( + override_key.id.clone(), + proxy_service::credential_is_materializable(db, override_key).await?, + ); + } + if override_key.status != "active" || !materializable[&override_key.id] { + explanations.insert( + service.id.clone(), + unavailable("Reconnect or replace this agent's credential override."), + ); + continue; + } + let target_url = if default_class == CredentialClass::NyxidManagedMaster { + catalog_service.map(|c| c.base_url.as_str()) + } else { + endpoints + .get(&service.endpoint_id) + .filter(|e| e.user_id == service.user_id) + .map(|e| e.url.as_str()) + }; + let Some(target_url) = target_url else { + explanations.insert( + service.id.clone(), + unavailable("The override's destination is unavailable."), + ); + continue; + }; + match crate::services::ifttt_oauth_service::validate_credential_route( + db, + override_key.provider_config_id.as_deref(), + &effective_auth_method, + target_url, + None, + ) + .await + { + Ok(()) => {} + Err(AppError::ValidationError(_)) => { + explanations.insert(service.id.clone(), unavailable("This credential override cannot be used with the connection's destination.")); + continue; + } + Err(error) => return Err(error), + } + // The proxy classifies a node without a default server credential + // as node-managed before considering an agent override. + if default_class == CredentialClass::NodeManaged { + default_class + } else { + CredentialClass::AgentOverrideUserOwned + } + } else { + default_class + }; + let owner_key = ( + service.user_id.clone(), + credential_class == CredentialClass::NyxidManagedMaster, + ); + if !resolved_owners.contains_key(&owner_key) { + let resolved = billing + .owner_resolver() + .resolve_for_execution(billing_principal_id, &service.user_id, credential_class) + .await?; + resolved_owners.insert(owner_key.clone(), resolved.owner_id); + } + let payer_id = &resolved_owners[&owner_key]; + let Some(payer) = owners.get(payer_id).filter(|u| u.is_active) else { + explanations.insert( + service.id.clone(), + unavailable("The billing account is unavailable."), + ); + continue; + }; + if !rollout.contains_key(payer_id) { + let enabled = billing.billing_enabled() + && feature_flag_service::billing_rollout_enabled(db, payer_id, actor_user_id) + .await?; + rollout.insert(payer_id.clone(), enabled); + } + let account = ServiceBillingAccount { + id: payer_id.clone(), + kind: if payer.user_type.is_org() { + BillingAccountKind::Organization + } else { + BillingAccountKind::Personal + }, + name: if payer.user_type.is_org() { + payer + .display_name + .clone() + .unwrap_or_else(|| "Organization".into()) + } else { + "Your personal account".into() + }, + }; + let mut explanation = project_billing( + service, + catalog_service.and_then(|s| s.billing.as_ref()), + credential_class, + account, + rollout[payer_id], + billing.resale_enabled(), + billing.lago_configured(), + ); + explanation.credential_supplier = Some( + if credential_class == CredentialClass::AgentOverrideUserOwned { + overrides + .get(service.id.as_str()) + .and_then(|binding| credentials.get(&binding.user_api_key_id)) + .map_or(CredentialSupplier::Unknown, |key| { + resolved_credential_supplier(key, &app_sources) + }) + } else { + let supplier = connection_credential_supplier(service, credential); + if supplier == CredentialSupplier::Unknown { + credential + .filter(|key| key.user_id == service.user_id) + .map_or(supplier, |key| { + resolved_credential_supplier(key, &app_sources) + }) + } else { + supplier + } + }, + ); + annotate_transport_pricing(&mut explanation, catalog_service); + if credential_class == CredentialClass::NodeManaged { + explanation.status = BillingExplanationStatus::Conditional; + explanation + .notes + .push("Uses the node credential if the node is available.".into()); + } + explanations.insert(service.id.clone(), explanation); + } + // Keep configured billability visible for disabled or unavailable connections. + // This reads the already-loaded metadata, without resolving credentials or a payer. + for service in services { + let Some(explanation) = explanations.get_mut(&service.id) else { + continue; + }; + if agent_key.is_none() + && explanation.status != BillingExplanationStatus::Restricted + && explanation.credential_supplier.is_none() + && !uses_platform_binding(service) + && service.auth_method != "none" + { + explanation.credential_supplier = service + .api_key_id + .as_ref() + .and_then(|id| credentials.get(id)) + .filter(|key| key.user_id == service.user_id) + .map(|key| resolved_credential_supplier(key, &app_sources)); + } + annotate_configured_charge( + explanation, + service, + service + .catalog_service_id + .as_ref() + .and_then(|id| catalog.get(id)), + service + .api_key_id + .as_ref() + .and_then(|id| credentials.get(id)), + agent_key.is_some(), + ); + } + Ok(explanations) +} + +fn annotate_configured_charge( + explanation: &mut ServiceBillingExplanation, + service: &UserService, + catalog: Option<&DownstreamService>, + credential: Option<&UserApiKey>, + agent_context: bool, +) { + if explanation.status == BillingExplanationStatus::Restricted + || (service.catalog_service_id.is_some() && catalog.is_none()) + { + return; + } + let configuration = catalog.and_then(|service| service.billing.as_ref()); + explanation.service_billing_configured = Some(service_billing_configured(configuration)); + if explanation.credential_supplier.is_none() { + explanation.credential_supplier = Some(if agent_context { + CredentialSupplier::Unknown + } else { + connection_credential_supplier(service, credential) + }); + } + if matches!( + explanation.status, + BillingExplanationStatus::Resolved | BillingExplanationStatus::Conditional + ) { + match explanation.credential_supplier { + Some(CredentialSupplier::Unknown) => { + explanation.provider_billing = ProviderBillingDisclosure::Unknown; + explanation.credential_label = "Credential supplier unverified".into(); + } + Some(CredentialSupplier::Nyxid) => { + explanation.provider_billing = ProviderBillingDisclosure::NyxidCredential; + explanation.credential_label = + if explanation.credential_class == Some(CredentialClass::NyxidManagedMaster) { + "NyxID key" + } else { + "NyxID OAuth app" + } + .into(); + } + _ => {} + } + } + if agent_context && explanation.credential_class.is_none() { + return; + } + let stored = credential.filter(|key| key.user_id == service.user_id); + let class = explanation.credential_class.or_else(|| { + if uses_platform_binding(service) { + Some(CredentialClass::NyxidManagedMaster) + } else if service.node_id.is_some() { + // Without materializing a disabled credential, shared OAuth versus node + // supply is ambiguous and can change a credential-restricted price. + if stored.is_some_and(|key| key.credential_source.as_deref() == Some("platform")) { + None + } else { + Some(CredentialClass::NodeManaged) + } + } else if service.auth_method == "none" { + Some(CredentialClass::NoAuth) + } else { + stored.map(|key| default_credential_class(service, key, true)) + } + }); + explanation.credit_billing_configured = if configuration.is_none() { + Some(false) + } else { + class.map(|class| configured_usage_charge(configuration, class)) + }; +} + +fn connection_credential_supplier( + service: &UserService, + credential: Option<&UserApiKey>, +) -> CredentialSupplier { + if uses_platform_binding(service) { + return CredentialSupplier::Nyxid; + } + if service.auth_method == "none" && service.node_id.is_none() { + return CredentialSupplier::None; + } + credential + .filter(|key| key.user_id == service.user_id) + .map_or(CredentialSupplier::Unknown, stored_credential_supplier) +} + +fn stored_credential_supplier(key: &UserApiKey) -> CredentialSupplier { + if matches!(key.credential_type.as_str(), "oauth2" | "device_code") { + match super::oauth_app_source::from_key(key) { + Some(super::oauth_app_source::OAuthAppSource::Platform) => CredentialSupplier::Nyxid, + Some(super::oauth_app_source::OAuthAppSource::Byo) => CredentialSupplier::Own, + None => CredentialSupplier::Unknown, + } + } else if matches!( + key.credential_type.as_str(), + "api_key" | "bearer" | "basic" | "token_exchange" | "ssh_certificate" | "node_managed" + ) { + CredentialSupplier::Own + } else { + CredentialSupplier::Unknown + } +} + +fn resolved_credential_supplier( + key: &UserApiKey, + sources: &HashMap, +) -> CredentialSupplier { + match sources.get(&key.id) { + Some(super::oauth_app_source::OAuthAppSource::Platform) => CredentialSupplier::Nyxid, + Some(super::oauth_app_source::OAuthAppSource::Byo) => CredentialSupplier::Own, + None => stored_credential_supplier(key), + } +} + +fn service_billing_configured(configuration: Option<&ServiceBilling>) -> bool { + [ + CredentialClass::NyxidManagedMaster, + CredentialClass::NyxidPlatformOauthApp, + CredentialClass::UserOwned, + CredentialClass::NoAuth, + ] + .into_iter() + .any(|class| configured_usage_charge(configuration, class)) +} + +fn configured_usage_charge(configuration: Option<&ServiceBilling>, class: CredentialClass) -> bool { + let Some(billing) = configuration else { + return false; + }; + if class == CredentialClass::NyxidManagedMaster && billing.resale_billable { + return true; + } + if billing.platform_charge_nyxid_credentials_only + && !matches!( + class, + CredentialClass::NyxidManagedMaster | CredentialClass::NyxidPlatformOauthApp + ) + { + return false; + } + let positive = |rate: &str| { + crate::services::billing::amounts::decimal_to_pico(rate).is_some_and(|rate| rate > 0) + }; + let legacy = billing.platform_billable + && billing.platform_pricing.as_ref().is_none_or(|price| { + price.sync_status != PricingSyncStatus::Synced || positive(&price.credits_per_unit) + }); + if billing.byok_pricing.is_none() && billing.platform_key_pricing.is_none() { + return legacy; + } + let lane = match class { + CredentialClass::NyxidManagedMaster => billing.platform_key_pricing.as_ref(), + CredentialClass::NoAuth => None, + _ => billing.byok_pricing.as_ref(), + }; + lane.is_some_and(|lane| { + positive(&lane.credits_per_unit) + || lane + .components + .iter() + .any(|rate| positive(&rate.credits_per_unit)) + || (lane.sync_status != PricingSyncStatus::Synced && legacy) + }) +} + +fn annotate_transport_pricing( + explanation: &mut ServiceBillingExplanation, + catalog: Option<&DownstreamService>, +) { + let Some(catalog) = catalog else { + return; + }; + if catalog + .capabilities + .as_ref() + .is_some_and(|capabilities| capabilities.supports_websocket) + && catalog + .billing + .as_ref() + .is_none_or(|billing| billing.platform_metric.is_none()) + && explanation.rates.iter().any(|rate| { + rate.layer == "platform" + && rate.source != "credential_lane" + && rate.metric != BillingMetric::Bytes + }) + { + explanation.status = BillingExplanationStatus::Conditional; + explanation.notes.push( + "Rates shown are for HTTP requests. WebSocket connections use bytes and may have a different rate.".into(), + ); + } +} + +fn uses_platform_binding(service: &UserService) -> bool { + platform_key_service::binding(service) == "platform" + && (service.auth_method != "none" + || service.credential_binding.as_deref() == Some("platform")) +} + +fn default_credential_class( + service: &UserService, + credential: &UserApiKey, + has_server_credential: bool, +) -> CredentialClass { + if service.node_id.is_some() && !has_server_credential { + CredentialClass::NodeManaged + } else if credential.credential_source.as_deref() == Some("platform") { + CredentialClass::NyxidPlatformOauthApp + } else { + CredentialClass::UserOwned + } +} + +fn restricted() -> ServiceBillingExplanation { + ServiceBillingExplanation { + status: BillingExplanationStatus::Restricted, + credential_class: None, + credential_label: "Credential restricted".into(), + account: None, + charge_status: ConnectionChargeStatus::Restricted, + credit_billing_configured: None, + service_billing_configured: None, + credential_supplier: None, + rates: Vec::new(), + provider_billing: ProviderBillingDisclosure::Unknown, + context: "for_you".into(), + notes: Vec::new(), + } +} + +fn unavailable(reason: &str) -> ServiceBillingExplanation { + ServiceBillingExplanation { + status: BillingExplanationStatus::Unavailable, + credential_label: "Credential unavailable".into(), + charge_status: ConnectionChargeStatus::Unavailable, + notes: vec![reason.into()], + ..restricted() + } +} + +#[allow(clippy::too_many_arguments)] +fn project_billing( + service: &UserService, + configuration: Option<&ServiceBilling>, + credential_class: CredentialClass, + account: ServiceBillingAccount, + charging_enabled: bool, + resale_enabled: bool, + provider_configured: bool, +) -> ServiceBillingExplanation { + // UserService proxy targets use the connection slug with the catalog's + // billing block. The catalog slug is not the metering heuristic here. + let metric = configuration + .and_then(|b| b.platform_metric) + .unwrap_or_else(|| { + if service.service_type == "ssh" { + BillingMetric::Bytes + } else if service.slug.starts_with("llm-") { + BillingMetric::Tokens + } else { + BillingMetric::Requests + } + }); + let ctx = BillingRouteContext::new( + BillingIngress::Proxy, + String::new(), + account.id.clone(), + String::new(), + None, + Some(service.id.clone()), + service.catalog_service_id.clone(), + Some(service.slug.clone()), + NodeIntent::Direct, + service.auth_method.clone(), + credential_class, + metric, + configuration, + resale_enabled, + ); + let (credential_label, provider_billing) = match credential_class { + CredentialClass::NyxidManagedMaster => ( + "NyxID credential", + ProviderBillingDisclosure::NyxidCredential, + ), + CredentialClass::NyxidPlatformOauthApp => ( + "NyxID OAuth app", + ProviderBillingDisclosure::SeparateProviderAccount, + ), + CredentialClass::NodeManaged => ( + "Node credential", + ProviderBillingDisclosure::SeparateProviderAccount, + ), + CredentialClass::NoAuth => ("No credential", ProviderBillingDisclosure::NoCredential), + CredentialClass::AgentOverrideUserOwned => ( + "Credential override", + ProviderBillingDisclosure::SeparateProviderAccount, + ), + CredentialClass::UserOwned if account.kind == BillingAccountKind::Organization => ( + "Organization credential", + ProviderBillingDisclosure::SeparateProviderAccount, + ), + CredentialClass::UserOwned => ( + "Your credential", + ProviderBillingDisclosure::SeparateProviderAccount, + ), + }; + let mut result = ServiceBillingExplanation { + status: BillingExplanationStatus::Resolved, + credential_class: Some(credential_class), + credential_label: credential_label.into(), + account: Some(account), + charge_status: ConnectionChargeStatus::NotCharged, + credit_billing_configured: Some(configured_usage_charge(configuration, credential_class)), + service_billing_configured: Some(service_billing_configured(configuration)), + credential_supplier: None, + rates: Vec::new(), + provider_billing, + context: "for_you".into(), + notes: Vec::new(), + }; + if !charging_enabled || (!ctx.service_platform_billable && ctx.resale.is_none()) { + result + .notes + .push("No NyxID usage charge applies to this caller and default credential.".into()); + return result; + } + result.charge_status = ConnectionChargeStatus::UsageBased; + if ctx.service_platform_billable { + for (metric, code) in ctx.platform_specs() { + result + .rates + .push(platform_rate(configuration, credential_class, metric, code)); + } + } + if let Some(resale) = &ctx.resale { + result.rates.push(ServiceBillingRate { + layer: "resale".into(), + metric: resale.metric, + credits_per_unit: None, + currency: "credits".into(), + source: "legacy_plan".into(), + }); + } + if result + .rates + .iter() + .any(|rate| rate.credits_per_unit.is_none()) + { + result.status = BillingExplanationStatus::Conditional; + result.notes.push( + "A legacy plan rate applies; the exact amount is determined at execution.".into(), + ); + } + if !provider_configured { + result.status = BillingExplanationStatus::Conditional; + result.charge_status = ConnectionChargeStatus::Conditional; + result.notes.push( + "Billing setup is unavailable; execution depends on the server's billing policy." + .into(), + ); + } + result.notes.push("Allowances and grants are applied before wallet credits. Actual debit is recorded after settlement.".into()); + result +} + +fn platform_rate( + configuration: Option<&ServiceBilling>, + class: CredentialClass, + metric: BillingMetric, + code: &str, +) -> ServiceBillingRate { + let lane = configuration + .and_then(|b| match class { + CredentialClass::NyxidManagedMaster => b.platform_key_pricing.as_ref(), + CredentialClass::NoAuth => None, + _ => b.byok_pricing.as_ref(), + }) + .filter(|l| l.sync_status == PricingSyncStatus::Synced); + let lane_price = lane.and_then(|l| { + if l.lago_metric_code == code { + Some(l.credits_per_unit.clone()) + } else { + l.components + .iter() + .find(|c| c.sync_status == PricingSyncStatus::Synced && c.lago_metric_code == code) + .map(|c| c.credits_per_unit.clone()) + } + }); + let service_price = configuration + .and_then(|b| b.platform_pricing.as_ref()) + .filter(|p| p.sync_status == PricingSyncStatus::Synced && p.lago_metric_code == code) + .map(|p| p.credits_per_unit.clone()); + let source = if lane_price.is_some() { + "credential_lane" + } else if service_price.is_some() { + "service_price" + } else { + "legacy_plan" + }; + ServiceBillingRate { + layer: "platform".into(), + metric, + credits_per_unit: lane_price.or(service_price), + currency: "credits".into(), + source: source.into(), + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::models::org_membership::{COLLECTION_NAME as MEMBERSHIPS, OrgMembership, OrgRole}; + use crate::models::service_billing::{LanePriceComponent, LanePricing, ServicePlatformPricing}; + use crate::models::user::UserType; + use crate::test_utils::{ + connect_test_database, test_app_config, test_membership, test_user, test_user_service, + }; + + fn connection() -> UserService { + bson::from_document(doc! { + "_id": "connection", "user_id": "owner", "slug": "openai-work", + "endpoint_id": "endpoint", "api_key_id": "credential", + "auth_method": "bearer", "auth_key_name": "Authorization", + "is_active": true, "created_at": bson::DateTime::now(), + "updated_at": bson::DateTime::now(), + }) + .unwrap() + } + + fn account(kind: BillingAccountKind) -> ServiceBillingAccount { + ServiceBillingAccount { + id: "owner".into(), + kind, + name: "Account".into(), + } + } + + fn lane(metric: BillingMetric, price: &str, code: &str) -> LanePricing { + LanePricing { + metric, + credits_per_unit: price.into(), + lago_metric_code: code.into(), + sync_status: PricingSyncStatus::Synced, + sync_error: None, + components: Vec::new(), + } + } + + fn metadata_credential(kind: &str) -> UserApiKey { + bson::from_document(doc! { + "_id": "credential", "user_id": "owner", "label": "Connection", + "credential_type": kind, "status": "active", + "created_at": bson::DateTime::now(), "updated_at": bson::DateTime::now(), + }) + .unwrap() + } + + #[test] + fn billing_metadata_service_gate_is_independent_of_twitter_oauth_lane() { + let config = ServiceBilling { + platform_billable: true, + platform_key_pricing: Some(lane(BillingMetric::Requests, "0.05", "twitter-pk")), + ..Default::default() + }; + assert!(service_billing_configured(Some(&config))); + let oauth = project_billing( + &connection(), + Some(&config), + CredentialClass::NyxidPlatformOauthApp, + account(BillingAccountKind::Personal), + true, + false, + true, + ); + assert_eq!(oauth.service_billing_configured, Some(true)); + assert_eq!(oauth.credit_billing_configured, Some(false)); + assert_eq!(oauth.charge_status, ConnectionChargeStatus::NotCharged); + assert!(oauth.rates.is_empty()); + assert!(!service_billing_configured(None)); + assert!(!service_billing_configured(Some(&ServiceBilling { + platform_key_pricing: Some(lane(BillingMetric::Requests, "0", "zero")), + ..Default::default() + }))); + } + + #[test] + fn billing_metadata_supplier_uses_durable_oauth_provenance() { + let service = connection(); + let mut key = metadata_credential("oauth2"); + assert_eq!( + default_credential_class(&service, &key, true), + CredentialClass::UserOwned + ); + assert_eq!( + stored_credential_supplier(&key), + CredentialSupplier::Unknown + ); + key.credential_source = Some("byo".into()); + assert_eq!(stored_credential_supplier(&key), CredentialSupplier::Own); + key.user_oauth_client_id_encrypted = Some(vec![1, 2, 3]); + key.credential_source = Some("platform".into()); + assert_eq!(stored_credential_supplier(&key), CredentialSupplier::Nyxid); + key.credential_source = None; + key.connection_id = Some("connection".into()); + key.provider_config_id = Some("provider".into()); + key.access_token_encrypted = Some(vec![1]); + assert_eq!( + stored_credential_supplier(&key), + CredentialSupplier::Unknown + ); + key.oauth_app_observation = Some( + super::super::oauth_app_source::OAuthAppSource::Byo.observation(key.credential_epoch), + ); + assert_eq!(stored_credential_supplier(&key), CredentialSupplier::Own); + key.user_oauth_client_id_encrypted = None; + key.oauth_app_observation = Some( + super::super::oauth_app_source::OAuthAppSource::Platform + .observation(key.credential_epoch), + ); + assert_eq!(stored_credential_supplier(&key), CredentialSupplier::Nyxid); + // Legacy selection must use the provider token, including for disabled + // connections and selected agent overrides, not a retained client hint. + key.connection_id = None; + key.oauth_app_observation = None; + assert_eq!( + stored_credential_supplier(&key), + CredentialSupplier::Unknown + ); + let sources = HashMap::from([( + key.id.clone(), + super::super::oauth_app_source::OAuthAppSource::Platform, + )]); + assert_eq!( + resolved_credential_supplier(&key, &sources), + CredentialSupplier::Nyxid + ); + assert_eq!( + stored_credential_supplier(&metadata_credential("api_key")), + CredentialSupplier::Own + ); + assert_eq!( + stored_credential_supplier(&metadata_credential("node_managed")), + CredentialSupplier::Own + ); + assert_eq!( + connection_credential_supplier(&service, None), + CredentialSupplier::Unknown + ); + let mut platform = service.clone(); + platform.credential_binding = Some("platform".into()); + assert_eq!( + connection_credential_supplier(&platform, Some(&key)), + CredentialSupplier::Nyxid + ); + } + + #[test] + fn billing_metadata_disabled_and_agent_preserve_service_gate() { + let mut service = connection(); + service.is_active = false; + let mut key = metadata_credential("oauth2"); + key.credential_source = Some("platform".into()); + let mut catalog = crate::models::downstream_service::test_helpers::dummy_service(); + catalog.billing = Some(ServiceBilling { + platform_key_pricing: Some(lane(BillingMetric::Requests, "0.05", "twitter-pk")), + ..Default::default() + }); + let mut result = unavailable("Disabled"); + annotate_configured_charge(&mut result, &service, Some(&catalog), Some(&key), false); + assert_eq!(result.service_billing_configured, Some(true)); + assert_eq!(result.credential_supplier, Some(CredentialSupplier::Nyxid)); + let mut agent = unavailable("Override unavailable"); + annotate_configured_charge(&mut agent, &service, Some(&catalog), Some(&key), true); + assert_eq!(agent.service_billing_configured, Some(true)); + assert_eq!(agent.credential_supplier, Some(CredentialSupplier::Unknown)); + catalog.billing = None; + annotate_configured_charge(&mut agent, &service, Some(&catalog), Some(&key), true); + assert_eq!(agent.service_billing_configured, Some(false)); + let mut hidden = restricted(); + annotate_configured_charge(&mut hidden, &service, Some(&catalog), Some(&key), false); + assert!(hidden.service_billing_configured.is_none()); + assert!(hidden.credential_supplier.is_none()); + } + + #[test] + fn configured_billability_for_disabled_connections_preserves_access_and_unknown_states() { + let mut catalog = crate::models::downstream_service::test_helpers::dummy_service(); + catalog.billing = Some(ServiceBilling { + platform_key_pricing: Some(lane(BillingMetric::Requests, "1", "pk")), + ..Default::default() + }); + let mut service = connection(); + service.catalog_service_id = Some(catalog.id.clone()); + service.is_active = false; + service.credential_binding = Some("platform".into()); + let mut disabled = unavailable("This connection is disabled."); + annotate_configured_charge(&mut disabled, &service, Some(&catalog), None, false); + assert_eq!(disabled.credit_billing_configured, Some(true)); + assert_eq!(disabled.status, BillingExplanationStatus::Unavailable); + assert!(disabled.account.is_none()); + let mut hidden = restricted(); + annotate_configured_charge(&mut hidden, &service, Some(&catalog), None, false); + assert_eq!(hidden.credit_billing_configured, None); + let mut missing_catalog = unavailable("Missing configuration"); + annotate_configured_charge(&mut missing_catalog, &service, None, None, false); + assert_eq!(missing_catalog.credit_billing_configured, None); + let mut override_unavailable = unavailable("Agent override unavailable"); + annotate_configured_charge( + &mut override_unavailable, + &service, + Some(&catalog), + None, + true, + ); + assert_eq!(override_unavailable.credit_billing_configured, None); + } + + #[test] + fn configured_billability_counts_the_credential_lane_independent_of_rollout() { + let mut config = ServiceBilling { + platform_key_pricing: Some(lane(BillingMetric::Requests, "1", "pk")), + ..Default::default() + }; + assert!(configured_usage_charge( + Some(&config), + CredentialClass::NyxidManagedMaster + )); + assert!(!configured_usage_charge( + Some(&config), + CredentialClass::UserOwned + )); + config.byok_pricing = Some(lane(BillingMetric::Requests, "0", "byok")); + assert!(!configured_usage_charge( + Some(&config), + CredentialClass::UserOwned + )); + config + .byok_pricing + .as_mut() + .unwrap() + .components + .push(LanePriceComponent { + metric: BillingMetric::Images, + credits_per_unit: "0.000000000001".into(), + lago_metric_code: "images".into(), + sync_status: PricingSyncStatus::Pending, + sync_error: None, + }); + assert!(configured_usage_charge( + Some(&config), + CredentialClass::UserOwned + )); + let mut service = connection(); + service.is_active = false; + let explanation = project_billing( + &service, + Some(&config), + CredentialClass::UserOwned, + ServiceBillingAccount { + id: "person".into(), + kind: BillingAccountKind::Personal, + name: "Personal".into(), + }, + false, + false, + false, + ); + assert_eq!(explanation.credit_billing_configured, Some(true)); + assert_eq!( + explanation.charge_status, + ConnectionChargeStatus::NotCharged + ); + config.platform_charge_nyxid_credentials_only = true; + assert!(!configured_usage_charge( + Some(&config), + CredentialClass::UserOwned + )); + assert!(configured_usage_charge( + Some(&config), + CredentialClass::NyxidPlatformOauthApp + )); + let mut pending = ServiceBilling { + platform_billable: true, + platform_pricing: Some(ServicePlatformPricing { + credits_per_unit: "0".into(), + lago_metric_code: "legacy".into(), + sync_status: PricingSyncStatus::Pending, + sync_error: None, + }), + ..Default::default() + }; + assert!(configured_usage_charge( + Some(&pending), + CredentialClass::UserOwned + )); + pending.platform_pricing.as_mut().unwrap().sync_status = PricingSyncStatus::Synced; + assert!(!configured_usage_charge( + Some(&pending), + CredentialClass::UserOwned + )); + } + + fn explain(config: &ServiceBilling, class: CredentialClass) -> ServiceBillingExplanation { + project_billing( + &connection(), + Some(config), + class, + account(BillingAccountKind::Personal), + true, + true, + true, + ) + } + + #[test] + fn platform_credential_and_personal_payer_are_independent() { + let config = ServiceBilling { + platform_key_pricing: Some(lane(BillingMetric::InputTokens, "0.000000125", "pk")), + ..Default::default() + }; + let result = explain(&config, CredentialClass::NyxidManagedMaster); + assert_eq!(result.credential_label, "NyxID credential"); + assert_eq!(result.account.unwrap().kind, BillingAccountKind::Personal); + assert_eq!(result.charge_status, ConnectionChargeStatus::UsageBased); + assert_eq!( + result.rates[0].credits_per_unit.as_deref(), + Some("0.000000125") + ); + assert_eq!(result.rates[0].metric, BillingMetric::InputTokens); + assert_eq!( + result.provider_billing, + ProviderBillingDisclosure::NyxidCredential + ); + } + + #[test] + fn shared_oauth_and_credential_override_use_own_key_prices() { + let config = ServiceBilling { + byok_pricing: Some(lane(BillingMetric::Requests, "2", "byok")), + platform_key_pricing: Some(lane(BillingMetric::Tokens, "3", "pk")), + ..Default::default() + }; + for class in [ + CredentialClass::UserOwned, + CredentialClass::AgentOverrideUserOwned, + CredentialClass::NyxidPlatformOauthApp, + CredentialClass::NodeManaged, + ] { + let result = explain(&config, class); + assert_eq!(result.rates[0].credits_per_unit.as_deref(), Some("2")); + assert_eq!( + result.provider_billing, + ProviderBillingDisclosure::SeparateProviderAccount + ); + } + } + + #[test] + fn missing_selected_lane_is_only_free_when_lane_configuration_confirms_it() { + let config = ServiceBilling { + platform_billable: true, + platform_key_pricing: Some(lane(BillingMetric::Tokens, "3", "pk")), + ..Default::default() + }; + assert_eq!( + explain(&config, CredentialClass::UserOwned).charge_status, + ConnectionChargeStatus::NotCharged + ); + let legacy = ServiceBilling { + platform_billable: true, + ..Default::default() + }; + let result = explain(&legacy, CredentialClass::UserOwned); + assert_eq!(result.charge_status, ConnectionChargeStatus::UsageBased); + assert_eq!(result.status, BillingExplanationStatus::Conditional); + assert_eq!(result.rates.len(), 1); + assert_eq!(result.rates[0].credits_per_unit, None); + } + + #[test] + fn unsynced_primary_uses_legacy_rate_and_excludes_components() { + let mut unsynced = lane(BillingMetric::InputTokens, "2", "byok"); + unsynced.sync_status = PricingSyncStatus::Pending; + unsynced.components.push(LanePriceComponent { + metric: BillingMetric::OutputTokens, + credits_per_unit: "4".into(), + lago_metric_code: "byok_output".into(), + sync_status: PricingSyncStatus::Synced, + sync_error: None, + }); + let config = ServiceBilling { + platform_billable: true, + platform_pricing: Some(ServicePlatformPricing { + credits_per_unit: "7".into(), + lago_metric_code: "legacy".into(), + sync_status: PricingSyncStatus::Synced, + sync_error: None, + }), + byok_pricing: Some(unsynced), + ..Default::default() + }; + let result = explain(&config, CredentialClass::UserOwned); + assert_eq!(result.rates.len(), 1); + assert_eq!(result.rates[0].credits_per_unit.as_deref(), Some("7")); + assert_eq!(result.rates[0].source, "service_price"); + assert_eq!(result.rates[0].metric, BillingMetric::Requests); + } + + #[test] + fn only_synced_components_are_exposed_as_effective_rates() { + let mut pricing = lane(BillingMetric::InputTokens, "1", "primary"); + for (metric, status) in [ + (BillingMetric::OutputTokens, PricingSyncStatus::Synced), + (BillingMetric::CacheReadTokens, PricingSyncStatus::Pending), + (BillingMetric::CacheWriteTokens, PricingSyncStatus::Failed), + ] { + pricing.components.push(LanePriceComponent { + metric, + credits_per_unit: "2".into(), + lago_metric_code: metric.as_str().into(), + sync_status: status, + sync_error: None, + }); + } + let config = ServiceBilling { + byok_pricing: Some(pricing), + ..Default::default() + }; + let result = explain(&config, CredentialClass::UserOwned); + assert_eq!(result.rates.len(), 2); + assert_eq!(result.rates[1].metric, BillingMetric::OutputTokens); + assert_eq!(result.rates[1].credits_per_unit.as_deref(), Some("2")); + } + + #[test] + fn charge_restriction_overrides_a_synced_own_key_lane() { + let config = ServiceBilling { + platform_charge_nyxid_credentials_only: true, + byok_pricing: Some(lane(BillingMetric::Requests, "1", "byok")), + ..Default::default() + }; + for class in [ + CredentialClass::UserOwned, + CredentialClass::AgentOverrideUserOwned, + CredentialClass::NodeManaged, + ] { + assert_eq!( + explain(&config, class).charge_status, + ConnectionChargeStatus::NotCharged + ); + } + assert_eq!( + explain(&config, CredentialClass::NyxidPlatformOauthApp).charge_status, + ConnectionChargeStatus::UsageBased + ); + } + + #[test] + fn rollout_or_global_disable_suppresses_both_charge_layers() { + let config = ServiceBilling { + platform_billable: true, + resale_billable: true, + lago_resale_metric_code: Some("resale".into()), + ..Default::default() + }; + let result = project_billing( + &connection(), + Some(&config), + CredentialClass::NyxidManagedMaster, + account(BillingAccountKind::Personal), + false, + true, + true, + ); + assert_eq!(result.charge_status, ConnectionChargeStatus::NotCharged); + assert!(result.rates.is_empty()); + } + + #[test] + fn resale_is_independent_of_the_platform_price_lane() { + let config = ServiceBilling { + resale_billable: true, + lago_resale_metric_code: Some("resale".into()), + ..Default::default() + }; + let master = explain(&config, CredentialClass::NyxidManagedMaster); + assert_eq!(master.charge_status, ConnectionChargeStatus::UsageBased); + assert_eq!(master.rates[0].layer, "resale"); + assert_eq!(master.rates[0].credits_per_unit, None); + assert_eq!( + explain(&config, CredentialClass::UserOwned).charge_status, + ConnectionChargeStatus::NotCharged + ); + } + + #[test] + fn billing_provider_missing_is_conditional_not_free() { + let config = ServiceBilling { + byok_pricing: Some(lane(BillingMetric::Requests, "1", "byok")), + ..Default::default() + }; + let result = project_billing( + &connection(), + Some(&config), + CredentialClass::UserOwned, + account(BillingAccountKind::Organization), + true, + true, + false, + ); + assert_eq!( + result.account.unwrap().kind, + BillingAccountKind::Organization + ); + assert_eq!(result.charge_status, ConnectionChargeStatus::Conditional); + assert_eq!(result.credential_label, "Organization credential"); + assert_eq!(result.rates[0].credits_per_unit.as_deref(), Some("1")); + } + + #[test] + fn no_auth_does_not_erase_legacy_opt_in_billing() { + let legacy = ServiceBilling { + platform_billable: true, + ..Default::default() + }; + assert_eq!( + explain(&legacy, CredentialClass::NoAuth).charge_status, + ConnectionChargeStatus::UsageBased + ); + let lanes = ServiceBilling { + platform_billable: true, + byok_pricing: Some(lane(BillingMetric::Requests, "1", "byok")), + ..Default::default() + }; + assert_eq!( + explain(&lanes, CredentialClass::NoAuth).charge_status, + ConnectionChargeStatus::NotCharged + ); + } + + #[test] + fn restricted_and_unavailable_never_masquerade_as_free_or_publish_an_account() { + for result in [restricted(), unavailable("Credential unavailable")] { + assert!(result.account.is_none()); + assert!(result.credential_class.is_none()); + assert!(result.rates.is_empty()); + assert_ne!(result.charge_status, ConnectionChargeStatus::NotCharged); + } + } + + #[test] + fn websocket_fallback_pricing_is_explicitly_conditional_but_fixed_lanes_are_not() { + let mut catalog = crate::models::downstream_service::test_helpers::dummy_service(); + catalog.capabilities = Some(crate::models::downstream_service::ServiceCapabilities { + supports_websocket: true, + ..Default::default() + }); + let mut config = ServiceBilling { + platform_billable: true, + platform_pricing: Some(ServicePlatformPricing { + credits_per_unit: "2".into(), + lago_metric_code: "service".into(), + sync_status: PricingSyncStatus::Synced, + sync_error: None, + }), + ..Default::default() + }; + catalog.billing = Some(config.clone()); + let mut fallback = explain(&config, CredentialClass::UserOwned); + assert_eq!(fallback.status, BillingExplanationStatus::Resolved); + annotate_transport_pricing(&mut fallback, Some(&catalog)); + assert_eq!(fallback.status, BillingExplanationStatus::Conditional); + assert!(fallback.notes.iter().any(|note| note.contains("WebSocket"))); + assert_eq!(fallback.rates[0].metric, BillingMetric::Requests); + + config.platform_metric = Some(BillingMetric::Requests); + catalog.billing = Some(config.clone()); + let mut fixed_metric = explain(&config, CredentialClass::UserOwned); + annotate_transport_pricing(&mut fixed_metric, Some(&catalog)); + assert_eq!(fixed_metric.status, BillingExplanationStatus::Resolved); + + config.platform_metric = None; + config.byok_pricing = Some(lane(BillingMetric::InputTokens, "1", "byok")); + catalog.billing = Some(config.clone()); + let mut fixed_lane = explain(&config, CredentialClass::UserOwned); + annotate_transport_pricing(&mut fixed_lane, Some(&catalog)); + assert_eq!(fixed_lane.status, BillingExplanationStatus::Resolved); + assert!( + !fixed_lane + .notes + .iter() + .any(|note| note.contains("WebSocket")) + ); + } + + fn stored_credential(id: &str, owner: &str) -> UserApiKey { + bson::from_document(doc! { + "_id": id, "user_id": owner, "label": "External credential", + "credential_type": "api_key", "status": "active", + // Intentionally not decryptable: this projection must only inspect metadata. + "credential_encrypted": bson::Binary { + subtype: bson::spec::BinarySubtype::Generic, bytes: vec![1, 2, 3], + }, + "created_at": bson::DateTime::now(), "updated_at": bson::DateTime::now(), + }) + .unwrap() + } + + async fn seed_accounts(db: &Database, membership: OrgMembership) { + db.collection::(USERS) + .insert_many([ + test_user("person", UserType::Person), + test_user("org", UserType::Org), + ]) + .await + .unwrap(); + db.collection::(MEMBERSHIPS) + .insert_one(membership) + .await + .unwrap(); + } + + #[tokio::test] + async fn database_preview_resolves_default_personal_org_and_platform_payers_without_writes() { + let db = connect_test_database("insights_billing_payers") + .await + .expect("database"); + seed_accounts(&db, test_membership("org", "person", OrgRole::Member, None)).await; + let mut catalog = crate::models::downstream_service::test_helpers::dummy_service(); + catalog.auth_method = "bearer".into(); + catalog.auth_key_name = "Authorization".into(); + catalog.service_category = "internal".into(); + catalog.credential_encrypted = vec![1, 2, 3]; + db.collection::(CATALOG) + .insert_one(&catalog) + .await + .unwrap(); + db.collection::(CREDENTIALS) + .insert_many([ + stored_credential("personal-key", "person"), + stored_credential("org-key", "org"), + ]) + .await + .unwrap(); + let mut personal = test_user_service( + "personal", + "person", + "personal", + "endpoint", + Some(&catalog.id), + None, + ); + personal.auth_method = "bearer".into(); + personal.api_key_id = Some("personal-key".into()); + let mut organization = test_user_service( + "organization", + "org", + "org-service", + "endpoint", + Some(&catalog.id), + None, + ); + organization.auth_method = "bearer".into(); + organization.api_key_id = Some("org-key".into()); + let mut platform = organization.clone(); + platform.id = "platform".into(); + platform.api_key_id = None; + platform.credential_binding = Some("platform".into()); + let billing = BillingService::new(db.clone(), Arc::new(test_app_config())); + let result = explain_connections( + &db, + &billing, + "person", + "person", + &[personal, organization, platform], + ) + .await + .unwrap(); + + let personal = &result["personal"]; + assert_eq!(personal.credential_class, Some(CredentialClass::UserOwned)); + assert_eq!(personal.account.as_ref().unwrap().id, "person"); + assert_eq!( + personal.account.as_ref().unwrap().kind, + BillingAccountKind::Personal + ); + let organization = &result["organization"]; + assert_eq!( + organization.credential_class, + Some(CredentialClass::UserOwned) + ); + assert_eq!(organization.account.as_ref().unwrap().id, "org"); + assert_eq!( + organization.account.as_ref().unwrap().kind, + BillingAccountKind::Organization + ); + let platform = &result["platform"]; + assert_eq!( + platform.credential_class, + Some(CredentialClass::NyxidManagedMaster) + ); + assert_eq!( + platform.account.as_ref().unwrap().id, + "person", + "org-visible platform credential bills the caller" + ); + assert_eq!( + platform.account.as_ref().unwrap().kind, + BillingAccountKind::Personal + ); + assert!( + result + .values() + .all(|r| r.charge_status == ConnectionChargeStatus::NotCharged) + ); + for collection in [ + crate::models::billing_wallet::COLLECTION_NAME, + crate::models::usage_meter::COLLECTION_NAME, + ] { + assert_eq!( + db.collection::(collection) + .count_documents(doc! {}) + .await + .unwrap(), + 0 + ); + } + let keys: Vec = db + .collection::(CREDENTIALS) + .find(doc! {}) + .await + .unwrap() + .try_collect() + .await + .unwrap(); + assert!(keys.iter().all(|key| key.last_used_at.is_none())); + db.drop().await.unwrap(); + } + + #[tokio::test] + async fn database_missing_or_invalid_credentials_cannot_publish_a_payer_or_free_state() { + let db = connect_test_database("insights_billing_missing") + .await + .expect("database"); + db.collection::(USERS) + .insert_one(test_user("person", UserType::Person)) + .await + .unwrap(); + let mut empty = stored_credential("empty", "person"); + empty.credential_encrypted = None; + let mut revoked = stored_credential("revoked", "person"); + revoked.status = "revoked".into(); + db.collection::(CREDENTIALS) + .insert_many([empty, revoked, stored_credential("foreign", "someone-else")]) + .await + .unwrap(); + let services: Vec<_> = ["missing", "empty", "revoked", "foreign"] + .into_iter() + .map(|id| { + let mut service = test_user_service(id, "person", id, "endpoint", None, None); + service.auth_method = "bearer".into(); + service.api_key_id = Some(id.into()); + service + }) + .collect(); + let billing = BillingService::new(db.clone(), Arc::new(test_app_config())); + let result = explain_connections(&db, &billing, "person", "person", &services) + .await + .unwrap(); + assert_eq!(result.len(), 4); + for explanation in result.values() { + assert_eq!(explanation.status, BillingExplanationStatus::Unavailable); + assert_eq!( + explanation.charge_status, + ConnectionChargeStatus::Unavailable + ); + assert!(explanation.account.is_none()); + assert!(explanation.rates.is_empty()); + } + db.drop().await.unwrap(); + } + + #[tokio::test] + async fn database_scope_and_viewer_restrictions_hide_billing_accounts() { + let db = connect_test_database("insights_billing_scope") + .await + .expect("database"); + seed_accounts( + &db, + test_membership( + "org", + "person", + OrgRole::Viewer, + Some(vec!["visible".into()]), + ), + ) + .await; + let services = [ + test_user_service("visible", "org", "visible", "endpoint", None, None), + test_user_service("hidden", "org", "hidden", "endpoint", None, None), + ]; + let billing = BillingService::new(db.clone(), Arc::new(test_app_config())); + let result = explain_connections(&db, &billing, "person", "person", &services) + .await + .unwrap(); + assert_eq!( + result["visible"].status, + BillingExplanationStatus::Unavailable + ); + assert_eq!( + result["hidden"].status, + BillingExplanationStatus::Restricted + ); + assert!( + result + .values() + .all(|r| r.account.is_none() && r.rates.is_empty()) + ); + db.drop().await.unwrap(); + } + + fn stored_agent_key(owner: &str) -> ApiKey { + bson::from_document(doc! { + "_id": "agent", "user_id": owner, "name": "Build agent", + "key_prefix": "nyxid_ag_test", "key_hash": "test-hash", + "scopes": "proxy", "is_active": true, "created_at": bson::DateTime::now(), + "allow_all_services": false, "allowed_service_ids": ["service"], + }) + .unwrap() + } + + #[tokio::test] + async fn agent_preview_uses_selected_principal_and_final_override_price_lane() { + let db = connect_test_database("insights_billing_agent") + .await + .expect("database"); + seed_accounts(&db, test_membership("org", "person", OrgRole::Admin, None)).await; + let mut catalog = crate::models::downstream_service::test_helpers::dummy_service(); + catalog.auth_method = "bearer".into(); + catalog.auth_key_name = "Authorization".into(); + catalog.service_category = "internal".into(); + catalog.credential_encrypted = vec![1, 2, 3]; + catalog.billing = Some(ServiceBilling { + byok_pricing: Some(lane(BillingMetric::Requests, "2", "byok")), + platform_key_pricing: Some(lane(BillingMetric::Requests, "5", "platform")), + ..Default::default() + }); + db.collection::(CATALOG) + .insert_one(&catalog) + .await + .unwrap(); + let mut service = test_user_service( + "service", + "org", + "platform", + "endpoint", + Some(&catalog.id), + None, + ); + service.auth_method = "bearer".into(); + service.credential_binding = Some("platform".into()); + db.collection::(crate::models::user_service::COLLECTION_NAME) + .insert_one(&service) + .await + .unwrap(); + db.collection::(AGENT_KEYS) + .insert_one(stored_agent_key("org")) + .await + .unwrap(); + let services = [service]; + let mut config = test_app_config(); + config.billing_enabled = true; + let billing = BillingService::new(db.clone(), Arc::new(config)); + let default = explain_connections(&db, &billing, "person", "person", &services) + .await + .unwrap(); + assert_eq!(default["service"].account.as_ref().unwrap().id, "person"); + assert_eq!( + default["service"].rates[0].credits_per_unit.as_deref(), + Some("5") + ); + let selected = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!(selected["service"].account.as_ref().unwrap().id, "org"); + assert_eq!( + selected["service"].credential_class, + Some(CredentialClass::NyxidManagedMaster) + ); + assert_eq!(selected["service"].context, "agent_key"); + + db.collection::(CREDENTIALS) + .insert_one(stored_credential("override", "org")) + .await + .unwrap(); + let now = chrono::Utc::now(); + db.collection::(BINDINGS) + .insert_one(AgentServiceBinding { + id: "binding".into(), + api_key_id: "agent".into(), + user_service_id: "service".into(), + user_api_key_id: "override".into(), + user_id: "org".into(), + created_at: now, + updated_at: now, + }) + .await + .unwrap(); + let selected = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + let selected = &selected["service"]; + assert_eq!(selected.account.as_ref().unwrap().id, "org"); + assert_eq!( + selected.account.as_ref().unwrap().kind, + BillingAccountKind::Organization + ); + assert_eq!( + selected.credential_class, + Some(CredentialClass::AgentOverrideUserOwned) + ); + assert_eq!(selected.credential_label, "Credential override"); + assert_eq!(selected.rates[0].credits_per_unit.as_deref(), Some("2")); + assert!( + db.collection::(CREDENTIALS) + .find_one(doc! { "_id": "override" }) + .await + .unwrap() + .unwrap() + .last_used_at + .is_none() + ); + + // A configured override never grants access by itself. + db.collection::(AGENT_KEYS) + .update_one( + doc! { "_id": "agent" }, + doc! { "$set": { "allowed_service_ids": [] } }, + ) + .await + .unwrap(); + let denied = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!( + denied["service"].status, + BillingExplanationStatus::Unavailable + ); + assert!(denied["service"].account.is_none()); + + // The same runtime auto-connected expansion admits an explicit platform binding. + db.collection::(AGENT_KEYS) + .update_one( + doc! { "_id": "agent" }, + doc! { "$set": { "allow_auto_connected_services": true } }, + ) + .await + .unwrap(); + let permitted = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!( + permitted["service"].credential_class, + Some(CredentialClass::AgentOverrideUserOwned) + ); + + // Invalid overrides fail closed instead of pretending the default key was selected. + db.collection::(CREDENTIALS) + .update_one( + doc! { "_id": "override" }, + doc! { "$set": { "status": "revoked" } }, + ) + .await + .unwrap(); + let invalid = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!( + invalid["service"].status, + BillingExplanationStatus::Unavailable + ); + assert!(invalid["service"].account.is_none()); + db.drop().await.unwrap(); + } + + #[tokio::test] + async fn agent_preview_preserves_key_management_lifecycle_and_service_scope_acl() { + let db = connect_test_database("insights_billing_agent_acl") + .await + .expect("database"); + seed_accounts(&db, test_membership("org", "person", OrgRole::Member, None)).await; + db.collection::(AGENT_KEYS) + .insert_one(stored_agent_key("org")) + .await + .unwrap(); + let services = [test_user_service( + "service", "org", "service", "endpoint", None, None, + )]; + let billing = BillingService::new(db.clone(), Arc::new(test_app_config())); + assert!(matches!( + explain_for_agent_key(&db, &billing, "person", &services, "agent").await, + Err(AppError::NotFound(_)) + )); + db.collection::(MEMBERSHIPS) + .update_one( + doc! { "org_user_id": "org", "member_user_id": "person" }, + doc! { "$set": { "role": "admin" } }, + ) + .await + .unwrap(); + for changes in [ + doc! { "is_active": false }, + doc! { "is_active": true, "expires_at": bson::DateTime::from_chrono(chrono::Utc::now() - chrono::Duration::minutes(1)) }, + doc! { "expires_at": null, "purpose": "scheduled_invocation" }, + doc! { "purpose": "general", "scopes": "read" }, + ] { + db.collection::(AGENT_KEYS) + .update_one(doc! { "_id": "agent" }, doc! { "$set": changes }) + .await + .unwrap(); + let result = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!( + result["service"].status, + BillingExplanationStatus::Unavailable + ); + assert!(result["service"].account.is_none()); + } + db.collection::(AGENT_KEYS) + .update_one( + doc! { "_id": "agent" }, + doc! { "$set": { "scopes": "proxy", "allow_all_services": true } }, + ) + .await + .unwrap(); + db.collection::(MEMBERSHIPS) + .update_one( + doc! { "org_user_id": "org", "member_user_id": "person" }, + doc! { "$set": { "allowed_service_ids": [] } }, + ) + .await + .unwrap(); + let result = explain_for_agent_key(&db, &billing, "person", &services, "agent") + .await + .unwrap(); + assert_eq!( + result["service"].status, + BillingExplanationStatus::Restricted + ); + assert!(result["service"].account.is_none()); + db.drop().await.unwrap(); + } +} diff --git a/backend/src/services/unified_key_service.rs b/backend/src/services/unified_key_service.rs index 42ce0c965..3e11307af 100644 --- a/backend/src/services/unified_key_service.rs +++ b/backend/src/services/unified_key_service.rs @@ -619,6 +619,8 @@ pub struct KeyView { /// re-typing the credential. `None` otherwise. The client_secret is /// never surfaced (write-only across the API). pub oauth_client_id: Option, + /// Resolved OAuth app source, including legacy provider-token metadata. + pub oauth_app_source: Option, /// Scopes currently granted on this OAuth connection, parsed from the /// backing `UserApiKey.token_scopes`. Lets the connect UIs pre-select and /// lock the existing grant when adding scopes to an existing connection @@ -2575,6 +2577,8 @@ pub async fn list_keys_read_only_with_grants( .await? }; let ak_map: HashMap<&str, &UserApiKey> = api_keys.iter().map(|k| (k.id.as_str(), k)).collect(); + let app_sources = + super::oauth_app_source::load(db, &api_keys.iter().collect::>()).await?; // Batch-load catalog services (for names + SSH config). let catalog_ids: Vec<&str> = tagged @@ -2637,6 +2641,11 @@ pub async fn list_keys_read_only_with_grants( // present. Sequential await is fine — N is bounded by the user's // key count and decrypt is fast. for view in views.iter_mut() { + view.oauth_app_source = view + .api_key_id + .as_ref() + .and_then(|id| app_sources.get(id)) + .map(|source| source.as_str().to_owned()); if let Some(catalog) = view .catalog_service_id .as_deref() @@ -2736,6 +2745,13 @@ pub async fn get_key( user_service_service::CredentialSource::Personal, ); + let app_sources = super::oauth_app_source::load(db, &ak.iter().collect::>()).await?; + view.oauth_app_source = view + .api_key_id + .as_ref() + .and_then(|id| app_sources.get(id)) + .map(|source| source.as_str().to_owned()); + if let Some(catalog) = catalog_ds.as_ref() { let provider = if let Some(id) = &catalog.provider_config_id { db.collection::(crate::models::provider_config::COLLECTION_NAME) @@ -4557,6 +4573,9 @@ fn build_key_view( // `EncryptionKeys` operations are async and `build_key_view` // is intentionally sync. oauth_client_id: None, + oauth_app_source: ak + .and_then(super::oauth_app_source::from_key) + .map(|source| source.as_str().to_owned()), // OAuth providers echo scopes using either spaces or commas. Normalize // both forms at the read boundary while preserving the raw provider // response in storage and the first-occurrence display order. @@ -4754,6 +4773,7 @@ mod tests { fn sample_api_key(credential_type: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: "key-1".to_string(), user_id: "user-1".to_string(), @@ -6265,6 +6285,29 @@ mod tests { assert_eq!(list_view.label, show_view.label); } + #[test] + fn oauth_app_metadata_key_view_exposes_observed_modern_oauth() { + let service = sample_service("oauth2"); + let mut key = sample_api_key("oauth2"); + key.connection_id = Some("connection".into()); + key.provider_config_id = Some("provider".into()); + key.access_token_encrypted = Some(vec![1]); + key.oauth_app_observation = Some( + super::super::oauth_app_source::OAuthAppSource::Platform + .observation(key.credential_epoch), + ); + let view = build_key_view( + &service, + &sample_endpoint(), + Some(&key), + &HashMap::new(), + &HashMap::new(), + crate::services::user_service_service::CredentialSource::Personal, + ); + assert_eq!(view.oauth_app_source.as_deref(), Some("platform")); + assert!(view.oauth_client_id.is_none()); + } + /// Companion guard: when the service has no api key (auto-provisioned /// no-auth), the label falls back to the endpoint label. If a caller /// later updates the endpoint label, both paths must reflect the new @@ -8146,6 +8189,7 @@ mod tests { let now = Utc::now(); db.collection::(USER_API_KEYS) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: stripped_key_id.clone(), user_id: user_id.clone(), diff --git a/backend/src/services/user_api_key_service.rs b/backend/src/services/user_api_key_service.rs index 9df155bbd..8b0868eae 100644 --- a/backend/src/services/user_api_key_service.rs +++ b/backend/src/services/user_api_key_service.rs @@ -13,6 +13,7 @@ use crate::models::user_provider_token::{ }; use crate::models::user_service::COLLECTION_NAME as USER_SERVICES; use crate::services::agent_binding_service; +use crate::services::oauth_app_source::OAuthAppSource; fn credential_epoch_add_expr() -> mongodb::bson::Document { doc! { @@ -23,6 +24,17 @@ fn credential_epoch_add_expr() -> mongodb::bson::Document { } } +fn oauth_app_observation_expr(source: Option, now: bson::DateTime) -> bson::Bson { + source.map_or(bson::Bson::Null, |source| { + doc! { + "source": source.as_str(), + "credential_epoch": credential_epoch_add_expr(), + "observed_at": now, + } + .into() + }) +} + /// Maximum credential length in bytes to prevent abuse. const MAX_CREDENTIAL_LENGTH: usize = 8192; pub(crate) const VALID_CREDENTIAL_TYPES: &[&str] = &[ @@ -241,6 +253,7 @@ pub async fn build_api_key( }; let api_key = UserApiKey { + oauth_app_observation: None, credential_source, id: Uuid::new_v4().to_string(), user_id: user_id.to_string(), @@ -335,6 +348,10 @@ pub(crate) fn api_key_from_provider_token( let now = Utc::now(); let api_key = UserApiKey { + oauth_app_observation: (credential_type == "oauth2").then(|| { + OAuthAppSource::from_credential_owner(provider_token.credential_user_id.as_deref()) + .observation(default_credential_epoch()) + }), credential_source: None, id: Uuid::new_v4().to_string(), user_id: user_id.to_string(), @@ -523,6 +540,13 @@ async fn sync_provider_token_to_api_keys_impl( "last_used_at": optional_datetime_bson(token.last_used_at), "error_message": optional_string_bson(token.error_message.as_deref()), "updated_at": &now, + "oauth_app_observation": if token.token_type == "oauth2" { + doc! { + "source": OAuthAppSource::from_credential_owner(token.credential_user_id.as_deref()).as_str(), + "credential_epoch": key.credential_epoch, + "observed_at": &now, + }.into() + } else { bson::Bson::Null }, } } else { doc! { @@ -552,6 +576,20 @@ async fn sync_provider_token_to_api_keys_impl( literal_set.insert(field, doc! { "$literal": value }); } literal_set.insert("credential_epoch", credential_epoch_add_expr()); + literal_set.insert( + "oauth_app_observation", + oauth_app_observation_expr( + provider_token + .as_ref() + .filter(|token| token.token_type == "oauth2") + .map(|token| { + OAuthAppSource::from_credential_owner( + token.credential_user_id.as_deref(), + ) + }), + now, + ), + ); crate::services::service_history::collection::(db, COLLECTION_NAME) .update_one(doc! { "_id": &key.id }, vec![doc! { "$set": literal_set }]) .await?; @@ -589,6 +627,7 @@ async fn sync_provider_token_to_api_keys_impl( /// - `AppError::NotFound` if no `UserApiKey` matches the connection_id /// (e.g. the user deleted the pending placeholder mid-flow). /// - Encryption / database errors bubble up unchanged. +#[allow(clippy::too_many_arguments)] pub async fn write_oauth_tokens_to_key( db: &mongodb::Database, encryption_keys: &EncryptionKeys, @@ -597,6 +636,7 @@ pub async fn write_oauth_tokens_to_key( refresh_token: Option<&str>, token_scopes: Option<&str>, expires_at: Option>, + app_source: Option, ) -> AppResult<()> { let access_enc = encryption_keys.encrypt(access_token.as_bytes()).await?; let refresh_enc = match refresh_token { @@ -640,6 +680,10 @@ pub async fn write_oauth_tokens_to_key( // mask the new access_token at proxy time. set_doc.insert("credential_encrypted", bson::Bson::Null); set_doc.insert("credential_epoch", credential_epoch_add_expr()); + set_doc.insert( + "oauth_app_observation", + oauth_app_observation_expr(app_source, now), + ); // Exclude terminal-status rows from the write. `revoked` / `failed` // are terminal by design (matching `sync_provider_token_to_api_keys` @@ -688,6 +732,7 @@ pub async fn write_chat_oauth_tokens_to_key( refresh_token: Option<&str>, token_scopes: Option<&str>, expires_at: Option>, + app_source: Option, ) -> AppResult { let access_enc = encryption_keys.encrypt(access_token.as_bytes()).await?; let refresh_enc = match refresh_token { @@ -715,6 +760,10 @@ pub async fn write_chat_oauth_tokens_to_key( } set_doc.insert("credential_encrypted", bson::Bson::Null); set_doc.insert("credential_epoch", credential_epoch_add_expr()); + set_doc.insert( + "oauth_app_observation", + oauth_app_observation_expr(app_source, now), + ); let result = crate::services::service_history::collection::(db, COLLECTION_NAME) .update_one( @@ -1789,6 +1838,7 @@ mod tests { fn sample_key(credential_type: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: "key-1".to_string(), user_id: "user-1".to_string(), @@ -1870,6 +1920,7 @@ mod tests { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: api_key_id.clone(), user_id: org_id.clone(), @@ -2506,6 +2557,7 @@ mod tests { Some("fresh-refresh"), Some("openid"), None, + Some(super::OAuthAppSource::Platform), ) .await .unwrap(); @@ -2513,6 +2565,28 @@ mod tests { assert_eq!(updated.status, "active"); assert!(updated.access_token_encrypted.is_some()); assert_eq!(updated.credential_epoch, 2); + let observed = updated.oauth_app_observation.as_ref().unwrap(); + assert_eq!(observed.source, "platform"); + assert_eq!(observed.credential_epoch, updated.credential_epoch); + assert_eq!(Some(observed.observed_at), updated.last_authorized_at); + assert_eq!(updated.credential_source, key.credential_source); + + // An unstamped replacement must clear the previous observation. + write_oauth_tokens_to_key( + &db, + &enc, + &connection_id, + "replacement", + None, + None, + None, + None, + ) + .await + .unwrap(); + let replaced = get_key(&db, &key.id).await; + assert_eq!(replaced.credential_epoch, 3); + assert!(replaced.oauth_app_observation.is_none()); } fn live_oauth_state(user_id: &str, provider_id: &str) -> OAuthState { @@ -2983,6 +3057,7 @@ mod tests { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.clone(), user_id: user_id.clone(), @@ -3020,6 +3095,7 @@ mod tests { Some("refresh-token-456"), Some("openid profile"), Some(expires), + None, ) .await .unwrap(); @@ -3525,6 +3601,7 @@ mod tests { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.clone(), user_id: uuid::Uuid::new_v4().to_string(), @@ -3564,6 +3641,7 @@ mod tests { Some(""), None, None, + None, ) .await .unwrap(); @@ -3599,6 +3677,7 @@ mod tests { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.clone(), user_id: uuid::Uuid::new_v4().to_string(), @@ -3637,6 +3716,7 @@ mod tests { None, None, None, + None, ) .await .unwrap(); @@ -3823,6 +3903,7 @@ mod tests { None, None, None, + None, ) .await; @@ -3850,6 +3931,7 @@ mod tests { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.clone(), user_id: uuid::Uuid::new_v4().to_string(), @@ -3886,6 +3968,7 @@ mod tests { None, None, None, + None, ) .await; @@ -3924,6 +4007,7 @@ mod tests { for (key_id, conn_id) in [(&key_a, &conn_a), (&key_b, &conn_b)] { db.collection::(super::COLLECTION_NAME) .insert_one(UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id.clone(), user_id: user_id.clone(), @@ -3962,6 +4046,7 @@ mod tests { None, None, None, + None, ) .await .unwrap(); @@ -5519,6 +5604,7 @@ mod tests { None, None, None, + None, ) .await .unwrap() @@ -5541,6 +5627,7 @@ mod tests { None, Some("read:user"), None, + None, ) .await .unwrap() @@ -5648,6 +5735,7 @@ mod tests { Some("replacement-refresh"), Some("read:user"), None, + None, ) .await .unwrap() @@ -5783,6 +5871,7 @@ mod tests { None, None, None, + None, ) .await .unwrap() @@ -5800,6 +5889,7 @@ mod tests { None, Some("legacy-scope"), None, + None, ) .await .unwrap(); @@ -5820,6 +5910,7 @@ mod tests { None, None, None, + None, ) .await .unwrap() diff --git a/backend/src/services/user_credentials_service.rs b/backend/src/services/user_credentials_service.rs index 49abd9185..2d7a37eaf 100644 --- a/backend/src/services/user_credentials_service.rs +++ b/backend/src/services/user_credentials_service.rs @@ -220,10 +220,11 @@ pub async fn delete_user_credentials( /// Resolved OAuth client credentials (decrypted). #[derive(Debug)] pub struct ResolvedOAuthCredentials { + pub app_source: super::oauth_app_source::OAuthAppSource, pub client_id: String, pub client_secret: Option, - /// `Some(user_id)` when user-provided OAuth app credentials were used. - /// `None` means provider-level credentials were used. + /// Legacy user-provider credential lookup. Embedded connection apps also + /// leave this empty; use `app_source` to identify the selected app source. pub credential_user_id: Option, } @@ -366,6 +367,7 @@ pub(crate) async fn decrypt_provider_credentials( }; Ok(ResolvedOAuthCredentials { + app_source: super::oauth_app_source::OAuthAppSource::Platform, client_id, client_secret, credential_user_id: None, @@ -400,6 +402,7 @@ pub async fn decrypt_claimed_key_oauth_credentials( }; Ok(ResolvedOAuthCredentials { + app_source: super::oauth_app_source::OAuthAppSource::Byo, client_id, client_secret, credential_user_id: Some(key.user_id.clone()), @@ -434,6 +437,7 @@ async fn decrypt_user_credentials( }; Ok(ResolvedOAuthCredentials { + app_source: super::oauth_app_source::OAuthAppSource::Byo, client_id, client_secret, credential_user_id: Some(credential_user_id.to_string()), @@ -525,6 +529,7 @@ pub async fn resolve_connection_oauth_credentials( }; Ok(Some(ResolvedOAuthCredentials { + app_source: super::oauth_app_source::OAuthAppSource::Byo, client_id, client_secret, // The `credential_user_id` field on `ResolvedOAuthCredentials` @@ -551,6 +556,7 @@ mod tests { fn placeholder_key(connection_id: &str) -> UserApiKey { UserApiKey { + oauth_app_observation: None, credential_source: None, id: uuid::Uuid::new_v4().to_string(), user_id: uuid::Uuid::new_v4().to_string(), @@ -1376,5 +1382,9 @@ mod tests { assert_eq!(resolved.client_secret.as_deref(), Some("super-secret")); // Per §12: the multi-connection branch leaves credential_user_id None. assert!(resolved.credential_user_id.is_none()); + assert_eq!( + resolved.app_source, + crate::services::oauth_app_source::OAuthAppSource::Byo + ); } } diff --git a/backend/src/services/user_preferences_service.rs b/backend/src/services/user_preferences_service.rs new file mode 100644 index 000000000..19616d41a --- /dev/null +++ b/backend/src/services/user_preferences_service.rs @@ -0,0 +1,51 @@ +use chrono::Utc; +use mongodb::{ + Database, + bson::{self, doc}, +}; + +use crate::errors::{AppError, AppResult}; +use crate::models::user::{COLLECTION_NAME, ServiceViewPreferences, User}; + +pub async fn save_services_view( + db: &Database, + user_id: &str, + mut preferences: ServiceViewPreferences, +) -> AppResult { + for (name, values) in [ + ("Organization", &mut preferences.organization_ids), + ("Service", &mut preferences.service_group_ids), + ] { + if values.len() > 100 { + return Err(AppError::ValidationError(format!( + "{name} filter is limited to 100 selections" + ))); + } + if values + .iter() + .any(|value| value.trim().is_empty() || value.chars().count() > 128) + { + return Err(AppError::ValidationError(format!( + "{name} filter must contain 1 to 128 characters per selection" + ))); + } + values.sort(); + values.dedup(); + } + if preferences.search.chars().count() > 200 { + return Err(AppError::ValidationError( + "Service search must be 200 characters or less".to_string(), + )); + } + let result = db.collection::(COLLECTION_NAME) + .update_one(doc! { "_id": user_id }, doc! { "$set": { + "profile_config.services_view": bson::to_bson(&preferences) + .map_err(|_| AppError::Internal("Could not encode service view preferences".to_string()))?, + "updated_at": bson::DateTime::from_chrono(Utc::now()), + } }) + .await?; + if result.matched_count == 0 { + return Err(AppError::NotFound("User not found".to_string())); + } + Ok(preferences) +} diff --git a/backend/src/services/user_token_service.rs b/backend/src/services/user_token_service.rs index d0bb20575..13c568b3a 100644 --- a/backend/src/services/user_token_service.rs +++ b/backend/src/services/user_token_service.rs @@ -1567,6 +1567,7 @@ pub async fn poll_device_code( oauth_state.connection_id.as_deref(), &token_data, now, + resolved.app_source, ) .await; } @@ -1582,6 +1583,7 @@ pub async fn poll_device_code( oauth_state.connection_id.as_deref(), &resp_data, now, + resolved.app_source, ) .await } @@ -1604,6 +1606,7 @@ async fn store_device_code_tokens( connection_id: Option<&str>, token_data: &serde_json::Value, now: chrono::DateTime, + app_source: super::oauth_app_source::OAuthAppSource, ) -> AppResult { let access_token = token_data["access_token"] .as_str() @@ -1630,6 +1633,7 @@ async fn store_device_code_tokens( refresh_token, scope, token_expires_at, + Some(app_source), ) .await .inspect_err(|e| { @@ -2003,6 +2007,7 @@ pub async fn handle_oauth_callback( refresh_token, scope, token_expires_at, + Some(resolved.app_source), ) .await?; if !wrote { @@ -2019,6 +2024,7 @@ pub async fn handle_oauth_callback( refresh_token, scope, token_expires_at, + Some(resolved.app_source), ) .await .inspect_err(|e| { @@ -2699,6 +2705,11 @@ async fn refresh_user_api_key_under_lease( let now = Utc::now(); let access_enc = encryption_keys.encrypt(new_access_token.as_bytes()).await?; + let observed_source = if api_key.user_oauth_client_id_encrypted.is_some() { + super::oauth_app_source::OAuthAppSource::Byo + } else { + super::oauth_app_source::OAuthAppSource::Platform + }; let mut set_doc = doc! { "access_token_encrypted": bson::Binary { subtype: bson::spec::BinarySubtype::Generic, @@ -2708,6 +2719,11 @@ async fn refresh_user_api_key_under_lease( "error_message": bson::Bson::Null, "last_used_at": bson::DateTime::from_chrono(now), "updated_at": bson::DateTime::from_chrono(now), + "oauth_app_observation": { + "source": observed_source.as_str(), + "credential_epoch": api_key.credential_epoch, + "observed_at": bson::DateTime::from_chrono(now), + }, }; if let Some(exp) = expires_in { let new_expires = now + Duration::seconds(exp); @@ -4039,6 +4055,10 @@ mod tests { .unwrap() .unwrap(); assert_eq!(saved.status, "active"); + let observation = saved.oauth_app_observation.as_ref().unwrap(); + assert_eq!(observation.source, "platform"); + assert_eq!(observation.credential_epoch, saved.credential_epoch); + assert_eq!(Some(observation.observed_at), saved.last_authorized_at); ( saved.access_token_encrypted, saved.refresh_token_encrypted, @@ -4656,6 +4676,7 @@ mod tests { }; let now = Utc::now(); let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: key_id, user_id: Uuid::new_v4().to_string(), @@ -5698,6 +5719,19 @@ mod tests { .unwrap(); assert_eq!(String::from_utf8(bytes).unwrap(), "fresh-access-token"); assert_eq!(refreshed.token_scopes.as_deref(), Some("openid profile")); + assert_eq!( + refreshed.oauth_app_observation.as_ref().unwrap().source, + "platform" + ); + assert_eq!( + refreshed + .oauth_app_observation + .as_ref() + .unwrap() + .credential_epoch, + key.credential_epoch + ); + assert_eq!(refreshed.credential_source, key.credential_source); // expires_at advanced past now. assert!(refreshed.expires_at.unwrap() > Utc::now()); } @@ -5747,6 +5781,19 @@ mod tests { .await .unwrap(); assert_eq!(String::from_utf8(bytes).unwrap(), "byo-access-token"); + assert_eq!( + refreshed.oauth_app_observation.as_ref().unwrap().source, + "byo" + ); + assert_eq!( + refreshed + .oauth_app_observation + .as_ref() + .unwrap() + .credential_epoch, + key.credential_epoch + ); + assert_eq!(refreshed.credential_source, key.credential_source); } #[tokio::test] @@ -5991,6 +6038,7 @@ mod tests { let now = Utc::now(); let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: Uuid::new_v4().to_string(), user_id: Uuid::new_v4().to_string(), @@ -7023,6 +7071,7 @@ mod tests { None }; let key = UserApiKey { + oauth_app_observation: None, credential_source: None, id: Uuid::new_v4().to_string(), user_id: Uuid::new_v4().to_string(), diff --git a/cli/src/wizard/assets/index.html b/cli/src/wizard/assets/index.html index e3d0defd0..ba80af52f 100644 --- a/cli/src/wizard/assets/index.html +++ b/cli/src/wizard/assets/index.html @@ -20,8 +20,8 @@ `);for(i=r=0;ri||c[r]!==l[i]){var u=` `+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ge=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?he(n):``}function ve(e,t){switch(e.tag){case 26:case 27:case 5:return he(e.type);case 16:return he(`Lazy`);case 13:return e.child!==t&&t!==null?he(`Suspense Fallback`):he(`Suspense`);case 19:return he(`SuspenseList`);case 0:case 15:return _e(e.type,!1);case 11:return _e(e.type.render,!1);case 1:return _e(e.type,!0);case 31:return he(`Activity`);default:return``}}function ye(e){try{var t=``,n=null;do t+=ve(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` -`+e.stack}}var be=Object.prototype.hasOwnProperty,xe=t.unstable_scheduleCallback,Se=t.unstable_cancelCallback,Ce=t.unstable_shouldYield,we=t.unstable_requestPaint,Te=t.unstable_now,Ee=t.unstable_getCurrentPriorityLevel,De=t.unstable_ImmediatePriority,Oe=t.unstable_UserBlockingPriority,ke=t.unstable_NormalPriority,Ae=t.unstable_LowPriority,je=t.unstable_IdlePriority,Me=t.log,Ne=t.unstable_setDisableYieldValue,Pe=null,Fe=null;function Ie(e){if(typeof Me==`function`&&Ne(e),Fe&&typeof Fe.setStrictMode==`function`)try{Fe.setStrictMode(Pe,e)}catch{}}var Le=Math.clz32?Math.clz32:Be,Re=Math.log,ze=Math.LN2;function Be(e){return e>>>=0,e===0?32:31-(Re(e)/ze|0)|0}var Ve=256,He=262144,Ue=4194304;function We(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ge(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=We(n))):i=We(o):i=We(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=We(n))):i=We(o)):i=We(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ke(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function z(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function B(){var e=Ue;return Ue<<=1,!(Ue&62914560)&&(Ue=4194304),e}function qe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Je(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ye(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),cn=!1;if(sn)try{var ln={};Object.defineProperty(ln,`passive`,{get:function(){cn=!0}}),window.addEventListener(`test`,ln,ln),window.removeEventListener(`test`,ln,ln)}catch{cn=!1}var un=null,dn=null,fn=null;function pn(){if(fn)return fn;var e,t=dn,n=t.length,r,i=`value`in un?un.value:un.textContent,a=i.length;for(e=0;e=Gn),Jn=` `,Yn=!1;function Xn(e,t){switch(e){case`keyup`:return Un.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Zn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Qn=!1;function $n(e,t){switch(e){case`compositionend`:return Zn(t);case`keypress`:return t.which===32?(Yn=!0,Jn):null;case`textInput`:return e=t.data,e===Jn&&Yn?null:e;default:return null}}function er(e,t){if(Qn)return e===`compositionend`||!Wn&&Xn(e,t)?(e=pn(),fn=dn=un=null,Qn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=xr(n)}}function Cr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Cr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function H(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Pt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Pt(e.document)}return t}function wr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Tr=sn&&`documentMode`in document&&11>=document.documentMode,Er=null,Dr=null,Or=null,kr=!1;function Ar(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;kr||Er==null||Er!==Pt(r)||(r=Er,`selectionStart`in r&&wr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Or&&br(Or,r)||(Or=r,r=J(Dr,`onSelect`),0>=o,i-=o,Si=1<<32-Le(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),ji&&wi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),ji&&wi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return ji&&wi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),ji&&wi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===D&&wa(l)===r.type){n(e,r.sibling),c=a(r,o.props),ja(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=li(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ci(o.type,o.key,o.props,null,e.mode,c),ja(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=fi(o,e.mode,c),c.return=e,e=c}return s(e);case D:return o=wa(o),b(e,r,o,c)}if(P(o))return h(e,r,o,c);if(j(o)){if(l=j(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Aa(o),c);if(o.$$typeof===C)return b(e,r,$i(e,o),c);Ma(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=ui(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{ka=0;var i=b(e,t,n,r);return Oa=null,i}catch(t){if(t===va||t===ba)throw t;var a=ii(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Pa=Na(!0),Fa=Na(!1),Ia=!1;function La(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ra(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function za(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ba(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Rl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ti(e),ei(e,null,n),t}return Zr(e,r,t,n),ti(e)}function Va(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ze(e,n)}}function Ha(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ua=!1;function Wa(){if(Ua){var e=la;if(e!==null)throw e}}function Ga(e,t,n,r){Ua=!1;var i=e.updateQueue;Ia=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(Vl&f)===f:(r&f)===f){f!==0&&f===ca&&(Ua=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:Ia=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Yl|=o,e.lanes=o,e.memoizedState=d}}function Ka(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function qa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=F.T,s={};F.T=s,js(e,!1,t,n);try{var c=i(),l=F.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?As(e,t,fa(c,r),_u(e)):As(e,t,r,_u(e))}catch(n){As(e,t,{then:function(){},status:`rejected`,reason:n},_u())}finally{I.p=a,o!==null&&s.types!==null&&(o.types=s.types),F.T=o}}function bs(){}function xs(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ss(e).queue;ys(e,a,t,te,n===null?bs:function(){return Cs(e),n(r)})}function Ss(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:te,baseState:te,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:No,lastRenderedState:te},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:No,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Cs(e){var t=Ss(e);t.next===null&&(t=e.alternate.memoizedState),As(e,t.next.queue,{},_u())}function ws(){return Qi(ep)}function Ts(){return Oo().memoizedState}function Es(){return Oo().memoizedState}function Ds(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=_u();e=za(n);var r=Ba(t,e,n);r!==null&&(yu(r,t,n),Va(r,t,n)),t={cache:aa()},e.payload=t;return}t=t.return}}function Os(e,t,n){var r=_u();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Ms(e)?Ns(t,n):(n=Qr(e,t,n,r),n!==null&&(yu(n,e,r),Ps(n,t,r)))}function ks(e,t,n){As(e,t,n,_u())}function As(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ms(e))Ns(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,yr(s,o))return Zr(e,t,i,0),zl===null&&Xr(),!1}catch{}if(n=Qr(e,t,i,r),n!==null)return yu(n,e,r),Ps(n,t,r),!0}return!1}function js(e,t,n,r){if(r={lane:2,revertLane:gd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Ms(e)){if(t)throw Error(i(479))}else t=Qr(e,n,r,2),t!==null&&yu(t,e,2)}function Ms(e){var t=e.alternate;return e===W||t!==null&&t===W}function Ns(e,t){po=fo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ps(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ze(e,n)}}var Fs={readContext:Qi,use:jo,useCallback:yo,useContext:yo,useEffect:yo,useImperativeHandle:yo,useLayoutEffect:yo,useInsertionEffect:yo,useMemo:yo,useReducer:yo,useRef:yo,useState:yo,useDebugValue:yo,useDeferredValue:yo,useTransition:yo,useSyncExternalStore:yo,useId:yo,useHostTransitionStatus:yo,useFormState:yo,useActionState:yo,useOptimistic:yo,useMemoCache:yo,useCacheRefresh:yo};Fs.useEffectEvent=yo;var Is={readContext:Qi,use:jo,useCallback:function(e,t){return Do().memoizedState=[e,t===void 0?null:t],e},useContext:Qi,useEffect:os,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),is(4194308,4,fs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return is(4194308,4,e,t)},useInsertionEffect:function(e,t){is(4,2,e,t)},useMemo:function(e,t){var n=Do();t=t===void 0?null:t;var r=e();if(mo){Ie(!0);try{e()}finally{Ie(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Do();if(n!==void 0){var i=n(t);if(mo){Ie(!0);try{n(t)}finally{Ie(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Os.bind(null,W,e),[r.memoizedState,e]},useRef:function(e){var t=Do();return e={current:e},t.memoizedState=e},useState:function(e){e=Uo(e);var t=e.queue,n=ks.bind(null,W,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ms,useDeferredValue:function(e,t){return _s(Do(),e,t)},useTransition:function(){var e=Uo(!1);return e=ys.bind(null,W,e.queue,!0,!1),Do().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=W,a=Do();if(ji){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),zl===null)throw Error(i(349));Vl&127||Ro(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,os(Bo.bind(null,r,o,e),[e]),r.flags|=2048,ns(9,{destroy:void 0},zo.bind(null,r,o,n,t),null),n},useId:function(){var e=Do(),t=zl.identifierPrefix;if(ji){var n=Ci,r=Si;n=(r&~(1<<32-Le(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=ho++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[it]=t,o[at]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Rd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ac(t)}}return Fc(t),jc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ac(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=se.current,Ri(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=ki,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[it]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Id(e.nodeValue,n)),e||Fi(t,!0)}else e=Ud(e).createTextNode(r),e[it]=t,t.stateNode=e}return Fc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ri(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[it]=t}else zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Fc(t),e=!1}else n=Bi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(ao(t),t):(ao(t),null);if(t.flags&128)throw Error(i(558))}return Fc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ri(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[it]=t}else zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Fc(t),a=!1}else a=Bi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(ao(t),t):(ao(t),null)}return ao(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Nc(t,t.updateQueue),Fc(t),null);case 4:return ue(),e===null&&Od(t.stateNode.containerInfo),Fc(t),null;case 10:return Ki(t.type),Fc(t),null;case 19:if(R(oo),r=t.memoizedState,r===null)return Fc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Pc(r,!1);else{if(Jl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=so(e),o!==null){for(t.flags|=128,Pc(r,!1),e=o.updateQueue,t.updateQueue=e,Nc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)si(n,e),n=n.sibling;return ie(oo,oo.current&1|2),ji&&wi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Te()>au&&(t.flags|=128,a=!0,Pc(r,!1),t.lanes=4194304)}else{if(!a)if(e=so(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Nc(t,e),Pc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!ji)return Fc(t),null}else 2*Te()-r.renderingStartTime>au&&n!==536870912&&(t.flags|=128,a=!0,Pc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Fc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Te(),e.sibling=null,n=oo.current,ie(oo,a?n&1|2:n&1),ji&&wi(t,r.treeForkCount),e);case 22:case 23:return ao(t),Qa(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Fc(t),t.subtreeFlags&6&&(t.flags|=8192)):Fc(t),n=t.updateQueue,n!==null&&Nc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&R(ma),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Ki(ia),Fc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Lc(e,t){switch(Di(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ki(ia),ue(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return fe(t),null;case 31:if(t.memoizedState!==null){if(ao(t),t.alternate===null)throw Error(i(340));zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(ao(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return R(oo),null;case 4:return ue(),null;case 10:return Ki(t.type),null;case 22:case 23:return ao(t),Qa(),e!==null&&R(ma),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ki(ia),null;case 25:return null;default:return null}}function Rc(e,t){switch(Di(t),t.tag){case 3:Ki(ia),ue();break;case 26:case 27:case 5:fe(t);break;case 4:ue();break;case 31:t.memoizedState!==null&&ao(t);break;case 13:ao(t);break;case 19:R(oo);break;case 10:Ki(t.type);break;case 22:case 23:ao(t),Qa(),e!==null&&R(ma);break;case 24:Ki(ia)}}function zc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Yu(t,t.return,e)}}function Bc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Yu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Yu(t,t.return,e)}}function Vc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{qa(t,n)}catch(t){Yu(e,e.return,t)}}}function Hc(e,t,n){n.props=Us(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Yu(e,t,n)}}function Uc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Yu(e,t,n)}}function Wc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Yu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Yu(e,t,n)}else n.current=null}function Gc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Yu(e,e.return,t)}}function Kc(e,t,n){try{var r=e.stateNode;zd(r,e.type,n,t),r[at]=t}catch(t){Yu(e,e.return,t)}}function qc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&ef(e.type)||e.tag===4}function Jc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||qc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&ef(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Yc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Zt));else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Yc(e,t,n),e=e.sibling;e!==null;)Yc(e,t,n),e=e.sibling}function Xc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Xc(e,t,n),e=e.sibling;e!==null;)Xc(e,t,n),e=e.sibling}function Zc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Rd(t,r,n),t[it]=e,t[at]=n}catch(t){Yu(e,e.return,t)}}var Qc=!1,$c=!1,el=!1,tl=typeof WeakSet==`function`?WeakSet:Set,nl=null;function rl(e,t){if(e=e.containerInfo,Vd=lp,e=H(e),wr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Hd={focusedElem:e,selectionRange:n},lp=!1,nl=t;nl!==null;)if(t=nl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,nl=e;else for(;nl!==null;){switch(t=nl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Rd(o,r,n),o[it]=e,_t(o),r=o;break a;case`link`:var s=Uf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Sr(s,h),v=Sr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,F.T=null,n=pu,pu=null;var o=lu,s=du;if(cu=0,uu=lu=null,du=0,Rl&6)throw Error(i(331));var c=Rl;if(Rl|=4,Nl(o.current),Tl(o,o.current,s,n),Rl=c,ld(0,!1),Fe&&typeof Fe.onPostCommitFiberRoot==`function`)try{Fe.onPostCommitFiberRoot(Pe,o)}catch{}return!0}finally{I.p=a,F.T=r,Gu(e,t)}}function Ju(e,t,n){t=mi(n,t),t=Ys(e.stateNode,t,2),e=Ba(e,t,2),e!==null&&(Je(e,2),cd(e))}function Yu(e,t,n){if(e.tag===3)Ju(e,e,n);else for(;t!==null;){if(t.tag===3){Ju(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(su===null||!su.has(r))){e=mi(n,e),n=Xs(2),r=Ba(t,n,2),r!==null&&(Zs(n,r,t,e),Je(r,2),cd(r));break}}t=t.return}}function Xu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Ll;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Kl=!0,i.add(n),e=Zu.bind(null,e,t,n),t.then(e,e))}function Zu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,zl===e&&(Vl&n)===n&&(Jl===4||Jl===3&&(Vl&62914560)===Vl&&300>Te()-ru?!(Rl&2)&&Eu(e,0):Zl|=n,$l===Vl&&($l=0)),cd(e)}function Qu(e,t){t===0&&(t=B()),e=$r(e,t),e!==null&&(Je(e,t),cd(e))}function $u(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Qu(e,n)}function ed(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Qu(e,n)}function td(e,t){return xe(e,t)}var nd=null,rd=null,id=!1,ad=!1,od=!1,sd=0;function cd(e){e!==rd&&e.next===null&&(rd===null?nd=rd=e:rd=rd.next=e),ad=!0,id||(id=!0,hd())}function ld(e,t){if(!od&&ad){od=!0;do for(var n=!1,r=nd;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Le(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,md(r,a))}else a=Vl,a=Ge(r,r===zl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Ke(r,a)||(n=!0,md(r,a));r=r.next}while(n);od=!1}}function ud(){dd()}function dd(){ad=id=!1;var e=0;sd!==0&&Jd()&&(e=sd);for(var t=Te(),n=null,r=nd;r!==null;){var i=r.next,a=fd(r,t);a===0?(r.next=null,n===null?nd=i:n.next=i,i===null&&(rd=n)):(n=r,(e!==0||a&3)&&(ad=!0)),r=i}cu!==0&&cu!==5||ld(e,!1),sd!==0&&(sd=0)}function fd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Bd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Cf(e,t,n){var r=Sf;if(r&&typeof t==`string`&&t){var i=It(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),_f.has(i)||(_f.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Rd(t,`link`,e),_t(t),r.head.appendChild(t)))}}function wf(e){yf.D(e),Cf(`dns-prefetch`,e,null)}function Tf(e,t){yf.C(e,t),Cf(`preconnect`,e,t)}function Ef(e,t,n){yf.L(e,t,n);var r=Sf;if(r&&e&&t){var i=`link[rel="preload"][as="`+It(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+It(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+It(n.imageSizes)+`"]`)):i+=`[href="`+It(e)+`"]`;var a=i;switch(t){case`style`:a=Mf(e);break;case`script`:a=If(e)}gf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),gf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Nf(a))||t===`script`&&r.querySelector(Lf(a))||(t=r.createElement(`link`),Rd(t,`link`,e),_t(t),r.head.appendChild(t)))}}function Df(e,t){yf.m(e,t);var n=Sf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+It(r)+`"][href="`+It(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=If(e)}if(!gf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),gf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Lf(a)))return}r=n.createElement(`link`),Rd(r,`link`,e),_t(r),n.head.appendChild(r)}}}function Of(e,t,n){yf.S(e,t,n);var r=Sf;if(r&&e){var i=gt(r).hoistableStyles,a=Mf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Nf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=gf.get(a))&&Bf(e,n);var c=o=r.createElement(`link`);_t(c),Rd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,zf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function kf(e,t){yf.X(e,t);var n=Sf;if(n&&e){var r=gt(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=p({src:e,async:!0},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),_t(a),Rd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Af(e,t){yf.M(e,t);var n=Sf;if(n&&e){var r=gt(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),_t(a),Rd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function jf(e,t,n,r){var a=(a=se.current)?vf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Mf(n.href),n=gt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Mf(n.href);var o=gt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Nf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),gf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},gf.set(e,n),o||Ff(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=If(n),n=gt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Mf(e){return`href="`+It(e)+`"`}function Nf(e){return`link[rel="stylesheet"][`+e+`]`}function Pf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Ff(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Rd(t,`link`,n),_t(t),e.head.appendChild(t))}function If(e){return`[src="`+It(e)+`"]`}function Lf(e){return`script[async]`+e}function Rf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+It(n.href)+`"]`);if(r)return t.instance=r,_t(r),r;var a=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),_t(r),Rd(r,`style`,a),zf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Mf(n.href);var o=e.querySelector(Nf(a));if(o)return t.state.loading|=4,t.instance=o,_t(o),o;r=Pf(n),(a=gf.get(a))&&Bf(r,a),o=(e.ownerDocument||e).createElement(`link`),_t(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Rd(o,`link`,r),t.state.loading|=4,zf(o,n.precedence,e),t.instance=o;case`script`:return o=If(n.src),(a=e.querySelector(Lf(o)))?(t.instance=a,_t(a),a):(r=n,(a=gf.get(o))&&(r=p({},n),Vf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),_t(a),Rd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,zf(r,n.precedence,e));return t.instance}function zf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Gf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Kf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function qf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Mf(r.href),a=t.querySelector(Nf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Xf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,_t(a);return}a=t.ownerDocument||t,r=Pf(r),(i=gf.get(i))&&Bf(r,i),a=a.createElement(`link`),_t(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Rd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Xf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Jf=0;function Yf(e,t){return e.stylesheets&&e.count===0&&Qf(e,e.stylesheets),0Jf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Xf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Qf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Zf=null;function Qf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Zf=new Map,t.forEach($f,e),Zf=null,Xf.call(e))}function $f(e,t){if(!(t.state.loading&4)){var n=Zf.get(e);if(n)var r=n.get(null);else{n=new Map,Zf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=_()})),y=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},b=new class extends y{#e;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()})}setFocused(e){this.#e!==e&&(this.#e=e,this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof this.#e==`boolean`?this.#e:globalThis.document?.visibilityState!==`hidden`}},x={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},S=new class{#e=x;setTimeoutProvider(e){this.#e=e}setTimeout(e,t){return this.#e.setTimeout(e,t)}clearTimeout(e){this.#e.clearTimeout(e)}setInterval(e,t){return this.#e.setInterval(e,t)}clearInterval(e){this.#e.clearInterval(e)}};function C(e){setTimeout(e,0)}var w=typeof window>`u`||`Deno`in globalThis;function T(){}function E(e,t){return typeof e==`function`?e(t):e}function ee(e){return typeof e==`number`&&e>=0&&e!==1/0}function D(e,t){return Math.max(e+(t||0)-Date.now(),0)}function O(e,t){return typeof e==`function`?e(t):e}function k(e,t){return typeof e==`function`?e(t):e}function A(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==M(o,t.options))return!1}else if(!P(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function j(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(N(t.options.mutationKey)!==N(a))return!1}else if(!P(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function M(e,t){return(t?.queryKeyHashFn||N)(e)}function N(e){return JSON.stringify(e,(e,t)=>re(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function P(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>P(e[n],t[n])):!1}var F=Object.prototype.hasOwnProperty;function I(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=ne(e)&&ne(t);if(!r&&!(re(e)&&re(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{S.setTimeout(t,e)})}function ie(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:I(e,t)}function ae(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function oe(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var se=Symbol();function ce(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===se?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function le(e,t){return typeof e==`function`?e(...t):!!e}function ue(e,t,n){let r=!1,i;return Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(i??=t(),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var de=(()=>{let e=()=>w;return{isServer(){return e()},setIsServer(t){e=t}}})();function fe(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var pe=C;function me(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=pe,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var he=me(),ge=new class extends y{#e=!0;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(this.setOnline.bind(this))}setOnline(e){this.#e!==e&&(this.#e=e,this.listeners.forEach(t=>{t(e)}))}isOnline(){return this.#e}};function _e(e){return Math.min(1e3*2**e,3e4)}function ve(e){return(e??`online`)===`online`?ge.isOnline():!0}var ye=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function be(e){let t=!1,n=0,r,i=fe(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new ye(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>b.isFocused()&&(e.networkMode===`always`||ge.isOnline())&&e.canRun(),u=()=>ve(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(de.isServer()?0:3),o=e.retryDelay??_e,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}var xe=class{#e;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ee(this.gcTime)&&(this.#e=S.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(de.isServer()?1/0:300*1e3))}clearGcTimeout(){this.#e&&=(S.clearTimeout(this.#e),void 0)}},Se=class extends xe{#e;#t;#n;#r;#i;#a;#o;constructor(e){super(),this.#o=!1,this.#a=e.defaultOptions,this.setOptions(e.options),this.observers=[],this.#r=e.client,this.#n=this.#r.getQueryCache(),this.queryKey=e.queryKey,this.queryHash=e.queryHash,this.#e=Te(this.options),this.state=e.state??this.#e,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#i?.promise}setOptions(e){if(this.options={...this.#a,...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=Te(this.options);e.data!==void 0&&(this.setState(we(e.data,e.dataUpdatedAt)),this.#e=e)}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&this.#n.remove(this)}setData(e,t){let n=ie(this.state.data,e,this.options);return this.#c({data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){this.#c({type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=this.#i?.promise;return this.#i?.cancel(e),t?t.then(T).catch(T):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return this.#e}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>k(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===se||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>O(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!D(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),this.#i?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),this.#i?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(this.#i&&(this.#o||this.#s()?this.#i.cancel({revert:!0}):this.#i.cancelRetry()),this.scheduleGc()),this.#n.notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}#s(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}invalidate(){this.state.isInvalidated||this.#c({type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&this.#i?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(this.#i)return this.#i.continueRetry(),this.#i.promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(this.#o=!0,n.signal)})},i=()=>{let e=ce(this.options,t),n=(()=>{let e={client:this.#r,queryKey:this.queryKey,meta:this.meta};return r(e),e})();return this.#o=!1,this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:this.#r,state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),this.#t=this.state,(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&this.#c({type:`fetch`,meta:a.fetchOptions?.meta}),this.#i=be({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof ye&&e.revert&&this.setState({...this.#t,fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{this.#c({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#c({type:`pause`})},onContinue:()=>{this.#c({type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0});try{let e=await this.#i.start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),this.#n.config.onSuccess?.(e,this),this.#n.config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof ye){if(e.silent)return this.#i.promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw this.#c({type:`error`,error:e}),this.#n.config.onError?.(e,this),this.#n.config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}#c(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...Ce(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...we(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return this.#t=e.manual?n:void 0,n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}})(this.state),he.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),this.#n.notify({query:this,type:`updated`,action:e})})}};function Ce(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:ve(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function we(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function Te(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}var Ee=class extends y{constructor(e,t){super(),this.options=t,this.#e=e,this.#s=null,this.#o=fe(),this.bindMethods(),this.setOptions(t)}#e;#t=void 0;#n=void 0;#r=void 0;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(this.#t.addObserver(this),Oe(this.#t,this.options)?this.#h():this.updateResult(),this.#y())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return ke(this.#t,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return ke(this.#t,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#b(),this.#x(),this.#t.removeObserver(this)}setOptions(e){let t=this.options,n=this.#t;if(this.options=this.#e.defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof k(this.options.enabled,this.#t)!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);this.#S(),this.#t.setOptions(this.options),t._defaulted&&!te(this.options,t)&&this.#e.getQueryCache().notify({type:`observerOptionsUpdated`,query:this.#t,observer:this});let r=this.hasListeners();r&&Ae(this.#t,n,this.options,t)&&this.#h(),this.updateResult(),r&&(this.#t!==n||k(this.options.enabled,this.#t)!==k(t.enabled,this.#t)||O(this.options.staleTime,this.#t)!==O(t.staleTime,this.#t))&&this.#g();let i=this.#_();r&&(this.#t!==n||k(this.options.enabled,this.#t)!==k(t.enabled,this.#t)||i!==this.#p)&&this.#v(i)}getOptimisticResult(e){let t=this.#e.getQueryCache().build(this.#e,e),n=this.createResult(t,e);return Me(this,n)&&(this.#r=n,this.#a=this.options,this.#i=this.#t.state),n}getCurrentResult(){return this.#r}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&this.#o.status===`pending`&&this.#o.reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){this.#m.add(e)}getCurrentQuery(){return this.#t}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=this.#e.defaultQueryOptions(e),n=this.#e.getQueryCache().build(this.#e,t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return this.#h({...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#r))}#h(e){this.#S();let t=this.#t.fetch(this.options,e);return e?.throwOnError||(t=t.catch(T)),t}#g(){this.#b();let e=O(this.options.staleTime,this.#t);if(de.isServer()||this.#r.isStale||!ee(e))return;let t=D(this.#r.dataUpdatedAt,e)+1;this.#d=S.setTimeout(()=>{this.#r.isStale||this.updateResult()},t)}#_(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(this.#t):this.options.refetchInterval)??!1}#v(e){this.#x(),this.#p=e,!(de.isServer()||k(this.options.enabled,this.#t)===!1||!ee(this.#p)||this.#p===0)&&(this.#f=S.setInterval(()=>{(this.options.refetchIntervalInBackground||b.isFocused())&&this.#h()},this.#p))}#y(){this.#g(),this.#v(this.#_())}#b(){this.#d&&=(S.clearTimeout(this.#d),void 0)}#x(){this.#f&&=(S.clearInterval(this.#f),void 0)}createResult(e,t){let n=this.#t,r=this.options,i=this.#r,a=this.#i,o=this.#a,s=e===n?this.#n:e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Oe(e,t),o=i&&Ae(e,n,t,r);(a||o)&&(l={...l,...Ce(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(this.#u?.state.data,this.#u):t.placeholderData,e!==void 0&&(m=`success`,d=ie(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===this.#c)d=this.#l;else try{this.#c=t.select,d=t.select(d),d=ie(i?.data,d,t),this.#l=d,this.#s=null}catch(e){this.#s=e}this.#s&&(f=this.#s,d=this.#l,p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:je(e,t),refetch:this.refetch,promise:this.#o,isEnabled:k(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(this.#o=x.promise=fe())},o=this.#o;switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=this.#r,t=this.createResult(this.#t,this.options);this.#i=this.#t.state,this.#a=this.options,this.#i.data!==void 0&&(this.#u=this.#t),!te(t,e)&&(this.#r=t,this.#C({listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!this.#m.size)return!0;let r=new Set(n??this.#m);return this.options.throwOnError&&r.add(`error`),Object.keys(this.#r).some(t=>{let n=t;return this.#r[n]!==e[n]&&r.has(n)})})()}))}#S(){let e=this.#e.getQueryCache().build(this.#e,this.options);if(e===this.#t)return;let t=this.#t;this.#t=e,this.#n=e.state,this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#y()}#C(e){he.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(this.#r)}),this.#e.getQueryCache().notify({query:this.#t,type:`observerResultsUpdated`})})}};function De(e,t){return k(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Oe(e,t){return De(e,t)||e.state.data!==void 0&&ke(e,t,t.refetchOnMount)}function ke(e,t,n){if(k(t.enabled,e)!==!1&&O(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&je(e,t)}return!1}function Ae(e,t,n,r){return(e!==t||k(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&je(e,n)}function je(e,t){return k(t.enabled,e)!==!1&&e.isStaleByTime(O(t.staleTime,e))}function Me(e,t){return!te(e.getCurrentResult(),t)}function Ne(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{ue(e,()=>t.signal,()=>n=!0)},u=ce(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?oe:ae;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Fe:Pe,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:Pe(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function Pe(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Fe(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}function Ie(e,t){return t?Pe(e,t)!=null:!1}function Le(e,t){return!t||!e.getPreviousPageParam?!1:Fe(e,t)!=null}var Re=class extends Ee{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:Ne()})}getOptimisticResult(e){return e.behavior=Ne(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:o,isRefetchError:s}=r,c=n.fetchMeta?.fetchMore?.direction,l=o&&c===`forward`,u=i&&c===`forward`,d=o&&c===`backward`,f=i&&c===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:Ie(t,n.data),hasPreviousPage:Le(t,n.data),isFetchNextPageError:l,isFetchingNextPage:u,isFetchPreviousPageError:d,isFetchingPreviousPage:f,isRefetchError:s&&!l&&!d,isRefetching:a&&!u&&!f}}},ze=class extends xe{#e;#t;#n;#r;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#n=e.mutationCache,this.#t=[],this.state=e.state||Be(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#n.notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){this.#t.length||(this.state.status===`pending`?this.scheduleGc():this.#n.remove(this))}continue(){return this.#r?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#i({type:`continue`})},n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#r=be({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{this.#i({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#i({type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#n.canRun(this)});let r=this.state.status===`pending`,i=!this.#r.canStart();try{if(r)t();else{this.#i({type:`pending`,variables:e,isPaused:i}),this.#n.config.onMutate&&await this.#n.config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&this.#i({type:`pending`,context:t,variables:e,isPaused:i})}let a=await this.#r.start();return await this.#n.config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await this.#n.config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),this.#i({type:`success`,data:a}),a}catch(t){try{await this.#n.config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await this.#n.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw this.#i({type:`error`,error:t}),t}finally{this.#n.runNext(this)}}#i(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}})(this.state),he.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#n.notify({mutation:this,type:`updated`,action:e})})}};function Be(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}var Ve=class extends y{constructor(e={}){super(),this.config=e,this.#e=new Set,this.#t=new Map,this.#n=0}#e;#t;#n;build(e,t,n){let r=new ze({client:e,mutationCache:this,mutationId:++this.#n,options:e.defaultMutationOptions(t),state:n});return this.add(r),r}add(e){this.#e.add(e);let t=He(e);if(typeof t==`string`){let n=this.#t.get(t);n?n.push(e):this.#t.set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(this.#e.delete(e)){let t=He(e);if(typeof t==`string`){let n=this.#t.get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&this.#t.delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=He(e);if(typeof t==`string`){let n=this.#t.get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=He(e);return typeof t==`string`?(this.#t.get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){he.batch(()=>{this.#e.forEach(e=>{this.notify({type:`removed`,mutation:e})}),this.#e.clear(),this.#t.clear()})}getAll(){return Array.from(this.#e)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>j(t,e))}findAll(e={}){return this.getAll().filter(t=>j(e,t))}notify(e){he.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return he.batch(()=>Promise.all(e.map(e=>e.continue().catch(T))))}};function He(e){return e.options.scope?.id}var Ue=class extends y{constructor(e={}){super(),this.config=e,this.#e=new Map}#e;build(e,t,n){let r=t.queryKey,i=t.queryHash??M(r,t),a=this.get(i);return a||(a=new Se({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){this.#e.has(e.queryHash)||(this.#e.set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=this.#e.get(e.queryHash);t&&(e.destroy(),t===e&&this.#e.delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){he.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#e.get(e)}getAll(){return[...this.#e.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>A(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>A(e,t)):t}notify(e){he.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){he.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){he.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},We=class{#e;#t;#n;#r;#i;#a;#o;#s;constructor(e={}){this.#e=e.queryCache||new Ue,this.#t=e.mutationCache||new Ve,this.#n=e.defaultOptions||{},this.#r=new Map,this.#i=new Map,this.#a=0}mount(){this.#a++,this.#a===1&&(this.#o=b.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onFocus())}),this.#s=ge.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onOnline())}))}unmount(){this.#a--,this.#a===0&&(this.#o?.(),this.#o=void 0,this.#s?.(),this.#s=void 0)}isFetching(e){return this.#e.findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return this.#t.findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=this.#e.build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(O(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return this.#e.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=this.#e.get(r.queryHash)?.state.data,a=E(t,i);if(a!==void 0)return this.#e.build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return he.batch(()=>this.#e.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state}removeQueries(e){let t=this.#e;he.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=this.#e;return he.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=he.batch(()=>this.#e.findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(T).catch(T)}invalidateQueries(e,t={}){return he.batch(()=>(this.#e.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=he.batch(()=>this.#e.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(T)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(T)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=this.#e.build(this,t);return n.isStaleByTime(O(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(T).catch(T)}fetchInfiniteQuery(e){return e.behavior=Ne(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(T).catch(T)}ensureInfiniteQueryData(e){return e.behavior=Ne(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return ge.isOnline()?this.#t.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#e}getMutationCache(){return this.#t}getDefaultOptions(){return this.#n}setDefaultOptions(e){this.#n=e}setQueryDefaults(e,t){this.#r.set(N(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#r.values()],n={};return t.forEach(t=>{P(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){this.#i.set(N(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#i.values()],n={};return t.forEach(t=>{P(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#n.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||=M(t.queryKey,t),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===se&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#n.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#e.clear(),this.#t.clear()}},Ge=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),Ke=s(((e,t)=>{t.exports=Ge()})),z=u(f(),1),B=Ke(),qe=z.createContext(void 0),Je=e=>{let t=z.useContext(qe);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},Ye=({client:e,children:t})=>(z.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,B.jsx)(qe.Provider,{value:e,children:t})),Xe=z.createContext(!1),Ze=()=>z.useContext(Xe);Xe.Provider;function Qe(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var $e=z.createContext(Qe()),et=()=>z.useContext($e),tt=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?le(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},nt=e=>{z.useEffect(()=>{e.clearReset()},[e])},rt=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||le(n,[e.error,r])),it=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},at=(e,t)=>e.isLoading&&e.isFetching&&!t,ot=(e,t)=>e?.suspense&&t.isPending,st=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function ct(e,t,n){let r=Ze(),i=et(),a=Je(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,it(o),tt(o,i,s),nt(i);let c=!a.getQueryCache().get(o.queryHash),[l]=z.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(z.useSyncExternalStore(z.useCallback(e=>{let t=d?l.subscribe(he.batchCalls(e)):T;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),z.useEffect(()=>{l.setOptions(o)},[o,l]),ot(o,u))throw st(o,l,i);if(rt({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!de.isServer()&&at(u,r)&&(c?st(o,l,i):s?.promise)?.catch(T).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function lt(e,t){return ct(e,Ee,t)}function ut(e,t){return ct(e,Re,t)}var dt=v();function ft({context:e,localOrigin:t}){return e===`local`?(0,B.jsxs)(`footer`,{className:`mt-6 flex flex-wrap gap-2 border-t border-border pt-4 text-12 text-muted-foreground`,children:[(0,B.jsxs)(`span`,{children:[`Served locally from`,` `,(0,B.jsx)(`code`,{className:`rounded bg-muted/60 px-1.5 py-0.5 font-mono text-xs`,children:t??window.location.host})]}),(0,B.jsx)(`span`,{children:`· Nothing leaves your machine`})]}):(0,B.jsxs)(`footer`,{className:`mt-6 flex flex-wrap gap-2 border-t border-border pt-4 text-12 text-muted-foreground`,children:[(0,B.jsx)(`span`,{children:`Pairing with a remote CLI`}),(0,B.jsx)(`span`,{children:`· Secrets never travel back through the pairing channel`})]})}var pt={"ai-key":3,"api-key-create":3,"api-key-rotate":2,"node-register-token":2,"node-rotate-token":2,"service-account-create":3,"service-account-rotate-secret":2,"developer-app-create":3,"developer-app-rotate-secret":2,"mfa-setup":2};function mt(e,t,n){let r=pt[t];if(e===`resumed-rotation-choice`)return{current:1,total:r,label:`Recovery · confirm outcome`};if(e===`resumed-create-warning`)return{current:1,total:r,label:`Recovery · pairing already started`};if(e===`resending-ack`)return{current:r,total:r,label:`Recovery · notifying CLI`};if(e===`done`)return{current:r,total:r,label:`done`};switch(t){case`ai-key`:return e===`claimed`?n?.slugPicked?{current:2,total:r,label:`enter credential`}:{current:1,total:r,label:`pick a service`}:e===`notifying-cli`||e===`acking`?{current:3,total:r,label:`notifying CLI`}:{current:3,total:r,label:`done`};case`api-key-create`:return e===`claimed`?{current:2,total:r,label:`configure scope`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the value`}:{current:3,total:r,label:`done`};case`api-key-rotate`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`node-register-token`:return e===`claimed`?{current:1,total:r,label:`name this node`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`node-rotate-token`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`service-account-create`:return e===`claimed`?{current:2,total:r,label:`configure account`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the secret`}:{current:3,total:r,label:`done`};case`service-account-rotate-secret`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the secret`}:{current:2,total:r,label:`done`};case`developer-app-create`:return e===`claimed`?{current:2,total:r,label:`configure app`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the secret`}:{current:3,total:r,label:`done`};case`developer-app-rotate-secret`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the secret`}:{current:2,total:r,label:`done`};case`mfa-setup`:return e===`claimed`?{current:1,total:r,label:`scan and verify`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save recovery codes`}:{current:2,total:r,label:`done`}}}function ht(e){return e.label.startsWith(`Recovery`)?e.label:`Step ${e.current} of ${e.total} · ${e.label}`}var gt=[{key:`background`,label:`Page background`,vars:[`background`]},{key:`sidebar`,label:`Sidebar`,vars:[`sidebar`]},{key:`card`,label:`Cards and menus`,vars:[`card`,`popover`,`surface`]},{key:`muted`,label:`Subtle fills`,vars:[`muted`]},{key:`foreground`,label:`Primary text`,vars:[`foreground`,`card-foreground`,`popover-foreground`,`accent-foreground`]},{key:`muted-foreground`,label:`Secondary text`,vars:[`muted-foreground`,`secondary-foreground`]},{key:`text-tertiary`,label:`Tertiary text`,vars:[`text-tertiary`]},{key:`border`,label:`Borders and dividers`,vars:[`border`]},{key:`input`,label:`Input borders`,vars:[`input`]},{key:`input-focus`,label:`Focused input border`,vars:[`input-focus`]},{key:`primary`,label:`Accent`,vars:[`primary`,`ring`]}],_t={light:{},dark:{}},vt=/^#[0-9a-fA-F]{6}$/,yt=new Set(gt.map(e=>e.key));function bt(e){return typeof e==`string`&&vt.test(e)}function xt(e){let t={light:{},dark:{}};if(typeof e!=`object`||!e)return t;for(let n of[`light`,`dark`]){let r=e[n];if(!(typeof r!=`object`||!r))for(let[e,i]of Object.entries(r))yt.has(e)&&bt(i)&&(t[n][e]=i.toUpperCase())}return t}function St(e){let t=xt(e);return[`dark`,`light`].map(e=>{let n=gt.flatMap(({key:n,vars:r})=>{let i=t[e][n];return i?r.map(e=>` --color-${e}: ${i};`):[]});return n.length?`html.theme-${e}.theme-${e} {\n${n.join(` +`+e.stack}}var be=Object.prototype.hasOwnProperty,xe=t.unstable_scheduleCallback,Se=t.unstable_cancelCallback,Ce=t.unstable_shouldYield,we=t.unstable_requestPaint,Te=t.unstable_now,Ee=t.unstable_getCurrentPriorityLevel,De=t.unstable_ImmediatePriority,Oe=t.unstable_UserBlockingPriority,ke=t.unstable_NormalPriority,Ae=t.unstable_LowPriority,je=t.unstable_IdlePriority,Me=t.log,Ne=t.unstable_setDisableYieldValue,Pe=null,Fe=null;function Ie(e){if(typeof Me==`function`&&Ne(e),Fe&&typeof Fe.setStrictMode==`function`)try{Fe.setStrictMode(Pe,e)}catch{}}var Le=Math.clz32?Math.clz32:Be,Re=Math.log,ze=Math.LN2;function Be(e){return e>>>=0,e===0?32:31-(Re(e)/ze|0)|0}var Ve=256,He=262144,Ue=4194304;function We(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ge(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=We(n))):i=We(o):i=We(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=We(n))):i=We(o)):i=We(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ke(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function z(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function B(){var e=Ue;return Ue<<=1,!(Ue&62914560)&&(Ue=4194304),e}function qe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Je(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ye(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),cn=!1;if(sn)try{var ln={};Object.defineProperty(ln,`passive`,{get:function(){cn=!0}}),window.addEventListener(`test`,ln,ln),window.removeEventListener(`test`,ln,ln)}catch{cn=!1}var un=null,dn=null,fn=null;function pn(){if(fn)return fn;var e,t=dn,n=t.length,r,i=`value`in un?un.value:un.textContent,a=i.length;for(e=0;e=Gn),Jn=` `,Yn=!1;function Xn(e,t){switch(e){case`keyup`:return Un.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Zn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Qn=!1;function $n(e,t){switch(e){case`compositionend`:return Zn(t);case`keypress`:return t.which===32?(Yn=!0,Jn):null;case`textInput`:return e=t.data,e===Jn&&Yn?null:e;default:return null}}function er(e,t){if(Qn)return e===`compositionend`||!Wn&&Xn(e,t)?(e=pn(),fn=dn=un=null,Qn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=xr(n)}}function Cr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Cr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function H(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Pt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Pt(e.document)}return t}function wr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Tr=sn&&`documentMode`in document&&11>=document.documentMode,Er=null,Dr=null,Or=null,kr=!1;function Ar(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;kr||Er==null||Er!==Pt(r)||(r=Er,`selectionStart`in r&&wr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Or&&br(Or,r)||(Or=r,r=jd(Dr,`onSelect`),0>=o,i-=o,Si=1<<32-Le(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),ji&&wi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),ji&&wi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return ji&&wi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),ji&&wi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===D&&wa(l)===r.type){n(e,r.sibling),c=a(r,o.props),ja(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=li(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ci(o.type,o.key,o.props,null,e.mode,c),ja(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=fi(o,e.mode,c),c.return=e,e=c}return s(e);case D:return o=wa(o),b(e,r,o,c)}if(P(o))return h(e,r,o,c);if(j(o)){if(l=j(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Aa(o),c);if(o.$$typeof===C)return b(e,r,$i(e,o),c);Ma(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=ui(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{ka=0;var i=b(e,t,n,r);return Oa=null,i}catch(t){if(t===va||t===ba)throw t;var a=ii(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Pa=Na(!0),Fa=Na(!1),Ia=!1;function La(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ra(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function za(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ba(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Rl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ti(e),ei(e,null,n),t}return Zr(e,r,t,n),ti(e)}function Va(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ze(e,n)}}function Ha(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ua=!1;function Wa(){if(Ua){var e=la;if(e!==null)throw e}}function Ga(e,t,n,r){Ua=!1;var i=e.updateQueue;Ia=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(Vl&f)===f:(r&f)===f){f!==0&&f===ca&&(Ua=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:Ia=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Yl|=o,e.lanes=o,e.memoizedState=d}}function Ka(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function qa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=F.T,s={};F.T=s,js(e,!1,t,n);try{var c=i(),l=F.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?As(e,t,fa(c,r),_u(e)):As(e,t,r,_u(e))}catch(n){As(e,t,{then:function(){},status:`rejected`,reason:n},_u())}finally{I.p=a,o!==null&&s.types!==null&&(o.types=s.types),F.T=o}}function bs(){}function xs(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ss(e).queue;ys(e,a,t,te,n===null?bs:function(){return Cs(e),n(r)})}function Ss(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:te,baseState:te,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:No,lastRenderedState:te},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:No,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Cs(e){var t=Ss(e);t.next===null&&(t=e.alternate.memoizedState),As(e,t.next.queue,{},_u())}function ws(){return Qi(ep)}function Ts(){return Oo().memoizedState}function Es(){return Oo().memoizedState}function Ds(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=_u();e=za(n);var r=Ba(t,e,n);r!==null&&(yu(r,t,n),Va(r,t,n)),t={cache:aa()},e.payload=t;return}t=t.return}}function Os(e,t,n){var r=_u();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Ms(e)?Ns(t,n):(n=Qr(e,t,n,r),n!==null&&(yu(n,e,r),Ps(n,t,r)))}function ks(e,t,n){As(e,t,n,_u())}function As(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ms(e))Ns(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,yr(s,o))return Zr(e,t,i,0),zl===null&&Xr(),!1}catch{}if(n=Qr(e,t,i,r),n!==null)return yu(n,e,r),Ps(n,t,r),!0}return!1}function js(e,t,n,r){if(r={lane:2,revertLane:gd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Ms(e)){if(t)throw Error(i(479))}else t=Qr(e,n,r,2),t!==null&&yu(t,e,2)}function Ms(e){var t=e.alternate;return e===W||t!==null&&t===W}function Ns(e,t){po=fo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ps(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ze(e,n)}}var Fs={readContext:Qi,use:jo,useCallback:yo,useContext:yo,useEffect:yo,useImperativeHandle:yo,useLayoutEffect:yo,useInsertionEffect:yo,useMemo:yo,useReducer:yo,useRef:yo,useState:yo,useDebugValue:yo,useDeferredValue:yo,useTransition:yo,useSyncExternalStore:yo,useId:yo,useHostTransitionStatus:yo,useFormState:yo,useActionState:yo,useOptimistic:yo,useMemoCache:yo,useCacheRefresh:yo};Fs.useEffectEvent=yo;var Is={readContext:Qi,use:jo,useCallback:function(e,t){return Do().memoizedState=[e,t===void 0?null:t],e},useContext:Qi,useEffect:os,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),is(4194308,4,fs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return is(4194308,4,e,t)},useInsertionEffect:function(e,t){is(4,2,e,t)},useMemo:function(e,t){var n=Do();t=t===void 0?null:t;var r=e();if(mo){Ie(!0);try{e()}finally{Ie(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Do();if(n!==void 0){var i=n(t);if(mo){Ie(!0);try{n(t)}finally{Ie(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Os.bind(null,W,e),[r.memoizedState,e]},useRef:function(e){var t=Do();return e={current:e},t.memoizedState=e},useState:function(e){e=Uo(e);var t=e.queue,n=ks.bind(null,W,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ms,useDeferredValue:function(e,t){return _s(Do(),e,t)},useTransition:function(){var e=Uo(!1);return e=ys.bind(null,W,e.queue,!0,!1),Do().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=W,a=Do();if(ji){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),zl===null)throw Error(i(349));Vl&127||Ro(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,os(Bo.bind(null,r,o,e),[e]),r.flags|=2048,ns(9,{destroy:void 0},zo.bind(null,r,o,n,t),null),n},useId:function(){var e=Do(),t=zl.identifierPrefix;if(ji){var n=Ci,r=Si;n=(r&~(1<<32-Le(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=ho++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[it]=t,o[at]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Rd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ac(t)}}return Fc(t),jc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ac(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=se.current,Ri(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=ki,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[it]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Id(e.nodeValue,n)),e||Fi(t,!0)}else e=Ud(e).createTextNode(r),e[it]=t,t.stateNode=e}return Fc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ri(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[it]=t}else zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Fc(t),e=!1}else n=Bi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(ao(t),t):(ao(t),null);if(t.flags&128)throw Error(i(558))}return Fc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ri(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[it]=t}else zi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Fc(t),a=!1}else a=Bi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(ao(t),t):(ao(t),null)}return ao(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Nc(t,t.updateQueue),Fc(t),null);case 4:return ue(),e===null&&Od(t.stateNode.containerInfo),Fc(t),null;case 10:return Ki(t.type),Fc(t),null;case 19:if(R(oo),r=t.memoizedState,r===null)return Fc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Pc(r,!1);else{if(Jl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=so(e),o!==null){for(t.flags|=128,Pc(r,!1),e=o.updateQueue,t.updateQueue=e,Nc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)si(n,e),n=n.sibling;return ie(oo,oo.current&1|2),ji&&wi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Te()>au&&(t.flags|=128,a=!0,Pc(r,!1),t.lanes=4194304)}else{if(!a)if(e=so(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Nc(t,e),Pc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!ji)return Fc(t),null}else 2*Te()-r.renderingStartTime>au&&n!==536870912&&(t.flags|=128,a=!0,Pc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Fc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Te(),e.sibling=null,n=oo.current,ie(oo,a?n&1|2:n&1),ji&&wi(t,r.treeForkCount),e);case 22:case 23:return ao(t),Qa(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Fc(t),t.subtreeFlags&6&&(t.flags|=8192)):Fc(t),n=t.updateQueue,n!==null&&Nc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&R(ma),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Ki(ia),Fc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Lc(e,t){switch(Di(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ki(ia),ue(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return fe(t),null;case 31:if(t.memoizedState!==null){if(ao(t),t.alternate===null)throw Error(i(340));zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(ao(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));zi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return R(oo),null;case 4:return ue(),null;case 10:return Ki(t.type),null;case 22:case 23:return ao(t),Qa(),e!==null&&R(ma),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ki(ia),null;case 25:return null;default:return null}}function Rc(e,t){switch(Di(t),t.tag){case 3:Ki(ia),ue();break;case 26:case 27:case 5:fe(t);break;case 4:ue();break;case 31:t.memoizedState!==null&&ao(t);break;case 13:ao(t);break;case 19:R(oo);break;case 10:Ki(t.type);break;case 22:case 23:ao(t),Qa(),e!==null&&R(ma);break;case 24:Ki(ia)}}function zc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Yu(t,t.return,e)}}function Bc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Yu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Yu(t,t.return,e)}}function Vc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{qa(t,n)}catch(t){Yu(e,e.return,t)}}}function Hc(e,t,n){n.props=Us(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Yu(e,t,n)}}function Uc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Yu(e,t,n)}}function Wc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Yu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Yu(e,t,n)}else n.current=null}function Gc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Yu(e,e.return,t)}}function Kc(e,t,n){try{var r=e.stateNode;zd(r,e.type,n,t),r[at]=t}catch(t){Yu(e,e.return,t)}}function qc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&ef(e.type)||e.tag===4}function Jc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||qc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&ef(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Yc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Zt));else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Yc(e,t,n),e=e.sibling;e!==null;)Yc(e,t,n),e=e.sibling}function Xc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Xc(e,t,n),e=e.sibling;e!==null;)Xc(e,t,n),e=e.sibling}function Zc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Rd(t,r,n),t[it]=e,t[at]=n}catch(t){Yu(e,e.return,t)}}var Qc=!1,$c=!1,el=!1,tl=typeof WeakSet==`function`?WeakSet:Set,nl=null;function rl(e,t){if(e=e.containerInfo,X=lp,e=H(e),wr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Hd={focusedElem:e,selectionRange:n},lp=!1,nl=t;nl!==null;)if(t=nl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,nl=e;else for(;nl!==null;){switch(t=nl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Rd(o,r,n),o[it]=e,_t(o),r=o;break a;case`link`:var s=Uf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Sr(s,h),v=Sr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,F.T=null,n=pu,pu=null;var o=lu,s=du;if(cu=0,uu=lu=null,du=0,Rl&6)throw Error(i(331));var c=Rl;if(Rl|=4,Nl(o.current),Tl(o,o.current,s,n),Rl=c,ld(0,!1),Fe&&typeof Fe.onPostCommitFiberRoot==`function`)try{Fe.onPostCommitFiberRoot(Pe,o)}catch{}return!0}finally{I.p=a,F.T=r,Gu(e,t)}}function Ju(e,t,n){t=mi(n,t),t=Ys(e.stateNode,t,2),e=Ba(e,t,2),e!==null&&(Je(e,2),cd(e))}function Yu(e,t,n){if(e.tag===3)Ju(e,e,n);else for(;t!==null;){if(t.tag===3){Ju(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(su===null||!su.has(r))){e=mi(n,e),n=Xs(2),r=Ba(t,n,2),r!==null&&(Zs(n,r,t,e),Je(r,2),cd(r));break}}t=t.return}}function Xu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Ll;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Kl=!0,i.add(n),e=Zu.bind(null,e,t,n),t.then(e,e))}function Zu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,zl===e&&(Vl&n)===n&&(Jl===4||Jl===3&&(Vl&62914560)===Vl&&300>Te()-ru?!(Rl&2)&&Eu(e,0):Zl|=n,$l===Vl&&($l=0)),cd(e)}function Qu(e,t){t===0&&(t=B()),e=$r(e,t),e!==null&&(Je(e,t),cd(e))}function $u(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Qu(e,n)}function ed(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Qu(e,n)}function td(e,t){return xe(e,t)}var nd=null,rd=null,id=!1,ad=!1,od=!1,sd=0;function cd(e){e!==rd&&e.next===null&&(rd===null?nd=rd=e:rd=rd.next=e),ad=!0,id||(id=!0,hd())}function ld(e,t){if(!od&&ad){od=!0;do for(var n=!1,r=nd;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Le(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,md(r,a))}else a=Vl,a=Ge(r,r===zl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Ke(r,a)||(n=!0,md(r,a));r=r.next}while(n);od=!1}}function ud(){dd()}function dd(){ad=id=!1;var e=0;sd!==0&&Jd()&&(e=sd);for(var t=Te(),n=null,r=nd;r!==null;){var i=r.next,a=fd(r,t);a===0?(r.next=null,n===null?nd=i:n.next=i,i===null&&(rd=n)):(n=r,(e!==0||a&3)&&(ad=!0)),r=i}cu!==0&&cu!==5||ld(e,!1),sd!==0&&(sd=0)}function fd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Bd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Cf(e,t,n){var r=Sf;if(r&&typeof t==`string`&&t){var i=It(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),_f.has(i)||(_f.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Rd(t,`link`,e),_t(t),r.head.appendChild(t)))}}function wf(e){yf.D(e),Cf(`dns-prefetch`,e,null)}function Tf(e,t){yf.C(e,t),Cf(`preconnect`,e,t)}function Ef(e,t,n){yf.L(e,t,n);var r=Sf;if(r&&e&&t){var i=`link[rel="preload"][as="`+It(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+It(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+It(n.imageSizes)+`"]`)):i+=`[href="`+It(e)+`"]`;var a=i;switch(t){case`style`:a=Mf(e);break;case`script`:a=If(e)}gf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),gf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Nf(a))||t===`script`&&r.querySelector(Lf(a))||(t=r.createElement(`link`),Rd(t,`link`,e),_t(t),r.head.appendChild(t)))}}function Df(e,t){yf.m(e,t);var n=Sf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+It(r)+`"][href="`+It(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=If(e)}if(!gf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),gf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Lf(a)))return}r=n.createElement(`link`),Rd(r,`link`,e),_t(r),n.head.appendChild(r)}}}function Of(e,t,n){yf.S(e,t,n);var r=Sf;if(r&&e){var i=gt(r).hoistableStyles,a=Mf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Nf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=gf.get(a))&&Bf(e,n);var c=o=r.createElement(`link`);_t(c),Rd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,zf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function kf(e,t){yf.X(e,t);var n=Sf;if(n&&e){var r=gt(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=p({src:e,async:!0},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),_t(a),Rd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Af(e,t){yf.M(e,t);var n=Sf;if(n&&e){var r=gt(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),_t(a),Rd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function jf(e,t,n,r){var a=(a=se.current)?vf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Mf(n.href),n=gt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Mf(n.href);var o=gt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Nf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),gf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},gf.set(e,n),o||Ff(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=If(n),n=gt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Mf(e){return`href="`+It(e)+`"`}function Nf(e){return`link[rel="stylesheet"][`+e+`]`}function Pf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Ff(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Rd(t,`link`,n),_t(t),e.head.appendChild(t))}function If(e){return`[src="`+It(e)+`"]`}function Lf(e){return`script[async]`+e}function Rf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+It(n.href)+`"]`);if(r)return t.instance=r,_t(r),r;var a=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),_t(r),Rd(r,`style`,a),zf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Mf(n.href);var o=e.querySelector(Nf(a));if(o)return t.state.loading|=4,t.instance=o,_t(o),o;r=Pf(n),(a=gf.get(a))&&Bf(r,a),o=(e.ownerDocument||e).createElement(`link`),_t(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Rd(o,`link`,r),t.state.loading|=4,zf(o,n.precedence,e),t.instance=o;case`script`:return o=If(n.src),(a=e.querySelector(Lf(o)))?(t.instance=a,_t(a),a):(r=n,(a=gf.get(o))&&(r=p({},n),Vf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),_t(a),Rd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,zf(r,n.precedence,e));return t.instance}function zf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Gf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Kf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function qf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Mf(r.href),a=t.querySelector(Nf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Xf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,_t(a);return}a=t.ownerDocument||t,r=Pf(r),(i=gf.get(i))&&Bf(r,i),a=a.createElement(`link`),_t(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Rd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Xf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Jf=0;function Yf(e,t){return e.stylesheets&&e.count===0&&Qf(e,e.stylesheets),0Jf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Xf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Qf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Zf=null;function Qf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Zf=new Map,t.forEach($f,e),Zf=null,Xf.call(e))}function $f(e,t){if(!(t.state.loading&4)){var n=Zf.get(e);if(n)var r=n.get(null);else{n=new Map,Zf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=_()})),y=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},b=new class extends y{#e;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()})}setFocused(e){this.#e!==e&&(this.#e=e,this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof this.#e==`boolean`?this.#e:globalThis.document?.visibilityState!==`hidden`}},x={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},S=new class{#e=x;setTimeoutProvider(e){this.#e=e}setTimeout(e,t){return this.#e.setTimeout(e,t)}clearTimeout(e){this.#e.clearTimeout(e)}setInterval(e,t){return this.#e.setInterval(e,t)}clearInterval(e){this.#e.clearInterval(e)}};function C(e){setTimeout(e,0)}var w=typeof window>`u`||`Deno`in globalThis;function T(){}function E(e,t){return typeof e==`function`?e(t):e}function ee(e){return typeof e==`number`&&e>=0&&e!==1/0}function D(e,t){return Math.max(e+(t||0)-Date.now(),0)}function O(e,t){return typeof e==`function`?e(t):e}function k(e,t){return typeof e==`function`?e(t):e}function A(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==M(o,t.options))return!1}else if(!P(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function j(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(N(t.options.mutationKey)!==N(a))return!1}else if(!P(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function M(e,t){return(t?.queryKeyHashFn||N)(e)}function N(e){return JSON.stringify(e,(e,t)=>re(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function P(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>P(e[n],t[n])):!1}var F=Object.prototype.hasOwnProperty;function I(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=ne(e)&&ne(t);if(!r&&!(re(e)&&re(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{S.setTimeout(t,e)})}function ie(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:I(e,t)}function ae(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function oe(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var se=Symbol();function ce(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===se?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function le(e,t){return typeof e==`function`?e(...t):!!e}function ue(e,t,n){let r=!1,i;return Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(i??=t(),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var de=(()=>{let e=()=>w;return{isServer(){return e()},setIsServer(t){e=t}}})();function fe(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var pe=C;function me(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=pe,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var he=me(),ge=new class extends y{#e=!0;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(this.setOnline.bind(this))}setOnline(e){this.#e!==e&&(this.#e=e,this.listeners.forEach(t=>{t(e)}))}isOnline(){return this.#e}};function _e(e){return Math.min(1e3*2**e,3e4)}function ve(e){return(e??`online`)===`online`?ge.isOnline():!0}var ye=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function be(e){let t=!1,n=0,r,i=fe(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new ye(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>b.isFocused()&&(e.networkMode===`always`||ge.isOnline())&&e.canRun(),u=()=>ve(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(de.isServer()?0:3),o=e.retryDelay??_e,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}var xe=class{#e;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ee(this.gcTime)&&(this.#e=S.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(de.isServer()?1/0:300*1e3))}clearGcTimeout(){this.#e&&=(S.clearTimeout(this.#e),void 0)}},Se=class extends xe{#e;#t;#n;#r;#i;#a;#o;constructor(e){super(),this.#o=!1,this.#a=e.defaultOptions,this.setOptions(e.options),this.observers=[],this.#r=e.client,this.#n=this.#r.getQueryCache(),this.queryKey=e.queryKey,this.queryHash=e.queryHash,this.#e=Te(this.options),this.state=e.state??this.#e,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#i?.promise}setOptions(e){if(this.options={...this.#a,...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=Te(this.options);e.data!==void 0&&(this.setState(we(e.data,e.dataUpdatedAt)),this.#e=e)}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&this.#n.remove(this)}setData(e,t){let n=ie(this.state.data,e,this.options);return this.#c({data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){this.#c({type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=this.#i?.promise;return this.#i?.cancel(e),t?t.then(T).catch(T):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return this.#e}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>k(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===se||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>O(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!D(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),this.#i?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),this.#i?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(this.#i&&(this.#o||this.#s()?this.#i.cancel({revert:!0}):this.#i.cancelRetry()),this.scheduleGc()),this.#n.notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}#s(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}invalidate(){this.state.isInvalidated||this.#c({type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&this.#i?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(this.#i)return this.#i.continueRetry(),this.#i.promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(this.#o=!0,n.signal)})},i=()=>{let e=ce(this.options,t),n=(()=>{let e={client:this.#r,queryKey:this.queryKey,meta:this.meta};return r(e),e})();return this.#o=!1,this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:this.#r,state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),this.#t=this.state,(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&this.#c({type:`fetch`,meta:a.fetchOptions?.meta}),this.#i=be({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof ye&&e.revert&&this.setState({...this.#t,fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{this.#c({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#c({type:`pause`})},onContinue:()=>{this.#c({type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0});try{let e=await this.#i.start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),this.#n.config.onSuccess?.(e,this),this.#n.config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof ye){if(e.silent)return this.#i.promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw this.#c({type:`error`,error:e}),this.#n.config.onError?.(e,this),this.#n.config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}#c(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...Ce(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...we(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return this.#t=e.manual?n:void 0,n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}})(this.state),he.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),this.#n.notify({query:this,type:`updated`,action:e})})}};function Ce(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:ve(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function we(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function Te(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}var Ee=class extends y{constructor(e,t){super(),this.options=t,this.#e=e,this.#s=null,this.#o=fe(),this.bindMethods(),this.setOptions(t)}#e;#t=void 0;#n=void 0;#r=void 0;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(this.#t.addObserver(this),Oe(this.#t,this.options)?this.#h():this.updateResult(),this.#y())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return ke(this.#t,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return ke(this.#t,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#b(),this.#x(),this.#t.removeObserver(this)}setOptions(e){let t=this.options,n=this.#t;if(this.options=this.#e.defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof k(this.options.enabled,this.#t)!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);this.#S(),this.#t.setOptions(this.options),t._defaulted&&!te(this.options,t)&&this.#e.getQueryCache().notify({type:`observerOptionsUpdated`,query:this.#t,observer:this});let r=this.hasListeners();r&&Ae(this.#t,n,this.options,t)&&this.#h(),this.updateResult(),r&&(this.#t!==n||k(this.options.enabled,this.#t)!==k(t.enabled,this.#t)||O(this.options.staleTime,this.#t)!==O(t.staleTime,this.#t))&&this.#g();let i=this.#_();r&&(this.#t!==n||k(this.options.enabled,this.#t)!==k(t.enabled,this.#t)||i!==this.#p)&&this.#v(i)}getOptimisticResult(e){let t=this.#e.getQueryCache().build(this.#e,e),n=this.createResult(t,e);return Me(this,n)&&(this.#r=n,this.#a=this.options,this.#i=this.#t.state),n}getCurrentResult(){return this.#r}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&this.#o.status===`pending`&&this.#o.reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){this.#m.add(e)}getCurrentQuery(){return this.#t}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=this.#e.defaultQueryOptions(e),n=this.#e.getQueryCache().build(this.#e,t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return this.#h({...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#r))}#h(e){this.#S();let t=this.#t.fetch(this.options,e);return e?.throwOnError||(t=t.catch(T)),t}#g(){this.#b();let e=O(this.options.staleTime,this.#t);if(de.isServer()||this.#r.isStale||!ee(e))return;let t=D(this.#r.dataUpdatedAt,e)+1;this.#d=S.setTimeout(()=>{this.#r.isStale||this.updateResult()},t)}#_(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(this.#t):this.options.refetchInterval)??!1}#v(e){this.#x(),this.#p=e,!(de.isServer()||k(this.options.enabled,this.#t)===!1||!ee(this.#p)||this.#p===0)&&(this.#f=S.setInterval(()=>{(this.options.refetchIntervalInBackground||b.isFocused())&&this.#h()},this.#p))}#y(){this.#g(),this.#v(this.#_())}#b(){this.#d&&=(S.clearTimeout(this.#d),void 0)}#x(){this.#f&&=(S.clearInterval(this.#f),void 0)}createResult(e,t){let n=this.#t,r=this.options,i=this.#r,a=this.#i,o=this.#a,s=e===n?this.#n:e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Oe(e,t),o=i&&Ae(e,n,t,r);(a||o)&&(l={...l,...Ce(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(this.#u?.state.data,this.#u):t.placeholderData,e!==void 0&&(m=`success`,d=ie(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===this.#c)d=this.#l;else try{this.#c=t.select,d=t.select(d),d=ie(i?.data,d,t),this.#l=d,this.#s=null}catch(e){this.#s=e}this.#s&&(f=this.#s,d=this.#l,p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:je(e,t),refetch:this.refetch,promise:this.#o,isEnabled:k(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(this.#o=x.promise=fe())},o=this.#o;switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=this.#r,t=this.createResult(this.#t,this.options);this.#i=this.#t.state,this.#a=this.options,this.#i.data!==void 0&&(this.#u=this.#t),!te(t,e)&&(this.#r=t,this.#C({listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!this.#m.size)return!0;let r=new Set(n??this.#m);return this.options.throwOnError&&r.add(`error`),Object.keys(this.#r).some(t=>{let n=t;return this.#r[n]!==e[n]&&r.has(n)})})()}))}#S(){let e=this.#e.getQueryCache().build(this.#e,this.options);if(e===this.#t)return;let t=this.#t;this.#t=e,this.#n=e.state,this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#y()}#C(e){he.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(this.#r)}),this.#e.getQueryCache().notify({query:this.#t,type:`observerResultsUpdated`})})}};function De(e,t){return k(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Oe(e,t){return De(e,t)||e.state.data!==void 0&&ke(e,t,t.refetchOnMount)}function ke(e,t,n){if(k(t.enabled,e)!==!1&&O(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&je(e,t)}return!1}function Ae(e,t,n,r){return(e!==t||k(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&je(e,n)}function je(e,t){return k(t.enabled,e)!==!1&&e.isStaleByTime(O(t.staleTime,e))}function Me(e,t){return!te(e.getCurrentResult(),t)}function Ne(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{ue(e,()=>t.signal,()=>n=!0)},u=ce(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?oe:ae;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Fe:Pe,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:Pe(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function Pe(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Fe(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}function Ie(e,t){return t?Pe(e,t)!=null:!1}function Le(e,t){return!t||!e.getPreviousPageParam?!1:Fe(e,t)!=null}var Re=class extends Ee{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:Ne()})}getOptimisticResult(e){return e.behavior=Ne(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:o,isRefetchError:s}=r,c=n.fetchMeta?.fetchMore?.direction,l=o&&c===`forward`,u=i&&c===`forward`,d=o&&c===`backward`,f=i&&c===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:Ie(t,n.data),hasPreviousPage:Le(t,n.data),isFetchNextPageError:l,isFetchingNextPage:u,isFetchPreviousPageError:d,isFetchingPreviousPage:f,isRefetchError:s&&!l&&!d,isRefetching:a&&!u&&!f}}},ze=class extends xe{#e;#t;#n;#r;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#n=e.mutationCache,this.#t=[],this.state=e.state||Be(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#n.notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){this.#t.length||(this.state.status===`pending`?this.scheduleGc():this.#n.remove(this))}continue(){return this.#r?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#i({type:`continue`})},n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#r=be({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{this.#i({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#i({type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#n.canRun(this)});let r=this.state.status===`pending`,i=!this.#r.canStart();try{if(r)t();else{this.#i({type:`pending`,variables:e,isPaused:i}),this.#n.config.onMutate&&await this.#n.config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&this.#i({type:`pending`,context:t,variables:e,isPaused:i})}let a=await this.#r.start();return await this.#n.config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await this.#n.config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),this.#i({type:`success`,data:a}),a}catch(t){try{await this.#n.config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await this.#n.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw this.#i({type:`error`,error:t}),t}finally{this.#n.runNext(this)}}#i(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}})(this.state),he.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#n.notify({mutation:this,type:`updated`,action:e})})}};function Be(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}var Ve=class extends y{constructor(e={}){super(),this.config=e,this.#e=new Set,this.#t=new Map,this.#n=0}#e;#t;#n;build(e,t,n){let r=new ze({client:e,mutationCache:this,mutationId:++this.#n,options:e.defaultMutationOptions(t),state:n});return this.add(r),r}add(e){this.#e.add(e);let t=He(e);if(typeof t==`string`){let n=this.#t.get(t);n?n.push(e):this.#t.set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(this.#e.delete(e)){let t=He(e);if(typeof t==`string`){let n=this.#t.get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&this.#t.delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=He(e);if(typeof t==`string`){let n=this.#t.get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=He(e);return typeof t==`string`?(this.#t.get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){he.batch(()=>{this.#e.forEach(e=>{this.notify({type:`removed`,mutation:e})}),this.#e.clear(),this.#t.clear()})}getAll(){return Array.from(this.#e)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>j(t,e))}findAll(e={}){return this.getAll().filter(t=>j(e,t))}notify(e){he.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return he.batch(()=>Promise.all(e.map(e=>e.continue().catch(T))))}};function He(e){return e.options.scope?.id}var Ue=class extends y{constructor(e={}){super(),this.config=e,this.#e=new Map}#e;build(e,t,n){let r=t.queryKey,i=t.queryHash??M(r,t),a=this.get(i);return a||(a=new Se({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){this.#e.has(e.queryHash)||(this.#e.set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=this.#e.get(e.queryHash);t&&(e.destroy(),t===e&&this.#e.delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){he.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#e.get(e)}getAll(){return[...this.#e.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>A(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>A(e,t)):t}notify(e){he.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){he.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){he.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},We=class{#e;#t;#n;#r;#i;#a;#o;#s;constructor(e={}){this.#e=e.queryCache||new Ue,this.#t=e.mutationCache||new Ve,this.#n=e.defaultOptions||{},this.#r=new Map,this.#i=new Map,this.#a=0}mount(){this.#a++,this.#a===1&&(this.#o=b.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onFocus())}),this.#s=ge.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onOnline())}))}unmount(){this.#a--,this.#a===0&&(this.#o?.(),this.#o=void 0,this.#s?.(),this.#s=void 0)}isFetching(e){return this.#e.findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return this.#t.findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=this.#e.build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(O(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return this.#e.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=this.#e.get(r.queryHash)?.state.data,a=E(t,i);if(a!==void 0)return this.#e.build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return he.batch(()=>this.#e.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state}removeQueries(e){let t=this.#e;he.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=this.#e;return he.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=he.batch(()=>this.#e.findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(T).catch(T)}invalidateQueries(e,t={}){return he.batch(()=>(this.#e.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=he.batch(()=>this.#e.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(T)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(T)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=this.#e.build(this,t);return n.isStaleByTime(O(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(T).catch(T)}fetchInfiniteQuery(e){return e.behavior=Ne(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(T).catch(T)}ensureInfiniteQueryData(e){return e.behavior=Ne(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return ge.isOnline()?this.#t.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#e}getMutationCache(){return this.#t}getDefaultOptions(){return this.#n}setDefaultOptions(e){this.#n=e}setQueryDefaults(e,t){this.#r.set(N(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#r.values()],n={};return t.forEach(t=>{P(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){this.#i.set(N(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#i.values()],n={};return t.forEach(t=>{P(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#n.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||=M(t.queryKey,t),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===se&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#n.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#e.clear(),this.#t.clear()}},Ge=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),Ke=s(((e,t)=>{t.exports=Ge()})),z=u(f(),1),B=Ke(),qe=z.createContext(void 0),Je=e=>{let t=z.useContext(qe);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},Ye=({client:e,children:t})=>(z.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,B.jsx)(qe.Provider,{value:e,children:t})),Xe=z.createContext(!1),Ze=()=>z.useContext(Xe);Xe.Provider;function Qe(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var $e=z.createContext(Qe()),et=()=>z.useContext($e),tt=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?le(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},nt=e=>{z.useEffect(()=>{e.clearReset()},[e])},rt=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||le(n,[e.error,r])),it=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},at=(e,t)=>e.isLoading&&e.isFetching&&!t,ot=(e,t)=>e?.suspense&&t.isPending,st=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function ct(e,t,n){let r=Ze(),i=et(),a=Je(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,it(o),tt(o,i,s),nt(i);let c=!a.getQueryCache().get(o.queryHash),[l]=z.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(z.useSyncExternalStore(z.useCallback(e=>{let t=d?l.subscribe(he.batchCalls(e)):T;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),z.useEffect(()=>{l.setOptions(o)},[o,l]),ot(o,u))throw st(o,l,i);if(rt({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!de.isServer()&&at(u,r)&&(c?st(o,l,i):s?.promise)?.catch(T).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function lt(e,t){return ct(e,Ee,t)}function ut(e,t){return ct(e,Re,t)}var dt=v();function ft({context:e,localOrigin:t}){return e===`local`?(0,B.jsxs)(`footer`,{className:`mt-6 flex flex-wrap gap-2 border-t border-border pt-4 text-12 text-muted-foreground`,children:[(0,B.jsxs)(`span`,{children:[`Served locally from`,` `,(0,B.jsx)(`code`,{className:`rounded bg-muted/60 px-1.5 py-0.5 font-mono text-xs`,children:t??window.location.host})]}),(0,B.jsx)(`span`,{children:`· Nothing leaves your machine`})]}):(0,B.jsxs)(`footer`,{className:`mt-6 flex flex-wrap gap-2 border-t border-border pt-4 text-12 text-muted-foreground`,children:[(0,B.jsx)(`span`,{children:`Pairing with a remote CLI`}),(0,B.jsx)(`span`,{children:`· Secrets never travel back through the pairing channel`})]})}var pt={"ai-key":3,"api-key-create":3,"api-key-rotate":2,"node-register-token":2,"node-rotate-token":2,"service-account-create":3,"service-account-rotate-secret":2,"developer-app-create":3,"developer-app-rotate-secret":2,"mfa-setup":2};function mt(e,t,n){let r=pt[t];if(e===`resumed-rotation-choice`)return{current:1,total:r,label:`Recovery · confirm outcome`};if(e===`resumed-create-warning`)return{current:1,total:r,label:`Recovery · pairing already started`};if(e===`resending-ack`)return{current:r,total:r,label:`Recovery · notifying CLI`};if(e===`done`)return{current:r,total:r,label:`done`};switch(t){case`ai-key`:return e===`claimed`?n?.slugPicked?{current:2,total:r,label:`enter credential`}:{current:1,total:r,label:`pick a service`}:e===`notifying-cli`||e===`acking`?{current:3,total:r,label:`notifying CLI`}:{current:3,total:r,label:`done`};case`api-key-create`:return e===`claimed`?{current:2,total:r,label:`configure scope`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the value`}:{current:3,total:r,label:`done`};case`api-key-rotate`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`node-register-token`:return e===`claimed`?{current:1,total:r,label:`name this node`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`node-rotate-token`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the value`}:{current:2,total:r,label:`done`};case`service-account-create`:return e===`claimed`?{current:2,total:r,label:`configure account`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the secret`}:{current:3,total:r,label:`done`};case`service-account-rotate-secret`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the secret`}:{current:2,total:r,label:`done`};case`developer-app-create`:return e===`claimed`?{current:2,total:r,label:`configure app`}:e===`notifying-cli`?{current:3,total:r,label:`notifying CLI`}:e===`secret`?{current:3,total:r,label:`save the secret`}:{current:3,total:r,label:`done`};case`developer-app-rotate-secret`:return e===`claimed`?{current:1,total:r,label:`confirm rotate`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save the secret`}:{current:2,total:r,label:`done`};case`mfa-setup`:return e===`claimed`?{current:1,total:r,label:`scan and verify`}:e===`notifying-cli`?{current:2,total:r,label:`notifying CLI`}:e===`secret`?{current:2,total:r,label:`save recovery codes`}:{current:2,total:r,label:`done`}}}function ht(e){return e.label.startsWith(`Recovery`)?e.label:`Step ${e.current} of ${e.total} · ${e.label}`}var gt=[{key:`background`,label:`Page background`,vars:[`background`]},{key:`sidebar`,label:`Sidebar`,vars:[`sidebar`]},{key:`card`,label:`Cards and menus`,vars:[`card`,`popover`,`surface`]},{key:`muted`,label:`Subtle fills`,vars:[`muted`]},{key:`foreground`,label:`Primary text`,vars:[`foreground`,`card-foreground`,`popover-foreground`,`accent-foreground`]},{key:`muted-foreground`,label:`Secondary text`,vars:[`muted-foreground`,`secondary-foreground`]},{key:`text-tertiary`,label:`Tertiary text`,vars:[`text-tertiary`]},{key:`border`,label:`Borders and dividers`,vars:[`border`]},{key:`input`,label:`Input borders`,vars:[`input`]},{key:`input-focus`,label:`Focused input border`,vars:[`input-focus`]},{key:`primary`,label:`Accent`,vars:[`primary`,`ring`]}],_t={light:{},dark:{}},vt=/^#[0-9a-fA-F]{6}$/,yt=new Set(gt.map(e=>e.key));function bt(e){return typeof e==`string`&&vt.test(e)}function xt(e){let t={light:{},dark:{}};if(typeof e!=`object`||!e)return t;for(let n of[`light`,`dark`]){let r=e[n];if(!(typeof r!=`object`||!r))for(let[e,i]of Object.entries(r))yt.has(e)&&bt(i)&&(t[n][e]=i.toUpperCase())}return t}function St(e){let t=xt(e);return[`dark`,`light`].map(e=>{let n=gt.flatMap(({key:n,vars:r})=>{let i=t[e][n];return i?r.map(e=>` --color-${e}: ${i};`):[]});return n.length?`html.theme-${e}.theme-${e} {\n${n.join(` `)}\n}`:``}).filter(Boolean).join(` `)}var Ct=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},wt=(e=>e?Ct(e):Ct),Tt=e=>e;function Et(e,t=Tt){let n=z.useSyncExternalStore(e.subscribe,z.useCallback(()=>t(e.getState()),[e,t]),z.useCallback(()=>t(e.getInitialState()),[e,t]));return z.useDebugValue(n),n}var Dt=e=>{let t=wt(e),n=e=>Et(t,e);return Object.assign(n,t),n},Ot=(e=>e?Dt(e):Dt);function kt(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var At=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return At(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return At(t)(e)}}}},jt=(e,t)=>(n,r,i)=>{let a={storage:kt(()=>window.localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=0,c=new Set,l=new Set,u=a.storage;if(!u)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let d=()=>{let e=a.partialize({...r()});return u.setItem(a.name,{state:e,version:a.version})},f=i.setState;i.setState=(e,t)=>(f(e,t),d());let p=e((...e)=>(n(...e),d()),r,i);i.getInitialState=()=>p;let m,h=()=>{if(!u)return;let e=++s;o=!1,c.forEach(e=>e(r()??p));let t=a.onRehydrateStorage?.call(a,r()??p)||void 0;return At(u.getItem.bind(u))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(t=>{if(e!==s)return;let[i,o]=t;if(m=a.merge(o,r()??p),n(m,!0),i)return d()}).then(()=>{e===s&&(t?.(r(),void 0),m=r(),o=!0,l.forEach(e=>e(m)))}).catch(n=>{e===s&&t?.(void 0,n)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(u=e.storage)},clearStorage:()=>{u?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>h(),hasHydrated:()=>o,onHydrate:e=>(c.add(e),()=>{c.delete(e)}),onFinishHydration:e=>(l.add(e),()=>{l.delete(e)})},a.skipHydration||h(),m||p},Mt=.5,Nt=.01;function Pt(e){return typeof e!=`number`||!Number.isFinite(e)?1:Math.round(Math.min(2,Math.max(Mt,e))/Nt)/(1/Nt)}var Ft={min:180,max:360,default:200};function It(e){return Math.round(Math.min(Ft.max,Math.max(Ft.min,e)))}function Lt(e){return typeof e==`number`&&Number.isFinite(e)?It(e):Ft.default}var Rt=`nyxid:sidebar-mode`,zt=[`expanded`,`collapsed`,`hover`];function Bt(){try{let e=localStorage.getItem(Rt);return zt.includes(e)?e:`expanded`}catch{return`expanded`}}var Vt=[`system`,`light`,`dark`],Ht=[`system`,`reduce`];function Ut(e,t,n){return e.includes(t)?t:n}function Wt(e,t){return e===`system`?t?`dark`:`light`:e}function Gt(){return typeof window>`u`||typeof window.matchMedia!=`function`?!0:window.matchMedia(`(prefers-color-scheme: dark)`).matches}var Kt={textScale:1,density:1,motion:`system`,customColors:_t,sidebarMode:`expanded`,sidebarWidths:{dashboard:Ft.default,assistant:Ft.default}},qt=Ot()(jt((e,t)=>({mode:`system`,...Kt,sidebarMode:Bt(),systemPrefersDark:Gt(),setMode:t=>e({mode:t}),setTextScale:t=>e({textScale:Pt(t)}),setDensity:t=>e({density:Pt(t)}),setMotion:t=>e({motion:t}),setSidebarMode:t=>e({sidebarMode:t}),setSidebarWidth:(n,r)=>e({sidebarWidths:{...t().sidebarWidths,[n]:It(r)}}),setCustomColor:(n,r,i)=>{if(!bt(i))return;let{customColors:a}=t();e({customColors:{...a,[n]:{...a[n],[r]:i.toUpperCase()}}})},resetCustomColors:(n,r)=>{let{customColors:i}=t(),a={...i[n]};r&&delete a[r],e({customColors:{...i,[n]:r?a:{}}})},resetDisplay:()=>e({...Kt}),toggle:()=>{let{mode:n,systemPrefersDark:r}=t();e({mode:Wt(n,r)===`dark`?`light`:`dark`})}}),{name:`nyxid.theme`,version:1,partialize:e=>({mode:e.mode,textScale:e.textScale,density:e.density,motion:e.motion,customColors:e.customColors,sidebarMode:e.sidebarMode,sidebarWidths:e.sidebarWidths}),merge:(e,t)=>{let n=e??{};return{...t,mode:Ut(Vt,n.mode,t.mode),textScale:Pt(n.textScale),density:Pt(n.density),motion:Ut(Ht,n.motion,t.motion),customColors:xt(n.customColors),sidebarMode:Ut(zt,n.sidebarMode,t.sidebarMode),sidebarWidths:{dashboard:Lt(n.sidebarWidths?.dashboard),assistant:Lt(n.sidebarWidths?.assistant)}}}}));typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-color-scheme: dark)`).addEventListener?.(`change`,e=>qt.setState({systemPrefersDark:e.matches}));function Jt(){return Wt(qt(e=>e.mode),qt(e=>e.systemPrefersDark))}function Yt(){let e=Jt(),t=qt(e=>e.textScale);(0,z.useLayoutEffect)(()=>{let t=document.documentElement;return t.classList.toggle(`theme-light`,e===`light`),t.classList.toggle(`theme-dark`,e===`dark`),()=>{t.classList.remove(`theme-light`,`theme-dark`)}},[e]);let n=qt(e=>e.density),r=qt(e=>e.motion),i=qt(e=>e.customColors),a=(0,z.useMemo)(()=>St(i),[i]);(0,z.useLayoutEffect)(()=>{let e=document.documentElement;return e.style.fontSize=t===1?``:`${t*100}%`,()=>{e.style.fontSize=``}},[t]),(0,z.useLayoutEffect)(()=>{let e=document.documentElement;return n===1?e.style.removeProperty(`--spacing`):e.style.setProperty(`--spacing`,`${4*n}px`),()=>{e.style.removeProperty(`--spacing`)}},[n]),(0,z.useLayoutEffect)(()=>{let e=document.documentElement;return e.classList.toggle(`motion-reduce`,r===`reduce`),()=>{e.classList.remove(`motion-reduce`)}},[r]),(0,z.useLayoutEffect)(()=>{if(!a)return;let e=document.createElement(`style`);return e.id=`nyxid-custom-colors`,e.textContent=a,document.head.appendChild(e),()=>{e.remove()}},[a])}function Xt({className:e=`h-7 w-auto`}){return(0,B.jsx)(`img`,{src:Jt()===`light`?`/nyxid-coloured-logo-dark.svg`:`/nyxid-coloured-logo.svg`,alt:`NyxID`,className:e})}function Zt({step:e,context:t,localOrigin:n,children:r}){return Yt(),(0,B.jsx)(`div`,{className:`min-h-screen bg-background text-foreground`,children:(0,B.jsxs)(`div`,{className:`mx-auto flex max-h-screen w-full max-w-[1040px] flex-col px-6 pt-10 pb-6`,children:[(0,B.jsxs)(`header`,{className:`mb-6 flex items-center justify-between`,children:[(0,B.jsx)(`div`,{className:`flex items-center`,children:(0,B.jsx)(Xt,{className:`h-9 w-auto`})}),e?(0,B.jsx)(`div`,{className:`text-12 text-muted-foreground`,children:ht(e)}):null]}),(0,B.jsx)(`main`,{className:`min-h-[240px] overflow-y-auto overscroll-contain rounded-xl border border-border bg-card p-8`,children:r}),(0,B.jsx)(ft,{context:t,localOrigin:n})]})})}function Qt(e,t){if(typeof e==`function`)return e(t);e!=null&&(e.current=t)}function $t(...e){return t=>{let n=!1,r=e.map(e=>{let r=Qt(e,t);return!n&&typeof r==`function`&&(n=!0),r});if(n)return()=>{for(let t=0;t{let{children:r,...i}=e;an(r)&&typeof nn==`function`&&(r=nn(r._payload));let a=z.Children.toArray(r),o=a.find(un);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}var sn=on(`Slot`);function cn(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(an(n)&&typeof nn==`function`&&(n=nn(n._payload)),z.isValidElement(n)){let e=fn(n),i=dn(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var ln=Symbol(`radix.slottable`);function un(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===ln}function dn(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function fn(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function pn(e){var t,n,r=``;if(typeof e==`string`||typeof e==`number`)r+=e;else if(typeof e==`object`)if(Array.isArray(e)){var i=e.length;for(t=0;ttypeof e==`boolean`?`${e}`:e===0?`0`:e,gn=mn,_n=(e,t)=>n=>{if(t?.variants==null)return gn(e,n?.class,n?.className);let{variants:r,defaultVariants:i}=t,a=Object.keys(r).map(e=>{let t=n?.[e],a=i?.[e];if(t===null)return null;let o=hn(t)||hn(a);return r[e][o]}),o=n&&Object.entries(n).reduce((e,t)=>{let[n,r]=t;return r===void 0||(e[n]=r),e},{});return gn(e,a,t?.compoundVariants?.reduce((e,t)=>{let{class:n,className:r,...a}=t;return Object.entries(a).every(e=>{let[t,n]=e;return Array.isArray(n)?n.includes({...i,...o}[t]):{...i,...o}[t]===n})?[...e,n,r]:e},[]),n?.class,n?.className)},vn=(e,t)=>{let n=Array(e.length+t.length);for(let t=0;t({classGroupId:e,validator:t}),bn=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),xn=`-`,Sn=[],Cn=`arbitrary..`,wn=e=>{let t=Dn(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:e=>{if(e.startsWith(`[`)&&e.endsWith(`]`))return En(e);let n=e.split(xn);return Tn(n,+(n[0]===``&&n.length>1),t)},getConflictingClassGroupIds:(e,t)=>{if(t){let t=r[e],i=n[e];return t?i?vn(i,t):t:i||Sn}return n[e]||Sn}}},Tn=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;let r=e[t],i=n.nextPart.get(r);if(i){let n=Tn(e,t+1,i);if(n)return n}let a=n.validators;if(a===null)return;let o=t===0?e.join(xn):e.slice(t).join(xn),s=a.length;for(let e=0;ee.slice(1,-1).indexOf(`:`)===-1?void 0:(()=>{let t=e.slice(1,-1),n=t.indexOf(`:`),r=t.slice(0,n);return r?Cn+r:void 0})(),Dn=e=>{let{theme:t,classGroups:n}=e;return On(n,t)},On=(e,t)=>{let n=bn();for(let r in e){let i=e[r];kn(i,n,r,t)}return n},kn=(e,t,n,r)=>{let i=e.length;for(let a=0;a{if(typeof e==`string`){jn(e,t,n);return}if(typeof e==`function`){Mn(e,t,n,r);return}Nn(e,t,n,r)},jn=(e,t,n)=>{let r=e===``?t:Pn(t,e);r.classGroupId=n},Mn=(e,t,n,r)=>{if(Fn(e)){kn(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(yn(n,e))},Nn=(e,t,n,r)=>{let i=Object.entries(e),a=i.length;for(let e=0;e{let n=e,r=t.split(xn),i=r.length;for(let e=0;e`isThemeGetter`in e&&e.isThemeGetter===!0,In=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null),i=(i,a)=>{n[i]=a,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){let t=n[e];if(t!==void 0)return t;if((t=r[e])!==void 0)return i(e,t),t},set(e,t){e in n?n[e]=t:i(e,t)}}},Ln=`!`,Rn=`:`,zn=[],Bn=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),Vn=e=>{let{prefix:t,experimentalParseClassName:n}=e,r=e=>{let t=[],n=0,r=0,i=0,a,o=e.length;for(let s=0;si?a-i:void 0;return Bn(t,l,c,u)};if(t){let e=t+Rn,n=r;r=t=>t.startsWith(e)?n(t.slice(e.length)):Bn(zn,!1,t,void 0,!0)}if(n){let e=r;r=t=>n({className:t,parseClassName:e})}return r},Hn=e=>{let t=new Map;return e.orderSensitiveModifiers.forEach((e,n)=>{t.set(e,1e6+n)}),e=>{let n=[],r=[];for(let i=0;i0&&(r.sort(),n.push(...r),r=[]),n.push(a)):r.push(a)}return r.length>0&&(r.sort(),n.push(...r)),n}},Un=e=>({cache:In(e.cacheSize),parseClassName:Vn(e),sortModifiers:Hn(e),...wn(e)}),Wn=/\s+/,Gn=(e,t)=>{let{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:a}=t,o=[],s=e.trim().split(Wn),c=``;for(let e=s.length-1;e>=0;--e){let t=s[e],{isExternal:l,modifiers:u,hasImportantModifier:d,baseClassName:f,maybePostfixModifierPosition:p}=n(t);if(l){c=t+(c.length>0?` `+c:c);continue}let m=!!p,h=r(m?f.substring(0,p):f);if(!h){if(!m){c=t+(c.length>0?` `+c:c);continue}if(h=r(f),!h){c=t+(c.length>0?` `+c:c);continue}m=!1}let g=u.length===0?``:u.length===1?u[0]:a(u).join(`:`),_=d?g+Ln:g,v=_+h;if(o.indexOf(v)>-1)continue;o.push(v);let y=i(h,m);for(let e=0;e0?` `+c:c)}return c},Kn=(...e)=>{let t=0,n,r,i=``;for(;t{if(typeof e==`string`)return e;let t,n=``;for(let r=0;r{let n,r,i,a,o=o=>(n=Un(t.reduce((e,t)=>t(e),e())),r=n.cache.get,i=n.cache.set,a=s,s(o)),s=e=>{let t=r(e);if(t)return t;let a=Gn(e,n);return i(e,a),a};return a=o,(...e)=>a(Kn(...e))},Yn=[],Xn=e=>{let t=t=>t[e]||Yn;return t.isThemeGetter=!0,t},Zn=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,Qn=/^\((?:(\w[\w-]*):)?(.+)\)$/i,$n=/^\d+(?:\.\d+)?\/\d+(?:\.\d+)?$/,er=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,tr=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,nr=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,rr=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,ir=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,ar=e=>$n.test(e),or=e=>!!e&&!Number.isNaN(Number(e)),sr=e=>!!e&&Number.isInteger(Number(e)),cr=e=>e.endsWith(`%`)&&or(e.slice(0,-1)),lr=e=>er.test(e),ur=()=>!0,dr=e=>tr.test(e)&&!nr.test(e),fr=()=>!1,pr=e=>rr.test(e),mr=e=>ir.test(e),hr=e=>!V(e)&&!H(e),gr=e=>jr(e,Fr,fr),V=e=>Zn.test(e),_r=e=>jr(e,Ir,dr),vr=e=>jr(e,Lr,or),yr=e=>jr(e,zr,ur),br=e=>jr(e,Rr,fr),xr=e=>jr(e,Nr,fr),Sr=e=>jr(e,Pr,mr),Cr=e=>jr(e,Br,pr),H=e=>Qn.test(e),wr=e=>Mr(e,Ir),Tr=e=>Mr(e,Rr),Er=e=>Mr(e,Nr),Dr=e=>Mr(e,Fr),Or=e=>Mr(e,Pr),kr=e=>Mr(e,Br,!0),Ar=e=>Mr(e,zr,!0),jr=(e,t,n)=>{let r=Zn.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},Mr=(e,t,n=!1)=>{let r=Qn.exec(e);return r?r[1]?t(r[1]):n:!1},Nr=e=>e===`position`||e===`percentage`,Pr=e=>e===`image`||e===`url`,Fr=e=>e===`length`||e===`size`||e===`bg-size`,Ir=e=>e===`length`,Lr=e=>e===`number`,Rr=e=>e===`family-name`,zr=e=>e===`number`||e===`weight`,Br=e=>e===`shadow`,Vr=()=>{let e=Xn(`color`),t=Xn(`font`),n=Xn(`text`),r=Xn(`font-weight`),i=Xn(`tracking`),a=Xn(`leading`),o=Xn(`breakpoint`),s=Xn(`container`),c=Xn(`spacing`),l=Xn(`radius`),u=Xn(`shadow`),d=Xn(`inset-shadow`),f=Xn(`text-shadow`),p=Xn(`drop-shadow`),m=Xn(`blur`),h=Xn(`perspective`),g=Xn(`aspect`),_=Xn(`ease`),v=Xn(`animate`),y=()=>[`auto`,`avoid`,`all`,`avoid-page`,`page`,`left`,`right`,`column`],b=()=>[`center`,`top`,`bottom`,`left`,`right`,`top-left`,`left-top`,`top-right`,`right-top`,`bottom-right`,`right-bottom`,`bottom-left`,`left-bottom`],x=()=>[...b(),H,V],S=()=>[`auto`,`hidden`,`clip`,`visible`,`scroll`],C=()=>[`auto`,`contain`,`none`],w=()=>[H,V,c],T=()=>[ar,`full`,`auto`,...w()],E=()=>[sr,`none`,`subgrid`,H,V],ee=()=>[`auto`,{span:[`full`,sr,H,V]},sr,H,V],D=()=>[sr,`auto`,H,V],O=()=>[`auto`,`min`,`max`,`fr`,H,V],k=()=>[`start`,`end`,`center`,`between`,`around`,`evenly`,`stretch`,`baseline`,`center-safe`,`end-safe`],A=()=>[`start`,`end`,`center`,`stretch`,`center-safe`,`end-safe`],j=()=>[`auto`,...w()],M=()=>[ar,`auto`,`full`,`dvw`,`dvh`,`lvw`,`lvh`,`svw`,`svh`,`min`,`max`,`fit`,...w()],N=()=>[ar,`screen`,`full`,`dvw`,`lvw`,`svw`,`min`,`max`,`fit`,...w()],P=()=>[ar,`screen`,`full`,`lh`,`dvh`,`lvh`,`svh`,`min`,`max`,`fit`,...w()],F=()=>[e,H,V],I=()=>[...b(),Er,xr,{position:[H,V]}],te=()=>[`no-repeat`,{repeat:[``,`x`,`y`,`space`,`round`]}],ne=()=>[`auto`,`cover`,`contain`,Dr,gr,{size:[H,V]}],re=()=>[cr,wr,_r],L=()=>[``,`none`,`full`,l,H,V],R=()=>[``,or,wr,_r],ie=()=>[`solid`,`dashed`,`dotted`,`double`],ae=()=>[`normal`,`multiply`,`screen`,`overlay`,`darken`,`lighten`,`color-dodge`,`color-burn`,`hard-light`,`soft-light`,`difference`,`exclusion`,`hue`,`saturation`,`color`,`luminosity`],oe=()=>[or,cr,Er,xr],se=()=>[``,`none`,m,H,V],ce=()=>[`none`,or,H,V],le=()=>[`none`,or,H,V],ue=()=>[or,H,V],de=()=>[ar,`full`,...w()];return{cacheSize:500,theme:{animate:[`spin`,`ping`,`pulse`,`bounce`],aspect:[`video`],blur:[lr],breakpoint:[lr],color:[ur],container:[lr],"drop-shadow":[lr],ease:[`in`,`out`,`in-out`],font:[hr],"font-weight":[`thin`,`extralight`,`light`,`normal`,`medium`,`semibold`,`bold`,`extrabold`,`black`],"inset-shadow":[lr],leading:[`none`,`tight`,`snug`,`normal`,`relaxed`,`loose`],perspective:[`dramatic`,`near`,`normal`,`midrange`,`distant`,`none`],radius:[lr],shadow:[lr],spacing:[`px`,or],text:[lr],"text-shadow":[lr],tracking:[`tighter`,`tight`,`normal`,`wide`,`wider`,`widest`]},classGroups:{aspect:[{aspect:[`auto`,`square`,ar,V,H,g]}],container:[`container`],columns:[{columns:[or,V,H,s]}],"break-after":[{"break-after":y()}],"break-before":[{"break-before":y()}],"break-inside":[{"break-inside":[`auto`,`avoid`,`avoid-page`,`avoid-column`]}],"box-decoration":[{"box-decoration":[`slice`,`clone`]}],box:[{box:[`border`,`content`]}],display:[`block`,`inline-block`,`inline`,`flex`,`inline-flex`,`table`,`inline-table`,`table-caption`,`table-cell`,`table-column`,`table-column-group`,`table-footer-group`,`table-header-group`,`table-row-group`,`table-row`,`flow-root`,`grid`,`inline-grid`,`contents`,`list-item`,`hidden`],sr:[`sr-only`,`not-sr-only`],float:[{float:[`right`,`left`,`none`,`start`,`end`]}],clear:[{clear:[`left`,`right`,`both`,`none`,`start`,`end`]}],isolation:[`isolate`,`isolation-auto`],"object-fit":[{object:[`contain`,`cover`,`fill`,`none`,`scale-down`]}],"object-position":[{object:x()}],overflow:[{overflow:S()}],"overflow-x":[{"overflow-x":S()}],"overflow-y":[{"overflow-y":S()}],overscroll:[{overscroll:C()}],"overscroll-x":[{"overscroll-x":C()}],"overscroll-y":[{"overscroll-y":C()}],position:[`static`,`fixed`,`absolute`,`relative`,`sticky`],inset:[{inset:T()}],"inset-x":[{"inset-x":T()}],"inset-y":[{"inset-y":T()}],start:[{"inset-s":T(),start:T()}],end:[{"inset-e":T(),end:T()}],"inset-bs":[{"inset-bs":T()}],"inset-be":[{"inset-be":T()}],top:[{top:T()}],right:[{right:T()}],bottom:[{bottom:T()}],left:[{left:T()}],visibility:[`visible`,`invisible`,`collapse`],z:[{z:[sr,`auto`,H,V]}],basis:[{basis:[ar,`full`,`auto`,s,...w()]}],"flex-direction":[{flex:[`row`,`row-reverse`,`col`,`col-reverse`]}],"flex-wrap":[{flex:[`nowrap`,`wrap`,`wrap-reverse`]}],flex:[{flex:[or,ar,`auto`,`initial`,`none`,V]}],grow:[{grow:[``,or,H,V]}],shrink:[{shrink:[``,or,H,V]}],order:[{order:[sr,`first`,`last`,`none`,H,V]}],"grid-cols":[{"grid-cols":E()}],"col-start-end":[{col:ee()}],"col-start":[{"col-start":D()}],"col-end":[{"col-end":D()}],"grid-rows":[{"grid-rows":E()}],"row-start-end":[{row:ee()}],"row-start":[{"row-start":D()}],"row-end":[{"row-end":D()}],"grid-flow":[{"grid-flow":[`row`,`col`,`dense`,`row-dense`,`col-dense`]}],"auto-cols":[{"auto-cols":O()}],"auto-rows":[{"auto-rows":O()}],gap:[{gap:w()}],"gap-x":[{"gap-x":w()}],"gap-y":[{"gap-y":w()}],"justify-content":[{justify:[...k(),`normal`]}],"justify-items":[{"justify-items":[...A(),`normal`]}],"justify-self":[{"justify-self":[`auto`,...A()]}],"align-content":[{content:[`normal`,...k()]}],"align-items":[{items:[...A(),{baseline:[``,`last`]}]}],"align-self":[{self:[`auto`,...A(),{baseline:[``,`last`]}]}],"place-content":[{"place-content":k()}],"place-items":[{"place-items":[...A(),`baseline`]}],"place-self":[{"place-self":[`auto`,...A()]}],p:[{p:w()}],px:[{px:w()}],py:[{py:w()}],ps:[{ps:w()}],pe:[{pe:w()}],pbs:[{pbs:w()}],pbe:[{pbe:w()}],pt:[{pt:w()}],pr:[{pr:w()}],pb:[{pb:w()}],pl:[{pl:w()}],m:[{m:j()}],mx:[{mx:j()}],my:[{my:j()}],ms:[{ms:j()}],me:[{me:j()}],mbs:[{mbs:j()}],mbe:[{mbe:j()}],mt:[{mt:j()}],mr:[{mr:j()}],mb:[{mb:j()}],ml:[{ml:j()}],"space-x":[{"space-x":w()}],"space-x-reverse":[`space-x-reverse`],"space-y":[{"space-y":w()}],"space-y-reverse":[`space-y-reverse`],size:[{size:M()}],"inline-size":[{inline:[`auto`,...N()]}],"min-inline-size":[{"min-inline":[`auto`,...N()]}],"max-inline-size":[{"max-inline":[`none`,...N()]}],"block-size":[{block:[`auto`,...P()]}],"min-block-size":[{"min-block":[`auto`,...P()]}],"max-block-size":[{"max-block":[`none`,...P()]}],w:[{w:[s,`screen`,...M()]}],"min-w":[{"min-w":[s,`screen`,`none`,...M()]}],"max-w":[{"max-w":[s,`screen`,`none`,`prose`,{screen:[o]},...M()]}],h:[{h:[`screen`,`lh`,...M()]}],"min-h":[{"min-h":[`screen`,`lh`,`none`,...M()]}],"max-h":[{"max-h":[`screen`,`lh`,...M()]}],"font-size":[{text:[`base`,n,wr,_r]}],"font-smoothing":[`antialiased`,`subpixel-antialiased`],"font-style":[`italic`,`not-italic`],"font-weight":[{font:[r,Ar,yr]}],"font-stretch":[{"font-stretch":[`ultra-condensed`,`extra-condensed`,`condensed`,`semi-condensed`,`normal`,`semi-expanded`,`expanded`,`extra-expanded`,`ultra-expanded`,cr,V]}],"font-family":[{font:[Tr,br,t]}],"font-features":[{"font-features":[V]}],"fvn-normal":[`normal-nums`],"fvn-ordinal":[`ordinal`],"fvn-slashed-zero":[`slashed-zero`],"fvn-figure":[`lining-nums`,`oldstyle-nums`],"fvn-spacing":[`proportional-nums`,`tabular-nums`],"fvn-fraction":[`diagonal-fractions`,`stacked-fractions`],tracking:[{tracking:[i,H,V]}],"line-clamp":[{"line-clamp":[or,`none`,H,vr]}],leading:[{leading:[a,...w()]}],"list-image":[{"list-image":[`none`,H,V]}],"list-style-position":[{list:[`inside`,`outside`]}],"list-style-type":[{list:[`disc`,`decimal`,`none`,H,V]}],"text-alignment":[{text:[`left`,`center`,`right`,`justify`,`start`,`end`]}],"placeholder-color":[{placeholder:F()}],"text-color":[{text:F()}],"text-decoration":[`underline`,`overline`,`line-through`,`no-underline`],"text-decoration-style":[{decoration:[...ie(),`wavy`]}],"text-decoration-thickness":[{decoration:[or,`from-font`,`auto`,H,_r]}],"text-decoration-color":[{decoration:F()}],"underline-offset":[{"underline-offset":[or,`auto`,H,V]}],"text-transform":[`uppercase`,`lowercase`,`capitalize`,`normal-case`],"text-overflow":[`truncate`,`text-ellipsis`,`text-clip`],"text-wrap":[{text:[`wrap`,`nowrap`,`balance`,`pretty`]}],indent:[{indent:w()}],"vertical-align":[{align:[`baseline`,`top`,`middle`,`bottom`,`text-top`,`text-bottom`,`sub`,`super`,H,V]}],whitespace:[{whitespace:[`normal`,`nowrap`,`pre`,`pre-line`,`pre-wrap`,`break-spaces`]}],break:[{break:[`normal`,`words`,`all`,`keep`]}],wrap:[{wrap:[`break-word`,`anywhere`,`normal`]}],hyphens:[{hyphens:[`none`,`manual`,`auto`]}],content:[{content:[`none`,H,V]}],"bg-attachment":[{bg:[`fixed`,`local`,`scroll`]}],"bg-clip":[{"bg-clip":[`border`,`padding`,`content`,`text`]}],"bg-origin":[{"bg-origin":[`border`,`padding`,`content`]}],"bg-position":[{bg:I()}],"bg-repeat":[{bg:te()}],"bg-size":[{bg:ne()}],"bg-image":[{bg:[`none`,{linear:[{to:[`t`,`tr`,`r`,`br`,`b`,`bl`,`l`,`tl`]},sr,H,V],radial:[``,H,V],conic:[sr,H,V]},Or,Sr]}],"bg-color":[{bg:F()}],"gradient-from-pos":[{from:re()}],"gradient-via-pos":[{via:re()}],"gradient-to-pos":[{to:re()}],"gradient-from":[{from:F()}],"gradient-via":[{via:F()}],"gradient-to":[{to:F()}],rounded:[{rounded:L()}],"rounded-s":[{"rounded-s":L()}],"rounded-e":[{"rounded-e":L()}],"rounded-t":[{"rounded-t":L()}],"rounded-r":[{"rounded-r":L()}],"rounded-b":[{"rounded-b":L()}],"rounded-l":[{"rounded-l":L()}],"rounded-ss":[{"rounded-ss":L()}],"rounded-se":[{"rounded-se":L()}],"rounded-ee":[{"rounded-ee":L()}],"rounded-es":[{"rounded-es":L()}],"rounded-tl":[{"rounded-tl":L()}],"rounded-tr":[{"rounded-tr":L()}],"rounded-br":[{"rounded-br":L()}],"rounded-bl":[{"rounded-bl":L()}],"border-w":[{border:R()}],"border-w-x":[{"border-x":R()}],"border-w-y":[{"border-y":R()}],"border-w-s":[{"border-s":R()}],"border-w-e":[{"border-e":R()}],"border-w-bs":[{"border-bs":R()}],"border-w-be":[{"border-be":R()}],"border-w-t":[{"border-t":R()}],"border-w-r":[{"border-r":R()}],"border-w-b":[{"border-b":R()}],"border-w-l":[{"border-l":R()}],"divide-x":[{"divide-x":R()}],"divide-x-reverse":[`divide-x-reverse`],"divide-y":[{"divide-y":R()}],"divide-y-reverse":[`divide-y-reverse`],"border-style":[{border:[...ie(),`hidden`,`none`]}],"divide-style":[{divide:[...ie(),`hidden`,`none`]}],"border-color":[{border:F()}],"border-color-x":[{"border-x":F()}],"border-color-y":[{"border-y":F()}],"border-color-s":[{"border-s":F()}],"border-color-e":[{"border-e":F()}],"border-color-bs":[{"border-bs":F()}],"border-color-be":[{"border-be":F()}],"border-color-t":[{"border-t":F()}],"border-color-r":[{"border-r":F()}],"border-color-b":[{"border-b":F()}],"border-color-l":[{"border-l":F()}],"divide-color":[{divide:F()}],"outline-style":[{outline:[...ie(),`none`,`hidden`]}],"outline-offset":[{"outline-offset":[or,H,V]}],"outline-w":[{outline:[``,or,wr,_r]}],"outline-color":[{outline:F()}],shadow:[{shadow:[``,`none`,u,kr,Cr]}],"shadow-color":[{shadow:F()}],"inset-shadow":[{"inset-shadow":[`none`,d,kr,Cr]}],"inset-shadow-color":[{"inset-shadow":F()}],"ring-w":[{ring:R()}],"ring-w-inset":[`ring-inset`],"ring-color":[{ring:F()}],"ring-offset-w":[{"ring-offset":[or,_r]}],"ring-offset-color":[{"ring-offset":F()}],"inset-ring-w":[{"inset-ring":R()}],"inset-ring-color":[{"inset-ring":F()}],"text-shadow":[{"text-shadow":[`none`,f,kr,Cr]}],"text-shadow-color":[{"text-shadow":F()}],opacity:[{opacity:[or,H,V]}],"mix-blend":[{"mix-blend":[...ae(),`plus-darker`,`plus-lighter`]}],"bg-blend":[{"bg-blend":ae()}],"mask-clip":[{"mask-clip":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]},`mask-no-clip`],"mask-composite":[{mask:[`add`,`subtract`,`intersect`,`exclude`]}],"mask-image-linear-pos":[{"mask-linear":[or]}],"mask-image-linear-from-pos":[{"mask-linear-from":oe()}],"mask-image-linear-to-pos":[{"mask-linear-to":oe()}],"mask-image-linear-from-color":[{"mask-linear-from":F()}],"mask-image-linear-to-color":[{"mask-linear-to":F()}],"mask-image-t-from-pos":[{"mask-t-from":oe()}],"mask-image-t-to-pos":[{"mask-t-to":oe()}],"mask-image-t-from-color":[{"mask-t-from":F()}],"mask-image-t-to-color":[{"mask-t-to":F()}],"mask-image-r-from-pos":[{"mask-r-from":oe()}],"mask-image-r-to-pos":[{"mask-r-to":oe()}],"mask-image-r-from-color":[{"mask-r-from":F()}],"mask-image-r-to-color":[{"mask-r-to":F()}],"mask-image-b-from-pos":[{"mask-b-from":oe()}],"mask-image-b-to-pos":[{"mask-b-to":oe()}],"mask-image-b-from-color":[{"mask-b-from":F()}],"mask-image-b-to-color":[{"mask-b-to":F()}],"mask-image-l-from-pos":[{"mask-l-from":oe()}],"mask-image-l-to-pos":[{"mask-l-to":oe()}],"mask-image-l-from-color":[{"mask-l-from":F()}],"mask-image-l-to-color":[{"mask-l-to":F()}],"mask-image-x-from-pos":[{"mask-x-from":oe()}],"mask-image-x-to-pos":[{"mask-x-to":oe()}],"mask-image-x-from-color":[{"mask-x-from":F()}],"mask-image-x-to-color":[{"mask-x-to":F()}],"mask-image-y-from-pos":[{"mask-y-from":oe()}],"mask-image-y-to-pos":[{"mask-y-to":oe()}],"mask-image-y-from-color":[{"mask-y-from":F()}],"mask-image-y-to-color":[{"mask-y-to":F()}],"mask-image-radial":[{"mask-radial":[H,V]}],"mask-image-radial-from-pos":[{"mask-radial-from":oe()}],"mask-image-radial-to-pos":[{"mask-radial-to":oe()}],"mask-image-radial-from-color":[{"mask-radial-from":F()}],"mask-image-radial-to-color":[{"mask-radial-to":F()}],"mask-image-radial-shape":[{"mask-radial":[`circle`,`ellipse`]}],"mask-image-radial-size":[{"mask-radial":[{closest:[`side`,`corner`],farthest:[`side`,`corner`]}]}],"mask-image-radial-pos":[{"mask-radial-at":b()}],"mask-image-conic-pos":[{"mask-conic":[or]}],"mask-image-conic-from-pos":[{"mask-conic-from":oe()}],"mask-image-conic-to-pos":[{"mask-conic-to":oe()}],"mask-image-conic-from-color":[{"mask-conic-from":F()}],"mask-image-conic-to-color":[{"mask-conic-to":F()}],"mask-mode":[{mask:[`alpha`,`luminance`,`match`]}],"mask-origin":[{"mask-origin":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]}],"mask-position":[{mask:I()}],"mask-repeat":[{mask:te()}],"mask-size":[{mask:ne()}],"mask-type":[{"mask-type":[`alpha`,`luminance`]}],"mask-image":[{mask:[`none`,H,V]}],filter:[{filter:[``,`none`,H,V]}],blur:[{blur:se()}],brightness:[{brightness:[or,H,V]}],contrast:[{contrast:[or,H,V]}],"drop-shadow":[{"drop-shadow":[``,`none`,p,kr,Cr]}],"drop-shadow-color":[{"drop-shadow":F()}],grayscale:[{grayscale:[``,or,H,V]}],"hue-rotate":[{"hue-rotate":[or,H,V]}],invert:[{invert:[``,or,H,V]}],saturate:[{saturate:[or,H,V]}],sepia:[{sepia:[``,or,H,V]}],"backdrop-filter":[{"backdrop-filter":[``,`none`,H,V]}],"backdrop-blur":[{"backdrop-blur":se()}],"backdrop-brightness":[{"backdrop-brightness":[or,H,V]}],"backdrop-contrast":[{"backdrop-contrast":[or,H,V]}],"backdrop-grayscale":[{"backdrop-grayscale":[``,or,H,V]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[or,H,V]}],"backdrop-invert":[{"backdrop-invert":[``,or,H,V]}],"backdrop-opacity":[{"backdrop-opacity":[or,H,V]}],"backdrop-saturate":[{"backdrop-saturate":[or,H,V]}],"backdrop-sepia":[{"backdrop-sepia":[``,or,H,V]}],"border-collapse":[{border:[`collapse`,`separate`]}],"border-spacing":[{"border-spacing":w()}],"border-spacing-x":[{"border-spacing-x":w()}],"border-spacing-y":[{"border-spacing-y":w()}],"table-layout":[{table:[`auto`,`fixed`]}],caption:[{caption:[`top`,`bottom`]}],transition:[{transition:[``,`all`,`colors`,`opacity`,`shadow`,`transform`,`none`,H,V]}],"transition-behavior":[{transition:[`normal`,`discrete`]}],duration:[{duration:[or,`initial`,H,V]}],ease:[{ease:[`linear`,`initial`,_,H,V]}],delay:[{delay:[or,H,V]}],animate:[{animate:[`none`,v,H,V]}],backface:[{backface:[`hidden`,`visible`]}],perspective:[{perspective:[h,H,V]}],"perspective-origin":[{"perspective-origin":x()}],rotate:[{rotate:ce()}],"rotate-x":[{"rotate-x":ce()}],"rotate-y":[{"rotate-y":ce()}],"rotate-z":[{"rotate-z":ce()}],scale:[{scale:le()}],"scale-x":[{"scale-x":le()}],"scale-y":[{"scale-y":le()}],"scale-z":[{"scale-z":le()}],"scale-3d":[`scale-3d`],skew:[{skew:ue()}],"skew-x":[{"skew-x":ue()}],"skew-y":[{"skew-y":ue()}],transform:[{transform:[H,V,``,`none`,`gpu`,`cpu`]}],"transform-origin":[{origin:x()}],"transform-style":[{transform:[`3d`,`flat`]}],translate:[{translate:de()}],"translate-x":[{"translate-x":de()}],"translate-y":[{"translate-y":de()}],"translate-z":[{"translate-z":de()}],"translate-none":[`translate-none`],accent:[{accent:F()}],appearance:[{appearance:[`none`,`auto`]}],"caret-color":[{caret:F()}],"color-scheme":[{scheme:[`normal`,`dark`,`light`,`light-dark`,`only-dark`,`only-light`]}],cursor:[{cursor:[`auto`,`default`,`pointer`,`wait`,`text`,`move`,`help`,`not-allowed`,`none`,`context-menu`,`progress`,`cell`,`crosshair`,`vertical-text`,`alias`,`copy`,`no-drop`,`grab`,`grabbing`,`all-scroll`,`col-resize`,`row-resize`,`n-resize`,`e-resize`,`s-resize`,`w-resize`,`ne-resize`,`nw-resize`,`se-resize`,`sw-resize`,`ew-resize`,`ns-resize`,`nesw-resize`,`nwse-resize`,`zoom-in`,`zoom-out`,H,V]}],"field-sizing":[{"field-sizing":[`fixed`,`content`]}],"pointer-events":[{"pointer-events":[`auto`,`none`]}],resize:[{resize:[`none`,``,`y`,`x`]}],"scroll-behavior":[{scroll:[`auto`,`smooth`]}],"scroll-m":[{"scroll-m":w()}],"scroll-mx":[{"scroll-mx":w()}],"scroll-my":[{"scroll-my":w()}],"scroll-ms":[{"scroll-ms":w()}],"scroll-me":[{"scroll-me":w()}],"scroll-mbs":[{"scroll-mbs":w()}],"scroll-mbe":[{"scroll-mbe":w()}],"scroll-mt":[{"scroll-mt":w()}],"scroll-mr":[{"scroll-mr":w()}],"scroll-mb":[{"scroll-mb":w()}],"scroll-ml":[{"scroll-ml":w()}],"scroll-p":[{"scroll-p":w()}],"scroll-px":[{"scroll-px":w()}],"scroll-py":[{"scroll-py":w()}],"scroll-ps":[{"scroll-ps":w()}],"scroll-pe":[{"scroll-pe":w()}],"scroll-pbs":[{"scroll-pbs":w()}],"scroll-pbe":[{"scroll-pbe":w()}],"scroll-pt":[{"scroll-pt":w()}],"scroll-pr":[{"scroll-pr":w()}],"scroll-pb":[{"scroll-pb":w()}],"scroll-pl":[{"scroll-pl":w()}],"snap-align":[{snap:[`start`,`end`,`center`,`align-none`]}],"snap-stop":[{snap:[`normal`,`always`]}],"snap-type":[{snap:[`none`,`x`,`y`,`both`]}],"snap-strictness":[{snap:[`mandatory`,`proximity`]}],touch:[{touch:[`auto`,`none`,`manipulation`]}],"touch-x":[{"touch-pan":[`x`,`left`,`right`]}],"touch-y":[{"touch-pan":[`y`,`up`,`down`]}],"touch-pz":[`touch-pinch-zoom`],select:[{select:[`none`,`text`,`all`,`auto`]}],"will-change":[{"will-change":[`auto`,`scroll`,`contents`,`transform`,H,V]}],fill:[{fill:[`none`,...F()]}],"stroke-w":[{stroke:[or,wr,_r,vr]}],stroke:[{stroke:[`none`,...F()]}],"forced-color-adjust":[{"forced-color-adjust":[`auto`,`none`]}]},conflictingClassGroups:{overflow:[`overflow-x`,`overflow-y`],overscroll:[`overscroll-x`,`overscroll-y`],inset:[`inset-x`,`inset-y`,`inset-bs`,`inset-be`,`start`,`end`,`top`,`right`,`bottom`,`left`],"inset-x":[`right`,`left`],"inset-y":[`top`,`bottom`],flex:[`basis`,`grow`,`shrink`],gap:[`gap-x`,`gap-y`],p:[`px`,`py`,`ps`,`pe`,`pbs`,`pbe`,`pt`,`pr`,`pb`,`pl`],px:[`pr`,`pl`],py:[`pt`,`pb`],m:[`mx`,`my`,`ms`,`me`,`mbs`,`mbe`,`mt`,`mr`,`mb`,`ml`],mx:[`mr`,`ml`],my:[`mt`,`mb`],size:[`w`,`h`],"font-size":[`leading`],"fvn-normal":[`fvn-ordinal`,`fvn-slashed-zero`,`fvn-figure`,`fvn-spacing`,`fvn-fraction`],"fvn-ordinal":[`fvn-normal`],"fvn-slashed-zero":[`fvn-normal`],"fvn-figure":[`fvn-normal`],"fvn-spacing":[`fvn-normal`],"fvn-fraction":[`fvn-normal`],"line-clamp":[`display`,`overflow`],rounded:[`rounded-s`,`rounded-e`,`rounded-t`,`rounded-r`,`rounded-b`,`rounded-l`,`rounded-ss`,`rounded-se`,`rounded-ee`,`rounded-es`,`rounded-tl`,`rounded-tr`,`rounded-br`,`rounded-bl`],"rounded-s":[`rounded-ss`,`rounded-es`],"rounded-e":[`rounded-se`,`rounded-ee`],"rounded-t":[`rounded-tl`,`rounded-tr`],"rounded-r":[`rounded-tr`,`rounded-br`],"rounded-b":[`rounded-br`,`rounded-bl`],"rounded-l":[`rounded-tl`,`rounded-bl`],"border-spacing":[`border-spacing-x`,`border-spacing-y`],"border-w":[`border-w-x`,`border-w-y`,`border-w-s`,`border-w-e`,`border-w-bs`,`border-w-be`,`border-w-t`,`border-w-r`,`border-w-b`,`border-w-l`],"border-w-x":[`border-w-r`,`border-w-l`],"border-w-y":[`border-w-t`,`border-w-b`],"border-color":[`border-color-x`,`border-color-y`,`border-color-s`,`border-color-e`,`border-color-bs`,`border-color-be`,`border-color-t`,`border-color-r`,`border-color-b`,`border-color-l`],"border-color-x":[`border-color-r`,`border-color-l`],"border-color-y":[`border-color-t`,`border-color-b`],translate:[`translate-x`,`translate-y`,`translate-none`],"translate-none":[`translate`,`translate-x`,`translate-y`,`translate-z`],"scroll-m":[`scroll-mx`,`scroll-my`,`scroll-ms`,`scroll-me`,`scroll-mbs`,`scroll-mbe`,`scroll-mt`,`scroll-mr`,`scroll-mb`,`scroll-ml`],"scroll-mx":[`scroll-mr`,`scroll-ml`],"scroll-my":[`scroll-mt`,`scroll-mb`],"scroll-p":[`scroll-px`,`scroll-py`,`scroll-ps`,`scroll-pe`,`scroll-pbs`,`scroll-pbe`,`scroll-pt`,`scroll-pr`,`scroll-pb`,`scroll-pl`],"scroll-px":[`scroll-pr`,`scroll-pl`],"scroll-py":[`scroll-pt`,`scroll-pb`],touch:[`touch-x`,`touch-y`,`touch-pz`],"touch-x":[`touch`],"touch-y":[`touch`],"touch-pz":[`touch`]},conflictingClassGroupModifiers:{"font-size":[`leading`]},orderSensitiveModifiers:[`*`,`**`,`after`,`backdrop`,`before`,`details-content`,`file`,`first-letter`,`first-line`,`marker`,`placeholder`,`selection`]}},Hr=(e,{cacheSize:t,prefix:n,experimentalParseClassName:r,extend:i={},override:a={}})=>(Ur(e,`cacheSize`,t),Ur(e,`prefix`,n),Ur(e,`experimentalParseClassName`,r),Wr(e.theme,a.theme),Wr(e.classGroups,a.classGroups),Wr(e.conflictingClassGroups,a.conflictingClassGroups),Wr(e.conflictingClassGroupModifiers,a.conflictingClassGroupModifiers),Ur(e,`orderSensitiveModifiers`,a.orderSensitiveModifiers),Gr(e.theme,i.theme),Gr(e.classGroups,i.classGroups),Gr(e.conflictingClassGroups,i.conflictingClassGroups),Gr(e.conflictingClassGroupModifiers,i.conflictingClassGroupModifiers),Kr(e,i,`orderSensitiveModifiers`),e),Ur=(e,t,n)=>{n!==void 0&&(e[t]=n)},Wr=(e,t)=>{if(t)for(let n in t)Ur(e,n,t[n])},Gr=(e,t)=>{if(t)for(let n in t)Kr(e,t,n)},Kr=(e,t,n)=>{let r=t[n];r!==void 0&&(e[n]=e[n]?e[n].concat(r):r)},qr=((e,...t)=>typeof e==`function`?Jn(Vr,e,...t):Jn(()=>Hr(Vr(),e),...t))({extend:{theme:{text:[`9`,`10`,`11`,`12`,`13`,`14`,`15`,`17`,`20`,`22`,`24`,`28`,`36`]}}});function Jr(...e){return qr(mn(e))}async function Yr(e){if(!navigator.clipboard)throw Error(`Clipboard API is not available in this browser context`);await navigator.clipboard.writeText(e)}var Xr=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),Zr=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),Qr=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),$r=e=>{let t=Qr(e);return t.charAt(0).toUpperCase()+t.slice(1)},ei={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},ti=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},ni=(0,z.forwardRef)(({color:e=`currentColor`,size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:i=``,children:a,iconNode:o,...s},c)=>(0,z.createElement)(`svg`,{ref:c,...ei,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:Xr(`lucide`,i),...!a&&!ti(s)&&{"aria-hidden":`true`},...s},[...o.map(([e,t])=>(0,z.createElement)(e,t)),...Array.isArray(a)?a:[a]])),ri=(e,t)=>{let n=(0,z.forwardRef)(({className:n,...r},i)=>(0,z.createElement)(ni,{ref:i,iconNode:t,className:Xr(`lucide-${Zr($r(e))}`,`lucide-${e}`,n),...r}));return n.displayName=$r(e),n},ii=ri(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),ai=ri(`box`,[[`path`,{d:`M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z`,key:`hh9hay`}],[`path`,{d:`m3.3 7 8.7 5 8.7-5`,key:`g66t2b`}],[`path`,{d:`M12 22V12`,key:`d0xqtd`}]]),oi=ri(`briefcase-business`,[[`path`,{d:`M12 12h.01`,key:`1mp3jc`}],[`path`,{d:`M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2`,key:`1ksdt3`}],[`path`,{d:`M22 13a18.15 18.15 0 0 1-20 0`,key:`12hx5q`}],[`rect`,{width:`20`,height:`14`,x:`2`,y:`6`,rx:`2`,key:`i6l2r4`}]]),si=ri(`building-2`,[[`path`,{d:`M10 12h4`,key:`a56b0p`}],[`path`,{d:`M10 8h4`,key:`1sr2af`}],[`path`,{d:`M14 21v-3a2 2 0 0 0-4 0v3`,key:`1rgiei`}],[`path`,{d:`M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2`,key:`secmi2`}],[`path`,{d:`M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16`,key:`16ra0t`}]]),ci=ri(`calculator`,[[`rect`,{width:`16`,height:`20`,x:`4`,y:`2`,rx:`2`,key:`1nb95v`}],[`line`,{x1:`8`,x2:`16`,y1:`6`,y2:`6`,key:`x4nwl0`}],[`line`,{x1:`16`,x2:`16`,y1:`14`,y2:`18`,key:`wjye3r`}],[`path`,{d:`M16 10h.01`,key:`1m94wz`}],[`path`,{d:`M12 10h.01`,key:`1nrarc`}],[`path`,{d:`M8 10h.01`,key:`19clt8`}],[`path`,{d:`M12 14h.01`,key:`1etili`}],[`path`,{d:`M8 14h.01`,key:`6423bh`}],[`path`,{d:`M12 18h.01`,key:`mhygvu`}],[`path`,{d:`M8 18h.01`,key:`lrp35t`}]]),li=ri(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),ui=ri(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),di=ri(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),fi=ri(`chevron-up`,[[`path`,{d:`m18 15-6-6-6 6`,key:`153udz`}]]),pi=ri(`clock`,[[`path`,{d:`M12 6v6l4 2`,key:`mmk7yg`}],[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),mi=ri(`cloud`,[[`path`,{d:`M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z`,key:`p7xjir`}]]),hi=ri(`code`,[[`path`,{d:`m16 18 6-6-6-6`,key:`eg8j8`}],[`path`,{d:`m8 6-6 6 6 6`,key:`ppft3o`}]]),gi=ri(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),_i=ri(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),vi=ri(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),yi=ri(`eye-off`,[[`path`,{d:`M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49`,key:`ct8e1f`}],[`path`,{d:`M14.084 14.158a3 3 0 0 1-4.242-4.242`,key:`151rxh`}],[`path`,{d:`M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143`,key:`13bj9a`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}]]),bi=ri(`eye`,[[`path`,{d:`M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0`,key:`1nclc0`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),xi=ri(`globe`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20`,key:`13o1zl`}],[`path`,{d:`M2 12h20`,key:`9i4pu4`}]]),Si=ri(`info`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 16v-4`,key:`1dtifu`}],[`path`,{d:`M12 8h.01`,key:`e9boi3`}]]),Ci=ri(`key-round`,[[`path`,{d:`M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z`,key:`1s6t7t`}],[`circle`,{cx:`16.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`w0ekpg`}]]),wi=ri(`loader-circle`,[[`path`,{d:`M21 12a9 9 0 1 1-6.219-8.56`,key:`13zald`}]]),Ti=ri(`lock`,[[`rect`,{width:`18`,height:`11`,x:`3`,y:`11`,rx:`2`,ry:`2`,key:`1w4ew1`}],[`path`,{d:`M7 11V7a5 5 0 0 1 10 0v4`,key:`fwvmzm`}]]),Ei=ri(`mail`,[[`path`,{d:`m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7`,key:`132q7q`}],[`rect`,{x:`2`,y:`4`,width:`20`,height:`16`,rx:`2`,key:`izxlao`}]]),Di=ri(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),Oi=ri(`sparkles`,[[`path`,{d:`M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z`,key:`1s2grr`}],[`path`,{d:`M20 2v4`,key:`1rf3ol`}],[`path`,{d:`M22 4h-4`,key:`gwowj6`}],[`circle`,{cx:`4`,cy:`20`,r:`2`,key:`6kqj1y`}]]),ki=ri(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),Ai=ri(`wifi-off`,[[`path`,{d:`M12 20h.01`,key:`zekei9`}],[`path`,{d:`M8.5 16.429a5 5 0 0 1 7 0`,key:`1bycff`}],[`path`,{d:`M5 12.859a10 10 0 0 1 5.17-2.69`,key:`1dl1wf`}],[`path`,{d:`M19 12.859a10 10 0 0 0-2.007-1.523`,key:`4k23kn`}],[`path`,{d:`M2 8.82a15 15 0 0 1 4.177-2.643`,key:`1grhjp`}],[`path`,{d:`M22 8.82a15 15 0 0 0-11.288-3.764`,key:`z3jwby`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}]]),ji=ri(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),Mi=_n(`inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-lg text-12 font-medium transition-all duration-200 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-35 [&_svg]:pointer-events-none [&_svg]:size-3 [&_svg]:shrink-0 cursor-pointer`,{variants:{variant:{default:`border border-hairline bg-overlay text-foreground hover:border-hairline-strong hover:bg-overlay-strong`,destructive:`border border-destructive/30 bg-destructive/10 text-destructive hover:border-destructive/50 hover:bg-destructive/15`,outline:`border border-hairline bg-transparent text-muted-foreground hover:border-hairline-strong hover:text-foreground`,secondary:`border border-hairline bg-overlay text-muted-foreground hover:border-hairline-strong hover:text-foreground`,ghost:`text-muted-foreground hover:bg-overlay hover:text-foreground`,link:`text-nyx-secondary-400 underline-offset-4 hover:underline`,primary:`nyx-gradient-vivid text-white shadow-[0_0_12px_rgba(90,42,241,0.25)] hover:shadow-[0_0_18px_rgba(90,42,241,0.35)] hover:brightness-110`},size:{default:`text-control h-8 px-3`,sm:`text-control h-7 px-2.5`,lg:`text-control h-9 px-4`,icon:`h-8 w-8`}},defaultVariants:{variant:`default`,size:`default`}});function Ni({children:e,className:t,variant:n}){return(0,B.jsx)(`span`,{className:Jr(`flex h-[18px] w-[18px] items-center justify-center rounded-[4px]`,n===`destructive`?`border border-destructive/20 bg-destructive/10`:n===`primary`?`border border-white/20 bg-white/10`:`border border-hairline bg-overlay`,t),children:e})}var Pi=z.forwardRef(({className:e,variant:t,size:n,asChild:r=!1,isLoading:i=!1,children:a,disabled:o,...s},c)=>(0,B.jsx)(r?sn:`button`,{className:Jr(Mi({variant:t,size:n,className:e})),ref:c,disabled:o??i,...s,children:i?(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(wi,{className:`animate-spin`}),a]}):a}));Pi.displayName=`Button`;var Fi=u(g(),1),Ii=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=on(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),Li=`Label`,Ri=z.forwardRef((e,t)=>(0,B.jsx)(Ii.label,{...e,ref:t,onMouseDown:t=>{t.target.closest(`button, input, select, textarea`)||(e.onMouseDown?.(t),!t.defaultPrevented&&t.detail>1&&t.preventDefault())}}));Ri.displayName=Li;var zi=Ri,Bi=_n(`text-xs font-medium leading-none text-muted-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70`),Vi=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(zi,{ref:n,className:Jr(Bi(),e),...t}));Vi.displayName=zi.displayName;function Hi({title:e,description:t,secret:n,secondarySecret:r,ackButtonLabel:i,onAcknowledge:a,isAcknowledging:o}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-6`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-amber-600 dark:text-amber-500`,children:[(0,B.jsx)(Ti,{className:`h-4 w-4`}),(0,B.jsx)(`span`,{className:`text-12 font-medium`,children:`Shown once — save it now`})]}),(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:e}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:t})]}),(0,B.jsx)(Wi,{label:`Secret`,value:n}),r?(0,B.jsx)(Wi,{label:r.label,value:r.value}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(Pi,{variant:`primary`,onClick:a,disabled:o,className:`w-full`,children:o?`Notifying CLI...`:i}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`This value will not be shown again. Confirm you have saved it before closing this tab.`}),(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[(0,B.jsx)(`strong`,{children:`Lost it?`}),` If you don't copy this now, you'll need to rotate the credential to get a new one. (Old value stops working immediately on rotate.)`]})]})]})}function Ui({codes:e,onAcknowledged:t}){let[n,r]=(0,z.useState)(!1),[i,a]=(0,z.useState)(!1);async function o(){try{await navigator.clipboard.writeText(e.join(` `)),a(!0),window.setTimeout(()=>{a(!1)},2e3)}catch{}}function s(){let t=new Blob([`NyxID MFA recovery codes — store these securely. @@ -67,15 +67,15 @@ `)}t.write(`payload.value = newResult;`),t.write(`return payload;`);let s=t.compile();return(t,n)=>s(e,t,n)},a,o=Ro,s=!Co.jitless,c=s&&zo.value,l=t.catchall,u;e._zod.parse=(d,f)=>{u??=r.value;let p=d.value;return o(p)?s&&c&&f?.async===!1&&f.jitless!==!0?(a||=i(t.shape),d=a(d,f),l?ol([],p,d,f,u,e):d):n(d,f):(d.issues.push({expected:`object`,code:`invalid_type`,input:p,inst:e}),d)}});function ll(e,t,n,r){for(let n of e)if(n.issues.length===0)return t.value=n.value,t;let i=e.filter(e=>!es(e));return i.length===1?(t.value=i[0].value,i[0]):(t.issues.push({code:`invalid_union`,input:t.value,inst:n,errors:e.map(e=>e.issues.map(e=>rs(e,r,wo())))}),t)}var ul=G(`$ZodUnion`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`optin`,()=>t.options.some(e=>e._zod.optin===`optional`)?`optional`:void 0),Mo(e._zod,`optout`,()=>t.options.some(e=>e._zod.optout===`optional`)?`optional`:void 0),Mo(e._zod,`values`,()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),Mo(e._zod,`pattern`,()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>ko(e.source)).join(`|`)})$`)}});let n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(i,a)=>{if(n)return r(i,a);let o=!1,s=[];for(let e of t.options){let t=e._zod.run({value:i.value,issues:[]},a);if(t instanceof Promise)s.push(t),o=!0;else{if(t.issues.length===0)return t;s.push(t)}}return o?Promise.all(s).then(t=>ll(t,i,e,a)):ll(s,i,e,a)}}),dl=G(`$ZodDiscriminatedUnion`,(e,t)=>{t.inclusive=!1,ul.init(e,t);let n=e._zod.parse;Mo(e._zod,`propValues`,()=>{let e={};for(let n of t.options){let r=n._zod.propValues;if(!r||Object.keys(r).length===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`);for(let[t,n]of Object.entries(r)){e[t]||(e[t]=new Set);for(let r of n)e[t].add(r)}}return e});let r=Do(()=>{let e=t.options,n=new Map;for(let r of e){let e=r._zod.propValues?.[t.discriminator];if(!e||e.size===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`);for(let t of e){if(n.has(t))throw Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,r)}}return n});e._zod.parse=(i,a)=>{let o=i.value;if(!Ro(o))return i.issues.push({code:`invalid_type`,expected:`object`,input:o,inst:e}),i;let s=r.value.get(o?.[t.discriminator]);return s?s._zod.run(i,a):t.unionFallback?n(i,a):(i.issues.push({code:`invalid_union`,errors:[],note:`No matching discriminator`,discriminator:t.discriminator,input:o,path:[t.discriminator],inst:e}),i)}}),fl=G(`$ZodIntersection`,(e,t)=>{Sc.init(e,t),e._zod.parse=(e,n)=>{let r=e.value,i=t.left._zod.run({value:r,issues:[]},n),a=t.right._zod.run({value:r,issues:[]},n);return i instanceof Promise||a instanceof Promise?Promise.all([i,a]).then(([t,n])=>ml(e,t,n)):ml(e,i,a)}});function pl(e,t){if(e===t||e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Bo(e)&&Bo(t)){let n=Object.keys(t),r=Object.keys(e).filter(e=>n.indexOf(e)!==-1),i={...e,...t};for(let n of r){let r=pl(e[n],t[n]);if(!r.valid)return{valid:!1,mergeErrorPath:[n,...r.mergeErrorPath]};i[n]=r.data}return{valid:!0,data:i}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};let n=[];for(let r=0;re.l&&e.r).map(([e])=>e);if(a.length&&i&&e.issues.push({...i,keys:a}),es(e))return e;let o=pl(t.value,n.value);if(!o.valid)throw Error(`Unmergable intersection. Error path: ${JSON.stringify(o.mergeErrorPath)}`);return e.value=o.data,e}var hl=G(`$ZodRecord`,(e,t)=>{Sc.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Bo(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...ts(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...ts(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&$s.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>rs(e,r,wo())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...ts(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...ts(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}}),gl=G(`$ZodEnum`,(e,t)=>{Sc.init(e,t);let n=To(t.entries),r=new Set(n);e._zod.values=r,e._zod.pattern=RegExp(`^(${n.filter(e=>Ho.has(typeof e)).map(e=>typeof e==`string`?Uo(e):e.toString()).join(`|`)})$`),e._zod.parse=(t,i)=>{let a=t.value;return r.has(a)||t.issues.push({code:`invalid_value`,values:n,input:a,inst:e}),t}}),_l=G(`$ZodLiteral`,(e,t)=>{if(Sc.init(e,t),t.values.length===0)throw Error(`Cannot create literal schema with no valid values`);let n=new Set(t.values);e._zod.values=n,e._zod.pattern=RegExp(`^(${t.values.map(e=>typeof e==`string`?Uo(e):e?Uo(e.toString()):String(e)).join(`|`)})$`),e._zod.parse=(r,i)=>{let a=r.value;return n.has(a)||r.issues.push({code:`invalid_value`,values:t.values,input:a,inst:e}),r}}),vl=G(`$ZodTransform`,(e,t)=>{Sc.init(e,t),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new So(e.constructor.name);let i=t.transform(n.value,n);if(r.async)return(i instanceof Promise?i:Promise.resolve(i)).then(e=>(n.value=e,n));if(i instanceof Promise)throw new xo;return n.value=i,n}});function yl(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}var bl=G(`$ZodOptional`,(e,t)=>{Sc.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,Mo(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),Mo(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ko(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>yl(t,e.value)):yl(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}}),xl=G(`$ZodExactOptional`,(e,t)=>{bl.init(e,t),Mo(e._zod,`values`,()=>t.innerType._zod.values),Mo(e._zod,`pattern`,()=>t.innerType._zod.pattern),e._zod.parse=(e,n)=>t.innerType._zod.run(e,n)}),Sl=G(`$ZodNullable`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`optin`,()=>t.innerType._zod.optin),Mo(e._zod,`optout`,()=>t.innerType._zod.optout),Mo(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ko(e.source)}|null)$`):void 0}),Mo(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,n)=>e.value===null?e:t.innerType._zod.run(e,n)}),Cl=G(`$ZodDefault`,(e,t)=>{Sc.init(e,t),e._zod.optin=`optional`,Mo(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);if(e.value===void 0)return e.value=t.defaultValue,e;let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(e=>wl(e,t)):wl(r,t)}});function wl(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}var Tl=G(`$ZodPrefault`,(e,t)=>{Sc.init(e,t),e._zod.optin=`optional`,Mo(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>(n.direction===`backward`||e.value===void 0&&(e.value=t.defaultValue),t.innerType._zod.run(e,n))}),El=G(`$ZodNonOptional`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`values`,()=>{let e=t.innerType._zod.values;return e?new Set([...e].filter(e=>e!==void 0)):void 0}),e._zod.parse=(n,r)=>{let i=t.innerType._zod.run(n,r);return i instanceof Promise?i.then(t=>Dl(t,e)):Dl(i,e)}});function Dl(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:`invalid_type`,expected:`nonoptional`,input:e.value,inst:t}),e}var Ol=G(`$ZodCatch`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`optin`,()=>t.innerType._zod.optin),Mo(e._zod,`optout`,()=>t.innerType._zod.optout),Mo(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(r=>(e.value=r.value,r.issues.length&&(e.value=t.catchValue({...e,error:{issues:r.issues.map(e=>rs(e,n,wo()))},input:e.value}),e.issues=[]),e)):(e.value=r.value,r.issues.length&&(e.value=t.catchValue({...e,error:{issues:r.issues.map(e=>rs(e,n,wo()))},input:e.value}),e.issues=[]),e)}}),kl=G(`$ZodPipe`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`values`,()=>t.in._zod.values),Mo(e._zod,`optin`,()=>t.in._zod.optin),Mo(e._zod,`optout`,()=>t.out._zod.optout),Mo(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if(n.direction===`backward`){let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>Al(e,t.in,n)):Al(r,t.in,n)}let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>Al(e,t.out,n)):Al(r,t.out,n)}});function Al(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}var jl=G(`$ZodReadonly`,(e,t)=>{Sc.init(e,t),Mo(e._zod,`propValues`,()=>t.innerType._zod.propValues),Mo(e._zod,`values`,()=>t.innerType._zod.values),Mo(e._zod,`optin`,()=>t.innerType?._zod?.optin),Mo(e._zod,`optout`,()=>t.innerType?._zod?.optout),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(Ml):Ml(r)}});function Ml(e){return e.value=Object.freeze(e.value),e}var Nl=G(`$ZodCustom`,(e,t)=>{rc.init(e,t),Sc.init(e,t),e._zod.parse=(e,t)=>e,e._zod.check=n=>{let r=n.value,i=t.fn(r);if(i instanceof Promise)return i.then(t=>Pl(t,n,r,e));Pl(i,n,r,e)}});function Pl(e,t,n,r){if(!e){let e={code:`custom`,input:n,inst:r,path:[...r._zod.def.path??[]],continue:!r._zod.def.abort};r._zod.def.params&&(e.params=r._zod.def.params),t.issues.push(as(e))}}var Fl,Il=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function Ll(){return new Il}(Fl=globalThis).__zod_globalRegistry??(Fl.__zod_globalRegistry=Ll());var Rl=globalThis.__zod_globalRegistry;function zl(e,t){return new e({type:`string`,...K(t)})}function Bl(e,t){return new e({type:`string`,format:`email`,check:`string_format`,abort:!1,...K(t)})}function Vl(e,t){return new e({type:`string`,format:`guid`,check:`string_format`,abort:!1,...K(t)})}function Hl(e,t){return new e({type:`string`,format:`uuid`,check:`string_format`,abort:!1,...K(t)})}function Ul(e,t){return new e({type:`string`,format:`uuid`,check:`string_format`,abort:!1,version:`v4`,...K(t)})}function Wl(e,t){return new e({type:`string`,format:`uuid`,check:`string_format`,abort:!1,version:`v6`,...K(t)})}function Gl(e,t){return new e({type:`string`,format:`uuid`,check:`string_format`,abort:!1,version:`v7`,...K(t)})}function Kl(e,t){return new e({type:`string`,format:`url`,check:`string_format`,abort:!1,...K(t)})}function ql(e,t){return new e({type:`string`,format:`emoji`,check:`string_format`,abort:!1,...K(t)})}function Jl(e,t){return new e({type:`string`,format:`nanoid`,check:`string_format`,abort:!1,...K(t)})}function Yl(e,t){return new e({type:`string`,format:`cuid`,check:`string_format`,abort:!1,...K(t)})}function Xl(e,t){return new e({type:`string`,format:`cuid2`,check:`string_format`,abort:!1,...K(t)})}function Zl(e,t){return new e({type:`string`,format:`ulid`,check:`string_format`,abort:!1,...K(t)})}function Ql(e,t){return new e({type:`string`,format:`xid`,check:`string_format`,abort:!1,...K(t)})}function $l(e,t){return new e({type:`string`,format:`ksuid`,check:`string_format`,abort:!1,...K(t)})}function eu(e,t){return new e({type:`string`,format:`ipv4`,check:`string_format`,abort:!1,...K(t)})}function tu(e,t){return new e({type:`string`,format:`ipv6`,check:`string_format`,abort:!1,...K(t)})}function nu(e,t){return new e({type:`string`,format:`cidrv4`,check:`string_format`,abort:!1,...K(t)})}function ru(e,t){return new e({type:`string`,format:`cidrv6`,check:`string_format`,abort:!1,...K(t)})}function iu(e,t){return new e({type:`string`,format:`base64`,check:`string_format`,abort:!1,...K(t)})}function au(e,t){return new e({type:`string`,format:`base64url`,check:`string_format`,abort:!1,...K(t)})}function ou(e,t){return new e({type:`string`,format:`e164`,check:`string_format`,abort:!1,...K(t)})}function su(e,t){return new e({type:`string`,format:`jwt`,check:`string_format`,abort:!1,...K(t)})}function cu(e,t){return new e({type:`string`,format:`datetime`,check:`string_format`,offset:!1,local:!1,precision:null,...K(t)})}function lu(e,t){return new e({type:`string`,format:`date`,check:`string_format`,...K(t)})}function uu(e,t){return new e({type:`string`,format:`time`,check:`string_format`,precision:null,...K(t)})}function du(e,t){return new e({type:`string`,format:`duration`,check:`string_format`,...K(t)})}function fu(e,t){return new e({type:`number`,checks:[],...K(t)})}function pu(e,t){return new e({type:`number`,check:`number_format`,abort:!1,format:`safeint`,...K(t)})}function mu(e,t){return new e({type:`boolean`,...K(t)})}function hu(e){return new e({type:`unknown`})}function gu(e,t){return new e({type:`never`,...K(t)})}function _u(e,t){return new ac({check:`less_than`,...K(t),value:e,inclusive:!1})}function vu(e,t){return new ac({check:`less_than`,...K(t),value:e,inclusive:!0})}function yu(e,t){return new oc({check:`greater_than`,...K(t),value:e,inclusive:!1})}function bu(e,t){return new oc({check:`greater_than`,...K(t),value:e,inclusive:!0})}function xu(e,t){return new sc({check:`multiple_of`,...K(t),value:e})}function Su(e,t){return new lc({check:`max_length`,...K(t),maximum:e})}function Cu(e,t){return new uc({check:`min_length`,...K(t),minimum:e})}function wu(e,t){return new dc({check:`length_equals`,...K(t),length:e})}function Tu(e,t){return new pc({check:`string_format`,format:`regex`,...K(t),pattern:e})}function Eu(e){return new mc({check:`string_format`,format:`lowercase`,...K(e)})}function Du(e){return new hc({check:`string_format`,format:`uppercase`,...K(e)})}function Ou(e,t){return new gc({check:`string_format`,format:`includes`,...K(t),includes:e})}function ku(e,t){return new _c({check:`string_format`,format:`starts_with`,...K(t),prefix:e})}function Au(e,t){return new vc({check:`string_format`,format:`ends_with`,...K(t),suffix:e})}function ju(e){return new yc({check:`overwrite`,tx:e})}function Mu(e){return ju(t=>t.normalize(e))}function Nu(){return ju(e=>e.trim())}function Pu(){return ju(e=>e.toLowerCase())}function Fu(){return ju(e=>e.toUpperCase())}function Iu(){return ju(e=>Io(e))}function Lu(e,t,n){return new e({type:`array`,element:t,...K(n)})}function Ru(e,t,n){return new e({type:`custom`,check:`custom`,fn:t,...K(n)})}function zu(e){let t=Bu(n=>(n.addIssue=e=>{if(typeof e==`string`)n.issues.push(as(e,n.value,t._zod.def));else{let r=e;r.fatal&&(r.continue=!1),r.code??=`custom`,r.input??=n.value,r.inst??=t,r.continue??=!t._zod.def.abort,n.issues.push(as(r))}},e(n.value,n)));return t}function Bu(e,t){let n=new rc({check:`custom`,...K(t)});return n._zod.check=e,n}function Vu(e){let t=e?.target??`draft-2020-12`;return t===`draft-4`&&(t=`draft-04`),t===`draft-7`&&(t=`draft-07`),{processors:e.processors??{},metadataRegistry:e?.metadata??Rl,target:t,unrepresentable:e?.unrepresentable??`throw`,override:e?.override??(()=>{}),io:e?.io??`output`,counter:0,seen:new Map,cycles:e?.cycles??`ref`,reused:e?.reused??`inline`,external:e?.external??void 0}}function Hu(e,t,n={path:[],schemaPath:[]}){var r;let i=e._zod.def,a=t.seen.get(e);if(a)return a.count++,n.schemaPath.includes(e)&&(a.cycle=n.path),a.schema;let o={schema:{},count:1,cycle:void 0,path:n.path};t.seen.set(e,o);let s=e._zod.toJSONSchema?.();if(s)o.schema=s;else{let r={...n,schemaPath:[...n.schemaPath,e],path:n.path};if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,o.schema,r);else{let n=o.schema,a=t.processors[i.type];if(!a)throw Error(`[toJSONSchema]: Non-representable type encountered: ${i.type}`);a(e,t,n,r)}let a=e._zod.parent;a&&(o.ref||=a,Hu(a,t,r),t.seen.get(a).isParent=!0)}let c=t.metadataRegistry.get(e);return c&&Object.assign(o.schema,c),t.io===`input`&&Gu(e)&&(delete o.schema.examples,delete o.schema.default),t.io===`input`&&o.schema._prefault&&((r=o.schema).default??(r.default=o.schema._prefault)),delete o.schema._prefault,t.seen.get(e).schema}function Uu(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=new Map;for(let t of e.seen.entries()){let n=e.metadataRegistry.get(t[0])?.id;if(n){let e=r.get(n);if(e&&e!==t[0])throw Error(`Duplicate schema id "${n}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);r.set(n,t[0])}}let i=t=>{let r=e.target===`draft-2020-12`?`$defs`:`definitions`;if(e.external){let n=e.external.registry.get(t[0])?.id,i=e.external.uri??(e=>e);if(n)return{ref:i(n)};let a=t[1].defId??t[1].schema.id??`schema${e.counter++}`;return t[1].defId=a,{defId:a,ref:`${i(`__shared`)}#/${r}/${a}`}}if(t[1]===n)return{ref:`#`};let i=`#/${r}/`,a=t[1].schema.id??`__schema${e.counter++}`;return{defId:a,ref:i+a}},a=e=>{if(e[1].schema.$ref)return;let t=e[1],{ref:n,defId:r}=i(e);t.def={...t.schema},r&&(t.defId=r);let a=t.schema;for(let e in a)delete a[e];a.$ref=n};if(e.cycles===`throw`)for(let t of e.seen.entries()){let e=t[1];if(e.cycle)throw Error(`Cycle detected: #/${e.cycle?.join(`/`)}/ -Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(let n of e.seen.entries()){let r=n[1];if(t===n[0]){a(n);continue}if(e.external){let r=e.external.registry.get(n[0])?.id;if(t!==n[0]&&r){a(n);continue}}if(e.metadataRegistry.get(n[0])?.id){a(n);continue}if(r.cycle){a(n);continue}if(r.count>1&&e.reused===`ref`){a(n);continue}}}function Wu(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=t=>{let n=e.seen.get(t);if(n.ref===null)return;let i=n.def??n.schema,a={...i},o=n.ref;if(n.ref=null,o){r(o);let n=e.seen.get(o),s=n.schema;if(s.$ref&&(e.target===`draft-07`||e.target===`draft-04`||e.target===`openapi-3.0`)?(i.allOf=i.allOf??[],i.allOf.push(s)):Object.assign(i,s),Object.assign(i,a),t._zod.parent===o)for(let e in i)e===`$ref`||e===`allOf`||e in a||delete i[e];if(s.$ref&&n.def)for(let e in i)e===`$ref`||e===`allOf`||e in n.def&&JSON.stringify(i[e])===JSON.stringify(n.def[e])&&delete i[e]}let s=t._zod.parent;if(s&&s!==o){r(s);let t=e.seen.get(s);if(t?.schema.$ref&&(i.$ref=t.schema.$ref,t.def))for(let e in i)e===`$ref`||e===`allOf`||e in t.def&&JSON.stringify(i[e])===JSON.stringify(t.def[e])&&delete i[e]}e.override({zodSchema:t,jsonSchema:i,path:n.path??[]})};for(let t of[...e.seen.entries()].reverse())r(t[0]);let i={};if(e.target===`draft-2020-12`?i.$schema=`https://json-schema.org/draft/2020-12/schema`:e.target===`draft-07`?i.$schema=`http://json-schema.org/draft-07/schema#`:e.target===`draft-04`?i.$schema=`http://json-schema.org/draft-04/schema#`:e.target,e.external?.uri){let n=e.external.registry.get(t)?.id;if(!n)throw Error("Schema is missing an `id` property");i.$id=e.external.uri(n)}Object.assign(i,n.def??n.schema);let a=e.external?.defs??{};for(let t of e.seen.entries()){let e=t[1];e.def&&e.defId&&(a[e.defId]=e.def)}e.external||Object.keys(a).length>0&&(e.target===`draft-2020-12`?i.$defs=a:i.definitions=a);try{let n=JSON.parse(JSON.stringify(i));return Object.defineProperty(n,`~standard`,{value:{...t[`~standard`],jsonSchema:{input:qu(t,`input`,e.processors),output:qu(t,`output`,e.processors)}},enumerable:!1,writable:!1}),n}catch{throw Error(`Error converting schema to JSON.`)}}function Gu(e,t){let n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);let r=e._zod.def;if(r.type===`transform`)return!0;if(r.type===`array`)return Gu(r.element,n);if(r.type===`set`)return Gu(r.valueType,n);if(r.type===`lazy`)return Gu(r.getter(),n);if(r.type===`promise`||r.type===`optional`||r.type===`nonoptional`||r.type===`nullable`||r.type===`readonly`||r.type===`default`||r.type===`prefault`)return Gu(r.innerType,n);if(r.type===`intersection`)return Gu(r.left,n)||Gu(r.right,n);if(r.type===`record`||r.type===`map`)return Gu(r.keyType,n)||Gu(r.valueType,n);if(r.type===`pipe`)return Gu(r.in,n)||Gu(r.out,n);if(r.type===`object`){for(let e in r.shape)if(Gu(r.shape[e],n))return!0;return!1}if(r.type===`union`){for(let e of r.options)if(Gu(e,n))return!0;return!1}if(r.type===`tuple`){for(let e of r.items)if(Gu(e,n))return!0;return!!(r.rest&&Gu(r.rest,n))}return!1}var Ku=(e,t={})=>n=>{let r=Vu({...n,processors:t});return Hu(e,r),Uu(r,e),Wu(r,e)},qu=(e,t,n={})=>r=>{let{libraryOptions:i,target:a}=r??{},o=Vu({...i??{},target:a,io:t,processors:n});return Hu(e,o),Uu(o,e),Wu(o,e)},Ju={guid:`uuid`,url:`uri`,datetime:`date-time`,json_string:`json-string`,regex:``},Yu=(e,t,n,r)=>{let i=n;i.type=`string`;let{minimum:a,maximum:o,format:s,patterns:c,contentEncoding:l}=e._zod.bag;if(typeof a==`number`&&(i.minLength=a),typeof o==`number`&&(i.maxLength=o),s&&(i.format=Ju[s]??s,i.format===``&&delete i.format,s===`time`&&delete i.format),l&&(i.contentEncoding=l),c&&c.size>0){let e=[...c];e.length===1?i.pattern=e[0].source:e.length>1&&(i.allOf=[...e.map(e=>({...t.target===`draft-07`||t.target===`draft-04`||t.target===`openapi-3.0`?{type:`string`}:{},pattern:e.source}))])}},Xu=(e,t,n,r)=>{let i=n,{minimum:a,maximum:o,format:s,multipleOf:c,exclusiveMaximum:l,exclusiveMinimum:u}=e._zod.bag;typeof s==`string`&&s.includes(`int`)?i.type=`integer`:i.type=`number`,typeof u==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.minimum=u,i.exclusiveMinimum=!0):i.exclusiveMinimum=u),typeof a==`number`&&(i.minimum=a,typeof u==`number`&&t.target!==`draft-04`&&(u>=a?delete i.minimum:delete i.exclusiveMinimum)),typeof l==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.maximum=l,i.exclusiveMaximum=!0):i.exclusiveMaximum=l),typeof o==`number`&&(i.maximum=o,typeof l==`number`&&t.target!==`draft-04`&&(l<=o?delete i.maximum:delete i.exclusiveMaximum)),typeof c==`number`&&(i.multipleOf=c)},Zu=(e,t,n,r)=>{n.type=`boolean`},Qu=(e,t,n,r)=>{n.not={}},$u=(e,t,n,r)=>{let i=e._zod.def,a=To(i.entries);a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),n.enum=a},ed=(e,t,n,r)=>{let i=e._zod.def,a=[];for(let e of i.values)if(e===void 0){if(t.unrepresentable===`throw`)throw Error("Literal `undefined` cannot be represented in JSON Schema")}else if(typeof e==`bigint`){if(t.unrepresentable===`throw`)throw Error(`BigInt literals cannot be represented in JSON Schema`);a.push(Number(e))}else a.push(e);if(a.length!==0)if(a.length===1){let e=a[0];n.type=e===null?`null`:typeof e,t.target===`draft-04`||t.target===`openapi-3.0`?n.enum=[e]:n.const=e}else a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),a.every(e=>typeof e==`boolean`)&&(n.type=`boolean`),a.every(e=>e===null)&&(n.type=`null`),n.enum=a},td=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Custom types cannot be represented in JSON Schema`)},nd=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Transforms cannot be represented in JSON Schema`)},rd=(e,t,n,r)=>{let i=n,a=e._zod.def,{minimum:o,maximum:s}=e._zod.bag;typeof o==`number`&&(i.minItems=o),typeof s==`number`&&(i.maxItems=s),i.type=`array`,i.items=Hu(a.element,t,{...r,path:[...r.path,`items`]})},id=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`,i.properties={};let o=a.shape;for(let e in o)i.properties[e]=Hu(o[e],t,{...r,path:[...r.path,`properties`,e]});let s=new Set(Object.keys(o)),c=new Set([...s].filter(e=>{let n=a.shape[e]._zod;return t.io===`input`?n.optin===void 0:n.optout===void 0}));c.size>0&&(i.required=Array.from(c)),a.catchall?._zod.def.type===`never`?i.additionalProperties=!1:a.catchall?a.catchall&&(i.additionalProperties=Hu(a.catchall,t,{...r,path:[...r.path,`additionalProperties`]})):t.io===`output`&&(i.additionalProperties=!1)},ad=(e,t,n,r)=>{let i=e._zod.def,a=i.inclusive===!1,o=i.options.map((e,n)=>Hu(e,t,{...r,path:[...r.path,a?`oneOf`:`anyOf`,n]}));a?n.oneOf=o:n.anyOf=o},od=(e,t,n,r)=>{let i=e._zod.def,a=Hu(i.left,t,{...r,path:[...r.path,`allOf`,0]}),o=Hu(i.right,t,{...r,path:[...r.path,`allOf`,1]}),s=e=>`allOf`in e&&Object.keys(e).length===1;n.allOf=[...s(a)?a.allOf:[a],...s(o)?o.allOf:[o]]},sd=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`;let o=a.keyType,s=o._zod.bag?.patterns;if(a.mode===`loose`&&s&&s.size>0){let e=Hu(a.valueType,t,{...r,path:[...r.path,`patternProperties`,`*`]});i.patternProperties={};for(let t of s)i.patternProperties[t.source]=e}else (t.target===`draft-07`||t.target===`draft-2020-12`)&&(i.propertyNames=Hu(a.keyType,t,{...r,path:[...r.path,`propertyNames`]})),i.additionalProperties=Hu(a.valueType,t,{...r,path:[...r.path,`additionalProperties`]});let c=o._zod.values;if(c){let e=[...c].filter(e=>typeof e==`string`||typeof e==`number`);e.length>0&&(i.required=e)}},cd=(e,t,n,r)=>{let i=e._zod.def,a=Hu(i.innerType,t,r),o=t.seen.get(e);t.target===`openapi-3.0`?(o.ref=i.innerType,n.nullable=!0):n.anyOf=[a,{type:`null`}]},ld=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},ud=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.default=JSON.parse(JSON.stringify(i.defaultValue))},dd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,t.io===`input`&&(n._prefault=JSON.parse(JSON.stringify(i.defaultValue)))},fd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType;let o;try{o=i.catchValue(void 0)}catch{throw Error(`Dynamic catch values are not supported in JSON Schema`)}n.default=o},pd=(e,t,n,r)=>{let i=e._zod.def,a=t.io===`input`?i.in._zod.def.type===`transform`?i.out:i.in:i.out;Hu(a,t,r);let o=t.seen.get(e);o.ref=a},md=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.readOnly=!0},hd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType};function gd(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}function _d(e,t){for(var n={};e.length;){var r=e[0],i=r.code,a=r.message,o=r.path.join(`.`);if(!n[o])if(`unionErrors`in r){var s=r.unionErrors[0].errors[0];n[o]={message:s.message,type:s.code}}else n[o]={message:a,type:i};if(`unionErrors`in r&&r.unionErrors.forEach(function(t){return t.errors.forEach(function(t){return e.push(t)})}),t){var c=n[o].types,l=c&&c[r.code];n[o]=Ca(o,t,n,i,l?[].concat(l,r.message):r.message)}e.shift()}return n}function vd(e,t){for(var n={};e.length;){var r=e[0],i=r.code,a=r.message,o=r.path.join(`.`);if(!n[o])if(r.code===`invalid_union`&&r.errors.length>0){var s=r.errors[0][0];n[o]={message:s.message,type:s.code}}else n[o]={message:a,type:i};if(r.code===`invalid_union`&&r.errors.forEach(function(t){return t.forEach(function(t){return e.push(t)})}),t){var c=n[o].types,l=c&&c[r.code];n[o]=Ca(o,t,n,i,l?[].concat(l,r.message):r.message)}e.shift()}return n}function yd(e,t,n){if(n===void 0&&(n={}),function(e){return`_def`in e&&typeof e._def==`object`&&`typeName`in e._def}(e))return function(r,i,a){try{return Promise.resolve(gd(function(){return Promise.resolve(e[n.mode===`sync`?`parse`:`parseAsync`](r,t)).then(function(e){return a.shouldUseNativeValidation&&_o({},a),{errors:{},values:n.raw?Object.assign({},r):e}})},function(e){if(function(e){return Array.isArray(e?.issues)}(e))return{values:{},errors:vo(_d(e.errors,!a.shouldUseNativeValidation&&a.criteriaMode===`all`),a)};throw e}))}catch(e){return Promise.reject(e)}};if(function(e){return`_zod`in e&&typeof e._zod==`object`}(e))return function(r,i,a){try{return Promise.resolve(gd(function(){return Promise.resolve((n.mode===`sync`?fs:ms)(e,r,t)).then(function(e){return a.shouldUseNativeValidation&&_o({},a),{errors:{},values:n.raw?Object.assign({},r):e}})},function(e){if(function(e){return e instanceof ss}(e))return{values:{},errors:vo(vd(e.issues,!a.shouldUseNativeValidation&&a.criteriaMode===`all`),a)};throw e}))}catch(e){return Promise.reject(e)}};throw Error(`Invalid input: not a Zod schema`)}function bd(e){let t=ho(e);return z.useMemo(()=>{let e=(e,n,r)=>t.setValue(e,n,{shouldDirty:!0,shouldTouch:!0,shouldValidate:!0,...r});return new Proxy(t,{get:(t,n,r)=>n===`setValue`?e:Reflect.get(t,n,r)})},[t])}var xd=z.createContext({}),Sd=z.createContext({});function Cd(){let e=z.useContext(xd),t=z.useContext(Sd),{getFieldState:n,formState:r}=Sa(),i=n(e.name,r),{id:a}=t;return{id:a,name:e.name,formItemId:`${a}-form-item`,formDescriptionId:`${a}-form-item-description`,formMessageId:`${a}-form-item-message`,...i}}var wd=z.forwardRef(({className:e,...t},n)=>{let r=z.useId(),i=z.useMemo(()=>({id:r}),[r]);return(0,B.jsx)(Sd.Provider,{value:i,children:(0,B.jsx)(`div`,{ref:n,className:Jr(`space-y-3`,e),...t})})});wd.displayName=`FormItem`;var Td=z.forwardRef(({className:e,...t},n)=>{let{error:r,formItemId:i}=Cd();return(0,B.jsx)(Vi,{ref:n,className:Jr(r&&`text-destructive`,e),htmlFor:i,...t})});Td.displayName=`FormLabel`;var Ed=z.forwardRef(({...e},t)=>{let{error:n,formItemId:r,formDescriptionId:i,formMessageId:a}=Cd();return(0,B.jsx)(sn,{ref:t,id:r,"aria-describedby":n?`${i} ${a}`:i,"aria-invalid":!!n,...e})});Ed.displayName=`FormControl`;var Dd=z.forwardRef(({className:e,...t},n)=>{let{formDescriptionId:r}=Cd();return(0,B.jsx)(`p`,{ref:n,id:r,className:Jr(`text-sm text-muted-foreground`,e),...t})});Dd.displayName=`FormDescription`;var Od=z.forwardRef(({className:e,children:t,...n},r)=>{let{error:i,formMessageId:a}=Cd(),o=i?.message?String(i.message):t;return o?(0,B.jsx)(`p`,{ref:r,id:a,className:Jr(`text-sm font-medium text-destructive`,e),...n,children:o}):null});Od.displayName=`FormMessage`;var q=typeof window<`u`?window:void 0,kd=typeof globalThis<`u`?globalThis:q;typeof self>`u`&&(kd.self=kd),typeof File>`u`&&(kd.File=function(){});var Ad=kd?.navigator,J=kd?.document,jd=kd?.location,Md=kd?.fetch,Nd=kd!=null&&kd.XMLHttpRequest&&`withCredentials`in new kd.XMLHttpRequest?kd.XMLHttpRequest:void 0,Pd=kd?.AbortController,Fd=kd?.CompressionStream,Id=Ad?.userAgent,Y=q??{},Ld=`1.370.0`,Rd={DEBUG:!1,LIB_VERSION:Ld,LIB_NAME:`web`,JS_SDK_VERSION:Ld};function zd(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function Bd(e){return function(){var t=this,n=arguments;return new Promise((function(r,i){var a=e.apply(t,n);function o(e){zd(a,r,i,o,s,`next`,e)}function s(e){zd(a,r,i,o,s,`throw`,e)}o(void 0)}))}}function X(){return X=Object.assign?Object.assign.bind():function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},X.apply(null,arguments)}function Vd(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}function Hd(){return(Hd=Bd((function*(e,t,n){t===void 0&&(t=!0);try{var r=new Blob([e],{type:`text/plain`}).stream().pipeThrough(new CompressionStream(`gzip`));return yield new Response(r).blob()}catch(e){if(n!=null&&n.rethrow)throw e;return t&&console.error(`Failed to gzip compress data`,e),null}}))).apply(this,arguments)}var Ud=[`$snapshot`,`$pageview`,`$pageleave`,`$set`,`survey dismissed`,`survey sent`,`survey shown`,`$identify`,`$groupidentify`,`$create_alias`,`$$client_ingestion_warning`,`$web_experiment_applied`,`$feature_enrollment_update`,`$feature_flag_called`],Wd=`amazonbot,amazonproductbot,app.hypefactors.com,applebot,archive.org_bot,awariobot,backlinksextendedbot,baiduspider,bingbot,bingpreview,chrome-lighthouse,dataforseobot,deepscan,duckduckbot,facebookexternal,facebookcatalog,http://yandex.com/bots,hubspot,ia_archiver,leikibot,linkedinbot,meta-externalagent,mj12bot,msnbot,nessus,petalbot,pinterest,prerender,rogerbot,screaming frog,sebot-wa,sitebulb,slackbot,slurp,trendictionbot,turnitin,twitterbot,vercel-screenshot,vercelbot,yahoo! slurp,yandexbot,zoombot,bot.htm,bot.php,(bot;,bot/,crawler,ahrefsbot,ahrefssiteaudit,semrushbot,siteauditbot,splitsignalbot,gptbot,oai-searchbot,chatgpt-user,perplexitybot,better uptime bot,sentryuptimebot,uptimerobot,headlesschrome,cypress,google-hoteladsverifier,adsbot-google,apis-google,duplexweb-google,feedfetcher-google,google favicon,google web preview,google-read-aloud,googlebot,googleother,google-cloudvertexbot,googleweblight,mediapartners-google,storebot-google,google-inspectiontool,bytespider`.split(`,`),Gd=function(e,t){if(t===void 0&&(t=[]),!e)return!1;var n=e.toLowerCase();return Wd.concat(t).some((e=>{var t=e.toLowerCase();return n.indexOf(t)!==-1}))};function Kd(e,t){return e.indexOf(t)!==-1}var qd=function(e){return e.trim()},Jd=function(e){return e.replace(/^\$/,``)},Yd=Object.prototype,Xd=Yd.hasOwnProperty,Zd=Yd.toString,Qd=Array.isArray||function(e){return Zd.call(e)===`[object Array]`},$d=e=>typeof e==`function`,ef=e=>e===Object(e)&&!Qd(e),tf=e=>{if(ef(e)){for(var t in e)if(Xd.call(e,t))return!1;return!0}return!1},Z=e=>e===void 0,nf=e=>Zd.call(e)==`[object String]`,rf=e=>nf(e)&&e.trim().length===0,af=e=>e===null,of=e=>Z(e)||af(e),sf=e=>Zd.call(e)==`[object Number]`&&e==e,cf=e=>sf(e)&&e>0,lf=e=>Zd.call(e)===`[object Boolean]`,uf=e=>e instanceof FormData,df=e=>Kd(Ud,e);function ff(e){return typeof e!=`object`||!e}function pf(e,t){return{}.toString.call(e)===`[object `+t+`]`}function mf(e){return typeof Event<`u`&&function(e,t){try{return e instanceof t}catch{return!1}}(e,Event)}var hf=[!0,`true`,1,`1`,`yes`],gf=e=>Kd(hf,e),_f=[!1,`false`,0,`0`,`no`];function vf(e,t,n,r,i){return t>n&&(r.warn(`min cannot be greater than max.`),t=n),sf(e)?e>n?(r.warn(` cannot be greater than max: `+n+`. Using max value instead.`),n):t>e?(r.warn(` cannot be less than min: `+t+`. Using min value instead.`),t):e:(r.warn(` must be a number. using max or fallback. max: `+n+`, fallback: `+i),vf(i||n,t,n,r))}var yf=class{constructor(e){this.Pt={},this.Dt=e.Dt,this.jt=vf(e.bucketSize,0,100,e.qt),this.$t=vf(e.refillRate,0,this.jt,e.qt),this.Ht=vf(e.refillInterval,0,864e5,e.qt)}Vt(e,t){var n=Math.floor((t-e.lastAccess)/this.Ht);n>0&&(e.tokens=Math.min(e.tokens+n*this.$t,this.jt),e.lastAccess+=n*this.Ht)}consumeRateLimit(e){var t,n=Date.now(),r=String(e),i=this.Pt[r];return i?this.Vt(i,n):this.Pt[r]=i={tokens:this.jt,lastAccess:n},i.tokens===0||(i.tokens--,i.tokens===0&&((t=this.Dt)==null||t.call(this,e)),i.tokens===0)}stop(){this.Pt={}}},bf,xf,Sf,Cf=`Mobile`,wf=`iOS`,Tf=`Android`,Ef=`Tablet`,Df=Tf+` `+Ef,Of=`iPad`,kf=`Apple`,Af=kf+` Watch`,jf=`Safari`,Mf=`BlackBerry`,Nf=`Samsung`,Pf=Nf+`Browser`,Ff=Nf+` Internet`,If=`Chrome`,Lf=If+` OS`,Rf=If+` `+wf,zf=`Internet Explorer`,Bf=zf+` `+Cf,Vf=`Opera`,Hf=Vf+` Mini`,Uf=`Edge`,Wf=`Microsoft `+Uf,Gf=`Firefox`,Kf=Gf+` `+wf,qf=`Nintendo`,Jf=`PlayStation`,Yf=`Xbox`,Xf=Tf+` `+Cf,Zf=Cf+` `+jf,Qf=`Windows`,$f=Qf+` Phone`,ep=`Nokia`,tp=`Ouya`,np=`Generic`,rp=np+` `+Cf.toLowerCase(),ip=np+` `+Ef.toLowerCase(),ap=`Konqueror`,op=`(\\d+(\\.\\d+)?)`,sp=RegExp(`Version/`+op),cp=new RegExp(Yf,`i`),lp=RegExp(Jf+` \\w+`,`i`),up=RegExp(qf+` \\w+`,`i`),dp=RegExp(Mf+`|PlayBook|BB10`,`i`),fp={"NT3.51":`NT 3.11`,"NT4.0":`NT 4.0`,"5.0":`2000`,5.1:`XP`,5.2:`XP`,"6.0":`Vista`,6.1:`7`,6.2:`8`,6.3:`8.1`,6.4:`10`,"10.0":`10`},pp=function(e,t){return t||=``,Kd(e,` OPR/`)&&Kd(e,`Mini`)?Hf:Kd(e,` OPR/`)?Vf:dp.test(e)?Mf:Kd(e,`IE`+Cf)||Kd(e,`WPDesktop`)?Bf:Kd(e,Pf)?Ff:Kd(e,Uf)||Kd(e,`Edg/`)?Wf:Kd(e,`FBIOS`)?`Facebook `+Cf:Kd(e,`UCWEB`)||Kd(e,`UCBrowser`)?`UC Browser`:Kd(e,`CriOS`)?Rf:Kd(e,`CrMo`)||Kd(e,If)?If:Kd(e,Tf)&&Kd(e,jf)?Xf:Kd(e,`FxiOS`)?Kf:Kd(e.toLowerCase(),ap.toLowerCase())?ap:((e,t)=>t&&Kd(t,kf)||function(e){return Kd(e,jf)&&!Kd(e,If)&&!Kd(e,Tf)}(e))(e,t)?Kd(e,Cf)?Zf:jf:Kd(e,Gf)?Gf:Kd(e,`MSIE`)||Kd(e,`Trident/`)?zf:Kd(e,`Gecko`)?Gf:``},mp={[Bf]:[RegExp(`rv:`+op)],[Wf]:[RegExp(Uf+`?\\/`+op)],[If]:[RegExp(`(`+If+`|CrMo)\\/`+op)],[Rf]:[RegExp(`CriOS\\/`+op)],"UC Browser":[RegExp(`(UCBrowser|UCWEB)\\/`+op)],[jf]:[sp],[Zf]:[sp],[Vf]:[RegExp(`(Opera|OPR)\\/`+op)],[Gf]:[RegExp(Gf+`\\/`+op)],[Kf]:[RegExp(`FxiOS\\/`+op)],[ap]:[RegExp(`Konqueror[:/]?`+op,`i`)],[Mf]:[RegExp(Mf+` `+op),sp],[Xf]:[RegExp(`android\\s`+op,`i`)],[Ff]:[RegExp(Pf+`\\/`+op)],[zf]:[RegExp(`(rv:|MSIE )`+op)],Mozilla:[RegExp(`rv:`+op)]},hp=function(e,t){var n=mp[pp(e,t)];if(Z(n))return null;for(var r=0;n.length>r;r++){var i=e.match(n[r]);if(i)return parseFloat(i[i.length-2])}return null},gp=[[RegExp(Yf+`; `+Yf+` (.*?)[);]`,`i`),e=>[Yf,e&&e[1]||``]],[new RegExp(qf,`i`),[qf,``]],[new RegExp(Jf,`i`),[Jf,``]],[dp,[Mf,``]],[new RegExp(Qf,`i`),(e,t)=>{if(/Phone/.test(t)||/WPDesktop/.test(t))return[$f,``];if(new RegExp(Cf).test(t)&&!/IEMobile\b/.test(t))return[Qf+` `+Cf,``];var n=/Windows NT ([0-9.]+)/i.exec(t);if(n&&n[1]){var r=fp[n[1]]||``;return/arm/i.test(t)&&(r=`RT`),[Qf,r]}return[Qf,``]}],[/((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/,e=>e&&e[3]?[wf,[e[3],e[4],e[5]||`0`].join(`.`)]:[wf,``]],[/(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i,e=>{var t=``;return e&&e.length>=3&&(t=Z(e[2])?e[3]:e[2]),[`watchOS`,t]}],[RegExp(`(`+Tf+` (\\d+)\\.(\\d+)\\.?(\\d+)?|`+Tf+`)`,`i`),e=>e&&e[2]?[Tf,[e[2],e[3],e[4]||`0`].join(`.`)]:[Tf,``]],[/Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i,e=>{var t=[`Mac OS X`,``];return e&&e[1]&&(t[1]=[e[1],e[2],e[3]||`0`].join(`.`)),t}],[/Mac/i,[`Mac OS X`,``]],[/CrOS/,[Lf,``]],[/Linux|debian/i,[`Linux`,``]]],_p=function(e){return up.test(e)?qf:lp.test(e)?Jf:cp.test(e)?Yf:new RegExp(tp,`i`).test(e)?tp:RegExp(`(`+$f+`|WPDesktop)`,`i`).test(e)?$f:/iPad/.test(e)?Of:/iPod/.test(e)?`iPod Touch`:/iPhone/.test(e)?`iPhone`:/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(e)?Af:dp.test(e)?Mf:/(kobo)\s(ereader|touch)/i.test(e)?`Kobo`:new RegExp(ep,`i`).test(e)?ep:/(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(e)||/(kf[a-z]+)( bui|\)).+silk\//i.test(e)?`Kindle Fire`:/(Android|ZTE)/i.test(e)?new RegExp(Cf).test(e)&&!/(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(e)||/pixel[\daxl ]{1,6}/i.test(e)&&!/pixel c/i.test(e)||/(huaweimed-al00|tah-|APA|SM-G92|i980|zte|U304AA)/i.test(e)||/lmy47v/i.test(e)&&!/QTAQZ3/i.test(e)?Tf:Df:RegExp(`(pda|`+Cf+`)`,`i`).test(e)?rp:new RegExp(Ef,`i`).test(e)&&!RegExp(Ef+` pc`,`i`).test(e)?ip:``},vp=e=>e instanceof Error;function yp(e){var t=globalThis._posthogChunkIds;if(t){var n=Object.keys(t);return Sf&&n.length===xf||(xf=n.length,Sf=n.reduce(((n,r)=>{bf||={};var i=bf[r];if(i)n[i[0]]=i[1];else for(var a=e(r),o=a.length-1;o>=0;o--){var s=a[o]?.filename,c=t[r];if(s&&c){n[s]=c,bf[r]=[s,c];break}}return n}),{})),Sf}}var bp=class{constructor(e,t,n){n===void 0&&(n=[]),this.coercers=e,this.stackParser=t,this.modifiers=n}buildFromUnknown(e,t){t===void 0&&(t={});var n=t&&t.mechanism||{handled:!0,type:`generic`},r=this.buildCoercingContext(n,t,0).apply(e),i=this.buildParsingContext(t),a=this.parseStacktrace(r,i);return{$exception_list:this.convertToExceptionList(a,n),$exception_level:`error`}}modifyFrames(e){var t=this;return Bd((function*(){for(var n of e)n.stacktrace&&n.stacktrace.frames&&Qd(n.stacktrace.frames)&&(n.stacktrace.frames=yield t.applyModifiers(n.stacktrace.frames));return e}))()}coerceFallback(e){return{type:`Error`,value:`Unknown error`,stack:e.syntheticException?.stack,synthetic:!0}}parseStacktrace(e,t){var n,r;return e.cause!=null&&(n=this.parseStacktrace(e.cause,t)),e.stack!=``&&e.stack!=null&&(r=this.applyChunkIds(this.stackParser(e.stack,e.synthetic?t.skipFirstLines:0),t.chunkIdMap)),X({},e,{cause:n,stack:r})}applyChunkIds(e,t){return e.map((e=>(e.filename&&t&&(e.chunk_id=t[e.filename]),e)))}applyCoercers(e,t){for(var n of this.coercers)if(n.match(e))return n.coerce(e,t);return this.coerceFallback(t)}applyModifiers(e){var t=this;return Bd((function*(){var n=e;for(var r of t.modifiers)n=yield r(n);return n}))()}convertToExceptionList(e,t){var n,r,i={type:e.type,value:e.value,mechanism:{type:t.type??`generic`,handled:(n=t.handled)==null||n,synthetic:(r=e.synthetic)!=null&&r}};e.stack&&(i.stacktrace={type:`raw`,frames:e.stack});var a=[i];return e.cause!=null&&a.push(...this.convertToExceptionList(e.cause,X({},t,{handled:!0}))),a}buildParsingContext(e){return{chunkIdMap:yp(this.stackParser),skipFirstLines:e.skipFirstLines??1}}buildCoercingContext(e,t,n){n===void 0&&(n=0);var r=(n,r)=>{if(4>=r){var i=this.buildCoercingContext(e,t,r);return this.applyCoercers(n,i)}};return X({},t,{syntheticException:n==0?t.syntheticException:void 0,mechanism:e,apply:e=>r(e,n),next:e=>r(e,n+1)})}},xp=`?`;function Sp(e,t,n,r,i){var a={platform:e,filename:t,function:n===``?xp:n,in_app:!0};return Z(r)||(a.lineno=r),Z(i)||(a.colno=i),a}var Cp=(e,t)=>{var n=e.indexOf(`safari-extension`)!==-1,r=e.indexOf(`safari-web-extension`)!==-1;return n||r?[e.indexOf(`@`)===-1?xp:e.split(`@`)[0],n?`safari-extension:`+t:`safari-web-extension:`+t]:[e,t]},wp=/^\s*at (\S+?)(?::(\d+))(?::(\d+))\s*$/i,Tp=/^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,Ep=/\((\S*)(?::(\d+))(?::(\d+))\)/,Dp=(e,t)=>{var n=wp.exec(e);if(n){var[,r,i,a]=n;return Sp(t,r,xp,+i,+a)}var o=Tp.exec(e);if(o){if(o[2]&&o[2].indexOf(`eval`)===0){var s=Ep.exec(o[2]);s&&(o[2]=s[1],o[3]=s[2],o[4]=s[3])}var[c,l]=Cp(o[1]||xp,o[2]);return Sp(t,l,c,o[3]?+o[3]:void 0,o[4]?+o[4]:void 0)}},Op=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i,kp=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,Ap=(e,t)=>{var n=Op.exec(e);if(n){if(n[3]&&n[3].indexOf(` > eval`)>-1){var r=kp.exec(n[3]);r&&(n[1]=n[1]||`eval`,n[3]=r[1],n[4]=r[2],n[5]=``)}var i=n[3],a=n[1]||xp;return[a,i]=Cp(a,i),Sp(t,i,a,n[4]?+n[4]:void 0,n[5]?+n[5]:void 0)}},jp=/\(error: (.*)\)/,Mp=class{match(e){return this.isDOMException(e)||this.isDOMError(e)}coerce(e,t){var n=nf(e.stack);return{type:this.getType(e),value:this.getValue(e),stack:n?e.stack:void 0,cause:e.cause?t.next(e.cause):void 0,synthetic:!1}}getType(e){return this.isDOMError(e)?`DOMError`:`DOMException`}getValue(e){var t=e.name||(this.isDOMError(e)?`DOMError`:`DOMException`);return e.message?t+`: `+e.message:t}isDOMException(e){return pf(e,`DOMException`)}isDOMError(e){return pf(e,`DOMError`)}},Np=class{match(e){return(e=>e instanceof Error)(e)}coerce(e,t){return{type:this.getType(e),value:this.getMessage(e,t),stack:this.getStack(e),cause:e.cause?t.next(e.cause):void 0,synthetic:!1}}getType(e){return e.name||e.constructor.name}getMessage(e,t){var n=e.message;return String(n.error&&typeof n.error.message==`string`?n.error.message:n)}getStack(e){return e.stacktrace||e.stack||void 0}},Pp=class{constructor(){}match(e){return pf(e,`ErrorEvent`)&&e.error!=null}coerce(e,t){return t.apply(e.error)||{type:`ErrorEvent`,value:e.message,stack:t.syntheticException?.stack,synthetic:!0}}},Fp=/^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i,Ip=class{match(e){return typeof e==`string`}coerce(e,t){var[n,r]=this.getInfos(e);return{type:n??`Error`,value:r??e,stack:t.syntheticException?.stack,synthetic:!0}}getInfos(e){var t=`Error`,n=e,r=e.match(Fp);return r&&(t=r[1],n=r[2]),[t,n]}},Lp=[`fatal`,`error`,`warning`,`log`,`info`,`debug`];function Rp(e,t){t===void 0&&(t=40);var n=Object.keys(e);if(n.sort(),!n.length)return`[object has no keys]`;for(var r=n.length;r>0;r--){var i=n.slice(0,r).join(`, `);if(t>=i.length)return r===n.length?i:i.length>t?i.slice(0,t)+`...`:i}return``}var zp=class{match(e){return typeof e==`object`&&!!e}coerce(e,t){var n=this.getErrorPropertyFromObject(e);return n?t.apply(n):{type:this.getType(e),value:this.getValue(e),stack:t.syntheticException?.stack,level:this.isSeverityLevel(e.level)?e.level:`error`,synthetic:!0}}getType(e){return mf(e)?e.constructor.name:`Error`}getValue(e){if(`name`in e&&typeof e.name==`string`){var t=`'`+e.name+`' captured as exception`;return`message`in e&&typeof e.message==`string`&&(t+=` with message: '`+e.message+`'`),t}if(`message`in e&&typeof e.message==`string`)return e.message;var n=this.getObjectClassName(e);return(n&&n!==`Object`?`'`+n+`'`:`Object`)+` captured as exception with keys: `+Rp(e)}isSeverityLevel(e){return nf(e)&&!rf(e)&&Lp.indexOf(e)>=0}getErrorPropertyFromObject(e){for(var t in e)if({}.hasOwnProperty.call(e,t)){var n=e[t];if(vp(n))return n}}getObjectClassName(e){try{var t=Object.getPrototypeOf(e);return t?t.constructor.name:void 0}catch{return}}},Bp=class{match(e){return mf(e)}coerce(e,t){var n=e.constructor.name;return{type:n,value:n+` captured as exception with keys: `+Rp(e),stack:t.syntheticException?.stack,synthetic:!0}}},Vp=class{match(e){return ff(e)}coerce(e,t){return{type:`Error`,value:`Primitive value captured as exception: `+String(e),stack:t.syntheticException?.stack,synthetic:!0}}},Hp=class{match(e){return pf(e,`PromiseRejectionEvent`)||this.isCustomEventWrappingRejection(e)}isCustomEventWrappingRejection(e){if(!mf(e))return!1;try{var t=e.detail;return typeof t==`object`&&!!t&&`reason`in t}catch{return!1}}coerce(e,t){var n=this.getUnhandledRejectionReason(e);return ff(n)?{type:`UnhandledRejection`,value:`Non-Error promise rejection captured with value: `+String(n),stack:t.syntheticException?.stack,synthetic:!0}:t.apply(n)}getUnhandledRejectionReason(e){try{if(`reason`in e)return e.reason;if(`detail`in e&&e.detail!=null&&typeof e.detail==`object`&&`reason`in e.detail)return e.detail.reason}catch{}return e}},Up=`$message`,Wp=`$timestamp`,Gp=new Set([Up,Wp]),Kp={enabled:!0,max_bytes:32768};function qp(e){return e?{enabled:e.enabled??Kp.enabled,max_bytes:Yp(e.max_bytes,Kp.max_bytes)}:X({},Kp)}var Jp=class{constructor(e){this.zt=[],this.Ut=0,this.Rt=qp(e)}setConfig(e){this.Rt=qp(e),this.Yt()}add(e){var t=function(e){var t=function(e){var t=new WeakSet;try{return JSON.stringify(e,((e,n)=>{if(typeof n==`bigint`)return n.toString();if(typeof n!=`function`&&typeof n!=`symbol`){if(n instanceof Date)return n.toISOString();if(n instanceof Error)return{name:n.name,message:n.message,stack:n.stack};if(n&&typeof n==`object`){if(t.has(n))return`[Circular]`;t.add(n)}return n}}))}catch{return}}(e);if(t)try{var n=JSON.parse(t);if(!ef(n))return;var r=n,i=r[Up],a=r[Wp];return!nf(i)||i.trim().length===0||!nf(a)&&!sf(a)?void 0:{step:r,json:t}}catch{return}}(e);if(t){var n=function(e){if(typeof TextEncoder<`u`)return new TextEncoder().encode(e).length;for(var t=encodeURIComponent(e),n=0,r=0;t.length>r;r++)t[r]===`%`?(n+=1,r+=2):n+=1;return n}(t.json);n>this.Rt.max_bytes||(this.zt.push({step:t.step,bytes:n}),this.Ut+=n,this.Yt())}}getAttachable(){return this.zt.map((e=>e.step))}clear(){this.zt=[],this.Ut=0}size(){return this.zt.length}Yt(){for(;this.Ut>this.Rt.max_bytes&&this.zt.length>0;){var e=this.zt.shift();e&&(this.Ut-=e.bytes)}}};function Yp(e,t){if(!sf(e)||e===1/0||e===-1/0)return t;var n=Math.floor(e);return 0>n?t:n}var Xp=function(e,t){var{debugEnabled:n}=t===void 0?{}:t,r={C(t){if(q&&(Rd.DEBUG||Y.POSTHOG_DEBUG||n)&&!Z(q.console)&&q.console){for(var r=(`__rrweb_original__`in q.console[t])?q.console[t].__rrweb_original__:q.console[t],i=arguments.length,a=Array(i>1?i-1:0),o=1;i>o;o++)a[o-1]=arguments[o];r(e,...a)}},info(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`log`,...t)},warn(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`warn`,...t)},error(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`error`,...t)},critical(){for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];console.error(e,...n)},uninitializedWarning(e){r.error(`You must initialize PostHog before calling `+e)},createLogger:(t,n)=>Xp(e+` `+t,n)};return r},Q=Xp(`[PostHog.js]`),Zp=Q.createLogger,Qp=Zp(`[ExternalScriptsLoader]`),$p=(e,t,n)=>{if(e.config.disable_external_dependency_loading)return Qp.warn(t+` was requested but loading of external scripts is disabled.`),n(`Loading of external scripts is disabled`);var r=J?.querySelectorAll(`script`);if(r){for(var i,a=function(){if(r[o].src===t){var e=r[o];return e.__posthog_loading_callback_fired?{v:n()}:(e.addEventListener(`load`,(t=>{e.__posthog_loading_callback_fired=!0,n(void 0,t)})),e.onerror=e=>n(e),{v:void 0})}},o=0;r.length>o;o++)if(i=a())return i.v}var s=()=>{if(!J)return n(`document not found`);var r=J.createElement(`script`);if(r.type=`text/javascript`,r.crossOrigin=`anonymous`,r.src=t,r.onload=e=>{r.__posthog_loading_callback_fired=!0,n(void 0,e)},r.onerror=e=>n(e),e.config.prepare_external_dependency_script&&(r=e.config.prepare_external_dependency_script(r)),!r)return n(`prepare_external_dependency_script returned null`);if(e.config.external_scripts_inject_target===`head`)J.head.appendChild(r);else{var i,a=J.querySelectorAll(`body > script`);a.length>0?(i=a[0].parentNode)==null||i.insertBefore(r,a[0]):J.body.appendChild(r)}};J!=null&&J.body?s():J?.addEventListener(`DOMContentLoaded`,s)};Y.__PosthogExtensions__=Y.__PosthogExtensions__||{},Y.__PosthogExtensions__.loadExternalDependency=(e,t,n)=>{if(t!==`remote-config`){var r;if(e.config.__preview_external_dependency_versioned_paths)r=e.requestRouter.endpointFor(`assets`,`/static/`+e.version+`/`+t+`.js`);else{var i=`/static/`+t+`.js?v=`+e.version;if(t===`toolbar`){var a=3e5;i=i+`&t=`+Math.floor(Date.now()/a)*a}r=e.requestRouter.endpointFor(`assets`,i)}$p(e,r,n)}else $p(e,e.requestRouter.endpointFor(`assets`,`/array/`+e.config.token+`/config.js`),n)},Y.__PosthogExtensions__.loadSiteApp=(e,t,n)=>{$p(e,e.requestRouter.endpointFor(`api`,t),n)};var em=`$people_distinct_id`,tm=`$device_id`,nm=`__alias`,rm=`__timers`,im=`$autocapture_disabled_server_side`,am=`$heatmaps_enabled_server_side`,om=`$exception_capture_enabled_server_side`,sm=`$error_tracking_suppression_rules`,cm=`$error_tracking_capture_extension_exceptions`,lm=`$web_vitals_enabled_server_side`,um=`$dead_clicks_enabled_server_side`,dm=`$product_tours_enabled_server_side`,fm=`$web_vitals_allowed_metrics`,pm=`$session_recording_remote_config`,mm=`$sesid`,hm=`$session_is_sampled`,gm=`$enabled_feature_flags`,_m=`$early_access_features`,vm=`$feature_flag_details`,ym=`$feature_flag_payloads`,bm=`$override_feature_flag_payloads`,xm=`$stored_person_properties`,Sm=`$stored_group_properties`,Cm=`$surveys`,wm=`ph_product_tours`,Tm=`$flag_call_reported`,Em=`$flag_call_reported_session_id`,Dm=`$feature_flag_errors`,Om=`$feature_flag_evaluated_at`,km=`$user_state`,Am=`$client_session_props`,jm=`$capture_rate_limit`,Mm=`$initial_campaign_params`,Nm=`$initial_referrer_info`,Pm=`$initial_person_info`,Fm=`$epp`,Im=`__POSTHOG_TOOLBAR__`,Lm=`$posthog_cookieless`,Rm=[em,nm,`__cmpns`,rm,`$session_recording_enabled_server_side`,am,mm,gm,sm,km,_m,vm,Sm,xm,Cm,Tm,Em,Dm,Om,Am,jm,Mm,Nm,Fm,Pm,wm,`$product_tours_activated`,dm,pm,bm],zm=`PostHog loadExternalDependency extension not found.`,Bm=`on_reject`,Vm=`always`,Hm=`anonymous`,Um=`identified`,Wm=`identified_only`,Gm=`visibilitychange`,Km=`beforeunload`,qm=`$pageview`,Jm=`$pageleave`,Ym=`$identify`,Xm=`$groupidentify`;function Zm(e,t){Qd(e)&&e.forEach(t)}function Qm(e,t){if(!of(e))if(Qd(e))e.forEach(t);else if(uf(e))e.forEach(((e,n)=>t(e,n)));else for(var n in e)Xd.call(e,n)&&t(e[n],n)}var $m=function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];for(var i of n)for(var a in i)i[a]!==void 0&&(e[a]=i[a]);return e};function eh(e){for(var t=Object.keys(e),n=t.length,r=Array(n);n--;)r[n]=[t[n],e[t[n]]];return r}var th=function(e){try{return e()}catch{return}},nh=function(e){return function(){try{for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];return e.apply(this,n)}catch(e){Q.critical(`Implementation error. Please turn on debug mode and open a ticket on https://app.posthog.com/home#panel=support%3Asupport%3A.`),Q.critical(e)}}},rh=function(e){var t={};return Qm(e,(function(e,n){(nf(e)&&e.length>0||sf(e))&&(t[n]=e)})),t},ih=[`herokuapp.com`,`vercel.app`,`netlify.app`];function ah(e){var t=e?.hostname;if(!nf(t))return!1;var n=t.split(`.`).slice(-2).join(`.`);for(var r of ih)if(n===r)return!1;return!0}function oh(e,t,n,r){var{capture:i=!1,passive:a=!0}=r??{};e?.addEventListener(t,n,{capture:i,passive:a})}function sh(e){return e.name===`ph_toolbar_internal`}Math.trunc||(Math.trunc=function(e){return 0>e?Math.ceil(e):Math.floor(e)}),Number.isInteger||(Number.isInteger=function(e){return sf(e)&&isFinite(e)&&Math.floor(e)===e});var ch=class e{constructor(e){if(this.bytes=e,e.length!==16)throw TypeError(`not 128-bit length`)}static fromFieldsV7(t,n,r,i){if(!Number.isInteger(t)||!Number.isInteger(n)||!Number.isInteger(r)||!Number.isInteger(i)||0>t||0>n||0>r||0>i||t>0xffffffffffff||n>4095||r>1073741823||i>4294967295)throw RangeError(`invalid field value`);var a=new Uint8Array(16);return a[0]=t/2**40,a[1]=t/2**32,a[2]=t/2**24,a[3]=t/2**16,a[4]=t/2**8,a[5]=t,a[6]=112|n>>>8,a[7]=n,a[8]=128|r>>>24,a[9]=r>>>16,a[10]=r>>>8,a[11]=r,a[12]=i>>>24,a[13]=i>>>16,a[14]=i>>>8,a[15]=i,new e(a)}toString(){for(var e=``,t=0;this.bytes.length>t;t++)e=e+(this.bytes[t]>>>4).toString(16)+(15&this.bytes[t]).toString(16),t!==3&&t!==5&&t!==7&&t!==9||(e+=`-`);if(e.length!==36)throw Error(`Invalid UUIDv7 was generated`);return e}clone(){return new e(this.bytes.slice(0))}equals(e){return this.compareTo(e)===0}compareTo(e){for(var t=0;16>t;t++){var n=this.bytes[t]-e.bytes[t];if(n!==0)return Math.sign(n)}return 0}},lh=class{constructor(){this.I=0,this.S=0,this.k=new fh}generate(){var e=this.generateOrAbort();if(Z(e)){this.I=0;var t=this.generateOrAbort();if(Z(t))throw Error(`Could not generate UUID after timestamp reset`);return t}return e}generateOrAbort(){var e=Date.now();if(e>this.I)this.I=e,this.A();else{if(this.I>=e+1e4)return;this.S++,this.S>4398046511103&&(this.I++,this.A())}return ch.fromFieldsV7(this.I,Math.trunc(this.S/2**30),this.S&2**30-1,this.k.nextUint32())}A(){this.S=1024*this.k.nextUint32()+(1023&this.k.nextUint32())}},uh,dh=e=>{if(typeof UUIDV7_DENY_WEAK_RNG<`u`&&UUIDV7_DENY_WEAK_RNG)throw Error(`no cryptographically strong RNG available`);for(var t=0;e.length>t;t++)e[t]=65536*Math.trunc(65536*Math.random())+Math.trunc(65536*Math.random());return e};q&&!Z(q.crypto)&&crypto.getRandomValues&&(dh=e=>crypto.getRandomValues(e));var fh=class{constructor(){this.T=new Uint32Array(8),this.N=1/0}nextUint32(){return this.T.length>this.N||(dh(this.T),this.N=0),this.T[this.N++]}},ph=()=>mh().toString(),mh=()=>(uh||=new lh).generate(),hh=``,gh=/[a-z0-9][a-z0-9-]+\.[a-z]{2,}$/i,_h={Gt:()=>!!J,Xt(e){Q.error(`cookieStore error: `+e)},Jt(e){if(J){try{for(var t=e+`=`,n=J.cookie.split(`;`).filter((e=>e.length)),r=0;n.length>r;r++){for(var i=n[r];i.charAt(0)==` `;)i=i.substring(1,i.length);if(i.indexOf(t)===0)return decodeURIComponent(i.substring(t.length,i.length))}}catch{}return null}},Kt(e){var t;try{t=JSON.parse(_h.Jt(e))||{}}catch{}return t},Qt(e,t,n,r,i){if(J)try{var a=``,o=``,s=function(e,t){if(t){var n=function(e,t){if(t===void 0&&(t=J),hh)return hh;if(!t||[`localhost`,`127.0.0.1`].includes(e))return``;for(var n=e.split(`.`),r=Math.min(n.length,8),i=`dmn_chk_`+ph();!hh&&r--;){var a=n.slice(r).join(`.`),o=i+`=1;domain=.`+a+`;path=/`;t.cookie=o+`;max-age=3`,t.cookie.includes(i)&&(t.cookie=o+`;max-age=0`,hh=a)}return hh}(e);if(!n){var r=(e=>{var t=e.match(gh);return t?t[0]:``})(e);r!==n&&Q.info(`Warning: cookie subdomain discovery mismatch`,r,n),n=r}return n?`; domain=.`+n:``}return``}(J.location.hostname,r);if(n){var c=new Date;c.setTime(c.getTime()+864e5*n),a=`; expires=`+c.toUTCString()}i&&(o=`; secure`);var l=e+`=`+encodeURIComponent(JSON.stringify(t))+a+`; SameSite=Lax; path=/`+s+o;return l.length>3686.4&&Q.warn(`cookieStore warning: large cookie, len=`+l.length),J.cookie=l,l}catch{return}},ti(e,t){if(J!=null&&J.cookie)try{_h.Qt(e,``,-1,t)}catch{return}}},vh=null,yh={Gt(){if(!af(vh))return vh;var e=!0;if(Z(q))e=!1;else try{var t=`__mplssupport__`;yh.Qt(t,`xyz`),yh.Jt(t)!==`"xyz"`&&(e=!1),yh.ti(t)}catch{e=!1}return e||Q.error(`localStorage unsupported; falling back to cookie store`),vh=e,e},Xt(e){Q.error(`localStorage error: `+e)},Jt(e){try{return q?.localStorage.getItem(e)}catch(e){yh.Xt(e)}return null},Kt(e){try{return JSON.parse(yh.Jt(e))||{}}catch{}return null},Qt(e,t){try{q?.localStorage.setItem(e,JSON.stringify(t))}catch(e){yh.Xt(e)}},ti(e){try{q?.localStorage.removeItem(e)}catch(e){yh.Xt(e)}}},bh=[tm,`distinct_id`,mm,hm,Fm,Pm,km],xh={},Sh={Gt:()=>!0,Xt(e){Q.error(`memoryStorage error: `+e)},Jt:e=>xh[e]||null,Kt:e=>xh[e]||null,Qt(e,t){xh[e]=t},ti(e){delete xh[e]}},Ch=null,wh={Gt(){if(!af(Ch))return Ch;if(Ch=!0,Z(q))Ch=!1;else try{var e=`__support__`;wh.Qt(e,`xyz`),wh.Jt(e)!==`"xyz"`&&(Ch=!1),wh.ti(e)}catch{Ch=!1}return Ch},Xt(e){Q.error(`sessionStorage error: `,e)},Jt(e){try{return q?.sessionStorage.getItem(e)}catch(e){wh.Xt(e)}return null},Kt(e){try{return JSON.parse(wh.Jt(e))||null}catch{}return null},Qt(e,t){try{q?.sessionStorage.setItem(e,JSON.stringify(t))}catch(e){wh.Xt(e)}},ti(e){try{q?.sessionStorage.removeItem(e)}catch(e){wh.Xt(e)}}},Th=class{constructor(e){this._instance=e}get Rt(){return this._instance.config}get consent(){return this.ei()?0:this.ii}isOptedOut(){return this.Rt.cookieless_mode===Vm||this.isRejected()||this.consent===-1&&this.Rt.cookieless_mode===Bm}isOptedIn(){return!this.isOptedOut()}isExplicitlyOptedOut(){return this.consent===0}isRejected(){return this.consent===0||this.consent===-1&&this.Rt.opt_out_capturing_by_default}optInOut(e){this.ri.Qt(this.ni,+!!e,this.Rt.cookie_expiration,this.Rt.cross_subdomain_cookie,this.Rt.secure_cookie)}reset(){this.ri.ti(this.ni,this.Rt.cross_subdomain_cookie)}get ni(){var{token:e,opt_out_capturing_cookie_prefix:t,consent_persistence_name:n}=this._instance.config;return n||(t?t+e:`__ph_opt_in_out_`+e)}get ii(){var e=this.ri.Jt(this.ni);return gf(e)?1:Kd(_f,e)?0:-1}get ri(){var e=this.Rt.opt_out_capturing_persistence_type,t=e===`localStorage`?yh:_h;if(!this.si||this.si!==t){this.si=t;var n=e===`localStorage`?_h:yh;n.Jt(this.ni)&&(this.si.Jt(this.ni)||this.optInOut(gf(n.Jt(this.ni))),n.ti(this.ni,this.Rt.cross_subdomain_cookie))}return this.si}ei(){return!!this.Rt.respect_dnt&&[Ad?.doNotTrack,Ad?.msDoNotTrack,Y.doNotTrack].some((e=>gf(e)))}},Eh=Zp(`[Dead Clicks]`),Dh=()=>!0,Oh=e=>{var t,n=!((t=e.instance.persistence)==null||!t.get_property(um)),r=e.instance.config.capture_dead_clicks;return lf(r)?r:!!ef(r)||n},kh=class{get lazyLoadedDeadClicksAutocapture(){return this.oi}constructor(e,t,n){this.instance=e,this.isEnabled=t,this.onCapture=n,this.startIfEnabledOrStop()}onRemoteConfig(e){`captureDeadClicks`in e&&(this.instance.persistence&&this.instance.persistence.register({[um]:e.captureDeadClicks}),this.startIfEnabledOrStop())}startIfEnabledOrStop(){this.isEnabled(this)?this.ai((()=>{this.li()})):this.stop()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.initDeadClicksAutocapture&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this.instance,`dead-clicks-autocapture`,(t=>{t?Eh.error(`failed to load script`,t):e()}))}li(){var e;if(J){if(!this.oi&&(e=Y.__PosthogExtensions__)!=null&&e.initDeadClicksAutocapture){var t=ef(this.instance.config.capture_dead_clicks)?this.instance.config.capture_dead_clicks:{};t.__onCapture=this.onCapture,this.oi=Y.__PosthogExtensions__.initDeadClicksAutocapture(this.instance,t),this.oi.start(J),Eh.info(`starting...`)}}else Eh.error("`document` not found. Cannot start.")}stop(){this.oi&&(this.oi.stop(),this.oi=void 0,Eh.info(`stopping...`))}},Ah=Zp(`[SegmentIntegration]`),jh=`posthog-js`;function Mh(e,t){var{organization:n,projectId:r,prefix:i,severityAllowList:a=[`error`],sendExceptionsToPostHog:o=!0}=t===void 0?{}:t;return t=>{if(a!==`*`&&!a.includes(t.level)||!e.__loaded)return t;t.tags||={};var s=e.requestRouter.endpointFor(`ui`,`/project/`+e.config.token+`/person/`+e.get_distinct_id());t.tags[`PostHog Person URL`]=s,e.sessionRecordingStarted()&&(t.tags[`PostHog Recording URL`]=e.get_session_replay_url({withTimestamp:!0}));var c,l=t.exception?.values||[],u=l.map((e=>X({},e,{stacktrace:e.stacktrace?X({},e.stacktrace,{type:`raw`,frames:(e.stacktrace.frames||[]).map((e=>X({},e,{platform:`web:javascript`})))}):void 0}))),d={$exception_message:l[0]?.value||t.message,$exception_type:l[0]?.type,$exception_level:t.level,$exception_list:u,$sentry_event_id:t.event_id,$sentry_exception:t.exception,$sentry_exception_message:l[0]?.value||t.message,$sentry_exception_type:l[0]?.type,$sentry_tags:t.tags};return n&&r&&(d.$sentry_url=(i||`https://sentry.io/organizations/`)+n+`/issues/?project=`+r+`&query=`+t.event_id),o&&((c=e.exceptions)==null||c.sendExceptionEvent(d)),t}}var Nh=class{constructor(e,t,n,r,i,a){this.name=jh,this.setupOnce=function(o){o(Mh(e,{organization:t,projectId:n,prefix:r,severityAllowList:i,sendExceptionsToPostHog:a==null||a}))}}},Ph=class{constructor(e){this.ui=(e,t,n)=>{n&&(n.noSessionId||n.activityTimeout||n.sessionPastMaximumLength)&&(Q.info(`[PageViewManager] Session rotated, clearing pageview state`,{sessionId:e,changeReason:n}),this.hi=void 0,this._instance.scrollManager.resetContext())},this._instance=e,this.ci()}ci(){this.di=this._instance.sessionManager?.onSessionId(this.ui)}destroy(){var e;(e=this.di)==null||e.call(this),this.di=void 0}doPageView(e,t){var n=this.vi(e,t);return this.hi={pathname:q?.location.pathname??``,pageViewId:t,timestamp:e},this._instance.scrollManager.resetContext(),n}doPageLeave(e){return this.vi(e,this.hi?.pageViewId)}doEvent(){return{$pageview_id:this.hi?.pageViewId}}vi(e,t){var n=this.hi;if(!n)return{$pageview_id:t};var r={$pageview_id:t,$prev_pageview_id:n.pageViewId},i=this._instance.scrollManager.getContext();if(i&&!this._instance.config.disable_scroll_properties){var{maxScrollHeight:a,lastScrollY:o,maxScrollY:s,maxContentHeight:c,lastContentY:l,maxContentY:u}=i;if(!(Z(a)||Z(o)||Z(s)||Z(c)||Z(l)||Z(u))){a=Math.ceil(a),o=Math.ceil(o),s=Math.ceil(s),c=Math.ceil(c),l=Math.ceil(l),u=Math.ceil(u);var d=a>1?vf(o/a,0,1,Q):1,f=a>1?vf(s/a,0,1,Q):1,p=c>1?vf(l/c,0,1,Q):1,m=c>1?vf(u/c,0,1,Q):1;r=$m(r,{$prev_pageview_last_scroll:o,$prev_pageview_last_scroll_percentage:d,$prev_pageview_max_scroll:s,$prev_pageview_max_scroll_percentage:f,$prev_pageview_last_content:l,$prev_pageview_last_content_percentage:p,$prev_pageview_max_content:u,$prev_pageview_max_content_percentage:m})}}return n.pathname&&(r.$prev_pageview_pathname=n.pathname),n.timestamp&&(r.$prev_pageview_duration=(e.getTime()-n.timestamp.getTime())/1e3),r}},Fh=e=>{var t=J?.createElement(`a`);return Z(t)?null:(t.href=e,t)},Ih=function(e,t){for(var n,r=((e.split(`#`)[0]||``).split(/\?(.*)/)[1]||``).replace(/^\?+/g,``).split(`&`),i=0;r.length>i;i++){var a=r[i].split(`=`);if(a[0]===t){n=a;break}}if(!Qd(n)||2>n.length)return``;var o=n[1];try{o=decodeURIComponent(o)}catch{Q.error(`Skipping decoding for malformed query param: `+o)}return o.replace(/\+/g,` `)},Lh=function(e,t,n){if(!e||!t||!t.length)return e;for(var r=e.split(`#`),i=r[1],a=(r[0]||``).split(`?`),o=a[1],s=a[0],c=(o||``).split(`&`),l=[],u=0;c.length>u;u++){var d=c[u].split(`=`);Qd(d)&&(t.includes(d[0])?l.push(d[0]+`=`+n):l.push(c[u]))}var f=s;return o!=null&&(f+=`?`+l.join(`&`)),i!=null&&(f+=`#`+i),f},Rh=function(e,t){var n=e.match(RegExp(t+`=([^&]*)`));return n?n[1]:null},zh=`https?://(.*)`,Bh=[`gclid`,`gclsrc`,`dclid`,`gbraid`,`wbraid`,`fbclid`,`msclkid`,`twclid`,`li_fat_id`,`igshid`,`ttclid`,`rdt_cid`,`epik`,`qclid`,`sccid`,`irclid`,`_kx`],Vh=[`utm_source`,`utm_medium`,`utm_campaign`,`utm_content`,`utm_term`,`gad_source`,`mc_cid`,...Bh],Hh=``,Uh=[`li_fat_id`];function Wh(e,t,n){if(!J)return{};var r,i=t?[...Bh,...n||[]]:[],a=Gh(Lh(J.URL,i,Hh),e);return $m((r={},Qm(Uh,(function(e){var t=_h.Jt(e);r[e]=t||null})),r),a)}function Gh(e,t){var n=Vh.concat(t||[]),r={};return Qm(n,(function(t){r[t]=Ih(e,t)||null})),r}function Kh(e){var t=function(e){return e?e.search(zh+`google.([^/?]*)`)===0?`google`:e.search(zh+`bing.com`)===0?`bing`:e.search(zh+`yahoo.com`)===0?`yahoo`:e.search(zh+`duckduckgo.com`)===0?`duckduckgo`:null:null}(e),n=t==`yahoo`?`p`:`q`,r={};if(!af(t)){r.$search_engine=t;var i=J?Ih(J.referrer,n):``;i.length&&(r.ph_keyword=i)}return r}function qh(){return navigator.language||navigator.userLanguage}var Jh=`$direct`;function Yh(){return J?.referrer||Jh}function Xh(e,t){var n=e?[...Bh,...t||[]]:[],r=jd?.href.substring(0,1e3);return{r:Yh().substring(0,1e3),u:r?Lh(r,n,Hh):void 0}}function Zh(e){var{r:t,u:n}=e,r={$referrer:t,$referring_domain:t==null?void 0:t==Jh?Jh:Fh(t)?.host};if(n){r.$current_url=n;var i=Fh(n);r.$host=i?.host,r.$pathname=i?.pathname,$m(r,Gh(n))}return t&&$m(r,Kh(t)),r}function Qh(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function $h(){try{return new Date().getTimezoneOffset()}catch{return}}var eg=[`cookie`,`localstorage`,`localstorage+cookie`,`sessionstorage`,`memory`],tg=class{constructor(e,t){this.Rt=e,this.props={},this.fi=!1,this.pi=(e=>{var t=``;return e.token&&(t=e.token.replace(/\+/g,`PL`).replace(/\//g,`SL`).replace(/=/g,`EQ`)),e.persistence_name?`ph_`+e.persistence_name:`ph_`+t+`_posthog`})(e),this.ri=this.gi(e),this.load(),e.debug&&Q.info(`Persistence loaded`,e.persistence,X({},this.props)),this.update_config(e,e,t),this.save()}isDisabled(){return!!this.mi}gi(e){eg.indexOf(e.persistence.toLowerCase())===-1&&(Q.critical(`Unknown persistence type `+e.persistence+`; falling back to localStorage+cookie`),e.persistence=`localStorage+cookie`);var t=function(e){e===void 0&&(e=[]);var t=[...bh,...e];return X({},yh,{Kt(e){try{var t={};try{t=_h.Kt(e)||{}}catch{}var n=$m(t,JSON.parse(yh.Jt(e)||`{}`));return yh.Qt(e,n),n}catch{}return null},Qt(e,n,r,i,a,o){try{yh.Qt(e,n,void 0,void 0,o);var s={};t.forEach((e=>{n[e]&&(s[e]=n[e])})),Object.keys(s).length&&_h.Qt(e,s,r,i,a,o)}catch(e){yh.Xt(e)}},ti(e,t){try{q?.localStorage.removeItem(e),_h.ti(e,t)}catch(e){yh.Xt(e)}}})}(e.cookie_persisted_properties||[]),n=e.persistence.toLowerCase();return n===`localstorage`&&yh.Gt()?yh:n===`localstorage+cookie`&&t.Gt()?t:n===`sessionstorage`&&wh.Gt()?wh:n===`memory`?Sh:n===`cookie`?_h:t.Gt()?t:_h}yi(e){var t=e??this.Rt.feature_flag_cache_ttl_ms;if(!t||0>=t)return!1;var n=this.props[Om];return!n||typeof n!=`number`||Date.now()-n>t}properties(){var e={};return Qm(this.props,((t,n)=>{if(n===gm&&ef(t)){if(!this.yi())for(var r=Object.keys(t),i=0;r.length>i;i++)e[`$feature/`+r[i]]=t[r[i]]}else Rm.indexOf(n)===-1&&(e[n]=t)})),e}load(){if(!this.mi){var e=this.ri.Kt(this.pi);e&&(this.props=$m({},e))}}save(){this.mi||this.ri.Qt(this.pi,this.props,this.bi,this.wi,this.Ii,this.Rt.debug)}remove(){this.ri.ti(this.pi,!1),this.ri.ti(this.pi,!0)}clear(){this.remove(),this.props={}}register_once(e,t,n){if(ef(e)){Z(t)&&(t=`None`),this.bi=Z(n)?this.Ci:n;var r=!1;if(Qm(e,((e,n)=>{this.props.hasOwnProperty(n)&&this.props[n]!==t||(this.props[n]=e,r=!0)})),r)return this.save(),!0}return!1}register(e,t){if(ef(e)){this.bi=Z(t)?this.Ci:t;var n=!1;if(Qm(e,((t,r)=>{e.hasOwnProperty(r)&&this.props[r]!==t&&(this.props[r]=t,n=!0)})),n)return this.save(),!0}return!1}unregister(e){e in this.props&&(delete this.props[e],this.save())}update_campaign_params(){if(!this.fi){var e=Wh(this.Rt.custom_campaign_params,this.Rt.mask_personal_data_properties,this.Rt.custom_personal_data_properties);tf(rh(e))||this.register(e),this.fi=!0}}update_search_keyword(){var e;this.register((e=J?.referrer)?Kh(e):{})}update_referrer_info(){this.register_once({$referrer:Yh(),$referring_domain:J!=null&&J.referrer&&Fh(J.referrer)?.host||Jh},void 0)}set_initial_person_info(){this.props[Mm]||this.props[Nm]||this.register_once({[Pm]:Xh(this.Rt.mask_personal_data_properties,this.Rt.custom_personal_data_properties)},void 0)}get_initial_props(){var e={};Qm([Nm,Mm],(t=>{var n=this.props[t];n&&Qm(n,(function(t,n){e[`$initial_`+Jd(n)]=t}))}));var t,n,r=this.props[Pm];return r&&$m(e,(t=Zh(r),n={},Qm(t,(function(e,t){n[`$initial_`+Jd(t)]=e})),n)),e}safe_merge(e){return Qm(this.props,(function(t,n){n in e||(e[n]=t)})),e}update_config(e,t,n){if(this.Ci=this.bi=e.cookie_expiration,this.set_disabled(e.disable_persistence||!!n),this.set_cross_subdomain(e.cross_subdomain_cookie),this.set_secure(e.secure_cookie),e.persistence!==t.persistence||!((e,t)=>{if(e.length!==t.length)return!1;var n=[...e].sort(),r=[...t].sort();return n.every(((e,t)=>e===r[t]))})(e.cookie_persisted_properties||[],t.cookie_persisted_properties||[])){var r=this.gi(e),i=this.props;this.clear(),this.ri=r,this.props=i,this.save()}}set_disabled(e){this.mi=e,this.mi?this.remove():this.save()}set_cross_subdomain(e){e!==this.wi&&(this.wi=e,this.remove(),this.save())}set_secure(e){e!==this.Ii&&(this.Ii=e,this.remove(),this.save())}set_event_timer(e,t){var n=this.props[rm]||{};n[e]=t,this.props[rm]=n,this.save()}remove_event_timer(e){var t=(this.props[rm]||{})[e];return Z(t)||(delete this.props[rm][e],this.save()),t}get_property(e){return this.props[e]}set_property(e,t){this.props[e]=t,this.save()}},ng={Activation:`events`,Cancellation:`cancelEvents`},rg={Popover:`popover`,API:`api`,Widget:`widget`,ExternalSurvey:`external_survey`},ig={SHOWN:`survey shown`,DISMISSED:`survey dismissed`,SENT:`survey sent`,ABANDONED:`survey abandoned`},ag={SURVEY_ID:`$survey_id`,SURVEY_NAME:`$survey_name`,SURVEY_RESPONSE:`$survey_response`,SURVEY_ITERATION:`$survey_iteration`,SURVEY_ITERATION_START_DATE:`$survey_iteration_start_date`,SURVEY_PARTIALLY_COMPLETED:`$survey_partially_completed`,SURVEY_SUBMISSION_ID:`$survey_submission_id`,SURVEY_QUESTIONS:`$survey_questions`,SURVEY_COMPLETED:`$survey_completed`,PRODUCT_TOUR_ID:`$product_tour_id`,SURVEY_LAST_SEEN_DATE:`$survey_last_seen_date`},og={Popover:`popover`,Inline:`inline`},sg={SHOWN:`product tour shown`,DISMISSED:`product tour dismissed`,COMPLETED:`product tour completed`,STEP_SHOWN:`product tour step shown`,STEP_COMPLETED:`product tour step completed`,BUTTON_CLICKED:`product tour button clicked`,STEP_SELECTOR_FAILED:`product tour step selector failed`,BANNER_CONTAINER_SELECTOR_FAILED:`product tour banner container selector failed`,BANNER_ACTION_CLICKED:`product tour banner action clicked`},cg={TOUR_ID:`$product_tour_id`,TOUR_NAME:`$product_tour_name`,TOUR_ITERATION:`$product_tour_iteration`,TOUR_RENDER_REASON:`$product_tour_render_reason`,TOUR_STEP_ID:`$product_tour_step_id`,TOUR_STEP_ORDER:`$product_tour_step_order`,TOUR_STEP_TYPE:`$product_tour_step_type`,TOUR_DISMISS_REASON:`$product_tour_dismiss_reason`,TOUR_BUTTON_TEXT:`$product_tour_button_text`,TOUR_BUTTON_ACTION:`$product_tour_button_action`,TOUR_BUTTON_LINK:`$product_tour_button_link`,TOUR_BUTTON_TOUR_ID:`$product_tour_button_tour_id`,TOUR_STEPS_COUNT:`$product_tour_steps_count`,TOUR_STEP_SELECTOR:`$product_tour_step_selector`,TOUR_STEP_SELECTOR_FOUND:`$product_tour_step_selector_found`,TOUR_STEP_ELEMENT_TAG:`$product_tour_step_element_tag`,TOUR_STEP_ELEMENT_ID:`$product_tour_step_element_id`,TOUR_STEP_ELEMENT_CLASSES:`$product_tour_step_element_classes`,TOUR_STEP_ELEMENT_TEXT:`$product_tour_step_element_text`,TOUR_ERROR:`$product_tour_error`,TOUR_MATCHES_COUNT:`$product_tour_matches_count`,TOUR_FAILURE_PHASE:`$product_tour_failure_phase`,TOUR_WAITED_FOR_ELEMENT:`$product_tour_waited_for_element`,TOUR_WAIT_DURATION_MS:`$product_tour_wait_duration_ms`,TOUR_BANNER_SELECTOR:`$product_tour_banner_selector`,TOUR_LINKED_SURVEY_ID:`$product_tour_linked_survey_id`,USE_MANUAL_SELECTOR:`$use_manual_selector`,INFERENCE_DATA_PRESENT:`$inference_data_present`,TOUR_LAST_SEEN_DATE:`$product_tour_last_seen_date`,TOUR_TYPE:`$product_tour_type`},lg=Zp(`[RateLimiter]`),ug=class{constructor(e){this.serverLimits={},this.lastEventRateLimited=!1,this.checkForLimiting=e=>{var t=e.text;if(t&&t.length)try{(JSON.parse(t).quota_limited||[]).forEach((e=>{lg.info((e||`events`)+` is quota limited.`),this.serverLimits[e]=new Date().getTime()+6e4}))}catch(e){lg.warn(`could not rate limit - continuing. Error: "`+e?.message+`"`,{text:t});return}},this.instance=e,this.lastEventRateLimited=this.clientRateLimitContext(!0).isRateLimited}get captureEventsPerSecond(){return this.instance.config.rate_limiting?.events_per_second||10}get captureEventsBurstLimit(){return Math.max(this.instance.config.rate_limiting?.events_burst_limit||10*this.captureEventsPerSecond,this.captureEventsPerSecond)}clientRateLimitContext(e){var t;e===void 0&&(e=!1);var{captureEventsBurstLimit:n,captureEventsPerSecond:r}=this,i=new Date().getTime(),a=this.instance.persistence?.get_property(jm)??{tokens:n,last:i};a.tokens+=(i-a.last)/1e3*r,a.last=i,a.tokens>n&&(a.tokens=n);var o=1>a.tokens;return o||e||(a.tokens=Math.max(0,a.tokens-1)),!o||this.lastEventRateLimited||e||this.instance.capture(`$$client_ingestion_warning`,{$$client_ingestion_warning_message:`posthog-js client rate limited. Config is set to `+r+` events per second and `+n+` events burst limit.`},{skip_client_rate_limiting:!0}),this.lastEventRateLimited=o,(t=this.instance.persistence)==null||t.set_property(jm,a),{isRateLimited:o,remainingTokens:a.tokens}}isServerRateLimited(e){var t=this.serverLimits[e||`events`]||!1;return!1!==t&&new Date().getTime()e(this.remoteConfig))):e()}xi(e){this._instance._send_request({method:`GET`,url:this._instance.requestRouter.endpointFor(`assets`,`/array/`+this._instance.config.token+`/config`),callback(t){e(t.json)}})}load(){try{if(this.remoteConfig)return dg.info(`Using preloaded remote config`,this.remoteConfig),this.ki(this.remoteConfig),void this.Ti();if(this._instance.Ai())return void dg.warn(`Remote config is disabled. Falling back to local config.`);this.Si((e=>{if(!e)return dg.info(`No config found after loading remote JS config. Falling back to JSON.`),void this.xi((e=>{this.ki(e),this.Ti()}));this.ki(e),this.Ti()}))}catch(e){dg.error(`Error loading remote config`,e)}}stop(){this.Ei&&=(clearInterval(this.Ei),void 0)}refresh(){this._instance.Ai()||J?.visibilityState===`hidden`||this._instance.reloadFeatureFlags()}Ti(){if(!this.Ei){var e=this._instance.config.remote_config_refresh_interval_ms??3e5;e!==0&&(this.Ei=setInterval((()=>{this.refresh()}),e))}}ki(e){var t;e||dg.error(`Failed to fetch remote config from PostHog.`),this._instance.ki(e??{}),!1!==e?.hasFeatureFlags&&(this._instance.config.advanced_disable_feature_flags_on_first_load||(t=this._instance.featureFlags)==null||t.ensureFlagsLoaded())}},pg={GZipJS:`gzip-js`,Base64:`base64`},mg=Uint8Array,hg=Uint16Array,gg=Uint32Array,_g=new mg([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),vg=new mg([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),yg=new mg([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),bg=function(e,t){for(var n=new hg(31),r=0;31>r;++r)n[r]=t+=1<r;++r)for(var a=n[r];n[r+1]>a;++a)i[a]=a-n[r]<<5|r;return[n,i]},xg=bg(_g,2),Sg=xg[1];xg[0][28]=258,Sg[258]=28;for(var Cg=bg(vg,0)[1],wg=new hg(32768),Tg=0;32768>Tg;++Tg){var Eg=(43690&Tg)>>>1|(21845&Tg)<<1;wg[Tg]=((65280&(Eg=(61680&(Eg=(52428&Eg)>>>2|(13107&Eg)<<2))>>>4|(3855&Eg)<<4))>>>8|(255&Eg)<<8)>>>1}var Dg=function(e,t,n){for(var r=e.length,i=0,a=new hg(t);r>i;++i)++a[e[i]-1];var o,s=new hg(t);for(i=0;t>i;++i)s[i]=s[i-1]+a[i-1]<<1;if(n){o=new hg(1<i;++i)if(e[i])for(var l=i<<4|e[i],u=t-e[i],d=s[e[i]-1]++<=d;++d)o[wg[d]>>>c]=l}else for(o=new hg(r),i=0;r>i;++i)o[i]=wg[s[e[i]-1]++]>>>15-e[i];return o},Og=new mg(288);for(Tg=0;144>Tg;++Tg)Og[Tg]=8;for(Tg=144;256>Tg;++Tg)Og[Tg]=9;for(Tg=256;280>Tg;++Tg)Og[Tg]=7;for(Tg=280;288>Tg;++Tg)Og[Tg]=8;var kg=new mg(32);for(Tg=0;32>Tg;++Tg)kg[Tg]=5;var Ag=Dg(Og,9,0),jg=Dg(kg,5,0),Mg=function(e){return(e/8>>0)+(7&e&&1)},Ng=function(e,t,n){(n==null||n>e.length)&&(n=e.length);var r=new(e instanceof hg?hg:e instanceof gg?gg:mg)(n-t);return r.set(e.subarray(t,n)),r},Pg=function(e,t,n){var r=t/8>>0;e[r]|=n<<=7&t,e[r+1]|=n>>>8},Fg=function(e,t,n){var r=t/8>>0;e[r]|=n<<=7&t,e[r+1]|=n>>>8,e[r+2]|=n>>>16},Ig=function(e,t){for(var n=[],r=0;e.length>r;++r)e[r]&&n.push({s:r,f:e[r]});var i=n.length,a=n.slice();if(!i)return[new mg(0),0];if(i==1){var o=new mg(n[0].s+1);return o[n[0].s]=1,[o,1]}n.sort((function(e,t){return e.f-t.f})),n.push({s:-1,f:25001});var s=n[0],c=n[1],l=0,u=1,d=2;for(n[0]={s:-1,f:s.f+c.f,l:s,r:c};u!=i-1;)s=n[n[d].f>n[l].f?l++:d++],c=n[l!=u&&n[d].f>n[l].f?l++:d++],n[u++]={s:-1,f:s.f+c.f,l:s,r:c};var f=a[0].s;for(r=1;i>r;++r)a[r].s>f&&(f=a[r].s);var p=new hg(f+1),m=Lg(n[u-1],p,0);if(m>t){r=0;var h=0,g=m-t,_=1<r;++r){var v=a[r].s;if(t>=p[v])break;h+=_-(1<>>=g;h>0;){var y=a[r].s;t>p[y]?h-=1<=0&&h;--r){var b=a[r].s;p[b]==t&&(--p[b],++h)}m=t}return[new mg(p),m]},Lg=function(e,t,n){return e.s==-1?Math.max(Lg(e.l,t,n+1),Lg(e.r,t,n+1)):t[e.s]=n},Rg=function(e){for(var t=e.length;t&&!e[--t];);for(var n=new hg(++t),r=0,i=e[0],a=1,o=function(e){n[r++]=e},s=1;t>=s;++s)if(e[s]==i&&s!=t)++a;else{if(!i&&a>2){for(;a>138;a-=138)o(32754);a>2&&(o(a>10?a-11<<5|28690:a-3<<5|12305),a=0)}else if(a>3){for(o(i),--a;a>6;a-=6)o(8304);a>2&&(o(a-3<<5|8208),a=0)}for(;a--;)o(i);a=1,i=e[s]}return[n.subarray(0,r),t]},zg=function(e,t){for(var n=0,r=0;t.length>r;++r)n+=e[r]*t[r];return n},Bg=function(e,t,n){var r=n.length,i=Mg(t+2);e[i]=255&r,e[i+1]=r>>>8,e[i+2]=255^e[i],e[i+3]=255^e[i+1];for(var a=0;r>a;++a)e[i+a+4]=n[a];return 8*(i+4+r)},Vg=function(e,t,n,r,i,a,o,s,c,l,u){Pg(t,u++,n),++i[256];for(var d=Ig(i,15),f=d[0],p=d[1],m=Ig(a,15),h=m[0],g=m[1],_=Rg(f),v=_[0],y=_[1],b=Rg(h),x=b[0],S=b[1],C=new hg(19),w=0;v.length>w;++w)C[31&v[w]]++;for(w=0;x.length>w;++w)C[31&x[w]]++;for(var T=Ig(C,7),E=T[0],ee=T[1],D=19;D>4&&!E[yg[D-1]];--D);var O,k,A,j,M=l+5<<3,N=zg(i,Og)+zg(a,kg)+o,P=zg(i,f)+zg(a,h)+o+14+3*D+zg(C,E)+(2*C[16]+3*C[17]+7*C[18]);if(N>=M&&P>=M)return Bg(t,u,e.subarray(c,c+l));if(Pg(t,u,1+(N>P)),u+=2,N>P){O=Dg(f,p,0),k=f,A=Dg(h,g,0),j=h;var F=Dg(E,ee,0);for(Pg(t,u,y-257),Pg(t,u+5,S-1),Pg(t,u+10,D-4),u+=14,w=0;D>w;++w)Pg(t,u+3*w,E[yg[w]]);u+=3*D;for(var I=[v,x],te=0;2>te;++te){var ne=I[te];for(w=0;ne.length>w;++w)Pg(t,u,F[re=31&ne[w]]),u+=E[re],re>15&&(Pg(t,u,ne[w]>>>5&127),u+=ne[w]>>>12)}}else O=Ag,k=Og,A=jg,j=kg;for(w=0;s>w;++w)if(r[w]>255){var re;Fg(t,u,O[257+(re=r[w]>>>18&31)]),u+=k[re+257],re>7&&(Pg(t,u,r[w]>>>23&31),u+=_g[re]);var L=31&r[w];Fg(t,u,A[L]),u+=j[L],L>3&&(Fg(t,u,r[w]>>>5&8191),u+=vg[L])}else Fg(t,u,O[r[w]]),u+=k[r[w]];return Fg(t,u,O[256]),u+k[256]},Hg=new gg([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Ug=function(){for(var e=new gg(256),t=0;256>t;++t){for(var n=t,r=9;--r;)n=(1&n&&3988292384)^n>>>1;e[t]=n}return e}(),Wg=function(e,t,n){for(;n;++t)e[t]=n,n>>>=8};function Gg(e,t){t===void 0&&(t={});var n=function(){var e=4294967295;return{p(t){for(var n=e,r=0;t.length>r;++r)n=Ug[255&n^t[r]]^n>>>8;e=n},d(){return 4294967295^e}}}(),r=e.length;n.p(e);var i,a,o,s,c,l=(s=10+((i=t).filename&&i.filename.length+1||0),c=8,function(e,t,n,r,i,a){var o=e.length,s=new mg(r+o+5*(1+Math.floor(o/7e3))+i),c=s.subarray(r,s.length-i),l=0;if(!t||8>o)for(var u=0;o>=u;u+=65535){var d=u+65535;o>d?l=Bg(c,l,e.subarray(u,d)):(c[u]=!0,l=Bg(c,l,e.subarray(u,o)))}else{for(var f=Hg[t-1],p=f>>>13,m=8191&f,h=(1<u;++u){var O=b(u),k=32767&u,A=_[O];if(g[k]=A,_[O]=k,u>=ee){var j=o-u;if((w>7e3||E>24576)&&j>423){l=Vg(e,c,0,x,S,C,T,E,D,u-D,l),E=w=T=0,D=u;for(var M=0;286>M;++M)S[M]=0;for(M=0;30>M;++M)C[M]=0}var N=2,P=0,F=m,I=k-A&32767;if(j>2&&O==b(u-I))for(var te=Math.min(p,j)-1,ne=Math.min(32767,u),re=Math.min(258,j);ne>=I&&--F&&k!=A;){if(e[u+N]==e[u+N-I]){for(var L=0;re>L&&e[u+L]==e[u+L-I];++L);if(L>N){if(N=L,P=I,L>te)break;var R=Math.min(I,L-2),ie=0;for(M=0;R>M;++M){var ae=u-I+M+32768&32767,oe=ae-g[ae]+32768&32767;oe>ie&&(ie=oe,A=ae)}}}I+=(k=A)-(A=g[k])+32768&32767}if(P){x[E++]=268435456|Sg[N]<<18|Cg[P];var se=31&Sg[N],ce=31&Cg[P];T+=_g[se]+vg[ce],++S[257+se],++C[ce],ee=u+N,++w}else x[E++]=e[u],++S[e[u]]}}l=Vg(e,c,!0,x,S,C,T,E,D,u-D,l)}return Ng(s,0,r+Mg(l)+i)}(a=e,(o=t).level==null?6:o.level,o.mem==null?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(a.length)))):12+o.mem,s,c)),u=l.length;return function(e,t){var n=t.filename;if(e[0]=31,e[1]=139,e[2]=8,e[8]=2>t.level?4:t.level==9?2:0,e[9]=3,t.mtime!=0&&Wg(e,4,Math.floor(new Date(t.mtime||Date.now())/1e3)),n){e[3]=8;for(var r=0;n.length>=r;++r)e[r+10]=n.charCodeAt(r)}}(l,t),Wg(l,u-8,n.d()),Wg(l,u-4,r),l}var Kg=!!Nd||!!Md,qg=`text/plain`,Jg=!1,Yg=function(e,t,n){n===void 0&&(n=!0);var[r,i]=e.split(`?`),a=X({},t),o=i?.split(`&`).map((e=>{var t,[r,i]=e.split(`=`),o=n&&(t=a[r])!=null?t:i;return delete a[r],r+`=`+o}))??[],s=function(e,t){var n,r;t===void 0&&(t=`&`);var i=[];return Qm(e,(function(e,t){Z(e)||Z(t)||t===`undefined`||(n=encodeURIComponent((e=>e instanceof File)(e)?e.name:e.toString()),r=encodeURIComponent(t),i[i.length]=r+`=`+n)})),i.join(t)}(a);return s&&o.push(s),r+`?`+o.join(`&`)},Xg=(e,t)=>JSON.stringify(e,((e,t)=>typeof t==`bigint`?t.toString():t),t),Zg=e=>{if(e.Wt)return e.Wt;var{data:t,compression:n}=e;if(t){if(n===pg.GZipJS){var r=Gg(function(e,t){var n=e.length;if(typeof TextEncoder<`u`)return new TextEncoder().encode(e);for(var r=new mg(e.length+(e.length>>>1)),i=0,a=function(e){r[i++]=e},o=0;n>o;++o){if(i+5>r.length){var s=new mg(i+8+(n-o<<1));s.set(r),r=s}var c=e.charCodeAt(o);128>c?a(c):2048>c?(a(192|c>>>6),a(128|63&c)):c>55295&&57344>c?(a(240|(c=65536+(1047552&c)|1023&e.charCodeAt(++o))>>>18),a(128|c>>>12&63),a(128|c>>>6&63),a(128|63&c)):(a(224|c>>>12),a(128|c>>>6&63),a(128|63&c))}return Ng(r,0,i)}(Xg(t)),{mtime:0});return{contentType:qg,body:r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength),estimatedSize:r.byteLength}}if(n===pg.Base64){var i=(e=>`data=`+encodeURIComponent(typeof e==`string`?e:Xg(e)))(function(e){return e&&btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,((e,t)=>String.fromCharCode(parseInt(t,16)))))}(Xg(t)));return{contentType:`application/x-www-form-urlencoded`,body:i,estimatedSize:new Blob([i]).size}}var a=Xg(t);return{contentType:`application/json`,body:a,estimatedSize:new Blob([a]).size}}},Qg=function(){var e=Bd((function*(e){var t=yield function(e,t,n){return Hd.apply(this,arguments)}(Xg(e.data),Rd.DEBUG,{rethrow:!0});if(!t)return e;var n=yield t.arrayBuffer();return X({},e,{Wt:{contentType:qg,body:n,estimatedSize:n.byteLength}})}));return function(t){return e.apply(this,arguments)}}(),$g=(e,t)=>Yg(e,{_:new Date().getTime().toString(),ver:Rd.JS_SDK_VERSION,compression:t}),e_=[];Md&&e_.push({transport:`fetch`,method(e){var{contentType:t,body:n,estimatedSize:r}=Zg(e)??{},i=new Headers;Qm(e.headers,(function(e,t){i.append(t,e)})),t&&i.append(`Content-Type`,t);var a=e.url,o=null;if(Pd){var s=new Pd;o={signal:s.signal,timeout:setTimeout((()=>s.abort()),e.timeout)}}Md(a,X({method:e?.method||`GET`,headers:i,keepalive:e.method===`POST`&&52428.8>(r||0),body:n,signal:o?.signal},e.fetchOptions)).then((t=>t.text().then((n=>{var r={statusCode:t.status,text:n};if(t.status===200)try{r.json=JSON.parse(n)}catch(e){Q.error(e)}e.callback==null||e.callback(r)})))).catch((t=>{Q.error(t),e.callback==null||e.callback({statusCode:0,error:t})})).finally((()=>o?clearTimeout(o.timeout):null))}}),Nd&&e_.push({transport:`XHR`,method(e){var t=new Nd;t.open(e.method||`GET`,e.url,!0);var{contentType:n,body:r}=Zg(e)??{};Qm(e.headers,(function(e,n){t.setRequestHeader(n,e)})),n&&t.setRequestHeader(`Content-Type`,n),e.timeout&&(t.timeout=e.timeout),e.disableXHRCredentials||(t.withCredentials=!0),t.onreadystatechange=()=>{if(t.readyState===4){var n={statusCode:t.status,text:t.responseText};if(t.status===200)try{n.json=JSON.parse(t.responseText)}catch{}e.callback==null||e.callback(n)}},t.send(r)}}),Ad!=null&&Ad.sendBeacon&&e_.push({transport:`sendBeacon`,method(e){var t=Yg(e.url,{beacon:`1`});try{var{contentType:n,body:r}=Zg(e)??{};if(!r)return;var i=r instanceof Blob?r:new Blob([r],{type:n});Ad.sendBeacon(t,i)}catch{}}});var t_=3e3,n_=class{constructor(e,t){this.Ri=!0,this.Ni=[],this.Mi=vf(t?.flush_interval_ms||t_,250,5e3,Q.createLogger(`flush interval`),t_),this.Fi=e}enqueue(e){this.Ni.push(e),this.Oi||this.Pi()}unload(){this.Li();var e=this.Ni.length>0?this.Di():{},t=Object.values(e);[...t.filter((e=>e.url.indexOf(`/e`)===0)),...t.filter((e=>e.url.indexOf(`/e`)!==0))].map((e=>{this.Fi(X({},e,{transport:`sendBeacon`}))}))}enable(){this.Ri=!1,this.Pi()}Pi(){var e=this;this.Ri||(this.Oi=setTimeout((()=>{if(this.Li(),this.Ni.length>0){var t=this.Di(),n=function(){var n=t[r],i=new Date().getTime();n.data&&Qd(n.data)&&Qm(n.data,(e=>{e.offset=Math.abs(e.timestamp-i),delete e.timestamp})),e.Fi(n)};for(var r in t)n()}}),this.Mi))}Li(){clearTimeout(this.Oi),this.Oi=void 0}Di(){var e={};return Qm(this.Ni,(t=>{var n,r=t,i=(r?r.batchKey:null)||r.url;Z(e[i])&&(e[i]=X({},r,{data:[]})),(n=e[i].data)==null||n.push(r.data)})),this.Ni=[],e}},r_=[`retriesPerformedSoFar`],i_=class{constructor(e){this.Bi=!1,this.ji=3e3,this.Ni=[],this._instance=e,this.Ni=[],this.qi=!0,!Z(q)&&`onLine`in q.navigator&&(this.qi=q.navigator.onLine,this.Zi=()=>{this.qi=!0,this.$i()},this.Hi=()=>{this.qi=!1},oh(q,`online`,this.Zi),oh(q,`offline`,this.Hi))}get length(){return this.Ni.length}retriableRequest(e){var{retriesPerformedSoFar:t}=e,n=Vd(e,r_);cf(t)&&(n.url=Yg(n.url,{retry_count:t})),this._instance._send_request(X({},n,{callback:e=>{e.statusCode===200||e.statusCode>=400&&500>e.statusCode||(t??0)>=10?n.callback==null||n.callback(e):this.Vi(X({retriesPerformedSoFar:t},n))}}))}Vi(e){var t=e.retriesPerformedSoFar||0;e.retriesPerformedSoFar=t+1;var n=function(e){var t=3e3*2**e,n=t/2,r=Math.min(18e5,t),i=Math.random()-.5;return Math.ceil(r+i*(r-n))}(t),r=Date.now()+n;this.Ni.push({retryAt:r,requestOptions:e});var i=`Enqueued failed request for retry in `+n;navigator.onLine||(i+=` (Browser is offline)`),Q.warn(i),this.Bi||(this.Bi=!0,this.zi())}zi(){if(this.Ui&&clearTimeout(this.Ui),this.Ni.length===0)return this.Bi=!1,void(this.Ui=void 0);this.Ui=setTimeout((()=>{this.qi&&this.Ni.length>0&&this.$i(),this.zi()}),this.ji)}$i(){var e=Date.now(),t=[],n=this.Ni.filter((n=>e>n.retryAt||(t.push(n),!1)));if(this.Ni=t,n.length>0)for(var{requestOptions:r}of n)this.retriableRequest(r)}unload(){for(var{requestOptions:e}of(this.Ui&&=(clearTimeout(this.Ui),void 0),this.Bi=!1,Z(q)||(this.Zi&&=(q.removeEventListener(`online`,this.Zi),void 0),this.Hi&&=(q.removeEventListener(`offline`,this.Hi),void 0)),this.Ni))try{this._instance._send_request(X({},e,{transport:`sendBeacon`}))}catch(e){Q.error(e)}this.Ni=[]}},a_=class{constructor(e){this.Yi=()=>{this.Wi||={};var e=this.scrollElement(),t=this.scrollY(),n=e?Math.max(0,e.scrollHeight-e.clientHeight):0,r=t+(e?.clientHeight||0),i=e?.scrollHeight||0;this.Wi.lastScrollY=Math.ceil(t),this.Wi.maxScrollY=Math.max(t,this.Wi.maxScrollY??0),this.Wi.maxScrollHeight=Math.max(n,this.Wi.maxScrollHeight??0),this.Wi.lastContentY=r,this.Wi.maxContentY=Math.max(r,this.Wi.maxContentY??0),this.Wi.maxContentHeight=Math.max(i,this.Wi.maxContentHeight??0)},this._instance=e}get Gi(){return this._instance.config.scroll_root_selector}getContext(){return this.Wi}resetContext(){var e=this.Wi;return setTimeout(this.Yi,0),e}startMeasuringScrollPosition(){oh(q,`scroll`,this.Yi,{capture:!0}),oh(q,`scrollend`,this.Yi,{capture:!0}),oh(q,`resize`,this.Yi)}scrollElement(){if(!this.Gi)return q?.document.documentElement;for(var e of Qd(this.Gi)?this.Gi:[this.Gi]){var t=q?.document.querySelector(e);if(t)return t}}scrollY(){if(this.Gi){var e=this.scrollElement();return e&&e.scrollTop||0}return q&&(q.scrollY||q.pageYOffset||q.document.documentElement.scrollTop)||0}scrollX(){if(this.Gi){var e=this.scrollElement();return e&&e.scrollLeft||0}return q&&(q.scrollX||q.pageXOffset||q.document.documentElement.scrollLeft)||0}},o_=e=>Xh(e?.config.mask_personal_data_properties,e?.config.custom_personal_data_properties),s_=class{constructor(e,t,n,r){this.Xi=e=>{var t=this.Ji();if(!t||t.sessionId!==e){var n={sessionId:e,props:this.Ki(this._instance)};this.Qi.register({[Am]:n})}},this._instance=e,this.tr=t,this.Qi=n,this.Ki=r||o_,this.tr.onSessionId(this.Xi)}Ji(){return this.Qi.props[Am]}getSetOnceProps(){var e=this.Ji()?.props;return e?`r`in e?Zh(e):{$referring_domain:e.referringDomain,$pathname:e.initialPathName,utm_source:e.utm_source,utm_campaign:e.utm_campaign,utm_medium:e.utm_medium,utm_content:e.utm_content,utm_term:e.utm_term}:{}}getSessionProps(){var e={};return Qm(rh(this.getSetOnceProps()),((t,n)=>{n===`$current_url`&&(n=`url`),e[`$session_entry_`+Jd(n)]=t})),e}},c_=class{constructor(){this.er={}}on(e,t){return this.er[e]||(this.er[e]=[]),this.er[e].push(t),()=>{this.er[e]=this.er[e].filter((e=>e!==t))}}emit(e,t){for(var n of this.er[e]||[])n(t);for(var r of this.er[`*`]||[])r(e,t)}},l_=Zp(`[SessionId]`),u_=class{on(e,t){return this.ir.on(e,t)}constructor(e,t,n){var r;if(this.rr=[],this.nr=void 0,this.ir=new c_,this.sr=(e,t)=>!(!cf(e)||!cf(t))&&Math.abs(e-t)>this.sessionTimeoutMs,!e.persistence)throw Error(`SessionIdManager requires a PostHogPersistence instance`);if(e.config.cookieless_mode===Vm)throw Error(`SessionIdManager cannot be used with cookieless_mode="always"`);this.Rt=e.config,this.Qi=e.persistence,this.ar=void 0,this.lr=void 0,this._sessionStartTimestamp=null,this._sessionActivityTimestamp=null,this.ur=t||ph,this.hr=n||ph;var i=this.Rt.persistence_name||this.Rt.token;if(this._sessionTimeoutMs=1e3*vf(this.Rt.session_idle_timeout_seconds||1800,60,36e3,l_.createLogger(`session_idle_timeout_seconds`),1800),e.register({$configured_session_timeout_ms:this._sessionTimeoutMs}),this.cr(),this.dr=`ph_`+i+`_window_id`,this.vr=`ph_`+i+`_primary_window_exists`,this.pr()){var a=wh.Kt(this.dr),o=wh.Kt(this.vr);a&&!o?this.ar=a:wh.ti(this.dr),wh.Qt(this.vr,!0)}if((r=this.Rt.bootstrap)!=null&&r.sessionID)try{var s=(e=>{var t=this.Rt.bootstrap.sessionID.replace(/-/g,``);if(t.length!==32)throw Error(`Not a valid UUID`);if(t[12]!==`7`)throw Error(`Not a UUIDv7`);return parseInt(t.substring(0,12),16)})();this.gr(this.Rt.bootstrap.sessionID,new Date().getTime(),s)}catch(e){l_.error(`Invalid sessionID in bootstrap`,e)}this.mr()}get sessionTimeoutMs(){return this._sessionTimeoutMs}onSessionId(e){return Z(this.rr)&&(this.rr=[]),this.rr.push(e),this.lr&&e(this.lr,this.ar),()=>{this.rr=this.rr.filter((t=>t!==e))}}pr(){return this.Rt.persistence!==`memory`&&!this.Qi.mi&&wh.Gt()}yr(e){e!==this.ar&&(this.ar=e,this.pr()&&wh.Qt(this.dr,e))}br(){return this.ar?this.ar:this.pr()?wh.Kt(this.dr):null}gr(e,t,n){e===this.lr&&t===this._sessionActivityTimestamp&&n===this._sessionStartTimestamp||(this._sessionStartTimestamp=n,this._sessionActivityTimestamp=t,this.lr=e,this.Qi.register({[mm]:[t,e,n]}))}wr(){var e=this.Qi.props[mm];return Qd(e)&&e.length===2&&e.push(e[0]),e||[0,null,0]}resetSessionId(){this.gr(null,null,null)}destroy(){clearTimeout(this._r),this._r=void 0,this.nr&&q&&(q.removeEventListener(Km,this.nr,{capture:!1}),this.nr=void 0),this.rr=[]}mr(){this.nr=()=>{this.pr()&&wh.ti(this.vr)},oh(q,Km,this.nr,{capture:!1})}checkAndGetSessionAndWindowId(e,t){if(e===void 0&&(e=!1),t===void 0&&(t=null),this.Rt.cookieless_mode===Vm)throw Error(`checkAndGetSessionAndWindowId should not be called with cookieless_mode="always"`);var n=t||new Date().getTime(),[r,i,a]=this.wr(),o=this.br(),s=cf(a)&&Math.abs(n-a)>864e5,c=!1,l=!i,u=!l&&!e&&this.sr(n,r);l||u||s?(i=this.ur(),o=this.hr(),l_.info(`new session ID generated`,{sessionId:i,windowId:o,changeReason:{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}}),a=n,c=!0):o||(o=this.hr(),c=!0);var d=cf(r)&&e&&!s?r:n,f=cf(a)?a:new Date().getTime();return this.yr(o),this.gr(i,d,f),e||this.cr(),c&&this.rr.forEach((e=>e(i,o,c?{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}:void 0))),{sessionId:i,windowId:o,sessionStartTimestamp:f,changeReason:c?{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}:void 0,lastActivityTimestamp:r}}cr(){clearTimeout(this._r),this._r=setTimeout((()=>{var[e]=this.wr();if(this.sr(new Date().getTime(),e)){var t=this.lr;this.resetSessionId(),this.ir.emit(`forcedIdleReset`,{idleSessionId:t})}}),1.1*this.sessionTimeoutMs)}},d_=function(e,t){if(!e)return!1;var n=e.userAgent;if(n&&Gd(n,t))return!0;try{var r=e?.userAgentData;if(r!=null&&r.brands&&r.brands.some((e=>Gd(e?.brand,t))))return!0}catch{}return!!e.webdriver},f_=function(e,t){if(!function(e){try{new RegExp(e)}catch{return!1}return!0}(t))return!1;try{return new RegExp(t).test(e)}catch{return!1}};function p_(e,t,n){return Xg({distinct_id:e,userPropertiesToSet:t,userPropertiesToSetOnce:n})}var m_={exact:(e,t)=>t.some((t=>e.some((e=>t===e)))),is_not:(e,t)=>t.every((t=>e.every((e=>t!==e)))),regex:(e,t)=>t.some((t=>e.some((e=>f_(t,e))))),not_regex:(e,t)=>t.every((t=>e.every((e=>!f_(t,e))))),icontains:(e,t)=>t.map(h_).some((t=>e.map(h_).some((e=>t.includes(e))))),not_icontains:(e,t)=>t.map(h_).every((t=>e.map(h_).every((e=>!t.includes(e))))),gt:(e,t)=>t.some((t=>{var n=parseFloat(t);return!isNaN(n)&&e.some((e=>n>parseFloat(e)))})),lt:(e,t)=>t.some((t=>{var n=parseFloat(t);return!isNaN(n)&&e.some((e=>ne.toLowerCase();function g_(e,t){return!e||Object.entries(e).every((e=>{var[n,r]=e,i=t?.[n];if(Z(i)||af(i))return!1;var a=[String(i)],o=m_[r.operator];return!!o&&o(r.values,a)}))}var __=`custom`,v_=`i.posthog.com`,y_=/^\/static\//,b_=class{constructor(e){this.Ir={},this.instance=e}get apiHost(){var e=this.instance.config.api_host.trim().replace(/\/$/,``);return e===`https://app.posthog.com`?`https://us.i.posthog.com`:e}get flagsApiHost(){var e=this.instance.config.flags_api_host;return e?e.trim().replace(/\/$/,``):this.apiHost}get uiHost(){var e=this.instance.config.ui_host?.replace(/\/$/,``);return e||=this.apiHost.replace(`.`+v_,`.posthog.com`),e===`https://app.posthog.com`?`https://us.posthog.com`:e}get region(){return this.Ir[this.apiHost]||(this.Ir[this.apiHost]=/https:\/\/(app|us|us-assets)(\.i)?\.posthog\.com/i.test(this.apiHost)?`us`:/https:\/\/(eu|eu-assets)(\.i)?\.posthog\.com/i.test(this.apiHost)?`eu`:__),this.Ir[this.apiHost]}Cr(e){var t=this.instance.config.__preview_external_dependency_versioned_paths;if(typeof t==`string`&&y_.test(e))return t.trim().replace(/\/$/,``)||void 0}endpointFor(e,t){if(t===void 0&&(t=``),t&&=t[0]===`/`?t:`/`+t,e===`ui`)return this.uiHost+t;if(e===`flags`)return this.flagsApiHost+t;if(e===`assets`){var n=this.Cr(t);if(n)return``+n+t}if(this.region===__)return this.apiHost+t;var r=v_+t;switch(e){case`assets`:return`https://`+this.region+`-assets.`+r;case`api`:return`https://`+this.region+`.`+r}}},x_=Zp(`[Surveys]`),S_=`seenSurvey_`,C_=(e,t)=>{var n=`$survey_`+t+`/`+e.id;return e.current_iteration&&e.current_iteration>0&&(n=`$survey_`+t+`/`+e.id+`/`+e.current_iteration),n},w_=e=>((e,t)=>{var n=``+S_+t.id;return t.current_iteration&&t.current_iteration>0&&(n=``+S_+t.id+`_`+t.current_iteration),n})(0,e),T_=[rg.Popover,rg.Widget,rg.API],E_={ignoreConditions:!1,ignoreDelay:!1,displayType:og.Popover},D_=Zp(`[PostHog ExternalIntegrations]`),O_={intercom:`intercom-integration`,crispChat:`crisp-chat-integration`},k_=class{constructor(e){this._instance=e}ai(e,t){var n;(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,e,(e=>{if(e)return D_.error(`failed to load script`,e);t()}))}startIfEnabledOrStop(){var e=this,t=function(t){var n,i,a;!r||(n=Y.__PosthogExtensions__)!=null&&(n=n.integrations)!=null&&n[t]||e.ai(O_[t],(()=>{var n;(n=Y.__PosthogExtensions__)==null||(n=n.integrations)==null||(n=n[t])==null||n.start(e._instance)})),!r&&(i=Y.__PosthogExtensions__)!=null&&(i=i.integrations)!=null&&i[t]&&((a=Y.__PosthogExtensions__)==null||(a=a.integrations)==null||(a=a[t])==null||a.stop())};for(var[n,r]of Object.entries((i=this._instance.config.integrations)??{})){var i;t(n)}}},A_,j_={},M_=0,N_=()=>{},P_=`Consent opt in/out is not valid with cookieless_mode="always" and will be ignored`,F_=`Surveys module not available`,I_=`sanitize_properties is deprecated. Use before_send instead`,L_=`Invalid value for property_denylist config: `,R_=`posthog`,z_=!Kg&&Id?.indexOf(`MSIE`)===-1&&Id?.indexOf(`Mozilla`)===-1,B_=e=>{var t;return X({api_host:`https://us.i.posthog.com`,flags_api_host:null,ui_host:null,token:``,autocapture:!0,cross_subdomain_cookie:ah(J?.location),persistence:`localStorage+cookie`,persistence_name:``,cookie_persisted_properties:[],loaded:N_,save_campaign_params:!0,custom_campaign_params:[],custom_blocked_useragents:[],save_referrer:!0,capture_pageleave:`if_capture_pageview`,defaults:e??`unset`,__preview_deferred_init_extensions:!1,__preview_external_dependency_versioned_paths:!1,debug:jd&&nf(jd?.search)&&jd.search.indexOf(`__posthog_debug=true`)!==-1||!1,cookie_expiration:365,upgrade:!1,disable_session_recording:!1,disable_persistence:!1,disable_web_experiments:!0,disable_surveys:!1,disable_surveys_automatic_display:!1,disable_conversations:!1,disable_product_tours:!1,disable_external_dependency_loading:!1,enable_recording_console_log:void 0,secure_cookie:(q==null||(t=q.location)==null?void 0:t.protocol)===`https:`,ip:!1,opt_out_capturing_by_default:!1,opt_out_persistence_by_default:!1,opt_out_useragent_filter:!1,opt_out_capturing_persistence_type:`localStorage`,consent_persistence_name:null,opt_out_capturing_cookie_prefix:null,opt_in_site_apps:!1,property_denylist:[],respect_dnt:!1,sanitize_properties:null,request_headers:{},request_batching:!0,properties_string_max_length:65535,mask_all_element_attributes:!1,mask_all_text:!1,mask_personal_data_properties:!1,custom_personal_data_properties:[],advanced_disable_flags:!1,advanced_disable_decide:!1,advanced_disable_feature_flags:!1,advanced_disable_feature_flags_on_first_load:!1,advanced_only_evaluate_survey_feature_flags:!1,advanced_feature_flags_dedup_per_session:!1,advanced_enable_surveys:!1,advanced_disable_toolbar_metrics:!1,feature_flag_request_timeout_ms:3e3,surveys_request_timeout_ms:1e4,on_request_error(e){Q.error(`Bad HTTP status: `+e.statusCode+` `+e.text)},get_device_id:e=>e,capture_performance:void 0,name:`posthog`,bootstrap:{},disable_compression:!1,session_idle_timeout_seconds:1800,person_profiles:Wm,before_send:void 0,request_queue_config:{flush_interval_ms:t_},error_tracking:{},_onCapture:N_,__preview_eager_load_replay:!1},(e=>({rageclick:!e||`2025-11-30`>e||{content_ignorelist:!0},capture_pageview:!e||`2025-05-24`>e||`history_change`,session_recording:e&&e>=`2025-11-30`?{strictMinimumDuration:!0}:{},external_scripts_inject_target:e&&e>=`2026-01-30`?`head`:`body`,internal_or_test_user_hostname:e&&e>=`2026-01-30`?/^(localhost|127\.0\.0\.1)$/:void 0}))(e))},V_=[[`process_person`,`person_profiles`],[`xhr_headers`,`request_headers`],[`cookie_name`,`persistence_name`],[`disable_cookie`,`disable_persistence`],[`store_google`,`save_campaign_params`],[`verbose`,`debug`]],H_=e=>{var t={};for(var[n,r]of V_)Z(e[n])||(t[r]=e[n]);var i=$m({},t,e);return Qd(e.property_blacklist)&&(Z(e.property_denylist)?i.property_denylist=e.property_blacklist:Qd(e.property_denylist)?i.property_denylist=[...e.property_blacklist,...e.property_denylist]:Q.error(L_+e.property_denylist)),i},U_=class{constructor(){this.__forceAllowLocalhost=!1}get Sr(){return this.__forceAllowLocalhost}set Sr(e){Q.error("WebPerformanceObserver is deprecated and has no impact on network capture. Use `_forceAllowLocalhostNetworkCapture` on `posthog.sessionRecording`"),this.__forceAllowLocalhost=e}},W_=class e{kr(e,t){if(e){var n=this.Tr.indexOf(e);n!==-1&&this.Tr.splice(n,1)}return this.Tr.push(t),t.initialize==null||t.initialize(),t}Ar(){return this.config.cookieless_mode===Vm||this.config.cookieless_mode===Bm&&this.consent.isRejected()}get decideEndpointWasHit(){var e;return(e=this.featureFlags?.hasLoadedFlags)!=null&&e}get flagsEndpointWasHit(){var e;return(e=this.featureFlags?.hasLoadedFlags)!=null&&e}constructor(){this.webPerformance=new U_,this.Er=!1,this.version=Rd.LIB_VERSION,this.Rr=new c_,this.Tr=[],this._calculate_event_properties=this.calculateEventProperties.bind(this),this.config=B_(),this.SentryIntegration=Nh,this.sentryIntegration=e=>function(e,t){var n=Mh(e,t);return{name:jh,processEvent:e=>n(e)}}(this,e),this.__request_queue=[],this.__loaded=!1,this.analyticsDefaultEndpoint=`/e/`,this.Nr=!1,this.Mr=null,this.Fr=null,this.Or=null,this.scrollManager=new a_(this),this.pageViewManager=new Ph(this),this.rateLimiter=new ug(this),this.requestRouter=new b_(this),this.consent=new Th(this),this.externalIntegrations=new k_(this);var t=e.__defaultExtensionClasses??{};this.featureFlags=t.featureFlags&&new t.featureFlags(this),this.toolbar=t.toolbar&&new t.toolbar(this),this.surveys=t.surveys&&new t.surveys(this),this.conversations=t.conversations&&new t.conversations(this),this.logs=t.logs&&new t.logs(this),this.experiments=t.experiments&&new t.experiments(this),this.exceptions=t.exceptions&&new t.exceptions(this),this.people={set:(e,t,n)=>{var r=nf(e)?{[e]:t}:e;this.setPersonProperties(r),n?.({})},set_once:(e,t,n)=>{var r=nf(e)?{[e]:t}:e;this.setPersonProperties(void 0,r),n?.({})}},this.on(`eventCaptured`,(e=>Q.info(`send "`+e?.event+`"`,e)))}init(t,n,r){if(r&&r!==R_){var i=j_[r]??new e;return i._init(t,n,r),j_[r]=i,j_[R_][r]=i,i}return this._init(t,n,r)}_init(e,t,n){var r;if(t===void 0&&(t={}),Z(e)||rf(e))return Q.critical(`PostHog was initialized without a token. This likely indicates a misconfiguration. Please check the first argument passed to posthog.init()`),this;if(this.__loaded)return console.warn(`[PostHog.js]`,`You have already initialized PostHog! Re-initializing is a no-op`),this;this.__loaded=!0,this.config={},t.debug=this.Pr(t.debug),this.Lr=t,this.Dr=[],t.person_profiles?this.Fr=t.person_profiles:t.process_person&&(this.Fr=t.process_person),this.set_config($m({},B_(t.defaults),H_(t),{name:n,token:e})),this.config.on_xhr_error&&Q.error(`on_xhr_error is deprecated. Use on_request_error instead`),this.compression=t.disable_compression?void 0:pg.GZipJS;var i=this.Br();this.persistence=new tg(this.config,i),this.sessionPersistence=this.config.persistence===`sessionStorage`||this.config.persistence===`memory`?this.persistence:new tg(X({},this.config,{persistence:`sessionStorage`}),i);var a=X({},this.persistence.props),o=X({},this.sessionPersistence.props);this.register({$initialization_time:new Date().toISOString()}),this.jr=new n_((e=>this.qr(e)),this.config.request_queue_config),this.Zr=new i_(this),this.__request_queue=[];var s=this.Ar();if(s||(this.sessionManager=new u_(this),this.sessionPropsManager=new s_(this,this.sessionManager,this.persistence)),this.config.__preview_deferred_init_extensions?(Q.info(`Deferring extension initialization to improve startup performance`),setTimeout((()=>{this.$r(s)}),0)):(Q.info(`Initializing extensions synchronously`),this.$r(s)),Rd.DEBUG=Rd.DEBUG||this.config.debug,Rd.DEBUG&&Q.info(`Starting in debug mode`,{this:this,config:t,thisC:X({},this.config),p:a,s:o}),!this.config.identity_distinct_id||(r=t.bootstrap)!=null&&r.distinctID||(t.bootstrap=X({},t.bootstrap,{distinctID:this.config.identity_distinct_id,isIdentifiedID:!0})),t.bootstrap?.distinctID!==void 0){var c=t.bootstrap.distinctID,l=this.get_distinct_id(),u=this.persistence.get_property(km);if(t.bootstrap.isIdentifiedID&&l!=null&&l!==c&&u===Hm)this.identify(c);else if(t.bootstrap.isIdentifiedID&&l!=null&&l!==c&&u===Um)Q.warn(`Bootstrap distinctID differs from an already-identified user. The existing identity is preserved. Call reset() before reinitializing if you intend to switch users.`);else{var d=this.config.get_device_id(ph()),f=t.bootstrap.isIdentifiedID?d:c;this.persistence.set_property(km,t.bootstrap.isIdentifiedID?Um:Hm),this.register({distinct_id:c,$device_id:f})}}if(s)this.register_once({distinct_id:Lm,$device_id:null},``);else if(!this.get_distinct_id()){var p=this.config.get_device_id(ph());this.register_once({distinct_id:p,$device_id:p},``),this.persistence.set_property(km,Hm)}return oh(q,`onpagehide`in self?`pagehide`:`unload`,this._handle_unload.bind(this),{passive:!1}),t.segment?function(e,t){var n=e.config.segment;if(!n)return t();(function(e,t){var n=e.config.segment;if(!n)return t();var r=n=>{var r=()=>n.anonymousId()||ph();e.config.get_device_id=r,n.id()&&(e.register({distinct_id:n.id(),$device_id:r()}),e.persistence.set_property(km,Um)),t()},i=n.user();`then`in i&&$d(i.then)?i.then(r):r(i)})(e,(()=>{n.register((e=>{Promise&&Promise.resolve||Ah.warn(`This browser does not have Promise support, and can not use the segment integration`);var t=(t,n)=>{if(!n)return t;t.event.userId||t.event.anonymousId===e.get_distinct_id()||(Ah.info(`No userId set, resetting PostHog`),e.reset()),t.event.userId&&t.event.userId!==e.get_distinct_id()&&(Ah.info(`UserId set, identifying with PostHog`),e.identify(t.event.userId));var r=e.calculateEventProperties(n,t.event.properties);return t.event.properties=Object.assign({},r,t.event.properties),t};return{name:`PostHog JS`,type:`enrichment`,version:`1.0.0`,isLoaded:()=>!0,load:()=>Promise.resolve(),track:e=>t(e,e.event.event),page:e=>t(e,qm),identify:e=>t(e,Ym),screen:e=>t(e,`$screen`)}})(e)).then((()=>{t()}))}))}(this,(()=>this.Hr())):this.Hr(),$d(this.config._onCapture)&&this.config._onCapture!==N_&&(Q.warn("onCapture is deprecated. Please use `before_send` instead"),this.on(`eventCaptured`,(e=>this.config._onCapture(e.event,e)))),this.config.ip&&Q.warn('The `ip` config option has NO EFFECT AT ALL and has been deprecated. Use a custom transformation or "Discard IP data" project setting instead. See https://posthog.com/tutorials/web-redact-properties#hiding-customer-ip-address for more information.'),this}$r(t){var n=performance.now(),r=X({},e.__defaultExtensionClasses,this.config.__extensionClasses),i=[];r.featureFlags&&this.Tr.push(this.featureFlags=this.featureFlags??new r.featureFlags(this)),r.exceptions&&this.Tr.push(this.exceptions=this.exceptions??new r.exceptions(this)),r.historyAutocapture&&this.Tr.push(this.historyAutocapture=new r.historyAutocapture(this)),r.tracingHeaders&&this.Tr.push(new r.tracingHeaders(this)),r.siteApps&&this.Tr.push(this.siteApps=new r.siteApps(this)),r.sessionRecording&&!t&&this.Tr.push(this.sessionRecording=new r.sessionRecording(this)),this.config.disable_scroll_properties||i.push((()=>{this.scrollManager.startMeasuringScrollPosition()})),r.autocapture&&this.Tr.push(this.autocapture=new r.autocapture(this)),r.surveys&&this.Tr.push(this.surveys=this.surveys??new r.surveys(this)),r.logs&&this.Tr.push(this.logs=this.logs??new r.logs(this)),r.conversations&&this.Tr.push(this.conversations=this.conversations??new r.conversations(this)),r.productTours&&this.Tr.push(this.productTours=new r.productTours(this)),r.heatmaps&&this.Tr.push(this.heatmaps=new r.heatmaps(this)),r.webVitalsAutocapture&&this.Tr.push(this.webVitalsAutocapture=new r.webVitalsAutocapture(this)),r.exceptionObserver&&this.Tr.push(this.exceptionObserver=new r.exceptionObserver(this)),r.deadClicksAutocapture&&this.Tr.push(this.deadClicksAutocapture=new r.deadClicksAutocapture(this,Oh)),r.toolbar&&this.Tr.push(this.toolbar=this.toolbar??new r.toolbar(this)),r.experiments&&this.Tr.push(this.experiments=this.experiments??new r.experiments(this)),this.Tr.forEach((e=>{e.initialize&&i.push((()=>{e.initialize==null||e.initialize()}))})),i.push((()=>{if(this.Vr){var e=this.Vr;this.Vr=void 0,this.ki(e)}})),this.zr(i,n)}zr(e,t){for(;e.length>0;){if(this.config.__preview_deferred_init_extensions&&performance.now()-t>=30&&e.length>0)return void setTimeout((()=>{this.zr(e,t)}),0);var n=e.shift();if(n)try{n()}catch(e){Q.error(`Error initializing extension:`,e)}}var r=Math.round(performance.now()-t);this.register_for_session({$sdk_debug_extensions_init_method:this.config.__preview_deferred_init_extensions?`deferred`:`synchronous`,$sdk_debug_extensions_init_time_ms:r}),this.config.__preview_deferred_init_extensions&&Q.info(`PostHog extensions initialized (`+r+`ms)`)}ki(e){var t;if(!J||!J.body)return Q.info(`document not ready yet, trying again in 500 milliseconds...`),void setTimeout((()=>{this.ki(e)}),500);this.config.__preview_deferred_init_extensions&&(this.Vr=e),this.Ur=e,this.compression=void 0,e.supportedCompression&&!this.config.disable_compression&&(this.compression=Kd(e.supportedCompression,pg.GZipJS)?pg.GZipJS:Kd(e.supportedCompression,pg.Base64)?pg.Base64:void 0),(t=e.analytics)!=null&&t.endpoint&&(this.analyticsDefaultEndpoint=e.analytics.endpoint),this.set_config({person_profiles:this.Fr?this.Fr:Wm}),this.Tr.forEach((t=>t.onRemoteConfig==null?void 0:t.onRemoteConfig(e)))}Hr(){try{this.config.loaded(this)}catch(e){Q.critical("`loaded` function failed",e)}if(this.Yr(),this.config.internal_or_test_user_hostname&&jd!=null&&jd.hostname){var e=jd.hostname,t=this.config.internal_or_test_user_hostname;(typeof t==`string`?e===t:t.test(e))&&this.setInternalOrTestUser()}this.config.capture_pageview&&setTimeout((()=>{(this.consent.isOptedIn()||this.Ar())&&this.Wr()}),1),this.Gr=new fg(this),this.Gr.load()}Yr(){var e;this.is_capturing()&&this.config.request_batching&&((e=this.jr)==null||e.enable())}_dom_loaded(){this.is_capturing()&&Zm(this.__request_queue,(e=>this.qr(e))),this.__request_queue=[],this.Yr()}_handle_unload(){var e,t,n,r;(e=this.surveys)==null||e.handlePageUnload(),this.config.request_batching?(this.Xr()&&this.capture(Jm),(t=this.logs)==null||t.flushLogs(`sendBeacon`),(n=this.jr)==null||n.unload(),(r=this.Zr)==null||r.unload()):this.Xr()&&this.capture(Jm,null,{transport:`sendBeacon`})}_send_request(e){this.__loaded&&(z_?this.__request_queue.push(e):this.rateLimiter.isServerRateLimited(e.batchKey)||(e.transport=e.transport||this.config.api_transport,e.url=Yg(e.url,{ip:+!!this.config.ip}),e.headers=X({},this.config.request_headers,e.headers),e.compression=e.compression===`best-available`?this.compression:e.compression,e.disableXHRCredentials=this.config.__preview_disable_xhr_credentials,this.config.__preview_disable_beacon&&(e.disableTransport=[`sendBeacon`]),e.fetchOptions=e.fetchOptions||this.config.fetch_options,(e=>{var t=X({},e);t.timeout=t.timeout||6e4,t.url=$g(t.url,t.compression);var n=t.transport??`fetch`,r=e_.filter((e=>!t.disableTransport||!e.transport||!t.disableTransport.includes(e.transport))),i=function(e,t){for(var r=0;e.length>r;r++)if(e[r].transport===n)return e[r]}(r)?.method??r[0].method;if(!i)throw Error(`No available transport method`);n!==`sendBeacon`&&t.data&&t.compression===pg.GZipJS&&Fd&&!Jg?Qg(t).then((e=>{i(e)})).catch((n=>{if((e=>!(!e||typeof e!=`object`)&&(`name`in e?String(e.name):``)===`NotReadableError`)(n))return Jg=!0,void i(X({},t,{compression:void 0,url:$g(e.url,void 0)}));i(t)})):i(t)})(X({},e,{callback:t=>{var n,r;this.rateLimiter.checkForLimiting(t),400>t.statusCode||(n=(r=this.config).on_request_error)==null||n.call(r,t),e.callback==null||e.callback(t)}}))))}qr(e){this.Zr?this.Zr.retriableRequest(e):this._send_request(e)}_execute_array(e){M_++;try{var t,n=[],r=[],i=[];Zm(e,(e=>{e&&(Qd(t=e[0])?i.push(e):$d(e)?e.call(this):Qd(e)&&t===`alias`?n.push(e):Qd(e)&&t.indexOf(`capture`)!==-1&&$d(this[t])?i.push(e):r.push(e))}));var a=function(e,t){Zm(e,(function(e){if(Qd(e[0])){var n=t;Qm(e,(function(e){n=n[e[0]].apply(n,e.slice(1))}))}else t[e[0]].apply(t,e.slice(1))}))};a(n,this),a(r,this),a(i,this)}finally{M_--}}push(t){if(M_>0&&Qd(t)&&nf(t[0])){var n=e.prototype[t[0]];$d(n)&&n.apply(this,t.slice(1))}else this._execute_array([t])}capture(e,t,n){var r,i,a,o;if(this.__loaded&&this.persistence&&this.sessionPersistence&&this.jr){if(this.is_capturing())if(!Z(e)&&nf(e)){var s=!this.config.opt_out_useragent_filter&&this._is_bot();if(!s||this.config.__preview_capture_bot_pageviews){var c=n!=null&&n.skip_client_rate_limiting?void 0:this.rateLimiter.clientRateLimitContext();if(c==null||!c.isRateLimited){t!=null&&t.$current_url&&!nf(t?.$current_url)&&(Q.error("Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value."),t==null||delete t.$current_url),e!==`$exception`||n!=null&&n.Jr||Q.warn("Using `posthog.capture('$exception')` is unreliable because it does not attach required metadata. Use `posthog.captureException(error)` instead, which attaches required metadata automatically."),this.sessionPersistence.update_search_keyword(),this.config.save_campaign_params&&this.sessionPersistence.update_campaign_params(),this.config.save_referrer&&this.sessionPersistence.update_referrer_info(),(this.config.save_campaign_params||this.config.save_referrer)&&this.persistence.set_initial_person_info();var l=new Date,u=n?.timestamp||l,d=ph(),f={uuid:d,event:e,properties:this.calculateEventProperties(e,t||{},u,d)};e===qm&&this.config.__preview_capture_bot_pageviews&&s&&(f.event=`$bot_pageview`,f.properties.$browser_type=`bot`),c&&(f.properties.$lib_rate_limit_remaining_tokens=c.remainingTokens),n!=null&&n.$set&&(f.$set=n?.$set);var p,m=this.Kr(n?.$set_once,e!==Xm,e===Ym);if(m&&(f.$set_once=m),n!=null&&n._noTruncate||(r=this.config.properties_string_max_length,i=f,a=e=>nf(e)?e.slice(0,r):e,o=new Set,f=function e(t,n){return t===Object(t)?o.has(t)?void 0:(o.add(t),Qd(t)?(r=[],Zm(t,(t=>{r.push(e(t))}))):(r={},Qm(t,((t,n)=>{o.has(t)||(r[n]=e(t,n))}))),r):a?a(t):t;var r}(i)),f.timestamp=u,Z(n?.timestamp)||(f.properties.$event_time_override_provided=!0,f.properties.$event_time_override_system_time=l),e===ig.DISMISSED||e===ig.SENT){var h=t?.[ag.SURVEY_ID],g=t?.[ag.SURVEY_ITERATION];p={id:h,current_iteration:g},localStorage.getItem(w_(p))||localStorage.setItem(w_(p),`true`),f.$set=X({},f.$set,{[C_({id:h,current_iteration:g},e===ig.SENT?`responded`:`dismissed`)]:!0})}else e===ig.SHOWN&&(f.$set=X({},f.$set,{[ag.SURVEY_LAST_SEEN_DATE]:new Date().toISOString()}));if(e===sg.SHOWN){var _=t?.[cg.TOUR_TYPE];_&&(f.$set=X({},f.$set,{[cg.TOUR_LAST_SEEN_DATE+`/`+_]:new Date().toISOString()}))}var v=X({},f.properties.$set,f.$set);if(tf(v)||this.setPersonPropertiesForFlags(v),!of(this.config.before_send)){var y=this.Qr(f);if(!y)return;f=y}this.Rr.emit(`eventCaptured`,f);var b={method:`POST`,url:n?._url??this.requestRouter.endpointFor(`api`,this.analyticsDefaultEndpoint),data:f,compression:`best-available`,batchKey:n?._batchKey};return!this.config.request_batching||n&&(n==null||!n._batchKey)||n!=null&&n.send_instantly?this.qr(b):this.jr.enqueue(b),f}Q.critical(`This capture call is ignored due to client rate limiting.`)}}else Q.error(`No event name provided to posthog.capture`)}else Q.uninitializedWarning(`posthog.capture`)}_addCaptureHook(e){return this.on(`eventCaptured`,(t=>e(t.event,t)))}calculateEventProperties(e,t,n,r,i){if(n||=new Date,!this.persistence||!this.sessionPersistence)return t;var a=i?void 0:this.persistence.remove_event_timer(e),o=X({},t);if(o.token=this.config.token,o.$config_defaults=this.config.defaults,this.Ar()&&(o.$cookieless_mode=!0),e===`$snapshot`){var s=X({},this.persistence.properties(),this.sessionPersistence.properties());return o.distinct_id=s.distinct_id,(!nf(o.distinct_id)&&!sf(o.distinct_id)||rf(o.distinct_id))&&Q.error(`Invalid distinct_id for replay event. This indicates a bug in your implementation`),o}var c,l=function(e,t){var n,r,i;if(!Id)return{};var a,o,s,c,l=e?[...Bh,...t||[]]:[],[u,d]=function(e){for(var t=0;gp.length>t;t++){var[n,r]=gp[t],i=n.exec(e),a=i&&($d(r)?r(i,e):r);if(a)return a}return[``,``]}(Id);return $m(rh({$os:u,$os_version:d,$browser:pp(Id,navigator.vendor),$device:_p(Id),$device_type:(o=Id,s={userAgentDataPlatform:(n=navigator)==null||(n=n.userAgentData)==null?void 0:n.platform,maxTouchPoints:navigator?.maxTouchPoints,screenWidth:q==null||(r=q.screen)==null?void 0:r.width,screenHeight:q==null||(i=q.screen)==null?void 0:i.height,devicePixelRatio:q?.devicePixelRatio},c=_p(o),c===Of||c===Df||c===`Kobo`||c===`Kindle Fire`||c===ip?Ef:c===qf||c===Yf||c===Jf||c===tp?`Console`:c===Af?`Wearable`:c?Cf:s?.userAgentDataPlatform===`Android`&&(s?.maxTouchPoints??0)>0?600>Math.min(s?.screenWidth??0,s?.screenHeight??0)/(s?.devicePixelRatio??1)?Cf:Ef:`Desktop`),$timezone:Qh(),$timezone_offset:$h()}),{$current_url:Lh(jd?.href,l,Hh),$host:jd?.host,$pathname:jd?.pathname,$raw_user_agent:Id.length>1e3?Id.substring(0,997)+`...`:Id,$browser_version:hp(Id,navigator.vendor),$browser_language:qh(),$browser_language_prefix:(a=qh(),typeof a==`string`?a.split(`-`)[0]:void 0),$screen_height:q?.screen.height,$screen_width:q?.screen.width,$viewport_height:q?.innerHeight,$viewport_width:q?.innerWidth,$lib:Rd.LIB_NAME,$lib_version:Rd.LIB_VERSION,$insert_id:Math.random().toString(36).substring(2,10)+Math.random().toString(36).substring(2,10),$time:Date.now()/1e3})}(this.config.mask_personal_data_properties,this.config.custom_personal_data_properties);if(this.sessionManager){var{sessionId:u,windowId:d}=this.sessionManager.checkAndGetSessionAndWindowId(i,n.getTime());o.$session_id=u,o.$window_id=d}this.sessionPropsManager&&$m(o,this.sessionPropsManager.getSessionProps());try{this.sessionRecording&&$m(o,this.sessionRecording.sdkDebugProperties),o.$sdk_debug_retry_queue_size=this.Zr?.length}catch(e){o.$sdk_debug_error_capturing_properties=String(e)}if(this.requestRouter.region===__&&(o.$lib_custom_api_host=this.config.api_host),c=e!==qm||i?e!==Jm||i?this.pageViewManager.doEvent():this.pageViewManager.doPageLeave(n):this.pageViewManager.doPageView(n,r),o=$m(o,c),e===qm&&J&&(o.title=J.title),!Z(a)){var f=n.getTime()-a;o.$duration=parseFloat((f/1e3).toFixed(3))}Id&&this.config.opt_out_useragent_filter&&(o.$browser_type=this._is_bot()?`bot`:`browser`),(o=$m({},l,this.persistence.properties(),this.sessionPersistence.properties(),o)).$is_identified=this._isIdentified(),Qd(this.config.property_denylist)?Qm(this.config.property_denylist,(function(e){delete o[e]})):Q.error(L_+this.config.property_denylist+` or property_blacklist config: `+this.config.property_blacklist);var p=this.config.sanitize_properties;p&&(Q.error(I_),o=p(o,e));var m=this.tn();return o.$process_person_profile=m,m&&!i&&this.en(`_calculate_event_properties`),o}Kr(e,t,n){if(t===void 0&&(t=!0),n===void 0&&(n=!1),!this.persistence||!this.tn()||this.Er&&!n)return e;var r=$m({},this.persistence.get_initial_props(),this.sessionPropsManager?.getSetOnceProps()||{},e||{}),i=this.config.sanitize_properties;return i&&(Q.error(I_),r=i(r,`$set_once`)),t&&(this.Er=!0),tf(r)?void 0:r}register(e,t){var n;(n=this.persistence)==null||n.register(e,t)}register_once(e,t,n){var r;(r=this.persistence)==null||r.register_once(e,t,n)}register_for_session(e){var t;(t=this.sessionPersistence)==null||t.register(e)}unregister(e){var t;(t=this.persistence)==null||t.unregister(e)}unregister_for_session(e){var t;(t=this.sessionPersistence)==null||t.unregister(e)}rn(e,t){this.register({[e]:t})}getFeatureFlag(e,t){return this.featureFlags?.getFeatureFlag(e,t)}getFeatureFlagPayload(e){return this.featureFlags?.getFeatureFlagPayload(e)}getFeatureFlagResult(e,t){return this.featureFlags?.getFeatureFlagResult(e,t)}isFeatureEnabled(e,t){return this.featureFlags?.isFeatureEnabled(e,t)}reloadFeatureFlags(){var e;(e=this.featureFlags)==null||e.reloadFeatureFlags()}updateFlags(e,t,n){var r;(r=this.featureFlags)==null||r.updateFlags(e,t,n)}updateEarlyAccessFeatureEnrollment(e,t,n){var r;(r=this.featureFlags)==null||r.updateEarlyAccessFeatureEnrollment(e,t,n)}getEarlyAccessFeatures(e,t,n){return t===void 0&&(t=!1),this.featureFlags?.getEarlyAccessFeatures(e,t,n)}on(e,t){return this.Rr.on(e,t)}onFeatureFlags(e){return this.featureFlags?this.featureFlags.onFeatureFlags(e):(e([],{},{errorsLoading:!0}),()=>{})}onSurveysLoaded(e){return this.surveys?this.surveys.onSurveysLoaded(e):(e([],{isLoaded:!1,error:F_}),()=>{})}onSessionId(e){return this.sessionManager?.onSessionId(e)??(()=>{})}getSurveys(e,t){t===void 0&&(t=!1),this.surveys?this.surveys.getSurveys(e,t):e([],{isLoaded:!1,error:F_})}getActiveMatchingSurveys(e,t){t===void 0&&(t=!1),this.surveys?this.surveys.getActiveMatchingSurveys(e,t):e([],{isLoaded:!1,error:F_})}renderSurvey(e,t){var n;(n=this.surveys)==null||n.renderSurvey(e,t)}displaySurvey(e,t){var n;t===void 0&&(t=E_),(n=this.surveys)==null||n.displaySurvey(e,t)}cancelPendingSurvey(e){var t;(t=this.surveys)==null||t.cancelPendingSurvey(e)}canRenderSurvey(e){return this.surveys?.canRenderSurvey(e)??{visible:!1,disabledReason:F_}}canRenderSurveyAsync(e,t){return t===void 0&&(t=!1),this.surveys?.canRenderSurveyAsync(e,t)??Promise.resolve({visible:!1,disabledReason:F_})}nn(e){return!e||rf(e)?(Q.critical(`Unique user id has not been set in posthog.identify`),!1):e===Lm?(Q.critical(`The string "`+e+`" was set in posthog.identify which indicates an error. This ID is only used as a sentinel value.`),!1):![`distinct_id`,`distinctid`].includes(e.toLowerCase())&&![`undefined`,`null`].includes(e.toLowerCase())||(Q.critical(`The string "`+e+`" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.`),!1)}identify(e,t,n){if(!this.__loaded||!this.persistence)return Q.uninitializedWarning(`posthog.identify`);if(sf(e)&&(e=e.toString(),Q.warn(`The first argument to posthog.identify was a number, but it should be a string. It has been converted to a string.`)),this.nn(e)&&this.en(`posthog.identify`)){var r=this.get_distinct_id();this.register({$user_id:e}),this.get_property(tm)||this.register_once({$had_persisted_distinct_id:!0,$device_id:r},``),e!==r&&e!==this.get_property(nm)&&(this.unregister(nm),this.register({distinct_id:e}));var i,a=(this.persistence.get_property(km)||Hm)===Hm;e!==r&&a?(this.persistence.set_property(km,Um),this.setPersonPropertiesForFlags({$set:t||{},$set_once:n||{}},!1),this.capture(Ym,{distinct_id:e,$anon_distinct_id:r},{$set:t||{},$set_once:n||{}}),this.Or=p_(e,t,n),(i=this.featureFlags)==null||i.setAnonymousDistinctId(r)):(t||n)&&this.setPersonProperties(t,n),e!==r&&(this.reloadFeatureFlags(),this.unregister(Tm))}}setPersonProperties(e,t){if((e||t)&&this.en(`posthog.setPersonProperties`)){var n=p_(this.get_distinct_id(),e,t);this.Or===n?Q.info(`A duplicate setPersonProperties call was made with the same properties. It has been ignored.`):(this.setPersonPropertiesForFlags({$set:e||{},$set_once:t||{}},!0),this.capture(`$set`,{$set:e||{},$set_once:t||{}}),this.Or=n)}}group(e,t,n){if(e&&t){var r=this.getGroups(),i=r[e]!==t;if(i&&this.resetGroupPropertiesForFlags(e),this.register({$groups:X({},r,{[e]:t})}),i||n){var a={$group_type:e,$group_key:t};n&&(a.$group_set=n),this.capture(Xm,a)}n&&this.setGroupPropertiesForFlags({[e]:n}),i&&!n&&this.reloadFeatureFlags()}else Q.error(`posthog.group requires a group type and group key`)}resetGroups(){this.register({$groups:{}}),this.resetGroupPropertiesForFlags(),this.reloadFeatureFlags()}setPersonPropertiesForFlags(e,t){var n;t===void 0&&(t=!0),(n=this.featureFlags)==null||n.setPersonPropertiesForFlags(e,t)}resetPersonPropertiesForFlags(){var e;(e=this.featureFlags)==null||e.resetPersonPropertiesForFlags()}setGroupPropertiesForFlags(e,t){var n;t===void 0&&(t=!0),this.en(`posthog.setGroupPropertiesForFlags`)&&((n=this.featureFlags)==null||n.setGroupPropertiesForFlags(e,t))}resetGroupPropertiesForFlags(e){var t;(t=this.featureFlags)==null||t.resetGroupPropertiesForFlags(e)}reset(e){var t,n,r,i,a,o,s,c;if(Q.info(`reset`),!this.__loaded)return Q.uninitializedWarning(`posthog.reset`);var l=this.get_property(tm);if(this.consent.reset(),(t=this.persistence)==null||t.clear(),(n=this.sessionPersistence)==null||n.clear(),(r=this.surveys)==null||r.reset(),(i=this.Gr)==null||i.stop(),(a=this.featureFlags)==null||a.reset(),(o=this.conversations)==null||o.reset(),(s=this.persistence)==null||s.set_property(km,Hm),(c=this.sessionManager)==null||c.resetSessionId(),this.Or=null,this.config.cookieless_mode===Vm)this.register_once({distinct_id:Lm,$device_id:null},``);else{var u=this.config.get_device_id(ph());this.register_once({distinct_id:u,$device_id:e?u:l},``)}this.register({$last_posthog_reset:new Date().toISOString()},1),delete this.config.identity_distinct_id,delete this.config.identity_hash,this.reloadFeatureFlags()}setIdentity(e,t){var n;this.config.identity_distinct_id=e,this.config.identity_hash=t,this.alias(e),(n=this.conversations)==null||n.sn()}clearIdentity(){var e;delete this.config.identity_distinct_id,delete this.config.identity_hash,(e=this.conversations)==null||e.an()}get_distinct_id(){return this.get_property(`distinct_id`)}getGroups(){return this.get_property(`$groups`)||{}}get_session_id(){return this.sessionManager?.checkAndGetSessionAndWindowId(!0).sessionId??``}get_session_replay_url(e){if(!this.sessionManager)return``;var{sessionId:t,sessionStartTimestamp:n}=this.sessionManager.checkAndGetSessionAndWindowId(!0),r=this.requestRouter.endpointFor(`ui`,`/project/`+this.config.token+`/replay/`+t);if(e!=null&&e.withTimestamp&&n){var i=e.timestampLookBack??10;if(!n)return r;r+=`?t=`+Math.max(Math.floor((new Date().getTime()-n)/1e3)-i,0)}return r}alias(e,t){return e===this.get_property(em)?(Q.critical(`Attempting to create alias for existing People user - aborting.`),-2):this.en(`posthog.alias`)?(Z(t)&&(t=this.get_distinct_id()),e===t?(Q.warn(`alias matches current distinct_id - skipping api call.`),this.identify(e),-1):(this.rn(nm,e),this.capture(`$create_alias`,{alias:e,distinct_id:t}))):void 0}set_config(e){var t=X({},this.config);if(ef(e)){var n,r,i,a,o,s,c,l,u,d;$m(this.config,H_(e));var f=this.Br();(n=this.persistence)==null||n.update_config(this.config,t,f),this.sessionPersistence=this.config.persistence===`sessionStorage`||this.config.persistence===`memory`?this.persistence:new tg(X({},this.config,{persistence:`sessionStorage`}),f);var p=this.Pr(this.config.debug);lf(p)&&(this.config.debug=p),lf(this.config.debug)&&(this.config.debug?(Rd.DEBUG=!0,yh.Gt()&&yh.Qt(`ph_debug`,!0),Q.info(`set_config`,{config:e,oldConfig:t,newConfig:X({},this.config)})):(Rd.DEBUG=!1,yh.Gt()&&yh.ti(`ph_debug`))),(r=this.exceptionObserver)==null||r.onConfigChange(),(i=this.exceptions)==null||i.onConfigChange(),(a=this.sessionRecording)==null||a.startIfEnabledOrStop(),(o=this.autocapture)==null||o.startIfEnabled(),(s=this.heatmaps)==null||s.startIfEnabled(),(c=this.exceptionObserver)==null||c.startIfEnabledOrStop(),(l=this.deadClicksAutocapture)==null||l.startIfEnabledOrStop(),(u=this.surveys)==null||u.loadIfEnabled(),this.ln(),(d=this.externalIntegrations)==null||d.startIfEnabledOrStop()}}_overrideSDKInfo(e,t){Rd.LIB_NAME=e,Rd.LIB_VERSION=t}startSessionRecording(e){var t,n,r,i,a,o=!0===e,s={sampling:o||!(e==null||!e.sampling),linked_flag:o||!(e==null||!e.linked_flag),url_trigger:o||!(e==null||!e.url_trigger),event_trigger:o||!(e==null||!e.event_trigger)};Object.values(s).some(Boolean)&&((t=this.sessionManager)==null||t.checkAndGetSessionAndWindowId(),s.sampling&&((n=this.sessionRecording)==null||n.overrideSampling()),s.linked_flag&&((r=this.sessionRecording)==null||r.overrideLinkedFlag()),s.url_trigger&&((i=this.sessionRecording)==null||i.overrideTrigger(`url`)),s.event_trigger&&((a=this.sessionRecording)==null||a.overrideTrigger(`event`))),this.set_config({disable_session_recording:!1})}stopSessionRecording(){this.set_config({disable_session_recording:!0})}sessionRecordingStarted(){var e;return!((e=this.sessionRecording)==null||!e.started)}captureException(e,t){if(this.exceptions){var n=Error(`PostHog syntheticException`),r=this.exceptions.buildProperties(e,{handled:!0,syntheticException:n});return this.exceptions.sendExceptionEvent(X({},r,t))}}addExceptionStep(e,t){var n;(n=this.exceptions)==null||n.addExceptionStep(e,t)}captureLog(e){var t;(t=this.logs)==null||t.captureLog(e)}get logger(){return this.logs?.logger??e.un}startExceptionAutocapture(e){this.set_config({capture_exceptions:e==null||e})}stopExceptionAutocapture(){this.set_config({capture_exceptions:!1})}loadToolbar(e){var t;return(t=this.toolbar?.loadToolbar(e))!=null&&t}get_property(e){return this.persistence?.props[e]}getSessionProperty(e){return this.sessionPersistence?.props[e]}toString(){var e=this.config.name??R_;return e!==R_&&(e=R_+`.`+e),e}_isIdentified(){return this.persistence?.get_property(km)===Um||this.sessionPersistence?.get_property(km)===Um}tn(){var e,t;return!(this.config.person_profiles===`never`||this.config.person_profiles===Wm&&!this._isIdentified()&&tf(this.getGroups())&&((e=this.persistence)==null||(e=e.props)==null||!e[nm])&&((t=this.persistence)==null||(t=t.props)==null||!t[Fm]))}Xr(){return!0===this.config.capture_pageleave||this.config.capture_pageleave===`if_capture_pageview`&&(!0===this.config.capture_pageview||this.config.capture_pageview===`history_change`)}createPersonProfile(){this.tn()||this.en(`posthog.createPersonProfile`)&&this.setPersonProperties({},{})}setInternalOrTestUser(){this.en(`posthog.setInternalOrTestUser`)&&this.setPersonProperties({$internal_or_test_user:!0})}en(e){return this.config.person_profiles===`never`?(Q.error(e+` was called, but process_person is set to "never". This call will be ignored.`),!1):(this.rn(Fm,!0),!0)}Br(){if(this.config.cookieless_mode===`always`)return!0;var e=this.consent.isOptedOut();return this.config.disable_persistence||e&&!(!this.config.opt_out_persistence_by_default&&this.config.cookieless_mode!==Bm)}ln(){var e,t,n=this.Br();return this.persistence?.mi!==n&&((e=this.persistence)==null||e.set_disabled(n)),this.sessionPersistence?.mi!==n&&((t=this.sessionPersistence)==null||t.set_disabled(n)),n}opt_in_capturing(t){var n;if(this.config.cookieless_mode!==Vm){if(this.Ar()){var r,i;this.reset(!0),(r=this.sessionManager)==null||r.destroy(),(i=this.pageViewManager)==null||i.destroy(),this.sessionManager=new u_(this),this.pageViewManager=new Ph(this),this.persistence&&(this.sessionPropsManager=new s_(this,this.sessionManager,this.persistence));var a,o=this.config.__extensionClasses?.sessionRecording??e.__defaultExtensionClasses?.sessionRecording;o&&(this.sessionRecording=this.kr(this.sessionRecording,new o(this)),this.Ur&&((a=this.sessionRecording)==null||a.onRemoteConfig==null||a.onRemoteConfig(this.Ur)))}var s;this.consent.optInOut(!0),this.ln(),this.Yr(),(n=this.sessionRecording)==null||n.startIfEnabledOrStop(),this.config.cookieless_mode==Bm&&((s=this.surveys)==null||s.loadIfEnabled()),(Z(t?.captureEventName)||t!=null&&t.captureEventName)&&this.capture(t?.captureEventName??`$opt_in`,t?.captureProperties,{send_instantly:!0}),this.config.capture_pageview&&this.Wr()}else Q.warn(P_)}opt_out_capturing(){var e,t,n;this.config.cookieless_mode===Vm?Q.warn(P_):(this.config.cookieless_mode===Bm&&this.consent.isOptedIn()&&this.reset(!0),this.consent.optInOut(!1),this.ln(),this.config.cookieless_mode===Bm&&(this.register({distinct_id:Lm,$device_id:null}),(e=this.sessionManager)==null||e.destroy(),(t=this.pageViewManager)==null||t.destroy(),this.sessionManager=void 0,this.sessionPropsManager=void 0,(n=this.sessionRecording)==null||n.stopRecording(),this.sessionRecording=void 0,this.Wr()))}has_opted_in_capturing(){return this.consent.isOptedIn()}has_opted_out_capturing(){return this.consent.isOptedOut()}get_explicit_consent_status(){var e=this.consent.consent;return e===1?`granted`:e===0?`denied`:`pending`}is_capturing(){return this.config.cookieless_mode===Vm||(this.config.cookieless_mode===Bm?this.consent.isRejected()||this.consent.isOptedIn():!this.has_opted_out_capturing())}clear_opt_in_out_capturing(){this.consent.reset(),this.ln()}_is_bot(){return Ad?d_(Ad,this.config.custom_blocked_useragents):void 0}Wr(){J&&(J.visibilityState===`visible`?this.Nr||(this.Nr=!0,this.capture(qm,{title:J.title},{send_instantly:!0}),this.Mr&&=(J.removeEventListener(Gm,this.Mr),null)):this.Mr||(this.Mr=this.Wr.bind(this),oh(J,Gm,this.Mr)))}debug(e){!1===e?(q?.console.log(`You've disabled debug mode.`),this.set_config({debug:!1})):(q?.console.log("You're now in debug mode. All calls to PostHog will be logged in your console.\nYou can disable this with `posthog.debug(false)`."),this.set_config({debug:!0}))}Ai(){var e,t,n,r,i,a,o=this.Lr||{};return`advanced_disable_flags`in o?!!o.advanced_disable_flags:!1===this.config.advanced_disable_flags?!0===this.config.advanced_disable_decide?(Q.warn(`Config field 'advanced_disable_decide' is deprecated. Please use 'advanced_disable_flags' instead. The old field will be removed in a future major version.`),!0):(n=`advanced_disable_decide`,r=Q,i=(t=`advanced_disable_flags`)in(e=o)&&!of(e[t]),a=n in e&&!of(e[n]),i?e[t]:!!a&&(r&&r.warn(`Config field '`+n+`' is deprecated. Please use '`+t+`' instead. The old field will be removed in a future major version.`),e[n])):!!this.config.advanced_disable_flags}Qr(e){if(of(this.config.before_send))return e;var t=Qd(this.config.before_send)?this.config.before_send:[this.config.before_send],n=e;for(var r of t){if(n=r(n),of(n)){var i=`Event '`+e.event+`' was rejected in beforeSend function`;return df(e.event)?Q.warn(i+`. This can cause unexpected behavior.`):Q.info(i),null}n.properties&&!tf(n.properties)||Q.warn(`Event '`+e.event+`' has no properties after beforeSend function, this is likely an error.`)}return n}getPageViewId(){return this.pageViewManager.hi?.pageViewId}captureTraceFeedback(e,t){this.capture(`$ai_feedback`,{$ai_trace_id:String(e),$ai_feedback_text:t})}captureTraceMetric(e,t,n){this.capture(`$ai_metric`,{$ai_trace_id:String(e),$ai_metric_name:t,$ai_metric_value:String(n)})}Pr(e){var t=lf(e)&&!e,n=yh.Gt()&&yh.Jt(`ph_debug`)===`true`;return!t&&(!!n||e)}};function G_(e){return e instanceof Element&&(e.id===Im||!(e.closest==null||!e.closest(`.toolbar-global-fade-container`)))}function K_(e){return!!e&&e.nodeType===1}function q_(e,t){return!!e&&!!e.tagName&&e.tagName.toLowerCase()===t.toLowerCase()}function J_(e){return!!e&&e.nodeType===3}function Y_(e){return!!e&&e.nodeType===11}function X_(e){return e?qd(e).split(/\s+/):[]}function Z_(e){var t=q?.location.href;return!!(t&&e&&e.some((e=>t.match(e))))}function Q_(e){var t=``;switch(typeof e.className){case`string`:t=e.className;break;case`object`:t=(e.className&&`baseVal`in e.className?e.className.baseVal:null)||e.getAttribute(`class`)||``;break;default:t=``}return X_(t)}function $_(e){return of(e)?null:qd(e).split(/(\s+)/).filter((e=>_v(e))).join(``).replace(/[\r\n]/g,` `).replace(/[ ]+/g,` `).substring(0,255)}function ev(e){var t=``;return lv(e)&&!uv(e)&&e.childNodes&&e.childNodes.length&&Qm(e.childNodes,(function(e){J_(e)&&e.textContent&&(t+=$_(e.textContent)??``)})),qd(t)}function tv(e){return Z(e.target)?e.srcElement||null:(t=e.target)!=null&&t.shadowRoot?e.composedPath()[0]||null:e.target||null;var t}W_.__defaultExtensionClasses={},W_.un={trace:A_=()=>{},debug:A_,info:A_,warn:A_,error:A_,fatal:A_},function(e,t){for(var n=0;t.length>n;n++)e.prototype[t[n]]=nh(e.prototype[t[n]])}(W_,[`identify`]);var nv=[`a`,`button`,`form`,`input`,`select`,`textarea`,`label`];function rv(e,t){if(Z(t))return!0;var n,r=function(e){if(t.some((t=>e.matches(t))))return{v:!0}};for(var i of e)if(n=r(i))return n.v;return!1}function iv(e){var t=e.parentNode;return!(!t||!K_(t))&&t}var av=[`next`,`previous`,`prev`,`>`,`<`],ov=[`.ph-no-rageclick`,`.ph-no-capture`],sv=e=>!e||q_(e,`html`)||!K_(e),cv=(e,t)=>{if(!q||sv(e))return{parentIsUsefulElement:!1,targetElementList:[]};for(var n=!1,r=[e],i=e;i.parentNode&&!q_(i,`body`);)if(Y_(i.parentNode))r.push(i.parentNode.host),i=i.parentNode.host;else{var a=iv(i);if(!a)break;if(t||nv.indexOf(a.tagName.toLowerCase())>-1)n=!0;else{var o=q.getComputedStyle(a);o&&o.getPropertyValue(`cursor`)===`pointer`&&(n=!0)}r.push(a),i=a}return{parentIsUsefulElement:n,targetElementList:r}};function lv(e){for(var t=e;t.parentNode&&!q_(t,`body`);t=t.parentNode){var n=Q_(t);if(Kd(n,`ph-sensitive`)||Kd(n,`ph-no-capture`))return!1}if(Kd(Q_(e),`ph-include`))return!0;var r=e.type||``;if(nf(r))switch(r.toLowerCase()){case`hidden`:case`password`:return!1}var i=e.name||e.id||``;return!nf(i)||!/^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i.test(i.replace(/[^a-zA-Z0-9]/g,``))}function uv(e){return!!(q_(e,`input`)&&![`button`,`checkbox`,`submit`,`reset`].includes(e.type)||q_(e,`select`)||q_(e,`textarea`)||e.getAttribute(`contenteditable`)===`true`)}var dv=`(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11})`,fv=RegExp(`^(?:`+dv+`)$`),pv=new RegExp(dv),mv=`\\d{3}-?\\d{2}-?\\d{4}`,hv=RegExp(`^(`+mv+`)$`),gv=RegExp(`(`+mv+`)`);function _v(e,t){return t===void 0&&(t=!0),!(of(e)||nf(e)&&(e=qd(e),(t?fv:pv).test((e||``).replace(/[- ]/g,``))||(t?hv:gv).test(e)))}function vv(e){var t=ev(e);return _v(t=(t+` `+yv(e)).trim())?t:``}function yv(e){var t=``;return e&&e.childNodes&&e.childNodes.length&&Qm(e.childNodes,(function(e){if(e&&e.tagName?.toLowerCase()===`span`)try{var n=ev(e);t=(t+` `+n).trim(),e.childNodes&&e.childNodes.length&&(t=(t+` `+yv(e)).trim())}catch(e){Q.error(`[AutoCapture]`,e)}})),t}function bv(e){return e.replace(/"|\\"/g,`\\"`)}function xv(e){var t=e.attr__class;return t?Qd(t)?t:X_(t):void 0}var Sv=class{constructor(e){this.disabled=!1===e;var t=ef(e)?e:{};this.thresholdPx=t.threshold_px||30,this.timeoutMs=t.timeout_ms||1e3,this.clickCount=t.click_count||3,this.clicks=[]}isRageClick(e,t,n){if(this.disabled)return!1;var r=this.clicks[this.clicks.length-1];if(r&&Math.abs(e-r.x)+Math.abs(t-r.y)n-r.timestamp){if(this.clicks.push({x:e,y:t,timestamp:n}),this.clicks.length===this.clickCount)return!0}else this.clicks=[{x:e,y:t,timestamp:n}];return!1}},Cv=`$copy_autocapture`,wv=Zp(`[AutoCapture]`);function Tv(e,t){return t.length>e?t.slice(0,e)+`...`:t}function Ev(e){if(e.previousElementSibling)return e.previousElementSibling;var t=e;do t=t.previousSibling;while(t&&!K_(t));return t}function Dv(e,t){for(var n,{e:r,maskAllElementAttributes:i,maskAllText:a,elementAttributeIgnoreList:o,elementsChainAsString:s}=t,c=[e],l=e;l.parentNode&&!q_(l,`body`);)Y_(l.parentNode)?(c.push(l.parentNode.host),l=l.parentNode.host):(c.push(l.parentNode),l=l.parentNode);var u,d,f=[],p={},m=!1,h=!1;if(Qm(c,(e=>{var t=lv(e);e.tagName.toLowerCase()===`a`&&(m=e.getAttribute(`href`),m=t&&m&&_v(m)&&m),Kd(Q_(e),`ph-no-capture`)&&(h=!0),f.push(function(e,t,n,r){var i=e.tagName.toLowerCase(),a={tag_name:i};nv.indexOf(i)>-1&&!n&&(a.$el_text=i.toLowerCase()===`a`||i.toLowerCase()===`button`?Tv(1024,vv(e)):Tv(1024,ev(e)));var o=Q_(e);o.length>0&&(a.classes=o.filter((function(e){return e!==``}))),Qm(e.attributes,(function(n){var i;if((!uv(e)||[`name`,`id`,`class`,`aria-label`].indexOf(n.name)!==-1)&&(r==null||!r.includes(n.name))&&!t&&_v(n.value)&&(!nf(i=n.name)||i.substring(0,10)!==`_ngcontent`&&i.substring(0,7)!==`_nghost`)){var o=n.value;n.name===`class`&&(o=X_(o).join(` `)),a[`attr__`+n.name]=Tv(1024,o)}}));for(var s=1,c=1,l=e;l=Ev(l);)s++,l.tagName===e.tagName&&c++;return a.nth_child=s,a.nth_of_type=c,a}(e,i,a,o)),$m(p,function(e){if(!lv(e))return{};var t={};return Qm(e.attributes,(function(e){if(e.name&&e.name.indexOf(`data-ph-capture-attribute`)===0){var n=e.name.replace(`data-ph-capture-attribute-`,``),r=e.value;n&&r&&_v(r)&&(t[n]=r)}})),t}(e))})),h)return{props:{},explicitNoCapture:h};if(a||(f[0].$el_text=e.tagName.toLowerCase()===`a`||e.tagName.toLowerCase()===`button`?vv(e):ev(e)),m){var g;f[0].attr__href=m;var _=Fh(m)?.host,v=q==null||(g=q.location)==null?void 0:g.host;_&&v&&_!==v&&(u=m)}return{props:$m({$event_type:r.type,$ce_version:1},s?{}:{$elements:f},{$elements_chain:(d=f,function(e){return e.map((e=>{var t=``;if(e.tag_name&&(t+=e.tag_name),e.attr_class)for(var n of(e.attr_class.sort(),e.attr_class))t+=`.`+n.replace(/"/g,``);var r=X({},e.text?{text:e.text}:{},{"nth-child":e.nth_child??0,"nth-of-type":e.nth_of_type??0},e.href?{href:e.href}:{},e.attr_id?{attr_id:e.attr_id}:{},e.attributes),i={};return eh(r).sort(((e,t)=>{var[n]=e,[r]=t;return n.localeCompare(r)})).forEach((e=>{var[t,n]=e;return i[bv(t.toString())]=bv(n.toString())})),(t+=`:`)+eh(i).map((e=>{var[t,n]=e;return t+`="`+n+`"`})).join(``)})).join(`;`)}(function(e){return e.map((e=>{var t={text:e.$el_text?.slice(0,400),tag_name:e.tag_name,href:e.attr__href?.slice(0,2048),attr_class:xv(e),attr_id:e.attr__id,nth_child:e.nth_child,nth_of_type:e.nth_of_type,attributes:{}};return eh(e).filter((e=>{var[t]=e;return t.indexOf(`attr__`)===0})).forEach((e=>{var[n,r]=e;return t.attributes[n]=r})),t}))}(d)))},(n=f[0])!=null&&n.$el_text?{$el_text:f[0]?.$el_text}:{},u&&r.type===`click`?{$external_click_url:u}:{},p)}}var Ov=Zp(`[ExceptionAutocapture]`);function kv(e,t,n){try{if(!(t in e))return()=>{};var r=e[t],i=n(r);return $d(i)&&(i.prototype=i.prototype||{},Object.defineProperties(i,{__posthog_wrapped__:{enumerable:!1,value:!0}})),e[t]=i,()=>{e[t]=r}}catch{return()=>{}}}var Av=Zp(`[TracingHeaders]`),jv=Zp(`[Web Vitals]`),Mv=9e5,Nv=`disabled`,Pv=`lazy_loading`,Fv=`awaiting_config`,Iv=`missing_config`;Zp(`[SessionRecording]`),Zp(`[SessionRecording]`);var Lv=`[SessionRecording]`,Rv=Zp(Lv),zv=Zp(`[Heatmaps]`);function Bv(e){return ef(e)&&`clientX`in e&&`clientY`in e&&sf(e.clientX)&&sf(e.clientY)}var Vv=Zp(`[Product Tours]`),Hv=[`$set_once`,`$set`],Uv=Zp(`[SiteApps]`),Wv=`Error while initializing PostHog app with config id `;function Gv(e,t,n){if(of(e))return!1;switch(n){case`exact`:return e===t;case`contains`:var r=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`).replace(/_/g,`.`).replace(/%/g,`.*`);return new RegExp(r,`i`).test(e);case`regex`:try{return new RegExp(t).test(e)}catch{return!1}default:return!1}}var Kv=class{constructor(e){this.hn=new c_,this.cn=(e,t)=>this.dn(e,t)&&this.vn(e,t)&&this.fn(e,t)&&this.pn(e,t),this.dn=(e,t)=>t==null||!t.event||e?.event===t?.event,this._instance=e,this.gn=new Set,this.mn=new Set}init(){var e;Z(this._instance?._addCaptureHook)||(e=this._instance)==null||e._addCaptureHook(((e,t)=>{this.on(e,t)}))}register(e){var t;if(!Z(this._instance?._addCaptureHook)&&(e.forEach((e=>{var t,n;(t=this.mn)==null||t.add(e),(n=e.steps)==null||n.forEach((e=>{var t;(t=this.gn)==null||t.add(e?.event||``)}))})),(t=this._instance)!=null&&t.autocapture)){var n,r=new Set;e.forEach((e=>{var t;(t=e.steps)==null||t.forEach((e=>{e!=null&&e.selector&&r.add(e?.selector)}))})),(n=this._instance)==null||n.autocapture.setElementSelectors(r)}}on(e,t){t!=null&&e.length!=0&&(this.gn.has(e)||this.gn.has(t?.event))&&this.mn&&this.mn?.size>0&&this.mn.forEach((e=>{this.yn(t,e)&&this.hn.emit(`actionCaptured`,e.name)}))}bn(e){this.onAction(`actionCaptured`,(t=>e(t)))}yn(e,t){if(t?.steps==null)return!1;for(var n of t.steps)if(this.cn(e,n))return!0;return!1}onAction(e,t){return this.hn.on(e,t)}vn(e,t){if(t!=null&&t.url){var n,r=e==null||(n=e.properties)==null?void 0:n.$current_url;if(!r||typeof r!=`string`||!Gv(r,t.url,t.url_matching||`contains`))return!1}return!0}fn(e,t){return!!this.wn(e,t)&&!!this._n(e,t)&&!!this.In(e,t)}wn(e,t){var n;if(t==null||!t.href)return!0;var r=this.Cn(e);if(r.length>0)return r.some((e=>Gv(e.href,t.href,t.href_matching||`exact`)));var i,a=(e==null||(n=e.properties)==null?void 0:n.$elements_chain)||``;return!!a&&Gv((i=a.match(/(?::|")href="(.*?)"/))?i[1]:``,t.href,t.href_matching||`exact`)}_n(e,t){var n;if(t==null||!t.text)return!0;var r=this.Cn(e);if(r.length>0)return r.some((e=>Gv(e.text,t.text,t.text_matching||`exact`)||Gv(e.$el_text,t.text,t.text_matching||`exact`)));var i,a,o,s=(e==null||(n=e.properties)==null?void 0:n.$elements_chain)||``;return!!s&&(i=function(e){for(var t,n=[],r=/(?::|")text="(.*?)"/g;!of(t=r.exec(e));)n.includes(t[1])||n.push(t[1]);return n}(s),a=t.text,o=t.text_matching||`exact`,i.some((e=>Gv(e,a,o))))}In(e,t){var n,r;if(t==null||!t.selector)return!0;var i=e==null||(n=e.properties)==null?void 0:n.$element_selectors;if(i!=null&&i.includes(t.selector))return!0;var a=(e==null||(r=e.properties)==null?void 0:r.$elements_chain)||``;if(t.selector_regex&&a)try{return new RegExp(t.selector_regex).test(a)}catch{return!1}return!1}Cn(e){var t;return(e==null||(t=e.properties)==null?void 0:t.$elements)==null?[]:e?.properties.$elements}pn(e,t){return t==null||!t.properties||t.properties.length===0||g_(t.properties.reduce(((e,t)=>{var n=Qd(t.value)?t.value.map(String):t.value==null?[]:[String(t.value)];return e[t.key]={values:n,operator:t.operator||`exact`},e}),{}),e?.properties)}},qv=class{constructor(e){this._instance=e,this.Sn=new Map,this.xn=new Map,this.kn=new Map}Tn(e,t){return!!e&&g_(e.propertyFilters,t?.properties)}An(e,t){var n=new Map;return e.forEach((e=>{var r;(r=e.conditions)==null||(r=r[t])==null||(r=r.values)==null||r.forEach((t=>{if(t!=null&&t.name){var r=n.get(t.name)||[];r.push(e.id),n.set(t.name,r)}}))})),n}En(e,t,n){var r=(n===ng.Activation?this.Sn:this.xn).get(e),i=[];return this.Rn((e=>{i=e.filter((e=>r?.includes(e.id)))})),i.filter((r=>{var i,a=(i=r.conditions)==null||(i=i[n])==null||(i=i.values)==null?void 0:i.find((t=>t.name===e));return this.Tn(a,t)}))}register(e){Z(this._instance?._addCaptureHook)||(this.Nn(e),this.Mn(e))}Mn(e){var t=e.filter((e=>{var t;return e.conditions?.actions&&((t=e.conditions)==null||(t=t.actions)==null||(t=t.values)==null?void 0:t.length)>0}));t.length!==0&&(this.Fn??(this.Fn=new Kv(this._instance),this.Fn.init(),this.Fn.bn((e=>{this.onAction(e)}))),t.forEach((e=>{var t,n,r,i,a;e.conditions&&(t=e.conditions)!=null&&t.actions&&(n=e.conditions)!=null&&(n=n.actions)!=null&&n.values&&((r=e.conditions)==null||(r=r.actions)==null||(r=r.values)==null?void 0:r.length)>0&&((i=this.Fn)==null||i.register(e.conditions.actions.values),(a=e.conditions)==null||(a=a.actions)==null||(a=a.values)==null||a.forEach((t=>{if(t&&t.name){var n=this.kn.get(t.name);n&&n.push(e.id),this.kn.set(t.name,n||[e.id])}})))})))}Nn(e){var t,n=e.filter((e=>{var t;return e.conditions?.events&&((t=e.conditions)==null||(t=t.events)==null||(t=t.values)==null?void 0:t.length)>0})),r=e.filter((e=>{var t;return e.conditions?.cancelEvents&&((t=e.conditions)==null||(t=t.cancelEvents)==null||(t=t.values)==null?void 0:t.length)>0}));n.length===0&&r.length===0||((t=this._instance)==null||t._addCaptureHook(((e,t)=>{this.onEvent(e,t)})),this.Sn=this.An(e,ng.Activation),this.xn=this.An(e,ng.Cancellation))}onEvent(e,t){var n,r=this.re(),i=this.On(),a=this.Pn(),o=((n=this._instance)==null||(n=n.persistence)==null?void 0:n.props[i])||[];if(a===e&&t&&o.length>0){var s,c;r.info(`event matched, removing item from activated items`,{event:e,eventPayload:t,existingActivatedItems:o});var l=(t==null||(s=t.properties)==null?void 0:s.$survey_id)||(t==null||(c=t.properties)==null?void 0:c.$product_tour_id);if(l){var u=o.indexOf(l);0>u||(o.splice(u,1),this.Ln(o))}}else{if(this.xn.has(e)){var d=this.En(e,t,ng.Cancellation);d.length>0&&(r.info(`cancel event matched, cancelling items`,{event:e,itemsToCancel:d.map((e=>e.id))}),d.forEach((e=>{var t=o.indexOf(e.id);0>t||o.splice(t,1),this.Dn(e.id)})),this.Ln(o))}if(this.Sn.has(e)){r.info(`event name matched`,{event:e,eventPayload:t,items:this.Sn.get(e)});var f=this.En(e,t,ng.Activation);this.Ln(o.concat(f.map((e=>e.id))||[]))}}}onAction(e){var t,n=this.On(),r=((t=this._instance)==null||(t=t.persistence)==null?void 0:t.props[n])||[];this.kn.has(e)&&this.Ln(r.concat(this.kn.get(e)||[]))}Ln(e){var t,n=this.re(),r=this.On(),i=[...new Set(e)].filter((e=>!this.Bn(e)));n.info(`updating activated items`,{activatedItems:i}),(t=this._instance)==null||(t=t.persistence)==null||t.register({[r]:i})}getActivatedIds(){var e,t=this.On();return((e=this._instance)==null||(e=e.persistence)==null?void 0:e.props[t])||[]}getEventToItemsMap(){return this.Sn}jn(){return this.Fn}},Jv=class extends qv{constructor(e){super(e)}On(){return`$surveys_activated`}Pn(){return ig.SHOWN}Rn(e){var t;(t=this._instance)==null||t.getSurveys(e)}Dn(e){var t;(t=this._instance)==null||t.cancelPendingSurvey(e)}re(){return x_}Bn(){return!1}getSurveys(){return this.getActivatedIds()}getEventToSurveys(){return this.getEventToItemsMap()}},Yv=`SDK is not enabled or survey functionality is not yet loaded`,Xv=`Disabled. Not loading surveys.`,Zv=q!=null&&q.location?Rh(q.location.hash,`__posthog`)||Rh(location.hash,`state`):null,Qv=`_postHogToolbarParams`,$v=Zp(`[Toolbar]`),ey=Zp(`[FeatureFlags]`),ty=Zp(`[FeatureFlags]`,{debugEnabled:!0}),ny=`" failed. Feature flags didn't load in time.`,ry=`$active_feature_flags`,iy=`$override_feature_flags`,ay=`$feature_flag_request_id`,oy=e=>{for(var t={},n=0;e.length>n;n++)t[e[n]]=!0;return t},sy=e=>{var t={};for(var[n,r]of eh(e||{}))r&&(t[n]=r);return t},cy=Zp(`[Error tracking]`),ly=`Refusing to render web experiment since the viewer is a likely bot`,uy={icontains:(e,t)=>!!q&&t.href.toLowerCase().indexOf(e.toLowerCase())>-1,not_icontains:(e,t)=>!!q&&t.href.toLowerCase().indexOf(e.toLowerCase())===-1,regex:(e,t)=>!!q&&f_(t.href,e),not_regex:(e,t)=>!!q&&!f_(t.href,e),exact:(e,t)=>t.href===e,is_not:(e,t)=>t.href!==e},dy=class e{get Rt(){return this._instance.config}constructor(t){var n=this;this.getWebExperimentsAndEvaluateDisplayLogic=function(t){t===void 0&&(t=!1),n.getWebExperiments((t=>{e.qn(`retrieved web experiments from the server`),n.Zn=new Map,t.forEach((t=>{if(t.feature_flag_key){var r;n.Zn&&(e.qn(`setting flag key `,t.feature_flag_key,` to web experiment `,t),(r=n.Zn)==null||r.set(t.feature_flag_key,t));var i=n._instance.getFeatureFlag(t.feature_flag_key);nf(i)&&t.variants[i]&&n.$n(t.name,i,t.variants[i].transforms)}else if(t.variants)for(var a in t.variants){var o=t.variants[a];e.Hn(o)&&n.$n(t.name,a,o.transforms)}}))}),t)},this._instance=t,this._instance.onFeatureFlags((e=>{this.onFeatureFlags(e)}))}initialize(){}onFeatureFlags(t){if(this._is_bot())e.qn(ly);else if(!this.Rt.disable_web_experiments){if(of(this.Zn))return this.Zn=new Map,this.loadIfEnabled(),void this.previewWebExperiment();e.qn(`applying feature flags`,t),t.forEach((e=>{var t;if(this.Zn&&(t=this.Zn)!=null&&t.has(e)){var n=this._instance.getFeatureFlag(e),r=this.Zn?.get(e);n&&r!=null&&r.variants[n]&&this.$n(r.name,n,r.variants[n].transforms)}}))}}previewWebExperiment(){var t=e.getWindowLocation();if(t!=null&&t.search){var n=Ih(t?.search,`__experiment_id`),r=Ih(t?.search,`__experiment_variant`);n&&r&&(e.qn(`previewing web experiments `+n+` && `+r),this.getWebExperiments((e=>{this.Vn(parseInt(n),r,e)}),!1,!0))}}loadIfEnabled(){this.Rt.disable_web_experiments||this.getWebExperimentsAndEvaluateDisplayLogic()}getWebExperiments(e,t,n){if(this.Rt.disable_web_experiments&&!n)return e([]);var r=this._instance.get_property(`$web_experiments`);if(r&&!t)return e(r);this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/web_experiments/?token=`+this.Rt.token),method:`GET`,callback:t=>e(t.statusCode===200&&t.json&&t.json.experiments||[])})}Vn(t,n,r){var i=r.filter((e=>e.id===t));i&&i.length>0&&(e.qn(`Previewing web experiment [`+i[0].name+`] with variant [`+n+`]`),this.$n(i[0].name,n,i[0].variants[n].transforms))}static Hn(t){return!of(t.conditions)&&e.zn(t)&&e.Un(t)}static zn(t){if(of(t.conditions)||of(t.conditions?.url))return!0;var n,r=e.getWindowLocation();return!!r&&((n=t.conditions)==null||!n.url||uy[t.conditions?.urlMatchType??`icontains`](t.conditions.url,r))}static getWindowLocation(){return q?.location}static Un(e){if(of(e.conditions)||of(e.conditions?.utm))return!0;var t=Wh();if(t.utm_source){var n,r,i,a,o,s,c,l,u=(n=e.conditions)==null||(n=n.utm)==null||!n.utm_campaign||((r=e.conditions)==null||(r=r.utm)==null?void 0:r.utm_campaign)==t.utm_campaign,d=(i=e.conditions)==null||(i=i.utm)==null||!i.utm_source||((a=e.conditions)==null||(a=a.utm)==null?void 0:a.utm_source)==t.utm_source,f=(o=e.conditions)==null||(o=o.utm)==null||!o.utm_medium||((s=e.conditions)==null||(s=s.utm)==null?void 0:s.utm_medium)==t.utm_medium,p=(c=e.conditions)==null||(c=c.utm)==null||!c.utm_term||((l=e.conditions)==null||(l=l.utm)==null?void 0:l.utm_term)==t.utm_term;return u&&f&&p&&d}return!1}static qn(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];Q.info(`[WebExperiments] `+e,n)}$n(t,n,r){this._is_bot()?e.qn(ly):n===`control`?e.qn(`Control variants leave the page unmodified.`):r.forEach((r=>{r.selector&&(e.qn(`applying transform of variant `+n+` for experiment `+t+` `,r),(document?.querySelectorAll(r.selector))?.forEach((e=>{var t=e;r.html&&(t.innerHTML=r.html),r.css&&t.setAttribute(`style`,r.css)})))}))}_is_bot(){return Ad&&this._instance?d_(Ad,this.Rt.custom_blocked_useragents):void 0}},fy=Zp(`[Conversations]`),py=`Conversations not available yet.`,my={trace:{text:`TRACE`,number:1},debug:{text:`DEBUG`,number:5},info:{text:`INFO`,number:9},warn:{text:`WARN`,number:13},error:{text:`ERROR`,number:17},fatal:{text:`FATAL`,number:21}},hy=my.info;function gy(e){if(lf(e))return{boolValue:e};if(sf(e))return Number.isInteger(e)?{intValue:e}:{doubleValue:e};if(typeof e==`string`)return{stringValue:e};if(Qd(e))return{arrayValue:{values:e.map((e=>gy(e)))}};try{return{stringValue:JSON.stringify(e)}}catch{return{stringValue:String(e)}}}function _y(e){var t=[];for(var n in e){var r=e[n];af(r)||Z(r)||t.push({key:n,value:gy(r)})}return t}var vy={featureFlags:class{constructor(e){this.Yn=!1,this.Wn=!1,this.Gn=!1,this.Xn=!1,this.Jn=!1,this.Kn=!1,this.Qn=!1,this.ts=!1,this._instance=e,this.featureFlagEventHandlers=[]}get Rt(){return this._instance.config}get Qi(){return this._instance.persistence}es(e){return this._instance.get_property(e)}rs(){var e;return(e=this.Qi?.yi(this.Rt.feature_flag_cache_ttl_ms))!=null&&e}ns(){return!!this.rs()&&(this.ts||this.Gn||(this.ts=!0,ey.warn(`Feature flag cache is stale, triggering refresh...`),this.reloadFeatureFlags()),!0)}ss(){var e=this.Rt.evaluation_contexts??this.Rt.evaluation_environments;return!this.Rt.evaluation_environments||this.Rt.evaluation_contexts||this.Qn||(ey.warn(`evaluation_environments is deprecated. Use evaluation_contexts instead. evaluation_environments will be removed in a future version.`),this.Qn=!0),e!=null&&e.length?e.filter((e=>{var t=e&&typeof e==`string`&&e.trim().length>0;return t||ey.error(`Invalid evaluation context found:`,e,`Expected non-empty string`),t})):[]}os(){return this.ss().length>0}initialize(){var{config:e}=this._instance,t=e.bootstrap?.featureFlags??{};if(Object.keys(t).length){var n=e.bootstrap?.featureFlagPayloads??{},r=Object.keys(t).filter((e=>!!t[e])).reduce(((e,n)=>(e[n]=t[n]||!1,e)),{}),i=Object.keys(n).filter((e=>r[e])).reduce(((e,t)=>(n[t]&&(e[t]=n[t]),e)),{});this.receivedFeatureFlags({featureFlags:r,featureFlagPayloads:i})}}updateFlags(e,t,n){var r=n!=null&&n.merge?this.getFlagVariants():{},i=n!=null&&n.merge?this.getFlagPayloads():{},a=X({},r,e),o=X({},i,t),s={};for(var[c,l]of Object.entries(a)){var u=typeof l==`string`;s[c]={key:c,enabled:!!u||!!l,variant:u?l:void 0,reason:void 0,metadata:Z(o?.[c])?void 0:{id:0,version:void 0,description:void 0,payload:o[c]}}}this.receivedFeatureFlags({flags:s})}get hasLoadedFlags(){return this.Wn}getFlags(){return Object.keys(this.getFlagVariants())}getFlagsWithDetails(){var e=this.es(vm),t=this.es(iy),n=this.es(bm);if(!n&&!t)return e||{};var r=$m({},e||{});for(var i of[...new Set([...Object.keys(n||{}),...Object.keys(t||{})])]){var a,o,s=r[i],c=t?.[i],l=Z(c)?(a=s?.enabled)!=null&&a:!!c,u=Z(c)?s.variant:typeof c==`string`?c:void 0,d=n?.[i],f=X({},s,{enabled:l,variant:l?u??s?.variant:void 0});l!==s?.enabled&&(f.original_enabled=s?.enabled),u!==s?.variant&&(f.original_variant=s?.variant),d&&(f.metadata=X({},s?.metadata,{payload:d,original_payload:s==null||(o=s.metadata)==null?void 0:o.payload})),r[i]=f}return this.Yn||=(ey.warn(` Overriding feature flag details!`,{flagDetails:e,overriddenPayloads:n,finalDetails:r}),!0),r}getFlagVariants(){var e=this.es(gm),t=this.es(iy);if(!t)return e||{};for(var n=$m({},e),r=Object.keys(t),i=0;r.length>i;i++)n[r[i]]=t[r[i]];return this.Yn||=(ey.warn(` Overriding feature flags!`,{enabledFlags:e,overriddenFlags:t,finalFlags:n}),!0),n}getFlagPayloads(){var e=this.es(ym),t=this.es(bm);if(!t)return e||{};for(var n=$m({},e||{}),r=Object.keys(t),i=0;r.length>i;i++)n[r[i]]=t[r[i]];return this.Yn||=(ey.warn(` Overriding feature flag payloads!`,{flagPayloads:e,overriddenPayloads:t,finalPayloads:n}),!0),n}reloadFeatureFlags(){this.Xn||this.Rt.advanced_disable_feature_flags||this.ls||(this._instance.Rr.emit(`featureFlagsReloading`,!0),this.ls=setTimeout((()=>{this.us()}),5))}hs(){clearTimeout(this.ls),this.ls=void 0}ensureFlagsLoaded(){this.Wn||this.Gn||this.ls||this.reloadFeatureFlags()}setAnonymousDistinctId(e){this.$anon_distinct_id=e}setReloadingPaused(e){this.Xn=e}us(e){if(this.hs(),!this._instance.Ai())if(this.Gn)this.Jn=!0;else{var t=this.Rt.token,n=this.es(tm),r={token:t,distinct_id:this._instance.get_distinct_id(),groups:this._instance.getGroups(),$anon_distinct_id:this.$anon_distinct_id,person_properties:X({},this.Qi?.get_initial_props()||{},this.es(xm)||{}),group_properties:this.es(Sm),timezone:Qh()};af(n)||Z(n)||(r.$device_id=n),(e!=null&&e.disableFlags||this.Rt.advanced_disable_feature_flags)&&(r.disable_flags=!0),this.os()&&(r.evaluation_contexts=this.ss());var i=this._instance.requestRouter.endpointFor(`flags`,`/flags/?v=2`+(this.Rt.advanced_only_evaluate_survey_feature_flags?`&only_evaluate_survey_feature_flags=true`:``));this.Gn=!0,this._instance._send_request({method:`POST`,url:i,data:r,compression:this.Rt.disable_compression?void 0:pg.Base64,timeout:this.Rt.feature_flag_request_timeout_ms,callback:e=>{var t,n,i,a=!0;if(e.statusCode===200&&(this.Jn||(this.$anon_distinct_id=void 0),a=!1),this.Gn=!1,!r.disable_flags||this.Jn){this.Kn=!a;var o=[];e.error?e.error instanceof Error?o.push(e.error.name===`AbortError`?`timeout`:`connection_error`):o.push(`unknown_error`):e.statusCode!==200&&o.push(`api_error_`+e.statusCode),(t=e.json)!=null&&t.errorsWhileComputingFlags&&o.push(`errors_while_computing_flags`);var s=!((n=e.json)==null||(n=n.quotaLimited)==null||!n.includes(`feature_flags`));s&&o.push(`quota_limited`),(i=this.Qi)==null||i.register({[Dm]:o}),s?ey.warn(`You have hit your feature flags quota limit, and will not be able to load feature flags until the quota is reset. Please visit https://posthog.com/docs/billing/limits-alerts to learn more.`):(r.disable_flags||this.receivedFeatureFlags(e.json??{},a,{partialResponse:!!this.Rt.advanced_only_evaluate_survey_feature_flags}),this.Jn&&(this.Jn=!1,this.us()))}}})}}getFeatureFlag(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn)if(this.Wn||this.getFlags()&&this.getFlags().length>0){if(!this.ns()){var n=this.getFeatureFlagResult(e,t);return n?.variant??n?.enabled}}else ey.warn(`getFeatureFlag for key "`+e+ny)}getFeatureFlagDetails(e){return this.getFlagsWithDetails()[e]}getFeatureFlagPayload(e){return this.getFeatureFlagResult(e,{send_event:!1})?.payload}getFeatureFlagResult(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn)if(this.Wn||this.getFlags()&&this.getFlags().length>0){if(!this.ns()){var n=this.getFlagVariants(),r=e in n,i=n[e],a=this.getFlagPayloads()[e],o=String(i),s=this.es(ay)||void 0,c=this.es(Om)||void 0,l=this.es(Tm)||{};if(this.Rt.advanced_feature_flags_dedup_per_session){var u,d=this._instance.get_session_id(),f=this.es(Em);d&&d!==f&&(l={},(u=this.Qi)==null||u.register({[Tm]:l,[Em]:d}))}if((t.send_event||!(`send_event`in t))&&(!(e in l)||!l[e].includes(o))){var p,m,h,g,_,v,y,b;Qd(l[e])?l[e].push(o):l[e]=[o],(p=this.Qi)==null||p.register({[Tm]:l});var x=this.getFeatureFlagDetails(e),S=[...this.es(Dm)??[]];Z(i)&&S.push(`flag_missing`);var C={$feature_flag:e,$feature_flag_response:i,$feature_flag_payload:a||null,$feature_flag_request_id:s,$feature_flag_evaluated_at:c,$feature_flag_bootstrapped_response:((m=this.Rt.bootstrap)==null||(m=m.featureFlags)==null?void 0:m[e])||null,$feature_flag_bootstrapped_payload:((h=this.Rt.bootstrap)==null||(h=h.featureFlagPayloads)==null?void 0:h[e])||null,$used_bootstrap_value:!this.Kn};Z(x==null||(g=x.metadata)==null?void 0:g.version)||(C.$feature_flag_version=x.metadata.version);var w,T=(x==null||(_=x.reason)==null?void 0:_.description)??(x==null||(v=x.reason)==null?void 0:v.code);T&&(C.$feature_flag_reason=T),x!=null&&(y=x.metadata)!=null&&y.id&&(C.$feature_flag_id=x.metadata.id),Z(x?.original_variant)&&Z(x?.original_enabled)||(C.$feature_flag_original_response=Z(x.original_variant)?x.original_enabled:x.original_variant),x!=null&&(b=x.metadata)!=null&&b.original_payload&&(C.$feature_flag_original_payload=x==null||(w=x.metadata)==null?void 0:w.original_payload),S.length&&(C.$feature_flag_error=S.join(`,`)),this._instance.capture(`$feature_flag_called`,C)}if(r){var E=a;if(!Z(a))try{E=JSON.parse(a)}catch{}return{key:e,enabled:!!i,variant:typeof i==`string`?i:void 0,payload:E}}}}else ey.warn(`getFeatureFlagResult for key "`+e+ny)}getRemoteConfigPayload(e,t){var n=this.Rt.token,r={distinct_id:this._instance.get_distinct_id(),token:n};this.os()&&(r.evaluation_contexts=this.ss()),this._instance._send_request({method:`POST`,url:this._instance.requestRouter.endpointFor(`flags`,`/flags/?v=2`),data:r,compression:this.Rt.disable_compression?void 0:pg.Base64,timeout:this.Rt.feature_flag_request_timeout_ms,callback(n){var r=n.json?.featureFlagPayloads;t(r?.[e]||void 0)}})}isFeatureEnabled(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn){if(this.Wn||this.getFlags()&&this.getFlags().length>0){var n=this.getFeatureFlag(e,t);return Z(n)?void 0:!!n}ey.warn(`isFeatureEnabled for key "`+e+ny)}}addFeatureFlagsHandler(e){this.featureFlagEventHandlers.push(e)}removeFeatureFlagsHandler(e){this.featureFlagEventHandlers=this.featureFlagEventHandlers.filter((t=>t!==e))}receivedFeatureFlags(e,t,n){if(this.Qi){this.Wn=!0;var r=this.getFlagVariants(),i=this.getFlagPayloads(),a=this.getFlagsWithDetails();(function(e,t,n,r,i,a){n===void 0&&(n={}),r===void 0&&(r={}),i===void 0&&(i={});var o=(e=>{var t=e.flags;return t?(e.featureFlags=Object.fromEntries(Object.keys(t).map((e=>[e,t[e].variant??t[e].enabled]))),e.featureFlagPayloads=Object.fromEntries(Object.keys(t).filter((e=>t[e].enabled)).filter((e=>t[e].metadata?.payload)).map((e=>[e,t[e].metadata?.payload])))):ey.warn(`Using an older version of the feature flags endpoint. Please upgrade your PostHog server to the latest version`),e})(e),s=o.flags,c=o.featureFlags,l=o.featureFlagPayloads;if(c){var u=e.requestId,d=e.evaluatedAt;if(Qd(c)){ey.warn(`v1 of the feature flags endpoint is deprecated. Please use the latest version.`);var f={};if(c)for(var p=0;c.length>p;p++)f[c[p]]=!0;t&&t.register({[ry]:c,[gm]:f})}else{var m=c,h=l,g=s;if(a!=null&&a.partialResponse)m=X({},n,m),h=X({},r,h),g=X({},i,g);else if(e.errorsWhileComputingFlags)if(s){var _=new Set(Object.keys(s).filter((e=>{var t;return!((t=s[e])!=null&&t.failed)})));m=X({},n,Object.fromEntries(Object.entries(m).filter((e=>{var[t]=e;return _.has(t)})))),h=X({},r,Object.fromEntries(Object.entries(h||{}).filter((e=>{var[t]=e;return _.has(t)})))),g=X({},i,Object.fromEntries(Object.entries(g||{}).filter((e=>{var[t]=e;return _.has(t)}))))}else m=X({},n,m),h=X({},r,h),g=X({},i,g);t&&t.register(X({[ry]:Object.keys(sy(m)),[gm]:m||{},[ym]:h||{},[vm]:g||{}},u?{[ay]:u}:{},d?{[Om]:d}:{}))}}})(e,this.Qi,r,i,a,n),t||(this.ts=!1),this.cs(t)}}override(e,t){t===void 0&&(t=!1),ey.warn(`override is deprecated. Please use overrideFeatureFlags instead.`),this.overrideFeatureFlags({flags:e,suppressWarning:t})}overrideFeatureFlags(e){if(!this._instance.__loaded||!this.Qi)return ey.uninitializedWarning(`posthog.featureFlags.overrideFeatureFlags`);if(!1===e)return this.Qi.unregister(iy),this.Qi.unregister(bm),this.cs(),ty.info(`All overrides cleared`);if(Qd(e)){var t=oy(e);return this.Qi.register({[iy]:t}),this.cs(),ty.info(`Flag overrides set`,{flags:e})}if(e&&typeof e==`object`&&(`flags`in e||`payloads`in e)){var n,r=e;if(this.Yn=!!((n=r.suppressWarning)!=null&&n),`flags`in r){if(!1===r.flags)this.Qi.unregister(iy),ty.info(`Flag overrides cleared`);else if(r.flags){if(Qd(r.flags)){var i=oy(r.flags);this.Qi.register({[iy]:i})}else this.Qi.register({[iy]:r.flags});ty.info(`Flag overrides set`,{flags:r.flags})}}`payloads`in r&&(!1===r.payloads?(this.Qi.unregister(bm),ty.info(`Payload overrides cleared`)):r.payloads&&(this.Qi.register({[bm]:r.payloads}),ty.info(`Payload overrides set`,{payloads:r.payloads}))),this.cs();return}if(e&&typeof e==`object`)return this.Qi.register({[iy]:e}),this.cs(),ty.info(`Flag overrides set`,{flags:e});ey.warn(`Invalid overrideOptions provided to overrideFeatureFlags`,{overrideOptions:e})}onFeatureFlags(e){if(this.addFeatureFlagsHandler(e),this.Wn){var{flags:t,flagVariants:n}=this.ds();e(t,n)}return()=>this.removeFeatureFlagsHandler(e)}updateEarlyAccessFeatureEnrollment(e,t,n){var r,i=(this.es(_m)||[]).find((t=>t.flagKey===e)),a={[`$feature_enrollment/`+e]:t},o={$feature_flag:e,$feature_enrollment:t,$set:a};i&&(o.$early_access_feature_name=i.name),n&&(o.$feature_enrollment_stage=n),this._instance.capture(`$feature_enrollment_update`,o),this.setPersonPropertiesForFlags(a,!1);var s=X({},this.getFlagVariants(),{[e]:t});(r=this.Qi)==null||r.register({[ry]:Object.keys(sy(s)),[gm]:s}),this.cs()}getEarlyAccessFeatures(e,t,n){t===void 0&&(t=!1);var r=this.es(_m),i=n?`&`+n.map((e=>`stage=`+e)).join(`&`):``;if(r&&!t)return e(r);this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/early_access_features/?token=`+this.Rt.token+i),method:`GET`,callback:t=>{var n,r;if(t.json){var i=t.json.earlyAccessFeatures;return(n=this.Qi)==null||n.unregister(_m),(r=this.Qi)==null||r.register({[_m]:i}),e(i)}}})}ds(){var e=this.getFlags(),t=this.getFlagVariants();return{flags:e.filter((e=>t[e])),flagVariants:Object.keys(t).filter((e=>t[e])).reduce(((e,n)=>(e[n]=t[n],e)),{})}}cs(e){var{flags:t,flagVariants:n}=this.ds();this.featureFlagEventHandlers.forEach((r=>r(t,n,{errorsLoading:e})))}setPersonPropertiesForFlags(e,t){t===void 0&&(t=!0);var n=this.es(xm)||{},r=e?.$set||(e!=null&&e.$set_once?{}:e),i=e?.$set_once,a={};if(i)for(var o in i)({}).hasOwnProperty.call(i,o)&&(o in n||(a[o]=i[o]));this._instance.register({[xm]:X({},n,a,r)}),t&&this._instance.reloadFeatureFlags()}resetPersonPropertiesForFlags(){this._instance.unregister(xm)}setGroupPropertiesForFlags(e,t){t===void 0&&(t=!0);var n=this.es(Sm)||{};Object.keys(n).length!==0&&Object.keys(n).forEach((t=>{n[t]=X({},n[t],e[t]),delete e[t]})),this._instance.register({[Sm]:X({},n,e)}),t&&this._instance.reloadFeatureFlags()}resetGroupPropertiesForFlags(e){if(e){var t=this.es(Sm)||{};this._instance.register({[Sm]:X({},t,{[e]:{}})})}else this._instance.unregister(Sm)}reset(){this.Wn=!1,this.Gn=!1,this.Xn=!1,this.Jn=!1,this.Kn=!1,this.$anon_distinct_id=void 0,this.hs(),this.Yn=!1}}},yy={sessionRecording:class{get Rt(){return this._instance.config}get Qi(){return this._instance.persistence}get started(){var e;return!((e=this.vs)==null||!e.isStarted)}get status(){return this.fs===Fv||this.fs===Iv?this.fs:this.vs?.status??this.fs}constructor(e){if(this._forceAllowLocalhostNetworkCapture=!1,this.fs=Nv,this.ps=void 0,this._instance=e,!this._instance.sessionManager)throw Rv.error(`started without valid sessionManager`),Error(Lv+` started without valid sessionManager. This is a bug.`);if(this.Rt.cookieless_mode===Vm)throw Error(Lv+` cannot be used with cookieless_mode="always"`)}initialize(){this.startIfEnabledOrStop()}get gs(){var e,t=!((e=this._instance.get_property(pm))==null||!e.enabled),n=!this.Rt.disable_session_recording,r=this.Rt.disable_session_recording||this._instance.consent.isOptedOut();return q&&t&&n&&!r}startIfEnabledOrStop(e){var t;if(!this.gs||(t=this.vs)==null||!t.isStarted){var n=!Z(Object.assign)&&!Z(Array.from);this.gs&&n?(this.ys(e),Rv.info(`starting`)):(this.fs=Nv,this.stopRecording())}}ys(e){var t,n,r;this.gs&&(this.fs!==Fv&&this.fs!==Iv&&(this.fs=Pv),Y!=null&&(t=Y.__PosthogExtensions__)!=null&&(t=t.rrweb)!=null&&t.record&&(n=Y.__PosthogExtensions__)!=null&&n.initSessionRecording?this.bs(e):(r=Y.__PosthogExtensions__)==null||r.loadExternalDependency==null||r.loadExternalDependency(this._instance,this.ws,(t=>{if(t)return Rv.error(`could not load recorder`,t);this.bs(e)})))}stopRecording(){var e,t;(e=this.ps)==null||e.call(this),this.ps=void 0,(t=this.vs)==null||t.stop()}_s(){var e,t;(e=this.ps)==null||e.call(this),this.ps=void 0,(t=this.vs)==null||t.discard()}Is(){var e;(e=this.Qi)==null||e.unregister(hm)}Cs(e,t){if(of(e))return null;var n,r=sf(e)?e:parseFloat(e);return typeof(n=r)!=`number`||!Number.isFinite(n)||0>n||n>1?(Rv.warn(t+` must be between 0 and 1. Ignoring invalid value:`,e),null):r}Ss(e){if(this.Qi){var t,n=this.Qi,r=()=>{var t=!1===e.sessionRecording?void 0:e.sessionRecording,r=this.Cs(this.Rt.session_recording?.sampleRate,`session_recording.sampleRate`),i=this.Cs(t?.sampleRate,`remote config sampleRate`),a=r??i;of(a)&&this.Is();var o=t?.minimumDurationMilliseconds;n.register({[pm]:X({cache_timestamp:Date.now(),enabled:!!t},t,{networkPayloadCapture:X({capturePerformance:e.capturePerformance},t?.networkPayloadCapture),canvasRecording:{enabled:t?.recordCanvas,fps:t?.canvasFps,quality:t?.canvasQuality},sampleRate:a,minimumDurationMilliseconds:Z(o)?null:o,endpoint:t?.endpoint,triggerMatchType:t?.triggerMatchType,masking:t?.masking,urlTriggers:t?.urlTriggers,version:t?.version,triggerGroups:t?.triggerGroups})})};r(),(t=this.ps)==null||t.call(this),this.ps=this._instance.sessionManager?.onSessionId(r)}}onRemoteConfig(e){`sessionRecording`in e?!1===e.sessionRecording?(this.Ss(e),this._s()):(this.Ss(e),this.startIfEnabledOrStop()):(this.fs===Fv&&(this.fs=Iv,Rv.warn(`config refresh failed, recording will not start until page reload`)),this.startIfEnabledOrStop())}log(e,t){var n;t===void 0&&(t=`log`),(n=this.vs)!=null&&n.log?this.vs.log(e,t):Rv.warn(`log called before recorder was ready`)}get ws(){var e,t,n=(e=this._instance)==null||(e=e.persistence)==null?void 0:e.get_property(pm);return(n==null||(t=n.scriptConfig)==null?void 0:t.script)||`lazy-recorder`}xs(){var e=this._instance.get_property(pm);if(!e)return!1;var t=(typeof e==`object`?e:JSON.parse(e)).cache_timestamp??Date.now();return 36e5>=Date.now()-t}bs(e){var t;if((t=Y.__PosthogExtensions__)==null||!t.initSessionRecording)return Rv.warn(`Called on script loaded before session recording is available. This can be caused by adblockers.`),void this._instance.register_for_session({$sdk_debug_recording_script_not_loaded:!0});if(this.vs||(this.vs=Y.__PosthogExtensions__?.initSessionRecording(this._instance),this.vs._forceAllowLocalhostNetworkCapture=this._forceAllowLocalhostNetworkCapture),!this.xs()){if(this.fs===Iv||this.fs===Fv)return;this.fs=Fv,Rv.info(`persisted remote config is stale, requesting fresh config before starting`),new fg(this._instance).load();return}this.fs=Pv,this.vs.start(e)}onRRwebEmit(e){var t;(t=this.vs)==null||t.onRRwebEmit==null||t.onRRwebEmit(e)}overrideLinkedFlag(){var e,t;this.vs||(t=this.Qi)==null||t.register({$replay_override_linked_flag:!0}),(e=this.vs)==null||e.overrideLinkedFlag()}overrideSampling(){var e,t;this.vs||(t=this.Qi)==null||t.register({$replay_override_sampling:!0}),(e=this.vs)==null||e.overrideSampling()}overrideTrigger(e){var t,n;this.vs||(n=this.Qi)==null||n.register({[e===`url`?`$replay_override_url_trigger`:`$replay_override_event_trigger`]:!0}),(t=this.vs)==null||t.overrideTrigger(e)}get sdkDebugProperties(){return this.vs?.sdkDebugProperties||{$recording_status:this.status}}tryAddCustomEvent(e,t){var n;return!((n=this.vs)==null||!n.tryAddCustomEvent(e,t))}}},by={autocapture:class{constructor(e){this.ks=!1,this.Ts=null,this.As=!1,this.instance=e,this.rageclicks=new Sv(e.config.rageclick),this.Es=null}initialize(){this.startIfEnabled()}get Rt(){var e=ef(this.instance.config.autocapture)?this.instance.config.autocapture:{};return e.url_allowlist=e.url_allowlist?.map((e=>new RegExp(e))),e.url_ignorelist=e.url_ignorelist?.map((e=>new RegExp(e))),e}Rs(){if(this.isBrowserSupported()){if(q&&J){var e=e=>{e||=q?.event;try{this.Ns(e)}catch(e){wv.error(`Failed to capture event`,e)}};if(oh(J,`submit`,e,{capture:!0}),oh(J,`change`,e,{capture:!0}),oh(J,`click`,e,{capture:!0}),this.Rt.capture_copied_text){var t=e=>{this.Ns(e||=q?.event,Cv)};oh(J,`copy`,t,{capture:!0}),oh(J,`cut`,t,{capture:!0})}}}else wv.info(`Disabling Automatic Event Collection because this browser is not supported`)}startIfEnabled(){this.isEnabled&&!this.ks&&(this.Rs(),this.ks=!0)}onRemoteConfig(e){e.elementsChainAsString&&(this.As=e.elementsChainAsString),this.instance.persistence&&this.instance.persistence.register({[im]:!!e.autocapture_opt_out}),this.Ts=!!e.autocapture_opt_out,this.startIfEnabled()}setElementSelectors(e){this.Es=e}getElementSelectors(e){var t,n=[];return(t=this.Es)==null||t.forEach((t=>{(J?.querySelectorAll(t))?.forEach((r=>{e===r&&n.push(t)}))})),n}get isEnabled(){var e=this.instance.persistence?.props[im];if(af(this.Ts)&&!lf(e)&&!this.instance.Ai())return!1;var t=this.Ts??!!e;return!!this.instance.config.autocapture&&!t}Ns(e,t){if(t===void 0&&(t=`$autocapture`),this.isEnabled){var n,r=tv(e);J_(r)&&(r=r.parentNode||null),t===`$autocapture`&&e.type===`click`&&e instanceof MouseEvent&&this.instance.config.rageclick&&(n=this.rageclicks)!=null&&n.isRageClick(e.clientX,e.clientY,e.timeStamp||new Date().getTime())&&function(e,t){if(!q||sv(e))return!1;var n,r;if(lf(t)?(n=!!t&&ov,r=void 0):(n=t?.css_selector_ignorelist??ov,r=t?.content_ignorelist),!1===n)return!1;var{targetElementList:i}=cv(e,!1);return!function(e,t){if(!1===e||Z(e))return!1;var n;if(!0===e)n=av;else{if(!Qd(e))return!1;if(e.length>10)return Q.error(`[PostHog] content_ignorelist array cannot exceed 10 items. Use css_selector_ignorelist for more complex matching.`),!1;n=e.map((e=>e.toLowerCase()))}return t.some((e=>{var{safeText:t,ariaLabel:r}=e;return n.some((e=>t.includes(e)||r.includes(e)))}))}(r,i.map((e=>({safeText:ev(e).toLowerCase(),ariaLabel:e.getAttribute(`aria-label`)?.toLowerCase().trim()||``}))))&&!rv(i,n)}(r,this.instance.config.rageclick)&&this.Ns(e,`$rageclick`);var i=t===Cv;if(r&&function(e,t,n,r,i){var a,o,s;if(n===void 0&&(n=void 0),!q||sv(e)||(a=n)!=null&&a.url_allowlist&&!Z_(n.url_allowlist)||(o=n)!=null&&o.url_ignorelist&&Z_(n.url_ignorelist))return!1;if((s=n)!=null&&s.dom_event_allowlist){var c=n.dom_event_allowlist;if(c&&!c.some((e=>t.type===e)))return!1}var{parentIsUsefulElement:l,targetElementList:u}=cv(e,r);if(!function(e,t){var n=t?.element_allowlist;if(Z(n))return!0;var r,i=function(e){if(n.some((t=>e.tagName.toLowerCase()===t)))return{v:!0}};for(var a of e)if(r=i(a))return r.v;return!1}(u,n)||!rv(u,n?.css_selector_allowlist))return!1;var d=q.getComputedStyle(e);if(d&&d.getPropertyValue(`cursor`)===`pointer`&&t.type===`click`)return!0;var f=e.tagName.toLowerCase();switch(f){case`html`:return!1;case`form`:return(i||[`submit`]).indexOf(t.type)>=0;case`input`:case`select`:case`textarea`:return(i||[`change`,`click`]).indexOf(t.type)>=0;default:return l?(i||[`click`]).indexOf(t.type)>=0:(i||[`click`]).indexOf(t.type)>=0&&(nv.indexOf(f)>-1||e.getAttribute(`contenteditable`)===`true`)}}(r,e,this.Rt,i,i?[`copy`,`cut`]:void 0)){var{props:a,explicitNoCapture:o}=Dv(r,{e,maskAllElementAttributes:this.instance.config.mask_all_element_attributes,maskAllText:this.instance.config.mask_all_text,elementAttributeIgnoreList:this.Rt.element_attribute_ignorelist,elementsChainAsString:this.As});if(o)return!1;var s=this.getElementSelectors(r);if(s&&s.length>0&&(a.$element_selectors=s),t===Cv){var c,l=$_(q==null||(c=q.getSelection())==null?void 0:c.toString()),u=e.type||`clipboard`;if(!l)return!1;a.$selected_content=l,a.$copy_type=u}return this.instance.capture(t,a),!0}}}isBrowserSupported(){return $d(J?.querySelectorAll)}},historyAutocapture:class{constructor(e){var t;this._instance=e,this.Ms=(q==null||(t=q.location)==null?void 0:t.pathname)||``}initialize(){this.startIfEnabled()}get isEnabled(){return this._instance.config.capture_pageview===`history_change`}startIfEnabled(){this.isEnabled&&(Q.info(`History API monitoring enabled, starting...`),this.monitorHistoryChanges())}stop(){this.Fs&&this.Fs(),this.Fs=void 0,Q.info(`History API monitoring stopped`)}monitorHistoryChanges(){var e,t;if(q&&q.history){var n=this;(e=q.history.pushState)!=null&&e.__posthog_wrapped__||kv(q.history,`pushState`,(e=>function(t,r,i){e.call(this,t,r,i),n.Os(`pushState`)})),(t=q.history.replaceState)!=null&&t.__posthog_wrapped__||kv(q.history,`replaceState`,(e=>function(t,r,i){e.call(this,t,r,i),n.Os(`replaceState`)})),this.Ps()}}Os(e){try{var t,n=q==null||(t=q.location)==null?void 0:t.pathname;if(!n)return;n!==this.Ms&&this.isEnabled&&this._instance.capture(qm,{navigation_type:e}),this.Ms=n}catch(t){Q.error(`Error capturing `+e+` pageview`,t)}}Ps(){if(!this.Fs){var e=()=>{this.Os(`popstate`)};oh(q,`popstate`,e),this.Fs=()=>{q&&q.removeEventListener(`popstate`,e)}}}},heatmaps:class{get Rt(){return this.instance.config}constructor(e){var t;this.Ls=!1,this.ks=!1,this.Ds=null,this.instance=e,this.Ls=!((t=this.instance.persistence)==null||!t.props[am]),this.rageclicks=new Sv(e.config.rageclick)}initialize(){this.startIfEnabled()}get flushIntervalMilliseconds(){var e=5e3;return ef(this.Rt.capture_heatmaps)&&this.Rt.capture_heatmaps.flush_interval_milliseconds&&(e=this.Rt.capture_heatmaps.flush_interval_milliseconds),e}get isEnabled(){return of(this.Rt.capture_heatmaps)?of(this.Rt.enable_heatmaps)?this.Ls:this.Rt.enable_heatmaps:!1!==this.Rt.capture_heatmaps}startIfEnabled(){if(this.isEnabled){if(this.ks)return;zv.info(`starting...`),this.Bs(),this.Tt()}else clearInterval(this.Ds??void 0),this.js(),this.getAndClearBuffer()}onRemoteConfig(e){if(`heatmaps`in e){var t=!!e.heatmaps;this.instance.persistence&&this.instance.persistence.register({[am]:t}),this.Ls=t,this.startIfEnabled()}}getAndClearBuffer(){var e=this.T;return this.T=void 0,e}qs(e){this.wt(e.originalEvent,`deadclick`)}Tt(){this.Ds&&clearInterval(this.Ds),this.Ds=J?.visibilityState===`visible`?setInterval(this.$i.bind(this),this.flushIntervalMilliseconds):null}Bs(){q&&J&&(this.Zs=this.$i.bind(this),oh(q,Km,this.Zs),this.$s=e=>this.wt(e||q?.event),oh(J,`click`,this.$s,{capture:!0}),this.Hs=e=>this.Vs(e||q?.event),oh(J,`mousemove`,this.Hs,{capture:!0}),this.zs=new kh(this.instance,Dh,this.qs.bind(this)),this.zs.startIfEnabledOrStop(),this.Us=this.Tt.bind(this),oh(J,Gm,this.Us),this.ks=!0)}js(){var e;q&&J&&(this.Zs&&q.removeEventListener(Km,this.Zs),this.$s&&J.removeEventListener(`click`,this.$s,{capture:!0}),this.Hs&&J.removeEventListener(`mousemove`,this.Hs,{capture:!0}),this.Us&&J.removeEventListener(Gm,this.Us),clearTimeout(this.Ys),(e=this.zs)==null||e.stop(),this.ks=!1)}Ws(e,t){var n=this.instance.scrollManager.scrollY(),r=this.instance.scrollManager.scrollX(),i=this.instance.scrollManager.scrollElement(),a=function(e,t,n){for(var r=e;r&&K_(r)&&!q_(r,`body`);){if(r===n)return!1;if(Kd(t,q?.getComputedStyle(r).position))return!0;r=iv(r)}return!1}(tv(e),[`fixed`,`sticky`],i);return{x:e.clientX+(a?0:r),y:e.clientY+(a?0:n),target_fixed:a,type:t}}wt(e,t){var n;if(t===void 0&&(t=`click`),!G_(e.target)&&Bv(e)){var r=this.Ws(e,t);(n=this.rageclicks)!=null&&n.isRageClick(e.clientX,e.clientY,new Date().getTime())&&this.Gs(X({},r,{type:`rageclick`})),this.Gs(r)}}Vs(e){!G_(e.target)&&Bv(e)&&(clearTimeout(this.Ys),this.Ys=setTimeout((()=>{this.Gs(this.Ws(e,`mousemove`))}),500))}Gs(e){if(q){var t=q.location.href,n=this.Rt.custom_personal_data_properties,r=Lh(t,this.Rt.mask_personal_data_properties?[...Bh,...n||[]]:[],Hh);this.T=this.T||{},this.T[r]||(this.T[r]=[]),this.T[r].push(e)}}$i(){this.T&&!tf(this.T)&&this.instance.capture(`$$heatmap`,{$heatmap_data:this.getAndClearBuffer()})}},deadClicksAutocapture:kh,webVitalsAutocapture:class{constructor(e){var t;this.Ls=!1,this.ks=!1,this.T={url:void 0,metrics:[],firstMetricTimestamp:void 0},this.Xs=()=>{clearTimeout(this.Js),this.T.metrics.length!==0&&(this._instance.capture(`$web_vitals`,this.T.metrics.reduce(((e,t)=>X({},e,{[`$web_vitals_`+t.name+`_event`]:X({},t),[`$web_vitals_`+t.name+`_value`]:t.value})),{})),this.T={url:void 0,metrics:[],firstMetricTimestamp:void 0})},this.nt=e=>{var t=this._instance.sessionManager?.checkAndGetSessionAndWindowId(!0);if(Z(t))jv.error(`Could not read session ID. Dropping metrics!`);else{this.T=this.T||{url:void 0,metrics:[],firstMetricTimestamp:void 0};var n=this.Ks();Z(n)||(of(e?.name)||of(e?.value)?jv.error(`Invalid metric received`,e):!this.Qs||this.Qs>e.value?(this.T.url!==n&&(this.Xs(),this.Js=setTimeout(this.Xs,this.flushToCaptureTimeoutMs)),Z(this.T.url)&&(this.T.url=n),this.T.firstMetricTimestamp=Z(this.T.firstMetricTimestamp)?Date.now():this.T.firstMetricTimestamp,e.attribution&&e.attribution.interactionTargetElement&&(e.attribution.interactionTargetElement=void 0),this.T.metrics.push(X({},e,{$current_url:n,$session_id:t.sessionId,$window_id:t.windowId,timestamp:Date.now()})),this.T.metrics.length===this.allowedMetrics.length&&this.Xs()):jv.error(`Ignoring metric with value >= `+this.Qs,e))}},this.eo=()=>{if(!this.ks){var e,t,n,r,i=Y.__PosthogExtensions__;Z(i)||Z(i.postHogWebVitalsCallbacks)||({onLCP:e,onCLS:t,onFCP:n,onINP:r}=i.postHogWebVitalsCallbacks),e&&t&&n&&r?(this.allowedMetrics.indexOf(`LCP`)>-1&&e(this.nt.bind(this)),this.allowedMetrics.indexOf(`CLS`)>-1&&t(this.nt.bind(this)),this.allowedMetrics.indexOf(`FCP`)>-1&&n(this.nt.bind(this)),this.allowedMetrics.indexOf(`INP`)>-1&&r(this.nt.bind(this)),this.ks=!0):jv.error(`web vitals callbacks not loaded - not starting`)}},this._instance=e,this.Ls=!((t=this._instance.persistence)==null||!t.props[lm]),this.startIfEnabled()}get io(){return this._instance.config.capture_performance}get allowedMetrics(){var e=ef(this.io)?this.io?.web_vitals_allowed_metrics:void 0;return of(e)?this._instance.persistence?.props[fm]||[`CLS`,`FCP`,`INP`,`LCP`]:e}get flushToCaptureTimeoutMs(){return(ef(this.io)?this.io.web_vitals_delayed_flush_ms:void 0)||5e3}get useAttribution(){var e=ef(this.io)?this.io.web_vitals_attribution:void 0;return e!=null&&e}get Qs(){var e=ef(this.io)&&sf(this.io.__web_vitals_max_value)?this.io.__web_vitals_max_value:Mv;return e>0&&6e4>=e?Mv:e}get isEnabled(){var e=jd?.protocol;if(e!==`http:`&&e!==`https:`)return jv.info(`Web Vitals are disabled on non-http/https protocols`),!1;var t=ef(this.io)?this.io.web_vitals:lf(this.io)?this.io:void 0;return lf(t)?t:this.Ls}startIfEnabled(){this.isEnabled&&!this.ks&&(jv.info(`enabled, starting...`),this.ai(this.eo))}onRemoteConfig(e){if(`capturePerformance`in e){var t=ef(e.capturePerformance)&&!!e.capturePerformance.web_vitals,n=ef(e.capturePerformance)?e.capturePerformance.web_vitals_allowed_metrics:void 0;this._instance.persistence&&(this._instance.persistence.register({[lm]:t}),this._instance.persistence.register({[fm]:n})),this.Ls=t,this.startIfEnabled()}}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.postHogWebVitalsCallbacks?e():(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,this.useAttribution?`web-vitals-with-attribution`:`web-vitals`,(t=>{t?jv.error(`failed to load script`,t):e()}))}Ks(){var e=q?q.location.href:void 0;if(e){var t=this._instance.config.custom_personal_data_properties;return Lh(e,this._instance.config.mask_personal_data_properties?[...Bh,...t||[]]:[],Hh)}jv.error(`Could not determine current URL`)}}},xy={exceptionObserver:class{constructor(e){var t;this.eo=()=>{var e;if(q&&this.isEnabled&&(e=Y.__PosthogExtensions__)!=null&&e.errorWrappingFunctions){var t=Y.__PosthogExtensions__.errorWrappingFunctions.wrapOnError,n=Y.__PosthogExtensions__.errorWrappingFunctions.wrapUnhandledRejection,r=Y.__PosthogExtensions__.errorWrappingFunctions.wrapConsoleError;try{!this.ro&&this.Rt.capture_unhandled_errors&&(this.ro=t(this.captureException.bind(this))),!this.no&&this.Rt.capture_unhandled_rejections&&(this.no=n(this.captureException.bind(this))),!this.so&&this.Rt.capture_console_errors&&(this.so=r(this.captureException.bind(this)))}catch(e){Ov.error(`failed to start`,e),this.oo()}}},this._instance=e,this.ao=!((t=this._instance.persistence)==null||!t.props[om]),this.lo=new yf({refillRate:this._instance.config.error_tracking.__exceptionRateLimiterRefillRate??1,bucketSize:this._instance.config.error_tracking.__exceptionRateLimiterBucketSize??10,refillInterval:1e4,qt:Ov}),this.Rt=this.uo(),this.startIfEnabledOrStop()}uo(){var e=this._instance.config.capture_exceptions,t={capture_unhandled_errors:!1,capture_unhandled_rejections:!1,capture_console_errors:!1};return ef(e)?t=X({},t,e):(Z(e)?this.ao:e)&&(t=X({},t,{capture_unhandled_errors:!0,capture_unhandled_rejections:!0})),t}get isEnabled(){return this.Rt.capture_console_errors||this.Rt.capture_unhandled_errors||this.Rt.capture_unhandled_rejections}startIfEnabledOrStop(){this.isEnabled?(Ov.info(`enabled`),this.oo(),this.ai(this.eo)):this.oo()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.errorWrappingFunctions&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`exception-autocapture`,(t=>{if(t)return Ov.error(`failed to load script`,t);e()}))}oo(){var e,t,n;(e=this.ro)==null||e.call(this),this.ro=void 0,(t=this.no)==null||t.call(this),this.no=void 0,(n=this.so)==null||n.call(this),this.so=void 0}onRemoteConfig(e){`autocaptureExceptions`in e&&(this.ao=!!e.autocaptureExceptions||!1,this._instance.persistence&&this._instance.persistence.register({[om]:this.ao}),this.Rt=this.uo(),this.startIfEnabledOrStop())}onConfigChange(){this.Rt=this.uo()}captureException(e){var t,n,r=(e==null||(t=e.$exception_list)==null||(t=t[0])==null?void 0:t.type)??`Exception`;this.lo.consumeRateLimit(r)?Ov.info(`Skipping exception capture because of client rate limiting.`,{exception:r}):(n=this._instance.exceptions)==null||n.sendExceptionEvent(e)}},exceptions:class{constructor(e){this.ho=[],this.co=new bp([new Mp,new Hp,new Pp,new Np,new Bp,new zp,new Ip,new Vp],function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];return function(t,r){r===void 0&&(r=0);for(var i=[],a=t.split(` -`),o=r;a.length>o;o++){var s=a[o];if(1024>=s.length){var c=jp.test(s)?s.replace(jp,`$1`):s;if(!c.match(/\S*Error: /)){for(var l of n){var u=l(c,e);if(u){i.push(u);break}}if(i.length>=50)break}}}return function(e){if(!e.length)return[];var t=Array.from(e);return t.reverse(),t.slice(0,50).map((e=>{return X({},e,{filename:e.filename||(n=t,n[n.length-1]||{}).filename,function:e.function||xp});var n}))}(i)}}(`web:javascript`,Dp,Ap)),this._instance=e,this.ho=this._instance.persistence?.get_property(sm)??[],this.do=qp(this.vo()),this.fo=new Jp(this.do)}onConfigChange(){this.do=qp(this.vo()),this.fo.setConfig(this.do)}onRemoteConfig(e){if(`errorTracking`in e){var t=e.errorTracking?.suppressionRules??[],n=e.errorTracking?.captureExtensionExceptions;this.ho=t,this._instance.persistence&&this._instance.persistence.register({[sm]:this.ho,[cm]:n})}}get po(){var e,t=!!this._instance.get_property(cm);return(e=this._instance.config.error_tracking.captureExtensionExceptions??t)!=null&&e}buildProperties(e,t){return this.co.buildFromUnknown(e,{syntheticException:t?.syntheticException,mechanism:{handled:t?.handled}})}addExceptionStep(e,t){if(this.do.enabled)try{if(!nf(e)||e.trim().length===0)return void cy.warn(`Ignoring exception step because message must be a non-empty string`);var{sanitizedProperties:n,droppedKeys:r}=function(e){if(!e)return{sanitizedProperties:{},droppedKeys:[]};var t=[];return{sanitizedProperties:Object.keys(e).reduce(((n,r)=>Gp.has(r)?(t.push(r),n):(n[r]=e[r],n)),{}),droppedKeys:t}}(this.mo(t));r.length>0&&cy.warn(`Ignoring reserved exception step fields`,{droppedKeys:r}),this.fo.add(X({[Up]:e,[Wp]:new Date().toISOString()},n))}catch(e){cy.error(`Failed to add exception step. Ignoring breadcrumb.`,e)}}sendExceptionEvent(e){try{var t=e.$exception_list;if(this.yo(t)){if(this.bo(t))return this.wo(`Exception dropped: matched a suppression rule`),void cy.info(`Skipping exception capture because a suppression rule matched`);if(!this.po&&this._o(t))return this.wo(`Exception dropped: thrown by a browser extension`),void cy.info(`Skipping exception capture because it was thrown by an extension`);if(!this._instance.config.error_tracking.__capturePostHogExceptions&&this.Io(t))return this.wo(`Exception dropped: thrown by the PostHog SDK`),void cy.info(`Skipping exception capture because it was thrown by the PostHog SDK`)}var n=this.do.enabled&&of(e.$exception_steps)?this.Co(e):e;try{var r=this._instance.capture(`$exception`,n,{_noTruncate:!0,_batchKey:`exceptionEvent`,Jr:!0});return r&&this.fo.clear(),r}catch(e){cy.error(`Failed to capture exception event. Dropping this exception.`,e),this.fo.clear();return}}catch(e){cy.error(`Failed to process exception event. Ignoring this exception.`,e);return}}Co(e){try{var t=this.fo.getAttachable();return t.length===0?e:X({},e,{$exception_steps:t})}catch(t){return cy.error(`Failed to read buffered exception steps. Capturing exception without steps.`,t),e}}wo(e){this.do.enabled&&this.fo.add({[Up]:e,[Wp]:new Date().toISOString()})}mo(e){return ef(e)?X({},e):{}}vo(){return this._instance.config.error_tracking?.exception_steps??{}}bo(e){if(e.length===0)return!1;var t=e.reduce(((e,t)=>{var{type:n,value:r}=t;return nf(n)&&n.length>0&&e.$exception_types.push(n),nf(r)&&r.length>0&&e.$exception_values.push(r),e}),{$exception_types:[],$exception_values:[]});return this.ho.some((e=>{var n=e.values.map((e=>{var n=m_[e.operator],r=Qd(e.value)?e.value:[e.value],i=t[e.key]??[];return r.length>0&&n(r,i)}));return e.type===`OR`?n.some(Boolean):n.every(Boolean)}))}_o(e){return e.flatMap((e=>e.stacktrace?.frames??[])).some((e=>e.filename&&e.filename.startsWith(`chrome-extension://`)))}Io(e){if(e.length>0){var t,n,r=e[0].stacktrace?.frames??[],i=r[r.length-1];return(t=i==null||(n=i.filename)==null?void 0:n.includes(`posthog.com/static`))!=null&&t}return!1}yo(e){return!of(e)&&Qd(e)}}},Sy=X({productTours:class{get Qi(){return this._instance.persistence}constructor(e){this.So=null,this.xo=null,this._instance=e}initialize(){this.loadIfEnabled()}onRemoteConfig(e){`productTours`in e&&(this.Qi&&this.Qi.register({[dm]:!!e.productTours}),this.loadIfEnabled())}loadIfEnabled(){var e,t;this.So||(e=this._instance).config.disable_product_tours||(t=e.persistence)==null||!t.get_property(dm)||this.ai((()=>this.ko()))}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.generateProductTours?e():(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`product-tours`,(t=>{t?Vv.error(`Could not load product tours script`,t):e()}))}ko(){var e;!this.So&&(e=Y.__PosthogExtensions__)!=null&&e.generateProductTours&&(this.So=Y.__PosthogExtensions__.generateProductTours(this._instance,!0))}getProductTours(e,t){if(t===void 0&&(t=!1),!Qd(this.xo)||t){var n=this.Qi;if(n){var r=n.props[wm];if(Qd(r)&&!t)return this.xo=r,void e(r,{isLoaded:!0})}this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/product_tours/?token=`+this._instance.config.token),method:`GET`,callback:t=>{var r=t.statusCode;if(r!==200||!t.json){var i=`Product Tours API could not be loaded, status: `+r;Vv.error(i),e([],{isLoaded:!1,error:i});return}var a=Qd(t.json.product_tours)?t.json.product_tours:[];this.xo=a,n&&n.register({[wm]:a}),e(a,{isLoaded:!0})}})}else e(this.xo,{isLoaded:!0})}getActiveProductTours(e){of(this.So)?e([],{isLoaded:!1,error:`Product tours not loaded`}):this.So.getActiveProductTours(e)}showProductTour(e){var t;(t=this.So)==null||t.showTourById(e)}previewTour(e){this.So?this.So.previewTour(e):this.ai((()=>{var t;this.ko(),(t=this.So)==null||t.previewTour(e)}))}dismissProductTour(){var e;(e=this.So)==null||e.dismissTour(`user_clicked_skip`)}nextStep(){var e;(e=this.So)==null||e.nextStep()}previousStep(){var e;(e=this.So)==null||e.previousStep()}clearCache(){var e;this.xo=null,(e=this.Qi)==null||e.unregister(wm)}resetTour(e){var t;(t=this.So)==null||t.resetTour(e)}resetAllTours(){var e;(e=this.So)==null||e.resetAllTours()}cancelPendingTour(e){var t;(t=this.So)==null||t.cancelPendingTour(e)}}},vy),Cy={siteApps:class{constructor(e){this._instance=e,this.To=[],this.apps={}}get isEnabled(){return!!this._instance.config.opt_in_site_apps}Ao(e,t){if(t){var n=this.globalsForEvent(t);this.To.push(n),this.To.length>1e3&&(this.To=this.To.slice(10))}}get siteAppLoaders(){var e;return(e=Y._POSTHOG_REMOTE_CONFIG)==null||(e=e[this._instance.config.token])==null?void 0:e.siteApps}initialize(){if(this.isEnabled){var e=this._instance._addCaptureHook(this.Ao.bind(this));this.Eo=()=>{e(),this.To=[],this.Eo=void 0}}}globalsForEvent(e){if(!e)throw Error(`Event payload is required`);var t={},n=this._instance.get_property(`$groups`)||[],r=this._instance.get_property(`$stored_group_properties`)||{};for(var[i,a]of Object.entries(r))t[i]={id:n[i],type:i,properties:a};var{$set_once:o,$set:s}=e;return{event:X({},Vd(e,Hv),{properties:X({},e.properties,s?{$set:X({},e.properties?.$set??{},s)}:{},o?{$set_once:X({},e.properties?.$set_once??{},o)}:{}),elements_chain:e.properties?.$elements_chain??``,distinct_id:e.properties?.distinct_id}),person:{properties:this._instance.get_property(`$stored_person_properties`)},groups:t}}setupSiteApp(e){var t=this.apps[e.id],n=()=>{var n;!t.errored&&this.To.length&&(Uv.info(`Processing `+this.To.length+` events for site app with id `+e.id),this.To.forEach((e=>t.processEvent==null?void 0:t.processEvent(e))),t.processedBuffer=!0),Object.values(this.apps).every((e=>e.processedBuffer||e.errored))&&((n=this.Eo)==null||n.call(this))},r=!1,i=i=>{t.errored=!i,t.loaded=!0,Uv.info(`Site app with id `+e.id+` `+(i?`loaded`:`errored`)),r&&n()};try{var{processEvent:a}=e.init({posthog:this._instance,callback(e){i(e)}});a&&(t.processEvent=a),r=!0}catch(t){Uv.error(Wv+e.id,t),i(!1)}if(r&&t.loaded)try{n()}catch(n){Uv.error(`Error while processing buffered events PostHog app with config id `+e.id,n),t.errored=!0}}Ro(){var e=this.siteAppLoaders||[];for(var t of e)this.apps[t.id]={id:t.id,loaded:!1,errored:!1,processedBuffer:!1};for(var n of e)this.setupSiteApp(n)}No(e){if(Object.keys(this.apps).length!==0){var t=this.globalsForEvent(e);for(var n of Object.values(this.apps))try{n.processEvent==null||n.processEvent(t)}catch(t){Uv.error(`Error while processing event `+e.event+` for site app `+n.id,t)}}}onRemoteConfig(e){var t,n,r,i=this;if((t=this.siteAppLoaders)!=null&&t.length)return this.isEnabled?(this.Ro(),void this._instance.on(`eventCaptured`,(e=>this.No(e)))):void Uv.error(`PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.`);if((n=this.Eo)==null||n.call(this),(r=e.siteApps)!=null&&r.length)if(this.isEnabled){var a=function(e){var t;Y[`__$$ph_site_app_`+e]=i._instance,(t=Y.__PosthogExtensions__)==null||t.loadSiteApp==null||t.loadSiteApp(i._instance,s,(t=>{if(t)return Uv.error(Wv+e,t)}))};for(var{id:o,url:s}of e.siteApps)a(o)}else Uv.error(`PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.`)}}},wy={tracingHeaders:class{constructor(e){this.Mo=void 0,this.Fo=void 0,this.eo=()=>{var e,t;Z(this.Mo)&&((e=Y.__PosthogExtensions__)==null||(e=e.tracingHeadersPatchFns)==null||e._patchXHR(this._instance.config.__add_tracing_headers||[],this._instance.get_distinct_id(),this._instance.sessionManager)),Z(this.Fo)&&((t=Y.__PosthogExtensions__)==null||(t=t.tracingHeadersPatchFns)==null||t._patchFetch(this._instance.config.__add_tracing_headers||[],this._instance.get_distinct_id(),this._instance.sessionManager))},this._instance=e}initialize(){this.startIfEnabledOrStop()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.tracingHeadersPatchFns&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`tracing-headers`,(t=>{if(t)return Av.error(`failed to load script`,t);e()}))}startIfEnabledOrStop(){var e,t;this._instance.config.__add_tracing_headers?this.ai(this.eo):((e=this.Mo)==null||e.call(this),(t=this.Fo)==null||t.call(this),this.Mo=void 0,this.Fo=void 0)}}},Ty=X({surveys:class{get Rt(){return this._instance.config}constructor(e){this.Oo=void 0,this._surveyManager=null,this.Po=!1,this.Lo=[],this.Do=null,this._instance=e,this._surveyEventReceiver=null}initialize(){this.loadIfEnabled()}onRemoteConfig(e){if(!this.Rt.disable_surveys){var t=e.surveys;if(of(t))return x_.warn(`Flags not loaded yet. Not loading surveys.`);this.Oo=Qd(t)?t.length>0:t,x_.info(`flags response received, isSurveysEnabled: `+this.Oo),this.loadIfEnabled()}}reset(){localStorage.removeItem(`lastSeenSurveyDate`);for(var e=[],t=0;tlocalStorage.removeItem(e)))}loadIfEnabled(){if(!this._surveyManager)if(this.Po)x_.info(`Already initializing surveys, skipping...`);else if(this.Rt.disable_surveys)x_.info(Xv);else if(this.Rt.cookieless_mode&&this._instance.consent.isOptedOut())x_.info(`Not loading surveys in cookieless mode without consent.`);else{var e=Y?.__PosthogExtensions__;if(e){if(!Z(this.Oo)||this.Rt.advanced_enable_surveys){var t=this.Oo||this.Rt.advanced_enable_surveys;this.Po=!0;try{var n=e.generateSurveys;if(n)return void this.Bo(n,t);var r=e.loadExternalDependency;if(!r)return void this.jo(zm);r(this._instance,`surveys`,(n=>{n||!e.generateSurveys?this.jo(`Could not load surveys script`,n):this.Bo(e.generateSurveys,t)}))}catch(e){throw this.jo(`Error initializing surveys`,e),e}finally{this.Po=!1}}}else x_.error(`PostHog Extensions not found.`)}}Bo(e,t){this._surveyManager=e(this._instance,t),this._surveyEventReceiver=new Jv(this._instance),x_.info(`Surveys loaded successfully`),this.qo({isLoaded:!0})}jo(e,t){x_.error(e,t),this.qo({isLoaded:!1,error:e})}onSurveysLoaded(e){return this.Lo.push(e),this._surveyManager&&this.qo({isLoaded:!0}),()=>{this.Lo=this.Lo.filter((t=>t!==e))}}getSurveys(e,t){if(t===void 0&&(t=!1),this.Rt.disable_surveys)return x_.info(Xv),e([]);var n,r=this._instance.get_property(Cm);if(r&&!t)return e(r,{isLoaded:!0});typeof Promise<`u`&&this.Do?this.Do.then((t=>{var{surveys:n,context:r}=t;return e(n,r)})):(typeof Promise<`u`&&(this.Do=new Promise((e=>{n=e}))),this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/surveys/?token=`+this.Rt.token),method:`GET`,timeout:this.Rt.surveys_request_timeout_ms,callback:t=>{var r;this.Do=null;var i=t.statusCode;if(i!==200||!t.json){var a=`Surveys API could not be loaded, status: `+i;x_.error(a);var o={isLoaded:!1,error:a};e([],o),n?.({surveys:[],context:o});return}var s,c=t.json.surveys||[],l=c.filter((e=>function(e){return!(!e.start_date||e.end_date)}(e)&&(function(e){var t;return!((t=e.conditions)==null||(t=t.events)==null||(t=t.values)==null||!t.length)}(e)||function(e){var t;return!((t=e.conditions)==null||(t=t.actions)==null||(t=t.values)==null||!t.length)}(e))));l.length>0&&((s=this._surveyEventReceiver)==null||s.register(l)),(r=this._instance.persistence)==null||r.register({[Cm]:c});var u={isLoaded:!0};e(c,u),n?.({surveys:c,context:u})}}))}qo(e){for(var t of this.Lo)try{if(!e.isLoaded)return t([],e);this.getSurveys(t)}catch(e){x_.error(`Error in survey callback`,e)}}getActiveMatchingSurveys(e,t){if(t===void 0&&(t=!1),!of(this._surveyManager))return this._surveyManager.getActiveMatchingSurveys(e,t);x_.warn(`init was not called`)}Zo(e){var t=null;return this.getSurveys((n=>{t=n.find((t=>t.id===e))??null})),t}$o(e){if(of(this._surveyManager))return{eligible:!1,reason:Yv};var t=typeof e==`string`?this.Zo(e):e;return t?this._surveyManager.checkSurveyEligibility(t):{eligible:!1,reason:`Survey not found`}}canRenderSurvey(e){if(of(this._surveyManager))return x_.warn(`init was not called`),{visible:!1,disabledReason:Yv};var t=this.$o(e);return{visible:t.eligible,disabledReason:t.reason}}canRenderSurveyAsync(e,t){return of(this._surveyManager)?(x_.warn(`init was not called`),Promise.resolve({visible:!1,disabledReason:Yv})):new Promise((n=>{this.getSurveys((t=>{var r=t.find((t=>t.id===e))??null;if(r){var i=this.$o(r);n({visible:i.eligible,disabledReason:i.reason})}else n({visible:!1,disabledReason:`Survey not found`})}),t)}))}renderSurvey(e,t,n){var r;if(of(this._surveyManager))x_.warn(`init was not called`);else{var i=typeof e==`string`?this.Zo(e):e;if(i!=null&&i.id)if(T_.includes(i.type)){var a=J?.querySelector(t);if(a)return(r=i.appearance)!=null&&r.surveyPopupDelaySeconds?(x_.info(`Rendering survey `+i.id+` with delay of `+i.appearance.surveyPopupDelaySeconds+` seconds`),void setTimeout((()=>{var e;x_.info(`Rendering survey `+i.id+` with delay of `+i.appearance?.surveyPopupDelaySeconds+` seconds`),(e=this._surveyManager)==null||e.renderSurvey(i,a,n),x_.info(`Survey `+i.id+` rendered`)}),1e3*i.appearance.surveyPopupDelaySeconds)):void this._surveyManager.renderSurvey(i,a,n);x_.warn(`Survey element not found`)}else x_.warn(`Surveys of type `+i.type+` cannot be rendered in the app`);else x_.warn(`Survey not found`)}}displaySurvey(e,t){var n;if(of(this._surveyManager))x_.warn(`init was not called`);else{var r=this.Zo(e);if(r){var i=r;if((n=r.appearance)!=null&&n.surveyPopupDelaySeconds&&t.ignoreDelay&&(i=X({},r,{appearance:X({},r.appearance,{surveyPopupDelaySeconds:0})})),t.displayType!==og.Popover&&t.initialResponses&&x_.warn(`initialResponses is only supported for popover surveys. prefill will not be applied.`),!1===t.ignoreConditions){var a=this.canRenderSurvey(r);if(!a.visible)return void x_.warn(`Survey is not eligible to be displayed: `,a.disabledReason)}t.displayType===og.Inline?this.renderSurvey(i,t.selector,t.properties):this._surveyManager.handlePopoverSurvey(i,t)}else x_.warn(`Survey not found`)}}cancelPendingSurvey(e){of(this._surveyManager)?x_.warn(`init was not called`):this._surveyManager.cancelSurvey(e)}handlePageUnload(){var e;(e=this._surveyManager)==null||e.handlePageUnload()}}},vy),Ey={toolbar:class{constructor(e){this.instance=e}Ho(e){Y.ph_toolbar_state=e}Vo(){return Y.ph_toolbar_state??0}initialize(){return this.maybeLoadToolbar()}maybeLoadToolbar(e,t,n){if(e===void 0&&(e=void 0),t===void 0&&(t=void 0),n===void 0&&(n=void 0),sh(this.instance.config)||!q||!J)return!1;e??=q.location,n??=q.history;try{if(!t){try{q.localStorage.setItem(`test`,`test`),q.localStorage.removeItem(`test`)}catch{return!1}t=q?.localStorage}var r,i=Zv||Rh(e.hash,`__posthog`)||Rh(e.hash,`state`),a=i?th((()=>JSON.parse(atob(decodeURIComponent(i)))))||th((()=>JSON.parse(decodeURIComponent(i)))):null;return a&&a.action===`ph_authorize`?((r=a).source=`url`,r&&Object.keys(r).length>0&&(a.desiredHash?e.hash=a.desiredHash:n?n.replaceState(n.state,``,e.pathname+e.search):e.hash=``)):((r=JSON.parse(t.getItem(Qv)||`{}`)).source=`localstorage`,delete r.userIntent),!(!r.token||this.instance.config.token!==r.token||(this.loadToolbar(r),0))}catch{return!1}}zo(e){var t=Y.ph_load_toolbar||Y.ph_load_editor;!of(t)&&$d(t)?t(e,this.instance):$v.warn(`No toolbar load function found`)}loadToolbar(e){var t=!(J==null||!J.getElementById(Im));if(!q||t)return!1;var n=this.instance.requestRouter.region===`custom`&&this.instance.config.advanced_disable_toolbar_metrics,r=X({token:this.instance.config.token},e,{apiURL:this.instance.requestRouter.endpointFor(`ui`)},n?{instrument:!1}:{});if(q.localStorage.setItem(Qv,JSON.stringify(X({},r,{source:void 0}))),this.Vo()===2)this.zo(r);else if(this.Vo()===0){var i;this.Ho(1),(i=Y.__PosthogExtensions__)==null||i.loadExternalDependency==null||i.loadExternalDependency(this.instance,`toolbar`,(e=>{if(e)return $v.error(`[Toolbar] Failed to load`,e),void this.Ho(0);this.Ho(2),this.zo(r)})),oh(q,`turbolinks:load`,(()=>{this.Ho(0),this.loadToolbar(r)}))}return!0}Uo(e){return this.loadToolbar(e)}maybeLoadEditor(e,t,n){return e===void 0&&(e=void 0),t===void 0&&(t=void 0),n===void 0&&(n=void 0),this.maybeLoadToolbar(e,t,n)}}},Dy=X({experiments:dy},vy),Oy=X({},vy,yy,by,xy,Sy,Cy,Ty,wy,Ey,Dy,{conversations:class{constructor(e){this.Yo=void 0,this._conversationsManager=null,this.Wo=!1,this.Go=null,this._instance=e}initialize(){this.loadIfEnabled()}onRemoteConfig(e){if(!this._instance.config.disable_conversations){var t=e.conversations;of(t)||(lf(t)?this.Yo=t:(this.Yo=t.enabled,this.Go=t),this.loadIfEnabled())}}reset(){var e;(e=this._conversationsManager)==null||e.reset(),this._conversationsManager=null,this.Yo=void 0,this.Go=null}loadIfEnabled(){if(!(this._conversationsManager||this.Wo||this._instance.config.disable_conversations||sh(this._instance.config)||this._instance.config.cookieless_mode&&this._instance.consent.isOptedOut())){var e=Y?.__PosthogExtensions__;if(e&&!Z(this.Yo)&&this.Yo)if(this.Go&&this.Go.token){this.Wo=!0;try{var t=e.initConversations;if(t)return this.Xo(t),void(this.Wo=!1);var n=e.loadExternalDependency;if(!n)return void this.Jo(zm);n(this._instance,`conversations`,(t=>{t||!e.initConversations?this.Jo(`Could not load conversations script`,t):this.Xo(e.initConversations),this.Wo=!1}))}catch(e){this.Jo(`Error initializing conversations`,e),this.Wo=!1}}else fy.error(`Conversations enabled but missing token in remote config.`)}}Xo(e){if(this.Go)try{this._conversationsManager=e(this.Go,this._instance),fy.info(`Conversations loaded successfully`)}catch(e){this.Jo(`Error completing conversations initialization`,e)}else fy.error(`Cannot complete initialization: remote config is null`)}Jo(e,t){fy.error(e,t),this._conversationsManager=null,this.Wo=!1}show(){this._conversationsManager?this._conversationsManager.show():fy.warn(`Conversations not loaded yet.`)}hide(){this._conversationsManager&&this._conversationsManager.hide()}isAvailable(){return!0===this.Yo&&!af(this._conversationsManager)}isVisible(){var e;return(e=this._conversationsManager?.isVisible())!=null&&e}sendMessage(e,t,n){var r=this;return Bd((function*(){return r._conversationsManager?r._conversationsManager.sendMessage(e,t,n):(fy.warn(py),null)}))()}getMessages(e,t){var n=this;return Bd((function*(){return n._conversationsManager?n._conversationsManager.getMessages(e,t):(fy.warn(py),null)}))()}markAsRead(e){var t=this;return Bd((function*(){return t._conversationsManager?t._conversationsManager.markAsRead(e):(fy.warn(py),null)}))()}getTickets(e){var t=this;return Bd((function*(){return t._conversationsManager?t._conversationsManager.getTickets(e):(fy.warn(py),null)}))()}requestRestoreLink(e){var t=this;return Bd((function*(){return t._conversationsManager?t._conversationsManager.requestRestoreLink(e):(fy.warn(py),null)}))()}restoreFromToken(e){var t=this;return Bd((function*(){return t._conversationsManager?t._conversationsManager.restoreFromToken(e):(fy.warn(py),null)}))()}restoreFromUrlToken(){var e=this;return Bd((function*(){return e._conversationsManager?e._conversationsManager.restoreFromUrlToken():(fy.warn(py),null)}))()}getCurrentTicketId(){return this._conversationsManager?.getCurrentTicketId()??null}getWidgetSessionId(){return this._conversationsManager?.getWidgetSessionId()??null}sn(){var e;(e=this._conversationsManager)==null||e.setIdentity()}an(){var e;(e=this._conversationsManager)==null||e.clearIdentity()}}},{logs:class{constructor(e){var t;this.Ko=!1,this.Qo=!1,this.qt=Zp(`[logs]`),this.ta=[],this.ea=0,this.ia=0,this.ra=!1,this._instance=e,this._instance&&(t=this._instance.config.logs)!=null&&t.captureConsoleLogs&&(this.Ko=!0)}initialize(){this.loadIfEnabled()}onRemoteConfig(e){var t=e.logs?.captureConsoleLogs;!of(t)&&t&&(this.Ko=!0,this.loadIfEnabled())}reset(){this.ta=[],this.Oi&&=(clearTimeout(this.Oi),void 0),this.ea=0,this.ia=0,this.ra=!1}loadIfEnabled(){if(this.Ko&&!this.Qo){var e=Y?.__PosthogExtensions__;if(e){var t=e.loadExternalDependency;t?t(this._instance,`logs`,(t=>{var n;t||(n=e.logs)==null||!n.initializeLogs?this.qt.error(`Could not load logs script`,t):(e.logs.initializeLogs(this._instance),this.Qo=!0)})):this.qt.error(zm)}else this.qt.error(`PostHog Extensions not found.`)}}captureLog(e){if(this._instance.is_capturing())if(e&&e.body){var t=this._instance.config.logs?.flushIntervalMs??3e3,n=this._instance.config.logs?.maxLogsPerInterval??1e3,r=Date.now();if(t>r-this.ia||(this.ia=r,this.ea=0,this.ra=!1),n>this.ea){this.ea++;var i=function(e,t){var{text:n,number:r}=my[e.level||`info`]||hy,i=String(Date.now())+`000000`,a={};t.distinctId&&(a.posthogDistinctId=t.distinctId),t.sessionId&&(a.sessionId=t.sessionId),t.currentUrl&&(a[`url.full`]=t.currentUrl),t.activeFeatureFlags&&t.activeFeatureFlags.length>0&&(a.feature_flags=t.activeFeatureFlags);var o=X({},a,e.attributes||{}),s={timeUnixNano:i,observedTimeUnixNano:i,severityNumber:r,severityText:n,body:{stringValue:e.body},attributes:_y(o)};return e.trace_id&&(s.traceId=e.trace_id),e.span_id&&(s.spanId=e.span_id),Z(e.trace_flags)||(s.flags=e.trace_flags),s}(e,this.na());this.ta.push({record:i}),(this._instance.config.logs?.maxBufferSize??100)>this.ta.length?this.sa():this.flushLogs()}else this.ra||=(this.qt.warn(`captureLog dropping logs: exceeded `+n+` logs per `+t+`ms`),!0)}else this.qt.warn(`captureLog requires a body`)}get logger(){return this.oa||={trace:(e,t)=>this.captureLog({body:e,level:`trace`,attributes:t}),debug:(e,t)=>this.captureLog({body:e,level:`debug`,attributes:t}),info:(e,t)=>this.captureLog({body:e,level:`info`,attributes:t}),warn:(e,t)=>this.captureLog({body:e,level:`warn`,attributes:t}),error:(e,t)=>this.captureLog({body:e,level:`error`,attributes:t}),fatal:(e,t)=>this.captureLog({body:e,level:`fatal`,attributes:t})},this.oa}flushLogs(e){if(this.Oi&&=(clearTimeout(this.Oi),void 0),this.ta.length!==0){var t=this.ta;this.ta=[];var n=this._instance.config.logs,r=X({"service.name":n?.serviceName||`unknown_service`},n?.environment&&{"deployment.environment":n.environment},n?.serviceVersion&&{"service.version":n.serviceVersion},n?.resourceAttributes),i=function(e,t){return{resourceLogs:[{resource:{attributes:_y(t)},scopeLogs:[{scope:{name:Rd.LIB_NAME},logRecords:e}]}]}}(t.map((e=>e.record)),r),a=this._instance.requestRouter.endpointFor(`api`,`/i/v1/logs`)+`?token=`+encodeURIComponent(this._instance.config.token);this._instance.qr({method:`POST`,url:a,data:i,compression:`best-available`,batchKey:`logs`,transport:e})}}sa(){this.Oi||=setTimeout((()=>{this.Oi=void 0,this.flushLogs()}),this._instance.config.logs?.flushIntervalMs??3e3)}na(){var e,t={lib:Rd.LIB_NAME};if(t.distinctId=this._instance.get_distinct_id(),this._instance.sessionManager){var{sessionId:n}=this._instance.sessionManager.checkAndGetSessionAndWindowId(!0);t.sessionId=n}if(Y!=null&&(e=Y.location)!=null&&e.href&&(t.currentUrl=Y.location.href),this._instance.featureFlags){var r=this._instance.featureFlags.getFlags();r&&r.length>0&&(t.activeFeatureFlags=r)}return t}}});W_.__defaultExtensionClasses=X({},Oy);var ky,Ay=(ky=j_[R_]=new W_,function(){function e(){e.done||(e.done=!0,z_=!1,Qm(j_,(function(e){e._dom_loaded()})))}J!=null&&J.addEventListener?J.readyState===`complete`?e():oh(J,`DOMContentLoaded`,e,{capture:!1}):q&&Q.error("Browser doesn't support `document.addEventListener` so PostHog couldn't be initialized")}(),ky),jy=!1,My=!1;function Ny(){if(typeof navigator>`u`)return!1;let e=navigator.doNotTrack;if(e===`1`||e===`yes`||navigator.msDoNotTrack===`1`)return!0;let t=(typeof window<`u`?window:null)?.doNotTrack;return t===`1`||t===`yes`}function Py(){return Ny()||Ly()?!1:My}var Fy=[/^\/connect\/bot\/[^/]+/,/^\/login\/(?:device|agent-key|code)(?:\/|$)/,/\/verify-email\/[^/]+/,/\/reset-password\/[^/]+/,/\/oauth\/callback/,/^\/oauth-complete$/,/\/approve\/[^/]+/];function Iy(e){return Fy.some(t=>t.test(e))}function Ly(){return typeof window<`u`&&Iy(window.location.pathname)}function Ry(e){Py()&&e&&Ay.identify(e)}function zy(){jy&&Ay.reset()}var By=null,Vy=new Set;function Hy(){return By}function Uy(e){if(e===By)return;let t=By;By=e;for(let n of Vy)n(e,t)}function Wy(e){return Vy.add(e),()=>Vy.delete(e)}var Gy=Ot((e,t)=>({current:null,seenKeys:new Set,notify:n=>{if(!n.actorId||n.actorId!==Hy())return;let{current:r,seenKeys:i}=t();i.has(n.key)||e({current:r??n,seenKeys:new Set(i).add(n.key)})},dismiss:()=>e({current:null}),reset:()=>e({current:null,seenKeys:new Set})}));Wy(()=>Gy.getState().reset());var Ky=`insufficient_credits`,qy=11300;function Jy(e){return typeof e==`object`&&!!e}function Yy(e,t){return e!==402||!Jy(t)?!1:t.error!==void 0&&t.error!==null?t.error===Ky:t.error_code===qy}function Xy(){return Hy()}function Zy(e,t){Gy.getState().notify({...e,actorId:t})}function Qy(e,t,n,r){e&&Yy(t,n)&&Zy(e,r)}var $y=`modulepreload`,eb=function(e,t){return new URL(e,t).href},tb={},nb=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=eb(t,n),t in tb)return;tb[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:$y,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},rb=c({hardRedirect:()=>ab,openExternal:()=>ib});function ib(e){window.location.assign(e)}function ab(e){window.location.href=e}var ob=o((()=>{}));function sb(e,t=``){return`${t.replace(/\/+$/,``)}/api/v1${e}`}var cb=class extends Error{status;errorCode;errorResponse;constructor(e,t){super(t.message),this.name=`ApiError`,this.status=e,this.errorCode=t.error_code,this.errorResponse=t}},lb=new Set([`/auth/login`,`/auth/register`,`/auth/refresh`,`/auth/forgot-password`,`/auth/reset-password`,`/auth/verify-email`,`/auth/setup`]);function ub(e){let{method:t=`GET`,body:n,headers:r={},signal:i}=e,a={method:t,headers:{"Content-Type":`application/json`,...Py()?{"X-NyxID-Client":`ui`}:{},...r},credentials:e.credentials??`include`,signal:i};return n!==void 0&&(a.body=JSON.stringify(n)),a}async function db(e){try{return await e.json()}catch{return{error:`unknown_error`,error_code:-1,message:`Request failed with status ${String(e.status)}`}}}function fb(e){if(!(e.error!==`consent_required`||!e.consent_url)&&typeof window<`u`){let t=e.consent_url;nb(async()=>{let{openExternal:e}=await Promise.resolve().then(()=>(ob(),rb));return{openExternal:e}},void 0,import.meta.url).then(({openExternal:e})=>e(t))}}async function pb(e,t={}){let n=await mb(e,t);if(n.status!==204)return n.json()}async function mb(e,t={}){let n=t.creditsDenial?Xy():null,r=await fetch(sb(e,t.apiBaseUrl),ub(t));try{t.onResponse?.(r)}catch{}if(r.status===401&&!t.preserveSessionOn401&&!lb.has(e)&&$S.getState().setUser(null),!r.ok){let e=await db(r);throw fb(e),Qy(t.creditsDenial,r.status,e,n),new cb(r.status,e)}return r}var hb={get(e){return pb(e)},post(e,t){return pb(e,{method:`POST`,body:t})},put(e,t){return pb(e,{method:`PUT`,body:t})},patch(e,t,n){return pb(e,{method:`PATCH`,body:t,signal:n?.signal})},delete(e){return pb(e,{method:`DELETE`})}},gb=`nyxid.assistant_context`,_b={ownerUserId:null,lastScreen:null};function vb(e){if(typeof e!=`object`||!e)return _b;let t=e;return{ownerUserId:typeof t.ownerUserId==`string`?t.ownerUserId:null,lastScreen:typeof t.lastScreen==`string`?t.lastScreen:null}}var yb=Ot()(jt(e=>({..._b,recordScreen:(t,n)=>{e(e=>e.ownerUserId===t?{lastScreen:n}:{..._b,ownerUserId:t,lastScreen:n})},clear:()=>{e(_b),typeof localStorage<`u`&&localStorage.removeItem(gb)}}),{name:gb,version:2,migrate:vb,partialize:({ownerUserId:e,lastScreen:t})=>({ownerUserId:e,lastScreen:t})})),bb=`nyxid.assistant_drafts`,xb=50,Sb={ownerUserId:null,drafts:{}};function Cb(e,t){return Object.fromEntries(Object.entries(e).sort(([e,n],[r,i])=>{let a=i.updatedAt-n.updatedAt;return a===0?e===t?-1:+(r===t):a}).slice(0,xb))}var wb=Ot()(jt((e,t)=>({...Sb,saveDraft:(t,n,r)=>{e(e=>{let i={...e.ownerUserId===t?e.drafts:Sb.drafts};return r.trim()?i[n]={text:r,updatedAt:Date.now()}:delete i[n],{ownerUserId:t,drafts:Cb(i,n)}})},getDraft:e=>t().drafts[e]?.text??``,clearDraft:(t,n)=>{e(e=>{let r=e.ownerUserId===t?{...e.drafts}:{};return delete r[n],{ownerUserId:t,drafts:r}})},pruneConversationDrafts:t=>{let n=new Set(t);e(e=>({drafts:Object.fromEntries(Object.entries(e.drafts).filter(([e])=>e.startsWith(`conv:`)?n.has(e.slice(5)):!0))}))},clear:()=>{e(Sb),typeof localStorage<`u`&&localStorage.removeItem(bb)}}),{name:bb,version:1,partialize:({ownerUserId:e,drafts:t})=>({ownerUserId:e,drafts:t})})),Tb=G(`ZodISODateTime`,(e,t)=>{Ic.init(e,t),Xb.init(e,t)});function Eb(e){return cu(Tb,e)}var Db=G(`ZodISODate`,(e,t)=>{Lc.init(e,t),Xb.init(e,t)});function Ob(e){return lu(Db,e)}var kb=G(`ZodISOTime`,(e,t)=>{Rc.init(e,t),Xb.init(e,t)});function Ab(e){return uu(kb,e)}var jb=G(`ZodISODuration`,(e,t)=>{zc.init(e,t),Xb.init(e,t)});function Mb(e){return du(jb,e)}var Nb=(e,t)=>{ss.init(e,t),e.name=`ZodError`,Object.defineProperties(e,{format:{value:t=>us(e,t)},flatten:{value:t=>ls(e,t)},addIssue:{value:t=>{e.issues.push(t),e.message=JSON.stringify(e.issues,Eo,2)}},addIssues:{value:t=>{e.issues.push(...t),e.message=JSON.stringify(e.issues,Eo,2)}},isEmpty:{get(){return e.issues.length===0}}})};G(`ZodError`,Nb);var Pb=G(`ZodError`,Nb,{Parent:Error}),Fb=ds(Pb),Ib=ps(Pb),Lb=hs(Pb),Rb=_s(Pb),zb=ys(Pb),Bb=bs(Pb),Vb=xs(Pb),Hb=Ss(Pb),Ub=Cs(Pb),Wb=ws(Pb),Gb=Ts(Pb),Kb=Es(Pb),qb=G(`ZodType`,(e,t)=>(Sc.init(e,t),Object.assign(e[`~standard`],{jsonSchema:{input:qu(e,`input`),output:qu(e,`output`)}}),e.toJSONSchema=Ku(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,`_def`,{value:t}),e.check=(...n)=>e.clone(Po(t,{checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),{parent:!0}),e.with=e.check,e.clone=(t,n)=>Wo(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.parse=(t,n)=>Fb(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>Lb(e,t,n),e.parseAsync=async(t,n)=>Ib(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>Rb(e,t,n),e.spa=e.safeParseAsync,e.encode=(t,n)=>zb(e,t,n),e.decode=(t,n)=>Bb(e,t,n),e.encodeAsync=async(t,n)=>Vb(e,t,n),e.decodeAsync=async(t,n)=>Hb(e,t,n),e.safeEncode=(t,n)=>Ub(e,t,n),e.safeDecode=(t,n)=>Wb(e,t,n),e.safeEncodeAsync=async(t,n)=>Gb(e,t,n),e.safeDecodeAsync=async(t,n)=>Kb(e,t,n),e.refine=(t,n)=>e.check(uS(t,n)),e.superRefine=t=>e.check(dS(t)),e.overwrite=t=>e.check(ju(t)),e.optional=()=>Kx(e),e.exactOptional=()=>Jx(e),e.nullable=()=>Xx(e),e.nullish=()=>Kx(Xx(e)),e.nonoptional=t=>nS(e,t),e.array=()=>Ox(e),e.or=t=>Mx([e,t]),e.and=t=>Ix(e,t),e.transform=t=>oS(e,Wx(t)),e.default=t=>Qx(e,t),e.prefault=t=>eS(e,t),e.catch=t=>iS(e,t),e.pipe=t=>oS(e,t),e.readonly=()=>cS(e),e.describe=t=>{let n=e.clone();return Rl.add(n,{description:t}),n},Object.defineProperty(e,`description`,{get(){return Rl.get(e)?.description},configurable:!0}),e.meta=(...t)=>{if(t.length===0)return Rl.get(e);let n=e.clone();return Rl.add(n,t[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e.apply=t=>t(e),e)),Jb=G(`_ZodString`,(e,t)=>{Cc.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Yu(e,t,n,r);let n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...t)=>e.check(Tu(...t)),e.includes=(...t)=>e.check(Ou(...t)),e.startsWith=(...t)=>e.check(ku(...t)),e.endsWith=(...t)=>e.check(Au(...t)),e.min=(...t)=>e.check(Cu(...t)),e.max=(...t)=>e.check(Su(...t)),e.length=(...t)=>e.check(wu(...t)),e.nonempty=(...t)=>e.check(Cu(1,...t)),e.lowercase=t=>e.check(Eu(t)),e.uppercase=t=>e.check(Du(t)),e.trim=()=>e.check(Nu()),e.normalize=(...t)=>e.check(Mu(...t)),e.toLowerCase=()=>e.check(Pu()),e.toUpperCase=()=>e.check(Fu()),e.slugify=()=>e.check(Iu())}),Yb=G(`ZodString`,(e,t)=>{Cc.init(e,t),Jb.init(e,t),e.email=t=>e.check(Bl(Zb,t)),e.url=t=>e.check(Kl(tx,t)),e.jwt=t=>e.check(su(gx,t)),e.emoji=t=>e.check(ql(nx,t)),e.guid=t=>e.check(Vl(Qb,t)),e.uuid=t=>e.check(Hl($b,t)),e.uuidv4=t=>e.check(Ul($b,t)),e.uuidv6=t=>e.check(Wl($b,t)),e.uuidv7=t=>e.check(Gl($b,t)),e.nanoid=t=>e.check(Jl(rx,t)),e.guid=t=>e.check(Vl(Qb,t)),e.cuid=t=>e.check(Yl(ix,t)),e.cuid2=t=>e.check(Xl(ax,t)),e.ulid=t=>e.check(Zl(ox,t)),e.base64=t=>e.check(iu(px,t)),e.base64url=t=>e.check(au(mx,t)),e.xid=t=>e.check(Ql(sx,t)),e.ksuid=t=>e.check($l(cx,t)),e.ipv4=t=>e.check(eu(lx,t)),e.ipv6=t=>e.check(tu(ux,t)),e.cidrv4=t=>e.check(nu(dx,t)),e.cidrv6=t=>e.check(ru(fx,t)),e.e164=t=>e.check(ou(hx,t)),e.datetime=t=>e.check(Eb(t)),e.date=t=>e.check(Ob(t)),e.time=t=>e.check(Ab(t)),e.duration=t=>e.check(Mb(t))});function $(e){return zl(Yb,e)}var Xb=G(`ZodStringFormat`,(e,t)=>{wc.init(e,t),Jb.init(e,t)}),Zb=G(`ZodEmail`,(e,t)=>{Dc.init(e,t),Xb.init(e,t)}),Qb=G(`ZodGUID`,(e,t)=>{Tc.init(e,t),Xb.init(e,t)}),$b=G(`ZodUUID`,(e,t)=>{Ec.init(e,t),Xb.init(e,t)});function ex(e){return Hl($b,e)}var tx=G(`ZodURL`,(e,t)=>{Oc.init(e,t),Xb.init(e,t)}),nx=G(`ZodEmoji`,(e,t)=>{kc.init(e,t),Xb.init(e,t)}),rx=G(`ZodNanoID`,(e,t)=>{Ac.init(e,t),Xb.init(e,t)}),ix=G(`ZodCUID`,(e,t)=>{jc.init(e,t),Xb.init(e,t)}),ax=G(`ZodCUID2`,(e,t)=>{Mc.init(e,t),Xb.init(e,t)}),ox=G(`ZodULID`,(e,t)=>{Nc.init(e,t),Xb.init(e,t)}),sx=G(`ZodXID`,(e,t)=>{Pc.init(e,t),Xb.init(e,t)}),cx=G(`ZodKSUID`,(e,t)=>{Fc.init(e,t),Xb.init(e,t)}),lx=G(`ZodIPv4`,(e,t)=>{Bc.init(e,t),Xb.init(e,t)}),ux=G(`ZodIPv6`,(e,t)=>{Vc.init(e,t),Xb.init(e,t)}),dx=G(`ZodCIDRv4`,(e,t)=>{Hc.init(e,t),Xb.init(e,t)}),fx=G(`ZodCIDRv6`,(e,t)=>{Uc.init(e,t),Xb.init(e,t)}),px=G(`ZodBase64`,(e,t)=>{Gc.init(e,t),Xb.init(e,t)}),mx=G(`ZodBase64URL`,(e,t)=>{qc.init(e,t),Xb.init(e,t)}),hx=G(`ZodE164`,(e,t)=>{Jc.init(e,t),Xb.init(e,t)}),gx=G(`ZodJWT`,(e,t)=>{Xc.init(e,t),Xb.init(e,t)}),_x=G(`ZodNumber`,(e,t)=>{Zc.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Xu(e,t,n,r),e.gt=(t,n)=>e.check(yu(t,n)),e.gte=(t,n)=>e.check(bu(t,n)),e.min=(t,n)=>e.check(bu(t,n)),e.lt=(t,n)=>e.check(_u(t,n)),e.lte=(t,n)=>e.check(vu(t,n)),e.max=(t,n)=>e.check(vu(t,n)),e.int=t=>e.check(bx(t)),e.safe=t=>e.check(bx(t)),e.positive=t=>e.check(yu(0,t)),e.nonnegative=t=>e.check(bu(0,t)),e.negative=t=>e.check(_u(0,t)),e.nonpositive=t=>e.check(vu(0,t)),e.multipleOf=(t,n)=>e.check(xu(t,n)),e.step=(t,n)=>e.check(xu(t,n)),e.finite=()=>e;let n=e._zod.bag;e.minValue=Math.max(n.minimum??-1/0,n.exclusiveMinimum??-1/0)??null,e.maxValue=Math.min(n.maximum??1/0,n.exclusiveMaximum??1/0)??null,e.isInt=(n.format??``).includes(`int`)||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null});function vx(e){return fu(_x,e)}var yx=G(`ZodNumberFormat`,(e,t)=>{Qc.init(e,t),_x.init(e,t)});function bx(e){return pu(yx,e)}var xx=G(`ZodBoolean`,(e,t)=>{$c.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Zu(e,t,n,r)});function Sx(e){return mu(xx,e)}var Cx=G(`ZodUnknown`,(e,t)=>{el.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(e,t,n)=>void 0});function wx(){return hu(Cx)}var Tx=G(`ZodNever`,(e,t)=>{tl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Qu(e,t,n,r)});function Ex(e){return gu(Tx,e)}var Dx=G(`ZodArray`,(e,t)=>{rl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>rd(e,t,n,r),e.element=t.element,e.min=(t,n)=>e.check(Cu(t,n)),e.nonempty=t=>e.check(Cu(1,t)),e.max=(t,n)=>e.check(Su(t,n)),e.length=(t,n)=>e.check(wu(t,n)),e.unwrap=()=>e.element});function Ox(e,t){return Lu(Dx,e,t)}var kx=G(`ZodObject`,(e,t)=>{cl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>id(e,t,n,r),Mo(e,`shape`,()=>t.shape),e.keyof=()=>Bx(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:wx()}),e.loose=()=>e.clone({...e._zod.def,catchall:wx()}),e.strict=()=>e.clone({...e._zod.def,catchall:Ex()}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>Yo(e,t),e.safeExtend=t=>Xo(e,t),e.merge=t=>Zo(e,t),e.pick=t=>qo(e,t),e.omit=t=>Jo(e,t),e.partial=(...t)=>Qo(Gx,e,t[0]),e.required=(...t)=>$o(tS,e,t[0])});function Ax(e,t){return new kx({type:`object`,shape:e??{},...K(t)})}var jx=G(`ZodUnion`,(e,t)=>{ul.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ad(e,t,n,r),e.options=t.options});function Mx(e,t){return new jx({type:`union`,options:e,...K(t)})}var Nx=G(`ZodDiscriminatedUnion`,(e,t)=>{jx.init(e,t),dl.init(e,t)});function Px(e,t,n){return new Nx({type:`union`,options:t,discriminator:e,...K(n)})}var Fx=G(`ZodIntersection`,(e,t)=>{fl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>od(e,t,n,r)});function Ix(e,t){return new Fx({type:`intersection`,left:e,right:t})}var Lx=G(`ZodRecord`,(e,t)=>{hl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>sd(e,t,n,r),e.keyType=t.keyType,e.valueType=t.valueType});function Rx(e,t,n){return new Lx({type:`record`,keyType:e,valueType:t,...K(n)})}var zx=G(`ZodEnum`,(e,t)=>{gl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>$u(e,t,n,r),e.enum=t.entries,e.options=Object.values(t.entries);let n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{let i={};for(let r of e)if(n.has(r))i[r]=t.entries[r];else throw Error(`Key ${r} not found in enum`);return new zx({...t,checks:[],...K(r),entries:i})},e.exclude=(e,r)=>{let i={...t.entries};for(let t of e)if(n.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new zx({...t,checks:[],...K(r),entries:i})}});function Bx(e,t){return new zx({type:`enum`,entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...K(t)})}var Vx=G(`ZodLiteral`,(e,t)=>{_l.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ed(e,t,n,r),e.values=new Set(t.values),Object.defineProperty(e,`value`,{get(){if(t.values.length>1)throw Error("This schema contains multiple valid literal values. Use `.values` instead.");return t.values[0]}})});function Hx(e,t){return new Vx({type:`literal`,values:Array.isArray(e)?e:[e],...K(t)})}var Ux=G(`ZodTransform`,(e,t)=>{vl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>nd(e,t,n,r),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new So(e.constructor.name);n.addIssue=r=>{if(typeof r==`string`)n.issues.push(as(r,n.value,t));else{let t=r;t.fatal&&(t.continue=!1),t.code??=`custom`,t.input??=n.value,t.inst??=e,n.issues.push(as(t))}};let i=t.transform(n.value,n);return i instanceof Promise?i.then(e=>(n.value=e,n)):(n.value=i,n)}});function Wx(e){return new Ux({type:`transform`,transform:e})}var Gx=G(`ZodOptional`,(e,t)=>{bl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>hd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Kx(e){return new Gx({type:`optional`,innerType:e})}var qx=G(`ZodExactOptional`,(e,t)=>{xl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>hd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Jx(e){return new qx({type:`optional`,innerType:e})}var Yx=G(`ZodNullable`,(e,t)=>{Sl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>cd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Xx(e){return new Yx({type:`nullable`,innerType:e})}var Zx=G(`ZodDefault`,(e,t)=>{Cl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ud(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Qx(e,t){return new Zx({type:`default`,innerType:e,get defaultValue(){return typeof t==`function`?t():Vo(t)}})}var $x=G(`ZodPrefault`,(e,t)=>{Tl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>dd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function eS(e,t){return new $x({type:`prefault`,innerType:e,get defaultValue(){return typeof t==`function`?t():Vo(t)}})}var tS=G(`ZodNonOptional`,(e,t)=>{El.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ld(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function nS(e,t){return new tS({type:`nonoptional`,innerType:e,...K(t)})}var rS=G(`ZodCatch`,(e,t)=>{Ol.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>fd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function iS(e,t){return new rS({type:`catch`,innerType:e,catchValue:typeof t==`function`?t:()=>t})}var aS=G(`ZodPipe`,(e,t)=>{kl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>pd(e,t,n,r),e.in=t.in,e.out=t.out});function oS(e,t){return new aS({type:`pipe`,in:e,out:t})}var sS=G(`ZodReadonly`,(e,t)=>{jl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>md(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function cS(e){return new sS({type:`readonly`,innerType:e})}var lS=G(`ZodCustom`,(e,t)=>{Nl.init(e,t),qb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>td(e,t,n,r)});function uS(e,t={}){return Ru(lS,e,t)}function dS(e){return zu(e)}var fS=Bx([`response`,`no_response`,`unknown`]),pS=Ax({mode:$(),forward_access_token:Sx(),inject_delegation_token:Sx(),bridge_minted:Sx()}).strict(),mS=Ax({value:$(),truncated:Sx()}).strict(),hS=Ax({status:vx().int().min(100).max(599),headers:Rx($(),mS),sse:Sx()}).strict(),gS=Px(`degraded`,[Ax({degraded:Hx(!1),method:$(),path:$(),commandType:$().nullable(),body:wx(),headers:Rx($(),$()),identity:pS,truncated:Sx(),response:hS.nullable().optional(),upstreamOutcome:fS.optional(),droppedBody:Sx().optional(),droppedHeaders:Sx().optional()}).strict(),Ax({degraded:Hx(!0),method:$(),path:$(),commandType:$().optional(),upstreamOutcome:fS.optional(),status:vx().int().min(100).max(599).optional()}).strict()]),_S=Ax({version:Hx(2),echoes:Ox(gS).min(1),droppedEchoCount:vx().int().nonnegative()}).strict();Ax({id:$(),conversation_id:$().nullable(),created_at:$(),payload:_S}).strict(),Mx([_S,Ox(Ax({method:$(),path:$(),commandType:$().nullable(),body:wx(),headers:Rx($(),$()),identity:pS.strip(),truncated:Sx()})).min(1)]).transform(e=>Array.isArray(e)?{version:2,echoes:e.map(e=>({...e,degraded:!1})),droppedEchoCount:0}:e);var vS=Ax({text:$(),ending:Bx([` +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(let n of e.seen.entries()){let r=n[1];if(t===n[0]){a(n);continue}if(e.external){let r=e.external.registry.get(n[0])?.id;if(t!==n[0]&&r){a(n);continue}}if(e.metadataRegistry.get(n[0])?.id){a(n);continue}if(r.cycle){a(n);continue}if(r.count>1&&e.reused===`ref`){a(n);continue}}}function Wu(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=t=>{let n=e.seen.get(t);if(n.ref===null)return;let i=n.def??n.schema,a={...i},o=n.ref;if(n.ref=null,o){r(o);let n=e.seen.get(o),s=n.schema;if(s.$ref&&(e.target===`draft-07`||e.target===`draft-04`||e.target===`openapi-3.0`)?(i.allOf=i.allOf??[],i.allOf.push(s)):Object.assign(i,s),Object.assign(i,a),t._zod.parent===o)for(let e in i)e===`$ref`||e===`allOf`||e in a||delete i[e];if(s.$ref&&n.def)for(let e in i)e===`$ref`||e===`allOf`||e in n.def&&JSON.stringify(i[e])===JSON.stringify(n.def[e])&&delete i[e]}let s=t._zod.parent;if(s&&s!==o){r(s);let t=e.seen.get(s);if(t?.schema.$ref&&(i.$ref=t.schema.$ref,t.def))for(let e in i)e===`$ref`||e===`allOf`||e in t.def&&JSON.stringify(i[e])===JSON.stringify(t.def[e])&&delete i[e]}e.override({zodSchema:t,jsonSchema:i,path:n.path??[]})};for(let t of[...e.seen.entries()].reverse())r(t[0]);let i={};if(e.target===`draft-2020-12`?i.$schema=`https://json-schema.org/draft/2020-12/schema`:e.target===`draft-07`?i.$schema=`http://json-schema.org/draft-07/schema#`:e.target===`draft-04`?i.$schema=`http://json-schema.org/draft-04/schema#`:e.target,e.external?.uri){let n=e.external.registry.get(t)?.id;if(!n)throw Error("Schema is missing an `id` property");i.$id=e.external.uri(n)}Object.assign(i,n.def??n.schema);let a=e.external?.defs??{};for(let t of e.seen.entries()){let e=t[1];e.def&&e.defId&&(a[e.defId]=e.def)}e.external||Object.keys(a).length>0&&(e.target===`draft-2020-12`?i.$defs=a:i.definitions=a);try{let n=JSON.parse(JSON.stringify(i));return Object.defineProperty(n,`~standard`,{value:{...t[`~standard`],jsonSchema:{input:qu(t,`input`,e.processors),output:qu(t,`output`,e.processors)}},enumerable:!1,writable:!1}),n}catch{throw Error(`Error converting schema to JSON.`)}}function Gu(e,t){let n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);let r=e._zod.def;if(r.type===`transform`)return!0;if(r.type===`array`)return Gu(r.element,n);if(r.type===`set`)return Gu(r.valueType,n);if(r.type===`lazy`)return Gu(r.getter(),n);if(r.type===`promise`||r.type===`optional`||r.type===`nonoptional`||r.type===`nullable`||r.type===`readonly`||r.type===`default`||r.type===`prefault`)return Gu(r.innerType,n);if(r.type===`intersection`)return Gu(r.left,n)||Gu(r.right,n);if(r.type===`record`||r.type===`map`)return Gu(r.keyType,n)||Gu(r.valueType,n);if(r.type===`pipe`)return Gu(r.in,n)||Gu(r.out,n);if(r.type===`object`){for(let e in r.shape)if(Gu(r.shape[e],n))return!0;return!1}if(r.type===`union`){for(let e of r.options)if(Gu(e,n))return!0;return!1}if(r.type===`tuple`){for(let e of r.items)if(Gu(e,n))return!0;return!!(r.rest&&Gu(r.rest,n))}return!1}var Ku=(e,t={})=>n=>{let r=Vu({...n,processors:t});return Hu(e,r),Uu(r,e),Wu(r,e)},qu=(e,t,n={})=>r=>{let{libraryOptions:i,target:a}=r??{},o=Vu({...i??{},target:a,io:t,processors:n});return Hu(e,o),Uu(o,e),Wu(o,e)},Ju={guid:`uuid`,url:`uri`,datetime:`date-time`,json_string:`json-string`,regex:``},Yu=(e,t,n,r)=>{let i=n;i.type=`string`;let{minimum:a,maximum:o,format:s,patterns:c,contentEncoding:l}=e._zod.bag;if(typeof a==`number`&&(i.minLength=a),typeof o==`number`&&(i.maxLength=o),s&&(i.format=Ju[s]??s,i.format===``&&delete i.format,s===`time`&&delete i.format),l&&(i.contentEncoding=l),c&&c.size>0){let e=[...c];e.length===1?i.pattern=e[0].source:e.length>1&&(i.allOf=[...e.map(e=>({...t.target===`draft-07`||t.target===`draft-04`||t.target===`openapi-3.0`?{type:`string`}:{},pattern:e.source}))])}},Xu=(e,t,n,r)=>{let i=n,{minimum:a,maximum:o,format:s,multipleOf:c,exclusiveMaximum:l,exclusiveMinimum:u}=e._zod.bag;typeof s==`string`&&s.includes(`int`)?i.type=`integer`:i.type=`number`,typeof u==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.minimum=u,i.exclusiveMinimum=!0):i.exclusiveMinimum=u),typeof a==`number`&&(i.minimum=a,typeof u==`number`&&t.target!==`draft-04`&&(u>=a?delete i.minimum:delete i.exclusiveMinimum)),typeof l==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.maximum=l,i.exclusiveMaximum=!0):i.exclusiveMaximum=l),typeof o==`number`&&(i.maximum=o,typeof l==`number`&&t.target!==`draft-04`&&(l<=o?delete i.maximum:delete i.exclusiveMaximum)),typeof c==`number`&&(i.multipleOf=c)},Zu=(e,t,n,r)=>{n.type=`boolean`},Qu=(e,t,n,r)=>{n.not={}},$u=(e,t,n,r)=>{let i=e._zod.def,a=To(i.entries);a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),n.enum=a},ed=(e,t,n,r)=>{let i=e._zod.def,a=[];for(let e of i.values)if(e===void 0){if(t.unrepresentable===`throw`)throw Error("Literal `undefined` cannot be represented in JSON Schema")}else if(typeof e==`bigint`){if(t.unrepresentable===`throw`)throw Error(`BigInt literals cannot be represented in JSON Schema`);a.push(Number(e))}else a.push(e);if(a.length!==0)if(a.length===1){let e=a[0];n.type=e===null?`null`:typeof e,t.target===`draft-04`||t.target===`openapi-3.0`?n.enum=[e]:n.const=e}else a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),a.every(e=>typeof e==`boolean`)&&(n.type=`boolean`),a.every(e=>e===null)&&(n.type=`null`),n.enum=a},td=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Custom types cannot be represented in JSON Schema`)},nd=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Transforms cannot be represented in JSON Schema`)},rd=(e,t,n,r)=>{let i=n,a=e._zod.def,{minimum:o,maximum:s}=e._zod.bag;typeof o==`number`&&(i.minItems=o),typeof s==`number`&&(i.maxItems=s),i.type=`array`,i.items=Hu(a.element,t,{...r,path:[...r.path,`items`]})},id=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`,i.properties={};let o=a.shape;for(let e in o)i.properties[e]=Hu(o[e],t,{...r,path:[...r.path,`properties`,e]});let s=new Set(Object.keys(o)),c=new Set([...s].filter(e=>{let n=a.shape[e]._zod;return t.io===`input`?n.optin===void 0:n.optout===void 0}));c.size>0&&(i.required=Array.from(c)),a.catchall?._zod.def.type===`never`?i.additionalProperties=!1:a.catchall?a.catchall&&(i.additionalProperties=Hu(a.catchall,t,{...r,path:[...r.path,`additionalProperties`]})):t.io===`output`&&(i.additionalProperties=!1)},ad=(e,t,n,r)=>{let i=e._zod.def,a=i.inclusive===!1,o=i.options.map((e,n)=>Hu(e,t,{...r,path:[...r.path,a?`oneOf`:`anyOf`,n]}));a?n.oneOf=o:n.anyOf=o},od=(e,t,n,r)=>{let i=e._zod.def,a=Hu(i.left,t,{...r,path:[...r.path,`allOf`,0]}),o=Hu(i.right,t,{...r,path:[...r.path,`allOf`,1]}),s=e=>`allOf`in e&&Object.keys(e).length===1;n.allOf=[...s(a)?a.allOf:[a],...s(o)?o.allOf:[o]]},sd=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`;let o=a.keyType,s=o._zod.bag?.patterns;if(a.mode===`loose`&&s&&s.size>0){let e=Hu(a.valueType,t,{...r,path:[...r.path,`patternProperties`,`*`]});i.patternProperties={};for(let t of s)i.patternProperties[t.source]=e}else (t.target===`draft-07`||t.target===`draft-2020-12`)&&(i.propertyNames=Hu(a.keyType,t,{...r,path:[...r.path,`propertyNames`]})),i.additionalProperties=Hu(a.valueType,t,{...r,path:[...r.path,`additionalProperties`]});let c=o._zod.values;if(c){let e=[...c].filter(e=>typeof e==`string`||typeof e==`number`);e.length>0&&(i.required=e)}},cd=(e,t,n,r)=>{let i=e._zod.def,a=Hu(i.innerType,t,r),o=t.seen.get(e);t.target===`openapi-3.0`?(o.ref=i.innerType,n.nullable=!0):n.anyOf=[a,{type:`null`}]},ld=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},ud=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.default=JSON.parse(JSON.stringify(i.defaultValue))},dd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,t.io===`input`&&(n._prefault=JSON.parse(JSON.stringify(i.defaultValue)))},fd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType;let o;try{o=i.catchValue(void 0)}catch{throw Error(`Dynamic catch values are not supported in JSON Schema`)}n.default=o},pd=(e,t,n,r)=>{let i=e._zod.def,a=t.io===`input`?i.in._zod.def.type===`transform`?i.out:i.in:i.out;Hu(a,t,r);let o=t.seen.get(e);o.ref=a},md=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.readOnly=!0},hd=(e,t,n,r)=>{let i=e._zod.def;Hu(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType};function gd(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}function _d(e,t){for(var n={};e.length;){var r=e[0],i=r.code,a=r.message,o=r.path.join(`.`);if(!n[o])if(`unionErrors`in r){var s=r.unionErrors[0].errors[0];n[o]={message:s.message,type:s.code}}else n[o]={message:a,type:i};if(`unionErrors`in r&&r.unionErrors.forEach(function(t){return t.errors.forEach(function(t){return e.push(t)})}),t){var c=n[o].types,l=c&&c[r.code];n[o]=Ca(o,t,n,i,l?[].concat(l,r.message):r.message)}e.shift()}return n}function vd(e,t){for(var n={};e.length;){var r=e[0],i=r.code,a=r.message,o=r.path.join(`.`);if(!n[o])if(r.code===`invalid_union`&&r.errors.length>0){var s=r.errors[0][0];n[o]={message:s.message,type:s.code}}else n[o]={message:a,type:i};if(r.code===`invalid_union`&&r.errors.forEach(function(t){return t.forEach(function(t){return e.push(t)})}),t){var c=n[o].types,l=c&&c[r.code];n[o]=Ca(o,t,n,i,l?[].concat(l,r.message):r.message)}e.shift()}return n}function yd(e,t,n){if(n===void 0&&(n={}),function(e){return`_def`in e&&typeof e._def==`object`&&`typeName`in e._def}(e))return function(r,i,a){try{return Promise.resolve(gd(function(){return Promise.resolve(e[n.mode===`sync`?`parse`:`parseAsync`](r,t)).then(function(e){return a.shouldUseNativeValidation&&_o({},a),{errors:{},values:n.raw?Object.assign({},r):e}})},function(e){if(function(e){return Array.isArray(e?.issues)}(e))return{values:{},errors:vo(_d(e.errors,!a.shouldUseNativeValidation&&a.criteriaMode===`all`),a)};throw e}))}catch(e){return Promise.reject(e)}};if(function(e){return`_zod`in e&&typeof e._zod==`object`}(e))return function(r,i,a){try{return Promise.resolve(gd(function(){return Promise.resolve((n.mode===`sync`?fs:ms)(e,r,t)).then(function(e){return a.shouldUseNativeValidation&&_o({},a),{errors:{},values:n.raw?Object.assign({},r):e}})},function(e){if(function(e){return e instanceof ss}(e))return{values:{},errors:vo(vd(e.issues,!a.shouldUseNativeValidation&&a.criteriaMode===`all`),a)};throw e}))}catch(e){return Promise.reject(e)}};throw Error(`Invalid input: not a Zod schema`)}function bd(e){let t=ho(e);return z.useMemo(()=>{let e=(e,n,r)=>t.setValue(e,n,{shouldDirty:!0,shouldTouch:!0,shouldValidate:!0,...r});return new Proxy(t,{get:(t,n,r)=>n===`setValue`?e:Reflect.get(t,n,r)})},[t])}var xd=z.createContext({}),Sd=z.createContext({});function Cd(){let e=z.useContext(xd),t=z.useContext(Sd),{getFieldState:n,formState:r}=Sa(),i=n(e.name,r),{id:a}=t;return{id:a,name:e.name,formItemId:`${a}-form-item`,formDescriptionId:`${a}-form-item-description`,formMessageId:`${a}-form-item-message`,...i}}var wd=z.forwardRef(({className:e,...t},n)=>{let r=z.useId(),i=z.useMemo(()=>({id:r}),[r]);return(0,B.jsx)(Sd.Provider,{value:i,children:(0,B.jsx)(`div`,{ref:n,className:Jr(`space-y-3`,e),...t})})});wd.displayName=`FormItem`;var Td=z.forwardRef(({className:e,...t},n)=>{let{error:r,formItemId:i}=Cd();return(0,B.jsx)(Vi,{ref:n,className:Jr(r&&`text-destructive`,e),htmlFor:i,...t})});Td.displayName=`FormLabel`;var Ed=z.forwardRef(({...e},t)=>{let{error:n,formItemId:r,formDescriptionId:i,formMessageId:a}=Cd();return(0,B.jsx)(sn,{ref:t,id:r,"aria-describedby":n?`${i} ${a}`:i,"aria-invalid":!!n,...e})});Ed.displayName=`FormControl`;var Dd=z.forwardRef(({className:e,...t},n)=>{let{formDescriptionId:r}=Cd();return(0,B.jsx)(`p`,{ref:n,id:r,className:Jr(`text-sm text-muted-foreground`,e),...t})});Dd.displayName=`FormDescription`;var Od=z.forwardRef(({className:e,children:t,...n},r)=>{let{error:i,formMessageId:a}=Cd(),o=i?.message?String(i.message):t;return o?(0,B.jsx)(`p`,{ref:r,id:a,className:Jr(`text-sm font-medium text-destructive`,e),...n,children:o}):null});Od.displayName=`FormMessage`;var kd=Ot(()=>({expanded:[]})),q=typeof window<`u`?window:void 0,Ad=typeof globalThis<`u`?globalThis:q;typeof self>`u`&&(Ad.self=Ad),typeof File>`u`&&(Ad.File=function(){});var jd=Ad?.navigator,J=Ad?.document,Md=Ad?.location,Nd=Ad?.fetch,Pd=Ad!=null&&Ad.XMLHttpRequest&&`withCredentials`in new Ad.XMLHttpRequest?Ad.XMLHttpRequest:void 0,Fd=Ad?.AbortController,Id=Ad?.CompressionStream,Ld=jd?.userAgent,Y=q??{},Rd=`1.370.0`,zd={DEBUG:!1,LIB_VERSION:Rd,LIB_NAME:`web`,JS_SDK_VERSION:Rd};function Bd(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function Vd(e){return function(){var t=this,n=arguments;return new Promise((function(r,i){var a=e.apply(t,n);function o(e){Bd(a,r,i,o,s,`next`,e)}function s(e){Bd(a,r,i,o,s,`throw`,e)}o(void 0)}))}}function X(){return X=Object.assign?Object.assign.bind():function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},X.apply(null,arguments)}function Hd(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}function Ud(){return(Ud=Vd((function*(e,t,n){t===void 0&&(t=!0);try{var r=new Blob([e],{type:`text/plain`}).stream().pipeThrough(new CompressionStream(`gzip`));return yield new Response(r).blob()}catch(e){if(n!=null&&n.rethrow)throw e;return t&&console.error(`Failed to gzip compress data`,e),null}}))).apply(this,arguments)}var Wd=[`$snapshot`,`$pageview`,`$pageleave`,`$set`,`survey dismissed`,`survey sent`,`survey shown`,`$identify`,`$groupidentify`,`$create_alias`,`$$client_ingestion_warning`,`$web_experiment_applied`,`$feature_enrollment_update`,`$feature_flag_called`],Gd=`amazonbot,amazonproductbot,app.hypefactors.com,applebot,archive.org_bot,awariobot,backlinksextendedbot,baiduspider,bingbot,bingpreview,chrome-lighthouse,dataforseobot,deepscan,duckduckbot,facebookexternal,facebookcatalog,http://yandex.com/bots,hubspot,ia_archiver,leikibot,linkedinbot,meta-externalagent,mj12bot,msnbot,nessus,petalbot,pinterest,prerender,rogerbot,screaming frog,sebot-wa,sitebulb,slackbot,slurp,trendictionbot,turnitin,twitterbot,vercel-screenshot,vercelbot,yahoo! slurp,yandexbot,zoombot,bot.htm,bot.php,(bot;,bot/,crawler,ahrefsbot,ahrefssiteaudit,semrushbot,siteauditbot,splitsignalbot,gptbot,oai-searchbot,chatgpt-user,perplexitybot,better uptime bot,sentryuptimebot,uptimerobot,headlesschrome,cypress,google-hoteladsverifier,adsbot-google,apis-google,duplexweb-google,feedfetcher-google,google favicon,google web preview,google-read-aloud,googlebot,googleother,google-cloudvertexbot,googleweblight,mediapartners-google,storebot-google,google-inspectiontool,bytespider`.split(`,`),Kd=function(e,t){if(t===void 0&&(t=[]),!e)return!1;var n=e.toLowerCase();return Gd.concat(t).some((e=>{var t=e.toLowerCase();return n.indexOf(t)!==-1}))};function qd(e,t){return e.indexOf(t)!==-1}var Jd=function(e){return e.trim()},Yd=function(e){return e.replace(/^\$/,``)},Xd=Object.prototype,Zd=Xd.hasOwnProperty,Qd=Xd.toString,$d=Array.isArray||function(e){return Qd.call(e)===`[object Array]`},ef=e=>typeof e==`function`,tf=e=>e===Object(e)&&!$d(e),nf=e=>{if(tf(e)){for(var t in e)if(Zd.call(e,t))return!1;return!0}return!1},Z=e=>e===void 0,rf=e=>Qd.call(e)==`[object String]`,af=e=>rf(e)&&e.trim().length===0,of=e=>e===null,sf=e=>Z(e)||of(e),cf=e=>Qd.call(e)==`[object Number]`&&e==e,lf=e=>cf(e)&&e>0,uf=e=>Qd.call(e)===`[object Boolean]`,df=e=>e instanceof FormData,ff=e=>qd(Wd,e);function pf(e){return typeof e!=`object`||!e}function mf(e,t){return{}.toString.call(e)===`[object `+t+`]`}function hf(e){return typeof Event<`u`&&function(e,t){try{return e instanceof t}catch{return!1}}(e,Event)}var gf=[!0,`true`,1,`1`,`yes`],_f=e=>qd(gf,e),vf=[!1,`false`,0,`0`,`no`];function yf(e,t,n,r,i){return t>n&&(r.warn(`min cannot be greater than max.`),t=n),cf(e)?e>n?(r.warn(` cannot be greater than max: `+n+`. Using max value instead.`),n):t>e?(r.warn(` cannot be less than min: `+t+`. Using min value instead.`),t):e:(r.warn(` must be a number. using max or fallback. max: `+n+`, fallback: `+i),yf(i||n,t,n,r))}var bf=class{constructor(e){this.Pt={},this.Dt=e.Dt,this.jt=yf(e.bucketSize,0,100,e.qt),this.$t=yf(e.refillRate,0,this.jt,e.qt),this.Ht=yf(e.refillInterval,0,864e5,e.qt)}Vt(e,t){var n=Math.floor((t-e.lastAccess)/this.Ht);n>0&&(e.tokens=Math.min(e.tokens+n*this.$t,this.jt),e.lastAccess+=n*this.Ht)}consumeRateLimit(e){var t,n=Date.now(),r=String(e),i=this.Pt[r];return i?this.Vt(i,n):this.Pt[r]=i={tokens:this.jt,lastAccess:n},i.tokens===0||(i.tokens--,i.tokens===0&&((t=this.Dt)==null||t.call(this,e)),i.tokens===0)}stop(){this.Pt={}}},xf,Sf,Cf,wf=`Mobile`,Tf=`iOS`,Ef=`Android`,Df=`Tablet`,Of=Ef+` `+Df,kf=`iPad`,Af=`Apple`,jf=Af+` Watch`,Mf=`Safari`,Nf=`BlackBerry`,Pf=`Samsung`,Ff=Pf+`Browser`,If=Pf+` Internet`,Lf=`Chrome`,Rf=Lf+` OS`,zf=Lf+` `+Tf,Bf=`Internet Explorer`,Vf=Bf+` `+wf,Hf=`Opera`,Uf=Hf+` Mini`,Wf=`Edge`,Gf=`Microsoft `+Wf,Kf=`Firefox`,qf=Kf+` `+Tf,Jf=`Nintendo`,Yf=`PlayStation`,Xf=`Xbox`,Zf=Ef+` `+wf,Qf=wf+` `+Mf,$f=`Windows`,ep=$f+` Phone`,tp=`Nokia`,np=`Ouya`,rp=`Generic`,ip=rp+` `+wf.toLowerCase(),ap=rp+` `+Df.toLowerCase(),op=`Konqueror`,sp=`(\\d+(\\.\\d+)?)`,cp=RegExp(`Version/`+sp),lp=new RegExp(Xf,`i`),up=RegExp(Yf+` \\w+`,`i`),dp=RegExp(Jf+` \\w+`,`i`),fp=RegExp(Nf+`|PlayBook|BB10`,`i`),pp={"NT3.51":`NT 3.11`,"NT4.0":`NT 4.0`,"5.0":`2000`,5.1:`XP`,5.2:`XP`,"6.0":`Vista`,6.1:`7`,6.2:`8`,6.3:`8.1`,6.4:`10`,"10.0":`10`},mp=function(e,t){return t||=``,qd(e,` OPR/`)&&qd(e,`Mini`)?Uf:qd(e,` OPR/`)?Hf:fp.test(e)?Nf:qd(e,`IE`+wf)||qd(e,`WPDesktop`)?Vf:qd(e,Ff)?If:qd(e,Wf)||qd(e,`Edg/`)?Gf:qd(e,`FBIOS`)?`Facebook `+wf:qd(e,`UCWEB`)||qd(e,`UCBrowser`)?`UC Browser`:qd(e,`CriOS`)?zf:qd(e,`CrMo`)||qd(e,Lf)?Lf:qd(e,Ef)&&qd(e,Mf)?Zf:qd(e,`FxiOS`)?qf:qd(e.toLowerCase(),op.toLowerCase())?op:((e,t)=>t&&qd(t,Af)||function(e){return qd(e,Mf)&&!qd(e,Lf)&&!qd(e,Ef)}(e))(e,t)?qd(e,wf)?Qf:Mf:qd(e,Kf)?Kf:qd(e,`MSIE`)||qd(e,`Trident/`)?Bf:qd(e,`Gecko`)?Kf:``},hp={[Vf]:[RegExp(`rv:`+sp)],[Gf]:[RegExp(Wf+`?\\/`+sp)],[Lf]:[RegExp(`(`+Lf+`|CrMo)\\/`+sp)],[zf]:[RegExp(`CriOS\\/`+sp)],"UC Browser":[RegExp(`(UCBrowser|UCWEB)\\/`+sp)],[Mf]:[cp],[Qf]:[cp],[Hf]:[RegExp(`(Opera|OPR)\\/`+sp)],[Kf]:[RegExp(Kf+`\\/`+sp)],[qf]:[RegExp(`FxiOS\\/`+sp)],[op]:[RegExp(`Konqueror[:/]?`+sp,`i`)],[Nf]:[RegExp(Nf+` `+sp),cp],[Zf]:[RegExp(`android\\s`+sp,`i`)],[If]:[RegExp(Ff+`\\/`+sp)],[Bf]:[RegExp(`(rv:|MSIE )`+sp)],Mozilla:[RegExp(`rv:`+sp)]},gp=function(e,t){var n=hp[mp(e,t)];if(Z(n))return null;for(var r=0;n.length>r;r++){var i=e.match(n[r]);if(i)return parseFloat(i[i.length-2])}return null},_p=[[RegExp(Xf+`; `+Xf+` (.*?)[);]`,`i`),e=>[Xf,e&&e[1]||``]],[new RegExp(Jf,`i`),[Jf,``]],[new RegExp(Yf,`i`),[Yf,``]],[fp,[Nf,``]],[new RegExp($f,`i`),(e,t)=>{if(/Phone/.test(t)||/WPDesktop/.test(t))return[ep,``];if(new RegExp(wf).test(t)&&!/IEMobile\b/.test(t))return[$f+` `+wf,``];var n=/Windows NT ([0-9.]+)/i.exec(t);if(n&&n[1]){var r=pp[n[1]]||``;return/arm/i.test(t)&&(r=`RT`),[$f,r]}return[$f,``]}],[/((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/,e=>e&&e[3]?[Tf,[e[3],e[4],e[5]||`0`].join(`.`)]:[Tf,``]],[/(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i,e=>{var t=``;return e&&e.length>=3&&(t=Z(e[2])?e[3]:e[2]),[`watchOS`,t]}],[RegExp(`(`+Ef+` (\\d+)\\.(\\d+)\\.?(\\d+)?|`+Ef+`)`,`i`),e=>e&&e[2]?[Ef,[e[2],e[3],e[4]||`0`].join(`.`)]:[Ef,``]],[/Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i,e=>{var t=[`Mac OS X`,``];return e&&e[1]&&(t[1]=[e[1],e[2],e[3]||`0`].join(`.`)),t}],[/Mac/i,[`Mac OS X`,``]],[/CrOS/,[Rf,``]],[/Linux|debian/i,[`Linux`,``]]],vp=function(e){return dp.test(e)?Jf:up.test(e)?Yf:lp.test(e)?Xf:new RegExp(np,`i`).test(e)?np:RegExp(`(`+ep+`|WPDesktop)`,`i`).test(e)?ep:/iPad/.test(e)?kf:/iPod/.test(e)?`iPod Touch`:/iPhone/.test(e)?`iPhone`:/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(e)?jf:fp.test(e)?Nf:/(kobo)\s(ereader|touch)/i.test(e)?`Kobo`:new RegExp(tp,`i`).test(e)?tp:/(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(e)||/(kf[a-z]+)( bui|\)).+silk\//i.test(e)?`Kindle Fire`:/(Android|ZTE)/i.test(e)?new RegExp(wf).test(e)&&!/(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(e)||/pixel[\daxl ]{1,6}/i.test(e)&&!/pixel c/i.test(e)||/(huaweimed-al00|tah-|APA|SM-G92|i980|zte|U304AA)/i.test(e)||/lmy47v/i.test(e)&&!/QTAQZ3/i.test(e)?Ef:Of:RegExp(`(pda|`+wf+`)`,`i`).test(e)?ip:new RegExp(Df,`i`).test(e)&&!RegExp(Df+` pc`,`i`).test(e)?ap:``},yp=e=>e instanceof Error;function bp(e){var t=globalThis._posthogChunkIds;if(t){var n=Object.keys(t);return Cf&&n.length===Sf||(Sf=n.length,Cf=n.reduce(((n,r)=>{xf||={};var i=xf[r];if(i)n[i[0]]=i[1];else for(var a=e(r),o=a.length-1;o>=0;o--){var s=a[o]?.filename,c=t[r];if(s&&c){n[s]=c,xf[r]=[s,c];break}}return n}),{})),Cf}}var xp=class{constructor(e,t,n){n===void 0&&(n=[]),this.coercers=e,this.stackParser=t,this.modifiers=n}buildFromUnknown(e,t){t===void 0&&(t={});var n=t&&t.mechanism||{handled:!0,type:`generic`},r=this.buildCoercingContext(n,t,0).apply(e),i=this.buildParsingContext(t),a=this.parseStacktrace(r,i);return{$exception_list:this.convertToExceptionList(a,n),$exception_level:`error`}}modifyFrames(e){var t=this;return Vd((function*(){for(var n of e)n.stacktrace&&n.stacktrace.frames&&$d(n.stacktrace.frames)&&(n.stacktrace.frames=yield t.applyModifiers(n.stacktrace.frames));return e}))()}coerceFallback(e){return{type:`Error`,value:`Unknown error`,stack:e.syntheticException?.stack,synthetic:!0}}parseStacktrace(e,t){var n,r;return e.cause!=null&&(n=this.parseStacktrace(e.cause,t)),e.stack!=``&&e.stack!=null&&(r=this.applyChunkIds(this.stackParser(e.stack,e.synthetic?t.skipFirstLines:0),t.chunkIdMap)),X({},e,{cause:n,stack:r})}applyChunkIds(e,t){return e.map((e=>(e.filename&&t&&(e.chunk_id=t[e.filename]),e)))}applyCoercers(e,t){for(var n of this.coercers)if(n.match(e))return n.coerce(e,t);return this.coerceFallback(t)}applyModifiers(e){var t=this;return Vd((function*(){var n=e;for(var r of t.modifiers)n=yield r(n);return n}))()}convertToExceptionList(e,t){var n,r,i={type:e.type,value:e.value,mechanism:{type:t.type??`generic`,handled:(n=t.handled)==null||n,synthetic:(r=e.synthetic)!=null&&r}};e.stack&&(i.stacktrace={type:`raw`,frames:e.stack});var a=[i];return e.cause!=null&&a.push(...this.convertToExceptionList(e.cause,X({},t,{handled:!0}))),a}buildParsingContext(e){return{chunkIdMap:bp(this.stackParser),skipFirstLines:e.skipFirstLines??1}}buildCoercingContext(e,t,n){n===void 0&&(n=0);var r=(n,r)=>{if(4>=r){var i=this.buildCoercingContext(e,t,r);return this.applyCoercers(n,i)}};return X({},t,{syntheticException:n==0?t.syntheticException:void 0,mechanism:e,apply:e=>r(e,n),next:e=>r(e,n+1)})}},Sp=`?`;function Cp(e,t,n,r,i){var a={platform:e,filename:t,function:n===``?Sp:n,in_app:!0};return Z(r)||(a.lineno=r),Z(i)||(a.colno=i),a}var wp=(e,t)=>{var n=e.indexOf(`safari-extension`)!==-1,r=e.indexOf(`safari-web-extension`)!==-1;return n||r?[e.indexOf(`@`)===-1?Sp:e.split(`@`)[0],n?`safari-extension:`+t:`safari-web-extension:`+t]:[e,t]},Tp=/^\s*at (\S+?)(?::(\d+))(?::(\d+))\s*$/i,Ep=/^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,Dp=/\((\S*)(?::(\d+))(?::(\d+))\)/,Op=(e,t)=>{var n=Tp.exec(e);if(n){var[,r,i,a]=n;return Cp(t,r,Sp,+i,+a)}var o=Ep.exec(e);if(o){if(o[2]&&o[2].indexOf(`eval`)===0){var s=Dp.exec(o[2]);s&&(o[2]=s[1],o[3]=s[2],o[4]=s[3])}var[c,l]=wp(o[1]||Sp,o[2]);return Cp(t,l,c,o[3]?+o[3]:void 0,o[4]?+o[4]:void 0)}},kp=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i,Ap=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,jp=(e,t)=>{var n=kp.exec(e);if(n){if(n[3]&&n[3].indexOf(` > eval`)>-1){var r=Ap.exec(n[3]);r&&(n[1]=n[1]||`eval`,n[3]=r[1],n[4]=r[2],n[5]=``)}var i=n[3],a=n[1]||Sp;return[a,i]=wp(a,i),Cp(t,i,a,n[4]?+n[4]:void 0,n[5]?+n[5]:void 0)}},Mp=/\(error: (.*)\)/,Np=class{match(e){return this.isDOMException(e)||this.isDOMError(e)}coerce(e,t){var n=rf(e.stack);return{type:this.getType(e),value:this.getValue(e),stack:n?e.stack:void 0,cause:e.cause?t.next(e.cause):void 0,synthetic:!1}}getType(e){return this.isDOMError(e)?`DOMError`:`DOMException`}getValue(e){var t=e.name||(this.isDOMError(e)?`DOMError`:`DOMException`);return e.message?t+`: `+e.message:t}isDOMException(e){return mf(e,`DOMException`)}isDOMError(e){return mf(e,`DOMError`)}},Pp=class{match(e){return(e=>e instanceof Error)(e)}coerce(e,t){return{type:this.getType(e),value:this.getMessage(e,t),stack:this.getStack(e),cause:e.cause?t.next(e.cause):void 0,synthetic:!1}}getType(e){return e.name||e.constructor.name}getMessage(e,t){var n=e.message;return String(n.error&&typeof n.error.message==`string`?n.error.message:n)}getStack(e){return e.stacktrace||e.stack||void 0}},Fp=class{constructor(){}match(e){return mf(e,`ErrorEvent`)&&e.error!=null}coerce(e,t){return t.apply(e.error)||{type:`ErrorEvent`,value:e.message,stack:t.syntheticException?.stack,synthetic:!0}}},Ip=/^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i,Lp=class{match(e){return typeof e==`string`}coerce(e,t){var[n,r]=this.getInfos(e);return{type:n??`Error`,value:r??e,stack:t.syntheticException?.stack,synthetic:!0}}getInfos(e){var t=`Error`,n=e,r=e.match(Ip);return r&&(t=r[1],n=r[2]),[t,n]}},Rp=[`fatal`,`error`,`warning`,`log`,`info`,`debug`];function zp(e,t){t===void 0&&(t=40);var n=Object.keys(e);if(n.sort(),!n.length)return`[object has no keys]`;for(var r=n.length;r>0;r--){var i=n.slice(0,r).join(`, `);if(t>=i.length)return r===n.length?i:i.length>t?i.slice(0,t)+`...`:i}return``}var Bp=class{match(e){return typeof e==`object`&&!!e}coerce(e,t){var n=this.getErrorPropertyFromObject(e);return n?t.apply(n):{type:this.getType(e),value:this.getValue(e),stack:t.syntheticException?.stack,level:this.isSeverityLevel(e.level)?e.level:`error`,synthetic:!0}}getType(e){return hf(e)?e.constructor.name:`Error`}getValue(e){if(`name`in e&&typeof e.name==`string`){var t=`'`+e.name+`' captured as exception`;return`message`in e&&typeof e.message==`string`&&(t+=` with message: '`+e.message+`'`),t}if(`message`in e&&typeof e.message==`string`)return e.message;var n=this.getObjectClassName(e);return(n&&n!==`Object`?`'`+n+`'`:`Object`)+` captured as exception with keys: `+zp(e)}isSeverityLevel(e){return rf(e)&&!af(e)&&Rp.indexOf(e)>=0}getErrorPropertyFromObject(e){for(var t in e)if({}.hasOwnProperty.call(e,t)){var n=e[t];if(yp(n))return n}}getObjectClassName(e){try{var t=Object.getPrototypeOf(e);return t?t.constructor.name:void 0}catch{return}}},Vp=class{match(e){return hf(e)}coerce(e,t){var n=e.constructor.name;return{type:n,value:n+` captured as exception with keys: `+zp(e),stack:t.syntheticException?.stack,synthetic:!0}}},Hp=class{match(e){return pf(e)}coerce(e,t){return{type:`Error`,value:`Primitive value captured as exception: `+String(e),stack:t.syntheticException?.stack,synthetic:!0}}},Up=class{match(e){return mf(e,`PromiseRejectionEvent`)||this.isCustomEventWrappingRejection(e)}isCustomEventWrappingRejection(e){if(!hf(e))return!1;try{var t=e.detail;return typeof t==`object`&&!!t&&`reason`in t}catch{return!1}}coerce(e,t){var n=this.getUnhandledRejectionReason(e);return pf(n)?{type:`UnhandledRejection`,value:`Non-Error promise rejection captured with value: `+String(n),stack:t.syntheticException?.stack,synthetic:!0}:t.apply(n)}getUnhandledRejectionReason(e){try{if(`reason`in e)return e.reason;if(`detail`in e&&e.detail!=null&&typeof e.detail==`object`&&`reason`in e.detail)return e.detail.reason}catch{}return e}},Wp=`$message`,Gp=`$timestamp`,Kp=new Set([Wp,Gp]),qp={enabled:!0,max_bytes:32768};function Jp(e){return e?{enabled:e.enabled??qp.enabled,max_bytes:Xp(e.max_bytes,qp.max_bytes)}:X({},qp)}var Yp=class{constructor(e){this.zt=[],this.Ut=0,this.Rt=Jp(e)}setConfig(e){this.Rt=Jp(e),this.Yt()}add(e){var t=function(e){var t=function(e){var t=new WeakSet;try{return JSON.stringify(e,((e,n)=>{if(typeof n==`bigint`)return n.toString();if(typeof n!=`function`&&typeof n!=`symbol`){if(n instanceof Date)return n.toISOString();if(n instanceof Error)return{name:n.name,message:n.message,stack:n.stack};if(n&&typeof n==`object`){if(t.has(n))return`[Circular]`;t.add(n)}return n}}))}catch{return}}(e);if(t)try{var n=JSON.parse(t);if(!tf(n))return;var r=n,i=r[Wp],a=r[Gp];return!rf(i)||i.trim().length===0||!rf(a)&&!cf(a)?void 0:{step:r,json:t}}catch{return}}(e);if(t){var n=function(e){if(typeof TextEncoder<`u`)return new TextEncoder().encode(e).length;for(var t=encodeURIComponent(e),n=0,r=0;t.length>r;r++)t[r]===`%`?(n+=1,r+=2):n+=1;return n}(t.json);n>this.Rt.max_bytes||(this.zt.push({step:t.step,bytes:n}),this.Ut+=n,this.Yt())}}getAttachable(){return this.zt.map((e=>e.step))}clear(){this.zt=[],this.Ut=0}size(){return this.zt.length}Yt(){for(;this.Ut>this.Rt.max_bytes&&this.zt.length>0;){var e=this.zt.shift();e&&(this.Ut-=e.bytes)}}};function Xp(e,t){if(!cf(e)||e===1/0||e===-1/0)return t;var n=Math.floor(e);return 0>n?t:n}var Zp=function(e,t){var{debugEnabled:n}=t===void 0?{}:t,r={C(t){if(q&&(zd.DEBUG||Y.POSTHOG_DEBUG||n)&&!Z(q.console)&&q.console){for(var r=(`__rrweb_original__`in q.console[t])?q.console[t].__rrweb_original__:q.console[t],i=arguments.length,a=Array(i>1?i-1:0),o=1;i>o;o++)a[o-1]=arguments[o];r(e,...a)}},info(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`log`,...t)},warn(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`warn`,...t)},error(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];r.C(`error`,...t)},critical(){for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];console.error(e,...n)},uninitializedWarning(e){r.error(`You must initialize PostHog before calling `+e)},createLogger:(t,n)=>Zp(e+` `+t,n)};return r},Q=Zp(`[PostHog.js]`),Qp=Q.createLogger,$p=Qp(`[ExternalScriptsLoader]`),em=(e,t,n)=>{if(e.config.disable_external_dependency_loading)return $p.warn(t+` was requested but loading of external scripts is disabled.`),n(`Loading of external scripts is disabled`);var r=J?.querySelectorAll(`script`);if(r){for(var i,a=function(){if(r[o].src===t){var e=r[o];return e.__posthog_loading_callback_fired?{v:n()}:(e.addEventListener(`load`,(t=>{e.__posthog_loading_callback_fired=!0,n(void 0,t)})),e.onerror=e=>n(e),{v:void 0})}},o=0;r.length>o;o++)if(i=a())return i.v}var s=()=>{if(!J)return n(`document not found`);var r=J.createElement(`script`);if(r.type=`text/javascript`,r.crossOrigin=`anonymous`,r.src=t,r.onload=e=>{r.__posthog_loading_callback_fired=!0,n(void 0,e)},r.onerror=e=>n(e),e.config.prepare_external_dependency_script&&(r=e.config.prepare_external_dependency_script(r)),!r)return n(`prepare_external_dependency_script returned null`);if(e.config.external_scripts_inject_target===`head`)J.head.appendChild(r);else{var i,a=J.querySelectorAll(`body > script`);a.length>0?(i=a[0].parentNode)==null||i.insertBefore(r,a[0]):J.body.appendChild(r)}};J!=null&&J.body?s():J?.addEventListener(`DOMContentLoaded`,s)};Y.__PosthogExtensions__=Y.__PosthogExtensions__||{},Y.__PosthogExtensions__.loadExternalDependency=(e,t,n)=>{if(t!==`remote-config`){var r;if(e.config.__preview_external_dependency_versioned_paths)r=e.requestRouter.endpointFor(`assets`,`/static/`+e.version+`/`+t+`.js`);else{var i=`/static/`+t+`.js?v=`+e.version;if(t===`toolbar`){var a=3e5;i=i+`&t=`+Math.floor(Date.now()/a)*a}r=e.requestRouter.endpointFor(`assets`,i)}em(e,r,n)}else em(e,e.requestRouter.endpointFor(`assets`,`/array/`+e.config.token+`/config.js`),n)},Y.__PosthogExtensions__.loadSiteApp=(e,t,n)=>{em(e,e.requestRouter.endpointFor(`api`,t),n)};var tm=`$people_distinct_id`,nm=`$device_id`,rm=`__alias`,im=`__timers`,am=`$autocapture_disabled_server_side`,om=`$heatmaps_enabled_server_side`,sm=`$exception_capture_enabled_server_side`,cm=`$error_tracking_suppression_rules`,lm=`$error_tracking_capture_extension_exceptions`,um=`$web_vitals_enabled_server_side`,dm=`$dead_clicks_enabled_server_side`,fm=`$product_tours_enabled_server_side`,pm=`$web_vitals_allowed_metrics`,mm=`$session_recording_remote_config`,hm=`$sesid`,gm=`$session_is_sampled`,_m=`$enabled_feature_flags`,vm=`$early_access_features`,ym=`$feature_flag_details`,bm=`$feature_flag_payloads`,xm=`$override_feature_flag_payloads`,Sm=`$stored_person_properties`,Cm=`$stored_group_properties`,wm=`$surveys`,Tm=`ph_product_tours`,Em=`$flag_call_reported`,Dm=`$flag_call_reported_session_id`,Om=`$feature_flag_errors`,km=`$feature_flag_evaluated_at`,Am=`$user_state`,jm=`$client_session_props`,Mm=`$capture_rate_limit`,Nm=`$initial_campaign_params`,Pm=`$initial_referrer_info`,Fm=`$initial_person_info`,Im=`$epp`,Lm=`__POSTHOG_TOOLBAR__`,Rm=`$posthog_cookieless`,zm=[tm,rm,`__cmpns`,im,`$session_recording_enabled_server_side`,om,hm,_m,cm,Am,vm,ym,Cm,Sm,wm,Em,Dm,Om,km,jm,Mm,Nm,Pm,Im,Fm,Tm,`$product_tours_activated`,fm,mm,xm],Bm=`PostHog loadExternalDependency extension not found.`,Vm=`on_reject`,Hm=`always`,Um=`anonymous`,Wm=`identified`,Gm=`identified_only`,Km=`visibilitychange`,qm=`beforeunload`,Jm=`$pageview`,Ym=`$pageleave`,Xm=`$identify`,Zm=`$groupidentify`;function Qm(e,t){$d(e)&&e.forEach(t)}function $m(e,t){if(!sf(e))if($d(e))e.forEach(t);else if(df(e))e.forEach(((e,n)=>t(e,n)));else for(var n in e)Zd.call(e,n)&&t(e[n],n)}var eh=function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];for(var i of n)for(var a in i)i[a]!==void 0&&(e[a]=i[a]);return e};function th(e){for(var t=Object.keys(e),n=t.length,r=Array(n);n--;)r[n]=[t[n],e[t[n]]];return r}var nh=function(e){try{return e()}catch{return}},rh=function(e){return function(){try{for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];return e.apply(this,n)}catch(e){Q.critical(`Implementation error. Please turn on debug mode and open a ticket on https://app.posthog.com/home#panel=support%3Asupport%3A.`),Q.critical(e)}}},ih=function(e){var t={};return $m(e,(function(e,n){(rf(e)&&e.length>0||cf(e))&&(t[n]=e)})),t},ah=[`herokuapp.com`,`vercel.app`,`netlify.app`];function oh(e){var t=e?.hostname;if(!rf(t))return!1;var n=t.split(`.`).slice(-2).join(`.`);for(var r of ah)if(n===r)return!1;return!0}function sh(e,t,n,r){var{capture:i=!1,passive:a=!0}=r??{};e?.addEventListener(t,n,{capture:i,passive:a})}function ch(e){return e.name===`ph_toolbar_internal`}Math.trunc||(Math.trunc=function(e){return 0>e?Math.ceil(e):Math.floor(e)}),Number.isInteger||(Number.isInteger=function(e){return cf(e)&&isFinite(e)&&Math.floor(e)===e});var lh=class e{constructor(e){if(this.bytes=e,e.length!==16)throw TypeError(`not 128-bit length`)}static fromFieldsV7(t,n,r,i){if(!Number.isInteger(t)||!Number.isInteger(n)||!Number.isInteger(r)||!Number.isInteger(i)||0>t||0>n||0>r||0>i||t>0xffffffffffff||n>4095||r>1073741823||i>4294967295)throw RangeError(`invalid field value`);var a=new Uint8Array(16);return a[0]=t/2**40,a[1]=t/2**32,a[2]=t/2**24,a[3]=t/2**16,a[4]=t/2**8,a[5]=t,a[6]=112|n>>>8,a[7]=n,a[8]=128|r>>>24,a[9]=r>>>16,a[10]=r>>>8,a[11]=r,a[12]=i>>>24,a[13]=i>>>16,a[14]=i>>>8,a[15]=i,new e(a)}toString(){for(var e=``,t=0;this.bytes.length>t;t++)e=e+(this.bytes[t]>>>4).toString(16)+(15&this.bytes[t]).toString(16),t!==3&&t!==5&&t!==7&&t!==9||(e+=`-`);if(e.length!==36)throw Error(`Invalid UUIDv7 was generated`);return e}clone(){return new e(this.bytes.slice(0))}equals(e){return this.compareTo(e)===0}compareTo(e){for(var t=0;16>t;t++){var n=this.bytes[t]-e.bytes[t];if(n!==0)return Math.sign(n)}return 0}},uh=class{constructor(){this.I=0,this.S=0,this.k=new ph}generate(){var e=this.generateOrAbort();if(Z(e)){this.I=0;var t=this.generateOrAbort();if(Z(t))throw Error(`Could not generate UUID after timestamp reset`);return t}return e}generateOrAbort(){var e=Date.now();if(e>this.I)this.I=e,this.A();else{if(this.I>=e+1e4)return;this.S++,this.S>4398046511103&&(this.I++,this.A())}return lh.fromFieldsV7(this.I,Math.trunc(this.S/2**30),this.S&2**30-1,this.k.nextUint32())}A(){this.S=1024*this.k.nextUint32()+(1023&this.k.nextUint32())}},dh,fh=e=>{if(typeof UUIDV7_DENY_WEAK_RNG<`u`&&UUIDV7_DENY_WEAK_RNG)throw Error(`no cryptographically strong RNG available`);for(var t=0;e.length>t;t++)e[t]=65536*Math.trunc(65536*Math.random())+Math.trunc(65536*Math.random());return e};q&&!Z(q.crypto)&&crypto.getRandomValues&&(fh=e=>crypto.getRandomValues(e));var ph=class{constructor(){this.T=new Uint32Array(8),this.N=1/0}nextUint32(){return this.T.length>this.N||(fh(this.T),this.N=0),this.T[this.N++]}},mh=()=>hh().toString(),hh=()=>(dh||=new uh).generate(),gh=``,_h=/[a-z0-9][a-z0-9-]+\.[a-z]{2,}$/i,vh={Gt:()=>!!J,Xt(e){Q.error(`cookieStore error: `+e)},Jt(e){if(J){try{for(var t=e+`=`,n=J.cookie.split(`;`).filter((e=>e.length)),r=0;n.length>r;r++){for(var i=n[r];i.charAt(0)==` `;)i=i.substring(1,i.length);if(i.indexOf(t)===0)return decodeURIComponent(i.substring(t.length,i.length))}}catch{}return null}},Kt(e){var t;try{t=JSON.parse(vh.Jt(e))||{}}catch{}return t},Qt(e,t,n,r,i){if(J)try{var a=``,o=``,s=function(e,t){if(t){var n=function(e,t){if(t===void 0&&(t=J),gh)return gh;if(!t||[`localhost`,`127.0.0.1`].includes(e))return``;for(var n=e.split(`.`),r=Math.min(n.length,8),i=`dmn_chk_`+mh();!gh&&r--;){var a=n.slice(r).join(`.`),o=i+`=1;domain=.`+a+`;path=/`;t.cookie=o+`;max-age=3`,t.cookie.includes(i)&&(t.cookie=o+`;max-age=0`,gh=a)}return gh}(e);if(!n){var r=(e=>{var t=e.match(_h);return t?t[0]:``})(e);r!==n&&Q.info(`Warning: cookie subdomain discovery mismatch`,r,n),n=r}return n?`; domain=.`+n:``}return``}(J.location.hostname,r);if(n){var c=new Date;c.setTime(c.getTime()+864e5*n),a=`; expires=`+c.toUTCString()}i&&(o=`; secure`);var l=e+`=`+encodeURIComponent(JSON.stringify(t))+a+`; SameSite=Lax; path=/`+s+o;return l.length>3686.4&&Q.warn(`cookieStore warning: large cookie, len=`+l.length),J.cookie=l,l}catch{return}},ti(e,t){if(J!=null&&J.cookie)try{vh.Qt(e,``,-1,t)}catch{return}}},yh=null,bh={Gt(){if(!of(yh))return yh;var e=!0;if(Z(q))e=!1;else try{var t=`__mplssupport__`;bh.Qt(t,`xyz`),bh.Jt(t)!==`"xyz"`&&(e=!1),bh.ti(t)}catch{e=!1}return e||Q.error(`localStorage unsupported; falling back to cookie store`),yh=e,e},Xt(e){Q.error(`localStorage error: `+e)},Jt(e){try{return q?.localStorage.getItem(e)}catch(e){bh.Xt(e)}return null},Kt(e){try{return JSON.parse(bh.Jt(e))||{}}catch{}return null},Qt(e,t){try{q?.localStorage.setItem(e,JSON.stringify(t))}catch(e){bh.Xt(e)}},ti(e){try{q?.localStorage.removeItem(e)}catch(e){bh.Xt(e)}}},xh=[nm,`distinct_id`,hm,gm,Im,Fm,Am],Sh={},Ch={Gt:()=>!0,Xt(e){Q.error(`memoryStorage error: `+e)},Jt:e=>Sh[e]||null,Kt:e=>Sh[e]||null,Qt(e,t){Sh[e]=t},ti(e){delete Sh[e]}},wh=null,Th={Gt(){if(!of(wh))return wh;if(wh=!0,Z(q))wh=!1;else try{var e=`__support__`;Th.Qt(e,`xyz`),Th.Jt(e)!==`"xyz"`&&(wh=!1),Th.ti(e)}catch{wh=!1}return wh},Xt(e){Q.error(`sessionStorage error: `,e)},Jt(e){try{return q?.sessionStorage.getItem(e)}catch(e){Th.Xt(e)}return null},Kt(e){try{return JSON.parse(Th.Jt(e))||null}catch{}return null},Qt(e,t){try{q?.sessionStorage.setItem(e,JSON.stringify(t))}catch(e){Th.Xt(e)}},ti(e){try{q?.sessionStorage.removeItem(e)}catch(e){Th.Xt(e)}}},Eh=class{constructor(e){this._instance=e}get Rt(){return this._instance.config}get consent(){return this.ei()?0:this.ii}isOptedOut(){return this.Rt.cookieless_mode===Hm||this.isRejected()||this.consent===-1&&this.Rt.cookieless_mode===Vm}isOptedIn(){return!this.isOptedOut()}isExplicitlyOptedOut(){return this.consent===0}isRejected(){return this.consent===0||this.consent===-1&&this.Rt.opt_out_capturing_by_default}optInOut(e){this.ri.Qt(this.ni,+!!e,this.Rt.cookie_expiration,this.Rt.cross_subdomain_cookie,this.Rt.secure_cookie)}reset(){this.ri.ti(this.ni,this.Rt.cross_subdomain_cookie)}get ni(){var{token:e,opt_out_capturing_cookie_prefix:t,consent_persistence_name:n}=this._instance.config;return n||(t?t+e:`__ph_opt_in_out_`+e)}get ii(){var e=this.ri.Jt(this.ni);return _f(e)?1:qd(vf,e)?0:-1}get ri(){var e=this.Rt.opt_out_capturing_persistence_type,t=e===`localStorage`?bh:vh;if(!this.si||this.si!==t){this.si=t;var n=e===`localStorage`?vh:bh;n.Jt(this.ni)&&(this.si.Jt(this.ni)||this.optInOut(_f(n.Jt(this.ni))),n.ti(this.ni,this.Rt.cross_subdomain_cookie))}return this.si}ei(){return!!this.Rt.respect_dnt&&[jd?.doNotTrack,jd?.msDoNotTrack,Y.doNotTrack].some((e=>_f(e)))}},Dh=Qp(`[Dead Clicks]`),Oh=()=>!0,kh=e=>{var t,n=!((t=e.instance.persistence)==null||!t.get_property(dm)),r=e.instance.config.capture_dead_clicks;return uf(r)?r:!!tf(r)||n},Ah=class{get lazyLoadedDeadClicksAutocapture(){return this.oi}constructor(e,t,n){this.instance=e,this.isEnabled=t,this.onCapture=n,this.startIfEnabledOrStop()}onRemoteConfig(e){`captureDeadClicks`in e&&(this.instance.persistence&&this.instance.persistence.register({[dm]:e.captureDeadClicks}),this.startIfEnabledOrStop())}startIfEnabledOrStop(){this.isEnabled(this)?this.ai((()=>{this.li()})):this.stop()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.initDeadClicksAutocapture&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this.instance,`dead-clicks-autocapture`,(t=>{t?Dh.error(`failed to load script`,t):e()}))}li(){var e;if(J){if(!this.oi&&(e=Y.__PosthogExtensions__)!=null&&e.initDeadClicksAutocapture){var t=tf(this.instance.config.capture_dead_clicks)?this.instance.config.capture_dead_clicks:{};t.__onCapture=this.onCapture,this.oi=Y.__PosthogExtensions__.initDeadClicksAutocapture(this.instance,t),this.oi.start(J),Dh.info(`starting...`)}}else Dh.error("`document` not found. Cannot start.")}stop(){this.oi&&(this.oi.stop(),this.oi=void 0,Dh.info(`stopping...`))}},jh=Qp(`[SegmentIntegration]`),Mh=`posthog-js`;function Nh(e,t){var{organization:n,projectId:r,prefix:i,severityAllowList:a=[`error`],sendExceptionsToPostHog:o=!0}=t===void 0?{}:t;return t=>{if(a!==`*`&&!a.includes(t.level)||!e.__loaded)return t;t.tags||={};var s=e.requestRouter.endpointFor(`ui`,`/project/`+e.config.token+`/person/`+e.get_distinct_id());t.tags[`PostHog Person URL`]=s,e.sessionRecordingStarted()&&(t.tags[`PostHog Recording URL`]=e.get_session_replay_url({withTimestamp:!0}));var c,l=t.exception?.values||[],u=l.map((e=>X({},e,{stacktrace:e.stacktrace?X({},e.stacktrace,{type:`raw`,frames:(e.stacktrace.frames||[]).map((e=>X({},e,{platform:`web:javascript`})))}):void 0}))),d={$exception_message:l[0]?.value||t.message,$exception_type:l[0]?.type,$exception_level:t.level,$exception_list:u,$sentry_event_id:t.event_id,$sentry_exception:t.exception,$sentry_exception_message:l[0]?.value||t.message,$sentry_exception_type:l[0]?.type,$sentry_tags:t.tags};return n&&r&&(d.$sentry_url=(i||`https://sentry.io/organizations/`)+n+`/issues/?project=`+r+`&query=`+t.event_id),o&&((c=e.exceptions)==null||c.sendExceptionEvent(d)),t}}var Ph=class{constructor(e,t,n,r,i,a){this.name=Mh,this.setupOnce=function(o){o(Nh(e,{organization:t,projectId:n,prefix:r,severityAllowList:i,sendExceptionsToPostHog:a==null||a}))}}},Fh=class{constructor(e){this.ui=(e,t,n)=>{n&&(n.noSessionId||n.activityTimeout||n.sessionPastMaximumLength)&&(Q.info(`[PageViewManager] Session rotated, clearing pageview state`,{sessionId:e,changeReason:n}),this.hi=void 0,this._instance.scrollManager.resetContext())},this._instance=e,this.ci()}ci(){this.di=this._instance.sessionManager?.onSessionId(this.ui)}destroy(){var e;(e=this.di)==null||e.call(this),this.di=void 0}doPageView(e,t){var n=this.vi(e,t);return this.hi={pathname:q?.location.pathname??``,pageViewId:t,timestamp:e},this._instance.scrollManager.resetContext(),n}doPageLeave(e){return this.vi(e,this.hi?.pageViewId)}doEvent(){return{$pageview_id:this.hi?.pageViewId}}vi(e,t){var n=this.hi;if(!n)return{$pageview_id:t};var r={$pageview_id:t,$prev_pageview_id:n.pageViewId},i=this._instance.scrollManager.getContext();if(i&&!this._instance.config.disable_scroll_properties){var{maxScrollHeight:a,lastScrollY:o,maxScrollY:s,maxContentHeight:c,lastContentY:l,maxContentY:u}=i;if(!(Z(a)||Z(o)||Z(s)||Z(c)||Z(l)||Z(u))){a=Math.ceil(a),o=Math.ceil(o),s=Math.ceil(s),c=Math.ceil(c),l=Math.ceil(l),u=Math.ceil(u);var d=a>1?yf(o/a,0,1,Q):1,f=a>1?yf(s/a,0,1,Q):1,p=c>1?yf(l/c,0,1,Q):1,m=c>1?yf(u/c,0,1,Q):1;r=eh(r,{$prev_pageview_last_scroll:o,$prev_pageview_last_scroll_percentage:d,$prev_pageview_max_scroll:s,$prev_pageview_max_scroll_percentage:f,$prev_pageview_last_content:l,$prev_pageview_last_content_percentage:p,$prev_pageview_max_content:u,$prev_pageview_max_content_percentage:m})}}return n.pathname&&(r.$prev_pageview_pathname=n.pathname),n.timestamp&&(r.$prev_pageview_duration=(e.getTime()-n.timestamp.getTime())/1e3),r}},Ih=e=>{var t=J?.createElement(`a`);return Z(t)?null:(t.href=e,t)},Lh=function(e,t){for(var n,r=((e.split(`#`)[0]||``).split(/\?(.*)/)[1]||``).replace(/^\?+/g,``).split(`&`),i=0;r.length>i;i++){var a=r[i].split(`=`);if(a[0]===t){n=a;break}}if(!$d(n)||2>n.length)return``;var o=n[1];try{o=decodeURIComponent(o)}catch{Q.error(`Skipping decoding for malformed query param: `+o)}return o.replace(/\+/g,` `)},Rh=function(e,t,n){if(!e||!t||!t.length)return e;for(var r=e.split(`#`),i=r[1],a=(r[0]||``).split(`?`),o=a[1],s=a[0],c=(o||``).split(`&`),l=[],u=0;c.length>u;u++){var d=c[u].split(`=`);$d(d)&&(t.includes(d[0])?l.push(d[0]+`=`+n):l.push(c[u]))}var f=s;return o!=null&&(f+=`?`+l.join(`&`)),i!=null&&(f+=`#`+i),f},zh=function(e,t){var n=e.match(RegExp(t+`=([^&]*)`));return n?n[1]:null},Bh=`https?://(.*)`,Vh=[`gclid`,`gclsrc`,`dclid`,`gbraid`,`wbraid`,`fbclid`,`msclkid`,`twclid`,`li_fat_id`,`igshid`,`ttclid`,`rdt_cid`,`epik`,`qclid`,`sccid`,`irclid`,`_kx`],Hh=[`utm_source`,`utm_medium`,`utm_campaign`,`utm_content`,`utm_term`,`gad_source`,`mc_cid`,...Vh],Uh=``,Wh=[`li_fat_id`];function Gh(e,t,n){if(!J)return{};var r,i=t?[...Vh,...n||[]]:[],a=Kh(Rh(J.URL,i,Uh),e);return eh((r={},$m(Wh,(function(e){var t=vh.Jt(e);r[e]=t||null})),r),a)}function Kh(e,t){var n=Hh.concat(t||[]),r={};return $m(n,(function(t){r[t]=Lh(e,t)||null})),r}function qh(e){var t=function(e){return e?e.search(Bh+`google.([^/?]*)`)===0?`google`:e.search(Bh+`bing.com`)===0?`bing`:e.search(Bh+`yahoo.com`)===0?`yahoo`:e.search(Bh+`duckduckgo.com`)===0?`duckduckgo`:null:null}(e),n=t==`yahoo`?`p`:`q`,r={};if(!of(t)){r.$search_engine=t;var i=J?Lh(J.referrer,n):``;i.length&&(r.ph_keyword=i)}return r}function Jh(){return navigator.language||navigator.userLanguage}var Yh=`$direct`;function Xh(){return J?.referrer||Yh}function Zh(e,t){var n=e?[...Vh,...t||[]]:[],r=Md?.href.substring(0,1e3);return{r:Xh().substring(0,1e3),u:r?Rh(r,n,Uh):void 0}}function Qh(e){var{r:t,u:n}=e,r={$referrer:t,$referring_domain:t==null?void 0:t==Yh?Yh:Ih(t)?.host};if(n){r.$current_url=n;var i=Ih(n);r.$host=i?.host,r.$pathname=i?.pathname,eh(r,Kh(n))}return t&&eh(r,qh(t)),r}function $h(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function eg(){try{return new Date().getTimezoneOffset()}catch{return}}var tg=[`cookie`,`localstorage`,`localstorage+cookie`,`sessionstorage`,`memory`],ng=class{constructor(e,t){this.Rt=e,this.props={},this.fi=!1,this.pi=(e=>{var t=``;return e.token&&(t=e.token.replace(/\+/g,`PL`).replace(/\//g,`SL`).replace(/=/g,`EQ`)),e.persistence_name?`ph_`+e.persistence_name:`ph_`+t+`_posthog`})(e),this.ri=this.gi(e),this.load(),e.debug&&Q.info(`Persistence loaded`,e.persistence,X({},this.props)),this.update_config(e,e,t),this.save()}isDisabled(){return!!this.mi}gi(e){tg.indexOf(e.persistence.toLowerCase())===-1&&(Q.critical(`Unknown persistence type `+e.persistence+`; falling back to localStorage+cookie`),e.persistence=`localStorage+cookie`);var t=function(e){e===void 0&&(e=[]);var t=[...xh,...e];return X({},bh,{Kt(e){try{var t={};try{t=vh.Kt(e)||{}}catch{}var n=eh(t,JSON.parse(bh.Jt(e)||`{}`));return bh.Qt(e,n),n}catch{}return null},Qt(e,n,r,i,a,o){try{bh.Qt(e,n,void 0,void 0,o);var s={};t.forEach((e=>{n[e]&&(s[e]=n[e])})),Object.keys(s).length&&vh.Qt(e,s,r,i,a,o)}catch(e){bh.Xt(e)}},ti(e,t){try{q?.localStorage.removeItem(e),vh.ti(e,t)}catch(e){bh.Xt(e)}}})}(e.cookie_persisted_properties||[]),n=e.persistence.toLowerCase();return n===`localstorage`&&bh.Gt()?bh:n===`localstorage+cookie`&&t.Gt()?t:n===`sessionstorage`&&Th.Gt()?Th:n===`memory`?Ch:n===`cookie`?vh:t.Gt()?t:vh}yi(e){var t=e??this.Rt.feature_flag_cache_ttl_ms;if(!t||0>=t)return!1;var n=this.props[km];return!n||typeof n!=`number`||Date.now()-n>t}properties(){var e={};return $m(this.props,((t,n)=>{if(n===_m&&tf(t)){if(!this.yi())for(var r=Object.keys(t),i=0;r.length>i;i++)e[`$feature/`+r[i]]=t[r[i]]}else zm.indexOf(n)===-1&&(e[n]=t)})),e}load(){if(!this.mi){var e=this.ri.Kt(this.pi);e&&(this.props=eh({},e))}}save(){this.mi||this.ri.Qt(this.pi,this.props,this.bi,this.wi,this.Ii,this.Rt.debug)}remove(){this.ri.ti(this.pi,!1),this.ri.ti(this.pi,!0)}clear(){this.remove(),this.props={}}register_once(e,t,n){if(tf(e)){Z(t)&&(t=`None`),this.bi=Z(n)?this.Ci:n;var r=!1;if($m(e,((e,n)=>{this.props.hasOwnProperty(n)&&this.props[n]!==t||(this.props[n]=e,r=!0)})),r)return this.save(),!0}return!1}register(e,t){if(tf(e)){this.bi=Z(t)?this.Ci:t;var n=!1;if($m(e,((t,r)=>{e.hasOwnProperty(r)&&this.props[r]!==t&&(this.props[r]=t,n=!0)})),n)return this.save(),!0}return!1}unregister(e){e in this.props&&(delete this.props[e],this.save())}update_campaign_params(){if(!this.fi){var e=Gh(this.Rt.custom_campaign_params,this.Rt.mask_personal_data_properties,this.Rt.custom_personal_data_properties);nf(ih(e))||this.register(e),this.fi=!0}}update_search_keyword(){var e;this.register((e=J?.referrer)?qh(e):{})}update_referrer_info(){this.register_once({$referrer:Xh(),$referring_domain:J!=null&&J.referrer&&Ih(J.referrer)?.host||Yh},void 0)}set_initial_person_info(){this.props[Nm]||this.props[Pm]||this.register_once({[Fm]:Zh(this.Rt.mask_personal_data_properties,this.Rt.custom_personal_data_properties)},void 0)}get_initial_props(){var e={};$m([Pm,Nm],(t=>{var n=this.props[t];n&&$m(n,(function(t,n){e[`$initial_`+Yd(n)]=t}))}));var t,n,r=this.props[Fm];return r&&eh(e,(t=Qh(r),n={},$m(t,(function(e,t){n[`$initial_`+Yd(t)]=e})),n)),e}safe_merge(e){return $m(this.props,(function(t,n){n in e||(e[n]=t)})),e}update_config(e,t,n){if(this.Ci=this.bi=e.cookie_expiration,this.set_disabled(e.disable_persistence||!!n),this.set_cross_subdomain(e.cross_subdomain_cookie),this.set_secure(e.secure_cookie),e.persistence!==t.persistence||!((e,t)=>{if(e.length!==t.length)return!1;var n=[...e].sort(),r=[...t].sort();return n.every(((e,t)=>e===r[t]))})(e.cookie_persisted_properties||[],t.cookie_persisted_properties||[])){var r=this.gi(e),i=this.props;this.clear(),this.ri=r,this.props=i,this.save()}}set_disabled(e){this.mi=e,this.mi?this.remove():this.save()}set_cross_subdomain(e){e!==this.wi&&(this.wi=e,this.remove(),this.save())}set_secure(e){e!==this.Ii&&(this.Ii=e,this.remove(),this.save())}set_event_timer(e,t){var n=this.props[im]||{};n[e]=t,this.props[im]=n,this.save()}remove_event_timer(e){var t=(this.props[im]||{})[e];return Z(t)||(delete this.props[im][e],this.save()),t}get_property(e){return this.props[e]}set_property(e,t){this.props[e]=t,this.save()}},rg={Activation:`events`,Cancellation:`cancelEvents`},ig={Popover:`popover`,API:`api`,Widget:`widget`,ExternalSurvey:`external_survey`},ag={SHOWN:`survey shown`,DISMISSED:`survey dismissed`,SENT:`survey sent`,ABANDONED:`survey abandoned`},og={SURVEY_ID:`$survey_id`,SURVEY_NAME:`$survey_name`,SURVEY_RESPONSE:`$survey_response`,SURVEY_ITERATION:`$survey_iteration`,SURVEY_ITERATION_START_DATE:`$survey_iteration_start_date`,SURVEY_PARTIALLY_COMPLETED:`$survey_partially_completed`,SURVEY_SUBMISSION_ID:`$survey_submission_id`,SURVEY_QUESTIONS:`$survey_questions`,SURVEY_COMPLETED:`$survey_completed`,PRODUCT_TOUR_ID:`$product_tour_id`,SURVEY_LAST_SEEN_DATE:`$survey_last_seen_date`},sg={Popover:`popover`,Inline:`inline`},cg={SHOWN:`product tour shown`,DISMISSED:`product tour dismissed`,COMPLETED:`product tour completed`,STEP_SHOWN:`product tour step shown`,STEP_COMPLETED:`product tour step completed`,BUTTON_CLICKED:`product tour button clicked`,STEP_SELECTOR_FAILED:`product tour step selector failed`,BANNER_CONTAINER_SELECTOR_FAILED:`product tour banner container selector failed`,BANNER_ACTION_CLICKED:`product tour banner action clicked`},lg={TOUR_ID:`$product_tour_id`,TOUR_NAME:`$product_tour_name`,TOUR_ITERATION:`$product_tour_iteration`,TOUR_RENDER_REASON:`$product_tour_render_reason`,TOUR_STEP_ID:`$product_tour_step_id`,TOUR_STEP_ORDER:`$product_tour_step_order`,TOUR_STEP_TYPE:`$product_tour_step_type`,TOUR_DISMISS_REASON:`$product_tour_dismiss_reason`,TOUR_BUTTON_TEXT:`$product_tour_button_text`,TOUR_BUTTON_ACTION:`$product_tour_button_action`,TOUR_BUTTON_LINK:`$product_tour_button_link`,TOUR_BUTTON_TOUR_ID:`$product_tour_button_tour_id`,TOUR_STEPS_COUNT:`$product_tour_steps_count`,TOUR_STEP_SELECTOR:`$product_tour_step_selector`,TOUR_STEP_SELECTOR_FOUND:`$product_tour_step_selector_found`,TOUR_STEP_ELEMENT_TAG:`$product_tour_step_element_tag`,TOUR_STEP_ELEMENT_ID:`$product_tour_step_element_id`,TOUR_STEP_ELEMENT_CLASSES:`$product_tour_step_element_classes`,TOUR_STEP_ELEMENT_TEXT:`$product_tour_step_element_text`,TOUR_ERROR:`$product_tour_error`,TOUR_MATCHES_COUNT:`$product_tour_matches_count`,TOUR_FAILURE_PHASE:`$product_tour_failure_phase`,TOUR_WAITED_FOR_ELEMENT:`$product_tour_waited_for_element`,TOUR_WAIT_DURATION_MS:`$product_tour_wait_duration_ms`,TOUR_BANNER_SELECTOR:`$product_tour_banner_selector`,TOUR_LINKED_SURVEY_ID:`$product_tour_linked_survey_id`,USE_MANUAL_SELECTOR:`$use_manual_selector`,INFERENCE_DATA_PRESENT:`$inference_data_present`,TOUR_LAST_SEEN_DATE:`$product_tour_last_seen_date`,TOUR_TYPE:`$product_tour_type`},ug=Qp(`[RateLimiter]`),dg=class{constructor(e){this.serverLimits={},this.lastEventRateLimited=!1,this.checkForLimiting=e=>{var t=e.text;if(t&&t.length)try{(JSON.parse(t).quota_limited||[]).forEach((e=>{ug.info((e||`events`)+` is quota limited.`),this.serverLimits[e]=new Date().getTime()+6e4}))}catch(e){ug.warn(`could not rate limit - continuing. Error: "`+e?.message+`"`,{text:t});return}},this.instance=e,this.lastEventRateLimited=this.clientRateLimitContext(!0).isRateLimited}get captureEventsPerSecond(){return this.instance.config.rate_limiting?.events_per_second||10}get captureEventsBurstLimit(){return Math.max(this.instance.config.rate_limiting?.events_burst_limit||10*this.captureEventsPerSecond,this.captureEventsPerSecond)}clientRateLimitContext(e){var t;e===void 0&&(e=!1);var{captureEventsBurstLimit:n,captureEventsPerSecond:r}=this,i=new Date().getTime(),a=this.instance.persistence?.get_property(Mm)??{tokens:n,last:i};a.tokens+=(i-a.last)/1e3*r,a.last=i,a.tokens>n&&(a.tokens=n);var o=1>a.tokens;return o||e||(a.tokens=Math.max(0,a.tokens-1)),!o||this.lastEventRateLimited||e||this.instance.capture(`$$client_ingestion_warning`,{$$client_ingestion_warning_message:`posthog-js client rate limited. Config is set to `+r+` events per second and `+n+` events burst limit.`},{skip_client_rate_limiting:!0}),this.lastEventRateLimited=o,(t=this.instance.persistence)==null||t.set_property(Mm,a),{isRateLimited:o,remainingTokens:a.tokens}}isServerRateLimited(e){var t=this.serverLimits[e||`events`]||!1;return!1!==t&&new Date().getTime()e(this.remoteConfig))):e()}xi(e){this._instance._send_request({method:`GET`,url:this._instance.requestRouter.endpointFor(`assets`,`/array/`+this._instance.config.token+`/config`),callback(t){e(t.json)}})}load(){try{if(this.remoteConfig)return fg.info(`Using preloaded remote config`,this.remoteConfig),this.ki(this.remoteConfig),void this.Ti();if(this._instance.Ai())return void fg.warn(`Remote config is disabled. Falling back to local config.`);this.Si((e=>{if(!e)return fg.info(`No config found after loading remote JS config. Falling back to JSON.`),void this.xi((e=>{this.ki(e),this.Ti()}));this.ki(e),this.Ti()}))}catch(e){fg.error(`Error loading remote config`,e)}}stop(){this.Ei&&=(clearInterval(this.Ei),void 0)}refresh(){this._instance.Ai()||J?.visibilityState===`hidden`||this._instance.reloadFeatureFlags()}Ti(){if(!this.Ei){var e=this._instance.config.remote_config_refresh_interval_ms??3e5;e!==0&&(this.Ei=setInterval((()=>{this.refresh()}),e))}}ki(e){var t;e||fg.error(`Failed to fetch remote config from PostHog.`),this._instance.ki(e??{}),!1!==e?.hasFeatureFlags&&(this._instance.config.advanced_disable_feature_flags_on_first_load||(t=this._instance.featureFlags)==null||t.ensureFlagsLoaded())}},mg={GZipJS:`gzip-js`,Base64:`base64`},hg=Uint8Array,gg=Uint16Array,_g=Uint32Array,vg=new hg([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),yg=new hg([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),bg=new hg([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),xg=function(e,t){for(var n=new gg(31),r=0;31>r;++r)n[r]=t+=1<r;++r)for(var a=n[r];n[r+1]>a;++a)i[a]=a-n[r]<<5|r;return[n,i]},Sg=xg(vg,2),Cg=Sg[1];Sg[0][28]=258,Cg[258]=28;for(var wg=xg(yg,0)[1],Tg=new gg(32768),Eg=0;32768>Eg;++Eg){var Dg=(43690&Eg)>>>1|(21845&Eg)<<1;Tg[Eg]=((65280&(Dg=(61680&(Dg=(52428&Dg)>>>2|(13107&Dg)<<2))>>>4|(3855&Dg)<<4))>>>8|(255&Dg)<<8)>>>1}var Og=function(e,t,n){for(var r=e.length,i=0,a=new gg(t);r>i;++i)++a[e[i]-1];var o,s=new gg(t);for(i=0;t>i;++i)s[i]=s[i-1]+a[i-1]<<1;if(n){o=new gg(1<i;++i)if(e[i])for(var l=i<<4|e[i],u=t-e[i],d=s[e[i]-1]++<=d;++d)o[Tg[d]>>>c]=l}else for(o=new gg(r),i=0;r>i;++i)o[i]=Tg[s[e[i]-1]++]>>>15-e[i];return o},kg=new hg(288);for(Eg=0;144>Eg;++Eg)kg[Eg]=8;for(Eg=144;256>Eg;++Eg)kg[Eg]=9;for(Eg=256;280>Eg;++Eg)kg[Eg]=7;for(Eg=280;288>Eg;++Eg)kg[Eg]=8;var Ag=new hg(32);for(Eg=0;32>Eg;++Eg)Ag[Eg]=5;var jg=Og(kg,9,0),Mg=Og(Ag,5,0),Ng=function(e){return(e/8>>0)+(7&e&&1)},Pg=function(e,t,n){(n==null||n>e.length)&&(n=e.length);var r=new(e instanceof gg?gg:e instanceof _g?_g:hg)(n-t);return r.set(e.subarray(t,n)),r},Fg=function(e,t,n){var r=t/8>>0;e[r]|=n<<=7&t,e[r+1]|=n>>>8},Ig=function(e,t,n){var r=t/8>>0;e[r]|=n<<=7&t,e[r+1]|=n>>>8,e[r+2]|=n>>>16},Lg=function(e,t){for(var n=[],r=0;e.length>r;++r)e[r]&&n.push({s:r,f:e[r]});var i=n.length,a=n.slice();if(!i)return[new hg(0),0];if(i==1){var o=new hg(n[0].s+1);return o[n[0].s]=1,[o,1]}n.sort((function(e,t){return e.f-t.f})),n.push({s:-1,f:25001});var s=n[0],c=n[1],l=0,u=1,d=2;for(n[0]={s:-1,f:s.f+c.f,l:s,r:c};u!=i-1;)s=n[n[d].f>n[l].f?l++:d++],c=n[l!=u&&n[d].f>n[l].f?l++:d++],n[u++]={s:-1,f:s.f+c.f,l:s,r:c};var f=a[0].s;for(r=1;i>r;++r)a[r].s>f&&(f=a[r].s);var p=new gg(f+1),m=Rg(n[u-1],p,0);if(m>t){r=0;var h=0,g=m-t,_=1<r;++r){var v=a[r].s;if(t>=p[v])break;h+=_-(1<>>=g;h>0;){var y=a[r].s;t>p[y]?h-=1<=0&&h;--r){var b=a[r].s;p[b]==t&&(--p[b],++h)}m=t}return[new hg(p),m]},Rg=function(e,t,n){return e.s==-1?Math.max(Rg(e.l,t,n+1),Rg(e.r,t,n+1)):t[e.s]=n},zg=function(e){for(var t=e.length;t&&!e[--t];);for(var n=new gg(++t),r=0,i=e[0],a=1,o=function(e){n[r++]=e},s=1;t>=s;++s)if(e[s]==i&&s!=t)++a;else{if(!i&&a>2){for(;a>138;a-=138)o(32754);a>2&&(o(a>10?a-11<<5|28690:a-3<<5|12305),a=0)}else if(a>3){for(o(i),--a;a>6;a-=6)o(8304);a>2&&(o(a-3<<5|8208),a=0)}for(;a--;)o(i);a=1,i=e[s]}return[n.subarray(0,r),t]},Bg=function(e,t){for(var n=0,r=0;t.length>r;++r)n+=e[r]*t[r];return n},Vg=function(e,t,n){var r=n.length,i=Ng(t+2);e[i]=255&r,e[i+1]=r>>>8,e[i+2]=255^e[i],e[i+3]=255^e[i+1];for(var a=0;r>a;++a)e[i+a+4]=n[a];return 8*(i+4+r)},Hg=function(e,t,n,r,i,a,o,s,c,l,u){Fg(t,u++,n),++i[256];for(var d=Lg(i,15),f=d[0],p=d[1],m=Lg(a,15),h=m[0],g=m[1],_=zg(f),v=_[0],y=_[1],b=zg(h),x=b[0],S=b[1],C=new gg(19),w=0;v.length>w;++w)C[31&v[w]]++;for(w=0;x.length>w;++w)C[31&x[w]]++;for(var T=Lg(C,7),E=T[0],ee=T[1],D=19;D>4&&!E[bg[D-1]];--D);var O,k,A,j,M=l+5<<3,N=Bg(i,kg)+Bg(a,Ag)+o,P=Bg(i,f)+Bg(a,h)+o+14+3*D+Bg(C,E)+(2*C[16]+3*C[17]+7*C[18]);if(N>=M&&P>=M)return Vg(t,u,e.subarray(c,c+l));if(Fg(t,u,1+(N>P)),u+=2,N>P){O=Og(f,p,0),k=f,A=Og(h,g,0),j=h;var F=Og(E,ee,0);for(Fg(t,u,y-257),Fg(t,u+5,S-1),Fg(t,u+10,D-4),u+=14,w=0;D>w;++w)Fg(t,u+3*w,E[bg[w]]);u+=3*D;for(var I=[v,x],te=0;2>te;++te){var ne=I[te];for(w=0;ne.length>w;++w)Fg(t,u,F[re=31&ne[w]]),u+=E[re],re>15&&(Fg(t,u,ne[w]>>>5&127),u+=ne[w]>>>12)}}else O=jg,k=kg,A=Mg,j=Ag;for(w=0;s>w;++w)if(r[w]>255){var re;Ig(t,u,O[257+(re=r[w]>>>18&31)]),u+=k[re+257],re>7&&(Fg(t,u,r[w]>>>23&31),u+=vg[re]);var L=31&r[w];Ig(t,u,A[L]),u+=j[L],L>3&&(Ig(t,u,r[w]>>>5&8191),u+=yg[L])}else Ig(t,u,O[r[w]]),u+=k[r[w]];return Ig(t,u,O[256]),u+k[256]},Ug=new _g([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Wg=function(){for(var e=new _g(256),t=0;256>t;++t){for(var n=t,r=9;--r;)n=(1&n&&3988292384)^n>>>1;e[t]=n}return e}(),Gg=function(e,t,n){for(;n;++t)e[t]=n,n>>>=8};function Kg(e,t){t===void 0&&(t={});var n=function(){var e=4294967295;return{p(t){for(var n=e,r=0;t.length>r;++r)n=Wg[255&n^t[r]]^n>>>8;e=n},d(){return 4294967295^e}}}(),r=e.length;n.p(e);var i,a,o,s,c,l=(s=10+((i=t).filename&&i.filename.length+1||0),c=8,function(e,t,n,r,i,a){var o=e.length,s=new hg(r+o+5*(1+Math.floor(o/7e3))+i),c=s.subarray(r,s.length-i),l=0;if(!t||8>o)for(var u=0;o>=u;u+=65535){var d=u+65535;o>d?l=Vg(c,l,e.subarray(u,d)):(c[u]=!0,l=Vg(c,l,e.subarray(u,o)))}else{for(var f=Ug[t-1],p=f>>>13,m=8191&f,h=(1<u;++u){var O=b(u),k=32767&u,A=_[O];if(g[k]=A,_[O]=k,u>=ee){var j=o-u;if((w>7e3||E>24576)&&j>423){l=Hg(e,c,0,x,S,C,T,E,D,u-D,l),E=w=T=0,D=u;for(var M=0;286>M;++M)S[M]=0;for(M=0;30>M;++M)C[M]=0}var N=2,P=0,F=m,I=k-A&32767;if(j>2&&O==b(u-I))for(var te=Math.min(p,j)-1,ne=Math.min(32767,u),re=Math.min(258,j);ne>=I&&--F&&k!=A;){if(e[u+N]==e[u+N-I]){for(var L=0;re>L&&e[u+L]==e[u+L-I];++L);if(L>N){if(N=L,P=I,L>te)break;var R=Math.min(I,L-2),ie=0;for(M=0;R>M;++M){var ae=u-I+M+32768&32767,oe=ae-g[ae]+32768&32767;oe>ie&&(ie=oe,A=ae)}}}I+=(k=A)-(A=g[k])+32768&32767}if(P){x[E++]=268435456|Cg[N]<<18|wg[P];var se=31&Cg[N],ce=31&wg[P];T+=vg[se]+yg[ce],++S[257+se],++C[ce],ee=u+N,++w}else x[E++]=e[u],++S[e[u]]}}l=Hg(e,c,!0,x,S,C,T,E,D,u-D,l)}return Pg(s,0,r+Ng(l)+i)}(a=e,(o=t).level==null?6:o.level,o.mem==null?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(a.length)))):12+o.mem,s,c)),u=l.length;return function(e,t){var n=t.filename;if(e[0]=31,e[1]=139,e[2]=8,e[8]=2>t.level?4:t.level==9?2:0,e[9]=3,t.mtime!=0&&Gg(e,4,Math.floor(new Date(t.mtime||Date.now())/1e3)),n){e[3]=8;for(var r=0;n.length>=r;++r)e[r+10]=n.charCodeAt(r)}}(l,t),Gg(l,u-8,n.d()),Gg(l,u-4,r),l}var qg=!!Pd||!!Nd,Jg=`text/plain`,Yg=!1,Xg=function(e,t,n){n===void 0&&(n=!0);var[r,i]=e.split(`?`),a=X({},t),o=i?.split(`&`).map((e=>{var t,[r,i]=e.split(`=`),o=n&&(t=a[r])!=null?t:i;return delete a[r],r+`=`+o}))??[],s=function(e,t){var n,r;t===void 0&&(t=`&`);var i=[];return $m(e,(function(e,t){Z(e)||Z(t)||t===`undefined`||(n=encodeURIComponent((e=>e instanceof File)(e)?e.name:e.toString()),r=encodeURIComponent(t),i[i.length]=r+`=`+n)})),i.join(t)}(a);return s&&o.push(s),r+`?`+o.join(`&`)},Zg=(e,t)=>JSON.stringify(e,((e,t)=>typeof t==`bigint`?t.toString():t),t),Qg=e=>{if(e.Wt)return e.Wt;var{data:t,compression:n}=e;if(t){if(n===mg.GZipJS){var r=Kg(function(e,t){var n=e.length;if(typeof TextEncoder<`u`)return new TextEncoder().encode(e);for(var r=new hg(e.length+(e.length>>>1)),i=0,a=function(e){r[i++]=e},o=0;n>o;++o){if(i+5>r.length){var s=new hg(i+8+(n-o<<1));s.set(r),r=s}var c=e.charCodeAt(o);128>c?a(c):2048>c?(a(192|c>>>6),a(128|63&c)):c>55295&&57344>c?(a(240|(c=65536+(1047552&c)|1023&e.charCodeAt(++o))>>>18),a(128|c>>>12&63),a(128|c>>>6&63),a(128|63&c)):(a(224|c>>>12),a(128|c>>>6&63),a(128|63&c))}return Pg(r,0,i)}(Zg(t)),{mtime:0});return{contentType:Jg,body:r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength),estimatedSize:r.byteLength}}if(n===mg.Base64){var i=(e=>`data=`+encodeURIComponent(typeof e==`string`?e:Zg(e)))(function(e){return e&&btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,((e,t)=>String.fromCharCode(parseInt(t,16)))))}(Zg(t)));return{contentType:`application/x-www-form-urlencoded`,body:i,estimatedSize:new Blob([i]).size}}var a=Zg(t);return{contentType:`application/json`,body:a,estimatedSize:new Blob([a]).size}}},$g=function(){var e=Vd((function*(e){var t=yield function(e,t,n){return Ud.apply(this,arguments)}(Zg(e.data),zd.DEBUG,{rethrow:!0});if(!t)return e;var n=yield t.arrayBuffer();return X({},e,{Wt:{contentType:Jg,body:n,estimatedSize:n.byteLength}})}));return function(t){return e.apply(this,arguments)}}(),e_=(e,t)=>Xg(e,{_:new Date().getTime().toString(),ver:zd.JS_SDK_VERSION,compression:t}),t_=[];Nd&&t_.push({transport:`fetch`,method(e){var{contentType:t,body:n,estimatedSize:r}=Qg(e)??{},i=new Headers;$m(e.headers,(function(e,t){i.append(t,e)})),t&&i.append(`Content-Type`,t);var a=e.url,o=null;if(Fd){var s=new Fd;o={signal:s.signal,timeout:setTimeout((()=>s.abort()),e.timeout)}}Nd(a,X({method:e?.method||`GET`,headers:i,keepalive:e.method===`POST`&&52428.8>(r||0),body:n,signal:o?.signal},e.fetchOptions)).then((t=>t.text().then((n=>{var r={statusCode:t.status,text:n};if(t.status===200)try{r.json=JSON.parse(n)}catch(e){Q.error(e)}e.callback==null||e.callback(r)})))).catch((t=>{Q.error(t),e.callback==null||e.callback({statusCode:0,error:t})})).finally((()=>o?clearTimeout(o.timeout):null))}}),Pd&&t_.push({transport:`XHR`,method(e){var t=new Pd;t.open(e.method||`GET`,e.url,!0);var{contentType:n,body:r}=Qg(e)??{};$m(e.headers,(function(e,n){t.setRequestHeader(n,e)})),n&&t.setRequestHeader(`Content-Type`,n),e.timeout&&(t.timeout=e.timeout),e.disableXHRCredentials||(t.withCredentials=!0),t.onreadystatechange=()=>{if(t.readyState===4){var n={statusCode:t.status,text:t.responseText};if(t.status===200)try{n.json=JSON.parse(t.responseText)}catch{}e.callback==null||e.callback(n)}},t.send(r)}}),jd!=null&&jd.sendBeacon&&t_.push({transport:`sendBeacon`,method(e){var t=Xg(e.url,{beacon:`1`});try{var{contentType:n,body:r}=Qg(e)??{};if(!r)return;var i=r instanceof Blob?r:new Blob([r],{type:n});jd.sendBeacon(t,i)}catch{}}});var n_=3e3,r_=class{constructor(e,t){this.Ri=!0,this.Ni=[],this.Mi=yf(t?.flush_interval_ms||n_,250,5e3,Q.createLogger(`flush interval`),n_),this.Fi=e}enqueue(e){this.Ni.push(e),this.Oi||this.Pi()}unload(){this.Li();var e=this.Ni.length>0?this.Di():{},t=Object.values(e);[...t.filter((e=>e.url.indexOf(`/e`)===0)),...t.filter((e=>e.url.indexOf(`/e`)!==0))].map((e=>{this.Fi(X({},e,{transport:`sendBeacon`}))}))}enable(){this.Ri=!1,this.Pi()}Pi(){var e=this;this.Ri||(this.Oi=setTimeout((()=>{if(this.Li(),this.Ni.length>0){var t=this.Di(),n=function(){var n=t[r],i=new Date().getTime();n.data&&$d(n.data)&&$m(n.data,(e=>{e.offset=Math.abs(e.timestamp-i),delete e.timestamp})),e.Fi(n)};for(var r in t)n()}}),this.Mi))}Li(){clearTimeout(this.Oi),this.Oi=void 0}Di(){var e={};return $m(this.Ni,(t=>{var n,r=t,i=(r?r.batchKey:null)||r.url;Z(e[i])&&(e[i]=X({},r,{data:[]})),(n=e[i].data)==null||n.push(r.data)})),this.Ni=[],e}},i_=[`retriesPerformedSoFar`],a_=class{constructor(e){this.Bi=!1,this.ji=3e3,this.Ni=[],this._instance=e,this.Ni=[],this.qi=!0,!Z(q)&&`onLine`in q.navigator&&(this.qi=q.navigator.onLine,this.Zi=()=>{this.qi=!0,this.$i()},this.Hi=()=>{this.qi=!1},sh(q,`online`,this.Zi),sh(q,`offline`,this.Hi))}get length(){return this.Ni.length}retriableRequest(e){var{retriesPerformedSoFar:t}=e,n=Hd(e,i_);lf(t)&&(n.url=Xg(n.url,{retry_count:t})),this._instance._send_request(X({},n,{callback:e=>{e.statusCode===200||e.statusCode>=400&&500>e.statusCode||(t??0)>=10?n.callback==null||n.callback(e):this.Vi(X({retriesPerformedSoFar:t},n))}}))}Vi(e){var t=e.retriesPerformedSoFar||0;e.retriesPerformedSoFar=t+1;var n=function(e){var t=3e3*2**e,n=t/2,r=Math.min(18e5,t),i=Math.random()-.5;return Math.ceil(r+i*(r-n))}(t),r=Date.now()+n;this.Ni.push({retryAt:r,requestOptions:e});var i=`Enqueued failed request for retry in `+n;navigator.onLine||(i+=` (Browser is offline)`),Q.warn(i),this.Bi||(this.Bi=!0,this.zi())}zi(){if(this.Ui&&clearTimeout(this.Ui),this.Ni.length===0)return this.Bi=!1,void(this.Ui=void 0);this.Ui=setTimeout((()=>{this.qi&&this.Ni.length>0&&this.$i(),this.zi()}),this.ji)}$i(){var e=Date.now(),t=[],n=this.Ni.filter((n=>e>n.retryAt||(t.push(n),!1)));if(this.Ni=t,n.length>0)for(var{requestOptions:r}of n)this.retriableRequest(r)}unload(){for(var{requestOptions:e}of(this.Ui&&=(clearTimeout(this.Ui),void 0),this.Bi=!1,Z(q)||(this.Zi&&=(q.removeEventListener(`online`,this.Zi),void 0),this.Hi&&=(q.removeEventListener(`offline`,this.Hi),void 0)),this.Ni))try{this._instance._send_request(X({},e,{transport:`sendBeacon`}))}catch(e){Q.error(e)}this.Ni=[]}},o_=class{constructor(e){this.Yi=()=>{this.Wi||={};var e=this.scrollElement(),t=this.scrollY(),n=e?Math.max(0,e.scrollHeight-e.clientHeight):0,r=t+(e?.clientHeight||0),i=e?.scrollHeight||0;this.Wi.lastScrollY=Math.ceil(t),this.Wi.maxScrollY=Math.max(t,this.Wi.maxScrollY??0),this.Wi.maxScrollHeight=Math.max(n,this.Wi.maxScrollHeight??0),this.Wi.lastContentY=r,this.Wi.maxContentY=Math.max(r,this.Wi.maxContentY??0),this.Wi.maxContentHeight=Math.max(i,this.Wi.maxContentHeight??0)},this._instance=e}get Gi(){return this._instance.config.scroll_root_selector}getContext(){return this.Wi}resetContext(){var e=this.Wi;return setTimeout(this.Yi,0),e}startMeasuringScrollPosition(){sh(q,`scroll`,this.Yi,{capture:!0}),sh(q,`scrollend`,this.Yi,{capture:!0}),sh(q,`resize`,this.Yi)}scrollElement(){if(!this.Gi)return q?.document.documentElement;for(var e of $d(this.Gi)?this.Gi:[this.Gi]){var t=q?.document.querySelector(e);if(t)return t}}scrollY(){if(this.Gi){var e=this.scrollElement();return e&&e.scrollTop||0}return q&&(q.scrollY||q.pageYOffset||q.document.documentElement.scrollTop)||0}scrollX(){if(this.Gi){var e=this.scrollElement();return e&&e.scrollLeft||0}return q&&(q.scrollX||q.pageXOffset||q.document.documentElement.scrollLeft)||0}},s_=e=>Zh(e?.config.mask_personal_data_properties,e?.config.custom_personal_data_properties),c_=class{constructor(e,t,n,r){this.Xi=e=>{var t=this.Ji();if(!t||t.sessionId!==e){var n={sessionId:e,props:this.Ki(this._instance)};this.Qi.register({[jm]:n})}},this._instance=e,this.tr=t,this.Qi=n,this.Ki=r||s_,this.tr.onSessionId(this.Xi)}Ji(){return this.Qi.props[jm]}getSetOnceProps(){var e=this.Ji()?.props;return e?`r`in e?Qh(e):{$referring_domain:e.referringDomain,$pathname:e.initialPathName,utm_source:e.utm_source,utm_campaign:e.utm_campaign,utm_medium:e.utm_medium,utm_content:e.utm_content,utm_term:e.utm_term}:{}}getSessionProps(){var e={};return $m(ih(this.getSetOnceProps()),((t,n)=>{n===`$current_url`&&(n=`url`),e[`$session_entry_`+Yd(n)]=t})),e}},l_=class{constructor(){this.er={}}on(e,t){return this.er[e]||(this.er[e]=[]),this.er[e].push(t),()=>{this.er[e]=this.er[e].filter((e=>e!==t))}}emit(e,t){for(var n of this.er[e]||[])n(t);for(var r of this.er[`*`]||[])r(e,t)}},u_=Qp(`[SessionId]`),d_=class{on(e,t){return this.ir.on(e,t)}constructor(e,t,n){var r;if(this.rr=[],this.nr=void 0,this.ir=new l_,this.sr=(e,t)=>!(!lf(e)||!lf(t))&&Math.abs(e-t)>this.sessionTimeoutMs,!e.persistence)throw Error(`SessionIdManager requires a PostHogPersistence instance`);if(e.config.cookieless_mode===Hm)throw Error(`SessionIdManager cannot be used with cookieless_mode="always"`);this.Rt=e.config,this.Qi=e.persistence,this.ar=void 0,this.lr=void 0,this._sessionStartTimestamp=null,this._sessionActivityTimestamp=null,this.ur=t||mh,this.hr=n||mh;var i=this.Rt.persistence_name||this.Rt.token;if(this._sessionTimeoutMs=1e3*yf(this.Rt.session_idle_timeout_seconds||1800,60,36e3,u_.createLogger(`session_idle_timeout_seconds`),1800),e.register({$configured_session_timeout_ms:this._sessionTimeoutMs}),this.cr(),this.dr=`ph_`+i+`_window_id`,this.vr=`ph_`+i+`_primary_window_exists`,this.pr()){var a=Th.Kt(this.dr),o=Th.Kt(this.vr);a&&!o?this.ar=a:Th.ti(this.dr),Th.Qt(this.vr,!0)}if((r=this.Rt.bootstrap)!=null&&r.sessionID)try{var s=(e=>{var t=this.Rt.bootstrap.sessionID.replace(/-/g,``);if(t.length!==32)throw Error(`Not a valid UUID`);if(t[12]!==`7`)throw Error(`Not a UUIDv7`);return parseInt(t.substring(0,12),16)})();this.gr(this.Rt.bootstrap.sessionID,new Date().getTime(),s)}catch(e){u_.error(`Invalid sessionID in bootstrap`,e)}this.mr()}get sessionTimeoutMs(){return this._sessionTimeoutMs}onSessionId(e){return Z(this.rr)&&(this.rr=[]),this.rr.push(e),this.lr&&e(this.lr,this.ar),()=>{this.rr=this.rr.filter((t=>t!==e))}}pr(){return this.Rt.persistence!==`memory`&&!this.Qi.mi&&Th.Gt()}yr(e){e!==this.ar&&(this.ar=e,this.pr()&&Th.Qt(this.dr,e))}br(){return this.ar?this.ar:this.pr()?Th.Kt(this.dr):null}gr(e,t,n){e===this.lr&&t===this._sessionActivityTimestamp&&n===this._sessionStartTimestamp||(this._sessionStartTimestamp=n,this._sessionActivityTimestamp=t,this.lr=e,this.Qi.register({[hm]:[t,e,n]}))}wr(){var e=this.Qi.props[hm];return $d(e)&&e.length===2&&e.push(e[0]),e||[0,null,0]}resetSessionId(){this.gr(null,null,null)}destroy(){clearTimeout(this._r),this._r=void 0,this.nr&&q&&(q.removeEventListener(qm,this.nr,{capture:!1}),this.nr=void 0),this.rr=[]}mr(){this.nr=()=>{this.pr()&&Th.ti(this.vr)},sh(q,qm,this.nr,{capture:!1})}checkAndGetSessionAndWindowId(e,t){if(e===void 0&&(e=!1),t===void 0&&(t=null),this.Rt.cookieless_mode===Hm)throw Error(`checkAndGetSessionAndWindowId should not be called with cookieless_mode="always"`);var n=t||new Date().getTime(),[r,i,a]=this.wr(),o=this.br(),s=lf(a)&&Math.abs(n-a)>864e5,c=!1,l=!i,u=!l&&!e&&this.sr(n,r);l||u||s?(i=this.ur(),o=this.hr(),u_.info(`new session ID generated`,{sessionId:i,windowId:o,changeReason:{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}}),a=n,c=!0):o||(o=this.hr(),c=!0);var d=lf(r)&&e&&!s?r:n,f=lf(a)?a:new Date().getTime();return this.yr(o),this.gr(i,d,f),e||this.cr(),c&&this.rr.forEach((e=>e(i,o,c?{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}:void 0))),{sessionId:i,windowId:o,sessionStartTimestamp:f,changeReason:c?{noSessionId:l,activityTimeout:u,sessionPastMaximumLength:s}:void 0,lastActivityTimestamp:r}}cr(){clearTimeout(this._r),this._r=setTimeout((()=>{var[e]=this.wr();if(this.sr(new Date().getTime(),e)){var t=this.lr;this.resetSessionId(),this.ir.emit(`forcedIdleReset`,{idleSessionId:t})}}),1.1*this.sessionTimeoutMs)}},f_=function(e,t){if(!e)return!1;var n=e.userAgent;if(n&&Kd(n,t))return!0;try{var r=e?.userAgentData;if(r!=null&&r.brands&&r.brands.some((e=>Kd(e?.brand,t))))return!0}catch{}return!!e.webdriver},p_=function(e,t){if(!function(e){try{new RegExp(e)}catch{return!1}return!0}(t))return!1;try{return new RegExp(t).test(e)}catch{return!1}};function m_(e,t,n){return Zg({distinct_id:e,userPropertiesToSet:t,userPropertiesToSetOnce:n})}var h_={exact:(e,t)=>t.some((t=>e.some((e=>t===e)))),is_not:(e,t)=>t.every((t=>e.every((e=>t!==e)))),regex:(e,t)=>t.some((t=>e.some((e=>p_(t,e))))),not_regex:(e,t)=>t.every((t=>e.every((e=>!p_(t,e))))),icontains:(e,t)=>t.map(g_).some((t=>e.map(g_).some((e=>t.includes(e))))),not_icontains:(e,t)=>t.map(g_).every((t=>e.map(g_).every((e=>!t.includes(e))))),gt:(e,t)=>t.some((t=>{var n=parseFloat(t);return!isNaN(n)&&e.some((e=>n>parseFloat(e)))})),lt:(e,t)=>t.some((t=>{var n=parseFloat(t);return!isNaN(n)&&e.some((e=>ne.toLowerCase();function __(e,t){return!e||Object.entries(e).every((e=>{var[n,r]=e,i=t?.[n];if(Z(i)||of(i))return!1;var a=[String(i)],o=h_[r.operator];return!!o&&o(r.values,a)}))}var v_=`custom`,y_=`i.posthog.com`,b_=/^\/static\//,x_=class{constructor(e){this.Ir={},this.instance=e}get apiHost(){var e=this.instance.config.api_host.trim().replace(/\/$/,``);return e===`https://app.posthog.com`?`https://us.i.posthog.com`:e}get flagsApiHost(){var e=this.instance.config.flags_api_host;return e?e.trim().replace(/\/$/,``):this.apiHost}get uiHost(){var e=this.instance.config.ui_host?.replace(/\/$/,``);return e||=this.apiHost.replace(`.`+y_,`.posthog.com`),e===`https://app.posthog.com`?`https://us.posthog.com`:e}get region(){return this.Ir[this.apiHost]||(this.Ir[this.apiHost]=/https:\/\/(app|us|us-assets)(\.i)?\.posthog\.com/i.test(this.apiHost)?`us`:/https:\/\/(eu|eu-assets)(\.i)?\.posthog\.com/i.test(this.apiHost)?`eu`:v_),this.Ir[this.apiHost]}Cr(e){var t=this.instance.config.__preview_external_dependency_versioned_paths;if(typeof t==`string`&&b_.test(e))return t.trim().replace(/\/$/,``)||void 0}endpointFor(e,t){if(t===void 0&&(t=``),t&&=t[0]===`/`?t:`/`+t,e===`ui`)return this.uiHost+t;if(e===`flags`)return this.flagsApiHost+t;if(e===`assets`){var n=this.Cr(t);if(n)return``+n+t}if(this.region===v_)return this.apiHost+t;var r=y_+t;switch(e){case`assets`:return`https://`+this.region+`-assets.`+r;case`api`:return`https://`+this.region+`.`+r}}},S_=Qp(`[Surveys]`),C_=`seenSurvey_`,w_=(e,t)=>{var n=`$survey_`+t+`/`+e.id;return e.current_iteration&&e.current_iteration>0&&(n=`$survey_`+t+`/`+e.id+`/`+e.current_iteration),n},T_=e=>((e,t)=>{var n=``+C_+t.id;return t.current_iteration&&t.current_iteration>0&&(n=``+C_+t.id+`_`+t.current_iteration),n})(0,e),E_=[ig.Popover,ig.Widget,ig.API],D_={ignoreConditions:!1,ignoreDelay:!1,displayType:sg.Popover},O_=Qp(`[PostHog ExternalIntegrations]`),k_={intercom:`intercom-integration`,crispChat:`crisp-chat-integration`},A_=class{constructor(e){this._instance=e}ai(e,t){var n;(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,e,(e=>{if(e)return O_.error(`failed to load script`,e);t()}))}startIfEnabledOrStop(){var e=this,t=function(t){var n,i,a;!r||(n=Y.__PosthogExtensions__)!=null&&(n=n.integrations)!=null&&n[t]||e.ai(k_[t],(()=>{var n;(n=Y.__PosthogExtensions__)==null||(n=n.integrations)==null||(n=n[t])==null||n.start(e._instance)})),!r&&(i=Y.__PosthogExtensions__)!=null&&(i=i.integrations)!=null&&i[t]&&((a=Y.__PosthogExtensions__)==null||(a=a.integrations)==null||(a=a[t])==null||a.stop())};for(var[n,r]of Object.entries((i=this._instance.config.integrations)??{})){var i;t(n)}}},j_,M_={},N_=0,P_=()=>{},F_=`Consent opt in/out is not valid with cookieless_mode="always" and will be ignored`,I_=`Surveys module not available`,L_=`sanitize_properties is deprecated. Use before_send instead`,R_=`Invalid value for property_denylist config: `,z_=`posthog`,B_=!qg&&Ld?.indexOf(`MSIE`)===-1&&Ld?.indexOf(`Mozilla`)===-1,V_=e=>{var t;return X({api_host:`https://us.i.posthog.com`,flags_api_host:null,ui_host:null,token:``,autocapture:!0,cross_subdomain_cookie:oh(J?.location),persistence:`localStorage+cookie`,persistence_name:``,cookie_persisted_properties:[],loaded:P_,save_campaign_params:!0,custom_campaign_params:[],custom_blocked_useragents:[],save_referrer:!0,capture_pageleave:`if_capture_pageview`,defaults:e??`unset`,__preview_deferred_init_extensions:!1,__preview_external_dependency_versioned_paths:!1,debug:Md&&rf(Md?.search)&&Md.search.indexOf(`__posthog_debug=true`)!==-1||!1,cookie_expiration:365,upgrade:!1,disable_session_recording:!1,disable_persistence:!1,disable_web_experiments:!0,disable_surveys:!1,disable_surveys_automatic_display:!1,disable_conversations:!1,disable_product_tours:!1,disable_external_dependency_loading:!1,enable_recording_console_log:void 0,secure_cookie:(q==null||(t=q.location)==null?void 0:t.protocol)===`https:`,ip:!1,opt_out_capturing_by_default:!1,opt_out_persistence_by_default:!1,opt_out_useragent_filter:!1,opt_out_capturing_persistence_type:`localStorage`,consent_persistence_name:null,opt_out_capturing_cookie_prefix:null,opt_in_site_apps:!1,property_denylist:[],respect_dnt:!1,sanitize_properties:null,request_headers:{},request_batching:!0,properties_string_max_length:65535,mask_all_element_attributes:!1,mask_all_text:!1,mask_personal_data_properties:!1,custom_personal_data_properties:[],advanced_disable_flags:!1,advanced_disable_decide:!1,advanced_disable_feature_flags:!1,advanced_disable_feature_flags_on_first_load:!1,advanced_only_evaluate_survey_feature_flags:!1,advanced_feature_flags_dedup_per_session:!1,advanced_enable_surveys:!1,advanced_disable_toolbar_metrics:!1,feature_flag_request_timeout_ms:3e3,surveys_request_timeout_ms:1e4,on_request_error(e){Q.error(`Bad HTTP status: `+e.statusCode+` `+e.text)},get_device_id:e=>e,capture_performance:void 0,name:`posthog`,bootstrap:{},disable_compression:!1,session_idle_timeout_seconds:1800,person_profiles:Gm,before_send:void 0,request_queue_config:{flush_interval_ms:n_},error_tracking:{},_onCapture:P_,__preview_eager_load_replay:!1},(e=>({rageclick:!e||`2025-11-30`>e||{content_ignorelist:!0},capture_pageview:!e||`2025-05-24`>e||`history_change`,session_recording:e&&e>=`2025-11-30`?{strictMinimumDuration:!0}:{},external_scripts_inject_target:e&&e>=`2026-01-30`?`head`:`body`,internal_or_test_user_hostname:e&&e>=`2026-01-30`?/^(localhost|127\.0\.0\.1)$/:void 0}))(e))},H_=[[`process_person`,`person_profiles`],[`xhr_headers`,`request_headers`],[`cookie_name`,`persistence_name`],[`disable_cookie`,`disable_persistence`],[`store_google`,`save_campaign_params`],[`verbose`,`debug`]],U_=e=>{var t={};for(var[n,r]of H_)Z(e[n])||(t[r]=e[n]);var i=eh({},t,e);return $d(e.property_blacklist)&&(Z(e.property_denylist)?i.property_denylist=e.property_blacklist:$d(e.property_denylist)?i.property_denylist=[...e.property_blacklist,...e.property_denylist]:Q.error(R_+e.property_denylist)),i},W_=class{constructor(){this.__forceAllowLocalhost=!1}get Sr(){return this.__forceAllowLocalhost}set Sr(e){Q.error("WebPerformanceObserver is deprecated and has no impact on network capture. Use `_forceAllowLocalhostNetworkCapture` on `posthog.sessionRecording`"),this.__forceAllowLocalhost=e}},G_=class e{kr(e,t){if(e){var n=this.Tr.indexOf(e);n!==-1&&this.Tr.splice(n,1)}return this.Tr.push(t),t.initialize==null||t.initialize(),t}Ar(){return this.config.cookieless_mode===Hm||this.config.cookieless_mode===Vm&&this.consent.isRejected()}get decideEndpointWasHit(){var e;return(e=this.featureFlags?.hasLoadedFlags)!=null&&e}get flagsEndpointWasHit(){var e;return(e=this.featureFlags?.hasLoadedFlags)!=null&&e}constructor(){this.webPerformance=new W_,this.Er=!1,this.version=zd.LIB_VERSION,this.Rr=new l_,this.Tr=[],this._calculate_event_properties=this.calculateEventProperties.bind(this),this.config=V_(),this.SentryIntegration=Ph,this.sentryIntegration=e=>function(e,t){var n=Nh(e,t);return{name:Mh,processEvent:e=>n(e)}}(this,e),this.__request_queue=[],this.__loaded=!1,this.analyticsDefaultEndpoint=`/e/`,this.Nr=!1,this.Mr=null,this.Fr=null,this.Or=null,this.scrollManager=new o_(this),this.pageViewManager=new Fh(this),this.rateLimiter=new dg(this),this.requestRouter=new x_(this),this.consent=new Eh(this),this.externalIntegrations=new A_(this);var t=e.__defaultExtensionClasses??{};this.featureFlags=t.featureFlags&&new t.featureFlags(this),this.toolbar=t.toolbar&&new t.toolbar(this),this.surveys=t.surveys&&new t.surveys(this),this.conversations=t.conversations&&new t.conversations(this),this.logs=t.logs&&new t.logs(this),this.experiments=t.experiments&&new t.experiments(this),this.exceptions=t.exceptions&&new t.exceptions(this),this.people={set:(e,t,n)=>{var r=rf(e)?{[e]:t}:e;this.setPersonProperties(r),n?.({})},set_once:(e,t,n)=>{var r=rf(e)?{[e]:t}:e;this.setPersonProperties(void 0,r),n?.({})}},this.on(`eventCaptured`,(e=>Q.info(`send "`+e?.event+`"`,e)))}init(t,n,r){if(r&&r!==z_){var i=M_[r]??new e;return i._init(t,n,r),M_[r]=i,M_[z_][r]=i,i}return this._init(t,n,r)}_init(e,t,n){var r;if(t===void 0&&(t={}),Z(e)||af(e))return Q.critical(`PostHog was initialized without a token. This likely indicates a misconfiguration. Please check the first argument passed to posthog.init()`),this;if(this.__loaded)return console.warn(`[PostHog.js]`,`You have already initialized PostHog! Re-initializing is a no-op`),this;this.__loaded=!0,this.config={},t.debug=this.Pr(t.debug),this.Lr=t,this.Dr=[],t.person_profiles?this.Fr=t.person_profiles:t.process_person&&(this.Fr=t.process_person),this.set_config(eh({},V_(t.defaults),U_(t),{name:n,token:e})),this.config.on_xhr_error&&Q.error(`on_xhr_error is deprecated. Use on_request_error instead`),this.compression=t.disable_compression?void 0:mg.GZipJS;var i=this.Br();this.persistence=new ng(this.config,i),this.sessionPersistence=this.config.persistence===`sessionStorage`||this.config.persistence===`memory`?this.persistence:new ng(X({},this.config,{persistence:`sessionStorage`}),i);var a=X({},this.persistence.props),o=X({},this.sessionPersistence.props);this.register({$initialization_time:new Date().toISOString()}),this.jr=new r_((e=>this.qr(e)),this.config.request_queue_config),this.Zr=new a_(this),this.__request_queue=[];var s=this.Ar();if(s||(this.sessionManager=new d_(this),this.sessionPropsManager=new c_(this,this.sessionManager,this.persistence)),this.config.__preview_deferred_init_extensions?(Q.info(`Deferring extension initialization to improve startup performance`),setTimeout((()=>{this.$r(s)}),0)):(Q.info(`Initializing extensions synchronously`),this.$r(s)),zd.DEBUG=zd.DEBUG||this.config.debug,zd.DEBUG&&Q.info(`Starting in debug mode`,{this:this,config:t,thisC:X({},this.config),p:a,s:o}),!this.config.identity_distinct_id||(r=t.bootstrap)!=null&&r.distinctID||(t.bootstrap=X({},t.bootstrap,{distinctID:this.config.identity_distinct_id,isIdentifiedID:!0})),t.bootstrap?.distinctID!==void 0){var c=t.bootstrap.distinctID,l=this.get_distinct_id(),u=this.persistence.get_property(Am);if(t.bootstrap.isIdentifiedID&&l!=null&&l!==c&&u===Um)this.identify(c);else if(t.bootstrap.isIdentifiedID&&l!=null&&l!==c&&u===Wm)Q.warn(`Bootstrap distinctID differs from an already-identified user. The existing identity is preserved. Call reset() before reinitializing if you intend to switch users.`);else{var d=this.config.get_device_id(mh()),f=t.bootstrap.isIdentifiedID?d:c;this.persistence.set_property(Am,t.bootstrap.isIdentifiedID?Wm:Um),this.register({distinct_id:c,$device_id:f})}}if(s)this.register_once({distinct_id:Rm,$device_id:null},``);else if(!this.get_distinct_id()){var p=this.config.get_device_id(mh());this.register_once({distinct_id:p,$device_id:p},``),this.persistence.set_property(Am,Um)}return sh(q,`onpagehide`in self?`pagehide`:`unload`,this._handle_unload.bind(this),{passive:!1}),t.segment?function(e,t){var n=e.config.segment;if(!n)return t();(function(e,t){var n=e.config.segment;if(!n)return t();var r=n=>{var r=()=>n.anonymousId()||mh();e.config.get_device_id=r,n.id()&&(e.register({distinct_id:n.id(),$device_id:r()}),e.persistence.set_property(Am,Wm)),t()},i=n.user();`then`in i&&ef(i.then)?i.then(r):r(i)})(e,(()=>{n.register((e=>{Promise&&Promise.resolve||jh.warn(`This browser does not have Promise support, and can not use the segment integration`);var t=(t,n)=>{if(!n)return t;t.event.userId||t.event.anonymousId===e.get_distinct_id()||(jh.info(`No userId set, resetting PostHog`),e.reset()),t.event.userId&&t.event.userId!==e.get_distinct_id()&&(jh.info(`UserId set, identifying with PostHog`),e.identify(t.event.userId));var r=e.calculateEventProperties(n,t.event.properties);return t.event.properties=Object.assign({},r,t.event.properties),t};return{name:`PostHog JS`,type:`enrichment`,version:`1.0.0`,isLoaded:()=>!0,load:()=>Promise.resolve(),track:e=>t(e,e.event.event),page:e=>t(e,Jm),identify:e=>t(e,Xm),screen:e=>t(e,`$screen`)}})(e)).then((()=>{t()}))}))}(this,(()=>this.Hr())):this.Hr(),ef(this.config._onCapture)&&this.config._onCapture!==P_&&(Q.warn("onCapture is deprecated. Please use `before_send` instead"),this.on(`eventCaptured`,(e=>this.config._onCapture(e.event,e)))),this.config.ip&&Q.warn('The `ip` config option has NO EFFECT AT ALL and has been deprecated. Use a custom transformation or "Discard IP data" project setting instead. See https://posthog.com/tutorials/web-redact-properties#hiding-customer-ip-address for more information.'),this}$r(t){var n=performance.now(),r=X({},e.__defaultExtensionClasses,this.config.__extensionClasses),i=[];r.featureFlags&&this.Tr.push(this.featureFlags=this.featureFlags??new r.featureFlags(this)),r.exceptions&&this.Tr.push(this.exceptions=this.exceptions??new r.exceptions(this)),r.historyAutocapture&&this.Tr.push(this.historyAutocapture=new r.historyAutocapture(this)),r.tracingHeaders&&this.Tr.push(new r.tracingHeaders(this)),r.siteApps&&this.Tr.push(this.siteApps=new r.siteApps(this)),r.sessionRecording&&!t&&this.Tr.push(this.sessionRecording=new r.sessionRecording(this)),this.config.disable_scroll_properties||i.push((()=>{this.scrollManager.startMeasuringScrollPosition()})),r.autocapture&&this.Tr.push(this.autocapture=new r.autocapture(this)),r.surveys&&this.Tr.push(this.surveys=this.surveys??new r.surveys(this)),r.logs&&this.Tr.push(this.logs=this.logs??new r.logs(this)),r.conversations&&this.Tr.push(this.conversations=this.conversations??new r.conversations(this)),r.productTours&&this.Tr.push(this.productTours=new r.productTours(this)),r.heatmaps&&this.Tr.push(this.heatmaps=new r.heatmaps(this)),r.webVitalsAutocapture&&this.Tr.push(this.webVitalsAutocapture=new r.webVitalsAutocapture(this)),r.exceptionObserver&&this.Tr.push(this.exceptionObserver=new r.exceptionObserver(this)),r.deadClicksAutocapture&&this.Tr.push(this.deadClicksAutocapture=new r.deadClicksAutocapture(this,kh)),r.toolbar&&this.Tr.push(this.toolbar=this.toolbar??new r.toolbar(this)),r.experiments&&this.Tr.push(this.experiments=this.experiments??new r.experiments(this)),this.Tr.forEach((e=>{e.initialize&&i.push((()=>{e.initialize==null||e.initialize()}))})),i.push((()=>{if(this.Vr){var e=this.Vr;this.Vr=void 0,this.ki(e)}})),this.zr(i,n)}zr(e,t){for(;e.length>0;){if(this.config.__preview_deferred_init_extensions&&performance.now()-t>=30&&e.length>0)return void setTimeout((()=>{this.zr(e,t)}),0);var n=e.shift();if(n)try{n()}catch(e){Q.error(`Error initializing extension:`,e)}}var r=Math.round(performance.now()-t);this.register_for_session({$sdk_debug_extensions_init_method:this.config.__preview_deferred_init_extensions?`deferred`:`synchronous`,$sdk_debug_extensions_init_time_ms:r}),this.config.__preview_deferred_init_extensions&&Q.info(`PostHog extensions initialized (`+r+`ms)`)}ki(e){var t;if(!J||!J.body)return Q.info(`document not ready yet, trying again in 500 milliseconds...`),void setTimeout((()=>{this.ki(e)}),500);this.config.__preview_deferred_init_extensions&&(this.Vr=e),this.Ur=e,this.compression=void 0,e.supportedCompression&&!this.config.disable_compression&&(this.compression=qd(e.supportedCompression,mg.GZipJS)?mg.GZipJS:qd(e.supportedCompression,mg.Base64)?mg.Base64:void 0),(t=e.analytics)!=null&&t.endpoint&&(this.analyticsDefaultEndpoint=e.analytics.endpoint),this.set_config({person_profiles:this.Fr?this.Fr:Gm}),this.Tr.forEach((t=>t.onRemoteConfig==null?void 0:t.onRemoteConfig(e)))}Hr(){try{this.config.loaded(this)}catch(e){Q.critical("`loaded` function failed",e)}if(this.Yr(),this.config.internal_or_test_user_hostname&&Md!=null&&Md.hostname){var e=Md.hostname,t=this.config.internal_or_test_user_hostname;(typeof t==`string`?e===t:t.test(e))&&this.setInternalOrTestUser()}this.config.capture_pageview&&setTimeout((()=>{(this.consent.isOptedIn()||this.Ar())&&this.Wr()}),1),this.Gr=new pg(this),this.Gr.load()}Yr(){var e;this.is_capturing()&&this.config.request_batching&&((e=this.jr)==null||e.enable())}_dom_loaded(){this.is_capturing()&&Qm(this.__request_queue,(e=>this.qr(e))),this.__request_queue=[],this.Yr()}_handle_unload(){var e,t,n,r;(e=this.surveys)==null||e.handlePageUnload(),this.config.request_batching?(this.Xr()&&this.capture(Ym),(t=this.logs)==null||t.flushLogs(`sendBeacon`),(n=this.jr)==null||n.unload(),(r=this.Zr)==null||r.unload()):this.Xr()&&this.capture(Ym,null,{transport:`sendBeacon`})}_send_request(e){this.__loaded&&(B_?this.__request_queue.push(e):this.rateLimiter.isServerRateLimited(e.batchKey)||(e.transport=e.transport||this.config.api_transport,e.url=Xg(e.url,{ip:+!!this.config.ip}),e.headers=X({},this.config.request_headers,e.headers),e.compression=e.compression===`best-available`?this.compression:e.compression,e.disableXHRCredentials=this.config.__preview_disable_xhr_credentials,this.config.__preview_disable_beacon&&(e.disableTransport=[`sendBeacon`]),e.fetchOptions=e.fetchOptions||this.config.fetch_options,(e=>{var t=X({},e);t.timeout=t.timeout||6e4,t.url=e_(t.url,t.compression);var n=t.transport??`fetch`,r=t_.filter((e=>!t.disableTransport||!e.transport||!t.disableTransport.includes(e.transport))),i=function(e,t){for(var r=0;e.length>r;r++)if(e[r].transport===n)return e[r]}(r)?.method??r[0].method;if(!i)throw Error(`No available transport method`);n!==`sendBeacon`&&t.data&&t.compression===mg.GZipJS&&Id&&!Yg?$g(t).then((e=>{i(e)})).catch((n=>{if((e=>!(!e||typeof e!=`object`)&&(`name`in e?String(e.name):``)===`NotReadableError`)(n))return Yg=!0,void i(X({},t,{compression:void 0,url:e_(e.url,void 0)}));i(t)})):i(t)})(X({},e,{callback:t=>{var n,r;this.rateLimiter.checkForLimiting(t),400>t.statusCode||(n=(r=this.config).on_request_error)==null||n.call(r,t),e.callback==null||e.callback(t)}}))))}qr(e){this.Zr?this.Zr.retriableRequest(e):this._send_request(e)}_execute_array(e){N_++;try{var t,n=[],r=[],i=[];Qm(e,(e=>{e&&($d(t=e[0])?i.push(e):ef(e)?e.call(this):$d(e)&&t===`alias`?n.push(e):$d(e)&&t.indexOf(`capture`)!==-1&&ef(this[t])?i.push(e):r.push(e))}));var a=function(e,t){Qm(e,(function(e){if($d(e[0])){var n=t;$m(e,(function(e){n=n[e[0]].apply(n,e.slice(1))}))}else t[e[0]].apply(t,e.slice(1))}))};a(n,this),a(r,this),a(i,this)}finally{N_--}}push(t){if(N_>0&&$d(t)&&rf(t[0])){var n=e.prototype[t[0]];ef(n)&&n.apply(this,t.slice(1))}else this._execute_array([t])}capture(e,t,n){var r,i,a,o;if(this.__loaded&&this.persistence&&this.sessionPersistence&&this.jr){if(this.is_capturing())if(!Z(e)&&rf(e)){var s=!this.config.opt_out_useragent_filter&&this._is_bot();if(!s||this.config.__preview_capture_bot_pageviews){var c=n!=null&&n.skip_client_rate_limiting?void 0:this.rateLimiter.clientRateLimitContext();if(c==null||!c.isRateLimited){t!=null&&t.$current_url&&!rf(t?.$current_url)&&(Q.error("Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value."),t==null||delete t.$current_url),e!==`$exception`||n!=null&&n.Jr||Q.warn("Using `posthog.capture('$exception')` is unreliable because it does not attach required metadata. Use `posthog.captureException(error)` instead, which attaches required metadata automatically."),this.sessionPersistence.update_search_keyword(),this.config.save_campaign_params&&this.sessionPersistence.update_campaign_params(),this.config.save_referrer&&this.sessionPersistence.update_referrer_info(),(this.config.save_campaign_params||this.config.save_referrer)&&this.persistence.set_initial_person_info();var l=new Date,u=n?.timestamp||l,d=mh(),f={uuid:d,event:e,properties:this.calculateEventProperties(e,t||{},u,d)};e===Jm&&this.config.__preview_capture_bot_pageviews&&s&&(f.event=`$bot_pageview`,f.properties.$browser_type=`bot`),c&&(f.properties.$lib_rate_limit_remaining_tokens=c.remainingTokens),n!=null&&n.$set&&(f.$set=n?.$set);var p,m=this.Kr(n?.$set_once,e!==Zm,e===Xm);if(m&&(f.$set_once=m),n!=null&&n._noTruncate||(r=this.config.properties_string_max_length,i=f,a=e=>rf(e)?e.slice(0,r):e,o=new Set,f=function e(t,n){return t===Object(t)?o.has(t)?void 0:(o.add(t),$d(t)?(r=[],Qm(t,(t=>{r.push(e(t))}))):(r={},$m(t,((t,n)=>{o.has(t)||(r[n]=e(t,n))}))),r):a?a(t):t;var r}(i)),f.timestamp=u,Z(n?.timestamp)||(f.properties.$event_time_override_provided=!0,f.properties.$event_time_override_system_time=l),e===ag.DISMISSED||e===ag.SENT){var h=t?.[og.SURVEY_ID],g=t?.[og.SURVEY_ITERATION];p={id:h,current_iteration:g},localStorage.getItem(T_(p))||localStorage.setItem(T_(p),`true`),f.$set=X({},f.$set,{[w_({id:h,current_iteration:g},e===ag.SENT?`responded`:`dismissed`)]:!0})}else e===ag.SHOWN&&(f.$set=X({},f.$set,{[og.SURVEY_LAST_SEEN_DATE]:new Date().toISOString()}));if(e===cg.SHOWN){var _=t?.[lg.TOUR_TYPE];_&&(f.$set=X({},f.$set,{[lg.TOUR_LAST_SEEN_DATE+`/`+_]:new Date().toISOString()}))}var v=X({},f.properties.$set,f.$set);if(nf(v)||this.setPersonPropertiesForFlags(v),!sf(this.config.before_send)){var y=this.Qr(f);if(!y)return;f=y}this.Rr.emit(`eventCaptured`,f);var b={method:`POST`,url:n?._url??this.requestRouter.endpointFor(`api`,this.analyticsDefaultEndpoint),data:f,compression:`best-available`,batchKey:n?._batchKey};return!this.config.request_batching||n&&(n==null||!n._batchKey)||n!=null&&n.send_instantly?this.qr(b):this.jr.enqueue(b),f}Q.critical(`This capture call is ignored due to client rate limiting.`)}}else Q.error(`No event name provided to posthog.capture`)}else Q.uninitializedWarning(`posthog.capture`)}_addCaptureHook(e){return this.on(`eventCaptured`,(t=>e(t.event,t)))}calculateEventProperties(e,t,n,r,i){if(n||=new Date,!this.persistence||!this.sessionPersistence)return t;var a=i?void 0:this.persistence.remove_event_timer(e),o=X({},t);if(o.token=this.config.token,o.$config_defaults=this.config.defaults,this.Ar()&&(o.$cookieless_mode=!0),e===`$snapshot`){var s=X({},this.persistence.properties(),this.sessionPersistence.properties());return o.distinct_id=s.distinct_id,(!rf(o.distinct_id)&&!cf(o.distinct_id)||af(o.distinct_id))&&Q.error(`Invalid distinct_id for replay event. This indicates a bug in your implementation`),o}var c,l=function(e,t){var n,r,i;if(!Ld)return{};var a,o,s,c,l=e?[...Vh,...t||[]]:[],[u,d]=function(e){for(var t=0;_p.length>t;t++){var[n,r]=_p[t],i=n.exec(e),a=i&&(ef(r)?r(i,e):r);if(a)return a}return[``,``]}(Ld);return eh(ih({$os:u,$os_version:d,$browser:mp(Ld,navigator.vendor),$device:vp(Ld),$device_type:(o=Ld,s={userAgentDataPlatform:(n=navigator)==null||(n=n.userAgentData)==null?void 0:n.platform,maxTouchPoints:navigator?.maxTouchPoints,screenWidth:q==null||(r=q.screen)==null?void 0:r.width,screenHeight:q==null||(i=q.screen)==null?void 0:i.height,devicePixelRatio:q?.devicePixelRatio},c=vp(o),c===kf||c===Of||c===`Kobo`||c===`Kindle Fire`||c===ap?Df:c===Jf||c===Xf||c===Yf||c===np?`Console`:c===jf?`Wearable`:c?wf:s?.userAgentDataPlatform===`Android`&&(s?.maxTouchPoints??0)>0?600>Math.min(s?.screenWidth??0,s?.screenHeight??0)/(s?.devicePixelRatio??1)?wf:Df:`Desktop`),$timezone:$h(),$timezone_offset:eg()}),{$current_url:Rh(Md?.href,l,Uh),$host:Md?.host,$pathname:Md?.pathname,$raw_user_agent:Ld.length>1e3?Ld.substring(0,997)+`...`:Ld,$browser_version:gp(Ld,navigator.vendor),$browser_language:Jh(),$browser_language_prefix:(a=Jh(),typeof a==`string`?a.split(`-`)[0]:void 0),$screen_height:q?.screen.height,$screen_width:q?.screen.width,$viewport_height:q?.innerHeight,$viewport_width:q?.innerWidth,$lib:zd.LIB_NAME,$lib_version:zd.LIB_VERSION,$insert_id:Math.random().toString(36).substring(2,10)+Math.random().toString(36).substring(2,10),$time:Date.now()/1e3})}(this.config.mask_personal_data_properties,this.config.custom_personal_data_properties);if(this.sessionManager){var{sessionId:u,windowId:d}=this.sessionManager.checkAndGetSessionAndWindowId(i,n.getTime());o.$session_id=u,o.$window_id=d}this.sessionPropsManager&&eh(o,this.sessionPropsManager.getSessionProps());try{this.sessionRecording&&eh(o,this.sessionRecording.sdkDebugProperties),o.$sdk_debug_retry_queue_size=this.Zr?.length}catch(e){o.$sdk_debug_error_capturing_properties=String(e)}if(this.requestRouter.region===v_&&(o.$lib_custom_api_host=this.config.api_host),c=e!==Jm||i?e!==Ym||i?this.pageViewManager.doEvent():this.pageViewManager.doPageLeave(n):this.pageViewManager.doPageView(n,r),o=eh(o,c),e===Jm&&J&&(o.title=J.title),!Z(a)){var f=n.getTime()-a;o.$duration=parseFloat((f/1e3).toFixed(3))}Ld&&this.config.opt_out_useragent_filter&&(o.$browser_type=this._is_bot()?`bot`:`browser`),(o=eh({},l,this.persistence.properties(),this.sessionPersistence.properties(),o)).$is_identified=this._isIdentified(),$d(this.config.property_denylist)?$m(this.config.property_denylist,(function(e){delete o[e]})):Q.error(R_+this.config.property_denylist+` or property_blacklist config: `+this.config.property_blacklist);var p=this.config.sanitize_properties;p&&(Q.error(L_),o=p(o,e));var m=this.tn();return o.$process_person_profile=m,m&&!i&&this.en(`_calculate_event_properties`),o}Kr(e,t,n){if(t===void 0&&(t=!0),n===void 0&&(n=!1),!this.persistence||!this.tn()||this.Er&&!n)return e;var r=eh({},this.persistence.get_initial_props(),this.sessionPropsManager?.getSetOnceProps()||{},e||{}),i=this.config.sanitize_properties;return i&&(Q.error(L_),r=i(r,`$set_once`)),t&&(this.Er=!0),nf(r)?void 0:r}register(e,t){var n;(n=this.persistence)==null||n.register(e,t)}register_once(e,t,n){var r;(r=this.persistence)==null||r.register_once(e,t,n)}register_for_session(e){var t;(t=this.sessionPersistence)==null||t.register(e)}unregister(e){var t;(t=this.persistence)==null||t.unregister(e)}unregister_for_session(e){var t;(t=this.sessionPersistence)==null||t.unregister(e)}rn(e,t){this.register({[e]:t})}getFeatureFlag(e,t){return this.featureFlags?.getFeatureFlag(e,t)}getFeatureFlagPayload(e){return this.featureFlags?.getFeatureFlagPayload(e)}getFeatureFlagResult(e,t){return this.featureFlags?.getFeatureFlagResult(e,t)}isFeatureEnabled(e,t){return this.featureFlags?.isFeatureEnabled(e,t)}reloadFeatureFlags(){var e;(e=this.featureFlags)==null||e.reloadFeatureFlags()}updateFlags(e,t,n){var r;(r=this.featureFlags)==null||r.updateFlags(e,t,n)}updateEarlyAccessFeatureEnrollment(e,t,n){var r;(r=this.featureFlags)==null||r.updateEarlyAccessFeatureEnrollment(e,t,n)}getEarlyAccessFeatures(e,t,n){return t===void 0&&(t=!1),this.featureFlags?.getEarlyAccessFeatures(e,t,n)}on(e,t){return this.Rr.on(e,t)}onFeatureFlags(e){return this.featureFlags?this.featureFlags.onFeatureFlags(e):(e([],{},{errorsLoading:!0}),()=>{})}onSurveysLoaded(e){return this.surveys?this.surveys.onSurveysLoaded(e):(e([],{isLoaded:!1,error:I_}),()=>{})}onSessionId(e){return this.sessionManager?.onSessionId(e)??(()=>{})}getSurveys(e,t){t===void 0&&(t=!1),this.surveys?this.surveys.getSurveys(e,t):e([],{isLoaded:!1,error:I_})}getActiveMatchingSurveys(e,t){t===void 0&&(t=!1),this.surveys?this.surveys.getActiveMatchingSurveys(e,t):e([],{isLoaded:!1,error:I_})}renderSurvey(e,t){var n;(n=this.surveys)==null||n.renderSurvey(e,t)}displaySurvey(e,t){var n;t===void 0&&(t=D_),(n=this.surveys)==null||n.displaySurvey(e,t)}cancelPendingSurvey(e){var t;(t=this.surveys)==null||t.cancelPendingSurvey(e)}canRenderSurvey(e){return this.surveys?.canRenderSurvey(e)??{visible:!1,disabledReason:I_}}canRenderSurveyAsync(e,t){return t===void 0&&(t=!1),this.surveys?.canRenderSurveyAsync(e,t)??Promise.resolve({visible:!1,disabledReason:I_})}nn(e){return!e||af(e)?(Q.critical(`Unique user id has not been set in posthog.identify`),!1):e===Rm?(Q.critical(`The string "`+e+`" was set in posthog.identify which indicates an error. This ID is only used as a sentinel value.`),!1):![`distinct_id`,`distinctid`].includes(e.toLowerCase())&&![`undefined`,`null`].includes(e.toLowerCase())||(Q.critical(`The string "`+e+`" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.`),!1)}identify(e,t,n){if(!this.__loaded||!this.persistence)return Q.uninitializedWarning(`posthog.identify`);if(cf(e)&&(e=e.toString(),Q.warn(`The first argument to posthog.identify was a number, but it should be a string. It has been converted to a string.`)),this.nn(e)&&this.en(`posthog.identify`)){var r=this.get_distinct_id();this.register({$user_id:e}),this.get_property(nm)||this.register_once({$had_persisted_distinct_id:!0,$device_id:r},``),e!==r&&e!==this.get_property(rm)&&(this.unregister(rm),this.register({distinct_id:e}));var i,a=(this.persistence.get_property(Am)||Um)===Um;e!==r&&a?(this.persistence.set_property(Am,Wm),this.setPersonPropertiesForFlags({$set:t||{},$set_once:n||{}},!1),this.capture(Xm,{distinct_id:e,$anon_distinct_id:r},{$set:t||{},$set_once:n||{}}),this.Or=m_(e,t,n),(i=this.featureFlags)==null||i.setAnonymousDistinctId(r)):(t||n)&&this.setPersonProperties(t,n),e!==r&&(this.reloadFeatureFlags(),this.unregister(Em))}}setPersonProperties(e,t){if((e||t)&&this.en(`posthog.setPersonProperties`)){var n=m_(this.get_distinct_id(),e,t);this.Or===n?Q.info(`A duplicate setPersonProperties call was made with the same properties. It has been ignored.`):(this.setPersonPropertiesForFlags({$set:e||{},$set_once:t||{}},!0),this.capture(`$set`,{$set:e||{},$set_once:t||{}}),this.Or=n)}}group(e,t,n){if(e&&t){var r=this.getGroups(),i=r[e]!==t;if(i&&this.resetGroupPropertiesForFlags(e),this.register({$groups:X({},r,{[e]:t})}),i||n){var a={$group_type:e,$group_key:t};n&&(a.$group_set=n),this.capture(Zm,a)}n&&this.setGroupPropertiesForFlags({[e]:n}),i&&!n&&this.reloadFeatureFlags()}else Q.error(`posthog.group requires a group type and group key`)}resetGroups(){this.register({$groups:{}}),this.resetGroupPropertiesForFlags(),this.reloadFeatureFlags()}setPersonPropertiesForFlags(e,t){var n;t===void 0&&(t=!0),(n=this.featureFlags)==null||n.setPersonPropertiesForFlags(e,t)}resetPersonPropertiesForFlags(){var e;(e=this.featureFlags)==null||e.resetPersonPropertiesForFlags()}setGroupPropertiesForFlags(e,t){var n;t===void 0&&(t=!0),this.en(`posthog.setGroupPropertiesForFlags`)&&((n=this.featureFlags)==null||n.setGroupPropertiesForFlags(e,t))}resetGroupPropertiesForFlags(e){var t;(t=this.featureFlags)==null||t.resetGroupPropertiesForFlags(e)}reset(e){var t,n,r,i,a,o,s,c;if(Q.info(`reset`),!this.__loaded)return Q.uninitializedWarning(`posthog.reset`);var l=this.get_property(nm);if(this.consent.reset(),(t=this.persistence)==null||t.clear(),(n=this.sessionPersistence)==null||n.clear(),(r=this.surveys)==null||r.reset(),(i=this.Gr)==null||i.stop(),(a=this.featureFlags)==null||a.reset(),(o=this.conversations)==null||o.reset(),(s=this.persistence)==null||s.set_property(Am,Um),(c=this.sessionManager)==null||c.resetSessionId(),this.Or=null,this.config.cookieless_mode===Hm)this.register_once({distinct_id:Rm,$device_id:null},``);else{var u=this.config.get_device_id(mh());this.register_once({distinct_id:u,$device_id:e?u:l},``)}this.register({$last_posthog_reset:new Date().toISOString()},1),delete this.config.identity_distinct_id,delete this.config.identity_hash,this.reloadFeatureFlags()}setIdentity(e,t){var n;this.config.identity_distinct_id=e,this.config.identity_hash=t,this.alias(e),(n=this.conversations)==null||n.sn()}clearIdentity(){var e;delete this.config.identity_distinct_id,delete this.config.identity_hash,(e=this.conversations)==null||e.an()}get_distinct_id(){return this.get_property(`distinct_id`)}getGroups(){return this.get_property(`$groups`)||{}}get_session_id(){return this.sessionManager?.checkAndGetSessionAndWindowId(!0).sessionId??``}get_session_replay_url(e){if(!this.sessionManager)return``;var{sessionId:t,sessionStartTimestamp:n}=this.sessionManager.checkAndGetSessionAndWindowId(!0),r=this.requestRouter.endpointFor(`ui`,`/project/`+this.config.token+`/replay/`+t);if(e!=null&&e.withTimestamp&&n){var i=e.timestampLookBack??10;if(!n)return r;r+=`?t=`+Math.max(Math.floor((new Date().getTime()-n)/1e3)-i,0)}return r}alias(e,t){return e===this.get_property(tm)?(Q.critical(`Attempting to create alias for existing People user - aborting.`),-2):this.en(`posthog.alias`)?(Z(t)&&(t=this.get_distinct_id()),e===t?(Q.warn(`alias matches current distinct_id - skipping api call.`),this.identify(e),-1):(this.rn(rm,e),this.capture(`$create_alias`,{alias:e,distinct_id:t}))):void 0}set_config(e){var t=X({},this.config);if(tf(e)){var n,r,i,a,o,s,c,l,u,d;eh(this.config,U_(e));var f=this.Br();(n=this.persistence)==null||n.update_config(this.config,t,f),this.sessionPersistence=this.config.persistence===`sessionStorage`||this.config.persistence===`memory`?this.persistence:new ng(X({},this.config,{persistence:`sessionStorage`}),f);var p=this.Pr(this.config.debug);uf(p)&&(this.config.debug=p),uf(this.config.debug)&&(this.config.debug?(zd.DEBUG=!0,bh.Gt()&&bh.Qt(`ph_debug`,!0),Q.info(`set_config`,{config:e,oldConfig:t,newConfig:X({},this.config)})):(zd.DEBUG=!1,bh.Gt()&&bh.ti(`ph_debug`))),(r=this.exceptionObserver)==null||r.onConfigChange(),(i=this.exceptions)==null||i.onConfigChange(),(a=this.sessionRecording)==null||a.startIfEnabledOrStop(),(o=this.autocapture)==null||o.startIfEnabled(),(s=this.heatmaps)==null||s.startIfEnabled(),(c=this.exceptionObserver)==null||c.startIfEnabledOrStop(),(l=this.deadClicksAutocapture)==null||l.startIfEnabledOrStop(),(u=this.surveys)==null||u.loadIfEnabled(),this.ln(),(d=this.externalIntegrations)==null||d.startIfEnabledOrStop()}}_overrideSDKInfo(e,t){zd.LIB_NAME=e,zd.LIB_VERSION=t}startSessionRecording(e){var t,n,r,i,a,o=!0===e,s={sampling:o||!(e==null||!e.sampling),linked_flag:o||!(e==null||!e.linked_flag),url_trigger:o||!(e==null||!e.url_trigger),event_trigger:o||!(e==null||!e.event_trigger)};Object.values(s).some(Boolean)&&((t=this.sessionManager)==null||t.checkAndGetSessionAndWindowId(),s.sampling&&((n=this.sessionRecording)==null||n.overrideSampling()),s.linked_flag&&((r=this.sessionRecording)==null||r.overrideLinkedFlag()),s.url_trigger&&((i=this.sessionRecording)==null||i.overrideTrigger(`url`)),s.event_trigger&&((a=this.sessionRecording)==null||a.overrideTrigger(`event`))),this.set_config({disable_session_recording:!1})}stopSessionRecording(){this.set_config({disable_session_recording:!0})}sessionRecordingStarted(){var e;return!((e=this.sessionRecording)==null||!e.started)}captureException(e,t){if(this.exceptions){var n=Error(`PostHog syntheticException`),r=this.exceptions.buildProperties(e,{handled:!0,syntheticException:n});return this.exceptions.sendExceptionEvent(X({},r,t))}}addExceptionStep(e,t){var n;(n=this.exceptions)==null||n.addExceptionStep(e,t)}captureLog(e){var t;(t=this.logs)==null||t.captureLog(e)}get logger(){return this.logs?.logger??e.un}startExceptionAutocapture(e){this.set_config({capture_exceptions:e==null||e})}stopExceptionAutocapture(){this.set_config({capture_exceptions:!1})}loadToolbar(e){var t;return(t=this.toolbar?.loadToolbar(e))!=null&&t}get_property(e){return this.persistence?.props[e]}getSessionProperty(e){return this.sessionPersistence?.props[e]}toString(){var e=this.config.name??z_;return e!==z_&&(e=z_+`.`+e),e}_isIdentified(){return this.persistence?.get_property(Am)===Wm||this.sessionPersistence?.get_property(Am)===Wm}tn(){var e,t;return!(this.config.person_profiles===`never`||this.config.person_profiles===Gm&&!this._isIdentified()&&nf(this.getGroups())&&((e=this.persistence)==null||(e=e.props)==null||!e[rm])&&((t=this.persistence)==null||(t=t.props)==null||!t[Im]))}Xr(){return!0===this.config.capture_pageleave||this.config.capture_pageleave===`if_capture_pageview`&&(!0===this.config.capture_pageview||this.config.capture_pageview===`history_change`)}createPersonProfile(){this.tn()||this.en(`posthog.createPersonProfile`)&&this.setPersonProperties({},{})}setInternalOrTestUser(){this.en(`posthog.setInternalOrTestUser`)&&this.setPersonProperties({$internal_or_test_user:!0})}en(e){return this.config.person_profiles===`never`?(Q.error(e+` was called, but process_person is set to "never". This call will be ignored.`),!1):(this.rn(Im,!0),!0)}Br(){if(this.config.cookieless_mode===`always`)return!0;var e=this.consent.isOptedOut();return this.config.disable_persistence||e&&!(!this.config.opt_out_persistence_by_default&&this.config.cookieless_mode!==Vm)}ln(){var e,t,n=this.Br();return this.persistence?.mi!==n&&((e=this.persistence)==null||e.set_disabled(n)),this.sessionPersistence?.mi!==n&&((t=this.sessionPersistence)==null||t.set_disabled(n)),n}opt_in_capturing(t){var n;if(this.config.cookieless_mode!==Hm){if(this.Ar()){var r,i;this.reset(!0),(r=this.sessionManager)==null||r.destroy(),(i=this.pageViewManager)==null||i.destroy(),this.sessionManager=new d_(this),this.pageViewManager=new Fh(this),this.persistence&&(this.sessionPropsManager=new c_(this,this.sessionManager,this.persistence));var a,o=this.config.__extensionClasses?.sessionRecording??e.__defaultExtensionClasses?.sessionRecording;o&&(this.sessionRecording=this.kr(this.sessionRecording,new o(this)),this.Ur&&((a=this.sessionRecording)==null||a.onRemoteConfig==null||a.onRemoteConfig(this.Ur)))}var s;this.consent.optInOut(!0),this.ln(),this.Yr(),(n=this.sessionRecording)==null||n.startIfEnabledOrStop(),this.config.cookieless_mode==Vm&&((s=this.surveys)==null||s.loadIfEnabled()),(Z(t?.captureEventName)||t!=null&&t.captureEventName)&&this.capture(t?.captureEventName??`$opt_in`,t?.captureProperties,{send_instantly:!0}),this.config.capture_pageview&&this.Wr()}else Q.warn(F_)}opt_out_capturing(){var e,t,n;this.config.cookieless_mode===Hm?Q.warn(F_):(this.config.cookieless_mode===Vm&&this.consent.isOptedIn()&&this.reset(!0),this.consent.optInOut(!1),this.ln(),this.config.cookieless_mode===Vm&&(this.register({distinct_id:Rm,$device_id:null}),(e=this.sessionManager)==null||e.destroy(),(t=this.pageViewManager)==null||t.destroy(),this.sessionManager=void 0,this.sessionPropsManager=void 0,(n=this.sessionRecording)==null||n.stopRecording(),this.sessionRecording=void 0,this.Wr()))}has_opted_in_capturing(){return this.consent.isOptedIn()}has_opted_out_capturing(){return this.consent.isOptedOut()}get_explicit_consent_status(){var e=this.consent.consent;return e===1?`granted`:e===0?`denied`:`pending`}is_capturing(){return this.config.cookieless_mode===Hm||(this.config.cookieless_mode===Vm?this.consent.isRejected()||this.consent.isOptedIn():!this.has_opted_out_capturing())}clear_opt_in_out_capturing(){this.consent.reset(),this.ln()}_is_bot(){return jd?f_(jd,this.config.custom_blocked_useragents):void 0}Wr(){J&&(J.visibilityState===`visible`?this.Nr||(this.Nr=!0,this.capture(Jm,{title:J.title},{send_instantly:!0}),this.Mr&&=(J.removeEventListener(Km,this.Mr),null)):this.Mr||(this.Mr=this.Wr.bind(this),sh(J,Km,this.Mr)))}debug(e){!1===e?(q?.console.log(`You've disabled debug mode.`),this.set_config({debug:!1})):(q?.console.log("You're now in debug mode. All calls to PostHog will be logged in your console.\nYou can disable this with `posthog.debug(false)`."),this.set_config({debug:!0}))}Ai(){var e,t,n,r,i,a,o=this.Lr||{};return`advanced_disable_flags`in o?!!o.advanced_disable_flags:!1===this.config.advanced_disable_flags?!0===this.config.advanced_disable_decide?(Q.warn(`Config field 'advanced_disable_decide' is deprecated. Please use 'advanced_disable_flags' instead. The old field will be removed in a future major version.`),!0):(n=`advanced_disable_decide`,r=Q,i=(t=`advanced_disable_flags`)in(e=o)&&!sf(e[t]),a=n in e&&!sf(e[n]),i?e[t]:!!a&&(r&&r.warn(`Config field '`+n+`' is deprecated. Please use '`+t+`' instead. The old field will be removed in a future major version.`),e[n])):!!this.config.advanced_disable_flags}Qr(e){if(sf(this.config.before_send))return e;var t=$d(this.config.before_send)?this.config.before_send:[this.config.before_send],n=e;for(var r of t){if(n=r(n),sf(n)){var i=`Event '`+e.event+`' was rejected in beforeSend function`;return ff(e.event)?Q.warn(i+`. This can cause unexpected behavior.`):Q.info(i),null}n.properties&&!nf(n.properties)||Q.warn(`Event '`+e.event+`' has no properties after beforeSend function, this is likely an error.`)}return n}getPageViewId(){return this.pageViewManager.hi?.pageViewId}captureTraceFeedback(e,t){this.capture(`$ai_feedback`,{$ai_trace_id:String(e),$ai_feedback_text:t})}captureTraceMetric(e,t,n){this.capture(`$ai_metric`,{$ai_trace_id:String(e),$ai_metric_name:t,$ai_metric_value:String(n)})}Pr(e){var t=uf(e)&&!e,n=bh.Gt()&&bh.Jt(`ph_debug`)===`true`;return!t&&(!!n||e)}};function K_(e){return e instanceof Element&&(e.id===Lm||!(e.closest==null||!e.closest(`.toolbar-global-fade-container`)))}function q_(e){return!!e&&e.nodeType===1}function J_(e,t){return!!e&&!!e.tagName&&e.tagName.toLowerCase()===t.toLowerCase()}function Y_(e){return!!e&&e.nodeType===3}function X_(e){return!!e&&e.nodeType===11}function Z_(e){return e?Jd(e).split(/\s+/):[]}function Q_(e){var t=q?.location.href;return!!(t&&e&&e.some((e=>t.match(e))))}function $_(e){var t=``;switch(typeof e.className){case`string`:t=e.className;break;case`object`:t=(e.className&&`baseVal`in e.className?e.className.baseVal:null)||e.getAttribute(`class`)||``;break;default:t=``}return Z_(t)}function ev(e){return sf(e)?null:Jd(e).split(/(\s+)/).filter((e=>vv(e))).join(``).replace(/[\r\n]/g,` `).replace(/[ ]+/g,` `).substring(0,255)}function tv(e){var t=``;return uv(e)&&!dv(e)&&e.childNodes&&e.childNodes.length&&$m(e.childNodes,(function(e){Y_(e)&&e.textContent&&(t+=ev(e.textContent)??``)})),Jd(t)}function nv(e){return Z(e.target)?e.srcElement||null:(t=e.target)!=null&&t.shadowRoot?e.composedPath()[0]||null:e.target||null;var t}G_.__defaultExtensionClasses={},G_.un={trace:j_=()=>{},debug:j_,info:j_,warn:j_,error:j_,fatal:j_},function(e,t){for(var n=0;t.length>n;n++)e.prototype[t[n]]=rh(e.prototype[t[n]])}(G_,[`identify`]);var rv=[`a`,`button`,`form`,`input`,`select`,`textarea`,`label`];function iv(e,t){if(Z(t))return!0;var n,r=function(e){if(t.some((t=>e.matches(t))))return{v:!0}};for(var i of e)if(n=r(i))return n.v;return!1}function av(e){var t=e.parentNode;return!(!t||!q_(t))&&t}var ov=[`next`,`previous`,`prev`,`>`,`<`],sv=[`.ph-no-rageclick`,`.ph-no-capture`],cv=e=>!e||J_(e,`html`)||!q_(e),lv=(e,t)=>{if(!q||cv(e))return{parentIsUsefulElement:!1,targetElementList:[]};for(var n=!1,r=[e],i=e;i.parentNode&&!J_(i,`body`);)if(X_(i.parentNode))r.push(i.parentNode.host),i=i.parentNode.host;else{var a=av(i);if(!a)break;if(t||rv.indexOf(a.tagName.toLowerCase())>-1)n=!0;else{var o=q.getComputedStyle(a);o&&o.getPropertyValue(`cursor`)===`pointer`&&(n=!0)}r.push(a),i=a}return{parentIsUsefulElement:n,targetElementList:r}};function uv(e){for(var t=e;t.parentNode&&!J_(t,`body`);t=t.parentNode){var n=$_(t);if(qd(n,`ph-sensitive`)||qd(n,`ph-no-capture`))return!1}if(qd($_(e),`ph-include`))return!0;var r=e.type||``;if(rf(r))switch(r.toLowerCase()){case`hidden`:case`password`:return!1}var i=e.name||e.id||``;return!rf(i)||!/^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i.test(i.replace(/[^a-zA-Z0-9]/g,``))}function dv(e){return!!(J_(e,`input`)&&![`button`,`checkbox`,`submit`,`reset`].includes(e.type)||J_(e,`select`)||J_(e,`textarea`)||e.getAttribute(`contenteditable`)===`true`)}var fv=`(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11})`,pv=RegExp(`^(?:`+fv+`)$`),mv=new RegExp(fv),hv=`\\d{3}-?\\d{2}-?\\d{4}`,gv=RegExp(`^(`+hv+`)$`),_v=RegExp(`(`+hv+`)`);function vv(e,t){return t===void 0&&(t=!0),!(sf(e)||rf(e)&&(e=Jd(e),(t?pv:mv).test((e||``).replace(/[- ]/g,``))||(t?gv:_v).test(e)))}function yv(e){var t=tv(e);return vv(t=(t+` `+bv(e)).trim())?t:``}function bv(e){var t=``;return e&&e.childNodes&&e.childNodes.length&&$m(e.childNodes,(function(e){if(e&&e.tagName?.toLowerCase()===`span`)try{var n=tv(e);t=(t+` `+n).trim(),e.childNodes&&e.childNodes.length&&(t=(t+` `+bv(e)).trim())}catch(e){Q.error(`[AutoCapture]`,e)}})),t}function xv(e){return e.replace(/"|\\"/g,`\\"`)}function Sv(e){var t=e.attr__class;return t?$d(t)?t:Z_(t):void 0}var Cv=class{constructor(e){this.disabled=!1===e;var t=tf(e)?e:{};this.thresholdPx=t.threshold_px||30,this.timeoutMs=t.timeout_ms||1e3,this.clickCount=t.click_count||3,this.clicks=[]}isRageClick(e,t,n){if(this.disabled)return!1;var r=this.clicks[this.clicks.length-1];if(r&&Math.abs(e-r.x)+Math.abs(t-r.y)n-r.timestamp){if(this.clicks.push({x:e,y:t,timestamp:n}),this.clicks.length===this.clickCount)return!0}else this.clicks=[{x:e,y:t,timestamp:n}];return!1}},wv=`$copy_autocapture`,Tv=Qp(`[AutoCapture]`);function Ev(e,t){return t.length>e?t.slice(0,e)+`...`:t}function Dv(e){if(e.previousElementSibling)return e.previousElementSibling;var t=e;do t=t.previousSibling;while(t&&!q_(t));return t}function Ov(e,t){for(var n,{e:r,maskAllElementAttributes:i,maskAllText:a,elementAttributeIgnoreList:o,elementsChainAsString:s}=t,c=[e],l=e;l.parentNode&&!J_(l,`body`);)X_(l.parentNode)?(c.push(l.parentNode.host),l=l.parentNode.host):(c.push(l.parentNode),l=l.parentNode);var u,d,f=[],p={},m=!1,h=!1;if($m(c,(e=>{var t=uv(e);e.tagName.toLowerCase()===`a`&&(m=e.getAttribute(`href`),m=t&&m&&vv(m)&&m),qd($_(e),`ph-no-capture`)&&(h=!0),f.push(function(e,t,n,r){var i=e.tagName.toLowerCase(),a={tag_name:i};rv.indexOf(i)>-1&&!n&&(a.$el_text=i.toLowerCase()===`a`||i.toLowerCase()===`button`?Ev(1024,yv(e)):Ev(1024,tv(e)));var o=$_(e);o.length>0&&(a.classes=o.filter((function(e){return e!==``}))),$m(e.attributes,(function(n){var i;if((!dv(e)||[`name`,`id`,`class`,`aria-label`].indexOf(n.name)!==-1)&&(r==null||!r.includes(n.name))&&!t&&vv(n.value)&&(!rf(i=n.name)||i.substring(0,10)!==`_ngcontent`&&i.substring(0,7)!==`_nghost`)){var o=n.value;n.name===`class`&&(o=Z_(o).join(` `)),a[`attr__`+n.name]=Ev(1024,o)}}));for(var s=1,c=1,l=e;l=Dv(l);)s++,l.tagName===e.tagName&&c++;return a.nth_child=s,a.nth_of_type=c,a}(e,i,a,o)),eh(p,function(e){if(!uv(e))return{};var t={};return $m(e.attributes,(function(e){if(e.name&&e.name.indexOf(`data-ph-capture-attribute`)===0){var n=e.name.replace(`data-ph-capture-attribute-`,``),r=e.value;n&&r&&vv(r)&&(t[n]=r)}})),t}(e))})),h)return{props:{},explicitNoCapture:h};if(a||(f[0].$el_text=e.tagName.toLowerCase()===`a`||e.tagName.toLowerCase()===`button`?yv(e):tv(e)),m){var g;f[0].attr__href=m;var _=Ih(m)?.host,v=q==null||(g=q.location)==null?void 0:g.host;_&&v&&_!==v&&(u=m)}return{props:eh({$event_type:r.type,$ce_version:1},s?{}:{$elements:f},{$elements_chain:(d=f,function(e){return e.map((e=>{var t=``;if(e.tag_name&&(t+=e.tag_name),e.attr_class)for(var n of(e.attr_class.sort(),e.attr_class))t+=`.`+n.replace(/"/g,``);var r=X({},e.text?{text:e.text}:{},{"nth-child":e.nth_child??0,"nth-of-type":e.nth_of_type??0},e.href?{href:e.href}:{},e.attr_id?{attr_id:e.attr_id}:{},e.attributes),i={};return th(r).sort(((e,t)=>{var[n]=e,[r]=t;return n.localeCompare(r)})).forEach((e=>{var[t,n]=e;return i[xv(t.toString())]=xv(n.toString())})),(t+=`:`)+th(i).map((e=>{var[t,n]=e;return t+`="`+n+`"`})).join(``)})).join(`;`)}(function(e){return e.map((e=>{var t={text:e.$el_text?.slice(0,400),tag_name:e.tag_name,href:e.attr__href?.slice(0,2048),attr_class:Sv(e),attr_id:e.attr__id,nth_child:e.nth_child,nth_of_type:e.nth_of_type,attributes:{}};return th(e).filter((e=>{var[t]=e;return t.indexOf(`attr__`)===0})).forEach((e=>{var[n,r]=e;return t.attributes[n]=r})),t}))}(d)))},(n=f[0])!=null&&n.$el_text?{$el_text:f[0]?.$el_text}:{},u&&r.type===`click`?{$external_click_url:u}:{},p)}}var kv=Qp(`[ExceptionAutocapture]`);function Av(e,t,n){try{if(!(t in e))return()=>{};var r=e[t],i=n(r);return ef(i)&&(i.prototype=i.prototype||{},Object.defineProperties(i,{__posthog_wrapped__:{enumerable:!1,value:!0}})),e[t]=i,()=>{e[t]=r}}catch{return()=>{}}}var jv=Qp(`[TracingHeaders]`),Mv=Qp(`[Web Vitals]`),Nv=9e5,Pv=`disabled`,Fv=`lazy_loading`,Iv=`awaiting_config`,Lv=`missing_config`;Qp(`[SessionRecording]`),Qp(`[SessionRecording]`);var Rv=`[SessionRecording]`,zv=Qp(Rv),Bv=Qp(`[Heatmaps]`);function Vv(e){return tf(e)&&`clientX`in e&&`clientY`in e&&cf(e.clientX)&&cf(e.clientY)}var Hv=Qp(`[Product Tours]`),Uv=[`$set_once`,`$set`],Wv=Qp(`[SiteApps]`),Gv=`Error while initializing PostHog app with config id `;function Kv(e,t,n){if(sf(e))return!1;switch(n){case`exact`:return e===t;case`contains`:var r=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`).replace(/_/g,`.`).replace(/%/g,`.*`);return new RegExp(r,`i`).test(e);case`regex`:try{return new RegExp(t).test(e)}catch{return!1}default:return!1}}var qv=class{constructor(e){this.hn=new l_,this.cn=(e,t)=>this.dn(e,t)&&this.vn(e,t)&&this.fn(e,t)&&this.pn(e,t),this.dn=(e,t)=>t==null||!t.event||e?.event===t?.event,this._instance=e,this.gn=new Set,this.mn=new Set}init(){var e;Z(this._instance?._addCaptureHook)||(e=this._instance)==null||e._addCaptureHook(((e,t)=>{this.on(e,t)}))}register(e){var t;if(!Z(this._instance?._addCaptureHook)&&(e.forEach((e=>{var t,n;(t=this.mn)==null||t.add(e),(n=e.steps)==null||n.forEach((e=>{var t;(t=this.gn)==null||t.add(e?.event||``)}))})),(t=this._instance)!=null&&t.autocapture)){var n,r=new Set;e.forEach((e=>{var t;(t=e.steps)==null||t.forEach((e=>{e!=null&&e.selector&&r.add(e?.selector)}))})),(n=this._instance)==null||n.autocapture.setElementSelectors(r)}}on(e,t){t!=null&&e.length!=0&&(this.gn.has(e)||this.gn.has(t?.event))&&this.mn&&this.mn?.size>0&&this.mn.forEach((e=>{this.yn(t,e)&&this.hn.emit(`actionCaptured`,e.name)}))}bn(e){this.onAction(`actionCaptured`,(t=>e(t)))}yn(e,t){if(t?.steps==null)return!1;for(var n of t.steps)if(this.cn(e,n))return!0;return!1}onAction(e,t){return this.hn.on(e,t)}vn(e,t){if(t!=null&&t.url){var n,r=e==null||(n=e.properties)==null?void 0:n.$current_url;if(!r||typeof r!=`string`||!Kv(r,t.url,t.url_matching||`contains`))return!1}return!0}fn(e,t){return!!this.wn(e,t)&&!!this._n(e,t)&&!!this.In(e,t)}wn(e,t){var n;if(t==null||!t.href)return!0;var r=this.Cn(e);if(r.length>0)return r.some((e=>Kv(e.href,t.href,t.href_matching||`exact`)));var i,a=(e==null||(n=e.properties)==null?void 0:n.$elements_chain)||``;return!!a&&Kv((i=a.match(/(?::|")href="(.*?)"/))?i[1]:``,t.href,t.href_matching||`exact`)}_n(e,t){var n;if(t==null||!t.text)return!0;var r=this.Cn(e);if(r.length>0)return r.some((e=>Kv(e.text,t.text,t.text_matching||`exact`)||Kv(e.$el_text,t.text,t.text_matching||`exact`)));var i,a,o,s=(e==null||(n=e.properties)==null?void 0:n.$elements_chain)||``;return!!s&&(i=function(e){for(var t,n=[],r=/(?::|")text="(.*?)"/g;!sf(t=r.exec(e));)n.includes(t[1])||n.push(t[1]);return n}(s),a=t.text,o=t.text_matching||`exact`,i.some((e=>Kv(e,a,o))))}In(e,t){var n,r;if(t==null||!t.selector)return!0;var i=e==null||(n=e.properties)==null?void 0:n.$element_selectors;if(i!=null&&i.includes(t.selector))return!0;var a=(e==null||(r=e.properties)==null?void 0:r.$elements_chain)||``;if(t.selector_regex&&a)try{return new RegExp(t.selector_regex).test(a)}catch{return!1}return!1}Cn(e){var t;return(e==null||(t=e.properties)==null?void 0:t.$elements)==null?[]:e?.properties.$elements}pn(e,t){return t==null||!t.properties||t.properties.length===0||__(t.properties.reduce(((e,t)=>{var n=$d(t.value)?t.value.map(String):t.value==null?[]:[String(t.value)];return e[t.key]={values:n,operator:t.operator||`exact`},e}),{}),e?.properties)}},Jv=class{constructor(e){this._instance=e,this.Sn=new Map,this.xn=new Map,this.kn=new Map}Tn(e,t){return!!e&&__(e.propertyFilters,t?.properties)}An(e,t){var n=new Map;return e.forEach((e=>{var r;(r=e.conditions)==null||(r=r[t])==null||(r=r.values)==null||r.forEach((t=>{if(t!=null&&t.name){var r=n.get(t.name)||[];r.push(e.id),n.set(t.name,r)}}))})),n}En(e,t,n){var r=(n===rg.Activation?this.Sn:this.xn).get(e),i=[];return this.Rn((e=>{i=e.filter((e=>r?.includes(e.id)))})),i.filter((r=>{var i,a=(i=r.conditions)==null||(i=i[n])==null||(i=i.values)==null?void 0:i.find((t=>t.name===e));return this.Tn(a,t)}))}register(e){Z(this._instance?._addCaptureHook)||(this.Nn(e),this.Mn(e))}Mn(e){var t=e.filter((e=>{var t;return e.conditions?.actions&&((t=e.conditions)==null||(t=t.actions)==null||(t=t.values)==null?void 0:t.length)>0}));t.length!==0&&(this.Fn??(this.Fn=new qv(this._instance),this.Fn.init(),this.Fn.bn((e=>{this.onAction(e)}))),t.forEach((e=>{var t,n,r,i,a;e.conditions&&(t=e.conditions)!=null&&t.actions&&(n=e.conditions)!=null&&(n=n.actions)!=null&&n.values&&((r=e.conditions)==null||(r=r.actions)==null||(r=r.values)==null?void 0:r.length)>0&&((i=this.Fn)==null||i.register(e.conditions.actions.values),(a=e.conditions)==null||(a=a.actions)==null||(a=a.values)==null||a.forEach((t=>{if(t&&t.name){var n=this.kn.get(t.name);n&&n.push(e.id),this.kn.set(t.name,n||[e.id])}})))})))}Nn(e){var t,n=e.filter((e=>{var t;return e.conditions?.events&&((t=e.conditions)==null||(t=t.events)==null||(t=t.values)==null?void 0:t.length)>0})),r=e.filter((e=>{var t;return e.conditions?.cancelEvents&&((t=e.conditions)==null||(t=t.cancelEvents)==null||(t=t.values)==null?void 0:t.length)>0}));n.length===0&&r.length===0||((t=this._instance)==null||t._addCaptureHook(((e,t)=>{this.onEvent(e,t)})),this.Sn=this.An(e,rg.Activation),this.xn=this.An(e,rg.Cancellation))}onEvent(e,t){var n,r=this.re(),i=this.On(),a=this.Pn(),o=((n=this._instance)==null||(n=n.persistence)==null?void 0:n.props[i])||[];if(a===e&&t&&o.length>0){var s,c;r.info(`event matched, removing item from activated items`,{event:e,eventPayload:t,existingActivatedItems:o});var l=(t==null||(s=t.properties)==null?void 0:s.$survey_id)||(t==null||(c=t.properties)==null?void 0:c.$product_tour_id);if(l){var u=o.indexOf(l);0>u||(o.splice(u,1),this.Ln(o))}}else{if(this.xn.has(e)){var d=this.En(e,t,rg.Cancellation);d.length>0&&(r.info(`cancel event matched, cancelling items`,{event:e,itemsToCancel:d.map((e=>e.id))}),d.forEach((e=>{var t=o.indexOf(e.id);0>t||o.splice(t,1),this.Dn(e.id)})),this.Ln(o))}if(this.Sn.has(e)){r.info(`event name matched`,{event:e,eventPayload:t,items:this.Sn.get(e)});var f=this.En(e,t,rg.Activation);this.Ln(o.concat(f.map((e=>e.id))||[]))}}}onAction(e){var t,n=this.On(),r=((t=this._instance)==null||(t=t.persistence)==null?void 0:t.props[n])||[];this.kn.has(e)&&this.Ln(r.concat(this.kn.get(e)||[]))}Ln(e){var t,n=this.re(),r=this.On(),i=[...new Set(e)].filter((e=>!this.Bn(e)));n.info(`updating activated items`,{activatedItems:i}),(t=this._instance)==null||(t=t.persistence)==null||t.register({[r]:i})}getActivatedIds(){var e,t=this.On();return((e=this._instance)==null||(e=e.persistence)==null?void 0:e.props[t])||[]}getEventToItemsMap(){return this.Sn}jn(){return this.Fn}},Yv=class extends Jv{constructor(e){super(e)}On(){return`$surveys_activated`}Pn(){return ag.SHOWN}Rn(e){var t;(t=this._instance)==null||t.getSurveys(e)}Dn(e){var t;(t=this._instance)==null||t.cancelPendingSurvey(e)}re(){return S_}Bn(){return!1}getSurveys(){return this.getActivatedIds()}getEventToSurveys(){return this.getEventToItemsMap()}},Xv=`SDK is not enabled or survey functionality is not yet loaded`,Zv=`Disabled. Not loading surveys.`,Qv=q!=null&&q.location?zh(q.location.hash,`__posthog`)||zh(location.hash,`state`):null,$v=`_postHogToolbarParams`,ey=Qp(`[Toolbar]`),ty=Qp(`[FeatureFlags]`),ny=Qp(`[FeatureFlags]`,{debugEnabled:!0}),ry=`" failed. Feature flags didn't load in time.`,iy=`$active_feature_flags`,ay=`$override_feature_flags`,oy=`$feature_flag_request_id`,sy=e=>{for(var t={},n=0;e.length>n;n++)t[e[n]]=!0;return t},cy=e=>{var t={};for(var[n,r]of th(e||{}))r&&(t[n]=r);return t},ly=Qp(`[Error tracking]`),uy=`Refusing to render web experiment since the viewer is a likely bot`,dy={icontains:(e,t)=>!!q&&t.href.toLowerCase().indexOf(e.toLowerCase())>-1,not_icontains:(e,t)=>!!q&&t.href.toLowerCase().indexOf(e.toLowerCase())===-1,regex:(e,t)=>!!q&&p_(t.href,e),not_regex:(e,t)=>!!q&&!p_(t.href,e),exact:(e,t)=>t.href===e,is_not:(e,t)=>t.href!==e},fy=class e{get Rt(){return this._instance.config}constructor(t){var n=this;this.getWebExperimentsAndEvaluateDisplayLogic=function(t){t===void 0&&(t=!1),n.getWebExperiments((t=>{e.qn(`retrieved web experiments from the server`),n.Zn=new Map,t.forEach((t=>{if(t.feature_flag_key){var r;n.Zn&&(e.qn(`setting flag key `,t.feature_flag_key,` to web experiment `,t),(r=n.Zn)==null||r.set(t.feature_flag_key,t));var i=n._instance.getFeatureFlag(t.feature_flag_key);rf(i)&&t.variants[i]&&n.$n(t.name,i,t.variants[i].transforms)}else if(t.variants)for(var a in t.variants){var o=t.variants[a];e.Hn(o)&&n.$n(t.name,a,o.transforms)}}))}),t)},this._instance=t,this._instance.onFeatureFlags((e=>{this.onFeatureFlags(e)}))}initialize(){}onFeatureFlags(t){if(this._is_bot())e.qn(uy);else if(!this.Rt.disable_web_experiments){if(sf(this.Zn))return this.Zn=new Map,this.loadIfEnabled(),void this.previewWebExperiment();e.qn(`applying feature flags`,t),t.forEach((e=>{var t;if(this.Zn&&(t=this.Zn)!=null&&t.has(e)){var n=this._instance.getFeatureFlag(e),r=this.Zn?.get(e);n&&r!=null&&r.variants[n]&&this.$n(r.name,n,r.variants[n].transforms)}}))}}previewWebExperiment(){var t=e.getWindowLocation();if(t!=null&&t.search){var n=Lh(t?.search,`__experiment_id`),r=Lh(t?.search,`__experiment_variant`);n&&r&&(e.qn(`previewing web experiments `+n+` && `+r),this.getWebExperiments((e=>{this.Vn(parseInt(n),r,e)}),!1,!0))}}loadIfEnabled(){this.Rt.disable_web_experiments||this.getWebExperimentsAndEvaluateDisplayLogic()}getWebExperiments(e,t,n){if(this.Rt.disable_web_experiments&&!n)return e([]);var r=this._instance.get_property(`$web_experiments`);if(r&&!t)return e(r);this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/web_experiments/?token=`+this.Rt.token),method:`GET`,callback:t=>e(t.statusCode===200&&t.json&&t.json.experiments||[])})}Vn(t,n,r){var i=r.filter((e=>e.id===t));i&&i.length>0&&(e.qn(`Previewing web experiment [`+i[0].name+`] with variant [`+n+`]`),this.$n(i[0].name,n,i[0].variants[n].transforms))}static Hn(t){return!sf(t.conditions)&&e.zn(t)&&e.Un(t)}static zn(t){if(sf(t.conditions)||sf(t.conditions?.url))return!0;var n,r=e.getWindowLocation();return!!r&&((n=t.conditions)==null||!n.url||dy[t.conditions?.urlMatchType??`icontains`](t.conditions.url,r))}static getWindowLocation(){return q?.location}static Un(e){if(sf(e.conditions)||sf(e.conditions?.utm))return!0;var t=Gh();if(t.utm_source){var n,r,i,a,o,s,c,l,u=(n=e.conditions)==null||(n=n.utm)==null||!n.utm_campaign||((r=e.conditions)==null||(r=r.utm)==null?void 0:r.utm_campaign)==t.utm_campaign,d=(i=e.conditions)==null||(i=i.utm)==null||!i.utm_source||((a=e.conditions)==null||(a=a.utm)==null?void 0:a.utm_source)==t.utm_source,f=(o=e.conditions)==null||(o=o.utm)==null||!o.utm_medium||((s=e.conditions)==null||(s=s.utm)==null?void 0:s.utm_medium)==t.utm_medium,p=(c=e.conditions)==null||(c=c.utm)==null||!c.utm_term||((l=e.conditions)==null||(l=l.utm)==null?void 0:l.utm_term)==t.utm_term;return u&&f&&p&&d}return!1}static qn(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];Q.info(`[WebExperiments] `+e,n)}$n(t,n,r){this._is_bot()?e.qn(uy):n===`control`?e.qn(`Control variants leave the page unmodified.`):r.forEach((r=>{r.selector&&(e.qn(`applying transform of variant `+n+` for experiment `+t+` `,r),(document?.querySelectorAll(r.selector))?.forEach((e=>{var t=e;r.html&&(t.innerHTML=r.html),r.css&&t.setAttribute(`style`,r.css)})))}))}_is_bot(){return jd&&this._instance?f_(jd,this.Rt.custom_blocked_useragents):void 0}},py=Qp(`[Conversations]`),my=`Conversations not available yet.`,hy={trace:{text:`TRACE`,number:1},debug:{text:`DEBUG`,number:5},info:{text:`INFO`,number:9},warn:{text:`WARN`,number:13},error:{text:`ERROR`,number:17},fatal:{text:`FATAL`,number:21}},gy=hy.info;function _y(e){if(uf(e))return{boolValue:e};if(cf(e))return Number.isInteger(e)?{intValue:e}:{doubleValue:e};if(typeof e==`string`)return{stringValue:e};if($d(e))return{arrayValue:{values:e.map((e=>_y(e)))}};try{return{stringValue:JSON.stringify(e)}}catch{return{stringValue:String(e)}}}function vy(e){var t=[];for(var n in e){var r=e[n];of(r)||Z(r)||t.push({key:n,value:_y(r)})}return t}var yy={featureFlags:class{constructor(e){this.Yn=!1,this.Wn=!1,this.Gn=!1,this.Xn=!1,this.Jn=!1,this.Kn=!1,this.Qn=!1,this.ts=!1,this._instance=e,this.featureFlagEventHandlers=[]}get Rt(){return this._instance.config}get Qi(){return this._instance.persistence}es(e){return this._instance.get_property(e)}rs(){var e;return(e=this.Qi?.yi(this.Rt.feature_flag_cache_ttl_ms))!=null&&e}ns(){return!!this.rs()&&(this.ts||this.Gn||(this.ts=!0,ty.warn(`Feature flag cache is stale, triggering refresh...`),this.reloadFeatureFlags()),!0)}ss(){var e=this.Rt.evaluation_contexts??this.Rt.evaluation_environments;return!this.Rt.evaluation_environments||this.Rt.evaluation_contexts||this.Qn||(ty.warn(`evaluation_environments is deprecated. Use evaluation_contexts instead. evaluation_environments will be removed in a future version.`),this.Qn=!0),e!=null&&e.length?e.filter((e=>{var t=e&&typeof e==`string`&&e.trim().length>0;return t||ty.error(`Invalid evaluation context found:`,e,`Expected non-empty string`),t})):[]}os(){return this.ss().length>0}initialize(){var{config:e}=this._instance,t=e.bootstrap?.featureFlags??{};if(Object.keys(t).length){var n=e.bootstrap?.featureFlagPayloads??{},r=Object.keys(t).filter((e=>!!t[e])).reduce(((e,n)=>(e[n]=t[n]||!1,e)),{}),i=Object.keys(n).filter((e=>r[e])).reduce(((e,t)=>(n[t]&&(e[t]=n[t]),e)),{});this.receivedFeatureFlags({featureFlags:r,featureFlagPayloads:i})}}updateFlags(e,t,n){var r=n!=null&&n.merge?this.getFlagVariants():{},i=n!=null&&n.merge?this.getFlagPayloads():{},a=X({},r,e),o=X({},i,t),s={};for(var[c,l]of Object.entries(a)){var u=typeof l==`string`;s[c]={key:c,enabled:!!u||!!l,variant:u?l:void 0,reason:void 0,metadata:Z(o?.[c])?void 0:{id:0,version:void 0,description:void 0,payload:o[c]}}}this.receivedFeatureFlags({flags:s})}get hasLoadedFlags(){return this.Wn}getFlags(){return Object.keys(this.getFlagVariants())}getFlagsWithDetails(){var e=this.es(ym),t=this.es(ay),n=this.es(xm);if(!n&&!t)return e||{};var r=eh({},e||{});for(var i of[...new Set([...Object.keys(n||{}),...Object.keys(t||{})])]){var a,o,s=r[i],c=t?.[i],l=Z(c)?(a=s?.enabled)!=null&&a:!!c,u=Z(c)?s.variant:typeof c==`string`?c:void 0,d=n?.[i],f=X({},s,{enabled:l,variant:l?u??s?.variant:void 0});l!==s?.enabled&&(f.original_enabled=s?.enabled),u!==s?.variant&&(f.original_variant=s?.variant),d&&(f.metadata=X({},s?.metadata,{payload:d,original_payload:s==null||(o=s.metadata)==null?void 0:o.payload})),r[i]=f}return this.Yn||=(ty.warn(` Overriding feature flag details!`,{flagDetails:e,overriddenPayloads:n,finalDetails:r}),!0),r}getFlagVariants(){var e=this.es(_m),t=this.es(ay);if(!t)return e||{};for(var n=eh({},e),r=Object.keys(t),i=0;r.length>i;i++)n[r[i]]=t[r[i]];return this.Yn||=(ty.warn(` Overriding feature flags!`,{enabledFlags:e,overriddenFlags:t,finalFlags:n}),!0),n}getFlagPayloads(){var e=this.es(bm),t=this.es(xm);if(!t)return e||{};for(var n=eh({},e||{}),r=Object.keys(t),i=0;r.length>i;i++)n[r[i]]=t[r[i]];return this.Yn||=(ty.warn(` Overriding feature flag payloads!`,{flagPayloads:e,overriddenPayloads:t,finalPayloads:n}),!0),n}reloadFeatureFlags(){this.Xn||this.Rt.advanced_disable_feature_flags||this.ls||(this._instance.Rr.emit(`featureFlagsReloading`,!0),this.ls=setTimeout((()=>{this.us()}),5))}hs(){clearTimeout(this.ls),this.ls=void 0}ensureFlagsLoaded(){this.Wn||this.Gn||this.ls||this.reloadFeatureFlags()}setAnonymousDistinctId(e){this.$anon_distinct_id=e}setReloadingPaused(e){this.Xn=e}us(e){if(this.hs(),!this._instance.Ai())if(this.Gn)this.Jn=!0;else{var t=this.Rt.token,n=this.es(nm),r={token:t,distinct_id:this._instance.get_distinct_id(),groups:this._instance.getGroups(),$anon_distinct_id:this.$anon_distinct_id,person_properties:X({},this.Qi?.get_initial_props()||{},this.es(Sm)||{}),group_properties:this.es(Cm),timezone:$h()};of(n)||Z(n)||(r.$device_id=n),(e!=null&&e.disableFlags||this.Rt.advanced_disable_feature_flags)&&(r.disable_flags=!0),this.os()&&(r.evaluation_contexts=this.ss());var i=this._instance.requestRouter.endpointFor(`flags`,`/flags/?v=2`+(this.Rt.advanced_only_evaluate_survey_feature_flags?`&only_evaluate_survey_feature_flags=true`:``));this.Gn=!0,this._instance._send_request({method:`POST`,url:i,data:r,compression:this.Rt.disable_compression?void 0:mg.Base64,timeout:this.Rt.feature_flag_request_timeout_ms,callback:e=>{var t,n,i,a=!0;if(e.statusCode===200&&(this.Jn||(this.$anon_distinct_id=void 0),a=!1),this.Gn=!1,!r.disable_flags||this.Jn){this.Kn=!a;var o=[];e.error?e.error instanceof Error?o.push(e.error.name===`AbortError`?`timeout`:`connection_error`):o.push(`unknown_error`):e.statusCode!==200&&o.push(`api_error_`+e.statusCode),(t=e.json)!=null&&t.errorsWhileComputingFlags&&o.push(`errors_while_computing_flags`);var s=!((n=e.json)==null||(n=n.quotaLimited)==null||!n.includes(`feature_flags`));s&&o.push(`quota_limited`),(i=this.Qi)==null||i.register({[Om]:o}),s?ty.warn(`You have hit your feature flags quota limit, and will not be able to load feature flags until the quota is reset. Please visit https://posthog.com/docs/billing/limits-alerts to learn more.`):(r.disable_flags||this.receivedFeatureFlags(e.json??{},a,{partialResponse:!!this.Rt.advanced_only_evaluate_survey_feature_flags}),this.Jn&&(this.Jn=!1,this.us()))}}})}}getFeatureFlag(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn)if(this.Wn||this.getFlags()&&this.getFlags().length>0){if(!this.ns()){var n=this.getFeatureFlagResult(e,t);return n?.variant??n?.enabled}}else ty.warn(`getFeatureFlag for key "`+e+ry)}getFeatureFlagDetails(e){return this.getFlagsWithDetails()[e]}getFeatureFlagPayload(e){return this.getFeatureFlagResult(e,{send_event:!1})?.payload}getFeatureFlagResult(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn)if(this.Wn||this.getFlags()&&this.getFlags().length>0){if(!this.ns()){var n=this.getFlagVariants(),r=e in n,i=n[e],a=this.getFlagPayloads()[e],o=String(i),s=this.es(oy)||void 0,c=this.es(km)||void 0,l=this.es(Em)||{};if(this.Rt.advanced_feature_flags_dedup_per_session){var u,d=this._instance.get_session_id(),f=this.es(Dm);d&&d!==f&&(l={},(u=this.Qi)==null||u.register({[Em]:l,[Dm]:d}))}if((t.send_event||!(`send_event`in t))&&(!(e in l)||!l[e].includes(o))){var p,m,h,g,_,v,y,b;$d(l[e])?l[e].push(o):l[e]=[o],(p=this.Qi)==null||p.register({[Em]:l});var x=this.getFeatureFlagDetails(e),S=[...this.es(Om)??[]];Z(i)&&S.push(`flag_missing`);var C={$feature_flag:e,$feature_flag_response:i,$feature_flag_payload:a||null,$feature_flag_request_id:s,$feature_flag_evaluated_at:c,$feature_flag_bootstrapped_response:((m=this.Rt.bootstrap)==null||(m=m.featureFlags)==null?void 0:m[e])||null,$feature_flag_bootstrapped_payload:((h=this.Rt.bootstrap)==null||(h=h.featureFlagPayloads)==null?void 0:h[e])||null,$used_bootstrap_value:!this.Kn};Z(x==null||(g=x.metadata)==null?void 0:g.version)||(C.$feature_flag_version=x.metadata.version);var w,T=(x==null||(_=x.reason)==null?void 0:_.description)??(x==null||(v=x.reason)==null?void 0:v.code);T&&(C.$feature_flag_reason=T),x!=null&&(y=x.metadata)!=null&&y.id&&(C.$feature_flag_id=x.metadata.id),Z(x?.original_variant)&&Z(x?.original_enabled)||(C.$feature_flag_original_response=Z(x.original_variant)?x.original_enabled:x.original_variant),x!=null&&(b=x.metadata)!=null&&b.original_payload&&(C.$feature_flag_original_payload=x==null||(w=x.metadata)==null?void 0:w.original_payload),S.length&&(C.$feature_flag_error=S.join(`,`)),this._instance.capture(`$feature_flag_called`,C)}if(r){var E=a;if(!Z(a))try{E=JSON.parse(a)}catch{}return{key:e,enabled:!!i,variant:typeof i==`string`?i:void 0,payload:E}}}}else ty.warn(`getFeatureFlagResult for key "`+e+ry)}getRemoteConfigPayload(e,t){var n=this.Rt.token,r={distinct_id:this._instance.get_distinct_id(),token:n};this.os()&&(r.evaluation_contexts=this.ss()),this._instance._send_request({method:`POST`,url:this._instance.requestRouter.endpointFor(`flags`,`/flags/?v=2`),data:r,compression:this.Rt.disable_compression?void 0:mg.Base64,timeout:this.Rt.feature_flag_request_timeout_ms,callback(n){var r=n.json?.featureFlagPayloads;t(r?.[e]||void 0)}})}isFeatureEnabled(e,t){if(t===void 0&&(t={}),!t.fresh||this.Kn){if(this.Wn||this.getFlags()&&this.getFlags().length>0){var n=this.getFeatureFlag(e,t);return Z(n)?void 0:!!n}ty.warn(`isFeatureEnabled for key "`+e+ry)}}addFeatureFlagsHandler(e){this.featureFlagEventHandlers.push(e)}removeFeatureFlagsHandler(e){this.featureFlagEventHandlers=this.featureFlagEventHandlers.filter((t=>t!==e))}receivedFeatureFlags(e,t,n){if(this.Qi){this.Wn=!0;var r=this.getFlagVariants(),i=this.getFlagPayloads(),a=this.getFlagsWithDetails();(function(e,t,n,r,i,a){n===void 0&&(n={}),r===void 0&&(r={}),i===void 0&&(i={});var o=(e=>{var t=e.flags;return t?(e.featureFlags=Object.fromEntries(Object.keys(t).map((e=>[e,t[e].variant??t[e].enabled]))),e.featureFlagPayloads=Object.fromEntries(Object.keys(t).filter((e=>t[e].enabled)).filter((e=>t[e].metadata?.payload)).map((e=>[e,t[e].metadata?.payload])))):ty.warn(`Using an older version of the feature flags endpoint. Please upgrade your PostHog server to the latest version`),e})(e),s=o.flags,c=o.featureFlags,l=o.featureFlagPayloads;if(c){var u=e.requestId,d=e.evaluatedAt;if($d(c)){ty.warn(`v1 of the feature flags endpoint is deprecated. Please use the latest version.`);var f={};if(c)for(var p=0;c.length>p;p++)f[c[p]]=!0;t&&t.register({[iy]:c,[_m]:f})}else{var m=c,h=l,g=s;if(a!=null&&a.partialResponse)m=X({},n,m),h=X({},r,h),g=X({},i,g);else if(e.errorsWhileComputingFlags)if(s){var _=new Set(Object.keys(s).filter((e=>{var t;return!((t=s[e])!=null&&t.failed)})));m=X({},n,Object.fromEntries(Object.entries(m).filter((e=>{var[t]=e;return _.has(t)})))),h=X({},r,Object.fromEntries(Object.entries(h||{}).filter((e=>{var[t]=e;return _.has(t)})))),g=X({},i,Object.fromEntries(Object.entries(g||{}).filter((e=>{var[t]=e;return _.has(t)}))))}else m=X({},n,m),h=X({},r,h),g=X({},i,g);t&&t.register(X({[iy]:Object.keys(cy(m)),[_m]:m||{},[bm]:h||{},[ym]:g||{}},u?{[oy]:u}:{},d?{[km]:d}:{}))}}})(e,this.Qi,r,i,a,n),t||(this.ts=!1),this.cs(t)}}override(e,t){t===void 0&&(t=!1),ty.warn(`override is deprecated. Please use overrideFeatureFlags instead.`),this.overrideFeatureFlags({flags:e,suppressWarning:t})}overrideFeatureFlags(e){if(!this._instance.__loaded||!this.Qi)return ty.uninitializedWarning(`posthog.featureFlags.overrideFeatureFlags`);if(!1===e)return this.Qi.unregister(ay),this.Qi.unregister(xm),this.cs(),ny.info(`All overrides cleared`);if($d(e)){var t=sy(e);return this.Qi.register({[ay]:t}),this.cs(),ny.info(`Flag overrides set`,{flags:e})}if(e&&typeof e==`object`&&(`flags`in e||`payloads`in e)){var n,r=e;if(this.Yn=!!((n=r.suppressWarning)!=null&&n),`flags`in r){if(!1===r.flags)this.Qi.unregister(ay),ny.info(`Flag overrides cleared`);else if(r.flags){if($d(r.flags)){var i=sy(r.flags);this.Qi.register({[ay]:i})}else this.Qi.register({[ay]:r.flags});ny.info(`Flag overrides set`,{flags:r.flags})}}`payloads`in r&&(!1===r.payloads?(this.Qi.unregister(xm),ny.info(`Payload overrides cleared`)):r.payloads&&(this.Qi.register({[xm]:r.payloads}),ny.info(`Payload overrides set`,{payloads:r.payloads}))),this.cs();return}if(e&&typeof e==`object`)return this.Qi.register({[ay]:e}),this.cs(),ny.info(`Flag overrides set`,{flags:e});ty.warn(`Invalid overrideOptions provided to overrideFeatureFlags`,{overrideOptions:e})}onFeatureFlags(e){if(this.addFeatureFlagsHandler(e),this.Wn){var{flags:t,flagVariants:n}=this.ds();e(t,n)}return()=>this.removeFeatureFlagsHandler(e)}updateEarlyAccessFeatureEnrollment(e,t,n){var r,i=(this.es(vm)||[]).find((t=>t.flagKey===e)),a={[`$feature_enrollment/`+e]:t},o={$feature_flag:e,$feature_enrollment:t,$set:a};i&&(o.$early_access_feature_name=i.name),n&&(o.$feature_enrollment_stage=n),this._instance.capture(`$feature_enrollment_update`,o),this.setPersonPropertiesForFlags(a,!1);var s=X({},this.getFlagVariants(),{[e]:t});(r=this.Qi)==null||r.register({[iy]:Object.keys(cy(s)),[_m]:s}),this.cs()}getEarlyAccessFeatures(e,t,n){t===void 0&&(t=!1);var r=this.es(vm),i=n?`&`+n.map((e=>`stage=`+e)).join(`&`):``;if(r&&!t)return e(r);this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/early_access_features/?token=`+this.Rt.token+i),method:`GET`,callback:t=>{var n,r;if(t.json){var i=t.json.earlyAccessFeatures;return(n=this.Qi)==null||n.unregister(vm),(r=this.Qi)==null||r.register({[vm]:i}),e(i)}}})}ds(){var e=this.getFlags(),t=this.getFlagVariants();return{flags:e.filter((e=>t[e])),flagVariants:Object.keys(t).filter((e=>t[e])).reduce(((e,n)=>(e[n]=t[n],e)),{})}}cs(e){var{flags:t,flagVariants:n}=this.ds();this.featureFlagEventHandlers.forEach((r=>r(t,n,{errorsLoading:e})))}setPersonPropertiesForFlags(e,t){t===void 0&&(t=!0);var n=this.es(Sm)||{},r=e?.$set||(e!=null&&e.$set_once?{}:e),i=e?.$set_once,a={};if(i)for(var o in i)({}).hasOwnProperty.call(i,o)&&(o in n||(a[o]=i[o]));this._instance.register({[Sm]:X({},n,a,r)}),t&&this._instance.reloadFeatureFlags()}resetPersonPropertiesForFlags(){this._instance.unregister(Sm)}setGroupPropertiesForFlags(e,t){t===void 0&&(t=!0);var n=this.es(Cm)||{};Object.keys(n).length!==0&&Object.keys(n).forEach((t=>{n[t]=X({},n[t],e[t]),delete e[t]})),this._instance.register({[Cm]:X({},n,e)}),t&&this._instance.reloadFeatureFlags()}resetGroupPropertiesForFlags(e){if(e){var t=this.es(Cm)||{};this._instance.register({[Cm]:X({},t,{[e]:{}})})}else this._instance.unregister(Cm)}reset(){this.Wn=!1,this.Gn=!1,this.Xn=!1,this.Jn=!1,this.Kn=!1,this.$anon_distinct_id=void 0,this.hs(),this.Yn=!1}}},by={sessionRecording:class{get Rt(){return this._instance.config}get Qi(){return this._instance.persistence}get started(){var e;return!((e=this.vs)==null||!e.isStarted)}get status(){return this.fs===Iv||this.fs===Lv?this.fs:this.vs?.status??this.fs}constructor(e){if(this._forceAllowLocalhostNetworkCapture=!1,this.fs=Pv,this.ps=void 0,this._instance=e,!this._instance.sessionManager)throw zv.error(`started without valid sessionManager`),Error(Rv+` started without valid sessionManager. This is a bug.`);if(this.Rt.cookieless_mode===Hm)throw Error(Rv+` cannot be used with cookieless_mode="always"`)}initialize(){this.startIfEnabledOrStop()}get gs(){var e,t=!((e=this._instance.get_property(mm))==null||!e.enabled),n=!this.Rt.disable_session_recording,r=this.Rt.disable_session_recording||this._instance.consent.isOptedOut();return q&&t&&n&&!r}startIfEnabledOrStop(e){var t;if(!this.gs||(t=this.vs)==null||!t.isStarted){var n=!Z(Object.assign)&&!Z(Array.from);this.gs&&n?(this.ys(e),zv.info(`starting`)):(this.fs=Pv,this.stopRecording())}}ys(e){var t,n,r;this.gs&&(this.fs!==Iv&&this.fs!==Lv&&(this.fs=Fv),Y!=null&&(t=Y.__PosthogExtensions__)!=null&&(t=t.rrweb)!=null&&t.record&&(n=Y.__PosthogExtensions__)!=null&&n.initSessionRecording?this.bs(e):(r=Y.__PosthogExtensions__)==null||r.loadExternalDependency==null||r.loadExternalDependency(this._instance,this.ws,(t=>{if(t)return zv.error(`could not load recorder`,t);this.bs(e)})))}stopRecording(){var e,t;(e=this.ps)==null||e.call(this),this.ps=void 0,(t=this.vs)==null||t.stop()}_s(){var e,t;(e=this.ps)==null||e.call(this),this.ps=void 0,(t=this.vs)==null||t.discard()}Is(){var e;(e=this.Qi)==null||e.unregister(gm)}Cs(e,t){if(sf(e))return null;var n,r=cf(e)?e:parseFloat(e);return typeof(n=r)!=`number`||!Number.isFinite(n)||0>n||n>1?(zv.warn(t+` must be between 0 and 1. Ignoring invalid value:`,e),null):r}Ss(e){if(this.Qi){var t,n=this.Qi,r=()=>{var t=!1===e.sessionRecording?void 0:e.sessionRecording,r=this.Cs(this.Rt.session_recording?.sampleRate,`session_recording.sampleRate`),i=this.Cs(t?.sampleRate,`remote config sampleRate`),a=r??i;sf(a)&&this.Is();var o=t?.minimumDurationMilliseconds;n.register({[mm]:X({cache_timestamp:Date.now(),enabled:!!t},t,{networkPayloadCapture:X({capturePerformance:e.capturePerformance},t?.networkPayloadCapture),canvasRecording:{enabled:t?.recordCanvas,fps:t?.canvasFps,quality:t?.canvasQuality},sampleRate:a,minimumDurationMilliseconds:Z(o)?null:o,endpoint:t?.endpoint,triggerMatchType:t?.triggerMatchType,masking:t?.masking,urlTriggers:t?.urlTriggers,version:t?.version,triggerGroups:t?.triggerGroups})})};r(),(t=this.ps)==null||t.call(this),this.ps=this._instance.sessionManager?.onSessionId(r)}}onRemoteConfig(e){`sessionRecording`in e?!1===e.sessionRecording?(this.Ss(e),this._s()):(this.Ss(e),this.startIfEnabledOrStop()):(this.fs===Iv&&(this.fs=Lv,zv.warn(`config refresh failed, recording will not start until page reload`)),this.startIfEnabledOrStop())}log(e,t){var n;t===void 0&&(t=`log`),(n=this.vs)!=null&&n.log?this.vs.log(e,t):zv.warn(`log called before recorder was ready`)}get ws(){var e,t,n=(e=this._instance)==null||(e=e.persistence)==null?void 0:e.get_property(mm);return(n==null||(t=n.scriptConfig)==null?void 0:t.script)||`lazy-recorder`}xs(){var e=this._instance.get_property(mm);if(!e)return!1;var t=(typeof e==`object`?e:JSON.parse(e)).cache_timestamp??Date.now();return 36e5>=Date.now()-t}bs(e){var t;if((t=Y.__PosthogExtensions__)==null||!t.initSessionRecording)return zv.warn(`Called on script loaded before session recording is available. This can be caused by adblockers.`),void this._instance.register_for_session({$sdk_debug_recording_script_not_loaded:!0});if(this.vs||(this.vs=Y.__PosthogExtensions__?.initSessionRecording(this._instance),this.vs._forceAllowLocalhostNetworkCapture=this._forceAllowLocalhostNetworkCapture),!this.xs()){if(this.fs===Lv||this.fs===Iv)return;this.fs=Iv,zv.info(`persisted remote config is stale, requesting fresh config before starting`),new pg(this._instance).load();return}this.fs=Fv,this.vs.start(e)}onRRwebEmit(e){var t;(t=this.vs)==null||t.onRRwebEmit==null||t.onRRwebEmit(e)}overrideLinkedFlag(){var e,t;this.vs||(t=this.Qi)==null||t.register({$replay_override_linked_flag:!0}),(e=this.vs)==null||e.overrideLinkedFlag()}overrideSampling(){var e,t;this.vs||(t=this.Qi)==null||t.register({$replay_override_sampling:!0}),(e=this.vs)==null||e.overrideSampling()}overrideTrigger(e){var t,n;this.vs||(n=this.Qi)==null||n.register({[e===`url`?`$replay_override_url_trigger`:`$replay_override_event_trigger`]:!0}),(t=this.vs)==null||t.overrideTrigger(e)}get sdkDebugProperties(){return this.vs?.sdkDebugProperties||{$recording_status:this.status}}tryAddCustomEvent(e,t){var n;return!((n=this.vs)==null||!n.tryAddCustomEvent(e,t))}}},xy={autocapture:class{constructor(e){this.ks=!1,this.Ts=null,this.As=!1,this.instance=e,this.rageclicks=new Cv(e.config.rageclick),this.Es=null}initialize(){this.startIfEnabled()}get Rt(){var e=tf(this.instance.config.autocapture)?this.instance.config.autocapture:{};return e.url_allowlist=e.url_allowlist?.map((e=>new RegExp(e))),e.url_ignorelist=e.url_ignorelist?.map((e=>new RegExp(e))),e}Rs(){if(this.isBrowserSupported()){if(q&&J){var e=e=>{e||=q?.event;try{this.Ns(e)}catch(e){Tv.error(`Failed to capture event`,e)}};if(sh(J,`submit`,e,{capture:!0}),sh(J,`change`,e,{capture:!0}),sh(J,`click`,e,{capture:!0}),this.Rt.capture_copied_text){var t=e=>{this.Ns(e||=q?.event,wv)};sh(J,`copy`,t,{capture:!0}),sh(J,`cut`,t,{capture:!0})}}}else Tv.info(`Disabling Automatic Event Collection because this browser is not supported`)}startIfEnabled(){this.isEnabled&&!this.ks&&(this.Rs(),this.ks=!0)}onRemoteConfig(e){e.elementsChainAsString&&(this.As=e.elementsChainAsString),this.instance.persistence&&this.instance.persistence.register({[am]:!!e.autocapture_opt_out}),this.Ts=!!e.autocapture_opt_out,this.startIfEnabled()}setElementSelectors(e){this.Es=e}getElementSelectors(e){var t,n=[];return(t=this.Es)==null||t.forEach((t=>{(J?.querySelectorAll(t))?.forEach((r=>{e===r&&n.push(t)}))})),n}get isEnabled(){var e=this.instance.persistence?.props[am];if(of(this.Ts)&&!uf(e)&&!this.instance.Ai())return!1;var t=this.Ts??!!e;return!!this.instance.config.autocapture&&!t}Ns(e,t){if(t===void 0&&(t=`$autocapture`),this.isEnabled){var n,r=nv(e);Y_(r)&&(r=r.parentNode||null),t===`$autocapture`&&e.type===`click`&&e instanceof MouseEvent&&this.instance.config.rageclick&&(n=this.rageclicks)!=null&&n.isRageClick(e.clientX,e.clientY,e.timeStamp||new Date().getTime())&&function(e,t){if(!q||cv(e))return!1;var n,r;if(uf(t)?(n=!!t&&sv,r=void 0):(n=t?.css_selector_ignorelist??sv,r=t?.content_ignorelist),!1===n)return!1;var{targetElementList:i}=lv(e,!1);return!function(e,t){if(!1===e||Z(e))return!1;var n;if(!0===e)n=ov;else{if(!$d(e))return!1;if(e.length>10)return Q.error(`[PostHog] content_ignorelist array cannot exceed 10 items. Use css_selector_ignorelist for more complex matching.`),!1;n=e.map((e=>e.toLowerCase()))}return t.some((e=>{var{safeText:t,ariaLabel:r}=e;return n.some((e=>t.includes(e)||r.includes(e)))}))}(r,i.map((e=>({safeText:tv(e).toLowerCase(),ariaLabel:e.getAttribute(`aria-label`)?.toLowerCase().trim()||``}))))&&!iv(i,n)}(r,this.instance.config.rageclick)&&this.Ns(e,`$rageclick`);var i=t===wv;if(r&&function(e,t,n,r,i){var a,o,s;if(n===void 0&&(n=void 0),!q||cv(e)||(a=n)!=null&&a.url_allowlist&&!Q_(n.url_allowlist)||(o=n)!=null&&o.url_ignorelist&&Q_(n.url_ignorelist))return!1;if((s=n)!=null&&s.dom_event_allowlist){var c=n.dom_event_allowlist;if(c&&!c.some((e=>t.type===e)))return!1}var{parentIsUsefulElement:l,targetElementList:u}=lv(e,r);if(!function(e,t){var n=t?.element_allowlist;if(Z(n))return!0;var r,i=function(e){if(n.some((t=>e.tagName.toLowerCase()===t)))return{v:!0}};for(var a of e)if(r=i(a))return r.v;return!1}(u,n)||!iv(u,n?.css_selector_allowlist))return!1;var d=q.getComputedStyle(e);if(d&&d.getPropertyValue(`cursor`)===`pointer`&&t.type===`click`)return!0;var f=e.tagName.toLowerCase();switch(f){case`html`:return!1;case`form`:return(i||[`submit`]).indexOf(t.type)>=0;case`input`:case`select`:case`textarea`:return(i||[`change`,`click`]).indexOf(t.type)>=0;default:return l?(i||[`click`]).indexOf(t.type)>=0:(i||[`click`]).indexOf(t.type)>=0&&(rv.indexOf(f)>-1||e.getAttribute(`contenteditable`)===`true`)}}(r,e,this.Rt,i,i?[`copy`,`cut`]:void 0)){var{props:a,explicitNoCapture:o}=Ov(r,{e,maskAllElementAttributes:this.instance.config.mask_all_element_attributes,maskAllText:this.instance.config.mask_all_text,elementAttributeIgnoreList:this.Rt.element_attribute_ignorelist,elementsChainAsString:this.As});if(o)return!1;var s=this.getElementSelectors(r);if(s&&s.length>0&&(a.$element_selectors=s),t===wv){var c,l=ev(q==null||(c=q.getSelection())==null?void 0:c.toString()),u=e.type||`clipboard`;if(!l)return!1;a.$selected_content=l,a.$copy_type=u}return this.instance.capture(t,a),!0}}}isBrowserSupported(){return ef(J?.querySelectorAll)}},historyAutocapture:class{constructor(e){var t;this._instance=e,this.Ms=(q==null||(t=q.location)==null?void 0:t.pathname)||``}initialize(){this.startIfEnabled()}get isEnabled(){return this._instance.config.capture_pageview===`history_change`}startIfEnabled(){this.isEnabled&&(Q.info(`History API monitoring enabled, starting...`),this.monitorHistoryChanges())}stop(){this.Fs&&this.Fs(),this.Fs=void 0,Q.info(`History API monitoring stopped`)}monitorHistoryChanges(){var e,t;if(q&&q.history){var n=this;(e=q.history.pushState)!=null&&e.__posthog_wrapped__||Av(q.history,`pushState`,(e=>function(t,r,i){e.call(this,t,r,i),n.Os(`pushState`)})),(t=q.history.replaceState)!=null&&t.__posthog_wrapped__||Av(q.history,`replaceState`,(e=>function(t,r,i){e.call(this,t,r,i),n.Os(`replaceState`)})),this.Ps()}}Os(e){try{var t,n=q==null||(t=q.location)==null?void 0:t.pathname;if(!n)return;n!==this.Ms&&this.isEnabled&&this._instance.capture(Jm,{navigation_type:e}),this.Ms=n}catch(t){Q.error(`Error capturing `+e+` pageview`,t)}}Ps(){if(!this.Fs){var e=()=>{this.Os(`popstate`)};sh(q,`popstate`,e),this.Fs=()=>{q&&q.removeEventListener(`popstate`,e)}}}},heatmaps:class{get Rt(){return this.instance.config}constructor(e){var t;this.Ls=!1,this.ks=!1,this.Ds=null,this.instance=e,this.Ls=!((t=this.instance.persistence)==null||!t.props[om]),this.rageclicks=new Cv(e.config.rageclick)}initialize(){this.startIfEnabled()}get flushIntervalMilliseconds(){var e=5e3;return tf(this.Rt.capture_heatmaps)&&this.Rt.capture_heatmaps.flush_interval_milliseconds&&(e=this.Rt.capture_heatmaps.flush_interval_milliseconds),e}get isEnabled(){return sf(this.Rt.capture_heatmaps)?sf(this.Rt.enable_heatmaps)?this.Ls:this.Rt.enable_heatmaps:!1!==this.Rt.capture_heatmaps}startIfEnabled(){if(this.isEnabled){if(this.ks)return;Bv.info(`starting...`),this.Bs(),this.Tt()}else clearInterval(this.Ds??void 0),this.js(),this.getAndClearBuffer()}onRemoteConfig(e){if(`heatmaps`in e){var t=!!e.heatmaps;this.instance.persistence&&this.instance.persistence.register({[om]:t}),this.Ls=t,this.startIfEnabled()}}getAndClearBuffer(){var e=this.T;return this.T=void 0,e}qs(e){this.wt(e.originalEvent,`deadclick`)}Tt(){this.Ds&&clearInterval(this.Ds),this.Ds=J?.visibilityState===`visible`?setInterval(this.$i.bind(this),this.flushIntervalMilliseconds):null}Bs(){q&&J&&(this.Zs=this.$i.bind(this),sh(q,qm,this.Zs),this.$s=e=>this.wt(e||q?.event),sh(J,`click`,this.$s,{capture:!0}),this.Hs=e=>this.Vs(e||q?.event),sh(J,`mousemove`,this.Hs,{capture:!0}),this.zs=new Ah(this.instance,Oh,this.qs.bind(this)),this.zs.startIfEnabledOrStop(),this.Us=this.Tt.bind(this),sh(J,Km,this.Us),this.ks=!0)}js(){var e;q&&J&&(this.Zs&&q.removeEventListener(qm,this.Zs),this.$s&&J.removeEventListener(`click`,this.$s,{capture:!0}),this.Hs&&J.removeEventListener(`mousemove`,this.Hs,{capture:!0}),this.Us&&J.removeEventListener(Km,this.Us),clearTimeout(this.Ys),(e=this.zs)==null||e.stop(),this.ks=!1)}Ws(e,t){var n=this.instance.scrollManager.scrollY(),r=this.instance.scrollManager.scrollX(),i=this.instance.scrollManager.scrollElement(),a=function(e,t,n){for(var r=e;r&&q_(r)&&!J_(r,`body`);){if(r===n)return!1;if(qd(t,q?.getComputedStyle(r).position))return!0;r=av(r)}return!1}(nv(e),[`fixed`,`sticky`],i);return{x:e.clientX+(a?0:r),y:e.clientY+(a?0:n),target_fixed:a,type:t}}wt(e,t){var n;if(t===void 0&&(t=`click`),!K_(e.target)&&Vv(e)){var r=this.Ws(e,t);(n=this.rageclicks)!=null&&n.isRageClick(e.clientX,e.clientY,new Date().getTime())&&this.Gs(X({},r,{type:`rageclick`})),this.Gs(r)}}Vs(e){!K_(e.target)&&Vv(e)&&(clearTimeout(this.Ys),this.Ys=setTimeout((()=>{this.Gs(this.Ws(e,`mousemove`))}),500))}Gs(e){if(q){var t=q.location.href,n=this.Rt.custom_personal_data_properties,r=Rh(t,this.Rt.mask_personal_data_properties?[...Vh,...n||[]]:[],Uh);this.T=this.T||{},this.T[r]||(this.T[r]=[]),this.T[r].push(e)}}$i(){this.T&&!nf(this.T)&&this.instance.capture(`$$heatmap`,{$heatmap_data:this.getAndClearBuffer()})}},deadClicksAutocapture:Ah,webVitalsAutocapture:class{constructor(e){var t;this.Ls=!1,this.ks=!1,this.T={url:void 0,metrics:[],firstMetricTimestamp:void 0},this.Xs=()=>{clearTimeout(this.Js),this.T.metrics.length!==0&&(this._instance.capture(`$web_vitals`,this.T.metrics.reduce(((e,t)=>X({},e,{[`$web_vitals_`+t.name+`_event`]:X({},t),[`$web_vitals_`+t.name+`_value`]:t.value})),{})),this.T={url:void 0,metrics:[],firstMetricTimestamp:void 0})},this.nt=e=>{var t=this._instance.sessionManager?.checkAndGetSessionAndWindowId(!0);if(Z(t))Mv.error(`Could not read session ID. Dropping metrics!`);else{this.T=this.T||{url:void 0,metrics:[],firstMetricTimestamp:void 0};var n=this.Ks();Z(n)||(sf(e?.name)||sf(e?.value)?Mv.error(`Invalid metric received`,e):!this.Qs||this.Qs>e.value?(this.T.url!==n&&(this.Xs(),this.Js=setTimeout(this.Xs,this.flushToCaptureTimeoutMs)),Z(this.T.url)&&(this.T.url=n),this.T.firstMetricTimestamp=Z(this.T.firstMetricTimestamp)?Date.now():this.T.firstMetricTimestamp,e.attribution&&e.attribution.interactionTargetElement&&(e.attribution.interactionTargetElement=void 0),this.T.metrics.push(X({},e,{$current_url:n,$session_id:t.sessionId,$window_id:t.windowId,timestamp:Date.now()})),this.T.metrics.length===this.allowedMetrics.length&&this.Xs()):Mv.error(`Ignoring metric with value >= `+this.Qs,e))}},this.eo=()=>{if(!this.ks){var e,t,n,r,i=Y.__PosthogExtensions__;Z(i)||Z(i.postHogWebVitalsCallbacks)||({onLCP:e,onCLS:t,onFCP:n,onINP:r}=i.postHogWebVitalsCallbacks),e&&t&&n&&r?(this.allowedMetrics.indexOf(`LCP`)>-1&&e(this.nt.bind(this)),this.allowedMetrics.indexOf(`CLS`)>-1&&t(this.nt.bind(this)),this.allowedMetrics.indexOf(`FCP`)>-1&&n(this.nt.bind(this)),this.allowedMetrics.indexOf(`INP`)>-1&&r(this.nt.bind(this)),this.ks=!0):Mv.error(`web vitals callbacks not loaded - not starting`)}},this._instance=e,this.Ls=!((t=this._instance.persistence)==null||!t.props[um]),this.startIfEnabled()}get io(){return this._instance.config.capture_performance}get allowedMetrics(){var e=tf(this.io)?this.io?.web_vitals_allowed_metrics:void 0;return sf(e)?this._instance.persistence?.props[pm]||[`CLS`,`FCP`,`INP`,`LCP`]:e}get flushToCaptureTimeoutMs(){return(tf(this.io)?this.io.web_vitals_delayed_flush_ms:void 0)||5e3}get useAttribution(){var e=tf(this.io)?this.io.web_vitals_attribution:void 0;return e!=null&&e}get Qs(){var e=tf(this.io)&&cf(this.io.__web_vitals_max_value)?this.io.__web_vitals_max_value:Nv;return e>0&&6e4>=e?Nv:e}get isEnabled(){var e=Md?.protocol;if(e!==`http:`&&e!==`https:`)return Mv.info(`Web Vitals are disabled on non-http/https protocols`),!1;var t=tf(this.io)?this.io.web_vitals:uf(this.io)?this.io:void 0;return uf(t)?t:this.Ls}startIfEnabled(){this.isEnabled&&!this.ks&&(Mv.info(`enabled, starting...`),this.ai(this.eo))}onRemoteConfig(e){if(`capturePerformance`in e){var t=tf(e.capturePerformance)&&!!e.capturePerformance.web_vitals,n=tf(e.capturePerformance)?e.capturePerformance.web_vitals_allowed_metrics:void 0;this._instance.persistence&&(this._instance.persistence.register({[um]:t}),this._instance.persistence.register({[pm]:n})),this.Ls=t,this.startIfEnabled()}}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.postHogWebVitalsCallbacks?e():(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,this.useAttribution?`web-vitals-with-attribution`:`web-vitals`,(t=>{t?Mv.error(`failed to load script`,t):e()}))}Ks(){var e=q?q.location.href:void 0;if(e){var t=this._instance.config.custom_personal_data_properties;return Rh(e,this._instance.config.mask_personal_data_properties?[...Vh,...t||[]]:[],Uh)}Mv.error(`Could not determine current URL`)}}},Sy={exceptionObserver:class{constructor(e){var t;this.eo=()=>{var e;if(q&&this.isEnabled&&(e=Y.__PosthogExtensions__)!=null&&e.errorWrappingFunctions){var t=Y.__PosthogExtensions__.errorWrappingFunctions.wrapOnError,n=Y.__PosthogExtensions__.errorWrappingFunctions.wrapUnhandledRejection,r=Y.__PosthogExtensions__.errorWrappingFunctions.wrapConsoleError;try{!this.ro&&this.Rt.capture_unhandled_errors&&(this.ro=t(this.captureException.bind(this))),!this.no&&this.Rt.capture_unhandled_rejections&&(this.no=n(this.captureException.bind(this))),!this.so&&this.Rt.capture_console_errors&&(this.so=r(this.captureException.bind(this)))}catch(e){kv.error(`failed to start`,e),this.oo()}}},this._instance=e,this.ao=!((t=this._instance.persistence)==null||!t.props[sm]),this.lo=new bf({refillRate:this._instance.config.error_tracking.__exceptionRateLimiterRefillRate??1,bucketSize:this._instance.config.error_tracking.__exceptionRateLimiterBucketSize??10,refillInterval:1e4,qt:kv}),this.Rt=this.uo(),this.startIfEnabledOrStop()}uo(){var e=this._instance.config.capture_exceptions,t={capture_unhandled_errors:!1,capture_unhandled_rejections:!1,capture_console_errors:!1};return tf(e)?t=X({},t,e):(Z(e)?this.ao:e)&&(t=X({},t,{capture_unhandled_errors:!0,capture_unhandled_rejections:!0})),t}get isEnabled(){return this.Rt.capture_console_errors||this.Rt.capture_unhandled_errors||this.Rt.capture_unhandled_rejections}startIfEnabledOrStop(){this.isEnabled?(kv.info(`enabled`),this.oo(),this.ai(this.eo)):this.oo()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.errorWrappingFunctions&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`exception-autocapture`,(t=>{if(t)return kv.error(`failed to load script`,t);e()}))}oo(){var e,t,n;(e=this.ro)==null||e.call(this),this.ro=void 0,(t=this.no)==null||t.call(this),this.no=void 0,(n=this.so)==null||n.call(this),this.so=void 0}onRemoteConfig(e){`autocaptureExceptions`in e&&(this.ao=!!e.autocaptureExceptions||!1,this._instance.persistence&&this._instance.persistence.register({[sm]:this.ao}),this.Rt=this.uo(),this.startIfEnabledOrStop())}onConfigChange(){this.Rt=this.uo()}captureException(e){var t,n,r=(e==null||(t=e.$exception_list)==null||(t=t[0])==null?void 0:t.type)??`Exception`;this.lo.consumeRateLimit(r)?kv.info(`Skipping exception capture because of client rate limiting.`,{exception:r}):(n=this._instance.exceptions)==null||n.sendExceptionEvent(e)}},exceptions:class{constructor(e){this.ho=[],this.co=new xp([new Np,new Up,new Fp,new Pp,new Vp,new Bp,new Lp,new Hp],function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];return function(t,r){r===void 0&&(r=0);for(var i=[],a=t.split(` +`),o=r;a.length>o;o++){var s=a[o];if(1024>=s.length){var c=Mp.test(s)?s.replace(Mp,`$1`):s;if(!c.match(/\S*Error: /)){for(var l of n){var u=l(c,e);if(u){i.push(u);break}}if(i.length>=50)break}}}return function(e){if(!e.length)return[];var t=Array.from(e);return t.reverse(),t.slice(0,50).map((e=>{return X({},e,{filename:e.filename||(n=t,n[n.length-1]||{}).filename,function:e.function||Sp});var n}))}(i)}}(`web:javascript`,Op,jp)),this._instance=e,this.ho=this._instance.persistence?.get_property(cm)??[],this.do=Jp(this.vo()),this.fo=new Yp(this.do)}onConfigChange(){this.do=Jp(this.vo()),this.fo.setConfig(this.do)}onRemoteConfig(e){if(`errorTracking`in e){var t=e.errorTracking?.suppressionRules??[],n=e.errorTracking?.captureExtensionExceptions;this.ho=t,this._instance.persistence&&this._instance.persistence.register({[cm]:this.ho,[lm]:n})}}get po(){var e,t=!!this._instance.get_property(lm);return(e=this._instance.config.error_tracking.captureExtensionExceptions??t)!=null&&e}buildProperties(e,t){return this.co.buildFromUnknown(e,{syntheticException:t?.syntheticException,mechanism:{handled:t?.handled}})}addExceptionStep(e,t){if(this.do.enabled)try{if(!rf(e)||e.trim().length===0)return void ly.warn(`Ignoring exception step because message must be a non-empty string`);var{sanitizedProperties:n,droppedKeys:r}=function(e){if(!e)return{sanitizedProperties:{},droppedKeys:[]};var t=[];return{sanitizedProperties:Object.keys(e).reduce(((n,r)=>Kp.has(r)?(t.push(r),n):(n[r]=e[r],n)),{}),droppedKeys:t}}(this.mo(t));r.length>0&&ly.warn(`Ignoring reserved exception step fields`,{droppedKeys:r}),this.fo.add(X({[Wp]:e,[Gp]:new Date().toISOString()},n))}catch(e){ly.error(`Failed to add exception step. Ignoring breadcrumb.`,e)}}sendExceptionEvent(e){try{var t=e.$exception_list;if(this.yo(t)){if(this.bo(t))return this.wo(`Exception dropped: matched a suppression rule`),void ly.info(`Skipping exception capture because a suppression rule matched`);if(!this.po&&this._o(t))return this.wo(`Exception dropped: thrown by a browser extension`),void ly.info(`Skipping exception capture because it was thrown by an extension`);if(!this._instance.config.error_tracking.__capturePostHogExceptions&&this.Io(t))return this.wo(`Exception dropped: thrown by the PostHog SDK`),void ly.info(`Skipping exception capture because it was thrown by the PostHog SDK`)}var n=this.do.enabled&&sf(e.$exception_steps)?this.Co(e):e;try{var r=this._instance.capture(`$exception`,n,{_noTruncate:!0,_batchKey:`exceptionEvent`,Jr:!0});return r&&this.fo.clear(),r}catch(e){ly.error(`Failed to capture exception event. Dropping this exception.`,e),this.fo.clear();return}}catch(e){ly.error(`Failed to process exception event. Ignoring this exception.`,e);return}}Co(e){try{var t=this.fo.getAttachable();return t.length===0?e:X({},e,{$exception_steps:t})}catch(t){return ly.error(`Failed to read buffered exception steps. Capturing exception without steps.`,t),e}}wo(e){this.do.enabled&&this.fo.add({[Wp]:e,[Gp]:new Date().toISOString()})}mo(e){return tf(e)?X({},e):{}}vo(){return this._instance.config.error_tracking?.exception_steps??{}}bo(e){if(e.length===0)return!1;var t=e.reduce(((e,t)=>{var{type:n,value:r}=t;return rf(n)&&n.length>0&&e.$exception_types.push(n),rf(r)&&r.length>0&&e.$exception_values.push(r),e}),{$exception_types:[],$exception_values:[]});return this.ho.some((e=>{var n=e.values.map((e=>{var n=h_[e.operator],r=$d(e.value)?e.value:[e.value],i=t[e.key]??[];return r.length>0&&n(r,i)}));return e.type===`OR`?n.some(Boolean):n.every(Boolean)}))}_o(e){return e.flatMap((e=>e.stacktrace?.frames??[])).some((e=>e.filename&&e.filename.startsWith(`chrome-extension://`)))}Io(e){if(e.length>0){var t,n,r=e[0].stacktrace?.frames??[],i=r[r.length-1];return(t=i==null||(n=i.filename)==null?void 0:n.includes(`posthog.com/static`))!=null&&t}return!1}yo(e){return!sf(e)&&$d(e)}}},Cy=X({productTours:class{get Qi(){return this._instance.persistence}constructor(e){this.So=null,this.xo=null,this._instance=e}initialize(){this.loadIfEnabled()}onRemoteConfig(e){`productTours`in e&&(this.Qi&&this.Qi.register({[fm]:!!e.productTours}),this.loadIfEnabled())}loadIfEnabled(){var e,t;this.So||(e=this._instance).config.disable_product_tours||(t=e.persistence)==null||!t.get_property(fm)||this.ai((()=>this.ko()))}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.generateProductTours?e():(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`product-tours`,(t=>{t?Hv.error(`Could not load product tours script`,t):e()}))}ko(){var e;!this.So&&(e=Y.__PosthogExtensions__)!=null&&e.generateProductTours&&(this.So=Y.__PosthogExtensions__.generateProductTours(this._instance,!0))}getProductTours(e,t){if(t===void 0&&(t=!1),!$d(this.xo)||t){var n=this.Qi;if(n){var r=n.props[Tm];if($d(r)&&!t)return this.xo=r,void e(r,{isLoaded:!0})}this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/product_tours/?token=`+this._instance.config.token),method:`GET`,callback:t=>{var r=t.statusCode;if(r!==200||!t.json){var i=`Product Tours API could not be loaded, status: `+r;Hv.error(i),e([],{isLoaded:!1,error:i});return}var a=$d(t.json.product_tours)?t.json.product_tours:[];this.xo=a,n&&n.register({[Tm]:a}),e(a,{isLoaded:!0})}})}else e(this.xo,{isLoaded:!0})}getActiveProductTours(e){sf(this.So)?e([],{isLoaded:!1,error:`Product tours not loaded`}):this.So.getActiveProductTours(e)}showProductTour(e){var t;(t=this.So)==null||t.showTourById(e)}previewTour(e){this.So?this.So.previewTour(e):this.ai((()=>{var t;this.ko(),(t=this.So)==null||t.previewTour(e)}))}dismissProductTour(){var e;(e=this.So)==null||e.dismissTour(`user_clicked_skip`)}nextStep(){var e;(e=this.So)==null||e.nextStep()}previousStep(){var e;(e=this.So)==null||e.previousStep()}clearCache(){var e;this.xo=null,(e=this.Qi)==null||e.unregister(Tm)}resetTour(e){var t;(t=this.So)==null||t.resetTour(e)}resetAllTours(){var e;(e=this.So)==null||e.resetAllTours()}cancelPendingTour(e){var t;(t=this.So)==null||t.cancelPendingTour(e)}}},yy),wy={siteApps:class{constructor(e){this._instance=e,this.To=[],this.apps={}}get isEnabled(){return!!this._instance.config.opt_in_site_apps}Ao(e,t){if(t){var n=this.globalsForEvent(t);this.To.push(n),this.To.length>1e3&&(this.To=this.To.slice(10))}}get siteAppLoaders(){var e;return(e=Y._POSTHOG_REMOTE_CONFIG)==null||(e=e[this._instance.config.token])==null?void 0:e.siteApps}initialize(){if(this.isEnabled){var e=this._instance._addCaptureHook(this.Ao.bind(this));this.Eo=()=>{e(),this.To=[],this.Eo=void 0}}}globalsForEvent(e){if(!e)throw Error(`Event payload is required`);var t={},n=this._instance.get_property(`$groups`)||[],r=this._instance.get_property(`$stored_group_properties`)||{};for(var[i,a]of Object.entries(r))t[i]={id:n[i],type:i,properties:a};var{$set_once:o,$set:s}=e;return{event:X({},Hd(e,Uv),{properties:X({},e.properties,s?{$set:X({},e.properties?.$set??{},s)}:{},o?{$set_once:X({},e.properties?.$set_once??{},o)}:{}),elements_chain:e.properties?.$elements_chain??``,distinct_id:e.properties?.distinct_id}),person:{properties:this._instance.get_property(`$stored_person_properties`)},groups:t}}setupSiteApp(e){var t=this.apps[e.id],n=()=>{var n;!t.errored&&this.To.length&&(Wv.info(`Processing `+this.To.length+` events for site app with id `+e.id),this.To.forEach((e=>t.processEvent==null?void 0:t.processEvent(e))),t.processedBuffer=!0),Object.values(this.apps).every((e=>e.processedBuffer||e.errored))&&((n=this.Eo)==null||n.call(this))},r=!1,i=i=>{t.errored=!i,t.loaded=!0,Wv.info(`Site app with id `+e.id+` `+(i?`loaded`:`errored`)),r&&n()};try{var{processEvent:a}=e.init({posthog:this._instance,callback(e){i(e)}});a&&(t.processEvent=a),r=!0}catch(t){Wv.error(Gv+e.id,t),i(!1)}if(r&&t.loaded)try{n()}catch(n){Wv.error(`Error while processing buffered events PostHog app with config id `+e.id,n),t.errored=!0}}Ro(){var e=this.siteAppLoaders||[];for(var t of e)this.apps[t.id]={id:t.id,loaded:!1,errored:!1,processedBuffer:!1};for(var n of e)this.setupSiteApp(n)}No(e){if(Object.keys(this.apps).length!==0){var t=this.globalsForEvent(e);for(var n of Object.values(this.apps))try{n.processEvent==null||n.processEvent(t)}catch(t){Wv.error(`Error while processing event `+e.event+` for site app `+n.id,t)}}}onRemoteConfig(e){var t,n,r,i=this;if((t=this.siteAppLoaders)!=null&&t.length)return this.isEnabled?(this.Ro(),void this._instance.on(`eventCaptured`,(e=>this.No(e)))):void Wv.error(`PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.`);if((n=this.Eo)==null||n.call(this),(r=e.siteApps)!=null&&r.length)if(this.isEnabled){var a=function(e){var t;Y[`__$$ph_site_app_`+e]=i._instance,(t=Y.__PosthogExtensions__)==null||t.loadSiteApp==null||t.loadSiteApp(i._instance,s,(t=>{if(t)return Wv.error(Gv+e,t)}))};for(var{id:o,url:s}of e.siteApps)a(o)}else Wv.error(`PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.`)}}},Ty={tracingHeaders:class{constructor(e){this.Mo=void 0,this.Fo=void 0,this.eo=()=>{var e,t;Z(this.Mo)&&((e=Y.__PosthogExtensions__)==null||(e=e.tracingHeadersPatchFns)==null||e._patchXHR(this._instance.config.__add_tracing_headers||[],this._instance.get_distinct_id(),this._instance.sessionManager)),Z(this.Fo)&&((t=Y.__PosthogExtensions__)==null||(t=t.tracingHeadersPatchFns)==null||t._patchFetch(this._instance.config.__add_tracing_headers||[],this._instance.get_distinct_id(),this._instance.sessionManager))},this._instance=e}initialize(){this.startIfEnabledOrStop()}ai(e){var t,n;(t=Y.__PosthogExtensions__)!=null&&t.tracingHeadersPatchFns&&e(),(n=Y.__PosthogExtensions__)==null||n.loadExternalDependency==null||n.loadExternalDependency(this._instance,`tracing-headers`,(t=>{if(t)return jv.error(`failed to load script`,t);e()}))}startIfEnabledOrStop(){var e,t;this._instance.config.__add_tracing_headers?this.ai(this.eo):((e=this.Mo)==null||e.call(this),(t=this.Fo)==null||t.call(this),this.Mo=void 0,this.Fo=void 0)}}},Ey=X({surveys:class{get Rt(){return this._instance.config}constructor(e){this.Oo=void 0,this._surveyManager=null,this.Po=!1,this.Lo=[],this.Do=null,this._instance=e,this._surveyEventReceiver=null}initialize(){this.loadIfEnabled()}onRemoteConfig(e){if(!this.Rt.disable_surveys){var t=e.surveys;if(sf(t))return S_.warn(`Flags not loaded yet. Not loading surveys.`);this.Oo=$d(t)?t.length>0:t,S_.info(`flags response received, isSurveysEnabled: `+this.Oo),this.loadIfEnabled()}}reset(){localStorage.removeItem(`lastSeenSurveyDate`);for(var e=[],t=0;tlocalStorage.removeItem(e)))}loadIfEnabled(){if(!this._surveyManager)if(this.Po)S_.info(`Already initializing surveys, skipping...`);else if(this.Rt.disable_surveys)S_.info(Zv);else if(this.Rt.cookieless_mode&&this._instance.consent.isOptedOut())S_.info(`Not loading surveys in cookieless mode without consent.`);else{var e=Y?.__PosthogExtensions__;if(e){if(!Z(this.Oo)||this.Rt.advanced_enable_surveys){var t=this.Oo||this.Rt.advanced_enable_surveys;this.Po=!0;try{var n=e.generateSurveys;if(n)return void this.Bo(n,t);var r=e.loadExternalDependency;if(!r)return void this.jo(Bm);r(this._instance,`surveys`,(n=>{n||!e.generateSurveys?this.jo(`Could not load surveys script`,n):this.Bo(e.generateSurveys,t)}))}catch(e){throw this.jo(`Error initializing surveys`,e),e}finally{this.Po=!1}}}else S_.error(`PostHog Extensions not found.`)}}Bo(e,t){this._surveyManager=e(this._instance,t),this._surveyEventReceiver=new Yv(this._instance),S_.info(`Surveys loaded successfully`),this.qo({isLoaded:!0})}jo(e,t){S_.error(e,t),this.qo({isLoaded:!1,error:e})}onSurveysLoaded(e){return this.Lo.push(e),this._surveyManager&&this.qo({isLoaded:!0}),()=>{this.Lo=this.Lo.filter((t=>t!==e))}}getSurveys(e,t){if(t===void 0&&(t=!1),this.Rt.disable_surveys)return S_.info(Zv),e([]);var n,r=this._instance.get_property(wm);if(r&&!t)return e(r,{isLoaded:!0});typeof Promise<`u`&&this.Do?this.Do.then((t=>{var{surveys:n,context:r}=t;return e(n,r)})):(typeof Promise<`u`&&(this.Do=new Promise((e=>{n=e}))),this._instance._send_request({url:this._instance.requestRouter.endpointFor(`api`,`/api/surveys/?token=`+this.Rt.token),method:`GET`,timeout:this.Rt.surveys_request_timeout_ms,callback:t=>{var r;this.Do=null;var i=t.statusCode;if(i!==200||!t.json){var a=`Surveys API could not be loaded, status: `+i;S_.error(a);var o={isLoaded:!1,error:a};e([],o),n?.({surveys:[],context:o});return}var s,c=t.json.surveys||[],l=c.filter((e=>function(e){return!(!e.start_date||e.end_date)}(e)&&(function(e){var t;return!((t=e.conditions)==null||(t=t.events)==null||(t=t.values)==null||!t.length)}(e)||function(e){var t;return!((t=e.conditions)==null||(t=t.actions)==null||(t=t.values)==null||!t.length)}(e))));l.length>0&&((s=this._surveyEventReceiver)==null||s.register(l)),(r=this._instance.persistence)==null||r.register({[wm]:c});var u={isLoaded:!0};e(c,u),n?.({surveys:c,context:u})}}))}qo(e){for(var t of this.Lo)try{if(!e.isLoaded)return t([],e);this.getSurveys(t)}catch(e){S_.error(`Error in survey callback`,e)}}getActiveMatchingSurveys(e,t){if(t===void 0&&(t=!1),!sf(this._surveyManager))return this._surveyManager.getActiveMatchingSurveys(e,t);S_.warn(`init was not called`)}Zo(e){var t=null;return this.getSurveys((n=>{t=n.find((t=>t.id===e))??null})),t}$o(e){if(sf(this._surveyManager))return{eligible:!1,reason:Xv};var t=typeof e==`string`?this.Zo(e):e;return t?this._surveyManager.checkSurveyEligibility(t):{eligible:!1,reason:`Survey not found`}}canRenderSurvey(e){if(sf(this._surveyManager))return S_.warn(`init was not called`),{visible:!1,disabledReason:Xv};var t=this.$o(e);return{visible:t.eligible,disabledReason:t.reason}}canRenderSurveyAsync(e,t){return sf(this._surveyManager)?(S_.warn(`init was not called`),Promise.resolve({visible:!1,disabledReason:Xv})):new Promise((n=>{this.getSurveys((t=>{var r=t.find((t=>t.id===e))??null;if(r){var i=this.$o(r);n({visible:i.eligible,disabledReason:i.reason})}else n({visible:!1,disabledReason:`Survey not found`})}),t)}))}renderSurvey(e,t,n){var r;if(sf(this._surveyManager))S_.warn(`init was not called`);else{var i=typeof e==`string`?this.Zo(e):e;if(i!=null&&i.id)if(E_.includes(i.type)){var a=J?.querySelector(t);if(a)return(r=i.appearance)!=null&&r.surveyPopupDelaySeconds?(S_.info(`Rendering survey `+i.id+` with delay of `+i.appearance.surveyPopupDelaySeconds+` seconds`),void setTimeout((()=>{var e;S_.info(`Rendering survey `+i.id+` with delay of `+i.appearance?.surveyPopupDelaySeconds+` seconds`),(e=this._surveyManager)==null||e.renderSurvey(i,a,n),S_.info(`Survey `+i.id+` rendered`)}),1e3*i.appearance.surveyPopupDelaySeconds)):void this._surveyManager.renderSurvey(i,a,n);S_.warn(`Survey element not found`)}else S_.warn(`Surveys of type `+i.type+` cannot be rendered in the app`);else S_.warn(`Survey not found`)}}displaySurvey(e,t){var n;if(sf(this._surveyManager))S_.warn(`init was not called`);else{var r=this.Zo(e);if(r){var i=r;if((n=r.appearance)!=null&&n.surveyPopupDelaySeconds&&t.ignoreDelay&&(i=X({},r,{appearance:X({},r.appearance,{surveyPopupDelaySeconds:0})})),t.displayType!==sg.Popover&&t.initialResponses&&S_.warn(`initialResponses is only supported for popover surveys. prefill will not be applied.`),!1===t.ignoreConditions){var a=this.canRenderSurvey(r);if(!a.visible)return void S_.warn(`Survey is not eligible to be displayed: `,a.disabledReason)}t.displayType===sg.Inline?this.renderSurvey(i,t.selector,t.properties):this._surveyManager.handlePopoverSurvey(i,t)}else S_.warn(`Survey not found`)}}cancelPendingSurvey(e){sf(this._surveyManager)?S_.warn(`init was not called`):this._surveyManager.cancelSurvey(e)}handlePageUnload(){var e;(e=this._surveyManager)==null||e.handlePageUnload()}}},yy),Dy={toolbar:class{constructor(e){this.instance=e}Ho(e){Y.ph_toolbar_state=e}Vo(){return Y.ph_toolbar_state??0}initialize(){return this.maybeLoadToolbar()}maybeLoadToolbar(e,t,n){if(e===void 0&&(e=void 0),t===void 0&&(t=void 0),n===void 0&&(n=void 0),ch(this.instance.config)||!q||!J)return!1;e??=q.location,n??=q.history;try{if(!t){try{q.localStorage.setItem(`test`,`test`),q.localStorage.removeItem(`test`)}catch{return!1}t=q?.localStorage}var r,i=Qv||zh(e.hash,`__posthog`)||zh(e.hash,`state`),a=i?nh((()=>JSON.parse(atob(decodeURIComponent(i)))))||nh((()=>JSON.parse(decodeURIComponent(i)))):null;return a&&a.action===`ph_authorize`?((r=a).source=`url`,r&&Object.keys(r).length>0&&(a.desiredHash?e.hash=a.desiredHash:n?n.replaceState(n.state,``,e.pathname+e.search):e.hash=``)):((r=JSON.parse(t.getItem($v)||`{}`)).source=`localstorage`,delete r.userIntent),!(!r.token||this.instance.config.token!==r.token||(this.loadToolbar(r),0))}catch{return!1}}zo(e){var t=Y.ph_load_toolbar||Y.ph_load_editor;!sf(t)&&ef(t)?t(e,this.instance):ey.warn(`No toolbar load function found`)}loadToolbar(e){var t=!(J==null||!J.getElementById(Lm));if(!q||t)return!1;var n=this.instance.requestRouter.region===`custom`&&this.instance.config.advanced_disable_toolbar_metrics,r=X({token:this.instance.config.token},e,{apiURL:this.instance.requestRouter.endpointFor(`ui`)},n?{instrument:!1}:{});if(q.localStorage.setItem($v,JSON.stringify(X({},r,{source:void 0}))),this.Vo()===2)this.zo(r);else if(this.Vo()===0){var i;this.Ho(1),(i=Y.__PosthogExtensions__)==null||i.loadExternalDependency==null||i.loadExternalDependency(this.instance,`toolbar`,(e=>{if(e)return ey.error(`[Toolbar] Failed to load`,e),void this.Ho(0);this.Ho(2),this.zo(r)})),sh(q,`turbolinks:load`,(()=>{this.Ho(0),this.loadToolbar(r)}))}return!0}Uo(e){return this.loadToolbar(e)}maybeLoadEditor(e,t,n){return e===void 0&&(e=void 0),t===void 0&&(t=void 0),n===void 0&&(n=void 0),this.maybeLoadToolbar(e,t,n)}}},Oy=X({experiments:fy},yy),ky=X({},yy,by,xy,Sy,Cy,wy,Ey,Ty,Dy,Oy,{conversations:class{constructor(e){this.Yo=void 0,this._conversationsManager=null,this.Wo=!1,this.Go=null,this._instance=e}initialize(){this.loadIfEnabled()}onRemoteConfig(e){if(!this._instance.config.disable_conversations){var t=e.conversations;sf(t)||(uf(t)?this.Yo=t:(this.Yo=t.enabled,this.Go=t),this.loadIfEnabled())}}reset(){var e;(e=this._conversationsManager)==null||e.reset(),this._conversationsManager=null,this.Yo=void 0,this.Go=null}loadIfEnabled(){if(!(this._conversationsManager||this.Wo||this._instance.config.disable_conversations||ch(this._instance.config)||this._instance.config.cookieless_mode&&this._instance.consent.isOptedOut())){var e=Y?.__PosthogExtensions__;if(e&&!Z(this.Yo)&&this.Yo)if(this.Go&&this.Go.token){this.Wo=!0;try{var t=e.initConversations;if(t)return this.Xo(t),void(this.Wo=!1);var n=e.loadExternalDependency;if(!n)return void this.Jo(Bm);n(this._instance,`conversations`,(t=>{t||!e.initConversations?this.Jo(`Could not load conversations script`,t):this.Xo(e.initConversations),this.Wo=!1}))}catch(e){this.Jo(`Error initializing conversations`,e),this.Wo=!1}}else py.error(`Conversations enabled but missing token in remote config.`)}}Xo(e){if(this.Go)try{this._conversationsManager=e(this.Go,this._instance),py.info(`Conversations loaded successfully`)}catch(e){this.Jo(`Error completing conversations initialization`,e)}else py.error(`Cannot complete initialization: remote config is null`)}Jo(e,t){py.error(e,t),this._conversationsManager=null,this.Wo=!1}show(){this._conversationsManager?this._conversationsManager.show():py.warn(`Conversations not loaded yet.`)}hide(){this._conversationsManager&&this._conversationsManager.hide()}isAvailable(){return!0===this.Yo&&!of(this._conversationsManager)}isVisible(){var e;return(e=this._conversationsManager?.isVisible())!=null&&e}sendMessage(e,t,n){var r=this;return Vd((function*(){return r._conversationsManager?r._conversationsManager.sendMessage(e,t,n):(py.warn(my),null)}))()}getMessages(e,t){var n=this;return Vd((function*(){return n._conversationsManager?n._conversationsManager.getMessages(e,t):(py.warn(my),null)}))()}markAsRead(e){var t=this;return Vd((function*(){return t._conversationsManager?t._conversationsManager.markAsRead(e):(py.warn(my),null)}))()}getTickets(e){var t=this;return Vd((function*(){return t._conversationsManager?t._conversationsManager.getTickets(e):(py.warn(my),null)}))()}requestRestoreLink(e){var t=this;return Vd((function*(){return t._conversationsManager?t._conversationsManager.requestRestoreLink(e):(py.warn(my),null)}))()}restoreFromToken(e){var t=this;return Vd((function*(){return t._conversationsManager?t._conversationsManager.restoreFromToken(e):(py.warn(my),null)}))()}restoreFromUrlToken(){var e=this;return Vd((function*(){return e._conversationsManager?e._conversationsManager.restoreFromUrlToken():(py.warn(my),null)}))()}getCurrentTicketId(){return this._conversationsManager?.getCurrentTicketId()??null}getWidgetSessionId(){return this._conversationsManager?.getWidgetSessionId()??null}sn(){var e;(e=this._conversationsManager)==null||e.setIdentity()}an(){var e;(e=this._conversationsManager)==null||e.clearIdentity()}}},{logs:class{constructor(e){var t;this.Ko=!1,this.Qo=!1,this.qt=Qp(`[logs]`),this.ta=[],this.ea=0,this.ia=0,this.ra=!1,this._instance=e,this._instance&&(t=this._instance.config.logs)!=null&&t.captureConsoleLogs&&(this.Ko=!0)}initialize(){this.loadIfEnabled()}onRemoteConfig(e){var t=e.logs?.captureConsoleLogs;!sf(t)&&t&&(this.Ko=!0,this.loadIfEnabled())}reset(){this.ta=[],this.Oi&&=(clearTimeout(this.Oi),void 0),this.ea=0,this.ia=0,this.ra=!1}loadIfEnabled(){if(this.Ko&&!this.Qo){var e=Y?.__PosthogExtensions__;if(e){var t=e.loadExternalDependency;t?t(this._instance,`logs`,(t=>{var n;t||(n=e.logs)==null||!n.initializeLogs?this.qt.error(`Could not load logs script`,t):(e.logs.initializeLogs(this._instance),this.Qo=!0)})):this.qt.error(Bm)}else this.qt.error(`PostHog Extensions not found.`)}}captureLog(e){if(this._instance.is_capturing())if(e&&e.body){var t=this._instance.config.logs?.flushIntervalMs??3e3,n=this._instance.config.logs?.maxLogsPerInterval??1e3,r=Date.now();if(t>r-this.ia||(this.ia=r,this.ea=0,this.ra=!1),n>this.ea){this.ea++;var i=function(e,t){var{text:n,number:r}=hy[e.level||`info`]||gy,i=String(Date.now())+`000000`,a={};t.distinctId&&(a.posthogDistinctId=t.distinctId),t.sessionId&&(a.sessionId=t.sessionId),t.currentUrl&&(a[`url.full`]=t.currentUrl),t.activeFeatureFlags&&t.activeFeatureFlags.length>0&&(a.feature_flags=t.activeFeatureFlags);var o=X({},a,e.attributes||{}),s={timeUnixNano:i,observedTimeUnixNano:i,severityNumber:r,severityText:n,body:{stringValue:e.body},attributes:vy(o)};return e.trace_id&&(s.traceId=e.trace_id),e.span_id&&(s.spanId=e.span_id),Z(e.trace_flags)||(s.flags=e.trace_flags),s}(e,this.na());this.ta.push({record:i}),(this._instance.config.logs?.maxBufferSize??100)>this.ta.length?this.sa():this.flushLogs()}else this.ra||=(this.qt.warn(`captureLog dropping logs: exceeded `+n+` logs per `+t+`ms`),!0)}else this.qt.warn(`captureLog requires a body`)}get logger(){return this.oa||={trace:(e,t)=>this.captureLog({body:e,level:`trace`,attributes:t}),debug:(e,t)=>this.captureLog({body:e,level:`debug`,attributes:t}),info:(e,t)=>this.captureLog({body:e,level:`info`,attributes:t}),warn:(e,t)=>this.captureLog({body:e,level:`warn`,attributes:t}),error:(e,t)=>this.captureLog({body:e,level:`error`,attributes:t}),fatal:(e,t)=>this.captureLog({body:e,level:`fatal`,attributes:t})},this.oa}flushLogs(e){if(this.Oi&&=(clearTimeout(this.Oi),void 0),this.ta.length!==0){var t=this.ta;this.ta=[];var n=this._instance.config.logs,r=X({"service.name":n?.serviceName||`unknown_service`},n?.environment&&{"deployment.environment":n.environment},n?.serviceVersion&&{"service.version":n.serviceVersion},n?.resourceAttributes),i=function(e,t){return{resourceLogs:[{resource:{attributes:vy(t)},scopeLogs:[{scope:{name:zd.LIB_NAME},logRecords:e}]}]}}(t.map((e=>e.record)),r),a=this._instance.requestRouter.endpointFor(`api`,`/i/v1/logs`)+`?token=`+encodeURIComponent(this._instance.config.token);this._instance.qr({method:`POST`,url:a,data:i,compression:`best-available`,batchKey:`logs`,transport:e})}}sa(){this.Oi||=setTimeout((()=>{this.Oi=void 0,this.flushLogs()}),this._instance.config.logs?.flushIntervalMs??3e3)}na(){var e,t={lib:zd.LIB_NAME};if(t.distinctId=this._instance.get_distinct_id(),this._instance.sessionManager){var{sessionId:n}=this._instance.sessionManager.checkAndGetSessionAndWindowId(!0);t.sessionId=n}if(Y!=null&&(e=Y.location)!=null&&e.href&&(t.currentUrl=Y.location.href),this._instance.featureFlags){var r=this._instance.featureFlags.getFlags();r&&r.length>0&&(t.activeFeatureFlags=r)}return t}}});G_.__defaultExtensionClasses=X({},ky);var Ay,jy=(Ay=M_[z_]=new G_,function(){function e(){e.done||(e.done=!0,B_=!1,$m(M_,(function(e){e._dom_loaded()})))}J!=null&&J.addEventListener?J.readyState===`complete`?e():sh(J,`DOMContentLoaded`,e,{capture:!1}):q&&Q.error("Browser doesn't support `document.addEventListener` so PostHog couldn't be initialized")}(),Ay),My=!1,Ny=!1;function Py(){if(typeof navigator>`u`)return!1;let e=navigator.doNotTrack;if(e===`1`||e===`yes`||navigator.msDoNotTrack===`1`)return!0;let t=(typeof window<`u`?window:null)?.doNotTrack;return t===`1`||t===`yes`}function Fy(){return Py()||Ry()?!1:Ny}var Iy=[/^\/connect\/bot\/[^/]+/,/^\/login\/(?:device|agent-key|code)(?:\/|$)/,/\/verify-email\/[^/]+/,/\/reset-password\/[^/]+/,/\/oauth\/callback/,/^\/oauth-complete$/,/\/approve\/[^/]+/];function Ly(e){return Iy.some(t=>t.test(e))}function Ry(){return typeof window<`u`&&Ly(window.location.pathname)}function zy(e){Fy()&&e&&jy.identify(e)}function By(){My&&jy.reset()}var Vy=null,Hy=new Set;function Uy(){return Vy}function Wy(e){if(e===Vy)return;let t=Vy;Vy=e;for(let n of Hy)n(e,t)}function Gy(e){return Hy.add(e),()=>Hy.delete(e)}var Ky=Ot((e,t)=>({current:null,seenKeys:new Set,notify:n=>{if(!n.actorId||n.actorId!==Uy())return;let{current:r,seenKeys:i}=t();i.has(n.key)||e({current:r??n,seenKeys:new Set(i).add(n.key)})},dismiss:()=>e({current:null}),reset:()=>e({current:null,seenKeys:new Set})}));Gy(()=>Ky.getState().reset());var qy=`insufficient_credits`,Jy=11300;function Yy(e){return typeof e==`object`&&!!e}function Xy(e,t){return e!==402||!Yy(t)?!1:t.error!==void 0&&t.error!==null?t.error===qy:t.error_code===Jy}function Zy(){return Uy()}function Qy(e,t){Ky.getState().notify({...e,actorId:t})}function $y(e,t,n,r){e&&Xy(t,n)&&Qy(e,r)}var eb=`modulepreload`,tb=function(e,t){return new URL(e,t).href},nb={},rb=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=tb(t,n),t in nb)return;nb[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:eb,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},ib=c({hardRedirect:()=>ob,openExternal:()=>ab});function ab(e){window.location.assign(e)}function ob(e){window.location.href=e}var sb=o((()=>{}));function cb(e,t=``){return`${t.replace(/\/+$/,``)}/api/v1${e}`}var lb=class extends Error{status;errorCode;errorResponse;constructor(e,t){super(t.message),this.name=`ApiError`,this.status=e,this.errorCode=t.error_code,this.errorResponse=t}},ub=new Set([`/auth/login`,`/auth/register`,`/auth/refresh`,`/auth/forgot-password`,`/auth/reset-password`,`/auth/verify-email`,`/auth/setup`]);function db(e){let{method:t=`GET`,body:n,headers:r={},signal:i}=e,a={method:t,headers:{"Content-Type":`application/json`,...Fy()?{"X-NyxID-Client":`ui`}:{},...r},credentials:e.credentials??`include`,signal:i};return n!==void 0&&(a.body=JSON.stringify(n)),a}async function fb(e){try{return await e.json()}catch{return{error:`unknown_error`,error_code:-1,message:`Request failed with status ${String(e.status)}`}}}function pb(e){if(!(e.error!==`consent_required`||!e.consent_url)&&typeof window<`u`){let t=e.consent_url;rb(async()=>{let{openExternal:e}=await Promise.resolve().then(()=>(sb(),ib));return{openExternal:e}},void 0,import.meta.url).then(({openExternal:e})=>e(t))}}async function mb(e,t={}){let n=await hb(e,t);if(n.status!==204)return n.json()}async function hb(e,t={}){let n=t.creditsDenial?Zy():null,r=await fetch(cb(e,t.apiBaseUrl),db(t));try{t.onResponse?.(r)}catch{}if(r.status===401&&!t.preserveSessionOn401&&!ub.has(e)&&eC.getState().setUser(null),!r.ok){let e=await fb(r);throw pb(e),$y(t.creditsDenial,r.status,e,n),new lb(r.status,e)}return r}var gb={get(e){return mb(e)},post(e,t){return mb(e,{method:`POST`,body:t})},put(e,t){return mb(e,{method:`PUT`,body:t})},patch(e,t,n){return mb(e,{method:`PATCH`,body:t,signal:n?.signal})},delete(e){return mb(e,{method:`DELETE`})}},_b=`nyxid.assistant_context`,vb={ownerUserId:null,lastScreen:null};function yb(e){if(typeof e!=`object`||!e)return vb;let t=e;return{ownerUserId:typeof t.ownerUserId==`string`?t.ownerUserId:null,lastScreen:typeof t.lastScreen==`string`?t.lastScreen:null}}var bb=Ot()(jt(e=>({...vb,recordScreen:(t,n)=>{e(e=>e.ownerUserId===t?{lastScreen:n}:{...vb,ownerUserId:t,lastScreen:n})},clear:()=>{e(vb),typeof localStorage<`u`&&localStorage.removeItem(_b)}}),{name:_b,version:2,migrate:yb,partialize:({ownerUserId:e,lastScreen:t})=>({ownerUserId:e,lastScreen:t})})),xb=`nyxid.assistant_drafts`,Sb=50,Cb={ownerUserId:null,drafts:{}};function wb(e,t){return Object.fromEntries(Object.entries(e).sort(([e,n],[r,i])=>{let a=i.updatedAt-n.updatedAt;return a===0?e===t?-1:+(r===t):a}).slice(0,Sb))}var Tb=Ot()(jt((e,t)=>({...Cb,saveDraft:(t,n,r)=>{e(e=>{let i={...e.ownerUserId===t?e.drafts:Cb.drafts};return r.trim()?i[n]={text:r,updatedAt:Date.now()}:delete i[n],{ownerUserId:t,drafts:wb(i,n)}})},getDraft:e=>t().drafts[e]?.text??``,clearDraft:(t,n)=>{e(e=>{let r=e.ownerUserId===t?{...e.drafts}:{};return delete r[n],{ownerUserId:t,drafts:r}})},pruneConversationDrafts:t=>{let n=new Set(t);e(e=>({drafts:Object.fromEntries(Object.entries(e.drafts).filter(([e])=>e.startsWith(`conv:`)?n.has(e.slice(5)):!0))}))},clear:()=>{e(Cb),typeof localStorage<`u`&&localStorage.removeItem(xb)}}),{name:xb,version:1,partialize:({ownerUserId:e,drafts:t})=>({ownerUserId:e,drafts:t})})),Eb=G(`ZodISODateTime`,(e,t)=>{Ic.init(e,t),Zb.init(e,t)});function Db(e){return cu(Eb,e)}var Ob=G(`ZodISODate`,(e,t)=>{Lc.init(e,t),Zb.init(e,t)});function kb(e){return lu(Ob,e)}var Ab=G(`ZodISOTime`,(e,t)=>{Rc.init(e,t),Zb.init(e,t)});function jb(e){return uu(Ab,e)}var Mb=G(`ZodISODuration`,(e,t)=>{zc.init(e,t),Zb.init(e,t)});function Nb(e){return du(Mb,e)}var Pb=(e,t)=>{ss.init(e,t),e.name=`ZodError`,Object.defineProperties(e,{format:{value:t=>us(e,t)},flatten:{value:t=>ls(e,t)},addIssue:{value:t=>{e.issues.push(t),e.message=JSON.stringify(e.issues,Eo,2)}},addIssues:{value:t=>{e.issues.push(...t),e.message=JSON.stringify(e.issues,Eo,2)}},isEmpty:{get(){return e.issues.length===0}}})};G(`ZodError`,Pb);var Fb=G(`ZodError`,Pb,{Parent:Error}),Ib=ds(Fb),Lb=ps(Fb),Rb=hs(Fb),zb=_s(Fb),Bb=ys(Fb),Vb=bs(Fb),Hb=xs(Fb),Ub=Ss(Fb),Wb=Cs(Fb),Gb=ws(Fb),Kb=Ts(Fb),qb=Es(Fb),Jb=G(`ZodType`,(e,t)=>(Sc.init(e,t),Object.assign(e[`~standard`],{jsonSchema:{input:qu(e,`input`),output:qu(e,`output`)}}),e.toJSONSchema=Ku(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,`_def`,{value:t}),e.check=(...n)=>e.clone(Po(t,{checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),{parent:!0}),e.with=e.check,e.clone=(t,n)=>Wo(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.parse=(t,n)=>Ib(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>Rb(e,t,n),e.parseAsync=async(t,n)=>Lb(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>zb(e,t,n),e.spa=e.safeParseAsync,e.encode=(t,n)=>Bb(e,t,n),e.decode=(t,n)=>Vb(e,t,n),e.encodeAsync=async(t,n)=>Hb(e,t,n),e.decodeAsync=async(t,n)=>Ub(e,t,n),e.safeEncode=(t,n)=>Wb(e,t,n),e.safeDecode=(t,n)=>Gb(e,t,n),e.safeEncodeAsync=async(t,n)=>Kb(e,t,n),e.safeDecodeAsync=async(t,n)=>qb(e,t,n),e.refine=(t,n)=>e.check(dS(t,n)),e.superRefine=t=>e.check(fS(t)),e.overwrite=t=>e.check(ju(t)),e.optional=()=>qx(e),e.exactOptional=()=>Yx(e),e.nullable=()=>Zx(e),e.nullish=()=>qx(Zx(e)),e.nonoptional=t=>rS(e,t),e.array=()=>kx(e),e.or=t=>Nx([e,t]),e.and=t=>Lx(e,t),e.transform=t=>sS(e,Gx(t)),e.default=t=>$x(e,t),e.prefault=t=>tS(e,t),e.catch=t=>aS(e,t),e.pipe=t=>sS(e,t),e.readonly=()=>lS(e),e.describe=t=>{let n=e.clone();return Rl.add(n,{description:t}),n},Object.defineProperty(e,`description`,{get(){return Rl.get(e)?.description},configurable:!0}),e.meta=(...t)=>{if(t.length===0)return Rl.get(e);let n=e.clone();return Rl.add(n,t[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e.apply=t=>t(e),e)),Yb=G(`_ZodString`,(e,t)=>{Cc.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Yu(e,t,n,r);let n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...t)=>e.check(Tu(...t)),e.includes=(...t)=>e.check(Ou(...t)),e.startsWith=(...t)=>e.check(ku(...t)),e.endsWith=(...t)=>e.check(Au(...t)),e.min=(...t)=>e.check(Cu(...t)),e.max=(...t)=>e.check(Su(...t)),e.length=(...t)=>e.check(wu(...t)),e.nonempty=(...t)=>e.check(Cu(1,...t)),e.lowercase=t=>e.check(Eu(t)),e.uppercase=t=>e.check(Du(t)),e.trim=()=>e.check(Nu()),e.normalize=(...t)=>e.check(Mu(...t)),e.toLowerCase=()=>e.check(Pu()),e.toUpperCase=()=>e.check(Fu()),e.slugify=()=>e.check(Iu())}),Xb=G(`ZodString`,(e,t)=>{Cc.init(e,t),Yb.init(e,t),e.email=t=>e.check(Bl(Qb,t)),e.url=t=>e.check(Kl(nx,t)),e.jwt=t=>e.check(su(_x,t)),e.emoji=t=>e.check(ql(rx,t)),e.guid=t=>e.check(Vl($b,t)),e.uuid=t=>e.check(Hl(ex,t)),e.uuidv4=t=>e.check(Ul(ex,t)),e.uuidv6=t=>e.check(Wl(ex,t)),e.uuidv7=t=>e.check(Gl(ex,t)),e.nanoid=t=>e.check(Jl(ix,t)),e.guid=t=>e.check(Vl($b,t)),e.cuid=t=>e.check(Yl(ax,t)),e.cuid2=t=>e.check(Xl(ox,t)),e.ulid=t=>e.check(Zl(sx,t)),e.base64=t=>e.check(iu(mx,t)),e.base64url=t=>e.check(au(hx,t)),e.xid=t=>e.check(Ql(cx,t)),e.ksuid=t=>e.check($l(lx,t)),e.ipv4=t=>e.check(eu(ux,t)),e.ipv6=t=>e.check(tu(dx,t)),e.cidrv4=t=>e.check(nu(fx,t)),e.cidrv6=t=>e.check(ru(px,t)),e.e164=t=>e.check(ou(gx,t)),e.datetime=t=>e.check(Db(t)),e.date=t=>e.check(kb(t)),e.time=t=>e.check(jb(t)),e.duration=t=>e.check(Nb(t))});function $(e){return zl(Xb,e)}var Zb=G(`ZodStringFormat`,(e,t)=>{wc.init(e,t),Yb.init(e,t)}),Qb=G(`ZodEmail`,(e,t)=>{Dc.init(e,t),Zb.init(e,t)}),$b=G(`ZodGUID`,(e,t)=>{Tc.init(e,t),Zb.init(e,t)}),ex=G(`ZodUUID`,(e,t)=>{Ec.init(e,t),Zb.init(e,t)});function tx(e){return Hl(ex,e)}var nx=G(`ZodURL`,(e,t)=>{Oc.init(e,t),Zb.init(e,t)}),rx=G(`ZodEmoji`,(e,t)=>{kc.init(e,t),Zb.init(e,t)}),ix=G(`ZodNanoID`,(e,t)=>{Ac.init(e,t),Zb.init(e,t)}),ax=G(`ZodCUID`,(e,t)=>{jc.init(e,t),Zb.init(e,t)}),ox=G(`ZodCUID2`,(e,t)=>{Mc.init(e,t),Zb.init(e,t)}),sx=G(`ZodULID`,(e,t)=>{Nc.init(e,t),Zb.init(e,t)}),cx=G(`ZodXID`,(e,t)=>{Pc.init(e,t),Zb.init(e,t)}),lx=G(`ZodKSUID`,(e,t)=>{Fc.init(e,t),Zb.init(e,t)}),ux=G(`ZodIPv4`,(e,t)=>{Bc.init(e,t),Zb.init(e,t)}),dx=G(`ZodIPv6`,(e,t)=>{Vc.init(e,t),Zb.init(e,t)}),fx=G(`ZodCIDRv4`,(e,t)=>{Hc.init(e,t),Zb.init(e,t)}),px=G(`ZodCIDRv6`,(e,t)=>{Uc.init(e,t),Zb.init(e,t)}),mx=G(`ZodBase64`,(e,t)=>{Gc.init(e,t),Zb.init(e,t)}),hx=G(`ZodBase64URL`,(e,t)=>{qc.init(e,t),Zb.init(e,t)}),gx=G(`ZodE164`,(e,t)=>{Jc.init(e,t),Zb.init(e,t)}),_x=G(`ZodJWT`,(e,t)=>{Xc.init(e,t),Zb.init(e,t)}),vx=G(`ZodNumber`,(e,t)=>{Zc.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Xu(e,t,n,r),e.gt=(t,n)=>e.check(yu(t,n)),e.gte=(t,n)=>e.check(bu(t,n)),e.min=(t,n)=>e.check(bu(t,n)),e.lt=(t,n)=>e.check(_u(t,n)),e.lte=(t,n)=>e.check(vu(t,n)),e.max=(t,n)=>e.check(vu(t,n)),e.int=t=>e.check(xx(t)),e.safe=t=>e.check(xx(t)),e.positive=t=>e.check(yu(0,t)),e.nonnegative=t=>e.check(bu(0,t)),e.negative=t=>e.check(_u(0,t)),e.nonpositive=t=>e.check(vu(0,t)),e.multipleOf=(t,n)=>e.check(xu(t,n)),e.step=(t,n)=>e.check(xu(t,n)),e.finite=()=>e;let n=e._zod.bag;e.minValue=Math.max(n.minimum??-1/0,n.exclusiveMinimum??-1/0)??null,e.maxValue=Math.min(n.maximum??1/0,n.exclusiveMaximum??1/0)??null,e.isInt=(n.format??``).includes(`int`)||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null});function yx(e){return fu(vx,e)}var bx=G(`ZodNumberFormat`,(e,t)=>{Qc.init(e,t),vx.init(e,t)});function xx(e){return pu(bx,e)}var Sx=G(`ZodBoolean`,(e,t)=>{$c.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Zu(e,t,n,r)});function Cx(e){return mu(Sx,e)}var wx=G(`ZodUnknown`,(e,t)=>{el.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(e,t,n)=>void 0});function Tx(){return hu(wx)}var Ex=G(`ZodNever`,(e,t)=>{tl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Qu(e,t,n,r)});function Dx(e){return gu(Ex,e)}var Ox=G(`ZodArray`,(e,t)=>{rl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>rd(e,t,n,r),e.element=t.element,e.min=(t,n)=>e.check(Cu(t,n)),e.nonempty=t=>e.check(Cu(1,t)),e.max=(t,n)=>e.check(Su(t,n)),e.length=(t,n)=>e.check(wu(t,n)),e.unwrap=()=>e.element});function kx(e,t){return Lu(Ox,e,t)}var Ax=G(`ZodObject`,(e,t)=>{cl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>id(e,t,n,r),Mo(e,`shape`,()=>t.shape),e.keyof=()=>Vx(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:Tx()}),e.loose=()=>e.clone({...e._zod.def,catchall:Tx()}),e.strict=()=>e.clone({...e._zod.def,catchall:Dx()}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>Yo(e,t),e.safeExtend=t=>Xo(e,t),e.merge=t=>Zo(e,t),e.pick=t=>qo(e,t),e.omit=t=>Jo(e,t),e.partial=(...t)=>Qo(Kx,e,t[0]),e.required=(...t)=>$o(nS,e,t[0])});function jx(e,t){return new Ax({type:`object`,shape:e??{},...K(t)})}var Mx=G(`ZodUnion`,(e,t)=>{ul.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ad(e,t,n,r),e.options=t.options});function Nx(e,t){return new Mx({type:`union`,options:e,...K(t)})}var Px=G(`ZodDiscriminatedUnion`,(e,t)=>{Mx.init(e,t),dl.init(e,t)});function Fx(e,t,n){return new Px({type:`union`,options:t,discriminator:e,...K(n)})}var Ix=G(`ZodIntersection`,(e,t)=>{fl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>od(e,t,n,r)});function Lx(e,t){return new Ix({type:`intersection`,left:e,right:t})}var Rx=G(`ZodRecord`,(e,t)=>{hl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>sd(e,t,n,r),e.keyType=t.keyType,e.valueType=t.valueType});function zx(e,t,n){return new Rx({type:`record`,keyType:e,valueType:t,...K(n)})}var Bx=G(`ZodEnum`,(e,t)=>{gl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>$u(e,t,n,r),e.enum=t.entries,e.options=Object.values(t.entries);let n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{let i={};for(let r of e)if(n.has(r))i[r]=t.entries[r];else throw Error(`Key ${r} not found in enum`);return new Bx({...t,checks:[],...K(r),entries:i})},e.exclude=(e,r)=>{let i={...t.entries};for(let t of e)if(n.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new Bx({...t,checks:[],...K(r),entries:i})}});function Vx(e,t){return new Bx({type:`enum`,entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...K(t)})}var Hx=G(`ZodLiteral`,(e,t)=>{_l.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ed(e,t,n,r),e.values=new Set(t.values),Object.defineProperty(e,`value`,{get(){if(t.values.length>1)throw Error("This schema contains multiple valid literal values. Use `.values` instead.");return t.values[0]}})});function Ux(e,t){return new Hx({type:`literal`,values:Array.isArray(e)?e:[e],...K(t)})}var Wx=G(`ZodTransform`,(e,t)=>{vl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>nd(e,t,n,r),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new So(e.constructor.name);n.addIssue=r=>{if(typeof r==`string`)n.issues.push(as(r,n.value,t));else{let t=r;t.fatal&&(t.continue=!1),t.code??=`custom`,t.input??=n.value,t.inst??=e,n.issues.push(as(t))}};let i=t.transform(n.value,n);return i instanceof Promise?i.then(e=>(n.value=e,n)):(n.value=i,n)}});function Gx(e){return new Wx({type:`transform`,transform:e})}var Kx=G(`ZodOptional`,(e,t)=>{bl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>hd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function qx(e){return new Kx({type:`optional`,innerType:e})}var Jx=G(`ZodExactOptional`,(e,t)=>{xl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>hd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Yx(e){return new Jx({type:`optional`,innerType:e})}var Xx=G(`ZodNullable`,(e,t)=>{Sl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>cd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Zx(e){return new Xx({type:`nullable`,innerType:e})}var Qx=G(`ZodDefault`,(e,t)=>{Cl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ud(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function $x(e,t){return new Qx({type:`default`,innerType:e,get defaultValue(){return typeof t==`function`?t():Vo(t)}})}var eS=G(`ZodPrefault`,(e,t)=>{Tl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>dd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function tS(e,t){return new eS({type:`prefault`,innerType:e,get defaultValue(){return typeof t==`function`?t():Vo(t)}})}var nS=G(`ZodNonOptional`,(e,t)=>{El.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ld(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function rS(e,t){return new nS({type:`nonoptional`,innerType:e,...K(t)})}var iS=G(`ZodCatch`,(e,t)=>{Ol.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>fd(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function aS(e,t){return new iS({type:`catch`,innerType:e,catchValue:typeof t==`function`?t:()=>t})}var oS=G(`ZodPipe`,(e,t)=>{kl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>pd(e,t,n,r),e.in=t.in,e.out=t.out});function sS(e,t){return new oS({type:`pipe`,in:e,out:t})}var cS=G(`ZodReadonly`,(e,t)=>{jl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>md(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function lS(e){return new cS({type:`readonly`,innerType:e})}var uS=G(`ZodCustom`,(e,t)=>{Nl.init(e,t),Jb.init(e,t),e._zod.processJSONSchema=(t,n,r)=>td(e,t,n,r)});function dS(e,t={}){return Ru(uS,e,t)}function fS(e){return zu(e)}var pS=Vx([`response`,`no_response`,`unknown`]),mS=jx({mode:$(),forward_access_token:Cx(),inject_delegation_token:Cx(),bridge_minted:Cx()}).strict(),hS=jx({value:$(),truncated:Cx()}).strict(),gS=jx({status:yx().int().min(100).max(599),headers:zx($(),hS),sse:Cx()}).strict(),_S=Fx(`degraded`,[jx({degraded:Ux(!1),method:$(),path:$(),commandType:$().nullable(),body:Tx(),headers:zx($(),$()),identity:mS,truncated:Cx(),response:gS.nullable().optional(),upstreamOutcome:pS.optional(),droppedBody:Cx().optional(),droppedHeaders:Cx().optional()}).strict(),jx({degraded:Ux(!0),method:$(),path:$(),commandType:$().optional(),upstreamOutcome:pS.optional(),status:yx().int().min(100).max(599).optional()}).strict()]),vS=jx({version:Ux(2),echoes:kx(_S).min(1),droppedEchoCount:yx().int().nonnegative()}).strict();jx({id:$(),conversation_id:$().nullable(),created_at:$(),payload:vS}).strict(),Nx([vS,kx(jx({method:$(),path:$(),commandType:$().nullable(),body:Tx(),headers:zx($(),$()),identity:mS.strip(),truncated:Cx()})).min(1)]).transform(e=>Array.isArray(e)?{version:2,echoes:e.map(e=>({...e,degraded:!1})),droppedEchoCount:0}:e);var yS=jx({text:$(),ending:Vx([` `,`\r -`,`\r`,``])}).strict(),yS=Bx([`complete`,`cancelled`,`network_error`,`worker_error`,`protocol_cancel`]),bS={transportOutcome:$().trim().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional(),framesSeen:vx().int().nonnegative().optional(),printableFramesSeen:vx().int().nonnegative().optional(),printableTurnEvents:vx().int().nonnegative().optional(),wireBytes:vx().int().nonnegative().optional(),terminalReceived:Sx().optional(),firstFrameMs:vx().finite().nonnegative().nullable().optional(),lastFrameMs:vx().finite().nonnegative().nullable().optional()},xS=Ax({lines:Ox(vS),bytes:vx().int().nonnegative(),retainedBytes:vx().int().nonnegative(),truncated:Sx()}).strict(),SS=Ax({text:$(),bytes:vx().int().nonnegative(),truncated:Sx()}).strict(),CS=Ax({state:Hx(`open`),sse:xS.optional(),body:SS.optional(),...bS}).strict(),wS=Ax({state:Hx(`settled`),outcome:yS,wireOutcome:yS,sse:xS.optional(),body:SS.optional(),...bS}).strict(),TS=Px(`state`,[Ax({state:Hx(`evicted`)}).strict(),CS,wS]),ES=Ax({id:$(),ts:vx().finite(),kind:Bx([`sse`,`header`]),status:vx().int().min(100).max(599),conversationId:$().nullable(),wireLogId:$().nullable(),label:$(),upstreamEchoes:Ox(gS).optional(),droppedEchoCount:vx().int().nonnegative().optional()}).strict();ES.extend({capture:TS.optional()}).strict();var DS=ES,OS=Ax({captureEnabled:Sx(),showResponses:Sx(),entries:Ox(DS)}).strict(),kS=`nyxid.assistant.wirelog.v1`,AS=100,jS=2*1024*1024,MS=4*1024*1024,NS=new TextEncoder,PS={captureEnabled:!1,showResponses:!0,entries:[]},FS={featureEnabled:!1,...PS,entries:[],totalBytes:0,captureBytes:0};function IS(e){return{id:e.id,ts:e.ts,kind:e.kind,status:e.status,conversationId:e.conversationId,wireLogId:e.wireLogId,label:e.label,...e.upstreamEchoes===void 0?{}:{upstreamEchoes:e.upstreamEchoes},...e.droppedEchoCount===void 0?{}:{droppedEchoCount:e.droppedEchoCount}}}function LS(e){return NS.encode(JSON.stringify(IS(e))).byteLength}function RS(e){return e.reduce((e,t)=>e+LS(t),0)}function zS(e){return!e||e.state===`evicted`?0:(e.sse?.retainedBytes??0)+(e.body?NS.encode(e.body.text).byteLength:0)}function BS(e){return e.reduce((e,t)=>e+zS(t.capture),0)}function VS(e){let t=e.slice(Math.max(0,e.length-AS)),n=RS(t);for(;t.length>0&&n>jS;)t=t.slice(1),n=RS(t);return{entries:t,totalBytes:n,captureBytes:BS(t)}}function HS(e){let t=BS(e);if(t<=MS)return{entries:e,captureBytes:t};let n=[...e];for(let e=0;ee+NS.encode(`${t.text}${t.ending}`).byteLength,0)}function WS(e,t,n){let r=e.findIndex(e=>e.id===t);if(r<0)return null;let i=e[r];if(!i?.capture||i.capture.state===`evicted`)return null;let a=n(i.capture);if(!a)return null;let o=[...e];return o[r]={...i,capture:a},o}function GS(e){return e instanceof DOMException&&(e.name===`QuotaExceededError`||e.name===`NS_ERROR_DOM_QUOTA_REACHED`)}var KS={getItem:e=>localStorage.getItem(e),removeItem:e=>localStorage.removeItem(e),setItem:(e,t)=>{try{localStorage.setItem(e,t);return}catch(e){if(!GS(e))return}try{let n=JSON.parse(t),r=n.state?.entries;if(!Array.isArray(r)||r.length===0)return;r.shift(),localStorage.setItem(e,JSON.stringify(n))}catch{}}};function qS(){typeof localStorage<`u`&&localStorage.removeItem(kS)}function JS(e,t){if(t<3)return qS(),PS;let n=OS.safeParse(e);return n.success?n.data:(qS(),PS)}var YS=Ot()(jt((e,t)=>({...FS,setFeatureEnabled:t=>e({featureEnabled:t}),setCaptureEnabled:t=>e({captureEnabled:t}),setShowResponses:t=>e({showResponses:t}),recordExchange:n=>{let r=n.envelopes;if(!t().featureEnabled||!t().captureEnabled||!n.wireLogId&&(!r||r.length===0))return null;let i=crypto.randomUUID(),a={id:i,ts:Date.now(),kind:n.kind,status:n.status,conversationId:n.conversationId,wireLogId:n.wireLogId,label:n.label,...r===void 0?{}:{upstreamEchoes:[...r]},...n.droppedEchoCount&&n.droppedEchoCount>0?{droppedEchoCount:n.droppedEchoCount}:{},capture:{state:`open`}};return e(e=>VS([...e.entries,a])),i},assignConversation:(t,n)=>{e(e=>{let r=e.entries.findIndex(e=>e.id===t),i=e.entries[r];if(!i||i.conversationId===n)return e;let a=[...e.entries];return a[r]={...i,conversationId:n},VS(a)})},attachWireLines:(t,n,r,i=!1)=>{e(e=>{let a=WS(e.entries,t,e=>{let t=e.sse??{lines:[],bytes:0,retainedBytes:0,truncated:!1};return{...e,sse:{lines:[...t.lines,...n],bytes:t.bytes+Math.max(0,r),retainedBytes:t.retainedBytes+US(n),truncated:t.truncated||i}}});return a?HS(a):e})},attachResponseBody:(t,n,r,i)=>{e(e=>{let a=WS(e.entries,t,e=>({...e,body:{text:n,bytes:Math.max(0,r),truncated:i}}));return a?HS(a):e})},finalizeCapture:(t,n)=>{e(e=>{let r=WS(e.entries,t,e=>({...e,state:`settled`,outcome:n,wireOutcome:n}));return r?{entries:r}:e})},attachTransportTelemetry:(t,n)=>{e(e=>{let r=WS(e.entries,t,e=>({...e,...n}));return r?{entries:r}:e})},clear:()=>e({entries:[],totalBytes:0,captureBytes:0}),reset:()=>{e(FS),qS()}}),{name:kS,version:3,storage:kt(()=>KS),migrate:JS,partialize:({captureEnabled:e,showResponses:t,entries:n})=>({captureEnabled:e,showResponses:t,entries:n.map(IS)}),merge:(e,t)=>{let n=OS.safeParse(e);if(!n.success)return qS(),t;let r=n.data.entries.map(e=>({...e}));return{...t,...n.data,entries:r,totalBytes:RS(r),captureBytes:0}}})),XS=2002;function ZS(){yb.getState().clear(),wb.getState().clear(),YS.getState().reset()}function QS(e,t){e?.id!==t?.id&&(e!==null&&ZS(),Uy(t?.id??null))}var $S=Ot((e,t)=>({user:null,isAuthenticated:!1,isLoading:!0,mfaRequired:!1,mfaToken:null,login:async(t,n)=>{try{let r=await hb.post(`/auth/login`,{email:t,password:n,client:`web`});return e({isAuthenticated:!0,mfaRequired:!1,mfaToken:null}),{mfaRequired:!1,response:r}}catch(t){if(t instanceof cb&&t.errorCode===XS)return e({mfaRequired:!0,mfaToken:t.errorResponse.session_token??null}),{mfaRequired:!0};throw t}},logout:async()=>{try{await hb.post(`/auth/logout`)}finally{zy(),ZS(),Uy(null),e({user:null,isAuthenticated:!1,mfaRequired:!1,mfaToken:null})}},checkAuth:async({ephemeral:n=!1}={})=>{e({isLoading:!0});try{let r=n?await pb(`/users/me`,{preserveSessionOn401:!0}):await hb.get(`/users/me`);QS(t().user,r),e({user:r,isAuthenticated:!0,isLoading:!1}),n||Ry(r.id)}catch(t){t instanceof cb&&t.status===401?(n||(zy(),ZS()),Uy(null),e({user:null,isAuthenticated:!1,isLoading:!1})):e({isLoading:!1})}},setUser:n=>{let r=t().user;n===null?(ZS(),Uy(null)):QS(r,n),e({user:n,isAuthenticated:n!==null}),n!==null&&Ry(n.id)},setMfaRequired:(t,n)=>{e({mfaRequired:t,mfaToken:n})},clearMfaState:()=>{e({mfaRequired:!1,mfaToken:null})}}));typeof window<`u`&&window.document&&window.document.createElement;function eC(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(e?.(r),n===!1||!r.defaultPrevented)return t?.(r)}}function tC(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,nC(i,...t)]}function nC(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function rC(e){let t=iC(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(oC);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function iC(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=cC(n),i=sC(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var aC=Symbol(`radix.slottable`);function oC(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===aC}function sC(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function cC(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var lC=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=rC(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function uC(e,t){e&&Fi.flushSync(()=>e.dispatchEvent(t))}function dC(e){let t=z.useRef(e);return z.useEffect(()=>{t.current=e}),z.useMemo(()=>(...e)=>t.current?.(...e),[])}function fC(e,t=globalThis?.document){let n=dC(e);z.useEffect(()=>{let e=e=>{e.key===`Escape`&&n(e)};return t.addEventListener(`keydown`,e,{capture:!0}),()=>t.removeEventListener(`keydown`,e,{capture:!0})},[n,t])}var pC=`DismissableLayer`,mC=`dismissableLayer.update`,hC=`dismissableLayer.pointerDownOutside`,gC=`dismissableLayer.focusOutside`,_C,vC=z.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),yC=z.forwardRef((e,t)=>{let{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:a,onInteractOutside:o,onDismiss:s,...c}=e,l=z.useContext(vC),[u,d]=z.useState(null),f=u?.ownerDocument??globalThis?.document,[,p]=z.useState({}),m=en(t,e=>d(e)),h=Array.from(l.layers),[g]=[...l.layersWithOutsidePointerEventsDisabled].slice(-1),_=h.indexOf(g),v=u?h.indexOf(u):-1,y=l.layersWithOutsidePointerEventsDisabled.size>0,b=v>=_,x=SC(e=>{let t=e.target,n=[...l.branches].some(e=>e.contains(t));!b||n||(i?.(e),o?.(e),e.defaultPrevented||s?.())},f),S=CC(e=>{let t=e.target;[...l.branches].some(e=>e.contains(t))||(a?.(e),o?.(e),e.defaultPrevented||s?.())},f);return fC(e=>{v===l.layers.size-1&&(r?.(e),!e.defaultPrevented&&s&&(e.preventDefault(),s()))},f),z.useEffect(()=>{if(u)return n&&(l.layersWithOutsidePointerEventsDisabled.size===0&&(_C=f.body.style.pointerEvents,f.body.style.pointerEvents=`none`),l.layersWithOutsidePointerEventsDisabled.add(u)),l.layers.add(u),wC(),()=>{n&&l.layersWithOutsidePointerEventsDisabled.size===1&&(f.body.style.pointerEvents=_C)}},[u,f,n,l]),z.useEffect(()=>()=>{u&&(l.layers.delete(u),l.layersWithOutsidePointerEventsDisabled.delete(u),wC())},[u,l]),z.useEffect(()=>{let e=()=>p({});return document.addEventListener(mC,e),()=>document.removeEventListener(mC,e)},[]),(0,B.jsx)(lC.div,{...c,ref:m,style:{pointerEvents:y?b?`auto`:`none`:void 0,...e.style},onFocusCapture:eC(e.onFocusCapture,S.onFocusCapture),onBlurCapture:eC(e.onBlurCapture,S.onBlurCapture),onPointerDownCapture:eC(e.onPointerDownCapture,x.onPointerDownCapture)})});yC.displayName=pC;var bC=`DismissableLayerBranch`,xC=z.forwardRef((e,t)=>{let n=z.useContext(vC),r=z.useRef(null),i=en(t,r);return z.useEffect(()=>{let e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}},[n.branches]),(0,B.jsx)(lC.div,{...e,ref:i})});xC.displayName=bC;function SC(e,t=globalThis?.document){let n=dC(e),r=z.useRef(!1),i=z.useRef(()=>{});return z.useEffect(()=>{let e=e=>{if(e.target&&!r.current){let r=function(){TC(hC,n,a,{discrete:!0})},a={originalEvent:e};e.pointerType===`touch`?(t.removeEventListener(`click`,i.current),i.current=r,t.addEventListener(`click`,i.current,{once:!0})):r()}else t.removeEventListener(`click`,i.current);r.current=!1},a=window.setTimeout(()=>{t.addEventListener(`pointerdown`,e)},0);return()=>{window.clearTimeout(a),t.removeEventListener(`pointerdown`,e),t.removeEventListener(`click`,i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function CC(e,t=globalThis?.document){let n=dC(e),r=z.useRef(!1);return z.useEffect(()=>{let e=e=>{e.target&&!r.current&&TC(gC,n,{originalEvent:e},{discrete:!1})};return t.addEventListener(`focusin`,e),()=>t.removeEventListener(`focusin`,e)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function wC(){let e=new CustomEvent(mC);document.dispatchEvent(e)}function TC(e,t,n,{discrete:r}){let i=n.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?uC(i,a):i.dispatchEvent(a)}var EC=0;function DC(){z.useEffect(()=>{let e=document.querySelectorAll(`[data-radix-focus-guard]`);return document.body.insertAdjacentElement(`afterbegin`,e[0]??OC()),document.body.insertAdjacentElement(`beforeend`,e[1]??OC()),EC++,()=>{EC===1&&document.querySelectorAll(`[data-radix-focus-guard]`).forEach(e=>e.remove()),EC--}},[])}function OC(){let e=document.createElement(`span`);return e.setAttribute(`data-radix-focus-guard`,``),e.tabIndex=0,e.style.outline=`none`,e.style.opacity=`0`,e.style.position=`fixed`,e.style.pointerEvents=`none`,e}function kC(e){let t=AC(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(MC);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function AC(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=PC(n),i=NC(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var jC=Symbol(`radix.slottable`);function MC(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===jC}function NC(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function PC(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var FC=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=kC(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),IC=`focusScope.autoFocusOnMount`,LC=`focusScope.autoFocusOnUnmount`,RC={bubbles:!1,cancelable:!0},zC=`FocusScope`,BC=z.forwardRef((e,t)=>{let{loop:n=!1,trapped:r=!1,onMountAutoFocus:i,onUnmountAutoFocus:a,...o}=e,[s,c]=z.useState(null),l=dC(i),u=dC(a),d=z.useRef(null),f=en(t,e=>c(e)),p=z.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;z.useEffect(()=>{if(r){let e=function(e){if(p.paused||!s)return;let t=e.target;s.contains(t)?d.current=t:qC(d.current,{select:!0})},t=function(e){if(p.paused||!s)return;let t=e.relatedTarget;t!==null&&(s.contains(t)||qC(d.current,{select:!0}))},n=function(e){if(document.activeElement===document.body)for(let t of e)t.removedNodes.length>0&&qC(s)};document.addEventListener(`focusin`,e),document.addEventListener(`focusout`,t);let r=new MutationObserver(n);return s&&r.observe(s,{childList:!0,subtree:!0}),()=>{document.removeEventListener(`focusin`,e),document.removeEventListener(`focusout`,t),r.disconnect()}}},[r,s,p.paused]),z.useEffect(()=>{if(s){JC.add(p);let e=document.activeElement;if(!s.contains(e)){let t=new CustomEvent(IC,RC);s.addEventListener(IC,l),s.dispatchEvent(t),t.defaultPrevented||(VC(ZC(UC(s)),{select:!0}),document.activeElement===e&&qC(s))}return()=>{s.removeEventListener(IC,l),setTimeout(()=>{let t=new CustomEvent(LC,RC);s.addEventListener(LC,u),s.dispatchEvent(t),t.defaultPrevented||qC(e??document.body,{select:!0}),s.removeEventListener(LC,u),JC.remove(p)},0)}}},[s,l,u,p]);let m=z.useCallback(e=>{if(!n&&!r||p.paused)return;let t=e.key===`Tab`&&!e.altKey&&!e.ctrlKey&&!e.metaKey,i=document.activeElement;if(t&&i){let t=e.currentTarget,[r,a]=HC(t);r&&a?!e.shiftKey&&i===a?(e.preventDefault(),n&&qC(r,{select:!0})):e.shiftKey&&i===r&&(e.preventDefault(),n&&qC(a,{select:!0})):i===t&&e.preventDefault()}},[n,r,p.paused]);return(0,B.jsx)(FC.div,{tabIndex:-1,...o,ref:f,onKeyDown:m})});BC.displayName=zC;function VC(e,{select:t=!1}={}){let n=document.activeElement;for(let r of e)if(qC(r,{select:t}),document.activeElement!==n)return}function HC(e){let t=UC(e);return[WC(t,e),WC(t.reverse(),e)]}function UC(e){let t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t=e.tagName===`INPUT`&&e.type===`hidden`;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function WC(e,t){for(let n of e)if(!GC(n,{upTo:t}))return n}function GC(e,{upTo:t}){if(getComputedStyle(e).visibility===`hidden`)return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display===`none`)return!0;e=e.parentElement}return!1}function KC(e){return e instanceof HTMLInputElement&&`select`in e}function qC(e,{select:t=!1}={}){if(e&&e.focus){let n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&KC(e)&&t&&e.select()}}var JC=YC();function YC(){let e=[];return{add(t){let n=e[0];t!==n&&n?.pause(),e=XC(e,t),e.unshift(t)},remove(t){e=XC(e,t),e[0]?.resume()}}}function XC(e,t){let n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function ZC(e){return e.filter(e=>e.tagName!==`A`)}var QC=globalThis?.document?z.useLayoutEffect:()=>{},$C=z.useId||(()=>void 0),ew=0;function tw(e){let[t,n]=z.useState($C());return QC(()=>{e||n(e=>e??String(ew++))},[e]),e||(t?`radix-${t}`:``)}var nw=[`top`,`right`,`bottom`,`left`],rw=Math.min,iw=Math.max,aw=Math.round,ow=Math.floor,sw=e=>({x:e,y:e}),cw={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function lw(e,t,n){return iw(e,rw(t,n))}function uw(e,t){return typeof e==`function`?e(t):e}function dw(e){return e.split(`-`)[0]}function fw(e){return e.split(`-`)[1]}function pw(e){return e===`x`?`y`:`x`}function mw(e){return e===`y`?`height`:`width`}function hw(e){let t=e[0];return t===`t`||t===`b`?`y`:`x`}function gw(e){return pw(hw(e))}function _w(e,t,n){n===void 0&&(n=!1);let r=fw(e),i=gw(e),a=mw(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=Ew(o)),[o,Ew(o)]}function vw(e){let t=Ew(e);return[yw(e),t,yw(t)]}function yw(e){return e.includes(`start`)?e.replace(`start`,`end`):e.replace(`end`,`start`)}var bw=[`left`,`right`],xw=[`right`,`left`],Sw=[`top`,`bottom`],Cw=[`bottom`,`top`];function ww(e,t,n){switch(e){case`top`:case`bottom`:return n?t?xw:bw:t?bw:xw;case`left`:case`right`:return t?Sw:Cw;default:return[]}}function Tw(e,t,n,r){let i=fw(e),a=ww(dw(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map(yw)))),a}function Ew(e){let t=dw(e);return cw[t]+e.slice(t.length)}function Dw(e){return{top:0,right:0,bottom:0,left:0,...e}}function Ow(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:Dw(e)}function kw(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function Aw(e,t,n){let{reference:r,floating:i}=e,a=hw(t),o=gw(t),s=mw(o),c=dw(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}switch(fw(t)){case`start`:p[o]-=f*(n&&l?-1:1);break;case`end`:p[o]+=f*(n&&l?-1:1);break}return p}async function jw(e,t){t===void 0&&(t={});let{x:n,y:r,platform:i,rects:a,elements:o,strategy:s}=e,{boundary:c=`clippingAncestors`,rootBoundary:l=`viewport`,elementContext:u=`floating`,altBoundary:d=!1,padding:f=0}=uw(t,e),p=Ow(f),m=o[d?u===`floating`?`reference`:`floating`:u],h=kw(await i.getClippingRect({element:await(i.isElement==null?void 0:i.isElement(m))??!0?m:m.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(o.floating)),boundary:c,rootBoundary:l,strategy:s})),g=u===`floating`?{x:n,y:r,width:a.floating.width,height:a.floating.height}:a.reference,_=await(i.getOffsetParent==null?void 0:i.getOffsetParent(o.floating)),v=await(i.isElement==null?void 0:i.isElement(_))&&await(i.getScale==null?void 0:i.getScale(_))||{x:1,y:1},y=kw(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:g,offsetParent:_,strategy:s}):g);return{top:(h.top-y.top+p.top)/v.y,bottom:(y.bottom-h.bottom+p.bottom)/v.y,left:(h.left-y.left+p.left)/v.x,right:(y.right-h.right+p.right)/v.x}}var Mw=50,Nw=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=o.detectOverflow?o:{...o,detectOverflow:jw},c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=Aw(l,r,c),f=r,p=0,m={};for(let n=0;n({name:`arrow`,options:e,async fn(t){let{x:n,y:r,placement:i,rects:a,platform:o,elements:s,middlewareData:c}=t,{element:l,padding:u=0}=uw(e,t)||{};if(l==null)return{};let d=Ow(u),f={x:n,y:r},p=gw(i),m=mw(p),h=await o.getDimensions(l),g=p===`y`,_=g?`top`:`left`,v=g?`bottom`:`right`,y=g?`clientHeight`:`clientWidth`,b=a.reference[m]+a.reference[p]-f[p]-a.floating[m],x=f[p]-a.reference[p],S=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l)),C=S?S[y]:0;(!C||!await(o.isElement==null?void 0:o.isElement(S)))&&(C=s.floating[y]||a.floating[m]);let w=b/2-x/2,T=C/2-h[m]/2-1,E=rw(d[_],T),ee=rw(d[v],T),D=E,O=C-h[m]-ee,k=C/2-h[m]/2+w,A=lw(D,k,O),j=!c.arrow&&fw(i)!=null&&k!==A&&a.reference[m]/2-(ke<=0)){let e=(i.flip?.index||0)+1,t=S[e];if(t&&(!(u===`alignment`&&_!==hw(t))||T.every(e=>hw(e.placement)===_?e.overflows[0]>0:!0)))return{data:{index:e,overflows:T},reset:{placement:t}};let n=T.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=T.filter(e=>{if(x){let t=hw(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o;break}if(r!==n)return{reset:{placement:n}}}return{}}}};function Iw(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function Lw(e){return nw.some(t=>e[t]>=0)}var Rw=function(e){return e===void 0&&(e={}),{name:`hide`,options:e,async fn(t){let{rects:n,platform:r}=t,{strategy:i=`referenceHidden`,...a}=uw(e,t);switch(i){case`referenceHidden`:{let e=Iw(await r.detectOverflow(t,{...a,elementContext:`reference`}),n.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:Lw(e)}}}case`escaped`:{let e=Iw(await r.detectOverflow(t,{...a,altBoundary:!0}),n.floating);return{data:{escapedOffsets:e,escaped:Lw(e)}}}default:return{}}}}},zw=new Set([`left`,`top`]);async function Bw(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=dw(n),s=fw(n),c=hw(n)===`y`,l=zw.has(o)?-1:1,u=a&&c?-1:1,d=uw(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}var Vw=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await Bw(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},Hw=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i,platform:a}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:c={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=uw(e,t),u={x:n,y:r},d=await a.detectOverflow(t,l),f=hw(dw(i)),p=pw(f),m=u[p],h=u[f];if(o){let e=p===`y`?`top`:`left`,t=p===`y`?`bottom`:`right`,n=m+d[e],r=m-d[t];m=lw(n,m,r)}if(s){let e=f===`y`?`top`:`left`,t=f===`y`?`bottom`:`right`,n=h+d[e],r=h-d[t];h=lw(n,h,r)}let g=c.fn({...t,[p]:m,[f]:h});return{...g,data:{x:g.x-n,y:g.y-r,enabled:{[p]:o,[f]:s}}}}}},Uw=function(e){return e===void 0&&(e={}),{options:e,fn(t){let{x:n,y:r,placement:i,rects:a,middlewareData:o}=t,{offset:s=0,mainAxis:c=!0,crossAxis:l=!0}=uw(e,t),u={x:n,y:r},d=hw(i),f=pw(d),p=u[f],m=u[d],h=uw(s,t),g=typeof h==`number`?{mainAxis:h,crossAxis:0}:{mainAxis:0,crossAxis:0,...h};if(c){let e=f===`y`?`height`:`width`,t=a.reference[f]-a.floating[e]+g.mainAxis,n=a.reference[f]+a.reference[e]-g.mainAxis;pn&&(p=n)}if(l){let e=f===`y`?`width`:`height`,t=zw.has(dw(i)),n=a.reference[d]-a.floating[e]+(t&&o.offset?.[d]||0)+(t?0:g.crossAxis),r=a.reference[d]+a.reference[e]+(t?0:o.offset?.[d]||0)-(t?g.crossAxis:0);mr&&(m=r)}return{[f]:p,[d]:m}}}},Ww=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){var n,r;let{placement:i,rects:a,platform:o,elements:s}=t,{apply:c=()=>{},...l}=uw(e,t),u=await o.detectOverflow(t,l),d=dw(i),f=fw(i),p=hw(i)===`y`,{width:m,height:h}=a.floating,g,_;d===`top`||d===`bottom`?(g=d,_=f===(await(o.isRTL==null?void 0:o.isRTL(s.floating))?`start`:`end`)?`left`:`right`):(_=d,g=f===`end`?`top`:`bottom`);let v=h-u.top-u.bottom,y=m-u.left-u.right,b=rw(h-u[g],v),x=rw(m-u[_],y),S=!t.middlewareData.shift,C=b,w=x;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(w=y),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(C=v),S&&!f){let e=iw(u.left,0),t=iw(u.right,0),n=iw(u.top,0),r=iw(u.bottom,0);p?w=m-2*(e!==0||t!==0?e+t:iw(u.left,u.right)):C=h-2*(n!==0||r!==0?n+r:iw(u.top,u.bottom))}await c({...t,availableWidth:w,availableHeight:C});let T=await o.getDimensions(s.floating);return m!==T.width||h!==T.height?{reset:{rects:!0}}:{}}}};function Gw(){return typeof window<`u`}function Kw(e){return Yw(e)?(e.nodeName||``).toLowerCase():`#document`}function qw(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function Jw(e){return((Yw(e)?e.ownerDocument:e.document)||window.document)?.documentElement}function Yw(e){return Gw()?e instanceof Node||e instanceof qw(e).Node:!1}function Xw(e){return Gw()?e instanceof Element||e instanceof qw(e).Element:!1}function Zw(e){return Gw()?e instanceof HTMLElement||e instanceof qw(e).HTMLElement:!1}function Qw(e){return!Gw()||typeof ShadowRoot>`u`?!1:e instanceof ShadowRoot||e instanceof qw(e).ShadowRoot}function $w(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=uT(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&i!==`inline`&&i!==`contents`}function eT(e){return/^(table|td|th)$/.test(Kw(e))}function tT(e){try{if(e.matches(`:popover-open`))return!0}catch{}try{return e.matches(`:modal`)}catch{return!1}}var nT=/transform|translate|scale|rotate|perspective|filter/,rT=/paint|layout|strict|content/,iT=e=>!!e&&e!==`none`,aT;function oT(e){let t=Xw(e)?uT(e):e;return iT(t.transform)||iT(t.translate)||iT(t.scale)||iT(t.rotate)||iT(t.perspective)||!cT()&&(iT(t.backdropFilter)||iT(t.filter))||nT.test(t.willChange||``)||rT.test(t.contain||``)}function sT(e){let t=fT(e);for(;Zw(t)&&!lT(t);){if(oT(t))return t;if(tT(t))return null;t=fT(t)}return null}function cT(){return aT??=typeof CSS<`u`&&CSS.supports&&CSS.supports(`-webkit-backdrop-filter`,`none`),aT}function lT(e){return/^(html|body|#document)$/.test(Kw(e))}function uT(e){return qw(e).getComputedStyle(e)}function dT(e){return Xw(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function fT(e){if(Kw(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||Qw(e)&&e.host||Jw(e);return Qw(t)?t.host:t}function pT(e){let t=fT(e);return lT(t)?e.ownerDocument?e.ownerDocument.body:e.body:Zw(t)&&$w(t)?t:pT(t)}function mT(e,t,n){t===void 0&&(t=[]),n===void 0&&(n=!0);let r=pT(e),i=r===e.ownerDocument?.body,a=qw(r);if(i){let e=hT(a);return t.concat(a,a.visualViewport||[],$w(r)?r:[],e&&n?mT(e):[])}else return t.concat(r,mT(r,[],n))}function hT(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function gT(e){let t=uT(e),n=parseFloat(t.width)||0,r=parseFloat(t.height)||0,i=Zw(e),a=i?e.offsetWidth:n,o=i?e.offsetHeight:r,s=aw(n)!==a||aw(r)!==o;return s&&(n=a,r=o),{width:n,height:r,$:s}}function _T(e){return Xw(e)?e:e.contextElement}function vT(e){let t=_T(e);if(!Zw(t))return sw(1);let n=t.getBoundingClientRect(),{width:r,height:i,$:a}=gT(t),o=(a?aw(n.width):n.width)/r,s=(a?aw(n.height):n.height)/i;return(!o||!Number.isFinite(o))&&(o=1),(!s||!Number.isFinite(s))&&(s=1),{x:o,y:s}}var yT=sw(0);function bT(e){let t=qw(e);return!cT()||!t.visualViewport?yT:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function xT(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==qw(e)?!1:t}function ST(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=_T(e),o=sw(1);t&&(r?Xw(r)&&(o=vT(r)):o=vT(e));let s=xT(a,n,r)?bT(a):sw(0),c=(i.left+s.x)/o.x,l=(i.top+s.y)/o.y,u=i.width/o.x,d=i.height/o.y;if(a){let e=qw(a),t=r&&Xw(r)?qw(r):r,n=e,i=hT(n);for(;i&&r&&t!==n;){let e=vT(i),t=i.getBoundingClientRect(),r=uT(i),a=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,o=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;c*=e.x,l*=e.y,u*=e.x,d*=e.y,c+=a,l+=o,n=qw(i),i=hT(n)}}return kw({width:u,height:d,x:c,y:l})}function CT(e,t){let n=dT(e).scrollLeft;return t?t.left+n:ST(Jw(e)).left+n}function wT(e,t){let n=e.getBoundingClientRect();return{x:n.left+t.scrollLeft-CT(e,n),y:n.top+t.scrollTop}}function TT(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e,a=i===`fixed`,o=Jw(r),s=t?tT(t.floating):!1;if(r===o||s&&a)return n;let c={scrollLeft:0,scrollTop:0},l=sw(1),u=sw(0),d=Zw(r);if((d||!d&&!a)&&((Kw(r)!==`body`||$w(o))&&(c=dT(r)),d)){let e=ST(r);l=vT(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}let f=o&&!d&&!a?wT(o,c):sw(0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-c.scrollLeft*l.x+u.x+f.x,y:n.y*l.y-c.scrollTop*l.y+u.y+f.y}}function ET(e){return Array.from(e.getClientRects())}function DT(e){let t=Jw(e),n=dT(e),r=e.ownerDocument.body,i=iw(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=iw(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+CT(e),s=-n.scrollTop;return uT(r).direction===`rtl`&&(o+=iw(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}var OT=25;function kT(e,t){let n=qw(e),r=Jw(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=cT();(!e||e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}let l=CT(r);if(l<=0){let e=r.ownerDocument,t=e.body,n=getComputedStyle(t),i=e.compatMode===`CSS1Compat`&&parseFloat(n.marginLeft)+parseFloat(n.marginRight)||0,o=Math.abs(r.clientWidth-t.clientWidth-i);o<=OT&&(a-=o)}else l<=OT&&(a+=l);return{width:a,height:o,x:s,y:c}}function AT(e,t){let n=ST(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=Zw(e)?vT(e):sw(1);return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}function jT(e,t,n){let r;if(t===`viewport`)r=kT(e,n);else if(t===`document`)r=DT(Jw(e));else if(Xw(t))r=AT(t,n);else{let n=bT(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return kw(r)}function MT(e,t){let n=fT(e);return n===t||!Xw(n)||lT(n)?!1:uT(n).position===`fixed`||MT(n,t)}function NT(e,t){let n=t.get(e);if(n)return n;let r=mT(e,[],!1).filter(e=>Xw(e)&&Kw(e)!==`body`),i=null,a=uT(e).position===`fixed`,o=a?fT(e):e;for(;Xw(o)&&!lT(o);){let t=uT(o),n=oT(o);!n&&t.position===`fixed`&&(i=null),(a?!n&&!i:!n&&t.position===`static`&&i&&(i.position===`absolute`||i.position===`fixed`)||$w(o)&&!n&&MT(e,o))?r=r.filter(e=>e!==o):i=t,o=fT(o)}return t.set(e,r),r}function PT(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?tT(t)?[]:NT(t,this._c):[].concat(n),r],o=jT(t,a[0],i),s=o.top,c=o.right,l=o.bottom,u=o.left;for(let e=1;e{o(!1,1e-7)},1e3)}n===1&&!UT(l,e.getBoundingClientRect())&&o(),y=!1}try{n=new IntersectionObserver(b,{...v,root:i.ownerDocument})}catch{n=new IntersectionObserver(b,v)}n.observe(e)}return o(!0),a}function GT(e,t,n,r){r===void 0&&(r={});let{ancestorScroll:i=!0,ancestorResize:a=!0,elementResize:o=typeof ResizeObserver==`function`,layoutShift:s=typeof IntersectionObserver==`function`,animationFrame:c=!1}=r,l=_T(e),u=i||a?[...l?mT(l):[],...t?mT(t):[]]:[];u.forEach(e=>{i&&e.addEventListener(`scroll`,n,{passive:!0}),a&&e.addEventListener(`resize`,n)});let d=l&&s?WT(l,n):null,f=-1,p=null;o&&(p=new ResizeObserver(e=>{let[r]=e;r&&r.target===l&&p&&t&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var e;(e=p)==null||e.observe(t)})),n()}),l&&!c&&p.observe(l),t&&p.observe(t));let m,h=c?ST(e):null;c&&g();function g(){let t=ST(e);h&&!UT(h,t)&&n(),h=t,m=requestAnimationFrame(g)}return n(),()=>{var e;u.forEach(e=>{i&&e.removeEventListener(`scroll`,n),a&&e.removeEventListener(`resize`,n)}),d?.(),(e=p)==null||e.disconnect(),p=null,c&&cancelAnimationFrame(m)}}var KT=Vw,qT=Hw,JT=Fw,YT=Ww,XT=Rw,ZT=Pw,QT=Uw,$T=(e,t,n)=>{let r=new Map,i={platform:HT,...n},a={...i.platform,_c:r};return Nw(e,t,{...i,platform:a})},eE=typeof document<`u`?z.useLayoutEffect:function(){};function tE(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(typeof e==`function`&&e.toString()===t.toString())return!0;let n,r,i;if(e&&t&&typeof e==`object`){if(Array.isArray(e)){if(n=e.length,n!==t.length)return!1;for(r=n;r--!==0;)if(!tE(e[r],t[r]))return!1;return!0}if(i=Object.keys(e),n=i.length,n!==Object.keys(t).length)return!1;for(r=n;r--!==0;)if(!{}.hasOwnProperty.call(t,i[r]))return!1;for(r=n;r--!==0;){let n=i[r];if(!(n===`_owner`&&e.$$typeof)&&!tE(e[n],t[n]))return!1}return!0}return e!==e&&t!==t}function nE(e){return typeof window>`u`?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function rE(e,t){let n=nE(e);return Math.round(t*n)/n}function iE(e){let t=z.useRef(e);return eE(()=>{t.current=e}),t}function aE(e){e===void 0&&(e={});let{placement:t=`bottom`,strategy:n=`absolute`,middleware:r=[],platform:i,elements:{reference:a,floating:o}={},transform:s=!0,whileElementsMounted:c,open:l}=e,[u,d]=z.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[f,p]=z.useState(r);tE(f,r)||p(r);let[m,h]=z.useState(null),[g,_]=z.useState(null),v=z.useCallback(e=>{e!==S.current&&(S.current=e,h(e))},[]),y=z.useCallback(e=>{e!==C.current&&(C.current=e,_(e))},[]),b=a||m,x=o||g,S=z.useRef(null),C=z.useRef(null),w=z.useRef(u),T=c!=null,E=iE(c),ee=iE(i),D=iE(l),O=z.useCallback(()=>{if(!S.current||!C.current)return;let e={placement:t,strategy:n,middleware:f};ee.current&&(e.platform=ee.current),$T(S.current,C.current,e).then(e=>{let t={...e,isPositioned:D.current!==!1};k.current&&!tE(w.current,t)&&(w.current=t,Fi.flushSync(()=>{d(t)}))})},[f,t,n,ee,D]);eE(()=>{l===!1&&w.current.isPositioned&&(w.current.isPositioned=!1,d(e=>({...e,isPositioned:!1})))},[l]);let k=z.useRef(!1);eE(()=>(k.current=!0,()=>{k.current=!1}),[]),eE(()=>{if(b&&(S.current=b),x&&(C.current=x),b&&x){if(E.current)return E.current(b,x,O);O()}},[b,x,O,E,T]);let A=z.useMemo(()=>({reference:S,floating:C,setReference:v,setFloating:y}),[v,y]),j=z.useMemo(()=>({reference:b,floating:x}),[b,x]),M=z.useMemo(()=>{let e={position:n,left:0,top:0};if(!j.floating)return e;let t=rE(j.floating,u.x),r=rE(j.floating,u.y);return s?{...e,transform:`translate(`+t+`px, `+r+`px)`,...nE(j.floating)>=1.5&&{willChange:`transform`}}:{position:n,left:t,top:r}},[n,s,j.floating,u.x,u.y]);return z.useMemo(()=>({...u,update:O,refs:A,elements:j,floatingStyles:M}),[u,O,A,j,M])}var oE=e=>{function t(e){return{}.hasOwnProperty.call(e,`current`)}return{name:`arrow`,options:e,fn(n){let{element:r,padding:i}=typeof e==`function`?e(n):e;return r&&t(r)?r.current==null?{}:ZT({element:r.current,padding:i}).fn(n):r?ZT({element:r,padding:i}).fn(n):{}}}},sE=(e,t)=>{let n=KT(e);return{name:n.name,fn:n.fn,options:[e,t]}},cE=(e,t)=>{let n=qT(e);return{name:n.name,fn:n.fn,options:[e,t]}},lE=(e,t)=>({fn:QT(e).fn,options:[e,t]}),uE=(e,t)=>{let n=JT(e);return{name:n.name,fn:n.fn,options:[e,t]}},dE=(e,t)=>{let n=YT(e);return{name:n.name,fn:n.fn,options:[e,t]}},fE=(e,t)=>{let n=XT(e);return{name:n.name,fn:n.fn,options:[e,t]}},pE=(e,t)=>{let n=oE(e);return{name:n.name,fn:n.fn,options:[e,t]}};function mE(e){let t=hE(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(_E);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function hE(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=yE(n),i=vE(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var gE=Symbol(`radix.slottable`);function _E(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===gE}function vE(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function yE(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var bE=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=mE(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),xE=`Arrow`,SE=z.forwardRef((e,t)=>{let{children:n,width:r=10,height:i=5,...a}=e;return(0,B.jsx)(bE.svg,{...a,ref:t,width:r,height:i,viewBox:`0 0 30 10`,preserveAspectRatio:`none`,children:e.asChild?n:(0,B.jsx)(`polygon`,{points:`0,0 30,0 15,10`})})});SE.displayName=xE;var CE=SE;function wE(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,TE(i,...t)]}function TE(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function EE(e){let t=DE(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(kE);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function DE(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=jE(n),i=AE(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var OE=Symbol(`radix.slottable`);function kE(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===OE}function AE(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function jE(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var ME=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=EE(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function NE(e){let[t,n]=z.useState(void 0);return QC(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{if(!Array.isArray(t)||!t.length)return;let r=t[0],i,a;if(`borderBoxSize`in r){let e=r.borderBoxSize,t=Array.isArray(e)?e[0]:e;i=t.inlineSize,a=t.blockSize}else i=e.offsetWidth,a=e.offsetHeight;n({width:i,height:a})});return t.observe(e,{box:`border-box`}),()=>t.unobserve(e)}else n(void 0)},[e]),t}var PE=`Popper`,[FE,IE]=wE(PE),[LE,RE]=FE(PE),zE=e=>{let{__scopePopper:t,children:n}=e,[r,i]=z.useState(null);return(0,B.jsx)(LE,{scope:t,anchor:r,onAnchorChange:i,children:n})};zE.displayName=PE;var BE=`PopperAnchor`,VE=z.forwardRef((e,t)=>{let{__scopePopper:n,virtualRef:r,...i}=e,a=RE(BE,n),o=z.useRef(null),s=en(t,o),c=z.useRef(null);return z.useEffect(()=>{let e=c.current;c.current=r?.current||o.current,e!==c.current&&a.onAnchorChange(c.current)}),r?null:(0,B.jsx)(ME.div,{...i,ref:s})});VE.displayName=BE;var HE=`PopperContent`,[UE,WE]=FE(HE),GE=z.forwardRef((e,t)=>{let{__scopePopper:n,side:r=`bottom`,sideOffset:i=0,align:a=`center`,alignOffset:o=0,arrowPadding:s=0,avoidCollisions:c=!0,collisionBoundary:l=[],collisionPadding:u=0,sticky:d=`partial`,hideWhenDetached:f=!1,updatePositionStrategy:p=`optimized`,onPlaced:m,...h}=e,g=RE(HE,n),[_,v]=z.useState(null),y=en(t,e=>v(e)),[b,x]=z.useState(null),S=NE(b),C=S?.width??0,w=S?.height??0,T=r+(a===`center`?``:`-`+a),E=typeof u==`number`?u:{top:0,right:0,bottom:0,left:0,...u},ee=Array.isArray(l)?l:[l],D=ee.length>0,O={padding:E,boundary:ee.filter(YE),altBoundary:D},{refs:k,floatingStyles:A,placement:j,isPositioned:M,middlewareData:N}=aE({strategy:`fixed`,placement:T,whileElementsMounted:(...e)=>GT(...e,{animationFrame:p===`always`}),elements:{reference:g.anchor},middleware:[sE({mainAxis:i+w,alignmentAxis:o}),c&&cE({mainAxis:!0,crossAxis:!1,limiter:d===`partial`?lE():void 0,...O}),c&&uE({...O}),dE({...O,apply:({elements:e,rects:t,availableWidth:n,availableHeight:r})=>{let{width:i,height:a}=t.reference,o=e.floating.style;o.setProperty(`--radix-popper-available-width`,`${n}px`),o.setProperty(`--radix-popper-available-height`,`${r}px`),o.setProperty(`--radix-popper-anchor-width`,`${i}px`),o.setProperty(`--radix-popper-anchor-height`,`${a}px`)}}),b&&pE({element:b,padding:s}),XE({arrowWidth:C,arrowHeight:w}),f&&fE({strategy:`referenceHidden`,...O})]}),[P,F]=ZE(j),I=dC(m);QC(()=>{M&&I?.()},[M,I]);let te=N.arrow?.x,ne=N.arrow?.y,re=N.arrow?.centerOffset!==0,[L,R]=z.useState();return QC(()=>{_&&R(window.getComputedStyle(_).zIndex)},[_]),(0,B.jsx)(`div`,{ref:k.setFloating,"data-radix-popper-content-wrapper":``,style:{...A,transform:M?A.transform:`translate(0, -200%)`,minWidth:`max-content`,zIndex:L,"--radix-popper-transform-origin":[N.transformOrigin?.x,N.transformOrigin?.y].join(` `),...N.hide?.referenceHidden&&{visibility:`hidden`,pointerEvents:`none`}},dir:e.dir,children:(0,B.jsx)(UE,{scope:n,placedSide:P,onArrowChange:x,arrowX:te,arrowY:ne,shouldHideArrow:re,children:(0,B.jsx)(ME.div,{"data-side":P,"data-align":F,...h,ref:y,style:{...h.style,animation:M?void 0:`none`}})})})});GE.displayName=HE;var KE=`PopperArrow`,qE={top:`bottom`,right:`left`,bottom:`top`,left:`right`},JE=z.forwardRef(function(e,t){let{__scopePopper:n,...r}=e,i=WE(KE,n),a=qE[i.placedSide];return(0,B.jsx)(`span`,{ref:i.onArrowChange,style:{position:`absolute`,left:i.arrowX,top:i.arrowY,[a]:0,transformOrigin:{top:``,right:`0 0`,bottom:`center 0`,left:`100% 0`}[i.placedSide],transform:{top:`translateY(100%)`,right:`translateY(50%) rotate(90deg) translateX(-50%)`,bottom:`rotate(180deg)`,left:`translateY(50%) rotate(-90deg) translateX(50%)`}[i.placedSide],visibility:i.shouldHideArrow?`hidden`:void 0},children:(0,B.jsx)(CE,{...r,ref:t,style:{...r.style,display:`block`}})})});JE.displayName=KE;function YE(e){return e!==null}var XE=e=>({name:`transformOrigin`,options:e,fn(t){let{placement:n,rects:r,middlewareData:i}=t,a=i.arrow?.centerOffset!==0,o=a?0:e.arrowWidth,s=a?0:e.arrowHeight,[c,l]=ZE(n),u={start:`0%`,center:`50%`,end:`100%`}[l],d=(i.arrow?.x??0)+o/2,f=(i.arrow?.y??0)+s/2,p=``,m=``;return c===`bottom`?(p=a?u:`${d}px`,m=`${-s}px`):c===`top`?(p=a?u:`${d}px`,m=`${r.floating.height+s}px`):c===`right`?(p=`${-s}px`,m=a?u:`${f}px`):c===`left`&&(p=`${r.floating.width+s}px`,m=a?u:`${f}px`),{data:{x:p,y:m}}}});function ZE(e){let[t,n=`center`]=e.split(`-`);return[t,n]}var QE=zE,$E=VE,eD=GE,tD=JE;function nD(e){let t=rD(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(aD);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function rD(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=sD(n),i=oD(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var iD=Symbol(`radix.slottable`);function aD(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===iD}function oD(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function sD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var cD=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=nD(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),lD=`Portal`,uD=z.forwardRef((e,t)=>{let{container:n,...r}=e,[i,a]=z.useState(!1);QC(()=>a(!0),[]);let o=n||i&&globalThis?.document?.body;return o?Fi.createPortal((0,B.jsx)(cD.div,{...r,ref:t}),o):null});uD.displayName=lD;function dD(e,t){return z.useReducer((e,n)=>t[e][n]??e,e)}var fD=e=>{let{present:t,children:n}=e,r=pD(t),i=typeof n==`function`?n({present:r.isPresent}):z.Children.only(n),a=en(r.ref,hD(i));return typeof n==`function`||r.isPresent?z.cloneElement(i,{ref:a}):null};fD.displayName=`Presence`;function pD(e){let[t,n]=z.useState(),r=z.useRef(null),i=z.useRef(e),a=z.useRef(`none`),[o,s]=dD(e?`mounted`:`unmounted`,{mounted:{UNMOUNT:`unmounted`,ANIMATION_OUT:`unmountSuspended`},unmountSuspended:{MOUNT:`mounted`,ANIMATION_END:`unmounted`},unmounted:{MOUNT:`mounted`}});return z.useEffect(()=>{let e=mD(r.current);a.current=o===`mounted`?e:`none`},[o]),QC(()=>{let t=r.current,n=i.current;if(n!==e){let r=a.current,o=mD(t);e?s(`MOUNT`):o===`none`||t?.display===`none`?s(`UNMOUNT`):s(n&&r!==o?`ANIMATION_OUT`:`UNMOUNT`),i.current=e}},[e,s]),QC(()=>{if(t){let e,n=t.ownerDocument.defaultView??window,o=a=>{let o=mD(r.current).includes(CSS.escape(a.animationName));if(a.target===t&&o&&(s(`ANIMATION_END`),!i.current)){let r=t.style.animationFillMode;t.style.animationFillMode=`forwards`,e=n.setTimeout(()=>{t.style.animationFillMode===`forwards`&&(t.style.animationFillMode=r)})}},c=e=>{e.target===t&&(a.current=mD(r.current))};return t.addEventListener(`animationstart`,c),t.addEventListener(`animationcancel`,o),t.addEventListener(`animationend`,o),()=>{n.clearTimeout(e),t.removeEventListener(`animationstart`,c),t.removeEventListener(`animationcancel`,o),t.removeEventListener(`animationend`,o)}}else s(`ANIMATION_END`)},[t,s]),{isPresent:[`mounted`,`unmountSuspended`].includes(o),ref:z.useCallback(e=>{r.current=e?getComputedStyle(e):null,n(e)},[])}}function mD(e){return e?.animationName||`none`}function hD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function gD(e){let t=_D(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(yD);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function _D(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=xD(n),i=bD(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var vD=Symbol(`radix.slottable`);function yD(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===vD}function bD(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function xD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var SD=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=gD(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),CD=z.useInsertionEffect||QC;function wD({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){let[i,a,o]=TD({defaultProp:t,onChange:n}),s=e!==void 0,c=s?e:i;{let t=z.useRef(e!==void 0);z.useEffect(()=>{let e=t.current;e!==s&&console.warn(`${r} is changing from ${e?`controlled`:`uncontrolled`} to ${s?`controlled`:`uncontrolled`}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),t.current=s},[s,r])}return[c,z.useCallback(t=>{if(s){let n=ED(t)?t(e):t;n!==e&&o.current?.(n)}else a(t)},[s,e,a,o])]}function TD({defaultProp:e,onChange:t}){let[n,r]=z.useState(e),i=z.useRef(n),a=z.useRef(t);return CD(()=>{a.current=t},[t]),z.useEffect(()=>{i.current!==n&&(a.current?.(n),i.current=n)},[n,i]),[n,r,a]}function ED(e){return typeof e==`function`}var DD=function(e){return typeof document>`u`?null:(Array.isArray(e)?e[0]:e).ownerDocument.body},OD=new WeakMap,kD=new WeakMap,AD={},jD=0,MD=function(e){return e&&(e.host||MD(e.parentNode))},ND=function(e,t){return t.map(function(t){if(e.contains(t))return t;var n=MD(t);return n&&e.contains(n)?n:(console.error(`aria-hidden`,t,`in not contained inside`,e,`. Doing nothing`),null)}).filter(function(e){return!!e})},PD=function(e,t,n,r){var i=ND(t,Array.isArray(e)?e:[e]);AD[n]||(AD[n]=new WeakMap);var a=AD[n],o=[],s=new Set,c=new Set(i),l=function(e){!e||s.has(e)||(s.add(e),l(e.parentNode))};i.forEach(l);var u=function(e){!e||c.has(e)||Array.prototype.forEach.call(e.children,function(e){if(s.has(e))u(e);else try{var t=e.getAttribute(r),i=t!==null&&t!==`false`,c=(OD.get(e)||0)+1,l=(a.get(e)||0)+1;OD.set(e,c),a.set(e,l),o.push(e),c===1&&i&&kD.set(e,!0),l===1&&e.setAttribute(n,`true`),i||e.setAttribute(r,`true`)}catch(t){console.error(`aria-hidden: cannot operate on `,e,t)}})};return u(t),s.clear(),jD++,function(){o.forEach(function(e){var t=OD.get(e)-1,i=a.get(e)-1;OD.set(e,t),a.set(e,i),t||(kD.has(e)||e.removeAttribute(r),kD.delete(e)),i||e.removeAttribute(n)}),jD--,jD||(OD=new WeakMap,OD=new WeakMap,kD=new WeakMap,AD={})}},FD=function(e,t,n){n===void 0&&(n=`data-aria-hidden`);var r=Array.from(Array.isArray(e)?e:[e]),i=t||DD(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll(`[aria-live], script`))),PD(r,i,n,`aria-hidden`)):function(){return null}},ID=function(){return ID=Object.assign||function(e){for(var t,n=1,r=arguments.length;n`u`)return uO;var t=fO(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},mO=lO(),hO=`data-scroll-locked`,gO=function(e,t,n,r){var i=e.left,a=e.top,o=e.right,s=e.gap;return n===void 0&&(n=`margin`),` - .${VD} { +`,`\r`,``])}).strict(),bS=Vx([`complete`,`cancelled`,`network_error`,`worker_error`,`protocol_cancel`]),xS={transportOutcome:$().trim().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional(),framesSeen:yx().int().nonnegative().optional(),printableFramesSeen:yx().int().nonnegative().optional(),printableTurnEvents:yx().int().nonnegative().optional(),wireBytes:yx().int().nonnegative().optional(),terminalReceived:Cx().optional(),firstFrameMs:yx().finite().nonnegative().nullable().optional(),lastFrameMs:yx().finite().nonnegative().nullable().optional()},SS=jx({lines:kx(yS),bytes:yx().int().nonnegative(),retainedBytes:yx().int().nonnegative(),truncated:Cx()}).strict(),CS=jx({text:$(),bytes:yx().int().nonnegative(),truncated:Cx()}).strict(),wS=jx({state:Ux(`open`),sse:SS.optional(),body:CS.optional(),...xS}).strict(),TS=jx({state:Ux(`settled`),outcome:bS,wireOutcome:bS,sse:SS.optional(),body:CS.optional(),...xS}).strict(),ES=Fx(`state`,[jx({state:Ux(`evicted`)}).strict(),wS,TS]),DS=jx({id:$(),ts:yx().finite(),kind:Vx([`sse`,`header`]),status:yx().int().min(100).max(599),conversationId:$().nullable(),wireLogId:$().nullable(),label:$(),upstreamEchoes:kx(_S).optional(),droppedEchoCount:yx().int().nonnegative().optional()}).strict();DS.extend({capture:ES.optional()}).strict();var OS=DS,kS=jx({captureEnabled:Cx(),showResponses:Cx(),entries:kx(OS)}).strict(),AS=`nyxid.assistant.wirelog.v1`,jS=100,MS=2*1024*1024,NS=4*1024*1024,PS=new TextEncoder,FS={captureEnabled:!1,showResponses:!0,entries:[]},IS={featureEnabled:!1,...FS,entries:[],totalBytes:0,captureBytes:0};function LS(e){return{id:e.id,ts:e.ts,kind:e.kind,status:e.status,conversationId:e.conversationId,wireLogId:e.wireLogId,label:e.label,...e.upstreamEchoes===void 0?{}:{upstreamEchoes:e.upstreamEchoes},...e.droppedEchoCount===void 0?{}:{droppedEchoCount:e.droppedEchoCount}}}function RS(e){return PS.encode(JSON.stringify(LS(e))).byteLength}function zS(e){return e.reduce((e,t)=>e+RS(t),0)}function BS(e){return!e||e.state===`evicted`?0:(e.sse?.retainedBytes??0)+(e.body?PS.encode(e.body.text).byteLength:0)}function VS(e){return e.reduce((e,t)=>e+BS(t.capture),0)}function HS(e){let t=e.slice(Math.max(0,e.length-jS)),n=zS(t);for(;t.length>0&&n>MS;)t=t.slice(1),n=zS(t);return{entries:t,totalBytes:n,captureBytes:VS(t)}}function US(e){let t=VS(e);if(t<=NS)return{entries:e,captureBytes:t};let n=[...e];for(let e=0;ee+PS.encode(`${t.text}${t.ending}`).byteLength,0)}function GS(e,t,n){let r=e.findIndex(e=>e.id===t);if(r<0)return null;let i=e[r];if(!i?.capture||i.capture.state===`evicted`)return null;let a=n(i.capture);if(!a)return null;let o=[...e];return o[r]={...i,capture:a},o}function KS(e){return e instanceof DOMException&&(e.name===`QuotaExceededError`||e.name===`NS_ERROR_DOM_QUOTA_REACHED`)}var qS={getItem:e=>localStorage.getItem(e),removeItem:e=>localStorage.removeItem(e),setItem:(e,t)=>{try{localStorage.setItem(e,t);return}catch(e){if(!KS(e))return}try{let n=JSON.parse(t),r=n.state?.entries;if(!Array.isArray(r)||r.length===0)return;r.shift(),localStorage.setItem(e,JSON.stringify(n))}catch{}}};function JS(){typeof localStorage<`u`&&localStorage.removeItem(AS)}function YS(e,t){if(t<3)return JS(),FS;let n=kS.safeParse(e);return n.success?n.data:(JS(),FS)}var XS=Ot()(jt((e,t)=>({...IS,setFeatureEnabled:t=>e({featureEnabled:t}),setCaptureEnabled:t=>e({captureEnabled:t}),setShowResponses:t=>e({showResponses:t}),recordExchange:n=>{let r=n.envelopes;if(!t().featureEnabled||!t().captureEnabled||!n.wireLogId&&(!r||r.length===0))return null;let i=crypto.randomUUID(),a={id:i,ts:Date.now(),kind:n.kind,status:n.status,conversationId:n.conversationId,wireLogId:n.wireLogId,label:n.label,...r===void 0?{}:{upstreamEchoes:[...r]},...n.droppedEchoCount&&n.droppedEchoCount>0?{droppedEchoCount:n.droppedEchoCount}:{},capture:{state:`open`}};return e(e=>HS([...e.entries,a])),i},assignConversation:(t,n)=>{e(e=>{let r=e.entries.findIndex(e=>e.id===t),i=e.entries[r];if(!i||i.conversationId===n)return e;let a=[...e.entries];return a[r]={...i,conversationId:n},HS(a)})},attachWireLines:(t,n,r,i=!1)=>{e(e=>{let a=GS(e.entries,t,e=>{let t=e.sse??{lines:[],bytes:0,retainedBytes:0,truncated:!1};return{...e,sse:{lines:[...t.lines,...n],bytes:t.bytes+Math.max(0,r),retainedBytes:t.retainedBytes+WS(n),truncated:t.truncated||i}}});return a?US(a):e})},attachResponseBody:(t,n,r,i)=>{e(e=>{let a=GS(e.entries,t,e=>({...e,body:{text:n,bytes:Math.max(0,r),truncated:i}}));return a?US(a):e})},finalizeCapture:(t,n)=>{e(e=>{let r=GS(e.entries,t,e=>({...e,state:`settled`,outcome:n,wireOutcome:n}));return r?{entries:r}:e})},attachTransportTelemetry:(t,n)=>{e(e=>{let r=GS(e.entries,t,e=>({...e,...n}));return r?{entries:r}:e})},clear:()=>e({entries:[],totalBytes:0,captureBytes:0}),reset:()=>{e(IS),JS()}}),{name:AS,version:3,storage:kt(()=>qS),migrate:YS,partialize:({captureEnabled:e,showResponses:t,entries:n})=>({captureEnabled:e,showResponses:t,entries:n.map(LS)}),merge:(e,t)=>{let n=kS.safeParse(e);if(!n.success)return JS(),t;let r=n.data.entries.map(e=>({...e}));return{...t,...n.data,entries:r,totalBytes:zS(r),captureBytes:0}}})),ZS=2002;function QS(){kd.setState({accountId:void 0,filters:void 0,expanded:[]}),bb.getState().clear(),Tb.getState().clear(),XS.getState().reset()}function $S(e,t){e?.id!==t?.id&&(kd.setState({accountId:t?.id,filters:void 0,expanded:[]}),e!==null&&QS(),Wy(t?.id??null))}var eC=Ot((e,t)=>({user:null,isAuthenticated:!1,isLoading:!0,mfaRequired:!1,mfaToken:null,login:async(t,n)=>{try{let r=await gb.post(`/auth/login`,{email:t,password:n,client:`web`});return e({isAuthenticated:!0,mfaRequired:!1,mfaToken:null}),{mfaRequired:!1,response:r}}catch(t){if(t instanceof lb&&t.errorCode===ZS)return e({mfaRequired:!0,mfaToken:t.errorResponse.session_token??null}),{mfaRequired:!0};throw t}},logout:async()=>{try{await gb.post(`/auth/logout`)}finally{By(),QS(),Wy(null),e({user:null,isAuthenticated:!1,mfaRequired:!1,mfaToken:null})}},checkAuth:async({ephemeral:n=!1}={})=>{e({isLoading:!0});try{let r=n?await mb(`/users/me`,{preserveSessionOn401:!0}):await gb.get(`/users/me`);$S(t().user,r),e({user:r,isAuthenticated:!0,isLoading:!1}),n||zy(r.id)}catch(t){t instanceof lb&&t.status===401?(n||(By(),QS()),Wy(null),e({user:null,isAuthenticated:!1,isLoading:!1})):e({isLoading:!1})}},setUser:n=>{let r=t().user;n===null?(QS(),Wy(null)):$S(r,n),e({user:n,isAuthenticated:n!==null}),n!==null&&zy(n.id)},setMfaRequired:(t,n)=>{e({mfaRequired:t,mfaToken:n})},clearMfaState:()=>{e({mfaRequired:!1,mfaToken:null})}}));typeof window<`u`&&window.document&&window.document.createElement;function tC(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(e?.(r),n===!1||!r.defaultPrevented)return t?.(r)}}function nC(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,rC(i,...t)]}function rC(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function iC(e){let t=aC(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(sC);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function aC(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=lC(n),i=cC(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var oC=Symbol(`radix.slottable`);function sC(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===oC}function cC(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function lC(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var uC=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=iC(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function dC(e,t){e&&Fi.flushSync(()=>e.dispatchEvent(t))}function fC(e){let t=z.useRef(e);return z.useEffect(()=>{t.current=e}),z.useMemo(()=>(...e)=>t.current?.(...e),[])}function pC(e,t=globalThis?.document){let n=fC(e);z.useEffect(()=>{let e=e=>{e.key===`Escape`&&n(e)};return t.addEventListener(`keydown`,e,{capture:!0}),()=>t.removeEventListener(`keydown`,e,{capture:!0})},[n,t])}var mC=`DismissableLayer`,hC=`dismissableLayer.update`,gC=`dismissableLayer.pointerDownOutside`,_C=`dismissableLayer.focusOutside`,vC,yC=z.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),bC=z.forwardRef((e,t)=>{let{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:a,onInteractOutside:o,onDismiss:s,...c}=e,l=z.useContext(yC),[u,d]=z.useState(null),f=u?.ownerDocument??globalThis?.document,[,p]=z.useState({}),m=en(t,e=>d(e)),h=Array.from(l.layers),[g]=[...l.layersWithOutsidePointerEventsDisabled].slice(-1),_=h.indexOf(g),v=u?h.indexOf(u):-1,y=l.layersWithOutsidePointerEventsDisabled.size>0,b=v>=_,x=CC(e=>{let t=e.target,n=[...l.branches].some(e=>e.contains(t));!b||n||(i?.(e),o?.(e),e.defaultPrevented||s?.())},f),S=wC(e=>{let t=e.target;[...l.branches].some(e=>e.contains(t))||(a?.(e),o?.(e),e.defaultPrevented||s?.())},f);return pC(e=>{v===l.layers.size-1&&(r?.(e),!e.defaultPrevented&&s&&(e.preventDefault(),s()))},f),z.useEffect(()=>{if(u)return n&&(l.layersWithOutsidePointerEventsDisabled.size===0&&(vC=f.body.style.pointerEvents,f.body.style.pointerEvents=`none`),l.layersWithOutsidePointerEventsDisabled.add(u)),l.layers.add(u),TC(),()=>{n&&l.layersWithOutsidePointerEventsDisabled.size===1&&(f.body.style.pointerEvents=vC)}},[u,f,n,l]),z.useEffect(()=>()=>{u&&(l.layers.delete(u),l.layersWithOutsidePointerEventsDisabled.delete(u),TC())},[u,l]),z.useEffect(()=>{let e=()=>p({});return document.addEventListener(hC,e),()=>document.removeEventListener(hC,e)},[]),(0,B.jsx)(uC.div,{...c,ref:m,style:{pointerEvents:y?b?`auto`:`none`:void 0,...e.style},onFocusCapture:tC(e.onFocusCapture,S.onFocusCapture),onBlurCapture:tC(e.onBlurCapture,S.onBlurCapture),onPointerDownCapture:tC(e.onPointerDownCapture,x.onPointerDownCapture)})});bC.displayName=mC;var xC=`DismissableLayerBranch`,SC=z.forwardRef((e,t)=>{let n=z.useContext(yC),r=z.useRef(null),i=en(t,r);return z.useEffect(()=>{let e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}},[n.branches]),(0,B.jsx)(uC.div,{...e,ref:i})});SC.displayName=xC;function CC(e,t=globalThis?.document){let n=fC(e),r=z.useRef(!1),i=z.useRef(()=>{});return z.useEffect(()=>{let e=e=>{if(e.target&&!r.current){let r=function(){EC(gC,n,a,{discrete:!0})},a={originalEvent:e};e.pointerType===`touch`?(t.removeEventListener(`click`,i.current),i.current=r,t.addEventListener(`click`,i.current,{once:!0})):r()}else t.removeEventListener(`click`,i.current);r.current=!1},a=window.setTimeout(()=>{t.addEventListener(`pointerdown`,e)},0);return()=>{window.clearTimeout(a),t.removeEventListener(`pointerdown`,e),t.removeEventListener(`click`,i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function wC(e,t=globalThis?.document){let n=fC(e),r=z.useRef(!1);return z.useEffect(()=>{let e=e=>{e.target&&!r.current&&EC(_C,n,{originalEvent:e},{discrete:!1})};return t.addEventListener(`focusin`,e),()=>t.removeEventListener(`focusin`,e)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function TC(){let e=new CustomEvent(hC);document.dispatchEvent(e)}function EC(e,t,n,{discrete:r}){let i=n.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?dC(i,a):i.dispatchEvent(a)}var DC=0;function OC(){z.useEffect(()=>{let e=document.querySelectorAll(`[data-radix-focus-guard]`);return document.body.insertAdjacentElement(`afterbegin`,e[0]??kC()),document.body.insertAdjacentElement(`beforeend`,e[1]??kC()),DC++,()=>{DC===1&&document.querySelectorAll(`[data-radix-focus-guard]`).forEach(e=>e.remove()),DC--}},[])}function kC(){let e=document.createElement(`span`);return e.setAttribute(`data-radix-focus-guard`,``),e.tabIndex=0,e.style.outline=`none`,e.style.opacity=`0`,e.style.position=`fixed`,e.style.pointerEvents=`none`,e}function AC(e){let t=jC(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(NC);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function jC(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=FC(n),i=PC(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var MC=Symbol(`radix.slottable`);function NC(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===MC}function PC(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function FC(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var IC=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=AC(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),LC=`focusScope.autoFocusOnMount`,RC=`focusScope.autoFocusOnUnmount`,zC={bubbles:!1,cancelable:!0},BC=`FocusScope`,VC=z.forwardRef((e,t)=>{let{loop:n=!1,trapped:r=!1,onMountAutoFocus:i,onUnmountAutoFocus:a,...o}=e,[s,c]=z.useState(null),l=fC(i),u=fC(a),d=z.useRef(null),f=en(t,e=>c(e)),p=z.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;z.useEffect(()=>{if(r){let e=function(e){if(p.paused||!s)return;let t=e.target;s.contains(t)?d.current=t:JC(d.current,{select:!0})},t=function(e){if(p.paused||!s)return;let t=e.relatedTarget;t!==null&&(s.contains(t)||JC(d.current,{select:!0}))},n=function(e){if(document.activeElement===document.body)for(let t of e)t.removedNodes.length>0&&JC(s)};document.addEventListener(`focusin`,e),document.addEventListener(`focusout`,t);let r=new MutationObserver(n);return s&&r.observe(s,{childList:!0,subtree:!0}),()=>{document.removeEventListener(`focusin`,e),document.removeEventListener(`focusout`,t),r.disconnect()}}},[r,s,p.paused]),z.useEffect(()=>{if(s){YC.add(p);let e=document.activeElement;if(!s.contains(e)){let t=new CustomEvent(LC,zC);s.addEventListener(LC,l),s.dispatchEvent(t),t.defaultPrevented||(HC(QC(WC(s)),{select:!0}),document.activeElement===e&&JC(s))}return()=>{s.removeEventListener(LC,l),setTimeout(()=>{let t=new CustomEvent(RC,zC);s.addEventListener(RC,u),s.dispatchEvent(t),t.defaultPrevented||JC(e??document.body,{select:!0}),s.removeEventListener(RC,u),YC.remove(p)},0)}}},[s,l,u,p]);let m=z.useCallback(e=>{if(!n&&!r||p.paused)return;let t=e.key===`Tab`&&!e.altKey&&!e.ctrlKey&&!e.metaKey,i=document.activeElement;if(t&&i){let t=e.currentTarget,[r,a]=UC(t);r&&a?!e.shiftKey&&i===a?(e.preventDefault(),n&&JC(r,{select:!0})):e.shiftKey&&i===r&&(e.preventDefault(),n&&JC(a,{select:!0})):i===t&&e.preventDefault()}},[n,r,p.paused]);return(0,B.jsx)(IC.div,{tabIndex:-1,...o,ref:f,onKeyDown:m})});VC.displayName=BC;function HC(e,{select:t=!1}={}){let n=document.activeElement;for(let r of e)if(JC(r,{select:t}),document.activeElement!==n)return}function UC(e){let t=WC(e);return[GC(t,e),GC(t.reverse(),e)]}function WC(e){let t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t=e.tagName===`INPUT`&&e.type===`hidden`;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function GC(e,t){for(let n of e)if(!KC(n,{upTo:t}))return n}function KC(e,{upTo:t}){if(getComputedStyle(e).visibility===`hidden`)return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display===`none`)return!0;e=e.parentElement}return!1}function qC(e){return e instanceof HTMLInputElement&&`select`in e}function JC(e,{select:t=!1}={}){if(e&&e.focus){let n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&qC(e)&&t&&e.select()}}var YC=XC();function XC(){let e=[];return{add(t){let n=e[0];t!==n&&n?.pause(),e=ZC(e,t),e.unshift(t)},remove(t){e=ZC(e,t),e[0]?.resume()}}}function ZC(e,t){let n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function QC(e){return e.filter(e=>e.tagName!==`A`)}var $C=globalThis?.document?z.useLayoutEffect:()=>{},ew=z.useId||(()=>void 0),tw=0;function nw(e){let[t,n]=z.useState(ew());return $C(()=>{e||n(e=>e??String(tw++))},[e]),e||(t?`radix-${t}`:``)}var rw=[`top`,`right`,`bottom`,`left`],iw=Math.min,aw=Math.max,ow=Math.round,sw=Math.floor,cw=e=>({x:e,y:e}),lw={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function uw(e,t,n){return aw(e,iw(t,n))}function dw(e,t){return typeof e==`function`?e(t):e}function fw(e){return e.split(`-`)[0]}function pw(e){return e.split(`-`)[1]}function mw(e){return e===`x`?`y`:`x`}function hw(e){return e===`y`?`height`:`width`}function gw(e){let t=e[0];return t===`t`||t===`b`?`y`:`x`}function _w(e){return mw(gw(e))}function vw(e,t,n){n===void 0&&(n=!1);let r=pw(e),i=_w(e),a=hw(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=Dw(o)),[o,Dw(o)]}function yw(e){let t=Dw(e);return[bw(e),t,bw(t)]}function bw(e){return e.includes(`start`)?e.replace(`start`,`end`):e.replace(`end`,`start`)}var xw=[`left`,`right`],Sw=[`right`,`left`],Cw=[`top`,`bottom`],ww=[`bottom`,`top`];function Tw(e,t,n){switch(e){case`top`:case`bottom`:return n?t?Sw:xw:t?xw:Sw;case`left`:case`right`:return t?Cw:ww;default:return[]}}function Ew(e,t,n,r){let i=pw(e),a=Tw(fw(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map(bw)))),a}function Dw(e){let t=fw(e);return lw[t]+e.slice(t.length)}function Ow(e){return{top:0,right:0,bottom:0,left:0,...e}}function kw(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:Ow(e)}function Aw(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function jw(e,t,n){let{reference:r,floating:i}=e,a=gw(t),o=_w(t),s=hw(o),c=fw(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}switch(pw(t)){case`start`:p[o]-=f*(n&&l?-1:1);break;case`end`:p[o]+=f*(n&&l?-1:1);break}return p}async function Mw(e,t){t===void 0&&(t={});let{x:n,y:r,platform:i,rects:a,elements:o,strategy:s}=e,{boundary:c=`clippingAncestors`,rootBoundary:l=`viewport`,elementContext:u=`floating`,altBoundary:d=!1,padding:f=0}=dw(t,e),p=kw(f),m=o[d?u===`floating`?`reference`:`floating`:u],h=Aw(await i.getClippingRect({element:await(i.isElement==null?void 0:i.isElement(m))??!0?m:m.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(o.floating)),boundary:c,rootBoundary:l,strategy:s})),g=u===`floating`?{x:n,y:r,width:a.floating.width,height:a.floating.height}:a.reference,_=await(i.getOffsetParent==null?void 0:i.getOffsetParent(o.floating)),v=await(i.isElement==null?void 0:i.isElement(_))&&await(i.getScale==null?void 0:i.getScale(_))||{x:1,y:1},y=Aw(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:g,offsetParent:_,strategy:s}):g);return{top:(h.top-y.top+p.top)/v.y,bottom:(y.bottom-h.bottom+p.bottom)/v.y,left:(h.left-y.left+p.left)/v.x,right:(y.right-h.right+p.right)/v.x}}var Nw=50,Pw=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=o.detectOverflow?o:{...o,detectOverflow:Mw},c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=jw(l,r,c),f=r,p=0,m={};for(let n=0;n({name:`arrow`,options:e,async fn(t){let{x:n,y:r,placement:i,rects:a,platform:o,elements:s,middlewareData:c}=t,{element:l,padding:u=0}=dw(e,t)||{};if(l==null)return{};let d=kw(u),f={x:n,y:r},p=_w(i),m=hw(p),h=await o.getDimensions(l),g=p===`y`,_=g?`top`:`left`,v=g?`bottom`:`right`,y=g?`clientHeight`:`clientWidth`,b=a.reference[m]+a.reference[p]-f[p]-a.floating[m],x=f[p]-a.reference[p],S=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l)),C=S?S[y]:0;(!C||!await(o.isElement==null?void 0:o.isElement(S)))&&(C=s.floating[y]||a.floating[m]);let w=b/2-x/2,T=C/2-h[m]/2-1,E=iw(d[_],T),ee=iw(d[v],T),D=E,O=C-h[m]-ee,k=C/2-h[m]/2+w,A=uw(D,k,O),j=!c.arrow&&pw(i)!=null&&k!==A&&a.reference[m]/2-(ke<=0)){let e=(i.flip?.index||0)+1,t=S[e];if(t&&(!(u===`alignment`&&_!==gw(t))||T.every(e=>gw(e.placement)===_?e.overflows[0]>0:!0)))return{data:{index:e,overflows:T},reset:{placement:t}};let n=T.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=T.filter(e=>{if(x){let t=gw(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o;break}if(r!==n)return{reset:{placement:n}}}return{}}}};function Lw(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function Rw(e){return rw.some(t=>e[t]>=0)}var zw=function(e){return e===void 0&&(e={}),{name:`hide`,options:e,async fn(t){let{rects:n,platform:r}=t,{strategy:i=`referenceHidden`,...a}=dw(e,t);switch(i){case`referenceHidden`:{let e=Lw(await r.detectOverflow(t,{...a,elementContext:`reference`}),n.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:Rw(e)}}}case`escaped`:{let e=Lw(await r.detectOverflow(t,{...a,altBoundary:!0}),n.floating);return{data:{escapedOffsets:e,escaped:Rw(e)}}}default:return{}}}}},Bw=new Set([`left`,`top`]);async function Vw(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=fw(n),s=pw(n),c=gw(n)===`y`,l=Bw.has(o)?-1:1,u=a&&c?-1:1,d=dw(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}var Hw=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await Vw(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},Uw=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i,platform:a}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:c={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=dw(e,t),u={x:n,y:r},d=await a.detectOverflow(t,l),f=gw(fw(i)),p=mw(f),m=u[p],h=u[f];if(o){let e=p===`y`?`top`:`left`,t=p===`y`?`bottom`:`right`,n=m+d[e],r=m-d[t];m=uw(n,m,r)}if(s){let e=f===`y`?`top`:`left`,t=f===`y`?`bottom`:`right`,n=h+d[e],r=h-d[t];h=uw(n,h,r)}let g=c.fn({...t,[p]:m,[f]:h});return{...g,data:{x:g.x-n,y:g.y-r,enabled:{[p]:o,[f]:s}}}}}},Ww=function(e){return e===void 0&&(e={}),{options:e,fn(t){let{x:n,y:r,placement:i,rects:a,middlewareData:o}=t,{offset:s=0,mainAxis:c=!0,crossAxis:l=!0}=dw(e,t),u={x:n,y:r},d=gw(i),f=mw(d),p=u[f],m=u[d],h=dw(s,t),g=typeof h==`number`?{mainAxis:h,crossAxis:0}:{mainAxis:0,crossAxis:0,...h};if(c){let e=f===`y`?`height`:`width`,t=a.reference[f]-a.floating[e]+g.mainAxis,n=a.reference[f]+a.reference[e]-g.mainAxis;pn&&(p=n)}if(l){let e=f===`y`?`width`:`height`,t=Bw.has(fw(i)),n=a.reference[d]-a.floating[e]+(t&&o.offset?.[d]||0)+(t?0:g.crossAxis),r=a.reference[d]+a.reference[e]+(t?0:o.offset?.[d]||0)-(t?g.crossAxis:0);mr&&(m=r)}return{[f]:p,[d]:m}}}},Gw=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){var n,r;let{placement:i,rects:a,platform:o,elements:s}=t,{apply:c=()=>{},...l}=dw(e,t),u=await o.detectOverflow(t,l),d=fw(i),f=pw(i),p=gw(i)===`y`,{width:m,height:h}=a.floating,g,_;d===`top`||d===`bottom`?(g=d,_=f===(await(o.isRTL==null?void 0:o.isRTL(s.floating))?`start`:`end`)?`left`:`right`):(_=d,g=f===`end`?`top`:`bottom`);let v=h-u.top-u.bottom,y=m-u.left-u.right,b=iw(h-u[g],v),x=iw(m-u[_],y),S=!t.middlewareData.shift,C=b,w=x;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(w=y),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(C=v),S&&!f){let e=aw(u.left,0),t=aw(u.right,0),n=aw(u.top,0),r=aw(u.bottom,0);p?w=m-2*(e!==0||t!==0?e+t:aw(u.left,u.right)):C=h-2*(n!==0||r!==0?n+r:aw(u.top,u.bottom))}await c({...t,availableWidth:w,availableHeight:C});let T=await o.getDimensions(s.floating);return m!==T.width||h!==T.height?{reset:{rects:!0}}:{}}}};function Kw(){return typeof window<`u`}function qw(e){return Xw(e)?(e.nodeName||``).toLowerCase():`#document`}function Jw(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function Yw(e){return((Xw(e)?e.ownerDocument:e.document)||window.document)?.documentElement}function Xw(e){return Kw()?e instanceof Node||e instanceof Jw(e).Node:!1}function Zw(e){return Kw()?e instanceof Element||e instanceof Jw(e).Element:!1}function Qw(e){return Kw()?e instanceof HTMLElement||e instanceof Jw(e).HTMLElement:!1}function $w(e){return!Kw()||typeof ShadowRoot>`u`?!1:e instanceof ShadowRoot||e instanceof Jw(e).ShadowRoot}function eT(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=dT(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&i!==`inline`&&i!==`contents`}function tT(e){return/^(table|td|th)$/.test(qw(e))}function nT(e){try{if(e.matches(`:popover-open`))return!0}catch{}try{return e.matches(`:modal`)}catch{return!1}}var rT=/transform|translate|scale|rotate|perspective|filter/,iT=/paint|layout|strict|content/,aT=e=>!!e&&e!==`none`,oT;function sT(e){let t=Zw(e)?dT(e):e;return aT(t.transform)||aT(t.translate)||aT(t.scale)||aT(t.rotate)||aT(t.perspective)||!lT()&&(aT(t.backdropFilter)||aT(t.filter))||rT.test(t.willChange||``)||iT.test(t.contain||``)}function cT(e){let t=pT(e);for(;Qw(t)&&!uT(t);){if(sT(t))return t;if(nT(t))return null;t=pT(t)}return null}function lT(){return oT??=typeof CSS<`u`&&CSS.supports&&CSS.supports(`-webkit-backdrop-filter`,`none`),oT}function uT(e){return/^(html|body|#document)$/.test(qw(e))}function dT(e){return Jw(e).getComputedStyle(e)}function fT(e){return Zw(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function pT(e){if(qw(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||$w(e)&&e.host||Yw(e);return $w(t)?t.host:t}function mT(e){let t=pT(e);return uT(t)?e.ownerDocument?e.ownerDocument.body:e.body:Qw(t)&&eT(t)?t:mT(t)}function hT(e,t,n){t===void 0&&(t=[]),n===void 0&&(n=!0);let r=mT(e),i=r===e.ownerDocument?.body,a=Jw(r);if(i){let e=gT(a);return t.concat(a,a.visualViewport||[],eT(r)?r:[],e&&n?hT(e):[])}else return t.concat(r,hT(r,[],n))}function gT(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function _T(e){let t=dT(e),n=parseFloat(t.width)||0,r=parseFloat(t.height)||0,i=Qw(e),a=i?e.offsetWidth:n,o=i?e.offsetHeight:r,s=ow(n)!==a||ow(r)!==o;return s&&(n=a,r=o),{width:n,height:r,$:s}}function vT(e){return Zw(e)?e:e.contextElement}function yT(e){let t=vT(e);if(!Qw(t))return cw(1);let n=t.getBoundingClientRect(),{width:r,height:i,$:a}=_T(t),o=(a?ow(n.width):n.width)/r,s=(a?ow(n.height):n.height)/i;return(!o||!Number.isFinite(o))&&(o=1),(!s||!Number.isFinite(s))&&(s=1),{x:o,y:s}}var bT=cw(0);function xT(e){let t=Jw(e);return!lT()||!t.visualViewport?bT:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function ST(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==Jw(e)?!1:t}function CT(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=vT(e),o=cw(1);t&&(r?Zw(r)&&(o=yT(r)):o=yT(e));let s=ST(a,n,r)?xT(a):cw(0),c=(i.left+s.x)/o.x,l=(i.top+s.y)/o.y,u=i.width/o.x,d=i.height/o.y;if(a){let e=Jw(a),t=r&&Zw(r)?Jw(r):r,n=e,i=gT(n);for(;i&&r&&t!==n;){let e=yT(i),t=i.getBoundingClientRect(),r=dT(i),a=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,o=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;c*=e.x,l*=e.y,u*=e.x,d*=e.y,c+=a,l+=o,n=Jw(i),i=gT(n)}}return Aw({width:u,height:d,x:c,y:l})}function wT(e,t){let n=fT(e).scrollLeft;return t?t.left+n:CT(Yw(e)).left+n}function TT(e,t){let n=e.getBoundingClientRect();return{x:n.left+t.scrollLeft-wT(e,n),y:n.top+t.scrollTop}}function ET(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e,a=i===`fixed`,o=Yw(r),s=t?nT(t.floating):!1;if(r===o||s&&a)return n;let c={scrollLeft:0,scrollTop:0},l=cw(1),u=cw(0),d=Qw(r);if((d||!d&&!a)&&((qw(r)!==`body`||eT(o))&&(c=fT(r)),d)){let e=CT(r);l=yT(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}let f=o&&!d&&!a?TT(o,c):cw(0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-c.scrollLeft*l.x+u.x+f.x,y:n.y*l.y-c.scrollTop*l.y+u.y+f.y}}function DT(e){return Array.from(e.getClientRects())}function OT(e){let t=Yw(e),n=fT(e),r=e.ownerDocument.body,i=aw(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=aw(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+wT(e),s=-n.scrollTop;return dT(r).direction===`rtl`&&(o+=aw(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}var kT=25;function AT(e,t){let n=Jw(e),r=Yw(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=lT();(!e||e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}let l=wT(r);if(l<=0){let e=r.ownerDocument,t=e.body,n=getComputedStyle(t),i=e.compatMode===`CSS1Compat`&&parseFloat(n.marginLeft)+parseFloat(n.marginRight)||0,o=Math.abs(r.clientWidth-t.clientWidth-i);o<=kT&&(a-=o)}else l<=kT&&(a+=l);return{width:a,height:o,x:s,y:c}}function jT(e,t){let n=CT(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=Qw(e)?yT(e):cw(1);return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}function MT(e,t,n){let r;if(t===`viewport`)r=AT(e,n);else if(t===`document`)r=OT(Yw(e));else if(Zw(t))r=jT(t,n);else{let n=xT(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return Aw(r)}function NT(e,t){let n=pT(e);return n===t||!Zw(n)||uT(n)?!1:dT(n).position===`fixed`||NT(n,t)}function PT(e,t){let n=t.get(e);if(n)return n;let r=hT(e,[],!1).filter(e=>Zw(e)&&qw(e)!==`body`),i=null,a=dT(e).position===`fixed`,o=a?pT(e):e;for(;Zw(o)&&!uT(o);){let t=dT(o),n=sT(o);!n&&t.position===`fixed`&&(i=null),(a?!n&&!i:!n&&t.position===`static`&&i&&(i.position===`absolute`||i.position===`fixed`)||eT(o)&&!n&&NT(e,o))?r=r.filter(e=>e!==o):i=t,o=pT(o)}return t.set(e,r),r}function FT(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?nT(t)?[]:PT(t,this._c):[].concat(n),r],o=MT(t,a[0],i),s=o.top,c=o.right,l=o.bottom,u=o.left;for(let e=1;e{o(!1,1e-7)},1e3)}n===1&&!WT(l,e.getBoundingClientRect())&&o(),y=!1}try{n=new IntersectionObserver(b,{...v,root:i.ownerDocument})}catch{n=new IntersectionObserver(b,v)}n.observe(e)}return o(!0),a}function KT(e,t,n,r){r===void 0&&(r={});let{ancestorScroll:i=!0,ancestorResize:a=!0,elementResize:o=typeof ResizeObserver==`function`,layoutShift:s=typeof IntersectionObserver==`function`,animationFrame:c=!1}=r,l=vT(e),u=i||a?[...l?hT(l):[],...t?hT(t):[]]:[];u.forEach(e=>{i&&e.addEventListener(`scroll`,n,{passive:!0}),a&&e.addEventListener(`resize`,n)});let d=l&&s?GT(l,n):null,f=-1,p=null;o&&(p=new ResizeObserver(e=>{let[r]=e;r&&r.target===l&&p&&t&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var e;(e=p)==null||e.observe(t)})),n()}),l&&!c&&p.observe(l),t&&p.observe(t));let m,h=c?CT(e):null;c&&g();function g(){let t=CT(e);h&&!WT(h,t)&&n(),h=t,m=requestAnimationFrame(g)}return n(),()=>{var e;u.forEach(e=>{i&&e.removeEventListener(`scroll`,n),a&&e.removeEventListener(`resize`,n)}),d?.(),(e=p)==null||e.disconnect(),p=null,c&&cancelAnimationFrame(m)}}var qT=Hw,JT=Uw,YT=Iw,XT=Gw,ZT=zw,QT=Fw,$T=Ww,eE=(e,t,n)=>{let r=new Map,i={platform:UT,...n},a={...i.platform,_c:r};return Pw(e,t,{...i,platform:a})},tE=typeof document<`u`?z.useLayoutEffect:function(){};function nE(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(typeof e==`function`&&e.toString()===t.toString())return!0;let n,r,i;if(e&&t&&typeof e==`object`){if(Array.isArray(e)){if(n=e.length,n!==t.length)return!1;for(r=n;r--!==0;)if(!nE(e[r],t[r]))return!1;return!0}if(i=Object.keys(e),n=i.length,n!==Object.keys(t).length)return!1;for(r=n;r--!==0;)if(!{}.hasOwnProperty.call(t,i[r]))return!1;for(r=n;r--!==0;){let n=i[r];if(!(n===`_owner`&&e.$$typeof)&&!nE(e[n],t[n]))return!1}return!0}return e!==e&&t!==t}function rE(e){return typeof window>`u`?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function iE(e,t){let n=rE(e);return Math.round(t*n)/n}function aE(e){let t=z.useRef(e);return tE(()=>{t.current=e}),t}function oE(e){e===void 0&&(e={});let{placement:t=`bottom`,strategy:n=`absolute`,middleware:r=[],platform:i,elements:{reference:a,floating:o}={},transform:s=!0,whileElementsMounted:c,open:l}=e,[u,d]=z.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[f,p]=z.useState(r);nE(f,r)||p(r);let[m,h]=z.useState(null),[g,_]=z.useState(null),v=z.useCallback(e=>{e!==S.current&&(S.current=e,h(e))},[]),y=z.useCallback(e=>{e!==C.current&&(C.current=e,_(e))},[]),b=a||m,x=o||g,S=z.useRef(null),C=z.useRef(null),w=z.useRef(u),T=c!=null,E=aE(c),ee=aE(i),D=aE(l),O=z.useCallback(()=>{if(!S.current||!C.current)return;let e={placement:t,strategy:n,middleware:f};ee.current&&(e.platform=ee.current),eE(S.current,C.current,e).then(e=>{let t={...e,isPositioned:D.current!==!1};k.current&&!nE(w.current,t)&&(w.current=t,Fi.flushSync(()=>{d(t)}))})},[f,t,n,ee,D]);tE(()=>{l===!1&&w.current.isPositioned&&(w.current.isPositioned=!1,d(e=>({...e,isPositioned:!1})))},[l]);let k=z.useRef(!1);tE(()=>(k.current=!0,()=>{k.current=!1}),[]),tE(()=>{if(b&&(S.current=b),x&&(C.current=x),b&&x){if(E.current)return E.current(b,x,O);O()}},[b,x,O,E,T]);let A=z.useMemo(()=>({reference:S,floating:C,setReference:v,setFloating:y}),[v,y]),j=z.useMemo(()=>({reference:b,floating:x}),[b,x]),M=z.useMemo(()=>{let e={position:n,left:0,top:0};if(!j.floating)return e;let t=iE(j.floating,u.x),r=iE(j.floating,u.y);return s?{...e,transform:`translate(`+t+`px, `+r+`px)`,...rE(j.floating)>=1.5&&{willChange:`transform`}}:{position:n,left:t,top:r}},[n,s,j.floating,u.x,u.y]);return z.useMemo(()=>({...u,update:O,refs:A,elements:j,floatingStyles:M}),[u,O,A,j,M])}var sE=e=>{function t(e){return{}.hasOwnProperty.call(e,`current`)}return{name:`arrow`,options:e,fn(n){let{element:r,padding:i}=typeof e==`function`?e(n):e;return r&&t(r)?r.current==null?{}:QT({element:r.current,padding:i}).fn(n):r?QT({element:r,padding:i}).fn(n):{}}}},cE=(e,t)=>{let n=qT(e);return{name:n.name,fn:n.fn,options:[e,t]}},lE=(e,t)=>{let n=JT(e);return{name:n.name,fn:n.fn,options:[e,t]}},uE=(e,t)=>({fn:$T(e).fn,options:[e,t]}),dE=(e,t)=>{let n=YT(e);return{name:n.name,fn:n.fn,options:[e,t]}},fE=(e,t)=>{let n=XT(e);return{name:n.name,fn:n.fn,options:[e,t]}},pE=(e,t)=>{let n=ZT(e);return{name:n.name,fn:n.fn,options:[e,t]}},mE=(e,t)=>{let n=sE(e);return{name:n.name,fn:n.fn,options:[e,t]}};function hE(e){let t=gE(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(vE);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function gE(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=bE(n),i=yE(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var _E=Symbol(`radix.slottable`);function vE(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===_E}function yE(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function bE(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var xE=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=hE(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),SE=`Arrow`,CE=z.forwardRef((e,t)=>{let{children:n,width:r=10,height:i=5,...a}=e;return(0,B.jsx)(xE.svg,{...a,ref:t,width:r,height:i,viewBox:`0 0 30 10`,preserveAspectRatio:`none`,children:e.asChild?n:(0,B.jsx)(`polygon`,{points:`0,0 30,0 15,10`})})});CE.displayName=SE;var wE=CE;function TE(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,EE(i,...t)]}function EE(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function DE(e){let t=OE(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(AE);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function OE(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=ME(n),i=jE(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var kE=Symbol(`radix.slottable`);function AE(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===kE}function jE(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function ME(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var NE=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=DE(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function PE(e){let[t,n]=z.useState(void 0);return $C(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{if(!Array.isArray(t)||!t.length)return;let r=t[0],i,a;if(`borderBoxSize`in r){let e=r.borderBoxSize,t=Array.isArray(e)?e[0]:e;i=t.inlineSize,a=t.blockSize}else i=e.offsetWidth,a=e.offsetHeight;n({width:i,height:a})});return t.observe(e,{box:`border-box`}),()=>t.unobserve(e)}else n(void 0)},[e]),t}var FE=`Popper`,[IE,LE]=TE(FE),[RE,zE]=IE(FE),BE=e=>{let{__scopePopper:t,children:n}=e,[r,i]=z.useState(null);return(0,B.jsx)(RE,{scope:t,anchor:r,onAnchorChange:i,children:n})};BE.displayName=FE;var VE=`PopperAnchor`,HE=z.forwardRef((e,t)=>{let{__scopePopper:n,virtualRef:r,...i}=e,a=zE(VE,n),o=z.useRef(null),s=en(t,o),c=z.useRef(null);return z.useEffect(()=>{let e=c.current;c.current=r?.current||o.current,e!==c.current&&a.onAnchorChange(c.current)}),r?null:(0,B.jsx)(NE.div,{...i,ref:s})});HE.displayName=VE;var UE=`PopperContent`,[WE,GE]=IE(UE),KE=z.forwardRef((e,t)=>{let{__scopePopper:n,side:r=`bottom`,sideOffset:i=0,align:a=`center`,alignOffset:o=0,arrowPadding:s=0,avoidCollisions:c=!0,collisionBoundary:l=[],collisionPadding:u=0,sticky:d=`partial`,hideWhenDetached:f=!1,updatePositionStrategy:p=`optimized`,onPlaced:m,...h}=e,g=zE(UE,n),[_,v]=z.useState(null),y=en(t,e=>v(e)),[b,x]=z.useState(null),S=PE(b),C=S?.width??0,w=S?.height??0,T=r+(a===`center`?``:`-`+a),E=typeof u==`number`?u:{top:0,right:0,bottom:0,left:0,...u},ee=Array.isArray(l)?l:[l],D=ee.length>0,O={padding:E,boundary:ee.filter(XE),altBoundary:D},{refs:k,floatingStyles:A,placement:j,isPositioned:M,middlewareData:N}=oE({strategy:`fixed`,placement:T,whileElementsMounted:(...e)=>KT(...e,{animationFrame:p===`always`}),elements:{reference:g.anchor},middleware:[cE({mainAxis:i+w,alignmentAxis:o}),c&&lE({mainAxis:!0,crossAxis:!1,limiter:d===`partial`?uE():void 0,...O}),c&&dE({...O}),fE({...O,apply:({elements:e,rects:t,availableWidth:n,availableHeight:r})=>{let{width:i,height:a}=t.reference,o=e.floating.style;o.setProperty(`--radix-popper-available-width`,`${n}px`),o.setProperty(`--radix-popper-available-height`,`${r}px`),o.setProperty(`--radix-popper-anchor-width`,`${i}px`),o.setProperty(`--radix-popper-anchor-height`,`${a}px`)}}),b&&mE({element:b,padding:s}),ZE({arrowWidth:C,arrowHeight:w}),f&&pE({strategy:`referenceHidden`,...O})]}),[P,F]=QE(j),I=fC(m);$C(()=>{M&&I?.()},[M,I]);let te=N.arrow?.x,ne=N.arrow?.y,re=N.arrow?.centerOffset!==0,[L,R]=z.useState();return $C(()=>{_&&R(window.getComputedStyle(_).zIndex)},[_]),(0,B.jsx)(`div`,{ref:k.setFloating,"data-radix-popper-content-wrapper":``,style:{...A,transform:M?A.transform:`translate(0, -200%)`,minWidth:`max-content`,zIndex:L,"--radix-popper-transform-origin":[N.transformOrigin?.x,N.transformOrigin?.y].join(` `),...N.hide?.referenceHidden&&{visibility:`hidden`,pointerEvents:`none`}},dir:e.dir,children:(0,B.jsx)(WE,{scope:n,placedSide:P,onArrowChange:x,arrowX:te,arrowY:ne,shouldHideArrow:re,children:(0,B.jsx)(NE.div,{"data-side":P,"data-align":F,...h,ref:y,style:{...h.style,animation:M?void 0:`none`}})})})});KE.displayName=UE;var qE=`PopperArrow`,JE={top:`bottom`,right:`left`,bottom:`top`,left:`right`},YE=z.forwardRef(function(e,t){let{__scopePopper:n,...r}=e,i=GE(qE,n),a=JE[i.placedSide];return(0,B.jsx)(`span`,{ref:i.onArrowChange,style:{position:`absolute`,left:i.arrowX,top:i.arrowY,[a]:0,transformOrigin:{top:``,right:`0 0`,bottom:`center 0`,left:`100% 0`}[i.placedSide],transform:{top:`translateY(100%)`,right:`translateY(50%) rotate(90deg) translateX(-50%)`,bottom:`rotate(180deg)`,left:`translateY(50%) rotate(-90deg) translateX(50%)`}[i.placedSide],visibility:i.shouldHideArrow?`hidden`:void 0},children:(0,B.jsx)(wE,{...r,ref:t,style:{...r.style,display:`block`}})})});YE.displayName=qE;function XE(e){return e!==null}var ZE=e=>({name:`transformOrigin`,options:e,fn(t){let{placement:n,rects:r,middlewareData:i}=t,a=i.arrow?.centerOffset!==0,o=a?0:e.arrowWidth,s=a?0:e.arrowHeight,[c,l]=QE(n),u={start:`0%`,center:`50%`,end:`100%`}[l],d=(i.arrow?.x??0)+o/2,f=(i.arrow?.y??0)+s/2,p=``,m=``;return c===`bottom`?(p=a?u:`${d}px`,m=`${-s}px`):c===`top`?(p=a?u:`${d}px`,m=`${r.floating.height+s}px`):c===`right`?(p=`${-s}px`,m=a?u:`${f}px`):c===`left`&&(p=`${r.floating.width+s}px`,m=a?u:`${f}px`),{data:{x:p,y:m}}}});function QE(e){let[t,n=`center`]=e.split(`-`);return[t,n]}var $E=BE,eD=HE,tD=KE,nD=YE;function rD(e){let t=iD(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(oD);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function iD(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=cD(n),i=sD(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var aD=Symbol(`radix.slottable`);function oD(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===aD}function sD(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function cD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var lD=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=rD(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),uD=`Portal`,dD=z.forwardRef((e,t)=>{let{container:n,...r}=e,[i,a]=z.useState(!1);$C(()=>a(!0),[]);let o=n||i&&globalThis?.document?.body;return o?Fi.createPortal((0,B.jsx)(lD.div,{...r,ref:t}),o):null});dD.displayName=uD;function fD(e,t){return z.useReducer((e,n)=>t[e][n]??e,e)}var pD=e=>{let{present:t,children:n}=e,r=mD(t),i=typeof n==`function`?n({present:r.isPresent}):z.Children.only(n),a=en(r.ref,gD(i));return typeof n==`function`||r.isPresent?z.cloneElement(i,{ref:a}):null};pD.displayName=`Presence`;function mD(e){let[t,n]=z.useState(),r=z.useRef(null),i=z.useRef(e),a=z.useRef(`none`),[o,s]=fD(e?`mounted`:`unmounted`,{mounted:{UNMOUNT:`unmounted`,ANIMATION_OUT:`unmountSuspended`},unmountSuspended:{MOUNT:`mounted`,ANIMATION_END:`unmounted`},unmounted:{MOUNT:`mounted`}});return z.useEffect(()=>{let e=hD(r.current);a.current=o===`mounted`?e:`none`},[o]),$C(()=>{let t=r.current,n=i.current;if(n!==e){let r=a.current,o=hD(t);e?s(`MOUNT`):o===`none`||t?.display===`none`?s(`UNMOUNT`):s(n&&r!==o?`ANIMATION_OUT`:`UNMOUNT`),i.current=e}},[e,s]),$C(()=>{if(t){let e,n=t.ownerDocument.defaultView??window,o=a=>{let o=hD(r.current).includes(CSS.escape(a.animationName));if(a.target===t&&o&&(s(`ANIMATION_END`),!i.current)){let r=t.style.animationFillMode;t.style.animationFillMode=`forwards`,e=n.setTimeout(()=>{t.style.animationFillMode===`forwards`&&(t.style.animationFillMode=r)})}},c=e=>{e.target===t&&(a.current=hD(r.current))};return t.addEventListener(`animationstart`,c),t.addEventListener(`animationcancel`,o),t.addEventListener(`animationend`,o),()=>{n.clearTimeout(e),t.removeEventListener(`animationstart`,c),t.removeEventListener(`animationcancel`,o),t.removeEventListener(`animationend`,o)}}else s(`ANIMATION_END`)},[t,s]),{isPresent:[`mounted`,`unmountSuspended`].includes(o),ref:z.useCallback(e=>{r.current=e?getComputedStyle(e):null,n(e)},[])}}function hD(e){return e?.animationName||`none`}function gD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function _D(e){let t=vD(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(bD);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function vD(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=SD(n),i=xD(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var yD=Symbol(`radix.slottable`);function bD(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===yD}function xD(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function SD(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var CD=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=_D(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),wD=z.useInsertionEffect||$C;function TD({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){let[i,a,o]=ED({defaultProp:t,onChange:n}),s=e!==void 0,c=s?e:i;{let t=z.useRef(e!==void 0);z.useEffect(()=>{let e=t.current;e!==s&&console.warn(`${r} is changing from ${e?`controlled`:`uncontrolled`} to ${s?`controlled`:`uncontrolled`}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),t.current=s},[s,r])}return[c,z.useCallback(t=>{if(s){let n=DD(t)?t(e):t;n!==e&&o.current?.(n)}else a(t)},[s,e,a,o])]}function ED({defaultProp:e,onChange:t}){let[n,r]=z.useState(e),i=z.useRef(n),a=z.useRef(t);return wD(()=>{a.current=t},[t]),z.useEffect(()=>{i.current!==n&&(a.current?.(n),i.current=n)},[n,i]),[n,r,a]}function DD(e){return typeof e==`function`}var OD=function(e){return typeof document>`u`?null:(Array.isArray(e)?e[0]:e).ownerDocument.body},kD=new WeakMap,AD=new WeakMap,jD={},MD=0,ND=function(e){return e&&(e.host||ND(e.parentNode))},PD=function(e,t){return t.map(function(t){if(e.contains(t))return t;var n=ND(t);return n&&e.contains(n)?n:(console.error(`aria-hidden`,t,`in not contained inside`,e,`. Doing nothing`),null)}).filter(function(e){return!!e})},FD=function(e,t,n,r){var i=PD(t,Array.isArray(e)?e:[e]);jD[n]||(jD[n]=new WeakMap);var a=jD[n],o=[],s=new Set,c=new Set(i),l=function(e){!e||s.has(e)||(s.add(e),l(e.parentNode))};i.forEach(l);var u=function(e){!e||c.has(e)||Array.prototype.forEach.call(e.children,function(e){if(s.has(e))u(e);else try{var t=e.getAttribute(r),i=t!==null&&t!==`false`,c=(kD.get(e)||0)+1,l=(a.get(e)||0)+1;kD.set(e,c),a.set(e,l),o.push(e),c===1&&i&&AD.set(e,!0),l===1&&e.setAttribute(n,`true`),i||e.setAttribute(r,`true`)}catch(t){console.error(`aria-hidden: cannot operate on `,e,t)}})};return u(t),s.clear(),MD++,function(){o.forEach(function(e){var t=kD.get(e)-1,i=a.get(e)-1;kD.set(e,t),a.set(e,i),t||(AD.has(e)||e.removeAttribute(r),AD.delete(e)),i||e.removeAttribute(n)}),MD--,MD||(kD=new WeakMap,kD=new WeakMap,AD=new WeakMap,jD={})}},ID=function(e,t,n){n===void 0&&(n=`data-aria-hidden`);var r=Array.from(Array.isArray(e)?e:[e]),i=t||OD(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll(`[aria-live], script`))),FD(r,i,n,`aria-hidden`)):function(){return null}},LD=function(){return LD=Object.assign||function(e){for(var t,n=1,r=arguments.length;n`u`)return dO;var t=pO(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},hO=uO(),gO=`data-scroll-locked`,_O=function(e,t,n,r){var i=e.left,a=e.top,o=e.right,s=e.gap;return n===void 0&&(n=`margin`),` + .${HD} { overflow: hidden ${r}; padding-right: ${s}px ${r}; } - body[${hO}] { + body[${gO}] { overflow: hidden ${r}; overscroll-behavior: contain; ${[t&&`position: relative ${r};`,n===`margin`&&` @@ -88,38 +88,38 @@ `,n===`padding`&&`padding-right: ${s}px ${r};`].filter(Boolean).join(``)} } - .${zD} { + .${BD} { right: ${s}px ${r}; } - .${BD} { + .${VD} { margin-right: ${s}px ${r}; } - .${zD} .${zD} { + .${BD} .${BD} { right: 0 ${r}; } - .${BD} .${BD} { + .${VD} .${VD} { margin-right: 0 ${r}; } - body[${hO}] { - ${HD}: ${s}px; + body[${gO}] { + ${UD}: ${s}px; } -`},_O=function(){var e=parseInt(document.body.getAttribute(`data-scroll-locked`)||`0`,10);return isFinite(e)?e:0},vO=function(){z.useEffect(function(){return document.body.setAttribute(hO,(_O()+1).toString()),function(){var e=_O()-1;e<=0?document.body.removeAttribute(hO):document.body.setAttribute(hO,e.toString())}},[])},yO=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,i=r===void 0?`margin`:r;vO();var a=z.useMemo(function(){return pO(i)},[i]);return z.createElement(mO,{styles:gO(a,!t,i,n?``:`!important`)})},bO=!1;if(typeof window<`u`)try{var xO=Object.defineProperty({},`passive`,{get:function(){return bO=!0,!0}});window.addEventListener(`test`,xO,xO),window.removeEventListener(`test`,xO,xO)}catch{bO=!1}var SO=bO?{passive:!1}:!1,CO=function(e){return e.tagName===`TEXTAREA`},wO=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return n[t]!==`hidden`&&!(n.overflowY===n.overflowX&&!CO(e)&&n[t]===`visible`)},TO=function(e){return wO(e,`overflowY`)},EO=function(e){return wO(e,`overflowX`)},DO=function(e,t){var n=t.ownerDocument,r=t;do{if(typeof ShadowRoot<`u`&&r instanceof ShadowRoot&&(r=r.host),AO(e,r)){var i=jO(e,r);if(i[1]>i[2])return!0}r=r.parentNode}while(r&&r!==n.body);return!1},OO=function(e){return[e.scrollTop,e.scrollHeight,e.clientHeight]},kO=function(e){return[e.scrollLeft,e.scrollWidth,e.clientWidth]},AO=function(e,t){return e===`v`?TO(t):EO(t)},jO=function(e,t){return e===`v`?OO(t):kO(t)},MO=function(e,t){return e===`h`&&t===`rtl`?-1:1},NO=function(e,t,n,r,i){var a=MO(e,window.getComputedStyle(t).direction),o=a*r,s=n.target,c=t.contains(s),l=!1,u=o>0,d=0,f=0;do{if(!s)break;var p=jO(e,s),m=p[0],h=p[1]-p[2]-a*m;(m||h)&&AO(e,s)&&(d+=h,f+=m);var g=s.parentNode;s=g&&g.nodeType===Node.DOCUMENT_FRAGMENT_NODE?g.host:g}while(!c&&s!==document.body||c&&(t.contains(s)||t===s));return(u&&(i&&Math.abs(d)<1||!i&&o>d)||!u&&(i&&Math.abs(f)<1||!i&&-o>f))&&(l=!0),l},PO=function(e){return`changedTouches`in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},FO=function(e){return[e.deltaX,e.deltaY]},IO=function(e){return e&&`current`in e?e.current:e},LO=function(e,t){return e[0]===t[0]&&e[1]===t[1]},RO=function(e){return` +`},vO=function(){var e=parseInt(document.body.getAttribute(`data-scroll-locked`)||`0`,10);return isFinite(e)?e:0},yO=function(){z.useEffect(function(){return document.body.setAttribute(gO,(vO()+1).toString()),function(){var e=vO()-1;e<=0?document.body.removeAttribute(gO):document.body.setAttribute(gO,e.toString())}},[])},bO=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,i=r===void 0?`margin`:r;yO();var a=z.useMemo(function(){return mO(i)},[i]);return z.createElement(hO,{styles:_O(a,!t,i,n?``:`!important`)})},xO=!1;if(typeof window<`u`)try{var SO=Object.defineProperty({},`passive`,{get:function(){return xO=!0,!0}});window.addEventListener(`test`,SO,SO),window.removeEventListener(`test`,SO,SO)}catch{xO=!1}var CO=xO?{passive:!1}:!1,wO=function(e){return e.tagName===`TEXTAREA`},TO=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return n[t]!==`hidden`&&!(n.overflowY===n.overflowX&&!wO(e)&&n[t]===`visible`)},EO=function(e){return TO(e,`overflowY`)},DO=function(e){return TO(e,`overflowX`)},OO=function(e,t){var n=t.ownerDocument,r=t;do{if(typeof ShadowRoot<`u`&&r instanceof ShadowRoot&&(r=r.host),jO(e,r)){var i=MO(e,r);if(i[1]>i[2])return!0}r=r.parentNode}while(r&&r!==n.body);return!1},kO=function(e){return[e.scrollTop,e.scrollHeight,e.clientHeight]},AO=function(e){return[e.scrollLeft,e.scrollWidth,e.clientWidth]},jO=function(e,t){return e===`v`?EO(t):DO(t)},MO=function(e,t){return e===`v`?kO(t):AO(t)},NO=function(e,t){return e===`h`&&t===`rtl`?-1:1},PO=function(e,t,n,r,i){var a=NO(e,window.getComputedStyle(t).direction),o=a*r,s=n.target,c=t.contains(s),l=!1,u=o>0,d=0,f=0;do{if(!s)break;var p=MO(e,s),m=p[0],h=p[1]-p[2]-a*m;(m||h)&&jO(e,s)&&(d+=h,f+=m);var g=s.parentNode;s=g&&g.nodeType===Node.DOCUMENT_FRAGMENT_NODE?g.host:g}while(!c&&s!==document.body||c&&(t.contains(s)||t===s));return(u&&(i&&Math.abs(d)<1||!i&&o>d)||!u&&(i&&Math.abs(f)<1||!i&&-o>f))&&(l=!0),l},FO=function(e){return`changedTouches`in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},IO=function(e){return[e.deltaX,e.deltaY]},LO=function(e){return e&&`current`in e?e.current:e},RO=function(e,t){return e[0]===t[0]&&e[1]===t[1]},zO=function(e){return` .block-interactivity-${e} {pointer-events: none;} .allow-interactivity-${e} {pointer-events: all;} -`},zO=0,BO=[];function VO(e){var t=z.useRef([]),n=z.useRef([0,0]),r=z.useRef(),i=z.useState(zO++)[0],a=z.useState(lO)[0],o=z.useRef(e);z.useEffect(function(){o.current=e},[e]),z.useEffect(function(){if(e.inert){document.body.classList.add(`block-interactivity-${i}`);var t=RD([e.lockRef.current],(e.shards||[]).map(IO),!0).filter(Boolean);return t.forEach(function(e){return e.classList.add(`allow-interactivity-${i}`)}),function(){document.body.classList.remove(`block-interactivity-${i}`),t.forEach(function(e){return e.classList.remove(`allow-interactivity-${i}`)})}}},[e.inert,e.lockRef.current,e.shards]);var s=z.useCallback(function(e,t){if(`touches`in e&&e.touches.length===2||e.type===`wheel`&&e.ctrlKey)return!o.current.allowPinchZoom;var i=PO(e),a=n.current,s=`deltaX`in e?e.deltaX:a[0]-i[0],c=`deltaY`in e?e.deltaY:a[1]-i[1],l,u=e.target,d=Math.abs(s)>Math.abs(c)?`h`:`v`;if(`touches`in e&&d===`h`&&u.type===`range`)return!1;var f=window.getSelection(),p=f&&f.anchorNode;if(p&&(p===u||p.contains(u)))return!1;var m=DO(d,u);if(!m)return!0;if(m?l=d:(l=d===`v`?`h`:`v`,m=DO(d,u)),!m)return!1;if(!r.current&&`changedTouches`in e&&(s||c)&&(r.current=l),!l)return!0;var h=r.current||l;return NO(h,t,e,h===`h`?s:c,!0)},[]),c=z.useCallback(function(e){var n=e;if(!(!BO.length||BO[BO.length-1]!==a)){var r=`deltaY`in n?FO(n):PO(n),i=t.current.filter(function(e){return e.name===n.type&&(e.target===n.target||n.target===e.shadowParent)&&LO(e.delta,r)})[0];if(i&&i.should){n.cancelable&&n.preventDefault();return}if(!i){var c=(o.current.shards||[]).map(IO).filter(Boolean).filter(function(e){return e.contains(n.target)});(c.length>0?s(n,c[0]):!o.current.noIsolation)&&n.cancelable&&n.preventDefault()}}},[]),l=z.useCallback(function(e,n,r,i){var a={name:e,delta:n,target:r,should:i,shadowParent:HO(r)};t.current.push(a),setTimeout(function(){t.current=t.current.filter(function(e){return e!==a})},1)},[]),u=z.useCallback(function(e){n.current=PO(e),r.current=void 0},[]),d=z.useCallback(function(t){l(t.type,FO(t),t.target,s(t,e.lockRef.current))},[]),f=z.useCallback(function(t){l(t.type,PO(t),t.target,s(t,e.lockRef.current))},[]);z.useEffect(function(){return BO.push(a),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:f}),document.addEventListener(`wheel`,c,SO),document.addEventListener(`touchmove`,c,SO),document.addEventListener(`touchstart`,u,SO),function(){BO=BO.filter(function(e){return e!==a}),document.removeEventListener(`wheel`,c,SO),document.removeEventListener(`touchmove`,c,SO),document.removeEventListener(`touchstart`,u,SO)}},[]);var p=e.removeScrollBar,m=e.inert;return z.createElement(z.Fragment,null,m?z.createElement(a,{styles:RO(i)}):null,p?z.createElement(yO,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function HO(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}var UO=QD($D,VO),WO=z.forwardRef(function(e,t){return z.createElement(tO,ID({},e,{ref:t,sideCar:UO}))});WO.classNames=tO.classNames;var GO=`Popover`,[KO,qO]=tC(GO,[IE]),JO=IE(),[YO,XO]=KO(GO),ZO=e=>{let{__scopePopover:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!1}=e,s=JO(t),c=z.useRef(null),[l,u]=z.useState(!1),[d,f]=wD({prop:r,defaultProp:i??!1,onChange:a,caller:GO});return(0,B.jsx)(QE,{...s,children:(0,B.jsx)(YO,{scope:t,contentId:tw(),triggerRef:c,open:d,onOpenChange:f,onOpenToggle:z.useCallback(()=>f(e=>!e),[f]),hasCustomAnchor:l,onCustomAnchorAdd:z.useCallback(()=>u(!0),[]),onCustomAnchorRemove:z.useCallback(()=>u(!1),[]),modal:o,children:n})})};ZO.displayName=GO;var QO=`PopoverAnchor`,$O=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=XO(QO,n),a=JO(n),{onCustomAnchorAdd:o,onCustomAnchorRemove:s}=i;return z.useEffect(()=>(o(),()=>s()),[o,s]),(0,B.jsx)($E,{...a,...r,ref:t})});$O.displayName=QO;var ek=`PopoverTrigger`,tk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=XO(ek,n),a=JO(n),o=en(t,i.triggerRef),s=(0,B.jsx)(SD.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.contentId,"data-state":gk(i.open),...r,ref:o,onClick:eC(e.onClick,i.onOpenToggle)});return i.hasCustomAnchor?s:(0,B.jsx)($E,{asChild:!0,...a,children:s})});tk.displayName=ek;var nk=`PopoverPortal`,[rk,ik]=KO(nk,{forceMount:void 0}),ak=e=>{let{__scopePopover:t,forceMount:n,children:r,container:i}=e,a=XO(nk,t);return(0,B.jsx)(rk,{scope:t,forceMount:n,children:(0,B.jsx)(fD,{present:n||a.open,children:(0,B.jsx)(uD,{asChild:!0,container:i,children:r})})})};ak.displayName=nk;var ok=`PopoverContent`,sk=z.forwardRef((e,t)=>{let n=ik(ok,e.__scopePopover),{forceMount:r=n.forceMount,...i}=e,a=XO(ok,e.__scopePopover);return(0,B.jsx)(fD,{present:r||a.open,children:a.modal?(0,B.jsx)(lk,{...i,ref:t}):(0,B.jsx)(uk,{...i,ref:t})})});sk.displayName=ok;var ck=gD(`PopoverContent.RemoveScroll`),lk=z.forwardRef((e,t)=>{let n=XO(ok,e.__scopePopover),r=z.useRef(null),i=en(t,r),a=z.useRef(!1);return z.useEffect(()=>{let e=r.current;if(e)return FD(e)},[]),(0,B.jsx)(WO,{as:ck,allowPinchZoom:!0,children:(0,B.jsx)(dk,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:eC(e.onCloseAutoFocus,e=>{e.preventDefault(),a.current||n.triggerRef.current?.focus()}),onPointerDownOutside:eC(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;a.current=t.button===2||n},{checkForDefaultPrevented:!1}),onFocusOutside:eC(e.onFocusOutside,e=>e.preventDefault(),{checkForDefaultPrevented:!1})})})}),uk=z.forwardRef((e,t)=>{let n=XO(ok,e.__scopePopover),r=z.useRef(!1),i=z.useRef(!1);return(0,B.jsx)(dk,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),dk=z.forwardRef((e,t)=>{let{__scopePopover:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,disableOutsidePointerEvents:o,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onInteractOutside:u,...d}=e,f=XO(ok,n),p=JO(n);return DC(),(0,B.jsx)(BC,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,B.jsx)(yC,{asChild:!0,disableOutsidePointerEvents:o,onInteractOutside:u,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onDismiss:()=>f.onOpenChange(!1),children:(0,B.jsx)(eD,{"data-state":gk(f.open),role:`dialog`,id:f.contentId,...p,...d,ref:t,style:{...d.style,"--radix-popover-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-popover-content-available-width":`var(--radix-popper-available-width)`,"--radix-popover-content-available-height":`var(--radix-popper-available-height)`,"--radix-popover-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-popover-trigger-height":`var(--radix-popper-anchor-height)`}})})})}),fk=`PopoverClose`,pk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=XO(fk,n);return(0,B.jsx)(SD.button,{type:`button`,...r,ref:t,onClick:eC(e.onClick,()=>i.onOpenChange(!1))})});pk.displayName=fk;var mk=`PopoverArrow`,hk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=JO(n);return(0,B.jsx)(tD,{...i,...r,ref:t})});hk.displayName=mk;function gk(e){return e?`open`:`closed`}var _k=ZO,vk=$O,yk=ak,bk=sk,xk=Ax({value:$(),label:$(),description:$(),group:$(),source:Bx([`backend_definition`,`configured_scope`]),owner_id:$().nullable(),resource_id:$().nullable(),disabled:Sx(),disabled_reason:$().nullable()}),Sk={items:Ox(xk),total:vx().int().nonnegative(),next_offset:vx().int().nonnegative().nullable(),version:$()},Ck={definitions_version:$(),evaluated_at:$(),max_age_seconds:vx().int().nonnegative()},wk=Px(`option_set`,[Ax({...Sk,option_set:Hx(`service-scope`),principal_type:Hx(`service_account`),owner_id:$(),service_account_id:$().nullable(),selected_items:Ox(xk),freshness:Ax({...Ck,resources:Hx(`live`)})}),...[`service-history-action`,`service-history-field`].map(e=>Ax({...Sk,option_set:Hx(e),selected_items:Ox(xk).max(0).default([]),freshness:Ax({...Ck,resources:Hx(`static`)})}).strict())]),Tk=class extends Error{};function Ek(e,t,n=``){let r=$S(e=>e.user?.id),i=Je(),a=ut({queryKey:[`options`,r,e,t,n],initialPageParam:{offset:0,version:null,pageCount:1},queryFn:async({pageParam:r,signal:i})=>{if(r.pageCount>(e===`service-scope`?102:12))throw Error(`Options exceed the supported page limit.`);let a=new URLSearchParams({search:n,offset:String(r.offset),limit:`100`});if(t.kind===`service-scope`){if(e!==`service-scope`)throw Error(`Invalid options context.`);a.set(`principal_type`,t.principal_type),a.set(`owner_id`,t.owner_id),t.service_account_id&&a.set(`service_account_id`,t.service_account_id)}else if(e===`service-scope`)throw Error(`Invalid options context.`);let o=wk.parse(await pb(`/options/${e}?${a.toString()}`,{signal:i}));if(o.option_set!==e||t.kind===`service-scope`&&(o.option_set!==`service-scope`||o.principal_type!==t.principal_type||o.owner_id!==t.owner_id||o.service_account_id!==(t.service_account_id??null)))throw Error(`Options returned for a different account. Reload and retry.`);if(r.version!==null&&o.version!==r.version)throw new Tk(`Options changed. Reloading the current choices…`);if(o.next_offset!==null&&(o.next_offset<=r.offset||o.next_offset>=o.total||o.next_offset>(e===`service-scope`?10007:1e3)))throw Error(`Options returned invalid pagination. Reload and retry.`);return o},getNextPageParam:(e,t)=>e.next_offset===null?void 0:{offset:e.next_offset,version:e.version,pageCount:t.length+1},enabled:!!(r&&(t.kind===`service-history`||t.owner_id)),staleTime:0,gcTime:1440*60*1e3,refetchOnMount:`always`,refetchOnWindowFocus:`always`,refetchInterval:1440*60*1e3,retry:!1});return(0,z.useEffect)(()=>i.getMutationCache().subscribe(e=>{e.type===`updated`&&e.action.type===`success`&&i.invalidateQueries({queryKey:[`options`,r]})}),[i,r]),(0,z.useEffect)(()=>{a.error instanceof Tk&&i.resetQueries({queryKey:[`options`,r,e,t,n],exact:!0})},[i,a.error,r,e,t,n]),{...a,versionChanged:a.error instanceof Tk,retainPartialData:a.isFetchNextPageError&&(a.error instanceof TypeError||a.error instanceof cb&&a.error.status>=500)}}var Dk=(0,z.createContext)(40);function Ok(){return(0,z.useContext)(Dk)+10}function kk({layer:e,children:t}){return(0,B.jsx)(Dk.Provider,{value:e,children:t})}var Ak=_k,jk=z.forwardRef(({className:e,align:t=`center`,sideOffset:n=4,style:r,...i},a)=>{let o=Ok();return(0,B.jsx)(kk,{layer:o,children:(0,B.jsx)(yk,{children:(0,B.jsx)(bk,{ref:a,align:t,sideOffset:n,className:Jr(`z-50 w-72 rounded-[12px] border border-border bg-popover p-4 text-popover-foreground shadow-lg shadow-primary/5 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,e),...i,style:{...r,zIndex:o}})})})});jk.displayName=bk.displayName;var Mk=e=>`${e.kind}:${e.value}`;function Nk(e,t,n){let r=new Map,i=n?t.lastIndexOf(n):-1,a=i<0?``:t.slice(0,i+n.length),o=t.slice(a.length).toLowerCase();for(let i of e){let e=i.value.toLowerCase().includes(t.toLowerCase())||i.label.toLowerCase().includes(t.toLowerCase()),s;if(n&&i.value.toLowerCase().startsWith(a.toLowerCase())){let t=i.value.slice(a.length),r=t.indexOf(n);r>=0&&t.slice(0,r).toLowerCase().startsWith(o)?s={kind:`branch`,value:i.value.slice(0,a.length+r+n.length)}:e&&(s={kind:`leaf`,value:i.value,label:i.label,disabled:i.disabled})}else !a&&e&&(s={kind:`leaf`,value:i.value,label:i.label,disabled:i.disabled});s&&r.set(Mk(s),s)}return[...r.values()]}function Pk(e){let t=$S(e=>e.user?.id),[n,r]=(0,z.useState)({identity:t,optionSet:e.optionSet,context:e.context,generation:0}),i=n.context.kind===`service-scope`?n.context:void 0,a=e.context.kind===`service-scope`?e.context:void 0,o=n.optionSet===e.optionSet&&n.context.kind===e.context.kind&&i?.principal_type===a?.principal_type&&i?.service_account_id===a?.service_account_id,s=i?.owner_id===a?.owner_id,c=n.generation;if(n.identity!==t||!o||!s){let l=n.identity===void 0&&t!==void 0,u=a!==void 0&&!i?.owner_id&&a.owner_id===t&&(l||n.identity===t);o&&(s&&l||u)||(c+=1),r({identity:t,optionSet:e.optionSet,context:e.context,generation:c})}return(0,B.jsx)(Ik,{...e},c)}function Fk(e,t,n){return[...new Set(t===void 0?[...e,...n]:e.flatMap(e=>e===t?n:[e]))]}function Ik({optionSet:e,context:t,value:n,onChange:r,label:i,disabled:a,allowCustom:o=!1,delimiter:s,ref:c,...l}){let u=(0,z.useId)(),d=l.id??u,f=`${d}-options`,p=(0,z.useRef)(null),m=(0,z.useRef)(null),h=(0,z.useRef)(null),g=(0,z.useRef)(null),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null),x=y&&JSON.stringify(n)===JSON.stringify(y.expected)?y:null;y&&!x&&b(null);let S=[...new Set(x?.base??n)],C=x?.text??``,w=x?.original,[T,E]=(0,z.useState)(null),[ee,D]=(0,z.useState)(``);(0,z.useImperativeHandle)(c,()=>p.current),(0,z.useLayoutEffect)(()=>{let e=g.current;e&&(g.current=null,e.value===void 0?(p.current?.focus(),e.select&&p.current?.select()):[...m.current?.querySelectorAll(`[data-scope-edit]`)??[]].find(t=>t.dataset.scopeEdit===e.value)?.focus())}),(0,z.useEffect)(()=>{let e=window.setTimeout(()=>D(C.slice(0,200)),250);return()=>window.clearTimeout(e)},[C]);let O=Ek(e,t,t.kind===`service-history`?``:ee),{hasNextPage:k,isFetching:A,isError:j,fetchNextPage:M}=O;(0,z.useEffect)(()=>{_&&k&&!A&&!j&&M()},[_,k,A,j,M]);let N=O.isError&&!O.retainPartialData?[]:O.data?.pages??[],P=Nk([...new Map(N.flatMap(e=>e.items).map(e=>[e.value,e])).values()].filter(e=>!S.includes(e.value)),C,s),F=P.findIndex(e=>Mk(e)===T);(0,z.useEffect)(()=>{F>=0&&h.current?.querySelector(`[data-option-index="${F}"]`)?.scrollIntoView?.({block:`nearest`})},[F]);function I(e,t=!1){if(a)return;let i=x?.base??[...n],s=!t&&o?Fk(i,w,e.split(/\s+/).filter(Boolean)):i;b({base:i,original:w,text:e,branch:t,expected:s}),JSON.stringify(s)!==JSON.stringify(n)&&r(s),E(null),v(!0)}function te(e){a||(r(Fk(S,w,e)),g.current={},b(null),E(null))}function ne(){!a&&o&&x&&!x.branch&&te(C.split(/\s+/).filter(Boolean))}function re(){a||!x||(r(x.base),g.current={value:w},b(null),E(null),v(!1))}function L(e){if(a)return;let t=[...n];g.current={select:!0},b({base:t,original:e,text:e,branch:!1,expected:[...n]}),E(null),v(!0)}function R(e){a||(r((e===w?S:[...n]).filter(t=>t!==e)),b(null),E(null))}function ie(e){a||e.disabled||(e.kind===`branch`?I(e.value,!0):te([e.value]),p.current?.focus())}function ae(e){if(a||(v(!0),!P.length))return;let t=F<0&&e<0?0:F;for(let n=0;n=0?`${d}-choice-${F}`:void 0,autoComplete:`off`,className:`h-6 min-w-20 flex-1 bg-transparent p-0 text-12 text-foreground outline-none placeholder:text-text-tertiary disabled:cursor-not-allowed`,size:w===void 0?void 0:Math.max(10,C.length+1),placeholder:o?S.length?`Add value…`:`Select or type…`:`Search choices…`,disabled:a,value:C,onFocus:()=>v(!0),onClick:()=>v(!0),onBlur:l.onBlur,onChange:e=>I(e.target.value),onPaste:e=>{if(!o||a)return;let t=e.clipboardData.getData(`text`);if(/\s/.test(t)){e.preventDefault();let n=e.currentTarget.selectionStart??C.length,r=e.currentTarget.selectionEnd??n;te((C.slice(0,n)+t+C.slice(r)).split(/\s+/).filter(Boolean))}},onKeyDown:e=>{e.nativeEvent.isComposing||(e.key===`ArrowDown`||e.key===`ArrowUp`?(e.preventDefault(),ae(e.key===`ArrowDown`?1:-1)):e.key===`Enter`?(e.preventDefault(),_&&F>=0?ie(P[F]):ne()):e.key===`Escape`&&(_||w!==void 0)&&(e.preventDefault(),e.stopPropagation(),w===void 0?(v(!1),E(null)):re()))}});return(0,B.jsxs)(Ak,{open:_,onOpenChange:v,children:[(0,B.jsx)(vk,{asChild:!0,children:(0,B.jsxs)(`div`,{ref:m,onBlur:e=>oe(e.relatedTarget),className:Jr(`flex min-h-8 w-full min-w-0 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent px-3 py-0.5 text-foreground transition-colors duration-200 focus-within:border-input-focus has-[[aria-invalid=true]]:border-destructive`,a&&`opacity-50`),onClick:e=>{!a&&e.target===e.currentTarget&&(p.current?.focus(),v(!0))},children:[S.map(t=>(0,B.jsxs)(`div`,{role:`group`,"aria-label":`Selected ${t}`,className:Jr(`flex min-h-6 max-w-full items-stretch overflow-hidden rounded-md border border-input bg-muted/25 text-12`,w===t&&`border-ring ring-1 ring-ring`),children:[e===`service-scope`?w===t?se:(0,B.jsx)(`button`,{type:`button`,"data-scope-edit":t,disabled:a,"aria-label":`Edit ${t}`,className:`min-w-0 break-all px-2 py-0.5 text-left font-mono outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring`,onClick:()=>L(t),children:t}):(0,B.jsx)(`span`,{className:`min-w-0 break-words px-2 py-0.5`,children:N.flatMap(e=>[...e.items,...e.selected_items]).find(e=>e.value===t)?.label??t}),(0,B.jsx)(`button`,{type:`button`,disabled:a,"aria-label":`Remove ${t}`,className:`flex w-6 shrink-0 items-center justify-center border-l border-border/70 text-text-tertiary outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring`,onPointerDown:e=>e.preventDefault(),onClick:()=>R(t),children:(0,B.jsx)(ji,{className:`size-3.5`,"aria-hidden":`true`})})]},t)),(0,B.jsxs)(`div`,{className:Jr(`flex items-center gap-2`,w===void 0?`min-w-36 flex-1`:`ml-auto`),children:[w===void 0&&se,(0,B.jsx)(`button`,{type:`button`,tabIndex:-1,disabled:a,className:`flex h-6 shrink-0 items-center rounded text-text-tertiary`,"aria-label":`Toggle ${i.toLowerCase()} suggestions`,onPointerDown:e=>e.preventDefault(),onClick:()=>{v(!_),_||p.current?.focus()},children:(0,B.jsx)(ui,{className:`size-3.5`,"aria-hidden":`true`})})]})]})}),(0,B.jsxs)(jk,{role:`presentation`,ref:h,onBlur:e=>oe(e.relatedTarget),align:`start`,className:`w-[var(--radix-popover-trigger-width)] min-w-56 max-w-[calc(100vw-2rem)] p-1.5`,onOpenAutoFocus:e=>e.preventDefault(),onCloseAutoFocus:e=>e.preventDefault(),onInteractOutside:e=>{m.current?.contains(e.target)&&e.preventDefault()},onEscapeKeyDown:e=>{e.preventDefault(),e.stopPropagation(),w===void 0?(v(!1),p.current?.focus()):re()},children:[w!==void 0&&(0,B.jsx)(`p`,{className:`border-b px-2 py-2 text-xs text-muted-foreground`,children:`Editing value. Enter saves; Escape cancels.`}),O.isFetching&&(0,B.jsx)(`p`,{role:`status`,className:`px-3 py-2 text-xs text-muted-foreground`,children:N.length?`Loading remaining suggestions…`:`Loading suggestions…`}),O.isError&&!O.versionChanged&&(0,B.jsxs)(`div`,{role:`alert`,className:`px-3 py-2 text-xs`,children:[(0,B.jsxs)(`p`,{className:`text-destructive`,children:[N.length?`Some suggestions could not load.`:`Suggestions unavailable.`,o?` Custom values still work.`:``]}),(0,B.jsx)(Pi,{type:`button`,variant:`ghost`,size:`sm`,onPointerDown:e=>e.preventDefault(),onClick:()=>void(O.isFetchNextPageError?O.fetchNextPage():O.refetch()),children:`Retry suggestions`})]}),(0,B.jsx)(`div`,{id:f,role:`listbox`,"aria-label":`${i} suggestions`,"aria-multiselectable":`true`,className:`max-h-64 overflow-y-auto overscroll-contain`,children:P.map((t,n)=>(0,B.jsxs)(`div`,{id:`${d}-choice-${n}`,role:`option`,"aria-label":t.kind===`branch`?`Explore ${t.value}`:e===`service-scope`?t.value:t.label??t.value,"aria-selected":`false`,"aria-disabled":a||t.disabled,"data-option-index":n,className:Jr(`flex cursor-pointer items-center gap-2 rounded-md px-3 py-1.5 text-12 transition-colors duration-200 hover:bg-overlay-strong`,F===n&&`bg-overlay-strong`,(a||t.disabled)&&`cursor-not-allowed opacity-50`),onPointerDown:e=>e.preventDefault(),onClick:()=>ie(t),children:[(0,B.jsxs)(`span`,{className:`min-w-0 flex-1`,children:[(0,B.jsx)(`span`,{className:Jr(`block break-words text-xs`,e===`service-scope`&&`font-mono`),children:e===`service-scope`?t.value:t.label??t.value}),e===`service-scope`&&t.label&&t.label!==t.value&&(0,B.jsx)(`span`,{className:`block break-words text-xs text-muted-foreground`,children:t.label})]}),t.kind===`branch`&&(0,B.jsx)(di,{className:`size-4 shrink-0`,"aria-hidden":`true`})]},Mk(t)))}),!P.length&&!O.isFetching&&O.isSuccess&&(0,B.jsx)(`p`,{role:`status`,className:`px-3 py-2 text-xs text-muted-foreground`,children:`No matching suggestions.`}),o&&C.trim()&&!x?.branch&&(0,B.jsx)(`p`,{className:`border-t px-2 py-2 text-xs text-muted-foreground`,children:`Press Enter to finish this scope.`})]})]})}function Lk({value:e,onChange:t,ownerId:n,serviceAccountId:r,...i}){let a=$S(e=>e.user?.is_admin??!1);return(0,B.jsxs)(`div`,{className:`space-y-2`,children:[(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Choose scope segments or type a complete scope. Select a pill to edit it.`}),(0,B.jsx)(Pk,{...i,optionSet:`service-scope`,label:`Allowed scopes`,allowCustom:!0,delimiter:`:`,context:{kind:`service-scope`,principal_type:`service_account`,owner_id:n,service_account_id:r},value:e.split(/\s+/).filter(Boolean),onChange:e=>t(e.join(` `))}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:a?`Saving catalog:skills:read grants catalog and key metadata reads across all current and future catalog services. Add catalog:skills:write to amend skill recommendations. These accounts are managed by platform administrators.`:`Platform catalog scopes must be granted by a platform administrator.`})]})}var Rk=$().refine(e=>e.trim()===``||/^[1-9]\d*$/.test(e.trim()),`Must be a whole number of at least 1 (or empty for no override)`).optional().or(Hx(``)),zk=Ax({name:$().min(1,`Name is required`).max(100,`Name must be 100 characters or less`),description:$().max(500,`Description must be 500 characters or less`).optional().or(Hx(``)),allowed_scopes:$().min(1,`At least one scope is required`),role_ids:$().optional().or(Hx(``)),rate_limit_override:Rk});Ax({name:$().min(1,`Name is required`).max(100,`Name must be 100 characters or less`),description:$().max(500,`Description must be 500 characters or less`).optional().or(Hx(``)),allowed_scopes:$().min(1,`At least one scope is required`),role_ids:$().optional().or(Hx(``)),rate_limit_override:Rk,is_active:Sx().optional()});var Bk=ex();Ax({service_ids:$().refine(e=>{let t=e.split(/[,\s]+/).filter(Boolean);return t.length>=1&&t.length<=100&&new Set(t).size===t.length&&t.every(e=>Bk.safeParse(e).success)},`Enter 1–100 distinct catalog service UUIDs`),ornn_proxy_service_id:$().refine(e=>e===``||Bk.safeParse(e).success,`Enter a catalog service UUID`),expires_at:$().refine(e=>e===``||!Number.isNaN(Date.parse(e))&&Date.parse(e)>Date.now(),`Choose a future expiry`),max_writes:$().regex(/^[1-9]\d*$/,`Enter a whole number`).refine(e=>Number(e)<=1e4,`At most 10,000 writes`),window_seconds:$().regex(/^\d+$/,`Enter a whole number`).refine(e=>Number(e)>=60&&Number(e)<=86400,`Use 60–86,400 seconds`)});var Vk=s(((e,t)=>{t.exports=function(){return typeof Promise==`function`&&Promise.prototype&&Promise.prototype.then}})),Hk=s((e=>{var t,n=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706];e.getSymbolSize=function(e){if(!e)throw Error(`"version" cannot be null or undefined`);if(e<1||e>40)throw Error(`"version" should be in range from 1 to 40`);return e*4+17},e.getSymbolTotalCodewords=function(e){return n[e]},e.getBCHDigit=function(e){let t=0;for(;e!==0;)t++,e>>>=1;return t},e.setToSJISFunction=function(e){if(typeof e!=`function`)throw Error(`"toSJISFunc" is not a valid function.`);t=e},e.isKanjiModeEnabled=function(){return t!==void 0},e.toSJIS=function(e){return t(e)}})),Uk=s((e=>{e.L={bit:1},e.M={bit:0},e.Q={bit:3},e.H={bit:2};function t(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`l`:case`low`:return e.L;case`m`:case`medium`:return e.M;case`q`:case`quartile`:return e.Q;case`h`:case`high`:return e.H;default:throw Error(`Unknown EC Level: `+t)}}e.isValid=function(e){return e&&e.bit!==void 0&&e.bit>=0&&e.bit<4},e.from=function(n,r){if(e.isValid(n))return n;try{return t(n)}catch{return r}}})),Wk=s(((e,t)=>{function n(){this.buffer=[],this.length=0}n.prototype={get:function(e){let t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)==1},put:function(e,t){for(let n=0;n>>t-n-1&1)==1)},getLengthInBits:function(){return this.length},putBit:function(e){let t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}},t.exports=n})),Gk=s(((e,t)=>{function n(e){if(!e||e<1)throw Error(`BitMatrix size must be defined and greater than 0`);this.size=e,this.data=new Uint8Array(e*e),this.reservedBit=new Uint8Array(e*e)}n.prototype.set=function(e,t,n,r){let i=e*this.size+t;this.data[i]=n,r&&(this.reservedBit[i]=!0)},n.prototype.get=function(e,t){return this.data[e*this.size+t]},n.prototype.xor=function(e,t,n){this.data[e*this.size+t]^=n},n.prototype.isReserved=function(e,t){return this.reservedBit[e*this.size+t]},t.exports=n})),Kk=s((e=>{var t=Hk().getSymbolSize;e.getRowColCoords=function(e){if(e===1)return[];let n=Math.floor(e/7)+2,r=t(e),i=r===145?26:Math.ceil((r-13)/(2*n-2))*2,a=[r-7];for(let e=1;e{var t=Hk().getSymbolSize,n=7;e.getPositions=function(e){let r=t(e);return[[0,0],[r-n,0],[0,r-n]]}})),Jk=s((e=>{e.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var t={N1:3,N2:3,N3:40,N4:10};e.isValid=function(e){return e!=null&&e!==``&&!isNaN(e)&&e>=0&&e<=7},e.from=function(t){return e.isValid(t)?parseInt(t,10):void 0},e.getPenaltyN1=function(e){let n=e.size,r=0,i=0,a=0,o=null,s=null;for(let c=0;c=5&&(r+=t.N1+(i-5)),o=n,i=1),n=e.get(l,c),n===s?a++:(a>=5&&(r+=t.N1+(a-5)),s=n,a=1)}i>=5&&(r+=t.N1+(i-5)),a>=5&&(r+=t.N1+(a-5))}return r},e.getPenaltyN2=function(e){let n=e.size,r=0;for(let t=0;t=10&&(i===1488||i===93)&&r++,a=a<<1&2047|e.get(o,t),o>=10&&(a===1488||a===93)&&r++}return r*t.N3},e.getPenaltyN4=function(e){let n=0,r=e.data.length;for(let t=0;t{var t=Uk(),n=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,40,48,16,31,43,51,17,33,45,54,18,35,48,57,19,37,51,60,19,38,53,63,20,40,56,66,21,43,59,70,22,45,62,74,24,47,65,77,25,49,68,81],r=[7,10,13,17,10,16,22,28,15,26,36,44,20,36,52,64,26,48,72,88,36,64,96,112,40,72,108,130,48,88,132,156,60,110,160,192,72,130,192,224,80,150,224,264,96,176,260,308,104,198,288,352,120,216,320,384,132,240,360,432,144,280,408,480,168,308,448,532,180,338,504,588,196,364,546,650,224,416,600,700,224,442,644,750,252,476,690,816,270,504,750,900,300,560,810,960,312,588,870,1050,336,644,952,1110,360,700,1020,1200,390,728,1050,1260,420,784,1140,1350,450,812,1200,1440,480,868,1290,1530,510,924,1350,1620,540,980,1440,1710,570,1036,1530,1800,570,1064,1590,1890,600,1120,1680,1980,630,1204,1770,2100,660,1260,1860,2220,720,1316,1950,2310,750,1372,2040,2430];e.getBlocksCount=function(e,r){switch(r){case t.L:return n[(e-1)*4+0];case t.M:return n[(e-1)*4+1];case t.Q:return n[(e-1)*4+2];case t.H:return n[(e-1)*4+3];default:return}},e.getTotalCodewordsCount=function(e,n){switch(n){case t.L:return r[(e-1)*4+0];case t.M:return r[(e-1)*4+1];case t.Q:return r[(e-1)*4+2];case t.H:return r[(e-1)*4+3];default:return}}})),Xk=s((e=>{var t=new Uint8Array(512),n=new Uint8Array(256);(function(){let e=1;for(let r=0;r<255;r++)t[r]=e,n[e]=r,e<<=1,e&256&&(e^=285);for(let e=255;e<512;e++)t[e]=t[e-255]})(),e.log=function(e){if(e<1)throw Error(`log(`+e+`)`);return n[e]},e.exp=function(e){return t[e]},e.mul=function(e,r){return e===0||r===0?0:t[n[e]+n[r]]}})),Zk=s((e=>{var t=Xk();e.mul=function(e,n){let r=new Uint8Array(e.length+n.length-1);for(let i=0;i=0;){let e=r[0];for(let i=0;i{var n=Zk();function r(e){this.genPoly=void 0,this.degree=e,this.degree&&this.initialize(this.degree)}r.prototype.initialize=function(e){this.degree=e,this.genPoly=n.generateECPolynomial(this.degree)},r.prototype.encode=function(e){if(!this.genPoly)throw Error(`Encoder not initialized`);let t=new Uint8Array(e.length+this.degree);t.set(e);let r=n.mod(t,this.genPoly),i=this.degree-r.length;if(i>0){let e=new Uint8Array(this.degree);return e.set(r,i),e}return r},t.exports=r})),$k=s((e=>{e.isValid=function(e){return!isNaN(e)&&e>=1&&e<=40}})),eA=s((e=>{var t=`[0-9]+`,n=`[A-Z $%*+\\-./:]+`,r=`(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+`;r=r.replace(/u/g,`\\u`);var i=`(?:(?![A-Z0-9 $%*+\\-./:]|`+r+`)(?:.|[\r -]))+`;e.KANJI=new RegExp(r,`g`),e.BYTE_KANJI=RegExp(`[^A-Z0-9 $%*+\\-./:]+`,`g`),e.BYTE=new RegExp(i,`g`),e.NUMERIC=new RegExp(t,`g`),e.ALPHANUMERIC=new RegExp(n,`g`);var a=RegExp(`^`+r+`$`),o=RegExp(`^`+t+`$`),s=RegExp(`^[A-Z0-9 $%*+\\-./:]+$`);e.testKanji=function(e){return a.test(e)},e.testNumeric=function(e){return o.test(e)},e.testAlphanumeric=function(e){return s.test(e)}})),tA=s((e=>{var t=$k(),n=eA();e.NUMERIC={id:`Numeric`,bit:1,ccBits:[10,12,14]},e.ALPHANUMERIC={id:`Alphanumeric`,bit:2,ccBits:[9,11,13]},e.BYTE={id:`Byte`,bit:4,ccBits:[8,16,16]},e.KANJI={id:`Kanji`,bit:8,ccBits:[8,10,12]},e.MIXED={bit:-1},e.getCharCountIndicator=function(e,n){if(!e.ccBits)throw Error(`Invalid mode: `+e);if(!t.isValid(n))throw Error(`Invalid version: `+n);return n>=1&&n<10?e.ccBits[0]:n<27?e.ccBits[1]:e.ccBits[2]},e.getBestModeForData=function(t){return n.testNumeric(t)?e.NUMERIC:n.testAlphanumeric(t)?e.ALPHANUMERIC:n.testKanji(t)?e.KANJI:e.BYTE},e.toString=function(e){if(e&&e.id)return e.id;throw Error(`Invalid mode`)},e.isValid=function(e){return e&&e.bit&&e.ccBits};function r(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`numeric`:return e.NUMERIC;case`alphanumeric`:return e.ALPHANUMERIC;case`kanji`:return e.KANJI;case`byte`:return e.BYTE;default:throw Error(`Unknown mode: `+t)}}e.from=function(t,n){if(e.isValid(t))return t;try{return r(t)}catch{return n}}})),nA=s((e=>{var t=Hk(),n=Yk(),r=Uk(),i=tA(),a=$k(),o=7973,s=t.getBCHDigit(o);function c(t,n,r){for(let i=1;i<=40;i++)if(n<=e.getCapacity(i,r,t))return i}function l(e,t){return i.getCharCountIndicator(e,t)+4}function u(e,t){let n=0;return e.forEach(function(e){let r=l(e.mode,t);n+=r+e.getBitsLength()}),n}function d(t,n){for(let r=1;r<=40;r++)if(u(t,r)<=e.getCapacity(r,n,i.MIXED))return r}e.from=function(e,t){return a.isValid(e)?parseInt(e,10):t},e.getCapacity=function(e,r,o){if(!a.isValid(e))throw Error(`Invalid QR Code version`);o===void 0&&(o=i.BYTE);let s=(t.getSymbolTotalCodewords(e)-n.getTotalCodewordsCount(e,r))*8;if(o===i.MIXED)return s;let c=s-l(o,e);switch(o){case i.NUMERIC:return Math.floor(c/10*3);case i.ALPHANUMERIC:return Math.floor(c/11*2);case i.KANJI:return Math.floor(c/13);case i.BYTE:default:return Math.floor(c/8)}},e.getBestVersionForData=function(e,t){let n,i=r.from(t,r.M);if(Array.isArray(e)){if(e.length>1)return d(e,i);if(e.length===0)return 1;n=e[0]}else n=e;return c(n.mode,n.getLength(),i)},e.getEncodedBits=function(e){if(!a.isValid(e)||e<7)throw Error(`Invalid QR Code version`);let n=e<<12;for(;t.getBCHDigit(n)-s>=0;)n^=o<{var t=Hk(),n=1335,r=21522,i=t.getBCHDigit(n);e.getEncodedBits=function(e,a){let o=e.bit<<3|a,s=o<<10;for(;t.getBCHDigit(s)-i>=0;)s^=n<{var n=tA();function r(e){this.mode=n.NUMERIC,this.data=e.toString()}r.getBitsLength=function(e){return 10*Math.floor(e/3)+(e%3?e%3*3+1:0)},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){let t,n,r;for(t=0;t+3<=this.data.length;t+=3)n=this.data.substr(t,3),r=parseInt(n,10),e.put(r,10);let i=this.data.length-t;i>0&&(n=this.data.substr(t),r=parseInt(n,10),e.put(r,i*3+1))},t.exports=r})),aA=s(((e,t)=>{var n=tA(),r=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`.split(``);function i(e){this.mode=n.ALPHANUMERIC,this.data=e}i.getBitsLength=function(e){return 11*Math.floor(e/2)+e%2*6},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t;for(t=0;t+2<=this.data.length;t+=2){let n=r.indexOf(this.data[t])*45;n+=r.indexOf(this.data[t+1]),e.put(n,11)}this.data.length%2&&e.put(r.indexOf(this.data[t]),6)},t.exports=i})),oA=s(((e,t)=>{var n=tA();function r(e){this.mode=n.BYTE,typeof e==`string`?this.data=new TextEncoder().encode(e):this.data=new Uint8Array(e)}r.getBitsLength=function(e){return e*8},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){for(let t=0,n=this.data.length;t{var n=tA(),r=Hk();function i(e){this.mode=n.KANJI,this.data=e}i.getBitsLength=function(e){return e*13},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t;for(t=0;t=33088&&n<=40956)n-=33088;else if(n>=57408&&n<=60351)n-=49472;else throw Error(`Invalid SJIS character: `+this.data[t]+` -Make sure your charset is UTF-8`);n=(n>>>8&255)*192+(n&255),e.put(n,13)}},t.exports=i})),cA=s(((e,t)=>{var n={single_source_shortest_paths:function(e,t,r){var i={},a={};a[t]=0;var o=n.PriorityQueue.make();o.push(t,0);for(var s,c,l,u,d,f,p,m,h;!o.empty();)for(l in s=o.pop(),c=s.value,u=s.cost,d=e[c]||{},d)d.hasOwnProperty(l)&&(f=d[l],p=u+f,m=a[l],h=a[l]===void 0,(h||m>p)&&(a[l]=p,o.push(l,p),i[l]=c));if(r!==void 0&&a[r]===void 0){var g=[`Could not find a path from `,t,` to `,r,`.`].join(``);throw Error(g)}return i},extract_shortest_path_from_predecessor_list:function(e,t){for(var n=[],r=t;r;)n.push(r),e[r],r=e[r];return n.reverse(),n},find_path:function(e,t,r){var i=n.single_source_shortest_paths(e,t,r);return n.extract_shortest_path_from_predecessor_list(i,r)},PriorityQueue:{make:function(e){var t=n.PriorityQueue,r={},i;for(i in e||={},t)t.hasOwnProperty(i)&&(r[i]=t[i]);return r.queue=[],r.sorter=e.sorter||t.default_sorter,r},default_sorter:function(e,t){return e.cost-t.cost},push:function(e,t){var n={value:e,cost:t};this.queue.push(n),this.queue.sort(this.sorter)},pop:function(){return this.queue.shift()},empty:function(){return this.queue.length===0}}};t!==void 0&&(t.exports=n)})),lA=s((e=>{var t=tA(),n=iA(),r=aA(),i=oA(),a=sA(),o=eA(),s=Hk(),c=cA();function l(e){return unescape(encodeURIComponent(e)).length}function u(e,t,n){let r=[],i;for(;(i=e.exec(n))!==null;)r.push({data:i[0],index:i.index,mode:t,length:i[0].length});return r}function d(e){let n=u(o.NUMERIC,t.NUMERIC,e),r=u(o.ALPHANUMERIC,t.ALPHANUMERIC,e),i,a;return s.isKanjiModeEnabled()?(i=u(o.BYTE,t.BYTE,e),a=u(o.KANJI,t.KANJI,e)):(i=u(o.BYTE_KANJI,t.BYTE,e),a=[]),n.concat(r,i,a).sort(function(e,t){return e.index-t.index}).map(function(e){return{data:e.data,mode:e.mode,length:e.length}})}function f(e,o){switch(o){case t.NUMERIC:return n.getBitsLength(e);case t.ALPHANUMERIC:return r.getBitsLength(e);case t.KANJI:return a.getBitsLength(e);case t.BYTE:return i.getBitsLength(e)}}function p(e){return e.reduce(function(e,t){let n=e.length-1>=0?e[e.length-1]:null;return n&&n.mode===t.mode?(e[e.length-1].data+=t.data,e):(e.push(t),e)},[])}function m(e){let n=[];for(let r=0;r{var t=Hk(),n=Uk(),r=Wk(),i=Gk(),a=Kk(),o=qk(),s=Jk(),c=Yk(),l=Qk(),u=nA(),d=rA(),f=tA(),p=lA();function m(e,t){let n=e.size,r=o.getPositions(t);for(let t=0;t=0&&t<=6&&(r===0||r===6)||r>=0&&r<=6&&(t===0||t===6)||t>=2&&t<=4&&r>=2&&r<=4?e.set(i+t,a+r,!0,!0):e.set(i+t,a+r,!1,!0))}}function h(e){let t=e.size;for(let n=8;n>t&1)==1,e.set(i,a,o,!0),e.set(a,i,o,!0)}function v(e,t,n){let r=e.size,i=d.getEncodedBits(t,n),a,o;for(a=0;a<15;a++)o=(i>>a&1)==1,a<6?e.set(a,8,o,!0):a<8?e.set(a+1,8,o,!0):e.set(r-15+a,8,o,!0),a<8?e.set(8,r-a-1,o,!0):a<9?e.set(8,15-a-1+1,o,!0):e.set(8,15-a-1,o,!0);e.set(r-8,8,1,!0)}function y(e,t){let n=e.size,r=-1,i=n-1,a=7,o=0;for(let s=n-1;s>0;s-=2)for(s===6&&s--;;){for(let n=0;n<2;n++)if(!e.isReserved(i,s-n)){let r=!1;o>>a&1)==1),e.set(i,s-n,r),a--,a===-1&&(o++,a=7)}if(i+=r,i<0||n<=i){i-=r,r=-r;break}}}function b(e,n,i){let a=new r;i.forEach(function(t){a.put(t.mode.bit,4),a.put(t.getLength(),f.getCharCountIndicator(t.mode,e)),t.write(a)});let o=(t.getSymbolTotalCodewords(e)-c.getTotalCodewordsCount(e,n))*8;for(a.getLengthInBits()+4<=o&&a.put(0,4);a.getLengthInBits()%8!=0;)a.putBit(0);let s=(o-a.getLengthInBits())/8;for(let e=0;eMath.abs(c)?`h`:`v`;if(`touches`in e&&d===`h`&&u.type===`range`)return!1;var f=window.getSelection(),p=f&&f.anchorNode;if(p&&(p===u||p.contains(u)))return!1;var m=OO(d,u);if(!m)return!0;if(m?l=d:(l=d===`v`?`h`:`v`,m=OO(d,u)),!m)return!1;if(!r.current&&`changedTouches`in e&&(s||c)&&(r.current=l),!l)return!0;var h=r.current||l;return PO(h,t,e,h===`h`?s:c,!0)},[]),c=z.useCallback(function(e){var n=e;if(!(!VO.length||VO[VO.length-1]!==a)){var r=`deltaY`in n?IO(n):FO(n),i=t.current.filter(function(e){return e.name===n.type&&(e.target===n.target||n.target===e.shadowParent)&&RO(e.delta,r)})[0];if(i&&i.should){n.cancelable&&n.preventDefault();return}if(!i){var c=(o.current.shards||[]).map(LO).filter(Boolean).filter(function(e){return e.contains(n.target)});(c.length>0?s(n,c[0]):!o.current.noIsolation)&&n.cancelable&&n.preventDefault()}}},[]),l=z.useCallback(function(e,n,r,i){var a={name:e,delta:n,target:r,should:i,shadowParent:UO(r)};t.current.push(a),setTimeout(function(){t.current=t.current.filter(function(e){return e!==a})},1)},[]),u=z.useCallback(function(e){n.current=FO(e),r.current=void 0},[]),d=z.useCallback(function(t){l(t.type,IO(t),t.target,s(t,e.lockRef.current))},[]),f=z.useCallback(function(t){l(t.type,FO(t),t.target,s(t,e.lockRef.current))},[]);z.useEffect(function(){return VO.push(a),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:f}),document.addEventListener(`wheel`,c,CO),document.addEventListener(`touchmove`,c,CO),document.addEventListener(`touchstart`,u,CO),function(){VO=VO.filter(function(e){return e!==a}),document.removeEventListener(`wheel`,c,CO),document.removeEventListener(`touchmove`,c,CO),document.removeEventListener(`touchstart`,u,CO)}},[]);var p=e.removeScrollBar,m=e.inert;return z.createElement(z.Fragment,null,m?z.createElement(a,{styles:zO(i)}):null,p?z.createElement(bO,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function UO(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}var WO=$D(eO,HO),GO=z.forwardRef(function(e,t){return z.createElement(nO,LD({},e,{ref:t,sideCar:WO}))});GO.classNames=nO.classNames;var KO=`Popover`,[qO,JO]=nC(KO,[LE]),YO=LE(),[XO,ZO]=qO(KO),QO=e=>{let{__scopePopover:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!1}=e,s=YO(t),c=z.useRef(null),[l,u]=z.useState(!1),[d,f]=TD({prop:r,defaultProp:i??!1,onChange:a,caller:KO});return(0,B.jsx)($E,{...s,children:(0,B.jsx)(XO,{scope:t,contentId:nw(),triggerRef:c,open:d,onOpenChange:f,onOpenToggle:z.useCallback(()=>f(e=>!e),[f]),hasCustomAnchor:l,onCustomAnchorAdd:z.useCallback(()=>u(!0),[]),onCustomAnchorRemove:z.useCallback(()=>u(!1),[]),modal:o,children:n})})};QO.displayName=KO;var $O=`PopoverAnchor`,ek=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=ZO($O,n),a=YO(n),{onCustomAnchorAdd:o,onCustomAnchorRemove:s}=i;return z.useEffect(()=>(o(),()=>s()),[o,s]),(0,B.jsx)(eD,{...a,...r,ref:t})});ek.displayName=$O;var tk=`PopoverTrigger`,nk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=ZO(tk,n),a=YO(n),o=en(t,i.triggerRef),s=(0,B.jsx)(CD.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.contentId,"data-state":_k(i.open),...r,ref:o,onClick:tC(e.onClick,i.onOpenToggle)});return i.hasCustomAnchor?s:(0,B.jsx)(eD,{asChild:!0,...a,children:s})});nk.displayName=tk;var rk=`PopoverPortal`,[ik,ak]=qO(rk,{forceMount:void 0}),ok=e=>{let{__scopePopover:t,forceMount:n,children:r,container:i}=e,a=ZO(rk,t);return(0,B.jsx)(ik,{scope:t,forceMount:n,children:(0,B.jsx)(pD,{present:n||a.open,children:(0,B.jsx)(dD,{asChild:!0,container:i,children:r})})})};ok.displayName=rk;var sk=`PopoverContent`,ck=z.forwardRef((e,t)=>{let n=ak(sk,e.__scopePopover),{forceMount:r=n.forceMount,...i}=e,a=ZO(sk,e.__scopePopover);return(0,B.jsx)(pD,{present:r||a.open,children:a.modal?(0,B.jsx)(uk,{...i,ref:t}):(0,B.jsx)(dk,{...i,ref:t})})});ck.displayName=sk;var lk=_D(`PopoverContent.RemoveScroll`),uk=z.forwardRef((e,t)=>{let n=ZO(sk,e.__scopePopover),r=z.useRef(null),i=en(t,r),a=z.useRef(!1);return z.useEffect(()=>{let e=r.current;if(e)return ID(e)},[]),(0,B.jsx)(GO,{as:lk,allowPinchZoom:!0,children:(0,B.jsx)(fk,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:tC(e.onCloseAutoFocus,e=>{e.preventDefault(),a.current||n.triggerRef.current?.focus()}),onPointerDownOutside:tC(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;a.current=t.button===2||n},{checkForDefaultPrevented:!1}),onFocusOutside:tC(e.onFocusOutside,e=>e.preventDefault(),{checkForDefaultPrevented:!1})})})}),dk=z.forwardRef((e,t)=>{let n=ZO(sk,e.__scopePopover),r=z.useRef(!1),i=z.useRef(!1);return(0,B.jsx)(fk,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),fk=z.forwardRef((e,t)=>{let{__scopePopover:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,disableOutsidePointerEvents:o,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onInteractOutside:u,...d}=e,f=ZO(sk,n),p=YO(n);return OC(),(0,B.jsx)(VC,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,B.jsx)(bC,{asChild:!0,disableOutsidePointerEvents:o,onInteractOutside:u,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onDismiss:()=>f.onOpenChange(!1),children:(0,B.jsx)(tD,{"data-state":_k(f.open),role:`dialog`,id:f.contentId,...p,...d,ref:t,style:{...d.style,"--radix-popover-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-popover-content-available-width":`var(--radix-popper-available-width)`,"--radix-popover-content-available-height":`var(--radix-popper-available-height)`,"--radix-popover-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-popover-trigger-height":`var(--radix-popper-anchor-height)`}})})})}),pk=`PopoverClose`,mk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=ZO(pk,n);return(0,B.jsx)(CD.button,{type:`button`,...r,ref:t,onClick:tC(e.onClick,()=>i.onOpenChange(!1))})});mk.displayName=pk;var hk=`PopoverArrow`,gk=z.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=YO(n);return(0,B.jsx)(nD,{...i,...r,ref:t})});gk.displayName=hk;function _k(e){return e?`open`:`closed`}var vk=QO,yk=ek,bk=ok,xk=ck,Sk=jx({value:$(),label:$(),description:$(),group:$(),source:Vx([`backend_definition`,`configured_scope`]),owner_id:$().nullable(),resource_id:$().nullable(),disabled:Cx(),disabled_reason:$().nullable()}),Ck={items:kx(Sk),total:yx().int().nonnegative(),next_offset:yx().int().nonnegative().nullable(),version:$()},wk={definitions_version:$(),evaluated_at:$(),max_age_seconds:yx().int().nonnegative()},Tk=Fx(`option_set`,[jx({...Ck,option_set:Ux(`service-scope`),principal_type:Ux(`service_account`),owner_id:$(),service_account_id:$().nullable(),selected_items:kx(Sk),freshness:jx({...wk,resources:Ux(`live`)})}),...[`service-history-action`,`service-history-field`].map(e=>jx({...Ck,option_set:Ux(e),selected_items:kx(Sk).max(0).default([]),freshness:jx({...wk,resources:Ux(`static`)})}).strict())]),Ek=class extends Error{};function Dk(e,t,n=``){let r=eC(e=>e.user?.id),i=Je(),a=ut({queryKey:[`options`,r,e,t,n],initialPageParam:{offset:0,version:null,pageCount:1},queryFn:async({pageParam:r,signal:i})=>{if(r.pageCount>(e===`service-scope`?102:12))throw Error(`Options exceed the supported page limit.`);let a=new URLSearchParams({search:n,offset:String(r.offset),limit:`100`});if(t.kind===`service-scope`){if(e!==`service-scope`)throw Error(`Invalid options context.`);a.set(`principal_type`,t.principal_type),a.set(`owner_id`,t.owner_id),t.service_account_id&&a.set(`service_account_id`,t.service_account_id)}else if(e===`service-scope`)throw Error(`Invalid options context.`);let o=Tk.parse(await mb(`/options/${e}?${a.toString()}`,{signal:i}));if(o.option_set!==e||t.kind===`service-scope`&&(o.option_set!==`service-scope`||o.principal_type!==t.principal_type||o.owner_id!==t.owner_id||o.service_account_id!==(t.service_account_id??null)))throw Error(`Options returned for a different account. Reload and retry.`);if(r.version!==null&&o.version!==r.version)throw new Ek(`Options changed. Reloading the current choices…`);if(o.next_offset!==null&&(o.next_offset<=r.offset||o.next_offset>=o.total||o.next_offset>(e===`service-scope`?10007:1e3)))throw Error(`Options returned invalid pagination. Reload and retry.`);return o},getNextPageParam:(e,t)=>e.next_offset===null?void 0:{offset:e.next_offset,version:e.version,pageCount:t.length+1},enabled:!!(r&&(t.kind===`service-history`||t.owner_id)),staleTime:0,gcTime:1440*60*1e3,refetchOnMount:`always`,refetchOnWindowFocus:`always`,refetchInterval:1440*60*1e3,retry:!1});return(0,z.useEffect)(()=>i.getMutationCache().subscribe(e=>{e.type===`updated`&&e.action.type===`success`&&i.invalidateQueries({queryKey:[`options`,r]})}),[i,r]),(0,z.useEffect)(()=>{a.error instanceof Ek&&i.resetQueries({queryKey:[`options`,r,e,t,n],exact:!0})},[i,a.error,r,e,t,n]),{...a,versionChanged:a.error instanceof Ek,retainPartialData:a.isFetchNextPageError&&(a.error instanceof TypeError||a.error instanceof lb&&a.error.status>=500)}}var Ok=(0,z.createContext)(40);function kk(){return(0,z.useContext)(Ok)+10}function Ak({layer:e,children:t}){return(0,B.jsx)(Ok.Provider,{value:e,children:t})}var jk=vk,Mk=z.forwardRef(({className:e,align:t=`center`,sideOffset:n=4,style:r,...i},a)=>{let o=kk();return(0,B.jsx)(Ak,{layer:o,children:(0,B.jsx)(bk,{children:(0,B.jsx)(xk,{ref:a,align:t,sideOffset:n,className:Jr(`z-50 w-72 rounded-[12px] border border-border bg-popover p-4 text-popover-foreground shadow-lg shadow-primary/5 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,e),...i,style:{...r,zIndex:o}})})})});Mk.displayName=xk.displayName;var Nk=e=>`${e.kind}:${e.value}`;function Pk(e,t,n){let r=new Map,i=n?t.lastIndexOf(n):-1,a=i<0?``:t.slice(0,i+n.length),o=t.slice(a.length).toLowerCase();for(let i of e){let e=i.value.toLowerCase().includes(t.toLowerCase())||i.label.toLowerCase().includes(t.toLowerCase()),s;if(n&&i.value.toLowerCase().startsWith(a.toLowerCase())){let t=i.value.slice(a.length),r=t.indexOf(n);r>=0&&t.slice(0,r).toLowerCase().startsWith(o)?s={kind:`branch`,value:i.value.slice(0,a.length+r+n.length)}:e&&(s={kind:`leaf`,value:i.value,label:i.label,disabled:i.disabled})}else !a&&e&&(s={kind:`leaf`,value:i.value,label:i.label,disabled:i.disabled});s&&r.set(Nk(s),s)}return[...r.values()]}function Fk(e){let t=eC(e=>e.user?.id),[n,r]=(0,z.useState)({identity:t,optionSet:e.optionSet,context:e.context,generation:0}),i=n.context.kind===`service-scope`?n.context:void 0,a=e.context.kind===`service-scope`?e.context:void 0,o=n.optionSet===e.optionSet&&n.context.kind===e.context.kind&&i?.principal_type===a?.principal_type&&i?.service_account_id===a?.service_account_id,s=i?.owner_id===a?.owner_id,c=n.generation;if(n.identity!==t||!o||!s){let l=n.identity===void 0&&t!==void 0,u=a!==void 0&&!i?.owner_id&&a.owner_id===t&&(l||n.identity===t);o&&(s&&l||u)||(c+=1),r({identity:t,optionSet:e.optionSet,context:e.context,generation:c})}return(0,B.jsx)(Lk,{...e},c)}function Ik(e,t,n){return[...new Set(t===void 0?[...e,...n]:e.flatMap(e=>e===t?n:[e]))]}function Lk({optionSet:e,context:t,value:n,onChange:r,label:i,disabled:a,allowCustom:o=!1,delimiter:s,ref:c,...l}){let u=(0,z.useId)(),d=l.id??u,f=`${d}-options`,p=(0,z.useRef)(null),m=(0,z.useRef)(null),h=(0,z.useRef)(null),g=(0,z.useRef)(null),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null),x=y&&JSON.stringify(n)===JSON.stringify(y.expected)?y:null;y&&!x&&b(null);let S=[...new Set(x?.base??n)],C=x?.text??``,w=x?.original,[T,E]=(0,z.useState)(null),[ee,D]=(0,z.useState)(``);(0,z.useImperativeHandle)(c,()=>p.current),(0,z.useLayoutEffect)(()=>{let e=g.current;e&&(g.current=null,e.value===void 0?(p.current?.focus(),e.select&&p.current?.select()):[...m.current?.querySelectorAll(`[data-scope-edit]`)??[]].find(t=>t.dataset.scopeEdit===e.value)?.focus())}),(0,z.useEffect)(()=>{let e=window.setTimeout(()=>D(C.slice(0,200)),250);return()=>window.clearTimeout(e)},[C]);let O=Dk(e,t,t.kind===`service-history`?``:ee),{hasNextPage:k,isFetching:A,isError:j,fetchNextPage:M}=O;(0,z.useEffect)(()=>{_&&k&&!A&&!j&&M()},[_,k,A,j,M]);let N=O.isError&&!O.retainPartialData?[]:O.data?.pages??[],P=Pk([...new Map(N.flatMap(e=>e.items).map(e=>[e.value,e])).values()].filter(e=>!S.includes(e.value)),C,s),F=P.findIndex(e=>Nk(e)===T);(0,z.useEffect)(()=>{F>=0&&h.current?.querySelector(`[data-option-index="${F}"]`)?.scrollIntoView?.({block:`nearest`})},[F]);function I(e,t=!1){if(a)return;let i=x?.base??[...n],s=!t&&o?Ik(i,w,e.split(/\s+/).filter(Boolean)):i;b({base:i,original:w,text:e,branch:t,expected:s}),JSON.stringify(s)!==JSON.stringify(n)&&r(s),E(null),v(!0)}function te(e){a||(r(Ik(S,w,e)),g.current={},b(null),E(null))}function ne(){!a&&o&&x&&!x.branch&&te(C.split(/\s+/).filter(Boolean))}function re(){a||!x||(r(x.base),g.current={value:w},b(null),E(null),v(!1))}function L(e){if(a)return;let t=[...n];g.current={select:!0},b({base:t,original:e,text:e,branch:!1,expected:[...n]}),E(null),v(!0)}function R(e){a||(r((e===w?S:[...n]).filter(t=>t!==e)),b(null),E(null))}function ie(e){a||e.disabled||(e.kind===`branch`?I(e.value,!0):te([e.value]),p.current?.focus())}function ae(e){if(a||(v(!0),!P.length))return;let t=F<0&&e<0?0:F;for(let n=0;n=0?`${d}-choice-${F}`:void 0,autoComplete:`off`,className:`h-6 min-w-20 flex-1 bg-transparent p-0 text-12 text-foreground outline-none placeholder:text-text-tertiary disabled:cursor-not-allowed`,size:w===void 0?void 0:Math.max(10,C.length+1),placeholder:o?S.length?`Add value…`:`Select or type…`:`Search choices…`,disabled:a,value:C,onFocus:()=>v(!0),onClick:()=>v(!0),onBlur:l.onBlur,onChange:e=>I(e.target.value),onPaste:e=>{if(!o||a)return;let t=e.clipboardData.getData(`text`);if(/\s/.test(t)){e.preventDefault();let n=e.currentTarget.selectionStart??C.length,r=e.currentTarget.selectionEnd??n;te((C.slice(0,n)+t+C.slice(r)).split(/\s+/).filter(Boolean))}},onKeyDown:e=>{e.nativeEvent.isComposing||(e.key===`ArrowDown`||e.key===`ArrowUp`?(e.preventDefault(),ae(e.key===`ArrowDown`?1:-1)):e.key===`Enter`?(e.preventDefault(),_&&F>=0?ie(P[F]):ne()):e.key===`Escape`&&(_||w!==void 0)&&(e.preventDefault(),e.stopPropagation(),w===void 0?(v(!1),E(null)):re()))}});return(0,B.jsxs)(jk,{open:_,onOpenChange:v,children:[(0,B.jsx)(yk,{asChild:!0,children:(0,B.jsxs)(`div`,{ref:m,onBlur:e=>oe(e.relatedTarget),className:Jr(`flex min-h-8 w-full min-w-0 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent px-3 py-0.5 text-foreground transition-colors duration-200 focus-within:border-input-focus has-[[aria-invalid=true]]:border-destructive`,a&&`opacity-50`),onClick:e=>{!a&&e.target===e.currentTarget&&(p.current?.focus(),v(!0))},children:[S.map(t=>(0,B.jsxs)(`div`,{role:`group`,"aria-label":`Selected ${t}`,className:Jr(`flex min-h-6 max-w-full items-stretch overflow-hidden rounded-md border border-input bg-muted/25 text-12`,w===t&&`border-ring ring-1 ring-ring`),children:[e===`service-scope`?w===t?se:(0,B.jsx)(`button`,{type:`button`,"data-scope-edit":t,disabled:a,"aria-label":`Edit ${t}`,className:`min-w-0 break-all px-2 py-0.5 text-left font-mono outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring`,onClick:()=>L(t),children:t}):(0,B.jsx)(`span`,{className:`min-w-0 break-words px-2 py-0.5`,children:N.flatMap(e=>[...e.items,...e.selected_items]).find(e=>e.value===t)?.label??t}),(0,B.jsx)(`button`,{type:`button`,disabled:a,"aria-label":`Remove ${t}`,className:`flex w-6 shrink-0 items-center justify-center border-l border-border/70 text-text-tertiary outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring`,onPointerDown:e=>e.preventDefault(),onClick:()=>R(t),children:(0,B.jsx)(ji,{className:`size-3.5`,"aria-hidden":`true`})})]},t)),(0,B.jsxs)(`div`,{className:Jr(`flex items-center gap-2`,w===void 0?`min-w-36 flex-1`:`ml-auto`),children:[w===void 0&&se,(0,B.jsx)(`button`,{type:`button`,tabIndex:-1,disabled:a,className:`flex h-6 shrink-0 items-center rounded text-text-tertiary`,"aria-label":`Toggle ${i.toLowerCase()} suggestions`,onPointerDown:e=>e.preventDefault(),onClick:()=>{v(!_),_||p.current?.focus()},children:(0,B.jsx)(ui,{className:`size-3.5`,"aria-hidden":`true`})})]})]})}),(0,B.jsxs)(Mk,{role:`presentation`,ref:h,onBlur:e=>oe(e.relatedTarget),align:`start`,className:`w-[var(--radix-popover-trigger-width)] min-w-56 max-w-[calc(100vw-2rem)] p-1.5`,onOpenAutoFocus:e=>e.preventDefault(),onCloseAutoFocus:e=>e.preventDefault(),onInteractOutside:e=>{m.current?.contains(e.target)&&e.preventDefault()},onEscapeKeyDown:e=>{e.preventDefault(),e.stopPropagation(),w===void 0?(v(!1),p.current?.focus()):re()},children:[w!==void 0&&(0,B.jsx)(`p`,{className:`border-b px-2 py-2 text-xs text-muted-foreground`,children:`Editing value. Enter saves; Escape cancels.`}),O.isFetching&&(0,B.jsx)(`p`,{role:`status`,className:`px-3 py-2 text-xs text-muted-foreground`,children:N.length?`Loading remaining suggestions…`:`Loading suggestions…`}),O.isError&&!O.versionChanged&&(0,B.jsxs)(`div`,{role:`alert`,className:`px-3 py-2 text-xs`,children:[(0,B.jsxs)(`p`,{className:`text-destructive`,children:[N.length?`Some suggestions could not load.`:`Suggestions unavailable.`,o?` Custom values still work.`:``]}),(0,B.jsx)(Pi,{type:`button`,variant:`ghost`,size:`sm`,onPointerDown:e=>e.preventDefault(),onClick:()=>void(O.isFetchNextPageError?O.fetchNextPage():O.refetch()),children:`Retry suggestions`})]}),(0,B.jsx)(`div`,{id:f,role:`listbox`,"aria-label":`${i} suggestions`,"aria-multiselectable":`true`,className:`max-h-64 overflow-y-auto overscroll-contain`,children:P.map((t,n)=>(0,B.jsxs)(`div`,{id:`${d}-choice-${n}`,role:`option`,"aria-label":t.kind===`branch`?`Explore ${t.value}`:e===`service-scope`?t.value:t.label??t.value,"aria-selected":`false`,"aria-disabled":a||t.disabled,"data-option-index":n,className:Jr(`flex cursor-pointer items-center gap-2 rounded-md px-3 py-1.5 text-12 transition-colors duration-200 hover:bg-overlay-strong`,F===n&&`bg-overlay-strong`,(a||t.disabled)&&`cursor-not-allowed opacity-50`),onPointerDown:e=>e.preventDefault(),onClick:()=>ie(t),children:[(0,B.jsxs)(`span`,{className:`min-w-0 flex-1`,children:[(0,B.jsx)(`span`,{className:Jr(`block break-words text-xs`,e===`service-scope`&&`font-mono`),children:e===`service-scope`?t.value:t.label??t.value}),e===`service-scope`&&t.label&&t.label!==t.value&&(0,B.jsx)(`span`,{className:`block break-words text-xs text-muted-foreground`,children:t.label})]}),t.kind===`branch`&&(0,B.jsx)(di,{className:`size-4 shrink-0`,"aria-hidden":`true`})]},Nk(t)))}),!P.length&&!O.isFetching&&O.isSuccess&&(0,B.jsx)(`p`,{role:`status`,className:`px-3 py-2 text-xs text-muted-foreground`,children:`No matching suggestions.`}),o&&C.trim()&&!x?.branch&&(0,B.jsx)(`p`,{className:`border-t px-2 py-2 text-xs text-muted-foreground`,children:`Press Enter to finish this scope.`})]})]})}function Rk({value:e,onChange:t,ownerId:n,serviceAccountId:r,...i}){let a=eC(e=>e.user?.is_admin??!1);return(0,B.jsxs)(`div`,{className:`space-y-2`,children:[(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Choose scope segments or type a complete scope. Select a pill to edit it.`}),(0,B.jsx)(Fk,{...i,optionSet:`service-scope`,label:`Allowed scopes`,allowCustom:!0,delimiter:`:`,context:{kind:`service-scope`,principal_type:`service_account`,owner_id:n,service_account_id:r},value:e.split(/\s+/).filter(Boolean),onChange:e=>t(e.join(` `))}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:a?`Saving catalog:skills:read grants catalog and key metadata reads across all current and future catalog services. Add catalog:skills:write to amend skill recommendations. These accounts are managed by platform administrators.`:`Platform catalog scopes must be granted by a platform administrator.`})]})}var zk=$().refine(e=>e.trim()===``||/^[1-9]\d*$/.test(e.trim()),`Must be a whole number of at least 1 (or empty for no override)`).optional().or(Ux(``)),Bk=jx({name:$().min(1,`Name is required`).max(100,`Name must be 100 characters or less`),description:$().max(500,`Description must be 500 characters or less`).optional().or(Ux(``)),allowed_scopes:$().min(1,`At least one scope is required`),role_ids:$().optional().or(Ux(``)),rate_limit_override:zk});jx({name:$().min(1,`Name is required`).max(100,`Name must be 100 characters or less`),description:$().max(500,`Description must be 500 characters or less`).optional().or(Ux(``)),allowed_scopes:$().min(1,`At least one scope is required`),role_ids:$().optional().or(Ux(``)),rate_limit_override:zk,is_active:Cx().optional()});var Vk=tx();jx({service_ids:$().refine(e=>{let t=e.split(/[,\s]+/).filter(Boolean);return t.length>=1&&t.length<=100&&new Set(t).size===t.length&&t.every(e=>Vk.safeParse(e).success)},`Enter 1–100 distinct catalog service UUIDs`),ornn_proxy_service_id:$().refine(e=>e===``||Vk.safeParse(e).success,`Enter a catalog service UUID`),expires_at:$().refine(e=>e===``||!Number.isNaN(Date.parse(e))&&Date.parse(e)>Date.now(),`Choose a future expiry`),max_writes:$().regex(/^[1-9]\d*$/,`Enter a whole number`).refine(e=>Number(e)<=1e4,`At most 10,000 writes`),window_seconds:$().regex(/^\d+$/,`Enter a whole number`).refine(e=>Number(e)>=60&&Number(e)<=86400,`Use 60–86,400 seconds`)});var Hk=s(((e,t)=>{t.exports=function(){return typeof Promise==`function`&&Promise.prototype&&Promise.prototype.then}})),Uk=s((e=>{var t,n=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706];e.getSymbolSize=function(e){if(!e)throw Error(`"version" cannot be null or undefined`);if(e<1||e>40)throw Error(`"version" should be in range from 1 to 40`);return e*4+17},e.getSymbolTotalCodewords=function(e){return n[e]},e.getBCHDigit=function(e){let t=0;for(;e!==0;)t++,e>>>=1;return t},e.setToSJISFunction=function(e){if(typeof e!=`function`)throw Error(`"toSJISFunc" is not a valid function.`);t=e},e.isKanjiModeEnabled=function(){return t!==void 0},e.toSJIS=function(e){return t(e)}})),Wk=s((e=>{e.L={bit:1},e.M={bit:0},e.Q={bit:3},e.H={bit:2};function t(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`l`:case`low`:return e.L;case`m`:case`medium`:return e.M;case`q`:case`quartile`:return e.Q;case`h`:case`high`:return e.H;default:throw Error(`Unknown EC Level: `+t)}}e.isValid=function(e){return e&&e.bit!==void 0&&e.bit>=0&&e.bit<4},e.from=function(n,r){if(e.isValid(n))return n;try{return t(n)}catch{return r}}})),Gk=s(((e,t)=>{function n(){this.buffer=[],this.length=0}n.prototype={get:function(e){let t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)==1},put:function(e,t){for(let n=0;n>>t-n-1&1)==1)},getLengthInBits:function(){return this.length},putBit:function(e){let t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}},t.exports=n})),Kk=s(((e,t)=>{function n(e){if(!e||e<1)throw Error(`BitMatrix size must be defined and greater than 0`);this.size=e,this.data=new Uint8Array(e*e),this.reservedBit=new Uint8Array(e*e)}n.prototype.set=function(e,t,n,r){let i=e*this.size+t;this.data[i]=n,r&&(this.reservedBit[i]=!0)},n.prototype.get=function(e,t){return this.data[e*this.size+t]},n.prototype.xor=function(e,t,n){this.data[e*this.size+t]^=n},n.prototype.isReserved=function(e,t){return this.reservedBit[e*this.size+t]},t.exports=n})),qk=s((e=>{var t=Uk().getSymbolSize;e.getRowColCoords=function(e){if(e===1)return[];let n=Math.floor(e/7)+2,r=t(e),i=r===145?26:Math.ceil((r-13)/(2*n-2))*2,a=[r-7];for(let e=1;e{var t=Uk().getSymbolSize,n=7;e.getPositions=function(e){let r=t(e);return[[0,0],[r-n,0],[0,r-n]]}})),Yk=s((e=>{e.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var t={N1:3,N2:3,N3:40,N4:10};e.isValid=function(e){return e!=null&&e!==``&&!isNaN(e)&&e>=0&&e<=7},e.from=function(t){return e.isValid(t)?parseInt(t,10):void 0},e.getPenaltyN1=function(e){let n=e.size,r=0,i=0,a=0,o=null,s=null;for(let c=0;c=5&&(r+=t.N1+(i-5)),o=n,i=1),n=e.get(l,c),n===s?a++:(a>=5&&(r+=t.N1+(a-5)),s=n,a=1)}i>=5&&(r+=t.N1+(i-5)),a>=5&&(r+=t.N1+(a-5))}return r},e.getPenaltyN2=function(e){let n=e.size,r=0;for(let t=0;t=10&&(i===1488||i===93)&&r++,a=a<<1&2047|e.get(o,t),o>=10&&(a===1488||a===93)&&r++}return r*t.N3},e.getPenaltyN4=function(e){let n=0,r=e.data.length;for(let t=0;t{var t=Wk(),n=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,40,48,16,31,43,51,17,33,45,54,18,35,48,57,19,37,51,60,19,38,53,63,20,40,56,66,21,43,59,70,22,45,62,74,24,47,65,77,25,49,68,81],r=[7,10,13,17,10,16,22,28,15,26,36,44,20,36,52,64,26,48,72,88,36,64,96,112,40,72,108,130,48,88,132,156,60,110,160,192,72,130,192,224,80,150,224,264,96,176,260,308,104,198,288,352,120,216,320,384,132,240,360,432,144,280,408,480,168,308,448,532,180,338,504,588,196,364,546,650,224,416,600,700,224,442,644,750,252,476,690,816,270,504,750,900,300,560,810,960,312,588,870,1050,336,644,952,1110,360,700,1020,1200,390,728,1050,1260,420,784,1140,1350,450,812,1200,1440,480,868,1290,1530,510,924,1350,1620,540,980,1440,1710,570,1036,1530,1800,570,1064,1590,1890,600,1120,1680,1980,630,1204,1770,2100,660,1260,1860,2220,720,1316,1950,2310,750,1372,2040,2430];e.getBlocksCount=function(e,r){switch(r){case t.L:return n[(e-1)*4+0];case t.M:return n[(e-1)*4+1];case t.Q:return n[(e-1)*4+2];case t.H:return n[(e-1)*4+3];default:return}},e.getTotalCodewordsCount=function(e,n){switch(n){case t.L:return r[(e-1)*4+0];case t.M:return r[(e-1)*4+1];case t.Q:return r[(e-1)*4+2];case t.H:return r[(e-1)*4+3];default:return}}})),Zk=s((e=>{var t=new Uint8Array(512),n=new Uint8Array(256);(function(){let e=1;for(let r=0;r<255;r++)t[r]=e,n[e]=r,e<<=1,e&256&&(e^=285);for(let e=255;e<512;e++)t[e]=t[e-255]})(),e.log=function(e){if(e<1)throw Error(`log(`+e+`)`);return n[e]},e.exp=function(e){return t[e]},e.mul=function(e,r){return e===0||r===0?0:t[n[e]+n[r]]}})),Qk=s((e=>{var t=Zk();e.mul=function(e,n){let r=new Uint8Array(e.length+n.length-1);for(let i=0;i=0;){let e=r[0];for(let i=0;i{var n=Qk();function r(e){this.genPoly=void 0,this.degree=e,this.degree&&this.initialize(this.degree)}r.prototype.initialize=function(e){this.degree=e,this.genPoly=n.generateECPolynomial(this.degree)},r.prototype.encode=function(e){if(!this.genPoly)throw Error(`Encoder not initialized`);let t=new Uint8Array(e.length+this.degree);t.set(e);let r=n.mod(t,this.genPoly),i=this.degree-r.length;if(i>0){let e=new Uint8Array(this.degree);return e.set(r,i),e}return r},t.exports=r})),eA=s((e=>{e.isValid=function(e){return!isNaN(e)&&e>=1&&e<=40}})),tA=s((e=>{var t=`[0-9]+`,n=`[A-Z $%*+\\-./:]+`,r=`(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+`;r=r.replace(/u/g,`\\u`);var i=`(?:(?![A-Z0-9 $%*+\\-./:]|`+r+`)(?:.|[\r +]))+`;e.KANJI=new RegExp(r,`g`),e.BYTE_KANJI=RegExp(`[^A-Z0-9 $%*+\\-./:]+`,`g`),e.BYTE=new RegExp(i,`g`),e.NUMERIC=new RegExp(t,`g`),e.ALPHANUMERIC=new RegExp(n,`g`);var a=RegExp(`^`+r+`$`),o=RegExp(`^`+t+`$`),s=RegExp(`^[A-Z0-9 $%*+\\-./:]+$`);e.testKanji=function(e){return a.test(e)},e.testNumeric=function(e){return o.test(e)},e.testAlphanumeric=function(e){return s.test(e)}})),nA=s((e=>{var t=eA(),n=tA();e.NUMERIC={id:`Numeric`,bit:1,ccBits:[10,12,14]},e.ALPHANUMERIC={id:`Alphanumeric`,bit:2,ccBits:[9,11,13]},e.BYTE={id:`Byte`,bit:4,ccBits:[8,16,16]},e.KANJI={id:`Kanji`,bit:8,ccBits:[8,10,12]},e.MIXED={bit:-1},e.getCharCountIndicator=function(e,n){if(!e.ccBits)throw Error(`Invalid mode: `+e);if(!t.isValid(n))throw Error(`Invalid version: `+n);return n>=1&&n<10?e.ccBits[0]:n<27?e.ccBits[1]:e.ccBits[2]},e.getBestModeForData=function(t){return n.testNumeric(t)?e.NUMERIC:n.testAlphanumeric(t)?e.ALPHANUMERIC:n.testKanji(t)?e.KANJI:e.BYTE},e.toString=function(e){if(e&&e.id)return e.id;throw Error(`Invalid mode`)},e.isValid=function(e){return e&&e.bit&&e.ccBits};function r(t){if(typeof t!=`string`)throw Error(`Param is not a string`);switch(t.toLowerCase()){case`numeric`:return e.NUMERIC;case`alphanumeric`:return e.ALPHANUMERIC;case`kanji`:return e.KANJI;case`byte`:return e.BYTE;default:throw Error(`Unknown mode: `+t)}}e.from=function(t,n){if(e.isValid(t))return t;try{return r(t)}catch{return n}}})),rA=s((e=>{var t=Uk(),n=Xk(),r=Wk(),i=nA(),a=eA(),o=7973,s=t.getBCHDigit(o);function c(t,n,r){for(let i=1;i<=40;i++)if(n<=e.getCapacity(i,r,t))return i}function l(e,t){return i.getCharCountIndicator(e,t)+4}function u(e,t){let n=0;return e.forEach(function(e){let r=l(e.mode,t);n+=r+e.getBitsLength()}),n}function d(t,n){for(let r=1;r<=40;r++)if(u(t,r)<=e.getCapacity(r,n,i.MIXED))return r}e.from=function(e,t){return a.isValid(e)?parseInt(e,10):t},e.getCapacity=function(e,r,o){if(!a.isValid(e))throw Error(`Invalid QR Code version`);o===void 0&&(o=i.BYTE);let s=(t.getSymbolTotalCodewords(e)-n.getTotalCodewordsCount(e,r))*8;if(o===i.MIXED)return s;let c=s-l(o,e);switch(o){case i.NUMERIC:return Math.floor(c/10*3);case i.ALPHANUMERIC:return Math.floor(c/11*2);case i.KANJI:return Math.floor(c/13);case i.BYTE:default:return Math.floor(c/8)}},e.getBestVersionForData=function(e,t){let n,i=r.from(t,r.M);if(Array.isArray(e)){if(e.length>1)return d(e,i);if(e.length===0)return 1;n=e[0]}else n=e;return c(n.mode,n.getLength(),i)},e.getEncodedBits=function(e){if(!a.isValid(e)||e<7)throw Error(`Invalid QR Code version`);let n=e<<12;for(;t.getBCHDigit(n)-s>=0;)n^=o<{var t=Uk(),n=1335,r=21522,i=t.getBCHDigit(n);e.getEncodedBits=function(e,a){let o=e.bit<<3|a,s=o<<10;for(;t.getBCHDigit(s)-i>=0;)s^=n<{var n=nA();function r(e){this.mode=n.NUMERIC,this.data=e.toString()}r.getBitsLength=function(e){return 10*Math.floor(e/3)+(e%3?e%3*3+1:0)},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){let t,n,r;for(t=0;t+3<=this.data.length;t+=3)n=this.data.substr(t,3),r=parseInt(n,10),e.put(r,10);let i=this.data.length-t;i>0&&(n=this.data.substr(t),r=parseInt(n,10),e.put(r,i*3+1))},t.exports=r})),oA=s(((e,t)=>{var n=nA(),r=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`.split(``);function i(e){this.mode=n.ALPHANUMERIC,this.data=e}i.getBitsLength=function(e){return 11*Math.floor(e/2)+e%2*6},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t;for(t=0;t+2<=this.data.length;t+=2){let n=r.indexOf(this.data[t])*45;n+=r.indexOf(this.data[t+1]),e.put(n,11)}this.data.length%2&&e.put(r.indexOf(this.data[t]),6)},t.exports=i})),sA=s(((e,t)=>{var n=nA();function r(e){this.mode=n.BYTE,typeof e==`string`?this.data=new TextEncoder().encode(e):this.data=new Uint8Array(e)}r.getBitsLength=function(e){return e*8},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(e){for(let t=0,n=this.data.length;t{var n=nA(),r=Uk();function i(e){this.mode=n.KANJI,this.data=e}i.getBitsLength=function(e){return e*13},i.prototype.getLength=function(){return this.data.length},i.prototype.getBitsLength=function(){return i.getBitsLength(this.data.length)},i.prototype.write=function(e){let t;for(t=0;t=33088&&n<=40956)n-=33088;else if(n>=57408&&n<=60351)n-=49472;else throw Error(`Invalid SJIS character: `+this.data[t]+` +Make sure your charset is UTF-8`);n=(n>>>8&255)*192+(n&255),e.put(n,13)}},t.exports=i})),lA=s(((e,t)=>{var n={single_source_shortest_paths:function(e,t,r){var i={},a={};a[t]=0;var o=n.PriorityQueue.make();o.push(t,0);for(var s,c,l,u,d,f,p,m,h;!o.empty();)for(l in s=o.pop(),c=s.value,u=s.cost,d=e[c]||{},d)d.hasOwnProperty(l)&&(f=d[l],p=u+f,m=a[l],h=a[l]===void 0,(h||m>p)&&(a[l]=p,o.push(l,p),i[l]=c));if(r!==void 0&&a[r]===void 0){var g=[`Could not find a path from `,t,` to `,r,`.`].join(``);throw Error(g)}return i},extract_shortest_path_from_predecessor_list:function(e,t){for(var n=[],r=t;r;)n.push(r),e[r],r=e[r];return n.reverse(),n},find_path:function(e,t,r){var i=n.single_source_shortest_paths(e,t,r);return n.extract_shortest_path_from_predecessor_list(i,r)},PriorityQueue:{make:function(e){var t=n.PriorityQueue,r={},i;for(i in e||={},t)t.hasOwnProperty(i)&&(r[i]=t[i]);return r.queue=[],r.sorter=e.sorter||t.default_sorter,r},default_sorter:function(e,t){return e.cost-t.cost},push:function(e,t){var n={value:e,cost:t};this.queue.push(n),this.queue.sort(this.sorter)},pop:function(){return this.queue.shift()},empty:function(){return this.queue.length===0}}};t!==void 0&&(t.exports=n)})),uA=s((e=>{var t=nA(),n=aA(),r=oA(),i=sA(),a=cA(),o=tA(),s=Uk(),c=lA();function l(e){return unescape(encodeURIComponent(e)).length}function u(e,t,n){let r=[],i;for(;(i=e.exec(n))!==null;)r.push({data:i[0],index:i.index,mode:t,length:i[0].length});return r}function d(e){let n=u(o.NUMERIC,t.NUMERIC,e),r=u(o.ALPHANUMERIC,t.ALPHANUMERIC,e),i,a;return s.isKanjiModeEnabled()?(i=u(o.BYTE,t.BYTE,e),a=u(o.KANJI,t.KANJI,e)):(i=u(o.BYTE_KANJI,t.BYTE,e),a=[]),n.concat(r,i,a).sort(function(e,t){return e.index-t.index}).map(function(e){return{data:e.data,mode:e.mode,length:e.length}})}function f(e,o){switch(o){case t.NUMERIC:return n.getBitsLength(e);case t.ALPHANUMERIC:return r.getBitsLength(e);case t.KANJI:return a.getBitsLength(e);case t.BYTE:return i.getBitsLength(e)}}function p(e){return e.reduce(function(e,t){let n=e.length-1>=0?e[e.length-1]:null;return n&&n.mode===t.mode?(e[e.length-1].data+=t.data,e):(e.push(t),e)},[])}function m(e){let n=[];for(let r=0;r{var t=Uk(),n=Wk(),r=Gk(),i=Kk(),a=qk(),o=Jk(),s=Yk(),c=Xk(),l=$k(),u=rA(),d=iA(),f=nA(),p=uA();function m(e,t){let n=e.size,r=o.getPositions(t);for(let t=0;t=0&&t<=6&&(r===0||r===6)||r>=0&&r<=6&&(t===0||t===6)||t>=2&&t<=4&&r>=2&&r<=4?e.set(i+t,a+r,!0,!0):e.set(i+t,a+r,!1,!0))}}function h(e){let t=e.size;for(let n=8;n>t&1)==1,e.set(i,a,o,!0),e.set(a,i,o,!0)}function v(e,t,n){let r=e.size,i=d.getEncodedBits(t,n),a,o;for(a=0;a<15;a++)o=(i>>a&1)==1,a<6?e.set(a,8,o,!0):a<8?e.set(a+1,8,o,!0):e.set(r-15+a,8,o,!0),a<8?e.set(8,r-a-1,o,!0):a<9?e.set(8,15-a-1+1,o,!0):e.set(8,15-a-1,o,!0);e.set(r-8,8,1,!0)}function y(e,t){let n=e.size,r=-1,i=n-1,a=7,o=0;for(let s=n-1;s>0;s-=2)for(s===6&&s--;;){for(let n=0;n<2;n++)if(!e.isReserved(i,s-n)){let r=!1;o>>a&1)==1),e.set(i,s-n,r),a--,a===-1&&(o++,a=7)}if(i+=r,i<0||n<=i){i-=r,r=-r;break}}}function b(e,n,i){let a=new r;i.forEach(function(t){a.put(t.mode.bit,4),a.put(t.getLength(),f.getCharCountIndicator(t.mode,e)),t.write(a)});let o=(t.getSymbolTotalCodewords(e)-c.getTotalCodewordsCount(e,n))*8;for(a.getLengthInBits()+4<=o&&a.put(0,4);a.getLengthInBits()%8!=0;)a.putBit(0);let s=(o-a.getLengthInBits())/8;for(let e=0;e=7&&_(d,n),y(d,l),isNaN(a)&&(a=s.getBestMask(d,v.bind(null,d,r))),s.applyMask(a,d),v(d,r,a),{modules:d,version:n,errorCorrectionLevel:r,maskPattern:a,segments:o}}e.create=function(e,r){if(e===void 0||e===``)throw Error(`No input text`);let i=n.M,a,o;return r!==void 0&&(i=n.from(r.errorCorrectionLevel,n.M),a=u.from(r.version),o=s.from(r.maskPattern),r.toSJISFunc&&t.setToSJISFunction(r.toSJISFunc)),S(e,a,i,o)}})),dA=s((e=>{function t(e){if(typeof e==`number`&&(e=e.toString()),typeof e!=`string`)throw Error(`Color should be defined as hex string`);let t=e.slice().replace(`#`,``).split(``);if(t.length<3||t.length===5||t.length>8)throw Error(`Invalid hex color: `+e);(t.length===3||t.length===4)&&(t=Array.prototype.concat.apply([],t.map(function(e){return[e,e]}))),t.length===6&&t.push(`F`,`F`);let n=parseInt(t.join(``),16);return{r:n>>24&255,g:n>>16&255,b:n>>8&255,a:n&255,hex:`#`+t.slice(0,6).join(``)}}e.getOptions=function(e){e||={},e.color||={};let n=e.margin===void 0||e.margin===null||e.margin<0?4:e.margin,r=e.width&&e.width>=21?e.width:void 0,i=e.scale||4;return{width:r,scale:r?4:i,margin:n,color:{dark:t(e.color.dark||`#000000ff`),light:t(e.color.light||`#ffffffff`)},type:e.type,rendererOpts:e.rendererOpts||{}}},e.getScale=function(e,t){return t.width&&t.width>=e+t.margin*2?t.width/(e+t.margin*2):t.scale},e.getImageWidth=function(t,n){let r=e.getScale(t,n);return Math.floor((t+n.margin*2)*r)},e.qrToImageData=function(t,n,r){let i=n.modules.size,a=n.modules.data,o=e.getScale(i,r),s=Math.floor((i+r.margin*2)*o),c=r.margin*o,l=[r.color.light,r.color.dark];for(let e=0;e=c&&n>=c&&e{var t=dA();function n(e,t,n){e.clearRect(0,0,t.width,t.height),t.style||={},t.height=n,t.width=n,t.style.height=n+`px`,t.style.width=n+`px`}function r(){try{return document.createElement(`canvas`)}catch{throw Error(`You need to specify a canvas element`)}}e.render=function(e,i,a){let o=a,s=i;o===void 0&&(!i||!i.getContext)&&(o=i,i=void 0),i||(s=r()),o=t.getOptions(o);let c=t.getImageWidth(e.modules.size,o),l=s.getContext(`2d`),u=l.createImageData(c,c);return t.qrToImageData(u.data,e,o),n(l,s,c),l.putImageData(u,0,0),s},e.renderToDataURL=function(t,n,r){let i=r;i===void 0&&(!n||!n.getContext)&&(i=n,n=void 0),i||={};let a=e.render(t,n,i),o=i.type||`image/png`,s=i.rendererOpts||{};return a.toDataURL(o,s.quality)}})),pA=s((e=>{var t=dA();function n(e,t){let n=e.a/255,r=t+`="`+e.hex+`"`;return n<1?r+` `+t+`-opacity="`+n.toFixed(2).slice(1)+`"`:r}function r(e,t,n){let r=e+t;return n!==void 0&&(r+=` `+n),r}function i(e,t,n){let i=``,a=0,o=!1,s=0;for(let c=0;c0&&l>0&&e[c-1]||(i+=o?r(`M`,l+n,.5+u+n):r(`m`,a,0),a=0,o=!1),l+1`:``,d=``,f=`viewBox="0 0 `+l+` `+l+`"`,p=``+u+d+` -`;return typeof a==`function`&&a(null,p),p}})),mA=u(s((e=>{var t=Vk(),n=uA(),r=fA(),i=pA();function a(e,r,i,a,o){let s=[].slice.call(arguments,1),c=s.length,l=typeof s[c-1]==`function`;if(!l&&!t())throw Error(`Callback required as last argument`);if(l){if(c<2)throw Error(`Too few arguments provided`);c===2?(o=i,i=r,r=a=void 0):c===3&&(r.getContext&&o===void 0?(o=a,a=void 0):(o=a,a=i,i=r,r=void 0))}else{if(c<1)throw Error(`Too few arguments provided`);return c===1?(i=r,r=a=void 0):c===2&&!r.getContext&&(a=i,i=r,r=void 0),new Promise(function(t,o){try{t(e(n.create(i,a),r,a))}catch(e){o(e)}})}try{let t=n.create(i,a);o(null,e(t,r,a))}catch(e){o(e)}}e.create=n.create,e.toCanvas=a.bind(null,r.render),e.toDataURL=a.bind(null,r.renderToDataURL),e.toString=a.bind(null,function(e,t,n){return i.render(e,n)})}))(),1);function hA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z`})})}function gA(e){return(0,B.jsx)(`svg`,{viewBox:`185 40 472 515`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsxs)(`g`,{children:[(0,B.jsx)(`polygon`,{points:`557.09,211.99 565.4,538.36 631.96,538.36 640.28,93.18`}),(0,B.jsx)(`polygon`,{points:`640.28,56.91 538.72,56.91 379.35,284.53 430.13,357.05`}),(0,B.jsx)(`polygon`,{points:`201.61,538.36 303.17,538.36 353.96,465.84 303.17,393.31`}),(0,B.jsx)(`polygon`,{points:`201.61,211.99 430.13,538.36 531.69,538.36 303.17,211.99`})]})})}function _A(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z`})})}function vA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z`})})}function yA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 600 600`,fill:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M407.5 31c-72.3 0-144.7-0.1-217 0.2-10.9 0-22 1.2-32.7 3.6C76.3 52.7 22.4 128 32.3 210.8c6.7 56.4 36.1 97.5 86.9 122.9 28.6 14.3 59.4 19.2 91.1 18.7 28.6-0.4 55.7-7.3 81.9-18.4 54.8-23.1 109.8-45.5 164.2-69.4 20.8-9.1 41.1-20.1 60-32.7 28.9-19.2 44-47.1 46.1-82.2C566.3 85.7 515.3 31 451 31c-14.2 0-28.3 0-43.5 0z`}),(0,B.jsx)(`path`,{d:`M315.3 562.3c13.2 5.6 26.6 8.7 40.6 8.8 25.2 0.1 50.3 0.1 75.5 0 9.6 0 19.3 0 28.9-1 27-2.7 50.8-13.5 70.5-32 30.2-28.5 43.8-63.7 39.8-105.2-3.3-33.2-17.5-61-43.2-82.2-38.2-31.5-81.1-39-127.5-21.1-27.6 10.6-54.8 22.5-82.1 33.9-46.7 19.5-73.4 64.9-66.2 115.1 5.6 39.2 27.6 66.9 63.7 83.7z`}),(0,B.jsx)(`path`,{d:`M97 565.6c38.4 11 77.5-2 101-33.2 18.3-24.3 21.1-52.1 16.9-80.9-5.9-40.4-37.2-71.1-79-77-42-5.9-77.7 16.4-95 49.8-10.8 20.9-12.5 43.2-8.9 65.7 6 37.7 27.8 62.8 64.8 75.6z`})]})}function bA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 50 72`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M41.7154 23.1929C38.9531 24.0129 36.8707 25.8677 35.3457 27.8826C35.0182 28.3151 34.3358 27.9901 34.4658 27.4601C37.3856 15.4534 33.5283 5.47401 21.5039 0.561817C20.894 0.311833 20.259 0.859299 20.419 1.49926C25.8887 23.4604 2.88236 21.608 5.78971 46.504C5.83971 46.9314 5.35973 47.2239 5.00975 46.9739C3.9198 46.1915 2.70237 44.5591 1.86741 43.4116C1.62242 43.0742 1.09245 43.1692 0.979951 43.5716C0.314984 45.9765 0 48.2413 0 50.4912C0 59.2407 4.49727 66.9427 11.3044 71.4074C11.6944 71.6624 12.1944 71.2974 12.0619 70.8499C11.7119 69.675 11.5144 68.4351 11.4994 67.1527C11.4994 66.3652 11.5494 65.5603 11.6719 64.8103C11.9569 62.9254 12.6119 61.1306 13.7118 59.4957C17.4841 53.8335 25.0462 48.3638 23.8388 40.9368C23.7613 40.4668 24.3163 40.1569 24.6663 40.4793C29.9935 45.3465 31.0485 51.8936 30.1735 57.7658C30.0985 58.2757 30.7385 58.5482 31.061 58.1482C31.8759 57.1283 32.8709 56.2334 33.9533 55.5609C34.2233 55.3934 34.5833 55.5209 34.6858 55.8209C35.2882 57.5733 36.1832 59.2182 37.0281 60.8631C38.0381 62.8404 38.5756 65.0978 38.4906 67.4877C38.4481 68.6501 38.2556 69.775 37.9331 70.8449C37.7956 71.2974 38.2906 71.6749 38.6881 71.4149C45.5002 66.9502 50 59.2482 50 50.4937C50 47.4514 49.4675 44.4691 48.4601 41.6743C46.3477 35.8121 40.988 31.4099 42.3429 23.7704C42.4079 23.4054 42.0704 23.0879 41.7154 23.1929Z`})})}function xA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45`})})}function SA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 512 512`,fill:`currentColor`,stroke:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M3 248.945C18 248.945 76 236 106 219C136 202 136 202 198 158C276.497 102.293 332 120.945 423 120.945`,fill:`none`,strokeWidth:`90`}),(0,B.jsx)(`path`,{d:`M511 121.5L357.25 210.268L357.25 32.7324L511 121.5Z`}),(0,B.jsx)(`path`,{d:`M0 249C15 249 73 261.945 103 278.945C133 295.945 133 295.945 195 339.945C273.497 395.652 329 377 420 377`,fill:`none`,strokeWidth:`90`}),(0,B.jsx)(`path`,{d:`M508 376.445L354.25 287.678L354.25 465.213L508 376.445Z`})]})}function CA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z`})})}function wA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z`})})}function TA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12`})})}function EA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z`})})}function DA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V8.999h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.287zM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124zM7.119 20.452H3.555V8.999h3.564v11.453zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0z`})})}function OA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.372-.01-.57-.01-.198 0-.52.075-.792.372-.272.298-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.29.173-1.413-.074-.124-.272-.198-.57-.347zm-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.999-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.886 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.89c0 2.096.547 4.142 1.588 5.945L.057 24l6.298-1.654a11.882 11.882 0 005.684 1.448h.005c6.554 0 11.89-5.335 11.893-11.89 0-3.177-1.239-6.161-3.486-8.416z`})})}function kA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z`})})}function AA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z`})})}function jA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z`})})}function MA(e){return(0,B.jsxs)(`svg`,{viewBox:`4 2 40 44`,fill:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M20.0842 3.02588L19.8595 3.16179C19.5021 3.37799 19.1654 3.61972 18.8512 3.88385L19.4993 3.42798H25L26 11L21 16L16 19.4754V23.4829C16 26.2819 17.4629 28.8774 19.8574 30.3268L25.1211 33.5129L14 40.0002H11.8551L7.85737 37.5804C5.46286 36.131 4 33.5355 4 30.7365V17.2606C4 14.4607 5.46379 11.8645 7.85952 10.4154L19.8595 3.15687C19.9339 3.11189 20.0088 3.06823 20.0842 3.02588Z`}),(0,B.jsx)(`path`,{d:`M32 19V23.4803C32 26.2793 30.5371 28.8748 28.1426 30.3242L16.1426 37.5878C13.6878 39.0737 10.6335 39.1273 8.1355 37.7487L19.8573 44.844C22.4039 46.3855 25.5959 46.3855 28.1426 44.844L40.1426 37.5803C42.5371 36.1309 43.9999 33.5354 43.9999 30.7364V27.5L42.9999 26L32 19Z`}),(0,B.jsx)(`path`,{d:`M40.1405 10.4153L28.1405 3.15678C25.6738 1.66471 22.6021 1.61849 20.0979 3.01811L19.8595 3.16231C17.4638 4.61143 16 7.20757 16 10.0075V19.4914L19.8595 17.1568C22.4051 15.6171 25.5949 15.6171 28.1405 17.1568L40.1405 24.4153C42.4613 25.8192 43.9076 28.2994 43.9957 30.9985C43.9986 30.9113 44 30.824 44 30.7364V17.2605C44 14.4606 42.5362 11.8644 40.1405 10.4153Z`})]})}function NA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z`})})}function PA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z`})})}function FA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z`})})}function IA({variant:e=`solid`,...t}){return(0,B.jsxs)(`svg`,{viewBox:`80 130 660 540`,fill:e===`outline`?`none`:`currentColor`,stroke:e===`outline`?`currentColor`:`none`,strokeWidth:e===`outline`?30:0,strokeLinejoin:`round`,"aria-hidden":`true`,...t,children:[(0,B.jsx)(`path`,{d:`M423.075867,410.677734 C415.744812,398.010834 408.757996,385.129547 401.024048,372.713654 C364.628082,314.284393 321.334015,261.391510 270.727295,214.787643 C248.109116,193.958496 223.566727,175.217773 199.869446,155.563187 C197.579849,153.664200 194.587845,152.249771 195.716263,148.465775 C196.920410,144.427872 200.280441,144.764893 203.488174,144.765289 C292.963928,144.776016 382.439758,144.824509 471.915405,144.718201 C484.221069,144.703568 493.629883,149.569427 501.132721,159.136398 C533.640015,200.586929 556.618713,246.732040 569.629150,298.312439 C533.666138,310.514313 503.289673,330.491913 477.096375,357.345367 C459.431030,375.455902 441.105896,392.922882 423.075867,410.677734 Z`}),(0,B.jsx)(`path`,{d:`M422.830688,410.940979 C441.105896,392.922882 459.431030,375.455902 477.096375,357.345367 C503.289673,330.491913 533.666138,310.514313 569.702515,298.715515 C580.198547,296.204865 590.355896,293.571594 600.645630,291.646088 C607.830872,290.301514 615.179077,289.508179 622.486084,289.187988 C658.613342,287.604736 693.312744,293.690247 726.160522,309.210541 C727.006897,309.610504 727.779114,310.167572 728.486572,310.591797 C721.357727,319.227142 714.146179,327.480194 707.472229,336.147247 C693.437988,354.372803 684.670654,375.585999 674.255493,395.854858 C664.208557,415.407074 654.319397,435.040466 644.235168,454.573242 C642.501099,457.931915 640.138306,460.965942 637.753174,464.651062 C637.353271,465.501129 637.266113,465.850037 637.178955,466.198975 C637.144836,466.101532 636.873535,466.197357 636.418274,466.883179 C636.234314,467.473175 636.050354,468.063171 636.095703,468.046875 C634.338562,469.671326 632.340759,471.098724 630.823547,472.925354 C606.515503,502.192047 575.273743,518.862732 537.765747,523.886047 C515.541626,526.862549 493.767212,524.121155 472.241058,518.067810 C427.652893,505.529175 384.314484,489.700134 341.877197,469.465759 C371.294128,453.300354 398.227325,434.038086 422.830688,410.940979 Z`}),(0,B.jsx)(`path`,{d:`M636.112793,468.058197 C633.715088,472.205719 631.564819,476.520996 628.883423,480.476196 C566.403625,572.638000 480.475006,628.906128 370.287048,645.529480 C276.822723,659.629700 189.911423,639.837524 110.412666,588.346252 C100.677971,582.041016 95.101463,573.316772 94.325127,561.051025 C94.781334,559.023499 94.988503,557.707825 94.988693,556.392212 C95.001305,471.848450 95.001999,387.304718 94.956757,302.760986 C94.955917,301.187042 94.391716,299.613464 94.090363,298.039703 C96.664818,292.874542 99.409256,292.618652 103.908760,297.033661 C118.990723,311.832458 133.654266,327.092285 149.266510,341.309784 C205.874664,392.860687 268.921021,434.931610 338.013672,467.929260 C339.056030,468.427094 340.173340,468.768005 341.877197,469.465759 C384.314484,489.700134 427.652893,505.529175 472.241058,518.067810 C493.767212,524.121155 515.541626,526.862549 537.765747,523.886047 C575.273743,518.862732 606.515503,502.192047 630.823547,472.925354 C632.340759,471.098724 634.338562,469.671326 636.112793,468.058197 Z`})]})}function LA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z`})})}function RA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M4.6035 0v24h4.9317V0zm9.8613 0v24h4.9317V0z`})})}function zA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.381-.008.008 5.352 0 11.971V12c0 6.64 5.359 12 12 12 6.64 0 12-5.36 12-12 0-6.641-5.36-12-12-12zm0 20.801c-4.846.015-8.786-3.904-8.801-8.75V12c-.014-4.846 3.904-8.786 8.75-8.801H12c4.847-.014 8.786 3.904 8.801 8.75V12c.015 4.847-3.904 8.786-8.75 8.801H12zm5.44-11.76c0 1.359-1.12 2.479-2.481 2.479-1.366-.007-2.472-1.113-2.479-2.479 0-1.361 1.12-2.481 2.479-2.481 1.361 0 2.481 1.12 2.481 2.481zm0 5.919c0 1.36-1.12 2.48-2.481 2.48-1.367-.008-2.473-1.114-2.479-2.48 0-1.359 1.12-2.479 2.479-2.479 1.361-.001 2.481 1.12 2.481 2.479zm-5.919 0c0 1.36-1.12 2.48-2.479 2.48-1.368-.007-2.475-1.113-2.481-2.48 0-1.359 1.12-2.479 2.481-2.479 1.358-.001 2.479 1.12 2.479 2.479zm0-5.919c0 1.359-1.12 2.479-2.479 2.479-1.367-.007-2.475-1.112-2.481-2.479 0-1.361 1.12-2.481 2.481-2.481 1.358 0 2.479 1.12 2.479 2.481z`})})}function BA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z`})})}function VA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,fillRule:`evenodd`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M9.046 7.104a.527.527 0 110 1.055.527.527 0 010-1.055z`}),(0,B.jsx)(`path`,{d:`M15.376 7.104a.528.528 0 110 1.056.528.528 0 010-1.056z`}),(0,B.jsx)(`path`,{clipRule:`evenodd`,d:`M16.877 1.912c.58-.27 1.14-.323 1.616-.037a.317.317 0 01-.326.542c-.227-.136-.547-.153-1.022.068-.352.165-.765.45-1.234.866 2.683 1.17 4.4 3.5 5.148 5.921a6.421 6.421 0 00-.704.184c-.578.016-1.174.204-1.502.735-.338.55-.268 1.276.072 2.069l.005.012.007.014c.523 1.045 1.318 1.91 2.2 2.284-.912 3.274-3.44 6.144-5.972 6.988v2.109h-2.11v-2.11c-1.043.417-2.086.01-2.11 0v2.11h-2.11v-2.11c-2.531-.843-5.061-3.713-5.973-6.987.882-.373 1.678-1.238 2.2-2.284l.007-.014.006-.012c.34-.793.41-1.518.071-2.069-.327-.531-.923-.719-1.503-.735a6.409 6.409 0 00-.704-.183c.749-2.421 2.466-4.751 5.149-5.922-.47-.416-.88-.701-1.234-.866-.474-.221-.794-.204-1.021-.068a.318.318 0 01-.435-.109.317.317 0 01.109-.433c.476-.286 1.036-.233 1.615.037.49.229 1.031.628 1.621 1.182A9.924 9.924 0 0112 2.568c1.199 0 2.284.19 3.256.526.59-.554 1.13-.953 1.62-1.182zM8.835 6.577a1.266 1.266 0 100 2.532 1.266 1.266 0 000-2.532zm6.33 0a1.267 1.267 0 100 2.533 1.267 1.267 0 000-2.533z`}),(0,B.jsx)(`path`,{d:`M.395 13.118c-.966-1.932-.163-3.863 2.41-3.365v-.001l.05.01c.084.018.17.038.26.06.033.009.067.017.1.027.084.022.168.048.255.076l.09.027c.528 0 .95.158 1.16.501.212.343.212.87-.105 1.61-.085.17-.178.333-.276.489l-.01.017a4.967 4.967 0 01-.62.791l-.019.02c-1.092 1.117-2.496 1.336-3.295-.262z`}),(0,B.jsx)(`path`,{d:`M21.193 9.753c2.574-.5 3.378 1.433 2.411 3.365-.58 1.159-1.476 1.361-2.342.96l-.011-.005a2.419 2.419 0 01-.114-.056l-.019-.01a2.751 2.751 0 01-.115-.067l-.023-.014c-.035-.022-.071-.044-.106-.068l-.05-.035c-.55-.388-1.062-1.007-1.44-1.76-.276-.647-.311-1.132-.174-1.472.176-.439.636-.639 1.23-.639.032-.011.066-.02.099-.03.08-.026.16-.05.238-.072l.117-.03a5.502 5.502 0 01.3-.067z`})]})}function HA({children:e,badge:t,className:n}){return(0,B.jsxs)(`span`,{className:`relative inline-flex shrink-0 items-center justify-center [&>svg]:!h-full [&>svg]:!w-full ${n??`h-5 w-5`}`,children:[e,(0,B.jsx)(UA,{badge:t})]})}function UA({badge:e}){return(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`absolute -bottom-[30%] -right-[30%] inline-flex h-[70%] w-[70%] items-center justify-center rounded-md border border-border bg-card text-foreground ring-2 ring-background [&>svg]:!h-full [&>svg]:!w-full`,children:e})}function WA({className:e}){return(0,B.jsx)(hA,{"data-slug":`llm-openai`,className:e})}function GA({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(hi,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(hA,{"data-slug":`llm-openai-codex`,className:`h-5 w-5`})})}function KA({className:e}){return(0,B.jsx)(gA,{"data-slug":`llm-xai`,className:e})}function qA({className:e}){return(0,B.jsx)(_A,{"data-slug":`llm-anthropic`,className:e})}function JA({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(Oi,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(wA,{"data-slug":`llm-google-ai`,className:`h-5 w-5`})})}function YA({className:e}){return(0,B.jsx)(vA,{"data-slug":`llm-mistral`,className:e})}function XA({className:e}){return(0,B.jsx)(yA,{"data-slug":`llm-cohere`,className:e})}function ZA({className:e}){return(0,B.jsx)(xA,{"data-slug":`llm-deepseek`,className:e})}function QA({className:e}){return(0,B.jsx)(VA,{"data-slug":`llm-openclaw`,className:e})}function $A({className:e}){return(0,B.jsx)(SA,{"data-slug":`llm-openrouter`,className:e})}function ej({className:e}){return(0,B.jsx)(bA,{"data-slug":`api-firecrawl`,className:e})}function tj({className:e}){return(0,B.jsx)(CA,{"data-slug":`api-twitter`,className:e})}function nj({className:e}){return(0,B.jsx)(wA,{"data-slug":`api-google`,className:e})}function rj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(oi,{strokeWidth:2.5}),children:(0,B.jsx)(wA,{"data-slug":`api-google-workspace`,className:`h-full w-full`})})}function ij({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-calendar`,className:e,children:(0,B.jsx)(`path`,{d:`M18.316 5.684H24v12.632h-5.684V5.684zM5.684 24h12.632v-5.684H5.684V24zM18.316 5.684V0H1.895A1.894 1.894 0 0 0 0 1.895v16.421h5.684V5.684h12.632zm-7.207 6.25v-.065c.272-.144.5-.349.687-.617s.279-.595.279-.982c0-.379-.099-.72-.3-1.025a2.05 2.05 0 0 0-.832-.714 2.703 2.703 0 0 0-1.197-.257c-.6 0-1.094.156-1.481.467-.386.311-.65.671-.793 1.078l1.085.452c.086-.249.224-.461.413-.633.189-.172.445-.257.767-.257.33 0 .602.088.816.264a.86.86 0 0 1 .322.703c0 .33-.12.589-.36.778-.24.19-.535.284-.886.284h-.567v1.085h.633c.407 0 .748.109 1.02.327.272.218.407.499.407.843 0 .336-.129.614-.387.832s-.565.327-.924.327c-.351 0-.651-.103-.897-.311-.248-.208-.422-.502-.521-.881l-1.096.452c.178.616.505 1.082.977 1.401.472.319.984.478 1.538.477a2.84 2.84 0 0 0 1.293-.291c.382-.193.684-.458.902-.794.218-.336.327-.72.327-1.149 0-.429-.115-.797-.344-1.105a2.067 2.067 0 0 0-.881-.689zm2.093-1.931l.602.913L15 10.045v5.744h1.187V8.446h-.827l-2.158 1.557zM22.105 0h-3.289v5.184H24V1.895A1.894 1.894 0 0 0 22.105 0zm-3.289 23.5l4.684-4.684h-4.684V23.5zM0 22.105C0 23.152.848 24 1.895 24h3.289v-5.184H0v3.289z`})})}function aj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-drive`,className:e,children:(0,B.jsx)(`path`,{d:`M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z`})})}function oj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(Ei,{strokeWidth:2.5}),children:(0,B.jsx)(wA,{"data-slug":`api-google-gmail`,className:`h-full w-full`})})}function sj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-docs`,className:e,children:(0,B.jsx)(`path`,{d:`M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727h-6zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818v1.364zm0-3.273H7.091V9.273h9.818v1.363zM14.727 6h6l-6-6v6z`})})}function cj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-sheets`,className:e,children:(0,B.jsx)(`path`,{d:`M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6l-6-6zm1.363 10.636h-3.41v1.91h3.41v-1.91zm0 3.273h-3.41v1.91h3.41v-1.91zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5h6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z`})})}function lj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-slides`,className:e,children:(0,B.jsx)(`path`,{d:`M16.09 15.273H7.91v-4.637h8.18v4.637zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75h3.841zm-.363 2.523H6.545v7.363h10.91V9.273zm-2.728-5.979V6h6.001l-6-6v3.294z`})})}function uj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 122.88 128.1`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-notion`,className:e,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M21.19,22.46c4,3.23,5.48,3,13,2.49l70.53-4.24c1.5,0,.25-1.49-.25-1.74L92.72,10.5a14.08,14.08,0,0,0-11-3.23l-68.29,5c-2.49.24-3,1.49-2,2.49l9.73,7.72ZM25.42,38.9v74.21c0,4,2,5.48,6.48,5.23l77.52-4.48c4.49-.25,5-3,5-6.23V33.91c0-3.23-1.25-5-4-4.73l-81,4.73c-3,.25-4,1.75-4,5Zm76.53,4c.49,2.24,0,4.48-2.25,4.73L96,48.36v54.79c-3.24,1.74-6.23,2.73-8.72,2.73-4,0-5-1.24-8-5L54.83,62.55V99.66l7.73,1.74s0,4.48-6.23,4.48l-17.2,1c-.5-1,0-3.48,1.75-4l4.48-1.25V52.59l-6.23-.5a4.66,4.66,0,0,1,4.24-5.73l18.44-1.24L87.24,84V49.6l-6.48-.74a4.21,4.21,0,0,1,4-5l17.21-1ZM7.72,5.52l71-5.23C87.49-.46,89.73.05,95.21,4L117.89,20c3.74,2.74,5,3.48,5,6.47v87.42c0,5.47-2,8.71-9,9.21l-82.5,5c-5.24.25-7.73-.5-10.47-4L4.24,102.4c-3-4-4.24-7-4.24-10.46V14.24C0,9.76,2,6,7.72,5.52Z`})})}function dj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(mi,{className:`h-2.5 w-2.5`,strokeWidth:2.5}),children:(0,B.jsx)(wA,{"data-slug":`api-google-cloud`,className:`h-full w-full`})})}function fj({className:e}){return(0,B.jsx)(TA,{"data-slug":`api-github`,className:e})}function pj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(Ci,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(TA,{"data-slug":`api-github-pat`,className:`h-5 w-5`})})}function mj({className:e}){return(0,B.jsx)(EA,{"data-slug":`api-facebook`,className:e})}function hj({className:e}){return(0,B.jsx)(DA,{"data-slug":`api-linkedin`,className:e})}function gj({className:e}){return(0,B.jsx)(kA,{"data-slug":`api-discord`,className:e})}function _j({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(kA,{"data-slug":`api-discord-bot`,className:`h-5 w-5`})})}function vj({className:e}){return(0,B.jsx)(AA,{"data-slug":`api-spotify`,className:e})}function yj({className:e}){return(0,B.jsx)(jA,{"data-slug":`api-slack`,className:e})}function bj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(jA,{"data-slug":`api-slack-bot`,className:`h-5 w-5`})})}function xj({className:e}){return(0,B.jsx)(MA,{"data-slug":`api-microsoft`,className:e})}function Sj({className:e}){return(0,B.jsx)(NA,{"data-slug":`api-tiktok`,className:e})}function Cj({className:e}){return(0,B.jsx)(PA,{"data-slug":`api-twitch`,className:e})}function wj({className:e}){return(0,B.jsx)(FA,{"data-slug":`api-reddit`,className:e})}function Tj({className:e}){return(0,B.jsx)(IA,{variant:`solid`,"data-slug":`api-lark`,className:e})}function Ej({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(IA,{variant:`solid`,"data-slug":`api-lark-bot`,className:`h-5 w-5`})})}function Dj({className:e}){return(0,B.jsx)(IA,{variant:`outline`,"data-slug":`api-feishu`,className:e})}function Oj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(IA,{variant:`outline`,"data-slug":`api-feishu-bot`,className:`h-5 w-5`})})}function kj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(LA,{"data-slug":`api-telegram-bot`,className:`h-5 w-5`})})}function Aj({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(oi,{strokeWidth:2.5}),children:(0,B.jsx)(OA,{"data-slug":`api-whatsapp-business`,className:`h-full w-full`})})}function jj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 512 512`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-supabase`,className:e,children:[(0,B.jsx)(`path`,{d:`M297.6 501c-12.9 16.3-39.2 7.4-39.5-13.4L253.6 183h204.8c37.1 0 57.8 42.8 34.7 71.9z`}),(0,B.jsx)(`path`,{d:`M214.4 11c12.9-16.3 39.2-7.4 39.5 13.4l2 304.5H53.7c-37.1 0-57.8-42.8-34.7-71.9z`})]})}function Mj({className:e}){return(0,B.jsx)(RA,{"data-slug":`api-elevenlabs`,className:e})}function Nj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 -14 384 384`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-telnyx`,className:e,children:[(0,B.jsx)(`path`,{d:`M376.033 322.631C382.445 311.064 384.269 297.54 381.15 284.708C380.256 281.162 379.02 277.708 377.459 274.396C376.966 273.309 376.406 272.254 375.782 271.236L324.947 176.013H272.939L326.122 275.81C327.791 278.71 328.67 281.992 328.67 285.332C328.67 288.672 327.791 291.954 326.122 294.854C324.495 297.564 322.187 299.809 319.422 301.369C316.658 302.929 313.532 303.75 310.351 303.753H249.702C249.031 313.044 246.121 322.039 241.217 329.984C236.312 337.928 229.557 344.59 221.517 349.41H333C340.443 348.919 347.687 346.819 354.223 343.256C363.448 338.751 371.059 331.553 376.033 322.631Z`}),(0,B.jsx)(`path`,{d:`M90.9084 113.732H142.917L168.838 65.9127C171.031 61.7857 174.319 58.3308 178.347 55.9206C182.375 53.5105 186.991 52.2367 191.696 52.2367C196.401 52.2367 201.017 53.5105 205.046 55.9206C209.074 58.3308 212.362 61.7857 214.555 65.9127L239.721 113.732H291.729L254.82 44.5395C248.7 33.1074 239.556 23.5431 228.369 16.873C217.181 10.2029 204.373 6.67871 191.319 6.67871C178.264 6.67871 165.456 10.2029 154.269 16.873C143.082 23.5431 133.938 33.1074 127.818 44.5395L90.9084 113.732Z`}),(0,B.jsx)(`path`,{d:`M132.851 167.707C133.396 158.454 136.191 149.47 140.996 141.52C145.802 133.569 152.476 126.89 160.449 122.05H59.7869V167.707H132.851Z`}),(0,B.jsx)(`path`,{d:`M141.153 171.034V295.78H164.809C169.742 295.725 174.522 294.069 178.415 291.065C182.309 288.061 185.102 283.875 186.367 279.147C186.876 277.333 187.131 275.458 187.122 273.575V198.727C187.144 190.602 190.398 182.813 196.178 177.052C201.958 171.29 209.796 168.022 217.992 167.956H323.268V122.05H190.478C177.388 122.072 164.843 127.242 155.595 136.426C146.347 145.61 141.153 158.057 141.153 171.034Z`}),(0,B.jsx)(`path`,{d:`M5.17648 274.402C3.61573 277.714 2.37973 281.168 1.48554 284.714C-1.63066 297.573 0.193473 311.122 6.60253 322.72C11.5903 331.612 19.2003 338.779 28.4126 343.262C34.9489 346.825 42.1929 348.925 49.6356 349.416H191.317C204.666 349.416 217.468 344.159 226.907 334.801C236.346 325.443 241.648 312.751 241.648 299.518V175.936H217.657C211.759 176.086 206.152 178.505 202.019 182.679C197.887 186.854 195.553 192.458 195.512 198.307V273.154C195.489 281.279 192.218 289.063 186.416 294.8C180.613 300.537 172.753 303.759 164.558 303.759H72.7039C69.5232 303.756 66.3976 302.935 63.6332 301.375C60.8688 299.815 58.5603 297.57 56.9336 294.86C55.2645 291.944 54.387 288.649 54.387 285.296C54.387 281.944 55.2645 278.648 56.9336 275.733L110.117 175.936H57.6885L6.85418 271.159C6.26699 272.323 5.67979 273.321 5.17648 274.402Z`})]})}function Pj({className:e}){return(0,B.jsx)(zA,{"data-slug":`api-twilio`,className:e})}function Fj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 390 388`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-aurinko`,className:e,children:(0,B.jsx)(`path`,{d:`M162.077 85.2174C163.314 84.8304 164.088 83.979 164.474 83.205L180.715 46.2078L203.452 79.5672C204.225 80.3412 205.076 81.1926 206.313 81.1926C207.55 81.1926 208.324 80.8056 209.561 79.9542L238.717 51.858L246.837 91.6416C247.224 92.88 247.61 93.654 248.848 94.041C249.621 94.428 250.859 94.815 252.096 94.428L290.223 79.5672L282.489 119.351C282.489 120.589 282.489 121.75 283.262 122.602C284.036 123.376 284.886 123.84 286.124 124.227L326.648 124.614L303.911 158.36C303.138 159.134 303.138 160.373 303.524 161.611C303.911 162.85 304.762 163.624 305.535 164.011L342.811 180.11L308.783 202.633C308.01 203.407 307.159 204.259 307.159 205.42C307.159 206.581 307.546 207.432 308.397 208.206L336.779 237.154L296.642 245.203C295.404 245.59 294.631 245.977 294.244 247.216C293.857 247.99 293.857 249.228 294.244 250.466L309.247 288.238L269.11 280.575C267.873 280.575 266.712 280.575 265.862 281.349C265.088 282.123 264.624 282.974 264.238 284.135L263.464 324.77L230.21 302.247C229.437 301.473 228.199 301.473 226.962 301.86C225.724 302.247 224.951 303.098 224.564 303.872L208.324 340.87L185.587 307.123C184.814 306.349 183.963 305.498 182.726 305.498C181.488 305.498 180.715 305.885 179.477 306.736L150.322 334.832L142.202 295.049C141.815 293.81 141.428 293.036 140.191 292.649C139.417 292.262 138.18 292.262 136.943 292.649L98.8161 307.51L106.55 267.727C106.936 266.488 106.55 265.327 105.776 264.476C105.003 263.702 104.152 263.237 102.915 263.237L62.0041 262.463L84.7409 228.717C85.1276 227.943 85.5143 226.705 85.1276 225.466C84.7409 224.228 83.8902 223.454 83.1168 223.067L45.8409 206.968L79.8687 184.444C80.6421 183.67 81.4928 182.819 81.4928 181.658C81.4928 180.419 81.1061 179.645 80.2554 178.407L51.8731 149.459L92.0105 141.41C93.2479 141.023 94.0212 140.636 94.4079 139.397C94.7946 138.623 95.1813 137.385 94.4079 136.147L79.4047 98.3754L119.542 106.038C120.779 106.038 121.94 106.038 122.79 105.264C123.564 104.49 124.028 103.639 124.028 102.478L124.801 62.307L158.829 84.8304C159.602 85.2174 160.066 85.6044 160.84 85.6044C161.304 85.6044 161.69 85.2174 162.077 85.2174V85.2174ZM193.707 0C192.47 0.387 191.697 1.2384 191.31 2.0124L179.168 30.186L161.69 4.7988C160.453 3.1734 158.055 2.7864 156.431 4.0248C154.807 5.2632 154.421 7.6626 155.194 9.288L175.069 38.5452L158.829 75.9294L124.801 53.406L125.188 18.0342C125.188 16.0218 123.564 14.3964 121.553 14.0094C120.779 14.0094 119.929 14.3964 119.155 14.7834C118.382 15.5574 117.918 16.4088 117.918 17.5698L117.531 48.1428L91.5465 31.347C89.9224 30.1086 87.525 30.573 86.2876 32.5854C85.0502 34.2108 85.5143 36.6102 87.525 37.8486L117.531 57.5082L116.758 98.5302L76.2339 90.8676L63.1642 57.8952C62.3908 55.8828 60.3027 55.1088 58.292 55.8828C57.5186 56.2698 56.6679 56.6568 56.2813 57.5082C55.8946 58.2822 55.5079 59.5206 56.2813 60.759L67.6497 89.3196L37.2566 83.2824C35.2459 82.8954 33.2351 84.0564 32.7711 86.0688C32.3844 88.0812 33.5445 90.0936 35.6326 90.4806L70.8978 97.2918L85.9009 135.063L45.7636 143.113L21.0161 117.803C19.392 116.177 16.9946 116.177 15.7572 117.803C14.9838 118.577 14.5198 119.428 14.5198 120.202C14.5198 121.441 14.9065 122.215 15.7572 123.453L37.2566 145.512L6.86357 151.549C4.85283 151.936 3.61546 153.949 4.00214 155.961C4.38882 157.973 6.39955 159.212 8.48762 158.747L43.3662 151.549L72.1351 180.884L38.1073 203.794L5.16218 189.707C3.15144 188.933 1.14071 189.707 0.29001 191.72C-0.09667 192.494 -0.09667 193.345 0.29001 194.119C0.67669 195.358 1.52739 196.132 2.30075 196.519L30.6831 208.593L5.16218 225.853C2.30075 227.092 1.91407 229.104 3.15144 231.116C4.38882 232.742 6.78623 233.129 8.41029 232.355L38.03 212.695L75.7699 228.794L53.0331 262.928L17.3813 262.541C15.3705 262.541 13.7465 264.166 13.3598 266.179C13.3598 266.953 13.7465 267.804 14.1332 268.578C14.9065 268.965 16.1439 269.352 17.3813 269.352L48.161 269.739L31.1471 295.436C29.9097 297.061 30.3737 299.461 32.3844 300.699C34.0085 301.937 36.4059 301.473 37.6433 299.461L57.5186 269.739L98.8934 270.513L91.1598 310.684L57.9053 323.532C55.8946 324.306 55.0439 326.318 55.8946 328.331C56.6679 330.343 58.756 331.117 60.7667 330.343L89.5357 319.12L83.0395 349.693C82.6528 351.706 83.8129 353.718 85.9009 354.105C87.9117 354.492 89.9224 353.331 90.3864 351.319L97.2693 316.334L135.396 301.473L143.516 341.257L117.995 365.792C116.371 367.418 116.371 369.817 117.995 371.056C119.619 372.681 122.017 372.681 123.254 371.056L145.527 349.771L151.637 379.957C152.023 381.969 154.034 383.207 156.122 382.743C158.133 382.356 159.37 380.344 158.983 378.331L151.714 343.346L181.334 314.786L204.457 348.532L190.691 381.892C189.918 383.904 190.691 385.916 192.702 386.69C194.713 387.464 196.723 386.69 197.574 384.678L209.716 356.504L227.116 381.814C228.354 383.44 230.751 383.827 232.375 383.053C233.999 381.814 234.386 379.415 233.613 377.789L213.737 348.455L229.978 311.071L264.47 333.594L264.083 368.966C264.083 370.978 265.707 372.604 267.718 372.991C269.729 372.991 271.353 371.365 271.739 369.353L272.126 338.78L298.034 355.653C299.658 356.891 302.055 356.427 303.292 354.415C304.53 352.789 304.066 350.39 302.055 349.151L271.662 329.492L272.435 288.47L312.959 296.132L325.952 329.105C326.725 331.117 328.813 331.891 330.824 331.117C332.835 330.343 333.685 328.331 332.835 326.318L321.466 297.758L351.859 303.795C353.87 304.182 355.881 303.021 356.345 301.009C356.732 298.996 355.572 296.984 353.483 296.597L318.218 289.786L303.215 252.014L343.352 243.965L368.1 269.275C369.724 270.9 372.121 270.9 373.359 269.275C374.983 267.649 374.983 265.25 373.359 264.011L351.859 241.875L382.252 235.838C384.263 235.451 385.501 233.438 385.114 231.426C384.727 229.414 382.716 228.175 380.628 228.64L345.75 235.838L316.981 206.503L351.009 183.593L383.876 197.68C385.887 198.454 387.898 197.68 388.749 195.667C389.522 193.655 388.749 191.642 386.738 190.868L358.356 178.794L383.876 161.534C385.501 160.295 385.887 157.896 385.114 156.271C383.876 154.645 381.479 154.258 379.855 155.032L350.235 174.692L312.495 158.593L335.619 124.459L371.271 124.846C373.281 124.846 374.906 123.221 375.292 121.208C375.292 119.196 373.668 117.571 371.657 117.184L340.878 116.797L357.892 91.0998C359.129 89.4744 358.665 87.075 356.654 85.8366C355.03 84.5982 352.633 85.0626 351.395 87.075L331.52 116.797L290.145 116.023L297.879 75.852L331.133 63.0036C333.144 62.2296 333.995 60.2172 333.144 58.2048C332.757 57.4308 332.371 56.5794 331.52 56.1924C330.747 55.8054 329.509 55.4184 328.272 56.1924L299.503 67.4154L305.612 37.2294C305.999 35.217 304.839 33.2046 302.751 32.8176C300.74 32.4306 298.73 33.5916 298.266 35.604L291.383 70.5888L253.256 85.4496L245.136 45.279L270.657 21.1302C272.281 19.5048 272.281 17.1054 270.657 15.867C269.883 15.093 269.033 14.6286 268.259 14.6286C267.022 14.6286 266.248 15.0156 265.398 15.867L243.125 37.3842L237.015 7.1982C236.629 5.1858 234.618 3.9474 232.53 4.4118C230.442 4.8762 229.282 6.8112 229.669 8.8236L236.938 43.4214L207.318 71.982L184.35 38.1582L198.502 5.5728C199.275 3.5604 198.502 1.548 196.491 0.774C196.105 0 195.254 0 194.867 0H193.707V0Z`})})}function Ij({className:e}){return(0,B.jsxs)(`svg`,{width:`122`,height:`37`,viewBox:`0 0 203 52`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,"aria-hidden":`true`,"data-slug":`api-ifttt`,className:e,children:[(0,B.jsx)(`title`,{children:`IFTTT`}),(0,B.jsx)(`desc`,{children:`IFTTT`}),(0,B.jsx)(`path`,{d:`M109.374-.25H68.0791V15.3654H80.3558V52.1734H97.0968V15.3654H109.374V-.25ZM156.249-.25H114.954V15.3654H127.231V52.1734H143.972V15.3654H156.249V-.25ZM203.123-.25H161.829V15.3654H174.105V52.1734H190.846V15.3654H203.123V-.25ZM16.741-.25H0V52.1734H16.741V-.25ZM62.4997-.25H24.5535V52.1734H41.2945V37.6734H55.8033V20.9425H41.2945V15.3654H62.4997V-.25Z`,fill:`currentColor`})]})}function Lj({className:e}){return(0,B.jsxs)(`svg`,{width:`122`,height:`37`,viewBox:`0 0 203 52`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,"aria-hidden":`true`,"data-slug":`api-ifttt-mcp`,className:e,children:[(0,B.jsx)(`title`,{children:`IFTTT`}),(0,B.jsx)(`desc`,{children:`IFTTT`}),(0,B.jsx)(`path`,{d:`M109.374-.25H68.0791V15.3654H80.3558V52.1734H97.0968V15.3654H109.374V-.25ZM156.249-.25H114.954V15.3654H127.231V52.1734H143.972V15.3654H156.249V-.25ZM203.123-.25H161.829V15.3654H174.105V52.1734H190.846V15.3654H203.123V-.25ZM16.741-.25H0V52.1734H16.741V-.25ZM62.4997-.25H24.5535V52.1734H41.2945V37.6734H55.8033V20.9425H41.2945V15.3654H62.4997V-.25Z`,fill:`currentColor`})]})}function Rj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-airtable`,className:e,children:(0,B.jsx)(`path`,{d:`M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257zM23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596zM.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z`})})}function zj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-asana`,className:e,children:(0,B.jsx)(`path`,{d:`M18.78 12.653c-2.882 0-5.22 2.336-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.34 5.22-5.22-2.336-5.22-5.22-5.22zm-13.56 0c-2.88 0-5.22 2.337-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.338 5.22-5.22-2.336-5.22-5.22-5.22zm12-6.525c0 2.883-2.337 5.22-5.22 5.22-2.882 0-5.22-2.337-5.22-5.22 0-2.88 2.338-5.22 5.22-5.22 2.883 0 5.22 2.34 5.22 5.22z`})})}function Bj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`-0.2 -3.57 36.48 36.48`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-attio`,className:e,children:(0,B.jsx)(`path`,{d:`m35.705 20.45-3.014-4.778s-.011-.02-.018-.029l-.238-.375a2.44 2.44 0 0 0-2.072-1.142l-4.854-.015-.34.537-5.8 9.195-.32.509 2.43 3.846a2.43 2.43 0 0 0 2.079 1.142h6.803c.839 0 1.633-.438 2.077-1.14l.24-.38s.009-.01.01-.015l3.02-4.784a2.41 2.41 0 0 0 0-2.572zm-.92 2-3.018 4.784q-.021.032-.042.058a.41.41 0 0 1-.652-.06l-3.02-4.784a1.3 1.3 0 0 1-.154-.344 1.37 1.37 0 0 1 0-.737c.034-.118.085-.236.152-.342l3.014-4.78.007-.01a.38.38 0 0 1 .24-.172c.031-.009.058-.011.08-.015h.034c.07 0 .243.022.35.195l3.014 4.777a1.34 1.34 0 0 1 0 1.43zM26.786 8.89a2.42 2.42 0 0 0 0-2.572l-3.014-4.777-.251-.402A2.44 2.44 0 0 0 21.442 0H14.64c-.85 0-1.626.426-2.08 1.142L.378 20.452A2.4 2.4 0 0 0 0 21.738c0 .453.13.9.374 1.284l3.268 5.181a2.44 2.44 0 0 0 2.076 1.14h6.804c.854 0 1.63-.427 2.079-1.142l.248-.391v-.005s.005-.006.005-.008l2.429-3.847 7.198-11.409 2.3-3.649zm-.71-1.286c0 .247-.07.496-.212.715L13.93 27.237a.41.41 0 0 1-.35.19c-.07 0-.24-.02-.35-.19l-3.016-4.786a1.35 1.35 0 0 1 0-1.428L22.15 2.11a.41.41 0 0 1 .35-.193c.069 0 .242.02.352.195l3.013 4.777c.142.22.211.469.211.715`})})}function Vj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-bitbucket`,className:e,children:(0,B.jsx)(`path`,{d:`M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z`})})}function Hj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-box`,className:e,children:(0,B.jsx)(`path`,{d:`M.959 5.523c-.54 0-.959.42-.959.899v7.549a4.59 4.59 0 004.613 4.494 4.717 4.717 0 004.135-2.457c.779 1.438 2.337 2.457 4.074 2.457 2.577 0 4.674-2.037 4.674-4.613.06-2.457-2.037-4.495-4.613-4.495-1.738 0-3.295.959-4.074 2.397-.78-1.438-2.338-2.397-4.135-2.397-1.079 0-2.038.36-2.817.899V6.422a.92.92 0 00-.898-.899zM17.602 9.26a.95.95 0 00-.704.158c-.36.3-.479.899-.18 1.318l2.397 3.116-2.396 3.115c-.3.42-.24.96.18 1.26.419.3 1.016.298 1.316-.122l2.039-2.636 2.096 2.697c.3.36.899.419 1.318.12.36-.3.42-.84.121-1.259l-2.338-3.115 2.338-3.057c.3-.419.298-1.018-.121-1.318-.48-.3-1.019-.24-1.318.18l-2.096 2.576-2.04-2.695c-.149-.18-.373-.3-.612-.338zM4.613 11.154c1.558 0 2.817 1.26 2.817 2.758 0 1.558-1.259 2.756-2.817 2.756-1.558 0-2.816-1.198-2.816-2.756 0-1.498 1.258-2.758 2.816-2.758zm8.27 0c1.558 0 2.816 1.26 2.816 2.758-.06 1.558-1.318 2.756-2.816 2.756-1.558 0-2.817-1.198-2.817-2.756 0-1.498 1.259-2.758 2.817-2.758Z`})})}function Uj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-calendly`,className:e,children:(0,B.jsx)(`path`,{d:`M19.655 14.262c.281 0 .557.023.828.064 0 .005-.005.01-.005.014-.105.267-.234.534-.381.786l-1.219 2.106c-1.112 1.936-3.177 3.127-5.411 3.127h-2.432c-2.23 0-4.294-1.191-5.412-3.127l-1.218-2.106a6.251 6.251 0 0 1 0-6.252l1.218-2.106C6.736 4.832 8.8 3.641 11.035 3.641h2.432c2.23 0 4.294 1.191 5.411 3.127l1.219 2.106c.147.252.271.519.381.786 0 .004.005.009.005.014-.267.041-.543.064-.828.064-1.816 0-2.501-.607-3.291-1.306-.764-.676-1.711-1.517-3.44-1.517h-1.029c-1.251 0-2.387.455-3.2 1.278-.796.805-1.233 1.904-1.233 3.099v1.411c0 1.196.437 2.295 1.233 3.099.813.823 1.949 1.278 3.2 1.278h1.034c1.729 0 2.676-.841 3.439-1.517.791-.703 1.471-1.306 3.287-1.301Zm.005-3.237c.399 0 .794-.036 1.179-.11-.002-.004-.002-.01-.002-.014-.073-.414-.193-.823-.349-1.218.731-.12 1.407-.396 1.986-.819 0-.004-.005-.013-.005-.018-.331-1.085-.832-2.101-1.489-3.03-.649-.915-1.435-1.719-2.331-2.395-1.867-1.398-4.088-2.138-6.428-2.138-1.448 0-2.855.28-4.175.841-1.273.543-2.423 1.315-3.407 2.299S2.878 6.552 2.341 7.83c-.557 1.324-.842 2.726-.842 4.175 0 1.448.281 2.855.842 4.174.542 1.274 1.314 2.423 2.298 3.407s2.129 1.761 3.407 2.299c1.324.556 2.727.841 4.175.841 2.34 0 4.561-.74 6.428-2.137a10.815 10.815 0 0 0 2.331-2.396c.652-.929 1.158-1.949 1.489-3.03 0-.004.005-.014.005-.018-.579-.423-1.255-.699-1.986-.819.161-.395.276-.804.349-1.218.005-.009.005-.014.005-.023.869.166 1.692.506 2.404 1.035.685.505.552 1.075.446 1.416C22.184 20.437 17.619 24 12.221 24c-6.625 0-12-5.375-12-12s5.37-12 12-12c5.398 0 9.963 3.563 11.471 8.464.106.341.239.915-.446 1.421-.717.529-1.535.873-2.404 1.034.128.716.128 1.45 0 2.166-.387-.074-.782-.11-1.182-.11-4.184 0-3.968 2.823-6.736 2.823h-1.029c-1.899 0-3.15-1.357-3.15-3.095v-1.411c0-1.738 1.251-3.094 3.15-3.094h1.034c2.768 0 2.552 2.823 6.731 2.827Z`})})}function Wj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`2 2 12 12`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-capsule-crm`,className:e,children:[(0,B.jsx)(`path`,{d:`M4.5 2C3.84635 2.00041 3.21885 2.25679 2.75193 2.71423C2.285 3.17166 2.01579 3.79376 2.00195 4.44727H2V11.4258H2.00195C2.00093 11.4505 2.00028 11.4753 2 11.5C2.00005 12.163 2.26346 12.7989 2.73229 13.2677C3.20112 13.7365 3.83697 14 4.5 14C5.16303 14 5.79888 13.7365 6.26771 13.2677C6.73654 12.7989 6.99995 12.163 7 11.5C7.00037 11.4753 7.00037 11.4505 7 11.4258V4.44727C6.98616 3.79342 6.71667 3.17103 6.24933 2.71354C5.78199 2.25606 5.15399 1.9999 4.5 2V2ZM4.5 3C4.96413 3 5.40925 3.18437 5.73744 3.51256C6.06563 3.84075 6.25 4.28587 6.25 4.75C6.25 5.21413 6.06563 5.65925 5.73744 5.98744C5.40925 6.31563 4.96413 6.5 4.5 6.5C4.03587 6.5 3.59075 6.31563 3.26256 5.98744C2.93437 5.65925 2.75 5.21413 2.75 4.75C2.75 4.28587 2.93437 3.84075 3.26256 3.51256C3.59075 3.18437 4.03587 3 4.5 3V3ZM4.48242 9.51172C4.48828 9.51169 4.49414 9.51169 4.5 9.51172C4.73394 9.51153 4.96555 9.55829 5.18111 9.64923C5.39667 9.74017 5.59181 9.87345 5.75498 10.0412C5.91815 10.2089 6.04603 10.4076 6.13105 10.6257C6.21608 10.8437 6.25653 11.0766 6.25 11.3105C6.23728 11.7663 6.04729 12.1991 5.72045 12.5168C5.39361 12.8345 4.95573 13.0121 4.5 13.0117C4.04427 13.0121 3.60639 12.8345 3.27955 12.5168C2.95271 12.1991 2.76272 11.7663 2.75 11.3105C2.7435 11.0781 2.78339 10.8466 2.86733 10.6298C2.95127 10.4129 3.07758 10.2149 3.23888 10.0474C3.40018 9.87996 3.59323 9.74632 3.80674 9.65434C4.02026 9.56235 4.24996 9.51387 4.48242 9.51172V9.51172ZM3.75 10.5117V12.0117H5.25V10.5117H3.75Z`}),(0,B.jsx)(`path`,{d:`M14 5.75C14 5.33579 13.6642 5 13.2499 5C12.8358 5 12.5 5.33579 12.5 5.75C12.5 6.1642 12.8358 6.5 13.2499 6.5C13.6642 6.5 14 6.1642 14 5.75V5.75Z`}),(0,B.jsx)(`path`,{d:`M13.2582 2C13.6725 2 14.0083 2.33579 14.0083 2.75C14.0083 3.16421 13.6725 3.5 13.2582 3.5C12.8442 3.5 12.5083 3.16421 12.5083 2.75C12.5083 2.33579 12.8442 2 13.2582 2Z`}),(0,B.jsx)(`path`,{d:`M10.5 5.75C10.5 5.33579 10.1642 5 9.75004 5C9.3358 5 9 5.33579 9 5.75C9 6.1642 9.3358 6.5 9.75004 6.5C10.1642 6.5 10.5 6.1642 10.5 5.75Z`}),(0,B.jsx)(`path`,{d:`M9.75004 2C10.1642 2 10.5 2.33579 10.5 2.75C10.5 3.16421 10.1642 3.5 9.75004 3.5C9.3358 3.5 9 3.16421 9 2.75C9 2.33579 9.3358 2 9.75004 2Z`}),(0,B.jsx)(`path`,{d:`M14 8.99805L9 9V13.998H14V8.99805ZM10 10.0078H13V10.998H10V10.0078ZM10 11.998H13V12.998H10V11.998Z`})]})}function Gj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-clickup`,className:e,children:(0,B.jsx)(`path`,{d:`M2 18.439l3.69-2.828c1.961 2.56 4.044 3.739 6.363 3.739 2.307 0 4.33-1.166 6.203-3.704L22 18.405C19.298 22.065 15.941 24 12.053 24 8.178 24 4.788 22.078 2 18.439zM12.04 6.15l-6.568 5.66-3.036-3.52L12.055 0l9.543 8.296-3.05 3.509z`})})}function Kj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-crowdin`,className:e,children:(0,B.jsx)(`path`,{d:`M16.119 17.793a2.619 2.619 0 0 1-1.667-.562c-.546-.436-1.004-1.09-1.018-1.858-.008-.388.414-.388.414-.388l1.018-.008c.332.008.43.47.445.586.128 1.04.717 1.495 1.168 1.702.273.123.204.513-.362.528zm-5.695-5.287L8.5 12.252c-.867-.214-.844-.982-.807-1.247a5.119 5.119 0 0 1 .814-2.125c.545-.804 1.303-1.508 2.29-2.073 1.856-1.074 4.45-1.673 7.31-1.673 2.09 0 4.256.27 4.29.27.197.025.328.213.333.437a.377.377 0 0 1-.355.393l-.92-.01c-2.902 0-4.968.394-6.506 1.248-1.527.837-2.57 2.117-3.287 4.012-.076.163-.335 1.12-1.24 1.022zm2.533 7.823c-1.44 0-2.797-.622-3.825-1.746-.87-.96-1.397-1.931-1.493-3.164-.06-.813.3-1.094.788-1.044l1.988.218c.45.092.75.34.825.854.397 2.736 2.122 3.814 3.15 4.046.18.042.292.157.283.365a.412.412 0 0 1-.322.398c-.458.074-.936.073-1.394.073zm-4.101 2.418a14.216 14.216 0 0 1-2.307-.214c-1.202-.214-2.208-.582-3.072-1.13C1.41 20.095.163 17.786.014 15.048c-.037-.65-.11-1.89 1.427-1.797.638.033 1.653.343 2.368.548.887.247 1.314.933 1.314 1.608 0 3.858 3.494 6.408 5.02 6.408.654 0 .414.701.127.779-.502.136-1.15.153-1.413.153zM3.525 11.419c-.605-.109-1.194-.358-1.768-.5C-.018 10.479.284 8.688.45 8.196c1.617-4.757 6.746-6.35 10.887-6.773 3.898-.4 7.978-.092 11.778.967.31.083 1.269.327.718.891-.35.358-1.7-.016-2.073-.041-2.23-.167-4.434-.192-6.656.15-2.349.357-4.768 1.099-6.71 2.665-.938.758-1.76 1.723-2.313 2.866-.144.3-.256.6-.354.9-.11.327-.47 1.91-2.215 1.6zm9.94.917c.332-1.488 1.81-3.848 6.385-3.686 1.05.033.57.749.052.731-2.586-.09-3.815 1.578-4.457 3.27-.219.546-.68.626-1.271.53-.415-.074-.866-.123-.71-.846Z`})})}function qj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`-0.72 0.25 27.7 27.7`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-dialpad`,className:e,children:[(0,B.jsx)(`path`,{d:`M18.3259 27.6509C14.4999 27.6509 11.7516 24.5636 11.7516 20.3387C11.7516 16.1139 14.4999 13.0536 18.3259 13.0536C20.1042 13.0536 21.8017 13.9473 22.5561 15.1118V7.58294H25.6816V27.3801H22.5561V25.5656C21.8017 26.7301 20.1042 27.6509 18.3259 27.6509ZM18.8378 24.9156C20.8586 24.9156 22.3675 23.3449 22.5561 21.2054V19.4179C22.3675 17.2784 20.8586 15.7889 18.8378 15.7889C16.5745 15.7889 14.9579 17.6034 14.9579 20.3116C14.9579 23.0199 16.5745 24.9156 18.8378 24.9156Z`}),(0,B.jsx)(`path`,{d:`M14.7974 7.43848C14.9975 7.48324 15.0372 7.80998 14.8501 7.89512C11.2371 9.125 8.39325 12.2693 6.90835 16.4551C6.82708 16.6581 6.47057 16.6091 6.43706 16.3925C5.92911 13.1096 3.66093 10.4884 0.738931 9.78109C0.545298 9.73422 0.508733 9.43003 0.688975 9.34393C4.39869 7.81758 7.18675 4.68653 8.47503 0.686265C8.55549 0.477551 8.94421 0.530264 8.97728 0.751999C9.47675 4.10041 11.8058 6.76949 14.7974 7.43848Z`})]})}function Jj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-dropbox`,className:e,children:(0,B.jsx)(`path`,{d:`M6 1.807L0 5.629l6 3.822 6.001-3.822L6 1.807zM18 1.807l-6 3.822 6 3.822 6-3.822-6-3.822zM0 13.274l6 3.822 6.001-3.822L6 9.452l-6 3.822zM18 9.452l-6 3.822 6 3.822 6-3.822-6-3.822zM6 18.371l6.001 3.822 6-3.822-6-3.822L6 18.371z`})})}function Yj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 256 256`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-eventbrite`,className:e,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M128 0a128 128 0 1 0 0 256A128 128 0 1 0 128 0zM117.475 82.729c19.297-4.288 38.595 3.574 49.315 18.582L81.502 120.61c2.859-18.345 16.676-33.592 35.973-37.88m49.792 70.756c-6.671 9.768-16.915 16.677-28.589 19.297c-19.297 4.288-38.832-3.573-49.553-18.82l85.527-19.298l13.818-3.097l26.682-5.956c-.238-5.717-.953-11.435-2.144-16.914c-10.72-45.98-57.653-74.806-104.586-64.086s-76.235 56.462-65.276 102.68s57.653 74.806 104.585 64.085c27.636-6.194 49.077-24.776 60.036-48.361z`})})}function Xj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-figma`,className:e,children:(0,B.jsx)(`path`,{d:`M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z`})})}function Zj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-gitlab`,className:e,children:(0,B.jsx)(`path`,{d:`m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z`})})}function Qj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-hubspot`,className:e,children:(0,B.jsx)(`path`,{d:`M18.164 7.93V5.084a2.198 2.198 0 001.267-1.978v-.067A2.2 2.2 0 0017.238.845h-.067a2.2 2.2 0 00-2.193 2.193v.067a2.196 2.196 0 001.252 1.973l.013.006v2.852a6.22 6.22 0 00-2.969 1.31l.012-.01-7.828-6.095A2.497 2.497 0 104.3 4.656l-.012.006 7.697 5.991a6.176 6.176 0 00-1.038 3.446c0 1.343.425 2.588 1.147 3.607l-.013-.02-2.342 2.343a1.968 1.968 0 00-.58-.095h-.002a2.033 2.033 0 102.033 2.033 1.978 1.978 0 00-.1-.595l.005.014 2.317-2.317a6.247 6.247 0 104.782-11.134l-.036-.005zm-.964 9.378a3.206 3.206 0 113.215-3.207v.002a3.206 3.206 0 01-3.207 3.207z`})})}function $j({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-intercom`,className:e,children:(0,B.jsx)(`path`,{d:`M21 0H3C1.343 0 0 1.343 0 3v18c0 1.658 1.343 3 3 3h18c1.658 0 3-1.342 3-3V3c0-1.657-1.342-3-3-3zm-5.801 4.399c0-.44.36-.8.802-.8.44 0 .8.36.8.8v10.688c0 .442-.36.801-.8.801-.443 0-.802-.359-.802-.801V4.399zM11.2 3.994c0-.44.357-.799.8-.799s.8.359.8.799v11.602c0 .44-.357.8-.8.8s-.8-.36-.8-.8V3.994zm-4 .405c0-.44.359-.8.799-.8.443 0 .802.36.802.8v10.688c0 .442-.36.801-.802.801-.44 0-.799-.359-.799-.801V4.399zM3.199 6c0-.442.36-.8.802-.8.44 0 .799.358.799.8v7.195c0 .441-.359.8-.799.8-.443 0-.802-.36-.802-.8V6zM20.52 18.202c-.123.105-3.086 2.593-8.52 2.593-5.433 0-8.397-2.486-8.521-2.593-.335-.288-.375-.792-.086-1.128.285-.334.79-.375 1.125-.09.047.041 2.693 2.211 7.481 2.211 4.848 0 7.456-2.186 7.479-2.207.334-.289.839-.25 1.128.086.289.336.25.84-.086 1.128zm.281-5.007c0 .441-.36.8-.801.8-.441 0-.801-.36-.801-.8V6c0-.442.361-.8.801-.8.441 0 .801.357.801.8v7.195z`})})}function eM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-jira`,className:e,children:(0,B.jsx)(`path`,{d:`M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z`})})}function tM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-linear`,className:e,children:(0,B.jsx)(`path`,{d:`M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z`})})}function nM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-miro`,className:e,children:(0,B.jsx)(`path`,{d:`M17.392 0H13.9L17 4.808 10.444 0H6.949l3.102 6.3L3.494 0H0l3.05 8.131L0 24h3.494L10.05 6.985 6.949 24h3.494L17 5.494 13.899 24h3.493L24 3.672 17.392 0z`})})}function rM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-pagerduty`,className:e,children:(0,B.jsx)(`path`,{d:`M16.965 1.18C15.085.164 13.769 0 10.683 0H3.73v14.55h6.926c2.743 0 4.8-.164 6.61-1.37 1.975-1.303 3.004-3.484 3.004-6.007 0-2.716-1.262-4.896-3.305-5.994zm-5.5 10.326h-4.21V3.113l3.977-.027c3.62-.028 5.43 1.234 5.43 4.128 0 3.113-2.248 4.292-5.197 4.292zM3.73 17.61h3.525V24H3.73Z`})})}function iM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 -44 256 256`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-productboard`,className:e,children:[(0,B.jsx)(`path`,{d:`M85.33 89.61 L160.89 163.99 L9.77 163.99z`}),(0,B.jsx)(`path`,{d:`M9.77 4 L85.33 78.38 L160.89 4z`}),(0,B.jsx)(`path`,{d:`M91.04 84 L170.67 162.38 L250.29 84 L170.67 5.61z`})]})}function aM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-sentry`,className:e,children:(0,B.jsx)(`path`,{d:`M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z`})})}function oM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`-0.2 -0.92 32.67 32.67`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-shippo`,className:e,children:(0,B.jsx)(`path`,{d:`M29.818 6.826c.865.695 1.539 1.612 1.956 2.659.382.935.546 1.949.48 2.962a6 6 0 0 1-.866 2.704c-.529.849-1.253 1.55-2.107 2.041a13.94 13.94 0 0 1-1.476.678l-1.378.667c-1.049.546-2.055 1.157-3.049 1.792l-3.206 2.165c-.769.543-3.395 4.498-4.702 6.511 5.978-.259 11.036-4.646 12.318-10.682l.171-.075.281-.12.094-.04c.392-.154.773-.334 1.142-.539a5.3 5.3 0 0 0 .242-.152c-.902 7.171-6.509 12.729-13.482 13.366S2.798 26.943.693 20.047 1.406 5.67 7.471 2.06 21.216-.321 25.938 5.018h-.182a7.2 7.2 0 0 0-1.182.098 6.79 6.79 0 0 0-.833.188C19.913 1.743 14.426.814 9.698 2.927s-7.831 6.878-7.94 12.198c1.56-.511 4.269-1.435 4.584-1.769.671-.714 1.342-1.433 1.971-2.184a6.32 6.32 0 0 0 .533-.695c.05-.081.09-.167.122-.257a2.68 2.68 0 0 1 .413-.734 4.6 4.6 0 0 1-.213-.684 2.17 2.17 0 0 1-.182-.543c-.112-.48-.152-.976-.118-1.469.029-.4.147-1.014.651-1.014a.92.92 0 0 1 .456.21 8.44 8.44 0 0 1 1.411 1.196h.047l1.611-.188a10.37 10.37 0 0 1 2.015-.107c.294.028.584.091.864.188a9.1 9.1 0 0 0 .884.248 7.44 7.44 0 0 0 1.729.188 6.27 6.27 0 0 0 3.167-.939l.381-.172.565-.254a8.29 8.29 0 0 1 2-.633 6.69 6.69 0 0 1 2.011-.032 6.52 6.52 0 0 1 3.16 1.345zm-14.221 4.657c.474.3 1.076.277 1.527-.058.429-.344.591-.936.4-1.461-.201-.541-.699-.903-1.26-.915a1.3 1.3 0 0 0-1.2.749l.034-.099s-.028.08-.065.169a1.39 1.39 0 0 1 .031-.071l-.033.098c-.105.317-.06.192-.003.037l.003-.008c-.02.057-.036.115-.047.175a1.37 1.37 0 0 0 .613 1.384z`})})}function sM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-square`,className:e,children:(0,B.jsx)(`path`,{d:`M4.01 0A4.01 4.01 0 000 4.01v15.98c0 2.21 1.8 4 4.01 4.01h15.98C22.2 24 24 22.2 24 19.99V4A4.01 4.01 0 0019.99 0H4zm1.62 4.36h12.74c.7 0 1.26.57 1.26 1.27v12.74c0 .7-.56 1.27-1.26 1.27H5.63c-.7 0-1.26-.57-1.26-1.27V5.63a1.27 1.27 0 011.26-1.27zm3.83 4.35a.73.73 0 00-.73.73v5.09c0 .4.32.72.72.72h5.1a.73.73 0 00.73-.72V9.44a.73.73 0 00-.73-.73h-5.1Z`})})}function cM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-todoist`,className:e,children:(0,B.jsx)(`path`,{d:`M21 0H3C1.35 0 0 1.35 0 3v3.858s3.854 2.24 4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.608 8.136-4.675.279-.161.58-.107.748-.01.164.097.606.348.84.48.232.134.221.502.013.622l-9.712 5.59c-.346.2-.69.204-1.048.002C3.478 10.907.998 9.463 0 8.882v2.02l4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.609 8.136-4.676.279-.16.58-.106.748-.008.164.096.606.347.84.48.232.133.221.5.013.62-.208.121-9.288 5.346-9.712 5.59-.346.2-.69.205-1.048.002C3.478 14.951.998 13.506 0 12.926v2.02l4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.609 8.136-4.676.279-.16.58-.106.748-.009.164.097.606.348.84.48.232.133.221.502.013.622l-9.712 5.59c-.346.199-.69.204-1.048.001C3.478 18.994.998 17.55 0 16.97V21c0 1.65 1.35 3 3 3h18c1.65 0 3-1.35 3-3V3c0-1.65-1.35-3-3-3z`})})}function lM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-zoom`,className:e,children:(0,B.jsx)(`path`,{d:`M5.033 14.649H.743a.74.74 0 0 1-.686-.458.74.74 0 0 1 .16-.808L3.19 10.41H1.06A1.06 1.06 0 0 1 0 9.35h3.957c.301 0 .57.18.686.458a.74.74 0 0 1-.161.808L1.51 13.59h2.464c.585 0 1.06.475 1.06 1.06zM24 11.338c0-1.14-.927-2.066-2.066-2.066-.61 0-1.158.265-1.537.686a2.061 2.061 0 0 0-1.536-.686c-1.14 0-2.066.926-2.066 2.066v3.311a1.06 1.06 0 0 0 1.06-1.06v-2.251a1.004 1.004 0 0 1 2.013 0v2.251c0 .586.474 1.06 1.06 1.06v-3.311a1.004 1.004 0 0 1 2.012 0v2.251c0 .586.475 1.06 1.06 1.06zM16.265 12a2.728 2.728 0 1 1-5.457 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0zm-4.82 0a2.728 2.728 0 1 1-5.458 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0z`})})}function uM({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(ci,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(BA,{"data-slug":`aws-cost-explorer`,className:`h-5 w-5`})})}function dM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 155.343 151`,className:e,fill:`currentColor`,"data-slug":`aevatar`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M69.2512 59.3131H57.5239V13.1698H41.5711V45.3592H11.8454V59.3291H0V32.6979H28.6214V0.23637C28.9318 0.212513 29.2172 0.185583 29.4858 0.160233C30.0282 0.109045 30.5024 0.0642984 30.9766 0.0642984C34.8603 0.0578252 38.7436 0.0559593 42.6267 0.0540935C50.7461 0.0501922 58.8649 0.0462912 66.9856 0.000271626C68.7622 -0.0117334 69.4182 0.368426 69.4101 2.29323C69.3484 16.1531 69.3521 30.0111 69.3557 43.8699C69.3569 48.3252 69.3581 52.7806 69.3571 57.2362C69.3571 57.6665 69.3284 58.0973 69.2971 58.5651C69.2813 58.8018 69.2648 59.0483 69.2512 59.3091V59.3131Z`}),(0,B.jsx)(`path`,{d:`M93.9689 83.7439V83.7479L93.9648 83.7439H93.9689Z`}),(0,B.jsx)(`path`,{d:`M93.9689 83.7439V72.159H143.294V45.4719H113.565V13.1383H97.844V59.3056H85.9334V0.204906H126.368V32.5424H155.343V119.371H126.364V151H85.9742V93.9121H97.6851V138.259H113.479V107.01H143.249V83.7439H93.9689Z`}),(0,B.jsx)(`path`,{d:`M0.0570124 72.1751H60.6452V83.676H11.988V106.89H41.404V138.223H57.3894V93.8642H69.1859V150.944H28.8373V119.483H0.0570124V72.1751Z`}),(0,B.jsx)(`path`,{d:`M69.4222 83.6277H85.1917V68.3493H69.4222V83.6277Z`})]})}function fM({className:e}){return(0,B.jsx)(pM,{className:e,"data-slug":`cma`})}function pM(e){return(0,B.jsxs)(`svg`,{viewBox:`13 13 74 65`,"aria-hidden":`true`,"data-cma-glyph":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M71 71 A30 30 0 1 0 29 71`,fill:`none`,stroke:`currentColor`,strokeWidth:`11`,strokeLinecap:`round`}),(0,B.jsx)(`path`,{d:`M71 71 L61 59 L50 71 L39 59 L29 71`,fill:`none`,stroke:`currentColor`,strokeWidth:`10`,strokeLinecap:`round`,strokeLinejoin:`round`}),(0,B.jsx)(`rect`,{x:`30`,y:`38`,width:`17`,height:`12`,rx:`4`,fill:`currentColor`}),(0,B.jsx)(`rect`,{x:`53`,y:`38`,width:`17`,height:`12`,rx:`4`,fill:`currentColor`}),(0,B.jsx)(`path`,{d:`M46 44 L54 44`,fill:`none`,stroke:`currentColor`,strokeWidth:`4`})]})}function mM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 64 64`,className:e,fill:`currentColor`,"data-slug":`cmaeg`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M6 60V27L19 9L30 3.9V19L19 31V60Z`}),(0,B.jsx)(`path`,{d:`M58 60V27L45 9L34 3.9V19L45 31V60Z`}),(0,B.jsx)(`path`,{d:`M32 29L39 38V49L32 58L25 49V38Z`})]})}function hM({className:e}){return(0,B.jsx)(HA,{className:e,badge:(0,B.jsx)(bi,{strokeWidth:2.5}),children:(0,B.jsx)(pM,{"data-slug":`cma-trigger-github-observer-staging`,className:`h-full w-full`})})}function gM(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 747 444`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{pathLength:1,d:`M216.2,411.1 C229.3,406.1 244.4,393.4 256.2,377.5 C272.6,355.6 284.6,329.3 304.5,271.5 C306.6,265.4 308.6,260.1 309.0,259.7 C309.4,259.3 313.0,264.4 317.0,271.2 C340.1,310.0 355.7,323.1 377.4,321.8 C394.8,320.7 406.0,311.3 425.4,281.8 C431.8,272.0 437.2,264.0 437.4,264.0 C437.6,264.0 439.1,267.7 440.5,272.2 C457.3,323.1 472.1,355.1 490.1,378.6 C513.2,409.1 541.4,420.8 565.7,410.0 L570.1,408.1 L560.1,398.3 C535.3,373.8 520.7,340.3 495.0,249.0 C490.1,231.7 484.5,212.3 482.6,206.0 L479.1,194.5 L481.1,190.5 C486.7,179.6 503.7,161.4 515.4,153.6 C525.3,147.1 530.1,145.6 542.0,145.6 C554.6,145.5 563.6,148.3 575.5,156.1 C588.4,164.5 612.0,189.2 612.0,194.3 C612.0,195.4 609.5,200.4 606.4,205.6 C595.9,223.3 590.4,238.8 578.8,283.5 C566.8,330.3 560.7,347.8 551.0,363.6 C548.2,368.1 546.0,372.4 546.0,373.1 C546.0,376.8 560.0,393.8 567.4,399.2 C569.5,400.7 572.4,402.0 573.7,402.0 C581.3,402.0 598.4,373.4 608.5,344.1 C614.8,325.6 619.6,307.0 628.0,269.7 C635.5,236.3 637.2,229.5 638.2,229.5 C638.5,229.5 644.4,236.1 651.3,244.1 C670.0,265.8 679.6,273.9 691.6,278.1 C698.0,280.4 707.7,280.6 712.5,278.6 C719.4,275.7 718.4,270.4 706.5,246.0 C681.7,195.5 664.5,178.0 639.5,178.0 L631.0,178.0 L626.2,171.3 C606.4,143.4 580.8,119.2 563.6,112.1 C555.5,108.8 542.7,108.1 534.4,110.5 C516.6,115.5 494.8,136.6 478.7,164.2 C475.8,169.0 473.2,172.6 472.9,172.2 C472.5,171.8 468.5,161.6 464.0,149.5 C447.3,104.0 438.3,84.1 426.8,66.9 C420.3,57.2 410.7,45.6 406.7,42.6 C404.0,40.6 403.9,40.6 400.4,42.5 C393.3,46.3 374.0,69.2 374.0,73.7 C374.0,74.8 376.4,80.8 379.4,87.1 C391.3,112.3 402.5,144.6 420.1,204.5 L431.2,242.5 L429.6,247.7 C426.1,258.8 410.8,277.3 398.7,285.1 C378.1,298.4 356.3,293.8 335.2,271.6 C327.3,263.2 314.0,245.1 314.0,242.5 C314.0,241.6 316.7,230.8 320.1,218.7 C340.3,145.0 347.4,122.6 359.1,95.6 C369.1,72.3 373.4,65.7 387.8,51.0 C394.5,44.2 399.8,38.0 399.6,37.3 C398.5,34.5 382.9,30.7 372.5,30.7 C336.4,30.6 308.6,65.6 282.0,144.6 C275.1,165.0 273.5,168.9 272.7,168.0 C272.5,167.7 268.7,162.1 264.3,155.5 C242.8,123.4 223.6,109.0 202.1,109.0 C177.1,109.0 155.8,124.7 126.3,164.7 L115.8,179.0 L107.1,179.0 C92.9,179.0 83.6,182.7 74.8,191.7 C58.3,208.7 29.1,261.9 30.2,273.1 C30.6,277.9 36.0,280.5 45.2,280.4 C60.6,280.1 79.2,266.8 97.9,242.6 C109.5,227.6 108.1,227.2 112.9,246.7 C115.1,255.9 119.5,274.9 122.5,288.8 C132.7,335.0 141.0,359.7 153.9,381.2 C161.2,393.5 169.2,403.0 172.2,403.0 C177.3,403.0 185.3,395.4 195.0,381.3 L200.0,374.2 L194.1,362.8 C183.5,342.6 178.2,327.3 166.5,282.0 C156.1,242.1 149.3,223.0 138.9,204.6 C136.1,199.8 134.1,195.3 134.3,194.6 C135.3,192.2 153.2,172.2 158.7,167.4 C176.7,151.7 191.8,145.2 208.0,146.2 C225.4,147.3 238.8,155.9 256.7,177.4 C268.0,190.9 267.9,190.4 263.7,204.1 C261.7,210.4 255.2,232.6 249.1,253.5 C222.6,345.2 205.5,383.4 183.4,400.4 C176.2,405.9 175.7,407.3 180.0,409.5 C189.6,414.4 205.5,415.1 216.2,411.1 Z M70.0,238.5 C70.0,235.0 86.4,209.0 93.2,201.9 C98.4,196.4 99.6,198.5 95.4,205.8 C89.7,215.8 70.1,241.0 70.0,238.5 Z M671.8,232.8 C662.9,222.2 648.0,201.5 648.0,199.8 C648.0,198.3 650.7,199.0 653.4,201.1 C657.8,204.6 678.9,238.0 676.7,238.0 C676.4,238.0 674.2,235.6 671.8,232.8 Z`})})}function _M({slug:e,badge:t,className:n}){return(0,B.jsxs)(`span`,{className:`relative inline-flex h-full w-full shrink-0 items-center justify-center ${n??`h-5 w-5`}`,children:[(0,B.jsx)(`span`,{className:`relative inline-flex h-full w-[168%] shrink-0 items-center justify-center [&>svg]:!h-full [&>svg]:!w-full`,children:(0,B.jsx)(gM,{"data-slug":e,className:`h-full w-full`})}),(0,B.jsx)(UA,{badge:t})]})}function vM({className:e}){return(0,B.jsx)(_M,{className:e,badge:(0,B.jsx)(ii,{strokeWidth:2.5}),slug:`chrono-llm`})}function yM({className:e}){return(0,B.jsx)(_M,{className:e,badge:(0,B.jsx)(ii,{strokeWidth:2.5}),slug:`chrono-llm-public`})}function bM({className:e}){return(0,B.jsx)(_M,{className:e,badge:(0,B.jsx)(ai,{strokeWidth:2.5}),slug:`chrono-sandbox`})}function xM({className:e}){return(0,B.jsx)(_M,{className:e,badge:(0,B.jsx)(_i,{strokeWidth:2.5}),slug:`chrono-storage-service`})}function SM({className:e}){return(0,B.jsxs)(`span`,{className:`relative inline-flex shrink-0 ${e??`h-5 w-5`}`,children:[(0,B.jsx)(`svg`,{viewBox:`0 0 424 424`,className:`h-full w-full`,fill:`currentColor`,"data-slug":`llm-nyx`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{d:`M422.875 88.0461V335.824C422.875 383.898 383.903 422.87 335.829 422.87H214.328C213.008 422.87 211.938 421.799 211.938 420.48V191.899C211.938 189.461 208.72 188.587 207.487 190.69L72.0088 421.69C71.5786 422.421 70.7947 422.87 69.9486 422.87H3.39006C2.07075 422.87 1 421.799 1 420.48V3.39006C1 2.07075 2.07075 1 3.39006 1H139.237C140.556 1 141.627 2.07075 141.627 3.39006V231.971C141.627 234.409 144.844 235.284 146.077 233.18L281.56 2.18069C281.99 1.44933 282.774 1 283.62 1H335.824C383.898 1 422.87 39.9724 422.87 88.0461H422.875Z`})}),(0,B.jsx)(ii,{"aria-hidden":`true`,className:`absolute bottom-[5%] right-[5%] !h-[42%] !w-[42%] text-background`,strokeWidth:2.5})]})}function CM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 64 64`,className:e,fill:`currentColor`,"data-slug":`ornn-api`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M63.39,38.24 L59.46,37.46 A28,28 0 0,1 55.28,47.56 L58.61,49.78 A32,32 0 0,1 49.78,58.61 L47.56,55.28 A28,28 0 0,1 37.46,59.46 L38.24,63.39 A32,32 0 0,1 25.76,63.39 L26.54,59.46 A28,28 0 0,1 16.44,55.28 L14.22,58.61 A32,32 0 0,1 5.39,49.78 L8.72,47.56 A28,28 0 0,1 4.54,37.46 L0.61,38.24 A32,32 0 0,1 0.61,25.76 L4.54,26.54 A28,28 0 0,1 8.72,16.44 L5.39,14.22 A32,32 0 0,1 14.22,5.39 L16.44,8.72 A28,28 0 0,1 26.54,4.54 L25.76,0.61 A32,32 0 0,1 38.24,0.61 L37.46,4.54 A28,28 0 0,1 47.56,8.72 L49.78,5.39 A32,32 0 0,1 58.61,14.22 L55.28,16.44 A28,28 0 0,1 59.46,26.54 L63.39,25.76 A32,32 0 0,1 63.39,38.24 Z M46,32 A14,14 0 1,0 18,32 A14,14 0 1,0 46,32 Z`})})}function wM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 424 424`,className:e,fill:`currentColor`,"data-slug":`talos`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M1 1H336C384 1 423 40 423 88V141H71C32 141 1 110 1 71Z`}),(0,B.jsx)(`path`,{d:`M142 165H282V353C282 392 251 423 212 423H142Z`})]})}var TM={"llm-xai":KA,"llm-openai":WA,"llm-openai-codex":GA,"llm-anthropic":qA,"llm-google-ai":JA,"llm-mistral":YA,"llm-cohere":XA,"llm-deepseek":ZA,"llm-openclaw":QA,"llm-openrouter":$A,"api-firecrawl":ej,"api-twitter":tj,"api-google":nj,"api-google-workspace":rj,"api-google-calendar":ij,"api-google-drive":aj,"api-google-gmail":oj,"api-google-docs":sj,"api-google-sheets":cj,"api-google-slides":lj,"api-google-cloud":dj,"api-notion":uj,"api-github":fj,"api-github-pat":pj,"api-facebook":mj,"api-linkedin":hj,"api-discord":gj,"api-discord-bot":_j,"api-spotify":vj,"api-slack":yj,"api-slack-bot":bj,"api-microsoft":xj,"api-tiktok":Sj,"api-twitch":Cj,"api-reddit":wj,"api-lark":Tj,"api-lark-bot":Ej,"api-feishu":Dj,"api-feishu-bot":Oj,"api-telegram-bot":kj,"api-whatsapp-business":Aj,"api-supabase":jj,"api-elevenlabs":Mj,"api-telnyx":Nj,telnyx:Nj,"platform-telnyx":Nj,"api-twilio":Pj,"api-aurinko":Fj,aurinko:Fj,"api-ifttt":Ij,"api-ifttt-mcp":Lj,"api-airtable":Rj,"api-asana":zj,"api-attio":Bj,"api-bitbucket":Vj,"api-box":Hj,"api-calendly":Uj,"api-capsule-crm":Wj,"api-clickup":Gj,"api-crowdin":Kj,"api-dialpad":qj,"api-dropbox":Jj,"api-eventbrite":Yj,"api-figma":Xj,"api-gitlab":Zj,"api-hubspot":Qj,"api-intercom":$j,"api-jira":eM,"api-linear":tM,"api-miro":nM,"api-pagerduty":rM,"api-productboard":iM,"api-sentry":aM,"api-shippo":oM,"api-square":sM,"api-todoist":cM,"api-zoom":lM,"aws-cost-explorer":uM,aevatar:dM,cma:fM,cmaeg:mM,"cma-trigger-github-observer-staging":hM,"chrono-llm":vM,"chrono-llm-public":yM,"chrono-sandbox":bM,"chrono-storage-service":xM,"llm-nyx":SM,"ornn-api":CM,talos:wM};function EM({className:e}){return(0,B.jsx)(xi,{className:e,"aria-hidden":`true`,"data-fallback":`true`})}var DM={"2xs":`!h-3.5 !w-3.5`,xs:`!h-4 !w-4`,sm:`!h-5 !w-5`,md:`!h-6 !w-6`,lg:`!h-8 !w-8`,xl:`!h-9 !w-9`};function OM(e){if(e.length>2048||e.includes(`#`))return null;try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`||!t.hostname||t.username||t.password?null:t.href}catch{return null}}function kM({slug:e,iconUrl:t,size:n=`sm`,className:r}){let i=t?OM(t):null;return i?(0,B.jsx)(AM,{slug:e,iconUrl:i,size:n,className:r},i):!e&&!t?null:(0,B.jsx)(TM[e??`custom`]??EM,{className:Jr(DM[n],`shrink-0 text-muted-foreground`,r)})}function AM({slug:e,iconUrl:t,size:n,className:r}){let[i,a]=(0,z.useState)(!1);return i?(0,B.jsx)(kM,{slug:e??`custom`,size:n,className:r}):(0,B.jsx)(`img`,{src:t,alt:``,"aria-hidden":`true`,referrerPolicy:`no-referrer`,onError:()=>a(!0),className:Jr(DM[n],`shrink-0 object-contain`,r)})}var jM={"claude-code":`llm-anthropic`,codex:`llm-openai-codex`,openclaw:`llm-openclaw`},MM={"2xs":`!h-3.5 !w-3.5`,xs:`!h-4 !w-4`,sm:`!h-5 !w-5`,md:`!h-6 !w-6`,lg:`!h-8 !w-8`,xl:`!h-9 !w-9`};function NM(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,fillRule:`evenodd`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z`})})}function PM({platform:e,size:t=`xs`,className:n}){if(!e||e===`__none__`)return null;let r=jM[e];if(r)return(0,B.jsx)(kM,{slug:r,size:t,className:n});let i=Jr(MM[t],`shrink-0 text-muted-foreground`,n);return e===`cursor`?(0,B.jsx)(NM,{className:i}):(0,B.jsx)(ii,{className:i,"aria-hidden":`true`})}var FM=z.forwardRef(({className:e,type:t,...n},r)=>(0,B.jsx)(`input`,{type:t,className:Jr(`text-input flex h-8 w-full rounded-lg border border-input bg-transparent px-3 py-1.5 text-12 text-foreground transition-colors duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-text-tertiary focus-visible:outline-none focus-visible:border-input-focus aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive disabled:cursor-not-allowed disabled:opacity-50`,e),ref:r,...n}));FM.displayName=`Input`;function IM(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,LM(i,...t)]}function LM(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function RM(e){let t=z.useRef({value:e,previous:e});return z.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function zM(e){let t=BM(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(HM);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function BM(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=WM(n),i=UM(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var VM=Symbol(`radix.slottable`);function HM(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===VM}function UM(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function WM(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var GM=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=zM(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),KM=`Switch`,[qM,JM]=IM(KM),[YM,XM]=qM(KM),ZM=z.forwardRef((e,t)=>{let{__scopeSwitch:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c=`on`,onCheckedChange:l,form:u,...d}=e,[f,p]=z.useState(null),m=en(t,e=>p(e)),h=z.useRef(!1),g=f?u||!!f.closest(`form`):!0,[_,v]=wD({prop:i,defaultProp:a??!1,onChange:l,caller:KM});return(0,B.jsxs)(YM,{scope:n,checked:_,disabled:s,children:[(0,B.jsx)(GM.button,{type:`button`,role:`switch`,"aria-checked":_,"aria-required":o,"data-state":nN(_),"data-disabled":s?``:void 0,disabled:s,value:c,...d,ref:m,onClick:eC(e.onClick,e=>{v(e=>!e),g&&(h.current=e.isPropagationStopped(),h.current||e.stopPropagation())})}),g&&(0,B.jsx)(tN,{control:f,bubbles:!h.current,name:r,value:c,checked:_,required:o,disabled:s,form:u,style:{transform:`translateX(-100%)`}})]})});ZM.displayName=KM;var QM=`SwitchThumb`,$M=z.forwardRef((e,t)=>{let{__scopeSwitch:n,...r}=e,i=XM(QM,n);return(0,B.jsx)(GM.span,{"data-state":nN(i.checked),"data-disabled":i.disabled?``:void 0,...r,ref:t})});$M.displayName=QM;var eN=`SwitchBubbleInput`,tN=z.forwardRef(({__scopeSwitch:e,control:t,checked:n,bubbles:r=!0,...i},a)=>{let o=z.useRef(null),s=en(o,a),c=RM(n),l=NE(t);return z.useEffect(()=>{let e=o.current;if(!e)return;let t=window.HTMLInputElement.prototype,i=Object.getOwnPropertyDescriptor(t,`checked`).set;if(c!==n&&i){let t=new Event(`click`,{bubbles:r});i.call(e,n),e.dispatchEvent(t)}},[c,n,r]),(0,B.jsx)(`input`,{type:`checkbox`,"aria-hidden":!0,defaultChecked:n,...i,tabIndex:-1,ref:s,style:{...i.style,...l,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0}})});tN.displayName=eN;function nN(e){return e?`checked`:`unchecked`}var rN=ZM,iN=$M,aN=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(rN,{className:Jr(`peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-300 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted`,e),...t,ref:n,children:(0,B.jsx)(iN,{className:Jr(`pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=checked]:bg-white data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-muted-foreground`)})}));aN.displayName=rN.displayName;function oN(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,sN(i,...t)]}function sN(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function cN(e){let t=lN(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(fN);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function lN(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=mN(n),i=pN(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var uN=Symbol(`radix.slottable`);function dN(e){let t=({children:e})=>(0,B.jsx)(B.Fragment,{children:e});return t.displayName=`${e}.Slottable`,t.__radixId=uN,t}function fN(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===uN}function pN(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function mN(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var hN=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=cN(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function gN(e){let t=_N(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(yN);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function _N(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=xN(n),i=bN(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var vN=Symbol(`radix.slottable`);function yN(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===vN}function bN(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function xN(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var SN=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=gN(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),CN=Object.freeze({position:`absolute`,border:0,width:1,height:1,padding:0,margin:-1,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,whiteSpace:`nowrap`,wordWrap:`normal`}),wN=`VisuallyHidden`,TN=z.forwardRef((e,t)=>(0,B.jsx)(SN.span,{...e,ref:t,style:{...CN,...e.style}}));TN.displayName=wN;var EN=TN,[DN,ON]=oN(`Tooltip`,[IE]),kN=IE(),AN=`TooltipProvider`,jN=700,MN=`tooltip.open`,[NN,PN]=DN(AN),FN=e=>{let{__scopeTooltip:t,delayDuration:n=jN,skipDelayDuration:r=300,disableHoverableContent:i=!1,children:a}=e,o=z.useRef(!0),s=z.useRef(!1),c=z.useRef(0);return z.useEffect(()=>{let e=c.current;return()=>window.clearTimeout(e)},[]),(0,B.jsx)(NN,{scope:t,isOpenDelayedRef:o,delayDuration:n,onOpen:z.useCallback(()=>{window.clearTimeout(c.current),o.current=!1},[]),onClose:z.useCallback(()=>{window.clearTimeout(c.current),c.current=window.setTimeout(()=>o.current=!0,r)},[r]),isPointerInTransitRef:s,onPointerInTransitChange:z.useCallback(e=>{s.current=e},[]),disableHoverableContent:i,children:a})};FN.displayName=AN;var IN=`Tooltip`,[LN,RN]=DN(IN),zN=e=>{let{__scopeTooltip:t,children:n,open:r,defaultOpen:i,onOpenChange:a,disableHoverableContent:o,delayDuration:s}=e,c=PN(IN,e.__scopeTooltip),l=kN(t),[u,d]=z.useState(null),f=tw(),p=z.useRef(0),m=o??c.disableHoverableContent,h=s??c.delayDuration,g=z.useRef(!1),[_,v]=wD({prop:r,defaultProp:i??!1,onChange:e=>{e?(c.onOpen(),document.dispatchEvent(new CustomEvent(MN))):c.onClose(),a?.(e)},caller:IN}),y=z.useMemo(()=>_?g.current?`delayed-open`:`instant-open`:`closed`,[_]),b=z.useCallback(()=>{window.clearTimeout(p.current),p.current=0,g.current=!1,v(!0)},[v]),x=z.useCallback(()=>{window.clearTimeout(p.current),p.current=0,v(!1)},[v]),S=z.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>{g.current=!0,v(!0),p.current=0},h)},[h,v]);return z.useEffect(()=>()=>{p.current&&=(window.clearTimeout(p.current),0)},[]),(0,B.jsx)(QE,{...l,children:(0,B.jsx)(LN,{scope:t,contentId:f,open:_,stateAttribute:y,trigger:u,onTriggerChange:d,onTriggerEnter:z.useCallback(()=>{c.isOpenDelayedRef.current?S():b()},[c.isOpenDelayedRef,S,b]),onTriggerLeave:z.useCallback(()=>{m?x():(window.clearTimeout(p.current),p.current=0)},[x,m]),onOpen:b,onClose:x,disableHoverableContent:m,children:n})})};zN.displayName=IN;var BN=`TooltipTrigger`,VN=z.forwardRef((e,t)=>{let{__scopeTooltip:n,...r}=e,i=RN(BN,n),a=PN(BN,n),o=kN(n),s=en(t,z.useRef(null),i.onTriggerChange),c=z.useRef(!1),l=z.useRef(!1),u=z.useCallback(()=>c.current=!1,[]);return z.useEffect(()=>()=>document.removeEventListener(`pointerup`,u),[u]),(0,B.jsx)($E,{asChild:!0,...o,children:(0,B.jsx)(hN.button,{"aria-describedby":i.open?i.contentId:void 0,"data-state":i.stateAttribute,...r,ref:s,onPointerMove:eC(e.onPointerMove,e=>{e.pointerType!==`touch`&&!l.current&&!a.isPointerInTransitRef.current&&(i.onTriggerEnter(),l.current=!0)}),onPointerLeave:eC(e.onPointerLeave,()=>{i.onTriggerLeave(),l.current=!1}),onPointerDown:eC(e.onPointerDown,()=>{i.open&&i.onClose(),c.current=!0,document.addEventListener(`pointerup`,u,{once:!0})}),onFocus:eC(e.onFocus,()=>{c.current||i.onOpen()}),onBlur:eC(e.onBlur,i.onClose),onClick:eC(e.onClick,i.onClose)})})});VN.displayName=BN;var HN=`TooltipPortal`,[UN,WN]=DN(HN,{forceMount:void 0}),GN=e=>{let{__scopeTooltip:t,forceMount:n,children:r,container:i}=e,a=RN(HN,t);return(0,B.jsx)(UN,{scope:t,forceMount:n,children:(0,B.jsx)(fD,{present:n||a.open,children:(0,B.jsx)(uD,{asChild:!0,container:i,children:r})})})};GN.displayName=HN;var KN=`TooltipContent`,qN=z.forwardRef((e,t)=>{let n=WN(KN,e.__scopeTooltip),{forceMount:r=n.forceMount,side:i=`top`,...a}=e,o=RN(KN,e.__scopeTooltip);return(0,B.jsx)(fD,{present:r||o.open,children:o.disableHoverableContent?(0,B.jsx)(QN,{side:i,...a,ref:t}):(0,B.jsx)(JN,{side:i,...a,ref:t})})}),JN=z.forwardRef((e,t)=>{let n=RN(KN,e.__scopeTooltip),r=PN(KN,e.__scopeTooltip),i=z.useRef(null),a=en(t,i),[o,s]=z.useState(null),{trigger:c,onClose:l}=n,u=i.current,{onPointerInTransitChange:d}=r,f=z.useCallback(()=>{s(null),d(!1)},[d]),p=z.useCallback((e,t)=>{let n=e.currentTarget,r={x:e.clientX,y:e.clientY},i=nP(r,tP(r,n.getBoundingClientRect())),a=rP(t.getBoundingClientRect());s(aP([...i,...a])),d(!0)},[d]);return z.useEffect(()=>()=>f(),[f]),z.useEffect(()=>{if(c&&u){let e=e=>p(e,u),t=e=>p(e,c);return c.addEventListener(`pointerleave`,e),u.addEventListener(`pointerleave`,t),()=>{c.removeEventListener(`pointerleave`,e),u.removeEventListener(`pointerleave`,t)}}},[c,u,p,f]),z.useEffect(()=>{if(o){let e=e=>{let t=e.target,n={x:e.clientX,y:e.clientY},r=c?.contains(t)||u?.contains(t),i=!iP(n,o);r?f():i&&(f(),l())};return document.addEventListener(`pointermove`,e),()=>document.removeEventListener(`pointermove`,e)}},[c,u,o,l,f]),(0,B.jsx)(QN,{...e,ref:a})}),[YN,XN]=DN(IN,{isInside:!1}),ZN=dN(`TooltipContent`),QN=z.forwardRef((e,t)=>{let{__scopeTooltip:n,children:r,"aria-label":i,onEscapeKeyDown:a,onPointerDownOutside:o,...s}=e,c=RN(KN,n),l=kN(n),{onClose:u}=c;return z.useEffect(()=>(document.addEventListener(MN,u),()=>document.removeEventListener(MN,u)),[u]),z.useEffect(()=>{if(c.trigger){let e=e=>{e.target?.contains(c.trigger)&&u()};return window.addEventListener(`scroll`,e,{capture:!0}),()=>window.removeEventListener(`scroll`,e,{capture:!0})}},[c.trigger,u]),(0,B.jsx)(yC,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:a,onPointerDownOutside:o,onFocusOutside:e=>e.preventDefault(),onDismiss:u,children:(0,B.jsxs)(eD,{"data-state":c.stateAttribute,...l,...s,ref:t,style:{...s.style,"--radix-tooltip-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-tooltip-content-available-width":`var(--radix-popper-available-width)`,"--radix-tooltip-content-available-height":`var(--radix-popper-available-height)`,"--radix-tooltip-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-tooltip-trigger-height":`var(--radix-popper-anchor-height)`},children:[(0,B.jsx)(ZN,{children:r}),(0,B.jsx)(YN,{scope:n,isInside:!0,children:(0,B.jsx)(EN,{id:c.contentId,role:`tooltip`,children:i||r})})]})})});qN.displayName=KN;var $N=`TooltipArrow`,eP=z.forwardRef((e,t)=>{let{__scopeTooltip:n,...r}=e,i=kN(n);return XN($N,n).isInside?null:(0,B.jsx)(tD,{...i,...r,ref:t})});eP.displayName=$N;function tP(e,t){let n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),i=Math.abs(t.right-e.x),a=Math.abs(t.left-e.x);switch(Math.min(n,r,i,a)){case a:return`left`;case i:return`right`;case n:return`top`;case r:return`bottom`;default:throw Error(`unreachable`)}}function nP(e,t,n=5){let r=[];switch(t){case`top`:r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case`bottom`:r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case`left`:r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case`right`:r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function rP(e){let{top:t,right:n,bottom:r,left:i}=e;return[{x:i,y:t},{x:n,y:t},{x:n,y:r},{x:i,y:r}]}function iP(e,t){let{x:n,y:r}=e,i=!1;for(let e=0,a=t.length-1;er!=d>r&&n<(u-c)*(r-l)/(d-l)+c&&(i=!i)}return i}function aP(e){let t=e.slice();return t.sort((e,t)=>e.xt.x?1:e.yt.y)),oP(t)}function oP(e){if(e.length<=1)return e.slice();let t=[];for(let n=0;n=2;){let e=t[t.length-1],n=t[t.length-2];if((e.x-n.x)*(r.y-n.y)>=(e.y-n.y)*(r.x-n.x))t.pop();else break}t.push(r)}t.pop();let n=[];for(let t=e.length-1;t>=0;t--){let r=e[t];for(;n.length>=2;){let e=n[n.length-1],t=n[n.length-2];if((e.x-t.x)*(r.y-t.y)>=(e.y-t.y)*(r.x-t.x))n.pop();else break}n.push(r)}return n.pop(),t.length===1&&n.length===1&&t[0].x===n[0].x&&t[0].y===n[0].y?t:t.concat(n)}var sP=FN,cP=zN,lP=VN,uP=GN,dP=qN,fP=sP,pP=cP,mP=lP,hP=z.forwardRef(({className:e,sideOffset:t=4,style:n,...r},i)=>{let a=Math.max(1e3,Ok());return(0,B.jsx)(kk,{layer:a,children:(0,B.jsx)(uP,{children:(0,B.jsx)(dP,{ref:i,sideOffset:t,className:Jr(`z-[100] overflow-hidden rounded-[6px] bg-muted px-3 py-1.5 text-xs text-foreground shadow-lg shadow-primary/5`,`data-[state=delayed-open]:animate-in data-[state=instant-open]:animate-in`,`data-[state=closed]:animate-out`,`data-[state=delayed-open]:fade-in-0 data-[state=instant-open]:fade-in-0`,`data-[state=closed]:fade-out-0`,`data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:zoom-in-95`,`data-[state=closed]:zoom-out-95`,`data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,e),...r,style:{...n,zIndex:a}})})})});hP.displayName=dP.displayName;async function gP(e){try{await hb.post(`/cli-pairings/${encodeURIComponent(e)}/reserve-action`,{})}catch(e){if(e instanceof cb&&(e.status===409||e.status===404))throw Error(`This pairing was already completed or started in another tab. Close this tab and check your CLI — if the CLI didn't finish the flow, run the command again for a fresh pairing.`);let t=e instanceof Error?e.message:String(e);throw Error(`Couldn't reserve this pairing with NyxID (${t}). Try again; if the problem persists, cancel and re-run the CLI command.`)}}async function _P(e){try{await hb.post(`/cli-pairings/${encodeURIComponent(e)}/rewind-action`,{})}catch{}}async function vP(e,t){try{return await t()}catch(t){throw t instanceof cb&&t.status>=400&&t.status<500&&await _P(e),t}}var yP=/^[a-z0-9-]+$/,bP=$().min(1,`Node name is required`).max(64,`Node name must be 64 characters or fewer`).regex(yP,`Lowercase letters, digits, and hyphens only`);$().min(1,`Slug is required`).max(64,`Slug must be 64 characters or fewer`).regex(yP,`Lowercase letters, digits, and hyphens only`).refine(e=>!e.startsWith(`-`)&&!e.endsWith(`-`),{message:`Slug must not start or end with a hyphen`}).refine(e=>!e.includes(`--`),{message:`Slug must not contain consecutive hyphens`});var xP=$().min(1,`Name is required`).max(200,`Name must be 200 characters or fewer`);$().min(1,`Label is required`).max(200,`Label must be 200 characters or fewer`);var SP=[`claude-code`,`cursor`,`codex`,`openclaw`,`generic`];Mx([Hx(``),Bx(SP)]);var CP={slug:$().optional(),label:$().optional(),via_node:$().optional(),org_id:$().uuid().optional(),endpoint_url:$().optional(),custom:Sx().optional(),custom_slug:$().optional(),auth_method:$().optional(),auth_key_name:$().optional(),reconnect_key_id:$().optional(),scope_override:Ox($()).optional()};Ax(CP);function wP(e){if(!e||typeof e!=`object`||Array.isArray(e))return{};let t=e,n={};for(let e of Object.keys(CP)){if(!Object.prototype.hasOwnProperty.call(t,e))continue;let r=CP[e].safeParse(t[e]);r.success&&r.data!==void 0&&(n[e]=r.data)}return n}function TP(e,t){let n=e.safeParse(t);return n.success?null:n.error.issues[0]?.message??`Invalid value`}function EP({label:e,schema:t,value:n,onChange:r,onValidityChange:i,hint:a,placeholder:o,optional:s=!1,autoFocus:c,autoComplete:l=`off`,id:u}){let d=(0,z.useId)(),f=u??d,p=s&&n.length===0?null:TP(t,n);(0,z.useEffect)(()=>{i&&i(p===null)},[p,i]);let m=`${f}-hint`,h=`${f}-error`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:f,children:e}),(0,B.jsx)(FM,{id:f,value:n,onChange:e=>{r(e.target.value)},placeholder:o,autoFocus:c,autoComplete:l,"aria-invalid":p!=null,"aria-describedby":p?h:a?m:void 0,className:p==null?void 0:`border-destructive focus-visible:border-destructive`}),p?(0,B.jsx)(`p`,{id:h,className:`text-xs text-destructive`,children:p}):a?(0,B.jsx)(`p`,{id:m,className:`text-xs text-muted-foreground`,children:a}):null]})}var DP=new Set([`http:`,`https:`]),OP=/^\d{1,3}(\.\d{1,3}){3}$/,kP=/^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;function AP(e){let t;try{t=new URL(e)}catch{return!1}if(!DP.has(t.protocol))return!1;if(t.hostname.startsWith(`[`))return!0;let n=t.hostname.replace(/\.$/,``).toLowerCase();return n.length===0?!1:n===`localhost`||OP.test(n)?!0:kP.test(n)}var jP=[`read`,`write`,`admin`,`openid`,`profile`,`email`,`services:read`,`services:write`,`proxy`];Ax({name:$().min(1,`Name is required`).max(64,`Name must be at most 64 characters`).refine(e=>e.trim().length>0,`Name must not be blank`),scopes:Ox(Bx(jP)).min(1,`At least one scope is required`),expires_at:$().nullable().optional().refine(e=>{if(e==null||e===``)return!0;let t=/^\d{4}-\d{2}-\d{2}$/.test(e)?new Date(`${e}T23:59:59Z`):new Date(e);return Number.isNaN(t.getTime())?!1:t.getTime()>Date.now()},{message:`Expiry date must be in the future`}),description:$().nullable().optional(),allow_all_services:Sx().optional(),allow_auto_connected_services:Sx().optional(),allow_all_nodes:Sx().optional(),allowed_service_ids:Ox($()).optional(),allowed_node_ids:Ox($()).optional(),callback_url:$().refine(AP,`Must be a valid URL`).nullable().optional(),platform:$().nullable().optional(),rate_limit_per_second:vx().int().positive().max(4294967295).optional(),rate_limit_burst:vx().int().positive().max(4294967295).optional(),target_org_id:$().optional()});function MP({value:e,onChange:t,label:n=`Scopes`,hint:r=`Must match the backend's allowed scope set. Pick at least one.`}){function i(n){let r=new Set(e);r.has(n)?r.delete(n):r.add(n),t(r)}let a=e.size===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:n}),(0,B.jsx)(`div`,{role:`group`,"aria-label":`Scopes`,"aria-invalid":a,className:`flex flex-wrap gap-2 rounded-lg p-2 transition-colors duration-300 `+(a?`border border-destructive`:`border border-transparent`),children:jP.map(t=>(0,B.jsx)(NP,{scope:t,checked:e.has(t),onToggle:()=>{i(t)}},t))}),(0,B.jsx)(`p`,{className:a?`text-xs text-destructive`:`text-xs text-muted-foreground`,children:a?`At least one scope is required.`:r})]})}function NP({scope:e,checked:t,onToggle:n}){return(0,B.jsxs)(`label`,{className:`inline-flex cursor-pointer select-none items-center gap-1.5 rounded-full border px-3 py-1.5 text-12 transition-colors duration-300 `+(t?`border-primary bg-primary/15 text-foreground`:`border-border bg-transparent text-muted-foreground hover:border-border hover:bg-muted/40`),children:[(0,B.jsx)(`input`,{type:`checkbox`,className:`peer sr-only`,checked:t,onChange:n,value:e}),(0,B.jsx)(`span`,{className:`text-xs`,children:e})]})}function PP(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,FP(i,...t)]}function FP(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function IP(e){let t=LP(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(zP);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function LP(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=VP(n),i=BP(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var RP=Symbol(`radix.slottable`);function zP(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===RP}function BP(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function VP(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var HP=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=IP(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),UP=`Checkbox`,[WP,GP]=PP(UP),[KP,qP]=WP(UP);function JP(e){let{__scopeCheckbox:t,checked:n,children:r,defaultChecked:i,disabled:a,form:o,name:s,onCheckedChange:c,required:l,value:u=`on`,internal_do_not_use_render:d}=e,[f,p]=wD({prop:n,defaultProp:i??!1,onChange:c,caller:UP}),[m,h]=z.useState(null),[g,_]=z.useState(null),v=z.useRef(!1),y=m?!!o||!!m.closest(`form`):!0,b={checked:f,disabled:a,setChecked:p,control:m,setControl:h,name:s,form:o,value:u,hasConsumerStoppedPropagationRef:v,required:l,defaultChecked:rF(i)?!1:i,isFormControl:y,bubbleInput:g,setBubbleInput:_};return(0,B.jsx)(KP,{scope:t,...b,children:nF(d)?d(b):r})}var YP=`CheckboxTrigger`,XP=z.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:n,...r},i)=>{let{control:a,value:o,disabled:s,checked:c,required:l,setControl:u,setChecked:d,hasConsumerStoppedPropagationRef:f,isFormControl:p,bubbleInput:m}=qP(YP,e),h=en(i,u),g=z.useRef(c);return z.useEffect(()=>{let e=a?.form;if(e){let t=()=>d(g.current);return e.addEventListener(`reset`,t),()=>e.removeEventListener(`reset`,t)}},[a,d]),(0,B.jsx)(HP.button,{type:`button`,role:`checkbox`,"aria-checked":rF(c)?`mixed`:c,"aria-required":l,"data-state":iF(c),"data-disabled":s?``:void 0,disabled:s,value:o,...r,ref:h,onKeyDown:eC(t,e=>{e.key===`Enter`&&e.preventDefault()}),onClick:eC(n,e=>{d(e=>rF(e)?!0:!e),m&&p&&(f.current=e.isPropagationStopped(),f.current||e.stopPropagation())})})});XP.displayName=YP;var ZP=z.forwardRef((e,t)=>{let{__scopeCheckbox:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c,onCheckedChange:l,form:u,...d}=e;return(0,B.jsx)(JP,{__scopeCheckbox:n,checked:i,defaultChecked:a,disabled:s,required:o,onCheckedChange:l,name:r,form:u,value:c,internal_do_not_use_render:({isFormControl:e})=>(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(XP,{...d,ref:t,__scopeCheckbox:n}),e&&(0,B.jsx)(tF,{__scopeCheckbox:n})]})})});ZP.displayName=UP;var QP=`CheckboxIndicator`,$P=z.forwardRef((e,t)=>{let{__scopeCheckbox:n,forceMount:r,...i}=e,a=qP(QP,n);return(0,B.jsx)(fD,{present:r||rF(a.checked)||a.checked===!0,children:(0,B.jsx)(HP.span,{"data-state":iF(a.checked),"data-disabled":a.disabled?``:void 0,...i,ref:t,style:{pointerEvents:`none`,...e.style}})})});$P.displayName=QP;var eF=`CheckboxBubbleInput`,tF=z.forwardRef(({__scopeCheckbox:e,...t},n)=>{let{control:r,hasConsumerStoppedPropagationRef:i,checked:a,defaultChecked:o,required:s,disabled:c,name:l,value:u,form:d,bubbleInput:f,setBubbleInput:p}=qP(eF,e),m=en(n,p),h=RM(a),g=NE(r);z.useEffect(()=>{let e=f;if(!e)return;let t=window.HTMLInputElement.prototype,n=Object.getOwnPropertyDescriptor(t,`checked`).set,r=!i.current;if(h!==a&&n){let t=new Event(`click`,{bubbles:r});e.indeterminate=rF(a),n.call(e,rF(a)?!1:a),e.dispatchEvent(t)}},[f,h,a,i]);let _=z.useRef(rF(a)?!1:a);return(0,B.jsx)(HP.input,{type:`checkbox`,"aria-hidden":!0,defaultChecked:o??_.current,required:s,disabled:c,name:l,value:u,form:d,...t,tabIndex:-1,ref:m,style:{...t.style,...g,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0,transform:`translateX(-100%)`}})});tF.displayName=eF;function nF(e){return typeof e==`function`}function rF(e){return e===`indeterminate`}function iF(e){return rF(e)?`indeterminate`:e?`checked`:`unchecked`}var aF=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(ZP,{ref:n,className:Jr(`peer h-4 w-4 shrink-0 rounded-[4px] border border-muted-foreground/40 bg-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground`,e),...t,children:(0,B.jsx)($P,{className:Jr(`flex items-center justify-center text-current`),children:(0,B.jsx)(li,{className:`h-3 w-3`})})}));aF.displayName=ZP.displayName;function oF({services:e,selectedIds:t,allowAll:n=!1,onAllowAllChange:r,onToggle:i,orgOwned:a=!1,disabled:o=!1}){let s=(0,z.useId)();return a&&!e.some(e=>e.auto_connected)?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`This org-owned key cannot use platform services from your personal account.`}):(0,B.jsxs)(`section`,{"aria-label":`Auto-connected platform services`,className:`space-y-2 border-t border-border/50 pt-3`,children:[(0,B.jsx)(`p`,{className:`text-12 font-medium`,children:`Auto-connected platform services`}),(0,B.jsxs)(Vi,{className:`flex items-start gap-2 text-12`,children:[(0,B.jsx)(aF,{checked:n,disabled:o,onCheckedChange:e=>r(e===!0)}),`Allow all auto-connected platform services (includes ones added later)`]}),e.filter(e=>e.auto_connected).map(e=>{let r=n&&e.platform_grant_eligible!==!1;return(0,B.jsxs)(Vi,{htmlFor:`${s}-${e.id}`,className:`flex items-center gap-2 text-12 ${r?`text-muted-foreground`:``}`,children:[(0,B.jsx)(aF,{id:`${s}-${e.id}`,checked:r||t.includes(e.id),disabled:o||r,onCheckedChange:()=>i(e.id)}),e.label||e.name||e.slug||e.id,e.platform_grant_eligible===!1&&` (Organization; select individually)`]},e.id)})]})}function sF(){let e=lt({queryKey:[`keys`,`list`,$S(e=>e.user?.id)],queryFn:async()=>(await hb.get(`/keys`)).keys,staleTime:0,refetchOnMount:`always`});return{...e,data:e.isError?void 0:e.data}}function cF(e={}){return lt({queryKey:[`nodes`],queryFn:async()=>(await hb.get(`/nodes`)).nodes,refetchInterval:e.pollIntervalMs&&e.pollIntervalMs>0?e.pollIntervalMs:void 0})}function lF({value:e,onChange:t,ownerId:n}){let r=sF(),i=cF();function a(n){let r=new Set(e.selectedServiceIds);r.has(n)?r.delete(n):r.add(n),t({...e,selectedServiceIds:r})}function o(n){let r=new Set(e.selectedNodeIds);r.has(n)?r.delete(n):r.add(n),t({...e,selectedNodeIds:r})}return(0,B.jsxs)(`section`,{"aria-labelledby":`access-scope-title`,className:`flex flex-col gap-4 rounded-lg border border-border bg-muted/30 p-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{id:`access-scope-title`,className:`text-13 font-semibold`,children:`Access Scope`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Restrict which services and nodes this key can access via proxy.`})]}),(0,B.jsx)(uF,{label:`Services`,icon:(0,B.jsx)(dF,{}),allowAll:e.allowAllServices,onAllowAllChange:n=>{t({...e,allowAllServices:n})},listLabel:`Select allowed services:`,loading:r.isLoading,items:r.data?.filter(e=>e.is_active&&!e.auto_connected&&(!n||e.credential_source?.type===`org`&&e.credential_source.org_id===n)&&(e.credential_source?.type!==`org`||e.credential_source.allowed)).map(e=>({id:e.id,primary:e.label,secondary:e.slug,iconSlug:e.catalog_service_slug}))??[],selectedIds:e.selectedServiceIds,onToggle:a}),!e.allowAllServices&&(0,B.jsx)(oF,{services:(r.data??[]).filter(e=>e.is_active&&(n?e.credential_source?.type===`org`&&e.credential_source.org_id===n:e.credential_source?.type!==`org`)),selectedIds:[...e.selectedServiceIds],allowAll:e.allowAutoConnectedServices,onAllowAllChange:n=>t({...e,allowAutoConnectedServices:n}),onToggle:a,orgOwned:!!n}),(0,B.jsx)(uF,{label:`Nodes`,icon:(0,B.jsx)(fF,{}),allowAll:e.allowAllNodes,onAllowAllChange:n=>{t({...e,allowAllNodes:n})},listLabel:`Select allowed nodes:`,loading:i.isLoading,items:i.data?.filter(e=>!n||e.owner.id===n).map(e=>({id:e.id,primary:e.name,secondary:e.status}))??[],selectedIds:e.selectedNodeIds,onToggle:o})]})}function uF({label:e,icon:t,allowAll:n,onAllowAllChange:r,listLabel:i,loading:a,items:o,selectedIds:s,onToggle:c}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5 text-12 font-medium`,children:[(0,B.jsx)(`span`,{className:`text-muted-foreground`,children:t}),(0,B.jsx)(`span`,{children:e})]}),(0,B.jsxs)(Vi,{className:`flex cursor-pointer items-center gap-2 text-12`,children:[(0,B.jsx)(aF,{checked:n,onCheckedChange:e=>{r(e===!0)}}),(0,B.jsxs)(`span`,{children:[`Allow all `,e.toLowerCase()]})]}),n?null:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5 rounded-lg border border-border bg-background/40 p-3`,children:[(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i}),a?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Loading…`}):o.length===0?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`None available. Add one first, then come back.`}):(0,B.jsx)(`div`,{className:`flex flex-col gap-1`,role:`list`,children:o.map(e=>(0,B.jsxs)(Vi,{className:`flex cursor-pointer items-center gap-2 text-12`,children:[(0,B.jsx)(aF,{checked:s.has(e.id),onCheckedChange:()=>{c(e.id)}}),(0,B.jsx)(kM,{slug:e.iconSlug,size:`2xs`}),(0,B.jsxs)(`span`,{className:`truncate`,children:[e.primary,e.secondary?(0,B.jsxs)(`span`,{className:`ml-1.5 text-xs text-muted-foreground`,children:[`(`,e.secondary,`)`]}):null]})]},e.id))})]})]})}function dF(){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{d:`M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z`})})}function fF(){return(0,B.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,B.jsx)(`rect`,{x:`2`,y:`3`,width:`20`,height:`7`,rx:`1.5`}),(0,B.jsx)(`rect`,{x:`2`,y:`14`,width:`20`,height:`7`,rx:`1.5`}),(0,B.jsx)(`line`,{x1:`6`,y1:`6.5`,x2:`6.01`,y2:`6.5`}),(0,B.jsx)(`line`,{x1:`6`,y1:`17.5`,x2:`6.01`,y2:`17.5`})]})}var pF=[`orgs`],mF={all:pF,list:()=>[...pF,`list`],detail:e=>[...pF,`detail`,e]};function hF(){return lt({queryKey:mF.list(),queryFn:async()=>(await hb.get(`/orgs`)).orgs})}function gF(e,[t,n]){return Math.min(n,Math.max(t,e))}function _F(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,vF(i,...t)]}function vF(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function yF(e){let t=bF(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(SF);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function bF(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=wF(n),i=CF(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var xF=Symbol(`radix.slottable`);function SF(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===xF}function CF(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function wF(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function TF(e){let t=e+`CollectionProvider`,[n,r]=_F(t),[i,a]=n(t,{collectionRef:{current:null},itemMap:new Map}),o=e=>{let{scope:t,children:n}=e,r=z.useRef(null),a=z.useRef(new Map).current;return(0,B.jsx)(i,{scope:t,itemMap:a,collectionRef:r,children:n})};o.displayName=t;let s=e+`CollectionSlot`,c=yF(s),l=z.forwardRef((e,t)=>{let{scope:n,children:r}=e;return(0,B.jsx)(c,{ref:en(t,a(s,n).collectionRef),children:r})});l.displayName=s;let u=e+`CollectionItemSlot`,d=`data-radix-collection-item`,f=yF(u),p=z.forwardRef((e,t)=>{let{scope:n,children:r,...i}=e,o=z.useRef(null),s=en(t,o),c=a(u,n);return z.useEffect(()=>(c.itemMap.set(o,{ref:o,...i}),()=>void c.itemMap.delete(o))),(0,B.jsx)(f,{[d]:``,ref:s,children:r})});p.displayName=u;function m(t){let n=a(e+`CollectionConsumer`,t);return z.useCallback(()=>{let e=n.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll(`[${d}]`));return Array.from(n.itemMap.values()).sort((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current))},[n.collectionRef,n.itemMap])}return[{Provider:o,Slot:l,ItemSlot:p},m,r]}function EF(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,DF(i,...t)]}function DF(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}var OF=z.createContext(void 0);function kF(e){let t=z.useContext(OF);return e||t||`ltr`}function AF(e){let t=jF(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(NF);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function jF(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=FF(n),i=PF(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var MF=Symbol(`radix.slottable`);function NF(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===MF}function PF(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function FF(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var IF=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=AF(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),LF=[` `,`Enter`,`ArrowUp`,`ArrowDown`],RF=[` `,`Enter`],zF=`Select`,[BF,VF,HF]=TF(zF),[UF,WF]=EF(zF,[HF,IE]),GF=IE(),[KF,qF]=UF(zF),[JF,YF]=UF(zF),XF=e=>{let{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:a,value:o,defaultValue:s,onValueChange:c,dir:l,name:u,autoComplete:d,disabled:f,required:p,form:m}=e,h=GF(t),[g,_]=z.useState(null),[v,y]=z.useState(null),[b,x]=z.useState(!1),S=kF(l),[C,w]=wD({prop:r,defaultProp:i??!1,onChange:a,caller:zF}),[T,E]=wD({prop:o,defaultProp:s,onChange:c,caller:zF}),ee=z.useRef(null),D=g?m||!!g.closest(`form`):!0,[O,k]=z.useState(new Set),A=Array.from(O).map(e=>e.props.value).join(`;`);return(0,B.jsx)(QE,{...h,children:(0,B.jsxs)(KF,{required:p,scope:t,trigger:g,onTriggerChange:_,valueNode:v,onValueNodeChange:y,valueNodeHasChildren:b,onValueNodeHasChildrenChange:x,contentId:tw(),value:T,onValueChange:E,open:C,onOpenChange:w,dir:S,triggerPointerDownPosRef:ee,disabled:f,children:[(0,B.jsx)(BF.Provider,{scope:t,children:(0,B.jsx)(JF,{scope:e.__scopeSelect,onNativeOptionAdd:z.useCallback(e=>{k(t=>new Set(t).add(e))},[]),onNativeOptionRemove:z.useCallback(e=>{k(t=>{let n=new Set(t);return n.delete(e),n})},[]),children:n})}),D?(0,B.jsxs)(GI,{"aria-hidden":!0,required:p,tabIndex:-1,name:u,autoComplete:d,value:T,onChange:e=>E(e.target.value),disabled:f,form:m,children:[T===void 0?(0,B.jsx)(`option`,{value:``}):null,Array.from(O)]},A):null]})})};XF.displayName=zF;var ZF=`SelectTrigger`,QF=z.forwardRef((e,t)=>{let{__scopeSelect:n,disabled:r=!1,...i}=e,a=GF(n),o=qF(ZF,n),s=o.disabled||r,c=en(t,o.onTriggerChange),l=VF(n),u=z.useRef(`touch`),[d,f,p]=qI(e=>{let t=l().filter(e=>!e.disabled),n=JI(t,e,t.find(e=>e.value===o.value));n!==void 0&&o.onValueChange(n.value)}),m=e=>{s||(o.onOpenChange(!0),p()),e&&(o.triggerPointerDownPosRef.current={x:Math.round(e.pageX),y:Math.round(e.pageY)})};return(0,B.jsx)($E,{asChild:!0,...a,children:(0,B.jsx)(IF.button,{type:`button`,role:`combobox`,"aria-controls":o.contentId,"aria-expanded":o.open,"aria-required":o.required,"aria-autocomplete":`none`,dir:o.dir,"data-state":o.open?`open`:`closed`,disabled:s,"data-disabled":s?``:void 0,"data-placeholder":KI(o.value)?``:void 0,...i,ref:c,onClick:eC(i.onClick,e=>{e.currentTarget.focus(),u.current!==`mouse`&&m(e)}),onPointerDown:eC(i.onPointerDown,e=>{u.current=e.pointerType;let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),e.button===0&&e.ctrlKey===!1&&e.pointerType===`mouse`&&(m(e),e.preventDefault())}),onKeyDown:eC(i.onKeyDown,e=>{let t=d.current!==``;!(e.ctrlKey||e.altKey||e.metaKey)&&e.key.length===1&&f(e.key),!(t&&e.key===` `)&&LF.includes(e.key)&&(m(),e.preventDefault())})})})});QF.displayName=ZF;var $F=`SelectValue`,eI=z.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,children:a,placeholder:o=``,...s}=e,c=qF($F,n),{onValueNodeHasChildrenChange:l}=c,u=a!==void 0,d=en(t,c.onValueNodeChange);return QC(()=>{l(u)},[l,u]),(0,B.jsx)(IF.span,{...s,ref:d,style:{pointerEvents:`none`},children:KI(c.value)?(0,B.jsx)(B.Fragment,{children:o}):a})});eI.displayName=$F;var tI=`SelectIcon`,nI=z.forwardRef((e,t)=>{let{__scopeSelect:n,children:r,...i}=e;return(0,B.jsx)(IF.span,{"aria-hidden":!0,...i,ref:t,children:r||`▼`})});nI.displayName=tI;var rI=`SelectPortal`,iI=e=>(0,B.jsx)(uD,{asChild:!0,...e});iI.displayName=rI;var aI=`SelectContent`,oI=z.forwardRef((e,t)=>{let n=qF(aI,e.__scopeSelect),[r,i]=z.useState();if(QC(()=>{i(new DocumentFragment)},[]),!n.open){let t=r;return t?Fi.createPortal((0,B.jsx)(cI,{scope:e.__scopeSelect,children:(0,B.jsx)(BF.Slot,{scope:e.__scopeSelect,children:(0,B.jsx)(`div`,{children:e.children})})}),t):null}return(0,B.jsx)(fI,{...e,ref:t})});oI.displayName=aI;var sI=10,[cI,lI]=UF(aI),uI=`SelectContentImpl`,dI=AF(`SelectContent.RemoveScroll`),fI=z.forwardRef((e,t)=>{let{__scopeSelect:n,position:r=`item-aligned`,onCloseAutoFocus:i,onEscapeKeyDown:a,onPointerDownOutside:o,side:s,sideOffset:c,align:l,alignOffset:u,arrowPadding:d,collisionBoundary:f,collisionPadding:p,sticky:m,hideWhenDetached:h,avoidCollisions:g,..._}=e,v=qF(aI,n),[y,b]=z.useState(null),[x,S]=z.useState(null),C=en(t,e=>b(e)),[w,T]=z.useState(null),[E,ee]=z.useState(null),D=VF(n),[O,k]=z.useState(!1),A=z.useRef(!1);z.useEffect(()=>{if(y)return FD(y)},[y]),DC();let j=z.useCallback(e=>{let[t,...n]=D().map(e=>e.ref.current),[r]=n.slice(-1),i=document.activeElement;for(let n of e)if(n===i||(n?.scrollIntoView({block:`nearest`}),n===t&&x&&(x.scrollTop=0),n===r&&x&&(x.scrollTop=x.scrollHeight),n?.focus(),document.activeElement!==i))return},[D,x]),M=z.useCallback(()=>j([w,y]),[j,w,y]);z.useEffect(()=>{O&&M()},[O,M]);let{onOpenChange:N,triggerPointerDownPosRef:P}=v;z.useEffect(()=>{if(y){let e={x:0,y:0},t=t=>{e={x:Math.abs(Math.round(t.pageX)-(P.current?.x??0)),y:Math.abs(Math.round(t.pageY)-(P.current?.y??0))}},n=n=>{e.x<=10&&e.y<=10?n.preventDefault():y.contains(n.target)||N(!1),document.removeEventListener(`pointermove`,t),P.current=null};return P.current!==null&&(document.addEventListener(`pointermove`,t),document.addEventListener(`pointerup`,n,{capture:!0,once:!0})),()=>{document.removeEventListener(`pointermove`,t),document.removeEventListener(`pointerup`,n,{capture:!0})}}},[y,N,P]),z.useEffect(()=>{let e=()=>N(!1);return window.addEventListener(`blur`,e),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`blur`,e),window.removeEventListener(`resize`,e)}},[N]);let[F,I]=qI(e=>{let t=D().filter(e=>!e.disabled),n=JI(t,e,t.find(e=>e.ref.current===document.activeElement));n&&setTimeout(()=>n.ref.current.focus())}),te=z.useCallback((e,t,n)=>{let r=!A.current&&!n;(v.value!==void 0&&v.value===t||r)&&(T(e),r&&(A.current=!0))},[v.value]),ne=z.useCallback(()=>y?.focus(),[y]),re=z.useCallback((e,t,n)=>{let r=!A.current&&!n;(v.value!==void 0&&v.value===t||r)&&ee(e)},[v.value]),L=r===`popper`?gI:mI,R=L===gI?{side:s,sideOffset:c,align:l,alignOffset:u,arrowPadding:d,collisionBoundary:f,collisionPadding:p,sticky:m,hideWhenDetached:h,avoidCollisions:g}:{};return(0,B.jsx)(cI,{scope:n,content:y,viewport:x,onViewportChange:S,itemRefCallback:te,selectedItem:w,onItemLeave:ne,itemTextRefCallback:re,focusSelectedItem:M,selectedItemText:E,position:r,isPositioned:O,searchRef:F,children:(0,B.jsx)(WO,{as:dI,allowPinchZoom:!0,children:(0,B.jsx)(BC,{asChild:!0,trapped:v.open,onMountAutoFocus:e=>{e.preventDefault()},onUnmountAutoFocus:eC(i,e=>{v.trigger?.focus({preventScroll:!0}),e.preventDefault()}),children:(0,B.jsx)(yC,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:a,onPointerDownOutside:o,onFocusOutside:e=>e.preventDefault(),onDismiss:()=>v.onOpenChange(!1),children:(0,B.jsx)(L,{role:`listbox`,id:v.contentId,"data-state":v.open?`open`:`closed`,dir:v.dir,onContextMenu:e=>e.preventDefault(),..._,...R,onPlaced:()=>k(!0),ref:C,style:{display:`flex`,flexDirection:`column`,outline:`none`,..._.style},onKeyDown:eC(_.onKeyDown,e=>{let t=e.ctrlKey||e.altKey||e.metaKey;if(e.key===`Tab`&&e.preventDefault(),!t&&e.key.length===1&&I(e.key),[`ArrowUp`,`ArrowDown`,`Home`,`End`].includes(e.key)){let t=D().filter(e=>!e.disabled).map(e=>e.ref.current);if([`ArrowUp`,`End`].includes(e.key)&&(t=t.slice().reverse()),[`ArrowUp`,`ArrowDown`].includes(e.key)){let n=e.target,r=t.indexOf(n);t=t.slice(r+1)}setTimeout(()=>j(t)),e.preventDefault()}})})})})})})});fI.displayName=uI;var pI=`SelectItemAlignedPosition`,mI=z.forwardRef((e,t)=>{let{__scopeSelect:n,onPlaced:r,...i}=e,a=qF(aI,n),o=lI(aI,n),[s,c]=z.useState(null),[l,u]=z.useState(null),d=en(t,e=>u(e)),f=VF(n),p=z.useRef(!1),m=z.useRef(!0),{viewport:h,selectedItem:g,selectedItemText:_,focusSelectedItem:v}=o,y=z.useCallback(()=>{if(a.trigger&&a.valueNode&&s&&l&&h&&g&&_){let e=a.trigger.getBoundingClientRect(),t=l.getBoundingClientRect(),n=a.valueNode.getBoundingClientRect(),i=_.getBoundingClientRect();if(a.dir!==`rtl`){let r=i.left-t.left,a=n.left-r,o=e.left-a,c=e.width+o,l=Math.max(c,t.width),u=window.innerWidth-sI,d=gF(a,[sI,Math.max(sI,u-l)]);s.style.minWidth=c+`px`,s.style.left=d+`px`}else{let r=t.right-i.right,a=window.innerWidth-n.right-r,o=window.innerWidth-e.right-a,c=e.width+o,l=Math.max(c,t.width),u=window.innerWidth-sI,d=gF(a,[sI,Math.max(sI,u-l)]);s.style.minWidth=c+`px`,s.style.right=d+`px`}let o=f(),c=window.innerHeight-sI*2,u=h.scrollHeight,d=window.getComputedStyle(l),m=parseInt(d.borderTopWidth,10),v=parseInt(d.paddingTop,10),y=parseInt(d.borderBottomWidth,10),b=parseInt(d.paddingBottom,10),x=m+v+u+b+y,S=Math.min(g.offsetHeight*5,x),C=window.getComputedStyle(h),w=parseInt(C.paddingTop,10),T=parseInt(C.paddingBottom,10),E=e.top+e.height/2-sI,ee=c-E,D=g.offsetHeight/2,O=g.offsetTop+D,k=m+v+O,A=x-k;if(k<=E){let e=o.length>0&&g===o[o.length-1].ref.current;s.style.bottom=`0px`;let t=l.clientHeight-h.offsetTop-h.offsetHeight,n=k+Math.max(ee,D+(e?T:0)+t+y);s.style.height=n+`px`}else{let e=o.length>0&&g===o[0].ref.current;s.style.top=`0px`;let t=Math.max(E,m+h.offsetTop+(e?w:0)+D)+A;s.style.height=t+`px`,h.scrollTop=k-E+h.offsetTop}s.style.margin=`${sI}px 0`,s.style.minHeight=S+`px`,s.style.maxHeight=c+`px`,r?.(),requestAnimationFrame(()=>p.current=!0)}},[f,a.trigger,a.valueNode,s,l,h,g,_,a.dir,r]);QC(()=>y(),[y]);let[b,x]=z.useState();return QC(()=>{l&&x(window.getComputedStyle(l).zIndex)},[l]),(0,B.jsx)(_I,{scope:n,contentWrapper:s,shouldExpandOnScrollRef:p,onScrollButtonChange:z.useCallback(e=>{e&&m.current===!0&&(y(),v?.(),m.current=!1)},[y,v]),children:(0,B.jsx)(`div`,{ref:c,style:{display:`flex`,flexDirection:`column`,position:`fixed`,zIndex:b},children:(0,B.jsx)(IF.div,{...i,ref:d,style:{boxSizing:`border-box`,maxHeight:`100%`,...i.style}})})})});mI.displayName=pI;var hI=`SelectPopperPosition`,gI=z.forwardRef((e,t)=>{let{__scopeSelect:n,align:r=`start`,collisionPadding:i=sI,...a}=e,o=GF(n);return(0,B.jsx)(eD,{...o,...a,ref:t,align:r,collisionPadding:i,style:{boxSizing:`border-box`,...a.style,"--radix-select-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-select-content-available-width":`var(--radix-popper-available-width)`,"--radix-select-content-available-height":`var(--radix-popper-available-height)`,"--radix-select-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-select-trigger-height":`var(--radix-popper-anchor-height)`}})});gI.displayName=hI;var[_I,vI]=UF(aI,{}),yI=`SelectViewport`,bI=z.forwardRef((e,t)=>{let{__scopeSelect:n,nonce:r,...i}=e,a=lI(yI,n),o=vI(yI,n),s=en(t,a.onViewportChange),c=z.useRef(0);return(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(`style`,{dangerouslySetInnerHTML:{__html:`[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`},nonce:r}),(0,B.jsx)(BF.Slot,{scope:n,children:(0,B.jsx)(IF.div,{"data-radix-select-viewport":``,role:`presentation`,...i,ref:s,style:{position:`relative`,flex:1,overflow:`hidden auto`,...i.style},onScroll:eC(i.onScroll,e=>{let t=e.currentTarget,{contentWrapper:n,shouldExpandOnScrollRef:r}=o;if(r?.current&&n){let e=Math.abs(c.current-t.scrollTop);if(e>0){let r=window.innerHeight-sI*2,i=parseFloat(n.style.minHeight),a=parseFloat(n.style.height),o=Math.max(i,a);if(o0?s:0,n.style.justifyContent=`flex-end`)}}}c.current=t.scrollTop})})})]})});bI.displayName=yI;var xI=`SelectGroup`,[SI,CI]=UF(xI),wI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=tw();return(0,B.jsx)(SI,{scope:n,id:i,children:(0,B.jsx)(IF.div,{role:`group`,"aria-labelledby":i,...r,ref:t})})});wI.displayName=xI;var TI=`SelectLabel`,EI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=CI(TI,n);return(0,B.jsx)(IF.div,{id:i.id,...r,ref:t})});EI.displayName=TI;var DI=`SelectItem`,[OI,kI]=UF(DI),AI=z.forwardRef((e,t)=>{let{__scopeSelect:n,value:r,disabled:i=!1,textValue:a,...o}=e,s=qF(DI,n),c=lI(DI,n),l=s.value===r,[u,d]=z.useState(a??``),[f,p]=z.useState(!1),m=en(t,e=>c.itemRefCallback?.(e,r,i)),h=tw(),g=z.useRef(`touch`),_=()=>{i||(s.onValueChange(r),s.onOpenChange(!1))};if(r===``)throw Error(`A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.`);return(0,B.jsx)(OI,{scope:n,value:r,disabled:i,textId:h,isSelected:l,onItemTextChange:z.useCallback(e=>{d(t=>t||(e?.textContent??``).trim())},[]),children:(0,B.jsx)(BF.ItemSlot,{scope:n,value:r,disabled:i,textValue:u,children:(0,B.jsx)(IF.div,{role:`option`,"aria-labelledby":h,"data-highlighted":f?``:void 0,"aria-selected":l&&f,"data-state":l?`checked`:`unchecked`,"aria-disabled":i||void 0,"data-disabled":i?``:void 0,tabIndex:i?void 0:-1,...o,ref:m,onFocus:eC(o.onFocus,()=>p(!0)),onBlur:eC(o.onBlur,()=>p(!1)),onClick:eC(o.onClick,()=>{g.current!==`mouse`&&_()}),onPointerUp:eC(o.onPointerUp,()=>{g.current===`mouse`&&_()}),onPointerDown:eC(o.onPointerDown,e=>{g.current=e.pointerType}),onPointerMove:eC(o.onPointerMove,e=>{g.current=e.pointerType,i?c.onItemLeave?.():g.current===`mouse`&&e.currentTarget.focus({preventScroll:!0})}),onPointerLeave:eC(o.onPointerLeave,e=>{e.currentTarget===document.activeElement&&c.onItemLeave?.()}),onKeyDown:eC(o.onKeyDown,e=>{c.searchRef?.current!==``&&e.key===` `||(RF.includes(e.key)&&_(),e.key===` `&&e.preventDefault())})})})})});AI.displayName=DI;var jI=`SelectItemText`,MI=z.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,...a}=e,o=qF(jI,n),s=lI(jI,n),c=kI(jI,n),l=YF(jI,n),[u,d]=z.useState(null),f=en(t,e=>d(e),c.onItemTextChange,e=>s.itemTextRefCallback?.(e,c.value,c.disabled)),p=u?.textContent,m=z.useMemo(()=>(0,B.jsx)(`option`,{value:c.value,disabled:c.disabled,children:p},c.value),[c.disabled,c.value,p]),{onNativeOptionAdd:h,onNativeOptionRemove:g}=l;return QC(()=>(h(m),()=>g(m)),[h,g,m]),(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(IF.span,{id:c.textId,...a,ref:f}),c.isSelected&&o.valueNode&&!o.valueNodeHasChildren?Fi.createPortal(a.children,o.valueNode):null]})});MI.displayName=jI;var NI=`SelectItemIndicator`,PI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return kI(NI,n).isSelected?(0,B.jsx)(IF.span,{"aria-hidden":!0,...r,ref:t}):null});PI.displayName=NI;var FI=`SelectScrollUpButton`,II=z.forwardRef((e,t)=>{let n=lI(FI,e.__scopeSelect),r=vI(FI,e.__scopeSelect),[i,a]=z.useState(!1),o=en(t,r.onScrollButtonChange);return QC(()=>{if(n.viewport&&n.isPositioned){let e=function(){a(t.scrollTop>0)},t=n.viewport;return e(),t.addEventListener(`scroll`,e),()=>t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,B.jsx)(zI,{...e,ref:o,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop-=t.offsetHeight)}}):null});II.displayName=FI;var LI=`SelectScrollDownButton`,RI=z.forwardRef((e,t)=>{let n=lI(LI,e.__scopeSelect),r=vI(LI,e.__scopeSelect),[i,a]=z.useState(!1),o=en(t,r.onScrollButtonChange);return QC(()=>{if(n.viewport&&n.isPositioned){let e=function(){let e=t.scrollHeight-t.clientHeight;a(Math.ceil(t.scrollTop)t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,B.jsx)(zI,{...e,ref:o,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop+=t.offsetHeight)}}):null});RI.displayName=LI;var zI=z.forwardRef((e,t)=>{let{__scopeSelect:n,onAutoScroll:r,...i}=e,a=lI(`SelectScrollButton`,n),o=z.useRef(null),s=VF(n),c=z.useCallback(()=>{o.current!==null&&(window.clearInterval(o.current),o.current=null)},[]);return z.useEffect(()=>()=>c(),[c]),QC(()=>{s().find(e=>e.ref.current===document.activeElement)?.ref.current?.scrollIntoView({block:`nearest`})},[s]),(0,B.jsx)(IF.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:eC(i.onPointerDown,()=>{o.current===null&&(o.current=window.setInterval(r,50))}),onPointerMove:eC(i.onPointerMove,()=>{a.onItemLeave?.(),o.current===null&&(o.current=window.setInterval(r,50))}),onPointerLeave:eC(i.onPointerLeave,()=>{c()})})}),BI=`SelectSeparator`,VI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return(0,B.jsx)(IF.div,{"aria-hidden":!0,...r,ref:t})});VI.displayName=BI;var HI=`SelectArrow`,UI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=GF(n),a=qF(HI,n),o=lI(HI,n);return a.open&&o.position===`popper`?(0,B.jsx)(tD,{...i,...r,ref:t}):null});UI.displayName=HI;var WI=`SelectBubbleInput`,GI=z.forwardRef(({__scopeSelect:e,value:t,...n},r)=>{let i=z.useRef(null),a=en(r,i),o=RM(t);return z.useEffect(()=>{let e=i.current;if(!e)return;let n=window.HTMLSelectElement.prototype,r=Object.getOwnPropertyDescriptor(n,`value`).set;if(o!==t&&r){let n=new Event(`change`,{bubbles:!0});r.call(e,t),e.dispatchEvent(n)}},[o,t]),(0,B.jsx)(IF.select,{...n,style:{...CN,...n.style},ref:a,defaultValue:t})});GI.displayName=WI;function KI(e){return e===``||e===void 0}function qI(e){let t=dC(e),n=z.useRef(``),r=z.useRef(0),i=z.useCallback(e=>{let i=n.current+e;t(i),(function e(t){n.current=t,window.clearTimeout(r.current),t!==``&&(r.current=window.setTimeout(()=>e(``),1e3))})(i)},[t]),a=z.useCallback(()=>{n.current=``,window.clearTimeout(r.current)},[]);return z.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,a]}function JI(e,t,n){let r=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,i=n?e.indexOf(n):-1,a=YI(e,Math.max(i,0));r.length===1&&(a=a.filter(e=>e!==n));let o=a.find(e=>e.textValue.toLowerCase().startsWith(r.toLowerCase()));return o===n?void 0:o}function YI(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var XI=XF,ZI=QF,QI=eI,$I=nI,eL=iI,tL=oI,nL=bI,rL=EI,iL=AI,aL=MI,oL=PI,sL=II,cL=RI,lL=VI,uL=XI,dL=QI,fL=z.forwardRef(({className:e,children:t,...n},r)=>(0,B.jsxs)(ZI,{ref:r,className:Jr(`text-control flex h-8 w-full items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-3 py-1.5 text-left text-12 text-foreground transition-colors duration-200 placeholder:text-text-tertiary focus-visible:outline-none focus-visible:border-input-focus aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive disabled:cursor-not-allowed disabled:opacity-50 [&>span]:min-w-0 [&>span]:line-clamp-1`,e),...n,children:[t,(0,B.jsx)($I,{asChild:!0,children:(0,B.jsx)(ui,{className:`h-3.5 w-3.5 shrink-0 text-text-tertiary`})})]}));fL.displayName=ZI.displayName;var pL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(sL,{ref:n,className:Jr(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,B.jsx)(fi,{className:`h-3.5 w-3.5 text-text-tertiary`})}));pL.displayName=sL.displayName;var mL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(cL,{ref:n,className:Jr(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,B.jsx)(ui,{className:`h-3.5 w-3.5 text-text-tertiary`})}));mL.displayName=cL.displayName;var hL=z.forwardRef(({className:e,children:t,position:n=`popper`,style:r,...i},a)=>{let o=Ok();return(0,B.jsx)(kk,{layer:o,children:(0,B.jsx)(eL,{children:(0,B.jsxs)(tL,{ref:a,className:Jr(`relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg shadow-primary/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,n===`popper`&&`w-[var(--radix-select-trigger-width)] data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1`,e),position:n,...i,style:{...r,zIndex:o},children:[(0,B.jsx)(pL,{}),(0,B.jsx)(nL,{className:Jr(`p-1.5`,n===`popper`&&`h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]`),children:t}),(0,B.jsx)(mL,{})]})})})});hL.displayName=tL.displayName;var gL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(rL,{ref:n,className:Jr(`px-3.5 py-2 text-13 font-medium text-muted-foreground`,e),...t}));gL.displayName=rL.displayName;var _L=z.forwardRef(({className:e,children:t,...n},r)=>(0,B.jsxs)(iL,{ref:r,className:Jr(`relative flex w-full cursor-pointer select-none items-center rounded-md py-1.5 pl-3 pr-8 text-12 outline-none transition-colors duration-200 focus:bg-overlay-strong focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50`,e),...n,children:[(0,B.jsx)(`span`,{className:`absolute right-3 flex h-3.5 w-3.5 items-center justify-center`,children:(0,B.jsx)(oL,{children:(0,B.jsx)(li,{className:`h-3.5 w-3.5 text-primary`})})}),(0,B.jsx)(aL,{children:t})]}));_L.displayName=iL.displayName;var vL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(lL,{ref:n,className:Jr(`-mx-1 my-1 h-px bg-border`,e),...t}));vL.displayName=lL.displayName;function yL(e){let t=(e??`read write`).split(/\s+/).map(e=>e.trim()).filter(Boolean),n=new Set(jP);return new Set(t.filter(e=>n.has(e)))}function bL(e){let t=e.allowed_services_csv,n=e.allowed_nodes_csv,r=new Set((t??``).split(`,`).map(e=>e.trim()).filter(Boolean)),i=new Set((n??``).split(`,`).map(e=>e.trim()).filter(Boolean)),a=e.allow_all_services||!t&&!e.allow_auto_connected_services,o=!n;return{allowAllServices:a,allowAutoConnectedServices:e.allow_auto_connected_services??!1,allowAllNodes:o,selectedServiceIds:r,selectedNodeIds:i}}function xL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),[a,o]=(0,z.useState)(e.platform??``),[s,c]=(0,z.useState)(!1),[l,u]=(0,z.useState)(()=>yL(e.scopes)),[d,f]=(0,z.useState)(()=>bL(e)),[p,m]=(0,z.useState)(e.org_id??``),h=hF(),[g,_]=(0,z.useState)(!1),[v,y]=(0,z.useState)(null);async function b(){_(!0),y(null);try{let i={name:r,scopes:Array.from(l).join(` `),allow_all_services:d.allowAllServices,allow_auto_connected_services:d.allowAutoConnectedServices??!1,allow_all_nodes:d.allowAllNodes};if(a&&(i.platform=a),e.callback_url&&(i.callback_url=e.callback_url),p&&(i.target_org_id=p),d.allowAllServices||(i.allowed_service_ids=Array.from(d.selectedServiceIds)),d.allowAllNodes||(i.allowed_node_ids=Array.from(d.selectedNodeIds)),e.expires_in_days!=null&&e.expires_in_days>0){let t=new Date;t.setDate(t.getDate()+e.expires_in_days),i.expires_at=t.toISOString()}await gP(t);let o=await vP(t,()=>hb.post(`/api-keys`,i));n({kind:`api-key-create`,api_key_id:o.id,full_key:o.full_key})}catch(e){y(NL(e))}finally{_(!1)}}let x=g||!s||l.size===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create an API key`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Review the details your CLI sent and confirm to mint the key.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(EP,{id:`pair-api-key-name`,label:`Name`,schema:xP,value:r,onChange:i,onValidityChange:c,placeholder:`e.g. coding-agent`,hint:"A short label so you can find this key in `nyxid api-key list`.",autoFocus:!0}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-api-key-platform`,children:`Platform`}),(0,B.jsx)(fP,{delayDuration:150,children:(0,B.jsxs)(pP,{children:[(0,B.jsx)(mP,{asChild:!0,children:(0,B.jsx)(`button`,{type:`button`,"aria-label":`About platform tags`,className:`text-muted-foreground transition-colors duration-300 hover:text-foreground`,children:(0,B.jsx)(Si,{className:`h-3.5 w-3.5`})})}),(0,B.jsx)(hP,{side:`right`,align:`start`,sideOffset:8,className:`max-w-[340px] whitespace-normal px-5 py-4 text-13 leading-[1.55]`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`p`,{children:[(0,B.jsx)(`span`,{className:`font-medium text-foreground`,children:`Platform`}),` `,`tags the key with the AI agent that will use it.`]}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:`It controls three things: audit attribution (logs show which agent made each proxy request), per- agent rate-limit buckets, and dashboard filtering on the API Keys page.`}),(0,B.jsxs)(`p`,{className:`text-muted-foreground`,children:[`Values are a fixed allowlist —`,` `,(0,B.jsx)(`code`,{children:`claude-code`}),`, `,(0,B.jsx)(`code`,{children:`cursor`}),`,`,` `,(0,B.jsx)(`code`,{children:`codex`}),`, `,(0,B.jsx)(`code`,{children:`openclaw`}),`,`,` `,(0,B.jsx)(`code`,{children:`generic`}),`. Custom strings are rejected by the backend.`]}),(0,B.jsxs)(`p`,{className:`text-muted-foreground`,children:[`Leave as `,(0,B.jsx)(`code`,{children:`— none —`}),` if you don't want the tag.`]})]})})]})})]}),(0,B.jsxs)(uL,{value:a===``?`__none__`:a,onValueChange:e=>{o(e===`__none__`?``:e)},children:[(0,B.jsx)(fL,{id:`pair-api-key-platform`,children:(0,B.jsx)(dL,{placeholder:`— none —`})}),(0,B.jsxs)(hL,{children:[(0,B.jsx)(_L,{value:`__none__`,children:`— none —`}),SP.map(e=>(0,B.jsx)(_L,{value:e,children:(0,B.jsxs)(`span`,{className:`inline-flex items-center gap-2`,children:[(0,B.jsx)(PM,{platform:e,size:`2xs`}),(0,B.jsx)(`span`,{children:e})]})},e))]})]}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Tags the key for audit attribution + per-agent rate limits.`})]}),(h.data?.length??0)>0?(0,B.jsxs)(jL,{label:`Owner`,htmlFor:`pair-api-key-owner`,children:[(0,B.jsxs)(`select`,{id:`pair-api-key-owner`,value:p,onChange:e=>{m(e.target.value),f(e=>({...e,selectedServiceIds:new Set,selectedNodeIds:new Set,allowAutoConnectedServices:!1}))},className:`flex h-10 w-full rounded-xl border border-input bg-transparent px-[14px] py-2 text-13 text-foreground transition-colors duration-300 focus-visible:outline-none focus-visible:border-input-focus`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal (your account)`}),h.data?.filter(e=>e.your_role===`admin`).map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]}),(0,B.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:`Org-owned keys authenticate as the org; every admin of the selected org can rotate or delete them.`})]}):null,(0,B.jsx)(MP,{value:l,onChange:u}),(0,B.jsx)(lF,{value:d,onChange:f,ownerId:p})]}),v?(0,B.jsx)(ML,{message:v}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void b(),disabled:x,children:g?`Creating...`:`Create Key`})]})}function SL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await gP(t);let r=await vP(t,()=>hb.post(`/api-keys/${encodeURIComponent(e.resource_id)}/rotate`));n({kind:`api-key-rotate`,resource_id:r.id,full_key:r.full_key,platform:r.platform})}catch(e){o(NL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate API key`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` will issue a new key and immediately revoke the previous one.`]})]}),a?(0,B.jsx)(ML,{message:a}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate key`})]})}var CL=`my-node`;function wL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),[a,o]=(0,z.useState)(!0),[s,c]=(0,z.useState)(!1),[l,u]=(0,z.useState)(null);async function d(){c(!0),u(null);try{let e=r.trim(),i=e.length>0?e:CL;await gP(t);let a=await vP(t,()=>hb.post(`/nodes/register-token`,{name:i}));n({kind:`node-register-token`,token_id:a.token_id,token:a.token})}catch(e){u(NL(e))}finally{c(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Generate node registration token`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Use this token with `,(0,B.jsx)(`code`,{children:`nyxid node register`}),` to connect a new node.`]})]}),(0,B.jsx)(EP,{id:`pair-node-name`,label:`Node name (optional)`,schema:bP,value:r,onChange:i,onValidityChange:o,placeholder:CL,hint:`Lowercase letters, digits, hyphens only (max 64). Leave blank for \`${CL}\`.`,optional:!0,autoFocus:!0}),l?(0,B.jsx)(ML,{message:l}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void d(),disabled:s||!a,children:s?`Generating...`:`Generate token`})]})}function TL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await gP(t);let r=await vP(t,()=>hb.post(`/nodes/${encodeURIComponent(e.resource_id)}/rotate-token`));n({kind:`node-rotate-token`,resource_id:e.resource_id,auth_token:r.auth_token,signing_secret:r.signing_secret})}catch(e){o(NL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate node token`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` issues a new auth token + signing secret and revokes the previous pair.`]})]}),a?(0,B.jsx)(ML,{message:a}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate token`})]})}function EL({prefill:e,pairingId:t,onSuccess:n}){let r=bd({resolver:yd(zk),defaultValues:{name:e.name??``,allowed_scopes:e.scopes??`openid profile`,description:e.description??``,role_ids:e.role_ids_csv??``}}),i=r.watch(`name`),a=r.watch(`allowed_scopes`),o=r.watch(`description`)??``,s=r.watch(`role_ids`)??``,[c,l]=(0,z.useState)(e.org_id??``),u=$S(e=>e.user),d=lt({queryKey:[`wizard-current-user`,t],enabled:!u,queryFn:async()=>{await $S.getState().checkAuth({ephemeral:!0});let e=$S.getState().user;if(!e)throw Error(`Unable to load your account. Check your CLI login and retry.`);return e},retry:!1}),f=hF(),[p,m]=(0,z.useState)(!1),[h,g]=(0,z.useState)(null);async function _(){if(await r.trigger()){m(!0),g(null);try{let r={name:i.trim(),allowed_scopes:a.trim()};o.trim()&&(r.description=o.trim()),e.rate_limit_override!=null&&(r.rate_limit_override=e.rate_limit_override);let l=s.split(`,`).map(e=>e.trim()).filter(Boolean);l.length>0&&(r.role_ids=l),c&&(r.target_org_id=c),await gP(t);let u=await vP(t,()=>hb.post(`/admin/service-accounts`,r));n({kind:`service-account-create`,service_account_id:u.id,client_id:u.client_id,client_secret:u.client_secret})}catch(e){g(NL(e))}finally{m(!1)}}}let v=p||i.trim().length===0||a.trim().length===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create a service account`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Service accounts authenticate via the OAuth client_credentials flow. The client_secret is shown once, on the next screen.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(jL,{label:`Name`,htmlFor:`pair-sa-name`,children:(0,B.jsx)(FM,{id:`pair-sa-name`,value:i,onChange:e=>{r.setValue(`name`,e.target.value)},placeholder:`e.g. ci-deploys`,autoFocus:!0})}),(0,B.jsx)(jL,{label:`Allowed scopes`,htmlFor:`pair-sa-scopes`,children:(0,B.jsx)(Lk,{id:`pair-sa-scopes`,value:a,onChange:e=>r.setValue(`allowed_scopes`,e),ownerId:c||u?.id||``})}),(0,B.jsx)(jL,{label:`Description (optional)`,htmlFor:`pair-sa-desc`,children:(0,B.jsx)(FM,{id:`pair-sa-desc`,value:o,onChange:e=>{r.setValue(`description`,e.target.value)},placeholder:`What this account is for`})}),(0,B.jsx)(jL,{label:`Role IDs (optional, comma-separated)`,htmlFor:`pair-sa-roles`,children:(0,B.jsx)(FM,{id:`pair-sa-roles`,value:s,onChange:e=>{r.setValue(`role_ids`,e.target.value)},placeholder:`role-id-1,role-id-2`})}),(f.data?.length??0)>0?(0,B.jsx)(jL,{label:`Owner`,htmlFor:`pair-sa-owner`,children:(0,B.jsxs)(`select`,{id:`pair-sa-owner`,value:c,onChange:e=>{l(e.target.value)},className:`flex h-10 w-full rounded-[10px] border border-input bg-transparent px-[14px] py-2 text-13 text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal (your admin account)`}),f.data?.map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]})}):null]}),!u&&d.isPending&&(0,B.jsx)(`p`,{role:`status`,children:`Loading your account…`}),!u&&d.isError&&(0,B.jsxs)(`div`,{role:`alert`,children:[(0,B.jsx)(ML,{message:d.error.message}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void d.refetch(),children:`Retry account`})]}),Object.entries(r.formState.errors).map(([e,t])=>(0,B.jsx)(ML,{message:t.message??`Invalid value`},e)),h?(0,B.jsx)(ML,{message:h}):null,(0,B.jsx)(Pi,{onClick:()=>void _(),disabled:v,children:p?`Creating...`:`Create Service Account`})]})}function DL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await gP(t);let r=await vP(t,()=>hb.post(`/admin/service-accounts/${encodeURIComponent(e.resource_id)}/rotate-secret`));n({kind:`service-account-rotate-secret`,resource_id:e.resource_id,client_id:r.client_id,client_secret:r.client_secret})}catch(e){o(NL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate service account secret`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` immediately revokes all existing access tokens issued under this service account and mints a new client_secret.`]})]}),a?(0,B.jsx)(ML,{message:a}):null,(0,B.jsx)(Pi,{onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate secret`})]})}function OL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),a=(e.redirect_uris??[]).filter(e=>typeof e==`string`&&e.length>0),[o,s]=(0,z.useState)(a.length>0?[...a]:[``]),[c,l]=(0,z.useState)(e.allowed_scopes??`openid profile email`),[u,d]=(0,z.useState)(e.delegation_scopes??``),[f,p]=(0,z.useState)(e.broker_capability??!1),[m,h]=(0,z.useState)(e.org_id??``),g=hF(),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null);function x(e,t){s(n=>n.map((n,r)=>r===e?t:n))}function S(){s(e=>[...e,``])}function C(e){s(t=>t.length===1?[``]:t.filter((t,n)=>n!==e))}async function w(){v(!0),b(null);try{let i=o.map(e=>e.trim()).filter(Boolean);if(i.length===0){b(`At least one redirect URI is required.`),v(!1);return}let a={name:r.trim(),redirect_uris:i,client_type:`confidential`};c.trim()&&(a.allowed_scopes=c.split(/\s+/).map(e=>e.trim()).filter(Boolean)),u.trim()&&(a.delegation_scopes=u.trim()),f&&(a.broker_capability_enabled=!0);let s=(e.default_service_catalog_slugs??[]).map(e=>e.trim()).filter(Boolean);s.length>0&&(a.default_service_catalog_slugs=s),m&&(a.target_org_id=m),await gP(t);let l=await vP(t,()=>hb.post(`/developer/oauth-clients`,a));if(!l.client_secret)throw Error(`Server didn't return a client_secret — was the client_type 'public'?`);n({kind:`developer-app-create`,developer_app_id:l.id,client_secret:l.client_secret})}catch(e){b(NL(e))}finally{v(!1)}}let T=_||r.trim().length===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create a developer OAuth app`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Confidential client — the client_secret is shown once on the next screen. Use it to sign Sign-in-with-NyxID requests from your downstream product.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(jL,{label:`App name`,htmlFor:`pair-app-name`,children:(0,B.jsx)(FM,{id:`pair-app-name`,value:r,onChange:e=>{i(e.target.value)},placeholder:`e.g. Acme Web`,autoFocus:!0})}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:`Redirect URIs`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[o.map((e,t)=>(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(FM,{value:e,onChange:e=>{x(t,e.target.value)},placeholder:`https://app.example.com/callback`,className:`flex-1`}),(0,B.jsx)(Pi,{type:`button`,variant:`outline`,size:`icon`,onClick:()=>{C(t)},"aria-label":`Remove redirect URI`,disabled:o.length===1&&e.trim().length===0,children:(0,B.jsx)(ki,{className:`h-4 w-4`})})]},t)),(0,B.jsxs)(Pi,{type:`button`,variant:`outline`,size:`sm`,onClick:S,className:`self-start`,children:[(0,B.jsx)(Di,{className:`mr-1 h-3 w-3`}),` Add redirect URI`]})]})]}),(0,B.jsxs)(jL,{label:`Allowed scopes`,htmlFor:`pair-app-scopes`,children:[(0,B.jsx)(FM,{id:`pair-app-scopes`,value:c,onChange:e=>{l(e.target.value)},placeholder:`openid profile email`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Space-separated.`})]}),(0,B.jsx)(jL,{label:`Delegation scopes (optional)`,htmlFor:`pair-app-delegation`,children:(0,B.jsx)(FM,{id:`pair-app-delegation`,value:u,onChange:e=>{d(e.target.value)},placeholder:`(blank disables token exchange)`})}),(0,B.jsxs)(`div`,{className:`flex items-center justify-between rounded-md border border-border bg-muted/20 px-3 py-2`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-app-broker`,children:`Broker capability`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Allow this app to broker downstream credentials.`})]}),(0,B.jsx)(aN,{id:`pair-app-broker`,checked:f,onCheckedChange:e=>{p(e)}})]}),(g.data?.length??0)>0?(0,B.jsx)(jL,{label:`Owner`,htmlFor:`pair-app-owner`,children:(0,B.jsxs)(`select`,{id:`pair-app-owner`,value:m,onChange:e=>{h(e.target.value)},className:`flex h-10 w-full rounded-[10px] border border-input bg-transparent px-[14px] py-2 text-13 text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal`}),g.data?.map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]})}):null]}),y?(0,B.jsx)(ML,{message:y}):null,(0,B.jsx)(Pi,{onClick:()=>void w(),disabled:T,children:_?`Creating...`:`Create app`})]})}function kL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await gP(t);let r=await vP(t,()=>hb.post(`/developer/oauth-clients/${encodeURIComponent(e.resource_id)}/rotate-secret`));n({kind:`developer-app-rotate-secret`,resource_id:r.id,client_secret:r.client_secret})}catch(e){o(NL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate developer app secret`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` mints a new client_secret. Update any deployments using the previous value immediately.`]})]}),a?(0,B.jsx)(ML,{message:a}):null,(0,B.jsx)(Pi,{onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate secret`})]})}function AL({pairingId:e,onSuccess:t}){let[n,r]=(0,z.useState)(`init`),[i,a]=(0,z.useState)(null),[o,s]=(0,z.useState)(null),[c,l]=(0,z.useState)(null),[u,d]=(0,z.useState)(null),[f,p]=(0,z.useState)(``),[m,h]=(0,z.useState)(null),[g,_]=(0,z.useState)(!1),v=(0,z.useRef)(!1);(0,z.useEffect)(()=>{v.current||(v.current=!0,(async()=>{try{await gP(e);let t=await vP(e,()=>hb.post(`/auth/mfa/setup`,{}));a(t.factor_id),s(t.secret),l(t.qr_code_url);try{d(await mA.toDataURL(t.qr_code_url,{width:240,margin:1}))}catch{}r(`ready`)}catch(e){h(NL(e)),v.current=!1}})())},[e]);async function y(){if(!(!i||n!==`ready`)){h(null),r(`confirming`);try{let e=f.trim();if(e.length===0){h(`Enter the 6-digit code from your authenticator.`),r(`ready`);return}t({kind:`mfa-setup`,factor_id:i,recovery_codes:(await hb.post(`/auth/mfa/confirm`,{code:e})).recovery_codes})}catch(e){h(NL(e)),r(`ready`)}}}return n===`init`?(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Setting up MFA`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Generating a TOTP secret on the server…`})]}),m?(0,B.jsx)(ML,{message:m}):null]}):(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Add MFA to your account`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Scan this QR with your authenticator app (1Password, Authy, Google Authenticator). Then enter the 6-digit code it shows to verify and finish enrollment.`})]}),u?(0,B.jsx)(`div`,{className:`flex justify-center rounded-md border border-border bg-white p-4 dark:bg-muted/30`,children:(0,B.jsx)(`img`,{src:u,alt:`MFA enrollment QR code`,className:`h-60 w-60`})}):(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Couldn't render the QR code. Use the otpauth URL below instead.`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:`Or enter the secret manually`}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(`code`,{className:`flex-1 overflow-x-auto rounded-md border bg-muted/40 px-3 py-2 font-mono text-sm`,children:g?o:`•`.repeat(Math.max(o?.length??12,12))}),(0,B.jsx)(Pi,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>{_(e=>!e)},children:g?`Hide`:`Reveal`})]}),c?(0,B.jsxs)(`p`,{className:`break-all text-11 text-muted-foreground`,children:[`otpauth URL: `,(0,B.jsx)(`code`,{className:`font-mono`,children:c})]}):null]}),(0,B.jsx)(jL,{label:`6-digit code from your authenticator`,htmlFor:`pair-mfa-code`,children:(0,B.jsx)(FM,{id:`pair-mfa-code`,value:f,onChange:e=>{p(e.target.value)},placeholder:`123456`,inputMode:`numeric`,autoFocus:!0,maxLength:10,className:`font-mono tracking-widest`})}),m?(0,B.jsx)(ML,{message:m}):null,(0,B.jsx)(Pi,{onClick:()=>void y(),disabled:n===`confirming`||f.trim().length===0,children:n===`confirming`?`Verifying...`:`Verify and enable MFA`})]})}function jL({label:e,htmlFor:t,children:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:t,children:e}),n]})}function ML({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}function NL(e){return e instanceof Error?e.message:`Something went wrong. Please try again.`}var PL=[`tokens`,`requests`,`bytes`,`input_tokens`,`output_tokens`,`cache_read_tokens`,`cache_write_tokens`,`images`,`voice_seconds`],FL={tokens:{label:`tokens`,singular:`token`,tokenFamily:!0},requests:{label:`requests`,singular:`request`,tokenFamily:!1},bytes:{label:`bytes`,singular:`byte`,tokenFamily:!1},input_tokens:{label:`input tokens`,singular:`input token`,tokenFamily:!0},output_tokens:{label:`output tokens`,singular:`output token`,tokenFamily:!0},cache_read_tokens:{label:`cache-read tokens`,singular:`cache-read token`,tokenFamily:!0},cache_write_tokens:{label:`cache-write tokens`,singular:`cache-write token`,tokenFamily:!0},voice_seconds:{label:`voice seconds`,singular:`voice second`,tokenFamily:!1},images:{label:`images`,singular:`image`,tokenFamily:!1}};function IL(e,t){let n=FL[e];return n?t===1?n.singular:n.label:e}var LL=RegExp(`^\\d+(?:\\.\\d{1,12})?$`);function RL(e){if(!LL.test(e))return!1;let[t,n=``]=e.split(`.`);return BigInt(t)*10n**12n+BigInt(n.padEnd(12,`0`))<=1000000n*10n**12n}var zL=$().trim().regex(LL,`Use a non-negative decimal with at most 12 decimal places`).refine(RL,`Price must not exceed 1,000,000 credits per unit`),BL=[`voice_seconds`,`tokens`,`input_tokens`,`output_tokens`,`cache_read_tokens`,`cache_write_tokens`],VL=$().min(1).max(128).regex(/^[A-Za-z0-9_.-]+$/,`Use a provider identifier, not a URL`),HL=$().trim().min(1).max(128).refine(e=>!/\p{Cc}/u.test(e),`Control characters are not allowed`),UL=Ax({protocol:Bx([`openai_live`,`xai_realtime`]),models:Ox(Ax({id:VL,label:HL,default:Sx().optional()})).min(1).max(32),voices:Ox(Ax({id:VL,label:HL})).min(1).max(64),usage_source:Bx([`provider_reported`,`server_measured`]),billing_metrics:Ox(Bx(BL)).min(1).max(6)}).superRefine((e,t)=>{for(let n of[`models`,`voices`])new Set(e[n].map(e=>e.id)).size!==e[n].length&&t.addIssue({code:`custom`,path:[n],message:`Provider IDs must be unique`});e.models.filter(e=>e.default).length>1&&t.addIssue({code:`custom`,path:[`models`],message:`Choose at most one default model`}),(new Set(e.billing_metrics).size!==e.billing_metrics.length||!e.billing_metrics.includes(`voice_seconds`))&&t.addIssue({code:`custom`,path:[`billing_metrics`],message:`Unique metrics including voice seconds are required`}),e.usage_source!==(e.protocol===`openai_live`?`provider_reported`:`server_measured`)&&t.addIssue({code:`custom`,path:[`usage_source`],message:`Usage source must match the provider protocol`})}),WL=Ax({wire_protocol:Bx([`anthropic_messages`,`openai_responses`,`openai_completions`]),model_list:Sx(),realtime:Sx().optional(),voice:UL.nullish()}).extend({binding:Bx([`platform`,`user`]),status_slug:$().optional()}),GL=Ax({metric:$(),credits_per_unit:zL,sync_status:Bx([`pending`,`synced`,`failed`]).optional()}),KL=GL.extend({components:Ox(GL).nullish()}),qL=GL.extend({metric:Bx(PL)});qL.extend({components:Ox(qL).max(PL.length-1).nullish()}).superRefine((e,t)=>{let n=new Set([e.metric]);e.components?.forEach((e,r)=>{n.has(e.metric)&&t.addIssue({code:`custom`,path:[`components`,r,`metric`],message:`Each unit may appear only once per lane`}),n.add(e.metric)})}),Ax({enabled:Sx(),audience:Bx([`public`,`restricted`]),allowed_owner_ids:Ox($().uuid()).max(1e3)}),Ax({inference:WL.nullish(),platform_key:Ax({available:Sx(),pricing:KL.nullish()}).optional(),byok_pricing:KL.nullish()});function JL(e){return e?[e,...e.components??[]].map(e=>`${e.credits_per_unit} credits / ${IL(e.metric,1)}${e.sync_status&&e.sync_status!==`synced`?` (price pending; current billing applies)`:``}`).join(` + `):`free`}function YL({value:e,onChange:t,platformPrice:n,byokPrice:r,legacyBillable:i=!1,resaleBillable:a=!1,disabled:o=!1}){return(0,B.jsxs)(`fieldset`,{className:`space-y-2`,disabled:o,children:[(0,B.jsx)(`legend`,{className:`mb-2 text-xs font-medium`,children:`Choose a key`}),[{platform:!0,title:`Use NyxID's key`,price:n},{platform:!1,title:`Use your own key`,price:r}].map(a=>(0,B.jsxs)(`label`,{className:Jr(`flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-xs`,e===a.platform?`border-primary/50`:`border-border/50`),children:[(0,B.jsx)(`input`,{type:`radio`,name:`credential-binding`,checked:e===a.platform,onChange:()=>t(a.platform),className:`mt-0.5 accent-primary`}),(0,B.jsxs)(`span`,{children:[(0,B.jsx)(`span`,{className:`block font-medium`,children:a.title}),(0,B.jsx)(`span`,{className:`text-muted-foreground`,children:!n&&!r&&i?`Current service/plan pricing applies`:JL(a.price)})]})]},String(a.platform))),a&&(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground`,children:`Platform-key use may also incur the separate resale fee.`})]})}function XL({className:e,...t}){return(0,B.jsx)(`div`,{className:Jr(`animate-pulse rounded-md bg-muted`,e),...t})}var ZL=`__personal__`;function QL({id:e,"aria-describedby":t,value:n,onChange:r,disabled:i,label:a=`Scope`,adminOnly:o=!0,allowAll:s=!1,personalLabel:c=`Personal`}){let{data:l,isLoading:u}=hF(),d=(l??[]).filter(e=>!o||e.your_role===`admin`);return(0,B.jsxs)(uL,{value:n??ZL,onValueChange:e=>r(e===ZL?null:e),disabled:i||u,children:[(0,B.jsx)(fL,{id:e,"aria-label":a,"aria-describedby":t,children:(0,B.jsx)(dL,{placeholder:c})}),(0,B.jsxs)(hL,{children:[s&&(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(_L,{value:`all`,children:`View all`}),(0,B.jsx)(vL,{asChild:!0,className:`border-0`,children:(0,B.jsx)(`hr`,{})})]}),(0,B.jsx)(_L,{value:ZL,children:c}),d.map(e=>(0,B.jsx)(_L,{value:e.id,children:e.display_name||e.id},e.id))]})]})}function $L(e,t=[]){return[...new Set([...e,...t.filter(e=>e.required).map(e=>e.scope)])]}function eR(e){let t=new Set,n=[];for(let r of e.split(/[,\s]+/)){let e=r.trim();e&&!t.has(e)&&(t.add(e),n.push(e))}return n}function tR(e,t,n,r){let i=new Set,a=new Set(t),o=new Set(r),s=[];for(let t of e)i.has(t.scope)||(i.add(t.scope),s.push({scope:t.scope,label:t.label||t.scope,description:t.description||null,sensitive:!!t.sensitive,isDefault:a.has(t.scope),locked:o.has(t.scope)}));for(let e of r)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!1,locked:!0}));for(let e of t)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!0,locked:!1}));for(let e of n)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!1,locked:!1}));return s}function nR({catalog:e,defaultScopes:t,value:n,onChange:r,customPlaceholder:i=`e.g. custom.scope`,idPrefix:a=`scope`,lockedScopes:o=[],grantedScopes:s,providerName:c,platformAllowlist:l}){let[u,d]=(0,z.useState)(``),f=$L(n,e),p=new Set(e.filter(e=>e.required).map(e=>e.scope)),m=new Set(f),h=new Set(o),g=tR(e,t,n,o),_=l?new Set(l):null,v=(e,t)=>_!==null&&!t&&!_.has(e),y=e=>g.find(t=>t.scope===e)?.label??e,b=s?new Set(s):null,x=b?f.filter(e=>!b.has(e)):[],S=s?s.filter(e=>!m.has(e)):[],C=x.length>0||S.length>0;function w(e){if(h.has(e)||p.has(e))return;let t=new Set(m);t.has(e)?t.delete(e):t.add(e),r(g.map(e=>e.scope).filter(e=>t.has(e)))}let[T,E]=(0,z.useState)(null);function ee(){let e=eR(u);if(e.length===0)return;if(_!==null){let t=e.filter(e=>!_.has(e));if(t.length>0){E(`${t.join(`, `)} — not available on NyxID's shared app. Use your own OAuth app to request ${t.length>1?`these`:`it`}.`);return}}E(null);let t=[...f];for(let n of e)t.includes(n)||t.push(n);d(``),r(t)}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(Vi,{className:`text-xs`,children:`Scopes`}),g.length>0?(0,B.jsx)(`div`,{role:`group`,"aria-label":`Scopes`,className:`flex flex-wrap gap-1.5`,children:g.map(e=>{let t=p.has(e.scope),n=v(e.scope,e.locked),r=(m.has(e.scope)||e.locked)&&!n;return(0,B.jsxs)(`button`,{type:`button`,"aria-pressed":r,disabled:e.locked||t||n,title:n?`${e.description??e.scope} — available only with your own OAuth app`:t?`${e.description??e.scope} — required for this service`:e.locked?`${e.description??e.scope} — already granted; can't be removed here`:e.description??e.scope,onClick:()=>{w(e.scope)},className:`group inline-flex max-w-full items-center gap-1.5 rounded-full border px-3 py-1.5 text-left text-12 transition-colors `+(n?`cursor-not-allowed border-dashed border-border/60 bg-transparent text-muted-foreground/50`:e.locked||t?`cursor-default border-primary/60 bg-primary/10 text-foreground`:r?`border-primary bg-primary/15 text-foreground`:`border-border bg-transparent text-muted-foreground hover:border-primary/50 hover:bg-muted/40`),children:[e.sensitive?(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 shrink-0 rounded-full bg-warning`}),(0,B.jsx)(`span`,{className:`sr-only`,children:`(write or admin access) `})]}):null,(0,B.jsx)(`span`,{className:`truncate`,children:e.label}),n?(0,B.jsx)(`span`,{className:`shrink-0 text-11 italic text-text-tertiary`,children:`own app`}):t?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`required`}):e.locked?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`granted`}):e.isDefault?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`default`}):null,r&&!e.locked&&!t?(0,B.jsx)(ji,{className:`h-3 w-3 shrink-0 opacity-50 group-hover:opacity-100`}):null]},e.scope)})}):null,g.some(e=>e.sensitive)?(0,B.jsxs)(`p`,{className:`flex items-center gap-1.5 text-11 text-muted-foreground`,children:[(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 shrink-0 rounded-full bg-warning`}),`Dot marks a write or admin-level scope.`]}):null,_!==null&&g.some(e=>v(e.scope,e.locked))?(0,B.jsxs)(`p`,{className:`text-11 text-muted-foreground`,children:[`Scopes marked “own app” aren’t offered on NyxID’s shared`,` `,c??`provider`,` app. Connect with your own OAuth app to request them.`]}):null,(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,B.jsx)(FM,{id:`${a}-custom`,value:u,onChange:e=>{d(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),ee())},placeholder:i,autoComplete:`off`,spellCheck:!1,className:`h-9 text-12`}),(0,B.jsxs)(Pi,{type:`button`,variant:`outline`,onClick:ee,disabled:u.trim().length===0,className:`h-9 shrink-0 px-3`,children:[(0,B.jsx)(Di,{className:`h-3.5 w-3.5`}),`Add`]})]}),T?(0,B.jsx)(`p`,{className:`text-11 text-destructive`,children:T}):null,b&&C?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1 rounded-lg border border-border bg-muted/40 px-3 py-2 text-12`,children:[(0,B.jsx)(`span`,{className:`text-11 font-medium uppercase tracking-wide text-muted-foreground`,children:`Changes`}),x.length>0?(0,B.jsxs)(`p`,{className:`text-foreground`,children:[(0,B.jsx)(`span`,{className:`text-success`,children:`+ Adding:`}),` `,x.map(y).join(`, `)]}):null,S.length>0?(0,B.jsxs)(B.Fragment,{children:[(0,B.jsxs)(`p`,{className:`text-foreground`,children:[(0,B.jsx)(`span`,{className:`text-destructive`,children:`− Removing:`}),` `,S.map(y).join(`, `)]}),(0,B.jsxs)(`p`,{className:`text-11 text-warning`,children:[`Removing a permission re-authorizes this connection and will stop any app that relies on it. NyxID will use only the remaining permissions; the old access at`,` `,c??`the provider`,` stays until you revoke it there.`]})]}):null]}):null,(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:h.size>0?`Scopes marked “granted” are already authorized and locked — this provider can’t narrow them by re-authorizing, so they can’t be removed here. Add anything missing above.`:b?`Tick to add a permission, untick to remove one, then update. Changes re-authorize this connection at the provider.`:g.length>0?`Selected scopes are requested at sign-in. Defaults are pre-selected — deselect to drop one. Add anything missing above; the upstream provider decides whether to grant them.`:`Comma- or space-separated. The upstream provider decides whether to grant them.`})]})}function rR(e){return e===`revoked`||e===`failed`||e===`expired`}var iR=5;async function aR({keyId:e,getKey:t,completeWithKey:n,isCancelled:r,onTerminalFailure:i,onTimeout:a,sleepMs:o=oR,nowMs:s=Date.now,timeoutMs:c=300*1e3,intervalMs:l=2e3,maxConsecutiveErrors:u=iR,isComplete:d=e=>e.status===`active`}){let f=s()+c,p=0;for(;s()=u){r()||i({status:`failed`,error_message:"Lost contact with the wizard. Authorization may have completed — run `nyxid status` to verify, then cancel and re-run the wizard if the service is missing."});return}}}r()||a()}function oR(e){return new Promise(t=>{window.setTimeout(t,e)})}var sR=Ax({api_base_url:$().trim().url(`API base URL must be a valid URL`).transform(e=>e.replace(/\/+$/,``)),release_integrity:Ax({enabled:Sx(),manifest_url:$().trim().url(`Release integrity manifest URL must be a valid URL`).nullable(),verification_ttl_secs:vx().int().positive()})});function cR(){return lt({queryKey:[`runtime-config`],queryFn:async()=>{let e=await hb.get(`/runtime-config`);return sR.parse(e)},staleTime:1/0})}function lR(e){if(!e||typeof document>`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}Array(12).fill(0);var uR=1,dR=new class{constructor(){this.subscribe=e=>(this.subscribers.push(e),()=>{let t=this.subscribers.indexOf(e);this.subscribers.splice(t,1)}),this.publish=e=>{this.subscribers.forEach(t=>t(e))},this.addToast=e=>{this.publish(e),this.toasts=[...this.toasts,e]},this.create=e=>{let{message:t,...n}=e,r=typeof e?.id==`number`||e.id?.length>0?e.id:uR++,i=this.toasts.find(e=>e.id===r),a=e.dismissible===void 0?!0:e.dismissible;return this.dismissedToasts.has(r)&&this.dismissedToasts.delete(r),i?this.toasts=this.toasts.map(n=>n.id===r?(this.publish({...n,...e,id:r,title:t}),{...n,...e,id:r,dismissible:a,title:t}):n):this.addToast({title:t,...n,dismissible:a,id:r}),r},this.dismiss=e=>(e?(this.dismissedToasts.add(e),requestAnimationFrame(()=>this.subscribers.forEach(t=>t({id:e,dismiss:!0})))):this.toasts.forEach(e=>{this.subscribers.forEach(t=>t({id:e.id,dismiss:!0}))}),e),this.message=(e,t)=>this.create({...t,message:e}),this.error=(e,t)=>this.create({...t,message:e,type:`error`}),this.success=(e,t)=>this.create({...t,type:`success`,message:e}),this.info=(e,t)=>this.create({...t,type:`info`,message:e}),this.warning=(e,t)=>this.create({...t,type:`warning`,message:e}),this.loading=(e,t)=>this.create({...t,type:`loading`,message:e}),this.promise=(e,t)=>{if(!t)return;let n;t.loading!==void 0&&(n=this.create({...t,promise:e,type:`loading`,message:t.loading,description:typeof t.description==`function`?void 0:t.description}));let r=Promise.resolve(e instanceof Function?e():e),i=n!==void 0,a,o=r.then(async e=>{if(a=[`resolve`,e],z.isValidElement(e))i=!1,this.create({id:n,type:`default`,message:e});else if(pR(e)&&!e.ok){i=!1;let r=typeof t.error==`function`?await t.error(`HTTP error! status: ${e.status}`):t.error,a=typeof t.description==`function`?await t.description(`HTTP error! status: ${e.status}`):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(e instanceof Error){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(t.success!==void 0){i=!1;let r=typeof t.success==`function`?await t.success(e):t.success,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`success`,description:a,...o})}}).catch(async e=>{if(a=[`reject`,e],t.error!==void 0){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}}).finally(()=>{i&&(this.dismiss(n),n=void 0),t.finally==null||t.finally.call(t)}),s=()=>new Promise((e,t)=>o.then(()=>a[0]===`reject`?t(a[1]):e(a[1])).catch(t));return typeof n!=`string`&&typeof n!=`number`?{unwrap:s}:Object.assign(n,{unwrap:s})},this.custom=(e,t)=>{let n=t?.id||uR++;return this.create({jsx:e(n),id:n,...t}),n},this.getActiveToasts=()=>this.toasts.filter(e=>!this.dismissedToasts.has(e.id)),this.subscribers=[],this.toasts=[],this.dismissedToasts=new Set}},fR=(e,t)=>{let n=t?.id||uR++;return dR.addToast({title:e,...t,id:n}),n},pR=e=>e&&typeof e==`object`&&`ok`in e&&typeof e.ok==`boolean`&&`status`in e&&typeof e.status==`number`,mR=Object.assign(fR,{success:dR.success,info:dR.info,warning:dR.warning,error:dR.error,custom:dR.custom,message:dR.message,promise:dR.promise,dismiss:dR.dismiss,loading:dR.loading},{getHistory:()=>dR.toasts,getToasts:()=>dR.getActiveToasts()});lR(`[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}`);function hR({label:e,url:t,description:n,docsHref:r,className:i}){let[a,o]=(0,z.useState)(!1);async function s(){try{await Yr(t),o(!0),mR.success(`${e} copied`),setTimeout(()=>o(!1),2e3)}catch{mR.error(`Failed to copy`)}}return(0,B.jsxs)(`div`,{className:Jr(`space-y-2 rounded-xl border border-border bg-muted/40 p-3`,i),children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:e}),(0,B.jsxs)(`div`,{className:`relative`,children:[(0,B.jsx)(`code`,{className:`flex min-h-[40px] items-center break-all rounded-lg border border-border bg-background px-3 py-2 pr-11 font-mono text-12 leading-relaxed text-foreground`,children:t}),(0,B.jsxs)(Pi,{type:`button`,variant:`ghost`,size:`icon`,className:`absolute right-1.5 top-1.5 h-8 w-8 shrink-0`,onClick:()=>void s(),"aria-label":`Copy ${e}`,children:[a?(0,B.jsx)(li,{className:`h-3.5 w-3.5 text-success`}):(0,B.jsx)(gi,{className:`h-3.5 w-3.5`}),(0,B.jsxs)(`span`,{className:`sr-only`,children:[`Copy `,e]})]})]}),n?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:n}):null,r?(0,B.jsxs)(`a`,{href:r,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-primary hover:underline`,children:[`Learn more →`,(0,B.jsx)(vi,{className:`h-3 w-3`,"aria-hidden":`true`})]}):null]})}function gR(e){return e===`twitter`||e===`api-twitter`}function _R(e){return e?`${e}/api/v1/providers/callback`:null}function vR({slug:e}){let{data:t,isError:n,isLoading:r}=cR(),i=_R(t?.api_base_url),a=gR(e);return i?(0,B.jsxs)(`div`,{className:`space-y-2`,children:[(0,B.jsx)(hR,{label:a?`Twitter / X OAuth setup`:`NyxID callback URL`,url:i,description:a?`This integration requires an X app with OAuth 2.0 enabled in User authentication settings in X Developer Console. Configure the callback URL below as one of your app's redirect URIs.`:`Add this URL as an authorized redirect URI in your OAuth app's settings on the provider's developer console, or authorization will fail.`}),a?(0,B.jsxs)(`a`,{href:`https://developer.x.com/en/portal/dashboard`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-primary hover:underline`,children:[`Where do I get Client ID and Client Secret? Open Keys & Tokens in X Developer Console`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}):r?(0,B.jsx)(`p`,{className:`rounded-md border border-border bg-background/60 p-2 text-xs text-muted-foreground`,children:`Loading callback URL...`}):(0,B.jsx)(`p`,{className:`rounded-md border border-warning/30 bg-warning/10 p-2 text-xs text-warning`,children:n?`Couldn't load callback URL. Please retry. If this persists, contact support.`:`Callback URL not yet available. Please retry. If this persists, contact support.`})}function yR(e){let t=(e??`system`).toLowerCase();return t===`user`||t===`both`}async function bR(e,t,n,r,i,a,o,s,c){let l={service_slug:e,label:t};r&&(l.node_id=r),a&&(l.target_org_id=a);let u=i?.trim();if(u&&(l.endpoint_url=u),c)l.copy_oauth_client_from=c;else{let e=o?.trim(),t=s?.trim();e&&t&&(l.oauth_client_id=e,l.oauth_client_secret=t)}try{return await hb.post(`/keys`,l)}catch(e){throw e instanceof cb&&e.status>=400&&e.status<500&&(n.current=!1),e}}function xR(e){if(!(typeof window>`u`))try{fetch(`/api/v1/keys/${encodeURIComponent(e)}?only_if_pending=true`,{method:`DELETE`,credentials:`include`,keepalive:!0})}catch{}}function SR(e){if(!(typeof window>`u`))try{fetch(`/api/v1/cli-pairings/${encodeURIComponent(e)}/cancel`,{method:`POST`,credentials:`include`,keepalive:!0,headers:{"Content-Type":`application/json`},body:`{}`})}catch{}}function CR(e,t,n,r){if(!(typeof window>`u`))try{fetch(`/api/v1/cli-pairings/${encodeURIComponent(e)}/complete`,{method:`POST`,credentials:`include`,keepalive:!0,headers:{"Content-Type":`application/json`},body:JSON.stringify({ack:{acknowledged:!0,service_id:t,slug:n,label:r}})})}catch{}}async function wR(e){if(!e)return{kind:`unknown`};try{if((await hb.delete(`/keys/${encodeURIComponent(e)}?only_if_pending=true`)).deleted===!0)return{kind:`deleted`};try{let t=await hb.get(`/keys/${encodeURIComponent(e)}`);return t.status===`active`?{kind:`active`,key:t}:{kind:`unknown`}}catch{return{kind:`unknown`}}}catch{return{kind:`unknown`}}}async function TR(e,t,n,r,i){let a=i.current;if(a)try{await a}catch{}let o=t.current,s=n.current,c=r.current;t.current=null,n.current=!1;let l=s&&!c;if(o)try{let t=await hb.delete(`/keys/${encodeURIComponent(o)}?only_if_pending=true`);if(t.deleted===!0&&s)l=!0;else if(t.deleted===!1){try{let t=await hb.get(`/keys/${encodeURIComponent(o)}`);await hb.post(`/cli-pairings/${encodeURIComponent(e)}/complete`,{ack:{acknowledged:!0,service_id:t.id,slug:t.slug,label:t.label}})}catch{}return}else l=!1}catch{}if(l)try{await _P(e)}catch{}}function ER({providerId:e,slug:t,label:n,nodeId:r,targetOrgId:i,endpointUrl:a,pairingId:o,credentialMode:s,documentationUrl:c,scopeOverride:l,reconnectKeyId:u,baselineAuthorizedAt:d,onSuccess:f,onCancel:p}){let m=!!u,[h,g]=(0,z.useState)(!m&&yR(s)?`checking-credentials`:`starting`),[_,v]=(0,z.useState)(null),[y,b]=(0,z.useState)(null),[x,S]=(0,z.useState)(``),[C,w]=(0,z.useState)(``),[T,E]=(0,z.useState)(null),[ee,D]=(0,z.useState)([]),O=(0,z.useRef)(u??null),k=(0,z.useRef)(!1),A=(0,z.useRef)(!1),j=(0,z.useRef)(!1),M=(0,z.useRef)(null),N=(0,z.useRef)(!1);(0,z.useEffect)(()=>{function e(){if(N.current||m)return;let e=O.current;if(e){xR(e),CR(o,e,t,n);return}j.current&&SR(o)}return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),!N.current&&(m||TR(o,O,A,j,M))}},[o,t,n,m]);async function P(){if(m)return`uncertain`;let e=M.current;if(e)try{await e}catch{}let t=O.current;O.current=null;let n=await wR(t);if(n.kind===`active`)return A.current=!1,N.current=!0,g(`done`),f({kind:`ai-key`,service_id:n.key.id,slug:n.key.slug,label:n.key.label}),`active`;let r=!j.current,i=n.kind===`deleted`;return A.current&&(i||r)?(A.current=!1,await _P(o),`released`):(A.current=!1,`uncertain`)}async function F(){k.current=!0,await P()!==`active`&&p()}(0,z.useEffect)(()=>{m||yR(s)&&(g(`needs-credentials`),(async()=>{try{D(((await hb.get(`/keys`)).keys??[]).filter(e=>e.status===`active`&&e.oauth_client_id&&e.api_key_id&&e.catalog_service_slug===t).map(e=>({id:e.api_key_id,slug:e.slug,oauthClientId:e.oauth_client_id})))}catch{}})())},[s,t]);function I(){!T&&(!x.trim()||!C.trim())||(v(null),g(`starting`))}(0,z.useEffect)(()=>{if(h!==`starting`)return;let s=!1;return k.current=!1,(async()=>{try{O.current||(await gP(o),A.current=!0);let c;if(O.current)c={id:O.current,status:`pending_auth`};else{j.current=!0;let e=bR(t,n,j,r,a,i,x,C,T??void 0);M.current=e;try{c=await e}finally{M.current===e&&(M.current=null)}O.current=c.id}if(s)return;if(c.status===`active`){await ne(c.id);return}let u=new URLSearchParams({redirect_path:`/keys/${c.id}`,key_id:c.id});l!==void 0&&u.set(`scope_override`,l.join(`,`));let d=await hb.get(`/providers/${encodeURIComponent(e)}/connect/oauth?${u.toString()}`);if(s)return;if(!d.authorization_url)throw Error(`provider did not return an authorization_url`);if(b(d.authorization_url),!window.open(d.authorization_url,`_blank`,`noopener,noreferrer`)){g(`waiting`),v(`Browser blocked the popup. Use the button below to open the provider sign-in.`),await te(c.id);return}g(`waiting`),await te(c.id)}catch(e){if(s)return;g(`error`),v(kR(e)),P()}})(),()=>{s=!0}},[h]),(0,z.useEffect)(()=>()=>{k.current=!0},[]);async function te(e){let t=d??null;await aR({keyId:e,getKey:e=>hb.get(`/keys/${encodeURIComponent(e)}`),completeWithKey:ne,isCancelled:()=>k.current,...m?{isComplete:e=>e.status===`active`&&!!e.last_authorized_at&&e.last_authorized_at!==t}:{},onTerminalFailure:()=>{g(`error`),v(`Authorization didn't complete (it may have been canceled or denied on the provider page). Cancel and re-run to try again.`)},onTimeout:()=>{g(`error`),v(`We didn't see authorization complete within 5 minutes. If you canceled on the provider page or it's taking longer than expected, cancel and re-run.`)}})}async function ne(e){let t=await hb.get(`/keys/${encodeURIComponent(e)}`);k.current||(N.current=!0,g(`done`),f({kind:`ai-key`,service_id:t.id,slug:t.slug,label:t.label}))}if(h===`needs-credentials`){let e=ee.length>0,n=!!T;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:e?`OAuth app credentials`:`Paste your OAuth app credentials`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`This provider expects you to register your own OAuth app and supply the resulting Client ID and Client Secret.`}),c?(0,B.jsxs)(`a`,{href:c,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-muted-foreground underline-offset-2 hover:underline`,children:[`How to create an OAuth app`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}),(0,B.jsx)(vR,{slug:t}),e?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(Vi,{className:`text-12 font-medium`,children:`Use credentials from an existing connection`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[ee.map(e=>(0,B.jsxs)(`button`,{type:`button`,onClick:()=>{E(e.id),S(``),w(``)},className:`flex items-center gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors ${T===e.id?`border-primary bg-primary/5`:`border-border hover:border-primary/50`}`,children:[(0,B.jsx)(`span`,{className:`flex-1 truncate`,children:e.slug}),(0,B.jsx)(`span`,{className:`shrink-0 text-xs text-muted-foreground`,children:e.oauthClientId})]},e.id)),(0,B.jsx)(`button`,{type:`button`,onClick:()=>{E(null)},className:`flex items-center gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors ${T?`border-border hover:border-primary/50`:`border-primary bg-primary/5`}`,children:`Enter new credentials`})]})]}):null,n?null:(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-aikey-oauth-client-id`,children:`Client ID`}),(0,B.jsx)(FM,{id:`pair-aikey-oauth-client-id`,value:x,onChange:e=>{S(e.target.value)},autoFocus:!0,autoComplete:`off`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-aikey-oauth-client-secret`,children:`Client Secret`}),(0,B.jsx)(FM,{id:`pair-aikey-oauth-client-secret`,type:`password`,value:C,onChange:e=>{w(e.target.value)},autoComplete:`off`})]})]}),_?(0,B.jsx)(OR,{message:_}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:I,disabled:!n&&(!x.trim()||!C.trim()),children:n?`Continue with existing credentials`:`Save and continue`}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void F(),children:`Cancel`})]})}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:`Complete sign-in on the provider`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`We opened a new tab where you'll authorize NyxID. When it completes, come back — this page will finish automatically.`})]}),h===`checking-credentials`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Checking provider credentials...`]}):h===`starting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Creating placeholder service...`]}):h===`waiting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Waiting for provider authorization...`]}):null,y&&h===`waiting`?(0,B.jsxs)(`a`,{href:y,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center justify-center gap-2 rounded-lg border bg-muted/40 px-3 py-2 text-12 hover:bg-muted`,children:[`Reopen provider sign-in`,(0,B.jsx)(vi,{className:`h-4 w-4`})]}):null,_?(0,B.jsx)(OR,{message:_}):null,h===`done`?null:(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void F(),children:`Cancel`})]})}function DR({providerId:e,slug:t,label:n,nodeId:r,targetOrgId:i,endpointUrl:a,pairingId:o,scopeOverride:s,onSuccess:c,onCancel:l}){let[u,d]=(0,z.useState)(null),[f,p]=(0,z.useState)(null),[m,h]=(0,z.useState)(`starting`),[g,_]=(0,z.useState)(null),[v,y]=(0,z.useState)(!1),[b,x]=(0,z.useState)(0),S=(0,z.useRef)(0),C=(0,z.useRef)(null),w=(0,z.useRef)(!1),T=(0,z.useRef)(!1),E=(0,z.useRef)(!1),ee=(0,z.useRef)(null),D=(0,z.useRef)(!1);async function O(){let e=ee.current;if(e)try{await e}catch{}let t=C.current;C.current=null;let n=await wR(t);if(n.kind===`active`)return T.current=!1,D.current=!0,h(`done`),c({kind:`ai-key`,service_id:n.key.id,slug:n.key.slug,label:n.key.label}),`active`;let r=!E.current,i=n.kind===`deleted`;return T.current&&(i||r)?(T.current=!1,await _P(o),`released`):(T.current=!1,`uncertain`)}async function k(){w.current=!0,S.current+=1,await O()!==`active`&&l()}(0,z.useEffect)(()=>{function e(){if(D.current)return;let e=C.current;if(e){xR(e),CR(o,e,t,n);return}E.current&&SR(o)}return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),!D.current&&TR(o,C,T,E,ee)}},[o,t,n]),(0,z.useEffect)(()=>(w.current=!1,j(),()=>{w.current=!0,S.current+=1}),[]),(0,z.useEffect)(()=>{if(m!==`waiting`)return;let e=window.setInterval(()=>{x(e=>e>0?e-1:0)},1e3);return()=>{window.clearInterval(e)}},[m]);function A(e){let t=Math.floor(e/60),n=e%60;return`${String(t)}:${String(n).padStart(2,`0`)}`}async function j(){let l=++S.current;h(`starting`),_(null);try{let u=C.current;if(!u){await gP(o),T.current=!0,E.current=!0;let e=bR(t,n,E,r,a,i);ee.current=e;let s;try{s=await e}finally{ee.current===e&&(ee.current=null)}if(u=s.id,C.current=u,l!==S.current)return;if(s.status===`active`){let e=await hb.get(`/keys/${encodeURIComponent(u)}`);if(l!==S.current)return;D.current=!0,h(`done`),c({kind:`ai-key`,service_id:e.id,slug:e.slug,label:e.label});return}}let f=new URLSearchParams;u&&f.set(`key_id`,u),s!==void 0&&f.set(`scope_override`,s.join(`,`));let m=f.toString(),g=await hb.post(`/providers/${encodeURIComponent(e)}/connect/device-code/initiate${m?`?${m}`:``}`,{});if(l!==S.current)return;d(g.user_code),p(g.verification_uri);let v=typeof window<`u`?Number(new URLSearchParams(window.location.search).get(`expires_in_override`)):NaN,y=Number.isFinite(v)&&v>0?v:Number(g.expires_in)>0?Number(g.expires_in):900;x(y),h(`waiting`);let b=Number(g.interval)||5,k=`/providers/${encodeURIComponent(e)}/connect/device-code/poll`,A=Date.now()+y*1e3;for(;Date.now(){y(!1)},2e3)}catch{}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:`Authorize via device code`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Open the verification URL, enter the code, and complete sign-in on the provider. This page will finish automatically.`})]}),m===`starting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Requesting device code...`]}):m===`waiting`&&u&&f?(0,B.jsxs)(`div`,{className:`flex flex-col gap-3 rounded-lg border bg-muted/30 p-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,B.jsx)(`span`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Code`}),b>0?(0,B.jsxs)(`span`,{className:`text-xs tabular-nums text-muted-foreground`,children:[`Expires in `,A(b)]}):null]}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(`code`,{className:`rounded bg-background px-3 py-1.5 font-mono text-lg`,children:u}),(0,B.jsxs)(Pi,{variant:`outline`,onClick:()=>void M(),children:[(0,B.jsx)(Ni,{children:(0,B.jsx)(gi,{className:`h-3.5 w-3.5`})}),v?`Copied`:`Copy`]})]})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`span`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Visit`}),(0,B.jsxs)(`a`,{href:f,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1.5 text-12 underline-offset-2 hover:underline`,children:[f,(0,B.jsx)(vi,{className:`h-3.5 w-3.5`})]})]}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-3 w-3 animate-spin`}),`Waiting for authorization...`]})]}):m===`expired`?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:`The device code expired before authorization completed.`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void j(),children:`Request a new code`})]}):null,g?(0,B.jsx)(OR,{message:g}):null,m===`done`?null:(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void k(),children:`Cancel`})]})}function OR({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}function kR(e){return e instanceof cb||e instanceof Error?e.message:`Something went wrong. Please try again.`}function AR(e){return new Promise(t=>{window.setTimeout(t,e)})}function jR(e){let t=(e.provider_type??``).toLowerCase();return(e.service_type??`http`)===`ssh`?`ssh`:t===`oauth2`?`oauth`:t===`device_code`?`device-code`:e.requires_credential===!1?`no-auth`:Array.isArray(e.token_exchange_credential_fields)&&e.token_exchange_credential_fields.length>0?`token-exchange`:e.requires_gateway_url?`gateway-url`:`paste-key`}function MR(e,t){switch(e){case`no-auth`:return`1-click connect`;case`gateway-url`:return`URL + API key`;case`token-exchange`:return`${(t.token_exchange_credential_fields??[]).length} fields`;case`oauth`:return`OAuth sign-in`;case`device-code`:return`device code`;case`ssh`:return`SSH cert`;case`paste-key`:return`paste API key`}}var NR={oauth:`OAuth`,"device-code":`Device code`,ssh:`SSH`};function PR(e,t){if(!t)return 0;let n=e.toLowerCase(),r=t.toLowerCase(),i=n.indexOf(r);if(i>=0)return i;let a=0,o=0,s=100,c=0;for(;a{let e=await hb.get(`/catalog?include_all=true`);return e.entries??e.services??[]}}),o=r??[],s=t.trim(),c=s?o.map(e=>{let t=PR(e.slug,s),n=PR(e.name??``,s),r=t===null?n:n===null?t:Math.min(t,n);return r===null?null:{entry:e,score:r}}).filter(e=>e!==null).sort((e,t)=>e.score-t.score).map(e=>e.entry):o;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(`label`,{htmlFor:`catalog-search`,className:`text-xs font-medium uppercase tracking-wide text-muted-foreground`,children:`Search`}),(0,B.jsx)(FM,{id:`catalog-search`,type:`search`,placeholder:`search services…`,autoComplete:`off`,spellCheck:!1,value:t,onChange:e=>{n(e.target.value)}})]}),(0,B.jsx)(IR,{children:`Simple setup`}),i?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Loading catalog…`}):a?(0,B.jsx)(`p`,{className:`text-12 text-destructive`,children:a instanceof cb?`Couldn't load the catalog: ${a.message} (${String(a.status)})`:`Couldn't load the catalog. Check the CLI logs for details.`}):c.length===0?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:s?`No services match your search.`:`Catalog is empty.`}):(0,B.jsx)(`div`,{className:`max-h-[420px] overflow-y-auto overscroll-contain pr-1`,role:`list`,children:(0,B.jsx)(`div`,{className:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:c.map(t=>(0,B.jsx)(LR,{entry:t,onClick:()=>{e(t.slug)}},t.slug))})}),(0,B.jsx)(IR,{children:`Advanced`}),(0,B.jsx)(`div`,{className:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:(0,B.jsx)(RR,{onClick:()=>{e(`__custom__`)}})})]})}function IR({children:e}){return(0,B.jsx)(`div`,{className:`text-xs font-medium uppercase tracking-wide text-muted-foreground`,children:e})}function LR({entry:e,onClick:t}){let n=jR(e),r=NR[n];return(0,B.jsxs)(`button`,{type:`button`,onClick:t,role:`listitem`,className:`group relative flex min-h-[132px] flex-col items-start gap-1 rounded-xl border border-border/50 bg-card/60 p-4 text-left transition-colors duration-300 hover:border-hairline-strong hover:bg-card focus-visible:outline-none`,children:[r?(0,B.jsx)(`span`,{className:`absolute right-3 top-3 rounded-full border border-border bg-muted/60 px-2 py-0.5 text-10 uppercase tracking-wide text-muted-foreground`,children:r}):null,(0,B.jsxs)(`div`,{className:`flex w-full items-center gap-2`,children:[(0,B.jsx)(kM,{slug:e.slug,size:`sm`}),(0,B.jsx)(`span`,{className:`text-13 font-semibold text-foreground`,children:e.name||e.slug})]}),e.description?(0,B.jsx)(`span`,{className:`line-clamp-2 text-xs text-muted-foreground`,children:e.description}):null,(0,B.jsx)(`span`,{className:`mt-auto text-11 text-text-tertiary`,children:MR(n,e)})]})}function RR({onClick:e}){return(0,B.jsxs)(`button`,{type:`button`,onClick:e,className:`flex min-h-[132px] flex-col items-start gap-1 rounded-xl border border-dashed border-border/50 bg-transparent p-4 text-left transition-colors duration-300 hover:border-hairline-strong hover:bg-card/40 focus-visible:outline-none`,children:[(0,B.jsx)(`span`,{className:`text-13 font-semibold text-foreground`,children:`Custom / self-hosted…`}),(0,B.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`For anything that isn't in the catalog above — paste your own endpoint URL + credential.`})]})}function zR(e){let t=(e.provider_type??``).toLowerCase();return t===`oauth2`?`oauth`:t===`device_code`?`device-code`:e.requires_credential===!1?`no-auth`:Array.isArray(e.token_exchange_credential_fields)&&e.token_exchange_credential_fields.length>0?`token-exchange`:e.requires_credential?`api-key`:`other`}function BR({prefill:e,pairingId:t,onSuccess:n,onSlugPicked:r}){let[i,a]=(0,z.useState)(e.custom?`__custom__`:e.slug??``),[o,s]=(0,z.useState)(e.org_id??null),c=i.trim(),l=(0,z.useRef)(c?null:``);(0,z.useEffect)(()=>{l.current!==c&&(l.current=c,r?.(c))},[c,r]);let{data:u,isLoading:d,error:f}=lt({queryKey:[`cli-pair`,`catalog`,c],queryFn:async()=>hb.get(`/catalog/${encodeURIComponent(c)}`),enabled:!!c&&c!==`__custom__`}),p=f?f instanceof cb?f.message:`Couldn't load catalog entry "${c}".`:null;if(e.reconnect_key_id)return(0,B.jsx)(VR,{keyId:e.reconnect_key_id,initialScopeOverride:e.scope_override??null,pairingId:t,onSuccess:n});let m=!c&&!p,h=c===`__custom__`,g=m?`Add an AI service`:h?`Custom / self-hosted service`:`Connect service`,_=m?`Pick a service to connect. Simple-bearer APIs (OpenAI, Anthropic, Gemini) land in the guided form. Anything else — self-hosted, OAuth, device code, custom endpoint — goes to the power-user form.`:h?`For services not in the catalog — paste your own endpoint URL and credential.`:`Your CLI wants to add ${c||`a service`} to NyxID. Confirm the details here.`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:g}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:_})]}),(0,B.jsx)(HR,{value:o,onChange:s}),m?(0,B.jsx)(FR,{onSelect:a}):h?(0,B.jsx)(qR,{prefill:e,targetOrgId:o,pairingId:t,onSuccess:n,onBack:()=>{a(``)}}):d?(0,B.jsx)(XL,{className:`h-24 w-full`}):p?(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsx)(XR,{message:p}),(0,B.jsx)(FR,{onSelect:a})]}):u?(0,B.jsx)(JR,{entry:u,prefill:e,targetOrgId:o,pairingId:t,onSuccess:n}):null]})}function VR({keyId:e,initialScopeOverride:t,pairingId:n,onSuccess:r}){let{data:i,isLoading:a,error:o}=lt({queryKey:[`cli-pair`,`manage-scopes`,`key`,e],queryFn:()=>hb.get(`/keys/${encodeURIComponent(e)}`)}),s=i?.catalog_service_slug??i?.slug??``,{data:c,isLoading:l,error:u}=lt({queryKey:[`cli-pair`,`manage-scopes`,`catalog`,s],queryFn:()=>hb.get(`/catalog/${encodeURIComponent(s)}`),enabled:!!s}),d=i?.granted_scopes??[],f=c?.default_scopes??[],p=t&&t.length>0?t:null,m=p??(d.length>0?d:f),[h,g]=(0,z.useState)(null),_=$L(h??m,c?.scope_catalog??[]),v=h!==null||p!==null||d.length>0||c?.scope_catalog?.some(e=>e.required)?_:void 0,y=g,[b,x]=(0,z.useState)(!1),S=(()=>{let e=o??u;return e?e instanceof cb?e.message:`Couldn't load this connection.`:null})();if(a||s&&l)return(0,B.jsx)(XL,{className:`h-24 w-full`});if(S)return(0,B.jsx)(XR,{message:S});if(!i||!c)return(0,B.jsx)(XR,{message:`Connection not found.`});if((c.provider_type??``).toLowerCase()!==`oauth2`||!c.provider_config_id||c.supports_oauth_scopes===!1)return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Manage permissions`}),(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[c.name,` doesn't support managing scopes here — its permissions are fixed by the provider.`]})]});let C=c.scope_removal===`unsupported`?d:[];return b?(0,B.jsx)(ER,{providerId:c.provider_config_id,slug:i.slug,label:i.label,pairingId:n,credentialMode:c.credential_mode,documentationUrl:c.documentation_url,scopeOverride:v,reconnectKeyId:e,baselineAuthorizedAt:i.last_authorized_at??null,onSuccess:r,onCancel:()=>{x(!1)}}):(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Manage permissions`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Adjust what `,i.label,` can do, then re-authorize at the provider. Your CLI is waiting for you to finish here.`]})]}),(0,B.jsxs)(`div`,{className:`flex items-start gap-3 rounded-lg border bg-muted/30 p-3`,children:[c.icon_url?(0,B.jsx)(`img`,{src:c.icon_url,alt:``,className:`h-8 w-8 rounded`,loading:`lazy`}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:c.name}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i.slug})]})]}),(0,B.jsx)(nR,{catalog:c.scope_catalog??[],defaultScopes:c.default_scopes??[],value:_,onChange:y,lockedScopes:C,grantedScopes:d,providerName:c.name,idPrefix:`pair-manage-scope`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>x(!0),children:`Re-authorize with these permissions`})]})}function HR({value:e,onChange:t}){let{data:n}=hF();return(n??[]).some(e=>e.your_role===`admin`)?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/30 px-3 py-2`,children:[(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-xs font-medium text-muted-foreground`,children:[(0,B.jsx)(si,{className:`h-3.5 w-3.5`}),`Owner`]}),(0,B.jsx)(`div`,{className:`w-[220px]`,children:(0,B.jsx)(QL,{value:e,onChange:t,label:`Owner`})})]}),(0,B.jsx)(`p`,{className:`mt-1 text-11 text-muted-foreground`,children:`Org-owned services are shared with every admin of that organization and can be proxied by its members.`})]}):null}function UR(e){switch(e){case`ifttt_webhook`:return``;case`header`:return`X-API-Key`;case`query`:return`key`;case`path`:return`bot`;case`body`:return`app_secret`;default:return`Authorization`}}function WR(e){switch(e){case`bearer`:case`header`:case`query`:case`path`:case`basic`:case`body`:case`ifttt_webhook`:case`bot_bearer`:case`none`:return e;default:return`bearer`}}function GR(e){return e===`header`||e===`query`||e===`path`||e===`body`}function KR(){return(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`text-destructive ml-0.5`,children:`*`})}function qR({prefill:e,targetOrgId:t,pairingId:n,onSuccess:r,onBack:i}){let[a,o]=(0,z.useState)(e.label??``),[s,c]=(0,z.useState)(e.endpoint_url??``),[l,u]=(0,z.useState)(``),[d,f]=(0,z.useState)(WR(e.auth_method)),[p,m]=(0,z.useState)(e.auth_key_name??UR(WR(e.auth_method))),[h,g]=(0,z.useState)(e.custom_slug??``),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null),x=e.via_node?.trim()??``,S=a.trim(),C=s.trim(),w=l.trim(),T=d!==`none`,E=GR(d),ee=_||!S||!C||T&&!w,D=d===`bot_bearer`?`Bot token`:d===`basic`?`user:pass`:d===`body`?`${p.trim()||UR(d)} value`:`API key / credential`;async function O(){v(!0),b(null);try{let e={label:S,endpoint_url:C,auth_method:d};T&&(e.credential=w),E&&(e.auth_key_name=p.trim()||UR(d));let i=h.trim();i&&(e.slug=i),x&&(e.node_id=x),t&&(e.target_org_id=t),await gP(n);let a=await vP(n,()=>hb.post(`/keys`,e));r({kind:`ai-key`,service_id:a.id,slug:a.slug,label:a.label})}catch(e){let t=(e instanceof cb?e.message:null)??e?.message;b(t&&t.length>0?t:`Couldn't connect this service. Please try again.`)}finally{v(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-label`,children:`Label`}),(0,B.jsx)(KR,{})]}),(0,B.jsx)(FM,{id:`pair-custom-label`,value:a,onChange:e=>{o(e.target.value)},placeholder:`e.g. My Self-hosted OpenAI Proxy`,autoFocus:!0,"aria-required":`true`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Shown everywhere in the CLI and web UI.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-endpoint`,children:`Endpoint URL`}),(0,B.jsx)(KR,{})]}),(0,B.jsx)(FM,{id:`pair-custom-endpoint`,value:s,onChange:e=>{c(e.target.value)},placeholder:`https://api.example.com`,"aria-required":`true`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`The base URL NyxID proxies requests to.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-auth-method`,children:`Auth method`}),(0,B.jsx)(KR,{})]}),(0,B.jsxs)(`select`,{id:`pair-custom-auth-method`,value:d,onChange:e=>{let t=e.target.value;f(t),(!p.trim()||[`Authorization`,`X-API-Key`,`key`,`bot`,`app_secret`].includes(p.trim()))&&m(UR(t))},className:`flex h-10 w-full rounded-lg border border-input bg-transparent px-[14px] py-2 text-13 text-foreground focus-visible:outline-none focus-visible:border-input-focus`,"aria-required":`true`,children:[(0,B.jsx)(`option`,{value:`bearer`,children:`bearer (Authorization: Bearer …)`}),(0,B.jsx)(`option`,{value:`bot_bearer`,children:`bot_bearer (Authorization: Bot …)`}),(0,B.jsx)(`option`,{value:`header`,children:`header (custom header)`}),(0,B.jsx)(`option`,{value:`query`,children:`query (?key=…)`}),(0,B.jsx)(`option`,{value:`path`,children:`path (path-prefix injection)`}),(0,B.jsx)(`option`,{value:`ifttt_webhook`,children:`IFTTT Webhooks (raw key)`}),(0,B.jsx)(`option`,{value:`basic`,children:`basic (Authorization: Basic …)`}),(0,B.jsx)(`option`,{value:`body`,children:`body (JSON-body field injection)`}),(0,B.jsx)(`option`,{value:`none`,children:`none (no auth injection)`})]}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`How NyxID attaches the credential to outgoing requests.`})]}),T?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-credential`,children:D}),(0,B.jsx)(KR,{})]}),(0,B.jsx)(FM,{id:`pair-custom-credential`,type:`password`,value:l,onChange:e=>{u(e.target.value)},placeholder:d===`basic`?`user:pass`:`sk-...`,autoFocus:!!e.custom,"aria-required":`true`}),(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`Pasted once, encrypted at rest.`,d===`basic`?` Format: user:pass.`:``]})]}):null,E?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-auth-key-name`,children:d===`header`?`Header name`:d===`query`?`Query parameter name`:d===`path`?`Path prefix segment`:`Body field name`}),(0,B.jsx)(FM,{id:`pair-custom-auth-key-name`,value:p,onChange:e=>{m(e.target.value)},placeholder:UR(d)})]}):null,x?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 px-3 py-2`,children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:`Routed via node`}),(0,B.jsx)(`code`,{className:`font-mono text-11 text-muted-foreground`,children:x}),(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground mt-1`,children:`Credential will be encrypted and pushed to this node over the existing WebSocket channel. NyxID never logs it.`})]}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-slug`,children:`Custom slug (optional)`}),(0,B.jsx)(FM,{id:`pair-custom-slug`,value:h,onChange:e=>{g(e.target.value)},placeholder:`auto-generated from label`}),(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`URL segment at `,(0,B.jsx)(`code`,{children:`/proxy/s//…`}),`. Leave blank to let NyxID derive it from the label.`]})]})]}),y?(0,B.jsx)(XR,{message:y}):null,(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,B.jsx)(Pi,{variant:`outline`,onClick:i,disabled:_,children:`← Back`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void O(),disabled:ee,children:_?`Connecting…`:`Connect service`})]})]})}function JR({entry:e,prefill:t,targetOrgId:n,pairingId:r,onSuccess:i}){let[a,o]=(0,z.useState)(!0),s=!!(e.platform_key?.available&&!t.via_node&&a),c=s?`no-auth`:zR(e),[l,u]=(0,z.useState)(t.label??e.name),[d,f]=(0,z.useState)(``),[p,m]=(0,z.useState)(t.endpoint_url??``),[h,g]=(0,z.useState)({}),[_,v]=(0,z.useState)(e.default_scopes??[]),y=$L(_,e.scope_catalog??[]),[b,x]=(0,z.useState)(!1),[S,C]=(0,z.useState)(null),w=t.via_node?.trim()??``,[T,E]=(0,z.useState)(!1);async function ee(){x(!0),C(null);try{let t={service_slug:e.slug,label:l};if(s&&(t.use_platform_key=!0),c===`token-exchange`&&!w){let n=e.token_exchange_credential_fields??[],r={};for(let e of n){let t=h[e.name]?.trim();if(!t){C(`${e.label||e.name} is required.`),x(!1);return}r[e.name]=t}t.credential=JSON.stringify(r)}else c===`api-key`&&e.requires_credential&&!w&&(t.credential=d);!s&&(e.requires_gateway_url||p)&&(t.endpoint_url=p),w&&(t.node_id=w),n&&(t.target_org_id=n),await gP(r);let a=await vP(r,()=>hb.post(`/keys`,t));i({kind:`ai-key`,service_id:a.id,slug:a.slug,label:a.label})}catch(e){let t=(e instanceof cb?e.message:null)??e?.message;C(t&&t.length>0?t:`Couldn't create the service. Please try again.`)}finally{x(!1)}}async function D(e){try{await hb.post(`/cli-pairings/${encodeURIComponent(r)}/cancel`,{})}catch{}if(e){if(window.__WIZARD_BOOTSTRAP__?.context===`local`){alert(`This auth shape isn't supported in the CLI wizard. Open your NyxID dashboard and complete setup on the Keys page (tab: External Services). You can close this tab now.`);return}window.location.assign(e)}else window.history.back()}if(e.service_type===`ssh`)return(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[e.name,` is an SSH service. Use`,` `,(0,B.jsx)(`code`,{children:`nyxid service add-ssh`}),` from your CLI instead (certificate-based auth, not a credential binding).`]}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void D(null),children:`Go Back`})]});if(c===`other`||(c===`oauth`||c===`device-code`)&&!e.provider_config_id){let t=`/keys?tab=services&slug=${encodeURIComponent(e.slug)}`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[e.name,` uses `,(0,B.jsx)(`code`,{children:e.auth_method}),` auth, which isn't supported via remote pairing. Complete setup on the main Keys page. Your CLI will receive a cancel and print a "finish in browser" hint.`]}),(0,B.jsxs)(Pi,{variant:`primary`,onClick:()=>void D(t),className:`justify-center gap-2`,children:[`Open Keys page`,(0,B.jsx)(vi,{className:`h-4 w-4`})]})]})}let O=p.trim()||t.endpoint_url,k=e.supports_oauth_scopes!==!1&&(c===`oauth`||c===`device-code`&&e.device_code_format!==`openai`),A=k?y:void 0;if(T&&c===`oauth`&&e.provider_config_id)return(0,B.jsx)(ER,{providerId:e.provider_config_id,slug:e.slug,label:l,nodeId:t.via_node,targetOrgId:n,endpointUrl:O,pairingId:r,credentialMode:e.credential_mode,documentationUrl:e.documentation_url,scopeOverride:A,onSuccess:i,onCancel:()=>{E(!1)}});if(T&&c===`device-code`&&e.provider_config_id)return(0,B.jsx)(DR,{providerId:e.provider_config_id,slug:e.slug,label:l,nodeId:t.via_node,targetOrgId:n,endpointUrl:O,pairingId:r,documentationUrl:e.documentation_url,scopeOverride:A,onSuccess:i,onCancel:()=>{E(!1)}});let j=c===`api-key`&&e.requires_credential,M=e.slug===`api-supabase`,N=b?`Creating...`:w?`Connect via node`:c===`oauth`?`Continue with provider sign-in`:c===`device-code`?`Get device code`:c===`no-auth`?`Connect`:`Create Service`,P=c===`token-exchange`?(e.token_exchange_credential_fields??[]).every(e=>(h[e.name]??``).trim().length>0):!0,F=b||!l.trim()||j&&!w&&!d.trim()||!s&&e.requires_gateway_url&&!p.trim()||!w&&!P;function I(){c===`oauth`||c===`device-code`?E(!0):ee()}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex items-start gap-3 rounded-lg border bg-muted/30 p-3`,children:[e.icon_url?(0,B.jsx)(`img`,{src:e.icon_url,alt:``,className:`h-8 w-8 rounded`,loading:`lazy`}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:e.name}),e.description?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:e.description}):null,(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`Auth: `,(0,B.jsx)(`code`,{children:e.auth_method})]})]})]}),e.platform_key?.available&&!w&&(0,B.jsx)(YL,{value:s,onChange:o,platformPrice:e.platform_key.pricing,byokPrice:e.byok_pricing,legacyBillable:e.billing?.platform_billable,resaleBillable:e.billing?.resale_billable,disabled:b}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsx)(YR,{label:`Label`,htmlFor:`pair-aikey-label`,children:(0,B.jsx)(FM,{id:`pair-aikey-label`,value:l,onChange:e=>{u(e.target.value)},autoFocus:!0})}),!s&&e.requires_gateway_url?(0,B.jsx)(YR,{label:M?`Supabase Project URL`:`Instance URL`,htmlFor:`pair-aikey-url`,children:(0,B.jsx)(FM,{id:`pair-aikey-url`,value:p,onChange:e=>{m(e.target.value)},placeholder:M?`https://project-ref.supabase.co`:`https://your-instance.example.com`})}):null,j&&!w?(0,B.jsxs)(YR,{label:M?`Supabase API key`:`API key`,htmlFor:`pair-aikey-credential`,children:[(0,B.jsx)(FM,{id:`pair-aikey-credential`,type:`password`,autoComplete:`off`,value:d,onChange:e=>{f(e.target.value)},placeholder:M?`sb_secret_... or sb_publishable_...`:`sk-...`}),e.api_key_url?(0,B.jsxs)(`a`,{href:e.api_key_url,target:`_blank`,rel:`noopener noreferrer`,className:`mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground underline-offset-2 hover:underline`,children:[`Get an API key`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}):null,c===`token-exchange`&&!w?(e.token_exchange_credential_fields??[]).map(e=>(0,B.jsx)(YR,{label:e.label||e.name,htmlFor:`pair-aikey-tx-${e.name}`,children:(0,B.jsx)(FM,{id:`pair-aikey-tx-${e.name}`,type:e.secret?`password`:`text`,autoComplete:`off`,value:h[e.name]??``,onChange:t=>{let n=t.target.value;g(t=>({...t,[e.name]:n}))},placeholder:e.placeholder??``})},e.name)):null,k?(0,B.jsx)(nR,{catalog:e.scope_catalog??[],defaultScopes:e.default_scopes??[],value:y,onChange:v,customPlaceholder:c===`oauth`?`e.g. media.write`:`e.g. repo,read:org`,idPrefix:`pair-aikey-scope`}):c===`device-code`?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`This provider does not accept additional scopes — they are fixed by the upstream client registration.`}):null,w?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 px-3 py-2`,children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:`Routed via node`}),(0,B.jsx)(`code`,{className:`font-mono text-11 text-muted-foreground`,children:w}),(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground mt-1`,children:`Credential will be configured on the node agent. NyxID never sees or stores it.`})]}):null,c===`no-auth`?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`This service doesn't need a credential. Click Connect to add it to your services.`}):null]}),S?(0,B.jsx)(XR,{message:S}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:I,disabled:F,children:N})]})}function YR({label:e,htmlFor:t,children:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:t,children:e}),n]})}function XR({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}var ZR=!1,QR=null;function $R(e){if(QR=e,ZR)return;ZR=!0;let t=window.fetch.bind(window);window.fetch=async(e,n)=>{let r=cz(e,n),i=new URL(r.url,window.location.origin);if(i.origin!==window.location.origin)return t(r);if(i.pathname.startsWith(`/api/v1/cli-pairings/`))return i.pathname.endsWith(`/cancel`)?t(`/api/proxy/cancel-unload`,{method:`POST`,headers:uz({"content-type":`application/json`}),body:`{}`,keepalive:n?.keepalive??!1}):new Response(JSON.stringify({ok:!0}),{status:200,headers:{"content-type":`application/json`}});if(r.method===`DELETE`&&/^\/api\/v1\/keys\/[^/]+$/.test(i.pathname)&&i.searchParams.get(`only_if_pending`)===`true`){let e=i.pathname.split(`/`).pop()??``;return t(`/api/proxy/abandon-placeholder`,{method:`POST`,headers:uz({"content-type":`application/json`}),body:JSON.stringify({key_id:e})})}if(i.pathname.startsWith(`/api/v1/`)){let e=new URL(i.toString());e.pathname=`/api/proxy${i.pathname}`;let a=uz(lz(r,n)),o=await t(e.toString(),{method:r.method,headers:a,body:await dz(r),credentials:r.credentials,signal:r.signal});return nz(o),o}return t(r)}}var ez=`nyxid-wizard-upstream-error`;function tz(e){let t=t=>{if(!(t instanceof CustomEvent))return;let n=t.detail?.kind;(n===`timeout`||n===`unreachable`)&&e(n)};return window.addEventListener(ez,t),()=>{window.removeEventListener(ez,t)}}function nz(e){if(e.ok)return;let t=e.headers.get(`content-type`);!t||!t.toLowerCase().includes(`application/json`)||e.clone().json().then(e=>{if(!e||typeof e!=`object`)return;let t=e.error;t===`upstream_timeout`?window.dispatchEvent(new CustomEvent(ez,{detail:{kind:`timeout`}})):t===`upstream_unreachable`&&window.dispatchEvent(new CustomEvent(ez,{detail:{kind:`unreachable`}}))}).catch(()=>{})}async function rz(e){let t=await fetch(`/api/proxy/complete`,{method:`POST`,headers:uz({"content-type":`application/json`}),body:JSON.stringify(e)});if(!t.ok)throw Error(`/api/proxy/complete failed: ${String(t.status)} ${t.statusText}`)}async function iz(){try{await fetch(`/api/proxy/cancel`,{method:`POST`,headers:uz({"content-type":`application/json`}),body:`{}`})}catch{}}var az=1200,oz=3;function sz(e){let t=0,n=!1,r=window.setInterval(()=>{fetch(`/api/proxy/heartbeat`,{method:`POST`,headers:uz({"content-type":`application/json`}),body:`{}`}).then(r=>{if(!r.ok)throw Error(`heartbeat ${String(r.status)}`);t=0,n&&(n=!1,e?.onReconnect?.())}).catch(()=>{t+=1,t>=oz&&!n&&(n=!0,e?.onDisconnect?.())})},az);return()=>{window.clearInterval(r)}}function cz(e,t){return e instanceof Request?e:new Request(e,t)}function lz(e,t){let n={};return e.headers.forEach((e,t)=>{n[t]=e}),t?.headers&&new Headers(t.headers).forEach((e,t)=>{n[t]=e}),n}function uz(e){return QR?{...e,"x-wizard-csrf":QR.csrf}:e}async function dz(e){if(e.method===`GET`||e.method===`HEAD`)return null;try{let t=await e.clone().text();return t.length>0?t:null}catch{return null}}function fz({state:e,context:t,pairingStatus:n}){return(0,B.jsxs)(`div`,{role:`alert`,"aria-live":`polite`,className:`mb-4 flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-12 text-foreground`,children:[(0,B.jsx)(e===`reconnecting`?wi:Ai,{className:`mt-0.5 h-4 w-4 shrink-0 text-destructive `+(e===`reconnecting`?`animate-spin`:``),"aria-hidden":!0}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`p`,{className:`font-medium`,children:e===`reconnecting`?`Reconnecting…`:t===`local`?`Connection to CLI interrupted`:n===`cancelled`?`CLI cancelled this pairing`:n===`expired`?`Pairing expired`:`Pairing went stale`}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:e===`reconnecting`?`Retrying the last check…`:t===`local`?`The nyxid CLI missed several heartbeat checks. Keep this tab open; the wizard will continue if the connection recovers. If this message persists, re-run the command in your terminal.`:n===`cancelled`?`The CLI sent a cancel — nothing was created on the server. You can close this tab.`:n===`expired`?`This pairing passed its 15-minute TTL. Re-run the command in your terminal to start a new one.`:`The pairing record is no longer reachable. Re-run the CLI command to start a fresh one.`})]})]})}function pz({kind:e,onDismiss:t}){return(0,B.jsxs)(`div`,{role:`alert`,"aria-live":`polite`,className:`mb-4 flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-12 text-foreground`,children:[(0,B.jsx)(e===`timeout`?pi:Ai,{className:`mt-0.5 h-4 w-4 shrink-0 text-destructive`,"aria-hidden":!0}),(0,B.jsxs)(`div`,{className:`flex flex-1 flex-col gap-1`,children:[(0,B.jsx)(`p`,{className:`font-medium`,children:e===`timeout`?`Request to NyxID timed out`:`NyxID backend unreachable`}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:e===`timeout`?`The page took too long to reach the NyxID backend. No changes were made. Try again from the form below — or close this tab and re-run the command in your terminal.`:`Couldn't reach the NyxID backend on the last attempt. No changes were made. Check your network, then try again from the form below — or close this tab and re-run the command in your terminal.`})]}),(0,B.jsx)(`button`,{type:`button`,onClick:t,"aria-label":`Dismiss`,className:`rounded p-1 text-muted-foreground hover:bg-destructive/10 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive`,children:(0,B.jsx)(ji,{className:`h-3.5 w-3.5`,"aria-hidden":!0})})]})}function mz(e){return(0,B.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`-5.0 -10.0 110.0 135.0`,fill:`currentColor`,...e,children:(0,B.jsx)(`path`,{d:`m74.719 41.191c0.011719-0.007812 0.023438-0.011718 0.03125-0.019531l3.0312-1.75c1.1172-0.64453 1.9922-2.1523 1.9883-3.4297l-0.039062-18.926c-0.003907-0.69141-0.25781-1.2227-0.72266-1.5 0 0 0-0.003906-0.003906-0.003906 0 0 0.003906 0.003906-0.015625-0.011719-0.003906 0-0.007812-0.003906-0.011719-0.003906-0.17187-0.097656-3.8789-2.2578-3.7031-2.1602-0.47656-0.27344-1.0781-0.23047-1.6992 0.12109l-1.25 0.72266c0-0.24219 0.015625-3.7422 0.015625-3.5273v-0.003906c0-0.011719-0.011719-0.015625-0.011719-0.027344-0.015625-0.70312-0.46094-1.3477-1.2656-1.8125-2.4922-1.4375-6.7812-0.44141-6.7812 1.8398 0.003906 2.3828-0.011719 8.3906-0.011719 8.1797l-7.0625 4.0781c-0.48828 0.28125-0.070312 1.0312 0.42969 0.74609 17.559-10.082 16.707-9.8555 17.203-9.5742l2.5547 1.4883c-12.867 7.4258-32.582 18.809-51.461 29.707-1.1133 0.64062-1.9805 2.1523-1.9766 3.4375 0.058594 27.855-0.03125 15.969 0.042969 19.02l-2.5547-1.4883c-0.49609-0.30469-0.26172 0.49609-0.35937-19.703 0-0.96094 0.71875-2.2109 1.5469-2.6758l28.973-16.727c0.20703-0.11719 0.27734-0.38281 0.15625-0.58984-0.12109-0.20703-0.38281-0.27734-0.58984-0.15625l-15.32 8.8438v-3.5156c0-0.015625-0.011718-0.023437-0.015624-0.035156-0.015626-0.70312-0.46094-1.3438-1.2617-1.8086-2.4688-1.4258-6.7852-0.46094-6.7812 1.8398 0.003906 3.2031-0.007813 8.3906-0.007813 8.1797l-5.5781 3.2188c-1.0938 0.61719-1.9805 2.1523-1.9805 3.4258l0.054688 18.926c0 0.69141 0.25391 1.2305 0.72656 1.5195 3.5742 2.0586 3.7812 2.2734 3.957 2.2227 0.14453 0.050782 0.28516 0.11719 0.44922 0.11719 0.66797 0 1.1758-0.39453 2.2891-1.0391l-0.023437 9.4297c-0.61328 0.19922-1.2109 0.44922-1.7773 0.77344-1.6172 0.93359-2.5117 2.2148-2.5195 3.6055-0.003906 1.6992-0.007812 4.7891-0.007812 4.6133-0.003906 1.4023 0.91016 2.7031 2.5703 3.6602 1.6055 0.92578 3.707 1.3867 5.8125 1.3867 2.1211 0 4.2461-0.46875 5.8594-1.4062 1.625-0.94531 2.5234-2.2266 2.5273-3.6094v-4.5977c0-0.011718-0.011719-0.019531-0.011719-0.03125-0.011718-1.3828-0.91016-2.6641-2.5391-3.6055-0.5625-0.32813-1.1953-0.58984-1.8633-0.79688l0.007812-2.5898c0-0.23828-0.19141-0.42969-0.42969-0.42969-0.23828 0-0.42969 0.19141-0.42969 0.42969l-0.015625 7.2227c0 1.3594-3.3945 2.3125-5.4883 1.1094-0.53906-0.31641-0.83984-0.71094-0.83984-1.1094l0.035156-14.559 6.3438-3.6602-0.015624 5.1836c0 0.23828 0.19141 0.42969 0.42969 0.43359 0.23828 0 0.42969-0.19141 0.42969-0.42969l0.015626-5.6836 28.43-16.414-0.035156 9.4492c-2.1211 0.69141-4.293 2.1523-4.3047 4.375-0.007812 1.168-0.003906 4.7812-0.003906 4.6055-0.015625 4.6992 9.1953 6.5742 14.242 3.6445 1.625-0.94531 2.5234-2.2266 2.5273-3.6094 0-0.18359 0.007813-4.4062 0.007813-4.5898 0-2.3008-2.2422-3.7305-4.418-4.4453l0.039062-14.078zm-8.7383-31.586c1.2539-0.73047 3.3828-0.73047 4.6562 0.003906 3.2852 1.8906-3.2969 4.0273-5.2227 1.7227-0.16406-0.20312-0.26172-0.41406-0.26172-0.63281 0-0.003907-0.003906-0.003907-0.003906-0.007813 0.003906-0.39062 0.29687-0.77344 0.83203-1.0859zm-0.83203 2.6758c1.5781 1.2578 4.7695 1.2148 6.3242-0.007812l-0.011718 2.4609-6.3281 3.6523zm-35.652 18.391c1.25-0.73047 3.3867-0.72656 4.6562 0.003906 1.9414 1.1211 0.25391 2.6562-2.168 2.7266-2.5859 0.070312-4.4766-1.5781-2.4883-2.7305zm-0.83203 2.6797c1.6094 1.2734 4.8477 1.168 6.332-0.011718v2.4492l-6.3438 3.6641zm10.617 53.434c-0.011719 3.6289-8.1406 5.6406-12.91 2.8867-1.3828-0.79688-2.1406-1.832-2.1367-2.9141v-2.332c3.1289 3.8086 12.098 3.6172 15.047 0.011719zm-10.344-2.5352c2.5078 1.4375 6.7852 0.40625 6.7852-1.8555l0.007813-3.7266c4.2617 1.4883 4.5586 4.6445 1.4453 6.3867-4.1406 2.4102-11.523 1.2734-12.777-2.043-0.003907-0.015625-0.015626-0.027344-0.019532-0.039063-0.011718-0.03125-0.007812-0.0625-0.015625-0.09375-0.47656-1.5352 0.58594-2.793 1.9688-3.5938 0.43359-0.24609 0.88672-0.44141 1.3477-0.60938l-0.007813 3.7148c-0.003906 0.72266 0.44531 1.3789 1.2656 1.8594zm35.391-38.055s-0.003906 0-0.007813 0.003907l-2.5117 1.4492 10.109-27.867 5.5664-3.2148-10.102 27.867zm-16.055 9.2695 10.105-27.863 5.6172-3.2461-10.105 27.863zm-13.496 7.793 10.105-27.859 5.6055-3.2383-10.105 27.859zm-6.7227 3.8789 10.086-27.848 5.5859-3.2266-10.105 27.859zm-3.2266-18.375c0-0.96094 0.71875-2.2109 1.5469-2.6758l3.8594-2.2305-5.3867 14.824zm26.82-17.273 5.5703-3.2188-10.105 27.863-5.5703 3.2188zm13.508-7.8008 5.6055-3.2383-10.109 27.867-5.6055 3.2344zm13.348-7.4023c0.62109 0.25 0.25781 0.64844 0.38672 11.164l-4.7422 13.074-5.6055 3.2383zm-1.1523 22.391-2.043 1.1797 3.5859-9.8906 0.011719 6.0312c0.003906 0.96484-0.70703 2.1914-1.5547 2.6797zm-52.188 29.777c-0.47656-0.27734-0.26953-1.0508-0.3125-7.2617l6.543-18.004 5.5859-3.2266-10.086 27.844c-0.58984 0.29297-1.2734 0.90625-1.7305 0.64844zm50.602-2.7305c-0.015625 5.5508-15.062 5.3945-15.047-0.03125v-2.332c3.1094 3.7969 12.074 3.6289 15.047 0.011719zm-2.1055-1.7344c-4.2344 2.4727-11.234 1.2031-12.672-1.8477-0.98438-2.1055 1.0117-3.7305 3.1758-4.5156l-0.011718 3.6992c0 3.3242 8.0508 3.3359 8.0508 0.019531l0.011719-3.7383c4.4492 1.5742 4.4688 4.6211 1.4453 6.3828zm-2.3203-2.6484c0 1.3672-3.4062 2.293-5.4766 1.0898-0.55078-0.3125-0.85156-0.70703-0.85156-1.1055l0.050781-14.562 2.9102-1.6797h0.003906l3.4102-1.9688z`})})}function hz({code:e=`404`,title:t=`Page not found`,description:n=`The page you're looking for doesn't exist or may have moved.`,action:r}){return(0,B.jsxs)(`div`,{className:`flex min-h-[60vh] w-full flex-col items-center justify-center gap-1 px-6 py-12 text-center`,children:[(0,B.jsx)(mz,{className:`h-48 w-48 text-muted-foreground/30`}),(0,B.jsx)(`p`,{className:`font-mono text-xs uppercase tracking-widest text-text-tertiary`,children:e}),(0,B.jsx)(`h1`,{className:`mt-2 font-serif text-28 font-normal text-foreground`,children:t}),(0,B.jsx)(`p`,{className:`mt-1 max-w-sm text-sm text-muted-foreground`,children:n}),r?(0,B.jsx)(`div`,{className:`mt-6`,children:r}):null]})}var gz=2e4,_z=window.__WIZARD_BOOTSTRAP__;_z&&$R(_z);var vz=new We({defaultOptions:{queries:{retry:1,staleTime:3e4}}});function yz(e,t){return t?e!==`done`&&e!==`cancelled`&&e!==`wizard-lost`:!1}function bz(e,t){return t?e===`claimed`:!1}function xz(){let[e,t]=(0,z.useState)({phase:`claimed`}),[n,r]=(0,z.useState)(null),[i,a]=(0,z.useState)(!!_z?.prefill?.slug),[o,s]=(0,z.useState)(!1),[c,l]=(0,z.useState)(null);if((0,z.useEffect)(()=>{if(!_z)return;let e=sz({onDisconnect:()=>{s(!0)},onReconnect:()=>{s(!1)}});return()=>{e()}},[]),(0,z.useEffect)(()=>{if(_z)return tz(e=>{l(e)})},[]),(0,z.useEffect)(()=>{if(!o||e.phase===`done`||e.phase===`cancelled`||e.phase===`wizard-lost`)return;let n=window.setTimeout(()=>{t(e=>e.phase===`done`||e.phase===`cancelled`||e.phase===`wizard-lost`?e:{phase:`wizard-lost`})},gz);return()=>{window.clearTimeout(n)}},[o,e.phase]),!_z)return(0,B.jsx)(Mz,{});let u=mt(wz(e.phase),_z.flow,{slugPicked:i});async function d(e){if(e.kind===`ai-key`){t({phase:`acking`,result:e}),await Sz(e,r,t);return}t({phase:`secret`,result:e})}async function f(){e.phase===`secret`&&await Sz(e.result,r,t)}return(0,B.jsxs)(Zt,{context:`local`,step:u,children:[bz(e.phase,c)?(0,B.jsx)(pz,{kind:c,onDismiss:()=>{l(null)}}):null,yz(e.phase,o)?(0,B.jsx)(fz,{state:`disconnected`,context:`local`}):null,e.phase===`claimed`?(0,B.jsx)(Tz,{flow:_z.flow,prefill:_z.prefill??{},onSuccess:e=>void d(e),onCancel:()=>{t({phase:`cancelled`}),iz()},onSlugPicked:e=>{a(!!e)}}):e.phase===`secret`?(0,B.jsx)(Ez,{result:e.result,completeError:n,onAck:()=>void f()}):e.phase===`acking`?(0,B.jsx)(Dz,{result:e.result,completeError:n,onRetry:()=>{d(e.result)}}):e.phase===`cancelled`?(0,B.jsx)(kz,{}):e.phase===`wizard-lost`?(0,B.jsx)(Az,{}):(0,B.jsx)(Oz,{})]})}async function Sz(e,t,n){try{await rz(Cz(e)),t(null),n({phase:`done`})}catch(e){t(e instanceof Error?e.message:String(e))}}function Cz(e){switch(e.kind){case`ai-key`:return{acknowledged:!0,service_id:e.service_id,slug:e.slug,label:e.label};case`api-key-create`:return{acknowledged:!0,api_key_id:e.api_key_id};case`api-key-rotate`:return{acknowledged:!0,resource_id:e.resource_id};case`node-register-token`:return{acknowledged:!0,token_id:e.token_id};case`node-rotate-token`:return{acknowledged:!0,resource_id:e.resource_id};case`service-account-create`:return{acknowledged:!0,service_account_id:e.service_account_id};case`service-account-rotate-secret`:return{acknowledged:!0,resource_id:e.resource_id};case`developer-app-create`:return{acknowledged:!0,developer_app_id:e.developer_app_id};case`developer-app-rotate-secret`:return{acknowledged:!0,resource_id:e.resource_id};case`mfa-setup`:return{acknowledged:!0,factor_id:e.factor_id}}}function wz(e){return e===`claimed`?`claimed`:e===`secret`?`secret`:e===`acking`?`acking`:`done`}function Tz({flow:e,prefill:t,onSuccess:n,onCancel:r,onSlugPicked:i}){let a=`local`;switch(e){case`api-key-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(xL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`api-key-rotate`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(SL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`node-register-token`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(wL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`node-rotate-token`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(TL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`ai-key`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(BR,{prefill:wP(t),pairingId:a,onSuccess:n,onSlugPicked:i}),(0,B.jsx)(jz,{onCancel:r})]});case`service-account-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(EL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`service-account-rotate-secret`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(DL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`developer-app-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(OL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`developer-app-rotate-secret`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(kL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]});case`mfa-setup`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(AL,{pairingId:a,onSuccess:n}),(0,B.jsx)(jz,{onCancel:r})]})}}function Ez({result:e,completeError:t,onAck:n}){let r=t===null;return e.kind===`api-key-create`?(0,B.jsx)(Hi,{title:`API key created`,description:`Save this key now — it won't be shown again.`,secret:e.full_key,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:r&&t===null&&!1}):e.kind===`api-key-rotate`?(0,B.jsx)(Gi,{result:e,description:`The previous key is revoked. Save this new value now — it won't be shown again.`,ackButtonLabel:`I have saved this — close`,onAcknowledge:n}):e.kind===`node-register-token`?(0,B.jsx)(Hi,{title:`Registration token generated`,description:"Use this with `nyxid node register`. Save it now — it won't be shown again.",secret:e.token,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`node-rotate-token`?(0,B.jsx)(Hi,{title:`Node tokens rotated`,description:"Update the node with `nyxid node rekey`. Save both values now — they won't be shown again.",secret:e.auth_token,secondarySecret:{label:`Signing secret`,value:e.signing_secret},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`service-account-create`?(0,B.jsx)(Hi,{title:`Service account created`,description:`Save the client_secret — it isn't shown again. Use it with the OAuth client_credentials flow.`,secret:e.client_secret,secondarySecret:{label:`Client ID`,value:e.client_id},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`service-account-rotate-secret`?(0,B.jsx)(Hi,{title:`Service account secret rotated`,description:`All previously-issued tokens have been revoked. Save this new client_secret — it isn't shown again.`,secret:e.client_secret,secondarySecret:{label:`Client ID`,value:e.client_id},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`developer-app-create`?(0,B.jsx)(Hi,{title:`Developer app created`,description:`Save the client_secret — it isn't shown again. Use it to sign Sign-in-with-NyxID requests.`,secret:e.client_secret,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`developer-app-rotate-secret`?(0,B.jsx)(Hi,{title:`Developer app secret rotated`,description:`The previous client_secret no longer authenticates. Update any deployments using it.`,secret:e.client_secret,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`mfa-setup`?(0,B.jsx)(Ui,{codes:e.recovery_codes,onAcknowledged:n}):(0,B.jsx)(`p`,{className:`text-sm text-destructive`,children:`Unknown result kind.`})}function Dz({result:e,completeError:t,onRetry:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Service added`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[(0,B.jsx)(`code`,{className:`font-mono text-xs`,children:e.slug}),` is now connected. Check your terminal for the final summary.`]}),t?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsxs)(`p`,{className:`text-sm text-destructive`,children:[`Couldn't notify CLI: `,t]}),(0,B.jsx)(Pi,{variant:`outline`,onClick:n,children:`Retry`})]}):null]})}function Oz(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Done`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`You can close this tab and return to your terminal.`})]})})}function kz(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Cancelled`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Nothing was created. You can close this tab — your CLI should already be back at the prompt.`})]})})}function Az(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Wizard interrupted`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Lost contact with the `,(0,B.jsx)(`code`,{children:`nyxid`}),` CLI. The CLI may have finished and exited successfully, or the connection was interrupted before the result reached this page.`]}),(0,B.jsxs)(`p`,{className:`mt-2 text-sm text-muted-foreground`,children:[`Run `,(0,B.jsx)(`code`,{children:`nyxid status`}),` in your terminal to see whether the service was created. If it’s missing, re-run the wizard command.`]})]})})}function jz({onCancel:e}){return(0,B.jsx)(`button`,{type:`button`,onClick:e,className:`self-start text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground`,children:`Cancel and return to terminal`})}function Mz(){return(0,B.jsx)(hz,{title:`Wizard not available here`,description:(0,B.jsxs)(B.Fragment,{children:[`This page is served by the `,(0,B.jsx)(`code`,{children:`nyxid`}),` CLI’s local wizard server, which injects its config on request. Open the URL printed by the CLI instead.`]})})}var Nz=document.getElementById(`wizard-root`);Nz&&(0,dt.createRoot)(Nz).render((0,B.jsx)(z.StrictMode,{children:(0,B.jsx)(Ye,{client:vz,children:(0,B.jsx)(xz,{})})})); +`);let l=b(n,r,o),d=new i(t.getSymbolSize(n));return m(d,n),h(d),g(d,n),v(d,r,0),n>=7&&_(d,n),y(d,l),isNaN(a)&&(a=s.getBestMask(d,v.bind(null,d,r))),s.applyMask(a,d),v(d,r,a),{modules:d,version:n,errorCorrectionLevel:r,maskPattern:a,segments:o}}e.create=function(e,r){if(e===void 0||e===``)throw Error(`No input text`);let i=n.M,a,o;return r!==void 0&&(i=n.from(r.errorCorrectionLevel,n.M),a=u.from(r.version),o=s.from(r.maskPattern),r.toSJISFunc&&t.setToSJISFunction(r.toSJISFunc)),S(e,a,i,o)}})),fA=s((e=>{function t(e){if(typeof e==`number`&&(e=e.toString()),typeof e!=`string`)throw Error(`Color should be defined as hex string`);let t=e.slice().replace(`#`,``).split(``);if(t.length<3||t.length===5||t.length>8)throw Error(`Invalid hex color: `+e);(t.length===3||t.length===4)&&(t=Array.prototype.concat.apply([],t.map(function(e){return[e,e]}))),t.length===6&&t.push(`F`,`F`);let n=parseInt(t.join(``),16);return{r:n>>24&255,g:n>>16&255,b:n>>8&255,a:n&255,hex:`#`+t.slice(0,6).join(``)}}e.getOptions=function(e){e||={},e.color||={};let n=e.margin===void 0||e.margin===null||e.margin<0?4:e.margin,r=e.width&&e.width>=21?e.width:void 0,i=e.scale||4;return{width:r,scale:r?4:i,margin:n,color:{dark:t(e.color.dark||`#000000ff`),light:t(e.color.light||`#ffffffff`)},type:e.type,rendererOpts:e.rendererOpts||{}}},e.getScale=function(e,t){return t.width&&t.width>=e+t.margin*2?t.width/(e+t.margin*2):t.scale},e.getImageWidth=function(t,n){let r=e.getScale(t,n);return Math.floor((t+n.margin*2)*r)},e.qrToImageData=function(t,n,r){let i=n.modules.size,a=n.modules.data,o=e.getScale(i,r),s=Math.floor((i+r.margin*2)*o),c=r.margin*o,l=[r.color.light,r.color.dark];for(let e=0;e=c&&n>=c&&e{var t=fA();function n(e,t,n){e.clearRect(0,0,t.width,t.height),t.style||={},t.height=n,t.width=n,t.style.height=n+`px`,t.style.width=n+`px`}function r(){try{return document.createElement(`canvas`)}catch{throw Error(`You need to specify a canvas element`)}}e.render=function(e,i,a){let o=a,s=i;o===void 0&&(!i||!i.getContext)&&(o=i,i=void 0),i||(s=r()),o=t.getOptions(o);let c=t.getImageWidth(e.modules.size,o),l=s.getContext(`2d`),u=l.createImageData(c,c);return t.qrToImageData(u.data,e,o),n(l,s,c),l.putImageData(u,0,0),s},e.renderToDataURL=function(t,n,r){let i=r;i===void 0&&(!n||!n.getContext)&&(i=n,n=void 0),i||={};let a=e.render(t,n,i),o=i.type||`image/png`,s=i.rendererOpts||{};return a.toDataURL(o,s.quality)}})),mA=s((e=>{var t=fA();function n(e,t){let n=e.a/255,r=t+`="`+e.hex+`"`;return n<1?r+` `+t+`-opacity="`+n.toFixed(2).slice(1)+`"`:r}function r(e,t,n){let r=e+t;return n!==void 0&&(r+=` `+n),r}function i(e,t,n){let i=``,a=0,o=!1,s=0;for(let c=0;c0&&l>0&&e[c-1]||(i+=o?r(`M`,l+n,.5+u+n):r(`m`,a,0),a=0,o=!1),l+1`:``,d=``,f=`viewBox="0 0 `+l+` `+l+`"`,p=``+u+d+` +`;return typeof a==`function`&&a(null,p),p}})),hA=u(s((e=>{var t=Hk(),n=dA(),r=pA(),i=mA();function a(e,r,i,a,o){let s=[].slice.call(arguments,1),c=s.length,l=typeof s[c-1]==`function`;if(!l&&!t())throw Error(`Callback required as last argument`);if(l){if(c<2)throw Error(`Too few arguments provided`);c===2?(o=i,i=r,r=a=void 0):c===3&&(r.getContext&&o===void 0?(o=a,a=void 0):(o=a,a=i,i=r,r=void 0))}else{if(c<1)throw Error(`Too few arguments provided`);return c===1?(i=r,r=a=void 0):c===2&&!r.getContext&&(a=i,i=r,r=void 0),new Promise(function(t,o){try{t(e(n.create(i,a),r,a))}catch(e){o(e)}})}try{let t=n.create(i,a);o(null,e(t,r,a))}catch(e){o(e)}}e.create=n.create,e.toCanvas=a.bind(null,r.render),e.toDataURL=a.bind(null,r.renderToDataURL),e.toString=a.bind(null,function(e,t,n){return i.render(e,n)})}))(),1);function gA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z`})})}function _A(e){return(0,B.jsx)(`svg`,{viewBox:`185 40 472 515`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsxs)(`g`,{children:[(0,B.jsx)(`polygon`,{points:`557.09,211.99 565.4,538.36 631.96,538.36 640.28,93.18`}),(0,B.jsx)(`polygon`,{points:`640.28,56.91 538.72,56.91 379.35,284.53 430.13,357.05`}),(0,B.jsx)(`polygon`,{points:`201.61,538.36 303.17,538.36 353.96,465.84 303.17,393.31`}),(0,B.jsx)(`polygon`,{points:`201.61,211.99 430.13,538.36 531.69,538.36 303.17,211.99`})]})})}function vA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z`})})}function yA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z`})})}function bA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 600 600`,fill:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M407.5 31c-72.3 0-144.7-0.1-217 0.2-10.9 0-22 1.2-32.7 3.6C76.3 52.7 22.4 128 32.3 210.8c6.7 56.4 36.1 97.5 86.9 122.9 28.6 14.3 59.4 19.2 91.1 18.7 28.6-0.4 55.7-7.3 81.9-18.4 54.8-23.1 109.8-45.5 164.2-69.4 20.8-9.1 41.1-20.1 60-32.7 28.9-19.2 44-47.1 46.1-82.2C566.3 85.7 515.3 31 451 31c-14.2 0-28.3 0-43.5 0z`}),(0,B.jsx)(`path`,{d:`M315.3 562.3c13.2 5.6 26.6 8.7 40.6 8.8 25.2 0.1 50.3 0.1 75.5 0 9.6 0 19.3 0 28.9-1 27-2.7 50.8-13.5 70.5-32 30.2-28.5 43.8-63.7 39.8-105.2-3.3-33.2-17.5-61-43.2-82.2-38.2-31.5-81.1-39-127.5-21.1-27.6 10.6-54.8 22.5-82.1 33.9-46.7 19.5-73.4 64.9-66.2 115.1 5.6 39.2 27.6 66.9 63.7 83.7z`}),(0,B.jsx)(`path`,{d:`M97 565.6c38.4 11 77.5-2 101-33.2 18.3-24.3 21.1-52.1 16.9-80.9-5.9-40.4-37.2-71.1-79-77-42-5.9-77.7 16.4-95 49.8-10.8 20.9-12.5 43.2-8.9 65.7 6 37.7 27.8 62.8 64.8 75.6z`})]})}function xA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 50 72`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M41.7154 23.1929C38.9531 24.0129 36.8707 25.8677 35.3457 27.8826C35.0182 28.3151 34.3358 27.9901 34.4658 27.4601C37.3856 15.4534 33.5283 5.47401 21.5039 0.561817C20.894 0.311833 20.259 0.859299 20.419 1.49926C25.8887 23.4604 2.88236 21.608 5.78971 46.504C5.83971 46.9314 5.35973 47.2239 5.00975 46.9739C3.9198 46.1915 2.70237 44.5591 1.86741 43.4116C1.62242 43.0742 1.09245 43.1692 0.979951 43.5716C0.314984 45.9765 0 48.2413 0 50.4912C0 59.2407 4.49727 66.9427 11.3044 71.4074C11.6944 71.6624 12.1944 71.2974 12.0619 70.8499C11.7119 69.675 11.5144 68.4351 11.4994 67.1527C11.4994 66.3652 11.5494 65.5603 11.6719 64.8103C11.9569 62.9254 12.6119 61.1306 13.7118 59.4957C17.4841 53.8335 25.0462 48.3638 23.8388 40.9368C23.7613 40.4668 24.3163 40.1569 24.6663 40.4793C29.9935 45.3465 31.0485 51.8936 30.1735 57.7658C30.0985 58.2757 30.7385 58.5482 31.061 58.1482C31.8759 57.1283 32.8709 56.2334 33.9533 55.5609C34.2233 55.3934 34.5833 55.5209 34.6858 55.8209C35.2882 57.5733 36.1832 59.2182 37.0281 60.8631C38.0381 62.8404 38.5756 65.0978 38.4906 67.4877C38.4481 68.6501 38.2556 69.775 37.9331 70.8449C37.7956 71.2974 38.2906 71.6749 38.6881 71.4149C45.5002 66.9502 50 59.2482 50 50.4937C50 47.4514 49.4675 44.4691 48.4601 41.6743C46.3477 35.8121 40.988 31.4099 42.3429 23.7704C42.4079 23.4054 42.0704 23.0879 41.7154 23.1929Z`})})}function SA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45`})})}function CA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 512 512`,fill:`currentColor`,stroke:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M3 248.945C18 248.945 76 236 106 219C136 202 136 202 198 158C276.497 102.293 332 120.945 423 120.945`,fill:`none`,strokeWidth:`90`}),(0,B.jsx)(`path`,{d:`M511 121.5L357.25 210.268L357.25 32.7324L511 121.5Z`}),(0,B.jsx)(`path`,{d:`M0 249C15 249 73 261.945 103 278.945C133 295.945 133 295.945 195 339.945C273.497 395.652 329 377 420 377`,fill:`none`,strokeWidth:`90`}),(0,B.jsx)(`path`,{d:`M508 376.445L354.25 287.678L354.25 465.213L508 376.445Z`})]})}function wA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z`})})}function TA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z`})})}function EA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12`})})}function DA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z`})})}function OA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V8.999h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.287zM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124zM7.119 20.452H3.555V8.999h3.564v11.453zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0z`})})}function kA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.372-.01-.57-.01-.198 0-.52.075-.792.372-.272.298-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.29.173-1.413-.074-.124-.272-.198-.57-.347zm-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.999-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.886 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.89c0 2.096.547 4.142 1.588 5.945L.057 24l6.298-1.654a11.882 11.882 0 005.684 1.448h.005c6.554 0 11.89-5.335 11.893-11.89 0-3.177-1.239-6.161-3.486-8.416z`})})}function AA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z`})})}function jA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z`})})}function MA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z`})})}function NA(e){return(0,B.jsxs)(`svg`,{viewBox:`4 2 40 44`,fill:`currentColor`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M20.0842 3.02588L19.8595 3.16179C19.5021 3.37799 19.1654 3.61972 18.8512 3.88385L19.4993 3.42798H25L26 11L21 16L16 19.4754V23.4829C16 26.2819 17.4629 28.8774 19.8574 30.3268L25.1211 33.5129L14 40.0002H11.8551L7.85737 37.5804C5.46286 36.131 4 33.5355 4 30.7365V17.2606C4 14.4607 5.46379 11.8645 7.85952 10.4154L19.8595 3.15687C19.9339 3.11189 20.0088 3.06823 20.0842 3.02588Z`}),(0,B.jsx)(`path`,{d:`M32 19V23.4803C32 26.2793 30.5371 28.8748 28.1426 30.3242L16.1426 37.5878C13.6878 39.0737 10.6335 39.1273 8.1355 37.7487L19.8573 44.844C22.4039 46.3855 25.5959 46.3855 28.1426 44.844L40.1426 37.5803C42.5371 36.1309 43.9999 33.5354 43.9999 30.7364V27.5L42.9999 26L32 19Z`}),(0,B.jsx)(`path`,{d:`M40.1405 10.4153L28.1405 3.15678C25.6738 1.66471 22.6021 1.61849 20.0979 3.01811L19.8595 3.16231C17.4638 4.61143 16 7.20757 16 10.0075V19.4914L19.8595 17.1568C22.4051 15.6171 25.5949 15.6171 28.1405 17.1568L40.1405 24.4153C42.4613 25.8192 43.9076 28.2994 43.9957 30.9985C43.9986 30.9113 44 30.824 44 30.7364V17.2605C44 14.4606 42.5362 11.8644 40.1405 10.4153Z`})]})}function PA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z`})})}function FA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z`})})}function IA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z`})})}function LA({variant:e=`solid`,...t}){return(0,B.jsxs)(`svg`,{viewBox:`80 130 660 540`,fill:e===`outline`?`none`:`currentColor`,stroke:e===`outline`?`currentColor`:`none`,strokeWidth:e===`outline`?30:0,strokeLinejoin:`round`,"aria-hidden":`true`,...t,children:[(0,B.jsx)(`path`,{d:`M423.075867,410.677734 C415.744812,398.010834 408.757996,385.129547 401.024048,372.713654 C364.628082,314.284393 321.334015,261.391510 270.727295,214.787643 C248.109116,193.958496 223.566727,175.217773 199.869446,155.563187 C197.579849,153.664200 194.587845,152.249771 195.716263,148.465775 C196.920410,144.427872 200.280441,144.764893 203.488174,144.765289 C292.963928,144.776016 382.439758,144.824509 471.915405,144.718201 C484.221069,144.703568 493.629883,149.569427 501.132721,159.136398 C533.640015,200.586929 556.618713,246.732040 569.629150,298.312439 C533.666138,310.514313 503.289673,330.491913 477.096375,357.345367 C459.431030,375.455902 441.105896,392.922882 423.075867,410.677734 Z`}),(0,B.jsx)(`path`,{d:`M422.830688,410.940979 C441.105896,392.922882 459.431030,375.455902 477.096375,357.345367 C503.289673,330.491913 533.666138,310.514313 569.702515,298.715515 C580.198547,296.204865 590.355896,293.571594 600.645630,291.646088 C607.830872,290.301514 615.179077,289.508179 622.486084,289.187988 C658.613342,287.604736 693.312744,293.690247 726.160522,309.210541 C727.006897,309.610504 727.779114,310.167572 728.486572,310.591797 C721.357727,319.227142 714.146179,327.480194 707.472229,336.147247 C693.437988,354.372803 684.670654,375.585999 674.255493,395.854858 C664.208557,415.407074 654.319397,435.040466 644.235168,454.573242 C642.501099,457.931915 640.138306,460.965942 637.753174,464.651062 C637.353271,465.501129 637.266113,465.850037 637.178955,466.198975 C637.144836,466.101532 636.873535,466.197357 636.418274,466.883179 C636.234314,467.473175 636.050354,468.063171 636.095703,468.046875 C634.338562,469.671326 632.340759,471.098724 630.823547,472.925354 C606.515503,502.192047 575.273743,518.862732 537.765747,523.886047 C515.541626,526.862549 493.767212,524.121155 472.241058,518.067810 C427.652893,505.529175 384.314484,489.700134 341.877197,469.465759 C371.294128,453.300354 398.227325,434.038086 422.830688,410.940979 Z`}),(0,B.jsx)(`path`,{d:`M636.112793,468.058197 C633.715088,472.205719 631.564819,476.520996 628.883423,480.476196 C566.403625,572.638000 480.475006,628.906128 370.287048,645.529480 C276.822723,659.629700 189.911423,639.837524 110.412666,588.346252 C100.677971,582.041016 95.101463,573.316772 94.325127,561.051025 C94.781334,559.023499 94.988503,557.707825 94.988693,556.392212 C95.001305,471.848450 95.001999,387.304718 94.956757,302.760986 C94.955917,301.187042 94.391716,299.613464 94.090363,298.039703 C96.664818,292.874542 99.409256,292.618652 103.908760,297.033661 C118.990723,311.832458 133.654266,327.092285 149.266510,341.309784 C205.874664,392.860687 268.921021,434.931610 338.013672,467.929260 C339.056030,468.427094 340.173340,468.768005 341.877197,469.465759 C384.314484,489.700134 427.652893,505.529175 472.241058,518.067810 C493.767212,524.121155 515.541626,526.862549 537.765747,523.886047 C575.273743,518.862732 606.515503,502.192047 630.823547,472.925354 C632.340759,471.098724 634.338562,469.671326 636.112793,468.058197 Z`})]})}function RA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z`})})}function zA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M4.6035 0v24h4.9317V0zm9.8613 0v24h4.9317V0z`})})}function BA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M12 0C5.381-.008.008 5.352 0 11.971V12c0 6.64 5.359 12 12 12 6.64 0 12-5.36 12-12 0-6.641-5.36-12-12-12zm0 20.801c-4.846.015-8.786-3.904-8.801-8.75V12c-.014-4.846 3.904-8.786 8.75-8.801H12c4.847-.014 8.786 3.904 8.801 8.75V12c.015 4.847-3.904 8.786-8.75 8.801H12zm5.44-11.76c0 1.359-1.12 2.479-2.481 2.479-1.366-.007-2.472-1.113-2.479-2.479 0-1.361 1.12-2.481 2.479-2.481 1.361 0 2.481 1.12 2.481 2.481zm0 5.919c0 1.36-1.12 2.48-2.481 2.48-1.367-.008-2.473-1.114-2.479-2.48 0-1.359 1.12-2.479 2.479-2.479 1.361-.001 2.481 1.12 2.481 2.479zm-5.919 0c0 1.36-1.12 2.48-2.479 2.48-1.368-.007-2.475-1.113-2.481-2.48 0-1.359 1.12-2.479 2.481-2.479 1.358-.001 2.479 1.12 2.479 2.479zm0-5.919c0 1.359-1.12 2.479-2.479 2.479-1.367-.007-2.475-1.112-2.481-2.479 0-1.361 1.12-2.481 2.481-2.481 1.358 0 2.479 1.12 2.479 2.481z`})})}function VA(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z`})})}function HA(e){return(0,B.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,fillRule:`evenodd`,"aria-hidden":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M9.046 7.104a.527.527 0 110 1.055.527.527 0 010-1.055z`}),(0,B.jsx)(`path`,{d:`M15.376 7.104a.528.528 0 110 1.056.528.528 0 010-1.056z`}),(0,B.jsx)(`path`,{clipRule:`evenodd`,d:`M16.877 1.912c.58-.27 1.14-.323 1.616-.037a.317.317 0 01-.326.542c-.227-.136-.547-.153-1.022.068-.352.165-.765.45-1.234.866 2.683 1.17 4.4 3.5 5.148 5.921a6.421 6.421 0 00-.704.184c-.578.016-1.174.204-1.502.735-.338.55-.268 1.276.072 2.069l.005.012.007.014c.523 1.045 1.318 1.91 2.2 2.284-.912 3.274-3.44 6.144-5.972 6.988v2.109h-2.11v-2.11c-1.043.417-2.086.01-2.11 0v2.11h-2.11v-2.11c-2.531-.843-5.061-3.713-5.973-6.987.882-.373 1.678-1.238 2.2-2.284l.007-.014.006-.012c.34-.793.41-1.518.071-2.069-.327-.531-.923-.719-1.503-.735a6.409 6.409 0 00-.704-.183c.749-2.421 2.466-4.751 5.149-5.922-.47-.416-.88-.701-1.234-.866-.474-.221-.794-.204-1.021-.068a.318.318 0 01-.435-.109.317.317 0 01.109-.433c.476-.286 1.036-.233 1.615.037.49.229 1.031.628 1.621 1.182A9.924 9.924 0 0112 2.568c1.199 0 2.284.19 3.256.526.59-.554 1.13-.953 1.62-1.182zM8.835 6.577a1.266 1.266 0 100 2.532 1.266 1.266 0 000-2.532zm6.33 0a1.267 1.267 0 100 2.533 1.267 1.267 0 000-2.533z`}),(0,B.jsx)(`path`,{d:`M.395 13.118c-.966-1.932-.163-3.863 2.41-3.365v-.001l.05.01c.084.018.17.038.26.06.033.009.067.017.1.027.084.022.168.048.255.076l.09.027c.528 0 .95.158 1.16.501.212.343.212.87-.105 1.61-.085.17-.178.333-.276.489l-.01.017a4.967 4.967 0 01-.62.791l-.019.02c-1.092 1.117-2.496 1.336-3.295-.262z`}),(0,B.jsx)(`path`,{d:`M21.193 9.753c2.574-.5 3.378 1.433 2.411 3.365-.58 1.159-1.476 1.361-2.342.96l-.011-.005a2.419 2.419 0 01-.114-.056l-.019-.01a2.751 2.751 0 01-.115-.067l-.023-.014c-.035-.022-.071-.044-.106-.068l-.05-.035c-.55-.388-1.062-1.007-1.44-1.76-.276-.647-.311-1.132-.174-1.472.176-.439.636-.639 1.23-.639.032-.011.066-.02.099-.03.08-.026.16-.05.238-.072l.117-.03a5.502 5.502 0 01.3-.067z`})]})}function UA({children:e,badge:t,className:n}){return(0,B.jsxs)(`span`,{className:`relative inline-flex shrink-0 items-center justify-center [&>svg]:!h-full [&>svg]:!w-full ${n??`h-5 w-5`}`,children:[e,(0,B.jsx)(WA,{badge:t})]})}function WA({badge:e}){return(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`absolute -bottom-[30%] -right-[30%] inline-flex h-[70%] w-[70%] items-center justify-center rounded-md border border-border bg-card text-foreground ring-2 ring-background [&>svg]:!h-full [&>svg]:!w-full`,children:e})}function GA({className:e}){return(0,B.jsx)(gA,{"data-slug":`llm-openai`,className:e})}function KA({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(hi,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(gA,{"data-slug":`llm-openai-codex`,className:`h-5 w-5`})})}function qA({className:e}){return(0,B.jsx)(_A,{"data-slug":`llm-xai`,className:e})}function JA({className:e}){return(0,B.jsx)(vA,{"data-slug":`llm-anthropic`,className:e})}function YA({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(Oi,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(TA,{"data-slug":`llm-google-ai`,className:`h-5 w-5`})})}function XA({className:e}){return(0,B.jsx)(yA,{"data-slug":`llm-mistral`,className:e})}function ZA({className:e}){return(0,B.jsx)(bA,{"data-slug":`llm-cohere`,className:e})}function QA({className:e}){return(0,B.jsx)(SA,{"data-slug":`llm-deepseek`,className:e})}function $A({className:e}){return(0,B.jsx)(HA,{"data-slug":`llm-openclaw`,className:e})}function ej({className:e}){return(0,B.jsx)(CA,{"data-slug":`llm-openrouter`,className:e})}function tj({className:e}){return(0,B.jsx)(xA,{"data-slug":`api-firecrawl`,className:e})}function nj({className:e}){return(0,B.jsx)(wA,{"data-slug":`api-twitter`,className:e})}function rj({className:e}){return(0,B.jsx)(TA,{"data-slug":`api-google`,className:e})}function ij({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(oi,{strokeWidth:2.5}),children:(0,B.jsx)(TA,{"data-slug":`api-google-workspace`,className:`h-full w-full`})})}function aj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-calendar`,className:e,children:(0,B.jsx)(`path`,{d:`M18.316 5.684H24v12.632h-5.684V5.684zM5.684 24h12.632v-5.684H5.684V24zM18.316 5.684V0H1.895A1.894 1.894 0 0 0 0 1.895v16.421h5.684V5.684h12.632zm-7.207 6.25v-.065c.272-.144.5-.349.687-.617s.279-.595.279-.982c0-.379-.099-.72-.3-1.025a2.05 2.05 0 0 0-.832-.714 2.703 2.703 0 0 0-1.197-.257c-.6 0-1.094.156-1.481.467-.386.311-.65.671-.793 1.078l1.085.452c.086-.249.224-.461.413-.633.189-.172.445-.257.767-.257.33 0 .602.088.816.264a.86.86 0 0 1 .322.703c0 .33-.12.589-.36.778-.24.19-.535.284-.886.284h-.567v1.085h.633c.407 0 .748.109 1.02.327.272.218.407.499.407.843 0 .336-.129.614-.387.832s-.565.327-.924.327c-.351 0-.651-.103-.897-.311-.248-.208-.422-.502-.521-.881l-1.096.452c.178.616.505 1.082.977 1.401.472.319.984.478 1.538.477a2.84 2.84 0 0 0 1.293-.291c.382-.193.684-.458.902-.794.218-.336.327-.72.327-1.149 0-.429-.115-.797-.344-1.105a2.067 2.067 0 0 0-.881-.689zm2.093-1.931l.602.913L15 10.045v5.744h1.187V8.446h-.827l-2.158 1.557zM22.105 0h-3.289v5.184H24V1.895A1.894 1.894 0 0 0 22.105 0zm-3.289 23.5l4.684-4.684h-4.684V23.5zM0 22.105C0 23.152.848 24 1.895 24h3.289v-5.184H0v3.289z`})})}function oj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-drive`,className:e,children:(0,B.jsx)(`path`,{d:`M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z`})})}function sj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(Ei,{strokeWidth:2.5}),children:(0,B.jsx)(TA,{"data-slug":`api-google-gmail`,className:`h-full w-full`})})}function cj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-docs`,className:e,children:(0,B.jsx)(`path`,{d:`M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727h-6zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818v1.364zm0-3.273H7.091V9.273h9.818v1.363zM14.727 6h6l-6-6v6z`})})}function lj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-sheets`,className:e,children:(0,B.jsx)(`path`,{d:`M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6l-6-6zm1.363 10.636h-3.41v1.91h3.41v-1.91zm0 3.273h-3.41v1.91h3.41v-1.91zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5h6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z`})})}function uj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-google-slides`,className:e,children:(0,B.jsx)(`path`,{d:`M16.09 15.273H7.91v-4.637h8.18v4.637zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75h3.841zm-.363 2.523H6.545v7.363h10.91V9.273zm-2.728-5.979V6h6.001l-6-6v3.294z`})})}function dj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 122.88 128.1`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-notion`,className:e,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M21.19,22.46c4,3.23,5.48,3,13,2.49l70.53-4.24c1.5,0,.25-1.49-.25-1.74L92.72,10.5a14.08,14.08,0,0,0-11-3.23l-68.29,5c-2.49.24-3,1.49-2,2.49l9.73,7.72ZM25.42,38.9v74.21c0,4,2,5.48,6.48,5.23l77.52-4.48c4.49-.25,5-3,5-6.23V33.91c0-3.23-1.25-5-4-4.73l-81,4.73c-3,.25-4,1.75-4,5Zm76.53,4c.49,2.24,0,4.48-2.25,4.73L96,48.36v54.79c-3.24,1.74-6.23,2.73-8.72,2.73-4,0-5-1.24-8-5L54.83,62.55V99.66l7.73,1.74s0,4.48-6.23,4.48l-17.2,1c-.5-1,0-3.48,1.75-4l4.48-1.25V52.59l-6.23-.5a4.66,4.66,0,0,1,4.24-5.73l18.44-1.24L87.24,84V49.6l-6.48-.74a4.21,4.21,0,0,1,4-5l17.21-1ZM7.72,5.52l71-5.23C87.49-.46,89.73.05,95.21,4L117.89,20c3.74,2.74,5,3.48,5,6.47v87.42c0,5.47-2,8.71-9,9.21l-82.5,5c-5.24.25-7.73-.5-10.47-4L4.24,102.4c-3-4-4.24-7-4.24-10.46V14.24C0,9.76,2,6,7.72,5.52Z`})})}function fj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(mi,{className:`h-2.5 w-2.5`,strokeWidth:2.5}),children:(0,B.jsx)(TA,{"data-slug":`api-google-cloud`,className:`h-full w-full`})})}function pj({className:e}){return(0,B.jsx)(EA,{"data-slug":`api-github`,className:e})}function mj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(Ci,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(EA,{"data-slug":`api-github-pat`,className:`h-5 w-5`})})}function hj({className:e}){return(0,B.jsx)(DA,{"data-slug":`api-facebook`,className:e})}function gj({className:e}){return(0,B.jsx)(OA,{"data-slug":`api-linkedin`,className:e})}function _j({className:e}){return(0,B.jsx)(AA,{"data-slug":`api-discord`,className:e})}function vj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(AA,{"data-slug":`api-discord-bot`,className:`h-5 w-5`})})}function yj({className:e}){return(0,B.jsx)(jA,{"data-slug":`api-spotify`,className:e})}function bj({className:e}){return(0,B.jsx)(MA,{"data-slug":`api-slack`,className:e})}function xj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(MA,{"data-slug":`api-slack-bot`,className:`h-5 w-5`})})}function Sj({className:e}){return(0,B.jsx)(NA,{"data-slug":`api-microsoft`,className:e})}function Cj({className:e}){return(0,B.jsx)(PA,{"data-slug":`api-tiktok`,className:e})}function wj({className:e}){return(0,B.jsx)(FA,{"data-slug":`api-twitch`,className:e})}function Tj({className:e}){return(0,B.jsx)(IA,{"data-slug":`api-reddit`,className:e})}function Ej({className:e}){return(0,B.jsx)(LA,{variant:`solid`,"data-slug":`api-lark`,className:e})}function Dj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(LA,{variant:`solid`,"data-slug":`api-lark-bot`,className:`h-5 w-5`})})}function Oj({className:e}){return(0,B.jsx)(LA,{variant:`outline`,"data-slug":`api-feishu`,className:e})}function kj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(LA,{variant:`outline`,"data-slug":`api-feishu-bot`,className:`h-5 w-5`})})}function Aj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ii,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(RA,{"data-slug":`api-telegram-bot`,className:`h-5 w-5`})})}function jj({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(oi,{strokeWidth:2.5}),children:(0,B.jsx)(kA,{"data-slug":`api-whatsapp-business`,className:`h-full w-full`})})}function Mj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 512 512`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-supabase`,className:e,children:[(0,B.jsx)(`path`,{d:`M297.6 501c-12.9 16.3-39.2 7.4-39.5-13.4L253.6 183h204.8c37.1 0 57.8 42.8 34.7 71.9z`}),(0,B.jsx)(`path`,{d:`M214.4 11c12.9-16.3 39.2-7.4 39.5 13.4l2 304.5H53.7c-37.1 0-57.8-42.8-34.7-71.9z`})]})}function Nj({className:e}){return(0,B.jsx)(zA,{"data-slug":`api-elevenlabs`,className:e})}function Pj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 -14 384 384`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-telnyx`,className:e,children:[(0,B.jsx)(`path`,{d:`M376.033 322.631C382.445 311.064 384.269 297.54 381.15 284.708C380.256 281.162 379.02 277.708 377.459 274.396C376.966 273.309 376.406 272.254 375.782 271.236L324.947 176.013H272.939L326.122 275.81C327.791 278.71 328.67 281.992 328.67 285.332C328.67 288.672 327.791 291.954 326.122 294.854C324.495 297.564 322.187 299.809 319.422 301.369C316.658 302.929 313.532 303.75 310.351 303.753H249.702C249.031 313.044 246.121 322.039 241.217 329.984C236.312 337.928 229.557 344.59 221.517 349.41H333C340.443 348.919 347.687 346.819 354.223 343.256C363.448 338.751 371.059 331.553 376.033 322.631Z`}),(0,B.jsx)(`path`,{d:`M90.9084 113.732H142.917L168.838 65.9127C171.031 61.7857 174.319 58.3308 178.347 55.9206C182.375 53.5105 186.991 52.2367 191.696 52.2367C196.401 52.2367 201.017 53.5105 205.046 55.9206C209.074 58.3308 212.362 61.7857 214.555 65.9127L239.721 113.732H291.729L254.82 44.5395C248.7 33.1074 239.556 23.5431 228.369 16.873C217.181 10.2029 204.373 6.67871 191.319 6.67871C178.264 6.67871 165.456 10.2029 154.269 16.873C143.082 23.5431 133.938 33.1074 127.818 44.5395L90.9084 113.732Z`}),(0,B.jsx)(`path`,{d:`M132.851 167.707C133.396 158.454 136.191 149.47 140.996 141.52C145.802 133.569 152.476 126.89 160.449 122.05H59.7869V167.707H132.851Z`}),(0,B.jsx)(`path`,{d:`M141.153 171.034V295.78H164.809C169.742 295.725 174.522 294.069 178.415 291.065C182.309 288.061 185.102 283.875 186.367 279.147C186.876 277.333 187.131 275.458 187.122 273.575V198.727C187.144 190.602 190.398 182.813 196.178 177.052C201.958 171.29 209.796 168.022 217.992 167.956H323.268V122.05H190.478C177.388 122.072 164.843 127.242 155.595 136.426C146.347 145.61 141.153 158.057 141.153 171.034Z`}),(0,B.jsx)(`path`,{d:`M5.17648 274.402C3.61573 277.714 2.37973 281.168 1.48554 284.714C-1.63066 297.573 0.193473 311.122 6.60253 322.72C11.5903 331.612 19.2003 338.779 28.4126 343.262C34.9489 346.825 42.1929 348.925 49.6356 349.416H191.317C204.666 349.416 217.468 344.159 226.907 334.801C236.346 325.443 241.648 312.751 241.648 299.518V175.936H217.657C211.759 176.086 206.152 178.505 202.019 182.679C197.887 186.854 195.553 192.458 195.512 198.307V273.154C195.489 281.279 192.218 289.063 186.416 294.8C180.613 300.537 172.753 303.759 164.558 303.759H72.7039C69.5232 303.756 66.3976 302.935 63.6332 301.375C60.8688 299.815 58.5603 297.57 56.9336 294.86C55.2645 291.944 54.387 288.649 54.387 285.296C54.387 281.944 55.2645 278.648 56.9336 275.733L110.117 175.936H57.6885L6.85418 271.159C6.26699 272.323 5.67979 273.321 5.17648 274.402Z`})]})}function Fj({className:e}){return(0,B.jsx)(BA,{"data-slug":`api-twilio`,className:e})}function Ij({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 390 388`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-aurinko`,className:e,children:(0,B.jsx)(`path`,{d:`M162.077 85.2174C163.314 84.8304 164.088 83.979 164.474 83.205L180.715 46.2078L203.452 79.5672C204.225 80.3412 205.076 81.1926 206.313 81.1926C207.55 81.1926 208.324 80.8056 209.561 79.9542L238.717 51.858L246.837 91.6416C247.224 92.88 247.61 93.654 248.848 94.041C249.621 94.428 250.859 94.815 252.096 94.428L290.223 79.5672L282.489 119.351C282.489 120.589 282.489 121.75 283.262 122.602C284.036 123.376 284.886 123.84 286.124 124.227L326.648 124.614L303.911 158.36C303.138 159.134 303.138 160.373 303.524 161.611C303.911 162.85 304.762 163.624 305.535 164.011L342.811 180.11L308.783 202.633C308.01 203.407 307.159 204.259 307.159 205.42C307.159 206.581 307.546 207.432 308.397 208.206L336.779 237.154L296.642 245.203C295.404 245.59 294.631 245.977 294.244 247.216C293.857 247.99 293.857 249.228 294.244 250.466L309.247 288.238L269.11 280.575C267.873 280.575 266.712 280.575 265.862 281.349C265.088 282.123 264.624 282.974 264.238 284.135L263.464 324.77L230.21 302.247C229.437 301.473 228.199 301.473 226.962 301.86C225.724 302.247 224.951 303.098 224.564 303.872L208.324 340.87L185.587 307.123C184.814 306.349 183.963 305.498 182.726 305.498C181.488 305.498 180.715 305.885 179.477 306.736L150.322 334.832L142.202 295.049C141.815 293.81 141.428 293.036 140.191 292.649C139.417 292.262 138.18 292.262 136.943 292.649L98.8161 307.51L106.55 267.727C106.936 266.488 106.55 265.327 105.776 264.476C105.003 263.702 104.152 263.237 102.915 263.237L62.0041 262.463L84.7409 228.717C85.1276 227.943 85.5143 226.705 85.1276 225.466C84.7409 224.228 83.8902 223.454 83.1168 223.067L45.8409 206.968L79.8687 184.444C80.6421 183.67 81.4928 182.819 81.4928 181.658C81.4928 180.419 81.1061 179.645 80.2554 178.407L51.8731 149.459L92.0105 141.41C93.2479 141.023 94.0212 140.636 94.4079 139.397C94.7946 138.623 95.1813 137.385 94.4079 136.147L79.4047 98.3754L119.542 106.038C120.779 106.038 121.94 106.038 122.79 105.264C123.564 104.49 124.028 103.639 124.028 102.478L124.801 62.307L158.829 84.8304C159.602 85.2174 160.066 85.6044 160.84 85.6044C161.304 85.6044 161.69 85.2174 162.077 85.2174V85.2174ZM193.707 0C192.47 0.387 191.697 1.2384 191.31 2.0124L179.168 30.186L161.69 4.7988C160.453 3.1734 158.055 2.7864 156.431 4.0248C154.807 5.2632 154.421 7.6626 155.194 9.288L175.069 38.5452L158.829 75.9294L124.801 53.406L125.188 18.0342C125.188 16.0218 123.564 14.3964 121.553 14.0094C120.779 14.0094 119.929 14.3964 119.155 14.7834C118.382 15.5574 117.918 16.4088 117.918 17.5698L117.531 48.1428L91.5465 31.347C89.9224 30.1086 87.525 30.573 86.2876 32.5854C85.0502 34.2108 85.5143 36.6102 87.525 37.8486L117.531 57.5082L116.758 98.5302L76.2339 90.8676L63.1642 57.8952C62.3908 55.8828 60.3027 55.1088 58.292 55.8828C57.5186 56.2698 56.6679 56.6568 56.2813 57.5082C55.8946 58.2822 55.5079 59.5206 56.2813 60.759L67.6497 89.3196L37.2566 83.2824C35.2459 82.8954 33.2351 84.0564 32.7711 86.0688C32.3844 88.0812 33.5445 90.0936 35.6326 90.4806L70.8978 97.2918L85.9009 135.063L45.7636 143.113L21.0161 117.803C19.392 116.177 16.9946 116.177 15.7572 117.803C14.9838 118.577 14.5198 119.428 14.5198 120.202C14.5198 121.441 14.9065 122.215 15.7572 123.453L37.2566 145.512L6.86357 151.549C4.85283 151.936 3.61546 153.949 4.00214 155.961C4.38882 157.973 6.39955 159.212 8.48762 158.747L43.3662 151.549L72.1351 180.884L38.1073 203.794L5.16218 189.707C3.15144 188.933 1.14071 189.707 0.29001 191.72C-0.09667 192.494 -0.09667 193.345 0.29001 194.119C0.67669 195.358 1.52739 196.132 2.30075 196.519L30.6831 208.593L5.16218 225.853C2.30075 227.092 1.91407 229.104 3.15144 231.116C4.38882 232.742 6.78623 233.129 8.41029 232.355L38.03 212.695L75.7699 228.794L53.0331 262.928L17.3813 262.541C15.3705 262.541 13.7465 264.166 13.3598 266.179C13.3598 266.953 13.7465 267.804 14.1332 268.578C14.9065 268.965 16.1439 269.352 17.3813 269.352L48.161 269.739L31.1471 295.436C29.9097 297.061 30.3737 299.461 32.3844 300.699C34.0085 301.937 36.4059 301.473 37.6433 299.461L57.5186 269.739L98.8934 270.513L91.1598 310.684L57.9053 323.532C55.8946 324.306 55.0439 326.318 55.8946 328.331C56.6679 330.343 58.756 331.117 60.7667 330.343L89.5357 319.12L83.0395 349.693C82.6528 351.706 83.8129 353.718 85.9009 354.105C87.9117 354.492 89.9224 353.331 90.3864 351.319L97.2693 316.334L135.396 301.473L143.516 341.257L117.995 365.792C116.371 367.418 116.371 369.817 117.995 371.056C119.619 372.681 122.017 372.681 123.254 371.056L145.527 349.771L151.637 379.957C152.023 381.969 154.034 383.207 156.122 382.743C158.133 382.356 159.37 380.344 158.983 378.331L151.714 343.346L181.334 314.786L204.457 348.532L190.691 381.892C189.918 383.904 190.691 385.916 192.702 386.69C194.713 387.464 196.723 386.69 197.574 384.678L209.716 356.504L227.116 381.814C228.354 383.44 230.751 383.827 232.375 383.053C233.999 381.814 234.386 379.415 233.613 377.789L213.737 348.455L229.978 311.071L264.47 333.594L264.083 368.966C264.083 370.978 265.707 372.604 267.718 372.991C269.729 372.991 271.353 371.365 271.739 369.353L272.126 338.78L298.034 355.653C299.658 356.891 302.055 356.427 303.292 354.415C304.53 352.789 304.066 350.39 302.055 349.151L271.662 329.492L272.435 288.47L312.959 296.132L325.952 329.105C326.725 331.117 328.813 331.891 330.824 331.117C332.835 330.343 333.685 328.331 332.835 326.318L321.466 297.758L351.859 303.795C353.87 304.182 355.881 303.021 356.345 301.009C356.732 298.996 355.572 296.984 353.483 296.597L318.218 289.786L303.215 252.014L343.352 243.965L368.1 269.275C369.724 270.9 372.121 270.9 373.359 269.275C374.983 267.649 374.983 265.25 373.359 264.011L351.859 241.875L382.252 235.838C384.263 235.451 385.501 233.438 385.114 231.426C384.727 229.414 382.716 228.175 380.628 228.64L345.75 235.838L316.981 206.503L351.009 183.593L383.876 197.68C385.887 198.454 387.898 197.68 388.749 195.667C389.522 193.655 388.749 191.642 386.738 190.868L358.356 178.794L383.876 161.534C385.501 160.295 385.887 157.896 385.114 156.271C383.876 154.645 381.479 154.258 379.855 155.032L350.235 174.692L312.495 158.593L335.619 124.459L371.271 124.846C373.281 124.846 374.906 123.221 375.292 121.208C375.292 119.196 373.668 117.571 371.657 117.184L340.878 116.797L357.892 91.0998C359.129 89.4744 358.665 87.075 356.654 85.8366C355.03 84.5982 352.633 85.0626 351.395 87.075L331.52 116.797L290.145 116.023L297.879 75.852L331.133 63.0036C333.144 62.2296 333.995 60.2172 333.144 58.2048C332.757 57.4308 332.371 56.5794 331.52 56.1924C330.747 55.8054 329.509 55.4184 328.272 56.1924L299.503 67.4154L305.612 37.2294C305.999 35.217 304.839 33.2046 302.751 32.8176C300.74 32.4306 298.73 33.5916 298.266 35.604L291.383 70.5888L253.256 85.4496L245.136 45.279L270.657 21.1302C272.281 19.5048 272.281 17.1054 270.657 15.867C269.883 15.093 269.033 14.6286 268.259 14.6286C267.022 14.6286 266.248 15.0156 265.398 15.867L243.125 37.3842L237.015 7.1982C236.629 5.1858 234.618 3.9474 232.53 4.4118C230.442 4.8762 229.282 6.8112 229.669 8.8236L236.938 43.4214L207.318 71.982L184.35 38.1582L198.502 5.5728C199.275 3.5604 198.502 1.548 196.491 0.774C196.105 0 195.254 0 194.867 0H193.707V0Z`})})}function Lj({className:e}){return(0,B.jsxs)(`svg`,{width:`122`,height:`37`,viewBox:`0 0 203 52`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,"aria-hidden":`true`,"data-slug":`api-ifttt`,className:e,children:[(0,B.jsx)(`title`,{children:`IFTTT`}),(0,B.jsx)(`desc`,{children:`IFTTT`}),(0,B.jsx)(`path`,{d:`M109.374-.25H68.0791V15.3654H80.3558V52.1734H97.0968V15.3654H109.374V-.25ZM156.249-.25H114.954V15.3654H127.231V52.1734H143.972V15.3654H156.249V-.25ZM203.123-.25H161.829V15.3654H174.105V52.1734H190.846V15.3654H203.123V-.25ZM16.741-.25H0V52.1734H16.741V-.25ZM62.4997-.25H24.5535V52.1734H41.2945V37.6734H55.8033V20.9425H41.2945V15.3654H62.4997V-.25Z`,fill:`currentColor`})]})}function Rj({className:e}){return(0,B.jsxs)(`svg`,{width:`122`,height:`37`,viewBox:`0 0 203 52`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,"aria-hidden":`true`,"data-slug":`api-ifttt-mcp`,className:e,children:[(0,B.jsx)(`title`,{children:`IFTTT`}),(0,B.jsx)(`desc`,{children:`IFTTT`}),(0,B.jsx)(`path`,{d:`M109.374-.25H68.0791V15.3654H80.3558V52.1734H97.0968V15.3654H109.374V-.25ZM156.249-.25H114.954V15.3654H127.231V52.1734H143.972V15.3654H156.249V-.25ZM203.123-.25H161.829V15.3654H174.105V52.1734H190.846V15.3654H203.123V-.25ZM16.741-.25H0V52.1734H16.741V-.25ZM62.4997-.25H24.5535V52.1734H41.2945V37.6734H55.8033V20.9425H41.2945V15.3654H62.4997V-.25Z`,fill:`currentColor`})]})}function zj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-airtable`,className:e,children:(0,B.jsx)(`path`,{d:`M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257zM23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596zM.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z`})})}function Bj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-asana`,className:e,children:(0,B.jsx)(`path`,{d:`M18.78 12.653c-2.882 0-5.22 2.336-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.34 5.22-5.22-2.336-5.22-5.22-5.22zm-13.56 0c-2.88 0-5.22 2.337-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.338 5.22-5.22-2.336-5.22-5.22-5.22zm12-6.525c0 2.883-2.337 5.22-5.22 5.22-2.882 0-5.22-2.337-5.22-5.22 0-2.88 2.338-5.22 5.22-5.22 2.883 0 5.22 2.34 5.22 5.22z`})})}function Vj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`-0.2 -3.57 36.48 36.48`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-attio`,className:e,children:(0,B.jsx)(`path`,{d:`m35.705 20.45-3.014-4.778s-.011-.02-.018-.029l-.238-.375a2.44 2.44 0 0 0-2.072-1.142l-4.854-.015-.34.537-5.8 9.195-.32.509 2.43 3.846a2.43 2.43 0 0 0 2.079 1.142h6.803c.839 0 1.633-.438 2.077-1.14l.24-.38s.009-.01.01-.015l3.02-4.784a2.41 2.41 0 0 0 0-2.572zm-.92 2-3.018 4.784q-.021.032-.042.058a.41.41 0 0 1-.652-.06l-3.02-4.784a1.3 1.3 0 0 1-.154-.344 1.37 1.37 0 0 1 0-.737c.034-.118.085-.236.152-.342l3.014-4.78.007-.01a.38.38 0 0 1 .24-.172c.031-.009.058-.011.08-.015h.034c.07 0 .243.022.35.195l3.014 4.777a1.34 1.34 0 0 1 0 1.43zM26.786 8.89a2.42 2.42 0 0 0 0-2.572l-3.014-4.777-.251-.402A2.44 2.44 0 0 0 21.442 0H14.64c-.85 0-1.626.426-2.08 1.142L.378 20.452A2.4 2.4 0 0 0 0 21.738c0 .453.13.9.374 1.284l3.268 5.181a2.44 2.44 0 0 0 2.076 1.14h6.804c.854 0 1.63-.427 2.079-1.142l.248-.391v-.005s.005-.006.005-.008l2.429-3.847 7.198-11.409 2.3-3.649zm-.71-1.286c0 .247-.07.496-.212.715L13.93 27.237a.41.41 0 0 1-.35.19c-.07 0-.24-.02-.35-.19l-3.016-4.786a1.35 1.35 0 0 1 0-1.428L22.15 2.11a.41.41 0 0 1 .35-.193c.069 0 .242.02.352.195l3.013 4.777c.142.22.211.469.211.715`})})}function Hj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-bitbucket`,className:e,children:(0,B.jsx)(`path`,{d:`M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z`})})}function Uj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-box`,className:e,children:(0,B.jsx)(`path`,{d:`M.959 5.523c-.54 0-.959.42-.959.899v7.549a4.59 4.59 0 004.613 4.494 4.717 4.717 0 004.135-2.457c.779 1.438 2.337 2.457 4.074 2.457 2.577 0 4.674-2.037 4.674-4.613.06-2.457-2.037-4.495-4.613-4.495-1.738 0-3.295.959-4.074 2.397-.78-1.438-2.338-2.397-4.135-2.397-1.079 0-2.038.36-2.817.899V6.422a.92.92 0 00-.898-.899zM17.602 9.26a.95.95 0 00-.704.158c-.36.3-.479.899-.18 1.318l2.397 3.116-2.396 3.115c-.3.42-.24.96.18 1.26.419.3 1.016.298 1.316-.122l2.039-2.636 2.096 2.697c.3.36.899.419 1.318.12.36-.3.42-.84.121-1.259l-2.338-3.115 2.338-3.057c.3-.419.298-1.018-.121-1.318-.48-.3-1.019-.24-1.318.18l-2.096 2.576-2.04-2.695c-.149-.18-.373-.3-.612-.338zM4.613 11.154c1.558 0 2.817 1.26 2.817 2.758 0 1.558-1.259 2.756-2.817 2.756-1.558 0-2.816-1.198-2.816-2.756 0-1.498 1.258-2.758 2.816-2.758zm8.27 0c1.558 0 2.816 1.26 2.816 2.758-.06 1.558-1.318 2.756-2.816 2.756-1.558 0-2.817-1.198-2.817-2.756 0-1.498 1.259-2.758 2.817-2.758Z`})})}function Wj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-calendly`,className:e,children:(0,B.jsx)(`path`,{d:`M19.655 14.262c.281 0 .557.023.828.064 0 .005-.005.01-.005.014-.105.267-.234.534-.381.786l-1.219 2.106c-1.112 1.936-3.177 3.127-5.411 3.127h-2.432c-2.23 0-4.294-1.191-5.412-3.127l-1.218-2.106a6.251 6.251 0 0 1 0-6.252l1.218-2.106C6.736 4.832 8.8 3.641 11.035 3.641h2.432c2.23 0 4.294 1.191 5.411 3.127l1.219 2.106c.147.252.271.519.381.786 0 .004.005.009.005.014-.267.041-.543.064-.828.064-1.816 0-2.501-.607-3.291-1.306-.764-.676-1.711-1.517-3.44-1.517h-1.029c-1.251 0-2.387.455-3.2 1.278-.796.805-1.233 1.904-1.233 3.099v1.411c0 1.196.437 2.295 1.233 3.099.813.823 1.949 1.278 3.2 1.278h1.034c1.729 0 2.676-.841 3.439-1.517.791-.703 1.471-1.306 3.287-1.301Zm.005-3.237c.399 0 .794-.036 1.179-.11-.002-.004-.002-.01-.002-.014-.073-.414-.193-.823-.349-1.218.731-.12 1.407-.396 1.986-.819 0-.004-.005-.013-.005-.018-.331-1.085-.832-2.101-1.489-3.03-.649-.915-1.435-1.719-2.331-2.395-1.867-1.398-4.088-2.138-6.428-2.138-1.448 0-2.855.28-4.175.841-1.273.543-2.423 1.315-3.407 2.299S2.878 6.552 2.341 7.83c-.557 1.324-.842 2.726-.842 4.175 0 1.448.281 2.855.842 4.174.542 1.274 1.314 2.423 2.298 3.407s2.129 1.761 3.407 2.299c1.324.556 2.727.841 4.175.841 2.34 0 4.561-.74 6.428-2.137a10.815 10.815 0 0 0 2.331-2.396c.652-.929 1.158-1.949 1.489-3.03 0-.004.005-.014.005-.018-.579-.423-1.255-.699-1.986-.819.161-.395.276-.804.349-1.218.005-.009.005-.014.005-.023.869.166 1.692.506 2.404 1.035.685.505.552 1.075.446 1.416C22.184 20.437 17.619 24 12.221 24c-6.625 0-12-5.375-12-12s5.37-12 12-12c5.398 0 9.963 3.563 11.471 8.464.106.341.239.915-.446 1.421-.717.529-1.535.873-2.404 1.034.128.716.128 1.45 0 2.166-.387-.074-.782-.11-1.182-.11-4.184 0-3.968 2.823-6.736 2.823h-1.029c-1.899 0-3.15-1.357-3.15-3.095v-1.411c0-1.738 1.251-3.094 3.15-3.094h1.034c2.768 0 2.552 2.823 6.731 2.827Z`})})}function Gj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`2 2 12 12`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-capsule-crm`,className:e,children:[(0,B.jsx)(`path`,{d:`M4.5 2C3.84635 2.00041 3.21885 2.25679 2.75193 2.71423C2.285 3.17166 2.01579 3.79376 2.00195 4.44727H2V11.4258H2.00195C2.00093 11.4505 2.00028 11.4753 2 11.5C2.00005 12.163 2.26346 12.7989 2.73229 13.2677C3.20112 13.7365 3.83697 14 4.5 14C5.16303 14 5.79888 13.7365 6.26771 13.2677C6.73654 12.7989 6.99995 12.163 7 11.5C7.00037 11.4753 7.00037 11.4505 7 11.4258V4.44727C6.98616 3.79342 6.71667 3.17103 6.24933 2.71354C5.78199 2.25606 5.15399 1.9999 4.5 2V2ZM4.5 3C4.96413 3 5.40925 3.18437 5.73744 3.51256C6.06563 3.84075 6.25 4.28587 6.25 4.75C6.25 5.21413 6.06563 5.65925 5.73744 5.98744C5.40925 6.31563 4.96413 6.5 4.5 6.5C4.03587 6.5 3.59075 6.31563 3.26256 5.98744C2.93437 5.65925 2.75 5.21413 2.75 4.75C2.75 4.28587 2.93437 3.84075 3.26256 3.51256C3.59075 3.18437 4.03587 3 4.5 3V3ZM4.48242 9.51172C4.48828 9.51169 4.49414 9.51169 4.5 9.51172C4.73394 9.51153 4.96555 9.55829 5.18111 9.64923C5.39667 9.74017 5.59181 9.87345 5.75498 10.0412C5.91815 10.2089 6.04603 10.4076 6.13105 10.6257C6.21608 10.8437 6.25653 11.0766 6.25 11.3105C6.23728 11.7663 6.04729 12.1991 5.72045 12.5168C5.39361 12.8345 4.95573 13.0121 4.5 13.0117C4.04427 13.0121 3.60639 12.8345 3.27955 12.5168C2.95271 12.1991 2.76272 11.7663 2.75 11.3105C2.7435 11.0781 2.78339 10.8466 2.86733 10.6298C2.95127 10.4129 3.07758 10.2149 3.23888 10.0474C3.40018 9.87996 3.59323 9.74632 3.80674 9.65434C4.02026 9.56235 4.24996 9.51387 4.48242 9.51172V9.51172ZM3.75 10.5117V12.0117H5.25V10.5117H3.75Z`}),(0,B.jsx)(`path`,{d:`M14 5.75C14 5.33579 13.6642 5 13.2499 5C12.8358 5 12.5 5.33579 12.5 5.75C12.5 6.1642 12.8358 6.5 13.2499 6.5C13.6642 6.5 14 6.1642 14 5.75V5.75Z`}),(0,B.jsx)(`path`,{d:`M13.2582 2C13.6725 2 14.0083 2.33579 14.0083 2.75C14.0083 3.16421 13.6725 3.5 13.2582 3.5C12.8442 3.5 12.5083 3.16421 12.5083 2.75C12.5083 2.33579 12.8442 2 13.2582 2Z`}),(0,B.jsx)(`path`,{d:`M10.5 5.75C10.5 5.33579 10.1642 5 9.75004 5C9.3358 5 9 5.33579 9 5.75C9 6.1642 9.3358 6.5 9.75004 6.5C10.1642 6.5 10.5 6.1642 10.5 5.75Z`}),(0,B.jsx)(`path`,{d:`M9.75004 2C10.1642 2 10.5 2.33579 10.5 2.75C10.5 3.16421 10.1642 3.5 9.75004 3.5C9.3358 3.5 9 3.16421 9 2.75C9 2.33579 9.3358 2 9.75004 2Z`}),(0,B.jsx)(`path`,{d:`M14 8.99805L9 9V13.998H14V8.99805ZM10 10.0078H13V10.998H10V10.0078ZM10 11.998H13V12.998H10V11.998Z`})]})}function Kj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-clickup`,className:e,children:(0,B.jsx)(`path`,{d:`M2 18.439l3.69-2.828c1.961 2.56 4.044 3.739 6.363 3.739 2.307 0 4.33-1.166 6.203-3.704L22 18.405C19.298 22.065 15.941 24 12.053 24 8.178 24 4.788 22.078 2 18.439zM12.04 6.15l-6.568 5.66-3.036-3.52L12.055 0l9.543 8.296-3.05 3.509z`})})}function qj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-crowdin`,className:e,children:(0,B.jsx)(`path`,{d:`M16.119 17.793a2.619 2.619 0 0 1-1.667-.562c-.546-.436-1.004-1.09-1.018-1.858-.008-.388.414-.388.414-.388l1.018-.008c.332.008.43.47.445.586.128 1.04.717 1.495 1.168 1.702.273.123.204.513-.362.528zm-5.695-5.287L8.5 12.252c-.867-.214-.844-.982-.807-1.247a5.119 5.119 0 0 1 .814-2.125c.545-.804 1.303-1.508 2.29-2.073 1.856-1.074 4.45-1.673 7.31-1.673 2.09 0 4.256.27 4.29.27.197.025.328.213.333.437a.377.377 0 0 1-.355.393l-.92-.01c-2.902 0-4.968.394-6.506 1.248-1.527.837-2.57 2.117-3.287 4.012-.076.163-.335 1.12-1.24 1.022zm2.533 7.823c-1.44 0-2.797-.622-3.825-1.746-.87-.96-1.397-1.931-1.493-3.164-.06-.813.3-1.094.788-1.044l1.988.218c.45.092.75.34.825.854.397 2.736 2.122 3.814 3.15 4.046.18.042.292.157.283.365a.412.412 0 0 1-.322.398c-.458.074-.936.073-1.394.073zm-4.101 2.418a14.216 14.216 0 0 1-2.307-.214c-1.202-.214-2.208-.582-3.072-1.13C1.41 20.095.163 17.786.014 15.048c-.037-.65-.11-1.89 1.427-1.797.638.033 1.653.343 2.368.548.887.247 1.314.933 1.314 1.608 0 3.858 3.494 6.408 5.02 6.408.654 0 .414.701.127.779-.502.136-1.15.153-1.413.153zM3.525 11.419c-.605-.109-1.194-.358-1.768-.5C-.018 10.479.284 8.688.45 8.196c1.617-4.757 6.746-6.35 10.887-6.773 3.898-.4 7.978-.092 11.778.967.31.083 1.269.327.718.891-.35.358-1.7-.016-2.073-.041-2.23-.167-4.434-.192-6.656.15-2.349.357-4.768 1.099-6.71 2.665-.938.758-1.76 1.723-2.313 2.866-.144.3-.256.6-.354.9-.11.327-.47 1.91-2.215 1.6zm9.94.917c.332-1.488 1.81-3.848 6.385-3.686 1.05.033.57.749.052.731-2.586-.09-3.815 1.578-4.457 3.27-.219.546-.68.626-1.271.53-.415-.074-.866-.123-.71-.846Z`})})}function Jj({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`-0.72 0.25 27.7 27.7`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-dialpad`,className:e,children:[(0,B.jsx)(`path`,{d:`M18.3259 27.6509C14.4999 27.6509 11.7516 24.5636 11.7516 20.3387C11.7516 16.1139 14.4999 13.0536 18.3259 13.0536C20.1042 13.0536 21.8017 13.9473 22.5561 15.1118V7.58294H25.6816V27.3801H22.5561V25.5656C21.8017 26.7301 20.1042 27.6509 18.3259 27.6509ZM18.8378 24.9156C20.8586 24.9156 22.3675 23.3449 22.5561 21.2054V19.4179C22.3675 17.2784 20.8586 15.7889 18.8378 15.7889C16.5745 15.7889 14.9579 17.6034 14.9579 20.3116C14.9579 23.0199 16.5745 24.9156 18.8378 24.9156Z`}),(0,B.jsx)(`path`,{d:`M14.7974 7.43848C14.9975 7.48324 15.0372 7.80998 14.8501 7.89512C11.2371 9.125 8.39325 12.2693 6.90835 16.4551C6.82708 16.6581 6.47057 16.6091 6.43706 16.3925C5.92911 13.1096 3.66093 10.4884 0.738931 9.78109C0.545298 9.73422 0.508733 9.43003 0.688975 9.34393C4.39869 7.81758 7.18675 4.68653 8.47503 0.686265C8.55549 0.477551 8.94421 0.530264 8.97728 0.751999C9.47675 4.10041 11.8058 6.76949 14.7974 7.43848Z`})]})}function Yj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-dropbox`,className:e,children:(0,B.jsx)(`path`,{d:`M6 1.807L0 5.629l6 3.822 6.001-3.822L6 1.807zM18 1.807l-6 3.822 6 3.822 6-3.822-6-3.822zM0 13.274l6 3.822 6.001-3.822L6 9.452l-6 3.822zM18 9.452l-6 3.822 6 3.822 6-3.822-6-3.822zM6 18.371l6.001 3.822 6-3.822-6-3.822L6 18.371z`})})}function Xj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 256 256`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-eventbrite`,className:e,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M128 0a128 128 0 1 0 0 256A128 128 0 1 0 128 0zM117.475 82.729c19.297-4.288 38.595 3.574 49.315 18.582L81.502 120.61c2.859-18.345 16.676-33.592 35.973-37.88m49.792 70.756c-6.671 9.768-16.915 16.677-28.589 19.297c-19.297 4.288-38.832-3.573-49.553-18.82l85.527-19.298l13.818-3.097l26.682-5.956c-.238-5.717-.953-11.435-2.144-16.914c-10.72-45.98-57.653-74.806-104.586-64.086s-76.235 56.462-65.276 102.68s57.653 74.806 104.585 64.085c27.636-6.194 49.077-24.776 60.036-48.361z`})})}function Zj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-figma`,className:e,children:(0,B.jsx)(`path`,{d:`M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z`})})}function Qj({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-gitlab`,className:e,children:(0,B.jsx)(`path`,{d:`m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z`})})}function $j({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-hubspot`,className:e,children:(0,B.jsx)(`path`,{d:`M18.164 7.93V5.084a2.198 2.198 0 001.267-1.978v-.067A2.2 2.2 0 0017.238.845h-.067a2.2 2.2 0 00-2.193 2.193v.067a2.196 2.196 0 001.252 1.973l.013.006v2.852a6.22 6.22 0 00-2.969 1.31l.012-.01-7.828-6.095A2.497 2.497 0 104.3 4.656l-.012.006 7.697 5.991a6.176 6.176 0 00-1.038 3.446c0 1.343.425 2.588 1.147 3.607l-.013-.02-2.342 2.343a1.968 1.968 0 00-.58-.095h-.002a2.033 2.033 0 102.033 2.033 1.978 1.978 0 00-.1-.595l.005.014 2.317-2.317a6.247 6.247 0 104.782-11.134l-.036-.005zm-.964 9.378a3.206 3.206 0 113.215-3.207v.002a3.206 3.206 0 01-3.207 3.207z`})})}function eM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-intercom`,className:e,children:(0,B.jsx)(`path`,{d:`M21 0H3C1.343 0 0 1.343 0 3v18c0 1.658 1.343 3 3 3h18c1.658 0 3-1.342 3-3V3c0-1.657-1.342-3-3-3zm-5.801 4.399c0-.44.36-.8.802-.8.44 0 .8.36.8.8v10.688c0 .442-.36.801-.8.801-.443 0-.802-.359-.802-.801V4.399zM11.2 3.994c0-.44.357-.799.8-.799s.8.359.8.799v11.602c0 .44-.357.8-.8.8s-.8-.36-.8-.8V3.994zm-4 .405c0-.44.359-.8.799-.8.443 0 .802.36.802.8v10.688c0 .442-.36.801-.802.801-.44 0-.799-.359-.799-.801V4.399zM3.199 6c0-.442.36-.8.802-.8.44 0 .799.358.799.8v7.195c0 .441-.359.8-.799.8-.443 0-.802-.36-.802-.8V6zM20.52 18.202c-.123.105-3.086 2.593-8.52 2.593-5.433 0-8.397-2.486-8.521-2.593-.335-.288-.375-.792-.086-1.128.285-.334.79-.375 1.125-.09.047.041 2.693 2.211 7.481 2.211 4.848 0 7.456-2.186 7.479-2.207.334-.289.839-.25 1.128.086.289.336.25.84-.086 1.128zm.281-5.007c0 .441-.36.8-.801.8-.441 0-.801-.36-.801-.8V6c0-.442.361-.8.801-.8.441 0 .801.357.801.8v7.195z`})})}function tM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-jira`,className:e,children:(0,B.jsx)(`path`,{d:`M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z`})})}function nM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-linear`,className:e,children:(0,B.jsx)(`path`,{d:`M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z`})})}function rM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-miro`,className:e,children:(0,B.jsx)(`path`,{d:`M17.392 0H13.9L17 4.808 10.444 0H6.949l3.102 6.3L3.494 0H0l3.05 8.131L0 24h3.494L10.05 6.985 6.949 24h3.494L17 5.494 13.899 24h3.493L24 3.672 17.392 0z`})})}function iM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-pagerduty`,className:e,children:(0,B.jsx)(`path`,{d:`M16.965 1.18C15.085.164 13.769 0 10.683 0H3.73v14.55h6.926c2.743 0 4.8-.164 6.61-1.37 1.975-1.303 3.004-3.484 3.004-6.007 0-2.716-1.262-4.896-3.305-5.994zm-5.5 10.326h-4.21V3.113l3.977-.027c3.62-.028 5.43 1.234 5.43 4.128 0 3.113-2.248 4.292-5.197 4.292zM3.73 17.61h3.525V24H3.73Z`})})}function aM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 -44 256 256`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-productboard`,className:e,children:[(0,B.jsx)(`path`,{d:`M85.33 89.61 L160.89 163.99 L9.77 163.99z`}),(0,B.jsx)(`path`,{d:`M9.77 4 L85.33 78.38 L160.89 4z`}),(0,B.jsx)(`path`,{d:`M91.04 84 L170.67 162.38 L250.29 84 L170.67 5.61z`})]})}function oM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-sentry`,className:e,children:(0,B.jsx)(`path`,{d:`M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z`})})}function sM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`-0.2 -0.92 32.67 32.67`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-shippo`,className:e,children:(0,B.jsx)(`path`,{d:`M29.818 6.826c.865.695 1.539 1.612 1.956 2.659.382.935.546 1.949.48 2.962a6 6 0 0 1-.866 2.704c-.529.849-1.253 1.55-2.107 2.041a13.94 13.94 0 0 1-1.476.678l-1.378.667c-1.049.546-2.055 1.157-3.049 1.792l-3.206 2.165c-.769.543-3.395 4.498-4.702 6.511 5.978-.259 11.036-4.646 12.318-10.682l.171-.075.281-.12.094-.04c.392-.154.773-.334 1.142-.539a5.3 5.3 0 0 0 .242-.152c-.902 7.171-6.509 12.729-13.482 13.366S2.798 26.943.693 20.047 1.406 5.67 7.471 2.06 21.216-.321 25.938 5.018h-.182a7.2 7.2 0 0 0-1.182.098 6.79 6.79 0 0 0-.833.188C19.913 1.743 14.426.814 9.698 2.927s-7.831 6.878-7.94 12.198c1.56-.511 4.269-1.435 4.584-1.769.671-.714 1.342-1.433 1.971-2.184a6.32 6.32 0 0 0 .533-.695c.05-.081.09-.167.122-.257a2.68 2.68 0 0 1 .413-.734 4.6 4.6 0 0 1-.213-.684 2.17 2.17 0 0 1-.182-.543c-.112-.48-.152-.976-.118-1.469.029-.4.147-1.014.651-1.014a.92.92 0 0 1 .456.21 8.44 8.44 0 0 1 1.411 1.196h.047l1.611-.188a10.37 10.37 0 0 1 2.015-.107c.294.028.584.091.864.188a9.1 9.1 0 0 0 .884.248 7.44 7.44 0 0 0 1.729.188 6.27 6.27 0 0 0 3.167-.939l.381-.172.565-.254a8.29 8.29 0 0 1 2-.633 6.69 6.69 0 0 1 2.011-.032 6.52 6.52 0 0 1 3.16 1.345zm-14.221 4.657c.474.3 1.076.277 1.527-.058.429-.344.591-.936.4-1.461-.201-.541-.699-.903-1.26-.915a1.3 1.3 0 0 0-1.2.749l.034-.099s-.028.08-.065.169a1.39 1.39 0 0 1 .031-.071l-.033.098c-.105.317-.06.192-.003.037l.003-.008c-.02.057-.036.115-.047.175a1.37 1.37 0 0 0 .613 1.384z`})})}function cM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-square`,className:e,children:(0,B.jsx)(`path`,{d:`M4.01 0A4.01 4.01 0 000 4.01v15.98c0 2.21 1.8 4 4.01 4.01h15.98C22.2 24 24 22.2 24 19.99V4A4.01 4.01 0 0019.99 0H4zm1.62 4.36h12.74c.7 0 1.26.57 1.26 1.27v12.74c0 .7-.56 1.27-1.26 1.27H5.63c-.7 0-1.26-.57-1.26-1.27V5.63a1.27 1.27 0 011.26-1.27zm3.83 4.35a.73.73 0 00-.73.73v5.09c0 .4.32.72.72.72h5.1a.73.73 0 00.73-.72V9.44a.73.73 0 00-.73-.73h-5.1Z`})})}function lM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-todoist`,className:e,children:(0,B.jsx)(`path`,{d:`M21 0H3C1.35 0 0 1.35 0 3v3.858s3.854 2.24 4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.608 8.136-4.675.279-.161.58-.107.748-.01.164.097.606.348.84.48.232.134.221.502.013.622l-9.712 5.59c-.346.2-.69.204-1.048.002C3.478 10.907.998 9.463 0 8.882v2.02l4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.609 8.136-4.676.279-.16.58-.106.748-.008.164.096.606.347.84.48.232.133.221.5.013.62-.208.121-9.288 5.346-9.712 5.59-.346.2-.69.205-1.048.002C3.478 14.951.998 13.506 0 12.926v2.02l4.098 2.38c.31.18.694.177 1.004 0 .26-.147 8.02-4.609 8.136-4.676.279-.16.58-.106.748-.009.164.097.606.348.84.48.232.133.221.502.013.622l-9.712 5.59c-.346.199-.69.204-1.048.001C3.478 18.994.998 17.55 0 16.97V21c0 1.65 1.35 3 3 3h18c1.65 0 3-1.35 3-3V3c0-1.65-1.35-3-3-3z`})})}function uM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,"aria-hidden":`true`,"data-slug":`api-zoom`,className:e,children:(0,B.jsx)(`path`,{d:`M5.033 14.649H.743a.74.74 0 0 1-.686-.458.74.74 0 0 1 .16-.808L3.19 10.41H1.06A1.06 1.06 0 0 1 0 9.35h3.957c.301 0 .57.18.686.458a.74.74 0 0 1-.161.808L1.51 13.59h2.464c.585 0 1.06.475 1.06 1.06zM24 11.338c0-1.14-.927-2.066-2.066-2.066-.61 0-1.158.265-1.537.686a2.061 2.061 0 0 0-1.536-.686c-1.14 0-2.066.926-2.066 2.066v3.311a1.06 1.06 0 0 0 1.06-1.06v-2.251a1.004 1.004 0 0 1 2.013 0v2.251c0 .586.474 1.06 1.06 1.06v-3.311a1.004 1.004 0 0 1 2.012 0v2.251c0 .586.475 1.06 1.06 1.06zM16.265 12a2.728 2.728 0 1 1-5.457 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0zm-4.82 0a2.728 2.728 0 1 1-5.458 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0z`})})}function dM({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(ci,{className:`h-3.5 w-3.5`,strokeWidth:2.5}),children:(0,B.jsx)(VA,{"data-slug":`aws-cost-explorer`,className:`h-5 w-5`})})}function fM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 155.343 151`,className:e,fill:`currentColor`,"data-slug":`aevatar`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M69.2512 59.3131H57.5239V13.1698H41.5711V45.3592H11.8454V59.3291H0V32.6979H28.6214V0.23637C28.9318 0.212513 29.2172 0.185583 29.4858 0.160233C30.0282 0.109045 30.5024 0.0642984 30.9766 0.0642984C34.8603 0.0578252 38.7436 0.0559593 42.6267 0.0540935C50.7461 0.0501922 58.8649 0.0462912 66.9856 0.000271626C68.7622 -0.0117334 69.4182 0.368426 69.4101 2.29323C69.3484 16.1531 69.3521 30.0111 69.3557 43.8699C69.3569 48.3252 69.3581 52.7806 69.3571 57.2362C69.3571 57.6665 69.3284 58.0973 69.2971 58.5651C69.2813 58.8018 69.2648 59.0483 69.2512 59.3091V59.3131Z`}),(0,B.jsx)(`path`,{d:`M93.9689 83.7439V83.7479L93.9648 83.7439H93.9689Z`}),(0,B.jsx)(`path`,{d:`M93.9689 83.7439V72.159H143.294V45.4719H113.565V13.1383H97.844V59.3056H85.9334V0.204906H126.368V32.5424H155.343V119.371H126.364V151H85.9742V93.9121H97.6851V138.259H113.479V107.01H143.249V83.7439H93.9689Z`}),(0,B.jsx)(`path`,{d:`M0.0570124 72.1751H60.6452V83.676H11.988V106.89H41.404V138.223H57.3894V93.8642H69.1859V150.944H28.8373V119.483H0.0570124V72.1751Z`}),(0,B.jsx)(`path`,{d:`M69.4222 83.6277H85.1917V68.3493H69.4222V83.6277Z`})]})}function pM({className:e}){return(0,B.jsx)(mM,{className:e,"data-slug":`cma`})}function mM(e){return(0,B.jsxs)(`svg`,{viewBox:`13 13 74 65`,"aria-hidden":`true`,"data-cma-glyph":`true`,...e,children:[(0,B.jsx)(`path`,{d:`M71 71 A30 30 0 1 0 29 71`,fill:`none`,stroke:`currentColor`,strokeWidth:`11`,strokeLinecap:`round`}),(0,B.jsx)(`path`,{d:`M71 71 L61 59 L50 71 L39 59 L29 71`,fill:`none`,stroke:`currentColor`,strokeWidth:`10`,strokeLinecap:`round`,strokeLinejoin:`round`}),(0,B.jsx)(`rect`,{x:`30`,y:`38`,width:`17`,height:`12`,rx:`4`,fill:`currentColor`}),(0,B.jsx)(`rect`,{x:`53`,y:`38`,width:`17`,height:`12`,rx:`4`,fill:`currentColor`}),(0,B.jsx)(`path`,{d:`M46 44 L54 44`,fill:`none`,stroke:`currentColor`,strokeWidth:`4`})]})}function hM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 64 64`,className:e,fill:`currentColor`,"data-slug":`cmaeg`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M6 60V27L19 9L30 3.9V19L19 31V60Z`}),(0,B.jsx)(`path`,{d:`M58 60V27L45 9L34 3.9V19L45 31V60Z`}),(0,B.jsx)(`path`,{d:`M32 29L39 38V49L32 58L25 49V38Z`})]})}function gM({className:e}){return(0,B.jsx)(UA,{className:e,badge:(0,B.jsx)(bi,{strokeWidth:2.5}),children:(0,B.jsx)(mM,{"data-slug":`cma-trigger-github-observer-staging`,className:`h-full w-full`})})}function _M(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 747 444`,fill:`currentColor`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{pathLength:1,d:`M216.2,411.1 C229.3,406.1 244.4,393.4 256.2,377.5 C272.6,355.6 284.6,329.3 304.5,271.5 C306.6,265.4 308.6,260.1 309.0,259.7 C309.4,259.3 313.0,264.4 317.0,271.2 C340.1,310.0 355.7,323.1 377.4,321.8 C394.8,320.7 406.0,311.3 425.4,281.8 C431.8,272.0 437.2,264.0 437.4,264.0 C437.6,264.0 439.1,267.7 440.5,272.2 C457.3,323.1 472.1,355.1 490.1,378.6 C513.2,409.1 541.4,420.8 565.7,410.0 L570.1,408.1 L560.1,398.3 C535.3,373.8 520.7,340.3 495.0,249.0 C490.1,231.7 484.5,212.3 482.6,206.0 L479.1,194.5 L481.1,190.5 C486.7,179.6 503.7,161.4 515.4,153.6 C525.3,147.1 530.1,145.6 542.0,145.6 C554.6,145.5 563.6,148.3 575.5,156.1 C588.4,164.5 612.0,189.2 612.0,194.3 C612.0,195.4 609.5,200.4 606.4,205.6 C595.9,223.3 590.4,238.8 578.8,283.5 C566.8,330.3 560.7,347.8 551.0,363.6 C548.2,368.1 546.0,372.4 546.0,373.1 C546.0,376.8 560.0,393.8 567.4,399.2 C569.5,400.7 572.4,402.0 573.7,402.0 C581.3,402.0 598.4,373.4 608.5,344.1 C614.8,325.6 619.6,307.0 628.0,269.7 C635.5,236.3 637.2,229.5 638.2,229.5 C638.5,229.5 644.4,236.1 651.3,244.1 C670.0,265.8 679.6,273.9 691.6,278.1 C698.0,280.4 707.7,280.6 712.5,278.6 C719.4,275.7 718.4,270.4 706.5,246.0 C681.7,195.5 664.5,178.0 639.5,178.0 L631.0,178.0 L626.2,171.3 C606.4,143.4 580.8,119.2 563.6,112.1 C555.5,108.8 542.7,108.1 534.4,110.5 C516.6,115.5 494.8,136.6 478.7,164.2 C475.8,169.0 473.2,172.6 472.9,172.2 C472.5,171.8 468.5,161.6 464.0,149.5 C447.3,104.0 438.3,84.1 426.8,66.9 C420.3,57.2 410.7,45.6 406.7,42.6 C404.0,40.6 403.9,40.6 400.4,42.5 C393.3,46.3 374.0,69.2 374.0,73.7 C374.0,74.8 376.4,80.8 379.4,87.1 C391.3,112.3 402.5,144.6 420.1,204.5 L431.2,242.5 L429.6,247.7 C426.1,258.8 410.8,277.3 398.7,285.1 C378.1,298.4 356.3,293.8 335.2,271.6 C327.3,263.2 314.0,245.1 314.0,242.5 C314.0,241.6 316.7,230.8 320.1,218.7 C340.3,145.0 347.4,122.6 359.1,95.6 C369.1,72.3 373.4,65.7 387.8,51.0 C394.5,44.2 399.8,38.0 399.6,37.3 C398.5,34.5 382.9,30.7 372.5,30.7 C336.4,30.6 308.6,65.6 282.0,144.6 C275.1,165.0 273.5,168.9 272.7,168.0 C272.5,167.7 268.7,162.1 264.3,155.5 C242.8,123.4 223.6,109.0 202.1,109.0 C177.1,109.0 155.8,124.7 126.3,164.7 L115.8,179.0 L107.1,179.0 C92.9,179.0 83.6,182.7 74.8,191.7 C58.3,208.7 29.1,261.9 30.2,273.1 C30.6,277.9 36.0,280.5 45.2,280.4 C60.6,280.1 79.2,266.8 97.9,242.6 C109.5,227.6 108.1,227.2 112.9,246.7 C115.1,255.9 119.5,274.9 122.5,288.8 C132.7,335.0 141.0,359.7 153.9,381.2 C161.2,393.5 169.2,403.0 172.2,403.0 C177.3,403.0 185.3,395.4 195.0,381.3 L200.0,374.2 L194.1,362.8 C183.5,342.6 178.2,327.3 166.5,282.0 C156.1,242.1 149.3,223.0 138.9,204.6 C136.1,199.8 134.1,195.3 134.3,194.6 C135.3,192.2 153.2,172.2 158.7,167.4 C176.7,151.7 191.8,145.2 208.0,146.2 C225.4,147.3 238.8,155.9 256.7,177.4 C268.0,190.9 267.9,190.4 263.7,204.1 C261.7,210.4 255.2,232.6 249.1,253.5 C222.6,345.2 205.5,383.4 183.4,400.4 C176.2,405.9 175.7,407.3 180.0,409.5 C189.6,414.4 205.5,415.1 216.2,411.1 Z M70.0,238.5 C70.0,235.0 86.4,209.0 93.2,201.9 C98.4,196.4 99.6,198.5 95.4,205.8 C89.7,215.8 70.1,241.0 70.0,238.5 Z M671.8,232.8 C662.9,222.2 648.0,201.5 648.0,199.8 C648.0,198.3 650.7,199.0 653.4,201.1 C657.8,204.6 678.9,238.0 676.7,238.0 C676.4,238.0 674.2,235.6 671.8,232.8 Z`})})}function vM({slug:e,badge:t,className:n}){return(0,B.jsxs)(`span`,{className:`relative inline-flex h-full w-full shrink-0 items-center justify-center ${n??`h-5 w-5`}`,children:[(0,B.jsx)(`span`,{className:`relative inline-flex h-full w-[168%] shrink-0 items-center justify-center [&>svg]:!h-full [&>svg]:!w-full`,children:(0,B.jsx)(_M,{"data-slug":e,className:`h-full w-full`})}),(0,B.jsx)(WA,{badge:t})]})}function yM({className:e}){return(0,B.jsx)(vM,{className:e,badge:(0,B.jsx)(ii,{strokeWidth:2.5}),slug:`chrono-llm`})}function bM({className:e}){return(0,B.jsx)(vM,{className:e,badge:(0,B.jsx)(ii,{strokeWidth:2.5}),slug:`chrono-llm-public`})}function xM({className:e}){return(0,B.jsx)(vM,{className:e,badge:(0,B.jsx)(ai,{strokeWidth:2.5}),slug:`chrono-sandbox`})}function SM({className:e}){return(0,B.jsx)(vM,{className:e,badge:(0,B.jsx)(_i,{strokeWidth:2.5}),slug:`chrono-storage-service`})}function CM({className:e}){return(0,B.jsxs)(`span`,{className:`relative inline-flex shrink-0 ${e??`h-5 w-5`}`,children:[(0,B.jsx)(`svg`,{viewBox:`0 0 424 424`,className:`h-full w-full`,fill:`currentColor`,"data-slug":`llm-nyx`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{d:`M422.875 88.0461V335.824C422.875 383.898 383.903 422.87 335.829 422.87H214.328C213.008 422.87 211.938 421.799 211.938 420.48V191.899C211.938 189.461 208.72 188.587 207.487 190.69L72.0088 421.69C71.5786 422.421 70.7947 422.87 69.9486 422.87H3.39006C2.07075 422.87 1 421.799 1 420.48V3.39006C1 2.07075 2.07075 1 3.39006 1H139.237C140.556 1 141.627 2.07075 141.627 3.39006V231.971C141.627 234.409 144.844 235.284 146.077 233.18L281.56 2.18069C281.99 1.44933 282.774 1 283.62 1H335.824C383.898 1 422.87 39.9724 422.87 88.0461H422.875Z`})}),(0,B.jsx)(ii,{"aria-hidden":`true`,className:`absolute bottom-[5%] right-[5%] !h-[42%] !w-[42%] text-background`,strokeWidth:2.5})]})}function wM({className:e}){return(0,B.jsx)(`svg`,{viewBox:`0 0 64 64`,className:e,fill:`currentColor`,"data-slug":`ornn-api`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{fillRule:`evenodd`,d:`M63.39,38.24 L59.46,37.46 A28,28 0 0,1 55.28,47.56 L58.61,49.78 A32,32 0 0,1 49.78,58.61 L47.56,55.28 A28,28 0 0,1 37.46,59.46 L38.24,63.39 A32,32 0 0,1 25.76,63.39 L26.54,59.46 A28,28 0 0,1 16.44,55.28 L14.22,58.61 A32,32 0 0,1 5.39,49.78 L8.72,47.56 A28,28 0 0,1 4.54,37.46 L0.61,38.24 A32,32 0 0,1 0.61,25.76 L4.54,26.54 A28,28 0 0,1 8.72,16.44 L5.39,14.22 A32,32 0 0,1 14.22,5.39 L16.44,8.72 A28,28 0 0,1 26.54,4.54 L25.76,0.61 A32,32 0 0,1 38.24,0.61 L37.46,4.54 A28,28 0 0,1 47.56,8.72 L49.78,5.39 A32,32 0 0,1 58.61,14.22 L55.28,16.44 A28,28 0 0,1 59.46,26.54 L63.39,25.76 A32,32 0 0,1 63.39,38.24 Z M46,32 A14,14 0 1,0 18,32 A14,14 0 1,0 46,32 Z`})})}function TM({className:e}){return(0,B.jsxs)(`svg`,{viewBox:`0 0 424 424`,className:e,fill:`currentColor`,"data-slug":`talos`,"aria-hidden":`true`,children:[(0,B.jsx)(`path`,{d:`M1 1H336C384 1 423 40 423 88V141H71C32 141 1 110 1 71Z`}),(0,B.jsx)(`path`,{d:`M142 165H282V353C282 392 251 423 212 423H142Z`})]})}var EM={"llm-xai":qA,"llm-openai":GA,"llm-openai-codex":KA,"llm-anthropic":JA,"llm-google-ai":YA,"llm-mistral":XA,"llm-cohere":ZA,"llm-deepseek":QA,"llm-openclaw":$A,"llm-openrouter":ej,"api-firecrawl":tj,"api-twitter":nj,"api-google":rj,"api-google-workspace":ij,"api-google-calendar":aj,"api-google-drive":oj,"api-google-gmail":sj,"api-google-docs":cj,"api-google-sheets":lj,"api-google-slides":uj,"api-google-cloud":fj,"api-notion":dj,"api-github":pj,"api-github-pat":mj,"api-facebook":hj,"api-linkedin":gj,"api-discord":_j,"api-discord-bot":vj,"api-spotify":yj,"api-slack":bj,"api-slack-bot":xj,"api-microsoft":Sj,"api-tiktok":Cj,"api-twitch":wj,"api-reddit":Tj,"api-lark":Ej,"api-lark-bot":Dj,"api-feishu":Oj,"api-feishu-bot":kj,"api-telegram-bot":Aj,"api-whatsapp-business":jj,"api-supabase":Mj,"api-elevenlabs":Nj,"api-telnyx":Pj,telnyx:Pj,"platform-telnyx":Pj,"api-twilio":Fj,"api-aurinko":Ij,aurinko:Ij,"api-ifttt":Lj,"api-ifttt-mcp":Rj,"api-airtable":zj,"api-asana":Bj,"api-attio":Vj,"api-bitbucket":Hj,"api-box":Uj,"api-calendly":Wj,"api-capsule-crm":Gj,"api-clickup":Kj,"api-crowdin":qj,"api-dialpad":Jj,"api-dropbox":Yj,"api-eventbrite":Xj,"api-figma":Zj,"api-gitlab":Qj,"api-hubspot":$j,"api-intercom":eM,"api-jira":tM,"api-linear":nM,"api-miro":rM,"api-pagerduty":iM,"api-productboard":aM,"api-sentry":oM,"api-shippo":sM,"api-square":cM,"api-todoist":lM,"api-zoom":uM,"aws-cost-explorer":dM,aevatar:fM,cma:pM,cmaeg:hM,"cma-trigger-github-observer-staging":gM,"chrono-llm":yM,"chrono-llm-public":bM,"chrono-sandbox":xM,"chrono-storage-service":SM,"llm-nyx":CM,"ornn-api":wM,talos:TM};function DM({className:e}){return(0,B.jsx)(xi,{className:e,"aria-hidden":`true`,"data-fallback":`true`})}var OM={"2xs":`!h-3.5 !w-3.5`,xs:`!h-4 !w-4`,sm:`!h-5 !w-5`,md:`!h-6 !w-6`,lg:`!h-8 !w-8`,xl:`!h-9 !w-9`};function kM(e){if(e.length>2048||e.includes(`#`))return null;try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`||!t.hostname||t.username||t.password?null:t.href}catch{return null}}function AM({slug:e,iconUrl:t,size:n=`sm`,className:r}){let i=t?kM(t):null;return i?(0,B.jsx)(jM,{slug:e,iconUrl:i,size:n,className:r},i):!e&&!t?null:(0,B.jsx)(EM[e??`custom`]??DM,{className:Jr(OM[n],`shrink-0 text-muted-foreground`,r)})}function jM({slug:e,iconUrl:t,size:n,className:r}){let[i,a]=(0,z.useState)(!1);return i?(0,B.jsx)(AM,{slug:e??`custom`,size:n,className:r}):(0,B.jsx)(`img`,{src:t,alt:``,"aria-hidden":`true`,referrerPolicy:`no-referrer`,onError:()=>a(!0),className:Jr(OM[n],`shrink-0 object-contain`,r)})}var MM={"claude-code":`llm-anthropic`,codex:`llm-openai-codex`,openclaw:`llm-openclaw`},NM={"2xs":`!h-3.5 !w-3.5`,xs:`!h-4 !w-4`,sm:`!h-5 !w-5`,md:`!h-6 !w-6`,lg:`!h-8 !w-8`,xl:`!h-9 !w-9`};function PM(e){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`currentColor`,fillRule:`evenodd`,"aria-hidden":`true`,...e,children:(0,B.jsx)(`path`,{d:`M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z`})})}function FM({platform:e,size:t=`xs`,className:n}){if(!e||e===`__none__`)return null;let r=MM[e];if(r)return(0,B.jsx)(AM,{slug:r,size:t,className:n});let i=Jr(NM[t],`shrink-0 text-muted-foreground`,n);return e===`cursor`?(0,B.jsx)(PM,{className:i}):(0,B.jsx)(ii,{className:i,"aria-hidden":`true`})}var IM=z.forwardRef(({className:e,type:t,...n},r)=>(0,B.jsx)(`input`,{type:t,className:Jr(`text-input flex h-8 w-full rounded-lg border border-input bg-transparent px-3 py-1.5 text-12 text-foreground transition-colors duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-text-tertiary focus-visible:outline-none focus-visible:border-input-focus aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive disabled:cursor-not-allowed disabled:opacity-50`,e),ref:r,...n}));IM.displayName=`Input`;function LM(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,RM(i,...t)]}function RM(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function zM(e){let t=z.useRef({value:e,previous:e});return z.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function BM(e){let t=VM(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(UM);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function VM(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=GM(n),i=WM(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var HM=Symbol(`radix.slottable`);function UM(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===HM}function WM(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function GM(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var KM=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=BM(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),qM=`Switch`,[JM,YM]=LM(qM),[XM,ZM]=JM(qM),QM=z.forwardRef((e,t)=>{let{__scopeSwitch:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c=`on`,onCheckedChange:l,form:u,...d}=e,[f,p]=z.useState(null),m=en(t,e=>p(e)),h=z.useRef(!1),g=f?u||!!f.closest(`form`):!0,[_,v]=TD({prop:i,defaultProp:a??!1,onChange:l,caller:qM});return(0,B.jsxs)(XM,{scope:n,checked:_,disabled:s,children:[(0,B.jsx)(KM.button,{type:`button`,role:`switch`,"aria-checked":_,"aria-required":o,"data-state":rN(_),"data-disabled":s?``:void 0,disabled:s,value:c,...d,ref:m,onClick:tC(e.onClick,e=>{v(e=>!e),g&&(h.current=e.isPropagationStopped(),h.current||e.stopPropagation())})}),g&&(0,B.jsx)(nN,{control:f,bubbles:!h.current,name:r,value:c,checked:_,required:o,disabled:s,form:u,style:{transform:`translateX(-100%)`}})]})});QM.displayName=qM;var $M=`SwitchThumb`,eN=z.forwardRef((e,t)=>{let{__scopeSwitch:n,...r}=e,i=ZM($M,n);return(0,B.jsx)(KM.span,{"data-state":rN(i.checked),"data-disabled":i.disabled?``:void 0,...r,ref:t})});eN.displayName=$M;var tN=`SwitchBubbleInput`,nN=z.forwardRef(({__scopeSwitch:e,control:t,checked:n,bubbles:r=!0,...i},a)=>{let o=z.useRef(null),s=en(o,a),c=zM(n),l=PE(t);return z.useEffect(()=>{let e=o.current;if(!e)return;let t=window.HTMLInputElement.prototype,i=Object.getOwnPropertyDescriptor(t,`checked`).set;if(c!==n&&i){let t=new Event(`click`,{bubbles:r});i.call(e,n),e.dispatchEvent(t)}},[c,n,r]),(0,B.jsx)(`input`,{type:`checkbox`,"aria-hidden":!0,defaultChecked:n,...i,tabIndex:-1,ref:s,style:{...i.style,...l,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0}})});nN.displayName=tN;function rN(e){return e?`checked`:`unchecked`}var iN=QM,aN=eN,oN=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(iN,{className:Jr(`peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-300 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted`,e),...t,ref:n,children:(0,B.jsx)(aN,{className:Jr(`pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=checked]:bg-white data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-muted-foreground`)})}));oN.displayName=iN.displayName;function sN(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,cN(i,...t)]}function cN(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function lN(e){let t=uN(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(pN);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function uN(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=hN(n),i=mN(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var dN=Symbol(`radix.slottable`);function fN(e){let t=({children:e})=>(0,B.jsx)(B.Fragment,{children:e});return t.displayName=`${e}.Slottable`,t.__radixId=dN,t}function pN(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===dN}function mN(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function hN(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var gN=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=lN(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function _N(e){let t=vN(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(bN);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function vN(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=SN(n),i=xN(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var yN=Symbol(`radix.slottable`);function bN(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===yN}function xN(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function SN(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var CN=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=_N(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),wN=Object.freeze({position:`absolute`,border:0,width:1,height:1,padding:0,margin:-1,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,whiteSpace:`nowrap`,wordWrap:`normal`}),TN=`VisuallyHidden`,EN=z.forwardRef((e,t)=>(0,B.jsx)(CN.span,{...e,ref:t,style:{...wN,...e.style}}));EN.displayName=TN;var DN=EN,[ON,kN]=sN(`Tooltip`,[LE]),AN=LE(),jN=`TooltipProvider`,MN=700,NN=`tooltip.open`,[PN,FN]=ON(jN),IN=e=>{let{__scopeTooltip:t,delayDuration:n=MN,skipDelayDuration:r=300,disableHoverableContent:i=!1,children:a}=e,o=z.useRef(!0),s=z.useRef(!1),c=z.useRef(0);return z.useEffect(()=>{let e=c.current;return()=>window.clearTimeout(e)},[]),(0,B.jsx)(PN,{scope:t,isOpenDelayedRef:o,delayDuration:n,onOpen:z.useCallback(()=>{window.clearTimeout(c.current),o.current=!1},[]),onClose:z.useCallback(()=>{window.clearTimeout(c.current),c.current=window.setTimeout(()=>o.current=!0,r)},[r]),isPointerInTransitRef:s,onPointerInTransitChange:z.useCallback(e=>{s.current=e},[]),disableHoverableContent:i,children:a})};IN.displayName=jN;var LN=`Tooltip`,[RN,zN]=ON(LN),BN=e=>{let{__scopeTooltip:t,children:n,open:r,defaultOpen:i,onOpenChange:a,disableHoverableContent:o,delayDuration:s}=e,c=FN(LN,e.__scopeTooltip),l=AN(t),[u,d]=z.useState(null),f=nw(),p=z.useRef(0),m=o??c.disableHoverableContent,h=s??c.delayDuration,g=z.useRef(!1),[_,v]=TD({prop:r,defaultProp:i??!1,onChange:e=>{e?(c.onOpen(),document.dispatchEvent(new CustomEvent(NN))):c.onClose(),a?.(e)},caller:LN}),y=z.useMemo(()=>_?g.current?`delayed-open`:`instant-open`:`closed`,[_]),b=z.useCallback(()=>{window.clearTimeout(p.current),p.current=0,g.current=!1,v(!0)},[v]),x=z.useCallback(()=>{window.clearTimeout(p.current),p.current=0,v(!1)},[v]),S=z.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>{g.current=!0,v(!0),p.current=0},h)},[h,v]);return z.useEffect(()=>()=>{p.current&&=(window.clearTimeout(p.current),0)},[]),(0,B.jsx)($E,{...l,children:(0,B.jsx)(RN,{scope:t,contentId:f,open:_,stateAttribute:y,trigger:u,onTriggerChange:d,onTriggerEnter:z.useCallback(()=>{c.isOpenDelayedRef.current?S():b()},[c.isOpenDelayedRef,S,b]),onTriggerLeave:z.useCallback(()=>{m?x():(window.clearTimeout(p.current),p.current=0)},[x,m]),onOpen:b,onClose:x,disableHoverableContent:m,children:n})})};BN.displayName=LN;var VN=`TooltipTrigger`,HN=z.forwardRef((e,t)=>{let{__scopeTooltip:n,...r}=e,i=zN(VN,n),a=FN(VN,n),o=AN(n),s=en(t,z.useRef(null),i.onTriggerChange),c=z.useRef(!1),l=z.useRef(!1),u=z.useCallback(()=>c.current=!1,[]);return z.useEffect(()=>()=>document.removeEventListener(`pointerup`,u),[u]),(0,B.jsx)(eD,{asChild:!0,...o,children:(0,B.jsx)(gN.button,{"aria-describedby":i.open?i.contentId:void 0,"data-state":i.stateAttribute,...r,ref:s,onPointerMove:tC(e.onPointerMove,e=>{e.pointerType!==`touch`&&!l.current&&!a.isPointerInTransitRef.current&&(i.onTriggerEnter(),l.current=!0)}),onPointerLeave:tC(e.onPointerLeave,()=>{i.onTriggerLeave(),l.current=!1}),onPointerDown:tC(e.onPointerDown,()=>{i.open&&i.onClose(),c.current=!0,document.addEventListener(`pointerup`,u,{once:!0})}),onFocus:tC(e.onFocus,()=>{c.current||i.onOpen()}),onBlur:tC(e.onBlur,i.onClose),onClick:tC(e.onClick,i.onClose)})})});HN.displayName=VN;var UN=`TooltipPortal`,[WN,GN]=ON(UN,{forceMount:void 0}),KN=e=>{let{__scopeTooltip:t,forceMount:n,children:r,container:i}=e,a=zN(UN,t);return(0,B.jsx)(WN,{scope:t,forceMount:n,children:(0,B.jsx)(pD,{present:n||a.open,children:(0,B.jsx)(dD,{asChild:!0,container:i,children:r})})})};KN.displayName=UN;var qN=`TooltipContent`,JN=z.forwardRef((e,t)=>{let n=GN(qN,e.__scopeTooltip),{forceMount:r=n.forceMount,side:i=`top`,...a}=e,o=zN(qN,e.__scopeTooltip);return(0,B.jsx)(pD,{present:r||o.open,children:o.disableHoverableContent?(0,B.jsx)($N,{side:i,...a,ref:t}):(0,B.jsx)(YN,{side:i,...a,ref:t})})}),YN=z.forwardRef((e,t)=>{let n=zN(qN,e.__scopeTooltip),r=FN(qN,e.__scopeTooltip),i=z.useRef(null),a=en(t,i),[o,s]=z.useState(null),{trigger:c,onClose:l}=n,u=i.current,{onPointerInTransitChange:d}=r,f=z.useCallback(()=>{s(null),d(!1)},[d]),p=z.useCallback((e,t)=>{let n=e.currentTarget,r={x:e.clientX,y:e.clientY},i=rP(r,nP(r,n.getBoundingClientRect())),a=iP(t.getBoundingClientRect());s(oP([...i,...a])),d(!0)},[d]);return z.useEffect(()=>()=>f(),[f]),z.useEffect(()=>{if(c&&u){let e=e=>p(e,u),t=e=>p(e,c);return c.addEventListener(`pointerleave`,e),u.addEventListener(`pointerleave`,t),()=>{c.removeEventListener(`pointerleave`,e),u.removeEventListener(`pointerleave`,t)}}},[c,u,p,f]),z.useEffect(()=>{if(o){let e=e=>{let t=e.target,n={x:e.clientX,y:e.clientY},r=c?.contains(t)||u?.contains(t),i=!aP(n,o);r?f():i&&(f(),l())};return document.addEventListener(`pointermove`,e),()=>document.removeEventListener(`pointermove`,e)}},[c,u,o,l,f]),(0,B.jsx)($N,{...e,ref:a})}),[XN,ZN]=ON(LN,{isInside:!1}),QN=fN(`TooltipContent`),$N=z.forwardRef((e,t)=>{let{__scopeTooltip:n,children:r,"aria-label":i,onEscapeKeyDown:a,onPointerDownOutside:o,...s}=e,c=zN(qN,n),l=AN(n),{onClose:u}=c;return z.useEffect(()=>(document.addEventListener(NN,u),()=>document.removeEventListener(NN,u)),[u]),z.useEffect(()=>{if(c.trigger){let e=e=>{e.target?.contains(c.trigger)&&u()};return window.addEventListener(`scroll`,e,{capture:!0}),()=>window.removeEventListener(`scroll`,e,{capture:!0})}},[c.trigger,u]),(0,B.jsx)(bC,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:a,onPointerDownOutside:o,onFocusOutside:e=>e.preventDefault(),onDismiss:u,children:(0,B.jsxs)(tD,{"data-state":c.stateAttribute,...l,...s,ref:t,style:{...s.style,"--radix-tooltip-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-tooltip-content-available-width":`var(--radix-popper-available-width)`,"--radix-tooltip-content-available-height":`var(--radix-popper-available-height)`,"--radix-tooltip-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-tooltip-trigger-height":`var(--radix-popper-anchor-height)`},children:[(0,B.jsx)(QN,{children:r}),(0,B.jsx)(XN,{scope:n,isInside:!0,children:(0,B.jsx)(DN,{id:c.contentId,role:`tooltip`,children:i||r})})]})})});JN.displayName=qN;var eP=`TooltipArrow`,tP=z.forwardRef((e,t)=>{let{__scopeTooltip:n,...r}=e,i=AN(n);return ZN(eP,n).isInside?null:(0,B.jsx)(nD,{...i,...r,ref:t})});tP.displayName=eP;function nP(e,t){let n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),i=Math.abs(t.right-e.x),a=Math.abs(t.left-e.x);switch(Math.min(n,r,i,a)){case a:return`left`;case i:return`right`;case n:return`top`;case r:return`bottom`;default:throw Error(`unreachable`)}}function rP(e,t,n=5){let r=[];switch(t){case`top`:r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case`bottom`:r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case`left`:r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case`right`:r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function iP(e){let{top:t,right:n,bottom:r,left:i}=e;return[{x:i,y:t},{x:n,y:t},{x:n,y:r},{x:i,y:r}]}function aP(e,t){let{x:n,y:r}=e,i=!1;for(let e=0,a=t.length-1;er!=d>r&&n<(u-c)*(r-l)/(d-l)+c&&(i=!i)}return i}function oP(e){let t=e.slice();return t.sort((e,t)=>e.xt.x?1:e.yt.y)),sP(t)}function sP(e){if(e.length<=1)return e.slice();let t=[];for(let n=0;n=2;){let e=t[t.length-1],n=t[t.length-2];if((e.x-n.x)*(r.y-n.y)>=(e.y-n.y)*(r.x-n.x))t.pop();else break}t.push(r)}t.pop();let n=[];for(let t=e.length-1;t>=0;t--){let r=e[t];for(;n.length>=2;){let e=n[n.length-1],t=n[n.length-2];if((e.x-t.x)*(r.y-t.y)>=(e.y-t.y)*(r.x-t.x))n.pop();else break}n.push(r)}return n.pop(),t.length===1&&n.length===1&&t[0].x===n[0].x&&t[0].y===n[0].y?t:t.concat(n)}var cP=IN,lP=BN,uP=HN,dP=KN,fP=JN,pP=cP,mP=lP,hP=uP,gP=z.forwardRef(({className:e,sideOffset:t=4,style:n,...r},i)=>{let a=Math.max(1e3,kk());return(0,B.jsx)(Ak,{layer:a,children:(0,B.jsx)(dP,{children:(0,B.jsx)(fP,{ref:i,sideOffset:t,className:Jr(`z-[100] overflow-hidden rounded-[6px] bg-muted px-3 py-1.5 text-xs text-foreground shadow-lg shadow-primary/5`,`data-[state=delayed-open]:animate-in data-[state=instant-open]:animate-in`,`data-[state=closed]:animate-out`,`data-[state=delayed-open]:fade-in-0 data-[state=instant-open]:fade-in-0`,`data-[state=closed]:fade-out-0`,`data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:zoom-in-95`,`data-[state=closed]:zoom-out-95`,`data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,e),...r,style:{...n,zIndex:a}})})})});gP.displayName=fP.displayName;async function _P(e){try{await gb.post(`/cli-pairings/${encodeURIComponent(e)}/reserve-action`,{})}catch(e){if(e instanceof lb&&(e.status===409||e.status===404))throw Error(`This pairing was already completed or started in another tab. Close this tab and check your CLI — if the CLI didn't finish the flow, run the command again for a fresh pairing.`);let t=e instanceof Error?e.message:String(e);throw Error(`Couldn't reserve this pairing with NyxID (${t}). Try again; if the problem persists, cancel and re-run the CLI command.`)}}async function vP(e){try{await gb.post(`/cli-pairings/${encodeURIComponent(e)}/rewind-action`,{})}catch{}}async function yP(e,t){try{return await t()}catch(t){throw t instanceof lb&&t.status>=400&&t.status<500&&await vP(e),t}}var bP=/^[a-z0-9-]+$/,xP=$().min(1,`Node name is required`).max(64,`Node name must be 64 characters or fewer`).regex(bP,`Lowercase letters, digits, and hyphens only`);$().min(1,`Slug is required`).max(64,`Slug must be 64 characters or fewer`).regex(bP,`Lowercase letters, digits, and hyphens only`).refine(e=>!e.startsWith(`-`)&&!e.endsWith(`-`),{message:`Slug must not start or end with a hyphen`}).refine(e=>!e.includes(`--`),{message:`Slug must not contain consecutive hyphens`});var SP=$().min(1,`Name is required`).max(200,`Name must be 200 characters or fewer`);$().min(1,`Label is required`).max(200,`Label must be 200 characters or fewer`);var CP=[`claude-code`,`cursor`,`codex`,`openclaw`,`generic`];Nx([Ux(``),Vx(CP)]);var wP={slug:$().optional(),label:$().optional(),via_node:$().optional(),org_id:$().uuid().optional(),endpoint_url:$().optional(),custom:Cx().optional(),custom_slug:$().optional(),auth_method:$().optional(),auth_key_name:$().optional(),reconnect_key_id:$().optional(),scope_override:kx($()).optional()};jx(wP);function TP(e){if(!e||typeof e!=`object`||Array.isArray(e))return{};let t=e,n={};for(let e of Object.keys(wP)){if(!Object.prototype.hasOwnProperty.call(t,e))continue;let r=wP[e].safeParse(t[e]);r.success&&r.data!==void 0&&(n[e]=r.data)}return n}function EP(e,t){let n=e.safeParse(t);return n.success?null:n.error.issues[0]?.message??`Invalid value`}function DP({label:e,schema:t,value:n,onChange:r,onValidityChange:i,hint:a,placeholder:o,optional:s=!1,autoFocus:c,autoComplete:l=`off`,id:u}){let d=(0,z.useId)(),f=u??d,p=s&&n.length===0?null:EP(t,n);(0,z.useEffect)(()=>{i&&i(p===null)},[p,i]);let m=`${f}-hint`,h=`${f}-error`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:f,children:e}),(0,B.jsx)(IM,{id:f,value:n,onChange:e=>{r(e.target.value)},placeholder:o,autoFocus:c,autoComplete:l,"aria-invalid":p!=null,"aria-describedby":p?h:a?m:void 0,className:p==null?void 0:`border-destructive focus-visible:border-destructive`}),p?(0,B.jsx)(`p`,{id:h,className:`text-xs text-destructive`,children:p}):a?(0,B.jsx)(`p`,{id:m,className:`text-xs text-muted-foreground`,children:a}):null]})}var OP=new Set([`http:`,`https:`]),kP=/^\d{1,3}(\.\d{1,3}){3}$/,AP=/^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;function jP(e){let t;try{t=new URL(e)}catch{return!1}if(!OP.has(t.protocol))return!1;if(t.hostname.startsWith(`[`))return!0;let n=t.hostname.replace(/\.$/,``).toLowerCase();return n.length===0?!1:n===`localhost`||kP.test(n)?!0:AP.test(n)}var MP=[`read`,`write`,`admin`,`openid`,`profile`,`email`,`services:read`,`services:write`,`proxy`];jx({name:$().min(1,`Name is required`).max(64,`Name must be at most 64 characters`).refine(e=>e.trim().length>0,`Name must not be blank`),scopes:kx(Vx(MP)).min(1,`At least one scope is required`),expires_at:$().nullable().optional().refine(e=>{if(e==null||e===``)return!0;let t=/^\d{4}-\d{2}-\d{2}$/.test(e)?new Date(`${e}T23:59:59Z`):new Date(e);return Number.isNaN(t.getTime())?!1:t.getTime()>Date.now()},{message:`Expiry date must be in the future`}),description:$().nullable().optional(),allow_all_services:Cx().optional(),allow_auto_connected_services:Cx().optional(),allow_all_nodes:Cx().optional(),allowed_service_ids:kx($()).optional(),allowed_node_ids:kx($()).optional(),callback_url:$().refine(jP,`Must be a valid URL`).nullable().optional(),platform:$().nullable().optional(),rate_limit_per_second:yx().int().positive().max(4294967295).optional(),rate_limit_burst:yx().int().positive().max(4294967295).optional(),target_org_id:$().optional()});function NP({value:e,onChange:t,label:n=`Scopes`,hint:r=`Must match the backend's allowed scope set. Pick at least one.`}){function i(n){let r=new Set(e);r.has(n)?r.delete(n):r.add(n),t(r)}let a=e.size===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:n}),(0,B.jsx)(`div`,{role:`group`,"aria-label":`Scopes`,"aria-invalid":a,className:`flex flex-wrap gap-2 rounded-lg p-2 transition-colors duration-300 `+(a?`border border-destructive`:`border border-transparent`),children:MP.map(t=>(0,B.jsx)(PP,{scope:t,checked:e.has(t),onToggle:()=>{i(t)}},t))}),(0,B.jsx)(`p`,{className:a?`text-xs text-destructive`:`text-xs text-muted-foreground`,children:a?`At least one scope is required.`:r})]})}function PP({scope:e,checked:t,onToggle:n}){return(0,B.jsxs)(`label`,{className:`inline-flex cursor-pointer select-none items-center gap-1.5 rounded-full border px-3 py-1.5 text-12 transition-colors duration-300 `+(t?`border-primary bg-primary/15 text-foreground`:`border-border bg-transparent text-muted-foreground hover:border-border hover:bg-muted/40`),children:[(0,B.jsx)(`input`,{type:`checkbox`,className:`peer sr-only`,checked:t,onChange:n,value:e}),(0,B.jsx)(`span`,{className:`text-xs`,children:e})]})}function FP(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,IP(i,...t)]}function IP(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function LP(e){let t=RP(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(BP);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function RP(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=HP(n),i=VP(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var zP=Symbol(`radix.slottable`);function BP(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===zP}function VP(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function HP(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var UP=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=LP(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),WP=`Checkbox`,[GP,KP]=FP(WP),[qP,JP]=GP(WP);function YP(e){let{__scopeCheckbox:t,checked:n,children:r,defaultChecked:i,disabled:a,form:o,name:s,onCheckedChange:c,required:l,value:u=`on`,internal_do_not_use_render:d}=e,[f,p]=TD({prop:n,defaultProp:i??!1,onChange:c,caller:WP}),[m,h]=z.useState(null),[g,_]=z.useState(null),v=z.useRef(!1),y=m?!!o||!!m.closest(`form`):!0,b={checked:f,disabled:a,setChecked:p,control:m,setControl:h,name:s,form:o,value:u,hasConsumerStoppedPropagationRef:v,required:l,defaultChecked:iF(i)?!1:i,isFormControl:y,bubbleInput:g,setBubbleInput:_};return(0,B.jsx)(qP,{scope:t,...b,children:rF(d)?d(b):r})}var XP=`CheckboxTrigger`,ZP=z.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:n,...r},i)=>{let{control:a,value:o,disabled:s,checked:c,required:l,setControl:u,setChecked:d,hasConsumerStoppedPropagationRef:f,isFormControl:p,bubbleInput:m}=JP(XP,e),h=en(i,u),g=z.useRef(c);return z.useEffect(()=>{let e=a?.form;if(e){let t=()=>d(g.current);return e.addEventListener(`reset`,t),()=>e.removeEventListener(`reset`,t)}},[a,d]),(0,B.jsx)(UP.button,{type:`button`,role:`checkbox`,"aria-checked":iF(c)?`mixed`:c,"aria-required":l,"data-state":aF(c),"data-disabled":s?``:void 0,disabled:s,value:o,...r,ref:h,onKeyDown:tC(t,e=>{e.key===`Enter`&&e.preventDefault()}),onClick:tC(n,e=>{d(e=>iF(e)?!0:!e),m&&p&&(f.current=e.isPropagationStopped(),f.current||e.stopPropagation())})})});ZP.displayName=XP;var QP=z.forwardRef((e,t)=>{let{__scopeCheckbox:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c,onCheckedChange:l,form:u,...d}=e;return(0,B.jsx)(YP,{__scopeCheckbox:n,checked:i,defaultChecked:a,disabled:s,required:o,onCheckedChange:l,name:r,form:u,value:c,internal_do_not_use_render:({isFormControl:e})=>(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(ZP,{...d,ref:t,__scopeCheckbox:n}),e&&(0,B.jsx)(nF,{__scopeCheckbox:n})]})})});QP.displayName=WP;var $P=`CheckboxIndicator`,eF=z.forwardRef((e,t)=>{let{__scopeCheckbox:n,forceMount:r,...i}=e,a=JP($P,n);return(0,B.jsx)(pD,{present:r||iF(a.checked)||a.checked===!0,children:(0,B.jsx)(UP.span,{"data-state":aF(a.checked),"data-disabled":a.disabled?``:void 0,...i,ref:t,style:{pointerEvents:`none`,...e.style}})})});eF.displayName=$P;var tF=`CheckboxBubbleInput`,nF=z.forwardRef(({__scopeCheckbox:e,...t},n)=>{let{control:r,hasConsumerStoppedPropagationRef:i,checked:a,defaultChecked:o,required:s,disabled:c,name:l,value:u,form:d,bubbleInput:f,setBubbleInput:p}=JP(tF,e),m=en(n,p),h=zM(a),g=PE(r);z.useEffect(()=>{let e=f;if(!e)return;let t=window.HTMLInputElement.prototype,n=Object.getOwnPropertyDescriptor(t,`checked`).set,r=!i.current;if(h!==a&&n){let t=new Event(`click`,{bubbles:r});e.indeterminate=iF(a),n.call(e,iF(a)?!1:a),e.dispatchEvent(t)}},[f,h,a,i]);let _=z.useRef(iF(a)?!1:a);return(0,B.jsx)(UP.input,{type:`checkbox`,"aria-hidden":!0,defaultChecked:o??_.current,required:s,disabled:c,name:l,value:u,form:d,...t,tabIndex:-1,ref:m,style:{...t.style,...g,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0,transform:`translateX(-100%)`}})});nF.displayName=tF;function rF(e){return typeof e==`function`}function iF(e){return e===`indeterminate`}function aF(e){return iF(e)?`indeterminate`:e?`checked`:`unchecked`}var oF=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(QP,{ref:n,className:Jr(`peer h-4 w-4 shrink-0 rounded-[4px] border border-muted-foreground/40 bg-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground`,e),...t,children:(0,B.jsx)(eF,{className:Jr(`flex items-center justify-center text-current`),children:(0,B.jsx)(li,{className:`h-3 w-3`})})}));oF.displayName=QP.displayName;function sF({services:e,selectedIds:t,allowAll:n=!1,onAllowAllChange:r,onToggle:i,orgOwned:a=!1,disabled:o=!1}){let s=(0,z.useId)();return a&&!e.some(e=>e.auto_connected)?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`This org-owned key cannot use platform services from your personal account.`}):(0,B.jsxs)(`section`,{"aria-label":`Auto-connected platform services`,className:`space-y-2 border-t border-border/50 pt-3`,children:[(0,B.jsx)(`p`,{className:`text-12 font-medium`,children:`Auto-connected platform services`}),(0,B.jsxs)(Vi,{className:`flex items-start gap-2 text-12`,children:[(0,B.jsx)(oF,{checked:n,disabled:o,onCheckedChange:e=>r(e===!0)}),`Allow all auto-connected platform services (includes ones added later)`]}),e.filter(e=>e.auto_connected).map(e=>{let r=n&&e.platform_grant_eligible!==!1;return(0,B.jsxs)(Vi,{htmlFor:`${s}-${e.id}`,className:`flex items-center gap-2 text-12 ${r?`text-muted-foreground`:``}`,children:[(0,B.jsx)(oF,{id:`${s}-${e.id}`,checked:r||t.includes(e.id),disabled:o||r,onCheckedChange:()=>i(e.id)}),e.label||e.name||e.slug||e.id,e.platform_grant_eligible===!1&&` (Organization; select individually)`]},e.id)})]})}function cF(){let e=lt({queryKey:[`keys`,`list`,eC(e=>e.user?.id)],queryFn:async()=>(await gb.get(`/keys`)).keys,staleTime:0,refetchOnMount:`always`});return{...e,data:e.isError?void 0:e.data}}function lF(e={}){return lt({queryKey:[`nodes`],queryFn:async()=>(await gb.get(`/nodes`)).nodes,refetchInterval:e.pollIntervalMs&&e.pollIntervalMs>0?e.pollIntervalMs:void 0})}function uF({value:e,onChange:t,ownerId:n}){let r=cF(),i=lF();function a(n){let r=new Set(e.selectedServiceIds);r.has(n)?r.delete(n):r.add(n),t({...e,selectedServiceIds:r})}function o(n){let r=new Set(e.selectedNodeIds);r.has(n)?r.delete(n):r.add(n),t({...e,selectedNodeIds:r})}return(0,B.jsxs)(`section`,{"aria-labelledby":`access-scope-title`,className:`flex flex-col gap-4 rounded-lg border border-border bg-muted/30 p-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{id:`access-scope-title`,className:`text-13 font-semibold`,children:`Access Scope`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Restrict which services and nodes this key can access via proxy.`})]}),(0,B.jsx)(dF,{label:`Services`,icon:(0,B.jsx)(fF,{}),allowAll:e.allowAllServices,onAllowAllChange:n=>{t({...e,allowAllServices:n})},listLabel:`Select allowed services:`,loading:r.isLoading,items:r.data?.filter(e=>e.is_active&&!e.auto_connected&&(!n||e.credential_source?.type===`org`&&e.credential_source.org_id===n)&&(e.credential_source?.type!==`org`||e.credential_source.allowed)).map(e=>({id:e.id,primary:e.label,secondary:e.slug,iconSlug:e.catalog_service_slug}))??[],selectedIds:e.selectedServiceIds,onToggle:a}),!e.allowAllServices&&(0,B.jsx)(sF,{services:(r.data??[]).filter(e=>e.is_active&&(n?e.credential_source?.type===`org`&&e.credential_source.org_id===n:e.credential_source?.type!==`org`)),selectedIds:[...e.selectedServiceIds],allowAll:e.allowAutoConnectedServices,onAllowAllChange:n=>t({...e,allowAutoConnectedServices:n}),onToggle:a,orgOwned:!!n}),(0,B.jsx)(dF,{label:`Nodes`,icon:(0,B.jsx)(pF,{}),allowAll:e.allowAllNodes,onAllowAllChange:n=>{t({...e,allowAllNodes:n})},listLabel:`Select allowed nodes:`,loading:i.isLoading,items:i.data?.filter(e=>!n||e.owner.id===n).map(e=>({id:e.id,primary:e.name,secondary:e.status}))??[],selectedIds:e.selectedNodeIds,onToggle:o})]})}function dF({label:e,icon:t,allowAll:n,onAllowAllChange:r,listLabel:i,loading:a,items:o,selectedIds:s,onToggle:c}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5 text-12 font-medium`,children:[(0,B.jsx)(`span`,{className:`text-muted-foreground`,children:t}),(0,B.jsx)(`span`,{children:e})]}),(0,B.jsxs)(Vi,{className:`flex cursor-pointer items-center gap-2 text-12`,children:[(0,B.jsx)(oF,{checked:n,onCheckedChange:e=>{r(e===!0)}}),(0,B.jsxs)(`span`,{children:[`Allow all `,e.toLowerCase()]})]}),n?null:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5 rounded-lg border border-border bg-background/40 p-3`,children:[(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i}),a?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Loading…`}):o.length===0?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`None available. Add one first, then come back.`}):(0,B.jsx)(`div`,{className:`flex flex-col gap-1`,role:`list`,children:o.map(e=>(0,B.jsxs)(Vi,{className:`flex cursor-pointer items-center gap-2 text-12`,children:[(0,B.jsx)(oF,{checked:s.has(e.id),onCheckedChange:()=>{c(e.id)}}),(0,B.jsx)(AM,{slug:e.iconSlug,size:`2xs`}),(0,B.jsxs)(`span`,{className:`truncate`,children:[e.primary,e.secondary?(0,B.jsxs)(`span`,{className:`ml-1.5 text-xs text-muted-foreground`,children:[`(`,e.secondary,`)`]}):null]})]},e.id))})]})]})}function fF(){return(0,B.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,B.jsx)(`path`,{d:`M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z`})})}function pF(){return(0,B.jsxs)(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,B.jsx)(`rect`,{x:`2`,y:`3`,width:`20`,height:`7`,rx:`1.5`}),(0,B.jsx)(`rect`,{x:`2`,y:`14`,width:`20`,height:`7`,rx:`1.5`}),(0,B.jsx)(`line`,{x1:`6`,y1:`6.5`,x2:`6.01`,y2:`6.5`}),(0,B.jsx)(`line`,{x1:`6`,y1:`17.5`,x2:`6.01`,y2:`17.5`})]})}var mF=[`orgs`],hF={all:mF,list:()=>[...mF,`list`],detail:e=>[...mF,`detail`,e]};function gF(){return lt({queryKey:hF.list(),queryFn:async()=>(await gb.get(`/orgs`)).orgs})}function _F(e,[t,n]){return Math.min(n,Math.max(t,e))}function vF(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,yF(i,...t)]}function yF(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}function bF(e){let t=xF(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(CF);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function xF(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=TF(n),i=wF(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var SF=Symbol(`radix.slottable`);function CF(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===SF}function wF(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function TF(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function EF(e){let t=e+`CollectionProvider`,[n,r]=vF(t),[i,a]=n(t,{collectionRef:{current:null},itemMap:new Map}),o=e=>{let{scope:t,children:n}=e,r=z.useRef(null),a=z.useRef(new Map).current;return(0,B.jsx)(i,{scope:t,itemMap:a,collectionRef:r,children:n})};o.displayName=t;let s=e+`CollectionSlot`,c=bF(s),l=z.forwardRef((e,t)=>{let{scope:n,children:r}=e;return(0,B.jsx)(c,{ref:en(t,a(s,n).collectionRef),children:r})});l.displayName=s;let u=e+`CollectionItemSlot`,d=`data-radix-collection-item`,f=bF(u),p=z.forwardRef((e,t)=>{let{scope:n,children:r,...i}=e,o=z.useRef(null),s=en(t,o),c=a(u,n);return z.useEffect(()=>(c.itemMap.set(o,{ref:o,...i}),()=>void c.itemMap.delete(o))),(0,B.jsx)(f,{[d]:``,ref:s,children:r})});p.displayName=u;function m(t){let n=a(e+`CollectionConsumer`,t);return z.useCallback(()=>{let e=n.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll(`[${d}]`));return Array.from(n.itemMap.values()).sort((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current))},[n.collectionRef,n.itemMap])}return[{Provider:o,Slot:l,ItemSlot:p},m,r]}function DF(e,t=[]){let n=[];function r(t,r){let i=z.createContext(r),a=n.length;n=[...n,r];let o=t=>{let{scope:n,children:r,...o}=t,s=n?.[e]?.[a]||i,c=z.useMemo(()=>o,Object.values(o));return(0,B.jsx)(s.Provider,{value:c,children:r})};o.displayName=t+`Provider`;function s(n,o){let s=o?.[e]?.[a]||i,c=z.useContext(s);if(c)return c;if(r!==void 0)return r;throw Error(`\`${n}\` must be used within \`${t}\``)}return[o,s]}let i=()=>{let t=n.map(e=>z.createContext(e));return function(n){let r=n?.[e]||t;return z.useMemo(()=>({[`__scope${e}`]:{...n,[e]:r}}),[n,r])}};return i.scopeName=e,[r,OF(i,...t)]}function OF(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let r=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return z.useMemo(()=>({[`__scope${t.scopeName}`]:r}),[r])}};return n.scopeName=t.scopeName,n}var kF=z.createContext(void 0);function AF(e){let t=z.useContext(kF);return e||t||`ltr`}function jF(e){let t=MF(e),n=z.forwardRef((e,n)=>{let{children:r,...i}=e,a=z.Children.toArray(r),o=a.find(PF);if(o){let e=o.props.children,r=a.map(t=>t===o?z.Children.count(e)>1?z.Children.only(null):z.isValidElement(e)?e.props.children:null:t);return(0,B.jsx)(t,{...i,ref:n,children:z.isValidElement(e)?z.cloneElement(e,void 0,r):null})}return(0,B.jsx)(t,{...i,ref:n,children:r})});return n.displayName=`${e}.Slot`,n}function MF(e){let t=z.forwardRef((e,t)=>{let{children:n,...r}=e;if(z.isValidElement(n)){let e=IF(n),i=FF(r,n.props);return n.type!==z.Fragment&&(i.ref=t?$t(t,e):e),z.cloneElement(n,i)}return z.Children.count(n)>1?z.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var NF=Symbol(`radix.slottable`);function PF(e){return z.isValidElement(e)&&typeof e.type==`function`&&`__radixId`in e.type&&e.type.__radixId===NF}function FF(e,t){let n={...t};for(let r in t){let i=e[r],a=t[r];/^on[A-Z]/.test(r)?i&&a?n[r]=(...e)=>{let t=a(...e);return i(...e),t}:i&&(n[r]=i):r===`style`?n[r]={...i,...a}:r===`className`&&(n[r]=[i,a].filter(Boolean).join(` `))}return{...e,...n}}function IF(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var LF=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=jF(`Primitive.${t}`),r=z.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,B.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{}),RF=[` `,`Enter`,`ArrowUp`,`ArrowDown`],zF=[` `,`Enter`],BF=`Select`,[VF,HF,UF]=EF(BF),[WF,GF]=DF(BF,[UF,LE]),KF=LE(),[qF,JF]=WF(BF),[YF,XF]=WF(BF),ZF=e=>{let{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:a,value:o,defaultValue:s,onValueChange:c,dir:l,name:u,autoComplete:d,disabled:f,required:p,form:m}=e,h=KF(t),[g,_]=z.useState(null),[v,y]=z.useState(null),[b,x]=z.useState(!1),S=AF(l),[C,w]=TD({prop:r,defaultProp:i??!1,onChange:a,caller:BF}),[T,E]=TD({prop:o,defaultProp:s,onChange:c,caller:BF}),ee=z.useRef(null),D=g?m||!!g.closest(`form`):!0,[O,k]=z.useState(new Set),A=Array.from(O).map(e=>e.props.value).join(`;`);return(0,B.jsx)($E,{...h,children:(0,B.jsxs)(qF,{required:p,scope:t,trigger:g,onTriggerChange:_,valueNode:v,onValueNodeChange:y,valueNodeHasChildren:b,onValueNodeHasChildrenChange:x,contentId:nw(),value:T,onValueChange:E,open:C,onOpenChange:w,dir:S,triggerPointerDownPosRef:ee,disabled:f,children:[(0,B.jsx)(VF.Provider,{scope:t,children:(0,B.jsx)(YF,{scope:e.__scopeSelect,onNativeOptionAdd:z.useCallback(e=>{k(t=>new Set(t).add(e))},[]),onNativeOptionRemove:z.useCallback(e=>{k(t=>{let n=new Set(t);return n.delete(e),n})},[]),children:n})}),D?(0,B.jsxs)(KI,{"aria-hidden":!0,required:p,tabIndex:-1,name:u,autoComplete:d,value:T,onChange:e=>E(e.target.value),disabled:f,form:m,children:[T===void 0?(0,B.jsx)(`option`,{value:``}):null,Array.from(O)]},A):null]})})};ZF.displayName=BF;var QF=`SelectTrigger`,$F=z.forwardRef((e,t)=>{let{__scopeSelect:n,disabled:r=!1,...i}=e,a=KF(n),o=JF(QF,n),s=o.disabled||r,c=en(t,o.onTriggerChange),l=HF(n),u=z.useRef(`touch`),[d,f,p]=JI(e=>{let t=l().filter(e=>!e.disabled),n=YI(t,e,t.find(e=>e.value===o.value));n!==void 0&&o.onValueChange(n.value)}),m=e=>{s||(o.onOpenChange(!0),p()),e&&(o.triggerPointerDownPosRef.current={x:Math.round(e.pageX),y:Math.round(e.pageY)})};return(0,B.jsx)(eD,{asChild:!0,...a,children:(0,B.jsx)(LF.button,{type:`button`,role:`combobox`,"aria-controls":o.contentId,"aria-expanded":o.open,"aria-required":o.required,"aria-autocomplete":`none`,dir:o.dir,"data-state":o.open?`open`:`closed`,disabled:s,"data-disabled":s?``:void 0,"data-placeholder":qI(o.value)?``:void 0,...i,ref:c,onClick:tC(i.onClick,e=>{e.currentTarget.focus(),u.current!==`mouse`&&m(e)}),onPointerDown:tC(i.onPointerDown,e=>{u.current=e.pointerType;let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),e.button===0&&e.ctrlKey===!1&&e.pointerType===`mouse`&&(m(e),e.preventDefault())}),onKeyDown:tC(i.onKeyDown,e=>{let t=d.current!==``;!(e.ctrlKey||e.altKey||e.metaKey)&&e.key.length===1&&f(e.key),!(t&&e.key===` `)&&RF.includes(e.key)&&(m(),e.preventDefault())})})})});$F.displayName=QF;var eI=`SelectValue`,tI=z.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,children:a,placeholder:o=``,...s}=e,c=JF(eI,n),{onValueNodeHasChildrenChange:l}=c,u=a!==void 0,d=en(t,c.onValueNodeChange);return $C(()=>{l(u)},[l,u]),(0,B.jsx)(LF.span,{...s,ref:d,style:{pointerEvents:`none`},children:qI(c.value)?(0,B.jsx)(B.Fragment,{children:o}):a})});tI.displayName=eI;var nI=`SelectIcon`,rI=z.forwardRef((e,t)=>{let{__scopeSelect:n,children:r,...i}=e;return(0,B.jsx)(LF.span,{"aria-hidden":!0,...i,ref:t,children:r||`▼`})});rI.displayName=nI;var iI=`SelectPortal`,aI=e=>(0,B.jsx)(dD,{asChild:!0,...e});aI.displayName=iI;var oI=`SelectContent`,sI=z.forwardRef((e,t)=>{let n=JF(oI,e.__scopeSelect),[r,i]=z.useState();if($C(()=>{i(new DocumentFragment)},[]),!n.open){let t=r;return t?Fi.createPortal((0,B.jsx)(lI,{scope:e.__scopeSelect,children:(0,B.jsx)(VF.Slot,{scope:e.__scopeSelect,children:(0,B.jsx)(`div`,{children:e.children})})}),t):null}return(0,B.jsx)(pI,{...e,ref:t})});sI.displayName=oI;var cI=10,[lI,uI]=WF(oI),dI=`SelectContentImpl`,fI=jF(`SelectContent.RemoveScroll`),pI=z.forwardRef((e,t)=>{let{__scopeSelect:n,position:r=`item-aligned`,onCloseAutoFocus:i,onEscapeKeyDown:a,onPointerDownOutside:o,side:s,sideOffset:c,align:l,alignOffset:u,arrowPadding:d,collisionBoundary:f,collisionPadding:p,sticky:m,hideWhenDetached:h,avoidCollisions:g,..._}=e,v=JF(oI,n),[y,b]=z.useState(null),[x,S]=z.useState(null),C=en(t,e=>b(e)),[w,T]=z.useState(null),[E,ee]=z.useState(null),D=HF(n),[O,k]=z.useState(!1),A=z.useRef(!1);z.useEffect(()=>{if(y)return ID(y)},[y]),OC();let j=z.useCallback(e=>{let[t,...n]=D().map(e=>e.ref.current),[r]=n.slice(-1),i=document.activeElement;for(let n of e)if(n===i||(n?.scrollIntoView({block:`nearest`}),n===t&&x&&(x.scrollTop=0),n===r&&x&&(x.scrollTop=x.scrollHeight),n?.focus(),document.activeElement!==i))return},[D,x]),M=z.useCallback(()=>j([w,y]),[j,w,y]);z.useEffect(()=>{O&&M()},[O,M]);let{onOpenChange:N,triggerPointerDownPosRef:P}=v;z.useEffect(()=>{if(y){let e={x:0,y:0},t=t=>{e={x:Math.abs(Math.round(t.pageX)-(P.current?.x??0)),y:Math.abs(Math.round(t.pageY)-(P.current?.y??0))}},n=n=>{e.x<=10&&e.y<=10?n.preventDefault():y.contains(n.target)||N(!1),document.removeEventListener(`pointermove`,t),P.current=null};return P.current!==null&&(document.addEventListener(`pointermove`,t),document.addEventListener(`pointerup`,n,{capture:!0,once:!0})),()=>{document.removeEventListener(`pointermove`,t),document.removeEventListener(`pointerup`,n,{capture:!0})}}},[y,N,P]),z.useEffect(()=>{let e=()=>N(!1);return window.addEventListener(`blur`,e),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`blur`,e),window.removeEventListener(`resize`,e)}},[N]);let[F,I]=JI(e=>{let t=D().filter(e=>!e.disabled),n=YI(t,e,t.find(e=>e.ref.current===document.activeElement));n&&setTimeout(()=>n.ref.current.focus())}),te=z.useCallback((e,t,n)=>{let r=!A.current&&!n;(v.value!==void 0&&v.value===t||r)&&(T(e),r&&(A.current=!0))},[v.value]),ne=z.useCallback(()=>y?.focus(),[y]),re=z.useCallback((e,t,n)=>{let r=!A.current&&!n;(v.value!==void 0&&v.value===t||r)&&ee(e)},[v.value]),L=r===`popper`?_I:hI,R=L===_I?{side:s,sideOffset:c,align:l,alignOffset:u,arrowPadding:d,collisionBoundary:f,collisionPadding:p,sticky:m,hideWhenDetached:h,avoidCollisions:g}:{};return(0,B.jsx)(lI,{scope:n,content:y,viewport:x,onViewportChange:S,itemRefCallback:te,selectedItem:w,onItemLeave:ne,itemTextRefCallback:re,focusSelectedItem:M,selectedItemText:E,position:r,isPositioned:O,searchRef:F,children:(0,B.jsx)(GO,{as:fI,allowPinchZoom:!0,children:(0,B.jsx)(VC,{asChild:!0,trapped:v.open,onMountAutoFocus:e=>{e.preventDefault()},onUnmountAutoFocus:tC(i,e=>{v.trigger?.focus({preventScroll:!0}),e.preventDefault()}),children:(0,B.jsx)(bC,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:a,onPointerDownOutside:o,onFocusOutside:e=>e.preventDefault(),onDismiss:()=>v.onOpenChange(!1),children:(0,B.jsx)(L,{role:`listbox`,id:v.contentId,"data-state":v.open?`open`:`closed`,dir:v.dir,onContextMenu:e=>e.preventDefault(),..._,...R,onPlaced:()=>k(!0),ref:C,style:{display:`flex`,flexDirection:`column`,outline:`none`,..._.style},onKeyDown:tC(_.onKeyDown,e=>{let t=e.ctrlKey||e.altKey||e.metaKey;if(e.key===`Tab`&&e.preventDefault(),!t&&e.key.length===1&&I(e.key),[`ArrowUp`,`ArrowDown`,`Home`,`End`].includes(e.key)){let t=D().filter(e=>!e.disabled).map(e=>e.ref.current);if([`ArrowUp`,`End`].includes(e.key)&&(t=t.slice().reverse()),[`ArrowUp`,`ArrowDown`].includes(e.key)){let n=e.target,r=t.indexOf(n);t=t.slice(r+1)}setTimeout(()=>j(t)),e.preventDefault()}})})})})})})});pI.displayName=dI;var mI=`SelectItemAlignedPosition`,hI=z.forwardRef((e,t)=>{let{__scopeSelect:n,onPlaced:r,...i}=e,a=JF(oI,n),o=uI(oI,n),[s,c]=z.useState(null),[l,u]=z.useState(null),d=en(t,e=>u(e)),f=HF(n),p=z.useRef(!1),m=z.useRef(!0),{viewport:h,selectedItem:g,selectedItemText:_,focusSelectedItem:v}=o,y=z.useCallback(()=>{if(a.trigger&&a.valueNode&&s&&l&&h&&g&&_){let e=a.trigger.getBoundingClientRect(),t=l.getBoundingClientRect(),n=a.valueNode.getBoundingClientRect(),i=_.getBoundingClientRect();if(a.dir!==`rtl`){let r=i.left-t.left,a=n.left-r,o=e.left-a,c=e.width+o,l=Math.max(c,t.width),u=window.innerWidth-cI,d=_F(a,[cI,Math.max(cI,u-l)]);s.style.minWidth=c+`px`,s.style.left=d+`px`}else{let r=t.right-i.right,a=window.innerWidth-n.right-r,o=window.innerWidth-e.right-a,c=e.width+o,l=Math.max(c,t.width),u=window.innerWidth-cI,d=_F(a,[cI,Math.max(cI,u-l)]);s.style.minWidth=c+`px`,s.style.right=d+`px`}let o=f(),c=window.innerHeight-cI*2,u=h.scrollHeight,d=window.getComputedStyle(l),m=parseInt(d.borderTopWidth,10),v=parseInt(d.paddingTop,10),y=parseInt(d.borderBottomWidth,10),b=parseInt(d.paddingBottom,10),x=m+v+u+b+y,S=Math.min(g.offsetHeight*5,x),C=window.getComputedStyle(h),w=parseInt(C.paddingTop,10),T=parseInt(C.paddingBottom,10),E=e.top+e.height/2-cI,ee=c-E,D=g.offsetHeight/2,O=g.offsetTop+D,k=m+v+O,A=x-k;if(k<=E){let e=o.length>0&&g===o[o.length-1].ref.current;s.style.bottom=`0px`;let t=l.clientHeight-h.offsetTop-h.offsetHeight,n=k+Math.max(ee,D+(e?T:0)+t+y);s.style.height=n+`px`}else{let e=o.length>0&&g===o[0].ref.current;s.style.top=`0px`;let t=Math.max(E,m+h.offsetTop+(e?w:0)+D)+A;s.style.height=t+`px`,h.scrollTop=k-E+h.offsetTop}s.style.margin=`${cI}px 0`,s.style.minHeight=S+`px`,s.style.maxHeight=c+`px`,r?.(),requestAnimationFrame(()=>p.current=!0)}},[f,a.trigger,a.valueNode,s,l,h,g,_,a.dir,r]);$C(()=>y(),[y]);let[b,x]=z.useState();return $C(()=>{l&&x(window.getComputedStyle(l).zIndex)},[l]),(0,B.jsx)(vI,{scope:n,contentWrapper:s,shouldExpandOnScrollRef:p,onScrollButtonChange:z.useCallback(e=>{e&&m.current===!0&&(y(),v?.(),m.current=!1)},[y,v]),children:(0,B.jsx)(`div`,{ref:c,style:{display:`flex`,flexDirection:`column`,position:`fixed`,zIndex:b},children:(0,B.jsx)(LF.div,{...i,ref:d,style:{boxSizing:`border-box`,maxHeight:`100%`,...i.style}})})})});hI.displayName=mI;var gI=`SelectPopperPosition`,_I=z.forwardRef((e,t)=>{let{__scopeSelect:n,align:r=`start`,collisionPadding:i=cI,...a}=e,o=KF(n);return(0,B.jsx)(tD,{...o,...a,ref:t,align:r,collisionPadding:i,style:{boxSizing:`border-box`,...a.style,"--radix-select-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-select-content-available-width":`var(--radix-popper-available-width)`,"--radix-select-content-available-height":`var(--radix-popper-available-height)`,"--radix-select-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-select-trigger-height":`var(--radix-popper-anchor-height)`}})});_I.displayName=gI;var[vI,yI]=WF(oI,{}),bI=`SelectViewport`,xI=z.forwardRef((e,t)=>{let{__scopeSelect:n,nonce:r,...i}=e,a=uI(bI,n),o=yI(bI,n),s=en(t,a.onViewportChange),c=z.useRef(0);return(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(`style`,{dangerouslySetInnerHTML:{__html:`[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`},nonce:r}),(0,B.jsx)(VF.Slot,{scope:n,children:(0,B.jsx)(LF.div,{"data-radix-select-viewport":``,role:`presentation`,...i,ref:s,style:{position:`relative`,flex:1,overflow:`hidden auto`,...i.style},onScroll:tC(i.onScroll,e=>{let t=e.currentTarget,{contentWrapper:n,shouldExpandOnScrollRef:r}=o;if(r?.current&&n){let e=Math.abs(c.current-t.scrollTop);if(e>0){let r=window.innerHeight-cI*2,i=parseFloat(n.style.minHeight),a=parseFloat(n.style.height),o=Math.max(i,a);if(o0?s:0,n.style.justifyContent=`flex-end`)}}}c.current=t.scrollTop})})})]})});xI.displayName=bI;var SI=`SelectGroup`,[CI,wI]=WF(SI),TI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=nw();return(0,B.jsx)(CI,{scope:n,id:i,children:(0,B.jsx)(LF.div,{role:`group`,"aria-labelledby":i,...r,ref:t})})});TI.displayName=SI;var EI=`SelectLabel`,DI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=wI(EI,n);return(0,B.jsx)(LF.div,{id:i.id,...r,ref:t})});DI.displayName=EI;var OI=`SelectItem`,[kI,AI]=WF(OI),jI=z.forwardRef((e,t)=>{let{__scopeSelect:n,value:r,disabled:i=!1,textValue:a,...o}=e,s=JF(OI,n),c=uI(OI,n),l=s.value===r,[u,d]=z.useState(a??``),[f,p]=z.useState(!1),m=en(t,e=>c.itemRefCallback?.(e,r,i)),h=nw(),g=z.useRef(`touch`),_=()=>{i||(s.onValueChange(r),s.onOpenChange(!1))};if(r===``)throw Error(`A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.`);return(0,B.jsx)(kI,{scope:n,value:r,disabled:i,textId:h,isSelected:l,onItemTextChange:z.useCallback(e=>{d(t=>t||(e?.textContent??``).trim())},[]),children:(0,B.jsx)(VF.ItemSlot,{scope:n,value:r,disabled:i,textValue:u,children:(0,B.jsx)(LF.div,{role:`option`,"aria-labelledby":h,"data-highlighted":f?``:void 0,"aria-selected":l&&f,"data-state":l?`checked`:`unchecked`,"aria-disabled":i||void 0,"data-disabled":i?``:void 0,tabIndex:i?void 0:-1,...o,ref:m,onFocus:tC(o.onFocus,()=>p(!0)),onBlur:tC(o.onBlur,()=>p(!1)),onClick:tC(o.onClick,()=>{g.current!==`mouse`&&_()}),onPointerUp:tC(o.onPointerUp,()=>{g.current===`mouse`&&_()}),onPointerDown:tC(o.onPointerDown,e=>{g.current=e.pointerType}),onPointerMove:tC(o.onPointerMove,e=>{g.current=e.pointerType,i?c.onItemLeave?.():g.current===`mouse`&&e.currentTarget.focus({preventScroll:!0})}),onPointerLeave:tC(o.onPointerLeave,e=>{e.currentTarget===document.activeElement&&c.onItemLeave?.()}),onKeyDown:tC(o.onKeyDown,e=>{c.searchRef?.current!==``&&e.key===` `||(zF.includes(e.key)&&_(),e.key===` `&&e.preventDefault())})})})})});jI.displayName=OI;var MI=`SelectItemText`,NI=z.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,...a}=e,o=JF(MI,n),s=uI(MI,n),c=AI(MI,n),l=XF(MI,n),[u,d]=z.useState(null),f=en(t,e=>d(e),c.onItemTextChange,e=>s.itemTextRefCallback?.(e,c.value,c.disabled)),p=u?.textContent,m=z.useMemo(()=>(0,B.jsx)(`option`,{value:c.value,disabled:c.disabled,children:p},c.value),[c.disabled,c.value,p]),{onNativeOptionAdd:h,onNativeOptionRemove:g}=l;return $C(()=>(h(m),()=>g(m)),[h,g,m]),(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(LF.span,{id:c.textId,...a,ref:f}),c.isSelected&&o.valueNode&&!o.valueNodeHasChildren?Fi.createPortal(a.children,o.valueNode):null]})});NI.displayName=MI;var PI=`SelectItemIndicator`,FI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return AI(PI,n).isSelected?(0,B.jsx)(LF.span,{"aria-hidden":!0,...r,ref:t}):null});FI.displayName=PI;var II=`SelectScrollUpButton`,LI=z.forwardRef((e,t)=>{let n=uI(II,e.__scopeSelect),r=yI(II,e.__scopeSelect),[i,a]=z.useState(!1),o=en(t,r.onScrollButtonChange);return $C(()=>{if(n.viewport&&n.isPositioned){let e=function(){a(t.scrollTop>0)},t=n.viewport;return e(),t.addEventListener(`scroll`,e),()=>t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,B.jsx)(BI,{...e,ref:o,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop-=t.offsetHeight)}}):null});LI.displayName=II;var RI=`SelectScrollDownButton`,zI=z.forwardRef((e,t)=>{let n=uI(RI,e.__scopeSelect),r=yI(RI,e.__scopeSelect),[i,a]=z.useState(!1),o=en(t,r.onScrollButtonChange);return $C(()=>{if(n.viewport&&n.isPositioned){let e=function(){let e=t.scrollHeight-t.clientHeight;a(Math.ceil(t.scrollTop)t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,B.jsx)(BI,{...e,ref:o,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop+=t.offsetHeight)}}):null});zI.displayName=RI;var BI=z.forwardRef((e,t)=>{let{__scopeSelect:n,onAutoScroll:r,...i}=e,a=uI(`SelectScrollButton`,n),o=z.useRef(null),s=HF(n),c=z.useCallback(()=>{o.current!==null&&(window.clearInterval(o.current),o.current=null)},[]);return z.useEffect(()=>()=>c(),[c]),$C(()=>{s().find(e=>e.ref.current===document.activeElement)?.ref.current?.scrollIntoView({block:`nearest`})},[s]),(0,B.jsx)(LF.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:tC(i.onPointerDown,()=>{o.current===null&&(o.current=window.setInterval(r,50))}),onPointerMove:tC(i.onPointerMove,()=>{a.onItemLeave?.(),o.current===null&&(o.current=window.setInterval(r,50))}),onPointerLeave:tC(i.onPointerLeave,()=>{c()})})}),VI=`SelectSeparator`,HI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return(0,B.jsx)(LF.div,{"aria-hidden":!0,...r,ref:t})});HI.displayName=VI;var UI=`SelectArrow`,WI=z.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=KF(n),a=JF(UI,n),o=uI(UI,n);return a.open&&o.position===`popper`?(0,B.jsx)(nD,{...i,...r,ref:t}):null});WI.displayName=UI;var GI=`SelectBubbleInput`,KI=z.forwardRef(({__scopeSelect:e,value:t,...n},r)=>{let i=z.useRef(null),a=en(r,i),o=zM(t);return z.useEffect(()=>{let e=i.current;if(!e)return;let n=window.HTMLSelectElement.prototype,r=Object.getOwnPropertyDescriptor(n,`value`).set;if(o!==t&&r){let n=new Event(`change`,{bubbles:!0});r.call(e,t),e.dispatchEvent(n)}},[o,t]),(0,B.jsx)(LF.select,{...n,style:{...wN,...n.style},ref:a,defaultValue:t})});KI.displayName=GI;function qI(e){return e===``||e===void 0}function JI(e){let t=fC(e),n=z.useRef(``),r=z.useRef(0),i=z.useCallback(e=>{let i=n.current+e;t(i),(function e(t){n.current=t,window.clearTimeout(r.current),t!==``&&(r.current=window.setTimeout(()=>e(``),1e3))})(i)},[t]),a=z.useCallback(()=>{n.current=``,window.clearTimeout(r.current)},[]);return z.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,a]}function YI(e,t,n){let r=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,i=n?e.indexOf(n):-1,a=XI(e,Math.max(i,0));r.length===1&&(a=a.filter(e=>e!==n));let o=a.find(e=>e.textValue.toLowerCase().startsWith(r.toLowerCase()));return o===n?void 0:o}function XI(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var ZI=ZF,QI=$F,$I=tI,eL=rI,tL=aI,nL=sI,rL=xI,iL=DI,aL=jI,oL=NI,sL=FI,cL=LI,lL=zI,uL=HI,dL=ZI,fL=$I,pL=z.forwardRef(({className:e,children:t,...n},r)=>(0,B.jsxs)(QI,{ref:r,className:Jr(`text-control flex h-8 w-full items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-3 py-1.5 text-left text-12 text-foreground transition-colors duration-200 placeholder:text-text-tertiary focus-visible:outline-none focus-visible:border-input-focus aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive disabled:cursor-not-allowed disabled:opacity-50 [&>span]:min-w-0 [&>span]:line-clamp-1`,e),...n,children:[t,(0,B.jsx)(eL,{asChild:!0,children:(0,B.jsx)(ui,{className:`h-3.5 w-3.5 shrink-0 text-text-tertiary`})})]}));pL.displayName=QI.displayName;var mL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(cL,{ref:n,className:Jr(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,B.jsx)(fi,{className:`h-3.5 w-3.5 text-text-tertiary`})}));mL.displayName=cL.displayName;var hL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(lL,{ref:n,className:Jr(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,B.jsx)(ui,{className:`h-3.5 w-3.5 text-text-tertiary`})}));hL.displayName=lL.displayName;var gL=z.forwardRef(({className:e,children:t,position:n=`popper`,style:r,...i},a)=>{let o=kk();return(0,B.jsx)(Ak,{layer:o,children:(0,B.jsx)(tL,{children:(0,B.jsxs)(nL,{ref:a,className:Jr(`relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg shadow-primary/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,n===`popper`&&`w-[var(--radix-select-trigger-width)] data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1`,e),position:n,...i,style:{...r,zIndex:o},children:[(0,B.jsx)(mL,{}),(0,B.jsx)(rL,{className:Jr(`p-1.5`,n===`popper`&&`h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]`),children:t}),(0,B.jsx)(hL,{})]})})})});gL.displayName=nL.displayName;var _L=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(iL,{ref:n,className:Jr(`px-3.5 py-2 text-13 font-medium text-muted-foreground`,e),...t}));_L.displayName=iL.displayName;var vL=z.forwardRef(({className:e,children:t,...n},r)=>(0,B.jsxs)(aL,{ref:r,className:Jr(`relative flex w-full cursor-pointer select-none items-center rounded-md py-1.5 pl-3 pr-8 text-12 outline-none transition-colors duration-200 focus:bg-overlay-strong focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50`,e),...n,children:[(0,B.jsx)(`span`,{className:`absolute right-3 flex h-3.5 w-3.5 items-center justify-center`,children:(0,B.jsx)(sL,{children:(0,B.jsx)(li,{className:`h-3.5 w-3.5 text-primary`})})}),(0,B.jsx)(oL,{children:t})]}));vL.displayName=aL.displayName;var yL=z.forwardRef(({className:e,...t},n)=>(0,B.jsx)(uL,{ref:n,className:Jr(`-mx-1 my-1 h-px bg-border`,e),...t}));yL.displayName=uL.displayName;function bL(e){let t=(e??`read write`).split(/\s+/).map(e=>e.trim()).filter(Boolean),n=new Set(MP);return new Set(t.filter(e=>n.has(e)))}function xL(e){let t=e.allowed_services_csv,n=e.allowed_nodes_csv,r=new Set((t??``).split(`,`).map(e=>e.trim()).filter(Boolean)),i=new Set((n??``).split(`,`).map(e=>e.trim()).filter(Boolean)),a=e.allow_all_services||!t&&!e.allow_auto_connected_services,o=!n;return{allowAllServices:a,allowAutoConnectedServices:e.allow_auto_connected_services??!1,allowAllNodes:o,selectedServiceIds:r,selectedNodeIds:i}}function SL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),[a,o]=(0,z.useState)(e.platform??``),[s,c]=(0,z.useState)(!1),[l,u]=(0,z.useState)(()=>bL(e.scopes)),[d,f]=(0,z.useState)(()=>xL(e)),[p,m]=(0,z.useState)(e.org_id??``),h=gF(),[g,_]=(0,z.useState)(!1),[v,y]=(0,z.useState)(null);async function b(){_(!0),y(null);try{let i={name:r,scopes:Array.from(l).join(` `),allow_all_services:d.allowAllServices,allow_auto_connected_services:d.allowAutoConnectedServices??!1,allow_all_nodes:d.allowAllNodes};if(a&&(i.platform=a),e.callback_url&&(i.callback_url=e.callback_url),p&&(i.target_org_id=p),d.allowAllServices||(i.allowed_service_ids=Array.from(d.selectedServiceIds)),d.allowAllNodes||(i.allowed_node_ids=Array.from(d.selectedNodeIds)),e.expires_in_days!=null&&e.expires_in_days>0){let t=new Date;t.setDate(t.getDate()+e.expires_in_days),i.expires_at=t.toISOString()}await _P(t);let o=await yP(t,()=>gb.post(`/api-keys`,i));n({kind:`api-key-create`,api_key_id:o.id,full_key:o.full_key})}catch(e){y(PL(e))}finally{_(!1)}}let x=g||!s||l.size===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create an API key`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Review the details your CLI sent and confirm to mint the key.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(DP,{id:`pair-api-key-name`,label:`Name`,schema:SP,value:r,onChange:i,onValidityChange:c,placeholder:`e.g. coding-agent`,hint:"A short label so you can find this key in `nyxid api-key list`.",autoFocus:!0}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-api-key-platform`,children:`Platform`}),(0,B.jsx)(pP,{delayDuration:150,children:(0,B.jsxs)(mP,{children:[(0,B.jsx)(hP,{asChild:!0,children:(0,B.jsx)(`button`,{type:`button`,"aria-label":`About platform tags`,className:`text-muted-foreground transition-colors duration-300 hover:text-foreground`,children:(0,B.jsx)(Si,{className:`h-3.5 w-3.5`})})}),(0,B.jsx)(gP,{side:`right`,align:`start`,sideOffset:8,className:`max-w-[340px] whitespace-normal px-5 py-4 text-13 leading-[1.55]`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`p`,{children:[(0,B.jsx)(`span`,{className:`font-medium text-foreground`,children:`Platform`}),` `,`tags the key with the AI agent that will use it.`]}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:`It controls three things: audit attribution (logs show which agent made each proxy request), per- agent rate-limit buckets, and dashboard filtering on the API Keys page.`}),(0,B.jsxs)(`p`,{className:`text-muted-foreground`,children:[`Values are a fixed allowlist —`,` `,(0,B.jsx)(`code`,{children:`claude-code`}),`, `,(0,B.jsx)(`code`,{children:`cursor`}),`,`,` `,(0,B.jsx)(`code`,{children:`codex`}),`, `,(0,B.jsx)(`code`,{children:`openclaw`}),`,`,` `,(0,B.jsx)(`code`,{children:`generic`}),`. Custom strings are rejected by the backend.`]}),(0,B.jsxs)(`p`,{className:`text-muted-foreground`,children:[`Leave as `,(0,B.jsx)(`code`,{children:`— none —`}),` if you don't want the tag.`]})]})})]})})]}),(0,B.jsxs)(dL,{value:a===``?`__none__`:a,onValueChange:e=>{o(e===`__none__`?``:e)},children:[(0,B.jsx)(pL,{id:`pair-api-key-platform`,children:(0,B.jsx)(fL,{placeholder:`— none —`})}),(0,B.jsxs)(gL,{children:[(0,B.jsx)(vL,{value:`__none__`,children:`— none —`}),CP.map(e=>(0,B.jsx)(vL,{value:e,children:(0,B.jsxs)(`span`,{className:`inline-flex items-center gap-2`,children:[(0,B.jsx)(FM,{platform:e,size:`2xs`}),(0,B.jsx)(`span`,{children:e})]})},e))]})]}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Tags the key for audit attribution + per-agent rate limits.`})]}),(h.data?.length??0)>0?(0,B.jsxs)(ML,{label:`Owner`,htmlFor:`pair-api-key-owner`,children:[(0,B.jsxs)(`select`,{id:`pair-api-key-owner`,value:p,onChange:e=>{m(e.target.value),f(e=>({...e,selectedServiceIds:new Set,selectedNodeIds:new Set,allowAutoConnectedServices:!1}))},className:`flex h-10 w-full rounded-xl border border-input bg-transparent px-[14px] py-2 text-13 text-foreground transition-colors duration-300 focus-visible:outline-none focus-visible:border-input-focus`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal (your account)`}),h.data?.filter(e=>e.your_role===`admin`).map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]}),(0,B.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:`Org-owned keys authenticate as the org; every admin of the selected org can rotate or delete them.`})]}):null,(0,B.jsx)(NP,{value:l,onChange:u}),(0,B.jsx)(uF,{value:d,onChange:f,ownerId:p})]}),v?(0,B.jsx)(NL,{message:v}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void b(),disabled:x,children:g?`Creating...`:`Create Key`})]})}function CL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await _P(t);let r=await yP(t,()=>gb.post(`/api-keys/${encodeURIComponent(e.resource_id)}/rotate`));n({kind:`api-key-rotate`,resource_id:r.id,full_key:r.full_key,platform:r.platform})}catch(e){o(PL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate API key`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` will issue a new key and immediately revoke the previous one.`]})]}),a?(0,B.jsx)(NL,{message:a}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate key`})]})}var wL=`my-node`;function TL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),[a,o]=(0,z.useState)(!0),[s,c]=(0,z.useState)(!1),[l,u]=(0,z.useState)(null);async function d(){c(!0),u(null);try{let e=r.trim(),i=e.length>0?e:wL;await _P(t);let a=await yP(t,()=>gb.post(`/nodes/register-token`,{name:i}));n({kind:`node-register-token`,token_id:a.token_id,token:a.token})}catch(e){u(PL(e))}finally{c(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Generate node registration token`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Use this token with `,(0,B.jsx)(`code`,{children:`nyxid node register`}),` to connect a new node.`]})]}),(0,B.jsx)(DP,{id:`pair-node-name`,label:`Node name (optional)`,schema:xP,value:r,onChange:i,onValidityChange:o,placeholder:wL,hint:`Lowercase letters, digits, hyphens only (max 64). Leave blank for \`${wL}\`.`,optional:!0,autoFocus:!0}),l?(0,B.jsx)(NL,{message:l}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void d(),disabled:s||!a,children:s?`Generating...`:`Generate token`})]})}function EL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await _P(t);let r=await yP(t,()=>gb.post(`/nodes/${encodeURIComponent(e.resource_id)}/rotate-token`));n({kind:`node-rotate-token`,resource_id:e.resource_id,auth_token:r.auth_token,signing_secret:r.signing_secret})}catch(e){o(PL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate node token`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` issues a new auth token + signing secret and revokes the previous pair.`]})]}),a?(0,B.jsx)(NL,{message:a}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate token`})]})}function DL({prefill:e,pairingId:t,onSuccess:n}){let r=bd({resolver:yd(Bk),defaultValues:{name:e.name??``,allowed_scopes:e.scopes??`openid profile`,description:e.description??``,role_ids:e.role_ids_csv??``}}),i=r.watch(`name`),a=r.watch(`allowed_scopes`),o=r.watch(`description`)??``,s=r.watch(`role_ids`)??``,[c,l]=(0,z.useState)(e.org_id??``),u=eC(e=>e.user),d=lt({queryKey:[`wizard-current-user`,t],enabled:!u,queryFn:async()=>{await eC.getState().checkAuth({ephemeral:!0});let e=eC.getState().user;if(!e)throw Error(`Unable to load your account. Check your CLI login and retry.`);return e},retry:!1}),f=gF(),[p,m]=(0,z.useState)(!1),[h,g]=(0,z.useState)(null);async function _(){if(await r.trigger()){m(!0),g(null);try{let r={name:i.trim(),allowed_scopes:a.trim()};o.trim()&&(r.description=o.trim()),e.rate_limit_override!=null&&(r.rate_limit_override=e.rate_limit_override);let l=s.split(`,`).map(e=>e.trim()).filter(Boolean);l.length>0&&(r.role_ids=l),c&&(r.target_org_id=c),await _P(t);let u=await yP(t,()=>gb.post(`/admin/service-accounts`,r));n({kind:`service-account-create`,service_account_id:u.id,client_id:u.client_id,client_secret:u.client_secret})}catch(e){g(PL(e))}finally{m(!1)}}}let v=p||i.trim().length===0||a.trim().length===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create a service account`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Service accounts authenticate via the OAuth client_credentials flow. The client_secret is shown once, on the next screen.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(ML,{label:`Name`,htmlFor:`pair-sa-name`,children:(0,B.jsx)(IM,{id:`pair-sa-name`,value:i,onChange:e=>{r.setValue(`name`,e.target.value)},placeholder:`e.g. ci-deploys`,autoFocus:!0})}),(0,B.jsx)(ML,{label:`Allowed scopes`,htmlFor:`pair-sa-scopes`,children:(0,B.jsx)(Rk,{id:`pair-sa-scopes`,value:a,onChange:e=>r.setValue(`allowed_scopes`,e),ownerId:c||u?.id||``})}),(0,B.jsx)(ML,{label:`Description (optional)`,htmlFor:`pair-sa-desc`,children:(0,B.jsx)(IM,{id:`pair-sa-desc`,value:o,onChange:e=>{r.setValue(`description`,e.target.value)},placeholder:`What this account is for`})}),(0,B.jsx)(ML,{label:`Role IDs (optional, comma-separated)`,htmlFor:`pair-sa-roles`,children:(0,B.jsx)(IM,{id:`pair-sa-roles`,value:s,onChange:e=>{r.setValue(`role_ids`,e.target.value)},placeholder:`role-id-1,role-id-2`})}),(f.data?.length??0)>0?(0,B.jsx)(ML,{label:`Owner`,htmlFor:`pair-sa-owner`,children:(0,B.jsxs)(`select`,{id:`pair-sa-owner`,value:c,onChange:e=>{l(e.target.value)},className:`flex h-10 w-full rounded-[10px] border border-input bg-transparent px-[14px] py-2 text-13 text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal (your admin account)`}),f.data?.map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]})}):null]}),!u&&d.isPending&&(0,B.jsx)(`p`,{role:`status`,children:`Loading your account…`}),!u&&d.isError&&(0,B.jsxs)(`div`,{role:`alert`,children:[(0,B.jsx)(NL,{message:d.error.message}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void d.refetch(),children:`Retry account`})]}),Object.entries(r.formState.errors).map(([e,t])=>(0,B.jsx)(NL,{message:t.message??`Invalid value`},e)),h?(0,B.jsx)(NL,{message:h}):null,(0,B.jsx)(Pi,{onClick:()=>void _(),disabled:v,children:p?`Creating...`:`Create Service Account`})]})}function OL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await _P(t);let r=await yP(t,()=>gb.post(`/admin/service-accounts/${encodeURIComponent(e.resource_id)}/rotate-secret`));n({kind:`service-account-rotate-secret`,resource_id:e.resource_id,client_id:r.client_id,client_secret:r.client_secret})}catch(e){o(PL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate service account secret`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` immediately revokes all existing access tokens issued under this service account and mints a new client_secret.`]})]}),a?(0,B.jsx)(NL,{message:a}):null,(0,B.jsx)(Pi,{onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate secret`})]})}function kL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(e.name??``),a=(e.redirect_uris??[]).filter(e=>typeof e==`string`&&e.length>0),[o,s]=(0,z.useState)(a.length>0?[...a]:[``]),[c,l]=(0,z.useState)(e.allowed_scopes??`openid profile email`),[u,d]=(0,z.useState)(e.delegation_scopes??``),[f,p]=(0,z.useState)(e.broker_capability??!1),[m,h]=(0,z.useState)(e.org_id??``),g=gF(),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null);function x(e,t){s(n=>n.map((n,r)=>r===e?t:n))}function S(){s(e=>[...e,``])}function C(e){s(t=>t.length===1?[``]:t.filter((t,n)=>n!==e))}async function w(){v(!0),b(null);try{let i=o.map(e=>e.trim()).filter(Boolean);if(i.length===0){b(`At least one redirect URI is required.`),v(!1);return}let a={name:r.trim(),redirect_uris:i,client_type:`confidential`};c.trim()&&(a.allowed_scopes=c.split(/\s+/).map(e=>e.trim()).filter(Boolean)),u.trim()&&(a.delegation_scopes=u.trim()),f&&(a.broker_capability_enabled=!0);let s=(e.default_service_catalog_slugs??[]).map(e=>e.trim()).filter(Boolean);s.length>0&&(a.default_service_catalog_slugs=s),m&&(a.target_org_id=m),await _P(t);let l=await yP(t,()=>gb.post(`/developer/oauth-clients`,a));if(!l.client_secret)throw Error(`Server didn't return a client_secret — was the client_type 'public'?`);n({kind:`developer-app-create`,developer_app_id:l.id,client_secret:l.client_secret})}catch(e){b(PL(e))}finally{v(!1)}}let T=_||r.trim().length===0;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Create a developer OAuth app`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Confidential client — the client_secret is shown once on the next screen. Use it to sign Sign-in-with-NyxID requests from your downstream product.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(ML,{label:`App name`,htmlFor:`pair-app-name`,children:(0,B.jsx)(IM,{id:`pair-app-name`,value:r,onChange:e=>{i(e.target.value)},placeholder:`e.g. Acme Web`,autoFocus:!0})}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:`Redirect URIs`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[o.map((e,t)=>(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(IM,{value:e,onChange:e=>{x(t,e.target.value)},placeholder:`https://app.example.com/callback`,className:`flex-1`}),(0,B.jsx)(Pi,{type:`button`,variant:`outline`,size:`icon`,onClick:()=>{C(t)},"aria-label":`Remove redirect URI`,disabled:o.length===1&&e.trim().length===0,children:(0,B.jsx)(ki,{className:`h-4 w-4`})})]},t)),(0,B.jsxs)(Pi,{type:`button`,variant:`outline`,size:`sm`,onClick:S,className:`self-start`,children:[(0,B.jsx)(Di,{className:`mr-1 h-3 w-3`}),` Add redirect URI`]})]})]}),(0,B.jsxs)(ML,{label:`Allowed scopes`,htmlFor:`pair-app-scopes`,children:[(0,B.jsx)(IM,{id:`pair-app-scopes`,value:c,onChange:e=>{l(e.target.value)},placeholder:`openid profile email`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Space-separated.`})]}),(0,B.jsx)(ML,{label:`Delegation scopes (optional)`,htmlFor:`pair-app-delegation`,children:(0,B.jsx)(IM,{id:`pair-app-delegation`,value:u,onChange:e=>{d(e.target.value)},placeholder:`(blank disables token exchange)`})}),(0,B.jsxs)(`div`,{className:`flex items-center justify-between rounded-md border border-border bg-muted/20 px-3 py-2`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-app-broker`,children:`Broker capability`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Allow this app to broker downstream credentials.`})]}),(0,B.jsx)(oN,{id:`pair-app-broker`,checked:f,onCheckedChange:e=>{p(e)}})]}),(g.data?.length??0)>0?(0,B.jsx)(ML,{label:`Owner`,htmlFor:`pair-app-owner`,children:(0,B.jsxs)(`select`,{id:`pair-app-owner`,value:m,onChange:e=>{h(e.target.value)},className:`flex h-10 w-full rounded-[10px] border border-input bg-transparent px-[14px] py-2 text-13 text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`,children:[(0,B.jsx)(`option`,{value:``,children:`Personal`}),g.data?.map(e=>(0,B.jsxs)(`option`,{value:e.id,children:[`Org · `,e.display_name??e.id]},e.id))]})}):null]}),y?(0,B.jsx)(NL,{message:y}):null,(0,B.jsx)(Pi,{onClick:()=>void w(),disabled:T,children:_?`Creating...`:`Create app`})]})}function AL({prefill:e,pairingId:t,onSuccess:n}){let[r,i]=(0,z.useState)(!1),[a,o]=(0,z.useState)(null);async function s(){i(!0),o(null);try{await _P(t);let r=await yP(t,()=>gb.post(`/developer/oauth-clients/${encodeURIComponent(e.resource_id)}/rotate-secret`));n({kind:`developer-app-rotate-secret`,resource_id:r.id,client_secret:r.client_secret})}catch(e){o(PL(e))}finally{i(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Rotate developer app secret`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Rotating `,(0,B.jsx)(`strong`,{children:e.display_name}),` mints a new client_secret. Update any deployments using the previous value immediately.`]})]}),a?(0,B.jsx)(NL,{message:a}):null,(0,B.jsx)(Pi,{onClick:()=>void s(),disabled:r,children:r?`Rotating...`:`Rotate secret`})]})}function jL({pairingId:e,onSuccess:t}){let[n,r]=(0,z.useState)(`init`),[i,a]=(0,z.useState)(null),[o,s]=(0,z.useState)(null),[c,l]=(0,z.useState)(null),[u,d]=(0,z.useState)(null),[f,p]=(0,z.useState)(``),[m,h]=(0,z.useState)(null),[g,_]=(0,z.useState)(!1),v=(0,z.useRef)(!1);(0,z.useEffect)(()=>{v.current||(v.current=!0,(async()=>{try{await _P(e);let t=await yP(e,()=>gb.post(`/auth/mfa/setup`,{}));a(t.factor_id),s(t.secret),l(t.qr_code_url);try{d(await hA.toDataURL(t.qr_code_url,{width:240,margin:1}))}catch{}r(`ready`)}catch(e){h(PL(e)),v.current=!1}})())},[e]);async function y(){if(!(!i||n!==`ready`)){h(null),r(`confirming`);try{let e=f.trim();if(e.length===0){h(`Enter the 6-digit code from your authenticator.`),r(`ready`);return}t({kind:`mfa-setup`,factor_id:i,recovery_codes:(await gb.post(`/auth/mfa/confirm`,{code:e})).recovery_codes})}catch(e){h(PL(e)),r(`ready`)}}}return n===`init`?(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Setting up MFA`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Generating a TOTP secret on the server…`})]}),m?(0,B.jsx)(NL,{message:m}):null]}):(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Add MFA to your account`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Scan this QR with your authenticator app (1Password, Authy, Google Authenticator). Then enter the 6-digit code it shows to verify and finish enrollment.`})]}),u?(0,B.jsx)(`div`,{className:`flex justify-center rounded-md border border-border bg-white p-4 dark:bg-muted/30`,children:(0,B.jsx)(`img`,{src:u,alt:`MFA enrollment QR code`,className:`h-60 w-60`})}):(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Couldn't render the QR code. Use the otpauth URL below instead.`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{children:`Or enter the secret manually`}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(`code`,{className:`flex-1 overflow-x-auto rounded-md border bg-muted/40 px-3 py-2 font-mono text-sm`,children:g?o:`•`.repeat(Math.max(o?.length??12,12))}),(0,B.jsx)(Pi,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>{_(e=>!e)},children:g?`Hide`:`Reveal`})]}),c?(0,B.jsxs)(`p`,{className:`break-all text-11 text-muted-foreground`,children:[`otpauth URL: `,(0,B.jsx)(`code`,{className:`font-mono`,children:c})]}):null]}),(0,B.jsx)(ML,{label:`6-digit code from your authenticator`,htmlFor:`pair-mfa-code`,children:(0,B.jsx)(IM,{id:`pair-mfa-code`,value:f,onChange:e=>{p(e.target.value)},placeholder:`123456`,inputMode:`numeric`,autoFocus:!0,maxLength:10,className:`font-mono tracking-widest`})}),m?(0,B.jsx)(NL,{message:m}):null,(0,B.jsx)(Pi,{onClick:()=>void y(),disabled:n===`confirming`||f.trim().length===0,children:n===`confirming`?`Verifying...`:`Verify and enable MFA`})]})}function ML({label:e,htmlFor:t,children:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:t,children:e}),n]})}function NL({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}function PL(e){return e instanceof Error?e.message:`Something went wrong. Please try again.`}var FL=[`tokens`,`requests`,`bytes`,`input_tokens`,`output_tokens`,`cache_read_tokens`,`cache_write_tokens`,`images`,`voice_seconds`],IL={tokens:{label:`tokens`,singular:`token`,tokenFamily:!0},requests:{label:`requests`,singular:`request`,tokenFamily:!1},bytes:{label:`bytes`,singular:`byte`,tokenFamily:!1},input_tokens:{label:`input tokens`,singular:`input token`,tokenFamily:!0},output_tokens:{label:`output tokens`,singular:`output token`,tokenFamily:!0},cache_read_tokens:{label:`cache-read tokens`,singular:`cache-read token`,tokenFamily:!0},cache_write_tokens:{label:`cache-write tokens`,singular:`cache-write token`,tokenFamily:!0},voice_seconds:{label:`voice seconds`,singular:`voice second`,tokenFamily:!1},images:{label:`images`,singular:`image`,tokenFamily:!1}};function LL(e,t){let n=IL[e];return n?t===1?n.singular:n.label:e}var RL=RegExp(`^\\d+(?:\\.\\d{1,12})?$`);function zL(e){if(!RL.test(e))return!1;let[t,n=``]=e.split(`.`);return BigInt(t)*10n**12n+BigInt(n.padEnd(12,`0`))<=1000000n*10n**12n}var BL=$().trim().regex(RL,`Use a non-negative decimal with at most 12 decimal places`).refine(zL,`Price must not exceed 1,000,000 credits per unit`),VL=[`voice_seconds`,`tokens`,`input_tokens`,`output_tokens`,`cache_read_tokens`,`cache_write_tokens`],HL=$().min(1).max(128).regex(/^[A-Za-z0-9_.-]+$/,`Use a provider identifier, not a URL`),UL=$().trim().min(1).max(128).refine(e=>!/\p{Cc}/u.test(e),`Control characters are not allowed`),WL=jx({protocol:Vx([`openai_live`,`xai_realtime`]),models:kx(jx({id:HL,label:UL,default:Cx().optional()})).min(1).max(32),voices:kx(jx({id:HL,label:UL})).min(1).max(64),usage_source:Vx([`provider_reported`,`server_measured`]),billing_metrics:kx(Vx(VL)).min(1).max(6)}).superRefine((e,t)=>{for(let n of[`models`,`voices`])new Set(e[n].map(e=>e.id)).size!==e[n].length&&t.addIssue({code:`custom`,path:[n],message:`Provider IDs must be unique`});e.models.filter(e=>e.default).length>1&&t.addIssue({code:`custom`,path:[`models`],message:`Choose at most one default model`}),(new Set(e.billing_metrics).size!==e.billing_metrics.length||!e.billing_metrics.includes(`voice_seconds`))&&t.addIssue({code:`custom`,path:[`billing_metrics`],message:`Unique metrics including voice seconds are required`}),e.usage_source!==(e.protocol===`openai_live`?`provider_reported`:`server_measured`)&&t.addIssue({code:`custom`,path:[`usage_source`],message:`Usage source must match the provider protocol`})}),GL=jx({wire_protocol:Vx([`anthropic_messages`,`openai_responses`,`openai_completions`]),model_list:Cx(),realtime:Cx().optional(),voice:WL.nullish()}).extend({binding:Vx([`platform`,`user`]),status_slug:$().optional()}),KL=jx({metric:$(),credits_per_unit:BL,sync_status:Vx([`pending`,`synced`,`failed`]).optional()}),qL=KL.extend({components:kx(KL).nullish()}),JL=KL.extend({metric:Vx(FL)});JL.extend({components:kx(JL).max(FL.length-1).nullish()}).superRefine((e,t)=>{let n=new Set([e.metric]);e.components?.forEach((e,r)=>{n.has(e.metric)&&t.addIssue({code:`custom`,path:[`components`,r,`metric`],message:`Each unit may appear only once per lane`}),n.add(e.metric)})}),jx({enabled:Cx(),audience:Vx([`public`,`restricted`]),allowed_owner_ids:kx($().uuid()).max(1e3)}),jx({inference:GL.nullish(),platform_key:jx({available:Cx(),pricing:qL.nullish()}).optional(),byok_pricing:qL.nullish()});function YL(e){return e?[e,...e.components??[]].map(e=>`${e.credits_per_unit} credits / ${LL(e.metric,1)}${e.sync_status&&e.sync_status!==`synced`?` (price pending; current billing applies)`:``}`).join(` + `):`free`}function XL({value:e,onChange:t,platformPrice:n,byokPrice:r,legacyBillable:i=!1,resaleBillable:a=!1,disabled:o=!1}){return(0,B.jsxs)(`fieldset`,{className:`space-y-2`,disabled:o,children:[(0,B.jsx)(`legend`,{className:`mb-2 text-xs font-medium`,children:`Choose a key`}),[{platform:!0,title:`Use NyxID's key`,price:n},{platform:!1,title:`Use your own key`,price:r}].map(a=>(0,B.jsxs)(`label`,{className:Jr(`flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-xs`,e===a.platform?`border-primary/50`:`border-border/50`),children:[(0,B.jsx)(`input`,{type:`radio`,name:`credential-binding`,checked:e===a.platform,onChange:()=>t(a.platform),className:`mt-0.5 accent-primary`}),(0,B.jsxs)(`span`,{children:[(0,B.jsx)(`span`,{className:`block font-medium`,children:a.title}),(0,B.jsx)(`span`,{className:`text-muted-foreground`,children:!n&&!r&&i?`Current service/plan pricing applies`:YL(a.price)})]})]},String(a.platform))),a&&(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground`,children:`Platform-key use may also incur the separate resale fee.`})]})}function ZL({className:e,...t}){return(0,B.jsx)(`div`,{className:Jr(`animate-pulse rounded-md bg-muted`,e),...t})}var QL=`__personal__`;function $L({id:e,"aria-describedby":t,value:n,onChange:r,disabled:i,label:a=`Scope`,adminOnly:o=!0,allowAll:s=!1,personalLabel:c=`Personal`}){let{data:l,isLoading:u}=gF(),d=(l??[]).filter(e=>!o||e.your_role===`admin`);return(0,B.jsxs)(dL,{value:n??QL,onValueChange:e=>r(e===QL?null:e),disabled:i||u,children:[(0,B.jsx)(pL,{id:e,"aria-label":a,"aria-describedby":t,children:(0,B.jsx)(fL,{placeholder:c})}),(0,B.jsxs)(gL,{children:[s&&(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(vL,{value:`all`,children:`View all`}),(0,B.jsx)(yL,{asChild:!0,className:`border-0`,children:(0,B.jsx)(`hr`,{})})]}),(0,B.jsx)(vL,{value:QL,children:c}),d.map(e=>(0,B.jsx)(vL,{value:e.id,children:e.display_name||e.id},e.id))]})]})}function eR(e,t=[]){return[...new Set([...e,...t.filter(e=>e.required).map(e=>e.scope)])]}function tR(e){let t=new Set,n=[];for(let r of e.split(/[,\s]+/)){let e=r.trim();e&&!t.has(e)&&(t.add(e),n.push(e))}return n}function nR(e,t,n,r){let i=new Set,a=new Set(t),o=new Set(r),s=[];for(let t of e)i.has(t.scope)||(i.add(t.scope),s.push({scope:t.scope,label:t.label||t.scope,description:t.description||null,sensitive:!!t.sensitive,isDefault:a.has(t.scope),locked:o.has(t.scope)}));for(let e of r)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!1,locked:!0}));for(let e of t)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!0,locked:!1}));for(let e of n)i.has(e)||(i.add(e),s.push({scope:e,label:e,description:null,sensitive:!1,isDefault:!1,locked:!1}));return s}function rR({catalog:e,defaultScopes:t,value:n,onChange:r,customPlaceholder:i=`e.g. custom.scope`,idPrefix:a=`scope`,lockedScopes:o=[],grantedScopes:s,providerName:c,platformAllowlist:l}){let[u,d]=(0,z.useState)(``),f=eR(n,e),p=new Set(e.filter(e=>e.required).map(e=>e.scope)),m=new Set(f),h=new Set(o),g=nR(e,t,n,o),_=l?new Set(l):null,v=(e,t)=>_!==null&&!t&&!_.has(e),y=e=>g.find(t=>t.scope===e)?.label??e,b=s?new Set(s):null,x=b?f.filter(e=>!b.has(e)):[],S=s?s.filter(e=>!m.has(e)):[],C=x.length>0||S.length>0;function w(e){if(h.has(e)||p.has(e))return;let t=new Set(m);t.has(e)?t.delete(e):t.add(e),r(g.map(e=>e.scope).filter(e=>t.has(e)))}let[T,E]=(0,z.useState)(null);function ee(){let e=tR(u);if(e.length===0)return;if(_!==null){let t=e.filter(e=>!_.has(e));if(t.length>0){E(`${t.join(`, `)} — not available on NyxID's shared app. Use your own OAuth app to request ${t.length>1?`these`:`it`}.`);return}}E(null);let t=[...f];for(let n of e)t.includes(n)||t.push(n);d(``),r(t)}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(Vi,{className:`text-xs`,children:`Scopes`}),g.length>0?(0,B.jsx)(`div`,{role:`group`,"aria-label":`Scopes`,className:`flex flex-wrap gap-1.5`,children:g.map(e=>{let t=p.has(e.scope),n=v(e.scope,e.locked),r=(m.has(e.scope)||e.locked)&&!n;return(0,B.jsxs)(`button`,{type:`button`,"aria-pressed":r,disabled:e.locked||t||n,title:n?`${e.description??e.scope} — available only with your own OAuth app`:t?`${e.description??e.scope} — required for this service`:e.locked?`${e.description??e.scope} — already granted; can't be removed here`:e.description??e.scope,onClick:()=>{w(e.scope)},className:`group inline-flex max-w-full items-center gap-1.5 rounded-full border px-3 py-1.5 text-left text-12 transition-colors `+(n?`cursor-not-allowed border-dashed border-border/60 bg-transparent text-muted-foreground/50`:e.locked||t?`cursor-default border-primary/60 bg-primary/10 text-foreground`:r?`border-primary bg-primary/15 text-foreground`:`border-border bg-transparent text-muted-foreground hover:border-primary/50 hover:bg-muted/40`),children:[e.sensitive?(0,B.jsxs)(B.Fragment,{children:[(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 shrink-0 rounded-full bg-warning`}),(0,B.jsx)(`span`,{className:`sr-only`,children:`(write or admin access) `})]}):null,(0,B.jsx)(`span`,{className:`truncate`,children:e.label}),n?(0,B.jsx)(`span`,{className:`shrink-0 text-11 italic text-text-tertiary`,children:`own app`}):t?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`required`}):e.locked?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`granted`}):e.isDefault?(0,B.jsx)(`span`,{className:`shrink-0 text-11 text-muted-foreground`,children:`default`}):null,r&&!e.locked&&!t?(0,B.jsx)(ji,{className:`h-3 w-3 shrink-0 opacity-50 group-hover:opacity-100`}):null]},e.scope)})}):null,g.some(e=>e.sensitive)?(0,B.jsxs)(`p`,{className:`flex items-center gap-1.5 text-11 text-muted-foreground`,children:[(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 shrink-0 rounded-full bg-warning`}),`Dot marks a write or admin-level scope.`]}):null,_!==null&&g.some(e=>v(e.scope,e.locked))?(0,B.jsxs)(`p`,{className:`text-11 text-muted-foreground`,children:[`Scopes marked “own app” aren’t offered on NyxID’s shared`,` `,c??`provider`,` app. Connect with your own OAuth app to request them.`]}):null,(0,B.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,B.jsx)(IM,{id:`${a}-custom`,value:u,onChange:e=>{d(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),ee())},placeholder:i,autoComplete:`off`,spellCheck:!1,className:`h-9 text-12`}),(0,B.jsxs)(Pi,{type:`button`,variant:`outline`,onClick:ee,disabled:u.trim().length===0,className:`h-9 shrink-0 px-3`,children:[(0,B.jsx)(Di,{className:`h-3.5 w-3.5`}),`Add`]})]}),T?(0,B.jsx)(`p`,{className:`text-11 text-destructive`,children:T}):null,b&&C?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1 rounded-lg border border-border bg-muted/40 px-3 py-2 text-12`,children:[(0,B.jsx)(`span`,{className:`text-11 font-medium uppercase tracking-wide text-muted-foreground`,children:`Changes`}),x.length>0?(0,B.jsxs)(`p`,{className:`text-foreground`,children:[(0,B.jsx)(`span`,{className:`text-success`,children:`+ Adding:`}),` `,x.map(y).join(`, `)]}):null,S.length>0?(0,B.jsxs)(B.Fragment,{children:[(0,B.jsxs)(`p`,{className:`text-foreground`,children:[(0,B.jsx)(`span`,{className:`text-destructive`,children:`− Removing:`}),` `,S.map(y).join(`, `)]}),(0,B.jsxs)(`p`,{className:`text-11 text-warning`,children:[`Removing a permission re-authorizes this connection and will stop any app that relies on it. NyxID will use only the remaining permissions; the old access at`,` `,c??`the provider`,` stays until you revoke it there.`]})]}):null]}):null,(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:h.size>0?`Scopes marked “granted” are already authorized and locked — this provider can’t narrow them by re-authorizing, so they can’t be removed here. Add anything missing above.`:b?`Tick to add a permission, untick to remove one, then update. Changes re-authorize this connection at the provider.`:g.length>0?`Selected scopes are requested at sign-in. Defaults are pre-selected — deselect to drop one. Add anything missing above; the upstream provider decides whether to grant them.`:`Comma- or space-separated. The upstream provider decides whether to grant them.`})]})}function iR(e){return e===`revoked`||e===`failed`||e===`expired`}var aR=5;async function oR({keyId:e,getKey:t,completeWithKey:n,isCancelled:r,onTerminalFailure:i,onTimeout:a,sleepMs:o=sR,nowMs:s=Date.now,timeoutMs:c=300*1e3,intervalMs:l=2e3,maxConsecutiveErrors:u=aR,isComplete:d=e=>e.status===`active`}){let f=s()+c,p=0;for(;s()=u){r()||i({status:`failed`,error_message:"Lost contact with the wizard. Authorization may have completed — run `nyxid status` to verify, then cancel and re-run the wizard if the service is missing."});return}}}r()||a()}function sR(e){return new Promise(t=>{window.setTimeout(t,e)})}var cR=jx({api_base_url:$().trim().url(`API base URL must be a valid URL`).transform(e=>e.replace(/\/+$/,``)),release_integrity:jx({enabled:Cx(),manifest_url:$().trim().url(`Release integrity manifest URL must be a valid URL`).nullable(),verification_ttl_secs:yx().int().positive()})});function lR(){return lt({queryKey:[`runtime-config`],queryFn:async()=>{let e=await gb.get(`/runtime-config`);return cR.parse(e)},staleTime:1/0})}function uR(e){if(!e||typeof document>`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}Array(12).fill(0);var dR=1,fR=new class{constructor(){this.subscribe=e=>(this.subscribers.push(e),()=>{let t=this.subscribers.indexOf(e);this.subscribers.splice(t,1)}),this.publish=e=>{this.subscribers.forEach(t=>t(e))},this.addToast=e=>{this.publish(e),this.toasts=[...this.toasts,e]},this.create=e=>{let{message:t,...n}=e,r=typeof e?.id==`number`||e.id?.length>0?e.id:dR++,i=this.toasts.find(e=>e.id===r),a=e.dismissible===void 0?!0:e.dismissible;return this.dismissedToasts.has(r)&&this.dismissedToasts.delete(r),i?this.toasts=this.toasts.map(n=>n.id===r?(this.publish({...n,...e,id:r,title:t}),{...n,...e,id:r,dismissible:a,title:t}):n):this.addToast({title:t,...n,dismissible:a,id:r}),r},this.dismiss=e=>(e?(this.dismissedToasts.add(e),requestAnimationFrame(()=>this.subscribers.forEach(t=>t({id:e,dismiss:!0})))):this.toasts.forEach(e=>{this.subscribers.forEach(t=>t({id:e.id,dismiss:!0}))}),e),this.message=(e,t)=>this.create({...t,message:e}),this.error=(e,t)=>this.create({...t,message:e,type:`error`}),this.success=(e,t)=>this.create({...t,type:`success`,message:e}),this.info=(e,t)=>this.create({...t,type:`info`,message:e}),this.warning=(e,t)=>this.create({...t,type:`warning`,message:e}),this.loading=(e,t)=>this.create({...t,type:`loading`,message:e}),this.promise=(e,t)=>{if(!t)return;let n;t.loading!==void 0&&(n=this.create({...t,promise:e,type:`loading`,message:t.loading,description:typeof t.description==`function`?void 0:t.description}));let r=Promise.resolve(e instanceof Function?e():e),i=n!==void 0,a,o=r.then(async e=>{if(a=[`resolve`,e],z.isValidElement(e))i=!1,this.create({id:n,type:`default`,message:e});else if(mR(e)&&!e.ok){i=!1;let r=typeof t.error==`function`?await t.error(`HTTP error! status: ${e.status}`):t.error,a=typeof t.description==`function`?await t.description(`HTTP error! status: ${e.status}`):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(e instanceof Error){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(t.success!==void 0){i=!1;let r=typeof t.success==`function`?await t.success(e):t.success,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`success`,description:a,...o})}}).catch(async e=>{if(a=[`reject`,e],t.error!==void 0){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!z.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}}).finally(()=>{i&&(this.dismiss(n),n=void 0),t.finally==null||t.finally.call(t)}),s=()=>new Promise((e,t)=>o.then(()=>a[0]===`reject`?t(a[1]):e(a[1])).catch(t));return typeof n!=`string`&&typeof n!=`number`?{unwrap:s}:Object.assign(n,{unwrap:s})},this.custom=(e,t)=>{let n=t?.id||dR++;return this.create({jsx:e(n),id:n,...t}),n},this.getActiveToasts=()=>this.toasts.filter(e=>!this.dismissedToasts.has(e.id)),this.subscribers=[],this.toasts=[],this.dismissedToasts=new Set}},pR=(e,t)=>{let n=t?.id||dR++;return fR.addToast({title:e,...t,id:n}),n},mR=e=>e&&typeof e==`object`&&`ok`in e&&typeof e.ok==`boolean`&&`status`in e&&typeof e.status==`number`,hR=Object.assign(pR,{success:fR.success,info:fR.info,warning:fR.warning,error:fR.error,custom:fR.custom,message:fR.message,promise:fR.promise,dismiss:fR.dismiss,loading:fR.loading},{getHistory:()=>fR.toasts,getToasts:()=>fR.getActiveToasts()});uR(`[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}`);function gR({label:e,url:t,description:n,docsHref:r,className:i}){let[a,o]=(0,z.useState)(!1);async function s(){try{await Yr(t),o(!0),hR.success(`${e} copied`),setTimeout(()=>o(!1),2e3)}catch{hR.error(`Failed to copy`)}}return(0,B.jsxs)(`div`,{className:Jr(`space-y-2 rounded-xl border border-border bg-muted/40 p-3`,i),children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:e}),(0,B.jsxs)(`div`,{className:`relative`,children:[(0,B.jsx)(`code`,{className:`flex min-h-[40px] items-center break-all rounded-lg border border-border bg-background px-3 py-2 pr-11 font-mono text-12 leading-relaxed text-foreground`,children:t}),(0,B.jsxs)(Pi,{type:`button`,variant:`ghost`,size:`icon`,className:`absolute right-1.5 top-1.5 h-8 w-8 shrink-0`,onClick:()=>void s(),"aria-label":`Copy ${e}`,children:[a?(0,B.jsx)(li,{className:`h-3.5 w-3.5 text-success`}):(0,B.jsx)(gi,{className:`h-3.5 w-3.5`}),(0,B.jsxs)(`span`,{className:`sr-only`,children:[`Copy `,e]})]})]}),n?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:n}):null,r?(0,B.jsxs)(`a`,{href:r,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-primary hover:underline`,children:[`Learn more →`,(0,B.jsx)(vi,{className:`h-3 w-3`,"aria-hidden":`true`})]}):null]})}function _R(e){return e===`twitter`||e===`api-twitter`}function vR(e){return e?`${e}/api/v1/providers/callback`:null}function yR({slug:e}){let{data:t,isError:n,isLoading:r}=lR(),i=vR(t?.api_base_url),a=_R(e);return i?(0,B.jsxs)(`div`,{className:`space-y-2`,children:[(0,B.jsx)(gR,{label:a?`Twitter / X OAuth setup`:`NyxID callback URL`,url:i,description:a?`This integration requires an X app with OAuth 2.0 enabled in User authentication settings in X Developer Console. Configure the callback URL below as one of your app's redirect URIs.`:`Add this URL as an authorized redirect URI in your OAuth app's settings on the provider's developer console, or authorization will fail.`}),a?(0,B.jsxs)(`a`,{href:`https://developer.x.com/en/portal/dashboard`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-primary hover:underline`,children:[`Where do I get Client ID and Client Secret? Open Keys & Tokens in X Developer Console`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}):r?(0,B.jsx)(`p`,{className:`rounded-md border border-border bg-background/60 p-2 text-xs text-muted-foreground`,children:`Loading callback URL...`}):(0,B.jsx)(`p`,{className:`rounded-md border border-warning/30 bg-warning/10 p-2 text-xs text-warning`,children:n?`Couldn't load callback URL. Please retry. If this persists, contact support.`:`Callback URL not yet available. Please retry. If this persists, contact support.`})}function bR(e){let t=(e??`system`).toLowerCase();return t===`user`||t===`both`}async function xR(e,t,n,r,i,a,o,s,c){let l={service_slug:e,label:t};r&&(l.node_id=r),a&&(l.target_org_id=a);let u=i?.trim();if(u&&(l.endpoint_url=u),c)l.copy_oauth_client_from=c;else{let e=o?.trim(),t=s?.trim();e&&t&&(l.oauth_client_id=e,l.oauth_client_secret=t)}try{return await gb.post(`/keys`,l)}catch(e){throw e instanceof lb&&e.status>=400&&e.status<500&&(n.current=!1),e}}function SR(e){if(!(typeof window>`u`))try{fetch(`/api/v1/keys/${encodeURIComponent(e)}?only_if_pending=true`,{method:`DELETE`,credentials:`include`,keepalive:!0})}catch{}}function CR(e){if(!(typeof window>`u`))try{fetch(`/api/v1/cli-pairings/${encodeURIComponent(e)}/cancel`,{method:`POST`,credentials:`include`,keepalive:!0,headers:{"Content-Type":`application/json`},body:`{}`})}catch{}}function wR(e,t,n,r){if(!(typeof window>`u`))try{fetch(`/api/v1/cli-pairings/${encodeURIComponent(e)}/complete`,{method:`POST`,credentials:`include`,keepalive:!0,headers:{"Content-Type":`application/json`},body:JSON.stringify({ack:{acknowledged:!0,service_id:t,slug:n,label:r}})})}catch{}}async function TR(e){if(!e)return{kind:`unknown`};try{if((await gb.delete(`/keys/${encodeURIComponent(e)}?only_if_pending=true`)).deleted===!0)return{kind:`deleted`};try{let t=await gb.get(`/keys/${encodeURIComponent(e)}`);return t.status===`active`?{kind:`active`,key:t}:{kind:`unknown`}}catch{return{kind:`unknown`}}}catch{return{kind:`unknown`}}}async function ER(e,t,n,r,i){let a=i.current;if(a)try{await a}catch{}let o=t.current,s=n.current,c=r.current;t.current=null,n.current=!1;let l=s&&!c;if(o)try{let t=await gb.delete(`/keys/${encodeURIComponent(o)}?only_if_pending=true`);if(t.deleted===!0&&s)l=!0;else if(t.deleted===!1){try{let t=await gb.get(`/keys/${encodeURIComponent(o)}`);await gb.post(`/cli-pairings/${encodeURIComponent(e)}/complete`,{ack:{acknowledged:!0,service_id:t.id,slug:t.slug,label:t.label}})}catch{}return}else l=!1}catch{}if(l)try{await vP(e)}catch{}}function DR({providerId:e,slug:t,label:n,nodeId:r,targetOrgId:i,endpointUrl:a,pairingId:o,credentialMode:s,documentationUrl:c,scopeOverride:l,reconnectKeyId:u,baselineAuthorizedAt:d,onSuccess:f,onCancel:p}){let m=!!u,[h,g]=(0,z.useState)(!m&&bR(s)?`checking-credentials`:`starting`),[_,v]=(0,z.useState)(null),[y,b]=(0,z.useState)(null),[x,S]=(0,z.useState)(``),[C,w]=(0,z.useState)(``),[T,E]=(0,z.useState)(null),[ee,D]=(0,z.useState)([]),O=(0,z.useRef)(u??null),k=(0,z.useRef)(!1),A=(0,z.useRef)(!1),j=(0,z.useRef)(!1),M=(0,z.useRef)(null),N=(0,z.useRef)(!1);(0,z.useEffect)(()=>{function e(){if(N.current||m)return;let e=O.current;if(e){SR(e),wR(o,e,t,n);return}j.current&&CR(o)}return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),!N.current&&(m||ER(o,O,A,j,M))}},[o,t,n,m]);async function P(){if(m)return`uncertain`;let e=M.current;if(e)try{await e}catch{}let t=O.current;O.current=null;let n=await TR(t);if(n.kind===`active`)return A.current=!1,N.current=!0,g(`done`),f({kind:`ai-key`,service_id:n.key.id,slug:n.key.slug,label:n.key.label}),`active`;let r=!j.current,i=n.kind===`deleted`;return A.current&&(i||r)?(A.current=!1,await vP(o),`released`):(A.current=!1,`uncertain`)}async function F(){k.current=!0,await P()!==`active`&&p()}(0,z.useEffect)(()=>{m||bR(s)&&(g(`needs-credentials`),(async()=>{try{D(((await gb.get(`/keys`)).keys??[]).filter(e=>e.status===`active`&&e.oauth_client_id&&e.api_key_id&&e.catalog_service_slug===t).map(e=>({id:e.api_key_id,slug:e.slug,oauthClientId:e.oauth_client_id})))}catch{}})())},[s,t]);function I(){!T&&(!x.trim()||!C.trim())||(v(null),g(`starting`))}(0,z.useEffect)(()=>{if(h!==`starting`)return;let s=!1;return k.current=!1,(async()=>{try{O.current||(await _P(o),A.current=!0);let c;if(O.current)c={id:O.current,status:`pending_auth`};else{j.current=!0;let e=xR(t,n,j,r,a,i,x,C,T??void 0);M.current=e;try{c=await e}finally{M.current===e&&(M.current=null)}O.current=c.id}if(s)return;if(c.status===`active`){await ne(c.id);return}let u=new URLSearchParams({redirect_path:`/keys/${c.id}`,key_id:c.id});l!==void 0&&u.set(`scope_override`,l.join(`,`));let d=await gb.get(`/providers/${encodeURIComponent(e)}/connect/oauth?${u.toString()}`);if(s)return;if(!d.authorization_url)throw Error(`provider did not return an authorization_url`);if(b(d.authorization_url),!window.open(d.authorization_url,`_blank`,`noopener,noreferrer`)){g(`waiting`),v(`Browser blocked the popup. Use the button below to open the provider sign-in.`),await te(c.id);return}g(`waiting`),await te(c.id)}catch(e){if(s)return;g(`error`),v(AR(e)),P()}})(),()=>{s=!0}},[h]),(0,z.useEffect)(()=>()=>{k.current=!0},[]);async function te(e){let t=d??null;await oR({keyId:e,getKey:e=>gb.get(`/keys/${encodeURIComponent(e)}`),completeWithKey:ne,isCancelled:()=>k.current,...m?{isComplete:e=>e.status===`active`&&!!e.last_authorized_at&&e.last_authorized_at!==t}:{},onTerminalFailure:()=>{g(`error`),v(`Authorization didn't complete (it may have been canceled or denied on the provider page). Cancel and re-run to try again.`)},onTimeout:()=>{g(`error`),v(`We didn't see authorization complete within 5 minutes. If you canceled on the provider page or it's taking longer than expected, cancel and re-run.`)}})}async function ne(e){let t=await gb.get(`/keys/${encodeURIComponent(e)}`);k.current||(N.current=!0,g(`done`),f({kind:`ai-key`,service_id:t.id,slug:t.slug,label:t.label}))}if(h===`needs-credentials`){let e=ee.length>0,n=!!T;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:e?`OAuth app credentials`:`Paste your OAuth app credentials`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`This provider expects you to register your own OAuth app and supply the resulting Client ID and Client Secret.`}),c?(0,B.jsxs)(`a`,{href:c,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1 text-xs text-muted-foreground underline-offset-2 hover:underline`,children:[`How to create an OAuth app`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}),(0,B.jsx)(yR,{slug:t}),e?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(Vi,{className:`text-12 font-medium`,children:`Use credentials from an existing connection`}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[ee.map(e=>(0,B.jsxs)(`button`,{type:`button`,onClick:()=>{E(e.id),S(``),w(``)},className:`flex items-center gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors ${T===e.id?`border-primary bg-primary/5`:`border-border hover:border-primary/50`}`,children:[(0,B.jsx)(`span`,{className:`flex-1 truncate`,children:e.slug}),(0,B.jsx)(`span`,{className:`shrink-0 text-xs text-muted-foreground`,children:e.oauthClientId})]},e.id)),(0,B.jsx)(`button`,{type:`button`,onClick:()=>{E(null)},className:`flex items-center gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors ${T?`border-border hover:border-primary/50`:`border-primary bg-primary/5`}`,children:`Enter new credentials`})]})]}):null,n?null:(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-aikey-oauth-client-id`,children:`Client ID`}),(0,B.jsx)(IM,{id:`pair-aikey-oauth-client-id`,value:x,onChange:e=>{S(e.target.value)},autoFocus:!0,autoComplete:`off`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-aikey-oauth-client-secret`,children:`Client Secret`}),(0,B.jsx)(IM,{id:`pair-aikey-oauth-client-secret`,type:`password`,value:C,onChange:e=>{w(e.target.value)},autoComplete:`off`})]})]}),_?(0,B.jsx)(kR,{message:_}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:I,disabled:!n&&(!x.trim()||!C.trim()),children:n?`Continue with existing credentials`:`Save and continue`}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void F(),children:`Cancel`})]})}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:`Complete sign-in on the provider`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`We opened a new tab where you'll authorize NyxID. When it completes, come back — this page will finish automatically.`})]}),h===`checking-credentials`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Checking provider credentials...`]}):h===`starting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Creating placeholder service...`]}):h===`waiting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Waiting for provider authorization...`]}):null,y&&h===`waiting`?(0,B.jsxs)(`a`,{href:y,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center justify-center gap-2 rounded-lg border bg-muted/40 px-3 py-2 text-12 hover:bg-muted`,children:[`Reopen provider sign-in`,(0,B.jsx)(vi,{className:`h-4 w-4`})]}):null,_?(0,B.jsx)(kR,{message:_}):null,h===`done`?null:(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void F(),children:`Cancel`})]})}function OR({providerId:e,slug:t,label:n,nodeId:r,targetOrgId:i,endpointUrl:a,pairingId:o,scopeOverride:s,onSuccess:c,onCancel:l}){let[u,d]=(0,z.useState)(null),[f,p]=(0,z.useState)(null),[m,h]=(0,z.useState)(`starting`),[g,_]=(0,z.useState)(null),[v,y]=(0,z.useState)(!1),[b,x]=(0,z.useState)(0),S=(0,z.useRef)(0),C=(0,z.useRef)(null),w=(0,z.useRef)(!1),T=(0,z.useRef)(!1),E=(0,z.useRef)(!1),ee=(0,z.useRef)(null),D=(0,z.useRef)(!1);async function O(){let e=ee.current;if(e)try{await e}catch{}let t=C.current;C.current=null;let n=await TR(t);if(n.kind===`active`)return T.current=!1,D.current=!0,h(`done`),c({kind:`ai-key`,service_id:n.key.id,slug:n.key.slug,label:n.key.label}),`active`;let r=!E.current,i=n.kind===`deleted`;return T.current&&(i||r)?(T.current=!1,await vP(o),`released`):(T.current=!1,`uncertain`)}async function k(){w.current=!0,S.current+=1,await O()!==`active`&&l()}(0,z.useEffect)(()=>{function e(){if(D.current)return;let e=C.current;if(e){SR(e),wR(o,e,t,n);return}E.current&&CR(o)}return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),!D.current&&ER(o,C,T,E,ee)}},[o,t,n]),(0,z.useEffect)(()=>(w.current=!1,j(),()=>{w.current=!0,S.current+=1}),[]),(0,z.useEffect)(()=>{if(m!==`waiting`)return;let e=window.setInterval(()=>{x(e=>e>0?e-1:0)},1e3);return()=>{window.clearInterval(e)}},[m]);function A(e){let t=Math.floor(e/60),n=e%60;return`${String(t)}:${String(n).padStart(2,`0`)}`}async function j(){let l=++S.current;h(`starting`),_(null);try{let u=C.current;if(!u){await _P(o),T.current=!0,E.current=!0;let e=xR(t,n,E,r,a,i);ee.current=e;let s;try{s=await e}finally{ee.current===e&&(ee.current=null)}if(u=s.id,C.current=u,l!==S.current)return;if(s.status===`active`){let e=await gb.get(`/keys/${encodeURIComponent(u)}`);if(l!==S.current)return;D.current=!0,h(`done`),c({kind:`ai-key`,service_id:e.id,slug:e.slug,label:e.label});return}}let f=new URLSearchParams;u&&f.set(`key_id`,u),s!==void 0&&f.set(`scope_override`,s.join(`,`));let m=f.toString(),g=await gb.post(`/providers/${encodeURIComponent(e)}/connect/device-code/initiate${m?`?${m}`:``}`,{});if(l!==S.current)return;d(g.user_code),p(g.verification_uri);let v=typeof window<`u`?Number(new URLSearchParams(window.location.search).get(`expires_in_override`)):NaN,y=Number.isFinite(v)&&v>0?v:Number(g.expires_in)>0?Number(g.expires_in):900;x(y),h(`waiting`);let b=Number(g.interval)||5,k=`/providers/${encodeURIComponent(e)}/connect/device-code/poll`,A=Date.now()+y*1e3;for(;Date.now(){y(!1)},2e3)}catch{}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:`Authorize via device code`}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Open the verification URL, enter the code, and complete sign-in on the provider. This page will finish automatically.`})]}),m===`starting`?(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-12 text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-4 w-4 animate-spin`}),`Requesting device code...`]}):m===`waiting`&&u&&f?(0,B.jsxs)(`div`,{className:`flex flex-col gap-3 rounded-lg border bg-muted/30 p-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,B.jsx)(`span`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Code`}),b>0?(0,B.jsxs)(`span`,{className:`text-xs tabular-nums text-muted-foreground`,children:[`Expires in `,A(b)]}):null]}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,B.jsx)(`code`,{className:`rounded bg-background px-3 py-1.5 font-mono text-lg`,children:u}),(0,B.jsxs)(Pi,{variant:`outline`,onClick:()=>void M(),children:[(0,B.jsx)(Ni,{children:(0,B.jsx)(gi,{className:`h-3.5 w-3.5`})}),v?`Copied`:`Copy`]})]})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`span`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Visit`}),(0,B.jsxs)(`a`,{href:f,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-1.5 text-12 underline-offset-2 hover:underline`,children:[f,(0,B.jsx)(vi,{className:`h-3.5 w-3.5`})]})]}),(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground`,children:[(0,B.jsx)(wi,{className:`h-3 w-3 animate-spin`}),`Waiting for authorization...`]})]}):m===`expired`?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsx)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:`The device code expired before authorization completed.`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void j(),children:`Request a new code`})]}):null,g?(0,B.jsx)(kR,{message:g}):null,m===`done`?null:(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void k(),children:`Cancel`})]})}function kR({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}function AR(e){return e instanceof lb||e instanceof Error?e.message:`Something went wrong. Please try again.`}function jR(e){return new Promise(t=>{window.setTimeout(t,e)})}function MR(e){let t=(e.provider_type??``).toLowerCase();return(e.service_type??`http`)===`ssh`?`ssh`:t===`oauth2`?`oauth`:t===`device_code`?`device-code`:e.requires_credential===!1?`no-auth`:Array.isArray(e.token_exchange_credential_fields)&&e.token_exchange_credential_fields.length>0?`token-exchange`:e.requires_gateway_url?`gateway-url`:`paste-key`}function NR(e,t){switch(e){case`no-auth`:return`1-click connect`;case`gateway-url`:return`URL + API key`;case`token-exchange`:return`${(t.token_exchange_credential_fields??[]).length} fields`;case`oauth`:return`OAuth sign-in`;case`device-code`:return`device code`;case`ssh`:return`SSH cert`;case`paste-key`:return`paste API key`}}var PR={oauth:`OAuth`,"device-code":`Device code`,ssh:`SSH`};function FR(e,t){if(!t)return 0;let n=e.toLowerCase(),r=t.toLowerCase(),i=n.indexOf(r);if(i>=0)return i;let a=0,o=0,s=100,c=0;for(;a{let e=await gb.get(`/catalog?include_all=true`);return e.entries??e.services??[]}}),o=r??[],s=t.trim(),c=s?o.map(e=>{let t=FR(e.slug,s),n=FR(e.name??``,s),r=t===null?n:n===null?t:Math.min(t,n);return r===null?null:{entry:e,score:r}}).filter(e=>e!==null).sort((e,t)=>e.score-t.score).map(e=>e.entry):o;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(`label`,{htmlFor:`catalog-search`,className:`text-xs font-medium uppercase tracking-wide text-muted-foreground`,children:`Search`}),(0,B.jsx)(IM,{id:`catalog-search`,type:`search`,placeholder:`search services…`,autoComplete:`off`,spellCheck:!1,value:t,onChange:e=>{n(e.target.value)}})]}),(0,B.jsx)(LR,{children:`Simple setup`}),i?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:`Loading catalog…`}):a?(0,B.jsx)(`p`,{className:`text-12 text-destructive`,children:a instanceof lb?`Couldn't load the catalog: ${a.message} (${String(a.status)})`:`Couldn't load the catalog. Check the CLI logs for details.`}):c.length===0?(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:s?`No services match your search.`:`Catalog is empty.`}):(0,B.jsx)(`div`,{className:`max-h-[420px] overflow-y-auto overscroll-contain pr-1`,role:`list`,children:(0,B.jsx)(`div`,{className:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:c.map(t=>(0,B.jsx)(RR,{entry:t,onClick:()=>{e(t.slug)}},t.slug))})}),(0,B.jsx)(LR,{children:`Advanced`}),(0,B.jsx)(`div`,{className:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:(0,B.jsx)(zR,{onClick:()=>{e(`__custom__`)}})})]})}function LR({children:e}){return(0,B.jsx)(`div`,{className:`text-xs font-medium uppercase tracking-wide text-muted-foreground`,children:e})}function RR({entry:e,onClick:t}){let n=MR(e),r=PR[n];return(0,B.jsxs)(`button`,{type:`button`,onClick:t,role:`listitem`,className:`group relative flex min-h-[132px] flex-col items-start gap-1 rounded-xl border border-border/50 bg-card/60 p-4 text-left transition-colors duration-300 hover:border-hairline-strong hover:bg-card focus-visible:outline-none`,children:[r?(0,B.jsx)(`span`,{className:`absolute right-3 top-3 rounded-full border border-border bg-muted/60 px-2 py-0.5 text-10 uppercase tracking-wide text-muted-foreground`,children:r}):null,(0,B.jsxs)(`div`,{className:`flex w-full items-center gap-2`,children:[(0,B.jsx)(AM,{slug:e.slug,size:`sm`}),(0,B.jsx)(`span`,{className:`text-13 font-semibold text-foreground`,children:e.name||e.slug})]}),e.description?(0,B.jsx)(`span`,{className:`line-clamp-2 text-xs text-muted-foreground`,children:e.description}):null,(0,B.jsx)(`span`,{className:`mt-auto text-11 text-text-tertiary`,children:NR(n,e)})]})}function zR({onClick:e}){return(0,B.jsxs)(`button`,{type:`button`,onClick:e,className:`flex min-h-[132px] flex-col items-start gap-1 rounded-xl border border-dashed border-border/50 bg-transparent p-4 text-left transition-colors duration-300 hover:border-hairline-strong hover:bg-card/40 focus-visible:outline-none`,children:[(0,B.jsx)(`span`,{className:`text-13 font-semibold text-foreground`,children:`Custom / self-hosted…`}),(0,B.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`For anything that isn't in the catalog above — paste your own endpoint URL + credential.`})]})}function BR(e){let t=(e.provider_type??``).toLowerCase();return t===`oauth2`?`oauth`:t===`device_code`?`device-code`:e.requires_credential===!1?`no-auth`:Array.isArray(e.token_exchange_credential_fields)&&e.token_exchange_credential_fields.length>0?`token-exchange`:e.requires_credential?`api-key`:`other`}function VR({prefill:e,pairingId:t,onSuccess:n,onSlugPicked:r}){let[i,a]=(0,z.useState)(e.custom?`__custom__`:e.slug??``),[o,s]=(0,z.useState)(e.org_id??null),c=i.trim(),l=(0,z.useRef)(c?null:``);(0,z.useEffect)(()=>{l.current!==c&&(l.current=c,r?.(c))},[c,r]);let{data:u,isLoading:d,error:f}=lt({queryKey:[`cli-pair`,`catalog`,c],queryFn:async()=>gb.get(`/catalog/${encodeURIComponent(c)}`),enabled:!!c&&c!==`__custom__`}),p=f?f instanceof lb?f.message:`Couldn't load catalog entry "${c}".`:null;if(e.reconnect_key_id)return(0,B.jsx)(HR,{keyId:e.reconnect_key_id,initialScopeOverride:e.scope_override??null,pairingId:t,onSuccess:n});let m=!c&&!p,h=c===`__custom__`,g=m?`Add an AI service`:h?`Custom / self-hosted service`:`Connect service`,_=m?`Pick a service to connect. Simple-bearer APIs (OpenAI, Anthropic, Gemini) land in the guided form. Anything else — self-hosted, OAuth, device code, custom endpoint — goes to the power-user form.`:h?`For services not in the catalog — paste your own endpoint URL and credential.`:`Your CLI wants to add ${c||`a service`} to NyxID. Confirm the details here.`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:g}),(0,B.jsx)(`p`,{className:`text-12 text-muted-foreground`,children:_})]}),(0,B.jsx)(UR,{value:o,onChange:s}),m?(0,B.jsx)(IR,{onSelect:a}):h?(0,B.jsx)(JR,{prefill:e,targetOrgId:o,pairingId:t,onSuccess:n,onBack:()=>{a(``)}}):d?(0,B.jsx)(ZL,{className:`h-24 w-full`}):p?(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsx)(ZR,{message:p}),(0,B.jsx)(IR,{onSelect:a})]}):u?(0,B.jsx)(YR,{entry:u,prefill:e,targetOrgId:o,pairingId:t,onSuccess:n}):null]})}function HR({keyId:e,initialScopeOverride:t,pairingId:n,onSuccess:r}){let{data:i,isLoading:a,error:o}=lt({queryKey:[`cli-pair`,`manage-scopes`,`key`,e],queryFn:()=>gb.get(`/keys/${encodeURIComponent(e)}`)}),s=i?.catalog_service_slug??i?.slug??``,{data:c,isLoading:l,error:u}=lt({queryKey:[`cli-pair`,`manage-scopes`,`catalog`,s],queryFn:()=>gb.get(`/catalog/${encodeURIComponent(s)}`),enabled:!!s}),d=i?.granted_scopes??[],f=c?.default_scopes??[],p=t&&t.length>0?t:null,m=p??(d.length>0?d:f),[h,g]=(0,z.useState)(null),_=eR(h??m,c?.scope_catalog??[]),v=h!==null||p!==null||d.length>0||c?.scope_catalog?.some(e=>e.required)?_:void 0,y=g,[b,x]=(0,z.useState)(!1),S=(()=>{let e=o??u;return e?e instanceof lb?e.message:`Couldn't load this connection.`:null})();if(a||s&&l)return(0,B.jsx)(ZL,{className:`h-24 w-full`});if(S)return(0,B.jsx)(ZR,{message:S});if(!i||!c)return(0,B.jsx)(ZR,{message:`Connection not found.`});if((c.provider_type??``).toLowerCase()!==`oauth2`||!c.provider_config_id||c.supports_oauth_scopes===!1)return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Manage permissions`}),(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[c.name,` doesn't support managing scopes here — its permissions are fixed by the provider.`]})]});let C=c.scope_removal===`unsupported`?d:[];return b?(0,B.jsx)(DR,{providerId:c.provider_config_id,slug:i.slug,label:i.label,pairingId:n,credentialMode:c.credential_mode,documentationUrl:c.documentation_url,scopeOverride:v,reconnectKeyId:e,baselineAuthorizedAt:i.last_authorized_at??null,onSuccess:r,onCancel:()=>{x(!1)}}):(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Manage permissions`}),(0,B.jsxs)(`p`,{className:`text-12 text-muted-foreground`,children:[`Adjust what `,i.label,` can do, then re-authorize at the provider. Your CLI is waiting for you to finish here.`]})]}),(0,B.jsxs)(`div`,{className:`flex items-start gap-3 rounded-lg border bg-muted/30 p-3`,children:[c.icon_url?(0,B.jsx)(`img`,{src:c.icon_url,alt:``,className:`h-8 w-8 rounded`,loading:`lazy`}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:c.name}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i.slug})]})]}),(0,B.jsx)(rR,{catalog:c.scope_catalog??[],defaultScopes:c.default_scopes??[],value:_,onChange:y,lockedScopes:C,grantedScopes:d,providerName:c.name,idPrefix:`pair-manage-scope`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>x(!0),children:`Re-authorize with these permissions`})]})}function UR({value:e,onChange:t}){let{data:n}=gF();return(n??[]).some(e=>e.your_role===`admin`)?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/30 px-3 py-2`,children:[(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex items-center gap-2 text-xs font-medium text-muted-foreground`,children:[(0,B.jsx)(si,{className:`h-3.5 w-3.5`}),`Owner`]}),(0,B.jsx)(`div`,{className:`w-[220px]`,children:(0,B.jsx)($L,{value:e,onChange:t,label:`Owner`})})]}),(0,B.jsx)(`p`,{className:`mt-1 text-11 text-muted-foreground`,children:`Org-owned services are shared with every admin of that organization and can be proxied by its members.`})]}):null}function WR(e){switch(e){case`ifttt_webhook`:return``;case`header`:return`X-API-Key`;case`query`:return`key`;case`path`:return`bot`;case`body`:return`app_secret`;default:return`Authorization`}}function GR(e){switch(e){case`bearer`:case`header`:case`query`:case`path`:case`basic`:case`body`:case`ifttt_webhook`:case`bot_bearer`:case`none`:return e;default:return`bearer`}}function KR(e){return e===`header`||e===`query`||e===`path`||e===`body`}function qR(){return(0,B.jsx)(`span`,{"aria-hidden":`true`,className:`text-destructive ml-0.5`,children:`*`})}function JR({prefill:e,targetOrgId:t,pairingId:n,onSuccess:r,onBack:i}){let[a,o]=(0,z.useState)(e.label??``),[s,c]=(0,z.useState)(e.endpoint_url??``),[l,u]=(0,z.useState)(``),[d,f]=(0,z.useState)(GR(e.auth_method)),[p,m]=(0,z.useState)(e.auth_key_name??WR(GR(e.auth_method))),[h,g]=(0,z.useState)(e.custom_slug??``),[_,v]=(0,z.useState)(!1),[y,b]=(0,z.useState)(null),x=e.via_node?.trim()??``,S=a.trim(),C=s.trim(),w=l.trim(),T=d!==`none`,E=KR(d),ee=_||!S||!C||T&&!w,D=d===`bot_bearer`?`Bot token`:d===`basic`?`user:pass`:d===`body`?`${p.trim()||WR(d)} value`:`API key / credential`;async function O(){v(!0),b(null);try{let e={label:S,endpoint_url:C,auth_method:d};T&&(e.credential=w),E&&(e.auth_key_name=p.trim()||WR(d));let i=h.trim();i&&(e.slug=i),x&&(e.node_id=x),t&&(e.target_org_id=t),await _P(n);let a=await yP(n,()=>gb.post(`/keys`,e));r({kind:`ai-key`,service_id:a.id,slug:a.slug,label:a.label})}catch(e){let t=(e instanceof lb?e.message:null)??e?.message;b(t&&t.length>0?t:`Couldn't connect this service. Please try again.`)}finally{v(!1)}}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-label`,children:`Label`}),(0,B.jsx)(qR,{})]}),(0,B.jsx)(IM,{id:`pair-custom-label`,value:a,onChange:e=>{o(e.target.value)},placeholder:`e.g. My Self-hosted OpenAI Proxy`,autoFocus:!0,"aria-required":`true`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Shown everywhere in the CLI and web UI.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-endpoint`,children:`Endpoint URL`}),(0,B.jsx)(qR,{})]}),(0,B.jsx)(IM,{id:`pair-custom-endpoint`,value:s,onChange:e=>{c(e.target.value)},placeholder:`https://api.example.com`,"aria-required":`true`}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`The base URL NyxID proxies requests to.`})]}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-auth-method`,children:`Auth method`}),(0,B.jsx)(qR,{})]}),(0,B.jsxs)(`select`,{id:`pair-custom-auth-method`,value:d,onChange:e=>{let t=e.target.value;f(t),(!p.trim()||[`Authorization`,`X-API-Key`,`key`,`bot`,`app_secret`].includes(p.trim()))&&m(WR(t))},className:`flex h-10 w-full rounded-lg border border-input bg-transparent px-[14px] py-2 text-13 text-foreground focus-visible:outline-none focus-visible:border-input-focus`,"aria-required":`true`,children:[(0,B.jsx)(`option`,{value:`bearer`,children:`bearer (Authorization: Bearer …)`}),(0,B.jsx)(`option`,{value:`bot_bearer`,children:`bot_bearer (Authorization: Bot …)`}),(0,B.jsx)(`option`,{value:`header`,children:`header (custom header)`}),(0,B.jsx)(`option`,{value:`query`,children:`query (?key=…)`}),(0,B.jsx)(`option`,{value:`path`,children:`path (path-prefix injection)`}),(0,B.jsx)(`option`,{value:`ifttt_webhook`,children:`IFTTT Webhooks (raw key)`}),(0,B.jsx)(`option`,{value:`basic`,children:`basic (Authorization: Basic …)`}),(0,B.jsx)(`option`,{value:`body`,children:`body (JSON-body field injection)`}),(0,B.jsx)(`option`,{value:`none`,children:`none (no auth injection)`})]}),(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`How NyxID attaches the credential to outgoing requests.`})]}),T?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsxs)(`div`,{className:`flex items-center`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-credential`,children:D}),(0,B.jsx)(qR,{})]}),(0,B.jsx)(IM,{id:`pair-custom-credential`,type:`password`,value:l,onChange:e=>{u(e.target.value)},placeholder:d===`basic`?`user:pass`:`sk-...`,autoFocus:!!e.custom,"aria-required":`true`}),(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`Pasted once, encrypted at rest.`,d===`basic`?` Format: user:pass.`:``]})]}):null,E?(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-auth-key-name`,children:d===`header`?`Header name`:d===`query`?`Query parameter name`:d===`path`?`Path prefix segment`:`Body field name`}),(0,B.jsx)(IM,{id:`pair-custom-auth-key-name`,value:p,onChange:e=>{m(e.target.value)},placeholder:WR(d)})]}):null,x?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 px-3 py-2`,children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:`Routed via node`}),(0,B.jsx)(`code`,{className:`font-mono text-11 text-muted-foreground`,children:x}),(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground mt-1`,children:`Credential will be encrypted and pushed to this node over the existing WebSocket channel. NyxID never logs it.`})]}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:`pair-custom-slug`,children:`Custom slug (optional)`}),(0,B.jsx)(IM,{id:`pair-custom-slug`,value:h,onChange:e=>{g(e.target.value)},placeholder:`auto-generated from label`}),(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`URL segment at `,(0,B.jsx)(`code`,{children:`/proxy/s//…`}),`. Leave blank to let NyxID derive it from the label.`]})]})]}),y?(0,B.jsx)(ZR,{message:y}):null,(0,B.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,B.jsx)(Pi,{variant:`outline`,onClick:i,disabled:_,children:`← Back`}),(0,B.jsx)(Pi,{variant:`primary`,onClick:()=>void O(),disabled:ee,children:_?`Connecting…`:`Connect service`})]})]})}function YR({entry:e,prefill:t,targetOrgId:n,pairingId:r,onSuccess:i}){let[a,o]=(0,z.useState)(!0),s=!!(e.platform_key?.available&&!t.via_node&&a),c=s?`no-auth`:BR(e),[l,u]=(0,z.useState)(t.label??e.name),[d,f]=(0,z.useState)(``),[p,m]=(0,z.useState)(t.endpoint_url??``),[h,g]=(0,z.useState)({}),[_,v]=(0,z.useState)(e.default_scopes??[]),y=eR(_,e.scope_catalog??[]),[b,x]=(0,z.useState)(!1),[S,C]=(0,z.useState)(null),w=t.via_node?.trim()??``,[T,E]=(0,z.useState)(!1);async function ee(){x(!0),C(null);try{let t={service_slug:e.slug,label:l};if(s&&(t.use_platform_key=!0),c===`token-exchange`&&!w){let n=e.token_exchange_credential_fields??[],r={};for(let e of n){let t=h[e.name]?.trim();if(!t){C(`${e.label||e.name} is required.`),x(!1);return}r[e.name]=t}t.credential=JSON.stringify(r)}else c===`api-key`&&e.requires_credential&&!w&&(t.credential=d);!s&&(e.requires_gateway_url||p)&&(t.endpoint_url=p),w&&(t.node_id=w),n&&(t.target_org_id=n),await _P(r);let a=await yP(r,()=>gb.post(`/keys`,t));i({kind:`ai-key`,service_id:a.id,slug:a.slug,label:a.label})}catch(e){let t=(e instanceof lb?e.message:null)??e?.message;C(t&&t.length>0?t:`Couldn't create the service. Please try again.`)}finally{x(!1)}}async function D(e){try{await gb.post(`/cli-pairings/${encodeURIComponent(r)}/cancel`,{})}catch{}if(e){if(window.__WIZARD_BOOTSTRAP__?.context===`local`){alert(`This auth shape isn't supported in the CLI wizard. Open your NyxID dashboard and complete setup on the Keys page (tab: External Services). You can close this tab now.`);return}window.location.assign(e)}else window.history.back()}if(e.service_type===`ssh`)return(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[e.name,` is an SSH service. Use`,` `,(0,B.jsx)(`code`,{children:`nyxid service add-ssh`}),` from your CLI instead (certificate-based auth, not a credential binding).`]}),(0,B.jsx)(Pi,{variant:`outline`,onClick:()=>void D(null),children:`Go Back`})]});if(c===`other`||(c===`oauth`||c===`device-code`)&&!e.provider_config_id){let t=`/keys?tab=services&slug=${encodeURIComponent(e.slug)}`;return(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsxs)(`p`,{className:`rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-12`,children:[e.name,` uses `,(0,B.jsx)(`code`,{children:e.auth_method}),` auth, which isn't supported via remote pairing. Complete setup on the main Keys page. Your CLI will receive a cancel and print a "finish in browser" hint.`]}),(0,B.jsxs)(Pi,{variant:`primary`,onClick:()=>void D(t),className:`justify-center gap-2`,children:[`Open Keys page`,(0,B.jsx)(vi,{className:`h-4 w-4`})]})]})}let O=p.trim()||t.endpoint_url,k=e.supports_oauth_scopes!==!1&&(c===`oauth`||c===`device-code`&&e.device_code_format!==`openai`),A=k?y:void 0;if(T&&c===`oauth`&&e.provider_config_id)return(0,B.jsx)(DR,{providerId:e.provider_config_id,slug:e.slug,label:l,nodeId:t.via_node,targetOrgId:n,endpointUrl:O,pairingId:r,credentialMode:e.credential_mode,documentationUrl:e.documentation_url,scopeOverride:A,onSuccess:i,onCancel:()=>{E(!1)}});if(T&&c===`device-code`&&e.provider_config_id)return(0,B.jsx)(OR,{providerId:e.provider_config_id,slug:e.slug,label:l,nodeId:t.via_node,targetOrgId:n,endpointUrl:O,pairingId:r,documentationUrl:e.documentation_url,scopeOverride:A,onSuccess:i,onCancel:()=>{E(!1)}});let j=c===`api-key`&&e.requires_credential,M=e.slug===`api-supabase`,N=b?`Creating...`:w?`Connect via node`:c===`oauth`?`Continue with provider sign-in`:c===`device-code`?`Get device code`:c===`no-auth`?`Connect`:`Create Service`,P=c===`token-exchange`?(e.token_exchange_credential_fields??[]).every(e=>(h[e.name]??``).trim().length>0):!0,F=b||!l.trim()||j&&!w&&!d.trim()||!s&&e.requires_gateway_url&&!p.trim()||!w&&!P;function I(){c===`oauth`||c===`device-code`?E(!0):ee()}return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsxs)(`div`,{className:`flex items-start gap-3 rounded-lg border bg-muted/30 p-3`,children:[e.icon_url?(0,B.jsx)(`img`,{src:e.icon_url,alt:``,className:`h-8 w-8 rounded`,loading:`lazy`}):null,(0,B.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,B.jsx)(`h3`,{className:`font-medium`,children:e.name}),e.description?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:e.description}):null,(0,B.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`Auth: `,(0,B.jsx)(`code`,{children:e.auth_method})]})]})]}),e.platform_key?.available&&!w&&(0,B.jsx)(XL,{value:s,onChange:o,platformPrice:e.platform_key.pricing,byokPrice:e.byok_pricing,legacyBillable:e.billing?.platform_billable,resaleBillable:e.billing?.resale_billable,disabled:b}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,B.jsx)(XR,{label:`Label`,htmlFor:`pair-aikey-label`,children:(0,B.jsx)(IM,{id:`pair-aikey-label`,value:l,onChange:e=>{u(e.target.value)},autoFocus:!0})}),!s&&e.requires_gateway_url?(0,B.jsx)(XR,{label:M?`Supabase Project URL`:`Instance URL`,htmlFor:`pair-aikey-url`,children:(0,B.jsx)(IM,{id:`pair-aikey-url`,value:p,onChange:e=>{m(e.target.value)},placeholder:M?`https://project-ref.supabase.co`:`https://your-instance.example.com`})}):null,j&&!w?(0,B.jsxs)(XR,{label:M?`Supabase API key`:`API key`,htmlFor:`pair-aikey-credential`,children:[(0,B.jsx)(IM,{id:`pair-aikey-credential`,type:`password`,autoComplete:`off`,value:d,onChange:e=>{f(e.target.value)},placeholder:M?`sb_secret_... or sb_publishable_...`:`sk-...`}),e.api_key_url?(0,B.jsxs)(`a`,{href:e.api_key_url,target:`_blank`,rel:`noopener noreferrer`,className:`mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground underline-offset-2 hover:underline`,children:[`Get an API key`,(0,B.jsx)(vi,{className:`h-3 w-3`})]}):null]}):null,c===`token-exchange`&&!w?(e.token_exchange_credential_fields??[]).map(e=>(0,B.jsx)(XR,{label:e.label||e.name,htmlFor:`pair-aikey-tx-${e.name}`,children:(0,B.jsx)(IM,{id:`pair-aikey-tx-${e.name}`,type:e.secret?`password`:`text`,autoComplete:`off`,value:h[e.name]??``,onChange:t=>{let n=t.target.value;g(t=>({...t,[e.name]:n}))},placeholder:e.placeholder??``})},e.name)):null,k?(0,B.jsx)(rR,{catalog:e.scope_catalog??[],defaultScopes:e.default_scopes??[],value:y,onChange:v,customPlaceholder:c===`oauth`?`e.g. media.write`:`e.g. repo,read:org`,idPrefix:`pair-aikey-scope`}):c===`device-code`?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`This provider does not accept additional scopes — they are fixed by the upstream client registration.`}):null,w?(0,B.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 px-3 py-2`,children:[(0,B.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:`Routed via node`}),(0,B.jsx)(`code`,{className:`font-mono text-11 text-muted-foreground`,children:w}),(0,B.jsx)(`p`,{className:`text-11 text-muted-foreground mt-1`,children:`Credential will be configured on the node agent. NyxID never sees or stores it.`})]}):null,c===`no-auth`?(0,B.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`This service doesn't need a credential. Click Connect to add it to your services.`}):null]}),S?(0,B.jsx)(ZR,{message:S}):null,(0,B.jsx)(Pi,{variant:`primary`,onClick:I,disabled:F,children:N})]})}function XR({label:e,htmlFor:t,children:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,B.jsx)(Vi,{htmlFor:t,children:e}),n]})}function ZR({message:e}){return(0,B.jsx)(`p`,{className:`rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-12 text-destructive`,children:e})}var QR=!1,$R=null;function ez(e){if($R=e,QR)return;QR=!0;let t=window.fetch.bind(window);window.fetch=async(e,n)=>{let r=lz(e,n),i=new URL(r.url,window.location.origin);if(i.origin!==window.location.origin)return t(r);if(i.pathname.startsWith(`/api/v1/cli-pairings/`))return i.pathname.endsWith(`/cancel`)?t(`/api/proxy/cancel-unload`,{method:`POST`,headers:dz({"content-type":`application/json`}),body:`{}`,keepalive:n?.keepalive??!1}):new Response(JSON.stringify({ok:!0}),{status:200,headers:{"content-type":`application/json`}});if(r.method===`DELETE`&&/^\/api\/v1\/keys\/[^/]+$/.test(i.pathname)&&i.searchParams.get(`only_if_pending`)===`true`){let e=i.pathname.split(`/`).pop()??``;return t(`/api/proxy/abandon-placeholder`,{method:`POST`,headers:dz({"content-type":`application/json`}),body:JSON.stringify({key_id:e})})}if(i.pathname.startsWith(`/api/v1/`)){let e=new URL(i.toString());e.pathname=`/api/proxy${i.pathname}`;let a=dz(uz(r,n)),o=await t(e.toString(),{method:r.method,headers:a,body:await fz(r),credentials:r.credentials,signal:r.signal});return rz(o),o}return t(r)}}var tz=`nyxid-wizard-upstream-error`;function nz(e){let t=t=>{if(!(t instanceof CustomEvent))return;let n=t.detail?.kind;(n===`timeout`||n===`unreachable`)&&e(n)};return window.addEventListener(tz,t),()=>{window.removeEventListener(tz,t)}}function rz(e){if(e.ok)return;let t=e.headers.get(`content-type`);!t||!t.toLowerCase().includes(`application/json`)||e.clone().json().then(e=>{if(!e||typeof e!=`object`)return;let t=e.error;t===`upstream_timeout`?window.dispatchEvent(new CustomEvent(tz,{detail:{kind:`timeout`}})):t===`upstream_unreachable`&&window.dispatchEvent(new CustomEvent(tz,{detail:{kind:`unreachable`}}))}).catch(()=>{})}async function iz(e){let t=await fetch(`/api/proxy/complete`,{method:`POST`,headers:dz({"content-type":`application/json`}),body:JSON.stringify(e)});if(!t.ok)throw Error(`/api/proxy/complete failed: ${String(t.status)} ${t.statusText}`)}async function az(){try{await fetch(`/api/proxy/cancel`,{method:`POST`,headers:dz({"content-type":`application/json`}),body:`{}`})}catch{}}var oz=1200,sz=3;function cz(e){let t=0,n=!1,r=window.setInterval(()=>{fetch(`/api/proxy/heartbeat`,{method:`POST`,headers:dz({"content-type":`application/json`}),body:`{}`}).then(r=>{if(!r.ok)throw Error(`heartbeat ${String(r.status)}`);t=0,n&&(n=!1,e?.onReconnect?.())}).catch(()=>{t+=1,t>=sz&&!n&&(n=!0,e?.onDisconnect?.())})},oz);return()=>{window.clearInterval(r)}}function lz(e,t){return e instanceof Request?e:new Request(e,t)}function uz(e,t){let n={};return e.headers.forEach((e,t)=>{n[t]=e}),t?.headers&&new Headers(t.headers).forEach((e,t)=>{n[t]=e}),n}function dz(e){return $R?{...e,"x-wizard-csrf":$R.csrf}:e}async function fz(e){if(e.method===`GET`||e.method===`HEAD`)return null;try{let t=await e.clone().text();return t.length>0?t:null}catch{return null}}function pz({state:e,context:t,pairingStatus:n}){return(0,B.jsxs)(`div`,{role:`alert`,"aria-live":`polite`,className:`mb-4 flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-12 text-foreground`,children:[(0,B.jsx)(e===`reconnecting`?wi:Ai,{className:`mt-0.5 h-4 w-4 shrink-0 text-destructive `+(e===`reconnecting`?`animate-spin`:``),"aria-hidden":!0}),(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`p`,{className:`font-medium`,children:e===`reconnecting`?`Reconnecting…`:t===`local`?`Connection to CLI interrupted`:n===`cancelled`?`CLI cancelled this pairing`:n===`expired`?`Pairing expired`:`Pairing went stale`}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:e===`reconnecting`?`Retrying the last check…`:t===`local`?`The nyxid CLI missed several heartbeat checks. Keep this tab open; the wizard will continue if the connection recovers. If this message persists, re-run the command in your terminal.`:n===`cancelled`?`The CLI sent a cancel — nothing was created on the server. You can close this tab.`:n===`expired`?`This pairing passed its 15-minute TTL. Re-run the command in your terminal to start a new one.`:`The pairing record is no longer reachable. Re-run the CLI command to start a fresh one.`})]})]})}function mz({kind:e,onDismiss:t}){return(0,B.jsxs)(`div`,{role:`alert`,"aria-live":`polite`,className:`mb-4 flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-12 text-foreground`,children:[(0,B.jsx)(e===`timeout`?pi:Ai,{className:`mt-0.5 h-4 w-4 shrink-0 text-destructive`,"aria-hidden":!0}),(0,B.jsxs)(`div`,{className:`flex flex-1 flex-col gap-1`,children:[(0,B.jsx)(`p`,{className:`font-medium`,children:e===`timeout`?`Request to NyxID timed out`:`NyxID backend unreachable`}),(0,B.jsx)(`p`,{className:`text-muted-foreground`,children:e===`timeout`?`The page took too long to reach the NyxID backend. No changes were made. Try again from the form below — or close this tab and re-run the command in your terminal.`:`Couldn't reach the NyxID backend on the last attempt. No changes were made. Check your network, then try again from the form below — or close this tab and re-run the command in your terminal.`})]}),(0,B.jsx)(`button`,{type:`button`,onClick:t,"aria-label":`Dismiss`,className:`rounded p-1 text-muted-foreground hover:bg-destructive/10 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive`,children:(0,B.jsx)(ji,{className:`h-3.5 w-3.5`,"aria-hidden":!0})})]})}function hz(e){return(0,B.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`-5.0 -10.0 110.0 135.0`,fill:`currentColor`,...e,children:(0,B.jsx)(`path`,{d:`m74.719 41.191c0.011719-0.007812 0.023438-0.011718 0.03125-0.019531l3.0312-1.75c1.1172-0.64453 1.9922-2.1523 1.9883-3.4297l-0.039062-18.926c-0.003907-0.69141-0.25781-1.2227-0.72266-1.5 0 0 0-0.003906-0.003906-0.003906 0 0 0.003906 0.003906-0.015625-0.011719-0.003906 0-0.007812-0.003906-0.011719-0.003906-0.17187-0.097656-3.8789-2.2578-3.7031-2.1602-0.47656-0.27344-1.0781-0.23047-1.6992 0.12109l-1.25 0.72266c0-0.24219 0.015625-3.7422 0.015625-3.5273v-0.003906c0-0.011719-0.011719-0.015625-0.011719-0.027344-0.015625-0.70312-0.46094-1.3477-1.2656-1.8125-2.4922-1.4375-6.7812-0.44141-6.7812 1.8398 0.003906 2.3828-0.011719 8.3906-0.011719 8.1797l-7.0625 4.0781c-0.48828 0.28125-0.070312 1.0312 0.42969 0.74609 17.559-10.082 16.707-9.8555 17.203-9.5742l2.5547 1.4883c-12.867 7.4258-32.582 18.809-51.461 29.707-1.1133 0.64062-1.9805 2.1523-1.9766 3.4375 0.058594 27.855-0.03125 15.969 0.042969 19.02l-2.5547-1.4883c-0.49609-0.30469-0.26172 0.49609-0.35937-19.703 0-0.96094 0.71875-2.2109 1.5469-2.6758l28.973-16.727c0.20703-0.11719 0.27734-0.38281 0.15625-0.58984-0.12109-0.20703-0.38281-0.27734-0.58984-0.15625l-15.32 8.8438v-3.5156c0-0.015625-0.011718-0.023437-0.015624-0.035156-0.015626-0.70312-0.46094-1.3438-1.2617-1.8086-2.4688-1.4258-6.7852-0.46094-6.7812 1.8398 0.003906 3.2031-0.007813 8.3906-0.007813 8.1797l-5.5781 3.2188c-1.0938 0.61719-1.9805 2.1523-1.9805 3.4258l0.054688 18.926c0 0.69141 0.25391 1.2305 0.72656 1.5195 3.5742 2.0586 3.7812 2.2734 3.957 2.2227 0.14453 0.050782 0.28516 0.11719 0.44922 0.11719 0.66797 0 1.1758-0.39453 2.2891-1.0391l-0.023437 9.4297c-0.61328 0.19922-1.2109 0.44922-1.7773 0.77344-1.6172 0.93359-2.5117 2.2148-2.5195 3.6055-0.003906 1.6992-0.007812 4.7891-0.007812 4.6133-0.003906 1.4023 0.91016 2.7031 2.5703 3.6602 1.6055 0.92578 3.707 1.3867 5.8125 1.3867 2.1211 0 4.2461-0.46875 5.8594-1.4062 1.625-0.94531 2.5234-2.2266 2.5273-3.6094v-4.5977c0-0.011718-0.011719-0.019531-0.011719-0.03125-0.011718-1.3828-0.91016-2.6641-2.5391-3.6055-0.5625-0.32813-1.1953-0.58984-1.8633-0.79688l0.007812-2.5898c0-0.23828-0.19141-0.42969-0.42969-0.42969-0.23828 0-0.42969 0.19141-0.42969 0.42969l-0.015625 7.2227c0 1.3594-3.3945 2.3125-5.4883 1.1094-0.53906-0.31641-0.83984-0.71094-0.83984-1.1094l0.035156-14.559 6.3438-3.6602-0.015624 5.1836c0 0.23828 0.19141 0.42969 0.42969 0.43359 0.23828 0 0.42969-0.19141 0.42969-0.42969l0.015626-5.6836 28.43-16.414-0.035156 9.4492c-2.1211 0.69141-4.293 2.1523-4.3047 4.375-0.007812 1.168-0.003906 4.7812-0.003906 4.6055-0.015625 4.6992 9.1953 6.5742 14.242 3.6445 1.625-0.94531 2.5234-2.2266 2.5273-3.6094 0-0.18359 0.007813-4.4062 0.007813-4.5898 0-2.3008-2.2422-3.7305-4.418-4.4453l0.039062-14.078zm-8.7383-31.586c1.2539-0.73047 3.3828-0.73047 4.6562 0.003906 3.2852 1.8906-3.2969 4.0273-5.2227 1.7227-0.16406-0.20312-0.26172-0.41406-0.26172-0.63281 0-0.003907-0.003906-0.003907-0.003906-0.007813 0.003906-0.39062 0.29687-0.77344 0.83203-1.0859zm-0.83203 2.6758c1.5781 1.2578 4.7695 1.2148 6.3242-0.007812l-0.011718 2.4609-6.3281 3.6523zm-35.652 18.391c1.25-0.73047 3.3867-0.72656 4.6562 0.003906 1.9414 1.1211 0.25391 2.6562-2.168 2.7266-2.5859 0.070312-4.4766-1.5781-2.4883-2.7305zm-0.83203 2.6797c1.6094 1.2734 4.8477 1.168 6.332-0.011718v2.4492l-6.3438 3.6641zm10.617 53.434c-0.011719 3.6289-8.1406 5.6406-12.91 2.8867-1.3828-0.79688-2.1406-1.832-2.1367-2.9141v-2.332c3.1289 3.8086 12.098 3.6172 15.047 0.011719zm-10.344-2.5352c2.5078 1.4375 6.7852 0.40625 6.7852-1.8555l0.007813-3.7266c4.2617 1.4883 4.5586 4.6445 1.4453 6.3867-4.1406 2.4102-11.523 1.2734-12.777-2.043-0.003907-0.015625-0.015626-0.027344-0.019532-0.039063-0.011718-0.03125-0.007812-0.0625-0.015625-0.09375-0.47656-1.5352 0.58594-2.793 1.9688-3.5938 0.43359-0.24609 0.88672-0.44141 1.3477-0.60938l-0.007813 3.7148c-0.003906 0.72266 0.44531 1.3789 1.2656 1.8594zm35.391-38.055s-0.003906 0-0.007813 0.003907l-2.5117 1.4492 10.109-27.867 5.5664-3.2148-10.102 27.867zm-16.055 9.2695 10.105-27.863 5.6172-3.2461-10.105 27.863zm-13.496 7.793 10.105-27.859 5.6055-3.2383-10.105 27.859zm-6.7227 3.8789 10.086-27.848 5.5859-3.2266-10.105 27.859zm-3.2266-18.375c0-0.96094 0.71875-2.2109 1.5469-2.6758l3.8594-2.2305-5.3867 14.824zm26.82-17.273 5.5703-3.2188-10.105 27.863-5.5703 3.2188zm13.508-7.8008 5.6055-3.2383-10.109 27.867-5.6055 3.2344zm13.348-7.4023c0.62109 0.25 0.25781 0.64844 0.38672 11.164l-4.7422 13.074-5.6055 3.2383zm-1.1523 22.391-2.043 1.1797 3.5859-9.8906 0.011719 6.0312c0.003906 0.96484-0.70703 2.1914-1.5547 2.6797zm-52.188 29.777c-0.47656-0.27734-0.26953-1.0508-0.3125-7.2617l6.543-18.004 5.5859-3.2266-10.086 27.844c-0.58984 0.29297-1.2734 0.90625-1.7305 0.64844zm50.602-2.7305c-0.015625 5.5508-15.062 5.3945-15.047-0.03125v-2.332c3.1094 3.7969 12.074 3.6289 15.047 0.011719zm-2.1055-1.7344c-4.2344 2.4727-11.234 1.2031-12.672-1.8477-0.98438-2.1055 1.0117-3.7305 3.1758-4.5156l-0.011718 3.6992c0 3.3242 8.0508 3.3359 8.0508 0.019531l0.011719-3.7383c4.4492 1.5742 4.4688 4.6211 1.4453 6.3828zm-2.3203-2.6484c0 1.3672-3.4062 2.293-5.4766 1.0898-0.55078-0.3125-0.85156-0.70703-0.85156-1.1055l0.050781-14.562 2.9102-1.6797h0.003906l3.4102-1.9688z`})})}function gz({code:e=`404`,title:t=`Page not found`,description:n=`The page you're looking for doesn't exist or may have moved.`,action:r}){return(0,B.jsxs)(`div`,{className:`flex min-h-[60vh] w-full flex-col items-center justify-center gap-1 px-6 py-12 text-center`,children:[(0,B.jsx)(hz,{className:`h-48 w-48 text-muted-foreground/30`}),(0,B.jsx)(`p`,{className:`font-mono text-xs uppercase tracking-widest text-text-tertiary`,children:e}),(0,B.jsx)(`h1`,{className:`mt-2 font-serif text-28 font-normal text-foreground`,children:t}),(0,B.jsx)(`p`,{className:`mt-1 max-w-sm text-sm text-muted-foreground`,children:n}),r?(0,B.jsx)(`div`,{className:`mt-6`,children:r}):null]})}var _z=2e4,vz=window.__WIZARD_BOOTSTRAP__;vz&&ez(vz);var yz=new We({defaultOptions:{queries:{retry:1,staleTime:3e4}}});function bz(e,t){return t?e!==`done`&&e!==`cancelled`&&e!==`wizard-lost`:!1}function xz(e,t){return t?e===`claimed`:!1}function Sz(){let[e,t]=(0,z.useState)({phase:`claimed`}),[n,r]=(0,z.useState)(null),[i,a]=(0,z.useState)(!!vz?.prefill?.slug),[o,s]=(0,z.useState)(!1),[c,l]=(0,z.useState)(null);if((0,z.useEffect)(()=>{if(!vz)return;let e=cz({onDisconnect:()=>{s(!0)},onReconnect:()=>{s(!1)}});return()=>{e()}},[]),(0,z.useEffect)(()=>{if(vz)return nz(e=>{l(e)})},[]),(0,z.useEffect)(()=>{if(!o||e.phase===`done`||e.phase===`cancelled`||e.phase===`wizard-lost`)return;let n=window.setTimeout(()=>{t(e=>e.phase===`done`||e.phase===`cancelled`||e.phase===`wizard-lost`?e:{phase:`wizard-lost`})},_z);return()=>{window.clearTimeout(n)}},[o,e.phase]),!vz)return(0,B.jsx)(Nz,{});let u=mt(Tz(e.phase),vz.flow,{slugPicked:i});async function d(e){if(e.kind===`ai-key`){t({phase:`acking`,result:e}),await Cz(e,r,t);return}t({phase:`secret`,result:e})}async function f(){e.phase===`secret`&&await Cz(e.result,r,t)}return(0,B.jsxs)(Zt,{context:`local`,step:u,children:[xz(e.phase,c)?(0,B.jsx)(mz,{kind:c,onDismiss:()=>{l(null)}}):null,bz(e.phase,o)?(0,B.jsx)(pz,{state:`disconnected`,context:`local`}):null,e.phase===`claimed`?(0,B.jsx)(Ez,{flow:vz.flow,prefill:vz.prefill??{},onSuccess:e=>void d(e),onCancel:()=>{t({phase:`cancelled`}),az()},onSlugPicked:e=>{a(!!e)}}):e.phase===`secret`?(0,B.jsx)(Dz,{result:e.result,completeError:n,onAck:()=>void f()}):e.phase===`acking`?(0,B.jsx)(Oz,{result:e.result,completeError:n,onRetry:()=>{d(e.result)}}):e.phase===`cancelled`?(0,B.jsx)(Az,{}):e.phase===`wizard-lost`?(0,B.jsx)(jz,{}):(0,B.jsx)(kz,{})]})}async function Cz(e,t,n){try{await iz(wz(e)),t(null),n({phase:`done`})}catch(e){t(e instanceof Error?e.message:String(e))}}function wz(e){switch(e.kind){case`ai-key`:return{acknowledged:!0,service_id:e.service_id,slug:e.slug,label:e.label};case`api-key-create`:return{acknowledged:!0,api_key_id:e.api_key_id};case`api-key-rotate`:return{acknowledged:!0,resource_id:e.resource_id};case`node-register-token`:return{acknowledged:!0,token_id:e.token_id};case`node-rotate-token`:return{acknowledged:!0,resource_id:e.resource_id};case`service-account-create`:return{acknowledged:!0,service_account_id:e.service_account_id};case`service-account-rotate-secret`:return{acknowledged:!0,resource_id:e.resource_id};case`developer-app-create`:return{acknowledged:!0,developer_app_id:e.developer_app_id};case`developer-app-rotate-secret`:return{acknowledged:!0,resource_id:e.resource_id};case`mfa-setup`:return{acknowledged:!0,factor_id:e.factor_id}}}function Tz(e){return e===`claimed`?`claimed`:e===`secret`?`secret`:e===`acking`?`acking`:`done`}function Ez({flow:e,prefill:t,onSuccess:n,onCancel:r,onSlugPicked:i}){let a=`local`;switch(e){case`api-key-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(SL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`api-key-rotate`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(CL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`node-register-token`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(TL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`node-rotate-token`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(EL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`ai-key`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(VR,{prefill:TP(t),pairingId:a,onSuccess:n,onSlugPicked:i}),(0,B.jsx)(Mz,{onCancel:r})]});case`service-account-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(DL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`service-account-rotate-secret`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(OL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`developer-app-create`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(kL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`developer-app-rotate-secret`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(AL,{prefill:t,pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]});case`mfa-setup`:return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(jL,{pairingId:a,onSuccess:n}),(0,B.jsx)(Mz,{onCancel:r})]})}}function Dz({result:e,completeError:t,onAck:n}){let r=t===null;return e.kind===`api-key-create`?(0,B.jsx)(Hi,{title:`API key created`,description:`Save this key now — it won't be shown again.`,secret:e.full_key,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:r&&t===null&&!1}):e.kind===`api-key-rotate`?(0,B.jsx)(Gi,{result:e,description:`The previous key is revoked. Save this new value now — it won't be shown again.`,ackButtonLabel:`I have saved this — close`,onAcknowledge:n}):e.kind===`node-register-token`?(0,B.jsx)(Hi,{title:`Registration token generated`,description:"Use this with `nyxid node register`. Save it now — it won't be shown again.",secret:e.token,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`node-rotate-token`?(0,B.jsx)(Hi,{title:`Node tokens rotated`,description:"Update the node with `nyxid node rekey`. Save both values now — they won't be shown again.",secret:e.auth_token,secondarySecret:{label:`Signing secret`,value:e.signing_secret},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`service-account-create`?(0,B.jsx)(Hi,{title:`Service account created`,description:`Save the client_secret — it isn't shown again. Use it with the OAuth client_credentials flow.`,secret:e.client_secret,secondarySecret:{label:`Client ID`,value:e.client_id},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`service-account-rotate-secret`?(0,B.jsx)(Hi,{title:`Service account secret rotated`,description:`All previously-issued tokens have been revoked. Save this new client_secret — it isn't shown again.`,secret:e.client_secret,secondarySecret:{label:`Client ID`,value:e.client_id},ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`developer-app-create`?(0,B.jsx)(Hi,{title:`Developer app created`,description:`Save the client_secret — it isn't shown again. Use it to sign Sign-in-with-NyxID requests.`,secret:e.client_secret,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`developer-app-rotate-secret`?(0,B.jsx)(Hi,{title:`Developer app secret rotated`,description:`The previous client_secret no longer authenticates. Update any deployments using it.`,secret:e.client_secret,ackButtonLabel:`I have saved this — close`,onAcknowledge:n,isAcknowledging:!1}):e.kind===`mfa-setup`?(0,B.jsx)(Ui,{codes:e.recovery_codes,onAcknowledged:n}):(0,B.jsx)(`p`,{className:`text-sm text-destructive`,children:`Unknown result kind.`})}function Oz({result:e,completeError:t,onRetry:n}){return(0,B.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Service added`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[(0,B.jsx)(`code`,{className:`font-mono text-xs`,children:e.slug}),` is now connected. Check your terminal for the final summary.`]}),t?(0,B.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,B.jsxs)(`p`,{className:`text-sm text-destructive`,children:[`Couldn't notify CLI: `,t]}),(0,B.jsx)(Pi,{variant:`outline`,onClick:n,children:`Retry`})]}):null]})}function kz(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Done`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`You can close this tab and return to your terminal.`})]})})}function Az(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Cancelled`}),(0,B.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Nothing was created. You can close this tab — your CLI should already be back at the prompt.`})]})})}function jz(){return(0,B.jsx)(`div`,{className:`flex flex-col gap-4`,children:(0,B.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,B.jsx)(`h2`,{className:`font-serif text-28 font-normal`,children:`Wizard interrupted`}),(0,B.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Lost contact with the `,(0,B.jsx)(`code`,{children:`nyxid`}),` CLI. The CLI may have finished and exited successfully, or the connection was interrupted before the result reached this page.`]}),(0,B.jsxs)(`p`,{className:`mt-2 text-sm text-muted-foreground`,children:[`Run `,(0,B.jsx)(`code`,{children:`nyxid status`}),` in your terminal to see whether the service was created. If it’s missing, re-run the wizard command.`]})]})})}function Mz({onCancel:e}){return(0,B.jsx)(`button`,{type:`button`,onClick:e,className:`self-start text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground`,children:`Cancel and return to terminal`})}function Nz(){return(0,B.jsx)(gz,{title:`Wizard not available here`,description:(0,B.jsxs)(B.Fragment,{children:[`This page is served by the `,(0,B.jsx)(`code`,{children:`nyxid`}),` CLI’s local wizard server, which injects its config on request. Open the URL printed by the CLI instead.`]})})}var Pz=document.getElementById(`wizard-root`);Pz&&(0,dt.createRoot)(Pz).render((0,B.jsx)(z.StrictMode,{children:(0,B.jsx)(Ye,{client:yz,children:(0,B.jsx)(Sz,{})})})); diff --git a/cli/src/wizard/bundle-meta/index.hash b/cli/src/wizard/bundle-meta/index.hash index 95464f0f1..4877554ad 100644 --- a/cli/src/wizard/bundle-meta/index.hash +++ b/cli/src/wizard/bundle-meta/index.hash @@ -1 +1 @@ -b10c0bbe2d0678ca314acce0f437cc1d60c629bb59f4b67cae8f8dbd3aad508b +453b87715aeb189dca4a4ddb4f76e519e0a3b1f30a31f3109427cfe812a373cf diff --git a/cli/src/wizard/bundle-meta/index.manifest b/cli/src/wizard/bundle-meta/index.manifest index 43d9137da..45c7277ce 100644 --- a/cli/src/wizard/bundle-meta/index.manifest +++ b/cli/src/wizard/bundle-meta/index.manifest @@ -161,6 +161,7 @@ frontend/src/stores/assistant-draft-store.ts frontend/src/stores/assistant-wire-log-store.ts frontend/src/stores/auth-store.ts frontend/src/stores/credits-denial-store.ts +frontend/src/stores/service-card-view-store.ts frontend/src/stores/theme-store.ts frontend/src/types/options.ts frontend/src/wizard-entry.tsx diff --git a/docs/AI_SERVICES_ARCHITECTURE.md b/docs/AI_SERVICES_ARCHITECTURE.md index 27900e2aa..d4783e443 100644 --- a/docs/AI_SERVICES_ARCHITECTURE.md +++ b/docs/AI_SERVICES_ARCHITECTURE.md @@ -8,6 +8,218 @@ NyxID's AI Services system lets users manage external API credentials, SSH servi --- +## Service cards and saved filter defaults + +The External Services grid groups catalog-backed connections by `catalog_service_id`. +Custom connections remain separate by ID. Each group starts collapsed with a +288px fixed collapsed height and two reserved description lines; expanding a group keeps its connection comparison +table inside the parent. Connection details and history remain on their existing detail pages. + +The expanded table shows Classification (personal, named organization with role, +or an actual platform connection), Status, Activity (latest configuration change +with actor and date, or creation), and the exact Slug. Each row links by connection +ID to its detail page, so identical slugs in different organizations remain +distinguishable. Repeated descriptions, endpoints and configuration belong on +the full service page. **Service details** opens +`/keys/services/{groupId}` for the complete catalog group (or one custom service). +This page has Connections, Service information and History tabs. History is +selected by connection ID, and each connection links to its existing full +configuration page. Last-caller attribution remains explicitly unreported when +absent from the API; credential preparation and provisioning-app metadata are +never presented as the last service use. + +Standalone **Organization** and **Service** multi-select menus precede search. +Each supports searching and immediate checkbox selection. Active values appear +as individual removable pills (`Org: ChronoAI`, `Service: Aevatar`, +`Service: Codex`). Values within one field match with OR; the two fields combine +with AND. Empty selections mean all. Options come +from actual connection owners and grouped services, keyed by organization ID and +group ID. Clearing either selector leaves other criteria intact. Missing saved +selections remain visible as unavailable until cleared. These selections are +included in account defaults. Organization ownership uses a small circular org +avatar with the existing initials fallback on cards, table rows, filter choices +and pills. Platform sources use the NyxID icon. + +The toolbar keeps Organization, Service, and search as its filtering controls; +the additional Filters menu has been removed. Search applies on submit or blur. +Older saved source, service-state, type, and auto-connected criteria remain visible +as removable pills so they cannot silently hide connections. Active criteria can +also be cleared together. In sticky mode Connect Service becomes **+ Connect** and +Personal/All services becomes its active view icon, with accessible labels and +hover tooltips. The preview refresh button is removed. Returning to the normal position restores their text. Explicit +12px toolbar padding and a 10px gap separate controls from the selected pills; +pills retain their own 6px gaps and fit within two rows before scrolling. +These controls apply to both the grid and table. Personal is the default service scope: a group is included when it has +a personal connection, and its accessible organization and platform counterparts +remain visible. Groups with no personal connection are available in All services. +All states/types and auto-connected services are included by default. +Source options come from the current connection list. Enabled/disabled +uses `UserService.is_active`; it does not assert credential health or readiness. +Personal scope selects groups; the other criteria filter individual connections. +Organization/platform source filters still match only those connection classes. +Expanded tables show only matching +rows and the parent displays the matching count against the group total. With no +filters all siblings are visible. The full service page always contains every +accessible connection in the group, independent of list filters. + +Source avatars sit at the bottom right of the card body without a visible label. +Hovering or focusing a stationary circle brings it forward and shows its source +name. Extra sources remain reachable through the overflow count. + +Billing checks the service configuration before credential supply. A service with +no configured NyxID usage charge shows **—** for every connection, including +supplied API keys, OAuth apps, disabled connections and nodes. Its tooltip is +exactly **Not billable by NyxID**. For a service with billing configured, a +connection shows **NyxID** when NyxID supplies the selected key or +OAuth developer app, **BYOK** when the person or organization supplies it, and +**Unverified** when the supplier or service configuration cannot be established. +A connection requiring no provider credential is also NyxID when service billing +is configured. Free grants, allowances, promotional credits, caller rollout and +wallet debits never change this classification. A bill covered entirely by a +grant remains NyxID; grant coverage of an additional fee on a supplied key does +not turn BYOK into NyxID. The compact **NyxID** label means platform billing. + +The service-wide gate is true if any credential class has a configured charge. +It is distinct from the selected connection's price lane: a service can have +platform pricing while a BYOK connection has no NyxID fee. Grouped cards count all +represented categories, including disabled connections; all-unpriced groups show +one dash. Hover lists each connection and its actual/configured charge separately. +Clicking opens the first NyxID connection's billing panel, or the first connection +when none uses NyxID credentials. Expanded rows use the same classifier. + +The additive insight fields `service_billing_configured` and `credential_supplier` +carry the service-wide gate and selected credential provenance. Existing +`credit_billing_configured`, `rates` and `charge_status` describe the connection's +charges. Restricted metadata stays null. Disabled connections preserve known +configuration/provenance; unavailable agent overrides never borrow the default +connection's supplier. None of these reads decrypts a credential or changes billing. + +`/keys` also exposes `oauth_app_source` (`platform` or `byo`), separately from +personal/organization ownership. Successful OAuth authorization and refresh +atomically record `oauth_app_observation` with the app source, credential epoch +and timestamp. Source comes from the actual credential-resolution branch, +including embedded BYO apps whose legacy owner-reference field is empty. +The shared read-only resolver honors explicit selection and current observations; +disagreement stays unknown. An epoch change or newer authorization invalidates +an old observation. It batch-loads legacy provider-token records to match owner, +provider, token association and identical copied ciphertext. Ciphertext is never +decrypted or exposed. Missing or ambiguous evidence remains unknown; connection +IDs, retained developer apps and token expiry are not provenance evidence. +Observation writes never alter execution's credential source or pricing lane. +Explicit platform binding wins over a retained user key. A legacy OAuth execution +class of `user_owned` does not prove BYOK. +Non-OAuth stored key records identify the supplied-key path; a user binding alone +does not prove a credential exists. Agent provenance comes from the selected +override rather than the connection default. + +Older servers use permitted catalog/connection metadata. Successfully loaded +catalog entries without billing and custom services without catalog links show +**—**. Missing or failed catalog reads remain unknown, including private catalog +entries omitted from discovery. OAuth connections without published provenance +remain unverified until the backend field is deployed. The dash tooltip stays +beside its content; longer details align below, with viewport collision handling. + +The label is not evidence of a settled debit. Shared-app OAuth currently uses the +BYOK price lane in execution. A service with only platform-key pricing therefore +has no OAuth usage fee even when the credential supplier is NyxID. The UI must not +attach the platform-key price to that OAuth connection. See the reviewed decision +table and rollout limitations in [Billing labels](plans/service-billing-labels.md). + +Agent keys are counted once across the group's connections; partial inventories +show a `+`. Agent key count and last-use time share one compact line. Hover or +keyboard focus shows the key names, recorded caller/application and exact time. +The lower-left **Last edit** summary uses the latest `authorship.last_change` event, +including its actor, and opens that connection's history. Creation and credential +preparation timestamps are not substituted for edits or usage. + +BYOK means a supplied API key or developer app. A user's OAuth token is not proof +of their own app: the internal BYOK price lane also includes NyxID's shared OAuth +app. Verified `nyxid_platform_oauth_app` shows **NyxID developer app**; a master key +shows **NyxID key**. The older-server compatibility view recognizes a supplied +OAuth app through its public `oauth_client_id`. Its absence does not establish +platform ownership: an OAuth login or user binding alone cannot identify the app. +It reads legacy catalog credit pricing as well as credential lanes, honors known +platform-only exclusions, and never invents free usage or a numeric plan rate. + +Credential source, NyxID charges, and funding are separate. A platform credit grant +can fund an eligible NyxID fee even when the caller supplies their own provider key; +it does not change the provider credential's owner. Conversely, signing into an +account through NyxID's app is not BYOK. This preview shows the configured funding +order; identifying the actual allowance, grant, or wallet used requires per-request +settlement data and is not claimed here. +Personal credentials normally use the personal account, organization credentials +use the owning organization's account, and a platform key uses the acting person's +personal account. These details remain separate for every connection in the table. +The pool summary shows the number of member connections and selection strategy; +the next line shows the pool name and configured failover. Multiple pools show +their count and how many have failover enabled. Hover/focus lists each pool's +members, strategy, policy and slug. Expanded connection rows link to their pools. +Grouping by catalog does not create a pool or change an individual connection slug. +Click the pool summary to inspect the pool slug, strategy, priority/weight, credential supplier, +billing account/rates, and operation-scoped eligibility/cooldown in a table inside +the card. All members of the selected pool are shown, including members of other +catalog services. Pool management inventory is currently personal or organization +admin only; incomplete access is labeled instead of asserting there are no pools. +AI service routing details are read-only. **Manage in Service Pools** opens the +selected pool under its personal or organization owner and scrolls its expanded +card into view. Pool configuration, member ordering and policy edits live in +**Service Pools** only. + +Priority pools support bounded failover; round-robin and weighted pools select a +single member per request. Omitted/null priority policies use server defaults, not +"failover off". Disabled pools, disabled members and failed inspection remain +explicit. Eligibility is metadata inspection, not proof of a successful upstream +request. The Same API inspector applies the entered method/path on Inspect; AI +chat inspection uses POST chat/completions. Reads refresh every 15 seconds while +open. A failed read discards cached eligibility. + +The pool editor saves settings and membership in one revision-checked PUT. Dragging +or using reorder arrows creates a strict priority sequence, preserving weights, +models, enabled state and compatibility declarations. Equal numeric priorities +configure rotation within a tier. The old local-storage-only priority preview is +removed. The Service Pools routing view uses equal-height collapsed cards with +one open route at a time and the same inline routing/billing table. + +Each pool attempt retains its own billing identity, credential class and payer. +Known consumption can charge more than one attempt. Within a selected billing +account, eligible allowances fund usage before credit grants and then wallet +credits. This funding order does not select another connection or payer; local +NyxID billing failures remain terminal. See [Service pools](SERVICE_POOLS.md). + +**Save as default** writes the current filters to the authenticated user's +`users.profile_config.services_view` embedded blob. Search text is included. +**Restore default** discards draft filters. **Clear filters** shows everything for +this visit; saving afterward makes that the account default. Card expansion is +session presentation state and is never persisted to the account. Draft filters +survive detail-page navigation, but reset on sign-out/account change or reload. + +`GET /api/v1/users/me` includes `profile_config.services_view` (null until saved). +`PUT /api/v1/users/me/preferences/services` replaces that one preference group: + +```json +{ + "search": "", + "organization_ids": [], + "service_group_ids": [], + "source": "org", + "state": "enabled", + "service_type": "all", + "show_auto_connected": true +} +``` + +The PUT returns the saved filter object. It derives ownership from `AuthUser`, +rejects unknown fields/enum values, limits search to 200 Unicode characters and +each identifier to 128 characters and each selection list to 100 entries, and uses a dotted MongoDB update to preserve other settings. Existing users need no +migration: legacy singular organization/service fields are read as one-item +arrays (null as empty). Writes use the plural array fields and deduplicate IDs. +Selection order does not change default-view equality. This endpoint changes display preferences only; it does not change +routing, service access, or connection priority. + +Older backends omit `services_view`; filtering remains available, but account +saving is disabled until the supporting backend is deployed. The UI never falls +back to browser storage while claiming the preference was saved to the account. + ## System Components ```mermaid @@ -62,16 +274,17 @@ graph TB ## Service-Pool Routing Boundary -NyxID#974 was narrowed to a routing proof before adding a user-facing pool -surface. The proof is recorded in -[SERVICE_POOL_ROUTING_PROOF.md](SERVICE_POOL_ROUTING_PROOF.md). +A `ServicePool` owns a stable slug and a set of concrete `UserService` members. +Priority ingress captures the pool revision, plans eligible candidates without +materializing credentials, and uses the exact selected member for authorization, +approval, dispatch and per-attempt billing. Round-robin and weighted strategies +retain single-member selection. See [Service pools](SERVICE_POOLS.md) and the +[architecture proof](SERVICE_POOL_ROUTING_PROOF.md). -The important boundary is that `UserService` remains the concrete proxy target -member, while any future `ServicePool` must be selected inside -`proxy_service::resolve_proxy_target_from_user_service()`. The existing -`node_routing_service::resolve_node_route()` / `fallback_node_ids` layer remains -node failover below a selected `UserService`; it is not sufficient by itself to -balance multiple endpoint/credential instances behind one stable slug. +Node routing selects transport below that concrete member. Node failover does +not replace pool selection, change connection ownership, or combine billing +accounts. The card presents these saved pool policies without changing the normal +resolution behavior of individual connection slugs. ## Data Model Relationships @@ -587,4 +800,105 @@ membership, owner activity, provider eligibility and catalog configuration. ## Service authorship and history -Service cards and tables include authorized creator/latest-editor summaries. Instance detail pages, including platform-managed instances, have a History tab. Deleted UUID histories remain discoverable from Services → Deleted service history under current personal-owner/org-admin/resource-scope checks. The transactional journal covers service, endpoint and credential writers; ordinary timestamps, usage and routine refresh do not count as configuration edits. See [SERVICE_HISTORY.md](SERVICE_HISTORY.md) for capture, safe values, writer inventory, audit publication and required MongoDB replica-set migration. +Service cards and tables include authorized creator/latest-editor summaries. Instance detail pages, including platform-managed instances, have a History tab. Deleted UUID histories remain discoverable from Services → Deleted service history under current personal-owner/org-membership/resource-scope checks. The transactional journal covers service, endpoint and credential writers; ordinary timestamps, usage and routine refresh do not count as configuration edits. See [SERVICE_HISTORY.md](SERVICE_HISTORY.md) for capture, safe values, writer inventory, audit publication and required MongoDB replica-set migration. + +### Connection comparison and configuration visibility + +The grid starts with one collapsed card per catalog service. Expanding a card keeps +its connection table inside the parent, replaces the fixed-height summary with a +compact header, and animates the card and neighboring grid positions using native +view transitions where supported. Reduced-motion users receive immediate updates. +The standalone table and service overview use the same comparison component. + +Rows combine connection name/slug/type, ownership/avatar/role, service and credential +state, configuration summary, and latest recorded change. Row disclosure exposes +additional metadata without nested connection cards. History has a separate action +for every visible connection; it is not conditional on a recorded creator. + +`GET /keys` and `GET /keys/{id}` include an additive `can_edit_configuration` flag: +true for personal owners and scoped org admins, false for auto-connected rows and +org members/viewers. It only gates editing affordances in the new comparison UI. +Responses keep their existing fields for every reader, so API clients and the CLI +(for example `nyxid ssh terminal` default principals and `nyxid service show`) are +unchanged; `/user-services` and endpoint discovery keep their existing contracts. +The full connection detail page keeps its existing read-only view for org members. + +History access is unchanged: personal owners and scoped org admins can read it under +the checks in [SERVICE_HISTORY.md](SERVICE_HISTORY.md). Other readers who open a +row's History see the existing "unavailable" message. Last editor and +credential-preparation time are never labeled last caller or successful upstream +execution. + +### Billing and caller information inside service cards + +Collapsed cards show credential sources, the latest recorded request, and a billing +summary for their active connections. The expanded comparison table exposes +**Owner / credential**, **Access & requests**, and **Billing** alongside identity, +state, and configuration. Each connection can open one inline panel: permitted +agent keys and credential overrides, its latest three recorded requests, or payer +and rates. Users do not need to open an individual connection page to inspect +these fields. Billing's **For** selector previews the viewer or a managed agent +key, including an applicable credential override. + +`GET /api/v1/service-insights?ids=` returns up to 100 exact +connection projections; optional `api_key_id` selects a managed agent's billing +context. It deliberately lives outside `/keys/{id_or_slug}` so no user slug is +reserved. Sessions, access tokens, and delegated account reads use existing live +owner/membership/resource checks. API keys, service accounts, and relay tokens +cannot enumerate the management projection. Responses are private and not cached +by HTTP intermediaries; a failed frontend refresh drops privileged summaries. + +Billing is a read-only explanation built from the execution owner resolver, +credential class, rollout flags, and pricing configuration. A NyxID credential +does not imply NyxID pays: the resolved account is displayed separately. Synced +rates retain exact decimal values. Unknown legacy prices remain conditional; +missing data is never represented as free. Provider charges may be separate, and +the preview is not a settled debit or proof that a provider credential works. +Inspecting the page does not decrypt credentials, reserve credits, or call providers. + +On servers without the insights route (404/405/501 only), the frontend reads the +existing managed-key inventories, binding metadata and catalog credential prices. +This compatibility projection is labelled **configured scope** and **expected +payer**, with a credential → payer → charges flow inside the connection row. +It does not assert live execution permission, credential health, a resolved payer, +or an effective price; synchronization status accompanies configured prices. +Restricted/network errors do not fall back. Incomplete inventories and unknown +overrides remain explicit, and exact caller history remains unavailable rather +than appearing as zero requests. Agent-specific billing remains on the new +server resolver; the compatibility view only describes the connection default. + +Agent access describes current scope grants, not use. Members see their own key +inventory and requests; scoped organization admins can also inspect organization +keys and permitted connection activity. Other members' private keys are excluded. +Request counts and the latest three events cover 30 days, with the privacy filter +applied before aggregation. Requests use verified auth identities; an agent's +platform label or the service's provisioning application cannot establish which +application executed a request. + +Cards keep separate **Billing**, **Last used**, and **Agent keys** summaries in +both collapsed and expanded states. Billing includes the NyxID payer, rate summary +and separate provider-charge disclosure. Last used includes the exact connection +slug, recorded personal/organization/platform layer, caller/application and time. +Its source comes from the audit event's credential class and owner under current +visibility checks, never today's credential binding or a shared credential's +`last_used_at`. Denied admission and unknown dispatch do not count as recorded use; +they remain in request history. Missing source evidence stays unrecorded. Historical +coverage is bounded to 30 days. The latest dispatched request is aggregated +separately from the three recent events, so newer denials cannot hide recorded use. +The compatibility key parser accepts omitted non-expiring expiry and zero binding +count fields, matching the deployed API contract; incomplete inventory never reads +as a definitive zero keys. + +The `service_request` event is appended through the existing audit hash chain for +resolved HTTP proxy, node, streaming, WebSocket, LLM, and MCP requests. It records +the exact `user_service_id` and a server-generated execution ID shared with the +billing request, plus verified caller and application identities when available. +Response received and connection opened are distinct from completed streams; +explicit admission denials are recorded as denied, and unclassified early exits +remain unknown. Historical and uninstrumented events keep coverage partial. +Catalog IDs and timestamps are never used to allocate old activity to duplicates. + +Raw usage meters also retain the exact connection ID. Charged hourly and daily +rollups retain it in existing cost-partition metadata, preserving established +bucket/replay identities during mixed-version deployment. These metadata additions +do not change pricing, settlement, or the routing policy. diff --git a/docs/plans/ai-service-connection-user-flow.md b/docs/plans/ai-service-connection-user-flow.md new file mode 100644 index 000000000..cd3c61b22 --- /dev/null +++ b/docs/plans/ai-service-connection-user-flow.md @@ -0,0 +1,150 @@ +# NyxID: service connections and pool ordering + +The current product recommendation is [Consolidated Services](consolidated-services-flow.md) +(revised 27 September 2026). This document records the earlier preview design; its view +names and readiness wording are superseded by that recommendation. + +**Revised 17 September 2026.** Proposal; the local frontend previews production +metadata. Health-based selection and Priority pool routing still require backend +implementation. This revision replaces the earlier global card-sorting and +placeholder-platform design. + +Local preview: + +## 1. Keep the service cards + +Open **External Services** and see the individual cards, including service name, +endpoint, proxy slug, credential state, owner and node routing. Expand **Details** +for description, permissions, dates and the existing full detail page. + +A compact **Individual cards / By service** switch changes presentation. By +service groups cards with the same catalog identity. It does not turn those +connections into a pool. Neither view has drag handles. + +Each service gets one short routing status once the server can supply it: + +| Status | Meaning | +| --- | --- | +| Ready via You | A verified personal connection is selected. | +| Ready via Acme | A verified, permitted Acme connection is selected. | +| Ready via NyxID | A real, verified platform connection is selected. | +| Not verified | The required checks have not completed. | +| Unavailable | No permitted working connection remains. | + +Credential status and routing readiness are separate. An active saved key does +not establish that the provider accepts it. A usage timestamp is not a successful +health check. Ready is a current, operation-scoped result with a verification +time; it cannot guarantee every future request will succeed. + +## 2. Show only actual connections + +Open **Connections** from a card. Show the actual connections, owner, and one +short status per row. Known failures stay visible here so the user can repair +them, but never enter the usable routing order. Unverified connections wait for +a check and do not count as working. + +If there is no platform execution service, show no platform row, option, fallback +slot, or platform label in the order. The catalog and shared OAuth application +credentials are not evidence that a platform execution service exists. + +For example, with a working personal connection, a working organization +connection, and no platform service: + +```text +OpenAI connections +Connection choice [Automatic ▾] + +1 Personal account You Selected +2 Team account Acme Available +``` + +If the personal connection fails verification, Acme becomes first. The personal +card remains accessible with **Reconnect needed**. If nothing can be verified, +show an empty usable order with **Not verified** or **Unavailable**, according +to whether checks are pending or conclusively failed. + +## 3. Automatic selection and explicit choice + +For an eligible canonical service slug, Automatic selects the first working, +authorized connection in this order: + +**User → organization → platform.** Absent sources are omitted. If none works, +return an actionable error and do not call the provider. + +**Connection choice** also offers actual verified connections as explicit +choices. Selecting a real platform connection is permitted even when a personal +connection works. This is an exact choice: if that connection is unavailable, +return its error. Do not silently switch back to the user's account. Missing +platform services never produce an option; existing but unverified/unavailable +connections show a disabled option and their reason. + +The proposed per-request override remains `X-NyxID-Connection-Source: platform` +(or `auto`) on the same canonical slug URL. Request choice takes precedence over +a saved choice, which takes precedence over the Automatic default. The header +and saved preferences are proposed additions, not currently deployed behavior. +Exact service IDs, custom slugs, pool slugs, agent credential bindings, narrower +scopes and approvals retain their contracts; they cannot be silently overridden. +Multiple equally eligible accounts require an explicit choice or an established +server policy rather than an arbitrary database order. + +## 4. Drag only members of a real pool + +Open **Service Pools**. Each pool shows its real name, proxy slug, current +selection strategy, description and member cards. Keep the same service +information and detail links found in External Services. + +The user decides whether they want ordered selection: + +| Selection | Behavior | +| --- | --- | +| Round robin | The current round-robin strategy. | +| Weighted | The current weighted strategy, retaining member weights. | +| Priority order | Proposed: use the first working member in the preferred order. | + +Choosing **Priority order** reveals drag handles on the members of that pool. +Dragging changes only that pool's preferred member order. It cannot reorder the +whole page, add another service to a pool, move a member between pools, or change +the user/organization/platform source hierarchy. Keyboard arrows and move buttons +provide equivalent controls. + +A preferred member order is not proof of health. Before dispatch, the server +checks members in that order, skips known unusable ones and resolves pending +checks. Disabled or excluded members cannot execute. If none works, the pool +returns an actionable error. The member cards retain their details and individual +states throughout. + +## 5. Server checks define “working” + +The UI and API must use the same server decision, bound to the actual caller, +requested operation and selected connection. At minimum it must verify: + +- Service/member enabled state, caller access and scope, organization permissions, + exact credential bindings and operation compatibility. +- Credential existence and validity, supported OAuth refresh, route/node + availability and provider authentication evidence from a safe supported check. +- Applicable approval and funding requirements without bypassing either through + fallback. The selected route supplies its actual payer and pricing. + +Pending or failed checks must not be represented as Ready. Providers without a +safe validation method need an explicit unverified state; never send arbitrary +production calls merely to manufacture a green badge. Verification freshness +must be bounded. Execution revalidates before dispatch and records the selected +service, source and reasons for skipped candidates in request history. + +Fallback is a decision made before the provider effect. Do not replay writes or +streams with another credential after a request has been sent. This avoids +changing identity, billing ownership or approval authority mid-request. + +## What is available in the local preview + +The local preview reads actual connection and personal-pool metadata, preserves +cards/details, omits absent platform choices, and lets users arrange real pool +members after opting into Priority. View and pool preferences are saved only in +this browser, scoped by account and pool. Production writes and execution are +blocked by the local gateway. + +There is currently no live per-connection readiness endpoint. The preview labels +saved connections **Not verified** and disables explicit connection choices; +it does not invent successful checks. Pool Priority is also a local proposal: +the production resolver currently selects enabled, active members using +round-robin or weighted selection, without a provider-health gate at that step. diff --git a/docs/plans/consolidated-services-flow.md b/docs/plans/consolidated-services-flow.md new file mode 100644 index 000000000..979a8776d --- /dev/null +++ b/docs/plans/consolidated-services-flow.md @@ -0,0 +1,650 @@ +# NyxID Services: consolidated flow + +Revised 27 September 2026 · One collapsed card per service, with its connections inside. +Execution and data contracts reviewed with Fable on 25 September; card layout +revised below to reflect the user’s subsequent direction. +Grounded in main `1b031c77`. This describes the target experience and required +server guarantees. The grouped collapsed-card UI is now implemented in the local +React frontend and uses production service data. Backend fallback, complete caller +attribution and the remaining target behavior below are not claimed implemented. +It supersedes the earlier UI proposal and its terminology. + +## The experience in one minute + +**One collapsed card per service by default. Its duplicate/configured connections +live inside that card. Expand to compare them; click into one to see everything.** + +1. See **OpenAI · 3 connections** once in the service grid. Personal, work and + sandbox configurations do not occupy three separate top-level cards. +2. Click **3 connections** or Expand. The same card grows to reveal three named + connection cards within its border, each with owner, address, credential state, + latest caller and an Open action. +3. Click the service title or **Open service** for its complete grouped view. + Click **Work account → Open** for that concrete connection’s Route, Activity + and Details. Its history and identity remain distinct from the others. +4. When an actual canonical address exists, its routing summary is labelled with + that address. Change its source in this card’s route section: Automatic keeps + personal → organization → platform; a specific-source choice is explicit. +5. Pools retain their own cards. Only a real pool’s Priority strategy enables + dragging its member services. A group of same-service connections is not a pool. + +The page stays collapsed on initial load. Expansion is local to each service and +is restored when returning from details. All connection data remains inside the +parent card or that service’s full page; there is no data panel beneath the grid. + +## Display reference + +**[Open the live frontend](http://127.0.0.1:4317/keys?view=routing)**. +This is the real React application, reading service/catalog metadata from +`https://nyx-api.chrono-ai.fun`. If the local session has expired, use the +[production sign-in flow](http://127.0.0.1:4317/__routing-preview/login). + +The implementation groups actual service records into collapsed cards. Expanding +a card contains the original connection cards and metadata within the parent; +clicking a connection opens its existing full detail/history page. Search matches +nested connections without losing siblings. The normal `/keys` grid uses the same +component. It does not invent routing decisions or last-caller evidence absent +from the backend. + +The earlier `services-card-reference.html` attachment is a historical design +reference, not the review surface. Use the live frontend link above. The updated +application passed 98 focused tests, TypeScript checking, lint and the production +build. The local server is serving the changed application modules. Signed-in +visual verification remains unavailable because no browser is connected. + +The pattern is **a grouped disclosure card**: a collapsed service summary, nested +individual connection cards, and a full detail page. The two states below are +views of the same card: + +```text +COLLAPSED EXPANDED +┌────────────────────────────┐ ┌─────────────────────────────────────┐ +│ OpenAI │ │ OpenAI │ +│ 3 connections │ │ 3 connections │ +│ │ │ │ +│ llm-openai · Automatic │ │ llm-openai · Automatic │ +│ Would use Work account │ │ Would use Work account │ +│ Checked 20s · for you │ │ Checked 20s · for you │ +│ │ │ │ +│ Latest: Codex dev · 2m │ │ ▾ Hide connections │ +│ │ │ ┌─────────────────────────────────┐ │ +│ ▸ 3 connections Open ↗ │ │ │ Personal production · You │ │ +└────────────────────────────┘ │ │ Needs reconnect Open ↗ │ │ + │ └─────────────────────────────────┘ │ + │ ┌─────────────────────────────────┐ │ + │ │ Work account · Acme │ │ + │ │ Selected for llm-openai Open ↗ │ │ + │ └─────────────────────────────────┘ │ + │ ┌─────────────────────────────────┐ │ + │ │ Sandbox · You │ │ + │ │ Exact calls only Open ↗ │ │ + │ └─────────────────────────────────┘ │ + │ Open service ↗ │ + └─────────────────────────────────────┘ +``` + +The diagram illustrates the full target design. The live implementation includes +actual connection slugs, saved state, metadata and detail links; evaluated routing +and per-service last-caller summaries still require the backend contracts below. + +## 1. A small vocabulary + +| Term | What the user means | +| --- | --- | +| **Service** | A configured service with an identity, address and full details. Existing UserService records keep this name. | +| **Address** | The slug/API address a caller requests. It may resolve automatically, target an exact service, or select a pool member. | +| **Connection** | A particular configured instance inside a service card: for example Personal production, Work account or Sandbox. It retains its existing UserService ID and full details. This UI label does not create or merge OAuth grants. | +| **Source** | Whose credential supplies a request: Personal, a named organization, or NyxID platform. This is separate from the service's owner. | +| **Pool** | An existing collection of member services with one callable address and a selection strategy. | +| **Origin** | How this service was created. | +| **Variant** | A separate service explicitly created from another service. Its origin records that relationship. | + +A catalog service is a template and catalog identity; some catalog addresses also +have execution behavior. Grouping cards by a catalog does not create a new callable +address, fallback policy, pool, shared history or readiness status. + +## 2. Services home: collapsed service groups + +Cards are the primary and default presentation. One top-level card represents one +service identity and contains its visible configured connections. The initial +view is collapsed; the count makes the contents discoverable. Use **Connections** +in the UI rather than **Duplicates**: these may be intentional accounts, endpoints +or variants, not redundant records to delete. + +Search, Owner, Needs attention, Kind (Services/Pools) and Add act on this card +collection. Search includes nested connection names and addresses. A connection +match keeps the parent visible and marks that there is a match inside; it does not +create a detached result row. Apply permissions before grouping, counts and search. + +### Grouping and identity + +- Group catalog-backed configurations by their explicit shared catalog service + identity. Do not group every OpenAI product together solely by provider name. +- Custom services stay separate unless they have an explicit common service/group + identity. A copied name, slug suffix, matching URL or shared credential is not + enough. Explicit variant provenance remains a relation even when a variant no + longer belongs to the same service identity. +- Personal and permitted org connections for the same service appear inside the + same card; each nested card labels its own owner. A parent group has no single + credential owner. Disabled connections remain nested with their state; the + count means configured visible connections, not working connections. +- Pools remain separate objects/cards and can contain members from several + service groups. Grouping connections never creates membership or enables drag. +- Preserve immutable IDs, addresses, approvals, scopes and histories for every + nested connection. No merging, deletion, automatic source preference or + inheritance happens merely because the cards are grouped. + +### Three levels of disclosure + +| Level | Content | Interaction | +| --- | --- | --- | +| **Collapsed service card** | Name/icon, description, visible connection count; an explicitly labelled canonical-address summary when one exists; a scoped latest request | Click the count/chevron to expand, or Open service | +| **Expanded service card** | Same header; named individual connection cards with owner, exact address, credential/route state and latest caller; canonical source controls scoped to that address | Compare or expand individual connection details inside the card; open a connection | +| **Full service / connection** | Complete Route, Activity and Details; full configuration, requests, changes, origin and related services | Service title opens the group; a nested Open action opens that exact connection | + +The service card expands vertically or takes more columns where space allows. +Its border encloses all nested cards and controls. Neighbors reflow in stable +reading order. Mobile uses the available width with a single column of nested +connections. There is no full-row detail area outside the parent border and no +separate inspector below the grid. + +Expansion is independent: users can compare several expanded service cards. +Keep expansion for the current browsing session and restore it on return from +full details; a fresh visit starts collapsed. This is presentation state, never a +routing preference. Show a bounded first set for unusually large groups, with +**Show all N connections** expanding inside that card or opening its full service +view. Never silently omit connections or make a hidden subset look like the total. + +### Resolution and last use belong to named targets + +A group is a browsing container. When a real canonical address exists, display +it explicitly, for example **llm-openai · Automatic**. Any **Would use Work account** +label belongs to that address and includes caller, operation and evidence age. +An alternate configured connection is not automatically a fallback candidate. +Distinguish **Selected for llm-openai**, **Preferred personal source**, **Exact calls +only**, **Disabled**, and pending verification using server facts. + +Without a real canonical address, the group shows its connection count and +**Open a connection**; it does not acquire an invented Automatic mode or API URL. +Its latest visible request may summarize the authorized group feed only when it +names the actual connection. A canonical latest-request summary labels that +address. Each nested connection shows its own latest request. Missing evidence +says Not recorded; a group never receives a blanket Ready status. + +**Change source** edits only the named canonical address. Each connection keeps +an Open/Copy exact address action. Platform offerings appear only when real and +permitted; catalog branding never creates a fake platform connection in the count. +Auto-provisioned platform instances retain their mutation restrictions. Disabled +duplicates remain distinct by ID; deleted services move to authorized archives. + +## 3. Click into the service: Route / Activity / Details + +The parent title and **Open service** open the complete service group. Route shows +its actual canonical behavior, when present, and all its connections. Activity +can aggregate authorized requests/changes only with the target connection named +on every event. It does not create a merged history or fabricate group authorship. +Details includes catalog metadata and links to each concrete configuration. + +A nested connection’s title/Open action opens its own full page, scoped by its +immutable service ID: Route, Activity and Details belong only to that connection. +Keep a breadcrumb **Services → OpenAI → Work account**. The group and concrete +views visibly state their scope. **View all activity** retains the scope of the +card it was clicked from. Returning restores filters, scroll and expanded cards. +All permitted data is available by clicking in; the collapsed state loses no +configuration or history. + +### Route: what will a call do? + +Show the requested address first. Then one short resolution summary, source +selection, and a **Why this source** disclosure. + +```text +OpenAI llm-openai +Route Activity Details + +Selection Automatic Change +For You · POST /v1/responses +Would use Acme · Work OpenAI +Checked 20 seconds ago +Paid by Acme · org wallet; provider charges to Acme’s key + +Why this source + Personal Needs reconnect + Acme Selected · credential and route checks passed + +Last completed Codex dev · Personal · 2 minutes ago +``` + +The platform row is absent in this example because no actual permitted platform +source exists. An existing broken personal service stays visible with its repair +reason; it is excluded from the usable set. A configured platform binding that +later loses availability remains a repairable record in that service's details, +but is not offered as a usable fallback or selectable source. + +**Do not use a generic “Ready via [source]” badge.** Distinguish these statements: + +| Label | Evidence it requires | +| --- | --- | +| **Saved · Not verified** | Configuration exists; upstream validity has not been established. | +| **Would use Acme · checked 20s ago** | Server evaluation for this caller, operation, policy version and current route/credential state. It predicts selection, not upstream success. | +| **Verified 2m ago** | A supported credential/operation check with version-bound evidence and a stated scope. | +| **Last completed 2m ago · Codex dev** | An actual completed execution record; not credential preparation or response headers alone. | +| **Needs reconnect / Node offline / Approval required** | A specific actionable state. | +| **Unavailable** | Evaluation found no permitted usable source; the action explains the repair needed. | + +“Working” cannot mean a permanent guarantee about a third party. The server must +validate authority, configuration and credential preparation at request time, +perform supported refresh, check transport and payment requirements, and exclude +known unusable sources. It must show verification evidence separately. Unknown +upstream health stays unknown; no generic active flag becomes proof of validity. +A read-only check never dispatches a business operation, advances a pool counter, +refreshes a credential, reserves money or grants approval. If such work is needed, +it reports **Refresh required**, **Verification needed**, or another pending gate. +A dedicated supported Verify action is separate and discloses any metered probe. + +The target's **working-only rule** is explicit: Automatic and pool selection do +not select a credential merely because it can be decrypted. They require +provider/operation-appropriate validity evidence: a completed OAuth authorization, +successful refresh, supported validation, or a qualifying recorded success bound +to the same credential, endpoint and configuration version. No arbitrary past 200 +proves every operation will work. Legitimate no-auth services use route/operation +checks appropriate to them. + +**Missing evidence is not failure.** Execution performs a supported safe check +when possible. If a higher-priority candidate still cannot be assessed, return +**Verification required**; do not replace its identity with an org/platform +account merely because evidence is absent. Known unusable candidates can be +skipped under the saved policy. A pending gate blocks a definite preview. +Round robin/Weighted select only within the working set explicitly approved for +that pool; their editor discloses excluded/unverified members before activation. +For Priority, an unassessed higher member requires verification before proceeding +past it, unless the user explicitly excludes it from the pool. + +Evidence includes its age and provider-defined validity. Credential/configuration +changes, known revocation and expiry require revalidation. An adapter may require +a freshness check, but elapsed time alone never marks a credential broken or +silently changes the selected identity. Show **Re-check required** and perform a +supported safe check; if unavailable, stop with an actionable state. Verification +still cannot guarantee that the third party accepts the next business operation. +An adapter may impose an age-based re-check only when it supplies a safe check. +For unsupported providers, version-bound evidence does not expire by age alone; +actual token expiry, configuration changes and classified failures still matter. + +For providers without a safe check, offer **Use exact service** with a generated +exact URL/agent configuration, or an explicit user-requested first real operation. +This authenticates and executes once through that chosen service, labelled +unverified, and can produce qualifying evidence. It is never a hidden business +probe or an automatic exception to working-only selection. Exact requests still +enforce authorization, known credential failures, route, approval and billing. +Setup must show this path rather than leaving a newly added key stuck at Saved. +While unverified, Copy request and generated agent configuration must use the +server-provided exact target/selector, not the unresolved Automatic slug URL. + +Default context is **You**. An inspectable agent/application context is available +only when the backend can evaluate its real scope and bindings and the viewer is +authorized to inspect it. Otherwise show recorded caller facts in Activity. +Changing this context does not impersonate the caller, execute a request or save +a preference. Advanced operation checks belong on the full page; a generic page +must not silently assume that permission for one operation grants all operations. +An unresolved higher-priority candidate prevents a definite selection preview: +show the pending check instead of claiming a lower-priority source will be used. + +### Choosing the source + +**Change** opens a focused editor with two choices: + +- **Automatic**: personal → named eligible organization(s) → actual platform + offering. Show only real authorized candidates. Users may exclude optional org + or platform fallback; the remaining source order is fixed and visible. +- **Use a specific source**: select a permitted concrete personal/org service or + the actual platform offering. This is strict: if it fails, return its error. + Selecting Platform works even while a personal service is usable. + +Platform joins Automatic only when an actual offering is available, its use is +permitted, and the payer already has explicit billing authorization covering this +route. Otherwise the editor requires opt-in with payer/pricing shown. Catalog +visibility or an existing wallet does not supply that consent. Migration never +enables new platform charges implicitly. Excluding a source removes it without +reordering the remaining tiers. + +The editor shows **Applies to: your calls to this address**, the actual payer and +any changed pricing before Save. A caller's explicit exact target or agent +binding remains stronger than a saved preference. Org-owned settings require +org write permission; their editor names the affected org scope. Do not silently +save a personal choice as an org-wide setting. A per-call exact selection is +supported; Copy request uses the existing UUID/exact-call form or explicitly +includes `_nyxid_via` in the slug URL. +Hard constraints intersect: an exact request and an agent binding that conflict +produce an error, rather than one silently overriding the other. + +Within a source tier, a user chooses a preferred concrete service when several +accounts are possible. Preserve an existing exact same-catalog match as the +migration default; select a sole compatible account when unambiguous. Do not pick +an arbitrary database record or move between unchosen accounts. An unresolved +account choice returns **Choose a source** instead of silently charging platform. +For multiple orgs, show the configured org order (initially primary org, then a +stable saved order); edit that order with explicit move controls. Dragging service +cards never changes source order. Intentional member failover belongs to a pool. + +### What the API must enforce + +Connect/Reconnect returns to the same service and starts supported safe validation. +The card progresses from Saved/Checking to Verified or a specific repair state. +A metered verification needs its own disclosed action. A repaired personal source +is preferred on the next Automatic call. Adding it does not change pool membership. +When no source qualifies, the Route panel shows Connect, Reconnect or Check as +appropriate; execution remains unavailable until the server requirements pass. + +Canonical Automatic is a versioned, explicitly enabled address contract, not a +new interpretation applied to every catalog slug during a UI rollout. A viewer +gets its entry when it is enabled and has at least one visible personal/org +candidate or an included, authorized platform offering. A saved policy or prior +activity keeps its entry visible when candidates disappear, as Unavailable. +Discoverable catalog entries with no such configured route stay in the catalog. +New calls to an enabled canonical contract with no usable candidate return its +structured error; they never fall through to a hidden legacy platform source. +Legacy personal connections are explicit migration candidates, not a second +resolver consulted after the new one fails. + +Existing exact/custom/pool address behavior is preserved until an explicit +migration. The migration preview names the affected addresses, agents, source +policy, evidence gaps and payer; it cannot enable Automatic while required +verification is unresolved. New setup likewise offers Automatic only after its +candidates and fallback choices are reviewed. Unsupported verification keeps a +usable exact address/configuration available; it does not silently move calls to +another account. Newly enabled tracking does not make existing traffic eligible +for new charges or force all old keys into a first-day fallback. + +Inventory legacy implicit platform traffic separately, including callers with no +service row. Use final credential class plus legacy route markers to identify it; +a missing concrete service ID alone does not prove platform use. Moving these +callers onto the new contract requires explicit platform billing authorization. +Retiring implicit access without it is a deliberate, announced migration cutoff, +not covered by the preservation promise for existing exact/custom/pool contracts. + +For an enabled canonical Automatic address, every call performs these steps: + +1. Resolve the address contract and authenticated caller. Apply caller-wide scope, + consent, operation and exact approval/binding restrictions. +2. Evaluate the selected personal candidate, then permitted organization + candidates, then an actual platform offering. Each must independently pass + account/operation compatibility, credential, transport and billing checks. +3. Skip only classified candidate failures that the saved policy permits, such + as a missing credential, terminal refresh rejection, or no permitted online + node route. Keep a safe reason. Missing/stale evidence requires checking or an + explicit choice, not fallback. Caller-wide denials, unresolved explicit account + choices, database/KMS failures and integrity errors are terminal. +4. Revalidate the selected identity and effective authority before dispatch. + If an exact approval is needed, obtain it for that identity. An existing + approval or agent binding for another identity never transfers to the fallback. +5. Dispatch once. Record the decision, target, verified caller and outcome. If no + usable source exists, return a structured error before any provider call. + +No legacy fallthrough may select a source excluded by the terminal decision. +An upstream timeout, error or interrupted stream after dispatch is that request's +outcome. It does not trigger replay under another account. Subsequent independent +calls evaluate again; a repaired personal source regains precedence in Automatic. +Do not globally invalidate credentials from an arbitrary resource 403, 429 or +provider outage. Candidate evidence is bound to identity, version and scope. + +Canonical catalog addresses are explicit server-reported contracts. Exact UUID/ +`_nyxid_via` selection, custom addresses and pool addresses retain their semantics. +Custom/pool collisions with catalog slugs require explicit migration; the UI must +not relabel existing traffic Automatic. A matching canonical personal record can +be the preferred candidate, but keeps its UUID, full card, exact-call link and +own history. There is no newly invented API address for a visual provider group. + +## 4. Pools: ordering lives here + +A pool has a self-contained card beside the service cards. Its expanded card +contains its strategy and member services; the full pool view’s Route tab +contains the same controls with complete member details. Current ownership rules +remain: all members must belong to the pool's owner. Provider similarity alone does not prove operation, +protocol, account or approval compatibility. + +- **Round robin**: selects among eligible members per request. +- **Weighted**: selects among eligible members with the configured weights. +- **Priority**: selects the first eligible, usable member in saved order. + +All three strategies use the same working set; their choice within it differs. +Preserve existing strategy/weights while adding the shared eligibility checks. + +Only Priority displays drag handles on member services inside that pool’s +expanded card or full view. The card itself never becomes draggable. The user +moves member services, reviews the new order and saves. Keyboard Move up/down +controls do the same thing. Changes name the pool, actor and version and appear in its change history. Unsaved edits +do not affect traffic; concurrent edits cannot silently overwrite one another. + +```text +Production AI · owned by You +Strategy: Priority + +1 OpenAI primary You Needs reconnect · skipped +2 OpenAI backup You Credential and route checks passed +3 OpenAI spare You Disabled · skipped + +One eligible member for the checked operation. +``` + +The saved order includes broken/disabled members so users can repair them. The +effective eligible set excludes them. Preview never consumes the round-robin +counter. No pool leaves its member set for a global org/platform fallback. A pool +member may already have an explicitly configured platform credential binding; +that remains the member's binding, subject to its own authorization and payer. +Priority is backend work: “first active row” is insufficient. + +## 5. Activity: who used it, and what happened? + +**Activity** has Requests and Changes filters, with Requests selected initially. +They share context and visual structure but retain separate records and clocks. + +Scope is named at the top: **Through this address** for a canonical address/pool, +**Across visible connections** for the service group, or **Handled by this +connection** for a concrete service. Grouped events retain their concrete target +labels and current disclosure checks; the group is not an execution identity. Canonical Changes records +that routing policy's edits; concrete service Changes records its own edits. +Selecting a source opens that concrete connection’s full view with a back breadcrumb. Do not merge a canonical address's policy, a candidate's history and +other same-catalog services into one unnamed timeline. + +A request entry shows **time · verified caller · operation · outcome**. Expanding +it shows: + +- Requested address and mode at that time. +- Actual selected service and source, credential override if applicable, and node + or pool member used. Selected-but-not-dispatched is distinct from executed. +- Safe skip reasons, actual payer, start/end time and request ID. +- The evidence captured for that request, not today's routing preview. + +Example: **Codex dev · Agent key → production-ai → OpenAI backup · Personal → +Completed**. Opening the pool or the member reaches the same execution record. +One request is counted once, regardless of routing or billing event count. + +Caller labels come from verified auth: **Alice · Session**, **Codex dev · Agent +key**, or **Release dashboard · Application · on behalf of Alice** when those +identities were authenticated. A user-assigned key name does not prove a particular +executable was running. User-Agent, service creator, provisioning app and key +owner are never substitutes for caller attribution. Generic keys show their +recorded name/identity with **Application not identified** when appropriate. + +**Latest request**, **Last completed request** and **Last change** are separate. +A failed latest request must not erase an earlier completed one. Outcomes include +in progress, completed, failed, denied, disconnected and unknown. HTTP completion, +stream termination and WebSocket closure need protocol-specific rules; a 200 +header or successful upgrade alone is not completed usage. “Completed” describes +the recorded transport/operation outcome, not proof of a provider's business +result. If a protocol cannot establish completion, keep unknown explicitly. + +No data reads **Not recorded**, with **Recorded since [date]** when coverage is +partial. Neither a shared credential's `last_used_at` nor missing retained events +justifies “Never used.” Projections expose an observation window and update lag. + +Visibility is enforced by the server before pagination, counts or summaries: + +| Viewer | Request activity | Change history | +| --- | --- | --- | +| Personal service owner | Authorized activity for that service | Existing owner access | +| Scoped org admin | Activity within permitted org resources | Existing write + resource scope | +| Org member | Their own authorized requests, labelled **Your latest request** | Restricted under current history rules | +| Org viewer | No execution activity through this feature | Restricted under current history rules | + +An org member's summary must be computed for that member; filtering a global +latest row is insufficient. Visible relationships, actors, targets and historical +snapshots need current disclosure checks too. Do not leak hidden caller names, +slugs or counts through provider headings, search, exports or related items. + +## 6. Details: metadata, origin and variants + +Preserve existing endpoint, protocol, auth method, safe headers, identity +propagation, node routing, catalog docs/capabilities/limitations, ownership, +credential binding, pricing and scoped-agent information. Show a compact summary +first, with technical sections expandable. Secrets retain existing protected +flows and never enter activity/history payloads. + +**Origin** states who created the service, when and through which recorded +channel/application. Older data says **Origin not recorded**. + +**Related** uses specific labels: + +| Relationship | What it means | +| --- | --- | +| Based on OpenAI | Shared catalog template/configuration origin. | +| Created from Production OpenAI | A recorded derivation from that specific service ID. | +| Used by Production AI | Actual pool membership. | +| Shares credential with Work OpenAI | Shared credential reference, subject to disclosure rights. | +| Bound to Codex dev | Explicit agent service/credential binding. | +| Created through Release dashboard | Provisioning provenance, not last caller. | + +**Create variant** is the explicit future action for a spin-off. It opens a form +prefilled from permitted configuration, shows what is copied, requires a new name/ +address and explicit credential choice, and creates a new immutable service ID +with `derived_from_service_id`. It does not inherit approvals, grants or agent +scope, and edits to the parent do not propagate. Record the creation and origin +in authorized histories; opening a parent/child preserves a breadcrumb to the +originating service and the card overview’s state. + +Credential replacement, rename and Enable/Disable are changes to the same service. +Similar slugs, shared keys, timestamps and `rotation_predecessor_id` are not +service lineage. Delete archives that ID's history; recreating the slug creates +a new identity and history. Authorized archived views retain safe snapshots. +Shared-credential edits already fan out through the existing journal, so there +is no extra “include related history” toggle. + +## 7. Backend delivery: the point at which this is solved + +The target flow requires backend work. A new layout alone cannot deliver it. + +| Capability | Present in checked main | Required addition | +| --- | --- | --- | +| Rich service cards/details, ownership and lifecycle | Yes | Collapsed service groups, nested connection cards and complete scoped views | +| Explicit platform credential binding | Yes; availability is metadata | Preserve this; add strict source preference to the canonical resolver | +| Service authorship and change journal | Yes, with restricted readers | Reuse it; add routing-policy/pool-order/variant events where not recorded | +| Working personal → org → platform fallback | No common resolver; an unusable personal match can return early | Shared resolver, versioned policy, consistent preview/discovery/execution and typed terminal decision | +| General upstream verification | No; Codex has a specific flow | Truthful check states; supported evidence bound to credential/configuration version | +| Pool Priority | No; RoundRobin and Weighted only | Authorized operation-compatible eligibility, Priority strategy and versioned order edits | +| Who last used this exact service | Partial audit fields, no complete correlated view | Complete execution attribution and authorized summaries/feed | +| Service spin-off lineage | No general derivation edge | Explicit variant creation and immutable origin relationship | + +Deliver in this order: + +1. **Contracts and resolver.** Record address identity, policy ownership/version, + request context, saved org order, candidate eligibility, typed skip/stop reasons, + approval and billing gates. Evaluation is read-only; execution revalidates and prepares. + All proxy paths, approvals, MCP/discovery and UI consume compatible decisions. +2. **Execution activity.** Mint a request ID at ingress. Thread it through + selection, audit and metering. Record requested target/slug snapshot, concrete + service/catalog/pool IDs, source, actor user, API key, authenticated application/ + delegation/child credential IDs when present, credential class, node, payer, + dispatch state and terminal outcome. Separate protocol completion from headers. + Pre-dispatch errors retain an execution record with no executed target. +3. **Reliable read models.** Build indexed, authorized recent-request queries and + rebuildable latest/latest-completed projections, including per-actor org views. + No N+1 audit scans. Persist terminal evidence with bounded retry/reconciliation; + a dropped fire-and-forget update must leave an explicit gap, not a false success. +4. **Pool and provenance writes.** Add real Priority semantics, compatibility + checks, concurrency-safe order saves and explicit variants/origin. Reuse safe + history writers and their authorization rules. +5. **The consolidated UI.** Bind collapsed/expanded service groups, nested + connection cards and their full scoped views to those contracts. Metadata/history improvements may ship earlier with accurate + no-data states; Automatic/source preview and Priority controls ship only with + their execution guarantees. + +The execution contract can extend existing audit events and projections; this +proposal does not require a second competing audit log. New audit rows must use +the existing tamper-evident append path. Changes stay in the service journal. +Platform absence, allowed fallback, final payer and caller visibility come from +the server. `platform_key_available`, row ownership or a last-used timestamp is +never enough for the frontend to reconstruct these decisions. + +## 8. Acceptance scenarios + +| Scenario | Required result | +| --- | --- | +| First visit with three OpenAI configurations | One collapsed OpenAI card says 3 connections; no three duplicate top-level cards. | +| Expand one service card | Its own border encloses all visible connections, their resolution/last caller and metadata; no detached data area. | +| Open Work account inside OpenAI | Exact connection identity, configuration and history; breadcrumb returns to the expanded group. | +| Different service IDs happen to share a provider name | Separate groups unless an explicit common service identity exists. | +| Hidden org connection shares this service | It contributes no count, search result, caller or history to an unauthorized viewer. | +| Keep several cards expanded | Each retains its own identity and controls; presentation changes do not change routing. | +| Click into a card or View all activity | Open that service’s full view; returning restores filters, scroll and expanded cards. | +| Expand a pool and choose Priority | Its member ordering stays inside the pool card/full view, with no global card drag. | +| Personal source works; org and platform also exist | Automatic uses personal. | +| A personal key is merely saved and org has current valid evidence | Check personal safely when supported; otherwise Verification required. Missing evidence alone cannot switch the caller to org. Offer an explicit exact/source choice. | +| Every configured credential is unverified and cannot be safely checked | Verification required for Automatic/Priority; setup provides an exact-call configuration and explicit first-request path. No hidden bootstrap or account substitution. | +| Existing keys lack the new activity history on rollout | Existing address contracts continue; Automatic migration requires a reviewed policy and qualifying evidence. No first-day outage or paid fallback. | +| Previously valid evidence needs a freshness check | Re-check or return a pending gate; age alone never marks failure or switches identity. | +| Personal refresh is terminally rejected; permitted org source works | A new Automatic request uses org and records why personal was skipped. | +| Personal and org unusable; actual permitted platform source works | Automatic uses platform only if allowed; payer/pricing are explicit. | +| No source qualifies | Structured error before provider dispatch; UI shows an actionable unavailable state. | +| No platform offering exists | No platform row, selector option or phantom fallback slot. | +| User explicitly selects platform while personal works | Platform is used; strict failure does not silently switch to personal. | +| Multiple unchosen accounts in one tier | Ask for a preferred account; no arbitrary database order or paid escape. | +| Agent binding or exact approval names a failing target | Honor its constraint; no cross-identity fallback. | +| A custom/pool slug collides with a catalog slug | Preserve current semantics pending explicit migration. | +| User enables Priority and reorders a pool | Only that pool's saved member order changes; broken members are skipped by real eligibility. | +| Preview a pool during concurrent traffic | No counter mutation and no guaranteed-next-member claim. | +| Upstream write timed out after dispatch | Record failure/unknown delivery as appropriate; do not replay under another identity. | +| Stream returned 200 then disconnected | Latest request shows disconnected; last completed does not advance. | +| Same credential is used by two services | Attribute use to the concrete executed service, not both. | +| One member is called directly and through a pool | Preserve requested entry point; deduplicate each execution by request ID. | +| Org member opens service activity | Their own latest request; no other actors or restricted change history leaks. | +| Rename, rotate key, delete and recreate slug | Rename/rotation keep history; recreation has a new immutable identity. | +| Create a variant | Explicit origin edge; no inferred lineage or inherited execution authorization. | +| Only legacy credential last-used data exists | Not recorded; no invented actor or success. | + +## 9. Adversarial review decisions + +Fable challenged the first draft against the actual resolver, pool and audit code. +The 25 September review settled the address identity, source resolution, activity, +privacy and origin contracts. The user’s 27 September direction supersedes the +review’s list-first/side-inspector presentation: use self-contained service cards, +one collapsed group per service with duplicate/configured connections inside, +individual expansion and a scoped full service/connection view on click. +Route / Activity / Details remain the full view’s structure. This grouping/card +revision was not separately reviewed by Fable; the user’s latest direction supersedes the earlier flat-list grouping +recommendation. It retains the reviewed execution and disclosure constraints. + +The review also established that last-caller needs correlated concrete execution +records and that org use rights do not grant org history rights. The final contract +adds per-viewer summaries and protocol completion rules. Priority must check usable +members, not merely active rows. Platform fallback is never promised from catalog +availability alone. The full target includes automatic resolution; a metadata-only +interim release is explicitly insufficient to call the original routing problem +solved. + +Fable's closing review found a first-call/migration trap in strict verification. +The final decision keeps working-only Automatic while treating missing evidence +as a pending gate, never as permission to switch accounts. Supported providers +validate during setup; unsupported ones get explicit exact-call setup and a +visible first-request path. Existing address contracts remain until a reviewed +migration. OAuth authorization counts as validity evidence. Freshness rechecks +never silently change identity. This preserves the user's requirement without +adding an invisible unverified exception to Automatic. + +The review also fixed platform billing opt-in, canonical entry/no-row behavior, +activity scope, and the org payer example. Working notes and the alternatives +challenged in review remain in `services-consolidated-fable-review.md`. + +**Final Fable disposition: review closed; G1 and G2 resolved.** Its three +nonblocking follow-ups are incorporated: inventory and disclose the legacy +platform consent cutoff, copy exact targets while unverified, and require a safe +adapter check before age alone can trigger mandatory revalidation. diff --git a/docs/plans/local-routing-preview.md b/docs/plans/local-routing-preview.md new file mode 100644 index 000000000..a1c4d6769 --- /dev/null +++ b/docs/plans/local-routing-preview.md @@ -0,0 +1,404 @@ +# Local routing preview with production metadata + +Refreshed 1 October 2026 from main `be1883bd` (frontend v0.39.0), including +priority failover, round-robin/weighted rotation, operation-scoped pool health, +and the latest service icon registry. +Grouped cards and service overview headers show the catalog icon; a group with +one connection also uses its custom icon. Each connection row shows its own icon +override, with the service glyph as fallback. Auto-connected and custom services +can resolve their glyph by service slug without a catalog identifier. +The prior preview is preserved in stash `e71429e8d5d19a1668df4f2e2925abba37625f61`. +Updated 27 September: the actual React frontend now renders one collapsed card +per catalog service, with its real configured connections inside. The same +`GroupedServiceCards` component is used by the normal `/keys` grid and the local +production-data view at `/keys?view=routing`. This is application code, not the +standalone HTML reference. Current main's full detail pages, authorship/history, +org permissions and reconnect flows are retained. + +Pool integration validation: 102 focused frontend tests and all 6 backend +pool billing tests passed. The backend tests used an isolated MongoDB 8.0.16 +replica set, including a regression joining each attempt's service history to its +billing request ID. TypeScript, the production frontend build, changed-file lint +and whitespace checks passed. Signed-in visual review is still outstanding. + +Updated 2 October: collapsed service cards are 288px tall with two description +lines and the existing icon/name/count and footer navigation. The body shows a +billing summary (including disabled connections), pool +member count/strategy and failover, deduplicated agent-key count, last-use time, +and latest recorded edit. Source avatars sit at the body's bottom right. The +expanded table exposes per-connection billing, pool membership, access/use, and +edit history. Clicking billing selects the first billable connection; clicking +last edit opens the affected connection's history. No pool editor is mounted in +AI service cards. + +102 focused frontend tests, 14 backend billing projection tests, production/credential-accept +builds and changed-file lint passed for this revision. Signed-in visual review remains unavailable. +The backend now reports configured billing separately from execution availability; +the production-data preview uses published inventory metadata until that backend +change is deployed. Missing data remains unverified. + +Updated 5 October after an Opus 5.5 xhigh review: billing checks the service-wide +configuration first, then the connection's credential supplier. Unpriced services +show **—** even when a person supplied an API key. A billable service can contain +NyxID, BYOK and Unverified connections. The connection's fee is a separate fact. +See [the decision table and backend contract](service-billing-labels.md). + +Read-only live metadata verification through the actual classifier produced: + +- Anthropic: all 30 connections unpriced, so one dash on the card. +- Chrono LLM and Spotify: dash. +- Twitter: expected **3 NyxID · 1 BYOK**: the owner confirms the three personal + OAuth connections used NyxID's app. Production still omits their app source, + so the current preview reports three Unverified until the shared backend + resolver is deployed. It covers unmarked modern keys and legacy provider-token + provenance. Unresolved metadata must not be guessed as either NyxID or BYOK + from the presence of a token record. +- DeepSeek: one stored API-key connection on the supplied-key path, so BYOK. +- Custom MacBook SSH: dash. Three other MacBook connections refer to private + catalog entries unavailable through production discovery, so billing remains + Unverified until the backend insight projection is deployed. + +The backend additions expose OAuth app provenance on `/keys` and the service-wide +billing gate plus selected supplier on `/service-insights`. These are additive, +read-only changes. They have not been deployed to production. Twitter's currently +configured 0.05 credits/request platform-key price does not apply to OAuth under +the existing execution rules; this revision does not change prices or charging. + +The dash tooltip remains beside the icon/value with an 8px gap. Longer details +align below the content with viewport collision handling. The local frontend uses +production metadata; signed-in visual inspection is unavailable in this session +because no browser is connected. + +Validation for the billing correction: 110 focused frontend tests and four backend +metadata regressions passed, along with TypeScript, production/credential-accept +builds, changed-file ESLint and whitespace checks. The backend test target compiled. + +Run from `frontend/`: + +```sh +NYXID_ROUTING_PREVIEW=1 \ +BACKEND_URL=https://nyx-api.chrono-ai.fun \ +FRONTEND_URL=https://nyx.chrono-ai.fun \ +npm run dev -- --host 127.0.0.1 --port 4317 --strictPort +``` + +Open . If sign-in is required, open +. The production site's existing +CLI Authentication flow sends a fresh session to the local preview. The access +token stays in server memory for at most 15 minutes; refresh tokens are discarded. +The browser receives an opaque HttpOnly session. Restarting Vite or session expiry +requires signing in again. Do not use `?mock`; that is an unrelated fixture mode. + +The gateway accepts allowlisted metadata GETs, including `/service-pools`, +UUID-addressed pool details, candidates and health, candidate discovery, +service history and Codex connection metadata from current main. Key and node +detail reads require UUID paths. Pool mutation requests, execution, credential +reveal and other mutation endpoints are blocked, except for the authenticated, +same-origin `PUT /users/me/preferences/services` with a validated filter payload. The existing backend's human +`GET /keys` still performs its normal platform auto-provisioning/reconciliation; +the gateway does not change that server behavior. Logout clears the local session. +The gateway and routing-specific diagnostics are development-only. Grouped service +cards and filter controls are also used by the normal production frontend. + +## Live frontend walkthrough — 27 September 2026 + +1. Open **External Services**. Each shared catalog service appears once with the + real connection count. Custom services remain separate by immutable identity. + All groups start collapsed on a fresh page load. +2. Click the service title or **View N connections**. The parent card grows to + contain a compact comparison table: Connection/Slug, Classification, Status, + Configuration and Activity. Editors have a Configure action; every visible + connection has a History action. +3. Status reflects known restrictions or missing verification. Activity shows + the latest recorded configuration change; no successful route or last caller + is invented. +4. Click a connection to open its existing full detail page and History tab. + Returning to services preserves group expansion and search in this browser + session, scoped to the account. Refreshing starts collapsed again. +5. Organization and Service have separate named selectors. Search matches names, + slugs and owners. Only matching rows appear in an expanded card; the full + group count and Service details link preserve context. +6. Each collapsed service card shows its saved **Pool** name and strategy, plus + **Failover**: for example, `Up to 3 attempts`, `Off · single attempt`, or + `Pool disabled`. Multiple pools show the additional count and how many have + failover enabled. Hover/focus shows each policy and its pool slug. These + summaries describe configured policies, not successful health probes. +7. Click the pool summary to see priority/rotation, the pool slug, + eligibility/cooldown and billing per member inside the card. This inspection + is read-only. **Manage in Service Pools** opens the selected pool, selects its + owner and scrolls its expanded card into view. **Configure** and member/policy + editing live only in Service Pools. The real revision-checked editor allows + drafting in this production-data preview but disables Save and mutations. + +The service-grid changes passed 98 focused frontend tests, TypeScript checking, +targeted lint and the production build. The running Vite server serves the updated +modules and reports the production API URL in runtime config. No browser was +connected for signed-in visual inspection. + +## Card sizing and account filter defaults — 28 September 2026 + +Collapsed summaries now share a 256px minimum height with reserved description +space and aligned footers. Filters cover search, source, enabled/disabled state, +HTTP/SSH type and auto-connected inclusion. They apply to the normal grid/table +and the production-data preview. A partial group match shows only matching rows and +shows their count against the complete group. + +The backend implementation stores **Save as default** in +`users.profile_config.services_view`; see +[AI Services Architecture](../AI_SERVICES_ARCHITECTURE.md#service-cards-and-saved-filter-defaults). +The local production-backed preview can filter immediately. Account saving is +available only when its connected backend exposes this field on `/users/me`. +The backend addition in this worktree has not been deployed to production. +There is no local-storage substitute for an account save. + +Validation: 159 frontend/gateway/authentication tests and 21 backend profile +tests pass, including real MongoDB preference persistence and sibling-setting +preservation. TypeScript, targeted ESLint, Rust formatting and the frontend +production build pass. The updated live modules return HTTP 200. Signed-in visual +inspection remains unavailable because no browser is connected. + +## Readiness and execution boundary + +Pool health comes from the saved configuration and the selected operation. Eligible +means the member passed metadata/admission inspection; it is not an upstream probe. +Do not infer working credentials from `status: active`, recent credential preparation +or node presence. Failed inspection shows unverified health, including when cached +results previously said eligible. + +Priority pools use the saved failover policy and durable cooldown; round-robin and +weighted pools select once. A null policy uses priority defaults. Direct connection +slugs keep their normal semantics. The UI does not create a routing policy merely +because multiple connections share a service card. See [Service pools](../SERVICE_POOLS.md) +for retry safety, per-attempt billing and supported entrances. + +The production metadata gateway remains read-only for pools. Saving settings and +resetting cooldowns use the normal backend endpoints outside this preview. The +preview does not synthesize routes or use local storage as execution configuration. + +## Connection tables and standalone selectors — 28 September 2026 + +Organization and Service are standalone searchable multi-select menus populated +from actual records. Each selected value has its own removable pill. Selections +within a menu match with OR; the two menus combine with AND. Organization ownership +uses circular avatars; platform sources use the NyxID icon. +Additional criteria reuse the audit log's filter panel, Apply/Cancel actions and +editable/removable chips. The saved-default blob now includes +`organization_ids` and `service_group_ids` arrays; older singular selections +migrate to one-item arrays and empty/null selections to empty arrays. +Expanded cards contain a comparison table, with the exact owner, status, latest +change and slug of each matching connection. **Service details** opens +`/keys/services/{groupId}`: Connections (the same table with row disclosures) and +per-connection History. This full page retains all accessible siblings regardless +of list filters. Each connection links to its original configuration page. + +Multi-select validation: 81 focused frontend/gateway tests and 22 backend profile +tests passed, including real MongoDB persistence, legacy single-selection reads, +individual pill removal, combined selections and empty-result recovery. The +frontend production build, TypeScript and targeted lint passed. No browser was +connected for signed-in visual inspection. + +## Dense connection tables and reader history — 28 September 2026 + +Expanded cards now have a compact header and take the grid width for the connection +table; collapsed cards retain their 256px minimum height. Native view transitions +animate card resizing and the surrounding grid, with a reduced-motion fallback. +The standalone table uses the same component. Extra dates, permissions, provisioning +source, header names and other metadata open inside a table row. The separate +per-connection information cards have been removed from the service overview. + +Editors see targets, auth/routing summaries and configuration links. Members and +viewers see connection identity, ownership, state and activity, with History always +available for authorized connections. Direct detail navigation enforces the same UI +boundary. A failed history request never restores cached history. + +The matching backend changes (private configuration projection and scoped reader +history) are local to this worktree and **not deployed**. The production-backed +preview shows the frontend changes, but production still enforces its deployed +history policy until the backend change is released. The preview does not claim +health-based fallback routing or last-caller data. + +Validation for this revision: 131 focused frontend tests, including filters, +connection/navigation permissions and history caching; 127 backend tests covering +history (21), keys (74), user-services (14) and endpoints (18). Backend integration +tests used an isolated MongoDB 8 replica set, removed after the run. Production +frontend build and TypeScript checks pass. Targeted ESLint has no findings; full +ESLint has no errors and 27 existing warnings. Signed-in visual animation review +remains unavailable because no browser is connected to this session. + +Only one service card can be expanded at a time. Opening a new card closes the +previous card in the same transition. Expanding a service scrolls its header into the main content viewport after the +card animation completes. Collapse and restored expansion state do not trigger +scrolling; reduced-motion users get an immediate reveal. Connection names now +link to their full details page for every authorized reader, including org +members and viewers. A separate chevron opens the inline summary; Configure +remains editor-only. + +The filter card is sticky within the dashboard content viewport and contains the +Organization/Service selectors, search, additional filters, selected pills, +Personal/All services view switch, save/default controls, result count, collapse +and refresh actions. The card's measured height sets the expanded service's scroll +margin, including after pills wrap. Personal is the initial view without a saved +account default and excludes organization and platform sources. Selecting an +organization switches to All services; returning to Personal clears organization +selections. Saved account defaults retain the user's chosen view. Clearing other +filters preserves Personal versus All services. + +Validation for the Personal default, sticky filters, exclusive expansion and reader +navigation: 104 focused frontend tests passed. The 17 routing-preview tests also +passed after the final test typing correction. Production build, TypeScript, +targeted ESLint and diff whitespace checks passed. The live preview returns HTTP +200; signed-in visual review still requires a connected browser. + +Saved views now has a dedicated header control in the sticky filter card, with a +count, a preview of the saved account default and a click to restore it. Save as +default / Update default is visible alongside it; matching the saved default +shows a checked status. This retains the existing single account-default model. +Personal / All services is one pill showing the active source icon and label; +clicking it switches to the other view. No saved default or persistence behavior +was migrated. These changes use the supplied Billing screenshot as a visual +reference for AI Services; the separate Billing checkout is unchanged. + +Validation: 47 focused frontend tests passed across routing preview, keys and +service-view state; production build, TypeScript, targeted ESLint and whitespace +checks passed. No browser was connected for signed-in visual inspection. + +The Organization and Service triggers now align label, selection and chevron in +fixed columns, with the selected value right-aligned beside the chevron; dropdown +rows reserve consistent checkbox/avatar space. The sticky +filters have an opaque background above the card and a scroll-dependent shadow +in both themes. The background extends to the full dashboard scrollport width, +using measured gutters to hide borders and shadows from scrolled cards. When +stuck, the card keeps its rounded corners and hides Saved views and the entire +results/action footer. Controls stay on one line, with selected filter pills +below, capped at two full rows (three rows total). Extra selections scroll within +that area and snap to complete rows. Pill heights are 44px on mobile and 36px on +desktop; long labels truncate with their full text available on hover. No empty +pill row is rendered. Narrow viewports scroll the controls +horizontally. The Personal / All services pill remains alongside the filters. +Returning to the normal position restores the saved view controls and footer. +There is no fixed padding or opaque band below the sticky card. +The cover now uses an 8px backdrop blur and fades out over 28px below the card; +its mask softens passing connection borders alongside the background instead of +cutting them off at a horizontal edge. A layered shadow keeps the rounded filter +card visually above that cover. This is an overlay only and adds no layout space. +The Vite production build passed after this styling adjustment. +An expanded service's name/count and actions remain sticky below the filters +while its connection rows scroll. Its offset uses the measured filter height +plus the existing 32px reveal gap. It starts moving up when the third-last +connection reaches the header's lower edge, keeping the final three connections +clear as the card scrolls away. The release threshold uses actual connection-row +positions, excluding expanded metadata/history rows. Scrolling back restores the +normal pinned position. Only the table content clips to the card corners, so the expanded section +does not introduce a scroll container that prevents the header from sticking. +When the service header pins, an opaque cover fills its top gap and rounded +corner cutouts across the full scrollport, hiding connection text and borders +that have scrolled above the header. The +cover is absent before pinning and after collapse. Scroll/resize tracking is +limited to the expanded card and cleaned up on collapse. Validation: 54 focused +tests passed, including pin/return/collapse behavior and releasing the header at +the final three connection rows; production build and targeted lint passed. +The search field keeps the same border color and thickness on focus in both +themes. The full service overview now shows the matching catalog icon beside its +title, with the same globe fallback as custom-service cards. The 14 shared filter +control tests and 6 service-overview tests passed after these changes, along with +the production build, TypeScript and targeted lint. +Collapsed service cards no longer list connection names beneath Sources; the +names remain in the expanded connection table. Active search retains its labelled +Matches summary so users can see why a connection was included. +The service grid uses 24px gaps and expanded headers have 20px padding. Expansion +scrolls the dashboard viewport using the live toolbar height and viewport padding, +with an additional 32px reveal offset. This spacing belongs to the scroll position, +so it moves away during manual scrolling. Browser scroll anchoring is disabled +within the changing service grid. Reduced motion remains immediate. +Validation: 60 focused tests passed, including changing-toolbar-height scrolling, +hiding/restoring Saved views and footer, and selecting organization pills while +stuck. Shared data-table controls retain their normal wrapping layout. Production +build, TypeScript, targeted ESLint and whitespace checks passed. The preview is +serving the changes; no browser is connected for visual verification. + +## Inline billing and caller insights — 29 September 2026 + +Cards now show Sources, Latest request (or Your latest), and Billing. Expanding a +card shows a connection table with separate Access & requests and Billing columns. +The inline panels list permitted agent keys and overrides, the latest three exact +requests in 30 days, and the billing account with applicable rates. Billing's For +selector compares the viewer's default with a managed agent key. Configuration +and permitted change history retain their existing access boundaries. + +The implementation includes `GET /api/v1/service-insights` and exact request +attribution in the HTTP proxy, both LLM routes, and MCP. The endpoint is allowed +through the local preview's read-only gateway. When an older server returns +404, 405 or 501, the frontend reads existing agent-key inventories, credential +binding metadata and catalog prices. The comparison table shows key names and +configured scope, plus expected payer and configured rates. Its inline billing +flow separates credential supply, expected payer and NyxID charges. Pending or +failed price synchronization stays visible. The compatibility view does not +resolve per-agent billing or claim recorded use; exact caller history and the +managed-key billing selector require the new endpoint. Authorization and network +failures never trigger this fallback. Partial key inventories and unknown +credential overrides are labelled explicitly. +Older request history remains partial after deployment. Rates are current billing +previews; settled transaction history is not added by this revision. + +Open `http://127.0.0.1:4317/keys?view=routing` to review the running frontend, or +`http://127.0.0.1:4317/__routing-preview/login` for a fresh preview login. Browser +automation was unavailable in this session, so signed-in visual review remains +outstanding. + +Validation: 103 focused frontend tests and 30 backend tests passed, including +real MongoDB privacy, payer/override, exact request capture, metering, and rollup +checks. TypeScript, the production frontend build, Rust formatting, targeted ESLint, +and diff whitespace checks passed. Full ESLint has zero errors and 27 existing +warnings. The committed CLI wizard source-closure hash remains current. + +The compact sticky filter now preserves its expanded height in normal page flow. +This prevents shrinking scroll height from clamping the scroll position back +across the sticky threshold. Its visible surface, cover, service-header offset and +card reveal still use the actual compact height; the reserved flow space is +transparent and does not intercept clicks. A regression test models repeated +resize/scroll frames near the bottom of a short filtered list. +Configured agent-key names also appear on collapsed cards when exact request +history is unavailable. Validation for this revision: 124 frontend tests, +TypeScript, production build, and changed-file ESLint passed. The running preview +serves the updated modules; signed-in visual verification remains outstanding. + +## Billing and last-used layer — 30 September 2026 + +Billing, Last used and Agent keys now keep separate positions on collapsed cards +and stay visible in the expanded header. Each table row includes the last-use +summary too. Billing shows payer, rates and separate provider charges; last use +shows recorded layer, exact connection slug, caller/application and time. Clicking +an expanded header summary opens its corresponding inline row panel. The last-use +time has reserved space so a long caller name cannot hide it. All collapsed card +summaries share a 320px minimum height. + +The draft backend now projects the layer recorded by each exact request. A change +to today's credential binding cannot rewrite the historical source. Requests +denied before dispatch remain in history but do not count as use. The latest +dispatched request is queried separately from the three recent events, so repeated +denials do not hide it. Production must +deploy this response before its recorded layer can populate locally. The existing +credential timestamp is deliberately not substituted for exact connection use. + +Fixed the compatibility inventory parser: absent `expires_at` and `bindings_count` +mean no expiry and zero overrides respectively, per the deployed response contract. +An incomplete inventory is labelled as incomplete rather than showing zero keys. + +Validation: 131 frontend tests, TypeScript, production build and changed-file +ESLint passed. The running preview serves the updated summary and schema modules. +Signed-in visual verification remains outstanding because no browser is connected. + +## OAuth billing provenance — 5 October 2026 + +The preview at `http://127.0.0.1:4317/keys?view=routing` still targets production. +The draft backend now records the app source at successful authorization/refresh +and exposes verified `oauth_app_source` through `/keys` and service insights. +Older copied tokens are matched against their original token data without +decryption. Neither a connection ID nor the presence/absence of a retained OAuth +client ID establishes billing source. + +Production has not received these changes. Its missing source fields can leave +all four X OAuth rows Unverified after the frontend guesses are removed. The +expected `3 NyxID · 1 BYOK` requires backend evidence; local tests proving that +rendering are not production verification. Rates, charge lanes and free-credit +funding are unaffected by the provenance metadata change. diff --git a/docs/plans/references/services-card-reference.html b/docs/plans/references/services-card-reference.html new file mode 100644 index 000000000..cbd8df0ac --- /dev/null +++ b/docs/plans/references/services-card-reference.html @@ -0,0 +1,63 @@ + + + + + +NyxID · Grouped service cards reference + + + +
Design reference · Example dataCollapsed service → connections inside → full detail
+
NyxIDServicesPersonal workspace
+
+
+
Your workspace

Services

Your connections, together by service.

3 services · 6 connections
+
+
+

Try 3 connections on OpenAI, then open Work account. Its route, activity and details belong to that exact connection. All connections start collapsed inside their service card.

+
+ +
+ + + diff --git a/docs/plans/service-billing-labels.md b/docs/plans/service-billing-labels.md new file mode 100644 index 000000000..0b7b08e67 --- /dev/null +++ b/docs/plans/service-billing-labels.md @@ -0,0 +1,128 @@ +# AI Services billing labels + +Reviewed with Claude Opus 5.5 at xhigh on 5 October 2026, before implementation. + +The label answers two questions in order: does this service have NyxID billing +configured, and who supplied the selected connection credential? Ownership of the +connection (personal, organization or platform) is a separate fact. + +Product clarification: **free credit grants, allowances, promotions and wallet +funding never decide this label**. A service configured as billable by NyxID uses +platform billing unless the selected key or developer app is confirmed to be +supplied by the user or organization. `NyxID` is the compact card label for NyxID +platform billing. Grant-funded usage remains NyxID; a supplied key remains BYOK +even when a grant covers an additional NyxID fee. The amount charged, including +a zero wallet debit, cannot change either classification. + +| Service billing | Selected credential supplier | Label | +| --- | --- | --- | +| No configured usage charges | Any | — | +| Configured | NyxID key or NyxID OAuth developer app | NyxID | +| Configured | No provider credential required | NyxID | +| Configured | Confirmed person/organization key or developer app | BYOK | +| Configured | Unknown or unresolved supplier | Unverified | +| Configuration unavailable/restricted | Any | Unverified | + +The dash tooltip is exactly **Not billable by NyxID**. A provider may charge +separately. All connection categories appear in grouped cards; disabled connections +retain their configured category. A no-auth connection on a service with billing +configured is also NyxID; an absent connection price is separate from the label. + +## Backend evidence + +- `billing.service_billing_configured`: any configured charge across credential + classes, independent of caller rollout, health or wallet funding. +- `billing.credential_supplier`: `nyxid`, `own`, `none` or `unknown` for the selected + context. Restricted results omit it. Agent overrides use the override's metadata. +- `KeyResponse.oauth_app_source`: resolved `platform` or `byo` OAuth app source. + Both `/keys` and insights use `oauth_app_source::load`: explicit selection, + an observation from a successful OAuth exchange/refresh, or an exactly matched + legacy token copy. The lookup never decrypts or returns credentials. +- `UserApiKey.oauth_app_observation` records the actual resolved app source, + credential epoch and observation time alongside the successful token write. + A replacement epoch or newer authorization invalidates an old observation. + A disagreement with explicit selection remains unknown. The observation is + descriptive metadata and never changes execution's `credential_source`. +- Existing `credit_billing_configured`, `rates` and `charge_status` keep their + connection-specific meaning and never decide the service-wide label gate. + +Explicit platform binding selects NyxID even when an old personal key is retained. +Durable OAuth source wins over retained app hints. A connection ID, stored client +ID or unexpired token alone does not prove which app issued the grant. Older BYO +connections can lack embedded app credentials, and migration could copy an +unrelated app onto a key. Legacy token matching checks owner, provider, token +association and identical copied access/refresh ciphertext; it also handles +migrated rows carrying a connection ID. Missing, changed or ambiguous token +evidence remains unknown. Ciphertext stays inside the resolver. The execution +class `UserOwned` is also the legacy fallback, so it is not +proof of app ownership. For non-OAuth connections, a stored +API-key record follows the supplied-key path; NyxID master credentials are kept +in the catalog and selected by platform binding. A user binding without a stored +key is insufficient. Node routing alone does not establish credential provenance. + +## Production-data limits + +The current preview can classify unpriced services and supplied keys from existing +APIs. It cannot prove NyxID OAuth app selection until `oauth_app_source` or the +insight supplier field is deployed. Legacy OAuth rows can be resolved from an +exact provider-token copy. Unmarked modern rows acquire verified provenance on +their next successful authorization or refresh; reads do not trigger a refresh. +Missing or ambiguous records still need reconciliation. Missing +private catalog entries cannot be interpreted as absent billing. + +Connected-service cards and overview pages request the full accessible catalog, +including internal services omitted by the credential-setup catalog. Insights +cache keys include credential selection, OAuth provenance, owner and connection +pricing metadata, so changes to those inputs cannot retain an earlier label just +because the connection UUID is unchanged. Regression tests cover both cases. + +On the latest live read, Chrono LLM is present in both catalog variants with no +billing. These fixes cover reproducible stale/incomplete-data cases; they do not +establish which case produced the previously reported browser label. + +Live checks found Twitter with one supplied organization app and three personal +OAuth rows without published app provenance. Two carry a connection ID; the +disabled third uses legacy storage. The owner confirms all three used NyxID's app, +so the expected card, once supported by backend evidence, is **3 NyxID · 1 BYOK**. +Production omits the source field and returns HTTP 404 for `/service-insights`. +Removing client-ID and connection-ID guesses means all four OAuth connections +can show Unverified on that older backend. This is an explicit rollout limit, +not a claim that the user connected them incorrectly. Deployment supplies source +metadata for selected, observed or exactly matched connections; historical rows +without that evidence need a successful refresh or reauthorization. +Available history does not supply that missing source. The legacy token's exact +contents could not be inspected through existing public metadata APIs. +Anthropic, Chrono LLM and Spotify have no billing configuration and show +a dash. DeepSeek has a stored supplied-key connection and platform-only pricing, +so its label is BYOK with no applicable NyxID fee. + +## Twitter charging discrepancy + +The live Twitter configuration has `platform_billable=true`, a synced platform-key +price of 0.05 credits/request, no BYOK price, and no NyxID-only restriction. Existing +execution deliberately selects the BYOK lane for `NyxidPlatformOauthApp`. A missing +lane is uncharged. Consequently, the catalog platform-key price must not be shown +as the charge for an OAuth connection. This is a configuration/runtime discrepancy +with the intended product behavior, independent of the label correction. + +This discrepancy does not make a confirmed NyxID OAuth connection BYOK or +nonbillable in the card. Its service billing is configured and its selected app +is NyxID's, so its label remains NyxID. Rates and settlement must be verified +separately; grants are not part of that classification decision. + +This change does not mutate billing configuration or move OAuth into another price +lane. Charging shared-app OAuth could be addressed through an explicit rate and +NyxID-only restriction using the existing lane, or a separately reviewed change to +OAuth lane selection. Either affects money, legacy provenance and agent overrides; +it must not be hidden in a frontend label fix. + +### Healthy-connection provenance (owner-approved, 2026-10-05) + +Modern OAuth connections exchange and refresh with their embedded app when one is +stored, otherwise with the provider's app, and providers refresh only tokens issued +to the same app. A row with a `connection_id`, `status: active` and +`connection_status: active` therefore proves its app: `oauth_client_id` present means +BYOK, absent means NyxID. Failed, expired, revoked and legacy rows stay Unverified +until backend provenance (`oauth_app_source`) resolves them. Do not remove this rule +without the owner's sign-off; showing every healthy connection as Unverified is not +an acceptable rollout state. diff --git a/docs/plans/service-route-resolution-flow.md b/docs/plans/service-route-resolution-flow.md new file mode 100644 index 000000000..6d61f1196 --- /dev/null +++ b/docs/plans/service-route-resolution-flow.md @@ -0,0 +1,117 @@ +# Service routing, billing and dependency presentation + +Status: proposed flow. The current refresh implements connection metadata and +insight panels; it does not implement ordered cross-owner fallback. + +## One service, one explained route + +Keep the collapsed service card and its expandable connection table. A group of +connections is not automatically an executable route. Show a service call slug +only after the backend has an explicit route configuration for it; preserve +individual connection identity and direct addressing. + +Use three distinct terms: + +- **Connection order** selects the connection to execute. Default to personal, + organization, then platform when available. A saved custom order can put + platform first. Personal A and Personal B remain distinct connections. +- **Billing flow** explains the payer and rates after caller context and final + credential selection. Moving to another connection may change the payer. +- **Derived from** describes catalog/copy lineage. It has no routing or billing + meaning by itself. + +## Expanded card + +The card header contains a caller selector: **For: You / managed agent key**. +Verified application identity appears with a recorded request; an arbitrary app +name is not a credential or execution context. + +Directly below, show the expected result in one compact line: + +`Next: ChronoAI OpenAI · Bills: ChronoAI · NyxID rate: [effective unit price]` + +The table stays inside this card and makes the effective order visible: + +| Order | Connection / source | Route state | Credential | NyxID payer | Rate | +| --- | --- | --- | --- | --- | --- | +| 1 | NyxID OpenAI | Skipped · reason | NyxID key | Acting person | Applicable platform-key rate | +| 2 | ChronoAI OpenAI | Selected now | Organization key | ChronoAI | Applicable own-key rate | +| 3 | Personal A | Fallback | Your key | You | Applicable own-key rate | +| 4 | Personal B | Fallback | Your key | You | Applicable own-key rate | + +This is an illustrative layout, not a claim about live availability or prices. +Render only real, authorized sources; an absent platform connection has no row +or empty placeholder. If an existing visible connection becomes unusable, retain +it with a concrete skip reason. Do not expose inaccessible connection metadata. + +Source-priority controls are explicit. Drag handles reorder connections within +their pool; card-grid ordering does not configure routing. Pinning a connection +is a separate mode with no fallback, and does not masquerade as an agent +credential override. A credential override can change the credential class and +payer without changing the selected connection. + +Billing and dependency information expands in the existing single row panel: + +`Caller → selected connection → effective credential → payer → rates` + +Show ownership, source catalog, direct predecessor when recorded, and change +history under **Details / Derived from**. A common catalog association does not +prove that one connection was copied from another. Keep private configuration +editor-only; authorized readers retain permitted history. + +## Execution contract + +Persist the chosen source policy and pool member priorities as execution +configuration, independently of saved list filters. Add an ordered strategy and +caller-aware cross-owner eligibility; the existing pools only select enabled, +active same-owner members using round-robin or weights. + +The real execution path and read-only explanation must use the same candidate +eligibility rules. For the authenticated caller, check live service scopes, +organization access, platform grants, enabled state, credential availability, +applicable overrides, node availability and request capabilities. Use credential +validation evidence with timestamps; missing or stale evidence is unverified, +not proof of a working connection. Execution still validates/materializes the +credential and rechecks authorization before provider effects. + +Select the first eligible candidate in the saved order. If none is usable, +return a structured no-usable-connection error and safe reasons. Do not silently +use an unauthorized platform key. Exact connection calls retain exact semantics. + +After final credential selection, use the existing billing owner and pricing +resolvers. NyxID master keys charge the acting person's account; organization +credentials normally charge the service owner organization; personal credentials +charge the person. Shared OAuth applications and agent credential overrides +require the actual credential-class resolver, not a source badge heuristic. + +Funding is a separate order inside the resolved account: matching allowance +units, grant credits, then wallet credits. A platform → organization → personal +connection order is never an implicit permission to cascade charges through +unrelated wallets. Show unit rates before a request and actual coverage/debit +after settlement. Own provider charges can be separate from NyxID charges. + +Automatic failover must be bounded and replay-safe. Skip known unusable +candidates before dispatch; do not replay an ambiguous request after provider +effects or streaming have begun. Any real attempted upstream work can incur +cost; record attempts independently rather than promising only successful calls +are charged. Any permitted automatic switch to a different payer must be part +of the saved policy and visible in the table. + +## Preview versus recorded result + +The explanation API reports **Expected route now**, ordered candidates and skip +reasons for the selected caller. Reading it does not call providers, refresh +credentials, reserve credits or charge anything. Reuse execution policy logic +without stateful execution effects. The preview can change before a call. + +The request record is authoritative for what happened. Capture the route/policy +version, candidate attempts and reasons, selected exact connection, verified +caller/app identity, final credential class, billing owner, rate basis and +settlement reference. Show the latest three requests inside the card; full +history remains on the service page. Old events without these fields remain +explicitly incomplete. + +The current `/api/v1/service-insights` endpoint in draft PR #1685 explains exact +connections and recorded callers. It must be extended to explain a configured +route once the shared ordered resolver exists. Deploying that endpoint alone +does not create cross-owner fallback. diff --git a/docs/plans/services-consolidated-fable-review.md b/docs/plans/services-consolidated-fable-review.md new file mode 100644 index 000000000..d4f107c60 --- /dev/null +++ b/docs/plans/services-consolidated-fable-review.md @@ -0,0 +1,657 @@ +# Consolidated Services: Fable adversarial review (pass 1) + +Status: read-only design review of the seed design and of +`consolidated-services-flow.md` (25 Sep 2026). Checked against main `1b031c77`. +No code changed. Line references are to that commit. + +## 1. Verdict in one paragraph + +Ship **one row per callable address** with stateless provider dividers, not a +grouped "service block" hierarchy. A row is something an agent can actually +call today: a configured service slug or a pool slug. The catalog identity is a +sort key and a divider label, never a container with its own status, counts of +"connections", or a family-level "uses now". The inspector is a Sheet with three +fixed tabs, **Route / Activity / Details**, and it is the same component as the +full detail page. Before any of that is truthful, two backend facts must exist +that do not exist today: a per-request record that names the concrete service, +the verified caller and the outcome in one row, and a compact `last_request` / +`last_success` projection on `UserService`. Everything in the draft that reads +"Would use", "checked N seconds ago", "Check: POST /responses", "Use a specific +connection" or "As: application" depends on a resolver and policy store that +are still proposals; those controls must not appear in the first release. + +## 2. What the data can and cannot say (evidence) + +| Question the UI wants to answer | What exists | Consequence | +| --- | --- | --- | +| Which concrete service handled a request? | `proxy_request` audit stores `service_id` = **catalog id** for catalog-backed rows (`proxy_service.rs:3658`), plus method, path, `response_status`, `acting_client_id`, `connection_id` (`handlers/proxy.rs:4300`). The concrete `user_service_id`, `routed_via` and pool `chosen_user_service_id` are in a **separate** `proxy_routed_via_personal/org` row (`handlers/proxy.rs:599-650, 660-680`). No shared request id between the two rows. | "Who last used *this* service" cannot be computed for catalog-backed rows without a new field. The Agent Keys usage dashboard already keys `top_services` by that catalog id (`handlers/api_keys.rs:867-900`) and so cannot distinguish two OpenAI services. | +| Who was the caller? | `AuditLog.user_id`, `api_key_id`, `api_key_name` (`models/audit_log.rs`), `acting_client_id` in event data. `AuthUser.oauth_client_id` and `api_key_credential_id` exist (`mw/auth.rs:60,84`) but are **not** written to the proxy audit. | Person, agent key and delegated app are attributable from verified auth. Ordinary app tokens and Agent Key child credentials are not. | +| Last use? | `last_used_at` lives on `UserApiKey`, touched fire-and-forget at credential materialization **before dispatch** (`proxy_service.rs:2993-3000`) and also for agent override credentials (`3355-3362`). One credential can back several services (journal fan-out, `SERVICE_HISTORY.md`). | It is "credential last prepared", not "service last used" and not "success". Must be relabelled or hidden. | +| Last success? | Only derivable by scanning audit rows with `response_status < 400` (`handlers/api_keys.rs:862-865`), window clamped to 30 days. `usage_meter` rows carry `actor_user_id`, `api_key_id`, `credential_class`, `billing_owner_id`, `service_id` but only when `BILLING_ENABLED`. | No per-service projection. A list page cannot scan audit per row. | +| Who last changed it, and what? | `service_change_events` journal with per-service sequence, `service_slug` snapshot per event, actor kinds Person/ApiKey/ServiceAccount/App/System, safe field diffs; `created_by` / `last_change` summaries on the row; archived deleted histories (`handlers/service_history.rs:343-379`). | Solid. Readable only when `access.can_write() && access.allows_resource(id)` (`service_history/read.rs:47`), so personal owner or scoped org admin; members and viewers get nothing. | +| Lineage / spin-off? | `UserService.rotation_predecessor_id` points to the previous **UserApiKey**, not a parent service (`handlers/keys.rs:621-629`). `source` values: `user_created`, `auto_provision`, `codex_import`, `channel_onboarding`, `connection`, `telegram_*`; `source_id`, `source_app_id` + resolved `source_app_name`. No service-to-service edge. | There is no spin-off relation to render. Only origin facts and computed relations (same credential, same catalog, pool membership, agent binding). | +| What will the catalog slug do? | Resolver: exact personal slug, then personal pool, then legacy personal guard, then org walk in `primary_org_id` order (`proxy_service.rs:1766-1770`, mirrored by the approval-owner lookup at `2285-2330`); an unusable personal row errors with 400 instead of continuing (`2981-2986`); a missing row falls to the legacy platform master-credential path silently. `find_by_catalog_service_id` is an unordered `find_one` (`user_service_service.rs:788-802`). | The only deterministic statements today: an exact slug row executes itself; a pool executes one enabled active member chosen at request time; a catalog slug with no active row and `platform_key_available` falls to the platform key. | +| Is the credential working? | `status` (`active/expired/revoked/failed/refresh_failed/pending_auth`), OAuth `connection_status` derived from expiry (`unified_key_service.rs:4548`), `credential_missing`, `node_status`. Codex only: `metadata.verification_status` `saved/usable/reconnect_required` with `verified_at`, bound to token version, epoch, service version and endpoint (`codex_connection_service.rs:291-306`, `CODEX_CONNECTION.md`). AWS SigV4 probe at creation is not persisted. | Vocabulary is fixed by data: **Saved**, **Expired/Revoked/Failed**, **Missing**, **Verified ** (Codex only), **Not verified** (everything else). Never "Ready". | +| Payer? | `credential_class` and `billing_owner_id` on the meter; `BillingOwnerResolver::resolve_for_execution`; platform-key usage is billed to the acting person even on org-owned rows (CLAUDE.md Rule 5). | Payer must come from the server. Inferring it from the row owner is wrong for platform-bound org services. | +| Pools? | `ServicePool { user_id, slug, strategy: RoundRobin|Weighted, members[{user_service_id, weight, enabled}], rr_counter }`; `resolve_member` increments `rr_counter` on every resolution (`service_pool_service.rs:363-410`). `list_pools` is by owner id only. | A preview must never call `resolve_member`. Member array order already persists, so a `Priority` strategy is a small backend addition. | +| Agent pinning? | `AgentServiceBinding (api_key_id, user_service_id) -> user_api_key_id`; `ApiKey.allowed_service_ids`, `allow_all_services`, `allow_auto_connected_services` (server-expanded at auth time). | Scope membership and credential override are exact client-side facts. The auto-connected expansion is not. | +| Same slug twice? | `/keys` can return a disabled row and an active row with the same slug (`AI_SERVICES_ARCHITECTURE.md:279-282`); known gap 1 (E11000 on re-enable) and gap 2 (tombstone revival zombie). | Rows must be keyed by UUID and the disabled duplicate labelled. | + +## 3. The grouping decision, resolved + +**Choose: one default row per callable address, sorted by catalog identity, +with stateless provider dividers. "Flat" removes the dividers. No nested +"service block" and no cards-versus-grouped toggle.** + +Why the draft's grouped-by-catalog default loses: + +1. **Pools break the hierarchy.** A pool can mix members from different + catalog services. The draft says "real pool routes appear once" but never + says under which service block. Any answer is either wrong or an "Other" + bucket, which proves the container is not a real grouping. +2. **Custom services become one-row families.** Every custom service would + carry a header with "1 route, 1 connection". That is chrome, not + consolidation. +3. **The block invites the exact claim the user forbade.** "Latest visible + request" and "resolution for the visible caller" at the family level are + aggregates over routes with different owners, payers and privacy rules. An + org member would see Acme's latest caller; the draft's own privacy section + forbids that. +4. **"Automatic" mode does not exist.** The draft's Route has modes Automatic, + Direct, Pool. Only Direct and Pool are objects today. A family header is + the natural place for the Automatic verdict, so the hierarchy will grow one + before the resolver ships. +5. **The user's actual scan target is the address.** An agent config, a CLI + call and an MCP tool all name a slug. The list should be scannable by the + thing that appears in those configs. + +What the row-first list keeps from the draft: OpenAI still appears once as a +divider, its three addresses are adjacent, and each row expands to the +existing card. Consolidation is achieved by sort order, not by a container. + +## 4. Vocabulary (fewer words than the draft) + +The draft renames the catalog capability to "Service" and the `UserService` +row to "Connection". That inverts existing product language: Rule 8 lifecycle +verbs act on a *service*, `/keys/{service_id}/history`, "Deleted service +history", `nyxid service list`, and "connection" already means the OAuth +connection (`connection_id`, `connection_status`, Reconnect, Codex +connection). Users and the CLI would need two dictionaries. + +Use instead: + +| Term | Meaning | Backed by | +| --- | --- | --- | +| **Service** | A configured row you enable, disable, delete and call by its slug. | `UserService` | +| **Catalog service** | The template a service was created from. Not callable by itself. | `DownstreamService` | +| **Address** | The slug a caller uses. A service has one; a pool has one. | `UserService.slug`, `ServicePool.slug` | +| **Credential** | The stored key or OAuth grant a service executes with. | `UserApiKey` | +| **Pool** | An address that picks one member service per request. | `ServicePool` | +| **Origin** | How a service came to exist. Write-once. | `source`, `source_id`, `source_app_id`, `created_by` | + +Drop "Route" as a noun, "Connection" as a noun for services, and "spin-off" +entirely. "Route" survives only as the inspector tab title, meaning "what a +call to this address does". + +## 5. Final flow + +### 5.1 Services home + +``` +Services [Search] Owner ▾ Attention ▾ Kind ▾ Grouped|Flat [+ Add] + +OPENAI · catalog llm-openai ─────────────────────────────────────────── 3 addresses + Address Owner Credential Transport Last request Last change +▸ llm-openai You OpenAI key · Saved Direct 2m · coding-agent · 200 3d · Alice +▸ llm-openai-2 Acme Acme key · Saved Direct 1h · you · 200 12 Sep · Ben +▸ llm-openai You NyxID platform · Disabled Direct — 5 Sep · you + (slug reserved by the active row above) +ANTHROPIC · catalog llm-anthropic ──────────────────────────────────── 1 address +▸ llm-anthropic You OAuth · Expired Direct Reconnect needed … +POOLS ────────────────────────────────────────────────────────────────── 1 address +▸ llm-pool You 2 members · round robin per request 5m · coding-agent · 200 … +CUSTOM ───────────────────────────────────────────────────────────────── 1 address +▸ internal-api You Bearer · Saved node lab-1 (online) — Creator not recorded +``` + +Rules: + +- Divider = catalog name, catalog slug, count. Nothing else. Not clickable. +- **Owner** is who owns the row (You / org name). **Credential** is whose key + executes (your key / Acme key / NyxID platform). These differ for + platform-bound rows and must be separate columns. +- **Last request** is the row's `last_request` projection (see 7.1), shown as + age, verified caller, outcome class. For org rows a member sees only their + own ("your last request"); an admin sees all. Absent: "No requests since + " or "Not recorded". +- **Last change** is `last_change` when the reader may see it; otherwise the + column is blank for that row, not "never". Legacy: "Creator not recorded". +- Expanding a row reveals the existing card content unchanged. The card/table + ViewToggle stays for the mobile-card split; there is no third view. +- A disabled row that shares its slug with an active row is labelled as such. + Rows are keyed by UUID. +- The `CodexConnectionSection` banner moves into the Codex-linked row's Route + tab as a "Verified " line. It is per-credential evidence, not page + state. +- Search covers address, label, owner, catalog name. Caller search comes only + after the projection exists. +- **Attention** filter is defined exactly: `credential_missing`, `status` in + expired/revoked/failed/refresh_failed/pending_auth, `connection_status = + expired`, `node_id` set and `node_status != online`, or `is_active = false`. + +### 5.2 Inspector (Sheet on desktop, page on mobile, same component) + +``` +llm-openai You · Direct [Open full page] +[Route] [Activity] [Details] + +ROUTE + Calls to /api/v1/proxy/s/llm-openai execute this service (exact slug). + Credential OpenAI key · Saved 12 Sep · Not verified + Transport Direct + Payer You · your key (server-computed) + Evidence Last successful request 2m ago · coding-agent · 200 + Note This is the catalog slug. If this service is disabled or deleted, + calls to llm-openai use the NyxID platform key and platform pricing. + Viewing as [You ▾] coding-agent: in allowlist · overrides credential "work key" + +ACTIVITY (Requests | Changes) + 2m coding-agent · agent key POST /v1/responses 200 this service + 1h you · session GET /v1/models 200 this service + 3d Alice changed label "Prod" → "Production" [expand] + ─ Recorded since 20 Sep 2026 ─ + +DETAILS + Endpoint, auth method, headers, identity propagation, node routing (existing) + Origin Added by you · CLI · 12 Sep 2026 + Related Same credential: none + Same catalog: llm-openai-2 (Acme), llm-openai (NyxID platform, disabled) + Pools: llm-pool (member 1 of 2) + Agents bound: coding-agent → "work key" +``` + +Pool inspector Route tab: + +``` +ROUTE + Calls to /api/v1/proxy/s/llm-pool pick one enabled member per request. + Strategy Round robin · 2 of 3 members eligible now (excluded: llm-openai-2, disabled) + Members 1 llm-openai You OpenAI key · Saved weight 2 + 2 llm-openai-2 Acme Acme key · Disabled weight 1 (skipped) + 3 llm-anthropic You OAuth · Expired weight 1 (skipped) + Evidence Last request 5m ago ran member llm-openai (coding-agent, 200) + [Strategy ▾ Round robin | Weighted | Priority] drag handles appear only under Priority +``` + +Rules: + +- The three tabs are fixed. The full page is the same three tabs; today's + Overview/Advanced/History become Route/Details/Activity. No fourth tab, no + "Connections" tab, no "Related" tab. +- **Route** shows facts and evidence, never a verdict, until the evaluator + ships. "Would use", "checked N seconds ago", "Check: POST /responses", + "Ready via" and an operation picker are out of the first release. +- **Viewing as** lists You and the reader's agent keys. For an agent it prints + only exact facts: in allowlist / not in allowlist / all services, credential + override label, rate limit. No route verdict "as agent" until the evaluator + accepts an actor. "Application context" is dropped; nothing can simulate it. +- **Payer** is a server field. The client never derives it from Owner. +- The platform fallback **Note** appears only when the row's slug equals its + catalog slug and `platform_key_available` is true. It is the one place the + legacy fallthrough is disclosed, and it is the "paid fallback" answer. +- **Activity** is one chronological feed with two filter chips. Requests come + from the per-request record (7.1); Changes from the journal. Members of an + org see "Changes: available to org admins" instead of an empty list. +- **Details → Related** is computed from `api_key_id`, `catalog_service_id`, + pool membership and bindings. Each relation is labelled with what it is; none + implies inheritance. The journal already fans out shared-credential edits to + every referencing service, so no "include related events" option is needed. +- Pool member drag exists only when strategy is Priority, only inside that + pool, with keyboard equivalents. The preview never calls `resolve_member`. + +## 6. Critique of the draft, itemised + +### 6.1 Accepted as written + +- One workspace; pools in it; Agent Keys stays a separate destination. +- No family-wide selection claim; no invented canonical route; omit absent + platform sources; unavailable records shown below with a repair reason. +- Deep-link by immutable id; preserve list filters and scroll. +- Three tabs. +- Pin is strict; multiple accounts in a tier need an explicit choice; org order + is not database order. +- No fallback after dispatch; a resolver/database failure is not permission to + try another identity. +- Last attempt and last success tracked separately; transport completion is + not business success; "Not recorded" and "Recorded since" wording. +- Do not infer caller from User-Agent, provisioning app, creator or owner. +- Relationship labels instead of "spin-off"; rename and credential rotation are + history on the same service; slug reuse never merges histories. +- Create variant as a future write-once origin link. +- Round robin / Weighted say "selects per request"; previews never advance the + counter; Priority is a real strategy before it has drag handles. + +### 6.2 Blocking (the draft cannot be built truthfully until fixed) + +| # | Draft claim | Problem | Required change | +| --- | --- | --- | --- | +| B1 | "Expanded Requests feed shows the requested route, actual connection, source/owner and result"; "same execution ID" for pool and direct calls | No single record has actor + concrete service + outcome; catalog-backed audit stores the catalog id; the two audit rows share no id; ordinary app `oauth_client_id` and child credential id are not audited. | Add `user_service_id`, `route_kind` (exact / pool_member / org / legacy_platform), `pool_id`, `credential_class`, `oauth_client_id`, `api_key_credential_id` and an outcome class to the `proxy_request`, `llm_proxy_request` and `proxy_request_denied` events, plus one request id shared with the routing row and the meter's `billing_request_id`. `DestinationAudit` (`destination_routing.rs:375-400`) carries service/target/origin but no request id, so one must be minted at the proxy entry and threaded through all three writers. | +| B2 | "Latest request … on the row" | Reading audit per row is a 30-day scan per service; the existing agent-key dashboard already pays this. | Add `last_request {at, actor, outcome_class, route_kind, request_id}` and `last_success {…}` projections on `UserService`, updated fire-and-forget after the response is written, same pattern as `last_change`. Outcome classes: `dispatched_ok` (upstream 2xx/3xx), `dispatched_error` (upstream 4xx/5xx), `not_dispatched` (denied or failed before upstream). `last_success` advances only on `dispatched_ok`. | +| B3 | `last_used_at` shown as usage | It is credential preparation time on a possibly shared credential, including agent override use. | Relabel to "Credential last prepared" inside Details, or hide. Never feed Last request or Last success from it. | +| B4 | "Would use … checked 24 seconds ago", "Ready via", "Check: POST /responses", "Use a specific connection", "As: application" | No resolver projection, no preflight endpoint, no per-service route preference, no actor-parameterised evaluation. The example's fallback to Acme after an expired personal key is not current behaviour: today that request returns 400. | Remove from release 1. Route tab shows facts and evidence. Label the §3 example as future-state. | +| B5 | Service / Route / Connection vocabulary | Inverts existing product terms and collides with OAuth "connection". | Adopt section 4 terms. | +| B6 | "Latest visible request" on the service block; caller names in the feed | Org privacy is stated in §5 but violated by the block aggregate; the rule is not concrete. | Concrete rule: personal rows show everything to the owner; org rows show all callers to org admins, only the reader's own requests to members, nothing to viewers. The list column obeys the same rule. | +| B7 | "Actual payer/pricing" before saving a policy; payer in resolution | Client cannot derive payer; platform-bound org rows bill the acting person. | Payer is a server-computed field on the key view; UI renders it verbatim or omits it. | +| B8 | History for "the visible caller context" | `read.rs` requires `can_write()` plus resource scope; members and viewers get no authorship or journal. | Activity → Changes must render an explicit "available to org admins" state for members; do not promise members edit history. | +| B9 | One row per address | `/keys` can return two rows with one slug (disabled + active); tombstones can be revived into zombies (known gaps 1 and 2). | Key rows by UUID; label the disabled duplicate; fix gap 1 (409 at create) before shipping the grouped sort, or the sort will show an impossible pair without explanation. | +| B10 | Agent "As:" simulation | `allow_auto_connected_services` expansion is server-side; org role scopes apply per membership. | Show only allowlist membership, binding override and rate limit as facts. No verdict. | + +### 6.3 Challenged as control-heavy or unusable (drop or defer) + +- **Cards toggle for individual connections** as a second presentation of the + same list. Row expand shows the card. Keep only the existing card/table + ViewToggle for the responsive split. +- **Search by authenticated caller** before B1/B2 exist. +- **"Changes can optionally include related-service events"**: the journal + already writes shared-credential edits into every referencing service's own + history. The option adds a control and a scope-leak risk for no new data. +- **Operation picker ("Check: POST /responses") in the Sheet**. If an + operation-scoped check ever ships, put it on the full page and the CLI, not + in the list inspector. +- **Saving a route/pin policy from the inspector** ("show who owns the setting, + who it affects"). No policy store exists. The only writes in release 1 are + the existing ones: Enable/Disable, node routing, pool strategy and members, + credential replacement. +- **"Release dashboard · Application · on behalf of Alice"** cannot be produced + for ordinary app tokens today (not audited). Show it only when + `acting_client_id` (delegated) or the new `oauth_client_id` field is present. +- **"API key ending …7K2"**: the audit row has `api_key_id` and name, not the + prefix; a lookup is needed, and Agent Key child credentials are invisible. + Render the key name; add the child credential id in B1 if per-login + attribution matters. + +### 6.4 Where I disagree with the seed + +- "Service family" as the hierarchy: no server object, misleading at the + family level, breaks on pools and custom rows. Divider only. +- Four inspector tabs (Overview, Connections, Activity, Related): "Connections" + duplicates the list, "Related" is a section of Details, "Overview + caller" + is a Route tab with a Viewing-as control. +- "Uses now": unsafe for pools (counter), unavailable for catalog slugs (no + resolver), and dishonest for anything requiring a probe. Replace with + "executes this service" (exact), "picks per request" (pool), and evidence + with age. +- "Spin-off": no data. Origin (write-once) plus computed relations. +- Actor from creator or User-Agent: rejected; the audit row's verified + `user_id` / `api_key_id` / `acting_client_id` is the only source. + +## 7. Backend additions the flow depends on (smallest set) + +1. **Per-request record fields** (B1) on the existing proxy audit events, plus + a shared request id. No new collection. +2. **`last_request` / `last_success` projections** (B2) on `UserService` and + on `ServicePool` (the pool's last request also names the member that ran). +3. **`payer` on the key view**, computed by the existing billing owner resolver + without reserving anything. +4. **`PoolStrategy::Priority`**: first enabled active member in array order, + error if none; member reorder endpoint already implied by member edits. +5. **Gap 1 fix**: 409 when creating a service on a disabled row's slug. +6. **Read endpoint for a row's recent requests** scoped by the privacy rule in + B6, cursor-paginated, 30-day cap like the agent-key dashboard. + +Release 2 (only after the evaluator from `slug-connection-resolution-proposal.md` +exists): canonical catalog-slug rows with mode Automatic, the Route tab +candidate order, a read-only "Check route" button, Viewing-as verdicts, and +per-service source preference. Release 3: Create variant with an immutable +`origin.derived_from_service_id`. + +## 8. Edge cases the final flow must render + +| Case | Rendering | +| --- | --- | +| No requests ever, no journal | Last request "Not recorded"; Last change "Creator not recorded"; Activity shows "Recorded since ". | +| Legacy row with edits before tracking | Footer "Earlier edits not recorded"; timeline starts at first journaled event. | +| Deleted service | Hidden from the list; "Deleted services" filter lists archived UUIDs with slug and last change; inspector opens read-only with Activity only. | +| Slug recreated after delete | New UUID, new history. Details → Related: "This slug previously belonged to a deleted service (last changed )", labelled as slug reuse, never as lineage. | +| Disabled row and active row share a slug | Both listed; disabled one labelled "slug reserved by the active row"; Enable on it is expected to fail until gap 1 is fixed, so the button explains why. | +| Multiple orgs | Each org row shows org name and the reader's role. No ordinal until the evaluator exposes membership order; the Route tab of a catalog-slug row says "org order: primary org first" only when the resolver reports it. | +| Two personal accounts for one catalog service | Two rows, two addresses (`llm-openai`, `llm-openai-2`); the catalog-slug row carries the fallback Note; the other carries none. No "default" control until the preference store exists. | +| Agent pinned to a credential | Viewing-as shows "overrides credential X"; Activity rows from that agent show the override in the route column once B1 lands. | +| Viewer role on org row | Row visible, reduced opacity, no Last request, no Last change, inspector Route tab facts only. | +| Member role on org row | Row visible; Last request = member's own; Changes tab "available to org admins". | +| Platform absent | No platform row, no Note, no option anywhere. | +| Platform configured but `platform_key_available = false` | Explicit platform-bound rows show Credential "NyxID platform · Unavailable" with the catalog reason; no fallback Note on the catalog-slug row. | +| Paid fallback live (catalog slug, platform key available, user row exists) | The Note in the Route tab, worded with "platform pricing". Disable confirmation repeats it: "Calls to llm-openai will use the NyxID platform key while this service is disabled." | +| Pool with mixed credentials or providers | Members listed with their own owner and credential; pool sits under the POOLS divider, never under a catalog divider. | +| Node-routed service, node offline | Transport "node lab-1 (offline, 2 fallbacks)"; Attention filter catches it; no request is claimed possible. | +| Codex-linked service | Route tab Credential line "Verified · usable" or "reconnect_required"; the only service kind allowed to say Verified. | + +## 9. Recommendation + +Adopt the row-per-address list with stateless dividers, the three-tab +Route/Activity/Details Sheet shared with the full page, and the vocabulary in +section 4. Treat B1 and B2 as the definition of done for "who last used this +service"; without them the Last request column must not ship. Keep every +resolver-dependent phrase out of release 1 and label the draft's §3 example +as future behaviour. The draft's sequence (contracts, server, UI) is right; the +contracts are smaller than the draft implies, and the first UI release is a +truthful metadata view, not a routing product. + +--- + +# Pass 2: closing disposition on the rewritten draft + +Reviewed `consolidated-services-flow.md` at its 25 September rewrite (412 +lines). Line numbers below refer to that file. Verdict: **converged once the +six wording fixes in P2.2 are applied.** They change sentences, not the design. + +## P2.1 Accepted, and pass-1 positions withdrawn + +The draft's structure stands: one entry per real callable address, stateless +catalog dividers, one inspector with Route / Activity / Details shared with the +full page, Service vocabulary, correlated execution activity, origin rather +than inferred lineage, explicit List/Cards with no third mode. + +Withdrawn from pass 1 after the user's corrections and re-checking code: + +- "Priority = first enabled active member in array order." Wrong; a pool + member must pass the same eligibility as an Automatic candidate. Draft + lines 212 and 235 are right. +- "Metadata-only release 1, resolver later." The target includes Automatic + resolution; the draft's delivery order (contracts and resolver first, UI + last) replaces my release split. +- The platform fallback Note derived from slug plus `platform_key_available`. + Draft line 366-368 is right: fallback is a server decision, never + reconstructed from availability metadata. +- "Member's own latest by filtering the row's latest." Needs an actor-scoped + projection; draft line 283-284 is right. +- Pools must stay same-owner (`resolve_member` filters `user_id: owner_id`, + `service_pool_service.rs:380-384`); draft line 206-207 is right. + +Checked and found consistent with code, no change needed: + +- No replay after dispatch (lines 189-192, 385). A node that accepted the + request and then failed returns `DurableOperationOutcomeUncertain` with no + retry (`handlers/proxy.rs:3282-3287`); `fallback_node_ids` is consulted + only before a node accepts. Node failover therefore already sits below the + source boundary. +- Streaming and Codex-transport audits record `response_status` at header + time (`handlers/proxy.rs:3697-3712`, `3290-3300`), which is exactly why step + 2 (line 349) must separate protocol completion from headers. Evidence, not + a blocker. +- Exact-call identity for the same-slug candidate exists in two forms today: + the UUID address (`KeyResponse.proxy_url`, resolved by + `resolve_proxy_target_by_user_service_id`, `handlers/proxy.rs:1059`) and the + pin `?_nyxid_via=` on the slug form + (`handlers/proxy.rs:902`). Line 75 can name them. +- Auto-provisioned and platform-bound rows reject user mutation + (`user_service_service.rs:76-83`), so the opt-out lives in the source editor + as the draft says (line 148). + +## P2.2 Blockers: six line fixes + +**F1. Lines 115-133 and 172-182: state the eligibility rule.** The draft +never says whether a fresh, unverified credential may enter Automatic. Code +answers it: the read-only snapshot counts an expired OAuth token with a refresh +token as materializable without refreshing (`credential_is_materializable`, +`proxy_service.rs:3536-3545`; `read_only_snapshot`, `1650-1657`), and +execution refreshes during preparation. The only alternative, requiring +verification first, would exclude every newly added key from its own address +and there is no safe generic probe (Codex and the at-creation AWS probe are +the only supported checks). Insert after line 133: + +> Eligibility is preparation, not verification. A candidate enters the +> usable order when authority, configuration and credential preparation +> succeed at evaluation time; a refreshable credential is eligible with a +> pending refresh gate. Verification evidence changes the label, never the +> order. A candidate leaves the usable order only on a classified terminal +> failure (missing, revoked, failed, terminal refresh rejection, disabled, +> no permitted node route) or an explicit user action (Disable, pin). An +> upstream 4xx, 429 or outage is recorded as that request's outcome and +> never demotes a candidate; repair is a human action. The first completed +> execution through a candidate is its verification evidence; no business +> request is sent to manufacture one. + +This also closes the "actual valid and working" question: "working" is a +dated outcome shown in Activity, "eligible" is a preparation result, and the +UI never conflates them with a badge. + +**F2. Lines 120 and 133: a pending gate above blocks "Would use" below.** +With F1, a personal candidate in "Refresh required" is still ahead of Acme. +The example at line 98 is fine because "Needs reconnect" is terminal, but the +rule is missing. Replace the "Would use" table row evidence with: + +> Server evaluation for this caller, operation and policy version in which +> every higher-priority candidate is in a classified terminal state. If a +> higher candidate is in a pending gate (refresh, verification, approval), +> show **Would use Personal after refresh, otherwise Acme**; never name the +> lower tier alone. + +**F3. Line 148 and line 376: state the default for platform inclusion.** +"Users may exclude optional org or platform fallback" and "only if allowed" +leave the default undefined, and the default decides whether migration opts +BYOK users into charges. Insert after line 149: + +> Platform is included in Automatic by default only where the offering is +> available to this caller and the payer already holds billing authorization +> for that route; otherwise it is excluded until the user opts in from this +> editor with payer and pricing shown. Migration never enables platform +> charges for an existing user without that opt-in. Excluding a tier removes +> it; it never reorders the remaining tiers. + +**F4. Line 172 and line 189: define when a canonical entry exists, and +close the no-row path.** "An enabled canonical Automatic address" is never +defined, so the list could show every catalog entry with a public platform +key, or hide a live path. Insert before line 172: + +> A canonical entry exists for a viewer when the evaluated candidate set is +> non-empty: at least one personal service for that catalog identity, one +> org service reachable through an active membership with proxy rights, or +> a platform offering that is included under F3. With no candidate, the +> catalog page is the entry point and calls to the address return the +> structured unavailable error; the legacy catalog-slug path is not consulted +> for a canonical address unless a legacy pre-migration connection exists. + +The last clause is the guard from the earlier reconciliation; without it +"no rows plus platform excluded" leaks through `resolve_service_by_slug`. + +**F5. Lines 55-57 and 73-79: Owner and Latest request on canonical +entries.** A canonical entry has no record owner; "You" there is the policy +scope, while "Acme" on the row below is a record owner. Same column, two +meanings. Fix the value, not the column name: on canonical entries Owner +reads **Your calls** (or **Acme's calls** for an org-scoped policy that an +org admin opens). Add to line 77: + +> A canonical entry's Latest request covers requests **to this address** by +> the viewer's scope; the nested candidate card's Latest request covers +> executions **through this service** by any permitted entry point. Both +> labels are shown. When the same-slug candidate is an auto-provisioned +> platform row, the nested card offers no Disable; exclusion lives in the +> source editor. + +**F6. Line 100: the payer in the example is wrong.** With "Would use Acme · +Work OpenAI" on a BYOK org credential, the billing owner is the resource +owner, not the acting person: `resolve_for_execution` charges the acting +person only for `NyxidManagedMaster` and otherwise resolves the resource +owner (`billing/owner_resolver.rs:53-67`). Change the line to **Paid by Acme +(org wallet) · provider charges to Acme's key** and add one sentence under +the label table: + +> Paid by always follows the selected candidate: org credential, org wallet; +> platform key, the acting person; personal key, you. While selection is +> pending, show **Payer depends on source** rather than a guess. + +## P2.3 Recommended, not blocking + +- Line 57: the list's source cell ("Automatic · Personal") needs a batched + read-only evaluation for the page. Say so, or show only **Automatic** in + the list and evaluate on open. The footnote's age is invisible in a + four-column row anyway. +- Line 75: name the two exact-call forms (UUID address, `_nyxid_via` pin). +- Lines 210-212: say that Round robin and Weighted also select among + *eligible* members under F1, replacing today's `is_active`-only filter + (`service_pool_service.rs:374-390`), and that a member failing preparation + is skipped before dispatch within the member set. +- Line 166-167: "a stable saved order" for multiple orgs is a new per-user + preference; list it in section 7 under versioned policy. +- Line 264: add "denied before dispatch" as an outcome with no executed + target, matching line 350. + +## P2.4 Status of F1-F6 after the user's edits + +F2 (pending higher candidate blocks a definite preview), F5 (Owner on +canonical entries, "Through this address" versus "Handled by this service", +policy journal versus concrete journal, back breadcrumb) and F6 are addressed +in the final text. F3 and F4 remain as written above and are still required. +F1 is superseded by the user's explicit working-only choice; the closing +assessment of that choice follows. + +--- + +# Pass 2 closing: the final text (460 lines) + +Assessed as final. Two blockers remain, both inside the new working-only +paragraph (lines 151-163) and its acceptance rows (422-423). Everything else +is converged. + +## G1. The working-only selection rule contradicts the draft's own skip rule and deadlocks the common case + +The paragraph says unverified candidates "do not enter the working set" and +row 423 says "do not ... dispatch an Automatic/Priority request". Three +consequences, each traced to the final text: + +1. **Internal contradiction.** Step 3 (line 219-221) permits skipping "only + classified candidate failures". Row 422 skips a saved personal key because + no evidence exists. Absence of a record is not a classified failure. Line + 197 forbids moving "between unchosen accounts"; row 422 moves the caller + from their own configured account to Acme's on that same absence. +2. **Deadlock for slug callers.** A user with one personal key for a provider + without a supported check (every provider except Codex and the at-creation + AWS probe), no org and no platform, whose agent is configured with the + canonical slug: the slug is Automatic (line 195-197), the candidate is + unverified, no safe check exists, so every call returns **Verification + required**. The only bootstrap is an exact UUID or `_nyxid_via` call (line + 161-162), which the agent never makes. The Connect flow (line 204-205) + cannot progress past Saved for that provider. Pools of fresh keys under + Priority deadlock the same way. +3. **Day-one migration.** The execution record that produces "qualifying + recorded success" does not exist yet; today's audit rows cannot be + backfilled per service (catalog id, no join). At rollout every existing + BYOK candidate is unverified. Under row 422 their traffic shifts to org or + platform accounts on the first call; under row 423 it stops. Either is a + silent identity or availability change for every current user. + +Keep working-only for **claims and preview**; change **selection** so +missing evidence never substitutes an identity. Replace lines 157-160 with: + +> Execution may perform a supported safe check before selection, at most +> once per credential and configuration version, and records the result as +> evidence. When the top-ranked candidate has no evidence and no supported +> check, Automatic and Priority execute that candidate as its **verification +> attempt**: the caller's own request, on the requested operation, with no +> other identity substituted because evidence is missing. Preview shows +> **Would try Personal · unverified**, never Would use or Ready. The outcome +> becomes that candidate's version-bound evidence; a classified failure then +> removes it from the working set on the next call. A policy may opt into +> **Require verified sources**, in which case an unverified top candidate +> returns **Verification required** naming the available actions (Verify, +> Use exactly once, Switch source) and no other account is used. + +Rewrite rows 422-423 accordingly: + +| Scenario | Required result | +| --- | --- | +| A personal key is merely saved and org has current valid evidence | Check personal safely when supported; otherwise Automatic executes personal as its verification attempt and records the outcome. Org is used only after a classified personal failure, or when the policy requires verified sources and the user chose Switch source. | +| Every configured credential is unverified and cannot be safely checked | Automatic and Priority execute the top candidate as a verification attempt; nothing is marked ready. Under Require verified sources: Verification required, no dispatch. | + +If the user keeps strict exclusion instead, the minimum to make it operable +is: (a) the single-candidate bootstrap (a canonical call whose candidate set +has exactly one unverified member executes it as an exact request), (b) a +migration rule that pre-rollout candidates receive one verification attempt, +(c) Connect and the row both stating **Unverified · Automatic will use Acme +until verified** before any traffic shifts. Without all three the rule ships +either an outage or a silent account switch. + +## G2. Evidence definition omits OAuth authorization and lets time expire eligibility + +Line 153-156: "supported validation/refresh, or a qualifying recorded +success ... Freshness limits ... defined by the provider adapter." + +- A just-completed OAuth authorization produces a valid token with no + refresh performed and no request yet made. Under the text it is + unverified. Most catalog services are OAuth. Add: **a completed OAuth + authorization or successful refresh is validity evidence for that token + version** (`last_authorized_at`, `write_oauth_tokens_to_key`). +- Adapter-defined freshness means an idle service with a success eight days + ago can drop out of the working set with no change to anything, flipping + Automatic to another account and filling Needs attention with healthy + rows. Replace with: **evidence is invalidated by a credential epoch change, + an endpoint or configuration version change, or a classified failure; not + by elapsed time. Age is always displayed.** If an adapter needs a + time-bound re-check, it downgrades the label to **Re-check recommended** + and never changes selection. + +## Recommended, not blocking + +- Line 55-63: Owner **—** on the canonical row shares a column with blank + cells on restricted rows and "Not recorded" elsewhere, so a dash reads as + unknown. **Your policy** (or **Acme policy** for org scope) scans cleanly + and matches footnote ². + +## Verdict + +Converged on structure, vocabulary, inspector, activity scope, lineage, +pools and privacy. Not converged on G1 and G2: apply the replacement +wording, or keep strict exclusion with the three operability conditions. +No further investigation is needed either way. + +--- + +# Final disposition on G1 and G2 (primary draft at 524 lines) + +**Closed.** The rewrite keeps working-only selection and removes the three +risks by making absence a pending gate rather than a terminal or substitutable +state. Checked against the final text: + +- **Deadlock.** Lines 159-163 return **Verification required** instead of + skipping; lines 177-183 give an explicit **Use exact service** path with a + generated exact URL/agent configuration and a user-requested first real + operation that produces version-bound evidence; line 183 requires setup to + surface that path. Lines 250-256 offer Automatic only after candidates are + reviewed. A slug-configured agent with one unsupported key is no longer + trapped; it is redirected. Opportunistic first-use verification was my + preference; explicit setup is a legitimate tradeoff, not an open gap. +- **Migration.** Lines 250-252 preserve existing exact/custom/pool contracts + until an explicit migration whose preview names addresses, agents, policy, + evidence gaps and payer, and cannot enable Automatic while verification is + unresolved. Row 476 rules out a first-day outage or paid fallback. Closed. +- **Identity.** Lines 161-163 and row 474 forbid substituting an org or + platform account for missing evidence; lines 171-173 forbid time alone from + marking a credential broken or changing identity; Priority stops at an + unassessed higher member unless explicitly excluded (167-168); Round + robin/Weighted use an explicitly approved working set disclosed before + activation (165-166). Closed. +- **G2.** OAuth authorization and refresh count as evidence (154-155); + freshness is a re-check, never a demotion by age. Closed. + +Three one-line residuals, none blocking: + +1. **Implicit no-row platform path.** Lines 246-249 retire the hidden legacy + platform source for canonical contracts with no usable candidate. Callers + who rely on it today (no rows, never opened `/keys`) are not covered by + row 476. Name this as a deliberate consent cutoff in the migration + section and measure it first: audit rows with `routed_via: "personal"` + and `user_service_id: null` are exactly that path + (`handlers/proxy.rs:632-640`). +2. **Copy target.** While a service is unverified and its canonical address + is Automatic, the card's copy action and agent config must hand out the + exact URL (`KeyResponse.proxy_url`), not `proxy_url_slug`, or the trap + returns through the existing copy button. +3. **Freshness loophole.** Line 171-174 lets an adapter require a re-check + and, if unavailable, "stop with an actionable state". Add: an adapter may + require a freshness re-check only when it supplies a safe check; otherwise + version-bound evidence does not expire. Without this, an idle key for an + unsupported provider can move from working to stopped by time alone, + which line 172 says must not happen. + +Review closed. No further pass requested. diff --git a/docs/plans/slug-connection-resolution-proposal.md b/docs/plans/slug-connection-resolution-proposal.md new file mode 100644 index 000000000..5850503b5 --- /dev/null +++ b/docs/plans/slug-connection-resolution-proposal.md @@ -0,0 +1,483 @@ +# Slug routing and connection availability + +Status: proposal for review; application behavior is unchanged. + +The current product flow is [Consolidated Services](consolidated-services-flow.md) +(revised 27 September 2026). This document retains the detailed execution rationale; +the newer flow supersedes its UI terminology and presentation. + +The current UI flow is in [service connections and pool ordering](ai-service-connection-user-flow.md), +revised 17 September: omit absent platform sources, require verified readiness, +retain individual cards, and drag only actual members within a ServicePool after +opting into Priority. Catalog grouping is a view, not a pool. This document +retains the execution and security rationale behind that flow. + +Date: 2026-09-16. Prepared by Codex with the independent +[Fable review](slug-routing-fable-review.md) incorporated. This document is the +reconciled recommendation; the review retains its original alternatives. + +User clarification: the required execution order is **user → organization → +platform**. Organization fallback after an unusable user connection is part of +the core delivery. This supersedes the earlier review's recommendation to defer it. + +Source choice: users can explicitly select **NyxID platform only** even when their +personal connection is usable. The three-tier order is the **Automatic** mode, +not a restriction on an authorized explicit source choice. + +## Product contract + +In Automatic mode, calling a service's canonical slug selects the first usable, authorized +connection in this order: the user's connection, an authorized organization +connection, then a platform connection explicitly offered for that service. +Missing or unusable user credentials advance to the organization tier; missing +or unusable organization credentials advance to the platform tier. +If none qualifies, NyxID rejects the request before +forwarding it and explains what the caller can do next. + +The user should see the same decision before calling: which connection the slug +would use, why an earlier connection cannot be used, and which account pays. + +This selection happens on the server for every canonical slug API request. The +client keeps one URL and does not need to choose a source or implement fallback. +In Automatic mode, repairing a user connection makes the next new request prefer +it again. In platform-only mode, the platform remains selected. An +in-flight request or established stream remains on its selected connection. + +“Usable” means the credential and route can be prepared for this operation and +pass its access and billing checks. It cannot promise that a third-party service +will accept the next request. Show verification evidence and its age separately +from connection configuration, and recheck at execution time. + +## What exists and where the behavior falls short + +These observations were checked against the current code, rather than inferred +from older design documents: + +| Current behavior | Consequence | Evidence | +| --- | --- | --- | +| Personal `UserService` resolution immediately calls `finish_resolution()` and propagates its error. Personal pools, legacy personal records and org resolution follow separate branches. | A matching but unusable personal row can stop resolution before another source is considered. | `backend/src/services/proxy_service.rs`, `resolve_proxy_target_from_user_service`, `finish_resolution` | +| An inactive personal row is skipped by active-only lookup; for an eligible master-credential catalog service, the legacy path can then select the platform credential, subject to its authorization and scope gates. | Expiry can stop access while Disable/Delete can permit platform fallthrough. The new resolver must own the terminal decision, including explicit platform opt-out. | `user_service_service.rs`, `find_by_slug`; `handlers/proxy.rs`, slug legacy fallback; `proxy_service.rs`, `resolve_proxy_target` | +| Auto-provisioned platform services are stored under the user's ID. | Ownership alone cannot identify a user-supplied credential. | `proxy_service.rs`, `AUTO_PROVISION_SOURCE` handling; `unified_key_service.rs`, `auto_provision_no_auth_services` | +| Auto-provisioning skips a catalog service if any matching user row exists, including inactive rows, and is invoked from the key listing. | A platform candidate must be derived from authorized catalog configuration without requiring an auto-provision row or a visit to `/keys`. | `unified_key_service.rs`, `auto_provision_no_auth_services`, `list_keys` | +| Concrete connection slugs can receive suffixes; catalog-ID lookup uses `find_one` without a preference order. | Calling the catalog slug may miss a healthy user connection named `llm-openai-2`; selecting by catalog ID is not a defined multiple-account policy. | `unified_key_service.rs`, `resolve_unique_slug`; `user_service_service.rs`, `find_by_catalog_service_id`; `frontend/src/types/keys.ts`, `catalog_service_slug` | +| Master credentials already require a qualified catalog service and authorization. They are not generic provider OAuth app credentials. | A configured OAuth client does not make a platform execution connection available. Existing platform authorization must be retained. | `proxy_service.rs`, `authorize_master_credential`, `is_valid_master_credential_service` | +| `/keys` combines credential status, connection expiry, node status and provenance in the frontend. Proxy discovery uses connection presence; MCP has its own credential classifier. | Surfaces can disagree about whether the same service is callable. | `frontend/src/pages/keys.tsx`; `proxy_discovery_service.rs`; `mcp_service.rs`, `classify_credential` | +| Approval-owner and approval-hint lookups mirror routing independently. Pools already exist; selection increments a database counter. | New precedence must cover approvals and previews, and merely opening a page must not rotate a pool. | `proxy_service.rs`, `find_effective_service_owner`, approval hint resolvers; `service_pool_service.rs`, `resolve_member` | +| Billing already distinguishes the final credential class; resale requires `NyxidManagedMaster`. | Charge attribution should consume the final selected route. User credentials do not imply that every NyxID proxy fee is zero. | `backend/src/services/billing/route_context.rs` | + +## Resolve service identity before choosing a credential + +A catalog slug names a logical service. A connection UUID or custom/instance slug +names a particular connection. Preserve that distinction: + +- **Canonical catalog slug:** enables the user-first selection rule. Find user + candidates by the catalog ID, including connections whose concrete slug has a + suffix. Never match credentials by a slug prefix or provider name alone. +- **Concrete connection UUID, the existing `_nyxid_via` selector, custom slug or pool + slug:** retain their declared target. An explicit account choice must not turn + into another account or platform identity when it fails. Existing pool selection + remains inside its member set. +- **Existing custom/pool slug colliding with a catalog slug:** preserve its current + meaning until an explicit migration resolves the conflict. Never silently + retarget that traffic. Block new ambiguous assignments for enabled canonical + routes. +- **An existing same-slug connection linked to the same catalog:** can remain the + preferred member of that canonical route. The detail page exposes a UUID-based + exact URL for callers that require that particular connection. +- **Agent credential overrides and exact approvals:** are execution constraints, + not optional preferences. A missing bound credential fails that bound request. + An approval for one concrete identity never authorizes another by implication. + +For multiple user connections, persist a preferred connection. Preserve the +existing same-catalog exact personal slug match as the initial default. When +there is no such match or stored preference, automatically use the only configured +compatible personal connection. With several unchosen accounts, ask the user to +choose a default; do not choose an arbitrary MongoDB row or silently change +accounts because one has failed. A default is an ID preference, never a slug +rename. Existing pools cover intentional balancing; v1 adds no ordered list of +alternative user accounts. + +## Selection policy + +```mermaid +flowchart TD + A[Call canonical service slug] --> B{Caller and request permitted?} + B -->|No| X[Reject with reason and next action] + B -->|Yes| C{Usable user connection?} + C -->|Yes| U[Use user connection] + C -->|No| D{Usable authorized org connection?} + D -->|Yes| O[Use organization connection] + D -->|No| E{Platform fallback offered and permitted?} + E -->|No| X + E -->|Yes| F{Platform credential, operation and funding ready?} + F -->|Yes| P[Use platform connection] + F -->|No| X +``` + +User-first includes legacy personal credentials during migration. A user +connection that fails a typed eligibility/preparation check advances to eligible +organization connections, then platform. Label the organization with its actual +name and billing owner. For multiple organizations, retain primary-organization +priority and a defined stable order for remaining eligible memberships; expose +that order in the expanded routing view. Apply role, service/node scope, consent, +operation/account compatibility, approval and payment gates to each candidate. +An unauthorized organization is not a usable candidate. Caller-wide denials and +exact execution constraints still stop the request. Public catalog visibility +does not confer execution permission. + +Eligibility has four parts: + +1. **Authority:** authenticated caller, owner ACL, API-key service/node scope, + consent, operation policy, resource restrictions and approval constraints. +2. **Configuration:** enabled connection, existing endpoint and credential, + correct provider/operation/account compatibility, and an eligible catalog + service. A deliberately no-auth service needs no credential; a missing + credential row does not mean no-auth. +3. **Preparation:** materialize an authorized credential; perform the existing + bounded OAuth refresh when needed; confirm the required node route can be + dispatched. An offline primary node may use existing permitted node failover. +4. **Payment:** resolve the actual billing owner and final credential class, + enforce entitlements and reserve any required funding before provider effects. + +Classify failures rather than catch every `AppError` and continue: + +| Condition | Action | +| --- | --- | +| No configured personal connection | Consider the next permitted source. | +| Missing/expired personal credential, terminal refresh rejection, or unavailable permitted node routes | Consider the next source only for a canonical route whose policy allows that substitution; retain a repair reason. | +| Credential refresh is pending or its result is unknown | Bound the preparation attempt; report uncertainty or a permitted alternative without declaring the credential revoked. | +| Explicit connection/agent binding, node-only execution constraint, platform opt-out, or an approval bound to another target | Honor the constraint; do not reinterpret it as a health failure. A disabled pinned connection remains unavailable. | +| Caller-wide permission, consent, operation policy or rate-limit denial | Return the applicable denial. Fallback must not bypass it. | +| Candidate-specific org ACL does not permit use | Exclude that candidate without revealing private details; any alternative requires its own complete authorization. Preserve existing denial semantics unless explicitly migrated. | +| Database/KMS/decrypt failure or an integrity violation such as a dangling credential/endpoint reference or inconsistent ownership | Return the server error; do not mask it as “no connection” or silently introduce paid fallback. Typed incomplete credential setup is distinct from corrupt references. | +| Business request already sent, response timed out, stream interrupted, or upstream returned an error | Return that outcome. Do not replay the request through a different source in v1. | + +Use existing typed credential/refresh outcomes to inform subsequent calls. Do not +globally disable a credential based on an arbitrary resource-specific 403, a 429, +or a provider outage. Defer a new persisted health/circuit-breaker subsystem; +v1 reports known configuration and credential states. If health observations are +added later, bind them to the credential version and operation/account scope. +Existing refresh behavior must not increment `credential_epoch`. + +## Platform fallback is an explicit product offering + +Start with an inventory of the actual catalog configuration. Production catalog +data has not been inspected for this proposal. For a catalog row that already +supports BYOK and a master credential, reuse the existing validated platform +predicate and authorization path, including `provider_config_id.is_none()`. +Use the existing `proxy_operation_policy` for its operation allowlist, with +additional compatibility checks where the request references account resources. +Do not relax `requires_user_credential` or infer an execution credential from an +OAuth client ID/secret. Server-only `platform-*` vendor templates remain outside +user-addressed slug fallback. + +Only if the inventory shows that the logical user service and its platform +offering are distinct catalog rows, add an explicit administrator mapping by +catalog ID. Follow at most that configured target, with no recursive alias chain +and no slug-prefix matching. The platform candidate is virtual: derive it from +authorized catalog configuration even if no auto-provisioned `UserService` row +exists and the user has never opened `/keys`. + +Examples: + +- A platform OpenAI offering may support stateless inference with its own billing + and model limits. A request referring to user-owned files, assistants, batches + or other provider resources requires the original account. Unknown compatibility + fails closed; a matching HTTP method/path alone may be insufficient. +- A platform GitHub OAuth app helps users authorize their own accounts. It is not + a fallback GitHub account for reading repositories or creating issues. +- A valid public no-auth route can be available without either credential source; + its ordinary execution and node rules still apply. + +The normal mode is **Automatic: You → Organization → NyxID**. An **Allow NyxID +fallback** setting controls the last tier; turning it off leaves user → +organization → error. Enable that last tier where fallback is offered and the +payer already has any required billing authorization for the route. Otherwise +keep it off until the user opts in with payer and pricing disclosed. This setting +never moves platform ahead of an eligible organization. Do not silently opt +existing BYOK-only users into new charges during migration. + +Once the evaluator handles a request, its decision is terminal. A rejected or +automatically opted-out platform candidate must not be retried through the old catalog +fallthrough. Existing legitimate legacy personal connections remain candidates. + +## Explicit platform choice + +Expose a **Connection choice** control on the logical service: + +| Choice | Execution behavior | +| --- | --- | +| Automatic — You → Organization → NyxID | Select the first usable authorized tier, respecting the automatic platform-fallback setting. | +| NyxID platform only | Evaluate the platform candidate directly, even if personal and org connections are ready. Return its error if unavailable or denied; never silently use personal/org instead. | + +Allow the user to save this preference per service and override it for an +individual canonical API request. A proposed request header is +`X-NyxID-Connection-Source: platform` (use `auto` to explicitly request Automatic). +This is a proposed interface, not a currently implemented header. Keep the same +canonical slug URL. Validate the header, reject invalid/ambiguous values and +consume it inside NyxID; do not forward it downstream. CLI/MCP controls should +map to the same resolver input where those transports expose source choice. + +The effective choice is the request's explicit mode, otherwise the saved +per-service mode, otherwise Automatic. This precedence applies only to source +selection; exact connection pins, agent bindings, approval authority, caller +permissions and administrator constraints remain mandatory. Reject conflicting +explicit choices rather than silently dropping a pin or source request. + +Selecting platform is a choice of an already configured, authorized offering, +not permission to access an arbitrary internal platform service. Apply its +operation compatibility, rate-limit, consent, funding and approval gates even +when a working user credential is available. Show the actual payer and pricing +when saving the preference. A per-request header never grants new billing or +execution authority. + +**Allow NyxID fallback** only controls Automatic mode. An explicit platform-only +choice can use the platform when authorized even if automatic fallback is off; +any hard platform-use restriction continues to deny it. Hide the fallback toggle +while platform-only is selected to avoid presenting it as a contradictory setting. + +For example, the card becomes **Ready via NyxID · Platform selected**. Show personal +and org connections as **Ready · Not selected** when healthy, rather than implying +they failed. If platform is unavailable, show **Platform unavailable · Platform +selected**, its repair reason, and **Switch to Automatic**. The user's own +connection stays enabled and immediately becomes eligible again in Automatic. + +## One decision shared by execution and display + +Extend the existing proxy resolver boundary with a typed resolution result; +avoid a second registry of credentials or endpoints. Its input includes the +verified caller, logical or exact target, operation descriptor and execution +constraints. Candidate provenance is explicit: `personal`, `organization`, +`platform`, or `no_auth`, independently of the row's owner. + +Conceptual result (field names are proposed, not an existing API): + +```text +ServiceResolution + requested_slug / catalog_service_id / target_kind + requested_mode / effective_mode / selection_origin: request | saved | default + availability: ready | conditional | unavailable | blocked + selected_source / selected_connection_id / display_label + reason / recovery_action + candidates[]: source, allowed display label, eligibility, reason + verification: not_verified | known_credential_failure + evaluated_at / last_used_at + billing: payer, applicable charge layers, pricing reference + constraints: exact binding, required node, approval requirement +``` + +Execution enumerates permitted candidates and evaluates the selected concrete +route through the existing ordered gates. In particular, exact approvals retain +their claim and live policy/authority checks before credential materialization, +and their second authority comparison after materialization and before provider +effects. Bind approval/execution-authority digests and audit attribution to that +concrete route. A candidate change restarts the applicable route gates; never +reuse a grant obtained for the failed one. Once an approval or execution has +pinned the target, changes require revalidation or a new approval as appropriate. +Billing reservation occurs only for the final authorized route before dispatch. + +Build each candidate as a complete `ProxyTarget`: endpoint, credential, default +headers, identity propagation and node constraints travel together. Switching +sources must never send a personal credential or private connection header to a +platform endpoint, or transplant platform authority onto a user-controlled URL. + +The read-only projection uses the same candidate rules without decrypting, +refreshing OAuth tokens, probing providers, incrementing pool counters, consuming +rate-limit allowances or reserving funds. It reports `conditional` when refresh, +operation details, pool selection, billing reservation or verification must still +be resolved. It is an explanation of current facts, not a reusable execution +grant. Reevaluate live state on every call. + +Expose this projection on the service listing/detail and an actor-authorized +availability read. HTTP proxy, MCP discovery/execution, LLM ingress and CLI should +consume the same rule set for canonical calls. Preserve exact instance endpoints +and instance-specific MCP operation schemas. Do not publish a canonical MCP tool +whose schema or account semantics differ across its permitted candidates. + +Batch projection inputs for a page and avoid one credential/node/provider lookup +per card. Keep any preview cache short and keyed by caller/agent, owner, operation +and policy/credential version. Execution never trusts a cached preview. + +## What the user sees + +Keep the existing AI Services page and individual connection management. Add a +service-level row or header for each canonical service that answers **“What will +this slug use?”** Its connection details remain expandable/manageable underneath. + +Show one top-level service card per canonical catalog identity, with source +connections underneath. The service header always shows its canonical slug/API +URL, **Ready via [source]** (or a blocking state), and either the priority caption +**Automatic · You → Organization → NyxID** or **Platform selected**. Use actual organization names when +known. This makes clear that one service address can use several connections. +Associate personal and organization rows with that service by catalog ID; derive +the platform candidate from its validated catalog configuration/mapping. Custom +services remain independently addressed. + +| Primary status | Supporting text | Primary action | +| --- | --- | --- | +| Ready via your connection | `Personal OpenAI · last used 2 min ago` | View routing | +| Ready via Acme | `Organization connection · billed to Acme` | View routing | +| Ready via NyxID | `Your connection needs reconnection; no usable org connection · platform pricing applies` | Reconnect | +| Ready via NyxID | `No usable personal or org connection · platform pricing applies` | Connect your account | +| Connection check needed | `Your connection will be checked on use` | View routing | +| Unavailable | `Your connection needs reconnection; no usable org or platform connection` | Reconnect | +| Unavailable | `No usable personal, org or platform connection` | Connect | +| Payment required | `NyxID fallback requires credits` | Add credits | +| Access denied | Specific caller-safe reason | Request access, when applicable | + +These are illustrative states, not live service claims. “Ready” describes route +readiness. For an unverified API key, say **Not yet verified**. Existing +`last_used_at` means last use/attempt, not success. A future last-success indicator +must be backed by actual outcome evidence. Do not synthesize a green health +indicator from `status: active` or `auto_connected: true`. + +An expanded example: + +```text +OpenAI Ready via Acme +Slug: llm-openai +Automatic · You → Acme → NyxID + +Connection order +1 Your connection Skipped: needs reconnection Reconnect +2 Acme Selected for the next request +3 NyxID Available as fallback + +Billing: Acme · View pricing +Last request: Acme · 2 minutes ago + +View connections Copy service URL +``` + +Label every visible candidate **Selected**, **Available as fallback**, +**Not selected**, or **Skipped: [reason]**, according to the effective mode. +Give missing/unusable connections an appropriate repair +action. Explain why the selected candidate wins: “Your connection needs +reconnection, so new requests use Acme.” In Automatic mode, higher-priority recovery +changes the preview and the next request's source automatically; refresh the projection +after connection edits/reconnection and on page focus. Record the actual source +per request so users can inspect when fallback occurred. + +The default card is scoped to the signed-in user. If an API key or agent has +narrower scope or a pinned credential, its result can differ. An expanded **Access +as: You / [authorized agent key]** preview should make that distinction visible; +evaluate the selected caller's real constraints server-side. The group status +is general readiness, while an operation-specific preview can explain additional +request-specific restrictions. + +Use text plus an icon for status; color is supplementary. The actual final route +also appears in request history/audit and response metadata, because the next call +can differ from the preview. CLI output should show `SOURCE`, `AVAILABILITY` and +`REASON`. MCP discovery should return repair information for unavailable services +while executable tool publication follows the shared eligibility result. + +**Disable remains a connection action.** It turns off that connection and states +the resulting source before the change: for example, “This service will use Acme +while your connection is disabled.” Exact calls to a disabled connection still +fail. Turning off **Allow NyxID fallback** excludes only the platform tier; a +usable authorized org connection still precedes the terminal error. Disabling a +connection must not be presented as disabling every source of the logical service. +Auto-provisioned platform rows remain platform-managed; the opt-out belongs in +the routing preference because those rows reject user mutation. Conservatively +migrate existing disabled/disconnected intent instead of interpreting an old +Disable action as new consent to paid fallback. Delete retains its existing +credential/endpoint removal behavior. + +## Failure contract + +Return a stable machine-readable reason, a safe human message, and an authorized +recovery action. Preserve the existing error envelope and numeric code registry +in `backend/src/errors/mod.rs`; allocate any new variant there during +implementation rather than inventing a competing code table in this proposal. + +Add structured reasons while retaining existing applicable HTTP status/code +contracts: credential setup failures are generally 400, access denial 403, and +funding failures 402. Preserve existing node/pool error mappings. Unknown or +invisible services remain 404. Define a new aggregate failure only where no +existing variant accurately represents it, with allocation in the error registry. +No failure path forwards without a qualified route. + +Illustrative new reason: `no_usable_connection`, message: “OpenAI is unavailable. +Your connection needs reconnection, and no usable organization or NyxID connection +is available.” The +response can include caller-visible candidate reasons and a reconnect action; +never include platform secrets, private endpoint URLs or unauthorized org/account +details. Real database/decryption errors retain their sanitized server-fault +contract rather than being rewritten as connection setup failures. + +## Implementation sequence and proof of completion + +1. **Explain existing decisions.** Extract candidate provenance and read-only + availability from the current resolver. Share the decision rules with proxy, + MCP, approval ownership and display. Add the service status/header, routing + explanation, repair actions, billing/approval disclosure and response/audit + metadata. This stage preserves routing choices; it must not silently replace + the unordered catalog-ID selection with a different identity. +2. **Deliver user → organization → platform fallback.** Introduce catalog-identity + selection and typed pre-dispatch fallback across all three tiers behind a + rollout switch. Organization fallback is required in this stage, including + when the user's configured connection is unusable. Check each organization's + permissions, compatibility, approval requirements and billing ownership. + Handle a single unambiguous personal connection even with a suffixed slug, + preserve exact matches/pins, and return a choice action for ambiguous accounts. + Ship the automatic platform opt-out and explicit platform-only choice, both + saved per service and overridable per canonical request, in the same stage. Inventory the + actual catalog, preserve disabled intent and billing authorization, and close + the legacy fallthrough around the new decision. Cover HTTP/WS, MCP and LLM + call sites for the same logical target; LLM ingress already shares the core + resolver. Verify consistency rather than adding a separate LLM resolver. +3. **Extend account selection separately.** Add richer default-selection UI and + organization preference controls. Keep alternative-account balancing in + existing pools. This stage is not required to ship the core three-tier order. + +Use the existing service models for credentials and routes. If a separate +per-owner `ServiceRoutePreference` document is needed, keep it small: UUID ID, +`user_id`, catalog ID, optional preferred connection, +`routing_mode: automatic | platform_only`, and `allow_platform_fallback` for +Automatic mode. +Apply the repository's MongoDB +datetime/collection conventions and owner ACLs. It records routing intent, not a +duplicate connection inventory. A unique owner/catalog key prevents competing +defaults. Review concurrent changes and rollout migration with execution checks. + +Acceptance cases must demonstrate: + +- In Automatic mode, a working personal connection beats platform even when its concrete slug is + suffixed and a platform auto-provision row exists. +- In Automatic mode, a working personal connection beats an available organization connection; an + unusable personal connection selects a usable authorized organization before + platform. Missing/unusable user and org candidates select eligible platform; + no usable tier returns an error without business dispatch. +- In Automatic mode, reconnecting the personal connection restores its priority on the next request + with the same API slug. Multiple organizations follow the displayed order. +- A recoverably expired OAuth credential refreshes and stays selected; a terminal + refresh failure permits an authorized fallback before business dispatch. +- No usable route produces an actionable error with zero business dispatches. +- Disabled connections stay disabled; turning off platform fallback excludes it + through every path while retaining eligible org routing. Existing disconnected + intent survives migration. +- Explicit connection IDs, agent bindings, custom/pool slugs, node-only contracts, + org ACLs and exact approvals retain their constraints. +- Platform substitution happens only for the configured compatible operation; + a GitHub account, OpenAI file ID or instance-only MCP operation cannot drift to + another account. +- Multiple accounts without a default are deterministic and actionable; no + unordered `find_one` decides identity. +- Preview performs no credential refresh, decryption, pool advancement, provider + request, billing reservation or grant issuance/redemption, and labels unknowns. +- Fallback billing uses the final route and payer; a rejected or abandoned + candidate does not create duplicate usage or leak a funding reservation. +- Platform-only selects an eligible platform while personal and org connections + are healthy; an unavailable/denied platform errors without fallback. Repairing + a personal connection does not override the saved platform choice. +- Per-request source choice overrides the saved mode without changing that + preference. Conflicting exact pins/agent bindings are rejected. Invalid source + values fail validation and the source selector never reaches the provider. +- UI preview and actual request attribution distinguish Automatic fallback from + deliberate platform selection, including payer and approval requirements. +- A timed-out write or interrupted stream is never sent through a second + connection. A stale health observation cannot disable a replaced credential. +- Proxy, MCP, LLM and CLI agree for the same caller, operation and current state; + a platform database row alone is insufficient to report availability. + +No application implementation or deployment is part of this proposal. diff --git a/docs/plans/slug-routing-fable-review.md b/docs/plans/slug-routing-fable-review.md new file mode 100644 index 000000000..011d233ef --- /dev/null +++ b/docs/plans/slug-routing-fable-review.md @@ -0,0 +1,494 @@ +# Slug routing: Fable review + +Status: design review for the "stable slug prefers a working user connection, +then platform, else explicit error" proposal. Read-only; no code changed. +Companion to the primary's combined proposal. Line refs are against `main` +at 9bb33bcf. + +## 1. Recommendation in one paragraph + +Ship this as three releases, not one. Release 0 adds a single route +evaluator that both execution and previews consume, replaces the resolver's +scattered 400/404 exits with one structured "no usable connection" error, and +makes every surface (`/keys`, detail page, CLI, MCP) render the evaluator's +verdict. It changes no routing decision except making the one silent +fallthrough that already exists visible. Release 1 turns on the only new +routing behaviour the user asked for: a present-but-unusable personal +connection falls to the platform candidate, gated by the catalog predicate +that already defines "platform credential", with a user opt-out and explicit +billing disclosure. Release 2 handles the cases the primary flagged as open +(unusable personal to org, multiple personal accounts, "make default"). Do not +ship any release that switches between two user-owned accounts automatically. + +## 2. What the code does today (evidence) + +### 2.1 The resolver short-circuits and errors instead of trying the next candidate + +`resolve_proxy_target_from_user_service` (`backend/src/services/proxy_service.rs:1394-1562`) +does: personal exact slug row -> `finish_resolution`; else personal pool; else +legacy personal guard -> `Ok(None)`; else org walk. Any failure inside +`finish_resolution` propagates: + +| Condition | Where | Result today | +|---|---|---| +| `UserApiKey.status != "active"` | `proxy_service.rs:2650-2655` | 400 `API key is expired` (or revoked/failed/...) | +| credential not materializable | `proxy_service.rs:2660, 2672` | 400 `OAuth connection is not complete...` / `No credential stored...` | +| auto-provisioned row no longer eligible | `proxy_service.rs:2359-2397, 2497` | 404 `Service is no longer available` | +| org role/scope blocked | `proxy_service.rs:1556-1560` | 403 `OrgRoleInsufficient` | +| pool has no active member | `service_pool_service.rs:395`, `errors/mod.rs:725` | 502 code 11403 | + +None of these carries "which candidates exist and why each was skipped". + +### 2.2 There already is a silent platform fallthrough, and it is inconsistent + +When no `UserService` matches (including when the personal row is +**disabled or deleted**, since `find_by_slug` filters `is_active: true`, +`user_service_service.rs:580-589`), the handler falls to the legacy path +(`handlers/proxy.rs:1342-1348`) and `resolve_proxy_target` +(`proxy_service.rs:986-1119`) injects the catalog master credential whenever +`!requires_user_credential && auth_method != "none"` (`proxy_service.rs:1102-1112`). + +So for a master-credential catalog slug today: + +- personal key **expired** -> 400, no platform +- personal row **disabled or deleted** -> platform credential, silently, with + platform billing class and platform rate limit, no header saying so + +That asymmetry is the strongest argument for an explicit evaluator. It also +means "Disable" already has slug-level consequences nobody designed. + +### 2.3 "Platform connection" is two different things + +1. **Catalog master credential**: `DownstreamService.credential_encrypted` + non-empty with `requires_user_credential=false`. Reached via the legacy + path above, or via an auto-provisioned personal `UserService` + (`source="auto_provision"`, `api_key_id=None`, `master_credential=true`, + `proxy_service.rs:2492-2555`). The auto-provision predicate + `is_public_internal_master_credential_service` (`proxy_service.rs:2269-2279`) + requires `visibility=public`, `service_category=internal`, + `service_type=http`, and **`provider_config_id.is_none()`**. +2. **Platform vendor templates**: internal `platform-*` slugs + (`catalog_spec_sync.rs:216-218`), hidden from catalog reads and from + `create_key` (`unified_key_service.rs:825-829`), used by server-chosen + assistant operations (`platform_operation_service.rs`). Not addressable by + user slug. Out of scope for slug routing. + +Only (1) is a fallback candidate. Note the `provider_config_id.is_none()` +clause: every OAuth-provider-backed catalog row (GitHub, Google, Lark, X) is +already **structurally ineligible** as a platform candidate. That is the +right answer to "account-bound credentials cannot substitute platform +identity", and the proposal should cite it rather than add a new rule. The +legacy path (`resolve_proxy_target`) does not check this clause itself; it +relies on `authorize_master_credential -> validate_master_credential_service` +(`proxy_service.rs:150-152`). Confirm that helper excludes provider-backed +rows before relying on it. + +### 2.4 Same slug, both a user key and a platform key: possible but data-dependent + +One catalog slug is one `DownstreamService` row. `create_key` on a catalog +slug does not reject a user-supplied credential when the row also carries a +master credential (`unified_key_service.rs:812-945`; the only rejection is +the `platform-*` vendor guard). So the user's scenario exists only for rows +that are `internal` + `public` + master credential + BYO allowed. Whether any +production catalog row is shaped like that must be checked against data, not +seeds. If none is, the feature is really "canonical slug maps to a +*different* platform slug", which needs an explicit catalog-identity mapping +(see 3.2), not slug-text matching. + +### 2.5 Identity is `catalog_service_id`, not slug text + +- `resolve_unique_slug` auto-suffixes (`llm-openai-2`), and `PreserveExact` + lets a user pick any slug for a catalog service + (`unified_key_service.rs:100-153`). `UserServiceResolution.catalog_service_slug` + is loaded separately (`proxy_service.rs:2844-2870`) and the tests assert it + can differ from `UserService.slug` (`proxy_service.rs:4378-4382`). +- Consequence today: a user with a working key under custom slug `oai` who + calls `/proxy/s/llm-openai` (the catalog slug) gets the legacy platform + path, not their own key. Canonical selection must key on + `catalog_service_id`. +- `find_by_catalog_service_id` is an unordered `find_one` + (`user_service_service.rs:644-656`). With two active personal rows for the + same catalog (multi-connection is explicitly supported for OAuth, + `unified_key_service.rs:860-869`), catalog-id resolution is nondeterministic. + The primary's evidence is correct. +- `auto_provision_no_auth_services` skips creating the platform row when + **any** row (active or inactive) exists for the catalog id + (`unified_key_service.rs:1728-1745`). So a user who ever added their own key + never gets a platform row, and after deleting it they get the legacy path + instead. Also it runs lazily from `list_keys`; agent-only users who never + open `/keys` never get platform rows at all. + +### 2.6 Four partial "is it usable" evaluators, none of them the resolver + +| Surface | Evaluator | What it checks | +|---|---|---| +| `/keys` | `build_key_view` (`unified_key_service.rs:3993-4002`, `oauth_connection_status` 4059) | `status`, `connection_status`, `credential_missing`, `node_status`, `is_active` | +| frontend card/table | `keys.tsx:152-156` precedence in the component | `connection_status` > node status > credential status | +| CLI | `commands/service.rs:25-47 display_status` | same rules, re-implemented | +| MCP tools | `mcp_service.rs classify_credential` (~1762) | active-key map + node online | +| `/proxy/services` | `proxy_discovery_service.rs` | connection presence only | +| assistant readiness | `assistant_readiness_service.rs:219-330` | fixed capability registry, mirrors personal > legacy > org | + +None of these knows about pools, approvals, API-key scope, org role, or the +auto-provision eligibility recheck. Each can disagree with what the proxy +will actually do. The user's "clear way to visualize" is unachievable until +they all read one decision. + +### 2.7 Things a fallback would silently change + +- **Billing class**: `final_credential_class` (`handlers/proxy.rs:4131-4160`) + maps `master_credential` to `NyxidManagedMaster`; resale pricing applies + only in that class (`billing/route_context.rs:55-60`); platform per-user + rate limiting applies only on master-credential paths + (`proxy_service.rs:2505-2511, 1104-1110`). A user-to-platform switch changes + what the request costs and who pays. +- **Approvals**: execution authority binds `user_service_id` + (`execution_authority.rs:65,104,160`); approval identity compares + `user_service_id` (`approval_service.rs:701`). A grant for the personal row + does not cover the platform row. Also auto-connected rows suppress the + implicit global default approval (`is_auto_connected`, + `proxy_service.rs:1292-1296`), so the platform candidate may need *less* + approval than the personal one. Both directions must be visible in preview. +- **Agent scope and bindings**: `ApiKey.allowed_service_ids` are `UserService` + ids; `AgentServiceBinding` is keyed by `(api_key_id, user_service_id)`. The + platform row is a different id. +- **Legacy guard counts unusable tokens as present**: + `legacy_personal_provider_token_filter` accepts `expired` and + `refresh_failed` (`proxy_service.rs:1638-1647`), so a dead legacy token + blocks org fallback today. "Present" and "working" are already conflated. + +### 2.8 Pins already exist + +`?_nyxid_via=` resolves one exact row and refuses a +cross-slug id (`proxy_service.rs:1894-1911`). Exact slugs are unique per +owner among active rows. The pin primitive does not need inventing; only the +canonical-slug selection is new. + +## 3. Proposal + +### 3.1 One evaluator, two modes + +Add `slug_route_service::evaluate(actor, requested_slug, mode)` returning: + +``` +RouteDecision { + requested_slug, + catalog_identity: Option<{ catalog_service_id, catalog_slug }>, + candidates: [ RouteCandidate ], // ordered as they will be tried + selected: Option, + unavailable_reason: Option, + evaluated_at, health_scope: "pre_dispatch" // never claims downstream health +} +RouteCandidate { + kind: exact | personal | legacy | org | platform, + user_service_id: Option, slug, owner: personal | org{org_user_id} | platform, + eligibility: eligible | eligible_needs_refresh | ineligible(ReasonCode) | unknown, + billing: user_owned | platform_resale | no_auth | node_managed, + approval: none | required | granted, + actions: [ enable | reconnect | continue_auth | rebind_node | ask_org_admin | update_key_scope ] +} +``` + +Execution mode wraps the existing resolver: evaluate eligibility read-only in +order (`ProxyCredentialResolution::read_only_snapshot()` already exists, +`proxy_service.rs:1367-1374`), materialize only the first eligible candidate +via `finish_resolution`, and if materialization itself fails (refresh +failure, decrypt failure) move to the next candidate. The boundary is "before +the first byte is sent downstream". Downstream responses never trigger +re-selection in any release; they may only update credential status through +the existing refresh-failure paths. Preview mode never refreshes, never +touches `last_used_at`, never rate-limits, and reports +`eligible_needs_refresh` honestly for OAuth keys with a refresh token and an +expired access token. + +Fast path preserved: when the exact personal row is eligible, the cost is the +same single query as today. + +### 3.2 Selection rules + +1. **Exact wins.** If the requested slug matches an active `UserService` + (or pool) for the actor, that is the exact candidate. A `_nyxid_via` id or + an `AgentServiceBinding` is a pin: no fallback, ever. An exact custom slug + with no `catalog_service_id` has no other candidates. +2. **Canonical identity from the exact row or the catalog.** If the exact row + is unusable and carries `catalog_service_id`, or the slug is a catalog + slug with no exact row, candidates are collected by `catalog_service_id`: + other personal rows (deterministic order: slug equal to catalog slug + first, then `created_at` asc), legacy personal, org rows in membership + order with role/scope checks, then the platform candidate. +3. **Platform candidate eligibility** = `is_public_internal_master_credential_service` + + actor-addressed operation policy + consent (all existing checks) + + the user has not disabled the platform row (3.4). No new admin flag in + release 1; the predicate is the policy. If admins later want per-row + opt-in, add `DownstreamService.platform_fallback` with a backfill equal to + the predicate so nothing changes on deploy. +4. **User-owned accounts never substitute for each other automatically.** + If the canonical row is unusable and another personal row for the same + catalog is usable, the decision is `unavailable` with reason + `other_personal_connection_available` and the other slug named in the + action. Same for org-owned rows in release 1 (keep the NyxID#209 order: + personal presence still outranks org). Release 2 may add unusable-personal + to org behind the same evaluator, with the org billing owner disclosed. +5. **API-key scope filters candidates**, it does not error. A candidate + outside `allowed_service_ids` is `ineligible(out_of_scope)`. + +### 3.3 One error + +New variant `SlugRouteUnavailable { slug, decision }`, HTTP 400 for client +compatibility (today's failures are already 400), numeric code in a new +block 11800-11809, `details.candidates[]` with reason and action per +candidate. Replace the exits in 2.1 for slug-addressed requests. Keep 403 +`OrgRoleInsufficient` when the only candidates are org rows the actor may +not use, since that is a permission statement, not an availability one. + +Reason codes (one per existing state, no new states): `disabled`, +`credential_missing`, `pending_auth`, `expired`, `revoked`, `failed`, +`refresh_failed`, `node_offline`, `node_draining`, `node_deleted`, +`out_of_scope`, `org_role_insufficient`, `auto_provision_ineligible`, +`platform_policy_blocked`, `other_personal_connection_available`. + +### 3.4 Disable intent (answering the primary's open question) + +Keep Disable as a connection control; do not introduce a second control +also called Disable at the slug level (Rule 8 forbids lifecycle synonyms, and +"disable the route" and "disable the connection" would collide in the UI). + +My first instinct was to express the opt-out by disabling the auto-provisioned +platform row itself. Verified against code, that does not work without a +guard change: `ensure_user_managed_service` (`user_service_service.rs:74-81`) +rejects every user mutation on `source = "auto_provision"` rows, including +`is_active`, and the `/user-services` and `/keys` routes document that 403. +So the opt-out needs its own small authoritative record (FI-004): a per-owner, +per-catalog preference document with `platform_mode: first_then_platform | +your_connection_only` and an optional `preferred_user_service_id`, unique on +`(user_id, catalog_service_id)`. The combined draft proposes the same shape +(`ServiceRoutePreference`); I agree with it, with two limits below in 4.2. + +The platform candidate is then virtual: synthesized from the catalog row the +way the legacy path already does, never dependent on `list_keys` having run. +That also fixes the agent-only-user gap in 2.5. + +Whichever is chosen: once the evaluator covers a slug, the handler must not +fall into the legacy `resolve_service_by_slug` path for it unless a legacy +connection exists. Otherwise `your_connection_only` is silently bypassed by +the fallthrough from 2.2. + +Whichever is chosen: once the evaluator covers a slug, the handler must not +fall into the legacy `resolve_service_by_slug` path for it unless a legacy +connection exists. Otherwise disabling the platform row re-enables the +silent fallthrough from 2.2. + +### 3.5 Visualisation + +- **`/keys` list**: group cards by catalog identity. Each group header shows + the verdict for the canonical slug: `Available via your connection`, + `Available via platform (wallet-billed)`, or `Unavailable: ` with + the action button. Child cards keep today's per-connection badges. The + `Disabled` and `Credential Missing` badges stay; they are candidate state, + not slug state. +- **Detail page**: extend the existing Routing section + (`components/dashboard/routing-section.tsx`) with the ordered candidate + list and a "Check route" button that calls the preview endpoint. Show + billing class and approval requirement per candidate. +- **API**: `GET /api/v1/keys/route/{slug}` returns `RouteDecision`. It is + read-only and must live in the delegated-read allowlist, not the deny + classes. +- **CLI**: `nyxid service route ` prints the table; `nyxid service list` + gains a `route` column derived from the same call, replacing the local + `display_status` re-implementation. +- **MCP**: `nyx__list_connected_services` and `discover_services` include + `available_via` and `unavailable_reason`; tool visibility follows the + evaluator so MCP never hides a tool the proxy would serve via platform, and + never advertises one it would refuse. +- **Response and audit**: every proxy response carries + `X-NyxID-Route: personal|org|platform|legacy` and the selected + `user_service_id`; the routing audit event records the requested slug, the + selected candidate, and the skipped candidates with reason codes. No + credential material, ever. + +### 3.6 Smallest safe first release (challenge to the primary's bundle) + +The primary's leaning bundles evaluator, fallback, preview, and UI. I would +cut it: + +**Release 0, no routing change**: evaluator + structured error + preview +endpoint + all surfaces reading it + one determinism fix (ordered +`find_by_catalog_service_id`). Leave the legacy guard's "expired counts as +present" rule alone until Release 2; Release 0 only reports it as a candidate +with reason `expired`. Make the existing legacy platform fallthrough visible +via header and audit. This is shippable behind no flag because every request +resolves to the same row it does today. + +**Release 1, one new behaviour**: present-but-ineligible personal row falls +to the platform candidate, with the platform-row opt-out from 3.4 shipping in +the same release. Do not ship fallback without the opt-out: fallback spends +wallet credits. + +**Release 2**: unusable personal to org; "Make default for ``" +as an atomic slug swap between two personal rows; per-agent preferences. + +### 3.7 Objections and risks + +1. **The premise may not match production data** (2.4). If no catalog row is + both BYO-able and master-credentialed, "same slug, user then platform" + has no instances and the real need is a catalog-identity alias between a + user slug and a platform slug. Check before building Release 1. +2. **Fallback changes price and identity per request.** Acceptable only with + the header, the audit record, the preview, and the opt-out. Without all + four it is a billing surprise. +3. **Materializing after a failed candidate** can mark keys `failed` and + fire expiry notifications for a credential the request did not end up + using. That is correct (the credential is dead) but the notification copy + should not imply the request failed. +4. **Node failover stays below the boundary.** `fallback_node_ids` is + node-level retry for one selected `UserService`; do not fold it into the + evaluator, and do not let a node dispatch failure trigger candidate + re-selection (the body may already be streaming). +5. **Do not route on downstream 401/403** in any release. The reply may + carry side effects, bodies are consumed, and provider errors are not + reliably auth errors. +6. **Pools** already balance identical members but filter only `is_active` + (`service_pool_service.rs:374-390`). If the evaluator gains credential + eligibility, pools should use the same predicate for member viability or + a pool will keep selecting an expired member while the evaluator would + have skipped it. + +## 4. Review of the combined draft (`slug-connection-resolution-proposal.md`) + +The draft's evidence table is accurate; I re-checked every cited symbol +(`is_valid_master_credential_service` at `proxy_service.rs:308`, +`find_effective_service_owner` at 1992, `classify_credential`, +`resolve_member`). The product contract, the exact-vs-canonical split, the +pins, the no-replay rule, the preview/prepare/execute separation, and the +"a platform database row alone is insufficient" acceptance case are all +right and match my findings. What follows is only what I would change. + +### 4.1 Contradictions + +1. **Org fallback after a failed personal connection is both allowed and + forbidden.** The flowchart (`Usable user connection? No -> Usable + authorized org connection?`) falls to org unconditionally. The prose two + paragraphs later says a failed personal account does not authorize a new + account substitution, and the failure table says "only for a canonical + route whose policy allows that substitution". Pick one and fix the + diagram. My recommendation: Release 1 keeps today's rule (org only when + the actor has no personal connection at all); unusable-personal-to-org is + Release 2 and needs the org billing owner disclosed. +2. **"Disabled connections stay disabled; do not revive access through a new + fallback" versus what the code does now.** Disabling or deleting the + personal row on a master-credential catalog slug already revives platform + access through the legacy path (section 2.2). The draft's connection-level + Disable copy ("This service will use NyxID while this connection is + disabled") describes that existing behaviour, so the acceptance case + "existing disconnected intent survives migration" is ambiguous about + which intent. State explicitly: the legacy fallthrough is preserved but + made visible, and `your_connection_only` closes it. +3. **Default mode versus migration promise.** "The normal mode is Your + connection first, then NyxID" and "do not silently opt existing BYOK-only + users into new charges" conflict unless the default is conditional. Make + the rule crisp: the fallback mode is the default only where the platform + target is configured **and** the payer already holds applicable billing + authorization for that route; everyone else starts in + `your_connection_only` until they opt in from the routing panel. +4. **Failure contract HTTP code.** "No configured connection (proposed + 409)" changes the status every existing client sees on this path. Today + these are 400s (`API key is expired`, `No credential stored...`, + `proxy_service.rs:2650-2672`) and CLI, MCP, and SDK callers already handle + 400. Keep 400 and add the numeric code; 409 also reads as "conflict" to an + agent. Likewise "temporary route unavailability (503)" must not remap the + existing pool 502 (11403) or `NodeOffline` (8001). +5. **Canonical default for multiple personal rows.** The draft says + "several exist without a preference: ask the user to choose". On the slug + path the row whose slug equals the catalog slug is already an exact, + deterministic match; forcing a chosen default there regresses every + multi-connection user on day one with a `choose_default` error. The + unordered `find_one` only affects the catalog-id path + (`/proxy/{service_id}`). Rule: exact slug row is the default when usable; + a stored preference is required only when that row is unusable and other + personal rows exist. + +### 4.2 Unjustified scope for the first releases + +1. **Service-level Disable at the canonical header.** It is a third + lifecycle verb next to the two Rule 8 allows, it needs the preference + document to carry `is_active`, and nothing in the user's ask needs it: the + mode selector plus connection-level Disable already answers "stop using my + key" and "never use platform". Drop it, or rename and defer. +2. **Persisted credential-health observations** (verification state, + `last_success_at`, epoch-bound invalidation, "last succeeded 2 min ago"). + That is a new subsystem with its own invalidation rules. The ask is + pre-dispatch eligibility plus a clear error. Ship `Not yet verified` and + the existing `last_used_at` (`touch_last_used`, `proxy_service.rs:2662-2667`) + and defer health evidence. +3. **Operation allowlist on the platform target.** `DownstreamService` + already carries `proxy_operation_policy` with the + `PLATFORM_REQUIRE_OPERATION_POLICY` fail-closed switch (`docs/ENV.md:222`) + for exactly the "platform credential must not reach account-bound + operations" concern. Reuse it; do not add a parallel allowlist. +4. **Alias to a separate platform catalog row.** Only needed if production + has no catalog row that is both BYO-able and master-credentialed (section + 2.4). Check the data first; same-row fallback is the smaller feature. +5. **"Explicitly allowed alternative IDs" on the preference document.** An + ordered failover list of user connections is a second selection engine + beside pools, which the draft says it will not add. Keep the document to + `preferred_user_service_id` and `platform_mode` in v1. +6. **LLM ingress as separate rollout work.** `llm_proxy_request` already goes + through `resolve_proxy_target_from_user_service` (see the call-site note at + `proxy_service.rs:1671-1672`), so placing the evaluator inside the resolver + covers it. No separate LLM step; just say so. + +### 4.3 Gaps the draft should add + +- The legacy guard treats `expired` and `refresh_failed` provider tokens as + present (`proxy_service.rs:1638-1647`); under the draft's own "usable" + definition they are not. Decide when that changes (I say Release 2). +- Auto-provisioned rows reject all user mutation + (`user_service_service.rs:74-81`), so the opt-out cannot be "disable the + platform row"; the preference document is required for it. +- `X-NyxID-Route` (or equivalent) on every proxy response, and the skipped + candidates with reason codes in the routing audit event, so preview and + actual can be compared during rollout as step 3 promises. +- The `provider_config_id.is_none()` clause of + `is_public_internal_master_credential_service` is the existing structural + proof that OAuth-provider services (GitHub, Google) are never platform + candidates. Cite it; it is stronger than a policy statement. + +## 5. Reconciliation with the primary (final dispositions) + +Agreed by both parties on 2026-09-16. Where this section conflicts with +sections 3 and 4 above, this section wins. + +1. **No fallback on server faults.** KMS/decrypt failures, database errors, + and integrity violations (missing endpoint, dangling `api_key_id`) stay + sanitized 5xx and never trigger candidate re-selection. Only typed + pre-dispatch outcomes do: `credential_missing`, `pending_auth`, + `expired`/`revoked`/`failed`/`refresh_failed`, terminal refresh rejection, + `disabled`, `auto_provision_ineligible`, and unavailable permitted node + routes. This narrows my section 3.1 "if materialization fails, move on": + only a *typed* materialization failure moves on. +2. **Ordered catalog-id lookup is a routing change.** Replacing the + unordered `find_by_catalog_service_id` changes which row a catalog-id + call selects, so it is not part of the no-routing-change Release 0. + Release 0 only reports candidates. Canonical selection by catalog + identity lands with the fallback release, selects only when exactly one + usable personal row exists, and otherwise stops with an advisory + `choose_default` reason. This supersedes the "one determinism fix" + wording in section 3.6. +3. **No numeric codes allocated here.** The 11800-11809 suggestion in + section 3.3 is withdrawn. `backend/src/errors/mod.rs` is the registry and + allocation happens at implementation. +4. **Platform mapping.** Same-row fallback reuses the existing validated + master-credential predicate and authorization path. An admin-configured + mapping from a logical user catalog row to a distinct platform catalog row + is added only if production inventory confirms the two are distinct rows. + In both cases operation compatibility stays strict via the existing + operation policy; unknown compatibility fails closed. +5. **No atomic slug swap.** "Make default" is `preferred_user_service_id` on + the preference document; existing slugs and proxy URLs are never renamed. + Section 3.6 Release 2 "atomic slug swap" is withdrawn. +6. **Adopted from this review:** staged rollout; the existing silent + master-credential fallthrough is preserved but made visible and closed by + `your_connection_only`; the platform candidate is virtual and independent + of `list_keys`; `_nyxid_via` and agent bindings are the pins; Disable + stays connection-only with an explicit platform-mode opt-out; + failed-personal-to-org and multiple-account fallback are deferred. diff --git a/frontend/dev/routing-preview.ts b/frontend/dev/routing-preview.ts new file mode 100644 index 000000000..f17029a78 --- /dev/null +++ b/frontend/dev/routing-preview.ts @@ -0,0 +1,246 @@ +import { serviceViewSchema } from "../src/schemas/service-view"; +import { randomBytes } from "node:crypto"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import type { Plugin } from "vite"; + +const READ_PATHS = new Set([ + "/api/v1/users/me", + "/api/v1/keys", + "/api/v1/service-insights", + "/api/v1/api-keys", + "/api/v1/user-services", + "/api/v1/service-pools", + "/api/v1/service-pools/candidates", + "/api/v1/catalog", + "/api/v1/orgs", + "/api/v1/nodes", + "/api/v1/runtime-config", + "/api/v1/public/config", + "/api/v1/providers/codex-connection", + "/api/v1/keys/history/archived", + "/api/v1/billing/usage", + "/api/v1/options/service-history-action", +]); + +const UUID_SEGMENT = + "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; +const DETAIL_PATH = new RegExp(`^/api/v1/(?:keys|nodes)/${UUID_SEGMENT}$`); +const AGENT_METADATA_PATH = new RegExp( + `^/api/v1/api-keys/${UUID_SEGMENT}(?:/(?:bindings|usage))?$`, +); +const POOL_METADATA_PATH = new RegExp( + `^/api/v1/service-pools/${UUID_SEGMENT}(?:/(?:candidates|health))?$`, +); +const HISTORY_PATH = new RegExp(`^/api/v1/keys/${UUID_SEGMENT}/history$`); + +function isMetadataPath(path: string) { + return ( + READ_PATHS.has(path) || + POOL_METADATA_PATH.test(path) || + AGENT_METADATA_PATH.test(path) || + DETAIL_PATH.test(path) || + HISTORY_PATH.test(path) || + /^\/api\/v1\/catalog\/[a-z0-9][a-z0-9-]*$/.test(path) + ); +} + +function cookie(req: IncomingMessage, name: string): string | undefined { + return req.headers.cookie + ?.split(";") + .map((part) => part.trim()) + .find((part) => part.startsWith(`${name}=`)) + ?.slice(name.length + 1); +} + +function redirect(res: ServerResponse, location: string) { + res.writeHead(302, { + Location: location, + "Cache-Control": "no-store", + "Referrer-Policy": "no-referrer", + }); + res.end(); +} + +function json(res: ServerResponse, status: number, message: string) { + res.writeHead(status, { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }); + res.end(JSON.stringify({ message, error_code: status })); +} + +/** Opt-in, loopback-only access to production metadata for the local proposal. */ +export function routingPreview( + backendUrl: string, + frontendUrl: string, +): Plugin { + const states = new Map(); + const sessions = new Map(); + const sessionCookie = "nyxid_routing_preview"; + const stateCookie = "nyxid_routing_preview_state"; + + return { + name: "nyxid-local-routing-preview", + apply: "serve", + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + const address = server.httpServer?.address(); + if (!address || typeof address === "string") return next(); + const origin = `http://127.0.0.1:${address.port}`; + if (req.headers.host !== `127.0.0.1:${address.port}`) { + return json(res, 403, "Open this preview on 127.0.0.1."); + } + if (req.headers.origin && req.headers.origin !== origin) { + return json(res, 403, "Cross-origin requests are not allowed."); + } + const url = new URL(req.url ?? "/", origin); + const now = Date.now(); + for (const [id, expires] of states) + if (expires < now) states.delete(id); + for (const [id, session] of sessions) + if (session.expires < now) sessions.delete(id); + + if ( + url.pathname === "/__routing-preview/login" || + url.pathname === "/login" + ) { + if (req.method !== "GET") + return json(res, 405, "Use GET to sign in."); + const state = randomBytes(32).toString("hex"); + states.set(state, now + 600_000); + res.setHeader( + "Set-Cookie", + `${stateCookie}=${state}; HttpOnly; SameSite=Lax; Path=/; Max-Age=600`, + ); + const login = new URL("/cli-auth", frontendUrl); + login.searchParams.set("port", String(address.port)); + login.searchParams.set("state", state); + login.searchParams.set( + "client_ua", + "NyxID local routing preview (metadata only)", + ); + return redirect(res, login.toString()); + } + + if (url.pathname === "/callback") { + const state = url.searchParams.get("state"); + const token = url.searchParams.get("access_token"); + if ( + req.method !== "GET" || + !state || + !states.has(state) || + cookie(req, stateCookie) !== state || + !token || + token.length > 16_384 || + !/^[A-Za-z0-9_.-]+$/.test(token) + ) { + return json( + res, + 400, + "Login could not be verified. Open /__routing-preview/login to try again.", + ); + } + states.delete(state); + const id = randomBytes(32).toString("hex"); + // Only the short-lived access token is retained, in memory. Refresh + // tokens from the existing CLI callback are deliberately discarded. + sessions.set(id, { token, expires: now + 900_000 }); + res.setHeader("Set-Cookie", [ + `${sessionCookie}=${id}; HttpOnly; SameSite=Strict; Path=/; Max-Age=900`, + `${stateCookie}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`, + ]); + return redirect(res, "/keys?view=routing"); + } + + if (url.pathname === "/api/v1/auth/logout" && req.method === "POST") { + sessions.delete(cookie(req, sessionCookie) ?? ""); + res.setHeader( + "Set-Cookie", + `${sessionCookie}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`, + ); + return json(res, 200, "Local preview signed out."); + } + + if ( + !/^\/(api|oauth|mcp)(\/|$)/.test(url.pathname) && + !url.pathname.startsWith("/.well-known") + ) + return next(); + const savingView = + req.method === "PUT" && + url.pathname === "/api/v1/users/me/preferences/services" && + !url.search; + if ( + !savingView && + (req.method !== "GET" || !isMetadataPath(url.pathname)) + ) { + return json( + res, + 403, + "This preview permits metadata reads and saving your service view. Service changes and execution are disabled.", + ); + } + if (savingView && req.headers.origin !== origin) + return json(res, 403, "Save preferences from this preview only."); + const session = sessions.get(cookie(req, sessionCookie) ?? ""); + const isPublic = + url.pathname === "/api/v1/public/config" || + url.pathname === "/api/v1/runtime-config"; + if (!session && !isPublic) + return json(res, 401, "Sign in to view your production connections."); + let preferenceBody: string | undefined; + if (savingView) { + try { + const chunks: Buffer[] = []; + let size = 0; + for await (const chunk of req) { + const buffer = Buffer.from(chunk); + size += buffer.length; + if (size > 131072) + return json(res, 413, "Preferences are too large."); + chunks.push(buffer); + } + preferenceBody = JSON.stringify( + serviceViewSchema.parse( + JSON.parse(Buffer.concat(chunks).toString("utf8")), + ), + ); + } catch { + return json(res, 400, "Invalid service view preferences."); + } + } + try { + const response = await fetch( + new URL(url.pathname + url.search, backendUrl), + { + method: savingView ? "PUT" : "GET", + headers: { + ...(session + ? { Authorization: `Bearer ${session.token}` } + : {}), + ...(savingView ? { "Content-Type": "application/json" } : {}), + }, + body: preferenceBody, + redirect: "error", + signal: AbortSignal.timeout(20_000), + }, + ); + const body = await response.text(); + res.writeHead(response.status, { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }); + res.end(body); + } catch { + json( + res, + 502, + savingView + ? "Your default view could not be saved. Try again." + : "Production data could not be loaded. Try refreshing.", + ); + } + }); + }, + }; +} diff --git a/frontend/src/app.css b/frontend/src/app.css index daf13bf49..8da50fde2 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -876,3 +876,81 @@ html.theme-dark .assistant-halo--visible { border-color: rgba(255, 255, 255, 0.15); box-shadow: 0 0 24px rgba(255, 255, 255, 0.03); } + +.service-filter-pills { + --service-filter-pill-height: 2.75rem; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 2px; + max-height: calc(2 * var(--service-filter-pill-height) + 10px); + overflow-y: auto; + overscroll-behavior-y: contain; + scroll-snap-type: y mandatory; + scroll-padding-block: 2px; +} + +.service-filter-pills > * { + height: var(--service-filter-pill-height); + min-height: var(--service-filter-pill-height); + flex-shrink: 0; + scroll-snap-align: start; +} + +@media (min-width: 768px) { + .service-filter-controls form > div, + .service-filter-controls > div:first-child > div button, + .service-filter-controls > div:first-child > button { + height: 2rem; + } + .service-filter-pills { + --service-filter-pill-height: 1.75rem; + } +} + +/* Blur passing card outlines and fade the cover together at the lower edge. */ +.service-filter-toolbar[data-stuck="true"]::before { + content: ""; + position: absolute; + pointer-events: none; + top: -100vh; + height: calc(100vh + var(--service-filters-height, 0px) + 28px); + left: calc(-1 * var(--service-filter-gutter-left, 0px)); + right: calc(-1 * var(--service-filter-gutter-right, 0px)); + background: var(--color-background); + backdrop-filter: blur(8px); + mask-image: linear-gradient( + to bottom, + #000 calc(100% - 28px), + rgb(0 0 0 / 0.95) calc(100% - 20px), + rgb(0 0 0 / 0.6) calc(100% - 10px), + transparent + ); +} + +.service-card-header[data-stuck="true"]::before { + content: ""; + position: absolute; + pointer-events: none; + top: -100vh; + bottom: 0; + left: calc(-1 * var(--service-filter-gutter-left, 0px) - 1px); + right: calc(-1 * var(--service-filter-gutter-right, 0px) - 1px); + background: var(--color-background); +} + +/* Keep the expanding service and its neighbors attached to their grid positions. */ +html.service-card-transition::view-transition-group(*) { + animation-duration: 240ms; + animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); +} +html.service-card-transition::view-transition-old(root), +html.service-card-transition::view-transition-new(root) { + animation: none; +} +@media (prefers-reduced-motion: reduce) { + html.service-card-transition::view-transition-group(*) { + animation-duration: 0s; + } +} diff --git a/frontend/src/components/dashboard/grouped-service-cards.tsx b/frontend/src/components/dashboard/grouped-service-cards.tsx new file mode 100644 index 000000000..02f855314 --- /dev/null +++ b/frontend/src/components/dashboard/grouped-service-cards.tsx @@ -0,0 +1,727 @@ +import { Link } from "@tanstack/react-router"; +import { + useId, + useLayoutEffect, + useRef, + useState, + type ReactNode, + type RefObject, +} from "react"; +import { ChevronRight, Clock3, History } from "lucide-react"; +import { useServiceView } from "@/hooks/use-service-view"; +import { useServiceCardTransition } from "@/hooks/use-service-card-transition"; +import { ServiceViewToolbar } from "./service-view-toolbar"; +import { ServiceConnectionTable } from "./service-connection-table"; +import { ServiceAvatarStack } from "./service-avatar-stack"; +import { ServiceBillingSummary } from "./service-billing-summary"; +import { latestServiceEdit } from "@/lib/service-card-summary"; +import { + useServiceRoutingPools, + type ServiceRoutingPools, +} from "@/hooks/use-service-routing-pools"; +import { ServicePoolRoutingPanel } from "./service-pool-routing-panel"; +import { ServicePoolSummary } from "./service-pool-summary"; +import { useAuthStore } from "@/stores/auth-store"; +import { + connectionSourceLabel as sourceLabel, + connectionSource, + matchingConnections, +} from "@/lib/service-view"; +import { ServiceIcon } from "@/components/service-icon"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn, formatRelativeTime } from "@/lib/utils"; +import { + groupServiceConnections, + type ServiceConnectionGroup, +} from "@/lib/service-groups"; +import { + useServiceInsights, + type ServiceInsightsState, +} from "@/hooks/use-service-insights"; +import { + callerLabel, + latestRecordedUse, + outcomeLabel, +} from "@/lib/service-insights"; +import type { CatalogEntry, KeyInfo } from "@/types/keys"; + +function GroupCard({ + group, + expanded, + onToggle, + insights, + connections, + search, + renderConnectionActions, + filtersRef, + routing, + allConnections, + catalog, +}: { + readonly catalog?: CatalogEntry; + readonly routing: ServiceRoutingPools; + readonly allConnections: readonly KeyInfo[]; + readonly group: ServiceConnectionGroup; + readonly expanded: boolean; + readonly onToggle: (card: HTMLElement | null) => void; + readonly insights: ServiceInsightsState; + readonly connections: readonly KeyInfo[]; + readonly search: string; + readonly renderConnectionActions?: (key: KeyInfo) => ReactNode; + readonly filtersRef: RefObject; +}) { + const identity = useAuthStore((state) => state.user?.id); + const [routingOpen, setRoutingOpen] = useState(false); + const [requestedPanel, setRequestedPanel] = useState<{ + id: string; + view: "billing" | "requests" | "access" | "history"; + version: number; + } | null>(null); + const [routeId, setRouteId] = useState(null); + const pools = routing.pools.filter((pool) => + pool.members.some((member) => + connections.some((key) => key.id === member.user_service_id), + ), + ); + const selectedPool = pools.find((pool) => pool.id === routeId) ?? pools[0]; + const contentId = useId(); + const headingId = useId(); + const cardRef = useRef(null); + const headerRef = useRef(null); + const headerOffset = useRef(0); + const connectionIds = connections + .map((connection) => connection.id) + .join(","); + const [headerStuck, setHeaderStuck] = useState(false); + useLayoutEffect(() => { + if (!expanded) return; + const card = cardRef.current; + const header = headerRef.current; + const scroller = card?.closest("main"); + if (!card || !header || !scroller) return; + const rows = card.querySelectorAll( + "[data-service-connection-row]", + ); + const finalRowsStart = rows[Math.max(0, rows.length - 3)]; + const update = () => { + const bounds = header.getBoundingClientRect(); + const nativeTop = bounds.top - headerOffset.current; + const offset = finalRowsStart + ? Math.min( + 0, + finalRowsStart.getBoundingClientRect().top - + nativeTop - + bounds.height, + ) + : 0; + headerOffset.current = offset; + header.style.translate = offset ? `0 ${offset}px` : ""; + setHeaderStuck( + nativeTop > card.getBoundingClientRect().top + card.clientTop + 1, + ); + }; + update(); + const observer = new ResizeObserver(update); + observer.observe(card); + observer.observe(header); + observer.observe(scroller); + if (finalRowsStart) observer.observe(finalRowsStart); + const filterSurface = filtersRef.current?.firstElementChild; + if (filterSurface) observer.observe(filterSurface); + scroller.addEventListener("scroll", update, { passive: true }); + return () => { + observer.disconnect(); + scroller.removeEventListener("scroll", update); + headerOffset.current = 0; + header.style.translate = ""; + }; + }, [ + expanded, + filtersRef, + connectionIds, + routingOpen, + selectedPool?.id, + requestedPanel?.version, + ]); + const count = group.connections.length; + const matchingCount = connections.length; + const sources = [ + ...new Map( + connections.map((key) => { + const type = connectionSource(key); + const org = + key.credential_source?.type === "org" ? key.credential_source : null; + return [ + type === "org" ? org!.org_id : type, + { + id: type === "org" ? org!.org_id : type, + type, + name: sourceLabel(key), + avatarUrl: org?.avatar_url, + description: + type === "personal" + ? "Connections you own. Billing is shown separately." + : type === "org" + ? "Connections owned by this organization. Billing is shown separately." + : "Connections using NyxID credentials. Billing is shown separately.", + }, + ] as const; + }), + ).values(), + ]; + const lastEdit = latestServiceEdit(connections); + const disabled = connections.filter((key) => !key.is_active).length; + const connectionInsights = connections.map((key) => + insights.connections.get(key.id), + ); + const access = connectionInsights.map((item) => item?.usage?.access); + const configuredKeys = [ + ...new Map( + access.flatMap((item) => + (item?.keys ?? []).map((key) => [key.id, key.name] as const), + ), + ).values(), + ]; + const accessIncomplete = access.some( + (item) => + !item || + item.incomplete || + item.truncated || + item.visibility === "unavailable", + ); + const latestUse = connectionInsights + .map((item) => latestRecordedUse(item?.usage)) + .filter((request) => request !== undefined) + .sort((a, b) => b.occurred_at.localeCompare(a.occurred_at))[0]; + const useTracked = connectionInsights.every((item) => { + const activity = item?.usage?.activity; + return ( + activity && + activity.tracking !== "unavailable" && + activity.visibility !== "unavailable" + ); + }); + const ownUseOnly = connectionInsights.every( + (item) => item?.usage?.activity.visibility === "own_requests", + ); + const keysText = configuredKeys.length + ? `${configuredKeys.length}${accessIncomplete ? "+" : ""} agent ${configuredKeys.length === 1 ? "key" : "keys"}` + : accessIncomplete + ? "Agent keys unverified" + : "0 agent keys"; + const useText = latestUse + ? `${ownUseOnly ? "Your last use" : "Last use"} ${formatRelativeTime(latestUse.occurred_at)}` + : useTracked + ? "No use recorded · 30d" + : "Last use not reported"; + const agents = + insights.status === "loading" + ? { text: "Loading…", title: "Loading agent keys and use" } + : insights.status === "restricted" + ? { text: "Restricted", title: "Agent key access is restricted" } + : insights.status !== "ready" + ? { + text: "—", + title: + insights.status === "unavailable" + ? "Agent keys and use are not reported by this server" + : "Agent keys and use couldn't load", + } + : { + text: keysText, + title: [ + configuredKeys.length + ? `Keys with access: ${configuredKeys.join(", ")}` + : accessIncomplete + ? "Key access incomplete" + : "No agent keys with access", + latestUse + ? `${ownUseOnly ? "Your last use" : "Last use"}: ${callerLabel(latestUse.caller)}${latestUse.caller.app_name ? ` · ${latestUse.caller.app_name}` : ""} · ${outcomeLabel(latestUse.outcome)} · ${latestUse.occurred_at}` + : useTracked + ? "No recorded use with exact connection attribution in the last 30 days" + : "Use is not reported by this server", + ].join("\n"), + }; + const openSummary = ( + view: "billing" | "requests" | "access" | "history", + connectionId?: string, + ) => { + const lastConnection = + view === "requests" + ? connections + .map((connection) => ({ + id: connection.id, + request: latestRecordedUse( + insights.connections.get(connection.id)?.usage, + ), + })) + .filter((item) => item.request) + .sort((a, b) => + b.request!.occurred_at.localeCompare(a.request!.occurred_at), + )[0]?.id + : undefined; + const id = connectionId ?? lastConnection ?? connections[0]?.id; + if (!id) return; + setRequestedPanel((current) => ({ + id, + view, + version: (current?.version ?? 0) + 1, + })); + setRoutingOpen(false); + if (!expanded) onToggle(cardRef.current); + }; + + return ( +
+
+
+
+
+
+ +
+
+

+ +

+

+ {matchingCount < count + ? `${matchingCount} of ${count}` + : count}{" "} + {count === 1 ? "connection" : "connections"} +

+
+ {disabled > 0 && ( + {disabled} disabled + )} +
+ {!expanded && ( +

+ {search.trim() + ? `Matches: ${connections.map((key) => key.label).join(" · ")}` + : group.description} +

+ )} +
+ openSummary("billing", id)} + /> + { + setRoutingOpen(true); + if (!expanded) onToggle(cardRef.current); + }} + /> + + + + + + + {agents.title} + + + +
+ + +
+
+
+
+ +
+ {matchingCount < count && ( + + {matchingCount} of {count} match + + )} + + Service details + +
+
+
+
+ +
+ ); +} + +export function GroupedServiceCards({ + keys, + catalog, + renderConnectionActions, + actions, + renderTable, +}: { + readonly keys: readonly KeyInfo[]; + readonly catalog?: readonly CatalogEntry[]; + readonly renderConnectionActions?: (key: KeyInfo) => ReactNode; + readonly actions?: ReactNode | ((compact: boolean) => ReactNode); + readonly renderTable?: (keys: readonly KeyInfo[]) => ReactNode; +}) { + const view = useServiceView(); + const animateCards = useServiceCardTransition(); + const containerRef = useRef(null); + const filtersRef = useRef(null); + const [filtersStuck, setFiltersStuck] = useState(false); + useLayoutEffect(() => { + const container = containerRef.current; + const toolbar = filtersRef.current; + if (!container || !toolbar) return; + const surface = toolbar.firstElementChild as HTMLElement; + const scroller = toolbar.closest("main"); + let pinned = false; + let expandedHeight = surface.getBoundingClientRect().height; + const updateShadow = () => { + const toolbarTop = toolbar.getBoundingClientRect().top; + const next = container.getBoundingClientRect().top < toolbarTop - 1; + if (!pinned) + expandedHeight = Math.max( + surface.getBoundingClientRect().height, + Number.parseFloat(toolbar.style.minHeight) || 0, + ); + if (next !== pinned) { + // Keep the original flow height: shrinking scrollHeight can clamp + // scrollTop back across the pin threshold and repeatedly unpin it. + toolbar.style.minHeight = next ? `${expandedHeight}px` : ""; + pinned = next; + } + setFiltersStuck(next); + }; + const measure = () => { + const bounds = surface.getBoundingClientRect(); + container.style.setProperty( + "--service-filters-height", + `${bounds.height}px`, + ); + if (scroller) { + const viewportLeft = + scroller.getBoundingClientRect().left + scroller.clientLeft; + container.style.setProperty( + "--service-filter-gutter-left", + `${Math.max(0, bounds.left - viewportLeft)}px`, + ); + container.style.setProperty( + "--service-filter-gutter-right", + `${Math.max(0, viewportLeft + scroller.clientWidth - bounds.right)}px`, + ); + } + updateShadow(); + }; + measure(); + const observer = new ResizeObserver(measure); + observer.observe(surface); + if (scroller) observer.observe(scroller); + scroller?.addEventListener("scroll", updateShadow, { passive: true }); + return () => { + observer.disconnect(); + scroller?.removeEventListener("scroll", updateShadow); + }; + }, [view.accountId]); + const { filters, expanded } = view; + const groups = groupServiceConnections(keys, catalog); + const visible = groups + .map((group) => ({ group, matches: matchingConnections(group, filters) })) + .filter(({ matches }) => matches.length > 0); + const matchingKeys = visible.flatMap(({ matches }) => matches); + const insights = useServiceInsights(renderTable ? [] : keys); + const routing = useServiceRoutingPools(keys, !renderTable); + + return ( +
+ + + {visible.length} {visible.length === 1 ? "service" : "services"} ·{" "} + {matchingKeys.length} matching{" "} + {matchingKeys.length === 1 ? "connection" : "connections"} + + {expanded.length > 0 && ( + + )} + + {visible.length ? ( + renderTable ? ( + renderTable(matchingKeys) + ) : ( +
+ {visible.map(({ group, matches }) => ( + entry.slug === group.slug)} + connections={matches} + search={filters.search} + onToggle={(card) => + animateCards( + () => + view.setExpanded( + expanded.includes(group.id) ? [] : [group.id], + ), + expanded.includes(group.id) ? undefined : card, + filtersRef.current, + ) + } + renderConnectionActions={renderConnectionActions} + filtersRef={filtersRef} + /> + ))} +
+ ) + ) : ( +

+ {keys.length + ? filters.source === "personal" + ? "No services with a personal connection match this view. Choose All services to include services available only through an organization or the platform." + : "No services match these filters. Clear filters to see all services." + : "No connected services."} +

+ )} +
+ ); +} diff --git a/frontend/src/components/dashboard/pool-editor.tsx b/frontend/src/components/dashboard/pool-editor.tsx index 2244fdd67..1d283c7a5 100644 --- a/frontend/src/components/dashboard/pool-editor.tsx +++ b/frontend/src/components/dashboard/pool-editor.tsx @@ -41,7 +41,7 @@ import { import { poolEditorDefaults, poolEditorPayload } from "./pool-editor-state"; import { PoolConnectionsEditor } from "./pool-connections-editor"; import { Choice, PolicyEditor, Toggle } from "./pool-controls"; -import { message, strategyLabels } from "./pool-labels"; +import { message, readOnlyPreview, strategyLabels } from "./pool-labels"; import type { PoolOperation } from "./pool-operation-check"; export function PoolEditor({ @@ -108,6 +108,7 @@ export function PoolEditor({ } } async function save(input: CreateServicePoolInput) { + if (readOnlyPreview) return; try { const normalized = { ...input, @@ -362,7 +363,9 @@ export function PoolEditor({ type="submit" variant="primary" isLoading={pending} - disabled={!isDirty || !isValid || conflict || pending} + disabled={ + readOnlyPreview || !isDirty || !isValid || conflict || pending + } > {pool ? "Save" : "Create pool"} diff --git a/frontend/src/components/dashboard/pool-health-dialog.tsx b/frontend/src/components/dashboard/pool-health-dialog.tsx index 0015e73a5..18ad28929 100644 --- a/frontend/src/components/dashboard/pool-health-dialog.tsx +++ b/frontend/src/components/dashboard/pool-health-dialog.tsx @@ -14,7 +14,13 @@ import { import { Skeleton } from "@/components/ui/skeleton"; import { usePoolHealth, useResetPoolHealth } from "@/hooks/use-pools"; import type { ServicePool } from "@/schemas/pools"; -import { bindingLabel, message, reason, strategyLabels } from "./pool-labels"; +import { + bindingLabel, + message, + readOnlyPreview, + reason, + strategyLabels, +} from "./pool-labels"; import { PoolOperationCheck, type PoolOperation } from "./pool-operation-check"; export function PoolHealthDialog({ @@ -132,7 +138,7 @@ export function PoolHealthDialog({ onClick={() => { void clear(row.user_service_id); }} - disabled={reset.isPending} + disabled={readOnlyPreview || reset.isPending} > Reset @@ -150,6 +156,7 @@ export function PoolHealthDialog({ void clear(); }} isLoading={reset.isPending} + disabled={readOnlyPreview} > Reset all cooldowns diff --git a/frontend/src/components/dashboard/pool-labels.ts b/frontend/src/components/dashboard/pool-labels.ts index 59baa5394..bbfd6d5fd 100644 --- a/frontend/src/components/dashboard/pool-labels.ts +++ b/frontend/src/components/dashboard/pool-labels.ts @@ -1,6 +1,10 @@ import { ApiError } from "@/lib/api-client"; import type { PoolCandidate } from "@/schemas/pools"; +/** The local production preview proxies reads only; keep pool writes disabled. */ +export const readOnlyPreview = + import.meta.env.DEV && import.meta.env.VITE_ROUTING_PREVIEW === "1"; + export const strategyLabels = { priority: "Automatic fallback", round_robin: "Round robin", diff --git a/frontend/src/components/dashboard/service-avatar-stack.test.tsx b/frontend/src/components/dashboard/service-avatar-stack.test.tsx new file mode 100644 index 000000000..e49307afd --- /dev/null +++ b/frontend/src/components/dashboard/service-avatar-stack.test.tsx @@ -0,0 +1,112 @@ +import { + cleanup, + render, + screen, + waitFor, + within, +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + ServiceAvatarStack, + type ServiceAvatarItem, +} from "./service-avatar-stack"; + +afterEach(cleanup); + +const items: ServiceAvatarItem[] = [ + { id: "personal", type: "personal", name: "Personal" }, + { id: "org", type: "org", name: "ChronoAI development organization" }, + { id: "platform", type: "platform", name: "NyxID platform" }, +]; + +describe("service avatar tooltips", () => { + it("shows details only for the hovered source without adding an inline label", async () => { + const user = userEvent.setup(); + render(); + const personal = screen.getByRole("button", { name: "Personal · Sources" }); + const org = screen.getByRole("button", { + name: "ChronoAI development organization · Sources", + }); + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); + await user.hover(personal); + expect( + within(await screen.findByRole("tooltip")).getByText("Personal"), + ).toBeInTheDocument(); + expect(within(personal).queryByText("Personal")).not.toBeInTheDocument(); + await user.hover(org); + // Happy DOM has no layout; move beyond the tooltip's zero-size grace area. + await user.pointer({ target: org, coords: { clientX: 40, clientY: 10 } }); + await user.pointer({ target: org, coords: { clientX: 41, clientY: 10 } }); + const tooltip = within( + await screen.findByRole("tooltip", { + name: /ChronoAI development organization/, + }), + ); + expect(tooltip.queryByText("Personal")).not.toBeInTheDocument(); + expect( + tooltip.getByText("ChronoAI development organization"), + ).toBeInTheDocument(); + await user.unhover(org); + await user.pointer({ + target: document.body, + coords: { clientX: 100, clientY: 100 }, + }); + await waitFor(() => + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(), + ); + }); + + it("reveals billing on keyboard focus, dismisses with Escape, and opens the selected connection", async () => { + const user = userEvent.setup(); + const onSelect = vi.fn(); + render( + , + ); + await user.tab(); + expect( + within(await screen.findByRole("tooltip")).getByText( + "Your personal account", + ), + ).toBeInTheDocument(); + await user.keyboard("{Escape}"); + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); + await user.keyboard("{Enter}"); + expect(onSelect).toHaveBeenCalledOnce(); + }); + + it("keeps sources beyond the initial stack reachable", async () => { + const user = userEvent.setup(); + const more = Array.from( + { length: 5 }, + (_, index): ServiceAvatarItem => ({ + id: `org-${index}`, + type: "org", + name: `Organization ${index}`, + }), + ); + render(); + expect( + screen.queryByRole("button", { name: "Organization 4 · Sources" }), + ).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Show all 5 entries · Sources" }), + ); + const last = screen.getByRole("button", { + name: "Organization 4 · Sources", + }); + await user.hover(last); + expect( + within(await screen.findByRole("tooltip")).getByText("Organization 4"), + ).toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Collapse · Sources" }), + ); + expect( + screen.queryByRole("button", { name: "Organization 4 · Sources" }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/dashboard/service-avatar-stack.tsx b/frontend/src/components/dashboard/service-avatar-stack.tsx new file mode 100644 index 000000000..83afd74d1 --- /dev/null +++ b/frontend/src/components/dashboard/service-avatar-stack.tsx @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { cn } from "@/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { ServiceOwnerAvatar } from "./service-owner-avatar"; + +export interface ServiceAvatarItem { + readonly id: string; + readonly type: "personal" | "org" | "platform"; + readonly name: string; + readonly avatarUrl?: string | null; + readonly detail?: string; + readonly description?: string; + readonly actionLabel?: string; + readonly onSelect?: () => void; +} + +export function ServiceAvatarStack({ + items, + label, +}: { + readonly items: readonly ServiceAvatarItem[]; + readonly label: string; +}) { + const [showAll, setShowAll] = useState(false); + const visible = showAll ? items : items.slice(0, 4); + return ( + +
4 ? 28 : 0), + }} + onBlurCapture={(event) => { + if (!event.currentTarget.contains(event.relatedTarget)) + setShowAll(false); + }} + onKeyDown={(event) => { + if (event.key === "Escape") setShowAll(false); + }} + > +
+ {visible.map((item, index) => ( + + + + + +

{item.name}

+ {item.detail &&

{item.detail}

} + {item.description && ( +

+ {item.description} +

+ )} +
+
+ ))} + {items.length > 4 && ( + + )} +
+
+
+ ); +} diff --git a/frontend/src/components/dashboard/service-billing-summary.tsx b/frontend/src/components/dashboard/service-billing-summary.tsx new file mode 100644 index 000000000..44ae6a835 --- /dev/null +++ b/frontend/src/components/dashboard/service-billing-summary.tsx @@ -0,0 +1,174 @@ +import { CreditCard } from "lucide-react"; +import type { ServiceInsightsState } from "@/hooks/use-service-insights"; +import { + connectionBillingCategory, + connectionBillingLabels, + type ConnectionBillingCategory, +} from "@/lib/service-card-summary"; +import { insightStatusLabel } from "@/lib/service-insights"; +import { plainBilling } from "@/lib/billing-plain"; +import type { CatalogEntry, KeyInfo } from "@/types/keys"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +/** Up to three names, then a count; disabled connections are counted apart. */ +function groupNames(connections: readonly KeyInfo[]): string { + const active = connections.filter((connection) => connection.is_active); + const disabled = connections.length - active.length; + const shown = active.slice(0, 3).map((connection) => connection.label); + const more = active.length - shown.length; + return [ + shown.join(", ") + (more > 0 ? ` and ${more} more` : ""), + disabled ? `${disabled} disabled` : "", + ] + .filter(Boolean) + .join(" · "); +} + +export function ServiceBillingSummary({ + connections, + insights, + catalog, + serviceName, + onOpen, +}: { + readonly connections: readonly KeyInfo[]; + readonly insights: ServiceInsightsState; + readonly catalog?: CatalogEntry; + readonly serviceName: string; + readonly onOpen: (connectionId: string) => void; +}) { + const rows = connections.map((connection) => ({ + connection, + billing: insights.connections.get(connection.id)?.billing, + category: + insights.status === "ready" + ? connectionBillingCategory( + connection, + insights.connections.get(connection.id)?.billing, + catalog, + ) + : ("unknown" as const), + })); + const categories: ConnectionBillingCategory[] = [ + "platform", + "byok", + "not_billable", + "unknown", + ]; + const countLabels = { + platform: "NyxID", + byok: "BYOK", + not_billable: "—", + unknown: "unverified", + }; + const notBillable = rows.every((row) => row.category === "not_billable"); + // Identical entries collapse into one line; a list of 30 equal rows says + // nothing more than "30 connections". + const groups = [ + ...rows + .reduce((all, row) => { + const detail = + row.category === "not_billable" + ? "Not billable by NyxID" + : row.category === "unknown" + ? row.billing?.service_billing_configured === true + ? "Whose key or app is used isn't confirmed" + : "Billing details unavailable" + : row.billing + ? plainBilling(row.connection, row.billing, catalog).short + : "Billing details unavailable"; + const key = `${row.category}|${detail}`; + const group = all.get(key) ?? { + key, + category: row.category, + detail, + rows: [] as typeof rows, + }; + group.rows.push(row); + return all.set(key, group); + }, new Map()) + .values(), + ].sort( + (a, b) => + categories.indexOf(a.category) - categories.indexOf(b.category) || + b.rows.length - a.rows.length, + ); + const label = + insights.status !== "ready" + ? insightStatusLabel(insights.status, "Billing") + : notBillable + ? "—" + : rows.length === 1 + ? connectionBillingLabels[rows[0]!.category] + : categories + .flatMap((category) => { + const count = rows.filter( + (row) => row.category === category, + ).length; + return count ? [`${count} ${countLabels[category]}`] : []; + }) + .join(" · "); + return ( + + + + + + + {notBillable ? ( +

Not billable by NyxID

+ ) : ( + <> +

Connection billing

+ {groups.map((group) => ( +
+

+ {connectionBillingLabels[group.category]} ·{" "} + {group.rows.length}{" "} + {group.rows.length === 1 ? "connection" : "connections"} +

+

{group.detail}

+

+ {groupNames(group.rows.map((row) => row.connection))} +

+
+ ))} +

+ NyxID: uses NyxID's key or app and costs NyxID credits. + BYOK: uses your or your organization's own key or app. —: + NyxID doesn't charge for it. +

+ + )} +
+
+
+ ); +} diff --git a/frontend/src/components/dashboard/service-connection-table.tsx b/frontend/src/components/dashboard/service-connection-table.tsx new file mode 100644 index 000000000..b89a53a7b --- /dev/null +++ b/frontend/src/components/dashboard/service-connection-table.tsx @@ -0,0 +1,687 @@ +import { Fragment, useState, type ReactNode } from "react"; +import { Link } from "@tanstack/react-router"; +import { + ArrowUpRight, + ChevronDown, + Clock3, + History, + LockKeyhole, + Settings2, + UsersRound, + CreditCard, + GitBranch, + Info, + type LucideIcon, +} from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { ServiceIcon } from "@/components/service-icon"; +import { ServiceOwnerAvatar } from "./service-owner-avatar"; +import { ServiceHistory } from "./service-history"; +import { + Table, + TableHeader, + TableHead, + TableBody, + TableRow, + TableCell, +} from "@/components/ui/table"; +import { connectionSource } from "@/lib/service-view"; +import { canEditConnection } from "@/lib/connection-access"; +import { classifyConnection } from "@/lib/service-routing-preview"; +import { + cn, + formatDate, + formatDateTime, + formatRelativeTime, +} from "@/lib/utils"; +import type { CatalogEntry, KeyInfo } from "@/types/keys"; +import type { ServicePool } from "@/schemas/pools"; +import { poolStrategyLabel } from "@/lib/service-pool-display"; +import { + connectionBillingCategory, + connectionBillingLabels, +} from "@/lib/service-card-summary"; +import type { ServiceInsight } from "@/schemas/service-insights"; +import { + useServiceInsights, + type ServiceInsightsState, +} from "@/hooks/use-service-insights"; +import { + ConnectionInsightPanel, + type InsightPanel, +} from "./service-insight-panels"; +import { plainBilling } from "@/lib/billing-plain"; +import { + callerLabel, + credentialLabel, + accessCountLabel, + latestRecordedUse, + outcomeLabel, + recordedSourceLabel, + insightStatusLabel, +} from "@/lib/service-insights"; + +const authNames: Record = { + bearer: "Bearer", + api_key: "API key", + oauth2: "OAuth 2.0", + basic: "Basic", + none: "No auth", + node_managed: "Node credential", +}; + +function target(key: KeyInfo): string { + if (key.credential_binding === "platform") return "Platform credential"; + if (key.service_type === "ssh") + return key.ssh_host + ? `${key.ssh_host}:${key.ssh_port ?? 22}` + : "Target not reported"; + return key.endpoint_url || "Target not reported"; +} + +function ConnectionMetadata({ + connection: key, + insight, +}: { + readonly connection: KeyInfo; + readonly insight?: ServiceInsight; +}) { + const editable = canEditConnection(key); + const rows = [ + ["Created", formatDateTime(key.created_at)], + ["Added via", key.source_app_name || "Not recorded"], + [ + "Last caller", + insight?.usage?.activity.requests[0]?.caller.name || "Not recorded", + ], + [ + "Credential prepared", + key.last_used_at ? formatDateTime(key.last_used_at) : "Not reported", + ], + ["Connection ID", key.id], + [ + "Credential expires", + key.expires_at ? formatDateTime(key.expires_at) : "Not reported", + ], + ...(editable + ? [ + ["API target", target(key)], + ["Node", key.node_id || "Direct"], + ["Permissions", key.granted_scopes?.join(", ") || "Not reported"], + [ + "Header names", + key.default_request_headers + ?.map((header) => header.name) + .join(", ") || "None", + ], + ["WebSocket rules", String(key.ws_frame_injections?.length ?? 0)], + ["User-Agent", key.custom_user_agent || "Client default"], + ["OpenAPI", key.openapi_spec_url || "Not configured"], + ["Recommended skills", key.recommended_skills?.join(", ") || "None"], + ] + : []), + ]; + return ( +
+ {rows.map(([label, value]) => ( +
+
{label}
+
+ {value} +
+
+ ))} +
+ ); +} + +/** A titled panel inside an opened connection, matching Billing and Agent keys. */ +function PanelSection({ + icon: Icon, + title, + children, +}: { + readonly icon: LucideIcon; + readonly title: string; + readonly children: ReactNode; +}) { + return ( +
+

+

+ {children} +
+ ); +} + +export function ServiceConnectionTable({ + connections, + serviceName, + renderActions, + onViewHistory, + insights: suppliedInsights, + initialPanel = null, + catalog, + pools = [], + onViewPool, +}: { + readonly connections: readonly KeyInfo[]; + readonly serviceName: string; + readonly renderActions?: (connection: KeyInfo) => ReactNode; + readonly onViewHistory?: (connection: KeyInfo) => void; + readonly insights?: ServiceInsightsState; + readonly initialPanel?: { id: string; view: InsightPanel | "history" } | null; + readonly catalog?: CatalogEntry; + readonly pools?: readonly ServicePool[]; + readonly onViewPool?: (poolId: string) => void; +}) { + const [open, setOpen] = useState<{ + id: string; + view: "details" | "history" | InsightPanel; + } | null>(initialPanel); + const [observedAt] = useState(Date.now); + const insights = useServiceInsights(connections, suppliedInsights); + const toggle = (id: string, view: "details" | "history" | InsightPanel) => + setOpen((current) => + current?.id === id && current.view === view ? null : { id, view }, + ); + + return ( + + + + + + Connection / Slug + + + Owner / Credential + + + Access & requests + + + Billing + + + Configuration + + + + + {connections.map((key) => { + const insight = insights.connections.get(key.id); + const billing = insight?.billing; + const billingCategory = + insights.status === "ready" + ? connectionBillingCategory(key, billing, catalog) + : "unknown"; + const memberships = pools.filter((pool) => + pool.members.some((member) => member.user_service_id === key.id), + ); + const usage = insight?.usage; + const latest = latestRecordedUse(usage); + const useTracked = + !!usage && + usage.activity.tracking !== "unavailable" && + usage.activity.visibility !== "unavailable"; + const overrideCount = + usage?.access.keys.filter((agent) => agent.credential_override) + .length ?? 0; + const source = connectionSource(key); + const org = + key.credential_source?.type === "org" + ? key.credential_source + : null; + const owner = + org?.org_name ?? + (source === "platform" ? "NyxID platform" : "Personal"); + const editable = canEditConnection(key); + const readiness = classifyConnection(key, [], observedAt); + const change = + key.authorship?.last_change ?? key.authorship?.created_by; + const changedAt = change?.at ?? key.created_at; + const activity = key.authorship?.last_change + ? "Changed" + : "Created"; + const expanded = open?.id === key.id; + const panelId = `connection-${key.id}-detail`; + const route = + key.node_id || key.has_node_binding + ? `Node · ${key.node_status ?? "Unknown"}` + : "Direct"; + const configCounts = [ + key.granted_scopes?.length + ? `${key.granted_scopes.length} permissions` + : null, + key.default_request_headers?.length + ? `${key.default_request_headers.length} headers` + : null, + key.ws_frame_injections?.length + ? `${key.ws_frame_injections.length} WS rules` + : null, + ] + .filter(Boolean) + .join(" · "); + return ( + + td]:align-top [&>td]:py-3", + // The opened panel continues this entry, so no rule between them. + expanded && "border-b-0 bg-muted/20 hover:bg-muted/20", + )} + > + +
+ + + + + {key.label} + +
+

+ + {key.slug} + + + · {key.service_type} + {key.streaming_supported ? " · Streaming" : ""} + {key.websocket_supported ? " · WebSocket" : ""} + +

+

+ + {key.is_active ? "Enabled" : "Disabled"} ·{" "} + {key.status.replaceAll("_", " ")} + {key.expires_at + ? ` · Expires ${formatDate(key.expires_at)}` + : ""} + + {editable && renderActions?.(key)} +

+ {memberships.map((pool) => { + const member = pool.members.find( + (member) => member.user_service_id === key.id, + )!; + return ( + + ); + })} +
+ +
+ + + {owner} + +
+

+ {credentialLabel(key, billing)} +

+
+ + + + + + + + + + + {billingCategory === "not_billable" ? ( +

Not billable by NyxID

+ ) : ( + <> +

+ {billing + ? plainBilling(key, billing, catalog).headline + : insightStatusLabel( + insights.status, + "Billing", + )} +

+ {billing && ( +

+ {plainBilling(key, billing, catalog).detail} +

+ )} +

+ Click for details and your usage. +

+ + )} +
+
+
+ + {editable ? ( + <> +

+ {target(key)} +

+

+ + {authNames[key.auth_method] ?? key.auth_method} ·{" "} + {route} + {configCounts ? ` · ${configCounts}` : ""} + + +

+ + ) : ( +

+

+ )} +

+ + {activity} + {changedAt ? ` ${formatDate(changedAt)}` : ""} + {change ? ` · ${change.actor.name}` : ""} + + +

+
+
+ {expanded && ( + + +
+ {open.view === "history" ? ( + + + + ) : open.view === "details" ? ( + + + + ) : ( + + )} +
+
+
+ )} +
+ ); + })} +
+
+
+ ); +} diff --git a/frontend/src/components/dashboard/service-filter-multiselect.tsx b/frontend/src/components/dashboard/service-filter-multiselect.tsx new file mode 100644 index 000000000..973aba276 --- /dev/null +++ b/frontend/src/components/dashboard/service-filter-multiselect.tsx @@ -0,0 +1,155 @@ +import { useState, type ReactNode } from "react"; +import { ChevronDown } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; +import { + Popover, + PopoverTrigger, + PopoverContent, +} from "@/components/ui/popover"; + +export interface ServiceFilterOption { + readonly id: string; + readonly label: string; + readonly icon?: ReactNode; +} + +export function ServiceFilterMultiselect({ + label, + plural, + options, + selected, + onChange, + className, +}: { + readonly label: string; + readonly plural: string; + readonly options: readonly ServiceFilterOption[]; + readonly selected: readonly string[]; + readonly onChange: (ids: string[]) => void; + readonly className?: string; +}) { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const hasIcons = options.some((option) => option.icon); + const visible = options.filter((option) => + option.label.toLowerCase().includes(search.trim().toLowerCase()), + ); + const summary = + selected.length === 0 + ? "All" + : selected.length === 1 + ? (options.find((option) => option.id === selected[0])?.label ?? + "Unavailable selection") + : `${selected.length} selected`; + return ( + { + setOpen(value); + if (!value) setSearch(""); + }} + > + + + + + setSearch(event.target.value)} + /> +
+ {visible.map((option) => { + const checked = selected.includes(option.id); + return ( + + ); + })} + {!visible.length && ( +

+ No matching {plural}. +

+ )} +
+
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/dashboard/service-insight-panels.tsx b/frontend/src/components/dashboard/service-insight-panels.tsx new file mode 100644 index 000000000..d0ec811cb --- /dev/null +++ b/frontend/src/components/dashboard/service-insight-panels.tsx @@ -0,0 +1,1014 @@ +import { useState } from "react"; +import { Link } from "@tanstack/react-router"; +import { + Activity, + ArrowUpRight, + Bot, + CreditCard, + UsersRound, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@/components/ui/select"; +import { metricLabel } from "@/schemas/billing-metrics"; +import type { BillingUsagePeriod } from "@/schemas/billing"; +import { useBillingUsage } from "@/hooks/use-billing"; +import { + Bar, + BarChart, + CartesianGrid, + Line, + LineChart, + XAxis, + YAxis, +} from "recharts"; +import { + ChartContainer, + ChartTooltip, + type ChartConfig, +} from "@/components/ui/chart"; +import { + serviceUsageDaily, + serviceUsageSummary, + serviceUsageTrend, + type ServiceUsageDay, + type ServiceUsageSummary, + type ServiceUsageWindow, +} from "@/lib/service-usage"; +import { formatExactCredits, hasCredits, parseCredits } from "@/lib/credits"; +import { plainBilling } from "@/lib/billing-plain"; +import type { + ConfiguredCatalogEntry, + ServiceInsight, +} from "@/schemas/service-insights"; +import { + useServiceInsights, + type ServiceInsightsState, +} from "@/hooks/use-service-insights"; +import { + accessReasonLabel, + callerKindLabel, + callerLabel, + outcomeLabel, + recordedSourceLabel, +} from "@/lib/service-insights"; +import { cn, formatDateTime } from "@/lib/utils"; +import type { KeyInfo } from "@/types/keys"; + +export type InsightPanel = "access" | "requests" | "billing"; + +export function InsightsUnavailable({ + state, +}: { + readonly state: ServiceInsightsState; +}) { + return ( +
+

+ {state.status === "loading" + ? "Loading billing and caller information…" + : state.status === "restricted" + ? "You do not have permission to view these connection insights." + : state.status === "unavailable" + ? "This server does not provide connection billing and caller insights yet." + : "Connection insights could not be loaded."} +

+ {state.status !== "loading" && ( + + )} +
+ ); +} + +const USAGE_PERIODS: readonly [BillingUsagePeriod, string][] = [ + ["24h", "Last 24 hours"], + ["7d", "Last 7 days"], + ["30d", "Last 30 days"], + ["90d", "Last 90 days"], +]; + +const usageChartConfig = { + value: { label: "Usage", color: "var(--color-primary)" }, +} satisfies ChartConfig; + +/** What the trend line plots: calls, one recorded metric, or credits. */ +type TrendMeasure = "calls" | "credits" | `metric:${string}`; + +function trendValue(day: ServiceUsageDay, measure: TrendMeasure) { + if (measure === "calls") return day.calls; + // Unsettled days have no exact charge yet; leave a gap, not a fake zero. + if (measure === "credits") + return day.charged == null ? null : Number(day.charged); + const metric = measure.slice("metric:".length); + return day.quantities.find((q) => q.metric === metric)?.quantity ?? 0; +} + +function trendLabel(measure: TrendMeasure) { + if (measure === "calls") return "Calls"; + if (measure === "credits") return "Credits"; + const label = metricLabel(measure.slice("metric:".length)); + return label.charAt(0).toUpperCase() + label.slice(1); +} + +const shortDay = (day: string) => + new Date(`${day}T00:00:00Z`).toLocaleDateString(undefined, { + month: "short", + day: "numeric", + timeZone: "UTC", + }); + +/** Where charged credits came from, in the order NyxID spends them. */ +function fundingParts(source: { + readonly allowance: string; + readonly grant: string; + readonly wallet: string; +}) { + return ( + [ + ["Free allowance", source.allowance], + ["Free credit grants", source.grant], + ["Wallet", source.wallet], + ] as const + ).filter(([, value]) => hasCredits(value)); +} + +function UsageDayTooltip({ + active, + payload, +}: { + readonly active?: boolean; + readonly payload?: readonly { payload: { source: ServiceUsageDay } }[]; +}) { + const day = active ? payload?.[0]?.payload.source : undefined; + if (!day) return null; + return ( +
+

{shortDay(day.day)} (UTC)

+

+ {day.calls.toLocaleString()} {day.calls === 1 ? "call" : "calls"} +

+ {day.quantities.map(({ metric, quantity }) => ( +

+ {quantity.toLocaleString()} {metricLabel(metric, quantity)} +

+ ))} + {day.charged == null ? ( +

Credits still being calculated

+ ) : ( + hasCredits(day.charged) && ( +

+ {formatExactCredits(day.charged)} credits +

+ ) + )} + {fundingParts(day).length > 0 && ( +

+ Paid from{" "} + {fundingParts(day) + .map( + ([label, value]) => + `${label.toLowerCase()} ${formatExactCredits(value)}`, + ) + .join(" · ")} +

+ )} +
+ ); +} + +function UsageTrendChart({ + series, + measures, +}: { + readonly series: ServiceUsageDay[]; + readonly measures: readonly TrendMeasure[]; +}) { + const [picked, setPicked] = useState("calls"); + const measure = measures.includes(picked) ? picked : "calls"; + const data = series.map((day) => ({ + day: day.day, + value: trendValue(day, measure), + source: day, + })); + const label = trendLabel(measure); + return ( +
+ {measures.length > 1 && ( +
+ {measures.map((option) => ( + + ))} +
+ )} + + + + + + value.toLocaleString(undefined, { + notation: "compact", + maximumSignificantDigits: 3, + }) + } + /> + } + /> + + + +
+ {data + .filter((point) => point.value) + .map((point) => `${shortDay(point.day)}: ${point.value} ${label}`) + .join(", ") || `No ${label.toLowerCase()} in this period`} +
+
+ ); +} + +const FUNDING_COLORS: Record = { + "Free allowance": "var(--color-success)", + "Free credit grants": "var(--chart-1)", + Wallet: "var(--color-primary)", +}; + +/** One bar split by where the period's credits came from. */ +function FundingBar({ summary }: { readonly summary: ServiceUsageSummary }) { + const parts = fundingParts(summary).map(([label, value]) => ({ + label, + value, + pico: parseCredits(value), + })); + const total = parts.reduce((sum, part) => sum + part.pico, 0n); + if (total === 0n) return null; + return ( +
+

Where the credits came from

+ + ); +} + +/** Ranked horizontal bars, capped at five rows. */ +function BreakdownBars({ + title, + items, +}: { + readonly title: string; + readonly items: readonly { label: string; value: number }[]; +}) { + const max = Math.max(1, ...items.map((item) => item.value)); + const shown = items.slice(0, 5); + return ( +
+

{title}

+
    + {shown.map((item) => ( +
  • + + {item.label} + +
  • + ))} +
+ {items.length > shown.length && ( +

+ +{items.length - shown.length} more +

+ )} +
+ ); +} + +const windowChartConfig = { + perDay: { label: "Calls per day", color: "var(--color-primary)" }, +} satisfies ChartConfig; + +/** + * Average calls per day over recent windows, built from period totals so it + * works before the server reports usage by day. + */ +function UsageWindows({ slug }: { readonly slug: string }) { + const day = useBillingUsage("24h"); + const week = useBillingUsage("7d"); + const month = useBillingUsage("30d"); + const quarter = useBillingUsage("90d"); + const queries = [day, week, month, quarter]; + if (queries.some((query) => query.isPending)) + return

Loading recent trend…

; + if (!day.data || !week.data || !month.data || !quarter.data) return null; + const windows = serviceUsageTrend( + { + "24h": day.data.rows, + "7d": week.data.rows, + "30d": month.data.rows, + "90d": quarter.data.rows, + }, + slug, + ); + return ( +
+

+ Recent trend · average calls per day +

+ + + + + + value.toLocaleString(undefined, { maximumFractionDigits: 1 }) + } + /> + } + /> + + + +
+ {windows + .map( + (slot) => + `${slot.label}: ${slot.calls.toLocaleString()} ${slot.calls === 1 ? "call" : "calls"}`, + ) + .join(" · ")} + . A day-by-day graph replaces this once NyxID reports usage by day. +
+
+ ); +} + +function WindowTooltip({ + active, + payload, +}: { + readonly active?: boolean; + readonly payload?: readonly { payload: ServiceUsageWindow }[]; +}) { + const slot = active ? payload?.[0]?.payload : undefined; + if (!slot) return null; + return ( +
+

{slot.label}

+

+ {slot.calls.toLocaleString()} {slot.calls === 1 ? "call" : "calls"} +

+

+ {slot.perDay.toLocaleString(undefined, { maximumFractionDigits: 1 })}{" "} + per day on average +

+
+ ); +} + +function ConnectionUsage({ + connection, + freeNow, +}: { + readonly connection: KeyInfo; + /** The connection is free on NyxID today, so charged usage needs a reason. */ + readonly freeNow: boolean; +}) { + const [period, setPeriod] = useState("30d"); + // One daily request feeds both the totals and the chart. + const usage = useBillingUsage(period, period === "24h" ? undefined : "day"); + const summary = usage.data + ? serviceUsageSummary(usage.data.rows, connection.slug) + : null; + const daily = usage.data + ? serviceUsageDaily(usage.data.rows, connection.slug, period) + : null; + const org = + connection.credential_source?.type === "org" + ? connection.credential_source.org_name + : null; + return ( +
+
+
+ Usage history +
+ +
+ {usage.isPending ? ( +

Loading usage…

+ ) : usage.isError ? ( +

Usage couldn’t load.

+ ) : !summary ? ( +

+ No usage recorded through {connection.slug} in this + period. +

+ ) : ( + <> +
+
+
Calls
+
+ {summary.calls.toLocaleString()} +
+
+
+
Usage
+
+ {summary.quantities + .map( + ({ metric, quantity }) => + `${quantity.toLocaleString()} ${metricLabel(metric, quantity)}`, + ) + .join(" · ")} +
+
+
+
NyxID credits used
+
+ {!summary.billable + ? "None" + : summary.charged == null + ? "Still being calculated" + : formatExactCredits(summary.charged)} +
+
+
+ {summary.billable && fundingParts(summary).length > 0 && ( + + )} + {daily ? ( + metric !== "requests") + .map(({ metric }) => `metric:${metric}` as const), + ...(summary.billable ? (["credits"] as const) : []), + ]} + /> + ) : ( + + )} + {freeNow && + summary.charged != null && + hasCredits(summary.charged) && ( +

+ Credits were charged in this period even though this connection + is free on NyxID now. They may come from an earlier price, or + from another connection that shares{" "} + {connection.slug}. +

+ )} + {(summary.agents.length > 1 || !!summary.agents[0]?.name) && ( + ({ + label: name ?? "You, signed in", + value: calls, + }))} + /> + )} + {summary.models.length > 1 ? ( + ({ + label: name, + value: calls, + }))} + /> + ) : ( + summary.models[0] && ( +

+ Model:{" "} + + {summary.models[0].name} + +

+ ) + )} + + )} +

+ Counts calls made through {connection.slug} that were + billed to your personal account. Other connections using the same + address are counted together. + {org && ` Usage billed to ${org} isn’t included.`} +

+
+ ); +} + +function ConnectionBillingPanel({ + connection, + insight, + state, + catalog, +}: { + readonly connection: KeyInfo; + readonly insight: ServiceInsight; + readonly state: ServiceInsightsState; + readonly catalog?: ConfiguredCatalogEntry; +}) { + const [caller, setCaller] = useState("you"); + const selectedState = useServiceInsights( + [connection], + caller === "you" ? state : undefined, + caller === "you" ? undefined : caller, + ); + const bill = + caller === "you" + ? insight.billing + : selectedState.connections.get(connection.id)?.billing; + const plain = bill ? plainBilling(connection, bill, catalog) : null; + // Settings say what should be charged; recorded usage says what was. + const recent = useBillingUsage("7d"); + const recentCharged = recent.data + ? serviceUsageSummary(recent.data.rows, connection.slug)?.charged + : null; + const chargedAnyway = + plain?.verdict === "free" && + recentCharged != null && + hasCredits(recentCharged); + return ( +
+
+

+ Billing +

+ {insight.billing?.context !== "configuration" && + !!insight.usage?.access.keys.length && ( +
+ Show for + +
+ )} +
+ {plain ? ( + <> +
+

+ {chargedAnyway ? "Credits were charged recently" : plain.headline} +

+

+ {chargedAnyway + ? `${formatExactCredits(recentCharged!)} NyxID credits were charged in the last 7 days for calls through ${connection.slug}, so don't treat this connection as free. ${plain.detail}` + : plain.detail} +

+
+
+
+ Details +
+
+
+
Whose key or app
+
{plain.key.title}
+ {plain.key.note && ( +
+ {plain.key.note} +
+ )} +
+
+
Who pays NyxID
+
{plain.payer}
+
+
+
NyxID price
+
{plain.price}
+
+
+ {!!plain.tips.length && ( +
    + {plain.tips.map((tip) => ( +
  • {tip}
  • + ))} +
+ )} +
+ + ) : ( + + )} + +
+ ); +} + +export function ConnectionInsightPanel({ + connection, + insight, + view, + state, + catalog, +}: { + readonly connection: KeyInfo; + readonly insight?: ServiceInsight; + readonly view: InsightPanel; + readonly state: ServiceInsightsState; + readonly catalog?: ConfiguredCatalogEntry; +}) { + const [showAllKeys, setShowAllKeys] = useState(false); + if (!insight || (view === "billing" ? !insight.billing : !insight.usage)) + return ; + const usage = insight.usage; + if (view === "billing") + return ( + + ); + if (!usage) return ; + if (view === "access") + return ( +
+
+

+ {" "} + {usage.access.basis === "configuration" + ? "Agent keys in scope" + : "Agent keys with access"} +

+ + {usage.access.visibility === "own_keys" + ? "Your keys only" + : "Keys you manage"}{" "} + · current scope + +
+ {usage.access.keys.length ? ( +
+ + + + + + + + + + {(showAllKeys + ? usage.access.keys + : usage.access.keys.slice(0, 3) + ).map((key) => ( + + + + + + ))} + +
Agent keyAccess throughCredential
+ + + {key.name} + + + {key.platform && ( + + {key.platform} + + )} + + {accessReasonLabel(key.permission)} + + {key.credential_override === null ? ( + "Override not reported" + ) : key.credential_override ? ( + Credential override + ) : ( + "Connection default" + )} +
+
+ ) : ( +

+ {usage.access.visibility === "unavailable" + ? "Agent key inventory could not be loaded." + : usage.access.incomplete + ? "No matching keys in the available inventory. Some key inventories could not be checked." + : "No agent keys in your permitted inventory currently include this connection in their scope."} +

+ )} + {usage.access.keys.length > 3 && ( + + )} + {usage.access.truncated && ( +

+ Showing a limited set of keys. Open Agent keys to review the full + inventory. +

+ )} +

+ {usage.access.basis === "configuration" + ? "Configured scope; live permissions and credentials are checked at execution. " + : ""} + {usage.access.incomplete && usage.access.keys.length > 0 + ? "Some key inventories could not be checked. " + : ""} + Scope access does not prove a working connection or previous use. Open + a key to manage its service scope or credential override. +

+
+ ); + if (usage.activity.tracking === "unavailable") + return ( +
+

Request attribution unavailable

+

+ This server does not yet report which agent key or application used + this exact connection. Configured key access is shown separately; it + is not evidence of use. +

+
+ ); + return ( +
+
+

+ Recent requests +

+ + {usage.activity.visibility === "own_requests" + ? "Your requests" + : "Visible requests"}{" "} + · {usage.activity.period_days} days + +
+ {usage.activity.requests.length ? ( +
+ + + + + + + + + + + + {usage.activity.requests.map((request) => ( + + + + + + + + ))} + +
CallerType / applicationRecorded layerTimeOutcome
+ {callerLabel(request.caller)} + + {callerKindLabel(request.caller.kind)} + {request.caller.kind !== "session" && ( + + {request.caller.app_name ?? + (request.caller.app_id + ? "Application name not recorded" + : "Application not recorded")} + + )} + + {recordedSourceLabel(request, connection)} + + + + {outcomeLabel(request.outcome)} + {request.response_status != null && ( + + · {request.response_status} + + )} +
+
+ ) : ( +

+ No requests with exact connection attribution were recorded in this + period. +

+ )} + {usage.activity.request_count > 0 && ( +

+ {usage.activity.request_count.toLocaleString()} recorded + {usage.activity.request_count === 1 ? " request" : " requests"} + {usage.activity.truncated + ? ` · showing the latest ${usage.activity.requests.length}` + : " in this period"} +

+ )} +

+ {usage.activity.tracking === "partial" + ? "Tracking is partial. Older requests and unsupported request paths may not identify this connection. " + : ""} + A shared agent key identifies the key, not every application using it. A + received response does not confirm stream completion or a settled + charge. +

+
+ ); +} diff --git a/frontend/src/components/dashboard/service-insights.test.tsx b/frontend/src/components/dashboard/service-insights.test.tsx new file mode 100644 index 000000000..9805159b7 --- /dev/null +++ b/frontend/src/components/dashboard/service-insights.test.tsx @@ -0,0 +1,644 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + render, + screen, + within, + cleanup, + waitFor, +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { ReactNode } from "react"; +import type { KeyInfo } from "@/types/keys"; +import type { ServiceInsight } from "@/schemas/service-insights"; +import type { ServiceInsightsState } from "@/hooks/use-service-insights"; +import { + billingAccountLabel, + billingModelLabel, + credentialLabel, + summarizeBilling, + summarizeBillingModel, +} from "@/lib/service-insights"; +import { configuredBilling } from "@/lib/service-insights-compat"; +import { api } from "@/lib/api-client"; +import { ServiceConnectionTable } from "./service-connection-table"; + +vi.mock("@tanstack/react-router", () => ({ + Link: ({ + children, + to, + params, + ...props + }: { + children: ReactNode; + to: string; + params?: { keyId: string }; + "aria-label"?: string; + }) => ( + + {children} + + ), +})); +vi.mock("@/stores/auth-store", () => ({ + useAuthStore: (selector: (state: { user: { id: string } }) => unknown) => + selector({ user: { id: "person" } }), +})); +vi.mock("./service-history", () => ({ + ServiceHistory: () =>
Permitted service history
, +})); + +const connection = { + id: "connection-a", + label: "Team OpenAI", + slug: "openai-team", + catalog_service_id: "openai", + catalog_service_slug: "openai", + catalog_service_name: "OpenAI", + service_type: "http", + is_active: true, + status: "active", + auth_method: "bearer", + credential_type: "api_key", + node_id: null, + auto_connected: false, + can_edit_configuration: false, + credential_binding: "platform", + credential_source: { + type: "org", + org_id: "org", + org_name: "ChronoAI", + role: "member", + allowed: true, + }, + endpoint_url: "https://private.example.test", + endpoint_id: "endpoint", + auth_key_name: "Authorization", + node_priority: 0, + expires_at: null, + last_used_at: null, + error_message: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + created_at: "2026-09-01T00:00:00Z", + source_app_name: "Provisioning app", + ws_frame_injections: [], +} as KeyInfo; +const insight: ServiceInsight = { + service_id: connection.id, + billing: { + status: "resolved", + credential_class: "nyxid_managed_master", + service_billing_configured: true, + credential_label: "NyxID credential", + account: { id: "person", kind: "personal", name: "Your personal account" }, + charge_status: "usage_based", + rates: [ + { + layer: "platform", + metric: "requests", + credits_per_unit: "0.25", + currency: "credits", + source: "credential_lane", + }, + ], + provider_billing: "nyxid_credential", + context: "for_you", + notes: [], + }, + usage: { + access: { + visibility: "own_keys", + keys: [ + { + id: "agent-1", + name: "Codex CI", + platform: "codex", + owner_id: "person", + permission: "selected_service", + credential_override: false, + }, + { + id: "agent-2", + name: "Unused worker", + platform: null, + owner_id: "person", + permission: "all_services", + credential_override: true, + }, + ], + truncated: false, + }, + activity: { + visibility: "own_requests", + period_days: 30, + tracking: "partial", + request_count: 1, + requests: [ + { + id: "event-1", + execution_id: "execution-1", + caller: { + id: "agent-1", + kind: "agent_key", + name: "Codex CI", + app_id: "app", + app_name: "Release app", + }, + occurred_at: "2026-09-29T00:00:00Z", + outcome: "response_received", + response_status: 200, + source: { kind: "platform", owner_id: "org" }, + }, + ], + truncated: false, + }, + }, +}; +function mount( + value: ServiceInsight | undefined = insight, + status: ServiceInsightsState["status"] = "ready", +) { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + return render( + + + , + ); +} +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe("service card billing and caller details", () => { + it("shows the recorded layer even when today's connection binding is different", () => { + mount({ + ...insight, + usage: { + ...insight.usage!, + activity: { + ...insight.usage!.activity, + requests: [ + { + ...insight.usage!.activity.requests[0]!, + source: { kind: "org", owner_id: "org" }, + }, + ], + }, + }, + }); + expect(screen.getByTitle(/^Organization · ChronoAI · /)).toBeVisible(); + expect(screen.getByText(/Codex CI · Release app/)).toBeVisible(); + expect(screen.getByText("You pay · 0.25 credits/request")).toBeVisible(); + }); + it("does not present incomplete key inventory as zero keys", () => { + mount({ + ...insight, + usage: { + ...insight.usage!, + access: { ...insight.usage!.access, keys: [], incomplete: true }, + }, + }); + expect(screen.getByText("Key access incomplete")).toBeVisible(); + expect(screen.queryByText("0 agent keys")).not.toBeInTheDocument(); + }); + it("shows the separately recorded last use even when all three recent requests were denied", () => { + const last = insight.usage!.activity.requests[0]!; + mount({ + ...insight, + usage: { + ...insight.usage!, + activity: { + ...insight.usage!.activity, + last_used: last, + requests: Array.from({ length: 3 }, (_, index) => ({ + ...last, + id: `denied-${index}`, + outcome: "denied", + response_status: 403, + source: null, + })), + }, + }, + }); + expect(screen.getByTitle(/^Platform · /)).toBeVisible(); + expect(screen.getByText(/Codex CI · Release app/)).toBeVisible(); + }); + it("leaves legacy request layers unknown even when the current binding is platform", () => { + mount({ + ...insight, + usage: { + ...insight.usage!, + activity: { + ...insight.usage!.activity, + requests: [{ ...insight.usage!.activity.requests[0]!, source: null }], + }, + }, + }); + expect(screen.getByTitle(/^Layer not recorded · /)).toBeVisible(); + expect(screen.queryByTitle(/^Platform · /)).not.toBeInTheDocument(); + }); + it("does not infer the last used layer from credential timestamps or rejected requests", () => { + mount({ + ...insight, + usage: { + ...insight.usage!, + activity: { + ...insight.usage!.activity, + requests: [ + { + ...insight.usage!.activity.requests[0]!, + outcome: "denied", + response_status: 403, + }, + ], + }, + }, + }); + expect(screen.getByText("Not recorded")).toBeVisible(); + expect( + screen.queryByText("Platform · openai-team"), + ).not.toBeInTheDocument(); + }); + it("shows configured key access and the billing flow on older servers without claiming recorded use", async () => { + const user = userEvent.setup(); + mount({ + ...insight, + billing: configuredBilling({ + ...connection, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "pending", + }, + }), + usage: { + access: { + ...insight.usage!.access, + basis: "configuration", + keys: [ + { ...insight.usage!.access.keys[0]!, credential_override: null }, + ], + }, + activity: { + ...insight.usage!.activity, + tracking: "unavailable", + visibility: "unavailable", + request_count: 0, + requests: [], + }, + }, + }); + expect(screen.getByTitle(/Configured scope[\s\S]*Codex CI/)).toBeVisible(); + expect( + screen.getByText("Caller pays · 0.05 credits/request"), + ).toBeVisible(); + expect(screen.queryByText(/No recorded requests/)).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + const panel = screen.getByRole("region", { + name: "Billing for Team OpenAI", + }); + expect(within(panel).getByText("Uses NyxID credits")).toBeVisible(); + expect(within(panel).getByText("Who pays NyxID")).toBeVisible(); + expect( + within(panel).getByText(/A new price is waiting to be activated/), + ).toBeVisible(); + expect( + within(panel).queryByRole("combobox", { name: "Preview billing for" }), + ).not.toBeInTheDocument(); + expect(within(panel).getByText("0.05 credits per request")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Recent requests for Team OpenAI" }), + ); + expect(screen.getByText("Request attribution unavailable")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Agent key access for Team OpenAI" }), + ); + expect(screen.getByText("Agent keys in scope")).toBeVisible(); + expect(screen.getByText("Override not reported")).toBeVisible(); + }); + it("exposes payer, credential and actual last caller directly in the expanded table", () => { + mount(); + expect(screen.getByRole("columnheader", { name: "Billing" })).toBeVisible(); + expect(screen.getByText("You pay · 0.25 credits/request")).toBeVisible(); + expect(screen.getByText(/NyxID key/)).toBeVisible(); + expect( + within( + screen.getByRole("button", { name: "Recent requests for Team OpenAI" }), + ).getByText(/Codex CI/), + ).toBeVisible(); + expect(screen.getByText("NyxID")).toBeVisible(); + expect(screen.getByText(/· 1 override$/)).toBeVisible(); + expect(screen.getByTitle(/^Your keys with access/)).toBeVisible(); + expect(screen.queryByText("Provisioning app")).not.toBeInTheDocument(); + expect( + screen.queryByText("https://private.example.test"), + ).not.toBeInTheDocument(); + }); + it("opens rates inside the table and replaces them with access and request attribution", async () => { + const user = userEvent.setup(); + mount(); + await user.click( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + expect(screen.getByText("0.25 credits per request")).toBeVisible(); + expect( + screen.getByText( + "Each request costs 0.25 NyxID credits, paid by you, from your personal credits.", + ), + ).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Agent key access for Team OpenAI" }), + ); + expect( + screen.queryByText("0.25 credits per request"), + ).not.toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Codex CI" })).toHaveAttribute( + "href", + "/keys/api-key/agent-1", + ); + expect(screen.getByText("Unused worker")).toBeVisible(); + expect(screen.getByText("Credential override")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Recent requests for Team OpenAI" }), + ); + const requests = screen.getByRole("table", { + name: "Recent connection requests", + }); + expect(within(requests).getByText("Release app")).toBeVisible(); + expect(within(requests).getByText("Agent key")).toBeVisible(); + expect(screen.queryByText("Unused worker")).not.toBeInTheDocument(); + expect(screen.getByText(/Tracking is partial/)).toBeVisible(); + }); + it("keeps history available to a reader while private configuration stays hidden", async () => { + mount(); + await userEvent.click( + screen.getByRole("button", { + name: "History for Team OpenAI (ChronoAI)", + }), + ); + expect(screen.getByText("Permitted service history")).toBeVisible(); + expect( + screen.queryByRole("link", { name: /Configure/ }), + ).not.toBeInTheDocument(); + }); + it("resolves the selected agent key's payer in place without retaining the default payer while loading", async () => { + const user = userEvent.setup(); + let resolvePreview!: (value: unknown) => void; + const request = vi.spyOn(api, "get").mockImplementation( + () => + new Promise((resolve) => { + resolvePreview = resolve; + }), + ); + mount(); + await user.click( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + const panel = screen.getByRole("region", { + name: "Billing for Team OpenAI", + }); + await user.click( + within(panel).getByRole("combobox", { name: "Preview billing for" }), + ); + await user.click( + screen.getByRole("option", { name: "Agent key: Codex CI" }), + ); + await waitFor(() => + expect(request).toHaveBeenCalledWith( + "/service-insights?ids=connection-a&api_key_id=agent-1", + ), + ); + expect( + within(panel).queryByText("You, from your personal credits"), + ).not.toBeInTheDocument(); + expect(within(panel).getByText(/Loading billing/)).toBeVisible(); + resolvePreview({ + connections: [ + { + ...insight, + billing: { + ...insight.billing!, + account: { id: "org", name: "ChronoAI", kind: "organization" }, + context: "agent_key", + }, + }, + ], + }); + expect( + await within(panel).findByText("ChronoAI, from its organization credits"), + ).toBeVisible(); + await user.click( + within(panel).getByRole("combobox", { name: "Preview billing for" }), + ); + await user.click(screen.getByRole("option", { name: "You" })); + expect( + within(panel).getByText("You, from your personal credits"), + ).toBeVisible(); + expect( + within(panel).queryByText("ChronoAI, from its organization credits"), + ).not.toBeInTheDocument(); + }); + it("shows server compatibility failures instead of claiming free service or no usage", async () => { + mount({ ...insight, billing: null, usage: null }, "unavailable"); + expect(screen.getByText("Billing not reported")).toBeVisible(); + expect(screen.getByText("Not recorded")).toBeVisible(); + expect(screen.queryByText(/free|never used/i)).not.toBeInTheDocument(); + await userEvent.click( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + expect(screen.getByText(/This server does not provide/)).toBeVisible(); + }); + it.each([ + ["restricted", "History restricted", "Billing restricted"], + ["error", "History couldn't load", "Billing couldn't load"], + ["loading", "Loading…", "Loading billing…"], + ] as const)( + "distinguishes %s insights from an empty history", + (status, activity, billing) => { + mount({ ...insight, billing: null, usage: null }, status); + expect(screen.getByText(activity)).toBeVisible(); + expect(screen.getByText(billing)).toBeVisible(); + expect( + screen.queryByText(/No recorded requests/), + ).not.toBeInTheDocument(); + }, + ); + it("labels an empty captured period without claiming that the service was never used", () => { + mount({ + ...insight, + usage: { + ...insight.usage!, + activity: { + ...insight.usage!.activity, + requests: [], + request_count: 0, + }, + }, + }); + expect( + screen.getByTitle(/No recorded use with exact connection attribution/), + ).toBeVisible(); + expect(screen.getByText("Not recorded")).toBeVisible(); + expect(screen.queryByText("Activity not reported")).not.toBeInTheDocument(); + }); + it("does not merge different payer accounts or hide a restricted connection in the group summary", () => { + const org = { + ...insight, + billing: { + ...insight.billing!, + account: { id: "org", name: "ChronoAI", kind: "organization" as const }, + }, + }; + expect(summarizeBilling([insight, org])).toBe("Varies by connection"); + expect( + summarizeBilling([ + insight, + { ...org, billing: { ...org.billing, status: "unavailable" } }, + ]), + ).toBe("Varies by connection"); + expect(summarizeBilling([insight, undefined])).toBe("Billing not reported"); + expect( + billingAccountLabel({ + ...insight.billing!, + status: "restricted", + charge_status: "restricted", + }), + ).toBe("Billing restricted"); + expect( + billingAccountLabel({ ...insight.billing!, status: "unavailable" }), + ).toBe("Billing unavailable"); + }); + + it("separates a supplied API key from its NyxID credit charges", async () => { + const user = userEvent.setup(); + mount({ + ...insight, + billing: { + ...insight.billing!, + provider_billing: "separate_provider_account", + credential_class: "user_owned", + }, + }); + const cell = within( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + expect(cell.getByText("BYOK")).toBeVisible(); + expect(cell.getByText("You pay · 0.25 credits/request")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + expect( + within( + screen.getByRole("region", { name: "Billing for Team OpenAI" }), + ).getByText(/This is a NyxID fee on top of anything/), + ).toBeVisible(); + }); + + it("keeps NyxID credential supply separate from a confirmed absence of NyxID charges", () => { + mount({ + ...insight, + billing: { ...insight.billing!, charge_status: "not_charged", rates: [] }, + }); + const cell = within( + screen.getByRole("button", { name: "Billing for Team OpenAI" }), + ); + expect(cell.getByText("Free on NyxID")).toBeVisible(); + expect(screen.getByText(/NyxID key/)).toBeVisible(); + expect(cell.queryByText("BYOK")).not.toBeInTheDocument(); + }); + + it("keeps unknown, restricted, and missing models explicit in group summaries", () => { + const byok = { + ...insight, + billing: { + ...insight.billing!, + provider_billing: "separate_provider_account" as const, + }, + }; + expect(summarizeBillingModel("ready", [insight, byok])).toBe( + "NyxID credits", + ); + expect(summarizeBillingModel("ready", [byok, undefined])).toBe( + "Billing partly reported", + ); + expect(summarizeBillingModel("restricted", [byok])).toBe( + "Billing restricted", + ); + expect( + billingModelLabel({ ...insight.billing!, provider_billing: "unknown" }), + ).toBe("NyxID credits"); + expect( + billingModelLabel({ ...insight.billing!, status: "restricted" }), + ).toBe("Billing restricted"); + expect( + billingModelLabel({ ...insight.billing!, status: "unavailable" }), + ).toBe("Billing unavailable"); + expect( + billingModelLabel({ + ...insight.billing!, + provider_billing: "no_credential", + }), + ).toBe("NyxID credits"); + }); + + it("identifies NyxID's shared OAuth app independently of its internal price category", () => { + const billing = { + ...insight.billing!, + credential_class: "nyxid_platform_oauth_app", + provider_billing: "separate_provider_account" as const, + }; + expect( + credentialLabel({ ...connection, credential_type: "oauth2" }, billing), + ).toBe("NyxID developer app"); + expect(billingModelLabel(billing)).toBe("NyxID credits"); + }); + + it("keeps configured pricing distinct from resolved credit charges and missing metadata", () => { + expect( + billingModelLabel( + configuredBilling({ + ...connection, + credential_binding: "user", + credential_type: "oauth2", + }), + ), + ).toBe("Credit billing unverified"); + expect( + billingModelLabel( + configuredBilling( + { ...connection, credential_binding: "user" }, + { slug: "openai", billing: { platform_billable: true } }, + ), + ), + ).toBe("NyxID credits · configured"); + expect( + summarizeBillingModel("ready", [ + insight, + { + ...insight, + billing: { ...insight.billing!, charge_status: "not_charged" }, + }, + ]), + ).toBe("Charges vary by connection"); + }); +}); diff --git a/frontend/src/components/dashboard/service-owner-avatar.tsx b/frontend/src/components/dashboard/service-owner-avatar.tsx new file mode 100644 index 000000000..2efb4d987 --- /dev/null +++ b/frontend/src/components/dashboard/service-owner-avatar.tsx @@ -0,0 +1,46 @@ +import { UserRound } from "lucide-react"; +import { OrgAvatar } from "@/components/orgs/org-avatar"; +import { NyxidIcon } from "@/components/brand/nyxid-icon"; +import { cn } from "@/lib/utils"; + +export function ServiceOwnerAvatar({ + type, + name, + avatarUrl, + className, +}: { + readonly type: "org" | "personal" | "platform"; + readonly name: string; + readonly avatarUrl?: string | null; + readonly className?: string; +}) { + if (type === "org") + return ( + *]:rounded-full [&>*]:text-10", + className, + )} + /> + ); + return ( + + {type === "platform" ? ( + + ) : ( + + ); +} diff --git a/frontend/src/components/dashboard/service-pool-cards.tsx b/frontend/src/components/dashboard/service-pool-cards.tsx new file mode 100644 index 000000000..3b44e06fc --- /dev/null +++ b/frontend/src/components/dashboard/service-pool-cards.tsx @@ -0,0 +1,139 @@ +import { useEffect, useRef, useState, type ReactNode } from "react"; +import { ChevronRight, GitBranch } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { useKeys } from "@/hooks/use-keys"; +import { useServiceInsights } from "@/hooks/use-service-insights"; +import { useServiceCardTransition } from "@/hooks/use-service-card-transition"; +import { + poolFailoverLabel, + poolStrategyLabel, +} from "@/lib/service-pool-display"; +import { cn } from "@/lib/utils"; +import type { ServicePool } from "@/schemas/pools"; +import { ServicePoolRoutingPanel } from "./service-pool-routing-panel"; + +export function ServicePoolCards({ + pools, + actions, + onEdit, + initialOpenId, +}: { + readonly pools: readonly ServicePool[]; + readonly actions: (pool: ServicePool) => ReactNode; + readonly onEdit: (pool: ServicePool) => void; + readonly initialOpenId?: string; +}) { + const [open, setOpen] = useState(initialOpenId ?? null); + const linkedCard = useRef(null); + useEffect(() => { + if (!initialOpenId) return; + const frame = requestAnimationFrame(() => { + linkedCard.current?.scrollIntoView({ block: "start", inline: "nearest" }); + }); + return () => cancelAnimationFrame(frame); + }, [initialOpenId]); + const transition = useServiceCardTransition(); + const keys = useKeys(); + const connections = keys.isError ? [] : (keys.data ?? []); + const insights = useServiceInsights(connections); + return ( +
+ {pools.map((pool) => { + const expanded = open === pool.id; + return ( +
+
+
+
+
+ +
+
+

+ {pool.name} +

+ + {pool.slug} + +
+ {actions(pool)} +
+

+ {pool.description || + "One route across compatible connections"} +

+
+
+ {poolStrategyLabel(pool)} + + {pool.is_active ? "Enabled" : "Disabled"} + +
+

{poolFailoverLabel(pool)}

+

+ {pool.members.filter((member) => member.enabled).length} /{" "} + {pool.members.length} members enabled ·{" "} + {pool.member_contract === "ai_chat" + ? "AI chat" + : "Same API"} +

+

+ Billing follows each attempted connection +

+
+
+
+ + +
+
+ {expanded && ( +
+ onEdit(pool)} + /> +
+ )} +
+ ); + })} +
+ ); +} diff --git a/frontend/src/components/dashboard/service-pool-routing-panel.test.tsx b/frontend/src/components/dashboard/service-pool-routing-panel.test.tsx new file mode 100644 index 000000000..787ca7628 --- /dev/null +++ b/frontend/src/components/dashboard/service-pool-routing-panel.test.tsx @@ -0,0 +1,122 @@ +import { render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { ServicePoolRoutingPanel } from "./service-pool-routing-panel"; +import type { PoolCandidate, ServicePool } from "@/schemas/pools"; +const state = vi.hoisted(() => ({ + health: vi.fn(), + result: { + data: { candidates: [] as PoolCandidate[] }, + isError: false, + isLoading: false, + }, +})); +vi.mock("@/hooks/use-pools", () => ({ + usePoolHealth: (options: unknown) => { + state.health(options); + return state.result; + }, +})); +const pool: ServicePool = { + id: "pool", + user_id: "me", + name: "Twitter route", + slug: "twitter-route", + strategy: "priority", + member_contract: "same_api", + config_revision: 4, + tier_balance: "round_robin", + failover: null, + members: [ + { user_service_id: "backup", enabled: true, priority: 10, weight: 1 }, + { user_service_id: "platform", enabled: true, priority: 0, weight: 1 }, + ], + rr_counter: 0, + is_active: true, + created_at: "2026-01-01", + updated_at: "2026-01-01", +}; +function view(overrides: Partial = {}) { + return ( + + ); +} +beforeEach(() => { + state.result = { data: { candidates: [] }, isError: false, isLoading: false }; + state.health.mockClear(); +}); +describe("inline saved pool route", () => { + it("shows actual priorities and unknown health without inventing free billing or readiness", () => { + render(view()); + const rows = within(screen.getByRole("table")).getAllByRole("row"); + expect(within(rows[1]!).getByText("Priority 0")).toBeVisible(); + expect(within(rows[2]!).getByText("Priority 10")).toBeVisible(); + expect(screen.getAllByText("Not inspected")).toHaveLength(2); + expect(screen.queryByText("No NyxID charge")).not.toBeInTheDocument(); + expect( + screen.getByText(/Platform-key usage bills the acting person/), + ).toBeVisible(); + }); + it("inspects the submitted operation without issuing service calls while editing the path", async () => { + const user = userEvent.setup(); + render(view()); + await user.selectOptions( + screen.getByLabelText("Method for Twitter route"), + "GET", + ); + await user.clear(screen.getByLabelText("Operation path for Twitter route")); + await user.type( + screen.getByLabelText("Operation path for Twitter route"), + "/2/users/me", + ); + expect(state.health).toHaveBeenLastCalledWith( + expect.objectContaining({ method: "POST", path: "/" }), + ); + await user.click(screen.getByRole("button", { name: "Inspect" })); + expect(state.health).toHaveBeenLastCalledWith( + expect.objectContaining({ method: "GET", path: "/2/users/me" }), + ); + }); + it("does not retain eligibility after an inspection error", () => { + state.result = { + data: { + candidates: [ + { + user_service_id: "platform", + eligible: true, + reason: null, + consecutive_failures: 0, + } as PoolCandidate, + ], + }, + isError: true, + isLoading: false, + }; + render(view()); + expect( + screen.queryByText("Eligible", { exact: true }), + ).not.toBeInTheDocument(); + expect(screen.getByText(/connection health is unverified/)).toBeVisible(); + }); + it("shows AI model mapping and the gateway alias", () => { + render( + view({ + member_contract: "ai_chat", + members: [{ ...pool.members[0]!, model: "model-b" }], + }), + ); + expect(screen.getByText("pool:twitter-route")).toBeVisible(); + expect(screen.getByText("Model: model-b")).toBeVisible(); + expect(state.health).toHaveBeenLastCalledWith( + expect.objectContaining({ method: "POST", path: "chat/completions" }), + ); + }); +}); diff --git a/frontend/src/components/dashboard/service-pool-routing-panel.tsx b/frontend/src/components/dashboard/service-pool-routing-panel.tsx new file mode 100644 index 000000000..0f4d55a21 --- /dev/null +++ b/frontend/src/components/dashboard/service-pool-routing-panel.tsx @@ -0,0 +1,356 @@ +import { useState } from "react"; +import { GitBranch, Settings2 } from "lucide-react"; +import { ServiceIcon } from "@/components/service-icon"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { usePoolHealth } from "@/hooks/use-pools"; +import type { ServiceInsightsState } from "@/hooks/use-service-insights"; +import { + billingAccountLabel, + billingModelLabel, + credentialLabel, + nyxidChargeLabel, +} from "@/lib/service-insights"; +import { + orderedPoolMembers, + poolFailoverLabel, + poolMemberStatus, + poolStrategyLabel, +} from "@/lib/service-pool-display"; +import { connectionSource, connectionSourceLabel } from "@/lib/service-view"; +import { defaultFailoverPolicy, type ServicePool } from "@/schemas/pools"; +import type { KeyInfo } from "@/types/keys"; +import { ServiceOwnerAvatar } from "./service-owner-avatar"; + +export function ServicePoolRoutingPanel({ + pool, + connections, + insights, + onEdit, +}: { + readonly pool: ServicePool; + readonly connections: readonly KeyInfo[]; + readonly insights: ServiceInsightsState; + readonly onEdit?: () => void; +}) { + const ai = pool.strategy === "priority" && pool.member_contract === "ai_chat"; + const [method, setMethod] = useState("POST"); + const [path, setPath] = useState("/"); + const [operation, setOperation] = useState({ method: "POST", path: "/" }); + const health = usePoolHealth({ + poolId: pool.id, + contract: pool.member_contract, + method: ai ? "POST" : operation.method, + path: ai ? "chat/completions" : operation.path, + }); + const inspected = new Map( + (health.isError ? [] : (health.data?.candidates ?? [])).map((candidate) => [ + candidate.user_service_id, + candidate, + ]), + ); + const keys = new Map(connections.map((key) => [key.id, key])); + const members = orderedPoolMembers(pool); + const policy = pool.failover ?? defaultFailoverPolicy; + const priority = pool.strategy === "priority"; + + return ( +
+
+
+
+
+ + /api/v1/proxy/s/{pool.slug} + + {ai && ( +

+ AI chat · Gateway model pool:{pool.slug} +

+ )} +
+ {onEdit && ( + + )} +
+
+ {poolFailoverLabel(pool)} + {priority && ( + <> + + {pool.tier_balance === "weighted" ? "Weighted" : "Round-robin"}{" "} + within each priority + + + {policy.per_attempt_timeout_ms / 1000}s per attempt ·{" "} + {policy.overall_deadline_ms / 1000}s total + + + )} +
+

+ {priority + ? "Lower priority runs first; ineligible and cooling connections are skipped. Use this pool slug to apply its routing policy." + : "Each request selects one eligible connection. Rotation applies when you call this pool slug."} +

+ {ai ? ( +

+ Eligibility for POST chat/completions · refreshed every 15s +

+ ) : ( +
{ + event.preventDefault(); + setOperation({ method, path: path.trim() || "/" }); + }} + > + + + +

+ Showing {operation.method} {operation.path} · metadata only, no + service call · refreshed every 15s +

+
+ )} + {health.isError && ( +

+ Eligibility could not be loaded. Saved routing is shown; connection + health is unverified. +

+ )} +
+ + + + + {priority ? "Priority / Weight" : "Rotation"} + + Connection / Credential + Billing / Payer + Eligibility / Cooldown + + + + {members.map((member, index) => { + const key = keys.get(member.user_service_id); + const billing = insights.connections.get( + member.user_service_id, + )?.billing; + const candidate = inspected.get(member.user_service_id); + const org = + key?.credential_source?.type === "org" + ? key.credential_source + : null; + const status = poolMemberStatus(member, candidate); + return ( + + +

+ {priority + ? `Priority ${member.priority ?? 0}` + : pool.strategy === "weighted" + ? `Weight ${member.weight}` + : `Member ${index + 1}`} +

+ {priority && pool.tier_balance === "weighted" && ( +

+ Weight {member.weight} +

+ )} +
+ +
+ {key && ( + + )} + + {key?.label ?? + candidate?.slug ?? + "Unavailable connection"} + +
+ {(key?.slug || candidate?.slug) && ( + + {key?.slug ?? candidate?.slug} + + )} + {key && ( +
+ + {connectionSourceLabel(key)} +
+ )} +

+ {key + ? credentialLabel(key, billing) + : "Credential not reported"} +

+ {ai && ( +

+ Model: {member.model ?? "Not configured"} +

+ )} +
+ +

{billingModelLabel(billing)}

+

+ {billing?.context === "configuration" + ? "Expected payer" + : "Payer"} + : {billingAccountLabel(billing)} +

+

+ {billing + ? nyxidChargeLabel(billing) + : "Rate not reported"} +

+
+ + + {health.isLoading && member.enabled + ? "Inspecting…" + : status} + + {candidate && ( +

+ {candidate.consecutive_failures} failures + {candidate.last_status + ? ` · Last HTTP ${candidate.last_status}` + : ""} +

+ )} + {candidate?.cooldown_until && ( +

+ Cooldown until{" "} + {new Date(candidate.cooldown_until).toLocaleString()} +

+ )} + {!pool.is_active && ( +

+ Pool disabled · no execution +

+ )} +
+
+ ); + })} +
+
+ {!members.length && ( +

+ No connections in this pool. +

+ )} +
+

+ Each attempted connection uses its own rates and billing account. + Reported usage can charge more than one attempt. Within that account: + eligible allowance → credit grants → wallet credits. Platform-key usage + bills the acting person. +

+ {priority && ( +
+ + Failover conditions + +
+

+ Retry causes:{" "} + {policy.retry_on.length + ? policy.retry_on + .map((trigger) => trigger.replaceAll("_", " ")) + .join(", ") + : "None"} + . +

+

+ Cooldown: {policy.cooldown.base_ms / 1000}– + {policy.cooldown.max_ms / 1000}s after{" "} + {policy.cooldown.failures_to_open} failure(s) + {policy.cooldown.honor_retry_after + ? "; honors provider Retry-After" + : ""} + . +

+

+ {policy.retry_ambiguous_dispatch + ? "Ambiguous replay enabled: a timeout or server error can retry completed work and incur extra charges." + : "Ambiguous replay off: a POST timeout or server error does not automatically retry work that may already have run."}{" "} + Failover stops once response data reaches the caller. NyxID + access, approval and billing errors stop the request. +

+
+
+ )} +
+ ); +} diff --git a/frontend/src/components/dashboard/service-pool-summary.tsx b/frontend/src/components/dashboard/service-pool-summary.tsx new file mode 100644 index 000000000..f015928fc --- /dev/null +++ b/frontend/src/components/dashboard/service-pool-summary.tsx @@ -0,0 +1,142 @@ +import { GitBranch } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + poolFailoverLabel, + poolFailoverSummary, + poolStrategyLabel, +} from "@/lib/service-pool-display"; +import type { ServicePool } from "@/schemas/pools"; + +export function ServicePoolSummary({ + pools, + loading, + incomplete, + serviceName, + expanded, + contentId, + onOpen, +}: { + readonly pools: readonly ServicePool[]; + readonly loading: boolean; + readonly incomplete: boolean; + readonly serviceName: string; + readonly expanded: boolean; + readonly contentId: string; + readonly onOpen: () => void; +}) { + const first = pools[0]; + const name = + first?.name ?? + (loading + ? "Loading pools…" + : incomplete + ? "Pool access incomplete" + : "Direct connections · no pool"); + const failover = first + ? poolFailoverSummary(pools) + : loading + ? "Loading…" + : incomplete + ? "Not confirmed" + : "No pool"; + const memberIds = new Set( + pools.flatMap((pool) => + pool.members.map((member) => member.user_service_id), + ), + ); + const formats = [ + ...new Set( + pools.map( + (pool) => + ({ + priority: "Priority", + weighted: "Weighted", + round_robin: "Round-robin", + })[pool.strategy], + ), + ), + ].join(" / "); + const config = first + ? `${memberIds.size} ${memberIds.size === 1 ? "connection" : "connections"} · ${formats}` + : loading + ? "Loading routing…" + : incomplete + ? "Routing not confirmed" + : "Each connection uses its own slug"; + + return ( + + + + + + + {pools.map((pool) => ( +
+

+ {pool.name} · {poolStrategyLabel(pool)} +

+

+ {pool.members.filter((member) => member.enabled).length} of{" "} + {pool.members.length} connections enabled ·{" "} + {poolFailoverLabel(pool)} +

+ /api/v1/proxy/s/{pool.slug} +
+ ))} +

+ {first + ? "Configured policy applies when calling the pool slug. Actual attempts depend on eligible members and the request. Individual connection slugs run directly." + : loading + ? "Loading saved pool membership." + : incomplete + ? "Pool membership could not be fully checked." + : "No saved pool contains these connections. Individual connection slugs run directly."} +

+ {first && incomplete && ( +

+ Showing known pools only; additional organization pools may + require admin access. +

+ )} +
+
+
+ ); +} diff --git a/frontend/src/components/dashboard/service-pools-tab.tsx b/frontend/src/components/dashboard/service-pools-tab.tsx index e21afb7ad..759254bc7 100644 --- a/frontend/src/components/dashboard/service-pools-tab.tsx +++ b/frontend/src/components/dashboard/service-pools-tab.tsx @@ -38,20 +38,27 @@ import type { ServicePool } from "@/schemas/pools"; import { Choice } from "./pool-controls"; import { PoolEditor } from "./pool-editor"; import { PoolHealthDialog } from "./pool-health-dialog"; -import { message, strategyLabels } from "./pool-labels"; +import { message, readOnlyPreview, strategyLabels } from "./pool-labels"; +import { ServicePoolCards } from "./service-pool-cards"; export { PoolEditor } from "./pool-editor"; export { PoolHealthDialog } from "./pool-health-dialog"; interface ServicePoolsTabProps { + readonly layout?: "cards" | "table"; + readonly initialOrgId?: string; + readonly initialPoolId?: string; readonly createOpen: boolean; readonly onCreateOpenChange: (open: boolean) => void; } export function ServicePoolsTab({ + layout = "table", + initialOrgId, + initialPoolId, createOpen, onCreateOpenChange, }: ServicePoolsTabProps) { - const [owner, setOwner] = useState("personal"); + const [owner, setOwner] = useState(initialOrgId ?? "personal"); const orgId = owner === "personal" ? undefined : owner; const { data: orgs } = useOrgs(); const managedOrgs = (orgs ?? []).filter((o) => @@ -123,6 +130,7 @@ export function ServicePoolsTab({ Connections & health { void toggle(pool); }} @@ -131,6 +139,7 @@ export function ServicePoolsTab({ setDeleting(pool)} > Delete @@ -206,7 +215,16 @@ export function ServicePoolsTab({
)} - {(pools.data?.length ?? 0) > 0 && ( + {(pools.data?.length ?? 0) > 0 && layout === "cards" && ( + + )} + {(pools.data?.length ?? 0) > 0 && layout === "table" && ( <>
@@ -274,9 +292,7 @@ export function ServicePoolsTab({ className="flex items-center justify-between gap-3 rounded-xl border border-border/50 bg-card p-4" >
-

- {pool.name} -

+

{pool.name}

{pool.slug} · {strategyLabels[pool.strategy]} ·{" "} {pool.is_active ? "Enabled" : "Disabled"} diff --git a/frontend/src/components/dashboard/service-routing-preview.test.tsx b/frontend/src/components/dashboard/service-routing-preview.test.tsx new file mode 100644 index 000000000..184e97c2f --- /dev/null +++ b/frontend/src/components/dashboard/service-routing-preview.test.tsx @@ -0,0 +1,1782 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + render as renderDom, + screen, + cleanup, + fireEvent, + act, + within, +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { ReactNode } from "react"; +import type { CatalogEntry, KeyInfo } from "@/types/keys"; +import type { ServicePool } from "@/schemas/pools"; +import type { ServiceInsight } from "@/schemas/service-insights"; +import { configuredBilling } from "@/lib/service-insights-compat"; +import { DEFAULT_SERVICE_FILTERS } from "@/schemas/service-view"; +import { AddCtaButton } from "@/components/shared/add-cta-button"; + +function render(ui: ReactNode) { + const client = new QueryClient({ + defaultOptions: { mutations: { retry: false }, queries: { retry: false } }, + }); + return renderDom(ui, { + wrapper: ({ children }) => ( + {children} + ), + }); +} + +const internalCatalog = vi.hoisted(() => [] as Partial[]); +const { records, account, poolState, insightConnections } = vi.hoisted(() => ({ + insightConnections: new Map(), + account: { id: "user-a" }, + poolState: { data: [] as ServicePool[], error: null as unknown }, + records: [ + { + id: "mine", + label: "Personal account", + slug: "openai-personal", + endpoint_url: "https://api.openai.com", + description: "My development connection", + granted_scopes: ["models:read"], + catalog_service_id: "openai-id", + catalog_service_slug: "openai", + catalog_service_name: "OpenAI", + service_type: "http", + is_active: true, + status: "active", + credential_type: "api_key", + auth_method: "bearer", + api_key_id: "credential-a", + node_id: null, + auto_connected: false, + credential_source: { type: "personal" }, + }, + { + id: "team", + label: "Team account", + slug: "openai-team", + catalog_service_id: "openai-id", + catalog_service_slug: "openai", + catalog_service_name: "OpenAI", + service_type: "http", + is_active: true, + status: "active", + credential_type: "api_key", + auth_method: "bearer", + api_key_id: "credential-b", + node_id: null, + auto_connected: false, + credential_source: { + type: "org", + org_id: "team-id", + org_name: "Chrono", + allowed: true, + role: "member", + }, + }, + ] as KeyInfo[], +})); + +vi.mock("@tanstack/react-router", () => ({ + Link: ({ + children, + params = {}, + to, + search, + ...props + }: { + children: ReactNode; + params: { keyId?: string; groupId?: string }; + to: string; + search?: Record; + "aria-label"?: string; + }) => ( + entry[1] !== undefined))}` : ""}` + } + > + {children} + + ), +})); +vi.mock("@/hooks/use-keys", () => ({ + useKeys: () => ({ data: records, refetch: vi.fn() }), + useCatalog: (options?: { includeAll?: boolean }) => ({ + data: options?.includeAll ? internalCatalog : [], + refetch: vi.fn(), + }), +})); +vi.mock("@/hooks/use-user-services", () => ({ + useUserServices: () => ({ data: [], refetch: vi.fn() }), +})); +vi.mock("@/hooks/use-pools", () => ({ + useServicePools: () => ({ ...poolState, refetch: vi.fn() }), + usePoolHealth: () => ({ + data: { candidates: [] }, + isError: false, + isLoading: false, + }), +})); +vi.mock("@/hooks/use-service-routing-pools", () => ({ + useServiceRoutingPools: () => ({ + pools: poolState.error ? [] : poolState.data, + loading: false, + incomplete: !!poolState.error, + }), +})); +vi.mock("@/stores/auth-store", () => ({ + useAuthStore: (selector: (state: { user: { id: string } }) => unknown) => + selector({ user: account }), +})); + +import ServiceRoutingPreview from "./service-routing-preview"; +import { useServiceCardView } from "@/stores/service-card-view-store"; + +vi.mock("@/hooks/use-service-insights", () => ({ + useServiceInsights: () => ({ + connections: insightConnections, + status: insightConnections.size ? "ready" : "unavailable", + refresh: vi.fn(), + }), +})); +vi.mock("@/hooks/use-nodes", () => ({ useNodes: () => ({ data: [] }) })); +function preview() { + return ; +} +function pool(id: string, members = ["mine", "team"]): ServicePool { + return { + id, + name: id, + slug: id.toLowerCase(), + user_id: "user-a", + strategy: "round_robin", + members: members.map((user_service_id) => ({ + user_service_id, + weight: 1, + enabled: true, + })), + rr_counter: 0, + is_active: true, + created_at: "2026-01-01", + updated_at: "2026-01-01", + }; +} + +beforeEach(() => { + useServiceCardView.setState({ + accountId: undefined, + expanded: [], + filters: undefined, + }); + localStorage.clear(); + account.id = "user-a"; + poolState.data = []; + poolState.error = null; + insightConnections.clear(); + internalCatalog.splice(0); +}); +afterEach(() => { + cleanup(); + records.splice(2); +}); + +describe("live grouped services", () => { + it("shows three NyxID OAuth connections and one supplied X app, including the disabled connection", () => { + const catalog = { + slug: "api-twitter", + billing: { + platform_billable: true, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced" as const, + }, + }, + }; + for (let index = 0; index < 4; index++) { + const connection: KeyInfo = { + ...records[index === 3 ? 1 : 0]!, + id: `x-${index}`, + label: index === 3 ? "Organization X app" : "NyxID X app", + catalog_service_id: "x", + catalog_service_slug: "api-twitter", + catalog_service_name: "Twitter / X API", + credential_binding: "user", + credential_type: "oauth2", + oauth_app_source: index === 3 ? "byo" : "platform", + is_active: index !== 2, + }; + records.push(connection); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, catalog), + usage: null, + }); + } + render(preview()); + const card = within( + screen.getByRole("region", { name: "Twitter / X API" }), + ); + expect( + card.getByRole("button", { name: "Show billing for Twitter / X API" }), + ).toHaveTextContent(/^3 NyxID · 1 BYOK$/); + expect(card.getByText("1 disabled")).toBeVisible(); + }); + it("verifies an internal service's absent billing from the full catalog when insights lack that metadata", () => { + const connection: KeyInfo = { + ...records[0]!, + id: "chrono-llm", + label: "Chrono LLM", + catalog_service_id: "chrono-llm", + catalog_service_slug: "chrono-llm", + catalog_service_name: "Chrono LLM", + api_key_id: null, + auth_method: "none", + }; + records.push(connection); + internalCatalog.push({ + slug: "chrono-llm", + name: "Chrono LLM", + billing: null, + }); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection), + usage: null, + }); + render(preview()); + const summary = screen.getByRole("button", { + name: "Show billing for Chrono LLM", + }); + expect(summary).toHaveTextContent("—"); + expect(summary).toHaveAccessibleDescription("Not billable by NyxID"); + expect(summary).not.toHaveTextContent("Unverified"); + }); + it("shows a dash and exact no-charge tooltip on the card and each expanded connection", async () => { + for (let i = 0; i < 2; i++) { + const connection = { + ...records[0]!, + id: `unpriced-${i}`, + label: `Unpriced account ${i}`, + catalog_service_id: "unpriced", + catalog_service_slug: "unpriced", + catalog_service_name: "Unpriced service", + credential_type: "oauth2", + }; + records.push(connection); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, { slug: "unpriced" }), + usage: null, + }); + } + const user = userEvent.setup(); + render(preview()); + const card = within( + screen.getByRole("region", { name: "Unpriced service" }), + ); + const summary = card.getByRole("button", { + name: "Show billing for Unpriced service", + }); + expect(summary).toHaveTextContent(/^—$/); + expect(summary).not.toHaveTextContent(/unverified/i); + await user.hover(summary); + expect(await screen.findByRole("tooltip")).toHaveTextContent( + /^Not billable by NyxID$/, + ); + await user.click(summary); + const billing = card.getByRole("region", { + name: "Billing for Unpriced account 0", + }); + expect( + within(billing).getByText("Free on NyxID", { selector: "p" }), + ).toBeVisible(); + expect( + within(billing).queryByText("Credit billing unverified"), + ).not.toBeInTheDocument(); + const row = card.getByRole("button", { + name: "Billing for Unpriced account 1", + }); + expect(row).toHaveTextContent(/^—$/); + await user.hover(row); + expect(await screen.findByRole("tooltip")).toHaveTextContent( + /^Not billable by NyxID$/, + ); + }); + + it.each([ + { type: "api_key", auth: "bearer", label: "BYOK" }, + { type: "api_key", auth: "none", label: "—" }, + { type: "oauth2", auth: "bearer", label: "—" }, + ])( + "shows $label for a private $type service using $auth auth", + ({ type, auth, label }) => { + const connection: KeyInfo = { + ...records[0]!, + id: "private", + label: "Private service", + slug: "private", + source: "custom", + catalog_service_id: null, + catalog_service_slug: null, + catalog_service_name: null, + credential_type: type, + auth_method: auth, + }; + records.push(connection); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection), + usage: null, + }); + render(preview()); + expect( + screen.getByRole("button", { + name: "Show billing for Private service", + }), + ).toHaveTextContent(label); + }, + ); + + it.each([ + { + name: "Twitter / X API", + slug: "api-twitter", + type: "oauth2", + app: "own-app", + }, + { name: "DeepSeek API", slug: "llm-deepseek", type: "api_key", app: null }, + ])( + "shows $name supplied credentials as BYOK despite a catalog platform price", + async ({ name, slug, type, app }) => { + const connection: KeyInfo = { + ...records[0]!, + id: slug, + label: name, + slug, + catalog_service_id: slug, + catalog_service_slug: slug, + catalog_service_name: name, + credential_binding: "user", + credential_type: type, + oauth_client_id: app, + oauth_app_source: app ? "byo" : null, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced", + }, + byok_pricing: null, + }; + records.push(connection); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, { + slug: connection.catalog_service_slug ?? "custom", + billing: { platform_billable: true }, + }), + usage: null, + }); + const user = userEvent.setup(); + render(preview()); + const card = within(screen.getByRole("region", { name })); + const summary = card.getByRole("button", { + name: `Show billing for ${name}`, + }); + expect(summary).toHaveTextContent(/^BYOK$/); + await user.hover(summary); + const tooltip = await screen.findByRole("tooltip"); + expect(tooltip).toHaveTextContent("BYOK · 1 connection"); + expect(tooltip).toHaveTextContent(name); + expect(tooltip).not.toHaveTextContent("0.05"); + await user.click(summary); + const panel = card.getByRole("region", { name: `Billing for ${name}` }); + expect( + within(panel).getByText( + type === "oauth2" ? /^Your own .+ app$/ : "Your own API key", + ), + ).toBeVisible(); + expect( + within(panel).getByText("Free on NyxID", { selector: "p" }), + ).toBeVisible(); + expect(panel).not.toHaveTextContent("0.05"); + expect( + card.queryByText("NyxID platform billing configured"), + ).not.toBeInTheDocument(); + }, + ); + + it("lists all connection billing types in a shared catalog group and opens each connection's own rates", async () => { + const catalog = { + slug: "api-twitter", + billing: { + platform_charge_nyxid_credentials_only: true, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced" as const, + }, + byok_pricing: { + metric: "requests", + credits_per_unit: "0.01", + sync_status: "synced" as const, + }, + }, + }; + const byo: KeyInfo = { + ...records[1]!, + id: "twitter-byo", + label: "ChronoAI Twitter", + catalog_service_id: "twitter", + catalog_service_slug: "api-twitter", + catalog_service_name: "Twitter", + credential_binding: "user", + credential_type: "oauth2", + oauth_client_id: "chrono-app", + oauth_app_source: "byo", + platform_key_pricing: catalog.billing.platform_key_pricing, + byok_pricing: catalog.billing.byok_pricing, + }; + const platform: KeyInfo = { + ...byo, + id: "twitter-platform", + label: "Personal Twitter", + credential_source: { type: "personal" }, + credential_binding: "platform", + }; + const noCharge: KeyInfo = { + ...byo, + id: "twitter-unpriced", + label: "Public Twitter", + auth_method: "none", + credential_source: { type: "personal" }, + }; + const unknown: KeyInfo = { + ...byo, + id: "twitter-unknown", + label: "Legacy Twitter", + oauth_client_id: null, + oauth_app_source: null, + }; + for (const connection of [byo, platform, noCharge, unknown]) { + records.push(connection); + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, catalog), + usage: null, + }); + } + const user = userEvent.setup(); + render(preview()); + const card = within(screen.getByRole("region", { name: "Twitter" })); + const summary = card.getByRole("button", { + name: "Show billing for Twitter", + }); + expect(summary).toHaveTextContent("2 NyxID · 1 BYOK · 1 unverified"); + await user.hover(summary); + const tooltip = await screen.findByRole("tooltip"); + for (const text of [ + "BYOK · 1 connection", + "ChronoAI Twitter", + "Personal Twitter", + "Public Twitter", + "Unverified · 1 connection", + "Legacy Twitter", + "0.05 credits/request", + ]) + expect(tooltip).toHaveTextContent(text); + await user.click(summary); + const platformPanel = card.getByRole("region", { + name: "Billing for Personal Twitter", + }); + expect( + within(platformPanel).getByText("0.05 credits per request"), + ).toBeVisible(); + await user.click( + card.getByRole("button", { name: "Billing for ChronoAI Twitter" }), + ); + const byoPanel = card.getByRole("region", { + name: "Billing for ChronoAI Twitter", + }); + expect(byoPanel).toHaveTextContent("Chrono's own Twitter app"); + expect(byoPanel).toHaveTextContent( + "so NyxID doesn't charge for it. Twitter may bill Chrono directly.", + ); + expect(byoPanel).not.toHaveTextContent("0.05"); + expect(byoPanel).not.toHaveTextContent("0.01"); + }); + + it("shows five supplied apps and one NyxID connection and opens the NyxID member", async () => { + for (let i = 0; i < 4; i++) + records.push({ + ...records[0]!, + id: `extra-${i}`, + label: `App ${i}`, + slug: `app-${i}`, + is_active: i !== 3, + credential_binding: i === 3 ? "platform" : "user", + }); + for (const connection of records) + insightConnections.set(connection.id, { + service_id: connection.id, + billing: { + ...configuredBilling(connection), + service_billing_configured: true, + credit_billing_configured: connection.id === "extra-3", + }, + usage: null, + }); + render(preview()); + expect(screen.getByText("1 NyxID · 5 BYOK")).toBeVisible(); + expect(screen.getByText("1 disabled")).toBeVisible(); + await userEvent.click( + screen.getByRole("button", { name: "Show billing for OpenAI" }), + ); + expect( + screen.getByRole("region", { name: "Billing for App 3" }), + ).toBeVisible(); + expect(screen.getByText("Uses NyxID credits")).toBeVisible(); + expect(screen.getByText("NyxID")).toBeVisible(); + }); + it("identifies supplied API keys without requiring published prices", () => { + insightConnections.set("mine", { + service_id: "mine", + billing: { + ...configuredBilling(records[0]!), + service_billing_configured: true, + credit_billing_configured: true, + }, + usage: null, + }); + insightConnections.set("team", { + service_id: "team", + billing: configuredBilling(records[1]!, { + slug: "openai", + billing: { platform_billable: true }, + }), + usage: null, + }); + render(preview()); + expect(screen.getByText("2 BYOK")).toBeVisible(); + }); + it("shows recorded last edit and opens that connection's history", async () => { + records.push({ + ...records[0]!, + id: "edited", + label: "Edited connection", + slug: "edited", + authorship: { + created_by: null, + last_change: { + actor: { + kind: "person", + id: "calvin", + name: "Calvin", + person_id: "calvin", + api_key_id: null, + app_id: null, + }, + at: "2026-10-01T10:00:00Z", + action: "updated", + change_group_id: "change", + }, + }, + }); + render(preview()); + expect(screen.getByText(/Last edit .* · Calvin/)).toBeVisible(); + await userEvent.click( + screen.getByRole("button", { name: "Show last edit for OpenAI" }), + ); + const row = screen.getByRole("row", { name: /Edited connection/ }); + expect( + within(row).getByRole("button", { name: /History/ }), + ).toHaveAttribute("aria-expanded", "true"); + }); + it("keeps mixed-source billing separate in Personal view and opens the selected source", async () => { + records.push({ + ...records[0]!, + id: "platform", + slug: "openai-platform", + label: "Platform account", + credential_binding: "platform", + auto_connected: true, + }); + records.push({ + ...records[2]!, + id: "platform-only", + catalog_service_id: "other-id", + catalog_service_name: "Platform-only service", + catalog_service_slug: "other", + }); + for (const connection of records) { + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, { + slug: connection.catalog_service_slug ?? "custom", + billing: { platform_billable: true }, + }), + usage: null, + }); + } + const user = userEvent.setup(); + render(preview()); + expect( + screen.queryByRole("region", { name: "Platform-only service" }), + ).not.toBeInTheDocument(); + fireEvent.click( + screen.getByRole("button", { name: "Auto-connected services: hidden" }), + ); + // Shown auto-connected services belong to your account, so they list too. + expect( + screen.getByRole("region", { name: "Platform-only service" }), + ).toBeInTheDocument(); + const card = screen.getByRole("region", { name: "OpenAI" }); + expect(within(card).getByText("3 connections")).toBeVisible(); + expect(within(card).getByText("1 NyxID · 2 BYOK")).toBeVisible(); + expect(within(card).queryByText("Sources")).not.toBeInTheDocument(); + for (const source of ["Personal", "Chrono", "NyxID platform", "Personal"]) { + await user.hover( + screen.getByRole("button", { + name: `${source} · Show sources for OpenAI`, + }), + ); + expect( + within(await screen.findByRole("tooltip")).getByText(source), + ).toBeVisible(); + } + expect(screen.getAllByRole("tooltip")).toHaveLength(1); + expect( + within(screen.getByRole("tooltip")).getByText( + "Connections you own. Billing is shown separately.", + ), + ).toBeVisible(); + await user.unhover( + screen.getByRole("button", { + name: "Personal · Show sources for OpenAI", + }), + ); + await user.click( + within(card).getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(within(card).getByText("openai-platform")).toBeVisible(); + expect(within(card).getByText("openai-team")).toBeVisible(); + await user.click( + screen.getByRole("button", { + name: "Billing for Platform account", + }), + ); + expect( + within(card).getByRole("region", { + name: "Billing for Platform account", + }), + ).toBeVisible(); + expect( + within(card).getByText( + /free allowances first, then credit grants, then your wallet balance/, + ), + ).toBeVisible(); + expect( + within(card).queryByRole("region", { + name: "Billing for Personal account", + }), + ).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expect( + screen.getByRole("region", { name: "Platform-only service" }), + ).toBeVisible(); + }); + it("shows configured agent associations and identifies credential supply on hover", async () => { + insightConnections.set("mine", { + service_id: "mine", + billing: configuredBilling(records[0]!, { + slug: "openai", + billing: { platform_billable: true }, + }), + usage: { + access: { + basis: "configuration", + visibility: "own_keys", + truncated: false, + keys: [ + { + id: "agent", + name: "Codex CI", + platform: "codex", + owner_id: "user-a", + permission: "selected_service", + credential_override: false, + }, + ], + }, + activity: { + visibility: "unavailable", + tracking: "unavailable", + period_days: 30, + request_count: 0, + requests: [], + truncated: false, + }, + }, + }); + const user = userEvent.setup(); + render(preview()); + expect(screen.getByText("1+ agent key")).toBeVisible(); + expect(screen.getByText("Last use not reported")).toBeVisible(); + await user.hover( + screen.getByRole("button", { + name: "Show agent keys and use for OpenAI", + }), + ); + expect(await screen.findByRole("tooltip")).toHaveTextContent( + "Keys with access: Codex CI", + ); + await user.hover( + screen.getByRole("button", { name: "Show billing for OpenAI" }), + ); + const tooltip = within(await screen.findByRole("tooltip")); + expect(tooltip.getByText("Connection billing")).toBeInTheDocument(); + expect(tooltip.getAllByText("BYOK · 1 connection")[0]).toBeInTheDocument(); + expect(tooltip.getByText("Personal account")).toBeInTheDocument(); + expect(screen.queryByText("Latest request")).not.toBeInTheDocument(); + expect(screen.queryByText(/No recorded requests/)).not.toBeInTheDocument(); + }); + it("keeps last-used layer and billing visible when the card is expanded and opens row details", async () => { + const user = userEvent.setup(); + insightConnections.set("mine", { + service_id: "mine", + billing: configuredBilling(records[0]!), + usage: { + access: { visibility: "own_keys", keys: [], truncated: false }, + activity: { + visibility: "own_requests", + tracking: "partial", + period_days: 30, + request_count: 1, + truncated: false, + requests: [ + { + id: "event", + execution_id: "request", + occurred_at: "2026-09-29T12:00:00Z", + outcome: "response_received", + response_status: 200, + caller: { + id: "agent", + kind: "agent_key", + name: "Codex worker", + app_id: "app", + app_name: "Release app", + }, + source: { kind: "platform", owner_id: "user-a" }, + }, + ], + }, + }, + }); + render(preview()); + expect(screen.getByText("Agent keys unverified")).toBeVisible(); + expect(screen.getByText(/^Last use /)).toBeVisible(); + await user.hover( + screen.getByRole("button", { + name: "Show agent keys and use for OpenAI", + }), + ); + expect(await screen.findByRole("tooltip")).toHaveTextContent( + /Last use: Codex worker · Release app/, + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + const summary = screen.getByRole("button", { + name: "Show agent keys and use for OpenAI", + }); + expect(within(summary).getByText(/^Last use /)).toBeVisible(); + await user.click(summary); + expect( + screen.getByRole("table", { name: "Recent connection requests" }), + ).toBeVisible(); + await user.hover( + screen.getByRole("button", { name: "Show billing for OpenAI" }), + ); + await user.click( + screen.getByRole("button", { name: "Show billing for OpenAI" }), + ); + expect( + screen.getByRole("region", { name: "Billing for Personal account" }), + ).toBeVisible(); + expect( + screen.queryByRole("table", { name: "Recent connection requests" }), + ).not.toBeInTheDocument(); + }); + it("keeps scroll height stable when compacting at the bottom of a short filtered view", () => { + const callbacks: ResizeObserverCallback[] = []; + vi.stubGlobal( + "ResizeObserver", + class { + constructor(callback: ResizeObserverCallback) { + callbacks.push(callback); + } + observe() {} + disconnect() {} + }, + ); + try { + render(

{preview()}
); + const main = screen.getByRole("main"); + const filters = screen.getByRole("region", { name: "Service filters" }); + const container = filters.parentElement!; + const surface = filters.firstElementChild!; + surface.getBoundingClientRect = () => + new DOMRect(0, 0, 900, filters.dataset.stuck === "true" ? 80 : 240); + container.getBoundingClientRect = () => + new DOMRect(0, 80 - main.scrollTop, 900, 1200); + filters.getBoundingClientRect = () => + new DOMRect( + 0, + Math.max(0, 80 - main.scrollTop), + 900, + Math.max( + surface.getBoundingClientRect().height, + parseFloat(filters.style.minHeight) || 0, + ), + ); + act(() => + callbacks.forEach((callback) => callback([], {} as ResizeObserver)), + ); + main.scrollTop = 100; + fireEvent.scroll(main); + for (let frame = 0; frame < 4; frame++) { + // Model the browser clamping scrollTop after scrollHeight shrinks. + main.scrollTop = Math.max( + 0, + Math.min( + main.scrollTop, + 100 + filters.getBoundingClientRect().height - 240, + ), + ); + act(() => + callbacks.forEach((callback) => callback([], {} as ResizeObserver)), + ); + fireEvent.scroll(main); + expect(main.scrollTop).toBe(100); + expect(filters).toHaveAttribute("data-stuck", "true"); + expect( + container.style.getPropertyValue("--service-filters-height"), + ).toBe("80px"); + } + main.scrollTop = 0; + fireEvent.scroll(main); + expect(filters).toHaveAttribute("data-stuck", "false"); + expect(filters.style.minHeight).toBe(""); + expect(screen.getByRole("button", { name: "Saved views" })).toBeVisible(); + } finally { + vi.unstubAllGlobals(); + } + }); + it("covers the gap above a pinned service header and clears it on return or collapse", async () => { + const user = userEvent.setup(); + render(
{preview()}
); + const main = screen.getByRole("main"); + main.scrollTo = vi.fn(); + const card = screen.getByRole("region", { name: "OpenAI" }); + const header = within(card) + .getByRole("heading", { name: "OpenAI" }) + .closest(".service-card-header")!; + card.getBoundingClientRect = () => + new DOMRect(0, 300 - main.scrollTop, 800, 1000); + header.getBoundingClientRect = () => + new DOMRect( + 0, + Math.max(132, 300 - main.scrollTop) + + (Number.parseFloat( + (header.style.translate ?? "").split(" ")[1] ?? "0", + ) || 0), + 800, + 120, + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + card.querySelector( + "[data-service-connection-row]", + )!.getBoundingClientRect = () => + new DOMRect(0, 452 - main.scrollTop, 800, 180); + fireEvent.scroll(main); + expect(header).toHaveAttribute("data-stuck", "false"); + main.scrollTop = 300; + fireEvent.scroll(main); + expect(header).toHaveAttribute("data-stuck", "true"); + main.scrollTop = 0; + fireEvent.scroll(main); + expect(header).toHaveAttribute("data-stuck", "false"); + main.scrollTop = 300; + fireEvent.scroll(main); + await user.click( + screen.getByRole("button", { name: "Collapse OpenAI connections" }), + ); + expect(header).toHaveAttribute("data-stuck", "false"); + }); + + it("releases the pinned header before the final three connections and restores it when scrolling back", async () => { + for (let index = 2; index <= 5; index++) { + records.push({ + ...records[0]!, + id: `personal-${index}`, + slug: `openai-${index}`, + }); + } + const user = userEvent.setup(); + render(
{preview()}
); + const main = screen.getByRole("main"); + main.scrollTo = vi.fn(); + const card = screen.getByRole("region", { name: "OpenAI" }); + const header = within(card) + .getByRole("heading", { name: "OpenAI" }) + .closest(".service-card-header")!; + card.getBoundingClientRect = () => + new DOMRect(0, 300 - main.scrollTop, 800, 900); + header.getBoundingClientRect = () => + new DOMRect( + 0, + Math.max(132, 300 - main.scrollTop) + + (Number.parseFloat( + (header.style.translate ?? "").split(" ")[1] ?? "0", + ) || 0), + 800, + 120, + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + const rows = [ + ...card.querySelectorAll("[data-service-connection-row]"), + ]; + expect(rows).toHaveLength(6); + const rowTops = [452, 572, 672, 752, 842, 992]; + rows.forEach((row, index) => { + row.getBoundingClientRect = () => + new DOMRect(0, rowTops[index]! - main.scrollTop, 800, 100); + }); + main.scrollTop = 400; + fireEvent.scroll(main); + expect(header.style.translate).toBe(""); + main.scrollTop = 550; + fireEvent.scroll(main); + expect(header.style.translate).toBe("0 -50px"); + expect(header.getBoundingClientRect().bottom).toBe( + rows[3]!.getBoundingClientRect().top, + ); + main.scrollTop = 600; + fireEvent.scroll(main); + expect(header.style.translate).toBe("0 -100px"); + main.scrollTop = 400; + fireEvent.scroll(main); + expect(header.style.translate).toBe(""); + await user.click( + screen.getByRole("button", { name: "Collapse OpenAI connections" }), + ); + expect(header.style.translate).toBe(""); + expect(header).toHaveAttribute("data-stuck", "false"); + }); + + it("shows only filters and active pills while stuck, then restores saved views and the footer", async () => { + const user = userEvent.setup(); + const connect = vi.fn(); + render( +
+ ( + + )} + /> +
, + ); + const main = screen.getByRole("main"); + const filters = screen.getByRole("region", { name: "Service filters" }); + const container = filters.parentElement!; + container.getBoundingClientRect = () => + new DOMRect(0, 80 - main.scrollTop, 900, 1200); + filters.getBoundingClientRect = () => + new DOMRect(0, Math.max(0, 80 - main.scrollTop), 900, 200); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.getByRole("button", { name: "Collapse" })).toBeVisible(); + await user.click(screen.getByRole("button", { name: "Saved views" })); + expect(screen.getByText(/No saved view yet/)).toBeVisible(); + expect( + screen.getByRole("button", { name: "Connect Service" }), + ).toHaveTextContent("Connect Service"); + const personalToggle = screen.getByRole("button", { + name: "Service view: Personal", + }); + await user.hover(personalToggle); + expect( + await screen.findByRole("tooltip", { name: /Switch to all services/ }), + ).toHaveTextContent( + /including their accessible organization and platform connections/, + ); + await user.unhover(personalToggle); + main.scrollTop = 120; + fireEvent.scroll(main); + expect( + screen.queryByRole("button", { name: "Saved views" }), + ).not.toBeInTheDocument(); + expect(screen.queryByText(/No saved view yet/)).not.toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Save as default" }), + ).not.toBeInTheDocument(); + const compactConnect = screen.getByRole("button", { + name: "Connect Service", + }); + expect(compactConnect).toHaveTextContent(/^Connect$/); + await user.hover(compactConnect); + expect( + await screen.findByRole("tooltip", { name: "Connect Service" }), + ).toBeInTheDocument(); + await user.keyboard("{Escape}"); + expect( + screen.getByRole("button", { name: "Service view: Personal" }), + ).toHaveTextContent(/^$/); + await user.click(compactConnect); + expect(connect).toHaveBeenCalledOnce(); + expect( + screen.queryByRole("button", { name: "Filters" }), + ).not.toBeInTheDocument(); + // Primary actions stay reachable in the stuck filter row. + expect( + screen.queryByRole("button", { name: "Refresh metadata" }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Collapse" }), + ).not.toBeInTheDocument(); + expect(screen.queryByText(/matching connection/)).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Service view: Personal" }), + ).toBeVisible(); + const compactPersonalToggle = screen.getByRole("button", { + name: "Service view: Personal", + }); + await user.hover(compactPersonalToggle); + expect( + await screen.findByRole("tooltip", { name: /Switch to all services/ }), + ).toBeVisible(); + await user.click(compactPersonalToggle); + const allToggle = screen.getByRole("button", { + name: "Service view: All services", + }); + expect(allToggle).toBeVisible(); + await user.unhover(allToggle); + await user.hover(allToggle); + expect( + await screen.findByRole("tooltip", { + name: /Switch to personal services/, + }), + ).toHaveTextContent("Showing all accessible services."); + await user.unhover(allToggle); + await user.click(screen.getByRole("button", { name: "Organization" })); + await user.click(screen.getByRole("checkbox", { name: "Chrono" })); + await user.click(screen.getByRole("button", { name: "Done" })); + expect( + within(filters).getByRole("button", { name: "Remove Org: Chrono" }), + ).toBeVisible(); + main.scrollTop = 0; + fireEvent.scroll(main); + expect( + screen.getByRole("button", { name: "Connect Service" }), + ).toHaveTextContent("Connect Service"); + expect( + screen.getByRole("button", { name: "Service view: All services" }), + ).toHaveTextContent("All services"); + expect(screen.getByRole("button", { name: "Saved views" })).toBeVisible(); + expect( + screen.getByRole("button", { name: "Save as default" }), + ).toBeVisible(); + expect( + screen.queryByRole("button", { name: "Refresh metadata" }), + ).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Collapse" })).toBeVisible(); + expect(screen.getByText(/matching connection/)).toBeVisible(); + expect( + screen.getAllByRole("button", { name: /^Service view:/ }), + ).toHaveLength(1); + }); + + it("starts in Personal view and keeps all view controls in the filter card", async () => { + records.push({ + ...records[0]!, + id: "platform", + auto_connected: true, + label: "Platform connection", + slug: "openai-platform", + }); + const user = userEvent.setup(); + render(preview()); + expect( + screen.getByText("1 service · 2 matching connections"), + ).toBeVisible(); + fireEvent.click( + screen.getByRole("button", { name: "Auto-connected services: hidden" }), + ); + const filters = screen.getByRole("region", { name: "Service filters" }); + expect( + within(filters).getByRole("button", { name: "Service view: Personal" }), + ).toBeVisible(); + expect( + within(filters).getByText("1 service · 3 matching connections"), + ).toBeVisible(); + expect( + within(filters).queryByRole("button", { name: "Refresh metadata" }), + ).not.toBeInTheDocument(); + expect( + within(filters).getByRole("button", { name: "Save as default" }), + ).toBeVisible(); + expect( + within(filters).getAllByRole("button", { name: /^Service view:/ }), + ).toHaveLength(1); + await user.click( + within(filters).getByRole("button", { name: "Saved views" }), + ); + expect(screen.getByText(/No saved view yet/)).toBeVisible(); + expect( + screen.getByRole("button", { name: "Save current filters as default" }), + ).toBeDisabled(); + await user.keyboard("{Escape}"); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.getByText("openai-team")).toBeVisible(); + expect(screen.getByText("Platform connection")).toBeVisible(); + await user.click( + within(filters).getByRole("button", { name: "Service view: Personal" }), + ); + expect(screen.getByText("openai-team")).toBeVisible(); + expect(screen.getByText("Platform connection")).toBeVisible(); + expect( + within(filters).getByText("1 service · 3 matching connections"), + ).toBeVisible(); + await user.click( + within(filters).getByRole("button", { + name: "Service view: All services", + }), + ); + await user.click( + within(filters).getByRole("button", { name: "Organization" }), + ); + await user.click(screen.getByRole("checkbox", { name: "Chrono" })); + await user.click(screen.getByRole("button", { name: "Done" })); + expect( + within(filters).getByRole("button", { + name: "Service view: All services", + }), + ).toBeVisible(); + expect( + within(filters).getByRole("button", { name: "Remove Org: Chrono" }), + ).toBeVisible(); + expect(screen.getByText("openai-team")).toBeVisible(); + await user.click( + within(filters).getByRole("button", { + name: "Service view: All services", + }), + ); + expect( + screen.queryByRole("button", { name: "Remove Org: Chrono" }), + ).not.toBeInTheDocument(); + expect(screen.getByText("openai-personal")).toBeVisible(); + }); + + it("starts collapsed and compares connections in a table inside one service", async () => { + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + const group = screen.getByRole("region", { name: "OpenAI" }); + const scroll = vi.fn(); + group.scrollIntoView = scroll; + expect(within(group).getByText("2 connections")).toBeVisible(); + expect( + screen.queryByText("https://api.openai.com"), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ).toHaveAttribute("aria-expanded", "false"); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(within(group).getByText("https://api.openai.com")).toBeVisible(); + const table = within(group).getByRole("table", { + name: "OpenAI connections", + }); + expect(within(table).getAllByRole("row")).toHaveLength(3); + expect(scroll).toHaveBeenCalledWith({ + behavior: "smooth", + block: "start", + inline: "nearest", + }); + for (const name of [ + "Owner / Credential", + "Access & requests", + "Billing", + "Connection / Slug", + "Configuration", + ]) { + expect(within(table).getByRole("columnheader", { name })).toBeVisible(); + } + expect(within(group).getByText("openai-team")).toBeVisible(); + expect(within(group).queryByText("1 granted")).not.toBeInTheDocument(); + expect( + within(group).getByRole("link", { + name: "Configure Personal account (Personal)", + }), + ).toHaveAttribute("href", "/keys/mine"); + expect( + within(group).getByRole("link", { + name: "View all OpenAI service details", + }), + ).toHaveAttribute("href", "/keys/services/catalog:openai-id"); + expect( + within(group).queryByText("Details", { selector: "summary" }), + ).not.toBeInTheDocument(); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Collapse OpenAI connections" }), + ); + expect(scroll).toHaveBeenCalledTimes(1); + expect( + screen.queryByRole("button", { + name: /Drag|By service|Individual cards/, + }), + ).not.toBeInTheDocument(); + }); + + it("closes the previous service and scrolls the most recently opened card", async () => { + records.push({ + ...records[0]!, + id: "twilio", + label: "Twilio account", + slug: "twilio", + catalog_service_id: "twilio-id", + catalog_service_name: "Twilio", + catalog_service_slug: "twilio", + }); + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + const openai = screen.getByRole("region", { name: "OpenAI" }); + const twilio = screen.getByRole("region", { name: "Twilio" }); + const scrollOpenai = vi.fn(); + const scrollTwilio = vi.fn(); + openai.scrollIntoView = scrollOpenai; + twilio.scrollIntoView = scrollTwilio; + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect( + screen.getByRole("table", { name: "OpenAI connections" }), + ).toBeVisible(); + await user.click(within(twilio).getByRole("button", { name: "Twilio" })); + expect( + screen.queryByRole("table", { name: "OpenAI connections" }), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("table", { name: "Twilio connections" }), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ).toHaveAttribute("aria-expanded", "false"); + expect(scrollOpenai).toHaveBeenCalledOnce(); + expect(scrollTwilio).toHaveBeenCalledOnce(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect( + screen.queryByRole("table", { name: "Twilio connections" }), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("table", { name: "OpenAI connections" }), + ).toBeVisible(); + expect(scrollOpenai).toHaveBeenCalledTimes(2); + expect(scrollTwilio).toHaveBeenCalledOnce(); + }); + + it("searches a nested connection and shows only matching rows with the group total", async () => { + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.type( + screen.getByRole("textbox", { name: "Search services and connections" }), + "Team account{Enter}", + ); + expect(screen.getByText("1 of 2 match")).toBeVisible(); + expect(screen.getByText("1 of 2 connections")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.queryByText("openai-personal")).not.toBeInTheDocument(); + expect(screen.getByText("openai-team")).toBeVisible(); + }); + + it("filters by actual organizations without the extra Filters menu", async () => { + const user = userEvent.setup(); + render(preview()); + expect( + screen.queryByRole("button", { name: "Filters" }), + ).not.toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: "Organization" })); + await user.click(screen.getByRole("checkbox", { name: "Chrono" })); + await user.click(screen.getByRole("button", { name: "Done" })); + expect(screen.getByText("1 of 2 match")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.queryByText("openai-personal")).not.toBeInTheDocument(); + expect(screen.getByText("openai-team")).toBeVisible(); + }); + + it("keeps criteria from older saved views visible and removable", async () => { + useServiceCardView.setState({ + accountId: account.id, + filters: { ...DEFAULT_SERVICE_FILTERS, state: "disabled" }, + }); + const user = userEvent.setup(); + render(preview()); + expect( + screen.queryByRole("button", { name: "Filters" }), + ).not.toBeInTheDocument(); + expect(screen.getByText("Disabled")).toBeVisible(); + expect( + screen.queryByRole("region", { name: "OpenAI" }), + ).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Remove Service state filter" }), + ); + expect(screen.getByRole("region", { name: "OpenAI" })).toBeVisible(); + }); + + it("supports multiple organizations and services with removable selection pills", async () => { + records.push( + { + ...records[1]!, + id: "elf", + label: "Elf account", + credential_source: { + type: "org", + org_id: "elf-id", + org_name: "Elf", + allowed: true, + role: "admin", + avatar_url: null, + }, + }, + { + ...records[1]!, + id: "twilio", + label: "Twilio", + slug: "twilio", + catalog_service_id: "twilio-id", + catalog_service_name: "Twilio", + catalog_service_slug: "twilio", + }, + ); + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.click(screen.getByRole("button", { name: "Organization" })); + await user.click(screen.getByRole("checkbox", { name: "Chrono" })); + await user.click(screen.getByRole("checkbox", { name: "Elf" })); + expect(screen.getByRole("checkbox", { name: "Chrono" })).toBeChecked(); + expect(screen.getByRole("checkbox", { name: "Elf" })).toBeChecked(); + await user.click(screen.getByRole("button", { name: "Done" })); + expect( + screen.getByRole("button", { name: "Organization" }), + ).toHaveTextContent("2 selected"); + expect( + screen.getByRole("button", { name: "Remove Org: Chrono" }), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: "Remove Org: Elf" }), + ).toBeVisible(); + await user.click(screen.getByRole("button", { name: "Service" })); + await user.type( + screen.getByRole("textbox", { name: "Search services" }), + "OpenAI", + ); + expect( + screen.queryByRole("checkbox", { name: "Twilio" }), + ).not.toBeInTheDocument(); + await user.click(screen.getByRole("checkbox", { name: "OpenAI" })); + await user.clear(screen.getByRole("textbox", { name: "Search services" })); + await user.click(screen.getByRole("checkbox", { name: "Twilio" })); + await user.click(screen.getByRole("button", { name: "Done" })); + expect( + screen.getByRole("button", { name: "Remove Service: OpenAI" }), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: "Remove Service: Twilio" }), + ).toBeVisible(); + expect(screen.getByRole("region", { name: "OpenAI" })).toBeVisible(); + expect(screen.getByRole("region", { name: "Twilio" })).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect( + within( + screen.getByRole("table", { name: "OpenAI connections" }), + ).getAllByRole("row"), + ).toHaveLength(3); + expect(screen.queryByText("openai-personal")).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Remove Org: Chrono" }), + ); + expect( + screen.queryByRole("region", { name: "Twilio" }), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Remove Service: Twilio" }), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: "Remove Org: Elf" }), + ).toBeVisible(); + expect( + within( + screen.getByRole("table", { name: "OpenAI connections" }), + ).getAllByRole("row"), + ).toHaveLength(2); + await user.click( + screen.getByRole("button", { name: "Remove Service: OpenAI" }), + ); + expect(screen.getByText(/No services match these filters/)).toBeVisible(); + await user.click(screen.getByRole("button", { name: "Organization" })); + expect(screen.getByRole("checkbox", { name: "Elf" })).toBeChecked(); + expect(screen.getByRole("checkbox", { name: "Chrono" })).not.toBeChecked(); + await user.click( + screen.getByRole("button", { name: "Clear organizations" }), + ); + await user.click(screen.getByRole("button", { name: "Done" })); + expect(screen.getByRole("region", { name: "Twilio" })).toBeVisible(); + expect( + screen.getByRole("button", { name: "Remove Service: Twilio" }), + ).toBeVisible(); + await user.click(screen.getByRole("button", { name: "Clear filters" })); + expect( + screen.queryByRole("button", { name: /Remove (Org|Service):/ }), + ).not.toBeInTheDocument(); + expect(screen.getByRole("region", { name: "OpenAI" })).toBeVisible(); + }); + + it("distinguishes disabled, inaccessible and missing credentials and reports changes without inventing usage", async () => { + records.push( + { + ...records[0]!, + id: "disabled", + label: "Disabled", + slug: "disabled", + is_active: false, + }, + { + ...records[1]!, + id: "denied", + label: "Denied", + slug: "denied", + credential_source: { + ...records[1]!.credential_source!, + type: "org", + org_id: "denied-org", + org_name: "Restricted", + allowed: false, + role: "viewer", + avatar_url: null, + }, + }, + { + ...records[0]!, + id: "missing", + label: "Missing", + slug: "missing", + credential_missing: true, + last_used_at: "2026-09-27", + source_app_name: "Provisioning app", + authorship: { + created_by: null, + last_change: { + actor: { + kind: "agent", + id: "agent-id", + name: "Build agent", + api_key_id: "agent-id", + app_id: null, + person_id: null, + }, + at: "2026-09-26T10:00:00Z", + action: "updated", + change_group_id: "change-id", + }, + }, + }, + ); + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + const table = screen.getByRole("table", { name: "OpenAI connections" }); + expect( + within(table).getByText("Disabled", { selector: "div" }), + ).toBeVisible(); + expect(within(table).getByText("No access")).toBeVisible(); + expect(within(table).getByText("Credential missing")).toBeVisible(); + expect(within(table).getByText(/Changed .*· Build agent/)).toBeVisible(); + expect( + within(table).queryByText(/Ready|Provisioning app/), + ).not.toBeInTheDocument(); + }); + + it("lets the user clear an empty filter result", async () => { + const user = userEvent.setup(); + render(preview()); + await user.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.type( + screen.getByRole("textbox", { name: "Search services and connections" }), + "no-matching-service{Enter}", + ); + expect(screen.getByText(/No services match these filters/)).toBeVisible(); + await user.click(screen.getByRole("button", { name: "Clear filters" })); + expect(screen.getByRole("region", { name: "OpenAI" })).toBeVisible(); + }); + + it("omits nonexistent platform sources and never invents a routing decision", async () => { + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.queryByText(/NyxID platform/)).not.toBeInTheDocument(); + expect(screen.getAllByText("Not verified")).toHaveLength(2); + expect(screen.queryByText(/Ready via|Would use/)).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Organization" })).toBeVisible(); + expect(screen.getByRole("button", { name: "Service" })).toBeVisible(); + }); + + it("shows a real platform connection inside its service without claiming health", async () => { + records.push({ + ...records[0]!, + id: "platform", + label: "Platform account", + auto_connected: true, + }); + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Auto-connected services: hidden" }), + ); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expect(screen.getByText("3 connections")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect(screen.getByText("Platform account")).toBeVisible(); + expect(screen.getAllByText("Not verified")).toHaveLength(3); + expect( + within( + screen.getByRole("table", { name: "OpenAI connections" }), + ).getByRole("img", { name: "NyxID platform" }), + ).toHaveAttribute("src", "/nyxid-coloured-icon.svg"); + expect(screen.queryByText(/Ready via/)).not.toBeInTheDocument(); + }); + + it("keeps custom services separate even when labels and addresses look alike", async () => { + records.push({ + ...records[0]!, + id: "custom", + catalog_service_id: null, + catalog_service_slug: null, + label: "OpenAI custom", + }); + const user = userEvent.setup(); + render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expect(screen.getByRole("region", { name: "OpenAI" })).toBeVisible(); + expect(screen.getByRole("region", { name: "OpenAI custom" })).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect( + screen.getByRole("button", { name: "Expand OpenAI custom connections" }), + ).toHaveAttribute("aria-expanded", "false"); + }); + + it("restores expansion after detail navigation and isolates it when accounts change", async () => { + const user = userEvent.setup(); + const mounted = render(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + await user.click( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ); + mounted.unmount(); + const next = render(preview()); + expect( + screen.getByRole("button", { name: "Service view: All services" }), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: "Collapse OpenAI connections" }), + ).toHaveAttribute("aria-expanded", "true"); + account.id = "user-b"; + next.rerender(preview()); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expect( + screen.getByRole("button", { name: "Expand OpenAI connections" }), + ).toHaveAttribute("aria-expanded", "false"); + }); +}); + +describe("saved routing in service cards", () => { + it("does not infer a pool from grouped connections", async () => { + render(preview()); + expect(screen.getByText("Direct connections · no pool")).toBeVisible(); + await userEvent.click( + screen.getByRole("button", { name: "Show routing for OpenAI" }), + ); + expect( + screen.getByText(/Create a pool to give compatible connections/), + ).toBeVisible(); + }); + it("opens actual priority failover and the full pool member table inside the card", async () => { + poolState.data = [ + { ...pool("Reliable"), strategy: "priority", failover: null }, + ]; + render(preview()); + expect(screen.getByText("2 connections · Priority")).toBeVisible(); + expect(screen.getByText("Reliable · Up to 3 attempts")).toBeVisible(); + expect( + screen.getByRole("button", { name: "Show routing for OpenAI" }), + ).toHaveAttribute("aria-expanded", "false"); + await userEvent.click( + screen.getByRole("button", { name: "Show routing for OpenAI" }), + ); + const card = screen.getByRole("region", { name: "OpenAI" }); + expect(within(card).getByText("/api/v1/proxy/s/reliable")).toBeVisible(); + expect(within(card).getByText("Failover · up to 3 attempts")).toBeVisible(); + expect( + within(card).getByRole("table", { name: "Reliable route members" }), + ).toBeVisible(); + expect(within(card).getByText("Personal account")).toBeVisible(); + expect(within(card).getByText("Team account")).toBeVisible(); + expect( + within(card).queryByRole("button", { name: "Configure pool" }), + ).not.toBeInTheDocument(); + expect( + within(card).getByRole("link", { name: "Manage in Service Pools" }), + ).toHaveAttribute("href", "/keys?tab=pools&view=routing&pool=Reliable"); + expect( + within(card).queryByRole("table", { name: "OpenAI connections" }), + ).not.toBeInTheDocument(); + await userEvent.click( + within(card).getByRole("button", { name: "Expand OpenAI connections" }), + ); + expect( + within(card).getByRole("table", { name: "OpenAI connections" }), + ).toBeVisible(); + }); + it("opens connection billing in one click when switching from the route table", async () => { + poolState.data = [{ ...pool("Reliable"), strategy: "priority" }]; + for (const connection of records) + insightConnections.set(connection.id, { + service_id: connection.id, + billing: configuredBilling(connection, { + slug: connection.catalog_service_slug ?? "custom", + billing: { platform_billable: true }, + }), + usage: null, + }); + render(preview()); + await userEvent.click( + screen.getByRole("button", { name: "Show routing for OpenAI" }), + ); + await userEvent.click( + screen.getByRole("button", { name: "Show billing for OpenAI" }), + ); + expect(screen.getByText("Billing", { selector: "h4" })).toBeVisible(); + expect( + screen.queryByRole("table", { name: "Reliable route members" }), + ).not.toBeInTheDocument(); + }); + it("does not describe weighted rotation as failover", async () => { + poolState.data = [{ ...pool("Rotate"), strategy: "weighted" }]; + render(preview()); + expect(screen.getByText("2 connections · Weighted")).toBeVisible(); + expect(screen.getByText("Rotate · Off · single attempt")).toBeVisible(); + await userEvent.click( + screen.getByRole("button", { name: "Show routing for OpenAI" }), + ); + expect(screen.getByText("Single attempt · no failover")).toBeVisible(); + }); + it("shows mixed failover at a glance and links the selected org pool to its owner", async () => { + poolState.data = [ + { ...pool("Personal"), strategy: "weighted" }, + { ...pool("Team"), user_id: "team-id", strategy: "priority" }, + ]; + render(preview()); + const summary = screen.getByRole("button", { + name: "Show routing for OpenAI", + }); + expect(within(summary).getByText("2 pools")).toBeVisible(); + expect( + within(summary).getByText("Failover · On in 1 of 2 pools"), + ).toBeVisible(); + await userEvent.click(summary); + await userEvent.click(screen.getByRole("button", { name: "Team" })); + expect( + screen.getByRole("link", { name: "Manage in Service Pools" }), + ).toHaveAttribute( + "href", + "/keys?tab=pools&view=routing&pool=Team&org=team-id", + ); + expect( + screen.queryByRole("button", { name: "Configure pool" }), + ).not.toBeInTheDocument(); + }); + it("reports pool read failures instead of claiming individual routes only", async () => { + poolState.error = new Error("Unavailable"); + render(preview()); + expect(screen.getByText("Pool access incomplete")).toBeVisible(); + }); +}); diff --git a/frontend/src/components/dashboard/service-routing-preview.tsx b/frontend/src/components/dashboard/service-routing-preview.tsx new file mode 100644 index 000000000..45f317ff1 --- /dev/null +++ b/frontend/src/components/dashboard/service-routing-preview.tsx @@ -0,0 +1,52 @@ +import { useState, type ReactNode } from "react"; +import { useKeys, useCatalog } from "@/hooks/use-keys"; +import { useUserServices } from "@/hooks/use-user-services"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { GroupedServiceCards } from "./grouped-service-cards"; +import type { KeyInfo } from "@/types/keys"; +import { classifyConnection } from "@/lib/service-routing-preview"; + +export default function ServiceRoutingPreview({ + renderConnectionActions, + actions, +}: { + readonly renderConnectionActions?: (key: KeyInfo) => ReactNode; + /** Page-level primary action (Connect Service) shown in the sticky toolbar. */ + readonly actions?: ReactNode | ((compact: boolean) => ReactNode); +}) { + const keys = useKeys(); + const catalog = useCatalog({ includeAll: true }); + const services = useUserServices(); + const [mountedAt] = useState(Date.now); + const candidates = (keys.data ?? []).map((key) => + classifyConnection( + key, + services.data ?? [], + keys.dataUpdatedAt || mountedAt, + ), + ); + + if (keys.isLoading) return ; + if (keys.error) + return ( +
+ Connections could not be loaded.{" "} + +
+ ); + + return ( + ({ + ...candidate.key, + credential_source: candidate.source, + }))} + catalog={catalog.data} + renderConnectionActions={renderConnectionActions} + actions={actions} + /> + ); +} diff --git a/frontend/src/components/dashboard/service-saved-views.tsx b/frontend/src/components/dashboard/service-saved-views.tsx new file mode 100644 index 000000000..3c10b58a7 --- /dev/null +++ b/frontend/src/components/dashboard/service-saved-views.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { Bookmark, Check, ChevronDown, Save, Star } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import type { useServiceView } from "@/hooks/use-service-view"; + +export function ServiceSavedViews({ + view, + onRestore, +}: { + readonly view: ReturnType; + readonly onRestore: () => void; +}) { + const [open, setOpen] = useState(false); + const source = { + personal: "Personal", + all: "All services", + org: "Organization", + platform: "NyxID platform", + }[view.savedFilters.source]; + const savedDescription = [ + source, + view.savedFilters.organization_ids.length + ? `${view.savedFilters.organization_ids.length} organizations` + : null, + view.savedFilters.service_group_ids.length + ? `${view.savedFilters.service_group_ids.length} services` + : null, + view.savedFilters.search ? `“${view.savedFilters.search}”` : null, + ] + .filter(Boolean) + .join(" · "); + + return ( +
+ + + + + +
+

Saved views

+

+ Your default opens when you return to AI Services. +

+
+ {view.hasDefault ? ( + + ) : ( +

+ No saved view yet. Set your filters, then save them as your + default. +

+ )} + {!view.canSave && ( +

+ Saving account defaults requires the updated server. +

+ )} + +
+
+ {view.isDefault ? ( + + + ) : ( + + )} +
+ ); +} diff --git a/frontend/src/components/dashboard/service-view-toolbar.tsx b/frontend/src/components/dashboard/service-view-toolbar.tsx new file mode 100644 index 000000000..d33e755d2 --- /dev/null +++ b/frontend/src/components/dashboard/service-view-toolbar.tsx @@ -0,0 +1,489 @@ +import { useRef, useState, type ReactNode, type Ref } from "react"; +import { + ArrowLeftRight, + Eye, + EyeOff, + Layers, + UserRound, + X, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + ServiceFilterMultiselect, + type ServiceFilterOption, +} from "./service-filter-multiselect"; +import { ServiceOwnerAvatar } from "./service-owner-avatar"; +import { ServiceSavedViews } from "./service-saved-views"; +import { DataTableSearch } from "@/components/data-table/data-table-controls"; +import { DEFAULT_SERVICE_FILTERS } from "@/schemas/service-view"; +import { cn } from "@/lib/utils"; +import type { ServiceConnectionGroup } from "@/lib/service-groups"; +import type { useServiceView } from "@/hooks/use-service-view"; +import type { KeyInfo } from "@/types/keys"; +const SOURCE_LABELS = { + personal: "Personal", + org: "Organization", + platform: "NyxID platform", +}; + +export function ServiceViewToolbar({ + view, + keys, + groups, + children, + actions, + ref, + stuck = false, +}: { + readonly view: ReturnType; + readonly keys: readonly KeyInfo[]; + readonly groups: readonly ServiceConnectionGroup[]; + readonly children?: ReactNode; + /** Primary actions (Add, refresh) kept in the filter row so they stay + * reachable while the toolbar is stuck. */ + readonly actions?: ReactNode | ((compact: boolean) => ReactNode); + readonly ref?: Ref; + readonly stuck?: boolean; +}) { + const { filters, setFilters } = view; + const inputRef = useRef(null); + const [draft, setDraft] = useState(null); + const organizations: ServiceFilterOption[] = [ + ...new Map( + keys.flatMap((key) => { + const source = key.credential_source; + return source?.type === "org" ? [[source.org_id, source] as const] : []; + }), + ).values(), + ] + .sort((a, b) => a.org_name.localeCompare(b.org_name)) + .map((source) => ({ + id: source.org_id, + label: source.org_name, + icon: ( + + ), + })); + const services: ServiceFilterOption[] = groups.map((group) => ({ + id: group.id, + label: group.name, + })); + const selections = [ + ...filters.organization_ids.map((id) => ({ + id, + field: "organization_ids" as const, + prefix: "Org", + label: + organizations.find((option) => option.id === id)?.label ?? + "Unavailable organization", + icon: organizations.find((option) => option.id === id)?.icon, + })), + ...filters.service_group_ids.map((id) => ({ + id, + field: "service_group_ids" as const, + prefix: "Service", + label: + services.find((option) => option.id === id)?.label ?? + "Unavailable service", + icon: undefined, + })), + ]; + const applied = [ + ...(filters.source === "org" || filters.source === "platform" + ? [ + { + key: "source" as const, + label: "Source", + value: SOURCE_LABELS[filters.source], + }, + ] + : []), + ...(filters.state !== "all" + ? [ + { + key: "state" as const, + label: "Service state", + value: filters.state === "enabled" ? "Enabled" : "Disabled", + }, + ] + : []), + ...(filters.service_type !== "all" + ? [ + { + key: "service_type" as const, + label: "Type", + value: filters.service_type.toUpperCase(), + }, + ] + : []), + ...(filters.show_auto_connected + ? [ + { + key: "show_auto_connected" as const, + label: "Auto-connected", + value: "Shown", + }, + ] + : []), + ]; + const editSearch = () => { + setDraft(filters.search); + inputRef.current?.focus(); + }; + const clear = () => { + setDraft(null); + setFilters({ + ...DEFAULT_SERVICE_FILTERS, + source: filters.source === "personal" ? "personal" : "all", + }); + }; + const sourceToggle = ( + + + + + + +

+ {filters.source === "personal" + ? "Switch to all services" + : "Switch to personal services"} +

+

+ {filters.source === "personal" + ? "Showing services with a personal connection, including their accessible organization and platform connections." + : "Showing all accessible services. Switch to keep only services with a personal connection and their counterparts."} +

+
+
+
+ ); + + const autoConnected = keys.filter((key) => key.auto_connected).length; + const autoToggle = autoConnected > 0 && ( + + + + + + +

+ {filters.show_auto_connected + ? "Hide auto-connected services" + : "Show auto-connected services"} +

+

+ {autoConnected} connection{autoConnected === 1 ? " was" : "s were"}{" "} + added automatically by NyxID.{" "} + {filters.show_auto_connected + ? "They are currently listed." + : "They are hidden by default."} +

+
+
+
+ ); + + return ( +
+
+ {!stuck && ( +
+ { + setDraft(null); + view.restoreDefault(); + }} + /> +
+ {autoToggle} + {sourceToggle} +
+
+ )} +
+
form]:min-w-48 [&>form]:w-auto" + : "flex-wrap", + )} + > +
+ + setFilters({ + ...filters, + organization_ids, + source: + organization_ids.length && filters.source === "personal" + ? "all" + : filters.source, + }) + } + /> + + setFilters({ ...filters, service_group_ids }) + } + /> +
+ undefined} + onValueChange={setDraft} + onApply={() => { + setFilters({ + ...filters, + search: (draft ?? filters.search).trim(), + }); + setDraft(null); + }} + onCancel={() => setDraft("")} + /> + {actions && ( +
+ {typeof actions === "function" ? actions(stuck) : actions} +
+ )} + {stuck && autoToggle} + {stuck && sourceToggle} +
+ {Boolean(selections.length || filters.search || applied.length) && ( +
+ {selections.map((selection) => ( +
+ {selection.icon} + + + {selection.prefix}: + {" "} + {selection.label} + + +
+ ))} + {filters.search && ( +
+ + +
+ )} + {applied.map(({ key, label, value }) => ( +
+ + {label}:{" "} + {value} + + +
+ ))} + +
+ )} +
+ + {!stuck && ( +
+ {children} +
+ )} + + {!stuck && view.saveError && ( +

+ {view.saveError} +

+ )} +
+
+ ); +} diff --git a/frontend/src/components/data-table/data-table-controls.tsx b/frontend/src/components/data-table/data-table-controls.tsx index 14867f4f2..2c772e0eb 100644 --- a/frontend/src/components/data-table/data-table-controls.tsx +++ b/frontend/src/components/data-table/data-table-controls.tsx @@ -154,31 +154,33 @@ export function DataTableSearch({ >
); @@ -454,94 +456,96 @@ function DataTableFilterEditor({ /> )} {!textOnly && ( -
- {multiple && ( - - )} - {field.options.map((option) => { - const checked = values.includes(option.value); - return ( +
+ {multiple && ( - ); - })} -
+ )} + {field.options.map((option) => { + const checked = values.includes(option.value); + return ( + + ); + })} +
)} 0} @@ -929,6 +933,7 @@ export interface DataTableFilterChipsProps< readonly filters: readonly AppliedDataTableFilter[]; readonly allFieldsLabel?: string; readonly ariaLabel?: string; + readonly className?: string; readonly onEditSearch: () => void; readonly onRemoveSearch: () => void; readonly onEditSearchValue: (field: SearchKey, value: string) => void; @@ -949,6 +954,7 @@ export function DataTableFilterChips< filters, allFieldsLabel = "All fields", ariaLabel = "Applied filters", + className, onEditSearch, onRemoveSearch, onEditSearchValue, @@ -967,7 +973,10 @@ export function DataTableFilterChips< ); return ( -
+
{search && (
{ + ({ + field, + values, + valueLabels, + operatorLabel, + valueSummary, + custom, + }) => { const resolvedOperatorLabel = operatorLabel ?? (values.length > 1 @@ -1074,7 +1090,10 @@ export function DataTableFilterChips< const fullValueSummary = valueSummary ?? valueLabels.join(", "); // A filter's options and its custom text get their own chips, so // clearing one leaves the other applied. - const chipLabel = custom === true ? `${field.label} custom text` : `${field.label} filter`; + const chipLabel = + custom === true + ? `${field.label} custom text` + : `${field.label} filter`; return (
-
+
+
form]:min-w-48 [&>form]:w-auto" + : "flex-wrap", + )} + > {search}
{filter}
{status} diff --git a/frontend/src/components/layout/dashboard-layout.tsx b/frontend/src/components/layout/dashboard-layout.tsx index ce512e1c7..de857bae5 100644 --- a/frontend/src/components/layout/dashboard-layout.tsx +++ b/frontend/src/components/layout/dashboard-layout.tsx @@ -99,7 +99,7 @@ export function DashboardLayout() { // Shared channel onboarding must stay reachable through setup and bot routing. const isChannelBotRoute = pathname === "/channel-bots" || pathname.startsWith("/channel-bots/"); if (onboarding.status === "loading") return null; - if (onboarding.status === "show" && !isChannelBotRoute) return ; + if (onboarding.status === "show" && !isChannelBotRoute && !(import.meta.env.DEV && import.meta.env.VITE_ROUTING_PREVIEW === "1")) return ; return ( diff --git a/frontend/src/components/shared/add-cta-button.tsx b/frontend/src/components/shared/add-cta-button.tsx index ed6231638..776f16ff4 100644 --- a/frontend/src/components/shared/add-cta-button.tsx +++ b/frontend/src/components/shared/add-cta-button.tsx @@ -1,10 +1,18 @@ import { Plus } from "lucide-react"; import { Button, ButtonIcon } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; interface AddCtaButtonProps { readonly label: string; readonly onClick: () => void; readonly disabled?: boolean; + readonly compact?: boolean; + readonly compactLabel?: string; readonly icon?: React.ComponentType<{ className?: string }>; /** * "primary" (default) → the goal-completing CTA on this page. Renders @@ -24,23 +32,47 @@ export function AddCtaButton({ label, onClick, disabled = false, + compact = false, + compactLabel, icon: Icon = Plus, variant = "primary", }: AddCtaButtonProps) { if (variant === "primary") { - return ( + const button = ( ); + return compact ? ( + + + {button} + + {label} + + + + ) : ( + button + ); } return ( diff --git a/frontend/src/hooks/use-agent-bindings.ts b/frontend/src/hooks/use-agent-bindings.ts index cc9c457ef..7b8c22f1c 100644 --- a/frontend/src/hooks/use-agent-bindings.ts +++ b/frontend/src/hooks/use-agent-bindings.ts @@ -38,6 +38,7 @@ export function useCreateBinding() { ); }, onSuccess: (_data, variables) => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); void queryClient.invalidateQueries({ queryKey: ["agent-bindings", variables.keyId], }); @@ -63,6 +64,7 @@ export function useDeleteBinding() { return api.delete(`/api-keys/${keyId}/bindings/${bindingId}`); }, onSuccess: (_data, variables) => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); void queryClient.invalidateQueries({ queryKey: ["agent-bindings", variables.keyId], }); diff --git a/frontend/src/hooks/use-api-keys.ts b/frontend/src/hooks/use-api-keys.ts index 0bf80c4dc..21837a873 100644 --- a/frontend/src/hooks/use-api-keys.ts +++ b/frontend/src/hooks/use-api-keys.ts @@ -239,6 +239,7 @@ export function useCreateApiKey() { return api.post("/api-keys", payload); }, onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); void queryClient.invalidateQueries({ predicate: (q) => Array.isArray(q.queryKey) && q.queryKey[0] === "api-keys", @@ -272,6 +273,7 @@ export function useUpdateApiKey() { return api.put(`/api-keys/${keyId}`, body); }, onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); void queryClient.invalidateQueries({ predicate: (q) => Array.isArray(q.queryKey) && q.queryKey[0] === "api-keys", @@ -288,6 +290,7 @@ export function useDeleteApiKey() { return api.delete(`/api-keys/${id}`); }, onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); // Invalidate both the personal scope and every org-scope cache. // `predicate` catches keys like `["api-keys", "org", ]` that // `useAllAdminedApiKeys` populates lazily. @@ -307,6 +310,7 @@ export function useRotateApiKey() { return api.post(`/api-keys/${id}/rotate`); }, onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["keys", "insights"] }); void queryClient.invalidateQueries({ predicate: (q) => Array.isArray(q.queryKey) && q.queryKey[0] === "api-keys", diff --git a/frontend/src/hooks/use-billing.ts b/frontend/src/hooks/use-billing.ts index f99e53dec..9e0386b82 100644 --- a/frontend/src/hooks/use-billing.ts +++ b/frontend/src/hooks/use-billing.ts @@ -18,11 +18,15 @@ import { const BILLING_WALLET_KEY = ["billing", "wallet"] as const; const BILLING_USAGE_KEY = ["billing", "usage"] as const; -export function billingUsagePath(period?: BillingUsagePeriod): string { - if (!period) { - return "/billing/usage"; - } - return `/billing/usage?period=${encodeURIComponent(period)}`; +export function billingUsagePath( + period?: BillingUsagePeriod, + bucket?: "day", +): string { + const params = new URLSearchParams(); + if (period) params.set("period", period); + if (bucket) params.set("bucket", bucket); + const query = params.toString(); + return query ? `/billing/usage?${query}` : "/billing/usage"; } export function useBillingWallet() { @@ -68,11 +72,13 @@ export function useTopUpBilling() { }); } -export function useBillingUsage(period?: BillingUsagePeriod) { +export function useBillingUsage(period?: BillingUsagePeriod, bucket?: "day") { return useQuery({ - queryKey: period ? [...BILLING_USAGE_KEY, period] : BILLING_USAGE_KEY, + queryKey: period + ? [...BILLING_USAGE_KEY, period, ...(bucket ? [bucket] : [])] + : BILLING_USAGE_KEY, queryFn: async (): Promise => { - const response = await api.get(billingUsagePath(period)); + const response = await api.get(billingUsagePath(period, bucket)); return billingUsageResponseSchema.parse(response); }, }); diff --git a/frontend/src/hooks/use-service-card-transition.test.ts b/frontend/src/hooks/use-service-card-transition.test.ts new file mode 100644 index 000000000..5e14293b6 --- /dev/null +++ b/frontend/src/hooks/use-service-card-transition.test.ts @@ -0,0 +1,137 @@ +import { act, cleanup, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { useServiceCardTransition } from "./use-service-card-transition"; + +const originalTransition = Object.getOwnPropertyDescriptor( + document, + "startViewTransition", +); +let card: HTMLElement; +let scroller: HTMLElement | undefined; +let scroll = vi.fn(); + +function installTransition() { + let finish!: () => void; + const finished = new Promise((resolve) => { + finish = resolve; + }); + const transition = { finished, skipTransition: vi.fn() }; + const start = vi.fn((update: () => void) => { + update(); + return transition; + }); + Object.defineProperty(document, "startViewTransition", { + configurable: true, + value: start, + }); + return { finish, transition, start }; +} + +beforeEach(() => { + card = document.createElement("section"); + scroll = vi.fn(); + card.scrollIntoView = scroll; + document.body.append(card); +}); +afterEach(() => { + cleanup(); + card.remove(); + scroller?.remove(); + scroller = undefined; + vi.restoreAllMocks(); + if (originalTransition) + Object.defineProperty(document, "startViewTransition", originalTransition); + else Reflect.deleteProperty(document, "startViewTransition"); +}); + +describe("service card scrolling", () => { + it("leaves space below the live toolbar height and the padded scroll viewport", async () => { + const { finish } = installTransition(); + scroller = document.createElement("main"); + scroller.style.paddingTop = "16px"; + scroller.scrollTop = 40; + Object.defineProperty(scroller, "clientTop", { value: 2 }); + scroller.getBoundingClientRect = () => new DOMRect(0, 100, 800, 900); + scroller.scrollTo = vi.fn(); + const toolbar = document.createElement("div"); + toolbar.style.top = "0px"; + toolbar.getBoundingClientRect = () => new DOMRect(0, 116, 800, 200); + card.getBoundingClientRect = () => new DOMRect(0, 650, 800, 300); + document.body.append(scroller); + scroller.append(toolbar, card); + const { result } = renderHook(useServiceCardTransition); + act(() => result.current(() => {}, card, toolbar)); + expect(scroller.scrollTo).not.toHaveBeenCalled(); + toolbar.getBoundingClientRect = () => new DOMRect(0, 116, 800, 280); + const surface = document.createElement("div"); + surface.getBoundingClientRect = () => new DOMRect(0, 116, 800, 100); + toolbar.append(surface); + await act(async () => { + finish(); + }); + expect(scroller.scrollTo).toHaveBeenCalledWith({ + top: 440, + behavior: "smooth", + }); + expect(scroll).not.toHaveBeenCalled(); + }); + + it("waits until the card transition finishes before scrolling", async () => { + const { finish } = installTransition(); + const { result } = renderHook(useServiceCardTransition); + const update = vi.fn(); + act(() => result.current(update, card)); + expect(update).toHaveBeenCalledOnce(); + expect(scroll).not.toHaveBeenCalled(); + await act(async () => { + finish(); + }); + expect(scroll).toHaveBeenCalledWith({ + behavior: "smooth", + block: "start", + inline: "nearest", + }); + }); + + it("cancels a pending scroll when the card is collapsed during expansion", async () => { + const first = installTransition(); + const { result } = renderHook(useServiceCardTransition); + act(() => result.current(() => {}, card)); + const second = installTransition(); + act(() => result.current(() => {})); + expect(first.transition.skipTransition).toHaveBeenCalledOnce(); + await act(async () => { + first.finish(); + second.finish(); + }); + expect(scroll).not.toHaveBeenCalled(); + }); + + it("scrolls immediately without animation when reduced motion is preferred", () => { + const { start } = installTransition(); + vi.spyOn(window, "matchMedia").mockReturnValue({ + matches: true, + } as MediaQueryList); + const { result } = renderHook(useServiceCardTransition); + act(() => result.current(() => {}, card)); + expect(start).not.toHaveBeenCalled(); + expect(scroll).toHaveBeenCalledWith({ + behavior: "instant", + block: "start", + inline: "nearest", + }); + }); + + it("does not scroll after leaving the service view", async () => { + const { finish, transition } = installTransition(); + const { result, unmount } = renderHook(useServiceCardTransition); + act(() => result.current(() => {}, card)); + unmount(); + await act(async () => { + finish(); + }); + expect(transition.skipTransition).toHaveBeenCalledOnce(); + expect(scroll).not.toHaveBeenCalled(); + expect(document.documentElement).not.toHaveClass("service-card-transition"); + }); +}); diff --git a/frontend/src/hooks/use-service-card-transition.ts b/frontend/src/hooks/use-service-card-transition.ts new file mode 100644 index 000000000..457105978 --- /dev/null +++ b/frontend/src/hooks/use-service-card-transition.ts @@ -0,0 +1,83 @@ +import { useCallback, useEffect, useRef } from "react"; +import { flushSync } from "react-dom"; + +export function useServiceCardTransition() { + const active = useRef(null); + useEffect( + () => () => { + active.current?.skipTransition(); + active.current = null; + document.documentElement.classList.remove("service-card-transition"); + }, + [], + ); + + return useCallback( + ( + change: () => void, + expandedCard?: HTMLElement | null, + stickyToolbar?: HTMLElement | null, + ) => { + active.current?.skipTransition(); + active.current = null; + const reducedMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + const revealCard = () => { + if (expandedCard?.isConnected) { + const scroller = expandedCard.closest("main"); + if (scroller && stickyToolbar?.isConnected) { + const inset = + Number.parseFloat(getComputedStyle(stickyToolbar).top) || 0; + const padding = + Number.parseFloat(getComputedStyle(scroller).paddingTop) || 0; + scroller.scrollTo({ + top: Math.max( + 0, + scroller.scrollTop + + expandedCard.getBoundingClientRect().top - + scroller.getBoundingClientRect().top - + scroller.clientTop - + padding - + ( + stickyToolbar.firstElementChild ?? stickyToolbar + ).getBoundingClientRect().height - + inset - + 32, + ), + behavior: reducedMotion ? "instant" : "smooth", + }); + return; + } + expandedCard.scrollIntoView({ + behavior: reducedMotion ? "instant" : "smooth", + block: "start", + inline: "nearest", + }); + } + }; + if (!document.startViewTransition || reducedMotion) { + document.documentElement.classList.remove("service-card-transition"); + flushSync(change); + revealCard(); + return; + } + document.documentElement.classList.add("service-card-transition"); + const transition = document.startViewTransition(() => flushSync(change)); + active.current = transition; + // A superseded transition must not remove the next transition's styles. + void transition.finished + .catch(() => undefined) + .then(() => { + if (active.current === transition) { + active.current = null; + document.documentElement.classList.remove( + "service-card-transition", + ); + revealCard(); + } + }); + }, + [], + ); +} diff --git a/frontend/src/hooks/use-service-insights.ts b/frontend/src/hooks/use-service-insights.ts new file mode 100644 index 000000000..fc2b9abf5 --- /dev/null +++ b/frontend/src/hooks/use-service-insights.ts @@ -0,0 +1,124 @@ +import { useQuery } from "@tanstack/react-query"; +import { api, ApiError } from "@/lib/api-client"; +import { useAuthStore } from "@/stores/auth-store"; +import { + serviceInsightsResponseSchema, + type ServiceInsight, +} from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; +import { loadConfiguredServiceInsights } from "@/lib/service-insights-compat"; + +export interface ServiceInsightsState { + readonly connections: ReadonlyMap; + readonly status: "loading" | "ready" | "unavailable" | "restricted" | "error"; + readonly refresh: () => void; +} + +export function useServiceInsights( + connections: readonly KeyInfo[], + supplied?: ServiceInsightsState, + apiKeyId?: string, +): ServiceInsightsState { + const identity = useAuthStore((state) => state.user?.id); + const ids = [ + ...new Set(connections.map((connection) => connection.id)), + ].sort(); + // Compatibility billing and key access depend on the selected credential and + // owner, which can change while the connection id stays the same. + const inputs = connections + .map((connection) => ({ + id: connection.id, + api_key_id: connection.api_key_id, + credential_binding: connection.credential_binding, + credential_source: connection.credential_source, + credential_type: connection.credential_type, + credential_missing: connection.credential_missing, + oauth_app_source: connection.oauth_app_source, + has_own_oauth_app: Boolean(connection.oauth_client_id?.trim()), + connection_id: connection.connection_id, + // Healthy OAuth rows prove their app, so reconnecting changes the label. + status: connection.status, + connection_status: connection.connection_status, + auth_method: connection.auth_method, + catalog_service_id: connection.catalog_service_id, + catalog_service_slug: connection.catalog_service_slug, + node_id: connection.node_id, + has_node_binding: connection.has_node_binding, + is_active: connection.is_active, + auto_connected: connection.auto_connected, + byok_pricing: connection.byok_pricing, + platform_key_pricing: connection.platform_key_pricing, + })) + .sort((a, b) => a.id.localeCompare(b.id)); + const query = useQuery({ + queryKey: ["keys", "insights", identity, inputs, apiKeyId ?? null], + enabled: !supplied && !!identity && ids.length > 0, + queryFn: async () => { + const batches = Array.from( + { length: Math.ceil(ids.length / 100) }, + (_, i) => ids.slice(i * 100, (i + 1) * 100), + ); + try { + const results = await Promise.allSettled( + batches.map(async (batch) => { + const response = await api.get( + `/service-insights?ids=${encodeURIComponent(batch.join(","))}${apiKeyId ? `&api_key_id=${encodeURIComponent(apiKeyId)}` : ""}`, + ); + return serviceInsightsResponseSchema.parse(response).connections; + }), + ); + const failures = results.flatMap((result) => + result.status === "rejected" ? [result.reason as unknown] : [], + ); + const blockingFailure = failures.find( + (error) => + !( + error instanceof ApiError && + [404, 405, 501].includes(error.status) + ), + ); + if (failures.length) throw blockingFailure ?? failures[0]; + return results.flatMap((result) => + result.status === "fulfilled" ? result.value : [], + ); + } catch (error) { + if ( + !apiKeyId && + error instanceof ApiError && + [404, 405, 501].includes(error.status) + ) { + return loadConfiguredServiceInsights(connections, identity!); + } + throw error; + } + }, + retry: false, + staleTime: 30_000, + refetchOnWindowFocus: true, + }); + if (supplied) return supplied; + const unavailable = + query.error instanceof ApiError && + [404, 405, 501].includes(query.error.status); + return { + // Never retain privileged summaries after an authorization/network failure. + connections: new Map( + query.isError + ? [] + : (query.data ?? []).map((item) => [item.service_id, item]), + ), + status: + query.error instanceof ApiError && query.error.status === 403 + ? "restricted" + : query.isError + ? unavailable + ? "unavailable" + : "error" + : query.isPending && ids.length + ? "loading" + : "ready", + refresh: () => { + void query.refetch(); + }, + }; +} diff --git a/frontend/src/hooks/use-service-routing-pools.test.tsx b/frontend/src/hooks/use-service-routing-pools.test.tsx new file mode 100644 index 000000000..41dc6c028 --- /dev/null +++ b/frontend/src/hooks/use-service-routing-pools.test.tsx @@ -0,0 +1,80 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, renderHook, waitFor } from "@testing-library/react"; +import type { PropsWithChildren } from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { useServiceRoutingPools } from "./use-service-routing-pools"; +import type { KeyInfo } from "@/types/keys"; +const mock = vi.hoisted(() => ({ get: vi.fn() })); +vi.mock("@/lib/api-client", () => ({ api: mock })); +vi.mock("@/stores/auth-store", () => ({ + useAuthStore: (selector: (state: { user: { id: string } }) => unknown) => + selector({ user: { id: "me" } }), +})); +const pool = { + id: "pool", + user_id: "me", + name: "Route", + slug: "route", + strategy: "priority", + members: [], + rr_counter: 0, + is_active: true, + created_at: "2026-01-01", + updated_at: "2026-01-01", +}; +const keys = [ + { id: "personal", credential_source: { type: "personal" } }, + { + id: "admin", + credential_source: { + type: "org", + org_id: "team", + role: "admin", + allowed: true, + }, + }, + { + id: "viewer", + credential_source: { + type: "org", + org_id: "read-only", + role: "viewer", + allowed: false, + }, + }, +] as KeyInfo[]; +function setup() { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, gcTime: 0 } }, + }); + const wrapper = ({ children }: PropsWithChildren) => ( + {children} + ); + return { client, wrapper }; +} +beforeEach(() => { + mock.get.mockReset(); + mock.get.mockResolvedValue({ pools: [pool] }); +}); +describe("service card pool inventory", () => { + it("reads personal and manageable org pools without requesting restricted org inventory", async () => { + const { result } = renderHook(() => useServiceRoutingPools(keys), setup()); + await waitFor(() => expect(result.current.loading).toBe(false)); + expect(mock.get.mock.calls.map(([path]) => path).sort()).toEqual([ + "/service-pools", + "/service-pools?org_id=team", + ]); + expect(result.current.incomplete).toBe(true); + }); + it("clears stale routing after a pool read loses access", async () => { + const { client, wrapper } = setup(); + const { result } = renderHook(() => useServiceRoutingPools([keys[0]!]), { + wrapper, + }); + await waitFor(() => expect(result.current.pools).toHaveLength(1)); + mock.get.mockRejectedValue(new Error("Access removed")); + await act(() => client.invalidateQueries({ queryKey: ["service-pools"] })); + await waitFor(() => expect(result.current.incomplete).toBe(true)); + expect(result.current.pools).toEqual([]); + }); +}); diff --git a/frontend/src/hooks/use-service-routing-pools.ts b/frontend/src/hooks/use-service-routing-pools.ts new file mode 100644 index 000000000..08ae6bf26 --- /dev/null +++ b/frontend/src/hooks/use-service-routing-pools.ts @@ -0,0 +1,64 @@ +import { useQueries } from "@tanstack/react-query"; +import { api } from "@/lib/api-client"; +import { useAuthStore } from "@/stores/auth-store"; +import { + servicePoolListResponseSchema, + type ServicePool, +} from "@/schemas/pools"; +import type { KeyInfo } from "@/types/keys"; + +export interface ServiceRoutingPools { + pools: readonly ServicePool[]; + loading: boolean; + incomplete: boolean; +} + +export function useServiceRoutingPools( + keys: readonly KeyInfo[], + enabled = true, +): ServiceRoutingPools { + const identity = useAuthStore((state) => state.user?.id); + // Pool management reads currently require organization admin access. + const orgIds = [ + ...new Set( + keys.flatMap((key) => { + const source = key.credential_source; + return source?.type === "org" && + source.allowed && + source.role === "admin" + ? [source.org_id] + : []; + }), + ), + ].sort(); + const queries = useQueries({ + queries: [undefined, ...orgIds].map((orgId) => ({ + queryKey: ["service-pools", "routing", identity, orgId], + enabled: enabled && !!identity && keys.length > 0, + queryFn: async () => + servicePoolListResponseSchema.parse( + await api.get( + orgId + ? `/service-pools?org_id=${encodeURIComponent(orgId)}` + : "/service-pools", + ), + ).pools, + retry: false, + staleTime: 30_000, + })), + }); + return { + pools: queries.flatMap((query) => + query.isError ? [] : (query.data ?? []), + ), + loading: queries.some((query) => query.isLoading), + incomplete: + queries.some((query) => query.isError) || + keys.some( + (key) => + key.credential_source?.type === "org" && + (key.credential_source.role !== "admin" || + !key.credential_source.allowed), + ), + }; +} diff --git a/frontend/src/hooks/use-service-view.test.tsx b/frontend/src/hooks/use-service-view.test.tsx new file mode 100644 index 000000000..759ccf35d --- /dev/null +++ b/frontend/src/hooks/use-service-view.test.tsx @@ -0,0 +1,208 @@ +import { act, cleanup, renderHook, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { ReactNode } from "react"; +import type { User } from "@/types/api"; +import { + DEFAULT_SERVICE_FILTERS, + type ServiceViewFilters, +} from "@/schemas/service-view"; + +const { put } = vi.hoisted(() => ({ put: vi.fn() })); +vi.mock("@/lib/api-client", () => ({ api: { put } })); +vi.mock("@/lib/telemetry", () => ({ identify: vi.fn(), reset: vi.fn() })); + +import { useAuthStore } from "@/stores/auth-store"; +import { useServiceCardView } from "@/stores/service-card-view-store"; +import { useServiceView } from "./use-service-view"; + +function user(id = "user-a", saved: ServiceViewFilters | null = null): User { + return { + id, + email: `${id}@example.com`, + display_name: null, + avatar_url: null, + email_verified: true, + mfa_enabled: false, + is_admin: false, + is_active: true, + created_at: "2026-01-01", + profile_config: { + onboarding: { ai_services_completed_at: "2026-01-01" }, + services_view: saved, + }, + }; +} +function mount() { + const client = new QueryClient({ + defaultOptions: { mutations: { retry: false }, queries: { retry: false } }, + }); + return { + client, + ...renderHook(useServiceView, { + wrapper: ({ children }: { children: ReactNode }) => ( + {children} + ), + }), + }; +} +const saved: ServiceViewFilters = { + ...DEFAULT_SERVICE_FILTERS, + source: "org", + search: "team", +}; + +beforeEach(() => { + vi.resetAllMocks(); + useAuthStore.setState({ user: user(), isAuthenticated: true }); + useServiceCardView.setState({ + accountId: undefined, + expanded: [], + filters: undefined, + }); +}); +afterEach(cleanup); + +describe("account service view preferences", () => { + it("starts personal, honors saved All services, and restores only the newest expanded card", () => { + const { result } = mount(); + expect(result.current.filters.source).toBe("personal"); + act(() => + useAuthStore.setState({ + user: user("user-a", { ...DEFAULT_SERVICE_FILTERS, source: "all" }), + }), + ); + expect(result.current.filters.source).toBe("all"); + act(() => result.current.setExpanded(["first", "latest"])); + expect(result.current.expanded).toEqual(["latest"]); + }); + + it("loads defaults, saves only on request, and restores them on a fresh visit", async () => { + useAuthStore.setState({ user: user("user-a", saved) }); + put.mockImplementation(async (_path, body) => body); + const mounted = mount(); + expect(mounted.result.current.filters).toEqual(saved); + expect(mounted.result.current.expanded).toEqual([]); + const next = { + ...saved, + state: "disabled" as const, + organization_ids: ["org-1", "org-2"], + service_group_ids: ["catalog:openai", "catalog:codex"], + }; + act(() => mounted.result.current.setFilters(next)); + expect(put).not.toHaveBeenCalled(); + act(() => mounted.result.current.saveDefault()); + await waitFor(() => expect(mounted.result.current.isDefault).toBe(true)); + expect(put).toHaveBeenCalledWith("/users/me/preferences/services", next); + expect( + mounted.client.getQueryData(["user", "me"])?.profile_config + ?.services_view, + ).toEqual(next); + expect( + useAuthStore.getState().user?.profile_config?.onboarding + .ai_services_completed_at, + ).toBe("2026-01-01"); + mounted.unmount(); + useServiceCardView.setState({ + accountId: undefined, + filters: undefined, + expanded: [], + }); + const fresh = mount(); + expect(fresh.result.current.filters).toEqual(next); + act(() => fresh.result.current.setFilters(DEFAULT_SERVICE_FILTERS)); + expect(fresh.result.current.differsFromDefault).toBe(true); + act(() => fresh.result.current.restoreDefault()); + expect(fresh.result.current.filters).toEqual(next); + }); + + it("keeps the previous default when saving fails and allows a retry", async () => { + useAuthStore.setState({ user: user("user-a", saved) }); + put + .mockRejectedValueOnce(new Error("offline")) + .mockImplementation(async (_path, body) => body); + const { result } = mount(); + act(() => result.current.setFilters(DEFAULT_SERVICE_FILTERS)); + act(() => result.current.saveDefault()); + await waitFor(() => + expect(result.current.saveError).toMatch(/Could not save/), + ); + expect(result.current.isDefault).toBe(false); + expect(useAuthStore.getState().user?.profile_config?.services_view).toEqual( + saved, + ); + act(() => result.current.saveDefault()); + await waitFor(() => expect(result.current.isDefault).toBe(true)); + expect(result.current.saveError).toBeNull(); + }); + + it("does not overwrite draft filters when profile data arrives late", () => { + const oldUser = user(); + useAuthStore.setState({ user: { ...oldUser, profile_config: undefined } }); + const { result } = mount(); + expect(result.current.canSave).toBe(false); + act(() => + result.current.setFilters({ + ...DEFAULT_SERVICE_FILTERS, + search: "my draft", + }), + ); + act(() => useAuthStore.setState({ user: user("user-a", saved) })); + expect(result.current.filters.search).toBe("my draft"); + expect(result.current.canSave).toBe(true); + }); + + it("ignores an old account's save response after switching accounts", async () => { + let resolveSave!: (value: ServiceViewFilters) => void; + put.mockImplementation( + () => + new Promise((resolve) => { + resolveSave = resolve; + }), + ); + const { result, client } = mount(); + act(() => result.current.setFilters(saved)); + act(() => result.current.setExpanded(["catalog:openai"])); + act(() => result.current.saveDefault()); + await waitFor(() => expect(put).toHaveBeenCalledOnce()); + act(() => useAuthStore.getState().setUser(user("user-b"))); + expect(result.current.filters).toEqual(DEFAULT_SERVICE_FILTERS); + expect(result.current.expanded).toEqual([]); + await act(async () => resolveSave(saved)); + expect(useAuthStore.getState().user?.id).toBe("user-b"); + expect( + useAuthStore.getState().user?.profile_config?.services_view, + ).toBeNull(); + expect(client.getQueryData(["user", "me"])).toBeUndefined(); + expect(result.current.saveError).toBeNull(); + }); + + it("keeps edits made while saving and clears drafts on sign-out", async () => { + let resolveSave!: (value: ServiceViewFilters) => void; + put.mockImplementation( + () => + new Promise((resolve) => { + resolveSave = resolve; + }), + ); + const { result } = mount(); + act(() => result.current.setFilters(saved)); + act(() => result.current.saveDefault()); + await waitFor(() => expect(put).toHaveBeenCalledOnce()); + act(() => result.current.setFilters({ ...saved, search: "new search" })); + await act(async () => resolveSave(saved)); + expect(result.current.filters.search).toBe("new search"); + expect(result.current.isDefault).toBe(false); + act(() => useAuthStore.getState().setUser(null)); + act(() => useAuthStore.getState().setUser(user("user-a", saved))); + expect(result.current.filters).toEqual(saved); + }); + + it("does not attempt account saves against older servers", () => { + useAuthStore.setState({ user: { ...user(), profile_config: undefined } }); + const { result } = mount(); + expect(result.current.canSave).toBe(false); + act(() => result.current.saveDefault()); + expect(put).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/hooks/use-service-view.ts b/frontend/src/hooks/use-service-view.ts new file mode 100644 index 000000000..0ede7ceff --- /dev/null +++ b/frontend/src/hooks/use-service-view.ts @@ -0,0 +1,98 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { api } from "@/lib/api-client"; +import { useAuthStore } from "@/stores/auth-store"; +import { useServiceCardView } from "@/stores/service-card-view-store"; +import { + DEFAULT_SERVICE_FILTERS, + sameServiceFilters, + serviceViewSchema, + type ServiceViewFilters, +} from "@/schemas/service-view"; +import type { User } from "@/types/api"; + +export function useServiceView() { + const user = useAuthStore((state) => state.user); + const queryClient = useQueryClient(); + const view = useServiceCardView(); + const parsed = serviceViewSchema.safeParse( + user?.profile_config?.services_view, + ); + const saved = parsed.success ? parsed.data : DEFAULT_SERVICE_FILTERS; + const sameAccount = view.accountId === user?.id; + const draftFilters = serviceViewSchema.safeParse( + sameAccount ? view.filters : undefined, + ); + const filters = draftFilters.success ? draftFilters.data : saved; + const expanded = sameAccount ? view.expanded.slice(-1) : []; + const canSave = user?.profile_config?.services_view !== undefined; + const hasDefault = parsed.success; + + const mutation = useMutation({ + mutationFn: async ({ + filters, + accountId, + }: { + filters: ServiceViewFilters; + accountId: string; + }) => { + await queryClient.cancelQueries({ queryKey: ["user", "me"] }); + if (useAuthStore.getState().user?.id !== accountId) + throw new Error("Account changed. Please try again."); + return serviceViewSchema.parse( + await api.put( + "/users/me/preferences/services", + serviceViewSchema.parse(filters), + ), + ); + }, + onSuccess: (savedFilters, { accountId }) => { + const current = useAuthStore.getState().user; + if (current?.id !== accountId || !current.profile_config) return; + const updated: User = { + ...current, + profile_config: { + ...current.profile_config, + services_view: savedFilters, + }, + }; + useAuthStore.getState().setUser(updated); + queryClient.setQueryData(["user", "me"], updated); + }, + }); + + const setView = (update: { + filters?: ServiceViewFilters; + expanded?: readonly string[]; + }) => { + useServiceCardView.setState({ + accountId: user?.id, + filters, + expanded, + ...update, + }); + }; + const mutationForAccount = mutation.variables?.accountId === user?.id; + + return { + accountId: user?.id, + filters, + expanded, + canSave, + hasDefault, + savedFilters: saved, + isDefault: hasDefault && sameServiceFilters(filters, saved), + differsFromDefault: !sameServiceFilters(filters, saved), + setFilters: (filters: ServiceViewFilters) => setView({ filters }), + setExpanded: (expanded: readonly string[]) => + setView({ expanded: expanded.slice(-1) }), + restoreDefault: () => setView({ filters: saved }), + saveDefault: () => { + if (user && canSave) mutation.mutate({ accountId: user.id, filters }); + }, + isSaving: mutationForAccount && mutation.isPending, + saveError: + mutationForAccount && mutation.error + ? "Could not save your default view. Please try again." + : null, + }; +} diff --git a/frontend/src/lib/billing-plain.test.ts b/frontend/src/lib/billing-plain.test.ts new file mode 100644 index 000000000..a4c68bb89 --- /dev/null +++ b/frontend/src/lib/billing-plain.test.ts @@ -0,0 +1,173 @@ +import { describe, expect, it } from "vitest"; +import type { ServiceBillingExplanation } from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; +import { plainBilling } from "./billing-plain"; + +const twitter = { + id: "t", + label: "Twitter / X API", + slug: "api-twitter", + catalog_service_name: "Twitter / X API", + credential_type: "oauth2", + credential_source: { type: "personal" }, +} as KeyInfo; +const deepseek = { + id: "d", + label: "DeepSeek API", + slug: "llm-deepseek", + catalog_service_name: "DeepSeek API", + credential_type: "api_key", + credential_source: { type: "personal" }, +} as KeyInfo; +const bill = ( + overrides: Partial, +): ServiceBillingExplanation => ({ + status: "conditional", + credential_class: null, + credential_label: "", + account: null, + charge_status: "conditional", + rates: [], + provider_billing: "unknown", + context: "configuration", + notes: [], + ...overrides, +}); +const rate = (credits_per_unit: string, metric = "requests") => ({ + layer: "platform", + metric, + credits_per_unit, + currency: "credits", + source: "configuration", + sync_status: "synced", +}); + +describe("plainBilling", () => { + it("says NyxID-app OAuth with no price is free, naming whose app it is", () => { + const plain = plainBilling( + twitter, + bill({ + credential_class: "nyxid_platform_oauth_app", + credential_supplier: "nyxid", + credit_billing_configured: false, + charge_status: "not_charged", + }), + ); + expect(plain).toMatchObject({ + verdict: "free", + headline: "Free on NyxID", + detail: + "This connection uses NyxID's Twitter / X app, and NyxID doesn't charge for it right now.", + key: { title: "NyxID's Twitter / X app" }, + short: "Free on NyxID", + }); + }); + + it("shows the service's NyxID rate for a NyxID-app connection, like any NyxID service", () => { + const plain = plainBilling( + { + ...twitter, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced", + }, + } as KeyInfo, + bill({ + credential_class: "nyxid_platform_oauth_app", + credential_supplier: "nyxid", + credit_billing_configured: false, + charge_status: "not_charged", + }), + ); + expect(plain).toMatchObject({ + verdict: "charged", + headline: "Uses NyxID credits", + detail: + "Each request costs 0.05 NyxID credits, paid by you, from your personal credits.", + price: "0.05 credits per request", + short: "You pay · 0.05 credits/request", + }); + }); + + it("explains an organization's own app is free on NyxID but billed by the provider", () => { + const plain = plainBilling( + { + ...twitter, + credential_source: { + type: "org", + org_id: "o", + org_name: "ChronoAI", + }, + } as KeyInfo, + bill({ + credential_class: "user_owned", + credential_supplier: "own", + credit_billing_configured: false, + }), + ); + expect(plain.key.title).toBe("ChronoAI's own Twitter / X app"); + expect(plain.detail).toBe( + "This connection uses ChronoAI's own app, so NyxID doesn't charge for it. Twitter / X may bill ChronoAI directly.", + ); + }); + + it("states the price and payer for a charged NyxID key in one sentence", () => { + const plain = plainBilling( + deepseek, + bill({ + credential_class: "nyxid_managed_master", + credential_supplier: "nyxid", + credit_billing_configured: true, + rates: [rate("0.000001", "tokens")], + }), + ); + expect(plain).toMatchObject({ + verdict: "charged", + headline: "Uses NyxID credits", + detail: + "Each token costs 0.000001 NyxID credits, paid by whoever makes the call, from their personal credits.", + price: "0.000001 credits per token", + short: "Caller pays · 0.000001 credits/token", + }); + expect(plain.tips[0]).toMatch(/free allowances first/); + }); + + it("adds the provider caveat when your own key carries a NyxID fee", () => { + const plain = plainBilling( + deepseek, + bill({ + credential_class: "user_owned", + credential_supplier: "own", + credit_billing_configured: true, + rates: [rate("0.01")], + }), + ); + expect(plain.detail).toBe( + "Each request costs 0.01 NyxID credits, paid by you, from your personal credits. This is a NyxID fee on top of anything DeepSeek bills you directly.", + ); + expect(plain.short).toBe("You pay · 0.01 credits/request"); + }); + + it("does not guess when a paid service's app is unknown", () => { + const plain = plainBilling( + twitter, + bill({ credential_supplier: "unknown", credit_billing_configured: true }), + ); + expect(plain).toMatchObject({ + verdict: "unconfirmed", + headline: "Cost not confirmed", + key: { title: "Not confirmed" }, + }); + }); + + it("keeps restricted billing private", () => { + expect(plainBilling(twitter, bill({ status: "restricted" }))).toMatchObject( + { + verdict: "hidden", + headline: "You can't see billing for this connection", + short: "Billing unavailable", + }, + ); + }); +}); diff --git a/frontend/src/lib/billing-plain.ts b/frontend/src/lib/billing-plain.ts new file mode 100644 index 000000000..9849ee7d7 --- /dev/null +++ b/frontend/src/lib/billing-plain.ts @@ -0,0 +1,240 @@ +import { metricLabel } from "@/schemas/billing-metrics"; +import type { + ConfiguredCatalogEntry, + ServiceBillingExplanation, +} from "@/schemas/service-insights"; +import { configuredPlatformPrice } from "./service-billing-config"; +import type { KeyInfo } from "@/types/keys"; + +export type BillingVerdict = "charged" | "free" | "unconfirmed" | "hidden"; + +/** Billing in words a first-time user can act on: cost, whose key, who pays. */ +export interface PlainBilling { + readonly verdict: BillingVerdict; + readonly headline: string; + readonly detail: string; + readonly key: { readonly title: string; readonly note?: string }; + readonly payer: string; + readonly price: string; + /** Short form for table rows, e.g. "You pay · 0.05 credits/request". */ + readonly short: string; + readonly tips: readonly string[]; +} + +const OAUTH = ["oauth2", "device_code"]; + +function providerName(connection: KeyInfo): string { + const name = + connection.catalog_service_name ?? connection.name ?? connection.label; + return name.replace(/\s+API$/i, ""); +} + +function supplierOf( + bill: ServiceBillingExplanation, +): "nyxid" | "own" | "none" | "unknown" { + if (bill.credential_supplier) return bill.credential_supplier; + switch (bill.credential_class) { + case "nyxid_managed_master": + case "nyxid_platform_oauth_app": + return "nyxid"; + case "user_owned": + case "agent_override_user_owned": + case "node_managed": + return "own"; + case "no_auth": + return "none"; + default: + return "unknown"; + } +} + +export function plainBilling( + connection: KeyInfo, + bill: ServiceBillingExplanation, + catalog?: ConfiguredCatalogEntry, +): PlainBilling { + const provider = providerName(connection); + const org = + connection.credential_source?.type === "org" + ? connection.credential_source.org_name + : null; + const supplier = supplierOf(bill); + const oauth = OAUTH.includes(connection.credential_type); + const master = bill.credential_class === "nyxid_managed_master"; + const thing = oauth ? "app" : "key"; + const owner = org ? `${org}'s` : "Your"; + const you = org ?? "you"; + + const key = + supplier === "nyxid" + ? master + ? { title: `NyxID's ${provider} key`, note: "NyxID provides the key." } + : { + title: `NyxID's ${provider} app`, + note: `You sign in with your own ${provider} account; NyxID provides the app.`, + } + : supplier === "own" + ? { + title: + connection.credential_type === "node_managed" || + bill.credential_class === "node_managed" + ? "A key stored on your node" + : oauth + ? `${owner} own ${provider} app` + : `${owner} own API key`, + } + : supplier === "none" + ? { title: "No key needed" } + : { + title: "Not confirmed", + note: oauth + ? `We can't tell yet whether this sign-in uses your own ${provider} app or NyxID's.` + : "We can't tell who supplied this key.", + }; + + if (bill.status === "restricted" || bill.status === "unavailable") { + const restricted = bill.status === "restricted"; + return { + verdict: "hidden", + headline: restricted + ? "You can't see billing for this connection" + : "Billing couldn't load", + detail: restricted + ? "Ask the owner of this connection what it costs." + : "Try again later.", + key, + payer: "Not available", + price: "Not available", + short: "Billing unavailable", + tips: [], + }; + } + + // A connection using NyxID's key or app is billed at the service's NyxID + // rate, shown the same way for every NyxID-supplied service. + const servicePrice = + supplier === "nyxid" && !bill.rates.length + ? configuredPlatformPrice(connection, catalog) + : null; + const free = + !servicePrice && + (bill.credit_billing_configured === false || + bill.charge_status === "not_charged"); + const rates = servicePrice + ? [ + { + layer: "platform", + metric: servicePrice.metric, + credits_per_unit: servicePrice.credits_per_unit, + currency: "credits", + source: "configuration", + sync_status: servicePrice.sync_status, + }, + ] + : bill.rates; + const unit = (metric: string) => metricLabel(metric, 1); + const price = free + ? "Free on NyxID" + : rates.length + ? rates + .map((rate) => + rate.credits_per_unit == null + ? `Plan price per ${unit(rate.metric)}` + : `${rate.credits_per_unit} credits per ${unit(rate.metric)}`, + ) + .join(" + ") + : "Not confirmed"; + const shortPrice = + rates.length === 1 && rates[0]!.credits_per_unit != null + ? `${rates[0]!.credits_per_unit} credits/${unit(rates[0]!.metric)}` + : "NyxID credits"; + + if (free) { + const detail = + supplier === "nyxid" + ? `This connection uses NyxID's ${provider} ${thing}, and NyxID doesn't charge for it right now.` + : supplier === "own" + ? `This connection uses ${org ? owner : "your"} own ${thing}, so NyxID doesn't charge for it. ${provider} may bill ${you} directly.` + : supplier === "none" + ? "This service doesn't need a key, and NyxID doesn't charge for it." + : "NyxID doesn't charge for this connection."; + return { + verdict: "free", + headline: "Free on NyxID", + detail, + key, + payer: "No one. NyxID doesn't charge for this.", + price, + short: "Free on NyxID", + tips: [], + }; + } + + if (supplier === "unknown") { + return { + verdict: "unconfirmed", + headline: "Cost not confirmed", + detail: `NyxID charges for ${provider} when you use NyxID's ${thing}, but not when you use your own. We can't tell yet which one this connection uses.`, + key, + payer: "Depends on whose app is used", + price, + short: "Cost not confirmed", + tips: [], + }; + } + + const payer = bill.account + ? bill.account.kind === "personal" + ? "You, from your personal credits" + : `${bill.account.name}, from its organization credits` + : master + ? "Whoever makes the call, from their personal credits" + : org + ? `${org}, from its organization credits` + : "You, from your personal credits"; + const payerShort = bill.account + ? bill.account.kind === "personal" + ? "You pay" + : `${bill.account.name} pays` + : master + ? "Caller pays" + : org + ? `${org} pays` + : "You pay"; + const each = + rates.length === 1 && rates[0]!.credits_per_unit != null + ? `Each ${unit(rates[0]!.metric)} costs ${rates[0]!.credits_per_unit} NyxID credits` + : "Usage costs NyxID credits"; + const payerPhrase = + payer.startsWith("You") || payer.startsWith("Whoever") + ? payer.charAt(0).toLowerCase() + payer.slice(1) + : payer; + const tips = [ + "Credits are used in this order: free allowances first, then credit grants, then your wallet balance.", + ...(rates.some((rate) => rate.sync_status && rate.sync_status !== "synced") + ? [ + "A new price is waiting to be activated. Until then, the previous price applies.", + ] + : []), + ...(rates.some((rate) => rate.credits_per_unit == null) + ? ["The price comes from your billing plan and isn't shown here."] + : []), + ...(bill.context === "configuration" + ? ["Agent keys set to use a different key can be billed differently."] + : []), + ]; + return { + verdict: "charged", + headline: "Uses NyxID credits", + detail: `${each}, paid by ${payerPhrase}.${ + supplier === "own" + ? ` This is a NyxID fee on top of anything ${provider} bills ${you} directly.` + : "" + }`, + key, + payer, + price, + short: `${payerShort} · ${shortPrice}`, + tips, + }; +} diff --git a/frontend/src/lib/connection-access.ts b/frontend/src/lib/connection-access.ts new file mode 100644 index 000000000..1bb942acc --- /dev/null +++ b/frontend/src/lib/connection-access.ts @@ -0,0 +1,11 @@ +import type { KeyInfo } from "@/types/keys"; + +export function canEditConnection(key: KeyInfo): boolean { + if (key.auto_connected || key.can_edit_configuration === false) return false; + // Older servers omit the explicit permission; require known ownership. + const source = key.credential_source; + return ( + source?.type === "personal" || + (source?.type === "org" && source.role === "admin" && source.allowed) + ); +} diff --git a/frontend/src/lib/mock-data.ts b/frontend/src/lib/mock-data.ts index c680e51b2..2a00efbb7 100644 --- a/frontend/src/lib/mock-data.ts +++ b/frontend/src/lib/mock-data.ts @@ -1,4 +1,5 @@ import { mockSetupResponse } from "@/lib/assistant/mock-setup-journeys"; +import { ApiError } from "@/lib/api-client"; // Keep the manually selected demo engine across chat navigation and reloads. const MOCK_NYXBOT_STORAGE = "nyxid.mock-nyxbot"; @@ -8,8 +9,10 @@ if (import.meta.env.DEV && typeof window !== "undefined") { sessionStorage.setItem(MOCK_NYXBOT_STORAGE, selection); } } -const mockNyxbotEnabled = globalThis.__nyxidAssistantHttpFaults?.nyxagentEnabled ?? - (import.meta.env.DEV && typeof window !== "undefined" && +const mockNyxbotEnabled = + globalThis.__nyxidAssistantHttpFaults?.nyxagentEnabled ?? + (import.meta.env.DEV && + typeof window !== "undefined" && sessionStorage.getItem(MOCK_NYXBOT_STORAGE) === "1"); // ── Mock User ── @@ -25,7 +28,10 @@ const MOCK_USER = { created_at: "2025-11-20T08:00:00Z", capabilities: { billing_available: true, - enabled_features: ["experimental:ai-assistant", ...(mockNyxbotEnabled ? ["assistant:nyxagent-engine"] : [])], + enabled_features: [ + "experimental:ai-assistant", + ...(mockNyxbotEnabled ? ["assistant:nyxagent-engine"] : []), + ], }, }; @@ -200,8 +206,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-01-15T09:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -233,8 +242,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-01-20T10:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -266,8 +278,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-02-01T11:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -299,8 +314,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-03-10T14:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -332,8 +350,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-03-25T09:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -365,8 +386,11 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-04-10T10:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, credential_source: { type: "personal" as const }, }, @@ -434,25 +458,103 @@ const MOCK_KEYS = [ error_message: null, created_at: "2026-02-15T09:00:00Z", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, - ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, openapi_spec_url: null, - credential_source: { type: "org" as const, org_name: "ChronoAI", role: "member", allowed: true }, + credential_source: { + type: "org" as const, + org_name: "ChronoAI", + role: "member", + allowed: true, + }, }, ]; +// Provisioned automatically by NyxID; hidden until "Auto-connected" is shown. +MOCK_KEYS.push({ + ...MOCK_KEYS[0]!, + id: "key-auto-0001", + label: "Tavily Search", + slug: "tavily-search", + endpoint_url: "https://api.tavily.com", + endpoint_id: "ep-auto-0001", + catalog_service_id: "cs-tavily", + catalog_service_slug: "tavily-search", + catalog_service_name: "Tavily Search", + auto_connected: true, + last_used_at: null, +} as (typeof MOCK_KEYS)[number]); // ── External API Keys (credentials) ── const MOCK_EXTERNAL_API_KEYS = [ - { id: "eak-0001", label: "OpenAI Production Key", credential_type: "api_key", auth_method: "bearer", auth_key_name: "Authorization", created_at: "2026-01-15T09:00:00Z", last_used_at: "2026-05-06T14:22:00Z", service_count: 1 }, - { id: "eak-0002", label: "Claude API Key", credential_type: "api_key", auth_method: "header", auth_key_name: "x-api-key", created_at: "2026-01-20T10:00:00Z", last_used_at: "2026-05-05T10:15:00Z", service_count: 1 }, - { id: "eak-0003", label: "GitHub Token", credential_type: "api_key", auth_method: "bearer", auth_key_name: "Authorization", created_at: "2026-02-01T11:00:00Z", last_used_at: "2026-05-04T16:30:00Z", service_count: 1 }, - { id: "eak-0004", label: "Stripe Secret Key", credential_type: "api_key", auth_method: "bearer", auth_key_name: "Authorization", created_at: "2026-03-10T14:00:00Z", last_used_at: null, service_count: 1 }, - { id: "eak-0005", label: "Supabase API Key", credential_type: "api_key", auth_method: "header", auth_key_name: "apikey", created_at: "2026-03-25T09:00:00Z", last_used_at: "2026-05-06T08:00:00Z", service_count: 1 }, - { id: "eak-0006", label: "Vercel Token", credential_type: "api_key", auth_method: "bearer", auth_key_name: "Authorization", created_at: "2026-04-10T10:00:00Z", last_used_at: null, service_count: 1 }, + { + id: "eak-0001", + label: "OpenAI Production Key", + credential_type: "api_key", + auth_method: "bearer", + auth_key_name: "Authorization", + created_at: "2026-01-15T09:00:00Z", + last_used_at: "2026-05-06T14:22:00Z", + service_count: 1, + }, + { + id: "eak-0002", + label: "Claude API Key", + credential_type: "api_key", + auth_method: "header", + auth_key_name: "x-api-key", + created_at: "2026-01-20T10:00:00Z", + last_used_at: "2026-05-05T10:15:00Z", + service_count: 1, + }, + { + id: "eak-0003", + label: "GitHub Token", + credential_type: "api_key", + auth_method: "bearer", + auth_key_name: "Authorization", + created_at: "2026-02-01T11:00:00Z", + last_used_at: "2026-05-04T16:30:00Z", + service_count: 1, + }, + { + id: "eak-0004", + label: "Stripe Secret Key", + credential_type: "api_key", + auth_method: "bearer", + auth_key_name: "Authorization", + created_at: "2026-03-10T14:00:00Z", + last_used_at: null, + service_count: 1, + }, + { + id: "eak-0005", + label: "Supabase API Key", + credential_type: "api_key", + auth_method: "header", + auth_key_name: "apikey", + created_at: "2026-03-25T09:00:00Z", + last_used_at: "2026-05-06T08:00:00Z", + service_count: 1, + }, + { + id: "eak-0006", + label: "Vercel Token", + credential_type: "api_key", + auth_method: "bearer", + auth_key_name: "Authorization", + created_at: "2026-04-10T10:00:00Z", + last_used_at: null, + service_count: 1, + }, ]; // ── User Endpoints ── -const MOCK_USER_ENDPOINTS = MOCK_KEYS.filter((k) => k.service_type === "http").map((k) => ({ +const MOCK_USER_ENDPOINTS = MOCK_KEYS.filter( + (k) => k.service_type === "http", +).map((k) => ({ id: k.endpoint_id, label: k.label, url: k.endpoint_url, @@ -460,7 +562,9 @@ const MOCK_USER_ENDPOINTS = MOCK_KEYS.filter((k) => k.service_type === "http").m })); // ── User Services (proxy routing) ── -const MOCK_USER_SERVICES = MOCK_KEYS.filter((k) => k.service_type === "http").map((k) => ({ +const MOCK_USER_SERVICES = MOCK_KEYS.filter( + (k) => k.service_type === "http", +).map((k) => ({ id: k.id, slug: k.slug, label: k.label, @@ -483,9 +587,33 @@ const MOCK_USER_SERVICES = MOCK_KEYS.filter((k) => k.service_type === "http").ma // ── Connections (legacy) ── const MOCK_CONNECTIONS = [ - { service_id: "svc-openai", service_name: "OpenAI", service_category: "ai", auth_type: "api_key", has_credential: true, credential_label: "Production Key", connected_at: "2026-01-15T09:00:00Z" }, - { service_id: "svc-anthropic", service_name: "Anthropic", service_category: "ai", auth_type: "api_key", has_credential: true, credential_label: "Claude Key", connected_at: "2026-01-20T10:00:00Z" }, - { service_id: "svc-github", service_name: "GitHub", service_category: "developer", auth_type: "oauth2", has_credential: true, credential_label: null, connected_at: "2026-02-01T11:00:00Z" }, + { + service_id: "svc-openai", + service_name: "OpenAI", + service_category: "ai", + auth_type: "api_key", + has_credential: true, + credential_label: "Production Key", + connected_at: "2026-01-15T09:00:00Z", + }, + { + service_id: "svc-anthropic", + service_name: "Anthropic", + service_category: "ai", + auth_type: "api_key", + has_credential: true, + credential_label: "Claude Key", + connected_at: "2026-01-20T10:00:00Z", + }, + { + service_id: "svc-github", + service_name: "GitHub", + service_category: "developer", + auth_type: "oauth2", + has_credential: true, + credential_label: null, + connected_at: "2026-02-01T11:00:00Z", + }, ]; // ── Nodes ── @@ -493,26 +621,62 @@ const MOCK_NODES = [ { id: "node-0001", name: "prod-us-east", - owner: { kind: "user" as const, id: MOCK_USER.id, display_name: "Dannick Young" }, + owner: { + kind: "user" as const, + id: MOCK_USER.id, + display_name: "Dannick Young", + }, status: "Online", is_connected: true, last_heartbeat_at: "2026-05-06T14:30:00Z", connected_at: "2026-05-01T08:00:00Z", - metadata: { agent_version: "0.9.2", os: "linux", arch: "x86_64", ip_address: "10.0.1.50" }, - metrics: { total_requests: 12450, success_count: 12380, error_count: 70, success_rate: 99.4, avg_latency_ms: 42, last_error: null, last_error_at: null, last_success_at: "2026-05-06T14:29:00Z" }, + metadata: { + agent_version: "0.9.2", + os: "linux", + arch: "x86_64", + ip_address: "10.0.1.50", + }, + metrics: { + total_requests: 12450, + success_count: 12380, + error_count: 70, + success_rate: 99.4, + avg_latency_ms: 42, + last_error: null, + last_error_at: null, + last_success_at: "2026-05-06T14:29:00Z", + }, binding_count: 3, created_at: "2026-02-10T09:00:00Z", }, { id: "node-0002", name: "staging-eu", - owner: { kind: "user" as const, id: MOCK_USER.id, display_name: "Dannick Young" }, + owner: { + kind: "user" as const, + id: MOCK_USER.id, + display_name: "Dannick Young", + }, status: "Online", is_connected: true, last_heartbeat_at: "2026-05-06T14:28:00Z", connected_at: "2026-05-03T10:00:00Z", - metadata: { agent_version: "0.9.2", os: "darwin", arch: "arm64", ip_address: "192.168.1.100" }, - metrics: { total_requests: 3200, success_count: 3180, error_count: 20, success_rate: 99.4, avg_latency_ms: 85, last_error: null, last_error_at: null, last_success_at: "2026-05-06T14:25:00Z" }, + metadata: { + agent_version: "0.9.2", + os: "darwin", + arch: "arm64", + ip_address: "192.168.1.100", + }, + metrics: { + total_requests: 3200, + success_count: 3180, + error_count: 20, + success_rate: 99.4, + avg_latency_ms: 85, + last_error: null, + last_error_at: null, + last_success_at: "2026-05-06T14:25:00Z", + }, binding_count: 2, created_at: "2026-03-15T14:00:00Z", }, @@ -539,12 +703,17 @@ const MOCK_APPROVAL_REQUESTS = { requests: [ { id: "ar-0000", - service_name: "Lark", service_slug: "lark-bot", - requester_type: "api_key", requester_label: "claude-code-agent", + service_name: "Lark", + service_slug: "lark-bot", + requester_type: "api_key", + requester_label: "claude-code-agent", operation_summary: "POST /im/v1/messages", action_description: "Post the drafted summary to #payments-oncall", - tool_name: null, tool_call_id: null, tool_arguments: null, - is_destructive: false, approval_mode: "per_request" as const, + tool_name: null, + tool_call_id: null, + tool_arguments: null, + is_destructive: false, + approval_mode: "per_request" as const, status: "pending" as const, created_at: new Date(Date.now() - 60_000).toISOString(), decided_at: null, @@ -553,64 +722,94 @@ const MOCK_APPROVAL_REQUESTS = { }, { id: "ar-0001", - service_name: "OpenAI", service_slug: "openai", - requester_type: "api_key", requester_label: "claude-code-agent", + service_name: "OpenAI", + service_slug: "openai", + requester_type: "api_key", + requester_label: "claude-code-agent", operation_summary: "POST /v1/chat/completions", action_description: "Generate chat completion with gpt-4o", - tool_name: null, tool_call_id: null, tool_arguments: null, - is_destructive: false, approval_mode: "per_request" as const, + tool_name: null, + tool_call_id: null, + tool_arguments: null, + is_destructive: false, + approval_mode: "per_request" as const, status: "approved" as const, - created_at: "2026-05-06T14:20:00Z", decided_at: "2026-05-06T14:20:05Z", + created_at: "2026-05-06T14:20:00Z", + decided_at: "2026-05-06T14:20:05Z", expires_at: "2026-05-06T14:25:00Z", decision_channel: "telegram", }, { id: "ar-0002", - service_name: "GitHub", service_slug: "github", - requester_type: "api_key", requester_label: "cursor-agent", + service_name: "GitHub", + service_slug: "github", + requester_type: "api_key", + requester_label: "cursor-agent", operation_summary: "DELETE /repos/nyxid/branch", action_description: "Delete branch feature/old-auth", - tool_name: null, tool_call_id: null, tool_arguments: null, - is_destructive: true, approval_mode: "per_request" as const, + tool_name: null, + tool_call_id: null, + tool_arguments: null, + is_destructive: true, + approval_mode: "per_request" as const, status: "rejected" as const, - created_at: "2026-05-05T18:00:00Z", decided_at: "2026-05-05T18:01:30Z", + created_at: "2026-05-05T18:00:00Z", + decided_at: "2026-05-05T18:01:30Z", expires_at: "2026-05-05T18:05:00Z", decision_channel: "push", }, { id: "ar-0003", - service_name: "Stripe", service_slug: "stripe", - requester_type: "api_key", requester_label: "ci-pipeline", + service_name: "Stripe", + service_slug: "stripe", + requester_type: "api_key", + requester_label: "ci-pipeline", operation_summary: "GET /v1/charges", action_description: "List recent charges", - tool_name: null, tool_call_id: null, tool_arguments: null, - is_destructive: false, approval_mode: "grant" as const, + tool_name: null, + tool_call_id: null, + tool_arguments: null, + is_destructive: false, + approval_mode: "grant" as const, status: "approved" as const, - created_at: "2026-05-04T10:00:00Z", decided_at: "2026-05-04T10:00:12Z", + created_at: "2026-05-04T10:00:00Z", + decided_at: "2026-05-04T10:00:12Z", expires_at: "2026-05-04T10:05:00Z", decision_channel: "telegram", }, ], - total: 4, page: 1, per_page: 20, + total: 4, + page: 1, + per_page: 20, }; // ── Approval Grants ── const MOCK_APPROVAL_GRANTS = { grants: [ { - id: "ag-0001", service_id: "svc-openai", service_name: "OpenAI", - requester_type: "api_key", requester_id: "k1-0001-0001-0001-000000000001", + id: "ag-0001", + service_id: "svc-openai", + service_name: "OpenAI", + requester_type: "api_key", + requester_id: "k1-0001-0001-0001-000000000001", requester_label: "claude-code-agent", - granted_at: "2026-05-01T08:00:00Z", expires_at: "2026-05-31T08:00:00Z", + granted_at: "2026-05-01T08:00:00Z", + expires_at: "2026-05-31T08:00:00Z", }, { - id: "ag-0002", service_id: "svc-github", service_name: "GitHub", - requester_type: "api_key", requester_id: "k1-0001-0001-0001-000000000002", + id: "ag-0002", + service_id: "svc-github", + service_name: "GitHub", + requester_type: "api_key", + requester_id: "k1-0001-0001-0001-000000000002", requester_label: "cursor-agent", - granted_at: "2026-04-28T12:00:00Z", expires_at: "2026-05-28T12:00:00Z", + granted_at: "2026-04-28T12:00:00Z", + expires_at: "2026-05-28T12:00:00Z", }, ], - total: 2, page: 1, per_page: 20, + total: 2, + page: 1, + per_page: 20, }; // ── Developer Apps ── @@ -652,7 +851,8 @@ const MOCK_CHANNEL_BOTS_DATA = [ platform_bot_id: "bot123456", platform_bot_username: "nyxid_approvals_bot", webhook_registered: true, - webhook_url: "https://auth.nyxid.dev/api/v1/webhooks/channel/telegram/bot-0001", + webhook_url: + "https://auth.nyxid.dev/api/v1/webhooks/channel/telegram/bot-0001", status: "active" as const, is_active: true, created_at: "2026-03-01T09:00:00Z", @@ -670,7 +870,8 @@ const MOCK_CHANNEL_BOTS_DATA = [ platform_bot_id: "bot789012", platform_bot_username: "NyxID Dev", webhook_registered: true, - webhook_url: "https://auth.nyxid.dev/api/v1/webhooks/channel/discord/bot-0002", + webhook_url: + "https://auth.nyxid.dev/api/v1/webhooks/channel/discord/bot-0002", status: "active" as const, is_active: true, created_at: "2026-04-10T14:00:00Z", @@ -724,7 +925,8 @@ const MOCK_CHANNEL_MESSAGES = { platform_message_id: "tg-msg-002", platform_sender_id: null, content_type: "text", - content: "The deployment to production completed successfully at 13:50 UTC. All health checks are passing.", + content: + "The deployment to production completed successfully at 13:50 UTC. All health checks are passing.", created_at: "2026-05-06T13:56:00Z", }, { @@ -739,7 +941,9 @@ const MOCK_CHANNEL_MESSAGES = { created_at: "2026-05-06T14:00:00Z", }, ], - total: 3, page: 1, per_page: 20, + total: 3, + page: 1, + per_page: 20, }; // ── Organizations ── @@ -758,11 +962,46 @@ const MOCK_ORGS = [ const MOCK_ORG_MEMBERS = { members: [ - { user_id: MOCK_USER.id, email: MOCK_USER.email, display_name: MOCK_USER.display_name, avatar_url: null, role: "owner", joined_at: "2025-12-01T08:00:00Z" }, - { user_id: "u-0002", email: "alex@chronoai.dev", display_name: "Alex Chen", avatar_url: null, role: "admin", joined_at: "2025-12-15T10:00:00Z" }, - { user_id: "u-0003", email: "sarah@chronoai.dev", display_name: "Sarah Park", avatar_url: null, role: "member", joined_at: "2026-01-05T09:00:00Z" }, - { user_id: "u-0004", email: "mike@chronoai.dev", display_name: "Mike Torres", avatar_url: null, role: "member", joined_at: "2026-02-10T14:00:00Z" }, - { user_id: "u-0005", email: "lin@chronoai.dev", display_name: "Lin Wei", avatar_url: null, role: "viewer", joined_at: "2026-03-20T11:00:00Z" }, + { + user_id: MOCK_USER.id, + email: MOCK_USER.email, + display_name: MOCK_USER.display_name, + avatar_url: null, + role: "owner", + joined_at: "2025-12-01T08:00:00Z", + }, + { + user_id: "u-0002", + email: "alex@chronoai.dev", + display_name: "Alex Chen", + avatar_url: null, + role: "admin", + joined_at: "2025-12-15T10:00:00Z", + }, + { + user_id: "u-0003", + email: "sarah@chronoai.dev", + display_name: "Sarah Park", + avatar_url: null, + role: "member", + joined_at: "2026-01-05T09:00:00Z", + }, + { + user_id: "u-0004", + email: "mike@chronoai.dev", + display_name: "Mike Torres", + avatar_url: null, + role: "member", + joined_at: "2026-02-10T14:00:00Z", + }, + { + user_id: "u-0005", + email: "lin@chronoai.dev", + display_name: "Lin Wei", + avatar_url: null, + role: "viewer", + joined_at: "2026-03-20T11:00:00Z", + }, ], total: 5, }; @@ -796,169 +1035,442 @@ const MOCK_BROKER_BINDINGS: readonly unknown[] = []; // ── Sessions ── const MOCK_SESSIONS = [ - { id: "sess-0001", ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", created_at: "2026-05-06T08:00:00Z", last_active_at: "2026-05-06T14:30:00Z", is_current: true }, - { id: "sess-0002", ip_address: "10.0.0.5", user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)", created_at: "2026-05-05T20:00:00Z", last_active_at: "2026-05-06T12:00:00Z", is_current: false }, + { + id: "sess-0001", + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", + created_at: "2026-05-06T08:00:00Z", + last_active_at: "2026-05-06T14:30:00Z", + is_current: true, + }, + { + id: "sess-0002", + ip_address: "10.0.0.5", + user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)", + created_at: "2026-05-05T20:00:00Z", + last_active_at: "2026-05-06T12:00:00Z", + is_current: false, + }, ]; // ── Push Devices ── const MOCK_PUSH_DEVICES = { devices: [ - { id: "dev-0001", platform: "ios", device_name: "iPhone 15 Pro", registered_at: "2026-04-01T09:00:00Z", last_used_at: "2026-05-06T14:00:00Z" }, + { + id: "dev-0001", + platform: "ios", + device_name: "iPhone 15 Pro", + registered_at: "2026-04-01T09:00:00Z", + last_used_at: "2026-05-06T14:00:00Z", + }, ], }; // ── Catalog ── const MOCK_CATALOG = [ { - slug: "api-supabase", name: "Supabase Data API", description: "Read and write a Supabase project's tables through its Data API.", - base_url: "https://project-ref.supabase.co/rest/v1", auth_method: "header", auth_key_name: "apikey", - provider_config_id: "provider-supabase", provider_type: "api_key", requires_gateway_url: true, + slug: "api-supabase", + name: "Supabase Data API", + description: + "Read and write a Supabase project's tables through its Data API.", + base_url: "https://project-ref.supabase.co/rest/v1", + auth_method: "header", + auth_key_name: "apikey", + provider_config_id: "provider-supabase", + provider_type: "api_key", + requires_gateway_url: true, credential_mode: "admin", - api_key_instructions: "Enter your project URL and Supabase API key. Secret keys bypass Row Level Security; publishable keys use your anonymous role's policies.", + api_key_instructions: + "Enter your project URL and Supabase API key. Secret keys bypass Row Level Security; publishable keys use your anonymous role's policies.", api_key_url: "https://supabase.com/dashboard/project/_/settings/api-keys", - icon_url: null, documentation_url: "https://supabase.com/docs/guides/api", + icon_url: null, + documentation_url: "https://supabase.com/docs/guides/api", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://supabase.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: true, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: false }, - auth_notes: "NyxID sends the stored key in the apikey header.", known_limitations: "Data API only; no PostgreSQL sessions, Storage, Edge Functions, or Realtime.", required_permissions: [], - }, - { - slug: "openai", name: "OpenAI", description: "OpenAI API — GPT-4o, DALL-E, Whisper", - base_url: "https://api.openai.com/v1", auth_method: "bearer", auth_key_name: "Authorization", - provider_config_id: null, provider_type: null, requires_gateway_url: false, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://supabase.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: true, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: false, + }, + auth_notes: "NyxID sends the stored key in the apikey header.", + known_limitations: + "Data API only; no PostgreSQL sessions, Storage, Edge Functions, or Realtime.", + required_permissions: [], + }, + { + slug: "openai", + name: "OpenAI", + description: "OpenAI API — GPT-4o, DALL-E, Whisper", + base_url: "https://api.openai.com/v1", + auth_method: "bearer", + auth_key_name: "Authorization", + provider_config_id: null, + provider_type: null, + requires_gateway_url: false, credential_mode: "api_key", api_key_instructions: "Get your API key from platform.openai.com", api_key_url: "https://platform.openai.com/api-keys", - icon_url: null, documentation_url: "https://platform.openai.com/docs", + icon_url: null, + documentation_url: "https://platform.openai.com/docs", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://openai.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: true }, - auth_notes: "Use your API key from the OpenAI dashboard.", known_limitations: null, required_permissions: [], - }, - { - slug: "anthropic", name: "Anthropic", description: "Anthropic Claude API", - base_url: "https://api.anthropic.com/v1", auth_method: "header", auth_key_name: "x-api-key", - provider_config_id: null, provider_type: null, requires_gateway_url: false, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://openai.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: true, + }, + auth_notes: "Use your API key from the OpenAI dashboard.", + known_limitations: null, + required_permissions: [], + }, + { + slug: "anthropic", + name: "Anthropic", + description: "Anthropic Claude API", + base_url: "https://api.anthropic.com/v1", + auth_method: "header", + auth_key_name: "x-api-key", + provider_config_id: null, + provider_type: null, + requires_gateway_url: false, credential_mode: "api_key", api_key_instructions: "Get your API key from console.anthropic.com", api_key_url: "https://console.anthropic.com/settings/keys", - icon_url: null, documentation_url: "https://docs.anthropic.com", + icon_url: null, + documentation_url: "https://docs.anthropic.com", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://anthropic.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: true }, - auth_notes: "Requires x-api-key header.", known_limitations: null, required_permissions: [], - }, - { - slug: "github", name: "GitHub", description: "GitHub REST & GraphQL API", - base_url: "https://api.github.com", auth_method: "oauth2", auth_key_name: "Authorization", - provider_config_id: "provider-github", provider_type: "oauth2", requires_gateway_url: false, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://anthropic.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: true, + }, + auth_notes: "Requires x-api-key header.", + known_limitations: null, + required_permissions: [], + }, + { + slug: "github", + name: "GitHub", + description: "GitHub REST & GraphQL API", + base_url: "https://api.github.com", + auth_method: "oauth2", + auth_key_name: "Authorization", + provider_config_id: "provider-github", + provider_type: "oauth2", + requires_gateway_url: false, credential_mode: "both", api_key_instructions: null, api_key_url: null, - icon_url: null, documentation_url: "https://docs.github.com/en/rest", + icon_url: null, + documentation_url: "https://docs.github.com/en/rest", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: "https://github.com/login/oauth/authorize", token_url: "https://github.com/login/oauth/access_token", device_code_url: null, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: "https://github.com/login/oauth/authorize", + token_url: "https://github.com/login/oauth/access_token", + device_code_url: null, default_scopes: ["repo"], - scope_catalog: [{ scope: "repo", label: "Repositories", description: "Read and write repository data." }], + scope_catalog: [ + { + scope: "repo", + label: "Repositories", + description: "Read and write repository data.", + }, + ], scope_removal: "unsupported", - supports_pkce: false, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, + supports_pkce: false, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, has_platform_oauth_credentials: true, platform_scope_allowlist: ["repo"], - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://github.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: false }, - auth_notes: null, known_limitations: null, required_permissions: [], + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://github.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: false, + }, + auth_notes: null, + known_limitations: null, + required_permissions: [], }, { - slug: "stripe", name: "Stripe", description: "Stripe Payments API", - base_url: "https://api.stripe.com/v1", auth_method: "bearer", auth_key_name: "Authorization", - provider_config_id: null, provider_type: null, requires_gateway_url: false, + slug: "stripe", + name: "Stripe", + description: "Stripe Payments API", + base_url: "https://api.stripe.com/v1", + auth_method: "bearer", + auth_key_name: "Authorization", + provider_config_id: null, + provider_type: null, + requires_gateway_url: false, credential_mode: "api_key", - api_key_instructions: "Find your secret key in the Stripe Dashboard under Developers > API keys", + api_key_instructions: + "Find your secret key in the Stripe Dashboard under Developers > API keys", api_key_url: "https://dashboard.stripe.com/apikeys", - icon_url: null, documentation_url: "https://stripe.com/docs/api", + icon_url: null, + documentation_url: "https://stripe.com/docs/api", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://stripe.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: false }, - auth_notes: null, known_limitations: null, required_permissions: [], + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://stripe.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: false, + }, + auth_notes: null, + known_limitations: null, + required_permissions: [], }, // Unconnected entries so the assistant Plugins marketplace shows an // "Available to add" section in mock mode (names mirror the mockup). { - slug: "lark-bot", name: "Lark Bot", description: "Send and receive messages in Lark/Feishu channels through your bot.", - base_url: "https://open.larksuite.com/open-apis", auth_method: "bearer", auth_key_name: "Authorization", - provider_config_id: null, provider_type: null, requires_gateway_url: false, + slug: "lark-bot", + name: "Lark Bot", + description: + "Send and receive messages in Lark/Feishu channels through your bot.", + base_url: "https://open.larksuite.com/open-apis", + auth_method: "bearer", + auth_key_name: "Authorization", + provider_config_id: null, + provider_type: null, + requires_gateway_url: false, credential_mode: "api_key", - api_key_instructions: "Create a bot in the Lark developer console and paste its token.", + api_key_instructions: + "Create a bot in the Lark developer console and paste its token.", api_key_url: "https://open.larksuite.com/app", - icon_url: null, documentation_url: "https://open.larksuite.com/document", + icon_url: null, + documentation_url: "https://open.larksuite.com/document", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://larksuite.com", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: false }, - auth_notes: null, known_limitations: null, required_permissions: [], - }, - { - slug: "postgres", name: "Postgres", description: "Read-only SQL over your database, executed on your own node.", - base_url: "http://localhost:5432", auth_method: "header", auth_key_name: "x-api-key", - provider_config_id: null, provider_type: null, requires_gateway_url: false, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://larksuite.com", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: false, + }, + auth_notes: null, + known_limitations: null, + required_permissions: [], + }, + { + slug: "postgres", + name: "Postgres", + description: "Read-only SQL over your database, executed on your own node.", + base_url: "http://localhost:5432", + auth_method: "header", + auth_key_name: "x-api-key", + provider_config_id: null, + provider_type: null, + requires_gateway_url: false, credential_mode: "api_key", - api_key_instructions: "Provide a read-only connection string for your database.", + api_key_instructions: + "Provide a read-only connection string for your database.", api_key_url: null, - icon_url: null, documentation_url: "https://www.postgresql.org/docs/", + icon_url: null, + documentation_url: "https://www.postgresql.org/docs/", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://www.postgresql.org", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: false, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: false }, - auth_notes: null, known_limitations: null, required_permissions: [], - }, - { - slug: "openclaw", name: "OpenClaw Gateway", description: "Route requests to your self-hosted OpenClaw instance via a local node.", - base_url: "http://localhost:18789", auth_method: "bearer", auth_key_name: "Authorization", - provider_config_id: null, provider_type: null, requires_gateway_url: true, + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://www.postgresql.org", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: false, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: false, + }, + auth_notes: null, + known_limitations: null, + required_permissions: [], + }, + { + slug: "openclaw", + name: "OpenClaw Gateway", + description: + "Route requests to your self-hosted OpenClaw instance via a local node.", + base_url: "http://localhost:18789", + auth_method: "bearer", + auth_key_name: "Authorization", + provider_config_id: null, + provider_type: null, + requires_gateway_url: true, credential_mode: "api_key", api_key_instructions: "Paste your OpenClaw gateway URL and bearer token.", api_key_url: null, - icon_url: null, documentation_url: "https://docs.openclaw.dev", + icon_url: null, + documentation_url: "https://docs.openclaw.dev", service_type: "http", - ssh_host: null, ssh_port: null, ssh_ca_public_key: null, ssh_allowed_principals: null, ssh_certificate_ttl_minutes: null, - authorization_url: null, token_url: null, device_code_url: null, - default_scopes: null, supports_pkce: null, device_code_format: null, - oauth_client_id: null, client_id_param_name: null, - requires_credential: true, token_exchange_credential_fields: null, default_request_headers: null, - homepage_url: "https://openclaw.dev", repository_url: null, issues_url: null, - capabilities: { supports_proxy_read: true, supports_proxy_write: true, supports_proxy_binary_upload: false, supports_direct_downstream_auth: false, supports_authoring_via_nyx: false, supports_websocket: false, supports_streaming: true }, - auth_notes: null, known_limitations: null, required_permissions: [], + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + authorization_url: null, + token_url: null, + device_code_url: null, + default_scopes: null, + supports_pkce: null, + device_code_format: null, + oauth_client_id: null, + client_id_param_name: null, + requires_credential: true, + token_exchange_credential_fields: null, + default_request_headers: null, + homepage_url: "https://openclaw.dev", + repository_url: null, + issues_url: null, + capabilities: { + supports_proxy_read: true, + supports_proxy_write: true, + supports_proxy_binary_upload: false, + supports_direct_downstream_auth: false, + supports_authoring_via_nyx: false, + supports_websocket: false, + supports_streaming: true, + }, + auth_notes: null, + known_limitations: null, + required_permissions: [], }, ]; @@ -983,9 +1495,27 @@ const MOCK_API_KEY_USAGE_LIST = MOCK_API_KEYS.map((k, i) => { total_tokens: total * 1160, reported_cost: total * 0.0032, top_services: [ - { service_id: "s1", service_slug: "openai", service_label: "OpenAI", request_count: Math.floor(total * 0.6), error_count: 0 }, - { service_id: "s2", service_slug: "anthropic", service_label: "Anthropic", request_count: Math.floor(total * 0.3), error_count: 0 }, - { service_id: "s3", service_slug: "github-copilot", service_label: "GitHub Copilot", request_count: Math.floor(total * 0.1), error_count: 0 }, + { + service_id: "s1", + service_slug: "openai", + service_label: "OpenAI", + request_count: Math.floor(total * 0.6), + error_count: 0, + }, + { + service_id: "s2", + service_slug: "anthropic", + service_label: "Anthropic", + request_count: Math.floor(total * 0.3), + error_count: 0, + }, + { + service_id: "s3", + service_slug: "github-copilot", + service_label: "GitHub Copilot", + request_count: Math.floor(total * 0.1), + error_count: 0, + }, ], daily_buckets: Array.from({ length: 7 }, (_, d) => { const date = new Date(baseDate); @@ -1003,8 +1533,30 @@ const MOCK_API_KEY_USAGE_LIST = MOCK_API_KEYS.map((k, i) => { // ── Approval Service Configs ── const MOCK_SERVICE_APPROVAL_CONFIGS = { configs: [ - { service_id: "svc-openai", service_name: "OpenAI", approval_required: true, approval_mode: "grant" as const, rules: [], default_effect: null, created_at: "2026-03-01T00:00:00Z", updated_at: "2026-03-01T00:00:00Z", user_service_id: "key-openai-1", user_service_slug: "openai" }, - { service_id: "svc-github", service_name: "GitHub", approval_required: true, approval_mode: "per_request" as const, rules: [], default_effect: null, created_at: "2026-03-01T00:00:00Z", updated_at: "2026-03-01T00:00:00Z", user_service_id: "key-github-1", user_service_slug: "github" }, + { + service_id: "svc-openai", + service_name: "OpenAI", + approval_required: true, + approval_mode: "grant" as const, + rules: [], + default_effect: null, + created_at: "2026-03-01T00:00:00Z", + updated_at: "2026-03-01T00:00:00Z", + user_service_id: "key-openai-1", + user_service_slug: "openai", + }, + { + service_id: "svc-github", + service_name: "GitHub", + approval_required: true, + approval_mode: "per_request" as const, + rules: [], + default_effect: null, + created_at: "2026-03-01T00:00:00Z", + updated_at: "2026-03-01T00:00:00Z", + user_service_id: "key-github-1", + user_service_slug: "github", + }, ], dominant_org_policies: [], }; @@ -1136,30 +1688,142 @@ const MOCK_ADMIN_USERS = [ // ── Admin Audit Log ── const MOCK_AUDIT_LOG = [ - { id: "aud-001", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: null, api_key_name: null, event_type: "user.login", event_data: { method: "password", ip: "192.168.1.10" }, ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-14T09:30:00Z" }, - { id: "aud-002", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: "k1-0001-0001-0001-000000000001", api_key_name: "claude-code-agent", event_type: "proxy.request", event_data: { service: "openai", method: "POST", path: "/v1/chat/completions" }, ip_address: "10.0.1.50", user_agent: "nyxid-agent/0.9.2", created_at: "2026-05-14T09:25:00Z" }, - { id: "aud-003", user_id: MOCK_ADMIN_USERS[1]!.id, api_key_id: null, api_key_name: null, event_type: "user.login", event_data: { method: "password" }, ip_address: "10.0.0.5", user_agent: "Mozilla/5.0 (Windows NT 10.0)", created_at: "2026-05-13T16:45:00Z" }, - { id: "aud-004", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: null, api_key_name: null, event_type: "service_account.create", event_data: { name: "CI/CD Pipeline" }, ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-13T14:00:00Z" }, - { id: "aud-005", user_id: MOCK_ADMIN_USERS[2]!.id, api_key_id: null, api_key_name: null, event_type: "mfa.setup", event_data: { method: "totp" }, ip_address: "172.16.0.20", user_agent: "Mozilla/5.0 (Linux)", created_at: "2026-05-13T10:00:00Z" }, - { id: "aud-006", user_id: MOCK_ADMIN_USERS[3]!.id, api_key_id: null, api_key_name: null, event_type: "user.login", event_data: { method: "password" }, ip_address: "192.168.1.42", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-14T08:10:00Z" }, - { id: "aud-007", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: null, api_key_name: null, event_type: "invite_code.create", event_data: { max_uses: 5 }, ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-12T15:00:00Z" }, - { id: "aud-008", user_id: MOCK_ADMIN_USERS[4]!.id, api_key_id: null, api_key_name: null, event_type: "user.register", event_data: { invite_code: "CHRONO-2026" }, ip_address: "203.0.113.50", user_agent: "Mozilla/5.0 (iPhone)", created_at: "2026-05-10T15:00:00Z" }, - { id: "aud-009", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: null, api_key_name: null, event_type: "role.create", event_data: { name: "API Consumer" }, ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-10T11:00:00Z" }, - { id: "aud-010", user_id: MOCK_ADMIN_USERS[0]!.id, api_key_id: null, api_key_name: null, event_type: "user.status_change", event_data: { target_user: "deactivated@example.com", is_active: false }, ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh)", created_at: "2026-05-09T10:00:00Z" }, + { + id: "aud-001", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: null, + api_key_name: null, + event_type: "user.login", + event_data: { method: "password", ip: "192.168.1.10" }, + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-14T09:30:00Z", + }, + { + id: "aud-002", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: "k1-0001-0001-0001-000000000001", + api_key_name: "claude-code-agent", + event_type: "proxy.request", + event_data: { + service: "openai", + method: "POST", + path: "/v1/chat/completions", + }, + ip_address: "10.0.1.50", + user_agent: "nyxid-agent/0.9.2", + created_at: "2026-05-14T09:25:00Z", + }, + { + id: "aud-003", + user_id: MOCK_ADMIN_USERS[1]!.id, + api_key_id: null, + api_key_name: null, + event_type: "user.login", + event_data: { method: "password" }, + ip_address: "10.0.0.5", + user_agent: "Mozilla/5.0 (Windows NT 10.0)", + created_at: "2026-05-13T16:45:00Z", + }, + { + id: "aud-004", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: null, + api_key_name: null, + event_type: "service_account.create", + event_data: { name: "CI/CD Pipeline" }, + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-13T14:00:00Z", + }, + { + id: "aud-005", + user_id: MOCK_ADMIN_USERS[2]!.id, + api_key_id: null, + api_key_name: null, + event_type: "mfa.setup", + event_data: { method: "totp" }, + ip_address: "172.16.0.20", + user_agent: "Mozilla/5.0 (Linux)", + created_at: "2026-05-13T10:00:00Z", + }, + { + id: "aud-006", + user_id: MOCK_ADMIN_USERS[3]!.id, + api_key_id: null, + api_key_name: null, + event_type: "user.login", + event_data: { method: "password" }, + ip_address: "192.168.1.42", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-14T08:10:00Z", + }, + { + id: "aud-007", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: null, + api_key_name: null, + event_type: "invite_code.create", + event_data: { max_uses: 5 }, + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-12T15:00:00Z", + }, + { + id: "aud-008", + user_id: MOCK_ADMIN_USERS[4]!.id, + api_key_id: null, + api_key_name: null, + event_type: "user.register", + event_data: { invite_code: "CHRONO-2026" }, + ip_address: "203.0.113.50", + user_agent: "Mozilla/5.0 (iPhone)", + created_at: "2026-05-10T15:00:00Z", + }, + { + id: "aud-009", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: null, + api_key_name: null, + event_type: "role.create", + event_data: { name: "API Consumer" }, + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-10T11:00:00Z", + }, + { + id: "aud-010", + user_id: MOCK_ADMIN_USERS[0]!.id, + api_key_id: null, + api_key_name: null, + event_type: "user.status_change", + event_data: { target_user: "deactivated@example.com", is_active: false }, + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh)", + created_at: "2026-05-09T10:00:00Z", + }, ]; // Mirrors the shape the backend advertises so the mock table offers the same // sorts, scoped-search fields, and filters as a live one. const MOCK_AUDIT_LOG_FILTER_OPTIONS = { sorts: [ - "-created_at", "created_at", - "event_type", "-event_type", - "api_key_name", "-api_key_name", - "api_key_id", "-api_key_id", - "user_id", "-user_id", - "ip_address", "-ip_address", - "user_agent", "-user_agent", - "status", "-status", + "-created_at", + "created_at", + "event_type", + "-event_type", + "api_key_name", + "-api_key_name", + "api_key_id", + "-api_key_id", + "user_id", + "-user_id", + "ip_address", + "-ip_address", + "user_agent", + "-user_agent", + "status", + "-status", ], search_fields: [ { key: "event_type", label: "Event type" }, @@ -1239,33 +1903,68 @@ const MOCK_AUDIT_LOG_FILTER_OPTIONS = { // ── Admin Invite Codes ── const MOCK_INVITE_CODES = [ { - id: "inv-001", code: "CHRONO-2026", max_uses: 5, used_count: 3, is_active: true, + id: "inv-001", + code: "CHRONO-2026", + max_uses: 5, + used_count: 3, + is_active: true, created_by: MOCK_ADMIN_USERS[0]!.id, creator: { email: "dannick@nyxid.dev", display_name: "Donnick Young" }, note: "Team onboarding Q1 2026", - created_at: "2026-01-10T09:00:00Z", updated_at: "2026-03-20T11:00:00Z", + created_at: "2026-01-10T09:00:00Z", + updated_at: "2026-03-20T11:00:00Z", usages: [ - { user_id: MOCK_ADMIN_USERS[2]!.id, used_at: "2026-01-05T09:00:00Z", user_email: "sarah@chronoai.dev", user_display_name: "Sarah Park" }, - { user_id: MOCK_ADMIN_USERS[3]!.id, used_at: "2026-02-10T14:00:00Z", user_email: "mike@chronoai.dev", user_display_name: "Mike Torres" }, - { user_id: MOCK_ADMIN_USERS[4]!.id, used_at: "2026-03-20T11:00:00Z", user_email: "lin@chronoai.dev", user_display_name: "Lin Wei" }, + { + user_id: MOCK_ADMIN_USERS[2]!.id, + used_at: "2026-01-05T09:00:00Z", + user_email: "sarah@chronoai.dev", + user_display_name: "Sarah Park", + }, + { + user_id: MOCK_ADMIN_USERS[3]!.id, + used_at: "2026-02-10T14:00:00Z", + user_email: "mike@chronoai.dev", + user_display_name: "Mike Torres", + }, + { + user_id: MOCK_ADMIN_USERS[4]!.id, + used_at: "2026-03-20T11:00:00Z", + user_email: "lin@chronoai.dev", + user_display_name: "Lin Wei", + }, ], }, { - id: "inv-002", code: "PARTNER-VIP", max_uses: 10, used_count: 0, is_active: true, + id: "inv-002", + code: "PARTNER-VIP", + max_uses: 10, + used_count: 0, + is_active: true, created_by: MOCK_ADMIN_USERS[0]!.id, creator: { email: "dannick@nyxid.dev", display_name: "Dannick Young" }, note: "Partner program invites", - created_at: "2026-04-01T12:00:00Z", updated_at: "2026-04-01T12:00:00Z", + created_at: "2026-04-01T12:00:00Z", + updated_at: "2026-04-01T12:00:00Z", usages: [], }, { - id: "inv-003", code: "BETA-TEST-42", max_uses: 1, used_count: 1, is_active: false, + id: "inv-003", + code: "BETA-TEST-42", + max_uses: 1, + used_count: 1, + is_active: false, created_by: MOCK_ADMIN_USERS[1]!.id, creator: { email: "alex@chronoai.dev", display_name: "Alex Chen" }, note: null, - created_at: "2025-12-20T08:00:00Z", updated_at: "2026-01-05T09:00:00Z", + created_at: "2025-12-20T08:00:00Z", + updated_at: "2026-01-05T09:00:00Z", usages: [ - { user_id: MOCK_ADMIN_USERS[2]!.id, used_at: "2026-01-05T09:00:00Z", user_email: "sarah@chronoai.dev", user_display_name: "Sarah Park" }, + { + user_id: MOCK_ADMIN_USERS[2]!.id, + used_at: "2026-01-05T09:00:00Z", + user_email: "sarah@chronoai.dev", + user_display_name: "Sarah Park", + }, ], }, ]; @@ -1273,79 +1972,144 @@ const MOCK_INVITE_CODES = [ // ── Admin Roles ── const MOCK_ROLES = [ { - id: "role-001", name: "Platform Admin", slug: "platform-admin", + id: "role-001", + name: "Platform Admin", + slug: "platform-admin", description: "Full administrative access to all platform features", - permissions: ["admin:read", "admin:write", "users:manage", "roles:manage", "audit:read"], - is_default: false, is_system: true, client_id: null, - created_at: "2025-11-01T00:00:00Z", updated_at: "2025-11-01T00:00:00Z", + permissions: [ + "admin:read", + "admin:write", + "users:manage", + "roles:manage", + "audit:read", + ], + is_default: false, + is_system: true, + client_id: null, + created_at: "2025-11-01T00:00:00Z", + updated_at: "2025-11-01T00:00:00Z", }, { - id: "role-002", name: "API Consumer", slug: "api-consumer", + id: "role-002", + name: "API Consumer", + slug: "api-consumer", description: "Can connect services and use the proxy", permissions: ["proxy:read", "proxy:write", "services:read", "keys:manage"], - is_default: true, is_system: false, client_id: null, - created_at: "2026-01-15T10:00:00Z", updated_at: "2026-03-10T14:00:00Z", + is_default: true, + is_system: false, + client_id: null, + created_at: "2026-01-15T10:00:00Z", + updated_at: "2026-03-10T14:00:00Z", }, { - id: "role-003", name: "Node Operator", slug: "node-operator", + id: "role-003", + name: "Node Operator", + slug: "node-operator", description: "Can register and manage credential nodes", permissions: ["nodes:manage", "proxy:read", "proxy:write"], - is_default: false, is_system: false, client_id: null, - created_at: "2026-02-20T09:00:00Z", updated_at: "2026-02-20T09:00:00Z", + is_default: false, + is_system: false, + client_id: null, + created_at: "2026-02-20T09:00:00Z", + updated_at: "2026-02-20T09:00:00Z", }, { - id: "role-004", name: "Audit Viewer", slug: "audit-viewer", + id: "role-004", + name: "Audit Viewer", + slug: "audit-viewer", description: "Read-only access to audit logs", permissions: ["audit:read"], - is_default: false, is_system: false, client_id: null, - created_at: "2026-03-05T11:00:00Z", updated_at: "2026-03-05T11:00:00Z", + is_default: false, + is_system: false, + client_id: null, + created_at: "2026-03-05T11:00:00Z", + updated_at: "2026-03-05T11:00:00Z", }, ]; // ── Admin Groups ── const MOCK_GROUPS = [ { - id: "grp-001", name: "Engineering", slug: "engineering", + id: "grp-001", + name: "Engineering", + slug: "engineering", description: "Core engineering team with full proxy and node access", roles: [MOCK_ROLES[1]!, MOCK_ROLES[2]!], - parent_group_id: null, member_count: 3, - created_at: "2025-12-01T08:00:00Z", updated_at: "2026-04-10T14:00:00Z", + parent_group_id: null, + member_count: 3, + created_at: "2025-12-01T08:00:00Z", + updated_at: "2026-04-10T14:00:00Z", }, { - id: "grp-002", name: "Product", slug: "product", + id: "grp-002", + name: "Product", + slug: "product", description: "Product team with service access", roles: [MOCK_ROLES[1]!], - parent_group_id: null, member_count: 2, - created_at: "2026-01-10T09:00:00Z", updated_at: "2026-03-15T10:00:00Z", + parent_group_id: null, + member_count: 2, + created_at: "2026-01-10T09:00:00Z", + updated_at: "2026-03-15T10:00:00Z", }, { - id: "grp-003", name: "Security", slug: "security", + id: "grp-003", + name: "Security", + slug: "security", description: "Security team with audit access", roles: [MOCK_ROLES[3]!], - parent_group_id: null, member_count: 1, - created_at: "2026-02-15T11:00:00Z", updated_at: "2026-02-15T11:00:00Z", + parent_group_id: null, + member_count: 1, + created_at: "2026-02-15T11:00:00Z", + updated_at: "2026-02-15T11:00:00Z", }, ]; -const MOCK_GROUP_MEMBERS: Record = { +const MOCK_GROUP_MEMBERS: Record< + string, + { members: unknown[]; total: number } +> = { "grp-001": { members: [ - { id: MOCK_ADMIN_USERS[0]!.id, email: "dannick@nyxid.dev", display_name: "Dannick Young" }, - { id: MOCK_ADMIN_USERS[1]!.id, email: "alex@chronoai.dev", display_name: "Alex Chen" }, - { id: MOCK_ADMIN_USERS[3]!.id, email: "mike@chronoai.dev", display_name: "Mike Torres" }, + { + id: MOCK_ADMIN_USERS[0]!.id, + email: "dannick@nyxid.dev", + display_name: "Dannick Young", + }, + { + id: MOCK_ADMIN_USERS[1]!.id, + email: "alex@chronoai.dev", + display_name: "Alex Chen", + }, + { + id: MOCK_ADMIN_USERS[3]!.id, + email: "mike@chronoai.dev", + display_name: "Mike Torres", + }, ], total: 3, }, "grp-002": { members: [ - { id: MOCK_ADMIN_USERS[2]!.id, email: "sarah@chronoai.dev", display_name: "Sarah Park" }, - { id: MOCK_ADMIN_USERS[3]!.id, email: "mike@chronoai.dev", display_name: "Mike Torres" }, + { + id: MOCK_ADMIN_USERS[2]!.id, + email: "sarah@chronoai.dev", + display_name: "Sarah Park", + }, + { + id: MOCK_ADMIN_USERS[3]!.id, + email: "mike@chronoai.dev", + display_name: "Mike Torres", + }, ], total: 2, }, "grp-003": { members: [ - { id: MOCK_ADMIN_USERS[1]!.id, email: "alex@chronoai.dev", display_name: "Alex Chen" }, + { + id: MOCK_ADMIN_USERS[1]!.id, + email: "alex@chronoai.dev", + display_name: "Alex Chen", + }, ], total: 1, }, @@ -1354,30 +2118,48 @@ const MOCK_GROUP_MEMBERS: Record // ── Admin Service Accounts ── const MOCK_SERVICE_ACCOUNTS = [ { - id: "sa-001", name: "CI/CD Pipeline", description: "Automated deployment pipeline", - client_id: "nyx_sa_ci_cd_pipeline_8f3a", secret_prefix: "nyx_ss_8f3a", - allowed_scopes: "openid proxy:* llm:proxy", role_ids: ["role-002"], - is_active: true, rate_limit_override: 50, + id: "sa-001", + name: "CI/CD Pipeline", + description: "Automated deployment pipeline", + client_id: "nyx_sa_ci_cd_pipeline_8f3a", + secret_prefix: "nyx_ss_8f3a", + allowed_scopes: "openid proxy:* llm:proxy", + role_ids: ["role-002"], + is_active: true, + rate_limit_override: 50, created_by: MOCK_ADMIN_USERS[0]!.id, - created_at: "2026-03-01T09:00:00Z", updated_at: "2026-05-10T14:00:00Z", + created_at: "2026-03-01T09:00:00Z", + updated_at: "2026-05-10T14:00:00Z", last_authenticated_at: "2026-05-14T06:00:00Z", }, { - id: "sa-002", name: "Monitoring Agent", description: "Health check and monitoring service", - client_id: "nyx_sa_monitoring_agent_2b7c", secret_prefix: "nyx_ss_2b7c", - allowed_scopes: "openid proxy:read", role_ids: [], - is_active: true, rate_limit_override: null, + id: "sa-002", + name: "Monitoring Agent", + description: "Health check and monitoring service", + client_id: "nyx_sa_monitoring_agent_2b7c", + secret_prefix: "nyx_ss_2b7c", + allowed_scopes: "openid proxy:read", + role_ids: [], + is_active: true, + rate_limit_override: null, created_by: MOCK_ADMIN_USERS[0]!.id, - created_at: "2026-04-15T11:00:00Z", updated_at: "2026-04-15T11:00:00Z", + created_at: "2026-04-15T11:00:00Z", + updated_at: "2026-04-15T11:00:00Z", last_authenticated_at: "2026-05-14T09:28:00Z", }, { - id: "sa-003", name: "Data Sync Worker", description: null, - client_id: "nyx_sa_data_sync_worker_9d1e", secret_prefix: "nyx_ss_9d1e", - allowed_scopes: "openid proxy:read proxy:write", role_ids: ["role-002"], - is_active: false, rate_limit_override: 20, + id: "sa-003", + name: "Data Sync Worker", + description: null, + client_id: "nyx_sa_data_sync_worker_9d1e", + secret_prefix: "nyx_ss_9d1e", + allowed_scopes: "openid proxy:read proxy:write", + role_ids: ["role-002"], + is_active: false, + rate_limit_override: 20, created_by: MOCK_ADMIN_USERS[1]!.id, - created_at: "2026-02-20T15:00:00Z", updated_at: "2026-05-01T10:00:00Z", + created_at: "2026-02-20T15:00:00Z", + updated_at: "2026-05-01T10:00:00Z", last_authenticated_at: "2026-04-28T22:00:00Z", }, ]; @@ -1385,59 +2167,194 @@ const MOCK_SERVICE_ACCOUNTS = [ // ── Admin Nodes ── const MOCK_ADMIN_NODES = [ { - id: "node-0001", name: "prod-us-east", - user_id: MOCK_ADMIN_USERS[0]!.id, user_email: "dannick@nyxid.dev", - status: "Online", is_connected: true, - last_heartbeat_at: "2026-05-14T09:30:00Z", connected_at: "2026-05-12T08:00:00Z", - metadata: { agent_version: "0.9.2", os: "linux", arch: "x86_64", ip_address: "10.0.1.50" }, - metrics: { total_requests: 12450, success_count: 12380, error_count: 70, success_rate: 0.994, avg_latency_ms: 42, last_error: null, last_error_at: null, last_success_at: "2026-05-14T09:29:00Z" }, - binding_count: 3, created_at: "2026-02-10T09:00:00Z", - }, - { - id: "node-0002", name: "staging-eu", - user_id: MOCK_ADMIN_USERS[0]!.id, user_email: "dannick@nyxid.dev", - status: "Online", is_connected: true, - last_heartbeat_at: "2026-05-14T09:28:00Z", connected_at: "2026-05-10T10:00:00Z", - metadata: { agent_version: "0.9.2", os: "darwin", arch: "arm64", ip_address: "192.168.1.100" }, - metrics: { total_requests: 3200, success_count: 3180, error_count: 20, success_rate: 0.994, avg_latency_ms: 85, last_error: null, last_error_at: null, last_success_at: "2026-05-14T09:25:00Z" }, - binding_count: 2, created_at: "2026-03-15T14:00:00Z", - }, - { - id: "node-0003", name: "alex-dev-local", - user_id: MOCK_ADMIN_USERS[1]!.id, user_email: "alex@chronoai.dev", - status: "Offline", is_connected: false, - last_heartbeat_at: "2026-05-13T18:00:00Z", connected_at: null, - metadata: { agent_version: "0.9.1", os: "darwin", arch: "arm64", ip_address: "192.168.1.42" }, - metrics: { total_requests: 890, success_count: 875, error_count: 15, success_rate: 0.983, avg_latency_ms: 120, last_error: "connection timeout", last_error_at: "2026-05-13T17:55:00Z", last_success_at: "2026-05-13T17:50:00Z" }, - binding_count: 1, created_at: "2026-04-01T10:00:00Z", - }, - { - id: "node-0004", name: "prod-drain-test", - user_id: MOCK_ADMIN_USERS[0]!.id, user_email: "dannick@nyxid.dev", - status: "Draining", is_connected: true, - last_heartbeat_at: "2026-05-14T09:29:00Z", connected_at: "2026-05-14T06:00:00Z", - metadata: { agent_version: "0.9.2", os: "linux", arch: "x86_64", ip_address: "10.0.1.51" }, - metrics: { total_requests: 450, success_count: 448, error_count: 2, success_rate: 0.996, avg_latency_ms: 38, last_error: null, last_error_at: null, last_success_at: "2026-05-14T09:20:00Z" }, - binding_count: 1, created_at: "2026-05-01T12:00:00Z", + id: "node-0001", + name: "prod-us-east", + user_id: MOCK_ADMIN_USERS[0]!.id, + user_email: "dannick@nyxid.dev", + status: "Online", + is_connected: true, + last_heartbeat_at: "2026-05-14T09:30:00Z", + connected_at: "2026-05-12T08:00:00Z", + metadata: { + agent_version: "0.9.2", + os: "linux", + arch: "x86_64", + ip_address: "10.0.1.50", + }, + metrics: { + total_requests: 12450, + success_count: 12380, + error_count: 70, + success_rate: 0.994, + avg_latency_ms: 42, + last_error: null, + last_error_at: null, + last_success_at: "2026-05-14T09:29:00Z", + }, + binding_count: 3, + created_at: "2026-02-10T09:00:00Z", + }, + { + id: "node-0002", + name: "staging-eu", + user_id: MOCK_ADMIN_USERS[0]!.id, + user_email: "dannick@nyxid.dev", + status: "Online", + is_connected: true, + last_heartbeat_at: "2026-05-14T09:28:00Z", + connected_at: "2026-05-10T10:00:00Z", + metadata: { + agent_version: "0.9.2", + os: "darwin", + arch: "arm64", + ip_address: "192.168.1.100", + }, + metrics: { + total_requests: 3200, + success_count: 3180, + error_count: 20, + success_rate: 0.994, + avg_latency_ms: 85, + last_error: null, + last_error_at: null, + last_success_at: "2026-05-14T09:25:00Z", + }, + binding_count: 2, + created_at: "2026-03-15T14:00:00Z", + }, + { + id: "node-0003", + name: "alex-dev-local", + user_id: MOCK_ADMIN_USERS[1]!.id, + user_email: "alex@chronoai.dev", + status: "Offline", + is_connected: false, + last_heartbeat_at: "2026-05-13T18:00:00Z", + connected_at: null, + metadata: { + agent_version: "0.9.1", + os: "darwin", + arch: "arm64", + ip_address: "192.168.1.42", + }, + metrics: { + total_requests: 890, + success_count: 875, + error_count: 15, + success_rate: 0.983, + avg_latency_ms: 120, + last_error: "connection timeout", + last_error_at: "2026-05-13T17:55:00Z", + last_success_at: "2026-05-13T17:50:00Z", + }, + binding_count: 1, + created_at: "2026-04-01T10:00:00Z", + }, + { + id: "node-0004", + name: "prod-drain-test", + user_id: MOCK_ADMIN_USERS[0]!.id, + user_email: "dannick@nyxid.dev", + status: "Draining", + is_connected: true, + last_heartbeat_at: "2026-05-14T09:29:00Z", + connected_at: "2026-05-14T06:00:00Z", + metadata: { + agent_version: "0.9.2", + os: "linux", + arch: "x86_64", + ip_address: "10.0.1.51", + }, + metrics: { + total_requests: 450, + success_count: 448, + error_count: 2, + success_rate: 0.996, + avg_latency_ms: 38, + last_error: null, + last_error_at: null, + last_success_at: "2026-05-14T09:20:00Z", + }, + binding_count: 1, + created_at: "2026-05-01T12:00:00Z", }, ]; // ── Admin Sessions ── -const MOCK_ADMIN_SESSIONS: Record = { +const MOCK_ADMIN_SESSIONS: Record< + string, + { sessions: unknown[]; total: number } +> = { [MOCK_ADMIN_USERS[0]!.id]: { sessions: [ - { id: "sess-a01", ip_address: "192.168.1.10", user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", created_at: "2026-05-14T09:30:00Z", expires_at: "2026-05-21T09:30:00Z", last_active_at: "2026-05-14T09:30:00Z", revoked: false }, - { id: "sess-a02", ip_address: "10.0.0.5", user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)", created_at: "2026-05-13T20:00:00Z", expires_at: "2026-05-20T20:00:00Z", last_active_at: "2026-05-14T08:00:00Z", revoked: false }, + { + id: "sess-a01", + ip_address: "192.168.1.10", + user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", + created_at: "2026-05-14T09:30:00Z", + expires_at: "2026-05-21T09:30:00Z", + last_active_at: "2026-05-14T09:30:00Z", + revoked: false, + }, + { + id: "sess-a02", + ip_address: "10.0.0.5", + user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)", + created_at: "2026-05-13T20:00:00Z", + expires_at: "2026-05-20T20:00:00Z", + last_active_at: "2026-05-14T08:00:00Z", + revoked: false, + }, ], total: 2, }, }; // ── Admin User Roles / Groups ── -const MOCK_USER_ROLES: Record = { - [MOCK_ADMIN_USERS[0]!.id]: { direct_roles: [MOCK_ROLES[0]], inherited_roles: [MOCK_ROLES[1], MOCK_ROLES[2]], effective_permissions: ["admin:read", "admin:write", "users:manage", "roles:manage", "audit:read", "proxy:read", "proxy:write", "services:read", "keys:manage", "nodes:manage"] }, - [MOCK_ADMIN_USERS[1]!.id]: { direct_roles: [MOCK_ROLES[1]], inherited_roles: [], effective_permissions: ["proxy:read", "proxy:write", "services:read", "keys:manage"] }, - [MOCK_ADMIN_USERS[3]!.id]: { direct_roles: [MOCK_ROLES[1]], inherited_roles: [], effective_permissions: ["proxy:read", "proxy:write", "services:read", "keys:manage"] }, +const MOCK_USER_ROLES: Record< + string, + { + direct_roles: unknown[]; + inherited_roles: unknown[]; + effective_permissions: string[]; + } +> = { + [MOCK_ADMIN_USERS[0]!.id]: { + direct_roles: [MOCK_ROLES[0]], + inherited_roles: [MOCK_ROLES[1], MOCK_ROLES[2]], + effective_permissions: [ + "admin:read", + "admin:write", + "users:manage", + "roles:manage", + "audit:read", + "proxy:read", + "proxy:write", + "services:read", + "keys:manage", + "nodes:manage", + ], + }, + [MOCK_ADMIN_USERS[1]!.id]: { + direct_roles: [MOCK_ROLES[1]], + inherited_roles: [], + effective_permissions: [ + "proxy:read", + "proxy:write", + "services:read", + "keys:manage", + ], + }, + [MOCK_ADMIN_USERS[3]!.id]: { + direct_roles: [MOCK_ROLES[1]], + inherited_roles: [], + effective_permissions: [ + "proxy:read", + "proxy:write", + "services:read", + "keys:manage", + ], + }, }; const MOCK_USER_GROUPS_MAP: Record = { @@ -1448,11 +2365,17 @@ const MOCK_USER_GROUPS_MAP: Record = { }; // ── Helper: find by ID in an array ── -function findById(items: readonly T[], id: string): T | undefined { +function findById( + items: readonly T[], + id: string, +): T | undefined { return items.find((item) => item.id === id); } -function findBySlug(items: readonly T[], slug: string): T | undefined { +function findBySlug( + items: readonly T[], + slug: string, +): T | undefined { return items.find((item) => item.slug === slug); } @@ -1603,7 +2526,12 @@ const MOCK_BILLING_ALLOWANCES = { mockAllowance("openai", "bytes", 1_000_000_000, 150_000_000), mockAllowance("anthropic", "tokens", 2_000_000, 0), mockAllowance("anthropic", "requests", 500, 500), - mockAllowance("enterprise-knowledge-graph-retrieval-gateway", "requests", 10_000, 9_990), + mockAllowance( + "enterprise-knowledge-graph-retrieval-gateway", + "requests", + 10_000, + 9_990, + ), ], }; function mockUsageRow( @@ -1637,17 +2565,76 @@ function mockUsageRow( ...overrides, }; } -function mockBillingUsage(period: string) { - const rows = [ - mockUsageRow("openai", "input_tokens", 412_000, 0.0213, { model: "gpt-4o" }), - mockUsageRow("openai", "output_tokens", 61_000, 0.0142, { model: "gpt-4o" }), - mockUsageRow("anthropic", "tokens", 18_400, 0.0071, { model: "claude-sonnet-5" }), +const MOCK_DAY_PATTERN = [0, 2, 5, 3, 0, 6, 1, 4, 2, 0, 3, 7, 1, 2]; + +/** Spread each fixture row over UTC days for `bucket=day`, deterministically. */ +function mockDailyRows( + rows: ReturnType[], + period: string, +) { + const days = { "7d": 7, "30d": 30, "90d": 90 }[period] ?? 1; + const today = Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate(), + ); + return rows.flatMap((row) => + Array.from({ length: days }, (_, i) => i).flatMap((i) => { + const weight = + MOCK_DAY_PATTERN[ + (i + row.service_slug.length) % MOCK_DAY_PATTERN.length + ]!; + if (!weight) return []; + const share = weight / (days * 3); + const micros = Math.round(row.estimated_credits_micros * share); + return [ + { + ...row, + quantity: Math.max(1, Math.round(row.quantity * share)), + requests: + row.metric === "requests" + ? Math.max(1, Math.round(row.quantity * share)) + : 0, + events: weight, + estimated_credits_micros: micros, + grant_credits_micros: micros, + day: new Date(today - (days - 1 - i) * 86_400_000).toISOString(), + }, + ]; + }), + ); +} + +function mockBillingUsage(period: string, bucket: string | null = null) { + const flatRows = [ + mockUsageRow("openai", "input_tokens", 412_000, 0.0213, { + model: "gpt-4o", + }), + mockUsageRow("openai", "output_tokens", 61_000, 0.0142, { + model: "gpt-4o", + }), + mockUsageRow("anthropic", "tokens", 18_400, 0.0071, { + model: "claude-sonnet-5", + }), mockUsageRow("github", "requests", 340, 0), mockUsageRow("stripe", "requests", 42, 0), - mockUsageRow("enterprise-knowledge-graph-retrieval-gateway", "requests", 9_990, 0), + mockUsageRow( + "enterprise-knowledge-graph-retrieval-gateway", + "requests", + 9_990, + 0, + ), ]; - const sum = (field: "quantity" | "requests" | "bytes" | "events" | "estimated_credits_micros" | "grant_credits_micros") => - rows.reduce((total, row) => total + (row[field] as number), 0); + const rows = bucket === "day" ? mockDailyRows(flatRows, period) : flatRows; + const sum = ( + field: + | "quantity" + | "requests" + | "bytes" + | "events" + | "estimated_credits_micros" + | "grant_credits_micros", + ) => rows.reduce((total, row) => total + (row[field] as number), 0); return { owner_id: MOCK_BILLING_OWNER, period, @@ -1702,24 +2689,49 @@ type MockHandler = ( const MOCK_HANDLERS: MockHandler[] = [ // User - (p) => p === "/users/me" ? MOCK_USER : undefined, - (p) => p === "/users/me/primary-org" ? MOCK_ORGS[0] : undefined, + (p) => (p === "/users/me" ? MOCK_USER : undefined), + (p) => (p === "/users/me/primary-org" ? MOCK_ORGS[0] : undefined), // Billing - (p) => p === "/billing/wallet" ? MOCK_BILLING_WALLET : undefined, - (p, q) => p === "/billing/usage" ? mockBillingUsage(q.get("period") ?? "30d") : undefined, - (p) => p === "/billing/topups" ? MOCK_BILLING_TOPUPS : undefined, - (p) => p === "/billing/grants" ? MOCK_BILLING_GRANTS : undefined, - (p) => p === "/billing/allowances" ? MOCK_BILLING_ALLOWANCES : undefined, + (p) => (p === "/billing/wallet" ? MOCK_BILLING_WALLET : undefined), + (p, q) => + p === "/billing/usage" + ? mockBillingUsage(q.get("period") ?? "30d", q.get("bucket")) + : undefined, + // Like current production: no insights route, so the configured fallback runs. + (p) => { + if (p === "/service-insights") + throw new ApiError(404, { + message: "Not found", + error_code: 404, + } as never); + return undefined; + }, + (p) => (p === "/billing/topups" ? MOCK_BILLING_TOPUPS : undefined), + (p) => (p === "/billing/grants" ? MOCK_BILLING_GRANTS : undefined), + (p) => (p === "/billing/allowances" ? MOCK_BILLING_ALLOWANCES : undefined), // API keys usage (must be before generic /api-keys patterns) - (p) => p.match(/^\/api-keys\/usage/) ? { usage: MOCK_API_KEY_USAGE_LIST, since: "2026-05-01T00:00:00Z", days: 7 } : undefined, + (p) => + p.match(/^\/api-keys\/usage/) + ? { + usage: MOCK_API_KEY_USAGE_LIST, + since: "2026-05-01T00:00:00Z", + days: 7, + } + : undefined, // API keys external - (p) => p.match(/^\/api-keys\/external$/) ? { keys: MOCK_EXTERNAL_API_KEYS } : undefined, + (p) => + p.match(/^\/api-keys\/external$/) + ? { keys: MOCK_EXTERNAL_API_KEYS } + : undefined, (p) => { const m = p.match(/^\/api-keys\/external\/([\w-]+)$/); - return m ? findById(MOCK_EXTERNAL_API_KEYS, m[1] ?? "") ?? MOCK_EXTERNAL_API_KEYS[0] : undefined; + return m + ? (findById(MOCK_EXTERNAL_API_KEYS, m[1] ?? "") ?? + MOCK_EXTERNAL_API_KEYS[0]) + : undefined; }, // API key bindings @@ -1733,36 +2745,44 @@ const MOCK_HANDLERS: MockHandler[] = [ (p) => { const m = p.match(/^\/api-keys\/([\w-]+)\/usage/); if (!m) return undefined; - return MOCK_API_KEY_USAGE_LIST.find((u) => u.api_key_id === m[1]) ?? MOCK_API_KEY_USAGE_LIST[0]; + return ( + MOCK_API_KEY_USAGE_LIST.find((u) => u.api_key_id === m[1]) ?? + MOCK_API_KEY_USAGE_LIST[0] + ); }, // API key detail (p) => { const m = p.match(/^\/api-keys\/([\w-]+)$/); - return m ? findById(MOCK_API_KEYS, m[1] ?? "") ?? MOCK_API_KEYS[0] : undefined; + return m + ? (findById(MOCK_API_KEYS, m[1] ?? "") ?? MOCK_API_KEYS[0]) + : undefined; }, // API keys list - (p) => p.match(/^\/api-keys$/) ? { keys: MOCK_API_KEYS } : undefined, + (p) => (p.match(/^\/api-keys$/) ? { keys: MOCK_API_KEYS } : undefined), // Keys (external services) (p) => { const m = p.match(/^\/keys\/([\w-]+)$/); - return m ? findById(MOCK_KEYS, m[1] ?? "") ?? MOCK_KEYS[0] : undefined; + return m ? (findById(MOCK_KEYS, m[1] ?? "") ?? MOCK_KEYS[0]) : undefined; }, - (p) => p === "/keys" ? { keys: MOCK_KEYS } : undefined, + (p) => (p === "/keys" ? { keys: MOCK_KEYS } : undefined), // User endpoints - (p) => p.match(/^\/endpoints/) ? { endpoints: MOCK_USER_ENDPOINTS } : undefined, + (p) => + p.match(/^\/endpoints/) ? { endpoints: MOCK_USER_ENDPOINTS } : undefined, // User services - (p) => p.match(/^\/user-services/) ? { services: MOCK_USER_SERVICES } : undefined, + (p) => + p.match(/^\/user-services/) ? { services: MOCK_USER_SERVICES } : undefined, // Connections - (p) => p.match(/^\/connections$/) ? { connections: MOCK_CONNECTIONS } : undefined, + (p) => + p.match(/^\/connections$/) ? { connections: MOCK_CONNECTIONS } : undefined, // Nodes - (p) => p.match(/^\/nodes\/my-bindings/) ? { bindings: [] } : undefined, + (p) => (p.match(/^\/nodes\/my-bindings/) ? { bindings: [] } : undefined), (p) => { const m = p.match(/^\/nodes\/([\w-]+)\/admins/); return m ? { admins: [] } : undefined; @@ -1773,14 +2793,22 @@ const MOCK_HANDLERS: MockHandler[] = [ }, (p) => { const m = p.match(/^\/nodes\/([\w-]+)$/); - return m ? findById(MOCK_NODES, m[1] ?? "") ?? MOCK_NODES[0] : undefined; + return m ? (findById(MOCK_NODES, m[1] ?? "") ?? MOCK_NODES[0]) : undefined; }, - (p) => p === "/nodes" ? { nodes: MOCK_NODES } : undefined, + (p) => (p === "/nodes" ? { nodes: MOCK_NODES } : undefined), // Notifications - (p) => p === "/notifications/settings" ? MOCK_NOTIFICATION_SETTINGS : undefined, - (p) => p.match(/^\/notifications\/devices/) ? MOCK_PUSH_DEVICES : undefined, - (p) => p.match(/^\/notifications\/telegram/) ? { link_code: "MOCK-LINK-CODE", bot_username: "nyxid_approvals_bot", expires_in_secs: 600 } : undefined, + (p) => + p === "/notifications/settings" ? MOCK_NOTIFICATION_SETTINGS : undefined, + (p) => (p.match(/^\/notifications\/devices/) ? MOCK_PUSH_DEVICES : undefined), + (p) => + p.match(/^\/notifications\/telegram/) + ? { + link_code: "MOCK-LINK-CODE", + bot_username: "nyxid_approvals_bot", + expires_in_secs: 600, + } + : undefined, // Approvals // The real handler filters by status server-side; the assistant view relies @@ -1794,22 +2822,36 @@ const MOCK_HANDLERS: MockHandler[] = [ ); return { ...MOCK_APPROVAL_REQUESTS, requests, total: requests.length }; }, - (p) => p.match(/^\/approvals\/grants/) ? MOCK_APPROVAL_GRANTS : undefined, - (p) => p.match(/^\/approvals\/service-configs/) ? MOCK_SERVICE_APPROVAL_CONFIGS : undefined, + (p) => (p.match(/^\/approvals\/grants/) ? MOCK_APPROVAL_GRANTS : undefined), + (p) => + p.match(/^\/approvals\/service-configs/) + ? MOCK_SERVICE_APPROVAL_CONFIGS + : undefined, // Developer apps (p) => { const m = p.match(/^\/developer\/oauth-clients\/([\w-]+)$/); - return m ? findById(MOCK_DEVELOPER_APPS, m[1] ?? "") ?? MOCK_DEVELOPER_APPS[0] : undefined; + return m + ? (findById(MOCK_DEVELOPER_APPS, m[1] ?? "") ?? MOCK_DEVELOPER_APPS[0]) + : undefined; }, - (p) => p === "/developer/oauth-clients" ? { clients: MOCK_DEVELOPER_APPS } : undefined, + (p) => + p === "/developer/oauth-clients" + ? { clients: MOCK_DEVELOPER_APPS } + : undefined, // Channel bots (p) => { const m = p.match(/^\/channel-bots\/([\w-]+)$/); - return m ? findById(MOCK_CHANNEL_BOTS_DATA, m[1] ?? "") ?? MOCK_CHANNEL_BOTS_DATA[0] : undefined; + return m + ? (findById(MOCK_CHANNEL_BOTS_DATA, m[1] ?? "") ?? + MOCK_CHANNEL_BOTS_DATA[0]) + : undefined; }, - (p) => p.match(/^\/channel-bots$/) ? { bots: MOCK_CHANNEL_BOTS_DATA, total: MOCK_CHANNEL_BOTS_DATA.length } : undefined, + (p) => + p.match(/^\/channel-bots$/) + ? { bots: MOCK_CHANNEL_BOTS_DATA, total: MOCK_CHANNEL_BOTS_DATA.length } + : undefined, // Channel conversations (p) => { @@ -1818,9 +2860,18 @@ const MOCK_HANDLERS: MockHandler[] = [ }, (p) => { const m = p.match(/^\/channel-conversations\/([\w-]+)$/); - return m ? findById(MOCK_CONVERSATIONS_DATA, m[1] ?? "") ?? MOCK_CONVERSATIONS_DATA[0] : undefined; - }, - (p) => p.match(/^\/channel-conversations/) ? { conversations: MOCK_CONVERSATIONS_DATA, total: MOCK_CONVERSATIONS_DATA.length } : undefined, + return m + ? (findById(MOCK_CONVERSATIONS_DATA, m[1] ?? "") ?? + MOCK_CONVERSATIONS_DATA[0]) + : undefined; + }, + (p) => + p.match(/^\/channel-conversations/) + ? { + conversations: MOCK_CONVERSATIONS_DATA, + total: MOCK_CONVERSATIONS_DATA.length, + } + : undefined, // Organizations (p) => { @@ -1837,44 +2888,90 @@ const MOCK_HANDLERS: MockHandler[] = [ }, (p) => { const m = p.match(/^\/orgs\/([\w-]+)$/); - return m ? findById(MOCK_ORGS, m[1] ?? "") ?? { ...MOCK_ORGS[0], id: m[1] } : undefined; + return m + ? (findById(MOCK_ORGS, m[1] ?? "") ?? { ...MOCK_ORGS[0], id: m[1] }) + : undefined; }, - (p) => p === "/orgs" ? { orgs: MOCK_ORGS } : undefined, + (p) => (p === "/orgs" ? { orgs: MOCK_ORGS } : undefined), // Consents & broker - (p) => p.match(/^\/users\/me\/consents/) ? { consents: MOCK_CONSENTS } : undefined, - (p) => p.match(/^\/users\/me\/broker-bindings/) ? { bindings: MOCK_BROKER_BINDINGS } : undefined, - (p) => p === "/auth/consents/me" ? { consents: MOCK_CONSENTS } : undefined, - (p) => p === "/broker/bindings/me" ? { bindings: MOCK_BROKER_BINDINGS } : undefined, + (p) => + p.match(/^\/users\/me\/consents/) ? { consents: MOCK_CONSENTS } : undefined, + (p) => + p.match(/^\/users\/me\/broker-bindings/) + ? { bindings: MOCK_BROKER_BINDINGS } + : undefined, + (p) => (p === "/auth/consents/me" ? { consents: MOCK_CONSENTS } : undefined), + (p) => + p === "/broker/bindings/me" + ? { bindings: MOCK_BROKER_BINDINGS } + : undefined, // Sessions - (p) => p === "/sessions" ? MOCK_SESSIONS : undefined, + (p) => (p === "/sessions" ? MOCK_SESSIONS : undefined), // Catalog (p) => { const m = p.match(/^\/catalog\/([\w-]+)\/shape/); - return m ? { endpoints: [{ method: "POST", path: "/v1/chat/completions", summary: "Create chat completion" }, { method: "GET", path: "/v1/models", summary: "List models" }] } : undefined; + return m + ? { + endpoints: [ + { + method: "POST", + path: "/v1/chat/completions", + summary: "Create chat completion", + }, + { method: "GET", path: "/v1/models", summary: "List models" }, + ], + } + : undefined; }, (p) => { const m = p.match(/^\/catalog\/([\w-]+)\/endpoints/); - return m ? { endpoints: [{ method: "POST", path: "/v1/chat/completions", summary: "Create chat completion" }, { method: "GET", path: "/v1/models", summary: "List models" }, { method: "POST", path: "/v1/embeddings", summary: "Create embeddings" }] } : undefined; + return m + ? { + endpoints: [ + { + method: "POST", + path: "/v1/chat/completions", + summary: "Create chat completion", + }, + { method: "GET", path: "/v1/models", summary: "List models" }, + { + method: "POST", + path: "/v1/embeddings", + summary: "Create embeddings", + }, + ], + } + : undefined; }, (p) => { const m = p.match(/^\/catalog\/([\w-]+)$/); - return m ? findBySlug(MOCK_CATALOG, m[1] ?? "") ?? MOCK_CATALOG[0] : undefined; + return m + ? (findBySlug(MOCK_CATALOG, m[1] ?? "") ?? MOCK_CATALOG[0]) + : undefined; }, - (p) => p.match(/^\/catalog$/) ? { entries: MOCK_CATALOG } : undefined, + (p) => (p.match(/^\/catalog$/) ? { entries: MOCK_CATALOG } : undefined), // ── Admin endpoints ── // Admin users (p) => { const m = p.match(/^\/admin\/users\/([\w-]+)\/sessions$/); - return m ? (MOCK_ADMIN_SESSIONS[m[1] ?? ""] ?? { sessions: [], total: 0 }) : undefined; + return m + ? (MOCK_ADMIN_SESSIONS[m[1] ?? ""] ?? { sessions: [], total: 0 }) + : undefined; }, (p) => { const m = p.match(/^\/admin\/users\/([\w-]+)\/roles$/); - return m ? (MOCK_USER_ROLES[m[1] ?? ""] ?? { direct_roles: [], inherited_roles: [], effective_permissions: [] }) : undefined; + return m + ? (MOCK_USER_ROLES[m[1] ?? ""] ?? { + direct_roles: [], + inherited_roles: [], + effective_permissions: [], + }) + : undefined; }, (p) => { const m = p.match(/^\/admin\/users\/([\w-]+)\/groups$/); @@ -1882,15 +2979,37 @@ const MOCK_HANDLERS: MockHandler[] = [ }, (p) => { const m = p.match(/^\/admin\/users\/([\w-]+)$/); - return m ? findById(MOCK_ADMIN_USERS, m[1] ?? "") ?? MOCK_ADMIN_USERS[0] : undefined; - }, - (p) => p.match(/^\/admin\/users$/) ? { users: MOCK_ADMIN_USERS, total: MOCK_ADMIN_USERS.length, page: 1, per_page: 20 } : undefined, + return m + ? (findById(MOCK_ADMIN_USERS, m[1] ?? "") ?? MOCK_ADMIN_USERS[0]) + : undefined; + }, + (p) => + p.match(/^\/admin\/users$/) + ? { + users: MOCK_ADMIN_USERS, + total: MOCK_ADMIN_USERS.length, + page: 1, + per_page: 20, + } + : undefined, // Admin audit log - (p) => p.match(/^\/admin\/audit-log/) ? { entries: MOCK_AUDIT_LOG, total: MOCK_AUDIT_LOG.length, page: 1, per_page: 50, filter_options: MOCK_AUDIT_LOG_FILTER_OPTIONS } : undefined, + (p) => + p.match(/^\/admin\/audit-log/) + ? { + entries: MOCK_AUDIT_LOG, + total: MOCK_AUDIT_LOG.length, + page: 1, + per_page: 50, + filter_options: MOCK_AUDIT_LOG_FILTER_OPTIONS, + } + : undefined, // Admin invite codes - (p) => p.match(/^\/admin\/invite-codes$/) ? { invite_codes: MOCK_INVITE_CODES } : undefined, + (p) => + p.match(/^\/admin\/invite-codes$/) + ? { invite_codes: MOCK_INVITE_CODES } + : undefined, // Admin service accounts (p) => { @@ -1903,46 +3022,71 @@ const MOCK_HANDLERS: MockHandler[] = [ }, (p) => { const m = p.match(/^\/admin\/service-accounts\/([\w-]+)$/); - return m ? findById(MOCK_SERVICE_ACCOUNTS, m[1] ?? "") ?? MOCK_SERVICE_ACCOUNTS[0] : undefined; - }, - (p) => p.match(/^\/admin\/service-accounts$/) ? { service_accounts: MOCK_SERVICE_ACCOUNTS, total: MOCK_SERVICE_ACCOUNTS.length, page: 1, per_page: 20 } : undefined, + return m + ? (findById(MOCK_SERVICE_ACCOUNTS, m[1] ?? "") ?? + MOCK_SERVICE_ACCOUNTS[0]) + : undefined; + }, + (p) => + p.match(/^\/admin\/service-accounts$/) + ? { + service_accounts: MOCK_SERVICE_ACCOUNTS, + total: MOCK_SERVICE_ACCOUNTS.length, + page: 1, + per_page: 20, + } + : undefined, // Admin nodes (p) => { const m = p.match(/^\/admin\/nodes\/([\w-]+)$/); - return m ? findById(MOCK_ADMIN_NODES, m[1] ?? "") ?? MOCK_ADMIN_NODES[0] : undefined; - }, - (p) => p.match(/^\/admin\/nodes$/) ? { nodes: MOCK_ADMIN_NODES, total: MOCK_ADMIN_NODES.length, page: 1, per_page: 50 } : undefined, + return m + ? (findById(MOCK_ADMIN_NODES, m[1] ?? "") ?? MOCK_ADMIN_NODES[0]) + : undefined; + }, + (p) => + p.match(/^\/admin\/nodes$/) + ? { + nodes: MOCK_ADMIN_NODES, + total: MOCK_ADMIN_NODES.length, + page: 1, + per_page: 50, + } + : undefined, // Roles (p) => { const m = p.match(/^\/roles\/([\w-]+)$/); - return m ? findById(MOCK_ROLES, m[1] ?? "") ?? MOCK_ROLES[0] : undefined; + return m ? (findById(MOCK_ROLES, m[1] ?? "") ?? MOCK_ROLES[0]) : undefined; }, - (p) => p.match(/^\/roles$/) ? { roles: MOCK_ROLES } : undefined, + (p) => (p.match(/^\/roles$/) ? { roles: MOCK_ROLES } : undefined), // Groups (p) => { const m = p.match(/^\/groups\/([\w-]+)\/members$/); - return m ? (MOCK_GROUP_MEMBERS[m[1] ?? ""] ?? { members: [], total: 0 }) : undefined; + return m + ? (MOCK_GROUP_MEMBERS[m[1] ?? ""] ?? { members: [], total: 0 }) + : undefined; }, (p) => { const m = p.match(/^\/groups\/([\w-]+)$/); - return m ? findById(MOCK_GROUPS, m[1] ?? "") ?? MOCK_GROUPS[0] : undefined; + return m + ? (findById(MOCK_GROUPS, m[1] ?? "") ?? MOCK_GROUPS[0]) + : undefined; }, - (p) => p.match(/^\/groups$/) ? { groups: MOCK_GROUPS } : undefined, + (p) => (p.match(/^\/groups$/) ? { groups: MOCK_GROUPS } : undefined), // Services (admin/legacy) - (p) => p === "/services" ? { services: [] } : undefined, + (p) => (p === "/services" ? { services: [] } : undefined), // MCP - (p) => p.match(/^\/mcp/) ? MOCK_MCP_CONFIG : undefined, + (p) => (p.match(/^\/mcp/) ? MOCK_MCP_CONFIG : undefined), // LLM - (p) => p.match(/^\/llm\/status/) ? MOCK_LLM_STATUS : undefined, + (p) => (p.match(/^\/llm\/status/) ? MOCK_LLM_STATUS : undefined), // Public config - (p) => p === "/public/config" ? MOCK_PUBLIC_CONFIG : undefined, + (p) => (p === "/public/config" ? MOCK_PUBLIC_CONFIG : undefined), // Auth device-code login (p) => { @@ -1992,7 +3136,9 @@ let _mockLatched: boolean | null = null; export function isMockMode(): boolean { if (_mockLatched !== null) return _mockLatched; - _mockLatched = import.meta.env.DEV && new URLSearchParams(window.location.search).has("mock"); + _mockLatched = + import.meta.env.DEV && + new URLSearchParams(window.location.search).has("mock"); return _mockLatched; } diff --git a/frontend/src/lib/routing-preview-gateway.test.ts b/frontend/src/lib/routing-preview-gateway.test.ts new file mode 100644 index 000000000..5eb82fd44 --- /dev/null +++ b/frontend/src/lib/routing-preview-gateway.test.ts @@ -0,0 +1,50 @@ +import type { IncomingMessage, ServerResponse } from "node:http"; +import type { ViteDevServer } from "vite"; +import { describe, expect, it, vi } from "vitest"; +import { routingPreview } from "../../dev/routing-preview"; + +async function request(method: string, url: string) { + const use = vi.fn(); + const plugin = routingPreview( + "https://backend.example", + "https://frontend.example", + ); + if (typeof plugin.configureServer !== "function") + throw new Error("Missing preview gateway"); + plugin.configureServer.call( + {} as ThisParameterType, + { + httpServer: { address: () => ({ port: 4317 }) }, + middlewares: { use }, + } as unknown as ViteDevServer, + ); + const response = { writeHead: vi.fn(), end: vi.fn() }; + await use.mock.calls[0]![0]( + { method, url, headers: { host: "127.0.0.1:4317" } } as IncomingMessage, + response as unknown as ServerResponse, + vi.fn(), + ); + return response.writeHead.mock.calls[0]?.[0]; +} +const id = "00000000-0000-4000-8000-000000000001"; +describe("pool metadata preview boundary", () => { + it.each([ + "/service-pools", + "/service-pools/candidates", + `/service-pools/${id}`, + `/service-pools/${id}/candidates`, + `/service-pools/${id}/health?method=GET&path=/2/users/me`, + ])("allows authenticated metadata reads of %s", async (path) => { + // No session: admitted metadata requests reach the authentication gate. + expect(await request("GET", `/api/v1${path}`)).toBe(401); + }); + it.each([ + ["PUT", `/service-pools/${id}`], + ["POST", "/service-pools"], + ["POST", `/service-pools/${id}/health/reset`], + ["GET", `/service-pools/${id}/health/reset`], + ["GET", "/proxy/s/twitter-route"], + ])("blocks %s %s", async (method, path) => { + expect(await request(method!, `/api/v1${path}`)).toBe(403); + }); +}); diff --git a/frontend/src/lib/service-billing-config.ts b/frontend/src/lib/service-billing-config.ts new file mode 100644 index 000000000..2c3917648 --- /dev/null +++ b/frontend/src/lib/service-billing-config.ts @@ -0,0 +1,188 @@ +import type { + ConfiguredCatalogEntry, + ServiceBillingExplanation, +} from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; + +export type CredentialSupplier = "nyxid" | "own" | "none" | "unknown"; + +/** Catalog pricing describes the service offering, not the selected credential. */ +export function configuredPlatformPrice( + connection: KeyInfo, + catalog?: ConfiguredCatalogEntry, +) { + return ( + connection.platform_key_pricing ?? + catalog?.platform_key?.pricing ?? + catalog?.billing?.platform_key_pricing + ); +} + +export function positiveUsageRate(rate: string): boolean { + return /^\d+(?:\.\d+)?$/.test(rate) && /[1-9]/.test(rate); +} + +export function credentialSupplier( + connection: KeyInfo, + billing?: ServiceBillingExplanation | null, +): CredentialSupplier { + if (billing?.status === "restricted") return "unknown"; + if (billing?.credential_supplier != null) return billing.credential_supplier; + if ( + ["nyxid_managed_master", "nyxid_platform_oauth_app"].includes( + billing?.credential_class ?? "", + ) + ) + return "nyxid"; + // A selected agent can use an entirely different key from this connection. + if (billing?.context === "agent_key") return "unknown"; + if ( + billing?.credential_class === "user_owned" && + !["oauth2", "device_code"].includes(connection.credential_type) + ) + return "own"; + if (connection.credential_binding === "platform") return "nyxid"; + const node = connection.node_id || connection.has_node_binding; + if (connection.auth_method === "none" && !node) return "none"; + if (connection.credential_missing) return "unknown"; + if (["oauth2", "device_code"].includes(connection.credential_type)) { + if (connection.oauth_app_source === "platform") return "nyxid"; + if (connection.oauth_app_source === "byo") return "own"; + // Modern connections exchange and refresh with their embedded app, else the + // provider's app, and providers only refresh tokens for the issuing app. A + // healthy row therefore proves its app; failed, expired and legacy rows do not. + if ( + connection.connection_id && + connection.status === "active" && + connection.connection_status === "active" + ) + return connection.oauth_client_id?.trim() ? "own" : "nyxid"; + return "unknown"; + } + if (node && connection.credential_type === "node_managed") return "own"; + if ( + connection.api_key_id && + [ + "api_key", + "bearer", + "basic", + "token_exchange", + "ssh_certificate", + ].includes(connection.credential_type) + ) + return "own"; + return "unknown"; +} + +/** Execution class for rates. Supplier and price lane are distinct for OAuth. */ +export function connectionCredentialClass( + connection: KeyInfo, + billing?: ServiceBillingExplanation | null, +) { + if (billing?.status === "restricted") return undefined; + if (billing?.credential_class) return billing.credential_class; + if (billing?.context === "agent_key") return undefined; + const supplier = credentialSupplier(connection); + if (connection.credential_binding === "platform") + return "nyxid_managed_master"; + if (supplier === "none") return "no_auth"; + if (supplier === "nyxid") return "nyxid_platform_oauth_app"; + if (supplier === "own") + return connection.credential_type === "node_managed" + ? "node_managed" + : "user_owned"; + return undefined; +} + +function configuration(connection: KeyInfo, catalog?: ConfiguredCatalogEntry) { + return { + ...catalog?.billing, + byok_pricing: + connection.byok_pricing ?? + catalog?.byok_pricing ?? + catalog?.billing?.byok_pricing, + platform_key_pricing: configuredPlatformPrice(connection, catalog), + }; +} + +/** Mirrors the backend's configured_usage_charge; no connection inference here. */ +export function configuredChargeForClass( + billing: NonNullable, + credentialClass: string, +): boolean { + const master = credentialClass === "nyxid_managed_master"; + if (master && billing.resale_billable) return true; + if ( + billing.platform_charge_nyxid_credentials_only && + !master && + credentialClass !== "nyxid_platform_oauth_app" + ) + return false; + const legacy = + Boolean(billing.platform_billable) && + (!billing.platform_pricing || + billing.platform_pricing.sync_status !== "synced" || + positiveUsageRate(billing.platform_pricing.credits_per_unit)); + if (!billing.byok_pricing && !billing.platform_key_pricing) return legacy; + const lane = + credentialClass === "no_auth" + ? undefined + : master + ? billing.platform_key_pricing + : billing.byok_pricing; + return Boolean( + lane && + ([lane, ...(lane.components ?? [])].some((rate) => + positiveUsageRate(rate.credits_per_unit), + ) || + (lane.sync_status !== "synced" && legacy)), + ); +} + +/** The service-wide gate must be checked before credential supply. */ +export function serviceBillingConfigured( + connection: KeyInfo, + catalog?: ConfiguredCatalogEntry, +): boolean | undefined { + const billing = configuration(connection, catalog); + if ( + [ + "nyxid_managed_master", + "nyxid_platform_oauth_app", + "user_owned", + "no_auth", + ].some((cls) => configuredChargeForClass(billing, cls)) + ) + return true; + // KeyResponse omits null catalog ids/slugs for custom services. + if ( + catalog || + (!connection.catalog_service_id && !connection.catalog_service_slug) + ) + return false; + return undefined; +} + +/** Charge for this connection, never the service-wide label gate. */ +export function configuredUsageCharge( + connection: KeyInfo, + catalog?: ConfiguredCatalogEntry, + credentialClass?: string | null, +): boolean | undefined { + if (serviceBillingConfigured(connection, catalog) === false) return false; + const billing = configuration(connection, catalog); + const hasConfiguration = Boolean( + catalog || billing.byok_pricing || billing.platform_key_pricing, + ); + if (!hasConfiguration) return undefined; + credentialClass ??= connectionCredentialClass(connection); + const classes = credentialClass + ? [credentialClass] + : ["oauth2", "device_code"].includes(connection.credential_type) + ? ["nyxid_platform_oauth_app", "user_owned"] + : ["nyxid_managed_master", "user_owned", "node_managed"]; + const charges = classes.map((cls) => configuredChargeForClass(billing, cls)); + return charges.every((value) => value === charges[0]) + ? charges[0] + : undefined; +} diff --git a/frontend/src/lib/service-card-summary.test.ts b/frontend/src/lib/service-card-summary.test.ts new file mode 100644 index 000000000..bb626d9e3 --- /dev/null +++ b/frontend/src/lib/service-card-summary.test.ts @@ -0,0 +1,450 @@ +import { describe, expect, it } from "vitest"; +import { + connectionBillability, + connectionBillingCategory, + latestServiceEdit, +} from "./service-card-summary"; +import { configuredBilling } from "./service-insights-compat"; +import type { KeyInfo } from "@/types/keys"; +import { + configuredPlatformPrice, + configuredUsageCharge, + serviceBillingConfigured, +} from "./service-billing-config"; + +const connection = { + id: "personal", + catalog_service_id: "catalog", + catalog_service_slug: "service", + credential_binding: "user", + credential_type: "api_key", + api_key_id: "key", + auth_method: "bearer", + is_active: true, +} as KeyInfo; +const lane = { + metric: "requests", + credits_per_unit: "1", + sync_status: "synced" as const, +}; +const twitter = { + slug: "api-twitter", + billing: { + platform_billable: true, + platform_charge_nyxid_credentials_only: false, + platform_key_pricing: { ...lane, credits_per_unit: "0.05" }, + byok_pricing: null, + }, +}; +const oauth = { ...connection, credential_type: "oauth2" }; +// A healthy modern connection last exchanged or refreshed with its resolved app. +const healthy = { + ...oauth, + connection_id: "conn", + status: "active", + connection_status: "active" as const, +}; + +describe("billing gate before credential supply — reviewed acceptance cases", () => { + it.each([ + connection, + { ...connection, node_id: "node", credential_type: "node_managed" }, + { ...connection, is_active: false }, + ])("shows BYOK for your own key even on an unpriced service: %j", (row) => { + const catalog = { slug: "llm-anthropic", billing: null }; + expect( + connectionBillingCategory(row, configuredBilling(row, catalog), catalog), + ).toBe("byok"); + }); + + it.each([ + { ...connection, credential_binding: "platform" as const }, + oauth, + { ...oauth, oauth_app_source: "platform" as const }, + ])("shows a dash for other credentials on an unpriced service: %j", (row) => { + const catalog = { slug: "llm-anthropic", billing: null }; + expect( + connectionBillingCategory(row, configuredBilling(row, catalog), catalog), + ).toBe("not_billable"); + }); + + it.each([ + { + row: { ...oauth, oauth_app_source: "platform" as const }, + category: "platform", + }, + { row: { ...oauth, oauth_app_source: "byo" as const }, category: "byok" }, + { row: { ...oauth, oauth_client_id: "retained-app" }, category: "unknown" }, + { row: oauth, category: "unknown" }, + { row: { ...oauth, connection_id: "conn" }, category: "unknown" }, + { + row: { ...oauth, connection_id: "conn", oauth_client_id: "org-app" }, + category: "unknown", + }, + { row: { ...healthy }, category: "platform" }, + { row: { ...healthy, oauth_client_id: "org-app" }, category: "byok" }, + { row: { ...healthy, status: "failed" }, category: "unknown" }, + { + row: { ...healthy, connection_status: "expired" as const }, + category: "unknown", + }, + { row: { ...healthy, connection_id: null }, category: "unknown" }, + { row: connection, category: "byok" }, + { row: { ...connection, api_key_id: null }, category: "unknown" }, + { row: { ...connection, credential_missing: true }, category: "unknown" }, + { + row: { ...connection, credential_binding: "platform" as const }, + category: "platform", + }, + { + row: { ...connection, node_id: "node", credential_type: "node_managed" }, + category: "byok", + }, + { row: { ...oauth, node_id: "node" }, category: "unknown" }, + ])( + "uses proven provenance on a billable service: $category %j", + ({ row, category }) => { + for (const is_active of [true, false]) { + const item = { ...row, is_active }; + expect( + connectionBillingCategory( + item, + configuredBilling(item, twitter), + twitter, + ), + ).toBe(category); + } + }, + ); + + it("keeps Twitter OAuth's supplier separate from its absent price lane", () => { + const row = { ...oauth, oauth_app_source: "platform" as const }; + const bill = configuredBilling(row, twitter); + expect(bill).toMatchObject({ + service_billing_configured: true, + credential_supplier: "nyxid", + credit_billing_configured: false, + rates: [], + }); + expect(connectionBillingCategory(row, bill, twitter)).toBe("platform"); + const legacy = configuredBilling(oauth, twitter); + expect(legacy.credit_billing_configured).toBe(false); + expect( + connectionBillingCategory( + oauth, + { ...legacy, credential_class: "user_owned" }, + twitter, + ), + ).toBe("unknown"); + }); + + it("uses durable OAuth selection ahead of retained app hints", () => { + const row = { + ...oauth, + oauth_app_source: "platform" as const, + oauth_client_id: "retained-app", + }; + expect( + connectionBillingCategory(row, configuredBilling(row, twitter)), + ).toBe("platform"); + }); + + it("does not mistake retained keys for the selected agent override", () => { + const row = { ...connection, credential_binding: "platform" as const }; + const bill = { + ...configuredBilling(row, twitter), + context: "agent_key", + credential_class: "agent_override_user_owned", + credential_supplier: "own" as const, + }; + expect(connectionBillingCategory(row, bill)).toBe("byok"); + expect( + connectionBillingCategory(row, { + ...bill, + credential_supplier: "unknown", + }), + ).toBe("unknown"); + expect( + connectionBillingCategory(row, { + ...bill, + service_billing_configured: false, + }), + ).toBe("byok"); + }); + + it("does not label missing catalog data or restricted rows as free", () => { + expect( + connectionBillingCategory( + { ...connection, api_key_id: null }, + configuredBilling({ ...connection, api_key_id: null }), + ), + ).toBe("unknown"); + expect( + connectionBillingCategory(connection, { + ...configuredBilling(connection, twitter), + status: "restricted", + }), + ).toBe("unknown"); + const custom = { + ...connection, + catalog_service_id: null, + catalog_service_slug: null, + node_id: "node", + }; + expect(connectionBillingCategory(custom, configuredBilling(custom))).toBe( + "byok", + ); + }); + + it("classifies a configured billable service without a supplied key as platform billing regardless of its price lane", () => { + const row = { ...connection, auth_method: "none", api_key_id: null }; + expect( + connectionBillingCategory(row, configuredBilling(row, twitter)), + ).toBe("platform"); + expect(connectionBillability(row, configuredBilling(row, twitter))).toBe( + false, + ); + const legacy = { slug: "legacy", billing: { platform_billable: true } }; + expect(connectionBillingCategory(row, configuredBilling(row, legacy))).toBe( + "platform", + ); + }); + + it.each(["usage_based", "not_charged"] as const)( + "keeps platform and BYOK labels independent of caller charge status %s and free funding", + (charge_status) => { + for (const credential_binding of ["platform", "user"] as const) { + const row = { ...connection, credential_binding }; + const bill = { + ...configuredBilling(row, twitter), + charge_status, + rates: [], + notes: ["Usage covered by a free credit grant; no wallet debit."], + }; + expect(connectionBillingCategory(row, bill)).toBe( + credential_binding === "platform" ? "platform" : "byok", + ); + } + }, + ); + + it("recognizes service-wide charges without requiring this connection's lane", () => { + expect(serviceBillingConfigured(oauth, twitter)).toBe(true); + expect( + serviceBillingConfigured(connection, { + slug: "chrono-llm", + billing: null, + }), + ).toBe(false); + expect(serviceBillingConfigured(connection)).toBeUndefined(); + }); +}); + +describe("card billing configuration", () => { + it("does not advertise a zero platform price as billable", () => { + const platform = { ...connection, credential_binding: "platform" as const }; + const catalog = { + slug: "zero", + billing: { platform_key_pricing: { ...lane, credits_per_unit: "0" } }, + }; + expect(configuredUsageCharge(platform, catalog)).toBe(false); + expect( + connectionBillingCategory( + platform, + configuredBilling(platform, catalog), + catalog, + ), + ).toBe("not_billable"); + expect( + configuredUsageCharge(platform, { + slug: "charged", + billing: { platform_key_pricing: lane }, + }), + ).toBe(true); + }); + it("keeps a supplied key BYOK when the catalog also offers platform pricing", () => { + const catalog = { + slug: "llm-deepseek", + billing: { platform_key_pricing: lane }, + }; + expect(configuredPlatformPrice(connection, catalog)).toEqual(lane); + const bill = configuredBilling(connection, catalog); + expect(connectionBillingCategory(connection, bill, catalog)).toBe("byok"); + expect(bill.credential_label).toBe("Your API key (BYOK)"); + expect(bill.provider_billing).toBe("separate_provider_account"); + expect(bill.rates).toEqual([]); + }); + it("counts only the billable connection across five personal apps and one platform connection, including disabled rows", () => { + const catalog = { + slug: "twitter", + billing: { platform_key_pricing: lane, byok_pricing: null }, + }; + const rows = [ + ...Array.from({ length: 5 }, (_, i) => ({ + ...connection, + id: `app-${i}`, + })), + { + ...connection, + id: "platform", + credential_binding: "platform" as const, + is_active: false, + }, + ]; + expect( + rows.map((row) => + connectionBillability(row, configuredBilling(row, catalog), catalog), + ), + ).toEqual([false, false, false, false, false, true]); + }); + it("checks additional usage prices even when the primary price is zero", () => { + const catalog = { + slug: "twitter", + byok_pricing: { + ...lane, + credits_per_unit: "0", + components: [ + { ...lane, metric: "images", credits_per_unit: "0.000000000001" }, + ], + }, + }; + expect( + connectionBillability( + connection, + configuredBilling(connection, catalog), + catalog, + ), + ).toBe(true); + const zero = { + slug: "twitter", + byok_pricing: { ...lane, credits_per_unit: "0.000" }, + }; + expect( + connectionBillability( + connection, + configuredBilling(connection, zero), + zero, + ), + ).toBe(false); + }); + it("does not equate missing data or a caller's free usage with a nonbillable service", () => { + const bill = configuredBilling(connection); + expect(connectionBillability(connection, bill)).toBeUndefined(); + expect( + connectionBillability(connection, { + ...bill, + charge_status: "not_charged", + }), + ).toBeUndefined(); + expect( + connectionBillability(connection, { + ...bill, + status: "restricted", + credit_billing_configured: true, + }), + ).toBeUndefined(); + }); + it("keeps legacy billing visible until a zero service price has synced", () => { + for (const sync_status of ["pending", "synced"] as const) { + const catalog = { + slug: "twitter", + billing: { + platform_billable: true, + platform_pricing: { credits_per_unit: "0", sync_status }, + }, + }; + expect( + connectionBillability( + connection, + configuredBilling(connection, catalog), + catalog, + ), + ).toBe(sync_status === "pending"); + } + }); + it("uses the backend's configured flag even when execution is unavailable or covered for the caller", () => { + const bill = configuredBilling(connection); + expect( + connectionBillability( + { ...connection, is_active: false }, + { ...bill, status: "unavailable", credit_billing_configured: true }, + ), + ).toBe(true); + expect( + connectionBillability(connection, { + ...bill, + charge_status: "not_charged", + credit_billing_configured: true, + }), + ).toBe(true); + expect( + connectionBillability(connection, { + ...bill, + credit_billing_configured: false, + }), + ).toBe(false); + }); + it("does not assume the owner of an OAuth developer app", () => { + const oauth = { ...connection, credential_type: "oauth2" }; + const catalog = { + slug: "twitter", + billing: { + platform_billable: true, + platform_charge_nyxid_credentials_only: true, + }, + }; + expect( + connectionBillability(oauth, configuredBilling(oauth, catalog), catalog), + ).toBeUndefined(); + expect( + connectionBillability( + connection, + configuredBilling(connection, catalog), + catalog, + ), + ).toBe(false); + }); +}); + +it("selects the most recent recorded edit without substituting credential use or creation", () => { + const actor = { + kind: "person", + id: "person", + name: "Calvin", + person_id: "person", + api_key_id: null, + app_id: null, + }; + const edit = { + at: "2026-10-01T10:00:00Z", + action: "updated", + change_group_id: "edit", + actor, + }; + const older = { + ...connection, + authorship: { created_by: null, last_change: edit }, + }; + const newer = { + ...older, + id: "newer", + authorship: { + created_by: null, + last_change: { ...edit, at: "2026-10-02T10:00:00Z" }, + }, + }; + const created = { + ...connection, + id: "created", + created_at: "2026-10-03T10:00:00Z", + last_used_at: "2026-10-04T10:00:00Z", + authorship: { + created_by: { ...edit, at: "2026-10-03T10:00:00Z" }, + last_change: null, + }, + }; + expect(latestServiceEdit([newer, older, created])?.connection.id).toBe( + "newer", + ); + expect(latestServiceEdit([created])).toBeUndefined(); +}); diff --git a/frontend/src/lib/service-card-summary.ts b/frontend/src/lib/service-card-summary.ts new file mode 100644 index 000000000..74f437953 --- /dev/null +++ b/frontend/src/lib/service-card-summary.ts @@ -0,0 +1,96 @@ +import type { + ServiceBillingExplanation, + ConfiguredCatalogEntry, +} from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; +import { + configuredUsageCharge, + credentialSupplier, + serviceBillingConfigured, + positiveUsageRate, +} from "./service-billing-config"; + +export type ConnectionBillingCategory = + | "platform" + | "byok" + | "not_billable" + | "unknown"; + +export const connectionBillingLabels: Record< + ConnectionBillingCategory, + string +> = { + platform: "NyxID", + byok: "BYOK", + not_billable: "—", + unknown: "Unverified", +}; + +export function connectionBillingCategory( + connection: KeyInfo, + billing?: ServiceBillingExplanation | null, + catalog?: ConfiguredCatalogEntry, +): ConnectionBillingCategory { + if (billing?.status === "restricted") return "unknown"; + // A confirmed own key or app is BYOK whether or not NyxID prices the service. + if (credentialSupplier(connection, billing) === "own") return "byok"; + const serviceConfigured = + billing?.service_billing_configured ?? + serviceBillingConfigured(connection, catalog) ?? + (billing?.credit_billing_configured === true || + billing?.charge_status === "usage_based" + ? true + : undefined); + if (serviceConfigured === false) return "not_billable"; + if (serviceConfigured !== true) return "unknown"; + // This label follows configured service billing and selected credential + // supply. Per-connection prices, grants and wallet debits do not decide it. + switch (credentialSupplier(connection, billing)) { + case "nyxid": + case "none": + return "platform"; + case "own": + return "byok"; + default: + return "unknown"; + } +} + +export function connectionBillability( + connection: KeyInfo, + billing?: ServiceBillingExplanation | null, + catalog?: ConfiguredCatalogEntry, +): boolean | undefined { + if (billing?.status === "restricted") return undefined; + if (billing?.credit_billing_configured != null) + return billing.credit_billing_configured; + if (billing?.context === "agent_key" && !billing.credential_class) + return undefined; + const configured = configuredUsageCharge( + connection, + catalog, + billing?.credential_class, + ); + if (configured === true) return true; + if (billing?.status === "unavailable") return configured; + if (billing?.rates.length) + return billing.rates.some( + (rate) => + rate.credits_per_unit == null || + positiveUsageRate(rate.credits_per_unit), + ); + if (billing?.charge_status === "usage_based") return true; + // "Not charged" can describe caller rollout rather than the service's configuration. + return configured; +} + +export function latestServiceEdit(connections: readonly KeyInfo[]) { + return connections + .flatMap((connection) => { + const edit = connection.authorship?.last_change; + return edit && Number.isFinite(Date.parse(edit.at)) + ? [{ connection, edit }] + : []; + }) + .sort((a, b) => Date.parse(b.edit.at) - Date.parse(a.edit.at))[0]; +} diff --git a/frontend/src/lib/service-groups.ts b/frontend/src/lib/service-groups.ts new file mode 100644 index 000000000..103d17b96 --- /dev/null +++ b/frontend/src/lib/service-groups.ts @@ -0,0 +1,47 @@ +import type { CatalogEntry, KeyInfo } from "@/types/keys"; + +export interface ServiceConnectionGroup { + readonly id: string; + readonly name: string; + readonly slug: string | null; + readonly iconSlug: string; + readonly iconUrl: string | null; + readonly description: string | null; + readonly connections: readonly KeyInfo[]; +} + +export function groupServiceConnections( + keys: readonly KeyInfo[], + catalog: readonly CatalogEntry[] = [], +): ServiceConnectionGroup[] { + const groups = new Map(); + for (const key of keys) { + const id = key.catalog_service_id + ? `catalog:${key.catalog_service_id}` + : `connection:${key.id}`; + const connections = groups.get(id) ?? []; + connections.push(key); + groups.set(id, connections); + } + return [...groups] + .map(([id, connections]) => { + const first = connections[0]!; + const slug = first.catalog_service_id ? first.catalog_service_slug : null; + const entry = catalog.find((item) => item.slug === slug); + return { + id, + name: first.catalog_service_id + ? (entry?.name ?? first.catalog_service_name ?? first.label) + : first.label, + slug, + iconSlug: first.catalog_service_slug ?? first.slug, + iconUrl: connections.length === 1 ? (first.icon_url ?? null) : null, + description: + entry?.description ?? + (connections.length === 1 ? first.description : null) ?? + null, + connections, + }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); +} diff --git a/frontend/src/lib/service-insights-compat.test.tsx b/frontend/src/lib/service-insights-compat.test.tsx new file mode 100644 index 000000000..8e8ee884c --- /dev/null +++ b/frontend/src/lib/service-insights-compat.test.tsx @@ -0,0 +1,537 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, cleanup, renderHook, waitFor } from "@testing-library/react"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, + type MockInstance, +} from "vitest"; +import type { ReactNode } from "react"; +import { api, ApiError } from "@/lib/api-client"; +import { useServiceInsights } from "@/hooks/use-service-insights"; +import { + loadConfiguredServiceInsights, + configuredBilling, +} from "./service-insights-compat"; +import type { KeyInfo } from "@/types/keys"; + +vi.mock("@/stores/auth-store", () => ({ + useAuthStore: (selector: (state: { user: { id: string } }) => unknown) => + selector({ user: { id: "person" } }), +})); +const personal = { + id: "personal", + api_key_id: "stored-api-key", + label: "OpenAI", + catalog_service_slug: "openai", + credential_source: { type: "personal" }, + credential_binding: "user", + auth_method: "bearer", + credential_type: "api_key", + is_active: true, +} as KeyInfo; +const org = { + ...personal, + id: "org-service", + credential_source: { + type: "org", + org_id: "org", + org_name: "ChronoAI", + role: "admin", + allowed: true, + }, +} as KeyInfo; +const key = { + id: "key", + name: "Codex", + platform: "codex", + purpose: "general", + is_active: true, + expires_at: null, + scopes: "proxy", + allow_all_services: false, + allowed_service_ids: ["personal"], + bindings_count: 0, +}; +const responses = new Map(); +const unavailable = (status = 404) => + new ApiError(status, { + error: "unavailable", + error_code: status, + message: "Unavailable", + }); +let get: MockInstance; +beforeEach(() => { + responses.clear(); + responses.set("/api-keys", { keys: [key] }); + responses.set("/orgs", { orgs: [{ id: "org", your_role: "admin" }] }); + responses.set("/api-keys?org_id=org", { + keys: [{ ...key, id: "org-key", allow_all_services: true }], + }); + responses.set("/catalog?include_all=true", { + entries: [ + { + slug: "openai", + byok_pricing: { + metric: "requests", + credits_per_unit: "0.12", + sync_status: "synced", + }, + }, + ], + }); + get = vi.spyOn(api, "get").mockImplementation(async (path) => { + const response = responses.get(path); + if (response instanceof Error) throw response; + if (!response) throw unavailable(); + return response; + }); +}); +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); +function mount(connections: KeyInfo[] = [personal]) { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + return renderHook(({ connections }) => useServiceInsights(connections), { + initialProps: { connections }, + wrapper: ({ children }: { children: ReactNode }) => ( + {children} + ), + }); +} + +describe("deployed service insight compatibility", () => { + it("uses the connection's own OAuth app and honors platform-only charge exclusions", () => { + const bill = configuredBilling( + { + ...org, + credential_type: "oauth2", + oauth_client_id: "organization-app", + oauth_app_source: "byo", + }, + { + slug: "twitter", + billing: { + platform_charge_nyxid_credentials_only: true, + byok_pricing: { + metric: "requests", + credits_per_unit: "0.01", + sync_status: "synced", + }, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced", + }, + }, + }, + ); + expect(bill).toMatchObject({ + credential_class: "user_owned", + credential_label: "Organization OAuth app (BYOK)", + provider_billing: "separate_provider_account", + credit_billing_configured: false, + rates: [], + }); + expect(bill.notes.join(" ")).not.toContain("does not report whether"); + }); + + it("selects a supplied key's own fee lane when both credential classes are priced", () => { + const bill = configuredBilling(personal, { + slug: "openai", + billing: { + byok_pricing: { + metric: "requests", + credits_per_unit: "0.01", + sync_status: "synced", + }, + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.05", + sync_status: "synced", + }, + }, + }); + expect(bill).toMatchObject({ + credential_class: "user_owned", + credential_label: "Your API key (BYOK)", + credit_billing_configured: true, + rates: [{ credits_per_unit: "0.01" }], + }); + expect(bill.rates).toHaveLength(1); + }); + + it("treats omitted catalog billing as unpriced while retaining OAuth provenance separately", async () => { + responses.set("/catalog?include_all=true", { + entries: [{ slug: "openai" }], + }); + const [item] = await loadConfiguredServiceInsights( + [{ ...personal, credential_type: "oauth2" }], + "person", + ); + expect(item!.billing).toMatchObject({ + credit_billing_configured: false, + charge_status: "not_charged", + credential_label: "Connected account · app unverified", + rates: [], + }); + expect(item!.billing!.notes.join(" ")).not.toContain( + "does not mean usage is free", + ); + }); + + it.each([403, 500, "missing"] as const)( + "does not call a catalog service unpriced after a %s catalog lookup", + async (failure) => { + responses.set( + "/catalog?include_all=true", + failure === "missing" ? { entries: [] } : unavailable(failure), + ); + const [catalogService, customService] = + await loadConfiguredServiceInsights( + [ + personal, + { + ...personal, + id: "custom", + source: "custom", + catalog_service_id: null, + catalog_service_slug: null, + }, + ], + "person", + ); + expect( + catalogService!.billing!.credit_billing_configured, + ).toBeUndefined(); + expect(catalogService!.billing!.charge_status).toBe("conditional"); + expect(customService!.billing!.credit_billing_configured).toBe(false); + expect(customService!.billing!.charge_status).toBe("not_charged"); + }, + ); + it("does not classify an OAuth login as a supplied developer app", () => { + const bill = configuredBilling({ + ...personal, + credential_type: "oauth2", + api_key_id: "oauth-token", + }); + expect(bill.provider_billing).toBe("unknown"); + expect(bill.credential_label).toBe("Connected account · app unverified"); + expect(bill.credential_label).not.toContain("BYOK"); + }); + + it("reads legacy catalog credit billing without claiming the caller has been charged", async () => { + responses.set("/catalog?include_all=true", { + entries: [ + { + slug: "openai", + billing: { + platform_billable: true, + platform_metric: "requests", + platform_pricing: { + credits_per_unit: "0.2", + sync_status: "synced", + }, + }, + }, + ], + }); + const [item] = await loadConfiguredServiceInsights([personal], "person"); + expect(item!.billing).toMatchObject({ + credit_billing_configured: true, + charge_status: "conditional", + rates: [{ credits_per_unit: "0.2", metric: "requests" }], + }); + }); + + it("does not apply legacy pricing over a different credential lane", () => { + const bill = configuredBilling(personal, { + slug: "openai", + billing: { + platform_billable: true, + platform_metric: "requests", + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.5", + sync_status: "synced", + }, + }, + }); + expect(bill.credit_billing_configured).toBe(false); + expect(bill.rates).toEqual([]); + expect(bill.charge_status).toBe("not_charged"); + }); + + it("does not advertise platform-only charges on a known user-supplied API key", () => { + const bill = configuredBilling(personal, { + slug: "openai", + billing: { + platform_billable: true, + platform_charge_nyxid_credentials_only: true, + byok_pricing: { + metric: "requests", + credits_per_unit: "0.5", + sync_status: "synced", + }, + }, + }); + expect(bill.credit_billing_configured).toBe(false); + expect(bill.rates).toEqual([]); + }); + + it("preserves unreported plan rates without inventing a numeric price", () => { + const bill = configuredBilling(personal, { + slug: "openai", + billing: { + platform_billable: true, + platform_metric: "requests", + }, + }); + expect(bill.credit_billing_configured).toBe(true); + expect(bill.rates[0]?.credits_per_unit).toBeNull(); + }); + + it("accepts the deployed key list's omitted expiry and zero binding count", async () => { + const { + expires_at: _expiry, + bindings_count: _count, + ...withoutOptionalFields + } = key; + void _expiry; + void _count; + responses.set("/api-keys", { keys: [withoutOptionalFields] }); + const [insight] = await loadConfiguredServiceInsights([personal], "person"); + expect(insight!.usage!.access.incomplete).toBe(false); + expect(insight!.usage!.access.keys).toMatchObject([ + { id: "key", name: "Codex", credential_override: false }, + ]); + }); + it("uses live scope and catalog metadata when the insights route is absent, without inventing recorded use or settled billing", async () => { + const { result } = mount(); + await waitFor(() => expect(result.current.status).toBe("ready")); + const insight = result.current.connections.get(personal.id)!; + expect(insight.usage?.access.keys.map((item) => item.name)).toEqual([ + "Codex", + ]); + expect(insight.usage?.access.basis).toBe("configuration"); + expect(insight.usage?.activity.tracking).toBe("unavailable"); + expect(insight.billing).toMatchObject({ + status: "conditional", + context: "configuration", + account: null, + payer_rule: "Your personal account", + rates: [{ credits_per_unit: "0.12", sync_status: "synced" }], + }); + }); + it("reclassifies a connection when its credential binding changes without changing its id", async () => { + const { result, rerender } = mount(); + await waitFor(() => + expect( + result.current.connections.get(personal.id)?.billing + ?.credential_supplier, + ).toBe("own"), + ); + rerender({ + connections: [{ ...personal, credential_binding: "platform" }], + }); + await waitFor(() => + expect( + result.current.connections.get(personal.id)?.billing + ?.credential_supplier, + ).toBe("nyxid"), + ); + }); + it("resolves an unverified OAuth supplier when the same connection gains provenance", async () => { + const oauth: KeyInfo = { ...personal, credential_type: "oauth2" }; + const { result, rerender } = mount([oauth]); + await waitFor(() => + expect( + result.current.connections.get(oauth.id)?.billing?.credential_supplier, + ).toBe("unknown"), + ); + rerender({ connections: [{ ...oauth, oauth_app_source: "platform" }] }); + await waitFor(() => + expect( + result.current.connections.get(oauth.id)?.billing?.credential_supplier, + ).toBe("nyxid"), + ); + }); + it("reclassifies a reconnected OAuth row whose failed refresh recovers", async () => { + const failed: KeyInfo = { + ...personal, + credential_type: "oauth2", + connection_id: "conn", + status: "failed", + connection_status: "expired", + }; + const { result, rerender } = mount([failed]); + await waitFor(() => + expect( + result.current.connections.get(failed.id)?.billing?.credential_supplier, + ).toBe("unknown"), + ); + rerender({ + connections: [ + { ...failed, status: "active", connection_status: "active" }, + ], + }); + await waitFor(() => + expect( + result.current.connections.get(failed.id)?.billing?.credential_supplier, + ).toBe("nyxid"), + ); + }); + it.each([403, 500])( + "does not fall back on an insights %s failure", + async (status) => { + responses.set("/service-insights?ids=personal", unavailable(status)); + const { result } = mount(); + await waitFor(() => + expect(result.current.status).toBe( + status === 403 ? "restricted" : "error", + ), + ); + expect(get).toHaveBeenCalledTimes(1); + expect(result.current.connections.size).toBe(0); + }, + ); + it("clears prior insight data when a refresh loses permission", async () => { + const { result } = mount(); + await waitFor(() => expect(result.current.connections.size).toBe(1)); + responses.set("/service-insights?ids=personal", unavailable(403)); + act(() => result.current.refresh()); + await waitFor(() => expect(result.current.status).toBe("restricted")); + expect(result.current.connections.size).toBe(0); + }); + it("does not hide an authorization failure in a second batch behind an unsupported first batch", async () => { + get + .mockRejectedValueOnce(unavailable()) + .mockRejectedValueOnce(unavailable(403)); + const { result } = mount( + Array.from({ length: 101 }, (_, index) => ({ + ...personal, + id: `connection-${index}`, + })), + ); + await waitFor(() => expect(result.current.status).toBe("restricted")); + expect(get).toHaveBeenCalledTimes(2); + expect(result.current.connections.size).toBe(0); + }); + it("matches exact connection ids, scope, active keys, expiry and purpose", async () => { + responses.set("/api-keys", { + keys: [ + key, + { ...key, id: "catalog-only", allowed_service_ids: ["openai"] }, + { ...key, id: "expired", expires_at: "2000-01-01T00:00:00Z" }, + { ...key, id: "inactive", is_active: false }, + { ...key, id: "read-only", scopes: "account:read" }, + { ...key, id: "scheduled", purpose: "scheduled_invocation" }, + { ...key, id: "unknown-purpose", purpose: undefined }, + { ...key, id: "wide", scopes: "llm:proxy", allow_all_services: true }, + ], + }); + const [a, duplicate] = await loadConfiguredServiceInsights( + [personal, { ...personal, id: "duplicate" }], + "person", + ); + expect(a!.usage!.access.keys.map((item) => item.id)).toEqual([ + "key", + "wide", + ]); + expect(duplicate!.usage!.access.keys.map((item) => item.id)).toEqual([ + "wide", + ]); + expect(a!.usage!.access.incomplete).toBe(true); + }); + it("keeps organization keys within their owner and personal keys within permitted org scope", async () => { + responses.set("/api-keys", { + keys: [{ ...key, allow_all_services: true }], + }); + const [a, b, c] = await loadConfiguredServiceInsights( + [ + personal, + org, + { + ...org, + id: "excluded", + credential_source: { + ...org.credential_source!, + type: "org", + org_id: "other", + org_name: "Other", + role: "viewer", + allowed: false, + }, + }, + ], + "person", + ); + expect(a!.usage!.access.keys.map((item) => item.id)).toEqual(["key"]); + expect(b!.usage!.access.keys.map((item) => item.id)).toEqual([ + "key", + "org-key", + ]); + expect(c!.usage!.access.keys).toEqual([]); + expect(get).not.toHaveBeenCalledWith("/api-keys?org_id=other"); + }); + it("matches overrides by both key and exact connection, and preserves unknown overrides on failures", async () => { + responses.set("/api-keys", { + keys: [ + { ...key, id: "bound", allow_all_services: true, bindings_count: 1 }, + { ...key, id: "unknown", bindings_count: 1 }, + ], + }); + responses.set("/api-keys/bound/bindings", { + bindings: [{ api_key_id: "bound", user_service_id: "personal" }], + }); + const [a, b] = await loadConfiguredServiceInsights( + [personal, { ...personal, id: "duplicate" }], + "person", + ); + expect( + a!.usage!.access.keys.map((item) => item.credential_override), + ).toEqual([true, null]); + expect( + b!.usage!.access.keys.map((item) => item.credential_override), + ).toEqual([false]); + }); + it("marks an unavailable key inventory instead of reporting zero accessible keys", async () => { + responses.set("/api-keys", unavailable(403)); + const [a] = await loadConfiguredServiceInsights([personal], "person"); + expect(a!.usage!.access.visibility).toBe("unavailable"); + expect(a!.usage!.access.incomplete).toBe(true); + }); + it("keeps platform credential supply separate from who pays and flags unsynced prices", () => { + const bill = configuredBilling({ + ...org, + credential_binding: "platform", + platform_key_pricing: { + metric: "requests", + credits_per_unit: "0.01", + sync_status: "pending", + components: [ + { + metric: "output_tokens", + credits_per_unit: "0.000000000012", + sync_status: "failed", + }, + ], + }, + }); + expect(bill.payer_rule).toBe("Acting user's personal account"); + expect(bill.account).toBeNull(); + expect(bill.rates.map((rate) => rate.sync_status)).toEqual([ + "pending", + "failed", + ]); + expect(bill.charge_status).toBe("conditional"); + expect(configuredBilling(org).payer_rule).toBe("ChronoAI · organization"); + expect(configuredBilling(personal).charge_status).not.toBe("not_charged"); + }); +}); diff --git a/frontend/src/lib/service-insights-compat.ts b/frontend/src/lib/service-insights-compat.ts new file mode 100644 index 000000000..27dfb9127 --- /dev/null +++ b/frontend/src/lib/service-insights-compat.ts @@ -0,0 +1,380 @@ +import { api } from "@/lib/api-client"; +import { + configuredAgentKeyListSchema, + configuredBindingsSchema, + configuredOrgListSchema, + configuredCatalogSchema, + type ConfiguredAgentKey, + type ConfiguredCatalogEntry, + type ServiceBillingExplanation, + type ServiceInsight, +} from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; +import { + configuredUsageCharge, + configuredPlatformPrice, + connectionCredentialClass, + credentialSupplier, + serviceBillingConfigured, +} from "./service-billing-config"; + +// Read only metadata from the deployed inventory APIs. This projection describes +// configuration; the execution resolver remains authoritative for ACLs and costs. +type ManagedKey = ConfiguredAgentKey & { + ownerId: string; + overrides: ReadonlySet | null; +}; + +export function configuredBilling( + connection: KeyInfo, + catalog?: ConfiguredCatalogEntry, +): ServiceBillingExplanation { + const credentialClass = connectionCredentialClass(connection); + const platform = credentialClass === "nyxid_managed_master"; + const supplier = credentialSupplier(connection); + const sharedOAuth = credentialClass === "nyxid_platform_oauth_app"; + const serviceConfigured = serviceBillingConfigured(connection, catalog); + const node = Boolean(connection.node_id || connection.has_node_binding); + const userCredential = + !platform && + (connection.auth_method !== "none" || node) && + Boolean( + connection.credential_binding === "user" || + connection.api_key_id || + connection.node_id || + connection.has_node_binding, + ); + const org = + connection.credential_source?.type === "org" + ? connection.credential_source + : null; + const oauth = ["oauth2", "device_code"].includes(connection.credential_type); + const billing = catalog?.billing; + const configuredLane = platform + ? configuredPlatformPrice(connection, catalog) + : userCredential + ? (connection.byok_pricing ?? + catalog?.byok_pricing ?? + billing?.byok_pricing) + : undefined; + const hasLanes = Boolean( + configuredLane || + connection.byok_pricing || + connection.platform_key_pricing || + catalog?.byok_pricing || + catalog?.platform_key?.pricing || + billing?.byok_pricing || + billing?.platform_key_pricing, + ); + const ownApiKey = + credentialClass === "user_owned" && + connection.credential_type === "api_key"; + const ownOAuthApp = oauth && supplier === "own"; + const excludedFromPlatformCharge = + billing?.platform_charge_nyxid_credentials_only === true && + !platform && + supplier !== "nyxid" && + (supplier === "own" || supplier === "none"); + const lane = excludedFromPlatformCharge ? undefined : configuredLane; + const legacyConfigured = + !hasLanes && + !excludedFromPlatformCharge && + billing?.platform_billable === true; + const credentialLabel = platform + ? "NyxID key" + : sharedOAuth + ? "NyxID OAuth app" + : node + ? supplier === "own" + ? "Node credential" + : "Node credential · supplier unverified" + : connection.auth_method === "none" + ? "No credential" + : ownOAuthApp + ? `${org ? "Organization" : "Your"} OAuth app (BYOK)` + : oauth + ? "Connected account · app unverified" + : ownApiKey + ? `${org ? "Organization" : "Your"} API key (BYOK)` + : supplier === "own" + ? `${org ? "Organization" : "Your"} credential (BYOK)` + : "Credential supplier unverified"; + const creditBillingConfigured = configuredUsageCharge(connection, catalog); + return { + status: "conditional", + credential_class: credentialClass ?? null, + credential_label: credentialLabel, + account: null, + payer_rule: + creditBillingConfigured === false + ? "Not billable by NyxID" + : platform + ? "Acting user's personal account" + : !userCredential + ? "Determined at execution" + : org + ? `${org.org_name} · organization` + : "Your personal account", + charge_status: + creditBillingConfigured === false ? "not_charged" : "conditional", + credit_billing_configured: creditBillingConfigured, + service_billing_configured: serviceConfigured, + credential_supplier: supplier, + rates: + lane && + creditBillingConfigured !== undefined && + (credentialClass || oauth) + ? [lane, ...(lane.components ?? [])].map((rate) => ({ + layer: "platform", + metric: rate.metric, + credits_per_unit: rate.credits_per_unit, + currency: "credits", + source: "configuration", + sync_status: rate.sync_status ?? "unknown", + })) + : legacyConfigured && + creditBillingConfigured === true && + billing?.platform_metric + ? [ + { + layer: "platform", + metric: billing.platform_metric, + credits_per_unit: + billing.platform_pricing?.credits_per_unit ?? null, + currency: "credits", + source: "configuration", + sync_status: billing.platform_pricing?.sync_status ?? "unknown", + }, + ] + : [], + provider_billing: + platform || sharedOAuth + ? "nyxid_credential" + : connection.auth_method === "none" + ? "no_credential" + : supplier === "own" + ? "separate_provider_account" + : "unknown", + context: "configuration", + notes: [ + ...(serviceConfigured === undefined + ? ["Billing configuration unavailable for this service."] + : []), + ...(sharedOAuth && serviceConfigured && creditBillingConfigured === false + ? [ + "NyxID supplies the OAuth app, but this connection has no configured NyxID usage charge. The service’s platform-key price does not apply to OAuth.", + ] + : []), + creditBillingConfigured === false + ? "No NyxID usage charges are configured for this connection. The provider may charge separately." + : "Configured billing for the connection default. The payer and applicable charges are verified at execution; agent credential overrides can change them.", + ...(oauth && supplier === "unknown" + ? [ + "Signing in does not identify the developer app's owner. This server does not report whether this connection uses your app or NyxID's app.", + ] + : []), + ...(billing?.platform_charge_nyxid_credentials_only + ? [ + "Configured NyxID charges are limited to NyxID-supplied credentials or OAuth apps; eligibility must be verified at execution.", + ] + : []), + ...(legacyConfigured + ? [ + "The catalog configures NyxID credit billing for this service. Caller eligibility and the active plan rate are verified at execution.", + ] + : []), + ...(platform && billing?.resale_billable + ? [ + "Provider usage through NyxID is configured for credit billing; its rate is not reported here.", + ] + : []), + ...(lane && + [lane, ...(lane.components ?? [])].some( + (rate) => rate.sync_status !== "synced", + ) + ? [ + "Unsynced prices are not confirmed as active. Current billing rules apply until price synchronization completes.", + ] + : []), + ...(lane || legacyConfigured || creditBillingConfigured === false + ? [] + : [ + "This server does not report a credential-specific rate here. This does not mean usage is free.", + ]), + ], + }; +} + +function permission( + key: ManagedKey, + connection: KeyInfo, + actorId: string, +): string | null { + const org = + connection.credential_source?.type === "org" + ? connection.credential_source + : null; + const ownerId = org?.org_id ?? actorId; + if (key.ownerId !== ownerId && !(key.ownerId === actorId && org?.allowed)) + return null; + if (key.allow_all_services) return "all_services"; + if (key.allowed_service_ids.includes(connection.id)) + return "selected_service"; + if ( + key.ownerId === ownerId && + key.allow_auto_connected_services && + (connection.auto_connected || connection.credential_binding === "platform") + ) + return "platform_services"; + return null; +} + +export async function loadConfiguredServiceInsights( + connections: readonly KeyInfo[], + actorId: string, +): Promise { + const [personal, organizations, catalog] = await Promise.allSettled([ + api + .get("/api-keys") + .then((data) => configuredAgentKeyListSchema.parse(data).keys), + api + .get("/orgs") + .then((data) => configuredOrgListSchema.parse(data).orgs), + api + .get("/catalog?include_all=true") + .then((data) => configuredCatalogSchema.parse(data).entries), + ]); + const relevantOrgs = new Set( + connections.flatMap((connection) => + connection.credential_source?.type === "org" + ? [connection.credential_source.org_id] + : [], + ), + ); + const adminIds = + organizations.status === "fulfilled" + ? organizations.value + .filter( + (org) => org.your_role === "admin" && relevantOrgs.has(org.id), + ) + .map((org) => org.id) + : []; + const orgKeys = await Promise.allSettled( + adminIds.map(async (id) => ({ + ownerId: id, + keys: configuredAgentKeyListSchema.parse( + await api.get(`/api-keys?org_id=${encodeURIComponent(id)}`), + ).keys, + })), + ); + const inventories = [ + ...(personal.status === "fulfilled" + ? [{ ownerId: actorId, keys: personal.value }] + : []), + ...orgKeys.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ), + ]; + const now = Date.now(); + const managed: ManagedKey[] = inventories.flatMap(({ ownerId, keys }) => + keys + .filter( + (key) => + key.purpose === "general" && + key.is_active && + (!key.expires_at || Date.parse(key.expires_at) > now) && + key.scopes + .split(/\s+/) + .some((scope) => ["proxy", "proxy:*", "llm:proxy"].includes(scope)), + ) + .map((key) => ({ + ...key, + ownerId, + overrides: key.bindings_count === 0 ? new Set() : null, + })), + ); + const withOverrides = managed.filter( + (key) => + key.bindings_count > 0 && + connections.some((connection) => permission(key, connection, actorId)), + ); + // Bound fanout for accounts with many agent keys; each request is shared + // across all visible connections rather than repeated per card. + for (let i = 0; i < withOverrides.length; i += 6) { + await Promise.allSettled( + withOverrides.slice(i, i + 6).map(async (key) => { + const data = configuredBindingsSchema.parse( + await api.get( + `/api-keys/${encodeURIComponent(key.id)}/bindings`, + ), + ); + key.overrides = new Set( + data.bindings + .filter((binding) => binding.api_key_id === key.id) + .map((binding) => binding.user_service_id), + ); + }), + ); + } + const prices = new Map( + catalog.status === "fulfilled" + ? catalog.value.map((entry) => [entry.slug, entry]) + : [], + ); + return connections.map((connection) => { + const orgId = + connection.credential_source?.type === "org" + ? connection.credential_source.org_id + : null; + const orgIndex = orgId ? adminIds.indexOf(orgId) : -1; + const incomplete = + personal.status !== "fulfilled" || + organizations.status !== "fulfilled" || + (orgIndex >= 0 && orgKeys[orgIndex]?.status !== "fulfilled") || + inventories.some((inventory) => + inventory.keys.some((key) => !key.purpose), + ); + const keys = managed.flatMap((key) => { + const reason = permission(key, connection, actorId); + return reason + ? [ + { + id: key.id, + name: key.name, + platform: key.platform ?? null, + owner_id: key.ownerId, + permission: reason, + credential_override: key.overrides?.has(connection.id) ?? null, + }, + ] + : []; + }); + const price = prices.get(connection.catalog_service_slug ?? ""); + return { + service_id: connection.id, + billing: configuredBilling(connection, price), + usage: { + access: { + visibility: + personal.status !== "fulfilled" && !inventories.length + ? "unavailable" + : orgIndex >= 0 + ? "managed_keys" + : "own_keys", + basis: "configuration", + incomplete, + keys, + truncated: false, + }, + activity: { + visibility: "unavailable", + tracking: "unavailable", + period_days: 30, + request_count: 0, + requests: [], + truncated: false, + }, + }, + }; + }); +} diff --git a/frontend/src/lib/service-insights.ts b/frontend/src/lib/service-insights.ts new file mode 100644 index 000000000..6e297f432 --- /dev/null +++ b/frontend/src/lib/service-insights.ts @@ -0,0 +1,344 @@ +import { metricLabel } from "@/schemas/billing-metrics"; +import type { + ServiceBillingExplanation, + ServiceCaller, + ServiceInsight, + ConnectionActivity, +} from "@/schemas/service-insights"; +import type { KeyInfo } from "@/types/keys"; +import type { ServiceInsightsState } from "@/hooks/use-service-insights"; + +export function insightStatusLabel( + state: ServiceInsightsState["status"], + field: "Access" | "Activity" | "Billing", +): string { + if (state === "loading") return `Loading ${field.toLowerCase()}…`; + if (state === "restricted") return `${field} restricted`; + if (state === "error") return `${field} couldn't load`; + return `${field} not reported`; +} + +export function credentialLabel( + connection: KeyInfo, + billing?: ServiceBillingExplanation | null, +): string { + if (billing) { + if (billing.status === "restricted" || billing.status === "unavailable") + return billing.credential_label; + if ( + billing.context === "configuration" || + billing.context === "agent_key" || + billing.credential_supplier === "unknown" + ) + return billing.credential_label; + if (billing.credential_class === "nyxid_platform_oauth_app") + return "NyxID developer app"; + if (billing.credential_class === "nyxid_managed_master") return "NyxID key"; + if ( + ["user_owned", "agent_override_user_owned"].includes( + billing.credential_class ?? "", + ) && + connection.credential_type === "api_key" + ) + return `${connection.credential_source?.type === "org" ? "Organization" : "Your"} API key (BYOK)`; + return billing.credential_label; + } + if (connection.credential_binding === "platform") return "NyxID credential"; + if (connection.node_id || connection.has_node_binding) + return "Node credential"; + if (connection.auth_method === "none") return "No credential"; + if (connection.credential_source?.type === "org") + return "Organization credential"; + return "Your credential"; +} + +export function billingAccountLabel( + billing?: ServiceBillingExplanation | null, +): string { + if (!billing) return "Billing not reported"; + if (billing.status === "restricted") return "Billing restricted"; + if (billing.context === "configuration" && billing.payer_rule) + return billing.payer_rule; + if (billing.charge_status === "not_charged") return "Not charged by NyxID"; + if (billing.status === "unavailable") return "Billing unavailable"; + if (billing.account) + return billing.account.kind === "personal" + ? "Personal account" + : `${billing.account.name} · organization`; + return billing.status === "conditional" + ? "Depends on execution" + : "Billing unavailable"; +} + +export function rateLabel(billing: ServiceBillingExplanation): string { + if (billing.charge_status === "not_charged") return "No NyxID charge"; + if (billing.charge_status === "restricted") return "Rates restricted"; + if (!billing.rates.length) return "Rate not reported"; + if (billing.context === "configuration") { + const pending = billing.rates.some((rate) => rate.sync_status !== "synced"); + const rate = billing.rates[0]!; + const label = + billing.rates.length === 1 + ? rate.credits_per_unit == null + ? "Plan rate not reported" + : `${rate.credits_per_unit} credits / ${metricLabel(rate.metric, 1)} · configured` + : `${billing.rates.length} configured rates`; + return `${label}${pending ? " · sync unconfirmed" : ""}`; + } + if (billing.rates.length > 1) return `${billing.rates.length} metered rates`; + const rate = billing.rates[0]!; + return rate.credits_per_unit == null + ? "Rate set at execution" + : `${rate.credits_per_unit} credits / ${metricLabel(rate.metric, 1)}`; +} + +export function callerKindLabel(kind: string): string { + return ( + ( + { + api_key: "Agent key", + agent_key: "Agent key", + app: "Application", + oauth_app: "OAuth app", + session: "Session", + access_token: "Access token", + service_account: "Service account", + delegated: "Delegated token", + relay: "Relay", + unknown: "Caller not recorded", + } as Record + )[kind] ?? kind.replaceAll("_", " ") + ); +} +export function callerLabel(caller: ServiceCaller): string { + return caller.name || callerKindLabel(caller.kind); +} +export function outcomeLabel(outcome: string): string { + return ( + ( + { + completed: "Completed", + failed: "Failed", + denied: "Denied", + disconnected: "Disconnected", + response_received: "Response received", + connection_opened: "Connection opened", + outcome_unknown: "Outcome unknown", + unknown: "Outcome unknown", + } as Record + )[outcome] ?? "Outcome unknown" + ); +} +export function accessReasonLabel(reason: string): string { + return ( + ( + { + selected: "Selected service", + explicit: "Selected service", + selected_service: "Selected service", + all_services: "All services", + platform_services: "Platform services", + auto_connected: "Platform services", + } as Record + )[reason] ?? reason.replaceAll("_", " ") + ); +} + +export function accessCountLabel(access: ConnectionActivity["access"]): string { + if (access.visibility === "unavailable") return "Key access unavailable"; + if (!access.keys.length && (access.incomplete || access.truncated)) + return "Key access incomplete"; + return `${access.keys.length}${access.incomplete || access.truncated ? "+" : ""} agent ${access.keys.length === 1 ? "key" : "keys"}`; +} + +export function latestRecordedUse(usage?: ConnectionActivity | null) { + if (usage?.activity.last_used !== undefined) + return usage.activity.last_used ?? undefined; + return usage?.activity.requests + .filter( + (request) => + request.outcome === "completed" || + (request.response_status != null && + ["response_received", "connection_opened", "failed"].includes( + request.outcome, + )), + ) + .sort((a, b) => b.occurred_at.localeCompare(a.occurred_at))[0]; +} + +export function recordedSourceLabel( + request: NonNullable>, + connection: KeyInfo, +): string { + if (request.source?.kind === "platform") return "Platform"; + if (request.source?.kind === "personal") return "Personal"; + if (request.source?.kind === "org") { + const source = connection.credential_source; + return source?.type === "org" && source.org_id === request.source.owner_id + ? `Organization · ${source.org_name}` + : "Organization"; + } + return "Layer not recorded"; +} + +export function providerBillingLabel( + billing?: ServiceBillingExplanation | null, +): string { + if (billing?.status === "restricted") return "Provider billing restricted"; + if (billing?.status === "unavailable") return "Provider billing unavailable"; + if (billing?.credential_supplier === "unknown") + return "Credential supplier unverified"; + if (billing?.credential_class === "nyxid_platform_oauth_app") + return "NyxID supplies the developer app; signing in connects your provider account"; + if (billing?.credential_supplier === "nyxid") + return "NyxID supplies the key or developer app"; + if (billing?.provider_billing === "separate_provider_account") + return "Provider billed separately"; + if (billing?.provider_billing === "nyxid_credential") + return "NyxID supplies the credential"; + if (billing?.provider_billing === "no_credential") + return "No provider credential"; + return "Provider billing not reported"; +} + +export function billingModelLabel( + billing?: ServiceBillingExplanation | null, +): string { + if (!billing) return "Billing not reported"; + if (billing.status === "restricted") return "Billing restricted"; + if (billing.status === "unavailable") return "Billing unavailable"; + if (billing.service_billing_configured === false) + return "Not billable by NyxID"; + if (billing.credit_billing_configured === false) + return "No NyxID charge for this connection"; + switch (billing.charge_status) { + case "usage_based": + return "NyxID credits"; + case "not_charged": + return "No NyxID charge"; + case "restricted": + return "Billing restricted"; + case "unavailable": + return "Billing unavailable"; + default: + return billing.context === "configuration" && + (billing.credit_billing_configured || billing.rates.length > 0) + ? "NyxID credits · configured" + : "Credit billing unverified"; + } +} + +export function summarizeBillingModel( + status: ServiceInsightsState["status"], + insights: readonly (ServiceInsight | undefined)[], +): string { + if (status !== "ready") return insightStatusLabel(status, "Billing"); + const models = new Set( + insights.map((item) => billingModelLabel(item?.billing)), + ); + if (!models.size) return "Billing not reported"; + if (models.size === 1) return [...models][0]!; + if ( + [...models].some( + (model) => + model.startsWith("Billing") || model === "Credit billing unverified", + ) + ) + return "Billing partly reported"; + return "Charges vary by connection"; +} + +export function nyxidChargeLabel(billing: ServiceBillingExplanation): string { + if (billing.status === "restricted") return "NyxID fees restricted"; + if (billing.status === "unavailable") return "NyxID fees unavailable"; + if (billing.charge_status === "not_charged") return "No NyxID charge"; + if (!billing.rates.length) return "Rate not reported"; + return `Rate: ${rateLabel(billing)}`; +} + +export function billingExplanation(billing: ServiceBillingExplanation): string { + if (billing.status === "restricted" || billing.status === "unavailable") + return billingModelLabel(billing); + const supply = + billing.credential_supplier === "unknown" + ? "The supplier of this connection's key or developer app is unverified." + : billing.credential_supplier === "nyxid" && + billing.credential_class === "agent_override_user_owned" + ? "The selected agent credential uses NyxID's developer app." + : billing.credential_class === "nyxid_platform_oauth_app" + ? "NyxID supplies the developer app. Signing into your provider account is not BYOK." + : billing.provider_billing === "nyxid_credential" + ? "NyxID supplies the provider key." + : billing.provider_billing === "separate_provider_account" + ? "Your supplied credential uses a separate provider account. Any NyxID fees are additional to the provider's charges." + : billing.provider_billing === "no_credential" + ? "No provider credential is required." + : "The supplier of this connection's key or developer app is unverified."; + const charges = + billing.credit_billing_configured === false + ? "No NyxID usage charges are configured for this connection." + : billing.charge_status === "not_charged" + ? "NyxID does not charge this caller for this connection." + : billing.charge_status === "usage_based" + ? "NyxID meters usage against the billing account shown." + : billing.credit_billing_configured || billing.rates.length + ? "NyxID credit billing is configured; caller eligibility and the active rate are verified at execution." + : "NyxID credit charges have not been verified. A missing rate does not mean usage is free."; + return `${supply} ${charges}`; +} + +export function summarizeBillingDetail( + insights: readonly (ServiceInsight | undefined)[], +): string { + const bills = insights.map((item) => item?.billing); + if (!bills.length || bills.some((bill) => !bill)) return "Rates unavailable"; + if (bills.some((bill) => bill?.status === "restricted")) + return "Billing details restricted"; + if (bills.some((bill) => bill?.status === "unavailable")) + return "Billing details unavailable"; + const first = bills[0]!; + const rates = bills.every( + (bill) => + JSON.stringify(bill!.rates) === JSON.stringify(first.rates) && + bill!.charge_status === first.charge_status, + ) + ? nyxidChargeLabel(first) + : "NyxID fees vary by connection"; + const providers = bills.every( + (bill) => bill!.provider_billing === first.provider_billing, + ) + ? providerBillingLabel(first) + : "Provider billing varies"; + return `${rates} · ${providers}`; +} + +export function summarizeBilling( + insights: readonly (ServiceInsight | undefined)[], +): string { + if (!insights.length || insights.some((item) => !item?.billing)) + return "Billing not reported"; + const bills = insights.map((item) => item!.billing!); + if (bills.some((bill) => bill.status === "restricted")) + return "Billing restricted"; + if (bills.every((bill) => bill.charge_status === "not_charged")) + return "Not charged by NyxID"; + const first = bills[0]!; + if (bills.every((bill) => bill.status === "unavailable")) + return "Billing unavailable"; + if (bills.some((bill) => bill.status === "unavailable")) + return "Varies by connection"; + if ( + bills.some( + (bill) => + bill.account?.id !== first.account?.id || + bill.payer_rule !== first.payer_rule || + bill.charge_status !== first.charge_status, + ) + ) + return "Varies by connection"; + if (first.context === "configuration") + return `Expected: ${billingAccountLabel(first)}`; + return first.account + ? `For you: ${billingAccountLabel(first)}` + : "Depends on execution"; +} diff --git a/frontend/src/lib/service-pool-display.test.ts b/frontend/src/lib/service-pool-display.test.ts new file mode 100644 index 000000000..84e10da6b --- /dev/null +++ b/frontend/src/lib/service-pool-display.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; +import { defaultFailoverPolicy, type ServicePool } from "@/schemas/pools"; +import { + orderedPoolMembers, + poolFailoverLabel, + poolFailoverSummary, + poolMemberStatus, +} from "./service-pool-display"; +const members = [ + { + user_service_id: "backup", + enabled: true, + priority: 20, + weight: 4, + model: "model-b", + same_api_compatible: true, + }, + { + user_service_id: "primary", + enabled: true, + priority: 0, + weight: 2, + model: "model-a", + }, + { + user_service_id: "disabled", + enabled: false, + priority: 10, + weight: 1, + model: null, + }, +]; +const pool = { + strategy: "priority", + members, + failover: null, + is_active: true, +} as ServicePool; +describe("saved pool presentation", () => { + it("summarizes configured failover without treating rotation or disabled pools as backups", () => { + expect(poolFailoverSummary([pool])).toBe("Up to 3 attempts"); + const rotation = { ...pool, strategy: "weighted" as const }; + const disabled = { ...pool, is_active: false }; + expect(poolFailoverSummary([rotation])).toBe("Off · single attempt"); + expect(poolFailoverSummary([disabled])).toBe("Pool disabled"); + expect(poolFailoverLabel(disabled)).toBe("Pool disabled · no failover"); + expect(poolFailoverSummary([pool, rotation, disabled])).toBe( + "On in 1 of 3 pools", + ); + expect(poolFailoverSummary([rotation, disabled])).toBe("Off in all pools"); + expect(poolFailoverSummary([disabled, disabled])).toBe("Pools disabled"); + expect( + poolFailoverSummary([ + { + ...pool, + members: members.map((member) => ({ ...member, enabled: false })), + }, + ]), + ).toBe("No enabled members"); + for (const failover of [ + { ...defaultFailoverPolicy, max_attempts: 1 }, + { ...defaultFailoverPolicy, retry_on: [] }, + ]) + expect(poolFailoverSummary([{ ...pool, failover }])).toBe( + "Off · single attempt", + ); + }); + it("uses default failover for null policy and honors disabled retry policies", () => { + expect(poolFailoverLabel(pool)).toBe("Failover · up to 3 attempts"); + expect( + poolFailoverLabel({ + ...pool, + failover: { ...defaultFailoverPolicy, max_attempts: 1 }, + }), + ).toBe("Failover off"); + expect( + poolFailoverLabel({ + ...pool, + failover: { ...defaultFailoverPolicy, retry_on: [] }, + }), + ).toBe("Failover off"); + for (const strategy of ["round_robin", "weighted"] as const) + expect(poolFailoverLabel({ ...pool, strategy })).toBe( + "Single attempt · no failover", + ); + }); + it("orders priority tiers numerically, preserves ties and leaves rotation order unchanged", () => { + expect(orderedPoolMembers(pool).map((m) => m.user_service_id)).toEqual([ + "primary", + "disabled", + "backup", + ]); + expect(orderedPoolMembers({ ...pool, strategy: "round_robin" })).toEqual( + members, + ); + expect( + orderedPoolMembers({ + strategy: "priority", + members: members.map((member) => ({ ...member, priority: 0 })), + }).map((m) => m.user_service_id), + ).toEqual(["backup", "primary", "disabled"]); + }); + it("does not infer health from an enabled member", () => { + expect(poolMemberStatus(members[0]!)).toBe("Not inspected"); + expect(poolMemberStatus(members[2]!)).toBe("Member disabled"); + }); +}); diff --git a/frontend/src/lib/service-pool-display.ts b/frontend/src/lib/service-pool-display.ts new file mode 100644 index 000000000..1281c6153 --- /dev/null +++ b/frontend/src/lib/service-pool-display.ts @@ -0,0 +1,79 @@ +import { + defaultFailoverPolicy, + type PoolCandidate, + type ServicePool, + type ServicePoolMember, +} from "@/schemas/pools"; + +export function poolStrategyLabel(pool: ServicePool): string { + return pool.strategy === "priority" + ? "Priority routing" + : pool.strategy === "weighted" + ? "Weighted rotation" + : "Round-robin rotation"; +} + +export function poolFailoverLabel(pool: ServicePool): string { + if (!pool.is_active) return "Pool disabled · no failover"; + if (pool.strategy !== "priority") return "Single attempt · no failover"; + const policy = pool.failover ?? defaultFailoverPolicy; + return policy.max_attempts === 1 || !policy.retry_on.length + ? "Failover off" + : `Failover · up to ${policy.max_attempts} attempts`; +} + +function configuredAttempts(pool: ServicePool): number { + if (!pool.is_active || !pool.members.some((member) => member.enabled)) + return 0; + if (pool.strategy !== "priority") return 1; + const policy = pool.failover ?? defaultFailoverPolicy; + return policy.retry_on.length ? policy.max_attempts : 1; +} + +export function poolFailoverSummary(pools: readonly ServicePool[]): string { + if (!pools.length) return "No pool"; + if (pools.length === 1) { + const pool = pools[0]!; + if (!pool.is_active) return "Pool disabled"; + const attempts = configuredAttempts(pool); + if (!attempts) return "No enabled members"; + return attempts > 1 ? `Up to ${attempts} attempts` : "Off · single attempt"; + } + if (pools.every((pool) => !pool.is_active)) return "Pools disabled"; + const enabled = pools.filter((pool) => configuredAttempts(pool) > 1).length; + return enabled + ? `On in ${enabled} of ${pools.length} pools` + : "Off in all pools"; +} + +export function orderedPoolMembers( + pool: Pick, +): ServicePoolMember[] { + return pool.strategy === "priority" + ? [...pool.members].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)) + : [...pool.members]; +} + +const reasons: Record = { + unavailable: "Connection unavailable", + inactive: "Service disabled", + disabled: "Member disabled", + cooldown: "Cooling down", + incompatible_protocol: "Incompatible protocol", + compatibility_declaration_required: "Compatibility confirmation needed", + inference_protocol_required: "Inference metadata required", + operation_unsupported: "Operation not permitted", + node_upgrade_required: "Node upgrade required", + node_offline: "Node offline", + unsupported_transport: "Unsupported transport", +}; +export function poolMemberStatus( + member: ServicePoolMember, + candidate?: PoolCandidate, +): string { + if (!member.enabled) return "Member disabled"; + if (!candidate) return "Not inspected"; + if (candidate.reason) + return reasons[candidate.reason] ?? candidate.reason.replaceAll("_", " "); + return candidate.eligible ? "Eligible" : "Not eligible"; +} diff --git a/frontend/src/lib/service-routing-preview.test.ts b/frontend/src/lib/service-routing-preview.test.ts new file mode 100644 index 000000000..53bc40229 --- /dev/null +++ b/frontend/src/lib/service-routing-preview.test.ts @@ -0,0 +1,190 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import type { KeyInfo, CatalogEntry } from "@/types/keys"; +import { buildRoutingGroups } from "./service-routing-preview"; + +function key(overrides: Partial = {}): KeyInfo { + return { + id: "personal", + label: "My OpenAI", + slug: "openai-my-account", + endpoint_id: "endpoint", + api_key_id: "credential", + catalog_service_id: "openai-id", + catalog_service_slug: "openai", + catalog_service_name: "OpenAI", + is_active: true, + status: "active", + credential_type: "api_key", + auth_method: "bearer", + auth_key_name: "Authorization", + auto_connected: false, + node_id: null, + node_priority: 0, + expires_at: null, + last_used_at: null, + error_message: null, + created_at: "2026-01-01", + service_type: "http", + ssh_host: null, + ssh_port: null, + ssh_ca_public_key: null, + ssh_allowed_principals: null, + ssh_certificate_ttl_minutes: null, + ws_frame_injections: [], + credential_source: { type: "personal" }, + ...overrides, + }; +} + +const org = key({ + id: "org", + label: "Team OpenAI", + credential_source: { + type: "org", + org_id: "team", + org_name: "Chrono", + role: "member", + allowed: true, + }, +}); +const platform = key({ + id: "platform", + label: "Shared OpenAI", + auto_connected: true, +}); +function group(keys: KeyInfo[]) { + return buildRoutingGroups(keys, [], [], Date.parse("2026-09-17"))[0]!; +} + +beforeEach(() => localStorage.clear()); + +describe("routing from actual connections", () => { + it("retains all real records and their provenance without inventing a platform source", () => { + const result = group([org, key()]); + expect(result.slug).toBe("openai"); + expect(result.candidates.map((candidate) => candidate.key.id)).toEqual([ + "personal", + "org", + ]); + expect( + result.candidates.some((candidate) => candidate.tier === "platform"), + ).toBe(false); + }); + + it("includes a platform source only when a platform service record exists", () => { + const result = group([key(), platform]); + expect( + result.candidates.find((candidate) => candidate.tier === "platform")?.key + .id, + ).toBe("platform"); + }); + + it("recognizes explicit platform bindings from current main", () => { + const candidate = group([ + key({ + credential_binding: "platform", + auto_connected: false, + api_key_id: null, + platform_key_available: true, + }), + ]).candidates[0]!; + expect(candidate.tier).toBe("platform"); + expect(candidate.reason).toBe("Not verified"); + }); + + it("keeps a configured platform record unavailable when the backend withdraws availability", () => { + const candidate = group([ + key({ credential_binding: "platform", platform_key_available: false }), + ]).candidates[0]!; + expect(candidate.state).toBe("unavailable"); + expect(candidate.reason).toBe("Platform unavailable"); + }); + + it("does not mistake an offered platform key for this connection's selected credential", () => { + const candidate = group([ + key({ + credential_binding: "user", + auto_connected: true, + platform_key_available: true, + }), + ]).candidates[0]!; + expect(candidate.tier).toBe("personal"); + }); + + it("does not turn catalog entries or shared OAuth apps into connected services", () => { + const catalog = [ + { + slug: "openai", + name: "OpenAI", + service_type: "http", + has_platform_oauth_credentials: true, + }, + ] as CatalogEntry[]; + expect(buildRoutingGroups([], catalog, [], 0)).toEqual([]); + }); + + it("keeps unrelated custom connections separate", () => { + const result = buildRoutingGroups( + [ + key(), + key({ + id: "custom", + catalog_service_id: null, + catalog_service_slug: null, + }), + ], + [], + [], + 0, + ); + expect(result).toHaveLength(2); + expect( + result.find((item) => item.id === "connection:custom")?.canonical, + ).toBe(false); + }); + + it.each([ + { is_active: false }, + { credential_missing: true }, + { status: "revoked" }, + { status: "failed" }, + { connection_status: "expired" as const }, + { expires_at: "2026-01-01" }, + { + credential_source: { + type: "org" as const, + org_id: "team", + org_name: "Chrono", + role: "viewer" as const, + allowed: false, + }, + }, + ])( + "recognizes a known blocker without hiding its repairable card: %j", + (overrides) => { + const result = group([key(overrides)]); + expect(result.candidates).toHaveLength(1); + expect(result.candidates[0]?.state).toBe("unavailable"); + }, + ); + + it.each([ + { connected: true, status: "active" }, + { auto_connected: true }, + { node_id: "node", node_status: "online" }, + { node_id: "node", node_status: "offline" }, + { credential_source: undefined }, + { api_key_id: null }, + { auth_method: "none", api_key_id: null }, + { + credential_type: "oauth2", + expires_at: "2026-01-01", + connection_status: "active" as const, + }, + ])( + "does not claim working status from incomplete evidence: %j", + (overrides) => { + expect(group([key(overrides)]).candidates[0]?.state).toBe("unverified"); + }, + ); +}); diff --git a/frontend/src/lib/service-routing-preview.ts b/frontend/src/lib/service-routing-preview.ts new file mode 100644 index 000000000..0c8e0c505 --- /dev/null +++ b/frontend/src/lib/service-routing-preview.ts @@ -0,0 +1,126 @@ +import type { KeyInfo, CatalogEntry } from "@/types/keys"; +import type { UserServiceResponse } from "@/schemas/keys"; +import type { CredentialSource } from "@/schemas/orgs"; + +export type RoutingTier = "personal" | "org" | "platform" | "unknown"; + +export interface RoutingCandidate { + key: KeyInfo; + tier: RoutingTier; + owner: string; + state: "unavailable" | "unverified"; + reason: string; + source?: CredentialSource; +} + +export interface RoutingGroup { + id: string; + name: string; + slug: string; + canonical: boolean; + candidates: RoutingCandidate[]; +} + +export function classifyConnection( + key: KeyInfo, + services: readonly UserServiceResponse[], + now: number, +): RoutingCandidate { + const source = + key.credential_source ?? + services.find((service) => service.id === key.id)?.credential_source; + const isPlatform = + key.credential_binding === "platform" || + (key.credential_binding === undefined && key.auto_connected); + const tier: RoutingTier = isPlatform + ? "platform" + : (source?.type ?? "unknown"); + const owner = isPlatform + ? "NyxID platform" + : source?.type === "org" + ? source.org_name + : source + ? "You" + : "Unknown owner"; + const result = ( + state: RoutingCandidate["state"], + reason: string, + ): RoutingCandidate => ({ key, tier, owner, state, reason, source }); + if (!key.is_active) return result("unavailable", "Disabled"); + if (source?.type === "org" && !source.allowed) + return result("unavailable", "No access"); + if (isPlatform && key.platform_key_available === false) + return result("unavailable", "Platform unavailable"); + if (key.credential_missing) + return result("unavailable", "Credential missing"); + if (["revoked", "failed", "refresh_failed"].includes(key.status)) + return result("unavailable", "Reconnect needed"); + if ( + key.status === "pending_auth" || + (key.requires_connection && key.connected === false) + ) + return result("unavailable", "Finish connecting"); + if (key.node_id && key.node_status !== "online") + return result("unverified", "Node check needed"); + if (key.connection_status === "expired" || key.status === "expired") + return result("unavailable", "Reconnect needed"); + if (key.expires_at && Date.parse(key.expires_at) <= now) { + return key.credential_type === "oauth2" + ? result("unverified", "Refresh needed") + : result("unavailable", "Credential expired"); + } + if (tier === "unknown") return result("unverified", "Owner not reported"); + if ( + !isPlatform && + !key.api_key_id && + !key.node_id && + key.auth_method !== "none" + ) + return result("unverified", "Credential check needed"); + // GET /keys reports configuration and credential state, not a successful + // provider check. Explicit platform bindings also need live verification. + return result("unverified", "Not verified"); +} + +export function buildRoutingGroups( + keys: readonly KeyInfo[], + catalog: readonly CatalogEntry[], + services: readonly UserServiceResponse[], + now: number, +): RoutingGroup[] { + const groups = new Map(); + for (const key of keys) { + const id = key.catalog_service_id + ? `catalog:${key.catalog_service_id}` + : `connection:${key.id}`; + let group = groups.get(id); + if (!group) { + const entry = catalog.find( + (item) => item.slug === key.catalog_service_slug, + ); + group = { + id, + name: entry?.name ?? key.catalog_service_name ?? key.label, + slug: key.catalog_service_slug ?? key.slug, + canonical: Boolean( + key.catalog_service_id && + key.catalog_service_slug && + key.service_type === "http", + ), + candidates: [], + }; + groups.set(id, group); + } + group.candidates.push(classifyConnection(key, services, now)); + } + const rank = { personal: 0, org: 1, platform: 2, unknown: 3 }; + for (const group of groups.values()) { + group.candidates.sort( + (a, b) => + rank[a.tier] - rank[b.tier] || a.key.label.localeCompare(b.key.label), + ); + } + // A catalog entry (including shared OAuth app credentials) is not a + // connection. Only records returned by /keys create groups or options. + return [...groups.values()].sort((a, b) => a.name.localeCompare(b.name)); +} diff --git a/frontend/src/lib/service-usage.test.ts b/frontend/src/lib/service-usage.test.ts new file mode 100644 index 000000000..ed9e1cdaa --- /dev/null +++ b/frontend/src/lib/service-usage.test.ts @@ -0,0 +1,171 @@ +import { describe, expect, it } from "vitest"; +import type { BillingUsageRow } from "@/schemas/billing"; +import { + serviceUsageDaily, + serviceUsageSummary, + serviceUsageTrend, +} from "./service-usage"; + +const row = (overrides: Partial): BillingUsageRow => ({ + service_slug: "llm-deepseek", + metric: "tokens", + lago_metric_code: "platform_svc_llm-deepseek_pk", + layer: "platform", + quantity: 0, + requests: 0, + bytes: 0, + events: 0, + lago_acked: true, + billable: true, + ...overrides, +}); + +describe("serviceUsageSummary", () => { + it("counts token-metered calls from events and splits funding", () => { + const summary = serviceUsageSummary( + [ + row({ + quantity: 300, + events: 2, + api_key_id: "k1", + api_key_name: "heca", + estimated_credits: "0.0003", + grant_credits: "0.0003", + }), + row({ + quantity: 210, + events: 1, + estimated_credits: "0.00021", + wallet_credits: "0.00021", + }), + row({ service_slug: "other", quantity: 99, events: 9 }), + ], + "llm-deepseek", + ); + expect(summary).toMatchObject({ + calls: 3, + quantities: [{ metric: "tokens", quantity: 510 }], + charged: "0.00051", + grant: "0.0003", + wallet: "0.00021", + allowance: "0", + billable: true, + agents: [ + { name: "heca", calls: 2 }, + { name: null, calls: 1 }, + ], + }); + }); + + it("reports metering-only usage without a charge and unsettled charges as unknown", () => { + expect( + serviceUsageSummary( + [row({ billable: false, metric: "requests", quantity: 4, events: 4 })], + "llm-deepseek", + ), + ).toMatchObject({ calls: 4, billable: false, charged: "0" }); + expect( + serviceUsageSummary([row({ events: 1 })], "llm-deepseek")?.charged, + ).toBeNull(); + expect(serviceUsageSummary([row({})], "api-twitter")).toBeNull(); + }); +}); + +describe("serviceUsageDaily", () => { + const now = new Date("2026-10-06T12:00:00Z"); + it("zero-fills each UTC day of the period, oldest first", () => { + const series = serviceUsageDaily( + [ + row({ + day: "2026-10-04T00:00:00Z", + events: 2, + quantity: 20, + estimated_credits: "0.1", + }), + row({ + day: "2026-10-06T00:00:00Z", + events: 1, + quantity: 5, + estimated_credits: "0.05", + }), + ], + "llm-deepseek", + "7d", + now, + ); + expect(series?.map((d) => [d.day, d.calls, d.charged])).toEqual([ + ["2026-09-30", 0, "0"], + ["2026-10-01", 0, "0"], + ["2026-10-02", 0, "0"], + ["2026-10-03", 0, "0"], + ["2026-10-04", 2, "0.1"], + ["2026-10-05", 0, "0"], + ["2026-10-06", 1, "0.05"], + ]); + }); + + it("has no daily view for 24h or a server without day buckets", () => { + expect(serviceUsageDaily([], "llm-deepseek", "24h", now)).toBeNull(); + expect( + serviceUsageDaily([row({ events: 1 })], "llm-deepseek", "30d", now), + ).toBeNull(); + expect( + serviceUsageDaily( + [row({ service_slug: "other" })], + "llm-deepseek", + "30d", + now, + ), + ).toBeNull(); + expect(serviceUsageDaily([], "llm-deepseek", "30d", now)).toHaveLength(30); + }); +}); + +describe("serviceUsageSummary models", () => { + it("ranks named models by calls and skips unnamed rows", () => { + const summary = serviceUsageSummary( + [ + row({ model: "deepseek-chat", events: 2 }), + row({ model: "deepseek-reasoner", events: 5 }), + row({ model: "deepseek-chat", events: 1 }), + row({ events: 4 }), + ], + "llm-deepseek", + ); + expect(summary?.models).toEqual([ + { name: "deepseek-reasoner", calls: 5 }, + { name: "deepseek-chat", calls: 3 }, + ]); + }); +}); + +describe("serviceUsageTrend", () => { + it("differences nested period totals into per-day windows", () => { + const calls = (events: number) => [row({ events })]; + const trend = serviceUsageTrend( + { "24h": calls(2), "7d": calls(8), "30d": calls(31), "90d": calls(151) }, + "llm-deepseek", + ); + expect( + trend.map(({ label, calls, perDay }) => [label, calls, perDay]), + ).toEqual([ + ["30–90 days ago", 120, 2], + ["7–30 days ago", 23, 1], + ["1–7 days ago", 6, 1], + ["Last 24 hours", 2, 2], + ]); + }); + + it("never reports a negative window when periods race", () => { + const trend = serviceUsageTrend( + { + "24h": [row({ events: 3 })], + "7d": [row({ events: 2 })], + "30d": [], + "90d": [], + }, + "llm-deepseek", + ); + expect(trend.every((slot) => slot.calls >= 0)).toBe(true); + }); +}); diff --git a/frontend/src/lib/service-usage.ts b/frontend/src/lib/service-usage.ts new file mode 100644 index 000000000..47c807a0b --- /dev/null +++ b/frontend/src/lib/service-usage.ts @@ -0,0 +1,187 @@ +import type { BillingUsageRow } from "@/schemas/billing"; +import { decimalCredits, exactCredits, parseCredits } from "./credits"; + +export interface ServiceUsageSummary { + /** Recorded calls; token-metered rows report `requests: 0`, so count events. */ + readonly calls: number; + /** Metered quantity per unit, largest first. */ + readonly quantities: readonly { metric: string; quantity: number }[]; + /** Total NyxID credits charged; null when a charged row has no settled amount. */ + readonly charged: string | null; + readonly wallet: string; + readonly grant: string; + readonly allowance: string; + /** Any row was chargeable; false means metering only. */ + readonly billable: boolean; + readonly agents: readonly { name: string | null; calls: number }[]; + /** Calls per model, most first; empty when no row names a model. */ + readonly models: readonly { name: string; calls: number }[]; +} + +function sum(values: readonly (string | null)[]): string { + return decimalCredits( + values.reduce((total, value) => total + parseCredits(value ?? "0"), 0n), + ); +} + +/** + * `/billing/usage` records usage by the proxy slug the caller used, for the + * caller's own billing account. Connections sharing a slug share these rows. + */ +export function serviceUsageSummary( + rows: readonly BillingUsageRow[], + slug: string, +): ServiceUsageSummary | null { + const matching = rows.filter((row) => row.service_slug === slug); + if (!matching.length) return null; + const quantities = new Map(); + const agents = new Map(); + const models = new Map(); + for (const row of matching) { + if (row.model) + models.set(row.model, (models.get(row.model) ?? 0) + row.events); + quantities.set( + row.metric, + (quantities.get(row.metric) ?? 0) + row.quantity, + ); + const name = row.api_key_id + ? (row.api_key_name ?? "Unnamed agent key") + : null; + agents.set(name, (agents.get(name) ?? 0) + row.events); + } + const estimates = matching.map((row) => + row.billable + ? exactCredits(row.estimated_credits, row.estimated_credits_micros) + : "0", + ); + return { + calls: matching.reduce((total, row) => total + row.events, 0), + quantities: [...quantities] + .map(([metric, quantity]) => ({ metric, quantity })) + .sort((a, b) => b.quantity - a.quantity), + charged: estimates.includes(null) ? null : sum(estimates), + wallet: sum( + matching.map((row) => + exactCredits(row.wallet_credits, row.wallet_credits_micros), + ), + ), + grant: sum( + matching.map((row) => + exactCredits(row.grant_credits, row.grant_credits_micros), + ), + ), + allowance: sum( + matching.map((row) => + exactCredits(row.allowance_credits, row.allowance_credits_micros), + ), + ), + billable: matching.some((row) => row.billable), + agents: [...agents] + .map(([name, calls]) => ({ name, calls })) + .sort((a, b) => b.calls - a.calls), + models: [...models] + .map(([name, calls]) => ({ name, calls })) + .sort((a, b) => b.calls - a.calls), + }; +} + +export interface ServiceUsageDay { + /** UTC calendar day, `YYYY-MM-DD`. */ + readonly day: string; + readonly calls: number; + readonly quantities: readonly { metric: string; quantity: number }[]; + /** NyxID credits charged that day; null when a charged row is unsettled. */ + readonly charged: string | null; + /** How that day's charge was paid: wallet, free credit grants, free allowances. */ + readonly wallet: string; + readonly grant: string; + readonly allowance: string; +} + +const PERIOD_DAYS: Record = { "7d": 7, "30d": 30, "90d": 90 }; + +/** + * Zero-filled UTC daily series from `bucket=day` rows, oldest first. Null when + * the period has no daily view or the server did not split rows by day. + */ +export function serviceUsageDaily( + rows: readonly BillingUsageRow[], + slug: string, + period: string, + now = new Date(), +): ServiceUsageDay[] | null { + const days = PERIOD_DAYS[period]; + const matching = rows.filter((row) => row.service_slug === slug); + // Older servers ignore `bucket=day`; any undated row means no daily split. + if (!days || rows.some((row) => !row.day)) return null; + const byDay = new Map(); + for (const row of matching) { + const key = row.day!.slice(0, 10); + byDay.set(key, [...(byDay.get(key) ?? []), row]); + } + const today = Date.UTC( + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate(), + ); + return Array.from({ length: days }, (_, i) => { + const day = new Date(today - (days - 1 - i) * 86_400_000) + .toISOString() + .slice(0, 10); + const summary = serviceUsageSummary(byDay.get(day) ?? [], slug); + return { + day, + calls: summary?.calls ?? 0, + quantities: summary?.quantities ?? [], + charged: summary ? summary.charged : "0", + wallet: summary?.wallet ?? "0", + grant: summary?.grant ?? "0", + allowance: summary?.allowance ?? "0", + }; + }); +} + +export interface ServiceUsageWindow { + readonly label: string; + /** Days the window spans, for a fair per-day comparison. */ + readonly days: number; + readonly calls: number; + readonly perDay: number; +} + +/** + * Coarse trend for servers without day buckets: the nested 24h/7d/30d/90d + * totals, differenced into non-overlapping windows, newest last. + */ +export function serviceUsageTrend( + totals: { + readonly "24h": readonly BillingUsageRow[]; + readonly "7d": readonly BillingUsageRow[]; + readonly "30d": readonly BillingUsageRow[]; + readonly "90d": readonly BillingUsageRow[]; + }, + slug: string, +): ServiceUsageWindow[] { + const calls = (rows: readonly BillingUsageRow[]) => + serviceUsageSummary(rows, slug)?.calls ?? 0; + const [day, week, month, quarter] = [ + calls(totals["24h"]), + calls(totals["7d"]), + calls(totals["30d"]), + calls(totals["90d"]), + ]; + // Periods are fetched separately, so a call landing between requests can + // make a longer period briefly smaller; never report negative usage. + const windows: [string, number, number][] = [ + ["30–90 days ago", 60, Math.max(0, quarter - month)], + ["7–30 days ago", 23, Math.max(0, month - week)], + ["1–7 days ago", 6, Math.max(0, week - day)], + ["Last 24 hours", 1, day], + ]; + return windows.map(([label, days, total]) => ({ + label, + days, + calls: total, + perDay: total / days, + })); +} diff --git a/frontend/src/lib/service-view.test.ts b/frontend/src/lib/service-view.test.ts new file mode 100644 index 000000000..0602c959c --- /dev/null +++ b/frontend/src/lib/service-view.test.ts @@ -0,0 +1,243 @@ +import { describe, expect, it } from "vitest"; +import { matchingConnections } from "./service-view"; +import { + DEFAULT_SERVICE_FILTERS, + serviceViewSchema, + sameServiceFilters, +} from "@/schemas/service-view"; +import type { ServiceConnectionGroup } from "./service-groups"; +import type { KeyInfo } from "@/types/keys"; + +// Matching cases below exercise every connection, including auto-connected ones. +const SHOWN = { ...DEFAULT_SERVICE_FILTERS, show_auto_connected: true }; + +const group: ServiceConnectionGroup = { + id: "catalog:openai", + name: "OpenAI", + slug: "openai", + iconSlug: "openai", + iconUrl: null, + description: null, + connections: [ + { + id: "personal", + label: "Personal development", + slug: "openai-personal", + credential_source: { type: "personal" }, + is_active: true, + status: "expired", + service_type: "http", + auto_connected: false, + }, + { + id: "org", + label: "Team", + slug: "openai-team", + credential_source: { type: "org", org_id: "org-1", org_name: "Chrono" }, + is_active: false, + status: "active", + service_type: "http", + auto_connected: false, + }, + { + id: "platform", + label: "Platform", + slug: "openai", + credential_binding: "platform", + is_active: true, + service_type: "http", + auto_connected: true, + }, + ] as KeyInfo[], +}; + +describe("service view matching", () => { + it("lists a service whose only connection is auto-connected once shown, in Personal view", () => { + const autoOnly = { + ...group, + connections: group.connections.filter((key) => key.auto_connected), + }; + expect(matchingConnections(autoOnly, DEFAULT_SERVICE_FILTERS)).toEqual([]); + expect(matchingConnections(autoOnly, SHOWN).map((key) => key.id)).toEqual([ + "platform", + ]); + }); + it("hides auto-connected connections by default", () => { + expect( + matchingConnections(group, DEFAULT_SERVICE_FILTERS).map((key) => key.id), + ).toEqual(["personal", "org"]); + }); + it("keeps organization and platform counterparts for services with a personal connection", () => { + expect(matchingConnections(group, SHOWN).map((key) => key.id)).toEqual([ + "personal", + "org", + "platform", + ]); + const withoutPersonal = { + ...group, + connections: group.connections.slice(1), + }; + expect( + matchingConnections(withoutPersonal, DEFAULT_SERVICE_FILTERS), + ).toEqual([]); + expect( + matchingConnections(withoutPersonal, SHOWN).map((key) => key.id), + ).toEqual(["org", "platform"]); + }); + it("applies explicit filters to counterparts without requiring them to match the personal row", () => { + expect( + matchingConnections(group, { + ...SHOWN, + organization_ids: ["org-1"], + }).map((key) => key.id), + ).toEqual(["org"]); + expect( + matchingConnections(group, { + ...SHOWN, + show_auto_connected: false, + }).map((key) => key.id), + ).toEqual(["personal", "org"]); + expect( + matchingConnections(group, { + ...SHOWN, + search: "platform", + }).map((key) => key.id), + ).toEqual(["platform"]); + expect( + matchingConnections(group, { + ...SHOWN, + source: "platform", + }).map((key) => key.id), + ).toEqual(["platform"]); + }); + it("combines filters on the same connection and uses service state, not credential status", () => { + expect( + matchingConnections(group, { + ...SHOWN, + source: "org", + state: "enabled", + }), + ).toEqual([]); + expect( + matchingConnections(group, { + ...SHOWN, + source: "all", + state: "disabled", + }).map((key) => key.id), + ).toEqual(["org"]); + expect( + matchingConnections(group, { + ...SHOWN, + source: "personal", + state: "enabled", + }).map((key) => key.id), + ).toEqual(["personal", "platform"]); + }); + it("searches both service and connection identity without modifying the group", () => { + expect( + matchingConnections(group, { + ...SHOWN, + source: "all", + search: " Chrono ", + }).map((key) => key.id), + ).toEqual(["org"]); + expect( + matchingConnections(group, { + ...SHOWN, + source: "all", + search: "OPENAI", + }), + ).toHaveLength(3); + expect( + matchingConnections(group, { + ...SHOWN, + source: "all", + service_type: "ssh", + }), + ).toEqual([]); + expect( + matchingConnections(group, { + ...SHOWN, + source: "all", + show_auto_connected: false, + }), + ).toHaveLength(2); + expect(group.connections).toHaveLength(3); + }); +}); + +describe("saved service selections", () => { + it("migrates legacy single selections and compares unordered sets", () => { + const legacy = { + search: "", + source: "all", + state: "all", + service_type: "all", + show_auto_connected: true, + }; + expect( + serviceViewSchema.parse({ + ...legacy, + organization_id: "org-1", + service_group_id: "catalog:openai", + }), + ).toEqual({ + ...SHOWN, + source: "all", + organization_ids: ["org-1"], + service_group_ids: ["catalog:openai"], + }); + expect(serviceViewSchema.parse(legacy)).toEqual({ + ...SHOWN, + source: "all", + }); + expect( + serviceViewSchema.parse({ + ...legacy, + organization_id: null, + service_group_id: null, + }), + ).toEqual({ ...SHOWN, source: "all" }); + expect( + sameServiceFilters( + { + ...SHOWN, + source: "all", + organization_ids: ["one", "two"], + }, + { + ...SHOWN, + source: "all", + organization_ids: ["two", "one"], + }, + ), + ).toBe(true); + expect( + serviceViewSchema.safeParse({ + ...SHOWN, + source: "all", + organization_ids: Array(101).fill("org"), + }).success, + ).toBe(false); + }); + it("matches any selected organization and service, requiring both filter groups", () => { + const filters = { + ...SHOWN, + source: "all" as const, + organization_ids: ["org-1", "org-2"], + service_group_ids: ["catalog:openai", "catalog:codex"], + }; + expect(matchingConnections(group, filters).map((key) => key.id)).toEqual([ + "org", + ]); + expect( + matchingConnections(group, { + ...filters, + service_group_ids: ["catalog:codex"], + }), + ).toEqual([]); + expect( + matchingConnections(group, { ...filters, organization_ids: ["org-2"] }), + ).toEqual([]); + }); +}); diff --git a/frontend/src/lib/service-view.ts b/frontend/src/lib/service-view.ts new file mode 100644 index 000000000..58230b9d9 --- /dev/null +++ b/frontend/src/lib/service-view.ts @@ -0,0 +1,67 @@ +import type { KeyInfo } from "@/types/keys"; +import type { ServiceViewFilters } from "@/schemas/service-view"; +import type { ServiceConnectionGroup } from "@/lib/service-groups"; + +export function connectionSource( + key: KeyInfo, +): "personal" | "org" | "platform" { + if ( + key.credential_binding === "platform" || + (key.credential_binding === undefined && key.auto_connected) + ) + return "platform"; + return key.credential_source?.type === "org" ? "org" : "personal"; +} + +export function connectionSourceLabel(key: KeyInfo): string { + if (connectionSource(key) === "platform") { + return key.auth_method === "none" ? "Platform managed" : "NyxID platform"; + } + return key.credential_source?.type === "org" + ? key.credential_source.org_name + : "Personal"; +} + +export function matchingConnections( + group: ServiceConnectionGroup, + filters: ServiceViewFilters, +): readonly KeyInfo[] { + if ( + filters.service_group_ids.length && + !filters.service_group_ids.includes(group.id) + ) + return []; + // Auto-connected rows are provisioned onto your own account, so once shown + // they qualify a service for the Personal view like a personal connection. + if ( + filters.source === "personal" && + !group.connections.some( + (key) => + connectionSource(key) === "personal" || + (filters.show_auto_connected && key.auto_connected), + ) + ) + return []; + const needle = filters.search.trim().toLowerCase(); + const groupMatches = [group.name, group.slug ?? ""].some((value) => + value.toLowerCase().includes(needle), + ); + return group.connections.filter( + (key) => + (!filters.organization_ids.length || + (key.credential_source?.type === "org" && + filters.organization_ids.includes(key.credential_source.org_id))) && + (filters.source === "all" || + filters.source === "personal" || + connectionSource(key) === filters.source) && + (filters.state === "all" || + (filters.state === "enabled" ? key.is_active : !key.is_active)) && + (filters.service_type === "all" || + key.service_type === filters.service_type) && + (filters.show_auto_connected || !key.auto_connected) && + (groupMatches || + [key.label, key.slug, connectionSourceLabel(key)].some((value) => + value.toLowerCase().includes(needle), + )), + ); +} diff --git a/frontend/src/lib/studio-breadcrumbs.test.ts b/frontend/src/lib/studio-breadcrumbs.test.ts index 43ec3cf0a..e4ea417e4 100644 --- a/frontend/src/lib/studio-breadcrumbs.test.ts +++ b/frontend/src/lib/studio-breadcrumbs.test.ts @@ -74,6 +74,18 @@ describe("Studio breadcrumbs", () => { expect(location.search).toMatchObject({ tab: "service-accounts" }); }); + it("returns a service overview to the AI Services tab with its name", () => { + expect( + buildStudioBreadcrumbs("/keys/services/catalog:cat-1", { + "/keys/services/catalog:cat-1": "OpenAI", + }), + ).toEqual([ + { label: expect.any(String), to: "/keys", search: expect.any(Object) }, + { label: expect.any(String), to: "/keys", search: { tab: "services" } }, + { label: "OpenAI" }, + ]); + }); + it("returns agent keys to the Agent Keys tab", () => { expect(buildStudioBreadcrumbs("/keys/api-key/key-1")[1]).toEqual({ label: "Agent Keys", diff --git a/frontend/src/lib/studio-breadcrumbs.ts b/frontend/src/lib/studio-breadcrumbs.ts index 9d52d728b..2fdd91abd 100644 --- a/frontend/src/lib/studio-breadcrumbs.ts +++ b/frontend/src/lib/studio-breadcrumbs.ts @@ -287,6 +287,9 @@ export function buildStudioBreadcrumbs( subsection(ORG_SECTION), ]); } + if (/^\/keys\/services\/[^/]+$/.test(path)) { + return finish([...keysParent("services"), named(path, "Service")]); + } if (/^\/keys\/[^/]+$/.test(path)) { return finish([ ...keysParent("services"), diff --git a/frontend/src/pages/key-detail.tsx b/frontend/src/pages/key-detail.tsx index f0bcedf11..e368922ae 100644 --- a/frontend/src/pages/key-detail.tsx +++ b/frontend/src/pages/key-detail.tsx @@ -2583,6 +2583,15 @@ function KeyDetailView({ keyId }: { readonly keyId: string }) { onRetry={refetch} /> )} + {import.meta.env.DEV && import.meta.env.VITE_ROUTING_PREVIEW === "1" && ( +
+

Local preview · full details from production. Account changes and execution are disabled.

+ Back to services +
+ )} + + ← All {keyInfo.catalog_service_name ?? keyInfo.label} service details +
diff --git a/frontend/src/pages/keys.test.tsx b/frontend/src/pages/keys.test.tsx index d2d494e4f..7d3457051 100644 --- a/frontend/src/pages/keys.test.tsx +++ b/frontend/src/pages/keys.test.tsx @@ -1,14 +1,40 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; -import { render, screen, waitFor } from "@testing-library/react"; +import { + render as renderDom, + screen, + waitFor, + fireEvent, + within, +} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { KeyInfo } from "@/types/keys"; -const { mockNavigate, state } = vi.hoisted(() => ({ +function render(ui: ReactNode) { + const client = new QueryClient({ + defaultOptions: { mutations: { retry: false }, queries: { retry: false } }, + }); + return renderDom(ui, { + wrapper: ({ children }) => ( + {children} + ), + }); +} + +const { mockNavigate, mockPoolOwner, state } = vi.hoisted(() => ({ mockNavigate: vi.fn(), + mockPoolOwner: vi.fn(), // Mutable containers populated per-test before render. state: { - search: {} as { tab?: string; slug?: string; action?: string }, + search: {} as { + tab?: string; + slug?: string; + action?: string; + view?: string; + pool?: string; + org?: string; + }, keys: [] as KeyInfo[], keysLoading: false, keysError: null as unknown, @@ -22,12 +48,17 @@ vi.mock("@tanstack/react-router", () => ({ children, to, params, + ...props }: { readonly children: ReactNode; readonly to: string; readonly params?: Record; + readonly "aria-label"?: string; }) => ( - + {children} ), @@ -36,6 +67,7 @@ vi.mock("@tanstack/react-router", () => ({ })); vi.mock("@/hooks/use-keys", () => ({ + useCatalog: () => ({ data: [], refetch: vi.fn() }), useKeys: () => ({ data: state.keys, isLoading: state.keysLoading, @@ -52,6 +84,51 @@ vi.mock("@/hooks/use-nodes", () => ({ useNodes: () => ({ data: state.nodes }), })); +vi.mock("@/hooks/use-pools", () => ({ + useUpdateServicePool: () => ({ mutateAsync: vi.fn(), isPending: false }), + useDeleteServicePool: () => ({ mutateAsync: vi.fn(), isPending: false }), + usePoolHealth: () => ({ + data: { candidates: [] }, + isError: false, + isLoading: false, + }), + useServicePools: (orgId?: string) => { + mockPoolOwner(orgId); + return { + data: [ + { + id: "pool-one", + name: "My pool", + slug: "my-pool", + strategy: "round_robin", + members: [{ user_service_id: "key-1", enabled: true, weight: 1 }], + is_active: true, + }, + ], + }; + }, +})); + +vi.mock("@/hooks/use-service-routing-pools", () => ({ + useServiceRoutingPools: () => ({ + pools: [], + loading: false, + incomplete: false, + }), +})); +vi.mock("@/hooks/use-orgs", () => ({ + useOrgs: () => ({ + data: [ + { + id: "team-id", + display_name: "Research", + slug: "research", + your_role: "admin", + }, + ], + }), +})); + // Heavy children — stubbed to assert wiring (open state, presence), not driven. vi.mock("@/components/providers/codex-connection", () => ({ CodexConnectionSection: () =>
, @@ -109,10 +186,19 @@ vi.mock("@/components/orgs/org-avatar", () => ({ })); import { KeysPage } from "./keys"; +import { useServiceCardView } from "@/stores/service-card-view-store"; + +function expandConnections() { + for (const button of screen.queryAllByRole("button", { + name: /^Expand .+ connections$/, + })) + fireEvent.click(button); +} function makeKey(overrides: Partial = {}): KeyInfo { return { id: "key-1", + credential_source: { type: "personal" }, label: "My OpenAI", slug: "openai", endpoint_url: "https://api.openai.com", @@ -146,6 +232,11 @@ function makeKey(overrides: Partial = {}): KeyInfo { describe("KeysPage", () => { beforeEach(() => { vi.clearAllMocks(); + useServiceCardView.setState({ + accountId: undefined, + expanded: [], + filters: undefined, + }); state.search = {}; state.keys = []; state.keysLoading = false; @@ -154,23 +245,95 @@ describe("KeysPage", () => { state.nodes = []; }); - it("defaults to the External Services tab and lists personal services as a flat grid", () => { + it("defaults to one collapsed card per service with duplicates inside", async () => { state.keys = [ - makeKey({ id: "key-1", label: "My OpenAI", slug: "openai" }), - makeKey({ id: "key-2", label: "My GitHub", slug: "github" }), + makeKey({ id: "key-1", label: "Personal OpenAI", slug: "openai" }), + makeKey({ id: "key-2", label: "Work OpenAI", slug: "openai-work" }), ]; + render(); + const group = screen.getByRole("region", { name: "OpenAI" }); + expect(within(group).getByText("2 connections")).toBeVisible(); + expect(screen.queryByText("openai")).not.toBeInTheDocument(); + expect(screen.queryByTestId("api-key-table")).not.toBeInTheDocument(); + expandConnections(); + expect(within(group).getByText("openai")).toBeVisible(); + expect(within(group).getByText("openai-work")).toBeVisible(); + expect( + within(group).getByRole("link", { + name: "View Personal OpenAI connection details (Personal)", + }), + ).toHaveAttribute("href", "/keys/$keyId:key-1"); + expect( + within(group).getByRole("link", { + name: "View Work OpenAI connection details (Personal)", + }), + ).toHaveAttribute("href", "/keys/$keyId:key-2"); + }); + it("uses the connection table and full detail navigation in the routing view", async () => { + state.search = { view: "routing" }; + state.keys = [ + makeKey({ + label: "My preserved connection", + slug: "my-openai", + credential_source: { type: "personal" }, + }), + ]; render(); + await screen.findByRole("button", { name: "Expand OpenAI connections" }); + expandConnections(); + expect(screen.getByText("https://api.openai.com")).toBeVisible(); + expect(screen.getByText("my-openai")).toBeVisible(); + expect( + screen.queryByText("Details", { selector: "summary" }), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("link", { + name: "Configure My preserved connection (Personal)", + }), + ).toHaveAttribute("href", "/keys/$keyId:key-1"); + expect( + screen.getByRole("link", { name: "View all OpenAI service details" }), + ).toHaveAttribute("href", "/keys/services/$groupId:catalog:cat-1"); + expect( + screen.queryByRole("button", { name: "Individual cards" }), + ).not.toBeInTheDocument(); + }); - // The personal-only path renders cards directly with no section header. - expect(screen.getByText("My OpenAI")).toBeInTheDocument(); - expect(screen.getByText("My GitHub")).toBeInTheDocument(); - // Default tab is "services", so the Agent Keys table is not mounted. - expect(screen.queryByTestId("api-key-table")).not.toBeInTheDocument(); - // Proxy slug for an HTTP service is rendered as /proxy/s/{slug}. - expect(screen.getByText("/proxy/s/openai")).toBeInTheDocument(); + it("opens saved pool routing and members inside the expanded pool card", async () => { + state.search = { view: "routing", tab: "pools" }; + state.keys = [makeKey({ credential_source: { type: "personal" } })]; + render(); + expect(await screen.findByText("My pool")).toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: "View route" })); + expect( + screen.getByRole("table", { name: "My pool route members" }), + ).toBeVisible(); + expect(screen.getByText("My OpenAI")).toBeVisible(); + expect(screen.getByText("/api/v1/proxy/s/my-pool")).toBeVisible(); + expect(screen.getByRole("button", { name: "Create Pool" })).toBeVisible(); + expect( + screen.getByRole("button", { name: "Configure pool" }), + ).toBeVisible(); }); + it.each([undefined, "team-id"])( + "opens a linked pool under its owner (%s) without opening an editor", + async (org) => { + state.search = { tab: "pools", pool: "pool-one", org }; + state.keys = [makeKey({ credential_source: { type: "personal" } })]; + render(); + expect( + await screen.findByRole("table", { name: "My pool route members" }), + ).toBeVisible(); + expect(mockPoolOwner).toHaveBeenLastCalledWith(org); + expect( + screen.getByRole("button", { name: "Configure pool" }), + ).toBeVisible(); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + }, + ); + it("omits oauth2 and api_key credential pills from service cards", () => { state.keys = [ makeKey({ @@ -194,11 +357,12 @@ describe("KeysPage", () => { ]; render(); + expandConnections(); expect(screen.queryByText("oauth2")).not.toBeInTheDocument(); expect(screen.queryByText("api_key")).not.toBeInTheDocument(); - expect(screen.getByText("bearer")).toBeInTheDocument(); - expect(screen.getAllByText("Direct")).toHaveLength(3); + expect(screen.queryByText("bearer")).not.toBeInTheDocument(); + expect(screen.queryByText("Direct")).not.toBeInTheDocument(); }); it("shows the empty state with an Add Your First Service CTA when there are no services", async () => { @@ -235,54 +399,35 @@ describe("KeysPage", () => { render(); - expect( - screen.getByText(/failed to load services/i), - ).toBeInTheDocument(); + expect(screen.getByText(/failed to load services/i)).toBeInTheDocument(); expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument(); }); - it("hides auto-connected services until the toggle is enabled", async () => { - const user = userEvent.setup(); + it("keeps personal and platform counterparts visible together without the removed Filters menu", async () => { state.keys = [ + makeKey(), makeKey({ - id: "user-1", - label: "Manual Key", - endpoint_url: "https://manual.example/v1", - auto_connected: false, - }), - makeKey({ - id: "auto-1", - label: "Auto Key", - endpoint_url: "https://platform.internal.example/v1", + id: "platform", + label: "Platform counterpart", + slug: "platform-openai", auto_connected: true, - source_app_name: "Claude Code", + credential_binding: "platform", }), ]; - render(); - - // Auto-connected hidden by default. - expect(screen.getByText("Manual Key")).toBeInTheDocument(); - expect(screen.getByText("https://manual.example/v1")).toBeInTheDocument(); - expect(screen.queryByText("Auto Key")).not.toBeInTheDocument(); - expect( - screen.queryByText("https://platform.internal.example/v1"), - ).not.toBeInTheDocument(); - // The toggle label reflects the auto-connected count. - expect(screen.getByText("Show auto-connected (1)")).toBeInTheDocument(); - - await user.click(screen.getByRole("switch")); - - expect(screen.getByText("Auto Key")).toBeInTheDocument(); + fireEvent.click( + screen.getByRole("button", { name: "Auto-connected services: hidden" }), + ); expect( - screen.queryByText("https://platform.internal.example/v1"), + screen.queryByRole("button", { name: "Filters" }), ).not.toBeInTheDocument(); - expect(screen.getAllByText("Platform managed").length).toBeGreaterThan(0); + expandConnections(); + expect(screen.getByText("My OpenAI")).toBeVisible(); + expect(screen.getByText("Platform counterpart")).toBeVisible(); }); it("hides auto-connected endpoint URLs in table rows without changing normal rows", async () => { localStorage.setItem("nyxid-view-mode:keys-services", "table"); - const user = userEvent.setup(); state.keys = [ makeKey({ id: "manual-table", @@ -299,17 +444,26 @@ describe("KeysPage", () => { try { render(); - await user.click(screen.getByRole("switch")); + fireEvent.click( + screen.getByRole("button", { name: "Auto-connected services: hidden" }), + ); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); - expect(screen.getByText("https://manual-table.example/v1")).toBeInTheDocument(); - expect(screen.queryByText("https://platform-table.internal/v1")).not.toBeInTheDocument(); + expect( + screen.getByText("https://manual-table.example/v1"), + ).toBeInTheDocument(); + expect( + screen.queryByText("https://platform-table.internal/v1"), + ).not.toBeInTheDocument(); expect(screen.getAllByText("Platform managed").length).toBeGreaterThan(0); } finally { localStorage.removeItem("nyxid-view-mode:keys-services"); } }); - it("groups org-inherited services into a labelled section with a role badge", () => { + it("keeps organization identity and role in the connection table", () => { state.keys = [ makeKey({ id: "org-key", @@ -326,16 +480,13 @@ describe("KeysPage", () => { ]; render(); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expandConnections(); - // Org section header (h3) + role badge come from the org credential source. - // ("Acme Org" also appears inside the stubbed OrgAvatar, so scope to the heading.) - expect( - screen.getByRole("heading", { name: "Acme Org" }), - ).toBeInTheDocument(); - expect(screen.getByTestId("role-badge")).toHaveTextContent("member"); - expect(screen.getByText("Shared from organization")).toBeInTheDocument(); - // Member (non-admin) org cards are flagged View-Only. - expect(screen.getByText("View-Only")).toBeInTheDocument(); + expect(screen.getAllByText("Acme Org").length).toBeGreaterThan(0); + expect(screen.getByTitle("Acme Org · Organization · member")).toBeVisible(); }); it("opens the Add Key dialog when the toolbar Connect Service button is clicked", async () => { @@ -365,6 +516,7 @@ describe("KeysPage", () => { ]; render(); + expandConnections(); await user.click(screen.getByRole("button", { name: /reconnect/i })); @@ -394,8 +546,9 @@ describe("KeysPage", () => { ]; render(); + expandConnections(); - expect(screen.getByText("Credential Missing")).toBeInTheDocument(); + expect(screen.getByText("Disabled")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /reconnect/i })); expect(screen.getByTestId("add-key-dialog")).toHaveAttribute( "data-reconnect", @@ -416,9 +569,12 @@ describe("KeysPage", () => { ]; render(); + expandConnections(); - expect(screen.getByText("Expired")).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /reconnect/i })).toBeInTheDocument(); + expect(screen.getByText("Reconnect needed")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /reconnect/i }), + ).toBeInTheDocument(); }); it("labels pending OAuth service cards as continue authentication", async () => { @@ -434,6 +590,7 @@ describe("KeysPage", () => { ]; render(); + expandConnections(); await user.click( screen.getByRole("button", { name: /continue authentication/i }), @@ -465,11 +622,42 @@ describe("KeysPage", () => { ]; render(); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expandConnections(); expect( screen.queryByRole("button", { name: /reconnect/i }), ).not.toBeInTheDocument(); - expect(screen.getByText("View-Only")).toBeInTheDocument(); + expect(screen.getByTitle("Acme Org · Organization · member")).toBeVisible(); + }); + + it("does not offer reconnect when organization access is denied despite an admin role", () => { + state.keys = [ + makeKey({ + credential_type: "oauth2", + auth_method: "oauth2", + status: "failed", + credential_source: { + type: "org", + org_id: "denied-org", + org_name: "Restricted", + role: "admin", + allowed: false, + avatar_url: null, + }, + }), + ]; + render(); + fireEvent.click( + screen.getByRole("button", { name: "Service view: Personal" }), + ); + expandConnections(); + expect(screen.getByText("No access")).toBeVisible(); + expect( + screen.queryByRole("button", { name: /Reconnect/ }), + ).not.toBeInTheDocument(); }); it("switches to the Agent Keys tab and mounts the API key table + usage dashboard", async () => { @@ -522,7 +710,7 @@ describe("KeysPage", () => { }); }); - it("renders services as a table and navigates to the key detail when a row is clicked", async () => { + it("shows connection metadata within table rows with explicit configuration navigation", async () => { // useViewMode reads localStorage to default the services tab into table mode. localStorage.setItem("nyxid-view-mode:keys-services", "table"); const user = userEvent.setup(); @@ -533,18 +721,26 @@ describe("KeysPage", () => { // Table view renders column headers instead of cards. expect( - screen.getByRole("columnheader", { name: "Endpoint" }), + screen.getByRole("columnheader", { name: "Configuration" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Proxy Slug" }), + screen.getByRole("columnheader", { name: "Connection / Slug" }), ).toBeInTheDocument(); - await user.click(screen.getByText("My OpenAI")); + await user.click( + screen.getByRole("button", { + name: "Details for My OpenAI (Personal)", + }), + ); - expect(mockNavigate).toHaveBeenCalledWith({ - to: "/keys/$keyId", - params: { keyId: "key-1" }, - }); + expect( + screen.getByRole("button", { + name: "Details for My OpenAI (Personal)", + }), + ).toHaveAttribute("aria-expanded", "true"); + expect( + screen.getByRole("link", { name: "Configure My OpenAI (Personal)" }), + ).toHaveAttribute("href", "/keys/$keyId:key-1"); } finally { localStorage.removeItem("nyxid-view-mode:keys-services"); } @@ -587,9 +783,10 @@ describe("KeysPage", () => { render(); await waitFor(() => { - expect( - screen.getByTestId("api-key-create-dialog"), - ).toHaveAttribute("data-open", "true"); + expect(screen.getByTestId("api-key-create-dialog")).toHaveAttribute( + "data-open", + "true", + ); }); }); }); diff --git a/frontend/src/pages/keys.tsx b/frontend/src/pages/keys.tsx index 83a11b887..93da4b06c 100644 --- a/frontend/src/pages/keys.tsx +++ b/frontend/src/pages/keys.tsx @@ -1,47 +1,31 @@ -import { ServiceAuthorshipFooter, ArchivedServiceHistory } from "@/components/dashboard/service-history"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { ServiceConnectionTable } from "@/components/dashboard/service-connection-table"; +import { ArchivedServiceHistory } from "@/components/dashboard/service-history"; +import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react"; import { Link, useSearch, useNavigate } from "@tanstack/react-router"; -import { useKeys } from "@/hooks/use-keys"; +import { useKeys, useCatalog } from "@/hooks/use-keys"; +import { GroupedServiceCards } from "@/components/dashboard/grouped-service-cards"; import { useUserServices } from "@/hooks/use-user-services"; import { PageHeader } from "@/components/shared/page-header"; import { CodexConnectionSection } from "@/components/providers/codex-connection"; import { AddCtaButton } from "@/components/shared/add-cta-button"; import { TeachingEmptyState } from "@/components/shared/teaching-empty-state"; import { Skeleton } from "@/components/ui/skeleton"; -import { Badge } from "@/components/ui/badge"; import { Button, ButtonIcon } from "@/components/ui/button"; import { ErrorBanner } from "@/components/shared/error-banner"; import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - Table, - TableHeader, - TableBody, - TableRow, - TableHead, - TableCell, -} from "@/components/ui/table"; -import { - Globe, - KeySquare, - Server, - Terminal, - RefreshCw, - Shield, -} from "lucide-react"; +import { KeySquare, Terminal, RefreshCw, Shield } from "lucide-react"; import { MagicKeyIcon } from "@/components/icons/empty-state"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; -import { useNodes } from "@/hooks/use-nodes"; -import { ViewToggle, useViewMode, type ViewMode } from "@/components/shared/view-toggle"; -import { ServiceIcon } from "@/components/service-icon"; +import { + ViewToggle, + useViewMode, + type ViewMode, +} from "@/components/shared/view-toggle"; import { AddKeyDialog } from "@/components/dashboard/add-key-dialog"; import { ApiKeyTable } from "@/components/dashboard/api-key-table"; import { ApiKeyCreateDialog } from "@/components/dashboard/api-key-create-dialog"; import { ApiKeyUsageDashboard } from "@/components/dashboard/api-key-usage-dashboard"; import { ServicePoolsTab } from "@/components/dashboard/service-pools-tab"; -import { RoleBadge } from "@/components/orgs/role-badge"; -import { OrgAvatar } from "@/components/orgs/org-avatar"; import type { KeyInfo } from "@/types/keys"; import type { CredentialSource } from "@/schemas/orgs"; import { @@ -54,35 +38,6 @@ import { parseTab, } from "@/lib/url-tabs"; -function statusVariant( - status: string, -): "success" | "secondary" | "destructive" { - switch (status) { - case "active": - case "online": - return "success"; - case "expired": - case "inaccessible": - case "draining": - return "secondary"; - case "revoked": - case "failed": - case "refresh_failed": - case "offline": - case "node_deleted": - case "unknown": - return "destructive"; - default: - return "secondary"; - } -} - -interface KeyCardProps { - readonly keyInfo: KeyInfo; - /** Credential provenance; undefined is treated as personal. */ - readonly source: CredentialSource | undefined; -} - const RECONNECTABLE_STATUSES = new Set([ "pending_auth", "refresh_failed", @@ -98,9 +53,17 @@ function isReconnectableKey( keyInfo: KeyInfo, source: CredentialSource | undefined, ): boolean { - if (keyInfo.auto_connected || isNonAdminOrgSource(source)) return false; + if ( + keyInfo.auto_connected || + isNonAdminOrgSource(source) || + (source?.type === "org" && !source.allowed) + ) + return false; const effectiveStatus = keyInfo.connection_status ?? keyInfo.status; - if (!keyInfo.credential_missing && !RECONNECTABLE_STATUSES.has(effectiveStatus)) { + if ( + !keyInfo.credential_missing && + !RECONNECTABLE_STATUSES.has(effectiveStatus) + ) { return false; } return ( @@ -111,510 +74,34 @@ function isReconnectableKey( } function reconnectLabel(status: string): string { - return status === "pending_auth" - ? "Continue authentication" - : "Reconnect"; -} - -function KeyCardContent({ - keyInfo, - source, - onReconnect, -}: KeyCardProps & { - readonly onReconnect?: (keyInfo: KeyInfo) => void; -}) { - const isSsh = keyInfo.service_type === "ssh"; - const hasSshCertificateAuth = isSsh && keyInfo.ssh_ca_public_key !== null; - // Issue #416: resolve the bound node's name so the list card shows - // "Via my-node" instead of bare "Via node". TanStack Query dedupes - // the request across all rendered cards. - const { data: nodes } = useNodes(); - const nodeName = keyInfo.node_id - ? (nodes?.find((n) => n.id === keyInfo.node_id)?.name ?? - keyInfo.node_id.slice(0, 8)) - : null; - const endpointUrl = keyInfo.endpoint_url ?? ""; - const displayUrl = keyInfo.auto_connected - ? "Platform managed" - : isSsh - ? `${keyInfo.ssh_host ?? "unknown"}:${keyInfo.ssh_port ?? 22}` - : endpointUrl.length > 50 - ? `${endpointUrl.slice(0, 50)}...` - : endpointUrl; - - const isOrgInherited = source?.type === "org"; - // Viewers and out-of-scope members see the card with reduced opacity. - const isBlocked = source?.type === "org" && !source.allowed; - // Members can USE the credential (allowed=true) but cannot MODIFY it. - // Non-admin org cards are non-clickable on the listing (see KeyCard) - // and flagged as read-only so the user knows why. - const isReadOnly = - source?.type === "org" && source.allowed && source.role !== "admin"; - - const displayStatus = keyInfo.connection_status === "expired" - ? "expired" - : keyInfo.node_id && keyInfo.node_status - ? (keyInfo.node_status === "unknown" ? "node_deleted" : keyInfo.node_status) - : keyInfo.status; - - const displayStatusLabel = - displayStatus === "node_deleted" - ? "Node Deleted" - : displayStatus.charAt(0).toUpperCase() + displayStatus.slice(1); - const showReconnect = onReconnect && isReconnectableKey(keyInfo, source); - const autoAuthLabel = keyInfo.auth_method === "none" - ? "No auth required" - : "Platform managed"; - - return ( - - -
- -
-

- {keyInfo.label} -

- {keyInfo.catalog_service_name && ( -

- {keyInfo.catalog_service_name} -

- )} -
-
-
- {isOrgInherited && ( - Org - )} - {isBlocked && ( - Read-Only - )} - {isReadOnly && !isBlocked && ( - View-Only - )} - {keyInfo.admin_only && ( - Admin-only - )} - - {displayStatusLabel} - - {keyInfo.credential_missing && ( - Credential Missing - )} - {isSsh && SSH} - {(keyInfo.auto_connected || - isSsh || - (keyInfo.credential_type !== "oauth2" && - keyInfo.credential_type !== "api_key")) && ( - - {keyInfo.auto_connected - ? autoAuthLabel - : isSsh - ? hasSshCertificateAuth - ? "certificate" - : "ssh tunnel" - : keyInfo.credential_type} - - )} - {/* Routing pill — moved to top so it aligns across cards. - When routed via a node, the badge becomes a real Link so the - user can jump straight to the node detail page (deferred Wave B - cleanup, ships with C.1 canon sweep). */} - {nodeName && keyInfo.node_id ? ( - e.stopPropagation()} - className="inline-flex" - > - - → {nodeName} - - - ) : ( - Direct - )} - {keyInfo.auto_connected && ( - - {keyInfo.source_app_name - ? `Via ${keyInfo.source_app_name}` - : "Auto-connected"} - - )} - {!keyInfo.is_active && Disabled} -
- - {showReconnect && ( - - )} - -
-
-
- {isSsh ? ( - - ) : ( - - )} - {displayUrl} -
-
- - - {isSsh ? keyInfo.slug : `/proxy/s/${keyInfo.slug}`} - -
-
- -
-
-
- ); -} - -function KeyCard({ - keyInfo, - source, - onReconnect, -}: KeyCardProps & { - readonly onReconnect?: (keyInfo: KeyInfo) => void; -}) { - // Navigation gating: - // - // - Personal credentials and admin-role org credentials: fully clickable - // and the detail page renders all mutation controls. - // - Member / viewer org credentials: clickable, but the detail page - // renders in read-only mode (see KeyDetailPage's `readOnly` branch). - // Members can still see endpoint / auth metadata and a usage snippet - // for credentials they're entitled to proxy through. - // - Out-of-scope org items (source.allowed === false due to scope, not - // role) don't even appear in the listing because - // `list_user_services_with_sources` drops them. - return ( - - - - ); -} - -function ServiceTableRow({ - keyInfo, - source, - onReconnect, -}: KeyCardProps & { - readonly onReconnect?: (keyInfo: KeyInfo) => void; -}) { - const navigate = useNavigate(); - const isSsh = keyInfo.service_type === "ssh"; - const hasSshCertificateAuth = isSsh && keyInfo.ssh_ca_public_key !== null; - const { data: nodes } = useNodes(); - const nodeName = keyInfo.node_id - ? (nodes?.find((n) => n.id === keyInfo.node_id)?.name ?? - keyInfo.node_id.slice(0, 8)) - : null; - - const isOrgInherited = source?.type === "org"; - const isBlocked = source?.type === "org" && !source.allowed; - const isReadOnly = - source?.type === "org" && source.allowed && source.role !== "admin"; - - const displayStatus = keyInfo.connection_status === "expired" - ? "expired" - : keyInfo.node_id && keyInfo.node_status - ? (keyInfo.node_status === "unknown" ? "node_deleted" : keyInfo.node_status) - : keyInfo.status; - - const displayStatusLabel = - displayStatus === "node_deleted" - ? "Node Deleted" - : displayStatus.charAt(0).toUpperCase() + displayStatus.slice(1); - - const displayUrl = keyInfo.auto_connected - ? "Platform managed" - : isSsh - ? `${keyInfo.ssh_host ?? "unknown"}:${keyInfo.ssh_port ?? 22}` - : (keyInfo.endpoint_url ?? ""); - - const authLabel = keyInfo.auto_connected - ? keyInfo.auth_method === "none" - ? "No auth" - : "Platform managed" - : isSsh - ? hasSshCertificateAuth - ? "certificate" - : "ssh tunnel" - : keyInfo.credential_type; - const showReconnect = onReconnect && isReconnectableKey(keyInfo, source); - - return ( - void navigate({ to: "/keys/$keyId", params: { keyId: keyInfo.id } })} - > - -
- -
-

{keyInfo.label}

-

- {keyInfo.catalog_service_name ?? " "} -

-
-
-
- - - - {displayUrl} - - - - {authLabel} - - - - {isSsh ? keyInfo.slug : `/proxy/s/${keyInfo.slug}`} - - - - - {nodeName && keyInfo.node_id ? ( - e.stopPropagation()} - className="text-foreground hover:underline" - > - → {nodeName} - - ) : ( - "Direct" - )} - - - -
-
- {isOrgInherited && Org} - {isBlocked && Read-Only} - {isReadOnly && !isBlocked && View-Only} - {keyInfo.admin_only && Admin-only} - {/* Disabled services are listed so they can be re-enabled, so the - table has to say so — the credential status badge beside this - one reports the credential, which stays healthy while paused. */} - {!keyInfo.is_active && Disabled} - - {displayStatusLabel} - - {keyInfo.credential_missing && ( - Credential Missing - )} - {isSsh && SSH} -
- {showReconnect && ( - - )} -
-
- -
- ); + return status === "pending_auth" ? "Continue authentication" : "Reconnect"; } -function ServiceTableView({ - groups, +function ConnectionReconnect({ + connection, onReconnect, }: { - readonly groups: readonly ServiceGroup[]; - readonly onReconnect: (keyInfo: KeyInfo) => void; + readonly connection: KeyInfo; + readonly onReconnect?: (key: KeyInfo) => void; }) { + if ( + !onReconnect || + !isReconnectableKey(connection, connection.credential_source) + ) + return null; return ( -
- {groups.map((group) => ( -
-
-
- {group.icon === "org" ? ( - - ) : ( - - )} -

- {group.title} -

-
- {group.role && } - {group.subtitle && ( - - {group.subtitle} - - )} -
-
-
- - - Name - Endpoint - Auth - Proxy Slug - Routing - Status - Authorship - - - - {group.keys.map(({ keyInfo, source }) => ( - - ))} - -
-
- - ))} - + ); } -interface ServiceGroup { - readonly key: string; - readonly title: string; - readonly subtitle: string | null; - readonly role: "owner" | "admin" | "member" | "viewer" | null; - readonly icon: "personal" | "org"; - /** - * Org avatar URL when `icon === "org"`. Surfaced via `credential_source` - * on the API response so we can render the same avatar as the - * Organizations page (#545). `null` when the org has no avatar configured - * — falls back to initials / building icon inside `OrgAvatar`. - */ - readonly avatarUrl: string | null; - readonly keys: readonly { - readonly keyInfo: KeyInfo; - readonly source: CredentialSource; - }[]; -} - -/** - * Group visible keys by credential source. Personal items first, then one - * section per org (ordered by first-seen in the incoming list). - * - * Keys without an explicit `credential_source` default to `personal` so the - * UI keeps working against older backends that have not yet been augmented. - */ -function groupKeysBySource( - keys: readonly KeyInfo[], - sourceById: ReadonlyMap, -): readonly ServiceGroup[] { - const personal: ServiceGroup = { - key: "personal", - title: "My Services", - subtitle: null, - role: null, - icon: "personal", - avatarUrl: null, - keys: [], - }; - - const orgGroups = new Map(); - const personalMut: { keyInfo: KeyInfo; source: CredentialSource }[] = []; - - for (const keyInfo of keys) { - const source: CredentialSource = keyInfo.credential_source ?? - sourceById.get(keyInfo.id) ?? { type: "personal" }; - - if (source.type === "personal") { - personalMut.push({ keyInfo, source }); - continue; - } - - const existing = orgGroups.get(source.org_id); - if (existing) { - orgGroups.set(source.org_id, { - ...existing, - // Prefer the first non-null avatar we see for this org. The backend - // returns the same avatar on every row, but when `/keys` loads - // before /user-services has finished hydrating the source map, the - // earliest entry may lack it — keep whichever value we've already - // captured. - avatarUrl: existing.avatarUrl ?? source.avatar_url ?? null, - keys: [...existing.keys, { keyInfo, source }], - }); - } else { - orgGroups.set(source.org_id, { - key: `org-${source.org_id}`, - title: source.org_name, - subtitle: "Shared from organization", - role: source.role, - icon: "org", - avatarUrl: source.avatar_url ?? null, - keys: [{ keyInfo, source }], - }); - } - } - - const groups: ServiceGroup[] = []; - if (personalMut.length > 0) { - groups.push({ ...personal, keys: personalMut }); - } - for (const g of orgGroups.values()) { - groups.push(g); - } - return groups; -} - function ServicesEmptyState({ onAdd }: { readonly onAdd: () => void }) { return ( void; readonly onReconnect: (keyInfo: KeyInfo) => void; - readonly showAutoConnected: boolean; readonly viewMode: ViewMode; }) { const { data: keys, isLoading, error, refetch } = useKeys(); @@ -653,6 +138,7 @@ function ExternalServicesTab({ // future change, the `credential_source` field on KeyInfo will take // precedence and this call becomes a no-op. const { data: userServices } = useUserServices(); + const { data: catalog } = useCatalog({ includeAll: true }); const sourceById = useMemo(() => { const map = new Map(); @@ -666,85 +152,53 @@ function ExternalServicesTab({ if (error) { return ( - + ); } - const userKeys = (keys ?? []).filter((k) => !k.auto_connected); - const autoKeys = (keys ?? []).filter((k) => k.auto_connected); - const visibleKeys = showAutoConnected ? (keys ?? []) : userKeys; - - if (visibleKeys.length === 0 && autoKeys.length === 0) { - return ; - } - - if (visibleKeys.length === 0) { - return ; - } - - const groups = groupKeysBySource(visibleKeys, sourceById); - - if (viewMode === "table") { - return ; - } - - // If only personal services exist, skip section headers to preserve the - // current flat-grid look-and-feel. - const [firstGroup] = groups; - if (groups.length === 1 && firstGroup && firstGroup.icon === "personal") { - return ( -
- {firstGroup.keys.map(({ keyInfo, source }) => ( - - ))} -
- ); - } + if (!keys?.length) return ; return ( -
- {groups.map((group) => ( -
-
-
- {group.icon === "org" ? ( - ({ + ...keyInfo, + credential_source: + keyInfo.credential_source ?? sourceById.get(keyInfo.id), + }))} + catalog={catalog} + actions={(compact) => ( + + )} + renderTable={ + viewMode === "table" + ? (filteredKeys) => ( +
+ ( + + )} /> - ) : ( - - )} -

- {group.title} -

-
- {group.role && } - {group.subtitle && ( - - {group.subtitle} - - )} -
-
- {group.keys.map(({ keyInfo, source }) => ( - - ))} -
-
- ))} -
+ + ) + : undefined + } + renderConnectionActions={(keyInfo) => ( + + )} + /> ); } @@ -783,7 +237,9 @@ function NyxIdApiKeysTab({ @@ -827,47 +283,41 @@ function AddButton({ return ; } -function AutoConnectedToggle({ - checked, - onCheckedChange, - count, -}: { - readonly checked: boolean; - readonly onCheckedChange: (checked: boolean) => void; - readonly count: number; -}) { - return ( -
- - -
- ); -} +const RoutingPreview = import.meta.env.DEV + ? lazy(() => import("@/components/dashboard/service-routing-preview")) + : null; export function KeysPage() { - const search: { tab?: string; slug?: string; action?: string; service?: string } = useSearch({ strict: false }); + const search: { + tab?: string; + slug?: string; + action?: string; + service?: string; + view?: string; + pool?: string; + org?: string; + } = useSearch({ strict: false }); const navigate = useNavigate(); const tab = parseTab(search.tab, KEYS_TABS, KEYS_TAB_DEFAULT); + const previewActive = Boolean( + RoutingPreview && + (search.view === "routing" || import.meta.env.VITE_ROUTING_PREVIEW === "1"), + ); const [addServiceOpen, setAddServiceOpen] = useState(false); const [createPoolOpen, setCreatePoolOpen] = useState(false); const [createKeyOpen, setCreateKeyOpen] = useState(false); const [createKeySetupMode, setCreateKeySetupMode] = useState(false); - const [initialSetupServiceId, setInitialSetupServiceId] = useState(null); - const [showAutoConnected, setShowAutoConnected] = useState(false); + const [initialSetupServiceId, setInitialSetupServiceId] = useState< + string | null + >(null); const [servicesViewMode, setServicesViewMode] = useViewMode("keys-services"); const [agentKeysViewMode, setAgentKeysViewMode] = useViewMode("keys-agent"); - const [pendingPrefillSlug, setPendingPrefillSlug] = useState(null); + // Shared query with ExternalServicesTab; only decides header CTA placement. + const { data: pageKeys } = useKeys(); + const [pendingPrefillSlug, setPendingPrefillSlug] = useState( + null, + ); const [reconnectKey, setReconnectKey] = useState(null); const appliedSlugRef = useRef(null); const appliedActionRef = useRef(null); @@ -939,11 +389,12 @@ export function KeysPage() { } } - const { data: keys } = useKeys(); - const autoCount = (keys ?? []).filter((k) => k.auto_connected).length; - function setTab(value: string) { - void navigate({ to: "/keys", search: { tab: value }, replace: true }); + void navigate({ + to: "/keys", + search: { tab: value, ...(previewActive ? { view: "routing" } : {}) }, + replace: true, + }); } return ( @@ -951,6 +402,15 @@ export function KeysPage() { + + Routing preview + + + ) : undefined + } /> @@ -961,44 +421,76 @@ export function KeysPage() { Agent Keys
- {tab === "services" && ( - )} - {tab !== "pools" && ( - setAddServiceOpen(true)} + onCreatePool={() => setCreatePoolOpen(true)} + onCreateKey={() => setCreateKeyOpen(true)} /> )} - setAddServiceOpen(true)} - onCreatePool={() => setCreatePoolOpen(true)} - onCreateKey={() => setCreateKeyOpen(true)} - />
- setAddServiceOpen(true)} - onReconnect={(keyInfo) => { - setReconnectKey(keyInfo); - setAddServiceOpen(true); - }} - showAutoConnected={showAutoConnected} - viewMode={servicesViewMode} - /> + {previewActive && RoutingPreview ? ( + }> + ( + setAddServiceOpen(true)} + compact={compact} + compactLabel="Connect" + /> + )} + renderConnectionActions={(connection) => ( + { + setReconnectKey(keyInfo); + setAddServiceOpen(true); + }} + /> + )} + /> + + ) : ( + setAddServiceOpen(true)} + onReconnect={(keyInfo) => { + setReconnectKey(keyInfo); + setAddServiceOpen(true); + }} + viewMode={servicesViewMode} + /> + )} diff --git a/frontend/src/pages/lazy.ts b/frontend/src/pages/lazy.ts index 44103edef..923bba288 100644 --- a/frontend/src/pages/lazy.ts +++ b/frontend/src/pages/lazy.ts @@ -237,7 +237,11 @@ export const AdminInviteCodesPage = lazy(() => default: m.AdminInviteCodesPage, })), ); -export const AdminPlatformCredentialsPage = lazy(() => import("./admin-platform-credentials").then((m) => ({ default: m.AdminPlatformCredentialsPage }))); +export const AdminPlatformCredentialsPage = lazy(() => + import("./admin-platform-credentials").then((m) => ({ + default: m.AdminPlatformCredentialsPage, + })), +); export const AdminFeatureFlagsPage = lazy(() => import("@/pages/admin-feature-flags").then((m) => ({ @@ -274,10 +278,14 @@ export const ChannelBotsPage = lazy(() => })), ); export const ChannelBotSetupPage = lazy(() => - import("@/pages/channel-bot-setup").then((m) => ({ default: m.ChannelBotSetupPage })), + import("@/pages/channel-bot-setup").then((m) => ({ + default: m.ChannelBotSetupPage, + })), ); export const ChannelBotSetupLinksPage = lazy(() => - import("@/pages/channel-bot-setup").then((m) => ({ default: m.ChannelBotSetupLinksPage })), + import("@/pages/channel-bot-setup").then((m) => ({ + default: m.ChannelBotSetupLinksPage, + })), ); export const ChannelBotDetailPage = lazy(() => import("@/pages/channel-bot-detail").then((m) => ({ @@ -331,17 +339,32 @@ export const DocsPage = lazy(() => import("@/features/docs/docs-page").then((m) => ({ default: m.DocsPage })), ); - export const AdminUsagePage = lazy(() => import("@/pages/admin-usage").then((m) => ({ default: m.AdminUsagePage })), ); -export const MachineSetupPage = lazy(() => import("./machine-setup").then((m) => ({ default: m.MachineSetupPage }))); -export const MachinePairPage = lazy(() => import("./machine-setup").then((m) => ({ default: m.MachinePairPage }))); -export const SavedLoginsPage = lazy(() => import("./saved-logins").then((m) => ({ default: m.SavedLoginsPage }))); +export const ServiceOverviewPage = lazy(() => + import("@/pages/service-overview").then((module) => ({ + default: module.ServiceOverviewPage, + })), +); -export const MachineDesktopPage = lazy(() => import("./machine-desktop").then((m) => ({ default: m.MachineDesktopPage }))); +export const MachineSetupPage = lazy(() => + import("./machine-setup").then((m) => ({ default: m.MachineSetupPage })), +); +export const MachinePairPage = lazy(() => + import("./machine-setup").then((m) => ({ default: m.MachinePairPage })), +); +export const SavedLoginsPage = lazy(() => + import("./saved-logins").then((m) => ({ default: m.SavedLoginsPage })), +); + +export const MachineDesktopPage = lazy(() => + import("./machine-desktop").then((m) => ({ default: m.MachineDesktopPage })), +); export const AdminUploadRetentionPage = lazy(() => - import("./admin-upload-retention").then((m) => ({ default: m.AdminUploadRetentionPage })), + import("./admin-upload-retention").then((m) => ({ + default: m.AdminUploadRetentionPage, + })), ); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index bd0302fc2..e4d2e02db 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,6 +1,7 @@ import { AuthFlow } from "@/components/auth/auth-flow"; import { MfaVerifyForm } from "@/components/auth/mfa-verify-form"; import { useAuthStore } from "@/stores/auth-store"; +import { Button } from "@/components/ui/button"; export function LoginPage() { const mfaRequired = useAuthStore((s) => s.mfaRequired); @@ -10,6 +11,10 @@ export function LoginPage() { const socialError = params.get("error") ?? undefined; const inviteCode = params.get("code") ?? undefined; + if (import.meta.env.DEV && import.meta.env.VITE_ROUTING_PREVIEW === "1") { + return

View your real connections

Sign in on NyxID, then return here to explore the local routing proposal with your production account data.

; + } + if (mfaRequired) { return ; } diff --git a/frontend/src/pages/service-overview.test.tsx b/frontend/src/pages/service-overview.test.tsx new file mode 100644 index 000000000..cd438ec05 --- /dev/null +++ b/frontend/src/pages/service-overview.test.tsx @@ -0,0 +1,235 @@ +import { cleanup, render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { ReactNode } from "react"; +import type { KeyInfo } from "@/types/keys"; + +const { state } = vi.hoisted(() => ({ + state: { + groupId: "catalog:openai", + keys: [] as KeyInfo[], + error: null as unknown, + }, +})); +vi.mock("@tanstack/react-router", () => ({ + useParams: () => ({ groupId: state.groupId }), + Link: ({ + to, + params, + children, + ...props + }: { + to: string; + params?: { keyId?: string }; + "aria-label"?: string; + children: ReactNode; + }) => ( + + {children} + + ), +})); +vi.mock("@/components/layout/dashboard-layout", () => ({ + useBreadcrumbLabel: vi.fn(), +})); +vi.mock("@/hooks/use-keys", () => ({ + useKeys: () => ({ data: state.keys, error: state.error, refetch: vi.fn() }), + useCatalog: () => ({ + data: [ + { + slug: "openai", + name: "OpenAI", + description: "Service description", + documentation_url: "https://openai.example/docs", + }, + ], + refetch: vi.fn(), + }), +})); +vi.mock("@/hooks/use-user-services", () => ({ + useUserServices: () => ({ data: [] }), +})); +vi.mock("@/hooks/use-nodes", () => ({ useNodes: () => ({ data: [] }) })); +vi.mock("@/hooks/use-service-insights", () => ({ + useServiceInsights: () => ({ + connections: new Map(), + status: "unavailable", + refresh: vi.fn(), + }), +})); +vi.mock("@/components/dashboard/service-history", () => ({ + ServiceHistory: ({ serviceId }: { serviceId: string }) => ( +
{serviceId}
+ ), + ServiceAuthorshipFooter: () => null, +})); +import { ServiceOverviewPage } from "./service-overview"; + +function key(id: string, overrides: Partial = {}): KeyInfo { + return { + id, + label: id, + slug: id, + catalog_service_id: "openai", + catalog_service_slug: "openai", + catalog_service_name: "OpenAI", + credential_source: { type: "personal" }, + service_type: "http", + is_active: true, + status: "active", + auth_method: "bearer", + credential_type: "api_key", + api_key_id: id, + node_id: null, + auto_connected: false, + created_at: "2026-01-01", + granted_scopes: ["models:read", "chat:write"], + ws_frame_injections: [], + ...overrides, + } as KeyInfo; +} +beforeEach(() => { + state.groupId = "catalog:openai"; + state.error = null; + state.keys = [ + key("Development"), + key("Team", { + source_app_name: "Provisioning app", + last_used_at: "2026-09-01", + }), + key("Custom", { catalog_service_id: null }), + ]; +}); +afterEach(cleanup); + +describe("full service page", () => { + it("contains all connections for the service with full information and configuration links", async () => { + const user = userEvent.setup(); + render(); + expect(screen.getByRole("heading", { name: "OpenAI" })).toBeVisible(); + expect(screen.getByText("2 connections")).toBeVisible(); + expect( + screen.getByRole("link", { + name: "Configure Development (Personal)", + }), + ).toHaveAttribute("href", "/keys/Development"); + expect( + screen.queryByRole("link", { name: /Configure Custom/ }), + ).not.toBeInTheDocument(); + expect(screen.queryByTestId("service-history")).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Details for Team (Personal)" }), + ); + const info = screen.getByRole("table", { name: "OpenAI connections" }); + expect(within(info).getByText("models:read, chat:write")).toBeVisible(); + expect(within(info).getByText("Provisioning app")).toBeVisible(); + expect(within(info).getByText("Last caller")).toBeVisible(); + expect( + within(info).getByRole("link", { name: "Configure Team (Personal)" }), + ).toHaveAttribute("href", "/keys/Team"); + expect( + screen.getByRole("link", { name: "Service documentation" }), + ).toHaveAttribute("href", "https://openai.example/docs"); + }); + + it.each(["member", "viewer"] as const)( + "lets an org %s inspect metadata and history without showing private configuration", + async (role) => { + const user = userEvent.setup(); + state.keys = [ + key("Shared", { + endpoint_url: "https://private.internal/v1", + openapi_spec_url: "https://private.internal/spec", + custom_user_agent: "private-client", + credential_source: { + type: "org", + org_id: "org", + org_name: "ChronoAI", + role, + allowed: role === "member", + }, + }), + ]; + render(); + expect(screen.getByText("Editors only")).toBeVisible(); + expect( + screen.getByRole("link", { + name: "View Shared connection details (ChronoAI)", + }), + ).toHaveAttribute("href", "/keys/Shared"); + expect( + screen.queryByRole("link", { name: /Configure/ }), + ).not.toBeInTheDocument(); + await user.click( + screen.getByRole("button", { name: "Details for Shared (ChronoAI)" }), + ); + expect(document.body.innerHTML).not.toContain("private.internal"); + expect(document.body.innerHTML).not.toContain("private-client"); + expect(document.body.innerHTML).not.toContain("models:read"); + expect(screen.getByText("Connection ID")).toBeVisible(); + await user.click( + screen.getByRole("button", { name: "History for Shared (ChronoAI)" }), + ); + expect(screen.getByRole("tab", { name: "History" })).toHaveAttribute( + "aria-selected", + "true", + ); + expect(screen.getByTestId("service-history")).toHaveTextContent("Shared"); + }, + ); + + it("honors an explicit server denial even when cached provenance says admin", async () => { + state.keys = [ + key("Restricted", { + can_edit_configuration: false, + endpoint_url: "https://private.internal", + }), + ]; + render(); + expect( + screen.queryByRole("link", { name: /Configure/ }), + ).not.toBeInTheDocument(); + expect(document.body.innerHTML).not.toContain("private.internal"); + expect( + screen.getByRole("button", { name: "History for Restricted (Personal)" }), + ).toBeVisible(); + }); + + it("loads history for the selected connection only", async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByRole("tab", { name: "History" })); + expect(screen.getByTestId("service-history")).toHaveTextContent( + "Development", + ); + await user.click( + screen.getByRole("combobox", { name: "Connection history" }), + ); + await user.click(screen.getByRole("option", { name: "Team · Personal" })); + expect(screen.getByTestId("service-history")).toHaveTextContent("Team"); + await user.click(screen.getByRole("tab", { name: "Connections" })); + expect(screen.queryByTestId("service-history")).not.toBeInTheDocument(); + }); + + it("does not manufacture a group from the catalog or expose stale connections after an access failure", () => { + state.groupId = "catalog:missing"; + const mounted = render(); + expect( + screen.getByRole("heading", { name: "Service unavailable" }), + ).toBeVisible(); + state.groupId = "catalog:openai"; + state.error = new Error("No access"); + mounted.rerender(); + expect( + screen.getByText("Service information could not be loaded."), + ).toBeVisible(); + expect( + screen.queryByRole("link", { + name: "Configure Development (Personal)", + }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/pages/service-overview.tsx b/frontend/src/pages/service-overview.tsx new file mode 100644 index 000000000..6fe6fe047 --- /dev/null +++ b/frontend/src/pages/service-overview.tsx @@ -0,0 +1,180 @@ +import { useBreadcrumbLabel } from "@/components/layout/dashboard-layout"; +import { useState } from "react"; +import { Link, useParams } from "@tanstack/react-router"; +import { ArrowLeft } from "lucide-react"; +import { useKeys, useCatalog } from "@/hooks/use-keys"; +import { useUserServices } from "@/hooks/use-user-services"; +import { PageHeader } from "@/components/shared/page-header"; +import { ServiceIcon } from "@/components/service-icon"; +import { ErrorBanner } from "@/components/shared/error-banner"; +import { ServiceConnectionTable } from "@/components/dashboard/service-connection-table"; +import { ServiceHistory } from "@/components/dashboard/service-history"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { groupServiceConnections } from "@/lib/service-groups"; +import { connectionSourceLabel } from "@/lib/service-view"; + +export function ServiceOverviewPage() { + const { groupId } = useParams({ strict: false }) as { groupId: string }; + const keys = useKeys(); + const catalog = useCatalog({ includeAll: true }); + const services = useUserServices(); + const [historyId, setHistoryId] = useState(null); + const [tab, setTab] = useState("connections"); + const connections = (keys.data ?? []).map((key) => ({ + ...key, + credential_source: + key.credential_source ?? + services.data?.find((service) => service.id === key.id) + ?.credential_source, + })); + const group = groupServiceConnections(connections, catalog.data).find( + (group) => group.id === groupId, + ); + useBreadcrumbLabel(group?.name); + const entry = catalog.data?.find((entry) => entry.slug === group?.slug); + const historyConnection = + group?.connections.find((connection) => connection.id === historyId) ?? + group?.connections[0]; + const back = ( + + + All services + + ); + + if (keys.isLoading) return ; + if (keys.error) + return ( +
+ {back} + +
+ ); + if (!group) + return ( +
+ {back} + +
+ ); + + return ( +
+ {back} +
+ } + description={ + group.description ?? + "Connections, configuration and history for this service." + } + /> +
+ + {group.connections.length}{" "} + {group.connections.length === 1 ? "connection" : "connections"} + + + {group.connections.filter((key) => key.is_active).length} enabled + + + {[...new Set(group.connections.map(connectionSourceLabel))].join( + " · ", + )} + + {entry?.documentation_url && + /^https?:\/\//i.test(entry.documentation_url) && ( + + Service documentation + + )} +
+ {catalog.error && ( + + )} + + + Connections + History + + + { + setHistoryId(connection.id); + setTab("history"); + }} + /> + + + + {tab === "history" && historyConnection && ( + + )} + + + + ); +} diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 05278e74d..8cd09bfab 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -111,6 +111,7 @@ import { KeysPage, BillingPage, KeyDetailPage, + ServiceOverviewPage, ChannelBotsPage, ChannelBotSetupPage, ChannelBotSetupLinksPage, @@ -790,7 +791,8 @@ const keysRoute = createRoute({ // service scope in the Agent Key create dialog. validateSearch: ( search: Record, - ): { tab?: string; slug?: string; action?: string; service?: string } => ({ + ): { tab?: string; slug?: string; action?: string; service?: string; view?: string; pool?: string; org?: string } => ({ + ...(import.meta.env.DEV && search.view === "routing" ? { view: "routing" } : {}), ...(typeof search.tab === "string" ? { tab: search.tab } : {}), ...(typeof search.slug === "string" && search.slug.length > 0 ? { slug: search.slug } @@ -799,6 +801,10 @@ const keysRoute = createRoute({ ...(typeof search.service === "string" && search.service.length > 0 ? { service: search.service } : {}), + ...(typeof search.pool === "string" && search.pool.length > 0 && search.pool.length <= 128 + ? { pool: search.pool } : {}), + ...(typeof search.org === "string" && search.org.length > 0 && search.org.length <= 128 + ? { org: search.org } : {}), }), component: KeysPage, }); @@ -821,6 +827,12 @@ const billingRoute = createRoute({ ), }); +const serviceOverviewRoute = createRoute({ + path: "/keys/services/$groupId", + getParentRoute: () => dashboardLayout, + component: ServiceOverviewPage, +}); + const keyDetailRoute = createRoute({ path: "/keys/$keyId", getParentRoute: () => dashboardLayout, @@ -1190,6 +1202,7 @@ const routeTree = rootRoute.addChildren([ approvalGrantsRoute, keysRoute, billingRoute, + serviceOverviewRoute, keyDetailRoute, apiKeyDetailRoute, nodesRoute, diff --git a/frontend/src/schemas/billing.ts b/frontend/src/schemas/billing.ts index 32fd8a7b4..d5d7369a4 100644 --- a/frontend/src/schemas/billing.ts +++ b/frontend/src/schemas/billing.ts @@ -119,6 +119,8 @@ export const billingUsageRowSchema = z.object({ billable: z.boolean().optional().default(true), estimated_credits_micros: z.number().int().nullable().optional(), token_breakdown: billingTokenBreakdownSchema.nullable().optional(), + /** UTC day start, only when requested with `bucket=day`. */ + day: z.string().nullable().optional(), }); export const billingUsageTotalsSchema = z.object({ diff --git a/frontend/src/schemas/service-insights.ts b/frontend/src/schemas/service-insights.ts new file mode 100644 index 000000000..cdaed59c6 --- /dev/null +++ b/frontend/src/schemas/service-insights.ts @@ -0,0 +1,175 @@ +import { z } from "zod"; +import { lanePricingViewSchema } from "./platform-keys"; + +export const serviceBillingExplanationSchema = z.object({ + status: z.enum(["resolved", "conditional", "restricted", "unavailable"]), + credential_class: z.string().nullable(), + credential_label: z.string(), + account: z + .object({ + id: z.string(), + kind: z.enum(["personal", "organization"]), + name: z.string(), + }) + .nullable(), + charge_status: z.enum([ + "usage_based", + "not_charged", + "conditional", + "restricted", + "unavailable", + ]), + rates: z.array( + z.object({ + layer: z.string(), + metric: z.string(), + credits_per_unit: z.string().nullable(), + currency: z.string(), + source: z.string(), + sync_status: z.string().optional(), + }), + ), + provider_billing: z.enum([ + "separate_provider_account", + "nyxid_credential", + "no_credential", + "unknown", + ]), + context: z.string(), + credit_billing_configured: z.boolean().nullish(), + service_billing_configured: z.boolean().nullish(), + credential_supplier: z.enum(["nyxid", "own", "none", "unknown"]).nullish(), + payer_rule: z.string().optional(), + notes: z.array(z.string()), +}); + +export const serviceCallerSchema = z.object({ + id: z.string().nullable(), + kind: z.string(), + name: z.string(), + app_id: z.string().nullable(), + app_name: z.string().nullable(), +}); + +export const serviceRequestSchema = z.object({ + id: z.string(), + execution_id: z.string().nullable(), + caller: serviceCallerSchema, + occurred_at: z.string(), + outcome: z.string(), + response_status: z.number().nullable(), + source: z + .object({ + kind: z.enum(["personal", "org", "platform"]), + owner_id: z.string(), + }) + .nullish(), +}); + +export const connectionActivitySchema = z.object({ + access: z.object({ + visibility: z.string(), + basis: z.enum(["resolved", "configuration"]).optional(), + incomplete: z.boolean().optional(), + keys: z.array( + z.object({ + id: z.string(), + name: z.string(), + platform: z.string().nullable(), + owner_id: z.string(), + permission: z.string(), + credential_override: z.boolean().nullable(), + }), + ), + truncated: z.boolean(), + }), + activity: z.object({ + visibility: z.string(), + period_days: z.number(), + tracking: z.string(), + request_count: z.number(), + requests: z.array(serviceRequestSchema), + last_used: serviceRequestSchema.nullish(), + truncated: z.boolean(), + }), +}); + +export const serviceInsightSchema = z.object({ + service_id: z.string(), + billing: serviceBillingExplanationSchema.nullable(), + usage: connectionActivitySchema.nullable(), +}); +export const serviceInsightsResponseSchema = z.object({ + connections: z.array(serviceInsightSchema), +}); +export type ServiceInsight = z.infer; +export type ConnectionActivity = z.infer; +export type ServiceBillingExplanation = z.infer< + typeof serviceBillingExplanationSchema +>; +export type ServiceCaller = z.infer; + +export const configuredAgentKeySchema = z.object({ + id: z.string(), + name: z.string(), + platform: z.string().nullish(), + purpose: z.string().optional(), + is_active: z.boolean(), + expires_at: z.string().nullish(), + scopes: z.string(), + allow_all_services: z.boolean(), + allow_auto_connected_services: z.boolean().optional(), + allowed_service_ids: z.array(z.string()), + bindings_count: z.number().default(0), +}); +export const configuredAgentKeyListSchema = z.object({ + keys: z.array(configuredAgentKeySchema), +}); +export const configuredBindingsSchema = z.object({ + bindings: z.array( + z.object({ + api_key_id: z.string(), + user_service_id: z.string(), + }), + ), +}); +export const configuredOrgListSchema = z.object({ + orgs: z.array( + z.object({ + id: z.string(), + your_role: z.string(), + }), + ), +}); +export const configuredCatalogSchema = z.object({ + entries: z.array( + z.object({ + slug: z.string(), + byok_pricing: lanePricingViewSchema.nullish(), + billing: z + .object({ + byok_pricing: lanePricingViewSchema.nullish(), + platform_key_pricing: lanePricingViewSchema.nullish(), + platform_billable: z.boolean().optional(), + platform_charge_nyxid_credentials_only: z.boolean().optional(), + platform_metric: z.string().nullish(), + platform_pricing: z + .object({ + credits_per_unit: z.string(), + sync_status: z.string().optional(), + }) + .nullish(), + resale_billable: z.boolean().optional(), + }) + .nullish(), + platform_key: z + .object({ pricing: lanePricingViewSchema.nullish() }) + .nullish(), + }), + ), +}); + +export type ConfiguredAgentKey = z.infer; +export type ConfiguredCatalogEntry = z.infer< + typeof configuredCatalogSchema +>["entries"][number]; diff --git a/frontend/src/schemas/service-view.ts b/frontend/src/schemas/service-view.ts new file mode 100644 index 000000000..698dd4897 --- /dev/null +++ b/frontend/src/schemas/service-view.ts @@ -0,0 +1,92 @@ +import { z } from "zod"; + +const selectionId = z + .string() + .refine( + (value) => value.trim().length > 0 && Array.from(value).length <= 128, + "Selections must contain 1 to 128 characters", + ); +const selections = z + .array(selectionId) + .max(100) + .transform((ids) => [...new Set(ids)]); + +export const serviceViewSchema = z + .object({ + search: z + .string() + .refine( + (value) => Array.from(value).length <= 200, + "Search is limited to 200 characters", + ), + organization_ids: selections.optional(), + service_group_ids: selections.optional(), + organization_id: selectionId.nullish(), + service_group_id: selectionId.nullish(), + source: z.enum(["all", "personal", "org", "platform"]), + state: z.enum(["all", "enabled", "disabled"]), + service_type: z.enum(["all", "http", "ssh"]), + show_auto_connected: z.boolean(), + }) + .strict() + .refine( + (value) => + !( + value.organization_ids !== undefined && + value.organization_id !== undefined + ), + "Use organization_ids", + ) + .refine( + (value) => + !( + value.service_group_ids !== undefined && + value.service_group_id !== undefined + ), + "Use service_group_ids", + ) + .transform( + ({ + organization_id, + service_group_id, + organization_ids, + service_group_ids, + ...filters + }) => ({ + ...filters, + organization_ids: + organization_ids ?? (organization_id ? [organization_id] : []), + service_group_ids: + service_group_ids ?? (service_group_id ? [service_group_id] : []), + }), + ); + +export type ServiceViewFilters = z.infer; + +export const DEFAULT_SERVICE_FILTERS: ServiceViewFilters = { + search: "", + organization_ids: [], + service_group_ids: [], + source: "personal", + state: "all", + service_type: "all", + // Platform auto-provisioned connections are noise until asked for. + show_auto_connected: false, +}; + +export function sameServiceFilters( + a: ServiceViewFilters, + b: ServiceViewFilters, +): boolean { + return ( + a.search === b.search && + a.organization_ids.length === b.organization_ids.length && + a.organization_ids.every((id) => b.organization_ids.includes(id)) && + a.service_group_ids.length === b.service_group_ids.length && + a.service_group_ids.every((id) => b.service_group_ids.includes(id)) && + a.source === b.source && + a.state === b.state && + a.service_type === b.service_type && + a.show_auto_connected === b.show_auto_connected + ); +} diff --git a/frontend/src/stores/auth-store.ts b/frontend/src/stores/auth-store.ts index 125de1cb4..a3a5ce3a0 100644 --- a/frontend/src/stores/auth-store.ts +++ b/frontend/src/stores/auth-store.ts @@ -1,3 +1,4 @@ +import { useServiceCardView } from "@/stores/service-card-view-store"; import { create } from "zustand"; import type { User, LoginResponse } from "@/types/api"; import { api, apiClient, ApiError } from "@/lib/api-client"; @@ -12,7 +13,8 @@ import { transitionAssistantIdentity } from "@/lib/assistant/identity"; const MFA_REQUIRED_ERROR_CODE = 2002; -function clearAssistantLocalState(): void { +function clearAccountLocalState(): void { + useServiceCardView.setState({ accountId: undefined, filters: undefined, expanded: [] }); useAssistantContextStore.getState().clear(); useAssistantDraftStore.getState().clear(); useAssistantWireLogStore.getState().reset(); @@ -23,7 +25,8 @@ function applyIdentityTransition( nextUser: User | null, ): void { if (previousUser?.id === nextUser?.id) return; - if (previousUser !== null) clearAssistantLocalState(); + useServiceCardView.setState({ accountId: nextUser?.id, filters: undefined, expanded: [] }); + if (previousUser !== null) clearAccountLocalState(); transitionAssistantIdentity(nextUser?.id ?? null); } @@ -98,7 +101,7 @@ export const useAuthStore = create((set, get) => ({ // Clear telemetry identity BEFORE state wipe so the next event // the app emits already carries a fresh anon distinct_id. telemetryReset(); - clearAssistantLocalState(); + clearAccountLocalState(); transitionAssistantIdentity(null); set({ user: null, @@ -129,7 +132,7 @@ export const useAuthStore = create((set, get) => ({ // ex-user. Parity with the explicit `logout()` branch above. if (!ephemeral) { telemetryReset(); - clearAssistantLocalState(); + clearAccountLocalState(); } transitionAssistantIdentity(null); set({ user: null, isAuthenticated: false, isLoading: false }); @@ -142,7 +145,7 @@ export const useAuthStore = create((set, get) => ({ setUser: (user: User | null): void => { const previousUser = get().user; if (user === null) { - clearAssistantLocalState(); + clearAccountLocalState(); transitionAssistantIdentity(null); } else { applyIdentityTransition(previousUser, user); diff --git a/frontend/src/stores/service-card-view-store.ts b/frontend/src/stores/service-card-view-store.ts new file mode 100644 index 000000000..8fab5134a --- /dev/null +++ b/frontend/src/stores/service-card-view-store.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; +import type { ServiceViewFilters } from "@/schemas/service-view"; + +interface ServiceCardView { + accountId?: string; + expanded: readonly string[]; + filters?: ServiceViewFilters; +} + +// Unsaved changes survive navigation only. Account defaults come from /users/me. +export const useServiceCardView = create(() => ({ + expanded: [], +})); diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index fb1f4770b..5dfe6f309 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -1,3 +1,4 @@ +import type { ServiceViewFilters } from "@/schemas/service-view"; import type { ProxyOperationPolicy } from "@/schemas/services"; import type { InferenceMetadata, PlatformKeyConfig, LanePricingView } from "@/schemas/platform-keys"; import type { BillingMetric } from "@/schemas/billing"; @@ -42,6 +43,7 @@ export interface OnboardingState { /// User-scoped configuration surfaced on `GET /users/me`. export interface ProfileConfig { + readonly services_view?: ServiceViewFilters | null; readonly onboarding: OnboardingState; } diff --git a/frontend/src/types/keys.ts b/frontend/src/types/keys.ts index 9792e9226..ffadd2449 100644 --- a/frontend/src/types/keys.ts +++ b/frontend/src/types/keys.ts @@ -6,6 +6,7 @@ import type { WsFrameInjection } from "@/schemas/services"; export type { DefaultRequestHeader } from "@/schemas/default-request-headers"; export interface KeyInfo { + readonly can_edit_configuration?: boolean; readonly inference?: InferenceView | null; readonly capabilities?: import("./api").ServiceCapabilities | null; readonly authorship?: import("@/schemas/service-history").ServiceAuthorship; @@ -26,6 +27,12 @@ export interface KeyInfo { /** The service references a credential row that no longer exists. */ readonly credential_missing?: boolean; readonly credential_type: string; + /** Public identifier of the connection's supplied OAuth developer app. */ + readonly oauth_client_id?: string | null; + /** Per-connection OAuth record; present on modern multi-connection rows. */ + readonly connection_id?: string | null; + /** Resolved OAuth app source, including supported legacy connections. */ + readonly oauth_app_source?: "platform" | "byo" | null; readonly auth_method: string; readonly auth_key_name: string; readonly status: string; @@ -163,7 +170,10 @@ export interface ScopeCatalogEntry { export interface CatalogEntry { readonly billing?: import("./api").ServiceBilling | null; readonly inference?: InferenceView | null; - readonly platform_key?: { readonly available: boolean; readonly pricing?: LanePricingView | null }; + readonly platform_key?: { + readonly available: boolean; + readonly pricing?: LanePricingView | null; + }; readonly byok_pricing?: LanePricingView | null; readonly slug: string; readonly resource_uri: string; diff --git a/frontend/test/routing-preview-gateway.test.ts b/frontend/test/routing-preview-gateway.test.ts new file mode 100644 index 000000000..fefae508e --- /dev/null +++ b/frontend/test/routing-preview-gateway.test.ts @@ -0,0 +1,171 @@ +// @vitest-environment node +import { createServer, type Server } from "node:http"; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import type { Connect, ViteDevServer } from "vite"; +import { routingPreview } from "../dev/routing-preview"; + +function listen(server: Server): Promise { + return new Promise((resolve) => server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (address && typeof address !== "string") resolve(`http://127.0.0.1:${address.port}`); + })); +} + +function close(server: Server): Promise { + return new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); +} + +describe("local preview metadata gateway", () => { + let local: Server; + let upstream: Server; + let origin: string; + const calls: { path?: string; authorization?: string; cookie?: string; method?: string; body?: string }[] = []; + + beforeAll(async () => { + upstream = createServer(async (req, res) => { + const chunks: Buffer[] = []; + for await (const chunk of req) chunks.push(Buffer.from(chunk)); + calls.push({ path: req.url, authorization: req.headers.authorization, cookie: req.headers.cookie, method: req.method, body: Buffer.concat(chunks).toString("utf8") }); + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ keys: [] })); + }); + const backend = await listen(upstream); + let middleware: Connect.NextHandleFunction; + local = createServer((req, res) => middleware(req, res, () => { res.writeHead(404); res.end(); })); + const plugin = routingPreview(backend, "https://nyx.example"); + const configure = plugin.configureServer as (server: ViteDevServer) => void; + configure({ httpServer: local, middlewares: { use: (handler: Connect.NextHandleFunction) => { middleware = handler; } } } as unknown as ViteDevServer); + origin = await listen(local); + }); + + beforeEach(() => calls.splice(0)); + afterAll(async () => { await Promise.all([close(local), close(upstream)]); }); + + async function startLogin() { + const response = await fetch(`${origin}/__routing-preview/login`, { redirect: "manual" }); + const target = new URL(response.headers.get("location")!); + expect(target.origin + target.pathname).toBe("https://nyx.example/cli-auth"); + expect(target.searchParams.get("port")).toBe(new URL(origin).port); + return { state: target.searchParams.get("state")!, cookie: response.headers.get("set-cookie")!.split(";")[0]! }; + } + + async function signIn() { + const login = await startLogin(); + const params = new URLSearchParams({ state: login.state, access_token: "test.access.token", refresh_token: "ignored.refresh.token" }); + const response = await fetch(`${origin}/callback?${params}`, { headers: { Cookie: login.cookie }, redirect: "manual" }); + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/keys?view=routing"); + const sessionCookie = response.headers.getSetCookie()[0]!; + expect(sessionCookie).toContain("HttpOnly"); + expect(sessionCookie).not.toContain("test.access.token"); + return sessionCookie.split(";")[0]!; + } + + it("requires a session for account metadata", async () => { + expect((await fetch(`${origin}/api/v1/keys`)).status).toBe(401); + expect(calls).toHaveLength(0); + }); + + it("accepts a bound login callback and forwards the access token only to allowed metadata reads", async () => { + const cookie = await signIn(); + const response = await fetch(`${origin}/api/v1/keys`, { headers: { Cookie: cookie } }); + expect(response.status).toBe(200); + expect(calls).toEqual([{ path: "/api/v1/keys", authorization: "Bearer test.access.token", cookie: undefined, method: "GET", body: "" }]); + }); + + it.each([ + "/api/v1/keys/13ae3c40-5ec0-4eee-9e20-25c60209dd12", + "/api/v1/catalog/llm-openai", + "/api/v1/nodes/13ae3c40-5ec0-4eee-9e20-25c60209dd12", + "/api/v1/api-keys", + "/api/v1/service-insights?ids=13ae3c40-5ec0-4eee-9e20-25c60209dd12", + "/api/v1/api-keys/13ae3c40-5ec0-4eee-9e20-25c60209dd12", + "/api/v1/api-keys/13ae3c40-5ec0-4eee-9e20-25c60209dd12/bindings", + "/api/v1/service-pools", + "/api/v1/providers/codex-connection", + "/api/v1/keys/history/archived", + "/api/v1/options/service-history-action", + "/api/v1/keys/13ae3c40-5ec0-4eee-9e20-25c60209dd12/history", + ])("preserves metadata access for the original detail pages: %s", async (path) => { + const cookie = await signIn(); + expect((await fetch(`${origin}${path}`, { headers: { Cookie: cookie } })).status).toBe(200); + expect(calls[0]?.path).toBe(path); + }); + + it("rejects an unbound callback even with a valid state", async () => { + const login = await startLogin(); + expect((await fetch(`${origin}/callback?state=${login.state}&access_token=test.token`, { redirect: "manual" })).status).toBe(400); + }); + + it("rejects replay of a consumed callback", async () => { + const login = await startLogin(); + const url = `${origin}/callback?state=${login.state}&access_token=test.token`; + const options = { headers: { Cookie: login.cookie }, redirect: "manual" as const }; + expect((await fetch(url, options)).status).toBe(302); + expect((await fetch(url, options)).status).toBe(400); + }); + + it.each([ + ["POST", "/api/v1/keys"], + ["DELETE", "/api/v1/keys/id"], + ["POST", "/api/v1/service-pools"], + ["POST", "/api/v1/providers/codex-connection/verify"], + ["PUT", "/api/v1/service-pools/id/members"], + ["DELETE", "/api/v1/service-pools/id"], + ["GET", "/api/v1/proxy/s/openai/models"], + ["GET", "/api/v1/keys/id/reveal"], + ["GET", "/api/v1/keys/13ae3c40-5ec0-4eee-9e20-25c60209dd12/reveal"], + ["GET", "/api/v1/nodes/13ae3c40-5ec0-4eee-9e20-25c60209dd12/pending-credentials"], + ["GET", "/api/v1/catalog/llm-openai/endpoints"], + ["GET", "/oauth/authorize"], + ["GET", "/mcp"], + ])("blocks %s %s before it reaches production", async (method, path) => { + const cookie = await signIn(); + expect((await fetch(`${origin}${path}`, { method, headers: { Cookie: cookie } })).status).toBe(403); + expect(calls).toHaveLength(0); + }); + + it("rejects cross-origin reads even with a local session", async () => { + const cookie = await signIn(); + const response = await fetch(`${origin}/api/v1/keys`, { headers: { Cookie: cookie, Origin: "https://other.example" } }); + expect(response.status).toBe(403); + expect(calls).toHaveLength(0); + }); + + const preferences = { search: "team", organization_ids: ["org-1", "org-2"], service_group_ids: ["catalog:openai", "catalog:codex"], source: "org", state: "enabled", service_type: "http", show_auto_connected: false }; + + it("forwards only validated service preferences under the preview user's identity", async () => { + const cookie = await signIn(); + const response = await fetch(`${origin}/api/v1/users/me/preferences/services`, { + method: "PUT", headers: { Cookie: cookie, Origin: origin, "Content-Type": "application/json" }, body: JSON.stringify(preferences), + }); + expect(response.status).toBe(200); + expect(calls).toHaveLength(1); + expect(calls[0]).toMatchObject({ path: "/api/v1/users/me/preferences/services", method: "PUT", authorization: "Bearer test.access.token" }); + expect(JSON.parse(calls[0]!.body)).toEqual(preferences); + expect(calls[0]?.cookie).toBeUndefined(); + }); + + it("rejects unauthenticated, cross-origin, malformed and oversized preference writes", async () => { + const cookie = await signIn(); + for (const [headers, body, status] of [ + [{ Origin: origin }, JSON.stringify(preferences), 401], + [{ Cookie: cookie }, JSON.stringify(preferences), 403], + [{ Cookie: cookie, Origin: "https://other.example" }, JSON.stringify(preferences), 403], + [{ Cookie: cookie, Origin: origin }, JSON.stringify({ ...preferences, user_id: "other" }), 400], + [{ Cookie: cookie, Origin: origin }, JSON.stringify({ ...preferences, state: "healthy" }), 400], + [{ Cookie: cookie, Origin: origin }, "x".repeat(131073), 413], + ] as const) { + const response = await fetch(`${origin}/api/v1/users/me/preferences/services`, { method: "PUT", headers, body }); + expect(response.status).toBe(status); + } + expect(calls).toHaveLength(0); + }); + + it("clears only the local session on logout", async () => { + const cookie = await signIn(); + expect((await fetch(`${origin}/api/v1/auth/logout`, { method: "POST", headers: { Cookie: cookie, Origin: origin } })).status).toBe(200); + expect((await fetch(`${origin}/api/v1/keys`, { headers: { Cookie: cookie } })).status).toBe(401); + expect(calls).toHaveLength(0); + }); +}); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c57fb76c7..02658b51f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,7 @@ import path from "path" import fs from "node:fs" import https from "node:https" import { allDocPages } from "./src/features/docs/manifest" +import { routingPreview } from "./dev/routing-preview" import { machineSeccomp } from "./scripts/machine-seccomp" const backendUrl = process.env.BACKEND_URL || "http://localhost:3001" @@ -241,9 +242,13 @@ function docsSync(): Plugin { const BUILD_ID = process.env.SOURCE_COMMIT || Date.now().toString(36) export default defineConfig({ - plugins: [telegramClaimReferrer(), react(), tailwindcss(), docsSync(), machineSeccomp(__dirname)], + plugins: [ + ...(process.env.NYXID_ROUTING_PREVIEW === "1" ? [routingPreview(backendUrl, expectedOrigin)] : []), + telegramClaimReferrer(), react(), tailwindcss(), docsSync(), machineSeccomp(__dirname), + ], define: { __BUILD_ID__: JSON.stringify(BUILD_ID), + "import.meta.env.VITE_ROUTING_PREVIEW": JSON.stringify(process.env.NYXID_ROUTING_PREVIEW === "1" ? "1" : "0"), }, resolve: { alias: {