aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-09 04:40:50 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-09 04:42:35 +0300
commitbb2c579df206e51d43833544d114f321423c997c (patch)
treea26c6702e048f31429ebaa260ee40c3c2b111d3e
parent5460a9601ea8dd3d05e64e97040c71c6be29172b (diff)
feat(cli): support labels when creating tabs and workspaces
-rw-r--r--SKILL.md18
-rw-r--r--SOCKET_API.md10
-rw-r--r--src/api/schema.rs5
-rw-r--r--src/app/mod.rs28
-rw-r--r--src/cli.rs27
5 files changed, 78 insertions, 10 deletions
diff --git a/SKILL.md b/SKILL.md
index 79b71dea..0b0efe2e 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -71,6 +71,14 @@ create a new tab:
herdr tab create --workspace 1
```
+without `--label`, the new tab keeps the default numbered tab name.
+
+create and name it in one step:
+
+```bash
+herdr tab create --workspace 1 --label "logs"
+```
+
rename it:
```bash
@@ -178,6 +186,14 @@ create a new workspace:
herdr workspace create --cwd /path/to/project
```
+without `--label`, the new workspace keeps the default cwd-based name.
+
+create and name one in one step:
+
+```bash
+herdr workspace create --cwd /path/to/project --label "api server"
+```
+
create one without focusing it:
```bash
@@ -276,4 +292,6 @@ herdr pane read 1-1 --source recent --lines 100
- parse ids from `workspace create`, `tab create`, and `pane split` responses when you need new ids. for `pane split`, the new pane id is at `result.pane.pane_id`.
- use `pane read` for current output that already exists. use `wait output` for future output you expect next.
- `--no-focus` on split, tab create, and workspace create keeps your current terminal context focused.
+- without `--label`, workspace create keeps cwd-based naming and tab create keeps numbered naming.
+- `--label` on tab create and workspace create applies the custom name immediately.
- if you are running inside herdr, the `HERDR_ENV` environment variable is set to `1`.
diff --git a/SOCKET_API.md b/SOCKET_API.md
index c73e0a7d..2739b96d 100644
--- a/SOCKET_API.md
+++ b/SOCKET_API.md
@@ -801,7 +801,7 @@ workspace commands:
```text
herdr workspace list
-herdr workspace create [--cwd PATH] [--no-focus]
+herdr workspace create [--cwd PATH] [--label TEXT] [--no-focus]
herdr workspace get <workspace_id>
herdr workspace focus <workspace_id>
herdr workspace rename <workspace_id> <label>
@@ -812,7 +812,7 @@ tab commands:
```text
herdr tab list [--workspace <workspace_id>]
-herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--no-focus]
+herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--no-focus]
herdr tab get <tab_id>
herdr tab focus <tab_id>
herdr tab rename <tab_id> <label>
@@ -842,7 +842,11 @@ herdr wait agent-status <pane_id> --status <idle|working|blocked|done|unknown> [
### cli behavior notes
- `workspace create` focuses by default; pass `--no-focus` to keep focus where it is
+- `workspace create` without `--label` keeps the default cwd-based workspace naming
+- `workspace create --label` applies the custom workspace name immediately
- `tab create` focuses by default; pass `--no-focus` to keep focus where it is
+- `tab create` without `--label` keeps the default numbered tab naming
+- `tab create --label` applies the custom tab name immediately
- `pane split` focuses the new pane by default; pass `--no-focus` to keep focus on the original pane
- `pane read` prints **text**, not json
- `pane read --source recent-unwrapped` returns recent terminal text with soft wraps joined back together
@@ -859,7 +863,7 @@ herdr wait agent-status <pane_id> --status <idle|working|blocked|done|unknown> [
create a workspace, split a pane, run a server, and wait for readiness:
```bash
-herdr workspace create --cwd /path/to/project
+herdr workspace create --cwd /path/to/project --label "api server"
herdr pane split 1-1 --direction right --no-focus
herdr pane run 1-2 "npm run dev"
herdr wait output 1-2 --match "ready" --timeout 30000
diff --git a/src/api/schema.rs b/src/api/schema.rs
index 568a276b..42a9c259 100644
--- a/src/api/schema.rs
+++ b/src/api/schema.rs
@@ -93,6 +93,8 @@ pub struct WorkspaceCreateParams {
pub cwd: Option<String>,
#[serde(default)]
pub focus: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -109,6 +111,8 @@ pub struct TabCreateParams {
pub cwd: Option<String>,
#[serde(default)]
pub focus: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
@@ -688,6 +692,7 @@ mod tests {
method: Method::WorkspaceCreate(WorkspaceCreateParams {
cwd: Some("/tmp".into()),
focus: true,
+ label: Some("api".into()),
}),
};
diff --git a/src/app/mod.rs b/src/app/mod.rs
index a39ed436..8d089a9d 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -1069,6 +1069,11 @@ impl App {
.unwrap_or_else(|| std::path::PathBuf::from("/"));
match self.create_workspace_with_options(cwd, params.focus) {
Ok(index) => {
+ if let Some(label) = params.label {
+ if let Some(workspace) = self.state.workspaces.get_mut(index) {
+ workspace.set_custom_name(label);
+ }
+ }
let workspace = self.workspace_info(index);
self.emit_event(crate::api::schema::EventEnvelope {
event: crate::api::schema::EventKind::WorkspaceCreated,
@@ -1280,7 +1285,13 @@ impl App {
}
}
Method::TabCreate(params) => {
- let ws_idx = if let Some(workspace_id) = params.workspace_id {
+ let crate::api::schema::TabCreateParams {
+ workspace_id,
+ cwd,
+ focus,
+ label,
+ } = params;
+ let ws_idx = if let Some(workspace_id) = workspace_id {
let Some(ws_idx) = self.parse_workspace_id(&workspace_id) else {
return serde_json::to_string(&ErrorResponse {
id: request.id,
@@ -1304,8 +1315,7 @@ impl App {
})
.unwrap();
};
- let cwd = params
- .cwd
+ let cwd = cwd
.map(std::path::PathBuf::from)
.or_else(|| {
self.state.workspaces.get(ws_idx).and_then(|ws| {
@@ -1333,7 +1343,17 @@ impl App {
});
match result {
Ok(tab_idx) => {
- if params.focus {
+ if let Some(label) = label {
+ if let Some(tab) = self
+ .state
+ .workspaces
+ .get_mut(ws_idx)
+ .and_then(|ws| ws.tabs.get_mut(tab_idx))
+ {
+ tab.set_custom_name(label);
+ }
+ }
+ if focus {
self.state.switch_workspace(ws_idx);
self.state.switch_tab(tab_idx);
self.state.mode = Mode::Terminal;
diff --git a/src/cli.rs b/src/cli.rs
index 8f3d3e5b..620a592f 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -144,6 +144,7 @@ fn workspace_list(args: &[String]) -> std::io::Result<i32> {
fn workspace_create(args: &[String]) -> std::io::Result<i32> {
let mut cwd = None;
let mut focus = true;
+ let mut label = None;
let mut index = 0;
while index < args.len() {
@@ -156,6 +157,14 @@ fn workspace_create(args: &[String]) -> std::io::Result<i32> {
cwd = Some(value.clone());
index += 2;
}
+ "--label" => {
+ let Some(value) = args.get(index + 1) else {
+ eprintln!("missing value for --label");
+ return Ok(2);
+ };
+ label = Some(value.clone());
+ index += 2;
+ }
"--no-focus" => {
focus = false;
index += 1;
@@ -169,7 +178,7 @@ fn workspace_create(args: &[String]) -> std::io::Result<i32> {
print_response(&send_request(&Request {
id: "cli:workspace:create".into(),
- method: Method::WorkspaceCreate(WorkspaceCreateParams { cwd, focus }),
+ method: Method::WorkspaceCreate(WorkspaceCreateParams { cwd, focus, label }),
})?)
}
@@ -273,6 +282,7 @@ fn tab_create(args: &[String]) -> std::io::Result<i32> {
let mut workspace_id = None;
let mut cwd = None;
let mut focus = true;
+ let mut label = None;
let mut index = 0;
while index < args.len() {
@@ -293,6 +303,14 @@ fn tab_create(args: &[String]) -> std::io::Result<i32> {
cwd = Some(value.clone());
index += 2;
}
+ "--label" => {
+ let Some(value) = args.get(index + 1) else {
+ eprintln!("missing value for --label");
+ return Ok(2);
+ };
+ label = Some(value.clone());
+ index += 2;
+ }
"--no-focus" => {
focus = false;
index += 1;
@@ -310,6 +328,7 @@ fn tab_create(args: &[String]) -> std::io::Result<i32> {
workspace_id,
cwd,
focus,
+ label,
}),
})?)
}
@@ -1055,7 +1074,7 @@ fn parse_u64_flag(flag: &str, value: &str) -> std::io::Result<u64> {
fn print_workspace_help() {
eprintln!("herdr workspace commands:");
eprintln!(" herdr workspace list");
- eprintln!(" herdr workspace create [--cwd PATH] [--no-focus]");
+ eprintln!(" herdr workspace create [--cwd PATH] [--label TEXT] [--no-focus]");
eprintln!(" herdr workspace get <workspace_id>");
eprintln!(" herdr workspace focus <workspace_id>");
eprintln!(" herdr workspace rename <workspace_id> <label>");
@@ -1065,7 +1084,9 @@ fn print_workspace_help() {
fn print_tab_help() {
eprintln!("herdr tab commands:");
eprintln!(" herdr tab list [--workspace <workspace_id>]");
- eprintln!(" herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--no-focus]");
+ eprintln!(
+ " herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--no-focus]"
+ );
eprintln!(" herdr tab get <tab_id>");
eprintln!(" herdr tab focus <tab_id>");
eprintln!(" herdr tab rename <tab_id> <label>");