Files
herdr/src/cli/plugin.rs
T

585 lines
18 KiB
Rust

use std::collections::HashMap;
use crate::api::schema::{
Method, PluginActionContext, PluginActionInvokeParams, PluginActionListParams,
PluginActionRegisterParams, PluginInvocationContext, PluginPaneCloseParams,
PluginPaneFocusParams, PluginPaneOpenParams, PluginPanePlacement, PluginStorageDeleteParams,
PluginStorageGetParams, PluginStorageListParams, PluginStorageScope, PluginStorageSetParams,
Request, SplitDirection,
};
pub(super) fn run_plugin_command(args: &[String]) -> std::io::Result<i32> {
let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
print_plugin_help();
return Ok(2);
};
match subcommand {
"action" => run_plugin_action_command(&args[1..]),
"storage" => run_plugin_storage_command(&args[1..]),
"pane" => run_plugin_pane_command(&args[1..]),
"help" | "--help" | "-h" => {
print_plugin_help();
Ok(0)
}
_ => {
print_plugin_help();
Ok(2)
}
}
}
fn run_plugin_action_command(args: &[String]) -> std::io::Result<i32> {
let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
print_plugin_action_help();
return Ok(2);
};
match subcommand {
"register" => plugin_action_register(&args[1..]),
"list" => plugin_action_list(&args[1..]),
"invoke" => plugin_action_invoke(&args[1..]),
"help" | "--help" | "-h" => {
print_plugin_action_help();
Ok(0)
}
_ => {
print_plugin_action_help();
Ok(2)
}
}
}
fn plugin_action_register(args: &[String]) -> std::io::Result<i32> {
let mut plugin_id = None;
let mut action_id = None;
let mut title = None;
let mut description = None;
let mut contexts = Vec::new();
let mut entrypoint = None;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--plugin" => {
plugin_id = Some(required_value(args, &mut index, "--plugin")?);
}
"--action" => {
action_id = Some(required_value(args, &mut index, "--action")?);
}
"--title" => {
title = Some(required_value(args, &mut index, "--title")?);
}
"--description" => {
description = Some(required_value(args, &mut index, "--description")?);
}
"--context" => {
let value = required_value(args, &mut index, "--context")?;
contexts.push(parse_action_context(&value)?);
}
"--entrypoint" => {
entrypoint = Some(required_value(args, &mut index, "--entrypoint")?);
}
other => {
eprintln!("unknown option: {other}");
return Ok(2);
}
}
}
let Some(plugin_id) = plugin_id else {
eprintln!("missing required --plugin");
return Ok(2);
};
let Some(action_id) = action_id else {
eprintln!("missing required --action");
return Ok(2);
};
let Some(title) = title else {
eprintln!("missing required --title");
return Ok(2);
};
print_plugin_response(Method::PluginActionRegister(PluginActionRegisterParams {
plugin_id,
action_id,
title,
description,
contexts,
entrypoint,
}))
}
fn plugin_action_list(args: &[String]) -> std::io::Result<i32> {
let mut plugin_id = None;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--plugin" => {
plugin_id = Some(required_value(args, &mut index, "--plugin")?);
}
other => {
eprintln!("unknown option: {other}");
return Ok(2);
}
}
}
print_plugin_response(Method::PluginActionList(PluginActionListParams {
plugin_id,
}))
}
fn plugin_action_invoke(args: &[String]) -> std::io::Result<i32> {
let Some(action_id) = args.first() else {
eprintln!("usage: herdr plugin action invoke <action_id> [--plugin ID]");
return Ok(2);
};
let mut plugin_id = None;
let mut index = 1;
while index < args.len() {
match args[index].as_str() {
"--plugin" => {
plugin_id = Some(required_value(args, &mut index, "--plugin")?);
}
other => {
eprintln!("unknown option: {other}");
return Ok(2);
}
}
}
print_plugin_response(Method::PluginActionInvoke(PluginActionInvokeParams {
action_id: action_id.clone(),
plugin_id,
context: Some(PluginInvocationContext {
workspace_id: None,
workspace_label: None,
workspace_cwd: None,
worktree: None,
tab_id: None,
tab_label: None,
focused_pane_id: None,
focused_pane_cwd: None,
focused_pane_agent: None,
focused_pane_status: None,
selected_text: None,
invocation_source: Some("cli".into()),
correlation_id: None,
}),
}))
}
fn run_plugin_storage_command(args: &[String]) -> std::io::Result<i32> {
let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
print_plugin_storage_help();
return Ok(2);
};
match subcommand {
"get" => plugin_storage_get(&args[1..]),
"set" => plugin_storage_set(&args[1..]),
"delete" => plugin_storage_delete(&args[1..]),
"list" => plugin_storage_list(&args[1..]),
"help" | "--help" | "-h" => {
print_plugin_storage_help();
Ok(0)
}
_ => {
print_plugin_storage_help();
Ok(2)
}
}
}
fn plugin_storage_get(args: &[String]) -> std::io::Result<i32> {
let Some((plugin_id, scope, key, workspace_id, project_id, _value)) =
parse_storage_args(args, false)?
else {
return Ok(2);
};
print_plugin_response(Method::PluginStorageGet(PluginStorageGetParams {
plugin_id,
scope,
key,
workspace_id,
project_id,
}))
}
fn plugin_storage_set(args: &[String]) -> std::io::Result<i32> {
let Some((plugin_id, scope, key, workspace_id, project_id, value)) =
parse_storage_args(args, true)?
else {
return Ok(2);
};
let value = value.expect("set requires value");
print_plugin_response(Method::PluginStorageSet(PluginStorageSetParams {
plugin_id,
scope,
key,
value,
workspace_id,
project_id,
}))
}
fn plugin_storage_delete(args: &[String]) -> std::io::Result<i32> {
let Some((plugin_id, scope, key, workspace_id, project_id, _value)) =
parse_storage_args(args, false)?
else {
return Ok(2);
};
print_plugin_response(Method::PluginStorageDelete(PluginStorageDeleteParams {
plugin_id,
scope,
key,
workspace_id,
project_id,
}))
}
fn plugin_storage_list(args: &[String]) -> std::io::Result<i32> {
let mut plugin_id = None;
let mut scope = None;
let mut workspace_id = None;
let mut project_id = None;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--plugin" => plugin_id = Some(required_value(args, &mut index, "--plugin")?),
"--scope" => {
scope = Some(parse_storage_scope(&required_value(
args, &mut index, "--scope",
)?)?)
}
"--workspace" => workspace_id = Some(required_value(args, &mut index, "--workspace")?),
"--project" => project_id = Some(required_value(args, &mut index, "--project")?),
other => {
eprintln!("unknown option: {other}");
return Ok(2);
}
}
}
let Some(plugin_id) = plugin_id else {
eprintln!("missing required --plugin");
return Ok(2);
};
print_plugin_response(Method::PluginStorageList(PluginStorageListParams {
plugin_id,
scope: scope.unwrap_or(PluginStorageScope::Global),
workspace_id,
project_id,
}))
}
fn run_plugin_pane_command(args: &[String]) -> std::io::Result<i32> {
let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
print_plugin_pane_help();
return Ok(2);
};
match subcommand {
"open" => plugin_pane_open(&args[1..]),
"focus" => plugin_pane_focus(&args[1..]),
"close" => plugin_pane_close(&args[1..]),
"help" | "--help" | "-h" => {
print_plugin_pane_help();
Ok(0)
}
_ => {
print_plugin_pane_help();
Ok(2)
}
}
}
fn plugin_pane_open(args: &[String]) -> std::io::Result<i32> {
let mut plugin_id = None;
let mut entrypoint = None;
let mut placement = PluginPanePlacement::Split;
let mut workspace_id = None;
let mut tab_id = None;
let mut target_pane_id = None;
let mut direction = None;
let mut cwd = None;
let mut focus = true;
let mut env = HashMap::new();
let mut argv = Vec::new();
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--" => {
argv = args[index + 1..].to_vec();
break;
}
"--plugin" => plugin_id = Some(required_value(args, &mut index, "--plugin")?),
"--entrypoint" => entrypoint = Some(required_value(args, &mut index, "--entrypoint")?),
"--placement" => {
placement =
parse_pane_placement(&required_value(args, &mut index, "--placement")?)?;
}
"--workspace" => workspace_id = Some(required_value(args, &mut index, "--workspace")?),
"--tab" => tab_id = Some(required_value(args, &mut index, "--tab")?),
"--target-pane" => {
target_pane_id = Some(required_value(args, &mut index, "--target-pane")?);
}
"--direction" => {
direction = Some(parse_split_direction(&required_value(
args,
&mut index,
"--direction",
)?)?);
}
"--cwd" => cwd = Some(required_value(args, &mut index, "--cwd")?),
"--env" => {
let value = required_value(args, &mut index, "--env")?;
let (key, value) = match super::parse_env_assignment(&value) {
Ok(pair) => pair,
Err(err) => {
eprintln!("{err}");
return Ok(2);
}
};
env.insert(key, value);
}
"--focus" => {
focus = true;
index += 1;
}
"--no-focus" => {
focus = false;
index += 1;
}
other => {
eprintln!("unknown option: {other}");
return Ok(2);
}
}
}
let Some(plugin_id) = plugin_id else {
eprintln!("missing required --plugin");
return Ok(2);
};
let Some(entrypoint) = entrypoint else {
eprintln!("missing required --entrypoint");
return Ok(2);
};
if argv.is_empty() {
eprintln!("missing argv after --");
return Ok(2);
}
print_plugin_response(Method::PluginPaneOpen(PluginPaneOpenParams {
plugin_id,
entrypoint,
argv,
placement,
workspace_id,
tab_id,
target_pane_id,
direction,
cwd,
focus,
env,
context: Some(PluginInvocationContext {
workspace_id: None,
workspace_label: None,
workspace_cwd: None,
worktree: None,
tab_id: None,
tab_label: None,
focused_pane_id: None,
focused_pane_cwd: None,
focused_pane_agent: None,
focused_pane_status: None,
selected_text: None,
invocation_source: Some("cli".into()),
correlation_id: None,
}),
}))
}
fn plugin_pane_focus(args: &[String]) -> std::io::Result<i32> {
let Some(pane_id) = args.first() else {
eprintln!("usage: herdr plugin pane focus <pane_id>");
return Ok(2);
};
if args.len() != 1 {
eprintln!("usage: herdr plugin pane focus <pane_id>");
return Ok(2);
}
print_plugin_response(Method::PluginPaneFocus(PluginPaneFocusParams {
pane_id: super::normalize_pane_id(pane_id),
}))
}
fn plugin_pane_close(args: &[String]) -> std::io::Result<i32> {
let Some(pane_id) = args.first() else {
eprintln!("usage: herdr plugin pane close <pane_id>");
return Ok(2);
};
if args.len() != 1 {
eprintln!("usage: herdr plugin pane close <pane_id>");
return Ok(2);
}
print_plugin_response(Method::PluginPaneClose(PluginPaneCloseParams {
pane_id: super::normalize_pane_id(pane_id),
}))
}
type StorageArgs = (
String,
PluginStorageScope,
String,
Option<String>,
Option<String>,
Option<serde_json::Value>,
);
fn parse_storage_args(
args: &[String],
require_value: bool,
) -> std::io::Result<Option<StorageArgs>> {
let mut plugin_id = None;
let mut scope = PluginStorageScope::Global;
let mut key = None;
let mut workspace_id = None;
let mut project_id = None;
let mut value = None;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--plugin" => plugin_id = Some(required_value(args, &mut index, "--plugin")?),
"--scope" => {
scope = parse_storage_scope(&required_value(args, &mut index, "--scope")?)?
}
"--key" => key = Some(required_value(args, &mut index, "--key")?),
"--workspace" => workspace_id = Some(required_value(args, &mut index, "--workspace")?),
"--project" => project_id = Some(required_value(args, &mut index, "--project")?),
"--value" => {
let raw = required_value(args, &mut index, "--value")?;
value = Some(serde_json::from_str(&raw).unwrap_or(serde_json::Value::String(raw)));
}
other => {
eprintln!("unknown option: {other}");
return Ok(None);
}
}
}
let Some(plugin_id) = plugin_id else {
eprintln!("missing required --plugin");
return Ok(None);
};
let Some(key) = key else {
eprintln!("missing required --key");
return Ok(None);
};
if require_value && value.is_none() {
eprintln!("missing required --value");
return Ok(None);
}
Ok(Some((
plugin_id,
scope,
key,
workspace_id,
project_id,
value,
)))
}
fn required_value(args: &[String], index: &mut usize, flag: &str) -> std::io::Result<String> {
let Some(value) = args.get(*index + 1) else {
eprintln!("missing value for {flag}");
return Err(std::io::Error::other(format!("missing value for {flag}")));
};
*index += 2;
Ok(value.clone())
}
fn parse_action_context(value: &str) -> std::io::Result<PluginActionContext> {
match value {
"global" => Ok(PluginActionContext::Global),
"workspace" => Ok(PluginActionContext::Workspace),
"tab" => Ok(PluginActionContext::Tab),
"pane" => Ok(PluginActionContext::Pane),
"selection" => Ok(PluginActionContext::Selection),
_ => Err(std::io::Error::other(format!(
"invalid action context: {value}"
))),
}
}
fn parse_storage_scope(value: &str) -> std::io::Result<PluginStorageScope> {
match value {
"global" => Ok(PluginStorageScope::Global),
"workspace" => Ok(PluginStorageScope::Workspace),
"project" => Ok(PluginStorageScope::Project),
_ => Err(std::io::Error::other(format!(
"invalid storage scope: {value}"
))),
}
}
fn parse_pane_placement(value: &str) -> std::io::Result<PluginPanePlacement> {
match value {
"split" => Ok(PluginPanePlacement::Split),
"tab" => Ok(PluginPanePlacement::Tab),
"zoomed" | "fullscreen" => Ok(PluginPanePlacement::Zoomed),
_ => Err(std::io::Error::other(format!(
"invalid pane placement: {value}"
))),
}
}
fn parse_split_direction(value: &str) -> std::io::Result<SplitDirection> {
match value {
"right" => Ok(SplitDirection::Right),
"down" => Ok(SplitDirection::Down),
_ => Err(std::io::Error::other(format!(
"invalid split direction: {value}"
))),
}
}
fn print_plugin_response(method: Method) -> std::io::Result<i32> {
super::print_response(&super::send_request(&Request {
id: "cli:plugin".into(),
method,
})?)
}
fn print_plugin_help() {
eprintln!("herdr plugin commands:");
eprintln!(" herdr plugin action <register|list|invoke>");
eprintln!(" herdr plugin storage <get|set|delete|list>");
eprintln!(" herdr plugin pane <open|focus|close>");
}
fn print_plugin_action_help() {
eprintln!("herdr plugin action commands:");
eprintln!(" herdr plugin action register --plugin ID --action ID --title TEXT [--description TEXT] [--context global|workspace|tab|pane|selection] [--entrypoint ID]");
eprintln!(" herdr plugin action list [--plugin ID]");
eprintln!(" herdr plugin action invoke <action_id> [--plugin ID]");
}
fn print_plugin_storage_help() {
eprintln!("herdr plugin storage commands:");
eprintln!(" herdr plugin storage get --plugin ID --scope global|workspace|project --key KEY [--workspace ID] [--project ID]");
eprintln!(" herdr plugin storage set --plugin ID --scope global|workspace|project --key KEY --value JSON [--workspace ID] [--project ID]");
eprintln!(" herdr plugin storage delete --plugin ID --scope global|workspace|project --key KEY [--workspace ID] [--project ID]");
eprintln!(" herdr plugin storage list --plugin ID --scope global|workspace|project [--workspace ID] [--project ID]");
}
fn print_plugin_pane_help() {
eprintln!("herdr plugin pane commands:");
eprintln!(" herdr plugin pane open --plugin ID --entrypoint ID [--placement split|tab|zoomed] [--target-pane PANE] [--direction right|down] [--cwd PATH] [--env KEY=VALUE] [--focus|--no-focus] -- <argv...>");
eprintln!(" herdr plugin pane focus <pane_id>");
eprintln!(" herdr plugin pane close <pane_id>");
}