Compare commits

..

11 Commits

Author SHA1 Message Date
Gregory Schier
0264e59553 Improve CLI streaming output, logging flags, and schema/help ergonomics 2026-02-23 08:01:30 -08:00
Gregory Schier
53d86f5568 Add workspace/environment schemas and shared agent hints 2026-02-23 07:25:33 -08:00
Gregory Schier
1e7e1232da cli: compact schema output by default and tighten field docs 2026-02-22 16:18:29 -08:00
Gregory Schier
c31d477a90 cli: share HTTP helpers and improve schema guidance 2026-02-22 16:03:05 -08:00
Gregory Schier
443e1b8262 ci(cli): stamp Cargo version from release tag before build 2026-02-22 15:35:42 -08:00
Gregory Schier
c6b7cb2e32 ci(cli): use build script with SKIP_WASM_BUILD in release workflow 2026-02-22 15:26:21 -08:00
Gregory Schier
4aef826a80 Initialize plugins in PluginManager::new and fix CLI release deps 2026-02-22 15:06:55 -08:00
Gregory Schier
50c7992b42 Unify plugin bootstrap and prep vendored assets in CLI release 2026-02-22 15:01:34 -08:00
Gregory Schier
5e9aebda6f Embed CLI plugin assets and share bundled plugin registration 2026-02-22 14:44:40 -08:00
Gregory Schier
a1e84c7785 Bump @yaakapp/cli to 0.4.0-beta.2 2026-02-22 14:28:49 -08:00
Gregory Schier
fea4411afa Remove recursive API npm publish script 2026-02-22 14:21:07 -08:00
29 changed files with 511 additions and 215 deletions

View File

@@ -14,8 +14,44 @@ permissions:
contents: read
jobs:
prepare-vendored-assets:
name: Prepare vendored plugin assets
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: npm ci
- name: Build plugin assets
env:
SKIP_WASM_BUILD: "1"
run: |
npm run build
npm run vendor:vendor-plugins
- name: Upload vendored assets
uses: actions/upload-artifact@v4
with:
name: vendored-assets
path: |
crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs
crates-tauri/yaak-app/vendored/plugins
if-no-files-found: error
build-binaries:
name: Build ${{ matrix.pkg }}
needs: prepare-vendored-assets
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
@@ -67,6 +103,27 @@ jobs:
sudo apt-get update
sudo apt-get install -y pkg-config libdbus-1-dev
- name: Download vendored assets
uses: actions/download-artifact@v4
with:
name: vendored-assets
path: crates-tauri/yaak-app/vendored
- name: Set CLI build version
shell: bash
env:
WORKFLOW_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="$WORKFLOW_VERSION"
else
VERSION="${GITHUB_REF_NAME#yaak-cli-}"
fi
VERSION="${VERSION#v}"
echo "Building yaak version: $VERSION"
echo "YAAK_CLI_VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Build yaak
run: cargo build --locked --release -p yaak-cli --bin yaak --target ${{ matrix.target }}

1
Cargo.lock generated
View File

