From 986143c4ae3778351c18a27a6a6b61d41775155a Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 1 Feb 2026 08:59:35 -0800 Subject: [PATCH] Add yaak-actions-builtin crate and integrate with CLI --- Cargo.lock | 18 ++ Cargo.toml | 2 + crates-cli/yaak-cli/Cargo.toml | 2 + crates-cli/yaak-cli/src/main.rs | 229 ++++---------- crates/yaak-actions-builtin/Cargo.toml | 18 ++ .../yaak-actions-builtin/src/dependencies.rs | 88 ++++++ crates/yaak-actions-builtin/src/http/mod.rs | 24 ++ crates/yaak-actions-builtin/src/http/send.rs | 293 ++++++++++++++++++ crates/yaak-actions-builtin/src/lib.rs | 11 + 9 files changed, 511 insertions(+), 174 deletions(-) create mode 100644 crates/yaak-actions-builtin/Cargo.toml create mode 100644 crates/yaak-actions-builtin/src/dependencies.rs create mode 100644 crates/yaak-actions-builtin/src/http/mod.rs create mode 100644 crates/yaak-actions-builtin/src/http/send.rs create mode 100644 crates/yaak-actions-builtin/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a7a01f40..fe59bf71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8005,6 +8005,22 @@ dependencies = [ "ts-rs", ] +[[package]] +name = "yaak-actions-builtin" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tokio", + "yaak-actions", + "yaak-crypto", + "yaak-http", + "yaak-models", + "yaak-plugins", + "yaak-templates", +] + [[package]] name = "yaak-app" version = "0.0.0" @@ -8074,6 +8090,8 @@ dependencies = [ "log", "serde_json", "tokio", + "yaak-actions", + "yaak-actions-builtin", "yaak-crypto", "yaak-http", "yaak-models", diff --git a/Cargo.toml b/Cargo.toml index da97dba1..523199cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ # Shared crates (no Tauri dependency) "crates/yaak-actions", + "crates/yaak-actions-builtin", "crates/yaak-core", "crates/yaak-common", "crates/yaak-crypto", @@ -47,6 +48,7 @@ ts-rs = "11.1.0" # Internal crates - shared yaak-actions = { path = "crates/yaak-actions" } +yaak-actions-builtin = { path = "crates/yaak-actions-builtin" } yaak-core = { path = "crates/yaak-core" } yaak-common = { path = "crates/yaak-common" } yaak-crypto = { path = "crates/yaak-crypto" } diff --git a/crates-cli/yaak-cli/Cargo.toml b/crates-cli/yaak-cli/Cargo.toml index f3c3c2fe..d5f55fb9 100644 --- a/crates-cli/yaak-cli/Cargo.toml +++ b/crates-cli/yaak-cli/Cargo.toml @@ -15,6 +15,8 @@ env_logger = "0.11" log = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +yaak-actions = { workspace = true } +yaak-actions-builtin = { workspace = true } yaak-crypto = { workspace = true } yaak-http = { workspace = true } yaak-models = { workspace = true } diff --git a/crates-cli/yaak-cli/src/main.rs b/crates-cli/yaak-cli/src/main.rs index ab8cd9f1..a105a256 100644 --- a/crates-cli/yaak-cli/src/main.rs +++ b/crates-cli/yaak-cli/src/main.rs @@ -1,21 +1,13 @@ use clap::{Parser, Subcommand}; -use log::info; -use serde_json::Value; -use std::collections::BTreeMap; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::mpsc; -use yaak_crypto::manager::EncryptionManager; -use yaak_http::path_placeholders::apply_path_placeholders; use yaak_http::sender::{HttpSender, ReqwestSender}; use yaak_http::types::{SendableHttpRequest, SendableHttpRequestOptions}; -use yaak_models::models::{HttpRequest, HttpRequestHeader, HttpUrlParameter}; -use yaak_models::render::make_vars_hashmap; +use yaak_models::models::HttpRequest; use yaak_models::util::UpdateSource; -use yaak_plugins::events::{PluginContext, RenderPurpose}; +use yaak_plugins::events::PluginContext; use yaak_plugins::manager::PluginManager; -use yaak_plugins::template_callback::PluginTemplateCallback; -use yaak_templates::{RenderOptions, parse_and_render, render_json_value_raw}; #[derive(Parser)] #[command(name = "yaakcli")] @@ -72,86 +64,6 @@ enum Commands { }, } -/// Render an HTTP request with template variables and plugin functions -async fn render_http_request( - r: &HttpRequest, - environment_chain: Vec, - cb: &PluginTemplateCallback, - opt: &RenderOptions, -) -> yaak_templates::error::Result { - let vars = &make_vars_hashmap(environment_chain); - - let mut url_parameters = Vec::new(); - for p in r.url_parameters.clone() { - if !p.enabled { - continue; - } - url_parameters.push(HttpUrlParameter { - enabled: p.enabled, - name: parse_and_render(p.name.as_str(), vars, cb, opt).await?, - value: parse_and_render(p.value.as_str(), vars, cb, opt).await?, - id: p.id, - }) - } - - let mut headers = Vec::new(); - for p in r.headers.clone() { - if !p.enabled { - continue; - } - headers.push(HttpRequestHeader { - enabled: p.enabled, - name: parse_and_render(p.name.as_str(), vars, cb, opt).await?, - value: parse_and_render(p.value.as_str(), vars, cb, opt).await?, - id: p.id, - }) - } - - let mut body = BTreeMap::new(); - for (k, v) in r.body.clone() { - body.insert(k, render_json_value_raw(v, vars, cb, opt).await?); - } - - let authentication = { - let mut disabled = false; - let mut auth = BTreeMap::new(); - match r.authentication.get("disabled") { - Some(Value::Bool(true)) => { - disabled = true; - } - Some(Value::String(tmpl)) => { - disabled = parse_and_render(tmpl.as_str(), vars, cb, opt) - .await - .unwrap_or_default() - .is_empty(); - info!( - "Rendering authentication.disabled as a template: {disabled} from \"{tmpl}\"" - ); - } - _ => {} - } - if disabled { - auth.insert("disabled".to_string(), Value::Bool(true)); - } else { - for (k, v) in r.authentication.clone() { - if k == "disabled" { - auth.insert(k, Value::Bool(false)); - } else { - auth.insert(k, render_json_value_raw(v, vars, cb, opt).await?); - } - } - } - auth - }; - - let url = parse_and_render(r.url.clone().as_str(), vars, cb, opt).await?; - - // Apply path placeholders (e.g., /users/:id -> /users/123) - let (url, url_parameters) = apply_path_placeholders(&url, &url_parameters); - - Ok(HttpRequest { url, url_parameters, headers, body, authentication, ..r.to_owned() }) -} - #[tokio::main] async fn main() { let cli = Cli::parse(); @@ -176,10 +88,6 @@ async fn main() { let db = query_manager.connect(); - // Initialize encryption manager for secure() template function - // Use the same app_id as the Tauri app for keyring access - let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id)); - // Initialize plugin manager for template functions let vendored_plugin_dir = data_dir.join("vendored-plugins"); let installed_plugin_dir = data_dir.join("installed-plugins"); @@ -198,9 +106,9 @@ async fn main() { // Create plugin manager (plugins may not be available in CLI context) let plugin_manager = Arc::new( PluginManager::new( - vendored_plugin_dir, - installed_plugin_dir, - node_bin_path, + vendored_plugin_dir.clone(), + installed_plugin_dir.clone(), + node_bin_path.clone(), plugin_runtime_main, false, ) @@ -239,94 +147,67 @@ async fn main() { } } Commands::Send { request_id } => { - let request = db.get_http_request(&request_id).expect("Failed to get request"); + use yaak_actions::{ + ActionExecutor, ActionId, ActionParams, ActionResult, ActionTarget, CurrentContext, + }; + use yaak_actions_builtin::{BuiltinActionDependencies, register_http_actions}; - // Resolve environment chain for variable substitution - let environment_chain = db - .resolve_environments( - &request.workspace_id, - request.folder_id.as_deref(), - cli.environment.as_deref(), - ) - .unwrap_or_default(); - - // Create template callback with plugin support - let plugin_context = PluginContext::new(None, Some(request.workspace_id.clone())); - let template_callback = PluginTemplateCallback::new( - plugin_manager.clone(), - encryption_manager.clone(), - &plugin_context, - RenderPurpose::Send, - ); - - // Render templates in the request - let rendered_request = render_http_request( - &request, - environment_chain, - &template_callback, - &RenderOptions::throw(), + // Create dependencies + let deps = BuiltinActionDependencies::new_standalone( + &db_path, + &blob_path, + &app_id, + vendored_plugin_dir.clone(), + installed_plugin_dir.clone(), + node_bin_path.clone(), ) .await - .expect("Failed to render request templates"); + .expect("Failed to initialize dependencies"); - if cli.verbose { - println!("> {} {}", rendered_request.method, rendered_request.url); - } + // Create executor and register actions + let executor = ActionExecutor::new(); + executor.register_builtin_groups().await.expect("Failed to register groups"); + register_http_actions(&executor, &deps).await.expect("Failed to register HTTP actions"); - // Convert to sendable request - let sendable = SendableHttpRequest::from_http_request( - &rendered_request, - SendableHttpRequestOptions::default(), - ) - .await - .expect("Failed to build request"); - - // Create event channel for progress - let (event_tx, mut event_rx) = mpsc::channel(100); - - // Spawn task to print events if verbose - let verbose = cli.verbose; - let verbose_handle = if verbose { - Some(tokio::spawn(async move { - while let Some(event) = event_rx.recv().await { - println!("{}", event); - } - })) - } else { - // Drain events silently - tokio::spawn(async move { while event_rx.recv().await.is_some() {} }); - None + // Prepare context + let context = CurrentContext { + target: Some(ActionTarget::HttpRequest { id: request_id.clone() }), + environment_id: cli.environment.clone(), + workspace_id: None, + has_window: false, + can_prompt: false, }; - // Send the request - let sender = ReqwestSender::new().expect("Failed to create HTTP client"); - let response = sender.send(sendable, event_tx).await.expect("Failed to send request"); + // Prepare params + let params = ActionParams { + data: serde_json::json!({ + "render": true, + "follow_redirects": false, + "timeout_ms": 30000, + }), + }; - // Wait for event handler to finish - if let Some(handle) = verbose_handle { - let _ = handle.await; - } + // Invoke action + let action_id = ActionId::builtin("http", "send-request"); + let result = executor.invoke(&action_id, context, params).await.expect("Action failed"); - // Print response - if verbose { - println!(); - } - println!( - "HTTP {} {}", - response.status, - response.status_reason.as_deref().unwrap_or("") - ); - - if verbose { - for (name, value) in &response.headers { - println!("{}: {}", name, value); + // Handle result + match result { + ActionResult::Success { data, message } => { + if let Some(msg) = message { + println!("{}", msg); + } + if let Some(data) = data { + println!("{}", serde_json::to_string_pretty(&data).unwrap()); + } + } + ActionResult::RequiresInput { .. } => { + eprintln!("Action requires input (not supported in CLI)"); + } + ActionResult::Cancelled => { + eprintln!("Action cancelled"); } - println!(); } - - // Print body - let (body, _stats) = response.text().await.expect("Failed to read response body"); - println!("{}", body); } Commands::Get { url } => { if cli.verbose { diff --git a/crates/yaak-actions-builtin/Cargo.toml b/crates/yaak-actions-builtin/Cargo.toml new file mode 100644 index 00000000..1aacf1a0 --- /dev/null +++ b/crates/yaak-actions-builtin/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "yaak-actions-builtin" +version = "0.1.0" +edition = "2024" +authors = ["Gregory Schier"] +publish = false + +[dependencies] +yaak-actions = { workspace = true } +yaak-http = { workspace = true } +yaak-models = { workspace = true } +yaak-templates = { workspace = true } +yaak-plugins = { workspace = true } +yaak-crypto = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["sync", "rt-multi-thread"] } +log = { workspace = true } diff --git a/crates/yaak-actions-builtin/src/dependencies.rs b/crates/yaak-actions-builtin/src/dependencies.rs new file mode 100644 index 00000000..cc1caabb --- /dev/null +++ b/crates/yaak-actions-builtin/src/dependencies.rs @@ -0,0 +1,88 @@ +//! Dependency injection for built-in actions. + +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use yaak_crypto::manager::EncryptionManager; +use yaak_models::query_manager::QueryManager; +use yaak_plugins::events::PluginContext; +use yaak_plugins::manager::PluginManager; + +/// Dependencies needed by built-in action implementations. +/// +/// This struct bundles all the dependencies that action handlers need, +/// providing a clean way to initialize them in different contexts +/// (CLI, Tauri app, MCP server, etc.). +pub struct BuiltinActionDependencies { + pub query_manager: Arc, + pub plugin_manager: Arc, + pub encryption_manager: Arc, +} + +impl BuiltinActionDependencies { + /// Create dependencies for standalone usage (CLI, MCP server, etc.) + /// + /// This initializes all the necessary managers following the same pattern + /// as the yaak-cli implementation. + pub async fn new_standalone( + db_path: &Path, + blob_path: &Path, + app_id: &str, + plugin_vendored_dir: PathBuf, + plugin_installed_dir: PathBuf, + node_path: PathBuf, + ) -> Result> { + // Initialize database + let (query_manager, _, _) = yaak_models::init_standalone(db_path, blob_path)?; + + // Initialize encryption manager (takes QueryManager by value) + let encryption_manager = Arc::new(EncryptionManager::new( + query_manager.clone(), + app_id.to_string(), + )); + + let query_manager = Arc::new(query_manager); + + // Find plugin runtime + let plugin_runtime_main = std::env::var("YAAK_PLUGIN_RUNTIME") + .map(PathBuf::from) + .unwrap_or_else(|_| { + // Development fallback + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs") + }); + + // Initialize plugin manager + let plugin_manager = Arc::new( + PluginManager::new( + plugin_vendored_dir, + plugin_installed_dir, + node_path, + plugin_runtime_main, + false, // not sandboxed in CLI + ) + .await, + ); + + // Initialize plugins from database + let db = query_manager.connect(); + let plugins = db.list_plugins().unwrap_or_default(); + if !plugins.is_empty() { + let errors = plugin_manager + .initialize_all_plugins(plugins, &PluginContext::new_empty()) + .await; + for (plugin_dir, error_msg) in errors { + log::warn!( + "Failed to initialize plugin '{}': {}", + plugin_dir, + error_msg + ); + } + } + + Ok(Self { + query_manager, + plugin_manager, + encryption_manager, + }) + } +} diff --git a/crates/yaak-actions-builtin/src/http/mod.rs b/crates/yaak-actions-builtin/src/http/mod.rs new file mode 100644 index 00000000..58868dc6 --- /dev/null +++ b/crates/yaak-actions-builtin/src/http/mod.rs @@ -0,0 +1,24 @@ +//! HTTP action implementations. + +pub mod send; + +use crate::BuiltinActionDependencies; +use yaak_actions::{ActionError, ActionExecutor, ActionSource}; + +/// Register all HTTP-related actions with the executor. +pub async fn register_http_actions( + executor: &ActionExecutor, + deps: &BuiltinActionDependencies, +) -> Result<(), ActionError> { + let handler = send::HttpSendActionHandler { + query_manager: deps.query_manager.clone(), + plugin_manager: deps.plugin_manager.clone(), + encryption_manager: deps.encryption_manager.clone(), + }; + + executor + .register(send::metadata(), ActionSource::Builtin, handler) + .await?; + + Ok(()) +} diff --git a/crates/yaak-actions-builtin/src/http/send.rs b/crates/yaak-actions-builtin/src/http/send.rs new file mode 100644 index 00000000..8238f5d1 --- /dev/null +++ b/crates/yaak-actions-builtin/src/http/send.rs @@ -0,0 +1,293 @@ +//! HTTP send action implementation. + +use std::collections::BTreeMap; +use std::sync::Arc; +use serde_json::{json, Value}; +use tokio::sync::mpsc; +use yaak_actions::{ + ActionError, ActionGroupId, ActionHandler, ActionId, ActionMetadata, + ActionParams, ActionResult, ActionScope, CurrentContext, + RequiredContext, +}; +use yaak_crypto::manager::EncryptionManager; +use yaak_http::path_placeholders::apply_path_placeholders; +use yaak_http::sender::{HttpSender, ReqwestSender}; +use yaak_http::types::{SendableHttpRequest, SendableHttpRequestOptions}; +use yaak_models::models::{HttpRequest, HttpRequestHeader, HttpUrlParameter}; +use yaak_models::query_manager::QueryManager; +use yaak_models::render::make_vars_hashmap; +use yaak_plugins::events::{PluginContext, RenderPurpose}; +use yaak_plugins::manager::PluginManager; +use yaak_plugins::template_callback::PluginTemplateCallback; +use yaak_templates::{parse_and_render, render_json_value_raw, RenderOptions}; + +/// Handler for HTTP send action. +pub struct HttpSendActionHandler { + pub query_manager: Arc, + pub plugin_manager: Arc, + pub encryption_manager: Arc, +} + +/// Metadata for the HTTP send action. +pub fn metadata() -> ActionMetadata { + ActionMetadata { + id: ActionId::builtin("http", "send-request"), + label: "Send HTTP Request".to_string(), + description: Some("Execute an HTTP request and return the response".to_string()), + icon: Some("play".to_string()), + scope: ActionScope::HttpRequest, + keyboard_shortcut: None, + requires_selection: true, + enabled_condition: None, + group_id: Some(ActionGroupId::builtin("send")), + order: 10, + required_context: RequiredContext::requires_target(), + } +} + +impl ActionHandler for HttpSendActionHandler { + fn handle( + &self, + context: CurrentContext, + params: ActionParams, + ) -> std::pin::Pin< + Box> + Send + 'static>, + > { + let query_manager = self.query_manager.clone(); + let plugin_manager = self.plugin_manager.clone(); + let encryption_manager = self.encryption_manager.clone(); + + Box::pin(async move { + // Extract request_id from context + let request_id = context + .target + .as_ref() + .ok_or_else(|| { + ActionError::ContextMissing { + missing_fields: vec!["target".to_string()], + } + })? + .id() + .ok_or_else(|| { + ActionError::ContextMissing { + missing_fields: vec!["target.id".to_string()], + } + })? + .to_string(); + + // Fetch request and environment from database (synchronous) + let (request, environment_chain) = { + let db = query_manager.connect(); + + // Fetch HTTP request from database + let request = db.get_http_request(&request_id).map_err(|e| { + ActionError::Internal(format!("Failed to fetch request {}: {}", request_id, e)) + })?; + + // Resolve environment chain for variable substitution + let environment_chain = if let Some(env_id) = &context.environment_id { + db.resolve_environments( + &request.workspace_id, + request.folder_id.as_deref(), + Some(env_id), + ) + .unwrap_or_default() + } else { + db.resolve_environments( + &request.workspace_id, + request.folder_id.as_deref(), + None, + ) + .unwrap_or_default() + }; + + (request, environment_chain) + }; // db is dropped here + + // Create template callback with plugin support + let plugin_context = PluginContext::new(None, Some(request.workspace_id.clone())); + let template_callback = PluginTemplateCallback::new( + plugin_manager, + encryption_manager, + &plugin_context, + RenderPurpose::Send, + ); + + // Render templates in the request + let rendered_request = render_http_request( + &request, + environment_chain, + &template_callback, + &RenderOptions::throw(), + ) + .await + .map_err(|e| ActionError::Internal(format!("Failed to render request: {}", e)))?; + + // Build sendable request + let options = SendableHttpRequestOptions { + timeout: params + .data + .get("timeout_ms") + .and_then(|v| v.as_u64()) + .map(|ms| std::time::Duration::from_millis(ms)), + follow_redirects: params + .data + .get("follow_redirects") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + }; + + let sendable = SendableHttpRequest::from_http_request(&rendered_request, options) + .await + .map_err(|e| ActionError::Internal(format!("Failed to build request: {}", e)))?; + + // Create event channel + let (event_tx, mut event_rx) = mpsc::channel(100); + + // Spawn task to drain events + let _event_handle = tokio::spawn(async move { + while event_rx.recv().await.is_some() { + // For now, just drain events + // In the future, we could log them or emit them to UI + } + }); + + // Send the request + let sender = ReqwestSender::new() + .map_err(|e| ActionError::Internal(format!("Failed to create HTTP client: {}", e)))?; + let response = sender + .send(sendable, event_tx) + .await + .map_err(|e| ActionError::Internal(format!("Failed to send request: {}", e)))?; + + // Consume response body + let status = response.status; + let status_reason = response.status_reason.clone(); + let headers = response.headers.clone(); + let url = response.url.clone(); + + let (body_text, stats) = response + .text() + .await + .map_err(|e| ActionError::Internal(format!("Failed to read response body: {}", e)))?; + + // Return success result with response data + Ok(ActionResult::Success { + data: Some(json!({ + "status": status, + "statusReason": status_reason, + "headers": headers, + "body": body_text, + "contentLength": stats.size_decompressed, + "url": url, + })), + message: Some(format!("HTTP {}", status)), + }) + }) + } +} + +/// Helper function to render templates in an HTTP request. +/// Copied from yaak-cli implementation. +async fn render_http_request( + r: &HttpRequest, + environment_chain: Vec, + cb: &PluginTemplateCallback, + opt: &RenderOptions, +) -> Result { + let vars = &make_vars_hashmap(environment_chain); + + let mut url_parameters = Vec::new(); + for p in r.url_parameters.clone() { + if !p.enabled { + continue; + } + url_parameters.push(HttpUrlParameter { + enabled: p.enabled, + name: parse_and_render(p.name.as_str(), vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + value: parse_and_render(p.value.as_str(), vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + id: p.id, + }) + } + + let mut headers = Vec::new(); + for p in r.headers.clone() { + if !p.enabled { + continue; + } + headers.push(HttpRequestHeader { + enabled: p.enabled, + name: parse_and_render(p.name.as_str(), vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + value: parse_and_render(p.value.as_str(), vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + id: p.id, + }) + } + + let mut body = BTreeMap::new(); + for (k, v) in r.body.clone() { + body.insert( + k, + render_json_value_raw(v, vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + ); + } + + let authentication = { + let mut disabled = false; + let mut auth = BTreeMap::new(); + match r.authentication.get("disabled") { + Some(Value::Bool(true)) => { + disabled = true; + } + Some(Value::String(tmpl)) => { + disabled = parse_and_render(tmpl.as_str(), vars, cb, opt) + .await + .unwrap_or_default() + .is_empty(); + } + _ => {} + } + if disabled { + auth.insert("disabled".to_string(), Value::Bool(true)); + } else { + for (k, v) in r.authentication.clone() { + if k == "disabled" { + auth.insert(k, Value::Bool(false)); + } else { + auth.insert( + k, + render_json_value_raw(v, vars, cb, opt) + .await + .map_err(|e| e.to_string())?, + ); + } + } + } + auth + }; + + let url = parse_and_render(r.url.clone().as_str(), vars, cb, opt) + .await + .map_err(|e| e.to_string())?; + + // Apply path placeholders (e.g., /users/:id -> /users/123) + let (url, url_parameters) = apply_path_placeholders(&url, &url_parameters); + + Ok(HttpRequest { + url, + url_parameters, + headers, + body, + authentication, + ..r.to_owned() + }) +} diff --git a/crates/yaak-actions-builtin/src/lib.rs b/crates/yaak-actions-builtin/src/lib.rs new file mode 100644 index 00000000..f67cde12 --- /dev/null +++ b/crates/yaak-actions-builtin/src/lib.rs @@ -0,0 +1,11 @@ +//! Built-in action implementations for Yaak. +//! +//! This crate provides concrete implementations of built-in actions using +//! the yaak-actions framework. It depends on domain-specific crates like +//! yaak-http, yaak-models, yaak-plugins, etc. + +pub mod dependencies; +pub mod http; + +pub use dependencies::BuiltinActionDependencies; +pub use http::register_http_actions;