From 26fabe378d72b325355a087a12a9ede40347a672 Mon Sep 17 00:00:00 2001 From: Juan Barbat Date: Sat, 31 Oct 2026 10:52:20 -0300 Subject: [PATCH] feat(herdr): add the missing Herdr TUI keys to Herdr Desktop Ctrl+A w opens the space/tab picker, Ctrl+A q hides the window, and Ctrl+A Ctrl+1..9 goes to the agent of the Nth space in sidebar order (the one that needs you most), handled by the refactoria-spaces mod. --- docs/keymaps.md | 4 +-- herdr/desktop/keys.json | 11 ++++++ herdr/desktop/mods/refactoria-spaces/main.js | 35 +++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/keymaps.md b/docs/keymaps.md index 339f25e..98f6d4c 100644 --- a/docs/keymaps.md +++ b/docs/keymaps.md @@ -47,8 +47,8 @@ This table favors the shortcuts used by these dotfiles and Herdr defaults. It is | Herdr | `Ctrl+A r` | Resize mode. | | Herdr | `Ctrl+A b` | Toggle sidebar. | | Herdr remote | `Ctrl+V` | Remote image paste. | -| Herdr Desktop | `Ctrl+A` + Herdr TUI key | Same prefix keys as Herdr TUI (`herdr/desktop/keys.json`): `v`, `-`, `x`, `z`, `h/j/k/l`, `c`, `n/p`, `1..9`, `Shift+X/T/N/W/D`, `b`, `s`, `?`, `Option+K/J`. | -| Herdr Desktop | not mapped | No equivalent for TUI detach, workspace picker, pane cycling, pane rename, resize mode, or focus agent 1–9 (`agent.focus` needs a terminal id). | +| Herdr Desktop | `Ctrl+A` + Herdr TUI key | Same prefix keys as Herdr TUI (`herdr/desktop/keys.json`): `v`, `-`, `x`, `z`, `h/j/k/l`, `c`, `n/p`, `1..9`, `Shift+X/T/N/W/D`, `b`, `s`, `?`, `Option+K/J`, `w` (space/tab picker), `q` (hide the window; terminals keep running), `Ctrl+1..9` (go to the agent of space 1–9 in sidebar order, the one that needs you most; a space without agents is shown). | +| Herdr Desktop | not mapped | No equivalent for TUI pane cycling, pane rename, or resize mode. | ## AI CLIs diff --git a/herdr/desktop/keys.json b/herdr/desktop/keys.json index c3c3cd4..89a18b6 100644 --- a/herdr/desktop/keys.json +++ b/herdr/desktop/keys.json @@ -18,6 +18,8 @@ "ctrl-a shift-d": "space.close", "ctrl-a b": "layout.toggle:sidebar", "ctrl-a s": "app.settings", + "ctrl-a w": "mod.open:picker", + "ctrl-a q": "app.hide", "ctrl-a shift-/": "app.help", "ctrl-a alt-k": "agent.prev", "ctrl-a alt-j": "agent.next", @@ -30,6 +32,15 @@ "ctrl-a 7": "tab.select:7", "ctrl-a 8": "tab.select:8", "ctrl-a 9": "tab.select:9", + "ctrl-a ctrl-1": "refactoria-spaces.agent-1", + "ctrl-a ctrl-2": "refactoria-spaces.agent-2", + "ctrl-a ctrl-3": "refactoria-spaces.agent-3", + "ctrl-a ctrl-4": "refactoria-spaces.agent-4", + "ctrl-a ctrl-5": "refactoria-spaces.agent-5", + "ctrl-a ctrl-6": "refactoria-spaces.agent-6", + "ctrl-a ctrl-7": "refactoria-spaces.agent-7", + "ctrl-a ctrl-8": "refactoria-spaces.agent-8", + "ctrl-a ctrl-9": "refactoria-spaces.agent-9", "cmd-q": "app.quit", "cmd-h": "app.hide", "cmd-alt-h": "app.hide_others", diff --git a/herdr/desktop/mods/refactoria-spaces/main.js b/herdr/desktop/mods/refactoria-spaces/main.js index d04e0f5..788fc1d 100644 --- a/herdr/desktop/mods/refactoria-spaces/main.js +++ b/herdr/desktop/mods/refactoria-spaces/main.js @@ -5,7 +5,7 @@ import { View, div, svg } from "gpui-kit"; import { v_flex, h_flex } from "gpui-base"; -import { spaces, agents, machines, panes, run, openOverlay, exec, hc, theme, shared, setShared, watchFolder, window, layout, home, readTextFile } from "herdr"; +import { spaces, agents, machines, panes, run, openOverlay, exec, hc, theme, shared, setShared, watchFolder, window, layout, home, readTextFile, nextCommand } from "herdr"; function find(node, id) { if (node?.id === id) return node; @@ -37,6 +37,12 @@ const FALLBACK = 60000; // that draws space avatars): one strong color per hue, light and dark alike. const MARK = { ansi6: "#22a99a", ansi3: "#e3814f", ansi4: "#5b8def", ansi5: "#8b7cf6", ansi2: "#3fae6a", ansi1: "#d9649a" }; +// The agent a space jumps to: the one that needs you most, then the latest to change. +const URGENCY = { blocked: 3, done: 2, working: 1, idle: 0 }; +export function pickAgent(list) { + return [...list].sort((x, y) => (URGENCY[y.status] ?? 0) - (URGENCY[x.status] ?? 0) || y.seq - x.seq)[0] ?? null; +} + // A space speaks up only when something in it needs you. export function waits(status) { return status === "blocked" || status === "done"; @@ -91,6 +97,7 @@ export default class Spaces extends View { this.pulse = false; this.seen = new Set(); this.readSeen(cx); + this.listenKeys(cx); cx.timer.every(2000, (cx) => this.readSeen(cx)); cx.timer.every(600, (cx) => { const waiting = spaces().some((s) => spaceWaits(s, this.seen, agents())); @@ -100,6 +107,32 @@ export default class Spaces extends View { }); } + // keys.json "refactoria-spaces.agent-N" (Ctrl+A Ctrl+N, as in Herdr TUI): go to + // the agent of the Nth space as this list numbers them; a space without one is shown. + listenKeys(cx) { + cx.spawn(async (cx) => { + for (;;) { + const m = /^agent-([1-9])$/.exec(await nextCommand()); + if (m) this.focusAgent(Number(m[1])); + cx.notify(); + } + }); + } + + // Spaces in the order the list shows them: this computer first, then machines by name. + spaceOrder() { + const rows = this.ordered(); + const remote = [...new Set(rows.map((r) => r.w.machine).filter(Boolean))].sort(); + return [null, ...remote].flatMap((m) => rows.filter((r) => (r.w.machine ?? null) === m).map((r) => r.w)); + } + + focusAgent(n) { + const w = this.spaceOrder()[n - 1]; + if (!w) return; + const best = pickAgent(agents().filter((a) => a.space === w.id)); + best ? run("pane.focus", { pane: best.pane }) : run("space.select", { id: w.id }); + } + // Done agents any screen has looked at, from every screen's view.json. readSeen(cx) { if (this.readingSeen) return;