@@ -10149,6 +10149,7 @@ dependencies = [
"env_logger",
"futures",
"hex",
"include_dir",
"keyring",
"log 0.4.29",
"oxc_resolver",

View File

@@ -16,6 +16,7 @@ dirs = "6"
env_logger = "0.11"
futures = "0.3"
hex = { workspace = true }
include_dir = "0.7"
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
log = { workspace = true }
rand = "0.8"

View File

@@ -8,7 +8,9 @@ Current top-level commands:
```text
yaakcli send <request_id>
yaakcli agent-help
yaakcli workspace list
yaakcli workspace schema [--pretty]
yaakcli workspace show <workspace_id>
yaakcli workspace create --name <name>
yaakcli workspace create --json '{"name":"My Workspace"}'
@@ -31,6 +33,7 @@ yaakcli folder create '{"workspaceId":"wk_abc","name":"Auth"}'
yaakcli folder update --json '{"id":"fl_abc","name":"Auth v2"}'
yaakcli folder delete <folder_id> [--yes]
yaakcli environment list <workspace_id>
yaakcli environment schema [--pretty]
yaakcli environment show <environment_id>
yaakcli environment create <workspace_id> --name <name>
yaakcli environment create --json '{"workspaceId":"wk_abc","name":"Production"}'
@@ -43,7 +46,8 @@ Global options:
- `--data-dir <path>`: use a custom data directory
- `-e, --environment <id>`: environment to use during request rendering/sending
- `-v, --verbose`: verbose logging and send output
- `-v, --verbose`: verbose send output (events and streamed response body)
- `--log [level]`: enable CLI logging; optional level is `error|warn|info|debug|trace`
Notes:
@@ -51,6 +55,8 @@ Notes:
- `delete` commands prompt for confirmation unless `--yes` is provided.
- In non-interactive mode, `delete` commands require `--yes`.
- `create` and `update` commands support `--json` and positional JSON shorthand.
- For `create` commands, use one input mode at a time. Example: do not combine `<workspace_id>` with `--json`.
- Template tags use `${[ ... ]}` syntax (for example `${[API_BASE_URL]}`), not `{{ ... }}`.
- `update` uses JSON Merge Patch semantics (RFC 7386) for partial updates.
## Examples

View File

@@ -4,7 +4,14 @@ use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "yaak")]
#[command(about = "Yaak CLI - API client from the command line")]
#[command(version)]
#[command(version = crate::version::cli_version())]
#[command(disable_help_subcommand = true)]
#[command(after_help = r#"Agent Hints:
- Template variable syntax is ${[ my_var ]}, not {{ ... }}
- Template function syntax is ${[ namespace.my_func(a='aaa',b='bbb') ]}
- View JSONSchema for models before creating or updating (eg. `yaak request schema http`)
- Deletion requires confirmation (--yes for non-interactive environments)
"#)]
pub struct Cli {
/// Use a custom data directory
#[arg(long, global = true)]
@@ -14,10 +21,14 @@ pub struct Cli {
#[arg(long, short, global = true)]
pub environment: Option<String>,
/// Enable verbose logging
/// Enable verbose send output (events and streamed response body)
#[arg(long, short, global = true)]
pub verbose: bool,
/// Enable CLI logging; optionally set level (error|warn|info|debug|trace)
#[arg(long, global = true, value_name = "LEVEL", num_args = 0..=1, ignore_case = true)]
pub log: Option<Option<LogLevel>>,
#[command(subcommand)]
pub command: Commands,
}
@@ -71,6 +82,7 @@ pub struct SendArgs {
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct WorkspaceArgs {
#[command(subcommand)]
pub command: WorkspaceCommands,
@@ -81,6 +93,13 @@ pub enum WorkspaceCommands {
/// List all workspaces
List,
/// Output JSON schema for workspace create/update payloads
Schema {
/// Pretty-print schema JSON output
#[arg(long)]
pretty: bool,
},
/// Show a workspace as JSON
Show {
/// Workspace ID
@@ -125,6 +144,7 @@ pub enum WorkspaceCommands {
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct RequestArgs {
#[command(subcommand)]
pub command: RequestCommands,
@@ -154,6 +174,10 @@ pub enum RequestCommands {
Schema {
#[arg(value_enum)]
request_type: RequestSchemaType,
/// Pretty-print schema JSON output
#[arg(long)]
pretty: bool,
},
/// Create a new HTTP request
@@ -207,7 +231,29 @@ pub enum RequestSchemaType {
Websocket,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
pub fn as_filter(self) -> log::LevelFilter {
match self {
LogLevel::Error => log::LevelFilter::Error,
LogLevel::Warn => log::LevelFilter::Warn,
LogLevel::Info => log::LevelFilter::Info,
LogLevel::Debug => log::LevelFilter::Debug,
LogLevel::Trace => log::LevelFilter::Trace,
}
}
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct FolderArgs {
#[command(subcommand)]
pub command: FolderCommands,
@@ -264,6 +310,7 @@ pub enum FolderCommands {
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct EnvironmentArgs {
#[command(subcommand)]
pub command: EnvironmentCommands,
@@ -277,6 +324,13 @@ pub enum EnvironmentCommands {
workspace_id: String,
},
/// Output JSON schema for environment create/update payloads
Schema {
/// Pretty-print schema JSON output
#[arg(long)]
pretty: bool,
},
/// Show an environment as JSON
Show {
/// Environment ID
@@ -284,15 +338,22 @@ pub enum EnvironmentCommands {
},
/// Create an environment
#[command(after_help = r#"Modes (choose one):
1) yaak environment create <workspace_id> --name <name>
2) yaak environment create --json '{"workspaceId":"wk_abc","name":"Production"}'
3) yaak environment create '{"workspaceId":"wk_abc","name":"Production"}'
Do not combine <workspace_id> with --json."#)]
Create {
/// Workspace ID (or positional JSON payload shorthand)
/// Workspace ID for flag-based mode, or positional JSON payload shorthand
#[arg(value_name = "WORKSPACE_ID_OR_JSON")]
workspace_id: Option<String>,
/// Environment name
#[arg(short, long)]
name: Option<String>,
/// JSON payload
/// JSON payload (use instead of WORKSPACE_ID/--name)
#[arg(long)]
json: Option<String>,
},
@@ -320,6 +381,7 @@ pub enum EnvironmentCommands {
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct AuthArgs {
#[command(subcommand)]
pub command: AuthCommands,
@@ -338,6 +400,7 @@ pub enum AuthCommands {
}
#[derive(Args)]
#[command(disable_help_subcommand = true)]
pub struct PluginArgs {
#[command(subcommand)]
pub command: PluginCommands,

View File

@@ -1,5 +1,6 @@
use crate::cli::{AuthArgs, AuthCommands};
use crate::ui;
use crate::utils::http;
use base64::Engine as _;
use keyring::Entry;
use rand::RngCore;
@@ -136,10 +137,8 @@ async fn whoami() -> CommandResult {
};
let url = format!("{}/api/v1/whoami", environment.api_base_url());
let response = reqwest::Client::new()
let response = http::build_client(Some(&token))?
.get(url)
.header("X-Yaak-Session", token)
.header(reqwest::header::USER_AGENT, user_agent())
.send()
.await
.map_err(|e| format!("Failed to call whoami endpoint: {e}"))?;
@@ -156,7 +155,7 @@ async fn whoami() -> CommandResult {
.to_string(),
);
}
return Err(parse_api_error(status.as_u16(), &body));
return Err(http::parse_api_error(status.as_u16(), &body));
}
println!("{body}");
@@ -342,9 +341,8 @@ async fn write_redirect(stream: &mut TcpStream, location: &str) -> std::io::Resu
}
async fn exchange_access_token(oauth: &OAuthFlow, code: &str) -> CommandResult<String> {
let response = reqwest::Client::new()
let response = http::build_client(None)?
.post(&oauth.token_url)
.header(reqwest::header::USER_AGENT, user_agent())
.form(&[
("grant_type", "authorization_code"),
("client_id", OAUTH_CLIENT_ID),
@@ -406,38 +404,12 @@ fn delete_auth_token(environment: Environment) -> CommandResult {
}
}
fn parse_api_error(status: u16, body: &str) -> String {
if let Ok(value) = serde_json::from_str::<Value>(body) {
if let Some(message) = value.get("message").and_then(Value::as_str) {
return message.to_string();
}
if let Some(error) = value.get("error").and_then(Value::as_str) {
return error.to_string();
}
}
format!("API error {status}: {body}")
}
fn random_hex(bytes: usize) -> String {
let mut data = vec![0_u8; bytes];
OsRng.fill_bytes(&mut data);
hex::encode(data)
}
fn user_agent() -> String {
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
}
fn ua_platform() -> &'static str {
match std::env::consts::OS {
"windows" => "Win",
"darwin" => "Mac",
"linux" => "Linux",
_ => "Unknown",
}
}
fn confirm_open_browser() -> CommandResult<bool> {
if !io::stdin().is_terminal() {
return Ok(true);

View File

@@ -5,6 +5,8 @@ use crate::utils::json::{
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
validate_create_id,
};
use crate::utils::schema::append_agent_hints;
use schemars::schema_for;
use yaak_models::models::Environment;
use yaak_models::util::UpdateSource;
@@ -13,6 +15,7 @@ type CommandResult<T = ()> = std::result::Result<T, String>;
pub fn run(ctx: &CliContext, args: EnvironmentArgs) -> i32 {
let result = match args.command {
EnvironmentCommands::List { workspace_id } => list(ctx, &workspace_id),
EnvironmentCommands::Schema { pretty } => schema(pretty),
EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id),
EnvironmentCommands::Create { workspace_id, name, json } => {
create(ctx, workspace_id, name, json)
@@ -30,6 +33,23 @@ pub fn run(ctx: &CliContext, args: EnvironmentArgs) -> i32 {
}
}
fn schema(pretty: bool) -> CommandResult {
let mut schema =
serde_json::to_value(schema_for!(Environment)).map_err(|e| format!(
"Failed to serialize environment schema: {e}"
))?;
append_agent_hints(&mut schema);
let output = if pretty {
serde_json::to_string_pretty(&schema)
} else {
serde_json::to_string(&schema)
}
.map_err(|e| format!("Failed to format environment schema JSON: {e}"))?;
println!("{output}");
Ok(())
}
fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
let environments = ctx
.db()

View File

@@ -1,5 +1,6 @@
use crate::cli::{GenerateArgs, PluginArgs, PluginCommands, PluginPathArg};
use crate::ui;
use crate::utils::http;
use keyring::Entry;
use rand::Rng;
use rolldown::{
@@ -7,7 +8,6 @@ use rolldown::{
WatchOption, Watcher,
};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashSet;
use std::fs;
use std::io::{self, IsTerminal, Read, Write};
@@ -186,10 +186,8 @@ async fn publish(args: PluginPathArg) -> CommandResult {
ui::info("Uploading plugin");
let url = format!("{}/api/v1/plugins/publish", environment.api_base_url());
let response = reqwest::Client::new()
let response = http::build_client(Some(&token))?
.post(url)
.header("X-Yaak-Session", token)
.header(reqwest::header::USER_AGENT, user_agent())
.header(reqwest::header::CONTENT_TYPE, "application/zip")
.body(archive)
.send()
@@ -201,7 +199,7 @@ async fn publish(args: PluginPathArg) -> CommandResult {
response.text().await.map_err(|e| format!("Failed reading publish response body: {e}"))?;
if !status.is_success() {
return Err(parse_api_error(status.as_u16(), &body));
return Err(http::parse_api_error(status.as_u16(), &body));
}
let published: PublishResponse = serde_json::from_str(&body)
@@ -389,32 +387,6 @@ fn get_auth_token(environment: Environment) -> CommandResult<Option<String>> {
}
}
fn parse_api_error(status: u16, body: &str) -> String {
if let Ok(value) = serde_json::from_str::<Value>(body) {
if let Some(message) = value.get("message").and_then(Value::as_str) {
return message.to_string();
}
if let Some(error) = value.get("error").and_then(Value::as_str) {
return error.to_string();
}
}
format!("API error {status}: {body}")
}
fn user_agent() -> String {
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
}
fn ua_platform() -> &'static str {
match std::env::consts::OS {
"windows" => "Win",
"darwin" => "Mac",
"linux" => "Linux",
_ => "Unknown",
}
}
fn random_name() -> String {
const ADJECTIVES: &[&str] = &[
"young", "youthful", "yellow", "yielding", "yappy", "yawning", "yummy", "yucky", "yearly",

View File

@@ -5,10 +5,13 @@ use crate::utils::json::{
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
validate_create_id,
};
use crate::utils::schema::append_agent_hints;
use schemars::schema_for;
use serde_json::{Map, Value, json};
use std::collections::HashMap;
use std::io::Write;
use tokio::sync::mpsc;
use yaak_http::sender::HttpResponseEvent as SenderHttpResponseEvent;
use yaak::send::{SendHttpRequestByIdWithPluginsParams, send_http_request_by_id_with_plugins};
use yaak_models::models::{GrpcRequest, HttpRequest, WebsocketRequest};
use yaak_models::queries::any_request::AnyRequest;
@@ -35,8 +38,8 @@ pub async fn run(
}
};
}
RequestCommands::Schema { request_type } => {
return match schema(ctx, request_type).await {
RequestCommands::Schema { request_type, pretty } => {
return match schema(ctx, request_type, pretty).await {
Ok(()) => 0,
Err(error) => {
eprintln!("Error: {error}");
@@ -75,7 +78,7 @@ fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
Ok(())
}
async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandResult {
async fn schema(ctx: &CliContext, request_type: RequestSchemaType, pretty: bool) -> CommandResult {
let mut schema = match request_type {
RequestSchemaType::Http => serde_json::to_value(schema_for!(HttpRequest))
.map_err(|e| format!("Failed to serialize HTTP request schema: {e}"))?,
@@ -85,16 +88,51 @@ async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandRes
.map_err(|e| format!("Failed to serialize WebSocket request schema: {e}"))?,
};
enrich_schema_guidance(&mut schema, request_type);
append_agent_hints(&mut schema);
if let Err(error) = merge_auth_schema_from_plugins(ctx, &mut schema).await {
eprintln!("Warning: Failed to enrich authentication schema from plugins: {error}");
}
let output = serde_json::to_string_pretty(&schema)
.map_err(|e| format!("Failed to format schema JSON: {e}"))?;
let output =
if pretty { serde_json::to_string_pretty(&schema) } else { serde_json::to_string(&schema) }
.map_err(|e| format!("Failed to format schema JSON: {e}"))?;
println!("{output}");
Ok(())
}
fn enrich_schema_guidance(schema: &mut Value, request_type: RequestSchemaType) {
if !matches!(request_type, RequestSchemaType::Http) {
return;
}
let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) else {
return;
};
if let Some(url_schema) = properties.get_mut("url").and_then(Value::as_object_mut) {
append_description(
url_schema,
"For path segments like `/foo/:id/comments/:commentId`, put concrete values in `urlParameters` using names without `:` (for example `id`, `commentId`).",
);
}
}
fn append_description(schema: &mut Map<String, Value>, extra: &str) {
match schema.get_mut("description") {
Some(Value::String(existing)) if !existing.trim().is_empty() => {
if !existing.ends_with(' ') {
existing.push(' ');
}
existing.push_str(extra);
}
_ => {
schema.insert("description".to_string(), Value::String(extra.to_string()));
}
}
}
async fn merge_auth_schema_from_plugins(
ctx: &CliContext,
schema: &mut Value,
@@ -434,14 +472,24 @@ async fn send_http_request_by_id(
) -> Result<(), String> {
let plugin_context = PluginContext::new(None, Some(workspace_id.to_string()));
let (event_tx, mut event_rx) = mpsc::channel(100);
let (event_tx, mut event_rx) = mpsc::channel::<SenderHttpResponseEvent>(100);
let (body_chunk_tx, mut body_chunk_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let event_handle = tokio::spawn(async move {
while let Some(event) = event_rx.recv().await {
if verbose {
if verbose && !matches!(event, SenderHttpResponseEvent::ChunkReceived { .. }) {
println!("{}", event);
}
}
});
let body_handle = tokio::task::spawn_blocking(move || {
let mut stdout = std::io::stdout();
while let Some(chunk) = body_chunk_rx.blocking_recv() {
if stdout.write_all(&chunk).is_err() {
break;
}
let _ = stdout.flush();
}
});
let response_dir = ctx.data_dir().join("responses");
let result = send_http_request_by_id_with_plugins(SendHttpRequestByIdWithPluginsParams {
@@ -453,6 +501,7 @@ async fn send_http_request_by_id(
cookie_jar_id: None,
response_dir: &response_dir,
emit_events_to: Some(event_tx),
emit_response_body_chunks_to: Some(body_chunk_tx),
plugin_manager: ctx.plugin_manager(),
encryption_manager: ctx.encryption_manager.clone(),
plugin_context: &plugin_context,
@@ -462,24 +511,7 @@ async fn send_http_request_by_id(
.await;
let _ = event_handle.await;
let result = result.map_err(|e| e.to_string())?;
if verbose {
println!();
}
println!(
"HTTP {} {}",
result.response.status,
result.response.status_reason.as_deref().unwrap_or("")
);
if verbose {
for header in &result.response.headers {
println!("{}: {}", header.name, header.value);
}
println!();
}
let body = String::from_utf8(result.response_body)
.map_err(|e| format!("Failed to read response body: {e}"))?;
println!("{}", body);
let _ = body_handle.await;
result.map_err(|e| e.to_string())?;
Ok(())
}

View File

@@ -4,6 +4,8 @@ use crate::utils::confirm::confirm_delete;
use crate::utils::json::{
apply_merge_patch, parse_optional_json, parse_required_json, require_id, validate_create_id,
};
use crate::utils::schema::append_agent_hints;
use schemars::schema_for;
use yaak_models::models::Workspace;
use yaak_models::util::UpdateSource;
@@ -12,6 +14,7 @@ type CommandResult<T = ()> = std::result::Result<T, String>;
pub fn run(ctx: &CliContext, args: WorkspaceArgs) -> i32 {
let result = match args.command {
WorkspaceCommands::List => list(ctx),
WorkspaceCommands::Schema { pretty } => schema(pretty),
WorkspaceCommands::Show { workspace_id } => show(ctx, &workspace_id),
WorkspaceCommands::Create { name, json, json_input } => create(ctx, name, json, json_input),
WorkspaceCommands::Update { json, json_input } => update(ctx, json, json_input),
@@ -27,6 +30,23 @@ pub fn run(ctx: &CliContext, args: WorkspaceArgs) -> i32 {
}
}
fn schema(pretty: bool) -> CommandResult {
let mut schema =
serde_json::to_value(schema_for!(Workspace)).map_err(|e| format!(
"Failed to serialize workspace schema: {e}"
))?;
append_agent_hints(&mut schema);
let output = if pretty {
serde_json::to_string_pretty(&schema)
} else {
serde_json::to_string(&schema)
}
.map_err(|e| format!("Failed to format workspace schema JSON: {e}"))?;
println!("{output}");
Ok(())
}
fn list(ctx: &CliContext) -> CommandResult {
let workspaces =
ctx.db().list_workspaces().map_err(|e| format!("Failed to list workspaces: {e}"))?;

View File

@@ -1,4 +1,6 @@
use crate::plugin_events::CliPluginEventBridge;
use include_dir::{Dir, include_dir};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -9,6 +11,13 @@ use yaak_models::query_manager::QueryManager;
use yaak_plugins::events::PluginContext;
use yaak_plugins::manager::PluginManager;
const EMBEDDED_PLUGIN_RUNTIME: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs"
));
static EMBEDDED_VENDORED_PLUGINS: Dir<'_> =
include_dir!("$CARGO_MANIFEST_DIR/../../crates-tauri/yaak-app/vendored/plugins");
pub struct CliContext {
data_dir: PathBuf,
query_manager: QueryManager,
@@ -33,37 +42,32 @@ impl CliContext {
let installed_plugin_dir = data_dir.join("installed-plugins");
let node_bin_path = PathBuf::from("node");
prepare_embedded_vendored_plugins(&vendored_plugin_dir)
.expect("Failed to prepare bundled plugins");
let plugin_runtime_main =
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs")
prepare_embedded_plugin_runtime(&data_dir)
.expect("Failed to prepare embedded plugin runtime")
});
let plugin_manager = Arc::new(
PluginManager::new(
vendored_plugin_dir,
installed_plugin_dir,
node_bin_path,
plugin_runtime_main,
false,
)
.await,
);
let plugins = query_manager.connect().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 {
eprintln!(
"Warning: Failed to initialize plugin '{}': {}",
plugin_dir, error_msg
);
match PluginManager::new(
vendored_plugin_dir,
installed_plugin_dir,
node_bin_path,
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
false,
)
.await
{
Ok(plugin_manager) => Some(Arc::new(plugin_manager)),
Err(err) => {
eprintln!("Warning: Failed to initialize plugins: {err}");
None
}
}
Some(plugin_manager)
} else {
None
};
@@ -113,3 +117,17 @@ impl CliContext {
}
}
}
fn prepare_embedded_plugin_runtime(data_dir: &Path) -> std::io::Result<PathBuf> {
let runtime_dir = data_dir.join("vendored").join("plugin-runtime");
fs::create_dir_all(&runtime_dir)?;
let runtime_main = runtime_dir.join("index.cjs");
fs::write(&runtime_main, EMBEDDED_PLUGIN_RUNTIME)?;
Ok(runtime_main)
}
fn prepare_embedded_vendored_plugins(vendored_plugin_dir: &Path) -> std::io::Result<()> {
fs::create_dir_all(vendored_plugin_dir)?;
EMBEDDED_VENDORED_PLUGINS.extract(vendored_plugin_dir)?;
Ok(())
}

View File

@@ -4,6 +4,7 @@ mod context;
mod plugin_events;
mod ui;
mod utils;
mod version;
use clap::Parser;
use cli::{Cli, Commands, RequestCommands};
@@ -11,10 +12,18 @@ use context::CliContext;
#[tokio::main]
async fn main() {
let Cli { data_dir, environment, verbose, command } = Cli::parse();
let Cli { data_dir, environment, verbose, log, command } = Cli::parse();
if verbose {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
if let Some(log_level) = log {
match log_level {
Some(level) => {
env_logger::Builder::new().filter_level(level.as_filter()).init();
}
None => {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.init();
}
}
}
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };

View File

@@ -0,0 +1,47 @@
use reqwest::Client;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT};
use serde_json::Value;
pub fn build_client(session_token: Option<&str>) -> Result<Client, String> {
let mut headers = HeaderMap::new();
let user_agent = HeaderValue::from_str(&user_agent())
.map_err(|e| format!("Failed to build user-agent header: {e}"))?;
headers.insert(USER_AGENT, user_agent);
if let Some(token) = session_token {
let token_value = HeaderValue::from_str(token)
.map_err(|e| format!("Failed to build session header: {e}"))?;
headers.insert(HeaderName::from_static("x-yaak-session"), token_value);
}
Client::builder()
.default_headers(headers)
.build()
.map_err(|e| format!("Failed to initialize HTTP client: {e}"))
}
pub fn parse_api_error(status: u16, body: &str) -> String {
if let Ok(value) = serde_json::from_str::<Value>(body) {
if let Some(message) = value.get("message").and_then(Value::as_str) {
return message.to_string();
}
if let Some(error) = value.get("error").and_then(Value::as_str) {
return error.to_string();
}
}
format!("API error {status}: {body}")
}
fn user_agent() -> String {
format!("YaakCli/{} ({})", crate::version::cli_version(), ua_platform())
}
fn ua_platform() -> &'static str {
match std::env::consts::OS {
"windows" => "Win",
"darwin" => "Mac",
"linux" => "Linux",
_ => "Unknown",
}
}

View File

@@ -1,2 +1,4 @@
pub mod confirm;
pub mod http;
pub mod json;
pub mod schema;

View File

@@ -0,0 +1,15 @@
use serde_json::{Value, json};
pub fn append_agent_hints(schema: &mut Value) {
let Some(schema_obj) = schema.as_object_mut() else {
return;
};
schema_obj.insert(
"x-yaak-agent-hints".to_string(),
json!({
"templateVariableSyntax": "${[ my_var ]}",
"templateFunctionSyntax": "${[ namespace.my_func(a='aaa',b='bbb') ]}",
}),
);
}

View File

@@ -0,0 +1,3 @@
pub fn cli_version() -> &'static str {
option_env!("YAAK_CLI_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
}

View File

@@ -78,3 +78,19 @@ fn json_create_and_update_merge_patch_round_trip() {
.stdout(contains("\"name\": \"Json Environment\""))
.stdout(contains("\"color\": \"#00ff00\""));
}
#[test]
fn environment_schema_outputs_json_schema() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path();
cli_cmd(data_dir)
.args(["environment", "schema"])
.assert()
.success()
.stdout(contains("\"type\":\"object\""))
.stdout(contains("\"x-yaak-agent-hints\""))
.stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
.stdout(contains("\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\""))
.stdout(contains("\"workspaceId\""));
}

View File

@@ -156,7 +156,6 @@ fn request_send_persists_response_body_and_events() {
.args(["request", "send", &request_id])
.assert()
.success()
.stdout(contains("HTTP 200 OK"))
.stdout(contains("hello from integration test"));
let qm = query_manager(data_dir);
@@ -189,6 +188,24 @@ fn request_schema_http_outputs_json_schema() {
.args(["request", "schema", "http"])
.assert()
.success()
.stdout(contains("\"type\":\"object\""))
.stdout(contains("\"x-yaak-agent-hints\""))
.stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
.stdout(contains("\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\""))
.stdout(contains("\"authentication\":"))
.stdout(contains("/foo/:id/comments/:commentId"))
.stdout(contains("put concrete values in `urlParameters`"));
}
#[test]
fn request_schema_http_pretty_prints_with_flag() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path();
cli_cmd(data_dir)
.args(["request", "schema", "http", "--pretty"])
.assert()
.success()
.stdout(contains("\"type\": \"object\""))
.stdout(contains("\"authentication\""));
}

View File

@@ -31,7 +31,6 @@ fn top_level_send_workspace_sends_http_requests_and_prints_summary() {
.args(["send", "wk_test"])
.assert()
.success()
.stdout(contains("HTTP 200 OK"))
.stdout(contains("workspace bulk send"))
.stdout(contains("Send summary: 1 succeeded, 0 failed"));
}
@@ -62,7 +61,6 @@ fn top_level_send_folder_sends_http_requests_and_prints_summary() {
.args(["send", "fl_test"])
.assert()
.success()
.stdout(contains("HTTP 200 OK"))
.stdout(contains("folder bulk send"))
.stdout(contains("Send summary: 1 succeeded, 0 failed"));
}

View File

@@ -57,3 +57,19 @@ fn json_create_and_update_merge_patch_round_trip() {
.stdout(contains("\"name\": \"Json Workspace\""))
.stdout(contains("\"description\": \"Updated via JSON\""));
}
#[test]
fn workspace_schema_outputs_json_schema() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path();
cli_cmd(data_dir)
.args(["workspace", "schema"])
.assert()
.success()
.stdout(contains("\"type\":\"object\""))
.stdout(contains("\"x-yaak-agent-hints\""))
.stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
.stdout(contains("\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\""))
.stdout(contains("\"name\""));
}

View File

@@ -154,6 +154,7 @@ async fn send_http_request_inner<R: Runtime>(
cookie_jar_id,
response_dir: &response_dir,
emit_events_to: None,
emit_response_body_chunks_to: None,
existing_response: Some(response_ctx.response().clone()),
plugin_manager,
encryption_manager,

View File

@@ -23,12 +23,11 @@ use tokio::sync::Mutex;
use ts_rs::TS;
use yaak_api::yaak_api_client;
use yaak_models::models::Plugin;
use yaak_models::util::UpdateSource;
use yaak_plugins::api::{
PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse, check_plugin_updates,
search_plugins,
};
use yaak_plugins::events::{Color, Icon, PluginContext, ShowToastRequest};
use yaak_plugins::events::PluginContext;
use yaak_plugins::install::{delete_and_uninstall, download_and_install};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_meta::get_plugin_meta;
@@ -268,6 +267,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.join("index.cjs");
let dev_mode = is_dev();
let query_manager =
app_handle.state::<yaak_models::query_manager::QueryManager>().inner().clone();
// Create plugin manager asynchronously
let app_handle_clone = app_handle.clone();
@@ -277,53 +278,12 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
installed_plugin_dir,
node_bin_path,
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
dev_mode,
)
.await;
// Initialize all plugins after manager is created
let bundled_dirs = manager
.list_bundled_plugin_dirs()
.await
.expect("Failed to list bundled plugins");
// Ensure all bundled plugins make it into the database
let db = app_handle_clone.db();
for dir in &bundled_dirs {
if db.get_plugin_by_directory(dir).is_none() {
db.upsert_plugin(
&Plugin {
directory: dir.clone(),
enabled: true,
url: None,
..Default::default()
},
&UpdateSource::Background,
)
.expect("Failed to upsert bundled plugin");
}
}
// Get all plugins from database and initialize
let plugins = db.list_plugins().expect("Failed to list plugins from database");
drop(db); // Explicitly drop the connection before await
let errors =
manager.initialize_all_plugins(plugins, &PluginContext::new_empty()).await;
// Show toast for any failed plugins
for (plugin_dir, error_msg) in errors {
let plugin_name = plugin_dir.split('/').last().unwrap_or(&plugin_dir);
let toast = ShowToastRequest {
message: format!("Failed to start plugin '{}': {}", plugin_name, error_msg),
color: Some(Color::Danger),
icon: Some(Icon::AlertTriangle),
timeout: Some(10000),
};
if let Err(emit_err) = app_handle_clone.emit("show_toast", toast) {
error!("Failed to emit toast for plugin error: {emit_err:?}");
}
}
.await
.expect("Failed to initialize plugins");
app_handle_clone.manage(manager);
});

View File

@@ -74,7 +74,7 @@ pub struct ClientCertificate {
pub enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct DnsOverride {
@@ -293,7 +293,7 @@ impl UpsertModelInfo for Settings {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
#[enum_def(table_name = "workspaces")]
@@ -590,7 +590,7 @@ impl UpsertModelInfo for CookieJar {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
#[enum_def(table_name = "environments")]
@@ -611,6 +611,8 @@ pub struct Environment {
pub base: bool,
pub parent_model: String,
pub parent_id: Option<String>,
/// Variables defined in this environment scope.
/// Child environments override parent variables by name.
pub variables: Vec<EnvironmentVariable>,
pub color: Option<String>,
pub sort_priority: f64,
@@ -698,7 +700,7 @@ impl UpsertModelInfo for Environment {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct EnvironmentVariable {
@@ -845,6 +847,8 @@ pub struct HttpUrlParameter {
#[serde(default = "default_true")]
#[ts(optional, as = "Option<bool>")]
pub enabled: bool,
/// Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
/// Other entries are appended as query parameters
pub name: String,
pub value: String,
#[ts(optional, as = "Option<String>")]
@@ -877,6 +881,7 @@ pub struct HttpRequest {
pub name: String,
pub sort_priority: f64,
pub url: String,
/// URL parameters used for both path placeholders (`:id`) and query string entries.
pub url_parameters: Vec<HttpUrlParameter>,
}
@@ -1118,6 +1123,7 @@ pub struct WebsocketRequest {
pub name: String,
pub sort_priority: f64,
pub url: String,
/// URL parameters used for both path placeholders (`:id`) and query string entries.
pub url_parameters: Vec<HttpUrlParameter>,
}
@@ -1728,6 +1734,7 @@ pub struct GrpcRequest {
pub name: String,
pub service: Option<String>,
pub sort_priority: f64,
/// Server URL (http for plaintext or https for secure)
pub url: String,
}

View File

@@ -34,7 +34,8 @@ use tokio::sync::mpsc::error::TrySendError;
use tokio::sync::{Mutex, mpsc, oneshot};
use tokio::time::{Instant, timeout};
use yaak_models::models::Plugin;
use yaak_models::util::generate_id;
use yaak_models::query_manager::QueryManager;
use yaak_models::util::{UpdateSource, generate_id};
use yaak_templates::error::Error::RenderError;
use yaak_templates::error::Result as TemplateResult;
@@ -61,14 +62,18 @@ impl PluginManager {
/// * `installed_plugin_dir` - Path to installed plugins directory
/// * `node_bin_path` - Path to the yaaknode binary
/// * `plugin_runtime_main` - Path to the plugin runtime index.cjs
/// * `query_manager` - Query manager for bundled plugin registration and loading
/// * `plugin_context` - Context to use while initializing plugins
/// * `dev_mode` - Whether the app is in dev mode (affects plugin loading)
pub async fn new(
vendored_plugin_dir: PathBuf,
installed_plugin_dir: PathBuf,
node_bin_path: PathBuf,
plugin_runtime_main: PathBuf,
query_manager: &QueryManager,
plugin_context: &PluginContext,
dev_mode: bool,
) -> PluginManager {
) -> Result<PluginManager> {
let (events_tx, mut events_rx) = mpsc::channel(2048);
let (kill_server_tx, kill_server_rx) = tokio::sync::watch::channel(false);
let (killed_tx, killed_rx) = oneshot::channel();
@@ -151,12 +156,40 @@ impl PluginManager {
&kill_server_rx,
killed_tx,
)
.await
.unwrap();
.await?;
info!("Waiting for plugins to initialize");
init_plugins_task.await.unwrap();
init_plugins_task.await.map_err(|e| PluginErr(e.to_string()))?;
plugin_manager
let bundled_dirs = plugin_manager.list_bundled_plugin_dirs().await?;
let db = query_manager.connect();
for dir in bundled_dirs {
if db.get_plugin_by_directory(&dir).is_none() {
db.upsert_plugin(
&Plugin {
directory: dir,
enabled: true,
url: None,
..Default::default()
},
&UpdateSource::Background,
)?;
}
}
let plugins = db.list_plugins()?;
drop(db);
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
if !init_errors.is_empty() {
let joined = init_errors
.into_iter()
.map(|(dir, err)| format!("{dir}: {err}"))
.collect::<Vec<_>>()
.join("; ");
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
}
Ok(plugin_manager)
}
/// Get the vendored plugin directory path (resolves dev mode path if applicable)

View File

@@ -239,6 +239,7 @@ pub struct SendHttpRequestByIdParams<'a, T: TemplateCallback> {
pub cookie_jar_id: Option<String>,
pub response_dir: &'a Path,
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
pub emit_response_body_chunks_to: Option<mpsc::UnboundedSender<Vec<u8>>>,
pub cancelled_rx: Option<watch::Receiver<bool>>,
pub prepare_sendable_request: Option<&'a dyn PrepareSendableRequest>,
pub executor: Option<&'a dyn SendRequestExecutor>,
@@ -255,6 +256,7 @@ pub struct SendHttpRequestParams<'a, T: TemplateCallback> {
pub cookie_jar_id: Option<String>,
pub response_dir: &'a Path,
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
pub emit_response_body_chunks_to: Option<mpsc::UnboundedSender<Vec<u8>>>,
pub cancelled_rx: Option<watch::Receiver<bool>>,
pub auth_context_id: Option<String>,
pub existing_response: Option<HttpResponse>,
@@ -271,6 +273,7 @@ pub struct SendHttpRequestWithPluginsParams<'a> {
pub cookie_jar_id: Option<String>,
pub response_dir: &'a Path,
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
pub emit_response_body_chunks_to: Option<mpsc::UnboundedSender<Vec<u8>>>,
pub existing_response: Option<HttpResponse>,
pub plugin_manager: Arc<PluginManager>,
pub encryption_manager: Arc<EncryptionManager>,
@@ -288,6 +291,7 @@ pub struct SendHttpRequestByIdWithPluginsParams<'a> {
pub cookie_jar_id: Option<String>,
pub response_dir: &'a Path,
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
pub emit_response_body_chunks_to: Option<mpsc::UnboundedSender<Vec<u8>>>,
pub plugin_manager: Arc<PluginManager>,
pub encryption_manager: Arc<EncryptionManager>,
pub plugin_context: &'a PluginContext,
@@ -353,6 +357,7 @@ pub async fn send_http_request_by_id_with_plugins(
cookie_jar_id: params.cookie_jar_id,
response_dir: params.response_dir,
emit_events_to: params.emit_events_to,
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
existing_response: None,
plugin_manager: params.plugin_manager,
encryption_manager: params.encryption_manager,
@@ -397,6 +402,7 @@ pub async fn send_http_request_with_plugins(
cookie_jar_id: params.cookie_jar_id,
response_dir: params.response_dir,
emit_events_to: params.emit_events_to,
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
cancelled_rx: params.cancelled_rx,
auth_context_id: None,
existing_response: params.existing_response,
@@ -427,6 +433,7 @@ pub async fn send_http_request_by_id<T: TemplateCallback>(
cookie_jar_id: params.cookie_jar_id,
response_dir: params.response_dir,
emit_events_to: params.emit_events_to,
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
cancelled_rx: params.cancelled_rx,
existing_response: None,
prepare_sendable_request: params.prepare_sendable_request,
@@ -687,13 +694,17 @@ pub async fn send_http_request<T: TemplateCallback>(
Ok(n) => {
written_bytes += n;
let start_idx = response_body.len() - n;
file.write_all(&response_body[start_idx..]).await.map_err(|source| {
let chunk = &response_body[start_idx..];
file.write_all(chunk).await.map_err(|source| {
SendHttpRequestError::WriteResponseBody { path: body_path.clone(), source }
})?;
file.flush().await.map_err(|source| SendHttpRequestError::WriteResponseBody {
path: body_path.clone(),
source,
})?;
if let Some(tx) = params.emit_response_body_chunks_to.as_ref() {
let _ = tx.send(chunk.to_vec());
}
let now = Instant::now();
let should_update = now.duration_since(last_progress_update).as_millis()

56
package-lock.json generated
View File

@@ -73,7 +73,7 @@
"devDependencies": {
"@biomejs/biome": "^2.3.13",
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.4.0-beta.1",
"@yaakapp/cli": "^0.4.0-beta.2",
"dotenv-cli": "^11.0.0",
"husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0",
@@ -4326,9 +4326,9 @@
"link": true
},
"node_modules/@yaakapp/cli": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.1.tgz",
"integrity": "sha512-Q/nRjS9nSNZy8PSBJ8VfczwSmfK4k/s9Co5YnsCiomyFppDiIR4hGjwwXAZDjcjnVZYrzAboRM2BXMjJ9PfZCA==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.2.tgz",
"integrity": "sha512-UXPxTS9oWVCIr4rShC7HjcAX+gSmw/BQ5F1Xp3Rub3vY/G7+513JJsc1HhLGVZqFfOVRSMEKRxtF9/9okSyiHg==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -4336,18 +4336,18 @@
"yaakcli": "bin/cli.js"
},
"optionalDependencies": {
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.1",
"@yaakapp/cli-darwin-x64": "0.4.0-beta.1",
"@yaakapp/cli-linux-arm64": "0.4.0-beta.1",
"@yaakapp/cli-linux-x64": "0.4.0-beta.1",
"@yaakapp/cli-win32-arm64": "0.4.0-beta.1",
"@yaakapp/cli-win32-x64": "0.4.0-beta.1"
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.2",
"@yaakapp/cli-darwin-x64": "0.4.0-beta.2",
"@yaakapp/cli-linux-arm64": "0.4.0-beta.2",
"@yaakapp/cli-linux-x64": "0.4.0-beta.2",
"@yaakapp/cli-win32-arm64": "0.4.0-beta.2",
"@yaakapp/cli-win32-x64": "0.4.0-beta.2"
}
},
"node_modules/@yaakapp/cli-darwin-arm64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.1.tgz",
"integrity": "sha512-afvIQeT35bI6d6fRyJ6hnfr0FnzajL4wiVPniezXXEFsVjG74/FPB7jYHRTnIVwG+tPziOND1RG1ff3Hle/Duw==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.2.tgz",
"integrity": "sha512-mqkyH5tIPRLs9JumP9ZmzjB5gIwmOL1yCDoJ1qVU8DIJ7mwlcQaPGYTK98pVdBcKOjofVakBTcpol9P8rBv4qw==",
"cpu": [
"arm64"
],
@@ -4358,9 +4358,9 @@
]
},
"node_modules/@yaakapp/cli-darwin-x64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.1.tgz",
"integrity": "sha512-4j2AwBnbmVgbzkqLDEZtSQ+/PvJ/eo6GecJcBW92YWnwR4+/R5vPT87Pd0Dy2L4X7Hy2VVmNbwNAEOVvef+u6g==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.2.tgz",
"integrity": "sha512-QI/H2yUF8CkJq+cnRthoUWWTEJPH4QPA78FYcGjFRhvBaj1m2G/GlCA5NkTXm/fvIjNkQEODSihXrhU+zoSSCw==",
"cpu": [
"x64"
],
@@ -4371,9 +4371,9 @@
]
},
"node_modules/@yaakapp/cli-linux-arm64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.1.tgz",
"integrity": "sha512-WgqeTcj7BIgCF1chunX/XcxmpArftYATO1q61aNPxNxIVDKVqbbOh/rLByvwFM8q9A49OjgcLI4QQT1CWdBLig==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.2.tgz",
"integrity": "sha512-nvAp97LkgRpqVHyMwDdpkzlKOWG2kJXezCLRZaRWaEpbnNuviSF+0yzCuFGZRHEEspj7B0TiM+sKGkpvjNlweA==",
"cpu": [
"arm64"
],
@@ -4384,9 +4384,9 @@
]
},
"node_modules/@yaakapp/cli-linux-x64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.1.tgz",
"integrity": "sha512-eMN7CiTbB4pH5NIHTGqNiv56PXb+V7cGg/yU+FopRk69ETH1n+cwGlx1UxSUlcLnaxx0s6pPoo3e+C4cq+i0BQ==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.2.tgz",
"integrity": "sha512-9/qAMNrtE9glxih3XWGfFssIJpQ4mHNUTuWYKroc0aZZUrunnCw3tX1tQtFDxy0QRIZcGlBeBRtgxuuBd2fYbg==",
"cpu": [
"x64"
],
@@ -4397,9 +4397,9 @@
]
},
"node_modules/@yaakapp/cli-win32-arm64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.1.tgz",
"integrity": "sha512-4ygqyEeHLNlTAWYpg83SuLK9dx1af6HqSfHnWFBigflENdZejD/oSGNr1XZeB61QQnjlvaJaqENs4BS9UI9piA==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.2.tgz",
"integrity": "sha512-eM1zL+hl0y3NBLxWO90y9VyaFsAf0HAsECBWvhKhvEdd6KG4K1XzpXrC30cHQBGePIrCa/az8eSuvTde0Z2C/g==",
"cpu": [
"arm64"
],
@@ -4410,9 +4410,9 @@
]
},
"node_modules/@yaakapp/cli-win32-x64": {
"version": "0.4.0-beta.1",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.1.tgz",
"integrity": "sha512-Xpxk+e9RWKOzY9siMDlgZPa0HU61GsTn5CTHOpPxUJHmUu+7urJ+sEgaoZx4fRjPBH+FVD9Y4s+zRCawd7O75w==",
"version": "0.4.0-beta.2",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.2.tgz",
"integrity": "sha512-ySdiK0h216EqURkM5KZoqbPTgbIX4eNK/IgrKwSazxRb369HOZYQ8X68as+VRxEL4NCMmWlQNdbBDuf+apg/mg==",
"cpu": [
"x64"
],

View File

@@ -70,7 +70,6 @@
"app-dev": "node scripts/run-dev.mjs",
"migration": "node scripts/create-migration.cjs",
"build": "npm run --workspaces --if-present build",
"build-plugins": "npm run --workspaces --if-present build",
"test": "npm run --workspaces --if-present test",
"icons": "run-p icons:*",
"icons:dev": "tauri icon crates-tauri/yaak-app/icons/icon-dev.png --output crates-tauri/yaak-app/icons/dev",
@@ -98,7 +97,7 @@
"devDependencies": {
"@biomejs/biome": "^2.3.13",
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.4.0-beta.1",
"@yaakapp/cli": "^0.4.0-beta.2",
"dotenv-cli": "^11.0.0",
"husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0",

View File

@@ -27,7 +27,6 @@
"build:copy-types": "run-p build:copy-types:*",
"build:copy-types:root": "cpy --flat ../../crates/yaak-plugins/bindings/*.ts ./src/bindings",
"build:copy-types:next": "cpy --flat ../../crates/yaak-plugins/bindings/serde_json/*.ts ./src/bindings/serde_json",
"publish": "npm publish",
"prepublishOnly": "npm run build"
},
"dependencies": {

View File

@@ -1,4 +1,4 @@
const { readdirSync, cpSync, existsSync } = require('node:fs');
const { readdirSync, cpSync, existsSync, mkdirSync } = require('node:fs');
const path = require('node:path');
const pluginsDir = path.join(__dirname, '..', 'plugins');
@@ -24,6 +24,7 @@ for (const name of readdirSync(pluginsDir)) {
continue;
}
const destDir = path.join(__dirname, '../crates-tauri/yaak-app/vendored/plugins/', name);
mkdirSync(destDir, { recursive: true });
console.log(`Copying ${name} to ${destDir}`);
cpSync(path.join(dir, 'package.json'), path.join(destDir, 'package.json'));
cpSync(path.join(dir, 'build'), path.join(destDir, 'build'), { recursive: true });