//! The client's RPC surface: every command, wired to its Tauri implementation. //! //! The *shape* of the surface — command names, request payloads, response //! types — is declared once in `yaak_rpc_schema`, which is also where the //! TypeScript bindings come from. This module supplies the desktop's half: an //! adapter per command that turns a `ClientCtx` and a request struct into a //! call on the existing implementation, and the single `rpc` Tauri command that //! is the only way the frontend reaches any of it — one envelope, //! `{ cmd, payload }`, exactly like the proxy app. //! //! Command bodies live in one of two places. Host-independent ones are in //! `yaak_commands`, written against its `Host` trait, which `ClientCtx` //! implements below; their adapters are one line. The rest still have their //! natural Tauri signatures (window, app handle, managed state) and their //! adapters unpack the request for them. Either way the wire format stays //! transport-agnostic: another host builds its router from the same schema //! with its own `Host`, and the frontend cannot tell. use crate::error::Result; use crate::notifications::YaakNotifier; use crate::updates::YaakUpdater; use log::warn; use serde::Serialize; use tauri::{Manager, Runtime, State, WebviewWindow}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; use yaak_commands::{Host, PluginHost}; use yaak_core::WorkspaceContext; use yaak_crypto::manager::EncryptionManager; use yaak_git::{ BranchDeleteResult, CloneResult, GitBranchInfo, GitCommit, GitFileDiff, GitRemote, GitStatusSummary, GitWorktreeStatus, PullResult, PushResult, }; use yaak_grpc::manager::GrpcHandle; use yaak_grpc::ServiceDefinition; use yaak_models::blob_manager::BlobManager; use yaak_models::models::{ GraphQlIntrospection, GrpcEvent, HttpRequest, HttpRequestHeader, HttpResponse, HttpResponseEvent, Plugin, Settings, WebsocketConnection, WebsocketEvent, WorkspaceMeta, }; use yaak_models::query_manager::QueryManager; use yaak_models::util::BatchUpsertResult; use yaak_plugins::events::{ CallFolderActionRequest, CallGrpcRequestActionRequest, CallHttpRequestActionRequest, CallWebsocketRequestActionRequest, CallWorkspaceActionRequest, FilterResponse, ImportResponse, JsonPrimitive, RenderPurpose, GetFolderActionsResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionConfigResponse, GetTemplateFunctionSummaryResponse, GetThemesResponse, GetWebsocketRequestActionsResponse, GetWorkspaceActionsResponse, }; use yaak_plugins::api::{PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse}; use yaak_plugins::manager::PluginManager; use yaak_plugins::native_template_functions::encrypt_secure_template_function; use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_plugins::plugin_meta::PluginMetadata; use yaak_rpc::RpcRouter; use yaak_rpc_schema::*; use yaak_sse::sse::ServerSentEvent; use yaak_sync::sync::SyncOp; use yaak_templates::TemplateCallback; use yaak_tauri_utils::window::WorkspaceWindowTrait; use yaak_ws::WebsocketManager; /// Per-call context: the window a command was invoked from. /// /// Window identity is load-bearing — model writes carry it for echo /// suppression, and plugin events and toasts are routed back through it — so it /// rides along with every dispatch rather than living in shared state. pub(crate) struct ClientCtx { pub window: WebviewWindow, } // Derived Clone would demand `R: Clone`, which `Runtime` types don't provide; // the window handle itself is always cloneable. impl Clone for ClientCtx { fn clone(&self) -> Self { Self { window: self.window.clone() } } } /// The desktop is a host: the client is the window, the session is the /// window's URL, and the shared managers are Tauri managed state. impl Host for ClientCtx { fn client_id(&self) -> &str { self.window.label() } fn session(&self) -> WorkspaceContext { self.window.workspace_context() } fn app_version(&self) -> String { self.window.package_info().version.to_string() } fn query_manager(&self) -> &QueryManager { self.window.state::().inner() } fn blob_manager(&self) -> &BlobManager { self.window.state::().inner() } fn encryption_manager(&self) -> &EncryptionManager { self.window.state::().inner() } } impl ClientCtx { /// The plugin runtime this window talks to. Only the `PluginHost` impl /// below uses it; everything else goes through the trait. fn pm(&self) -> State<'_, PluginManager> { self.window.state::() } } /// The desktop answers all of these out of the `PluginManager` it already /// runs — the Node sidecar. Each is a delegation, which is the point: the /// operations are what the handlers need, and this is one host's way of /// providing them. impl PluginHost for ClientCtx { async fn loaded_plugin_metadata(&self, directory: &str) -> Option { let handle = self.pm().get_plugin_by_dir(directory).await?; Some(handle.info()) } async fn take_plugin_init_errors(&self) -> Vec<(String, String)> { self.pm().take_init_errors().await } async fn resolve_plugins(&self, plugins: Vec) -> Vec { self.pm().resolve_plugins_for_runtime_from_db(plugins).await } fn template_callback(&self, purpose: RenderPurpose) -> impl TemplateCallback { PluginTemplateCallback::new( Arc::new((*self.pm()).clone()), Arc::new(self.encryption_manager().clone()), &self.plugin_context(), purpose, ) } async fn template_function_summaries( &self, ) -> yaak_commands::Result> { Ok(self .window .state::() .get_template_function_summaries(&self.plugin_context()) .await?) } async fn template_function_config( &self, function_name: &str, values: HashMap, model_id: &str, ) -> yaak_commands::Result { Ok(self .window .state::() .get_template_function_config(&self.plugin_context(), function_name, values, model_id) .await?) } async fn themes(&self) -> yaak_commands::Result> { Ok(self.pm().get_themes(&self.plugin_context()).await?) } async fn http_request_actions( &self, ) -> yaak_commands::Result> { Ok(self.pm().get_http_request_actions(&self.plugin_context()).await?) } async fn websocket_request_actions( &self, ) -> yaak_commands::Result> { Ok(self.pm().get_websocket_request_actions(&self.plugin_context()).await?) } async fn grpc_request_actions( &self, ) -> yaak_commands::Result> { Ok(self.pm().get_grpc_request_actions(&self.plugin_context()).await?) } async fn workspace_actions(&self) -> yaak_commands::Result> { Ok(self.pm().get_workspace_actions(&self.plugin_context()).await?) } async fn folder_actions(&self) -> yaak_commands::Result> { Ok(self.pm().get_folder_actions(&self.plugin_context()).await?) } async fn call_http_request_action( &self, req: CallHttpRequestActionRequest, ) -> yaak_commands::Result<()> { Ok(self.pm().call_http_request_action(&self.plugin_context(), req).await?) } async fn call_grpc_request_action( &self, req: CallGrpcRequestActionRequest, ) -> yaak_commands::Result<()> { Ok(self.pm().call_grpc_request_action(&self.plugin_context(), req).await?) } async fn call_websocket_request_action( &self, req: CallWebsocketRequestActionRequest, ) -> yaak_commands::Result<()> { Ok(self.pm().call_websocket_request_action(&self.plugin_context(), req).await?) } async fn call_workspace_action( &self, req: CallWorkspaceActionRequest, ) -> yaak_commands::Result<()> { Ok(self.pm().call_workspace_action(&self.plugin_context(), req).await?) } async fn call_folder_action( &self, req: CallFolderActionRequest, ) -> yaak_commands::Result<()> { Ok(self.pm().call_folder_action(&self.plugin_context(), req).await?) } async fn http_authentication_summaries( &self, ) -> yaak_commands::Result> { let results = self.pm().get_http_authentication_summaries(&self.plugin_context()).await?; Ok(results.into_iter().map(|(_, a)| a).collect()) } async fn http_authentication_config( &self, auth_name: &str, values: HashMap, model_id: &str, ) -> yaak_commands::Result { Ok(self .pm() .get_http_authentication_config(&self.plugin_context(), auth_name, values, model_id) .await?) } async fn call_http_authentication_action( &self, auth_name: &str, action_index: i32, values: HashMap, model_id: &str, ) -> yaak_commands::Result<()> { Ok(self .pm() .call_http_authentication_action( &self.plugin_context(), auth_name, action_index, values, model_id, ) .await?) } async fn import_data(&self, content: &str) -> yaak_commands::Result { Ok(self.pm().import_data(&self.plugin_context(), content).await?) } async fn reload_plugins(&self, plugins: Vec) -> Vec<(String, String)> { self.pm().initialize_all_plugins(plugins, &self.plugin_context()).await } async fn encrypt_secure_template(&self, template: &str) -> yaak_commands::Result { let plugin_manager = Arc::new((*self.pm()).clone()); let encryption_manager = Arc::new(self.encryption_manager().clone()); Ok(encrypt_secure_template_function( plugin_manager, encryption_manager, &self.plugin_context(), template, )?) } } /// The one Tauri command. The payload is the yaak-rpc envelope's payload; /// a missing payload means an empty one. #[tauri::command] pub(crate) async fn rpc( window: WebviewWindow, router: State<'_, RpcRouter>>, cmd: String, payload: Option, ) -> std::result::Result { let ctx = ClientCtx { window }; let payload = payload.unwrap_or_else(|| serde_json::Value::Object(Default::default())); log::debug!("RPC {cmd}"); router.dispatch(&cmd, payload, &ctx).await.map_err(|e| { log::warn!("RPC {cmd} failed: {}", e.message); e.message }) } /// A callback that forwards a stream of messages to the calling window as /// `stream_{id}` events. The stream id is minted by the caller so it can /// subscribe before the command starts and never miss a message. fn stream_emitter( ctx: &ClientCtx, stream_id: &str, ) -> impl Fn(T) + Send + Sync + 'static { use tauri::Emitter; let window = ctx.window.clone(); let event = format!("stream_{stream_id}"); move |payload: T| { let value = match serde_json::to_value(payload) { Ok(value) => value, Err(e) => { warn!("Failed to serialize stream event: {e}"); return; } }; if let Err(e) = window.emit_to(window.label(), &event, value) { warn!("Failed to emit stream event: {e}"); } } } // -- Streaming commands (hand-written: they push events while they run) -- async fn cmd_git_watch_worktree_status( ctx: ClientCtx, req: CmdGitWatchWorktreeStatusReq, ) -> Result { let on_status = stream_emitter(&ctx, &req.stream_id); crate::git_watcher::watch_git_worktree_status( ctx.window.app_handle().clone(), &req.dir, on_status, ) .await } async fn cmd_sync_watch( ctx: ClientCtx, req: CmdSyncWatchReq, ) -> Result { let on_event = stream_emitter(&ctx, &req.stream_id); Ok(crate::sync_ext::sync_watch( ctx.window.app_handle().clone(), &req.sync_dir, &req.workspace_id, on_event, ) .await?) } /// Build the router with every command in the schema registered to its /// adapter here. Generic over the runtime so the same registry serves Wry and /// CEF builds. Adding a command means adding it to the schema *and* writing an /// adapter below; a missing adapter fails to compile rather than 404 at runtime. macro_rules! register_commands { ( $( $name:ident ( $req:ty ) -> $res:ty ),* $(,)? ) => { pub(crate) fn build_rpc_router() -> RpcRouter> { let mut router = RpcRouter::new(); $( router.register(stringify!($name), yaak_rpc::rpc_handler_async!($name)); )* router } }; } yaak_rpc_schema::with_commands!(register_commands); // -- Adapters -- async fn cmd_metadata(ctx: ClientCtx, _req: CmdMetadataReq) -> Result { Ok(crate::cmd_metadata(ctx.window.app_handle().clone()).await?) } async fn cmd_template_tokens_to_string(ctx: ClientCtx, req: CmdTemplateTokensToStringReq) -> Result { Ok(yaak_commands::templates::cmd_template_tokens_to_string(ctx, req).await?) } async fn cmd_render_template(ctx: ClientCtx, req: CmdRenderTemplateReq) -> Result { Ok(yaak_commands::templates::cmd_render_template(ctx, req).await?) } async fn cmd_send_feedback(ctx: ClientCtx, req: CmdSendFeedbackReq) -> Result<()> { Ok(crate::cmd_send_feedback(ctx.window.app_handle().clone(), req.feature, req.text).await?) } async fn cmd_dismiss_notification(ctx: ClientCtx, req: CmdDismissNotificationReq) -> Result<()> { Ok(crate::cmd_dismiss_notification(ctx.window.clone(), &req.notification_id, ctx.window.app_handle().state::>()).await?) } async fn cmd_grpc_reflect(ctx: ClientCtx, req: CmdGrpcReflectReq) -> Result> { Ok(crate::cmd_grpc_reflect(&req.request_id, req.environment_id.as_deref(), req.proto_files, ctx.window.clone(), ctx.window.app_handle().clone(), ctx.window.app_handle().state::>()).await?) } async fn cmd_grpc_go(ctx: ClientCtx, req: CmdGrpcGoReq) -> Result { Ok(crate::cmd_grpc_go(&req.request_id, req.environment_id.as_deref(), req.proto_files, ctx.window.app_handle().clone(), ctx.window.clone(), ctx.window.app_handle().state::>()).await?) } async fn cmd_restart(ctx: ClientCtx, _req: CmdRestartReq) -> Result<()> { Ok(crate::cmd_restart(ctx.window.app_handle().clone()).await?) } async fn cmd_send_ephemeral_request(ctx: ClientCtx, req: CmdSendEphemeralRequestReq) -> Result { Ok(crate::cmd_send_ephemeral_request(req.request, req.environment_id.as_deref(), req.cookie_jar_id.as_deref(), ctx.window.clone(), ctx.window.app_handle().clone()).await?) } async fn cmd_format_json(ctx: ClientCtx, req: CmdFormatJsonReq) -> Result { Ok(yaak_commands::data::cmd_format_json(ctx, req).await?) } async fn cmd_format_graphql(_ctx: ClientCtx, req: CmdFormatGraphqlReq) -> Result { Ok(crate::cmd_format_graphql(&req.text).await?) } async fn cmd_http_response_body(ctx: ClientCtx, req: CmdHttpResponseBodyReq) -> Result { Ok(crate::cmd_http_response_body(ctx.window.clone(), ctx.window.app_handle().state::(), &req.response_id, req.filter.as_deref()).await?) } async fn cmd_http_response_body_path(ctx: ClientCtx, req: CmdHttpResponseBodyPathReq) -> Result> { Ok(yaak_commands::responses::cmd_http_response_body_path(ctx, req).await?) } async fn cmd_http_request_body(ctx: ClientCtx, req: CmdHttpRequestBodyReq) -> Result>> { Ok(yaak_commands::responses::cmd_http_request_body(ctx, req).await?) } async fn cmd_get_sse_events(ctx: ClientCtx, req: CmdGetSseEventsReq) -> Result> { Ok(crate::cmd_get_sse_events(ctx.window.app_handle().clone(), &req.response_id).await?) } async fn cmd_get_http_response_events(ctx: ClientCtx, req: CmdGetHttpResponseEventsReq) -> Result> { Ok(yaak_commands::responses::cmd_get_http_response_events(ctx, req).await?) } async fn cmd_import_data(ctx: ClientCtx, req: CmdImportDataReq) -> Result { Ok(crate::cmd_import_data(ctx.window.clone(), &req.file_path).await?) } async fn cmd_import_url(ctx: ClientCtx, req: CmdImportUrlReq) -> Result { Ok(crate::cmd_import_url(ctx.window.clone(), &req.url).await?) } async fn cmd_http_request_actions(ctx: ClientCtx, req: CmdHttpRequestActionsReq) -> Result> { Ok(yaak_commands::actions::cmd_http_request_actions(ctx, req).await?) } async fn cmd_websocket_request_actions(ctx: ClientCtx, req: CmdWebsocketRequestActionsReq) -> Result> { Ok(yaak_commands::actions::cmd_websocket_request_actions(ctx, req).await?) } async fn cmd_call_websocket_request_action(ctx: ClientCtx, req: CmdCallWebsocketRequestActionReq) -> Result<()> { Ok(yaak_commands::actions::cmd_call_websocket_request_action(ctx, req).await?) } async fn cmd_workspace_actions(ctx: ClientCtx, req: CmdWorkspaceActionsReq) -> Result> { Ok(yaak_commands::actions::cmd_workspace_actions(ctx, req).await?) } async fn cmd_call_workspace_action(ctx: ClientCtx, req: CmdCallWorkspaceActionReq) -> Result<()> { Ok(yaak_commands::actions::cmd_call_workspace_action(ctx, req).await?) } async fn cmd_folder_actions(ctx: ClientCtx, req: CmdFolderActionsReq) -> Result> { Ok(yaak_commands::actions::cmd_folder_actions(ctx, req).await?) } async fn cmd_call_folder_action(ctx: ClientCtx, req: CmdCallFolderActionReq) -> Result<()> { Ok(yaak_commands::actions::cmd_call_folder_action(ctx, req).await?) } async fn cmd_grpc_request_actions(ctx: ClientCtx, req: CmdGrpcRequestActionsReq) -> Result> { Ok(yaak_commands::actions::cmd_grpc_request_actions(ctx, req).await?) } async fn cmd_template_function_summaries(ctx: ClientCtx, req: CmdTemplateFunctionSummariesReq) -> Result> { Ok(yaak_commands::templates::cmd_template_function_summaries(ctx, req).await?) } async fn cmd_template_function_config(ctx: ClientCtx, req: CmdTemplateFunctionConfigReq) -> Result { Ok(yaak_commands::templates::cmd_template_function_config(ctx, req).await?) } async fn cmd_get_http_authentication_summaries(ctx: ClientCtx, req: CmdGetHttpAuthenticationSummariesReq) -> Result> { Ok(yaak_commands::auth::cmd_get_http_authentication_summaries(ctx, req).await?) } async fn cmd_get_http_authentication_config(ctx: ClientCtx, req: CmdGetHttpAuthenticationConfigReq) -> Result { Ok(yaak_commands::auth::cmd_get_http_authentication_config(ctx, req).await?) } async fn cmd_call_http_request_action(ctx: ClientCtx, req: CmdCallHttpRequestActionReq) -> Result<()> { Ok(yaak_commands::actions::cmd_call_http_request_action(ctx, req).await?) } async fn cmd_call_grpc_request_action(ctx: ClientCtx, req: CmdCallGrpcRequestActionReq) -> Result<()> { Ok(yaak_commands::actions::cmd_call_grpc_request_action(ctx, req).await?) } async fn cmd_call_http_authentication_action(ctx: ClientCtx, req: CmdCallHttpAuthenticationActionReq) -> Result<()> { Ok(yaak_commands::auth::cmd_call_http_authentication_action(ctx, req).await?) } async fn cmd_curl_to_request(ctx: ClientCtx, req: CmdCurlToRequestReq) -> Result { Ok(yaak_commands::actions::cmd_curl_to_request(ctx, req).await?) } async fn cmd_export_data(ctx: ClientCtx, req: CmdExportDataReq) -> Result<()> { Ok(yaak_commands::data::cmd_export_data(ctx, req).await?) } async fn cmd_save_base64_to_binary(ctx: ClientCtx, req: CmdSaveBase64ToBinaryReq) -> Result<()> { Ok(crate::cmd_save_base64_to_binary(ctx.window.app_handle().clone(), &req.filepath, &req.data).await?) } async fn cmd_save_response(ctx: ClientCtx, req: CmdSaveResponseReq) -> Result<()> { Ok(yaak_commands::responses::cmd_save_response(ctx, req).await?) } async fn cmd_send_http_request(ctx: ClientCtx, req: CmdSendHttpRequestReq) -> Result { Ok(crate::cmd_send_http_request(ctx.window.app_handle().clone(), ctx.window.clone(), req.environment_id.as_deref(), req.cookie_jar_id.as_deref(), req.request_id).await?) } async fn cmd_reload_plugins(ctx: ClientCtx, req: CmdReloadPluginsReq) -> Result> { Ok(yaak_commands::actions::cmd_reload_plugins(ctx, req).await?) } async fn cmd_plugin_info(ctx: ClientCtx, req: CmdPluginInfoReq) -> Result { Ok(yaak_commands::plugins::cmd_plugin_info(ctx, req).await?) } async fn cmd_delete_all_grpc_connections(ctx: ClientCtx, req: CmdDeleteAllGrpcConnectionsReq) -> Result<()> { Ok(yaak_commands::models::cmd_delete_all_grpc_connections(ctx, req).await?) } async fn cmd_delete_send_history(ctx: ClientCtx, req: CmdDeleteSendHistoryReq) -> Result<()> { Ok(yaak_commands::models::cmd_delete_send_history(ctx, req).await?) } async fn cmd_delete_all_http_responses(ctx: ClientCtx, req: CmdDeleteAllHttpResponsesReq) -> Result<()> { Ok(yaak_commands::models::cmd_delete_all_http_responses(ctx, req).await?) } async fn cmd_get_workspace_meta(ctx: ClientCtx, req: CmdGetWorkspaceMetaReq) -> Result { Ok(yaak_commands::models::cmd_get_workspace_meta(ctx, req).await?) } async fn cmd_new_child_window(ctx: ClientCtx, req: CmdNewChildWindowReq) -> Result<()> { Ok(crate::cmd_new_child_window(ctx.window.clone(), &req.url, &req.label, &req.title, req.inner_size).await?) } async fn cmd_new_main_window(ctx: ClientCtx, req: CmdNewMainWindowReq) -> Result<()> { Ok(crate::cmd_new_main_window(ctx.window.app_handle().clone(), &req.url).await?) } async fn cmd_check_for_updates(ctx: ClientCtx, _req: CmdCheckForUpdatesReq) -> Result { Ok(crate::cmd_check_for_updates(ctx.window.clone(), ctx.window.app_handle().state::>()).await?) } async fn cmd_decrypt_template(ctx: ClientCtx, req: CmdDecryptTemplateReq) -> Result { Ok(yaak_commands::encryption::cmd_decrypt_template(ctx, req).await?) } async fn cmd_secure_template(ctx: ClientCtx, req: CmdSecureTemplateReq) -> Result { Ok(yaak_commands::encryption::cmd_secure_template(ctx, req).await?) } async fn cmd_get_themes(ctx: ClientCtx, req: CmdGetThemesReq) -> Result> { Ok(yaak_commands::templates::cmd_get_themes(ctx, req).await?) } async fn cmd_enable_encryption(ctx: ClientCtx, req: CmdEnableEncryptionReq) -> Result<()> { Ok(yaak_commands::encryption::cmd_enable_encryption(ctx, req).await?) } async fn cmd_reveal_workspace_key(ctx: ClientCtx, req: CmdRevealWorkspaceKeyReq) -> Result { Ok(yaak_commands::encryption::cmd_reveal_workspace_key(ctx, req).await?) } async fn cmd_set_workspace_key(ctx: ClientCtx, req: CmdSetWorkspaceKeyReq) -> Result<()> { Ok(yaak_commands::encryption::cmd_set_workspace_key(ctx, req).await?) } async fn cmd_disable_encryption(ctx: ClientCtx, req: CmdDisableEncryptionReq) -> Result<()> { Ok(yaak_commands::encryption::cmd_disable_encryption(ctx, req).await?) } async fn cmd_default_headers(ctx: ClientCtx, req: CmdDefaultHeadersReq) -> Result> { Ok(yaak_commands::models::cmd_default_headers(ctx, req).await?) } async fn models_upsert(ctx: ClientCtx, req: ModelsUpsertReq) -> Result { Ok(yaak_commands::models::models_upsert(ctx, req).await?) } /// Runs on a blocking thread rather than the async runtime: a cascading delete /// (a workspace with thousands of requests) holds a transaction for its whole /// duration, and stalling the runtime stalls every other IPC call behind it. /// That is this host's concern, so the shared handler stays plain and the /// relocation happens here. async fn models_delete(ctx: ClientCtx, req: ModelsDeleteReq) -> Result { let deleted = tauri::async_runtime::spawn_blocking(move || { yaak_commands::models::models_delete_blocking(&ctx, req) }) .await .map_err(|e| crate::error::Error::GenericError(format!("Delete task failed: {e}")))?; Ok(deleted?) } async fn models_duplicate(ctx: ClientCtx, req: ModelsDuplicateReq) -> Result { Ok(yaak_commands::models::models_duplicate(ctx, req).await?) } async fn models_websocket_events(ctx: ClientCtx, req: ModelsWebsocketEventsReq) -> Result> { Ok(yaak_commands::models::models_websocket_events(ctx, req).await?) } async fn models_grpc_events(ctx: ClientCtx, req: ModelsGrpcEventsReq) -> Result> { Ok(yaak_commands::models::models_grpc_events(ctx, req).await?) } async fn models_get_settings(ctx: ClientCtx, req: ModelsGetSettingsReq) -> Result { Ok(yaak_commands::models::models_get_settings(ctx, req).await?) } async fn models_get_graphql_introspection(ctx: ClientCtx, req: ModelsGetGraphqlIntrospectionReq) -> Result> { Ok(yaak_commands::models::models_get_graphql_introspection(ctx, req).await?) } async fn models_upsert_graphql_introspection(ctx: ClientCtx, req: ModelsUpsertGraphqlIntrospectionReq) -> Result { Ok(yaak_commands::models::models_upsert_graphql_introspection(ctx, req).await?) } /// Non-ASCII is escaped to `\uXXXX` before the JSON crosses into the webview: /// on Linux, sending Cyrillic (and possibly other) characters through this /// payload leaves every string in the parsed models subtly mis-encoded and /// CodeMirror unable to place the cursor (feedback: "editing the URL sometimes /// freezes the app"). Escape sequences sidestep it. This is a quirk of the /// webview transport, not of the data, so it lives in the adapter rather than /// the shared handler. async fn models_workspace_models(ctx: ClientCtx, req: ModelsWorkspaceModelsReq) -> Result { let json = yaak_commands::models::models_workspace_models(ctx, req).await?; Ok(escape_str_for_webview(&json)) } fn escape_str_for_webview(input: &str) -> String { input .chars() .map(|c| { let code = c as u32; // ASCII if code <= 0x7F { c.to_string() // BMP characters encoded normally } else if code < 0xFFFF { format!("\\u{:04X}", code) // Beyond BMP encoded a surrogate pairs } else { let high = ((code - 0x10000) >> 10) + 0xD800; let low = ((code - 0x10000) & 0x3FF) + 0xDC00; format!("\\u{:04X}\\u{:04X}", high, low) } }) .collect() } async fn cmd_git_checkout(_ctx: ClientCtx, req: CmdGitCheckoutReq) -> Result { Ok(crate::git_ext::cmd_git_checkout(&req.dir, &req.branch, req.force).await?) } async fn cmd_git_branch(_ctx: ClientCtx, req: CmdGitBranchReq) -> Result<()> { Ok(crate::git_ext::cmd_git_branch(&req.dir, &req.branch, req.base.as_deref()).await?) } async fn cmd_git_delete_branch(_ctx: ClientCtx, req: CmdGitDeleteBranchReq) -> Result { Ok(crate::git_ext::cmd_git_delete_branch(&req.dir, &req.branch, req.force).await?) } async fn cmd_git_delete_remote_branch(_ctx: ClientCtx, req: CmdGitDeleteRemoteBranchReq) -> Result<()> { Ok(crate::git_ext::cmd_git_delete_remote_branch(&req.dir, &req.branch).await?) } async fn cmd_git_merge_branch(_ctx: ClientCtx, req: CmdGitMergeBranchReq) -> Result<()> { Ok(crate::git_ext::cmd_git_merge_branch(&req.dir, &req.branch).await?) } async fn cmd_git_rename_branch(_ctx: ClientCtx, req: CmdGitRenameBranchReq) -> Result<()> { Ok(crate::git_ext::cmd_git_rename_branch(&req.dir, &req.old_name, &req.new_name).await?) } async fn cmd_git_status(_ctx: ClientCtx, req: CmdGitStatusReq) -> Result { Ok(crate::git_ext::cmd_git_status(&req.dir).await?) } async fn cmd_git_branch_info(_ctx: ClientCtx, req: CmdGitBranchInfoReq) -> Result { Ok(crate::git_ext::cmd_git_branch_info(&req.dir).await?) } async fn cmd_git_worktree_status(_ctx: ClientCtx, req: CmdGitWorktreeStatusReq) -> Result { Ok(crate::git_ext::cmd_git_worktree_status(&req.dir).await?) } async fn cmd_git_log(_ctx: ClientCtx, req: CmdGitLogReq) -> Result> { Ok(crate::git_ext::cmd_git_log(&req.dir).await?) } async fn cmd_git_log_for_file(_ctx: ClientCtx, req: CmdGitLogForFileReq) -> Result> { Ok(crate::git_ext::cmd_git_log_for_file(&req.dir, req.rela_path).await?) } async fn cmd_git_file_diff_for_commit(_ctx: ClientCtx, req: CmdGitFileDiffForCommitReq) -> Result { Ok(crate::git_ext::cmd_git_file_diff_for_commit(&req.dir, &req.commit_oid, req.rela_path).await?) } async fn cmd_git_initialize(_ctx: ClientCtx, req: CmdGitInitializeReq) -> Result<()> { Ok(crate::git_ext::cmd_git_initialize(&req.dir).await?) } async fn cmd_git_clone(_ctx: ClientCtx, req: CmdGitCloneReq) -> Result { Ok(crate::git_ext::cmd_git_clone(&req.url, &req.dir).await?) } async fn cmd_git_commit(_ctx: ClientCtx, req: CmdGitCommitReq) -> Result<()> { Ok(crate::git_ext::cmd_git_commit(&req.dir, &req.message).await?) } async fn cmd_git_fetch_all(_ctx: ClientCtx, req: CmdGitFetchAllReq) -> Result<()> { Ok(crate::git_ext::cmd_git_fetch_all(&req.dir).await?) } async fn cmd_git_push(_ctx: ClientCtx, req: CmdGitPushReq) -> Result { Ok(crate::git_ext::cmd_git_push(&req.dir).await?) } async fn cmd_git_pull(_ctx: ClientCtx, req: CmdGitPullReq) -> Result { Ok(crate::git_ext::cmd_git_pull(&req.dir).await?) } async fn cmd_git_pull_force_reset(_ctx: ClientCtx, req: CmdGitPullForceResetReq) -> Result { Ok(crate::git_ext::cmd_git_pull_force_reset(&req.dir, &req.remote, &req.branch).await?) } async fn cmd_git_pull_merge(_ctx: ClientCtx, req: CmdGitPullMergeReq) -> Result { Ok(crate::git_ext::cmd_git_pull_merge(&req.dir, &req.remote, &req.branch).await?) } async fn cmd_git_add(_ctx: ClientCtx, req: CmdGitAddReq) -> Result<()> { Ok(crate::git_ext::cmd_git_add(&req.dir, req.rela_paths).await?) } async fn cmd_git_unstage(_ctx: ClientCtx, req: CmdGitUnstageReq) -> Result<()> { Ok(crate::git_ext::cmd_git_unstage(&req.dir, req.rela_paths).await?) } async fn cmd_git_reset_changes(_ctx: ClientCtx, req: CmdGitResetChangesReq) -> Result<()> { Ok(crate::git_ext::cmd_git_reset_changes(&req.dir).await?) } async fn cmd_git_restore_files(_ctx: ClientCtx, req: CmdGitRestoreFilesReq) -> Result<()> { Ok(crate::git_ext::cmd_git_restore_files(&req.dir, req.rela_paths).await?) } async fn cmd_git_restore_file_from_commit(_ctx: ClientCtx, req: CmdGitRestoreFileFromCommitReq) -> Result<()> { Ok(crate::git_ext::cmd_git_restore_file_from_commit(&req.dir, &req.commit_oid, req.rela_path).await?) } async fn cmd_git_add_credential(_ctx: ClientCtx, req: CmdGitAddCredentialReq) -> Result<()> { Ok(crate::git_ext::cmd_git_add_credential(&req.remote_url, &req.username, &req.password).await?) } async fn cmd_git_remotes(_ctx: ClientCtx, req: CmdGitRemotesReq) -> Result> { Ok(crate::git_ext::cmd_git_remotes(&req.dir).await?) } async fn cmd_git_add_remote(_ctx: ClientCtx, req: CmdGitAddRemoteReq) -> Result { Ok(crate::git_ext::cmd_git_add_remote(&req.dir, &req.name, &req.url).await?) } async fn cmd_git_rm_remote(_ctx: ClientCtx, req: CmdGitRmRemoteReq) -> Result<()> { Ok(crate::git_ext::cmd_git_rm_remote(&req.dir, &req.name).await?) } async fn cmd_sync_calculate(ctx: ClientCtx, req: CmdSyncCalculateReq) -> Result> { Ok(crate::sync_ext::cmd_sync_calculate(ctx.window.app_handle().clone(), &req.workspace_id, &req.sync_dir).await?) } async fn cmd_sync_calculate_fs(_ctx: ClientCtx, req: CmdSyncCalculateFsReq) -> Result> { Ok(crate::sync_ext::cmd_sync_calculate_fs(&req.dir).await?) } async fn cmd_sync_apply(ctx: ClientCtx, req: CmdSyncApplyReq) -> Result<()> { Ok(crate::sync_ext::cmd_sync_apply(ctx.window.app_handle().clone(), req.sync_ops, &req.sync_dir, &req.workspace_id).await?) } async fn cmd_ws_delete_connections(ctx: ClientCtx, req: CmdWsDeleteConnectionsReq) -> Result<()> { Ok(yaak_commands::models::cmd_ws_delete_connections(ctx, req).await?) } async fn cmd_ws_send(ctx: ClientCtx, req: CmdWsSendReq) -> Result { Ok(crate::ws_ext::cmd_ws_send(&req.connection_id, req.environment_id.as_deref(), ctx.window.app_handle().clone(), ctx.window.clone(), ctx.window.app_handle().state::>()).await?) } async fn cmd_ws_close(ctx: ClientCtx, req: CmdWsCloseReq) -> Result { Ok(crate::ws_ext::cmd_ws_close(&req.connection_id, ctx.window.app_handle().clone(), ctx.window.clone(), ctx.window.app_handle().state::>()).await?) } async fn cmd_ws_connect(ctx: ClientCtx, req: CmdWsConnectReq) -> Result { Ok(crate::ws_ext::cmd_ws_connect(&req.request_id, req.environment_id.as_deref(), req.cookie_jar_id.as_deref(), ctx.window.app_handle().clone(), ctx.window.clone(), ctx.window.app_handle().state::(), ctx.window.app_handle().state::>()).await?) } async fn cmd_plugins_search(ctx: ClientCtx, req: CmdPluginsSearchReq) -> Result { Ok(crate::plugins_ext::cmd_plugins_search(ctx.window.app_handle().clone(), &req.query).await?) } async fn cmd_plugins_install(ctx: ClientCtx, req: CmdPluginsInstallReq) -> Result<()> { Ok(crate::plugins_ext::cmd_plugins_install(ctx.window.clone(), &req.name, req.version).await?) } async fn cmd_plugins_install_from_directory(ctx: ClientCtx, req: CmdPluginsInstallFromDirectoryReq) -> Result { Ok(crate::plugins_ext::cmd_plugins_install_from_directory(ctx.window.clone(), &req.directory).await?) } async fn cmd_plugins_uninstall(ctx: ClientCtx, req: CmdPluginsUninstallReq) -> Result { Ok(crate::plugins_ext::cmd_plugins_uninstall(&req.plugin_id, ctx.window.clone()).await?) } async fn cmd_plugin_init_errors(ctx: ClientCtx, req: CmdPluginInitErrorsReq) -> Result> { Ok(yaak_commands::plugins::cmd_plugin_init_errors(ctx, req).await?) } async fn cmd_plugins_updates(ctx: ClientCtx, _req: CmdPluginsUpdatesReq) -> Result { Ok(crate::plugins_ext::cmd_plugins_updates(ctx.window.app_handle().clone()).await?) } async fn cmd_plugins_update_all(ctx: ClientCtx, _req: CmdPluginsUpdateAllReq) -> Result> { Ok(crate::plugins_ext::cmd_plugins_update_all(ctx.window.clone()).await?) }