Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/keymaps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions herdr/desktop/keys.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
35 changes: 34 additions & 1 deletion herdr/desktop/mods/refactoria-spaces/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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()));
Expand All @@ -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;
Expand Down
Loading