mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-27 12:20:24 -05:00
Compare commits
2 Commits
codex/cli-
...
yaak-cli-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0264e59553 | ||
|
|
53d86f5568 |
@@ -8,7 +8,9 @@ Current top-level commands:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
yaakcli send <request_id>
|
yaakcli send <request_id>
|
||||||
|
yaakcli agent-help
|
||||||
yaakcli workspace list
|
yaakcli workspace list
|
||||||
|
yaakcli workspace schema [--pretty]
|
||||||
yaakcli workspace show <workspace_id>
|
yaakcli workspace show <workspace_id>
|
||||||
yaakcli workspace create --name <name>
|
yaakcli workspace create --name <name>
|
||||||
yaakcli workspace create --json '{"name":"My Workspace"}'
|
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 update --json '{"id":"fl_abc","name":"Auth v2"}'
|
||||||
yaakcli folder delete <folder_id> [--yes]
|
yaakcli folder delete <folder_id> [--yes]
|
||||||
yaakcli environment list <workspace_id>
|
yaakcli environment list <workspace_id>
|
||||||
|
yaakcli environment schema [--pretty]
|
||||||
yaakcli environment show <environment_id>
|
yaakcli environment show <environment_id>
|
||||||
yaakcli environment create <workspace_id> --name <name>
|
yaakcli environment create <workspace_id> --name <name>
|
||||||
yaakcli environment create --json '{"workspaceId":"wk_abc","name":"Production"}'
|
yaakcli environment create --json '{"workspaceId":"wk_abc","name":"Production"}'
|
||||||
@@ -43,7 +46,8 @@ Global options:
|
|||||||
|
|
||||||
- `--data-dir <path>`: use a custom data directory
|
- `--data-dir <path>`: use a custom data directory
|
||||||
- `-e, --environment <id>`: environment to use during request rendering/sending
|
- `-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:
|
Notes:
|
||||||
|
|
||||||
@@ -51,6 +55,8 @@ Notes:
|
|||||||
- `delete` commands prompt for confirmation unless `--yes` is provided.
|
- `delete` commands prompt for confirmation unless `--yes` is provided.
|
||||||
- In non-interactive mode, `delete` commands require `--yes`.
|
- In non-interactive mode, `delete` commands require `--yes`.
|
||||||
- `create` and `update` commands support `--json` and positional JSON shorthand.
|
- `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.
|
- `update` uses JSON Merge Patch semantics (RFC 7386) for partial updates.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ use std::path::PathBuf;
|
|||||||
#[command(name = "yaak")]
|
#[command(name = "yaak")]
|
||||||
#[command(about = "Yaak CLI - API client from the command line")]
|
#[command(about = "Yaak CLI - API client from the command line")]
|
||||||
#[command(version = crate::version::cli_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 {
|
pub struct Cli {
|
||||||
/// Use a custom data directory
|
/// Use a custom data directory
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
@@ -14,10 +21,14 @@ pub struct Cli {
|
|||||||
#[arg(long, short, global = true)]
|
#[arg(long, short, global = true)]
|
||||||
pub environment: Option<String>,
|
pub environment: Option<String>,
|
||||||
|
|
||||||
/// Enable verbose logging
|
/// Enable verbose send output (events and streamed response body)
|
||||||
#[arg(long, short, global = true)]
|
#[arg(long, short, global = true)]
|
||||||
pub verbose: bool,
|
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)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
@@ -71,6 +82,7 @@ pub struct SendArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct WorkspaceArgs {
|
pub struct WorkspaceArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: WorkspaceCommands,
|
pub command: WorkspaceCommands,
|
||||||
@@ -81,6 +93,13 @@ pub enum WorkspaceCommands {
|
|||||||
/// List all workspaces
|
/// List all workspaces
|
||||||
List,
|
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 a workspace as JSON
|
||||||
Show {
|
Show {
|
||||||
/// Workspace ID
|
/// Workspace ID
|
||||||
@@ -125,6 +144,7 @@ pub enum WorkspaceCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct RequestArgs {
|
pub struct RequestArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: RequestCommands,
|
pub command: RequestCommands,
|
||||||
@@ -211,7 +231,29 @@ pub enum RequestSchemaType {
|
|||||||
Websocket,
|
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)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct FolderArgs {
|
pub struct FolderArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: FolderCommands,
|
pub command: FolderCommands,
|
||||||
@@ -268,6 +310,7 @@ pub enum FolderCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct EnvironmentArgs {
|
pub struct EnvironmentArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: EnvironmentCommands,
|
pub command: EnvironmentCommands,
|
||||||
@@ -281,6 +324,13 @@ pub enum EnvironmentCommands {
|
|||||||
workspace_id: String,
|
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 an environment as JSON
|
||||||
Show {
|
Show {
|
||||||
/// Environment ID
|
/// Environment ID
|
||||||
@@ -288,15 +338,22 @@ pub enum EnvironmentCommands {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// Create an environment
|
/// 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 {
|
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>,
|
workspace_id: Option<String>,
|
||||||
|
|
||||||
/// Environment name
|
/// Environment name
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
|
|
||||||
/// JSON payload
|
/// JSON payload (use instead of WORKSPACE_ID/--name)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: Option<String>,
|
json: Option<String>,
|
||||||
},
|
},
|
||||||
@@ -324,6 +381,7 @@ pub enum EnvironmentCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct AuthArgs {
|
pub struct AuthArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: AuthCommands,
|
pub command: AuthCommands,
|
||||||
@@ -342,6 +400,7 @@ pub enum AuthCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct PluginArgs {
|
pub struct PluginArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: PluginCommands,
|
pub command: PluginCommands,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use crate::utils::json::{
|
|||||||
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
|
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
|
||||||
validate_create_id,
|
validate_create_id,
|
||||||
};
|
};
|
||||||
|
use crate::utils::schema::append_agent_hints;
|
||||||
|
use schemars::schema_for;
|
||||||
use yaak_models::models::Environment;
|
use yaak_models::models::Environment;
|
||||||
use yaak_models::util::UpdateSource;
|
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 {
|
pub fn run(ctx: &CliContext, args: EnvironmentArgs) -> i32 {
|
||||||
let result = match args.command {
|
let result = match args.command {
|
||||||
EnvironmentCommands::List { workspace_id } => list(ctx, &workspace_id),
|
EnvironmentCommands::List { workspace_id } => list(ctx, &workspace_id),
|
||||||
|
EnvironmentCommands::Schema { pretty } => schema(pretty),
|
||||||
EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id),
|
EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id),
|
||||||
EnvironmentCommands::Create { workspace_id, name, json } => {
|
EnvironmentCommands::Create { workspace_id, name, json } => {
|
||||||
create(ctx, 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 {
|
fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
|
||||||
let environments = ctx
|
let environments = ctx
|
||||||
.db()
|
.db()
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ use crate::utils::json::{
|
|||||||
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
|
apply_merge_patch, is_json_shorthand, parse_optional_json, parse_required_json, require_id,
|
||||||
validate_create_id,
|
validate_create_id,
|
||||||
};
|
};
|
||||||
|
use crate::utils::schema::append_agent_hints;
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use serde_json::{Map, Value, json};
|
use serde_json::{Map, Value, json};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::Write;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use yaak_http::sender::HttpResponseEvent as SenderHttpResponseEvent;
|
||||||
use yaak::send::{SendHttpRequestByIdWithPluginsParams, send_http_request_by_id_with_plugins};
|
use yaak::send::{SendHttpRequestByIdWithPluginsParams, send_http_request_by_id_with_plugins};
|
||||||
use yaak_models::models::{GrpcRequest, HttpRequest, WebsocketRequest};
|
use yaak_models::models::{GrpcRequest, HttpRequest, WebsocketRequest};
|
||||||
use yaak_models::queries::any_request::AnyRequest;
|
use yaak_models::queries::any_request::AnyRequest;
|
||||||
@@ -86,17 +89,15 @@ async fn schema(ctx: &CliContext, request_type: RequestSchemaType, pretty: bool)
|
|||||||
};
|
};
|
||||||
|
|
||||||
enrich_schema_guidance(&mut schema, request_type);
|
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 {
|
if let Err(error) = merge_auth_schema_from_plugins(ctx, &mut schema).await {
|
||||||
eprintln!("Warning: Failed to enrich authentication schema from plugins: {error}");
|
eprintln!("Warning: Failed to enrich authentication schema from plugins: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = if pretty {
|
let output =
|
||||||
serde_json::to_string_pretty(&schema)
|
if pretty { serde_json::to_string_pretty(&schema) } else { serde_json::to_string(&schema) }
|
||||||
} else {
|
.map_err(|e| format!("Failed to format schema JSON: {e}"))?;
|
||||||
serde_json::to_string(&schema)
|
|
||||||
}
|
|
||||||
.map_err(|e| format!("Failed to format schema JSON: {e}"))?;
|
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -471,14 +472,24 @@ async fn send_http_request_by_id(
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let plugin_context = PluginContext::new(None, Some(workspace_id.to_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 {
|
let event_handle = tokio::spawn(async move {
|
||||||
while let Some(event) = event_rx.recv().await {
|
while let Some(event) = event_rx.recv().await {
|
||||||
if verbose {
|
if verbose && !matches!(event, SenderHttpResponseEvent::ChunkReceived { .. }) {
|
||||||
println!("{}", event);
|
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 response_dir = ctx.data_dir().join("responses");
|
||||||
|
|
||||||
let result = send_http_request_by_id_with_plugins(SendHttpRequestByIdWithPluginsParams {
|
let result = send_http_request_by_id_with_plugins(SendHttpRequestByIdWithPluginsParams {
|
||||||
@@ -490,6 +501,7 @@ async fn send_http_request_by_id(
|
|||||||
cookie_jar_id: None,
|
cookie_jar_id: None,
|
||||||
response_dir: &response_dir,
|
response_dir: &response_dir,
|
||||||
emit_events_to: Some(event_tx),
|
emit_events_to: Some(event_tx),
|
||||||
|
emit_response_body_chunks_to: Some(body_chunk_tx),
|
||||||
plugin_manager: ctx.plugin_manager(),
|
plugin_manager: ctx.plugin_manager(),
|
||||||
encryption_manager: ctx.encryption_manager.clone(),
|
encryption_manager: ctx.encryption_manager.clone(),
|
||||||
plugin_context: &plugin_context,
|
plugin_context: &plugin_context,
|
||||||
@@ -499,24 +511,7 @@ async fn send_http_request_by_id(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let _ = event_handle.await;
|
let _ = event_handle.await;
|
||||||
let result = result.map_err(|e| e.to_string())?;
|
let _ = body_handle.await;
|
||||||
|
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);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use crate::utils::confirm::confirm_delete;
|
|||||||
use crate::utils::json::{
|
use crate::utils::json::{
|
||||||
apply_merge_patch, parse_optional_json, parse_required_json, require_id, validate_create_id,
|
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::models::Workspace;
|
||||||
use yaak_models::util::UpdateSource;
|
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 {
|
pub fn run(ctx: &CliContext, args: WorkspaceArgs) -> i32 {
|
||||||
let result = match args.command {
|
let result = match args.command {
|
||||||
WorkspaceCommands::List => list(ctx),
|
WorkspaceCommands::List => list(ctx),
|
||||||
|
WorkspaceCommands::Schema { pretty } => schema(pretty),
|
||||||
WorkspaceCommands::Show { workspace_id } => show(ctx, &workspace_id),
|
WorkspaceCommands::Show { workspace_id } => show(ctx, &workspace_id),
|
||||||
WorkspaceCommands::Create { name, json, json_input } => create(ctx, name, json, json_input),
|
WorkspaceCommands::Create { name, json, json_input } => create(ctx, name, json, json_input),
|
||||||
WorkspaceCommands::Update { json, json_input } => update(ctx, 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 {
|
fn list(ctx: &CliContext) -> CommandResult {
|
||||||
let workspaces =
|
let workspaces =
|
||||||
ctx.db().list_workspaces().map_err(|e| format!("Failed to list workspaces: {e}"))?;
|
ctx.db().list_workspaces().map_err(|e| format!("Failed to list workspaces: {e}"))?;
|
||||||
|
|||||||
@@ -12,10 +12,18 @@ use context::CliContext;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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 {
|
if let Some(log_level) = log {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
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" };
|
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod confirm;
|
pub mod confirm;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod json;
|
pub mod json;
|
||||||
|
pub mod schema;
|
||||||
|
|||||||
15
crates-cli/yaak-cli/src/utils/schema.rs
Normal file
15
crates-cli/yaak-cli/src/utils/schema.rs
Normal 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') ]}",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -78,3 +78,19 @@ fn json_create_and_update_merge_patch_round_trip() {
|
|||||||
.stdout(contains("\"name\": \"Json Environment\""))
|
.stdout(contains("\"name\": \"Json Environment\""))
|
||||||
.stdout(contains("\"color\": \"#00ff00\""));
|
.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\""));
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ fn request_send_persists_response_body_and_events() {
|
|||||||
.args(["request", "send", &request_id])
|
.args(["request", "send", &request_id])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("HTTP 200 OK"))
|
|
||||||
.stdout(contains("hello from integration test"));
|
.stdout(contains("hello from integration test"));
|
||||||
|
|
||||||
let qm = query_manager(data_dir);
|
let qm = query_manager(data_dir);
|
||||||
@@ -190,6 +189,9 @@ fn request_schema_http_outputs_json_schema() {
|
|||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("\"type\":\"object\""))
|
.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("\"authentication\":"))
|
||||||
.stdout(contains("/foo/:id/comments/:commentId"))
|
.stdout(contains("/foo/:id/comments/:commentId"))
|
||||||
.stdout(contains("put concrete values in `urlParameters`"));
|
.stdout(contains("put concrete values in `urlParameters`"));
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ fn top_level_send_workspace_sends_http_requests_and_prints_summary() {
|
|||||||
.args(["send", "wk_test"])
|
.args(["send", "wk_test"])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("HTTP 200 OK"))
|
|
||||||
.stdout(contains("workspace bulk send"))
|
.stdout(contains("workspace bulk send"))
|
||||||
.stdout(contains("Send summary: 1 succeeded, 0 failed"));
|
.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"])
|
.args(["send", "fl_test"])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("HTTP 200 OK"))
|
|
||||||
.stdout(contains("folder bulk send"))
|
.stdout(contains("folder bulk send"))
|
||||||
.stdout(contains("Send summary: 1 succeeded, 0 failed"));
|
.stdout(contains("Send summary: 1 succeeded, 0 failed"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,3 +57,19 @@ fn json_create_and_update_merge_patch_round_trip() {
|
|||||||
.stdout(contains("\"name\": \"Json Workspace\""))
|
.stdout(contains("\"name\": \"Json Workspace\""))
|
||||||
.stdout(contains("\"description\": \"Updated via JSON\""));
|
.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\""));
|
||||||
|
}
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ async fn send_http_request_inner<R: Runtime>(
|
|||||||
cookie_jar_id,
|
cookie_jar_id,
|
||||||
response_dir: &response_dir,
|
response_dir: &response_dir,
|
||||||
emit_events_to: None,
|
emit_events_to: None,
|
||||||
|
emit_response_body_chunks_to: None,
|
||||||
existing_response: Some(response_ctx.response().clone()),
|
existing_response: Some(response_ctx.response().clone()),
|
||||||
plugin_manager,
|
plugin_manager,
|
||||||
encryption_manager,
|
encryption_manager,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ pub struct ClientCertificate {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct DnsOverride {
|
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")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "workspaces")]
|
#[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")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "environments")]
|
#[enum_def(table_name = "environments")]
|
||||||
@@ -700,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")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct EnvironmentVariable {
|
pub struct EnvironmentVariable {
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ pub struct SendHttpRequestByIdParams<'a, T: TemplateCallback> {
|
|||||||
pub cookie_jar_id: Option<String>,
|
pub cookie_jar_id: Option<String>,
|
||||||
pub response_dir: &'a Path,
|
pub response_dir: &'a Path,
|
||||||
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
|
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 cancelled_rx: Option<watch::Receiver<bool>>,
|
||||||
pub prepare_sendable_request: Option<&'a dyn PrepareSendableRequest>,
|
pub prepare_sendable_request: Option<&'a dyn PrepareSendableRequest>,
|
||||||
pub executor: Option<&'a dyn SendRequestExecutor>,
|
pub executor: Option<&'a dyn SendRequestExecutor>,
|
||||||
@@ -255,6 +256,7 @@ pub struct SendHttpRequestParams<'a, T: TemplateCallback> {
|
|||||||
pub cookie_jar_id: Option<String>,
|
pub cookie_jar_id: Option<String>,
|
||||||
pub response_dir: &'a Path,
|
pub response_dir: &'a Path,
|
||||||
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
|
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 cancelled_rx: Option<watch::Receiver<bool>>,
|
||||||
pub auth_context_id: Option<String>,
|
pub auth_context_id: Option<String>,
|
||||||
pub existing_response: Option<HttpResponse>,
|
pub existing_response: Option<HttpResponse>,
|
||||||
@@ -271,6 +273,7 @@ pub struct SendHttpRequestWithPluginsParams<'a> {
|
|||||||
pub cookie_jar_id: Option<String>,
|
pub cookie_jar_id: Option<String>,
|
||||||
pub response_dir: &'a Path,
|
pub response_dir: &'a Path,
|
||||||
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
|
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 existing_response: Option<HttpResponse>,
|
||||||
pub plugin_manager: Arc<PluginManager>,
|
pub plugin_manager: Arc<PluginManager>,
|
||||||
pub encryption_manager: Arc<EncryptionManager>,
|
pub encryption_manager: Arc<EncryptionManager>,
|
||||||
@@ -288,6 +291,7 @@ pub struct SendHttpRequestByIdWithPluginsParams<'a> {
|
|||||||
pub cookie_jar_id: Option<String>,
|
pub cookie_jar_id: Option<String>,
|
||||||
pub response_dir: &'a Path,
|
pub response_dir: &'a Path,
|
||||||
pub emit_events_to: Option<mpsc::Sender<SenderHttpResponseEvent>>,
|
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 plugin_manager: Arc<PluginManager>,
|
||||||
pub encryption_manager: Arc<EncryptionManager>,
|
pub encryption_manager: Arc<EncryptionManager>,
|
||||||
pub plugin_context: &'a PluginContext,
|
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,
|
cookie_jar_id: params.cookie_jar_id,
|
||||||
response_dir: params.response_dir,
|
response_dir: params.response_dir,
|
||||||
emit_events_to: params.emit_events_to,
|
emit_events_to: params.emit_events_to,
|
||||||
|
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
|
||||||
existing_response: None,
|
existing_response: None,
|
||||||
plugin_manager: params.plugin_manager,
|
plugin_manager: params.plugin_manager,
|
||||||
encryption_manager: params.encryption_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,
|
cookie_jar_id: params.cookie_jar_id,
|
||||||
response_dir: params.response_dir,
|
response_dir: params.response_dir,
|
||||||
emit_events_to: params.emit_events_to,
|
emit_events_to: params.emit_events_to,
|
||||||
|
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
|
||||||
cancelled_rx: params.cancelled_rx,
|
cancelled_rx: params.cancelled_rx,
|
||||||
auth_context_id: None,
|
auth_context_id: None,
|
||||||
existing_response: params.existing_response,
|
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,
|
cookie_jar_id: params.cookie_jar_id,
|
||||||
response_dir: params.response_dir,
|
response_dir: params.response_dir,
|
||||||
emit_events_to: params.emit_events_to,
|
emit_events_to: params.emit_events_to,
|
||||||
|
emit_response_body_chunks_to: params.emit_response_body_chunks_to,
|
||||||
cancelled_rx: params.cancelled_rx,
|
cancelled_rx: params.cancelled_rx,
|
||||||
existing_response: None,
|
existing_response: None,
|
||||||
prepare_sendable_request: params.prepare_sendable_request,
|
prepare_sendable_request: params.prepare_sendable_request,
|
||||||
@@ -687,13 +694,17 @@ pub async fn send_http_request<T: TemplateCallback>(
|
|||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
written_bytes += n;
|
written_bytes += n;
|
||||||
let start_idx = response_body.len() - 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 }
|
SendHttpRequestError::WriteResponseBody { path: body_path.clone(), source }
|
||||||
})?;
|
})?;
|
||||||
file.flush().await.map_err(|source| SendHttpRequestError::WriteResponseBody {
|
file.flush().await.map_err(|source| SendHttpRequestError::WriteResponseBody {
|
||||||
path: body_path.clone(),
|
path: body_path.clone(),
|
||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
|
if let Some(tx) = params.emit_response_body_chunks_to.as_ref() {
|
||||||
|
let _ = tx.send(chunk.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let should_update = now.duration_since(last_progress_update).as_millis()
|
let should_update = now.duration_since(last_progress_update).as_millis()
|
||||||
|
|||||||
Reference in New Issue
Block a user