From fbd20ad6e488fb3b9efc1612fdffee6e672976c1 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 14 Jun 2026 02:43:45 +0300 Subject: feat: add plugin v1 system Add manifest-driven plugin actions, event hooks, pane entrypoints, link handlers, and host APIs. Support plugin link, GitHub install, build commands, installed plugin registry, pane movement, runtime context, and plugin docs. --- src/cli/agent.rs | 25 +- src/cli/pane.rs | 118 +++- src/cli/plugin.rs | 1694 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/tab.rs | 21 +- src/cli/workspace.rs | 27 +- 5 files changed, 1871 insertions(+), 14 deletions(-) create mode 100644 src/cli/plugin.rs (limited to 'src/cli') diff --git a/src/cli/agent.rs b/src/cli/agent.rs index 8c3c907b..80148046 100644 --- a/src/cli/agent.rs +++ b/src/cli/agent.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::api::schema::{ AgentReadParams, AgentRenameParams, AgentSendParams, AgentStartParams, AgentStatus, AgentTarget, EmptyParams, Method, ReadFormat, ReadSource, Request, Subscription, @@ -267,12 +269,12 @@ fn matched_rule_region_preview<'a>( fn agent_start(args: &[String]) -> std::io::Result { let Some(name) = args.first() else { - eprintln!("usage: herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--focus|--no-focus] -- "); + eprintln!("usage: herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--env KEY=VALUE] [--focus|--no-focus] -- "); return Ok(2); }; let Some(separator) = args.iter().position(|arg| arg == "--") else { - eprintln!("usage: herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--focus|--no-focus] -- "); + eprintln!("usage: herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--env KEY=VALUE] [--focus|--no-focus] -- "); return Ok(2); }; if separator == args.len() - 1 { @@ -285,6 +287,7 @@ fn agent_start(args: &[String]) -> std::io::Result { let mut tab_id = None; let mut split = None; let mut focus = false; + let mut env = HashMap::new(); let mut index = 1; while index < separator { @@ -329,6 +332,21 @@ fn agent_start(args: &[String]) -> std::io::Result { focus = false; index += 1; } + "--env" => { + let Some(value) = args.get(index + 1).filter(|_| index + 1 < separator) else { + eprintln!("missing value for --env"); + return Ok(2); + }; + let (key, value) = match super::parse_env_assignment(value) { + Ok(pair) => pair, + Err(err) => { + eprintln!("{err}"); + return Ok(2); + } + }; + env.insert(key, value); + index += 2; + } other => { eprintln!("unknown option: {other}"); return Ok(2); @@ -346,6 +364,7 @@ fn agent_start(args: &[String]) -> std::io::Result { split, focus, argv: args[separator + 1..].to_vec(), + env, }), })?) } @@ -656,7 +675,7 @@ fn print_agent_help() { eprintln!(" herdr agent focus "); eprintln!(" herdr agent wait --status [--timeout MS]"); eprintln!(" herdr agent attach [--takeover]"); - eprintln!(" herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--focus|--no-focus] -- "); + eprintln!(" herdr agent start [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--env KEY=VALUE] [--focus|--no-focus] -- "); eprintln!(" herdr agent explain [--json]"); eprintln!(" herdr agent explain --file PATH --agent LABEL [--json]"); eprintln!(" targets accept terminal ids, unique agent names, detected/reported agent labels, and legacy pane ids"); diff --git a/src/cli/pane.rs b/src/cli/pane.rs index 5f0b42f2..2e5ce485 100644 --- a/src/cli/pane.rs +++ b/src/cli/pane.rs @@ -1,10 +1,11 @@ use crate::api::schema::{ - Method, PaneDirection, PaneEdgesParams, PaneFocusDirectionParams, PaneLayoutParams, - PaneListParams, PaneMoveDestination, PaneMoveParams, PaneNeighborParams, PaneReadParams, - PaneReleaseAgentParams, PaneRenameParams, PaneReportAgentParams, PaneReportAgentSessionParams, - PaneReportMetadataParams, PaneResizeParams, PaneSendInputParams, PaneSendKeysParams, - PaneSendTextParams, PaneSplitParams, PaneSwapParams, PaneTarget, PaneZoomMode, PaneZoomParams, - ReadFormat, ReadSource, Request, SplitDirection, + Method, PaneCurrentParams, PaneDirection, PaneEdgesParams, PaneFocusDirectionParams, + PaneLayoutParams, PaneListParams, PaneMoveDestination, PaneMoveParams, PaneNeighborParams, + PaneProcessInfoParams, PaneReadParams, PaneReleaseAgentParams, PaneRenameParams, + PaneReportAgentParams, PaneReportAgentSessionParams, PaneReportMetadataParams, + PaneResizeParams, PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, + PaneSwapParams, PaneTarget, PaneZoomMode, PaneZoomParams, ReadFormat, ReadSource, Request, + SplitDirection, }; pub(super) fn run_pane_command(args: &[String]) -> std::io::Result { @@ -15,8 +16,10 @@ pub(super) fn run_pane_command(args: &[String]) -> std::io::Result { match subcommand { "list" => pane_list(&args[1..]), + "current" => pane_current(&args[1..]), "get" => pane_get(&args[1..]), "layout" => pane_layout(&args[1..]), + "process-info" => pane_process_info(&args[1..]), "neighbor" => pane_neighbor(&args[1..]), "edges" => pane_edges(&args[1..]), "focus" => pane_focus(&args[1..]), @@ -91,6 +94,49 @@ fn pane_get(args: &[String]) -> std::io::Result { })?) } +fn pane_current(args: &[String]) -> std::io::Result { + let env_pane_id = std::env::var("HERDR_PANE_ID") + .ok() + .filter(|value| !value.trim().is_empty()); + let caller_pane_id = match parse_pane_current_args(args, env_pane_id.as_deref()) { + Ok(caller_pane_id) => caller_pane_id, + Err(message) => { + eprintln!("{message}"); + return Ok(2); + } + }; + + super::print_response(&super::send_request(&Request { + id: "cli:pane:current".into(), + method: Method::PaneCurrent(PaneCurrentParams { caller_pane_id }), + })?) +} + +fn parse_pane_current_args( + args: &[String], + env_pane_id: Option<&str>, +) -> Result, String> { + let mut caller_pane_id = env_pane_id.map(super::normalize_pane_id); + let mut index = 0; + while index < args.len() { + match args[index].as_str() { + "--pane" => { + let Some(value) = args.get(index + 1) else { + return Err("missing value for --pane".into()); + }; + caller_pane_id = Some(super::normalize_pane_id(value)); + index += 2; + } + "--current" => { + caller_pane_id = env_pane_id.map(super::normalize_pane_id); + index += 1; + } + other => return Err(format!("unknown option: {other}")), + } + } + Ok(caller_pane_id) +} + fn pane_layout(args: &[String]) -> std::io::Result { let pane_id = match parse_optional_current_pane_args(args) { Ok(pane_id) => pane_id, @@ -106,6 +152,21 @@ fn pane_layout(args: &[String]) -> std::io::Result { })?) } +fn pane_process_info(args: &[String]) -> std::io::Result { + let pane_id = match parse_optional_current_pane_args(args) { + Ok(pane_id) => pane_id, + Err(message) => { + eprintln!("{message}"); + return Ok(2); + } + }; + + super::print_response(&super::send_request(&Request { + id: "cli:pane:process_info".into(), + method: Method::PaneProcessInfo(PaneProcessInfoParams { pane_id }), + })?) +} + fn pane_edges(args: &[String]) -> std::io::Result { let pane_id = match parse_optional_current_pane_args(args) { Ok(pane_id) => pane_id, @@ -483,6 +544,7 @@ fn pane_split(args: &[String]) -> std::io::Result { } fn parse_pane_split_args(args: &[String]) -> Result { + let mut env = std::collections::HashMap::new(); let mut pane_id = None; let mut direction = None; let mut ratio = None; @@ -546,13 +608,21 @@ fn parse_pane_split_args(args: &[String]) -> Result { focus = false; index += 1; } + "--env" => { + let Some(value) = args.get(index + 1) else { + return Err("missing value for --env".into()); + }; + let (key, value) = super::parse_env_assignment(value)?; + env.insert(key, value); + index += 2; + } other => return Err(format!("unknown option: {other}")), } } let Some(direction) = direction else { return Err( - "usage: herdr pane split [|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--focus] [--no-focus]" + "usage: herdr pane split [|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus]" .into(), ); }; @@ -564,6 +634,7 @@ fn parse_pane_split_args(args: &[String]) -> Result { ratio, cwd, focus, + env, }) } @@ -1348,8 +1419,10 @@ fn pane_report_metadata(args: &[String]) -> std::io::Result { fn print_pane_help() { eprintln!("herdr pane commands:"); eprintln!(" herdr pane list [--workspace ]"); + eprintln!(" herdr pane current [--pane ID|--current]"); eprintln!(" herdr pane get "); eprintln!(" herdr pane layout [--pane ID|--current]"); + eprintln!(" herdr pane process-info [--pane ID|--current]"); eprintln!(" herdr pane neighbor --direction left|right|up|down [--pane ID|--current]"); eprintln!(" herdr pane edges [--pane ID|--current]"); eprintln!(" herdr pane focus --direction left|right|up|down [--pane ID|--current]"); @@ -1360,7 +1433,7 @@ fn print_pane_help() { eprintln!(" herdr pane rename