mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-16 16:42:01 +02:00
The skill had grown into a reference manual: body type tables, auth strategy lists, a template function table, field-by-field OAuth 2.0 config. All of it goes stale, because the user's CLI version and their installed plugins decide what actually exists. On this machine a faker plugin contributes 274 template functions; the skill listed eleven, and got some names wrong. The CLI should carry that knowledge, so: - `yaak template-function list [filter]` and `template-function show <name>` report what the loaded plugins actually provide, the same way `request schema http` already merges in plugin auth strategies. - `yaak folder schema` now exists, so folder payloads are discoverable like every other model. Required deriving JsonSchema on Folder. - The request schema documents `bodyType` and `body` shapes, and states that setting a body type does not add a Content-Type header. The skill drops to a single 135-line SKILL.md that teaches the model, the workflows, and how to interrogate the CLI, and says outright that the CLI wins when the two disagree.
112 lines
3.7 KiB
Rust
112 lines
3.7 KiB
Rust
use crate::cli::{TemplateFunctionArgs, TemplateFunctionCommands};
|
|
use crate::context::CliContext;
|
|
use yaak_plugins::events::{PluginContext, TemplateFunction};
|
|
|
|
type CommandResult<T = ()> = std::result::Result<T, String>;
|
|
|
|
pub async fn run(ctx: &CliContext, args: TemplateFunctionArgs) -> i32 {
|
|
let result = match args.command {
|
|
TemplateFunctionCommands::List { filter } => list(ctx, filter.as_deref()).await,
|
|
TemplateFunctionCommands::Show { name, pretty } => show(ctx, &name, pretty).await,
|
|
};
|
|
|
|
match result {
|
|
Ok(()) => 0,
|
|
Err(error) => {
|
|
eprintln!("Error: {error}");
|
|
1
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Template functions come from plugins, so the only accurate list is the one the
|
|
/// installed plugins report right now.
|
|
async fn all(ctx: &CliContext) -> CommandResult<Vec<TemplateFunction>> {
|
|
let plugin_context = PluginContext::new_empty();
|
|
let summaries = ctx
|
|
.plugin_manager()
|
|
.get_template_function_summaries(&plugin_context)
|
|
.await
|
|
.map_err(|e| format!("Failed to list template functions: {e}"))?;
|
|
|
|
let mut functions: Vec<TemplateFunction> =
|
|
summaries.into_iter().flat_map(|summary| summary.functions).collect();
|
|
functions.sort_by(|a, b| a.name.cmp(&b.name));
|
|
Ok(functions)
|
|
}
|
|
|
|
async fn list(ctx: &CliContext, filter: Option<&str>) -> CommandResult {
|
|
let mut functions = all(ctx).await?;
|
|
|
|
if let Some(filter) = filter {
|
|
let needle = filter.to_lowercase();
|
|
functions.retain(|f| f.name.to_lowercase().contains(&needle));
|
|
}
|
|
|
|
if functions.is_empty() {
|
|
match filter {
|
|
Some(filter) => println!("No template functions matching '{filter}'"),
|
|
None => println!("No template functions found"),
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
for function in functions {
|
|
let args = function.args.iter().filter_map(arg_name).collect::<Vec<_>>().join(", ");
|
|
match function.description {
|
|
Some(description) if !description.is_empty() => {
|
|
println!("{}({}) - {}", function.name, args, description)
|
|
}
|
|
_ => println!("{}({})", function.name, args),
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn show(ctx: &CliContext, name: &str, pretty: bool) -> CommandResult {
|
|
let functions = all(ctx).await?;
|
|
let function = functions
|
|
.iter()
|
|
.find(|f| {
|
|
f.name == name
|
|
|| f.aliases.as_ref().is_some_and(|aliases| aliases.iter().any(|a| a == name))
|
|
})
|
|
.ok_or_else(|| {
|
|
let names = functions.iter().map(|f| f.name.as_str()).collect::<Vec<_>>();
|
|
format!("No template function named '{name}'. Available: {}", names.join(", "))
|
|
})?;
|
|
|
|
let output = if pretty {
|
|
serde_json::to_string_pretty(function)
|
|
} else {
|
|
serde_json::to_string(function)
|
|
}
|
|
.map_err(|e| format!("Failed to serialize template function: {e}"))?;
|
|
|
|
println!("{output}");
|
|
Ok(())
|
|
}
|
|
|
|
fn arg_name(arg: &yaak_plugins::events::TemplateFunctionArg) -> Option<String> {
|
|
use yaak_plugins::events::{FormInput, TemplateFunctionArg};
|
|
|
|
let TemplateFunctionArg::FormInput(input) = arg;
|
|
let base = match input {
|
|
FormInput::Text(v) => &v.base,
|
|
FormInput::Editor(v) => &v.base,
|
|
FormInput::Select(v) => &v.base,
|
|
FormInput::Checkbox(v) => &v.base,
|
|
FormInput::File(v) => &v.base,
|
|
FormInput::HttpRequest(v) => &v.base,
|
|
FormInput::KeyValue(v) => &v.base,
|
|
// Layout-only inputs have no value of their own
|
|
FormInput::Accordion(_)
|
|
| FormInput::HStack(_)
|
|
| FormInput::Banner(_)
|
|
| FormInput::Markdown(_) => return None,
|
|
};
|
|
|
|
if base.name.trim().is_empty() { None } else { Some(base.name.clone()) }
|
|
}
|