aboutsummaryrefslogtreecommitdiff
path: root/src/cli.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-14 02:43:45 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-14 03:47:07 +0300
commitfbd20ad6e488fb3b9efc1612fdffee6e672976c1 (patch)
treed21daf93241786f8aa6fde23b41cdb9e15182b79 /src/cli.rs
parentd35c642ef36ee5ac2afe7c1b18ad5a717bc807d8 (diff)
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.
Diffstat (limited to 'src/cli.rs')
-rw-r--r--src/cli.rs75
1 files changed, 73 insertions, 2 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 276462b7..3facba8b 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -4,20 +4,34 @@ use serde::Serialize;
use crate::api::client::{ApiClient, ApiClientError};
use crate::api::schema::{
- AgentStatus, Method, OutputMatch, PaneAgentState, PaneWaitForOutputParams, ReadFormat,
- ReadSource, Request, SplitDirection, Subscription,
+ AgentStatus, ClientWindowTitleSetParams, EmptyParams, Method, OutputMatch, PaneAgentState,
+ PaneWaitForOutputParams, ReadFormat, ReadSource, Request, SplitDirection, Subscription,
};
mod agent;
mod integration;
mod notification;
mod pane;
+mod plugin;
mod server;
mod status;
mod tab;
mod workspace;
mod worktree;
+pub(crate) fn parse_env_assignment(raw: &str) -> Result<(String, String), String> {
+ let Some((key, value)) = raw.split_once('=') else {
+ return Err("env must use KEY=VALUE".into());
+ };
+ if key.is_empty() {
+ return Err("env key must not be empty".into());
+ }
+ if key.contains('\0') || value.contains('\0') {
+ return Err("env must not contain NUL bytes".into());
+ }
+ Ok((key.to_string(), value.to_string()))
+}
+
pub enum CommandOutcome {
Handled(i32),
NotCli,
@@ -45,6 +59,7 @@ pub fn maybe_run(args: &[String]) -> std::io::Result<CommandOutcome> {
"agent" => agent::run_agent_command(&args[2..])?,
"terminal" => run_terminal_command(&args[2..])?,
"pane" => pane::run_pane_command(&args[2..])?,
+ "plugin" => plugin::run_plugin_command(&args[2..])?,
"wait" => run_wait_command(&args[2..])?,
"integration" => integration::run_integration_command(&args[2..])?,
"session" => run_session_command(&args[2..])?,
@@ -305,6 +320,7 @@ fn run_terminal_command(args: &[String]) -> std::io::Result<i32> {
match subcommand {
"attach" => terminal_attach(&args[1..]),
+ "title" => terminal_title(&args[1..]),
"help" | "--help" | "-h" => {
print_terminal_help();
Ok(0)
@@ -458,6 +474,43 @@ fn terminal_attach(args: &[String]) -> std::io::Result<i32> {
Ok(0)
}
+fn terminal_title(args: &[String]) -> std::io::Result<i32> {
+ match args.first().map(|arg| arg.as_str()) {
+ Some("set") => {
+ if args.len() != 2 {
+ eprintln!("usage: herdr terminal title set <title>");
+ return Ok(2);
+ }
+ print_response(&send_request(&Request {
+ id: "cli:terminal:title:set".into(),
+ method: Method::ClientWindowTitleSet(ClientWindowTitleSetParams {
+ title: args[1].clone(),
+ }),
+ })?)
+ }
+ Some("clear") => {
+ if args.len() != 1 {
+ eprintln!("usage: herdr terminal title clear");
+ return Ok(2);
+ }
+ print_response(&send_request(&Request {
+ id: "cli:terminal:title:clear".into(),
+ method: Method::ClientWindowTitleClear(EmptyParams::default()),
+ })?)
+ }
+ Some("help" | "--help" | "-h") => {
+ eprintln!("usage: herdr terminal title set <title>");
+ eprintln!(" herdr terminal title clear");
+ Ok(0)
+ }
+ _ => {
+ eprintln!("usage: herdr terminal title set <title>");
+ eprintln!(" herdr terminal title clear");
+ Ok(2)
+ }
+ }
+}
+
pub(super) fn parse_attach_target(args: &[String], usage: &str) -> Result<(String, bool), i32> {
let Some(target) = args.first() else {
eprintln!("{usage}");
@@ -861,6 +914,8 @@ fn print_config_help() {
fn print_terminal_help() {
eprintln!("herdr terminal commands:");
eprintln!(" herdr terminal attach <terminal_id> [--takeover]");
+ eprintln!(" herdr terminal title set <title>");
+ eprintln!(" herdr terminal title clear");
eprintln!(" detach from direct attach with ctrl+b q; send literal ctrl+b with ctrl+b ctrl+b");
}
@@ -948,4 +1003,20 @@ mod tests {
super::ChannelSetInstallAction::RunSelfUpdate
);
}
+
+ #[test]
+ fn parse_env_assignment_accepts_empty_values() {
+ assert_eq!(
+ super::parse_env_assignment("HERDR_ROLE=").unwrap(),
+ ("HERDR_ROLE".to_string(), String::new())
+ );
+ }
+
+ #[test]
+ fn parse_env_assignment_requires_key_value_separator() {
+ assert_eq!(
+ super::parse_env_assignment("HERDR_ROLE").unwrap_err(),
+ "env must use KEY=VALUE"
+ );
+ }
}